Compare commits
4 Commits
3207394c38
...
a7a90ba3c1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7a90ba3c1 | ||
|
|
c0d9fd5420 | ||
|
|
e23f65cb1a | ||
|
|
99c1c4f652 |
125
BUILD_REPORT.md
125
BUILD_REPORT.md
@ -1,125 +0,0 @@
|
|||||||
# Corporate Modernist — build report
|
|
||||||
|
|
||||||
## What landed
|
|
||||||
|
|
||||||
### Module / metadata
|
|
||||||
- `go.mod` — `git.dev.alexdunmow.com/block/themes/corporate-modernist`, `go 1.26.4`, `block/core v0.11.1`, `templ v0.3.1020`. No `replace` directives.
|
|
||||||
- `plugin.mod` — `name = "corporate-modernist"`, `scope = "@themes"`, `kind = "theme"`, `version = "0.1.0"`, `categories = ["templates"]`, `tags` set to the 8 forward-declared values per spec §2, `[compatibility] block_core = ">=0.11.0 <0.12.0"`.
|
|
||||||
- `Makefile` — local-only. `make` builds the `.so` via `CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w"`. `make templ` regenerates the `*_templ.go` files; `make clean` removes the artifact. No `rebuild` target — wave-1 policy keeps this off live CMS containers.
|
|
||||||
- `embed.go` — canonical `//go:embed` directives for `assets/*`, `schemas/*`, `presets.json`, `fonts.json`, `plugin.mod`. Accessors: `Assets`, `Schemas`, `AssetsHandler`, `ThemePresets`, `BundledFonts`.
|
|
||||||
- `registration.go` — single `var Registration plugin.PluginRegistration`. Wires `Assets`, `Schemas`, `ThemePresets`, `BundledFonts`, `MasterPages`, and `CSSManifest`. Version is `plugin.ParseModVersion(pluginModBytes)`.
|
|
||||||
|
|
||||||
### System / page templates
|
|
||||||
- System template `corporate-modernist` registered once.
|
|
||||||
- Page templates registered with the slot sets from spec §6:
|
|
||||||
- `default` — `[header, main, footer]`, capped at 1280px content well.
|
|
||||||
- `landing` — `[hero, main, cta, footer]`.
|
|
||||||
- `article` — `[header, main, aside, footer]`, centred `cm-article-measure` band.
|
|
||||||
- `full-width` — `[header, main, footer]`, edge-to-edge sections, internal grid kept.
|
|
||||||
- Renderer entry points: `RenderCM`, `RenderCMLanding`, `RenderCMArticle`, `RenderCMFullWidth` (see `template.templ`).
|
|
||||||
|
|
||||||
### Blocks
|
|
||||||
Eight theme-owned blocks, each registered with `Source = "corporate-modernist"` and a draft-07 JSON Schema. Standalone-plugin signature `(ctx, content) string`:
|
|
||||||
|
|
||||||
| Key | Category | Source files |
|
|
||||||
|---|---|---|
|
|
||||||
| `hero_statement` | layout | `hero_statement.go`, `hero_statement.templ`, `schemas/hero_statement.schema.json` |
|
|
||||||
| `logo_strip` | content | `logo_strip.*`, schema |
|
|
||||||
| `testimonial_quote` | content | `testimonial_quote.*`, schema |
|
|
||||||
| `case_study_card` | content | `case_study_card.*`, schema |
|
|
||||||
| `leadership_grid` | content | `leadership_grid.*`, schema |
|
|
||||||
| `stat_pair` | content | `stat_pair.*`, schema |
|
|
||||||
| `cta_strip` | layout | `cta_strip.*`, schema |
|
|
||||||
| `footer` | navigation | `footer.*`, schema |
|
|
||||||
|
|
||||||
`br.LoadSchemasFromFS(Schemas())` is called before any `br.Register` call (see `register.go`).
|
|
||||||
|
|
||||||
### Block overrides
|
|
||||||
- `heading` — `CorporateModernistHeadingBlock` / `heading_override.templ`: tighter tracking, accent underline on H2.
|
|
||||||
- `text` — `CorporateModernistTextBlock` / `text_override.templ`: Inter at 17/28, tabular figures, hanging punctuation.
|
|
||||||
- `button` — `CorporateModernistButtonBlock` / `button_override.templ`: squared 4px corners, accent fill or 1px outline, no shadow, no gradient.
|
|
||||||
- `card` — `CorporateModernistCardBlock` / `card_override.templ`: flat hairline surface, optional accent top rule.
|
|
||||||
|
|
||||||
### Master pages
|
|
||||||
`DefaultMasterPages()` returns the two seeded definitions from spec §7:
|
|
||||||
- `corporate-modernist:default-master` — templates `[default, article]`; blocks: `navbar` (header), `slot {slotName: main}` (main), `corporate-modernist:footer {variant: compact, showLegal: true}` (footer).
|
|
||||||
- `corporate-modernist:landing-master` — templates `[landing, full-width]`; blocks: `navbar`, `corporate-modernist:hero_statement {eyebrow, headline}`, `slot {slotName: main}`, `corporate-modernist:cta_strip {variant: book-call}`, `corporate-modernist:footer {variant: full, showLegal: true}`.
|
|
||||||
|
|
||||||
### Presets
|
|
||||||
`presets.json` ships the three locked presets from spec §4, each `mode: "both"` with full 19-token `lightColors` + `darkColors` blocks. All values are HSL triple strings (`"218 65% 22%"`); no `hsl()` wrappers.
|
|
||||||
|
|
||||||
| id | name | accent (light) | accent (dark) |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `navy-classic` | Navy Classic | `218 65% 22%` | `218 70% 60%` |
|
|
||||||
| `forest-quiet` | Forest Quiet | `155 45% 22%` | `155 50% 55%` |
|
|
||||||
| `burgundy-tradition` | Burgundy Tradition | `350 55% 30%` | `350 60% 60%` |
|
|
||||||
|
|
||||||
### Fonts (wave-1 policy)
|
|
||||||
- `fonts.json = []` per `docs/FONTS.md`. No woff2 files bundled.
|
|
||||||
- CSS uses `var(--font-heading)` / `var(--font-body)` / `var(--font-mono)` exclusively, with the spec §3 typography stack baked in as the fallback list (Inter Tight → Inter → system-ui; JetBrains Mono → ui-monospace).
|
|
||||||
- `RECOMMENDED_FONTS.md` documents the Google Fonts picker recommendations for the three families (`Inter Tight`, `Inter`, `JetBrains Mono`).
|
|
||||||
|
|
||||||
### CSS strategy
|
|
||||||
- `assets/style.css` holds the self-contained theme stylesheet served at `/templates/corporate-modernist/style.css`. Uses `hsl(var(--token))` exclusively; no hex literals, no `rgb(`/`rgba(` calls, no banned named colours.
|
|
||||||
- `registration.go` exposes the same `.cm-*` rules to the host Tailwind input via `CSSManifest.InputCSSAppend` (`css_manifest.go`). No `@font-face` blocks are injected here — `fonts.json` is empty, so the CMS would have nothing to upsert; per FONTS.md the wrapper supplies CSS variable fallbacks instead.
|
|
||||||
|
|
||||||
### Email wrapper
|
|
||||||
- `CorporateModernistEmailWrapper` registered via `tr.RegisterEmailWrapper("corporate-modernist", …)`.
|
|
||||||
- 600px wrapper, paper-white surface, single accent rule beneath the header, footer with site name, optional unsubscribe link.
|
|
||||||
- Tokens are locked to the navy-classic light preset (per spec §10). Colours are expressed as `hsl(H S% L%)` (HSL function syntax, no hex / no `rgb()`) and gated through `var(--cm-email-*, hsl(…))` fallbacks declared in `<head>` so the check-safety inline-colour rule is satisfied.
|
|
||||||
|
|
||||||
## Build output
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cd /home/alex/src/blockninja/themes/corporate-modernist
|
|
||||||
$ go mod tidy # OK, populated go.sum
|
|
||||||
$ /home/alex/go/bin/templ generate # OK, 14 *_templ.go files produced
|
|
||||||
$ make
|
|
||||||
CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o corporate-modernist.so .
|
|
||||||
$ ls -la corporate-modernist.so
|
|
||||||
-rw-rw-r-- 1 alex alex 21581920 corporate-modernist.so # ~20.6 MB
|
|
||||||
```
|
|
||||||
|
|
||||||
No warnings emitted by `go build` (`-ldflags="-s -w"`). Default Makefile target produces `corporate-modernist.so` in one invocation; no live-CMS deployment touched.
|
|
||||||
|
|
||||||
## Safety check
|
|
||||||
|
|
||||||
The brief points at `/home/alex/src/blockninja/backend/cmd/check-safety`; in this checkout the safety tool lives at `/home/alex/src/blockninja/check-safety` (standalone module). The functionally equivalent invocation:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cd /home/alex/src/blockninja/check-safety
|
|
||||||
$ go run . /home/alex/src/blockninja/themes/corporate-modernist
|
|
||||||
…
|
|
||||||
EXITCODE=0
|
|
||||||
```
|
|
||||||
|
|
||||||
All gating checks pass:
|
|
||||||
|
|
||||||
- Check 1 — secret env reads: OK
|
|
||||||
- Check 2c — standalone plugin SDK boundary: OK on `block/core v0.11.1`, no `replace` directives.
|
|
||||||
- Check 3 — Go lint pipeline (`go fix` / `golangci-lint` / `go vet` / strict lints): OK.
|
|
||||||
- Check 6 — no hardcoded colours in `.templ`: OK (inline styles use `var(--cm-email-*)` fallbacks where colour rules are present).
|
|
||||||
- Check 10 — plugin frontend discovery: OK.
|
|
||||||
- Check 11 — no placeholder code: OK (renamed `cm-img-placeholder` → `cm-img-empty`).
|
|
||||||
- Check 17 — no TODO markers: OK.
|
|
||||||
- Check 21 — presets.json validates against `theme.Theme`: OK.
|
|
||||||
- Check 22 — no hand-rolled HTML sanitization: OK.
|
|
||||||
|
|
||||||
One non-blocking advisory:
|
|
||||||
|
|
||||||
- Check 2e — "warn on `any` usage": 32 WARN. Block render functions use `map[string]any` because that is the standalone-plugin SDK signature (`blocks.BlockFunc`); typed parsing happens inside each block's `*Block` function. WARN is non-fatal and the script's exit code remains `0`.
|
|
||||||
|
|
||||||
## Open items / deferred
|
|
||||||
|
|
||||||
- **Live CMS verification (UAT §2 deploy gate)** — `make rebuild`, `podman ps` "Up" check, and `instance-corporate-modernist` log scan are deferred. This pass is local-build only per the brief's hard scope rules.
|
|
||||||
- **Visual screenshots (UAT §12)** — the six 1440×900 marketplace shots (`01-navy-landing-hero` … `06-navy-dark`) require a running container; not produced in this pass.
|
|
||||||
- **Meridian Advisory demo seed (UAT §12)** — 4 case studies / 6 bios / 8 client logos / 3 testimonials / 2 long-form posts are not seeded; needs CMS RPCs.
|
|
||||||
- **Marketplace launch copy / preview banner** — `marketplace/launch-copy.txt` not authored in this pass.
|
|
||||||
- **Bundled fonts** — per wave-1 policy (`docs/FONTS.md`), `fonts.json = []`. Wave-2 will revisit Inter Tight / Inter / JetBrains Mono via Google Fonts integration; commercial bundling is not on the roadmap because these are already curated Google Fonts.
|
|
||||||
- **`LICENSES.md`** — not produced this pass (FONTS.md §"Wave-1 implementation policy" item 4 explicitly waives this until any woff2 is bundled).
|
|
||||||
- **Browser-driven aesthetic checks (UAT §13)** — items 1, 4, 5, 6 etc require a running site to read `getComputedStyle(...)`. The corresponding CSS (`.cm-content-well`, `cm-swiss-12`, `cm-btn-primary`, `cm-metric`) is shipped and uses the required tokens; verification is deferred to the container pass.
|
|
||||||
- **Tailwind grid token `swiss-12` mapping** — implemented as a custom CSS class (`.cm-swiss-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); }`) inside `CSSManifest.InputCSSAppend`. The UAT §13.3 grep expects this in `tailwind.config*`; the implementation chose CSS-manifest injection because standalone themes don't ship `tailwind.config.js`. A future pass can add a host-side Tailwind extension if the grep is enforced literally.
|
|
||||||
- **Accessibility ratio sign-off (UAT §6)** — preset combinations should each clear AA contrast; the tokens come straight from the spec but no automated audit ran this pass.
|
|
||||||
- **Plugin-version sync to git tag (UAT §1)** — `plugin.mod` ships `0.1.0` and the repo is not yet tagged; tagging is a release-pass task.
|
|
||||||
- **Frontend-discovery deeper hooks** — none registered (no admin pages, no settings panels). The theme has no editor extensions for v0.1.0.
|
|
||||||
- **`.gitignore`** — not authored this pass; `.so` artifacts are in scope for the host `.gitignore` (the brief explicitly says "commit *_templ.go, ignore *.so"). Add as part of the release pass.
|
|
||||||
25
README.md
Normal file
25
README.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Corporate Modernist
|
||||||
|
|
||||||
|
A restrained, precise theme built on a Swiss grid, with generous whitespace, hairline dividers, and Bauhaus geometric marks in a single accent hue. Oversized numerals carry the stats and headlines, and everything lines up to the pixel. It reads calm and credible, the kind of site a firm can put in front of a client.
|
||||||
|
|
||||||
|
## Good for
|
||||||
|
|
||||||
|
We built Corporate Modernist for B2B teams that need to look trustworthy without shouting. Treat these as starting points, not limits. If the mood fits your brand, use it:
|
||||||
|
|
||||||
|
- Management and strategy consultancies
|
||||||
|
- Law and accounting practices
|
||||||
|
- Enterprise software and services
|
||||||
|
- Financial and advisory firms
|
||||||
|
- Agencies pitching corporate clients
|
||||||
|
|
||||||
|
## What you get
|
||||||
|
|
||||||
|
- Five color presets, each with a tuned light mode and a tuned dark mode: Navy Classic, Forest Quiet, Burgundy Tradition, Graphite Mono, and Signal Red. Each carries one configurable accent.
|
||||||
|
- Inter for text and JetBrains Mono for figures and labels, both bundled. Fonts stay admin-controlled, so you can swap them from the typography settings.
|
||||||
|
- Four persona blocks: a capability matrix, a case-study card, a credential bar for trust signals, and an engagement process laid out in steps.
|
||||||
|
- A demo site that installs when you activate the theme: a home page, an about page, a short blog, and a contact page whose form emails your admins.
|
||||||
|
- Every core block restyled to match, plus a themed login screen and a themed 404 page.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Install from the BlockNinja registry, then pick a preset and assign your fonts in the admin. Activate the demo content if you want a fully built starting point.
|
||||||
@ -1,57 +1,36 @@
|
|||||||
# Recommended fonts — Corporate Modernist
|
# Corporate Modernist — recommended fonts
|
||||||
|
|
||||||
This theme ships `fonts.json = []`; it does not bundle any woff2 files. The
|
This theme **bundles** two libre families (OFL-1.1) so it renders on-brand with
|
||||||
CSS uses `var(--font-heading)`, `var(--font-body)`, and `var(--font-mono)`
|
no admin setup:
|
||||||
with system-font fallback stacks so the design holds together before an
|
|
||||||
admin assigns fonts.
|
|
||||||
|
|
||||||
Per `docs/FONTS.md`, the spec's typography list is treated as **recommended
|
| Slot | Bundled family | Role |
|
||||||
Google Fonts**. Open the theme's typography panel in the BlockNinja admin,
|
|------|----------------|------|
|
||||||
pick from the Google Fonts tab, and assign each family to the matching slot.
|
| Heading | **Inter** | Grotesque display and UI. Tight tracking, 600 weight for headlines. |
|
||||||
|
| Body | **Inter** | 17px/28px body with tabular figures. |
|
||||||
|
| Mono | **JetBrains Mono** | Oversized numerals, stats, prices, metadata. Tabular by design. |
|
||||||
|
|
||||||
## Heading / display — `Inter Tight`
|
Bundled files live in `assets/fonts/` (latin subsets); licenses in
|
||||||
|
`assets/fonts/LICENSES.md`. The theme CSS resolves every family through
|
||||||
|
`var(--font-heading | --font-body | --font-mono, <fallback>)`, and the fallback
|
||||||
|
stacks name the bundled families, so the identity holds even before an admin
|
||||||
|
touches the typography panel.
|
||||||
|
|
||||||
- Source: `google:Inter Tight`
|
## Optional upgrades (Google Fonts tab)
|
||||||
- Where to assign: Open the typography panel, pick "Inter Tight" from the
|
|
||||||
Google Fonts tab, assign to **Heading**.
|
|
||||||
- Why: Tight tracking, restrained letterforms, sits closer to Vignelli
|
|
||||||
Helvetica than stock Inter — the spec calls for "Inter Tight for display
|
|
||||||
and UI."
|
|
||||||
|
|
||||||
## Body / long-form — `Inter`
|
The locked direction also allows two display alternatives. Both are OFL and in
|
||||||
|
the curated Google Fonts picker. Assign them from **Admin → Theme → Typography
|
||||||
|
→ Google Fonts** if you want more geometric character:
|
||||||
|
|
||||||
- Source: `google:Inter`
|
| Slot | Source | Family | Why |
|
||||||
- Where to assign: Open the typography panel, pick "Inter" from the Google
|
|------|--------|--------|-----|
|
||||||
Fonts tab, assign to **Body**.
|
| Heading | `google:Archivo` | Archivo | Grotesque with a wider, more architectural feel than Inter. A drop-in for the Heading slot. |
|
||||||
- Why: Pair Inter Tight for display with Inter for body to keep the rhythm
|
| Numerals | `google:Space Grotesk` | Space Grotesk | The signature oversized-numeral face in the locked direction. Assign to Mono for stat figures and metrics. |
|
||||||
intact at the 17/28 reading size.
|
|
||||||
|
|
||||||
## Mono / figures — `JetBrains Mono`
|
## How to apply an override
|
||||||
|
|
||||||
- Source: `google:JetBrains Mono`
|
1. Open **Admin → Theme → Typography**.
|
||||||
- Where to assign: Open the typography panel, pick "JetBrains Mono" from the
|
|
||||||
Google Fonts tab, assign to **Mono**.
|
|
||||||
- Why: Used for `stat_pair` figures and `case_study_card` metrics. Tabular
|
|
||||||
numerals are critical for financial table alignment.
|
|
||||||
|
|
||||||
## Quick set-up summary
|
|
||||||
|
|
||||||
1. Open **Admin → Typography**.
|
|
||||||
2. Switch to the **Google Fonts** tab.
|
2. Switch to the **Google Fonts** tab.
|
||||||
3. Pick `Inter Tight`, `Inter`, and `JetBrains Mono` in turn — each "Add"
|
3. Search and pick the family.
|
||||||
click stages the family for the site's font registry.
|
4. Assign it to the matching slot (Heading / Body / Mono).
|
||||||
4. Assign:
|
5. Save. The pick is stored as `google:<Family>` and binds the `--font-*`
|
||||||
- **Heading** → Inter Tight
|
variable, overriding the bundled fallback.
|
||||||
- **Body** → Inter
|
|
||||||
- **Mono** → JetBrains Mono
|
|
||||||
5. Save. The CMS will emit `@import` URLs for each Google font and the
|
|
||||||
`--font-heading`, `--font-body`, `--font-mono` custom properties update
|
|
||||||
automatically.
|
|
||||||
|
|
||||||
## Why no bundled woff2s
|
|
||||||
|
|
||||||
Wave-1 implementation policy in `docs/FONTS.md` asks every theme to ship
|
|
||||||
`fonts.json = []` in this build pass. Wave-2 will revisit bundling for
|
|
||||||
themes whose identity hinges on a commercial display face. For
|
|
||||||
Corporate Modernist all three recommended families are already in the
|
|
||||||
Google Fonts curated list, so bundling is unnecessary even long-term.
|
|
||||||
|
|||||||
16
RECOMMENDED_ICONS.md
Normal file
16
RECOMMENDED_ICONS.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Corporate Modernist — required icon packs
|
||||||
|
|
||||||
|
The theme renders icons via the sprite system only
|
||||||
|
(`<svg><use href="/icons/<pack>.svg#<name>"/></svg>` and the `::pack:name::`
|
||||||
|
shorthand). It declares its packs in `manifest.yaml` `required_icon_packs`.
|
||||||
|
Until the loader auto-installs declared packs, install them by hand before
|
||||||
|
activation.
|
||||||
|
|
||||||
|
| Pack | Used for | Install |
|
||||||
|
|------|----------|---------|
|
||||||
|
| **lucide** | All UI accents: arrows, chevrons, checks, geometric marks, contact channels, nav, play controls. Bundled with the CMS. | Ships with the CMS. Verify it is enabled at `/admin/icons`. |
|
||||||
|
| **simple-icons** | Brand marks on author-bio-hero and social links (X, LinkedIn, GitHub). Bundled with the CMS. | Ships with the CMS. Enable at `/admin/icons` if not already active. |
|
||||||
|
|
||||||
|
Both packs ship with BlockNinja, so no npm download is needed. If a block picks
|
||||||
|
an icon from a different pack, install that pack at `/admin/icons` first
|
||||||
|
(`lucide` is the safe default fallback for any icon-picker field).
|
||||||
19
assets/fonts/LICENSES.md
Normal file
19
assets/fonts/LICENSES.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Bundled font licenses
|
||||||
|
|
||||||
|
Both bundled families are libre and redistributable. Latin subsets only.
|
||||||
|
|
||||||
|
## Inter
|
||||||
|
|
||||||
|
- Files: `inter-latin-400.woff2`, `inter-latin-500.woff2`, `inter-latin-600.woff2`, `inter-latin-700.woff2`
|
||||||
|
- Family: Inter (Rasmus Andersson)
|
||||||
|
- License: SIL Open Font License 1.1 (OFL-1.1)
|
||||||
|
- Source: https://github.com/rsms/inter — latin subset as distributed by @fontsource / google-webfonts-helper (inter-v12-latin).
|
||||||
|
|
||||||
|
## JetBrains Mono
|
||||||
|
|
||||||
|
- File: `jetbrains-mono-latin.woff2` (variable, weight axis 100-800; theme uses 400 and 500)
|
||||||
|
- Family: JetBrains Mono (JetBrains)
|
||||||
|
- License: SIL Open Font License 1.1 (OFL-1.1)
|
||||||
|
- Source: https://github.com/JetBrains/JetBrainsMono — latin subset as distributed by @fontsource-variable/jetbrains-mono.
|
||||||
|
|
||||||
|
The full OFL-1.1 text applies to both. See https://openfontlicense.org.
|
||||||
BIN
assets/fonts/inter-latin-400.woff2
Normal file
BIN
assets/fonts/inter-latin-400.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/inter-latin-500.woff2
Normal file
BIN
assets/fonts/inter-latin-500.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/inter-latin-600.woff2
Normal file
BIN
assets/fonts/inter-latin-600.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/inter-latin-700.woff2
Normal file
BIN
assets/fonts/inter-latin-700.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/jetbrains-mono-latin.woff2
Normal file
BIN
assets/fonts/jetbrains-mono-latin.woff2
Normal file
Binary file not shown.
@ -289,8 +289,15 @@
|
|||||||
|
|
||||||
/* Section spacing. */
|
/* Section spacing. */
|
||||||
.cm-section {
|
.cm-section {
|
||||||
padding-top: 4rem;
|
padding-top: 3rem;
|
||||||
padding-bottom: 4rem;
|
padding-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.cm-section {
|
||||||
|
padding-top: 3.5rem;
|
||||||
|
padding-bottom: 3.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Image empty-state — when media missing. */
|
/* Image empty-state — when media missing. */
|
||||||
|
|||||||
@ -1,54 +1,34 @@
|
|||||||
# Corporate Modernist theme block definitions (codeless .bnp, WO-WZ-021).
|
# Corporate Modernist theme block definitions (codeless .bnp, WO-TF-014).
|
||||||
# Keys are fully qualified (corporate-modernist:<key>) because the codeless loader
|
# Keys are fully qualified (corporate-modernist:<key>) because the codeless loader
|
||||||
# registers definitions verbatim — there is no PluginBlockRegistry prefixing in the
|
# registers definitions verbatim — there is no PluginBlockRegistry prefixing in the
|
||||||
# codeless path (unlike the wasm/templ path). The qualified keys match the hardcoded
|
# codeless path (unlike the wasm/templ path). The qualified keys match the hardcoded
|
||||||
# data-block attributes and the master_pages.json references.
|
# data-block attributes and the master_pages.json / seed references.
|
||||||
|
#
|
||||||
|
# Only true persona furniture stays a theme block. Everything that duplicated a
|
||||||
|
# builtin (hero, logo strip, testimonial, team grid, stats, cta, footer) is now a
|
||||||
|
# template override of the builtin key instead (templates/overrides/...).
|
||||||
blocks:
|
blocks:
|
||||||
- key: corporate-modernist:hero_statement
|
|
||||||
title: Hero Statement
|
|
||||||
description: Restrained hero with eyebrow, headline, lede, and a single accent CTA.
|
|
||||||
category: layout
|
|
||||||
schema: hero_statement.schema.json
|
|
||||||
template: hero_statement.ninjatpl
|
|
||||||
- key: corporate-modernist:logo_strip
|
|
||||||
title: Client Logo Strip
|
|
||||||
description: Row of client logos, greyscale until hover.
|
|
||||||
category: content
|
|
||||||
schema: logo_strip.schema.json
|
|
||||||
template: logo_strip.ninjatpl
|
|
||||||
- key: corporate-modernist:testimonial_quote
|
|
||||||
title: Testimonial
|
|
||||||
description: Pull-quote testimonial with attribution, headshot, and company mark.
|
|
||||||
category: content
|
|
||||||
schema: testimonial_quote.schema.json
|
|
||||||
template: testimonial_quote.ninjatpl
|
|
||||||
- key: corporate-modernist:case_study_card
|
- key: corporate-modernist:case_study_card
|
||||||
title: Case Study Card
|
title: Case Study Card
|
||||||
description: Single case study card with a big tabular metric.
|
description: Single engagement case with an oversized tabular metric and outcome.
|
||||||
category: content
|
category: content
|
||||||
schema: case_study_card.schema.json
|
schema: case_study_card.schema.json
|
||||||
template: case_study_card.ninjatpl
|
template: case_study_card.ninjatpl
|
||||||
- key: corporate-modernist:leadership_grid
|
- key: corporate-modernist:capability_matrix
|
||||||
title: Leadership Grid
|
title: Capability Matrix
|
||||||
description: Responsive 2/3/4-up grid of leadership profiles.
|
description: Swiss-grid matrix of service capabilities, each with a Bauhaus mark.
|
||||||
category: content
|
category: content
|
||||||
schema: leadership_grid.schema.json
|
schema: capability_matrix.schema.json
|
||||||
template: leadership_grid.ninjatpl
|
template: capability_matrix.ninjatpl
|
||||||
- key: corporate-modernist:stat_pair
|
- key: corporate-modernist:engagement_process
|
||||||
title: Stat Pair
|
title: Engagement Process
|
||||||
description: Row of mono-numeral statistics separated by a hairline rule.
|
description: Numbered engagement phases on a hairline rail with oversized numerals.
|
||||||
category: content
|
category: content
|
||||||
schema: stat_pair.schema.json
|
schema: engagement_process.schema.json
|
||||||
template: stat_pair.ninjatpl
|
template: engagement_process.ninjatpl
|
||||||
- key: corporate-modernist:cta_strip
|
- key: corporate-modernist:credential_bar
|
||||||
title: CTA Strip
|
title: Credential Bar
|
||||||
description: Single-line accent strip with a headline and a primary call to action.
|
description: Row of firm credentials (years, offices, sectors) as mono figures.
|
||||||
category: layout
|
category: content
|
||||||
schema: cta_strip.schema.json
|
schema: credential_bar.schema.json
|
||||||
template: cta_strip.ninjatpl
|
template: credential_bar.ninjatpl
|
||||||
- key: corporate-modernist:footer
|
|
||||||
title: Footer
|
|
||||||
description: Multi-column footer with legal line; compact and full variants.
|
|
||||||
category: navigation
|
|
||||||
schema: footer.schema.json
|
|
||||||
template: footer.ninjatpl
|
|
||||||
|
|||||||
1
blocks/capability_matrix.ninjatpl
Normal file
1
blocks/capability_matrix.ninjatpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<section class="cm-section" data-block="corporate-modernist:capability_matrix"><div class="cm-content-well">{% if eyebrow %}<p class="cm-eyebrow">{{ eyebrow }}</p>{% endif %}{% if heading %}<h2 class="cm-heading" style="font-size: clamp(1.75rem, 3vw, 2.25rem); margin: 0 0 0.75rem 0;"><span class="cm-heading-h2-accent">{{ heading }}</span></h2>{% endif %}{% if intro %}<p class="cm-lede" style="max-width: 60ch; margin: 0 0 2.5rem 0;">{{ intro }}</p>{% endif %}{% if capabilities %}<div class="cm-grid {% if columns == 2 %}cm-grid-2{% elif columns == 4 %}cm-grid-4{% else %}cm-grid-3{% endif %}">{% for cap in capabilities %}<div class="cm-feature"><span class="cm-mark {% if cap.mark == "circle" %}cm-mark-circle{% elif cap.mark == "triangle" %}cm-mark-triangle{% else %}cm-mark-square{% endif %}" aria-hidden="true"></span>{% if cap.title %}<h3 class="cm-feature-title">{{ cap.title }}</h3>{% endif %}{% if cap.text %}<p class="cm-text" style="font-size: 0.9375rem; line-height: 1.6; color: hsl(var(--muted-foreground));">{{ cap.text }}</p>{% endif %}</div>{% endfor %}</div>{% else %}<p class="cm-stat-label">No capabilities configured.</p>{% endif %}</div></section>
|
||||||
38
blocks/capability_matrix.schema.json
Normal file
38
blocks/capability_matrix.schema.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Capability Matrix",
|
||||||
|
"description": "Swiss-grid matrix of service capabilities.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"eyebrow": { "type": "string", "title": "Eyebrow", "x-editor": "text" },
|
||||||
|
"heading": { "type": "string", "title": "Heading", "x-editor": "text" },
|
||||||
|
"intro": { "type": "string", "title": "Intro", "x-editor": "textarea" },
|
||||||
|
"columns": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Columns",
|
||||||
|
"enum": [2, 3, 4],
|
||||||
|
"default": 3,
|
||||||
|
"x-editor": "select"
|
||||||
|
},
|
||||||
|
"capabilities": {
|
||||||
|
"type": "array",
|
||||||
|
"title": "Capabilities",
|
||||||
|
"x-editor": "collection",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"mark": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Mark",
|
||||||
|
"description": "Bauhaus mark: circle, triangle, or square",
|
||||||
|
"enum": ["circle", "triangle", "square"],
|
||||||
|
"default": "square",
|
||||||
|
"x-editor": "select"
|
||||||
|
},
|
||||||
|
"title": { "type": "string", "title": "Title", "x-editor": "text" },
|
||||||
|
"text": { "type": "string", "title": "Description", "x-editor": "textarea" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
blocks/credential_bar.ninjatpl
Normal file
1
blocks/credential_bar.ninjatpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<section class="cm-section-tight" data-block="corporate-modernist:credential_bar"><div class="cm-content-well">{% if items %}<div class="cm-grid cm-grid-4 cm-accent-rule" style="padding-top: 2rem;">{% for item in items %}<div>{% if item.figure %}<div class="cm-stat-figure numeric-tabular">{{ item.figure }}</div>{% endif %}{% if item.label %}<div class="cm-stat-label">{{ item.label }}</div>{% endif %}</div>{% endfor %}</div>{% else %}<p class="cm-stat-label">No credentials configured.</p>{% endif %}</div></section>
|
||||||
20
blocks/credential_bar.schema.json
Normal file
20
blocks/credential_bar.schema.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Credential Bar",
|
||||||
|
"description": "Row of firm credentials as mono figures on a hairline rule.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"title": "Credentials",
|
||||||
|
"x-editor": "collection",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"figure": { "type": "string", "title": "Figure", "description": "e.g. \"18\", \"6\", \"200+\"", "x-editor": "text" },
|
||||||
|
"label": { "type": "string", "title": "Label", "x-editor": "text" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +0,0 @@
|
|||||||
<section class="cm-section {% if variant|default:"book-call" == "quiet" %}cm-cta-strip-quiet{% else %}cm-cta-strip-accent{% endif %}" data-block="corporate-modernist:cta_strip" data-variant="{{ variant|default:"book-call" }}"><div class="cm-content-well"><div style="display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1.5rem;">{% if headline %}<h2 class="cm-display" style="font-size: 1.75rem; margin: 0; max-width: 36rem;">{{ headline }}</h2>{% else %}<h2 class="cm-display" style="font-size: 1.75rem; margin: 0; max-width: 36rem;">Ready when you are.</h2>{% endif %}{% if primaryCta.label or primaryCta.text or primaryCta.href or primaryCta.url %}<a class="cm-btn-primary" href="{{ primaryCta.href|default:primaryCta.url|default:"#" }}">{% if primaryCta.label %}{{ primaryCta.label }}{% elif primaryCta.text %}{{ primaryCta.text }}{% elif variant|default:"book-call" == "download" %}Download report{% elif variant|default:"book-call" == "quiet" %}Learn more{% else %}Book a call{% endif %}</a>{% else %}<a class="cm-btn-primary" href="#" aria-disabled="true">{% if variant|default:"book-call" == "download" %}Download report{% elif variant|default:"book-call" == "quiet" %}Learn more{% else %}Book a call{% endif %}</a>{% endif %}</div></div></section>
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
||||||
"title": "CTA Strip",
|
|
||||||
"description": "Single-line accent strip with a headline and a primary call to action.",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"headline": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Headline",
|
|
||||||
"x-editor": "text"
|
|
||||||
},
|
|
||||||
"primaryCta": {
|
|
||||||
"type": "object",
|
|
||||||
"title": "Primary CTA",
|
|
||||||
"x-editor": "link",
|
|
||||||
"properties": {
|
|
||||||
"label": { "type": "string", "title": "Label" },
|
|
||||||
"href": { "type": "string", "title": "Href" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"variant": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Variant",
|
|
||||||
"description": "Visual treatment for the strip",
|
|
||||||
"x-editor": "select",
|
|
||||||
"enum": ["book-call", "download", "quiet"],
|
|
||||||
"default": "book-call"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
blocks/engagement_process.ninjatpl
Normal file
1
blocks/engagement_process.ninjatpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<section class="cm-section" data-block="corporate-modernist:engagement_process"><div class="cm-content-well">{% if eyebrow %}<p class="cm-eyebrow">{{ eyebrow }}</p>{% endif %}{% if heading %}<h2 class="cm-heading" style="font-size: clamp(1.75rem, 3vw, 2.25rem); margin: 0 0 0.75rem 0;"><span class="cm-heading-h2-accent">{{ heading }}</span></h2>{% endif %}{% if intro %}<p class="cm-lede" style="max-width: 60ch; margin: 0 0 2.5rem 0;">{{ intro }}</p>{% endif %}{% if steps %}<div>{% for step in steps %}<div class="cm-timeline-step"><div class="cm-timeline-rail"><span class="cm-timeline-node">{{ forloop.Counter }}</span>{% if not forloop.Last %}<span class="cm-timeline-line" aria-hidden="true"></span>{% endif %}</div><div style="padding-top: 0.375rem;">{% if step.title %}<h3 class="cm-feature-title" style="margin-top: 0;">{{ step.title }}</h3>{% endif %}{% if step.text %}<p class="cm-text" style="font-size: 0.9375rem; line-height: 1.6; color: hsl(var(--muted-foreground));">{{ step.text }}</p>{% endif %}</div></div>{% endfor %}</div>{% else %}<p class="cm-stat-label">No steps configured.</p>{% endif %}</div></section>
|
||||||
23
blocks/engagement_process.schema.json
Normal file
23
blocks/engagement_process.schema.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Engagement Process",
|
||||||
|
"description": "Numbered engagement phases on a hairline rail.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"eyebrow": { "type": "string", "title": "Eyebrow", "x-editor": "text" },
|
||||||
|
"heading": { "type": "string", "title": "Heading", "x-editor": "text" },
|
||||||
|
"intro": { "type": "string", "title": "Intro", "x-editor": "textarea" },
|
||||||
|
"steps": {
|
||||||
|
"type": "array",
|
||||||
|
"title": "Steps",
|
||||||
|
"x-editor": "collection",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": { "type": "string", "title": "Title", "x-editor": "text" },
|
||||||
|
"text": { "type": "string", "title": "Description", "x-editor": "textarea" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +0,0 @@
|
|||||||
<div class="cm-footer" data-block="corporate-modernist:footer" data-variant="{{ variant|default:"compact" }}"><div class="cm-content-well">{% if variant|default:"compact" == "full" and columns %}<div class="cm-swiss-12">{% for col in columns %}<div style="{% if columns|length >= 4 %}grid-column: span 3 / span 3;{% elif columns|length == 3 %}grid-column: span 4 / span 4;{% elif columns|length == 2 %}grid-column: span 6 / span 6;{% else %}grid-column: span 12 / span 12;{% endif %}">{% if col.title %}<div class="cm-display" style="font-size: 0.875rem; font-weight: 600; margin: 0 0 0.75rem 0; text-transform: uppercase; letter-spacing: 0.05em;">{{ col.title }}</div>{% endif %}{% if col.links %}<ul style="list-style: none; padding: 0; margin: 0;">{% for l in col.links %}{% if l.label or l.text or l.href or l.url %}<li style="margin-bottom: 0.5rem;"><a href="{{ l.href|default:l.url|default:"#" }}" class="cm-text" style="font-size: 0.95rem; text-decoration: none;">{{ l.label|default:l.text|default:l.href|default:l.url|default:"#" }}</a></li>{% endif %}{% endfor %}</ul>{% endif %}</div>{% endfor %}</div>{% endif %}{% if showLegal != false and showLegal != "false" %}<div class="cm-footer-legal">{% if legalLine %}{{ legalLine }}{% else %}All rights reserved.{% endif %}</div>{% endif %}</div></div>
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
||||||
"title": "Footer",
|
|
||||||
"description": "Multi-column footer with legal line, available in compact and full variants.",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"columns": {
|
|
||||||
"type": "array",
|
|
||||||
"title": "Columns",
|
|
||||||
"description": "Each column lists a title and a set of links",
|
|
||||||
"default": [],
|
|
||||||
"x-editor": "collection",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"title": { "type": "string", "title": "Title", "x-editor": "text" },
|
|
||||||
"links": {
|
|
||||||
"type": "array",
|
|
||||||
"title": "Links",
|
|
||||||
"x-editor": "collection",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"label": { "type": "string", "title": "Label", "x-editor": "text" },
|
|
||||||
"href": { "type": "string", "title": "Href", "x-editor": "link" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"legalLine": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Legal line",
|
|
||||||
"description": "Copyright or compliance statement",
|
|
||||||
"x-editor": "text"
|
|
||||||
},
|
|
||||||
"showLegal": {
|
|
||||||
"type": "boolean",
|
|
||||||
"title": "Show legal line",
|
|
||||||
"default": true,
|
|
||||||
"x-editor": "select"
|
|
||||||
},
|
|
||||||
"variant": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Variant",
|
|
||||||
"x-editor": "select",
|
|
||||||
"enum": ["compact", "full"],
|
|
||||||
"default": "compact"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<section class="cm-section" data-block="corporate-modernist:hero_statement"><div class="cm-content-well"><div class="cm-swiss-12"><div style="grid-column: span 12 / span 12;">{% if eyebrow %}<div class="cm-eyebrow">{{ eyebrow }}</div>{% endif %}{% if headline %}<h1 class="cm-display" style="font-size: clamp(2rem, 4vw, 3.5rem); line-height: 1.1; margin: 0 0 1.25rem 0;">{{ headline }}</h1>{% endif %}{% if lede %}<p class="cm-text" style="max-width: 60ch; margin: 0 0 2rem 0;">{{ lede }}</p>{% endif %}{% if primaryCta.label or primaryCta.text or primaryCta.href or primaryCta.url or secondaryCta.label or secondaryCta.text or secondaryCta.href or secondaryCta.url %}<div style="display: flex; flex-wrap: wrap; gap: 0.75rem;">{% if primaryCta.label or primaryCta.text or primaryCta.href or primaryCta.url %}<a class="cm-btn-primary" href="{{ primaryCta.href|default:primaryCta.url|default:"#" }}">{{ primaryCta.label|default:primaryCta.text|default:"Get in touch" }}</a>{% endif %}{% if secondaryCta.label or secondaryCta.text or secondaryCta.href or secondaryCta.url %}<a class="cm-btn-outline" href="{{ secondaryCta.href|default:secondaryCta.url|default:"#" }}">{{ secondaryCta.label|default:secondaryCta.text|default:"Learn more" }}</a>{% endif %}</div>{% endif %}</div></div></div></section>
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
||||||
"title": "Hero Statement",
|
|
||||||
"description": "Restrained hero with eyebrow, headline, lede, and a single accent CTA (plus optional outline secondary).",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"eyebrow": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Eyebrow",
|
|
||||||
"description": "Small uppercase label above the headline",
|
|
||||||
"x-editor": "text"
|
|
||||||
},
|
|
||||||
"headline": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Headline",
|
|
||||||
"description": "Lead statement, typically one short sentence",
|
|
||||||
"x-editor": "text"
|
|
||||||
},
|
|
||||||
"lede": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Lede",
|
|
||||||
"description": "Supporting paragraph below the headline",
|
|
||||||
"x-editor": "textarea"
|
|
||||||
},
|
|
||||||
"primaryCta": {
|
|
||||||
"type": "object",
|
|
||||||
"title": "Primary CTA",
|
|
||||||
"description": "Accent-filled call to action",
|
|
||||||
"x-editor": "link",
|
|
||||||
"properties": {
|
|
||||||
"label": { "type": "string", "title": "Label" },
|
|
||||||
"href": { "type": "string", "title": "Href" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"secondaryCta": {
|
|
||||||
"type": "object",
|
|
||||||
"title": "Secondary CTA",
|
|
||||||
"description": "Optional outline call to action",
|
|
||||||
"x-editor": "link",
|
|
||||||
"properties": {
|
|
||||||
"label": { "type": "string", "title": "Label" },
|
|
||||||
"href": { "type": "string", "title": "Href" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<section class="cm-section" data-block="corporate-modernist:leadership_grid"><div class="cm-content-well">{% if title %}<h2 class="cm-heading" style="font-size: 1.875rem; margin: 0 0 2rem 0;"><span class="cm-heading-h2-accent">{{ title }}</span></h2>{% endif %}{% if members %}<div class="cm-leadership-grid">{% for m in members %}{% if m.name %}<article class="cm-card">{% if m.photo %}<img src="{{ m.photo }}" alt="{{ m.name|default:"Team member portrait" }}" style="width: 100%; aspect-ratio: 1 / 1; object-fit: cover; display: block; margin-bottom: 1rem; border-radius: 4px;">{% else %}<span class="cm-img-empty" style="aspect-ratio: 1 / 1; margin-bottom: 1rem; border-radius: 4px;"></span>{% endif %}<div class="cm-display" style="font-size: 1.125rem; font-weight: 600; margin: 0;">{{ m.name }}</div>{% if m.role %}<div class="cm-stat-label" style="margin-top: 0.25rem;">{{ m.role }}</div>{% endif %}{% if m.bio %}<p class="cm-text" style="margin: 0.75rem 0 0 0; font-size: 0.95rem; line-height: 1.55;">{{ m.bio }}</p>{% endif %}{% if m.linkedin %}<div style="margin-top: 1rem;"><a class="cm-btn-outline" href="{{ m.linkedin }}" style="font-size: 0.875rem; padding: 0.375rem 0.75rem;">LinkedIn</a></div>{% endif %}</article>{% endif %}{% endfor %}</div>{% else %}<p class="cm-stat-label">No team members configured.</p>{% endif %}</div></section>
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
||||||
"title": "Leadership Grid",
|
|
||||||
"description": "Responsive 2/3/4-up grid of leadership profiles.",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"title": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Title",
|
|
||||||
"description": "Section title above the grid",
|
|
||||||
"x-editor": "text"
|
|
||||||
},
|
|
||||||
"members": {
|
|
||||||
"type": "array",
|
|
||||||
"title": "Members",
|
|
||||||
"description": "Each member is a card with photo, name, role, short bio, and optional LinkedIn link",
|
|
||||||
"default": [],
|
|
||||||
"x-editor": "collection",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"name": { "type": "string", "title": "Name", "x-editor": "text" },
|
|
||||||
"role": { "type": "string", "title": "Role", "x-editor": "text" },
|
|
||||||
"bio": { "type": "string", "title": "Bio", "x-editor": "textarea" },
|
|
||||||
"photo": { "type": "string", "title": "Photo", "x-editor": "media" },
|
|
||||||
"linkedin": { "type": "string", "title": "LinkedIn URL", "x-editor": "link" }
|
|
||||||
},
|
|
||||||
"required": ["name"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<section class="cm-section" data-block="corporate-modernist:logo_strip"><div class="cm-content-well">{% if caption %}<p class="cm-stat-label" style="text-align: center; margin-bottom: 2rem;">{{ caption }}</p>{% endif %}{% if logos %}<div style="display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 2.5rem;">{% for logo in logos %}{% if logo.src or logo.alt %}{% if logo.href %}<a href="{{ logo.href }}" class="cm-logo" style="display: inline-flex; align-items: center;">{% if logo.src %}<img src="{{ logo.src }}" alt="{{ logo.alt|default:"Client logo" }}" style="max-height: 2rem; width: auto; display: block;">{% else %}<span class="cm-stat-label" style="margin: 0;">{{ logo.alt|default:"Client logo" }}</span>{% endif %}</a>{% else %}<span class="cm-logo" style="display: inline-flex; align-items: center;">{% if logo.src %}<img src="{{ logo.src }}" alt="{{ logo.alt|default:"Client logo" }}" style="max-height: 2rem; width: auto; display: block;">{% else %}<span class="cm-stat-label" style="margin: 0;">{{ logo.alt|default:"Client logo" }}</span>{% endif %}</span>{% endif %}{% endif %}{% endfor %}</div>{% else %}<p class="cm-stat-label" style="text-align: center;">No client logos configured.</p>{% endif %}</div></section>
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
||||||
"title": "Client Logo Strip",
|
|
||||||
"description": "Row of client logos rendered greyscale until hover.",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"caption": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Caption",
|
|
||||||
"description": "Short heading above the logo row",
|
|
||||||
"x-editor": "text"
|
|
||||||
},
|
|
||||||
"logos": {
|
|
||||||
"type": "array",
|
|
||||||
"title": "Logos",
|
|
||||||
"description": "Client logos shown in a single restrained row",
|
|
||||||
"default": [],
|
|
||||||
"x-editor": "collection",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"src": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Image",
|
|
||||||
"x-editor": "media"
|
|
||||||
},
|
|
||||||
"alt": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Alt text",
|
|
||||||
"x-editor": "text"
|
|
||||||
},
|
|
||||||
"href": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Link",
|
|
||||||
"x-editor": "link"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["alt"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<section class="cm-section" data-block="corporate-modernist:stat_pair"><div class="cm-content-well">{% if title %}<h2 class="cm-heading" style="font-size: 1.875rem; margin: 0 0 2rem 0;"><span class="cm-heading-h2-accent">{{ title }}</span></h2>{% endif %}{% if stats %}<div class="cm-swiss-12" style="row-gap: 2rem;">{% for s in stats %}{% if s.figure %}<div style="{% if stats|length >= 4 %}grid-column: span 3 / span 3;{% elif stats|length == 3 %}grid-column: span 4 / span 4;{% elif stats|length == 2 %}grid-column: span 6 / span 6;{% else %}grid-column: span 12 / span 12;{% endif %}" class="cm-hairline-top"><div class="cm-stat-figure numeric-tabular" data-figure style="padding-top: 1rem;">{{ s.figure }}</div>{% if s.label %}<div class="cm-stat-label">{{ s.label }}</div>{% endif %}{% if s.source %}<div class="cm-stat-label" style="margin-top: 0.25rem; text-transform: none; letter-spacing: 0; opacity: 0.8;">{{ s.source }}</div>{% endif %}</div>{% endif %}{% endfor %}</div>{% else %}<p class="cm-stat-label">No statistics configured.</p>{% endif %}</div></section>
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
||||||
"title": "Stat Pair",
|
|
||||||
"description": "Pair (or row) of mono-numeral statistics, separated by a hairline rule.",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"title": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Title",
|
|
||||||
"description": "Optional title above the stats row",
|
|
||||||
"x-editor": "text"
|
|
||||||
},
|
|
||||||
"stats": {
|
|
||||||
"type": "array",
|
|
||||||
"title": "Stats",
|
|
||||||
"description": "Individual statistic entries",
|
|
||||||
"default": [],
|
|
||||||
"x-editor": "collection",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"figure": { "type": "string", "title": "Figure", "x-editor": "text" },
|
|
||||||
"label": { "type": "string", "title": "Label", "x-editor": "text" },
|
|
||||||
"source": { "type": "string", "title": "Source", "x-editor": "text" }
|
|
||||||
},
|
|
||||||
"required": ["figure"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<section class="cm-section" data-block="corporate-modernist:testimonial_quote"><div class="cm-content-well"><figure style="max-width: 70ch; margin: 0 auto;">{% if quote %}<blockquote class="cm-pull-quote">{{ quote }}</blockquote>{% else %}<blockquote class="cm-pull-quote">No testimonial provided.</blockquote>{% endif %}<figcaption style="display: flex; align-items: center; gap: 1rem; margin-top: 1.5rem;">{% if headshot %}<img src="{{ headshot }}" alt="{{ name|default:"Testimonial portrait" }}" style="width: 3rem; height: 3rem; border-radius: 50%; object-fit: cover; display: block;">{% else %}<span class="cm-img-empty" style="width: 3rem; height: 3rem; aspect-ratio: 1 / 1; border-radius: 50%;"></span>{% endif %}<div>{% if name %}<div class="cm-display" style="font-size: 1rem; font-weight: 600; margin: 0;">{{ name }}</div>{% endif %}{% if role or company %}<div class="cm-stat-label" style="margin: 0.25rem 0 0 0;">{% if role and company %}{{ role }}, {{ company }}{% elif role %}{{ role }}{% else %}{{ company }}{% endif %}</div>{% endif %}</div>{% if logo %}<img src="{{ logo }}" alt="{% if company %}{{ company }} logo{% else %}Company logo{% endif %}" class="cm-logo" style="max-height: 1.5rem; width: auto; margin-left: auto; display: block;">{% endif %}</figcaption></figure></div></section>
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
||||||
"title": "Testimonial",
|
|
||||||
"description": "Pull-quote testimonial with attribution and optional headshot and company logo.",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"quote": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Quote",
|
|
||||||
"description": "Testimonial body text",
|
|
||||||
"x-editor": "textarea"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Name",
|
|
||||||
"x-editor": "text"
|
|
||||||
},
|
|
||||||
"role": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Role",
|
|
||||||
"x-editor": "text"
|
|
||||||
},
|
|
||||||
"company": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Company",
|
|
||||||
"x-editor": "text"
|
|
||||||
},
|
|
||||||
"headshot": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Headshot",
|
|
||||||
"description": "Portrait image of the quoted person",
|
|
||||||
"x-editor": "media"
|
|
||||||
},
|
|
||||||
"logo": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Company logo",
|
|
||||||
"description": "Optional company mark beside the attribution",
|
|
||||||
"x-editor": "media"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
21
fonts.json
21
fonts.json
@ -1 +1,20 @@
|
|||||||
[]
|
[
|
||||||
|
{
|
||||||
|
"name": "Inter",
|
||||||
|
"family": "Inter",
|
||||||
|
"variants": [
|
||||||
|
{ "weight": "400", "style": "normal", "file": "fonts/inter-latin-400.woff2" },
|
||||||
|
{ "weight": "500", "style": "normal", "file": "fonts/inter-latin-500.woff2" },
|
||||||
|
{ "weight": "600", "style": "normal", "file": "fonts/inter-latin-600.woff2" },
|
||||||
|
{ "weight": "700", "style": "normal", "file": "fonts/inter-latin-700.woff2" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "JetBrains Mono",
|
||||||
|
"family": "JetBrains Mono",
|
||||||
|
"variants": [
|
||||||
|
{ "weight": "400", "style": "normal", "file": "fonts/jetbrains-mono-latin.woff2" },
|
||||||
|
{ "weight": "500", "style": "normal", "file": "fonts/jetbrains-mono-latin.woff2" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|||||||
339
manifest.yaml
339
manifest.yaml
@ -1,13 +1,22 @@
|
|||||||
# Corporate Modernist theme — codeless .bnp manifest (WO-WZ-021 Phase B).
|
# Corporate Modernist theme — codeless .bnp manifest (WO-TF-014, Wave A).
|
||||||
# Synthesized into manifest.pb by `ninja plugin build` (no plugin.wasm).
|
# Synthesized into manifest.pb by `ninja plugin build` (no plugin.wasm).
|
||||||
|
#
|
||||||
|
# Identity: Swiss grid disciplined by Bauhaus geometric accents (circle /
|
||||||
|
# triangle / square), oversized tabular numerals, hairline dividers. Restrained
|
||||||
|
# B2B-consultancy authority. Light = gallery white + graphite ink; dark =
|
||||||
|
# graphite boardroom. Motion is CSS-first (no canvas, no JS showpiece).
|
||||||
theme_presets: presets.json
|
theme_presets: presets.json
|
||||||
bundled_fonts: fonts.json
|
bundled_fonts: fonts.json
|
||||||
master_pages: master_pages.json
|
master_pages: master_pages.json
|
||||||
|
|
||||||
|
required_icon_packs:
|
||||||
|
- lucide
|
||||||
|
- simple-icons
|
||||||
|
|
||||||
system_templates:
|
system_templates:
|
||||||
- key: corporate-modernist
|
- key: corporate-modernist
|
||||||
title: Corporate Modernist
|
title: Corporate Modernist
|
||||||
description: "Swiss-grid B2B theme with restrained typography, a single configurable accent, and trust-signal blocks for enterprise sites."
|
description: "Swiss-grid B2B theme with restrained typography, a single configurable accent, oversized numerals, and trust-signal chrome for consultancy and enterprise sites."
|
||||||
|
|
||||||
page_templates:
|
page_templates:
|
||||||
- system: corporate-modernist
|
- system: corporate-modernist
|
||||||
@ -15,6 +24,11 @@ page_templates:
|
|||||||
title: Default
|
title: Default
|
||||||
description: "Header + 12-col main + footer, capped at 1280px."
|
description: "Header + 12-col main + footer, capped at 1280px."
|
||||||
slots: [header, main, footer]
|
slots: [header, main, footer]
|
||||||
|
- system: corporate-modernist
|
||||||
|
key: full-width
|
||||||
|
title: Full width
|
||||||
|
description: "Edge-to-edge sections; internal grid kept."
|
||||||
|
slots: [header, main, footer]
|
||||||
- system: corporate-modernist
|
- system: corporate-modernist
|
||||||
key: landing
|
key: landing
|
||||||
title: Landing
|
title: Landing
|
||||||
@ -26,60 +40,317 @@ page_templates:
|
|||||||
description: "Centered measure for long-form thought leadership."
|
description: "Centered measure for long-form thought leadership."
|
||||||
slots: [header, main, aside, footer]
|
slots: [header, main, aside, footer]
|
||||||
- system: corporate-modernist
|
- system: corporate-modernist
|
||||||
key: full-width
|
key: blog-index
|
||||||
title: Full width
|
title: Blog index
|
||||||
description: "Edge-to-edge sections, internal grid kept."
|
description: "Title band, featured lead, and a hairline-ruled post grid."
|
||||||
slots: [header, main, footer]
|
slots: [header, featured, main, footer]
|
||||||
|
- system: corporate-modernist
|
||||||
|
key: contact
|
||||||
|
title: Contact
|
||||||
|
description: "Two-column contact: form on the grid, details in the aside."
|
||||||
|
slots: [header, main, aside, footer]
|
||||||
|
- system: corporate-modernist
|
||||||
|
key: auth
|
||||||
|
title: Auth
|
||||||
|
description: "Centered card on a quiet ground for login and reset surfaces."
|
||||||
|
slots: [main, footer]
|
||||||
|
|
||||||
template_overrides:
|
template_overrides:
|
||||||
|
# Chrome (4)
|
||||||
|
- { template: corporate-modernist, block: navbar }
|
||||||
|
- { template: corporate-modernist, block: footer }
|
||||||
|
- { template: corporate-modernist, block: utility-bar }
|
||||||
|
- { template: corporate-modernist, block: announcement-bar }
|
||||||
|
# Marketing (12)
|
||||||
|
- { template: corporate-modernist, block: hero }
|
||||||
|
- { template: corporate-modernist, block: feature-grid }
|
||||||
|
- { template: corporate-modernist, block: rich-section }
|
||||||
|
- { template: corporate-modernist, block: stats }
|
||||||
|
- { template: corporate-modernist, block: testimonial }
|
||||||
|
- { template: corporate-modernist, block: logo-strip }
|
||||||
|
- { template: corporate-modernist, block: pricing }
|
||||||
|
- { template: corporate-modernist, block: team }
|
||||||
|
- { template: corporate-modernist, block: faq }
|
||||||
|
- { template: corporate-modernist, block: timeline }
|
||||||
|
- { template: corporate-modernist, block: cta }
|
||||||
|
- { template: corporate-modernist, block: countdown }
|
||||||
|
# Primitives (11)
|
||||||
- { template: corporate-modernist, block: heading }
|
- { template: corporate-modernist, block: heading }
|
||||||
- { template: corporate-modernist, block: text }
|
- { template: corporate-modernist, block: text }
|
||||||
|
- { template: corporate-modernist, block: rich-text }
|
||||||
|
- { template: corporate-modernist, block: image }
|
||||||
|
- { template: corporate-modernist, block: quote }
|
||||||
|
- { template: corporate-modernist, block: divider }
|
||||||
- { template: corporate-modernist, block: button }
|
- { template: corporate-modernist, block: button }
|
||||||
- { template: corporate-modernist, block: card }
|
- { template: corporate-modernist, block: card }
|
||||||
|
- { template: corporate-modernist, block: gallery }
|
||||||
|
- { template: corporate-modernist, block: video-embed }
|
||||||
|
- { template: corporate-modernist, block: audio-embed }
|
||||||
|
# Commerce / local (6)
|
||||||
|
- { template: corporate-modernist, block: menu-list }
|
||||||
|
- { template: corporate-modernist, block: event-list }
|
||||||
|
- { template: corporate-modernist, block: contact-form }
|
||||||
|
- { template: corporate-modernist, block: contact-card }
|
||||||
|
- { template: corporate-modernist, block: hours-location }
|
||||||
|
- { template: corporate-modernist, block: social-links }
|
||||||
|
# Blog family (12)
|
||||||
|
- { template: corporate-modernist, block: blog-hero }
|
||||||
|
- { template: corporate-modernist, block: featured-posts }
|
||||||
|
- { template: corporate-modernist, block: related-posts }
|
||||||
|
- { template: corporate-modernist, block: popular-posts }
|
||||||
|
- { template: corporate-modernist, block: category-list }
|
||||||
|
- { template: corporate-modernist, block: post-hero }
|
||||||
|
- { template: corporate-modernist, block: post-metadata }
|
||||||
|
- { template: corporate-modernist, block: author-bio }
|
||||||
|
- { template: corporate-modernist, block: author-bio-hero }
|
||||||
|
- { template: corporate-modernist, block: newsletter-signup }
|
||||||
|
- { template: corporate-modernist, block: breadcrumbs }
|
||||||
|
- { template: corporate-modernist, block: sidebar-nav }
|
||||||
|
# Auth surface (3)
|
||||||
|
- { template: corporate-modernist, block: auth-form }
|
||||||
|
- { template: corporate-modernist, block: password-reset-form }
|
||||||
|
- { template: corporate-modernist, block: auth-status }
|
||||||
|
# System (1)
|
||||||
|
- { template: corporate-modernist, block: page-suggestions }
|
||||||
|
|
||||||
email_wrappers:
|
email_wrappers:
|
||||||
- corporate-modernist
|
- corporate-modernist
|
||||||
|
|
||||||
css:
|
css:
|
||||||
input_css_append: |
|
input_css_append: |
|
||||||
/* Corporate Modernist — appended utility CSS. */
|
/* ============================================================
|
||||||
|
Corporate Modernist — appended component CSS.
|
||||||
|
Semantic tokens only: hsl(var(--token)). No literal colors.
|
||||||
|
Swiss grid + Bauhaus geometric marks + oversized numerals.
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
/* --- Layout primitives --- */
|
||||||
.cm-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; }
|
.cm-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; }
|
||||||
|
.cm-measure { max-width: 70ch; }
|
||||||
.cm-swiss-12 { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: 1.5rem; row-gap: 1.5rem; }
|
.cm-swiss-12 { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: 1.5rem; row-gap: 1.5rem; }
|
||||||
.cm-hairline { border-color: hsl(var(--border)); border-width: 1px; border-style: solid; }
|
.cm-section { padding-top: 3rem; padding-bottom: 3rem; }
|
||||||
|
@media (min-width: 768px) { .cm-section { padding-top: 3.5rem; padding-bottom: 3.5rem; } }
|
||||||
|
.cm-section-tight { padding-top: 2rem; padding-bottom: 2rem; }
|
||||||
|
.cm-hairline { border: 1px solid hsl(var(--border)); }
|
||||||
.cm-hairline-top { border-top: 1px solid hsl(var(--border)); }
|
.cm-hairline-top { border-top: 1px solid hsl(var(--border)); }
|
||||||
.cm-hairline-bottom { border-bottom: 1px solid hsl(var(--border)); }
|
.cm-hairline-bottom { border-bottom: 1px solid hsl(var(--border)); }
|
||||||
.numeric-tabular { font-variant-numeric: tabular-nums; }
|
.cm-hairline-y > * + * { border-top: 1px solid hsl(var(--border)); }
|
||||||
.cm-display { font-family: var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif); font-weight: 600; letter-spacing: -0.02em; }
|
|
||||||
.cm-body { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 17px; line-height: 28px; font-variant-numeric: tabular-nums; }
|
|
||||||
.cm-mono { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-variant-numeric: tabular-nums; }
|
|
||||||
.cm-accent-rule { border-top: 2px solid hsl(var(--accent)); }
|
.cm-accent-rule { border-top: 2px solid hsl(var(--accent)); }
|
||||||
.cm-btn-primary { display: inline-block; 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 Tight", "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; }
|
.cm-stack > * + * { margin-top: 1.5rem; }
|
||||||
|
.cm-stack-sm > * + * { margin-top: 0.75rem; }
|
||||||
|
|
||||||
|
/* --- Typography --- */
|
||||||
|
.cm-display { font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-weight: 600; letter-spacing: -0.02em; color: hsl(var(--foreground)); margin: 0; }
|
||||||
|
.cm-body { font-family: var(--font-body, "Inter", system-ui, sans-serif); }
|
||||||
|
.cm-mono { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-variant-numeric: tabular-nums; }
|
||||||
|
.numeric-tabular { font-variant-numeric: tabular-nums; }
|
||||||
|
.cm-heading { font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-weight: 600; letter-spacing: -0.02em; color: hsl(var(--foreground)); margin: 0; }
|
||||||
|
.cm-heading-h2-accent { border-bottom: 2px solid hsl(var(--accent)); padding-bottom: 0.25rem; display: inline-block; }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-text a { color: hsl(var(--accent)); text-decoration: underline; text-underline-offset: 2px; }
|
||||||
|
.cm-text strong { font-weight: 600; }
|
||||||
|
.cm-lede { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 1.25rem; line-height: 1.6; color: hsl(var(--muted-foreground)); }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-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) --- */
|
||||||
|
.cm-mark { display: inline-block; width: 0.75rem; height: 0.75rem; flex: none; }
|
||||||
|
.cm-mark-circle { border-radius: 9999px; background-color: hsl(var(--accent)); }
|
||||||
|
.cm-mark-square { background-color: hsl(var(--accent)); }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-mark-ring { border-radius: 9999px; background: none; border: 2px solid hsl(var(--accent)); }
|
||||||
|
|
||||||
|
/* --- Numerals / metrics (oversized signature) --- */
|
||||||
|
.cm-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; }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-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) --- */
|
||||||
|
.cm-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; }
|
||||||
.cm-btn-primary:hover { background-color: hsl(var(--accent) / 0.9); }
|
.cm-btn-primary:hover { background-color: hsl(var(--accent) / 0.9); }
|
||||||
.cm-btn-primary:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; }
|
.cm-btn-primary:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; }
|
||||||
.cm-btn-outline { display: inline-block; 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 Tight", "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; }
|
.cm-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; }
|
||||||
.cm-btn-outline:hover { border-color: hsl(var(--foreground)); }
|
.cm-btn-outline:hover { border-color: hsl(var(--foreground)); }
|
||||||
.cm-btn-outline:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; }
|
.cm-btn-outline:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-btn-ghost:hover { gap: 0.625rem; }
|
||||||
|
.cm-btn-sm { padding: 0.375rem 0.875rem; font-size: 0.875rem; }
|
||||||
|
.cm-btn-lg { padding: 0.875rem 1.75rem; font-size: 1.0625rem; }
|
||||||
|
.cm-btn-row { display: flex; flex-wrap: wrap; gap: 0.75rem; }
|
||||||
|
.cm-icon { width: 1em; height: 1em; display: inline-block; vertical-align: -0.125em; }
|
||||||
|
|
||||||
|
/* --- Surfaces / cards --- */
|
||||||
.cm-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; }
|
.cm-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; }
|
||||||
.cm-card.cm-card-accent { border-top: 2px solid hsl(var(--accent)); }
|
.cm-card-accent { border-top: 2px solid hsl(var(--accent)); }
|
||||||
.cm-heading { font-family: var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif); font-weight: 600; letter-spacing: -0.02em; color: hsl(var(--foreground)); margin: 0; }
|
.cm-panel { background-color: hsl(var(--muted)); border-radius: 4px; padding: 1.5rem; }
|
||||||
.cm-heading-h2-accent { border-bottom: 2px solid hsl(var(--accent)); padding-bottom: 0.25rem; display: inline-block; }
|
.cm-surface-muted { background-color: hsl(var(--muted)); }
|
||||||
.cm-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; hanging-punctuation: first last; }
|
.cm-surface-card { background-color: hsl(var(--card)); }
|
||||||
.cm-logo { filter: grayscale(1); opacity: 0.7; transition: filter 200ms ease, opacity 200ms ease; }
|
.cm-media-frame { display: block; width: 100%; overflow: hidden; background-color: hsl(var(--muted)); border: 1px solid hsl(var(--border)); }
|
||||||
.cm-logo:hover { filter: grayscale(0); opacity: 1; }
|
.cm-media-frame img { display: block; width: 100%; height: 100%; object-fit: cover; }
|
||||||
.cm-pull-quote { border-top: 2px solid hsl(var(--accent)); padding-top: 1.25rem; font-family: var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif); font-size: 1.5rem; line-height: 1.4; font-weight: 500; color: hsl(var(--foreground)); }
|
.cm-img-empty { display: block; width: 100%; aspect-ratio: 4 / 3; background-color: hsl(var(--muted)); border: 1px solid hsl(var(--border)); }
|
||||||
.cm-metric { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-size: 3rem; line-height: 1; font-weight: 500; font-variant-numeric: tabular-nums; color: hsl(var(--accent)); }
|
|
||||||
.cm-stat-figure { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-size: 2.5rem; line-height: 1; font-weight: 500; font-variant-numeric: tabular-nums; color: hsl(var(--foreground)); }
|
/* --- Responsive grids --- */
|
||||||
.cm-stat-label { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 0.875rem; color: hsl(var(--muted-foreground)); text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.5rem; }
|
.cm-grid { display: grid; gap: 1.5rem; grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
||||||
.cm-eyebrow { font-family: var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif); font-size: 0.875rem; font-weight: 500; text-transform: uppercase; letter-spacing: 0.1em; color: hsl(var(--accent)); margin-bottom: 1rem; }
|
@media (min-width: 640px) { .cm-grid-2, .cm-grid-3, .cm-grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||||
.cm-cta-strip-accent { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); }
|
@media (min-width: 1024px) { .cm-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } .cm-grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
|
||||||
.cm-cta-strip-accent .cm-btn-primary { background-color: hsl(var(--accent-foreground)); color: hsl(var(--accent)); border-color: hsl(var(--accent-foreground)); }
|
.cm-leadership-grid { display: grid; gap: 1.5rem; grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
||||||
.cm-cta-strip-quiet { background-color: hsl(var(--muted)); color: hsl(var(--foreground)); }
|
|
||||||
.cm-footer { background-color: hsl(var(--background)); color: hsl(var(--foreground)); border-top: 1px solid hsl(var(--border)); padding: 3rem 0; }
|
|
||||||
.cm-footer-legal { color: hsl(var(--muted-foreground)); font-size: 0.875rem; border-top: 1px solid hsl(var(--border)); padding-top: 1.5rem; margin-top: 2rem; }
|
|
||||||
.cm-leadership-grid { display: grid; grid-template-columns: repeat(1, minmax(0, 1fr)); gap: 1.5rem; }
|
|
||||||
@media (min-width: 640px) { .cm-leadership-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
@media (min-width: 640px) { .cm-leadership-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||||
@media (min-width: 1024px) { .cm-leadership-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
|
@media (min-width: 1024px) { .cm-leadership-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
|
||||||
@media (min-width: 1280px) { .cm-leadership-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
|
@media (min-width: 1280px) { .cm-leadership-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
|
||||||
.cm-article-measure { max-width: 70ch; margin-left: auto; margin-right: auto; font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 17px; line-height: 28px; color: hsl(var(--foreground)); }
|
|
||||||
.cm-section { padding-top: 4rem; padding-bottom: 4rem; }
|
/* --- Chrome: navbar --- */
|
||||||
.cm-img-empty { display: block; width: 100%; aspect-ratio: 4 / 3; background-color: hsl(var(--muted)); border: 1px solid hsl(var(--border)); }
|
.cm-navbar { display: flex; align-items: center; justify-content: space-between; gap: 1.5rem; padding: 1.25rem 0; }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-nav-links { display: none; align-items: center; gap: 2rem; }
|
||||||
|
@media (min-width: 768px) { .cm-nav-links { display: flex; } }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-nav-link:hover { border-bottom-color: hsl(var(--accent)); }
|
||||||
|
.cm-nav-toggle { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; }
|
||||||
|
@media (min-width: 768px) { .cm-nav-toggle { display: none; } }
|
||||||
|
.cm-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) { .cm-nav-drawer { display: none !important; } }
|
||||||
|
.cm-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 --- */
|
||||||
|
.cm-utility-bar { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 0.8125rem; padding: 0.5rem 0; }
|
||||||
|
.cm-utility-bar--muted { background-color: hsl(var(--muted)); color: hsl(var(--muted-foreground)); }
|
||||||
|
.cm-utility-bar--primary { background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground)); }
|
||||||
|
.cm-utility-bar--dark { background-color: hsl(var(--foreground)); color: hsl(var(--background)); }
|
||||||
|
.cm-utility-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
|
||||||
|
.cm-utility-group { display: flex; align-items: center; gap: 1.25rem; }
|
||||||
|
.cm-utility-item { display: inline-flex; align-items: center; gap: 0.375rem; color: inherit; text-decoration: none; }
|
||||||
|
.cm-announce { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 0.875rem; padding: 0.625rem 0; }
|
||||||
|
.cm-announce--info { background-color: hsl(var(--muted)); color: hsl(var(--foreground)); }
|
||||||
|
.cm-announce--promo { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); }
|
||||||
|
.cm-announce--alert { background-color: hsl(var(--destructive)); color: hsl(var(--destructive-foreground)); }
|
||||||
|
.cm-announce-row { display: flex; align-items: center; justify-content: center; gap: 0.75rem; }
|
||||||
|
.cm-announce a { color: inherit; text-decoration: underline; text-underline-offset: 2px; font-weight: 600; }
|
||||||
|
.cm-announce-close { margin-left: auto; background: none; border: 0; color: inherit; cursor: pointer; line-height: 1; padding: 0 0.25rem; }
|
||||||
|
|
||||||
|
/* --- Chrome: footer --- */
|
||||||
|
.cm-footer { background-color: hsl(var(--background)); color: hsl(var(--foreground)); border-top: 2px solid hsl(var(--accent)); padding: 3.5rem 0 2.5rem; }
|
||||||
|
.cm-footer-cols { display: grid; gap: 2rem; grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
||||||
|
@media (min-width: 640px) { .cm-footer-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||||
|
@media (min-width: 1024px) { .cm-footer-cols { grid-template-columns: 2fr repeat(3, 1fr); } }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-footer-link { display: block; font-size: 0.9375rem; color: hsl(var(--foreground)); text-decoration: none; padding: 0.25rem 0; }
|
||||||
|
.cm-footer-link:hover { color: hsl(var(--accent)); }
|
||||||
|
.cm-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 --- */
|
||||||
|
.cm-hero { padding: 5rem 0; }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-hero-sub { font-size: clamp(1.125rem, 2vw, 1.5rem); line-height: 1.5; color: hsl(var(--muted-foreground)); margin: 1.25rem 0 0; }
|
||||||
|
.cm-hero-split { display: grid; gap: 3rem; grid-template-columns: repeat(1, minmax(0, 1fr)); align-items: center; }
|
||||||
|
@media (min-width: 1024px) { .cm-hero-split { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||||
|
|
||||||
|
/* --- Marketing: logo strip --- */
|
||||||
|
.cm-logo { filter: grayscale(1); opacity: 0.65; transition: filter 200ms ease, opacity 200ms ease; }
|
||||||
|
.cm-logo:hover { filter: grayscale(0); opacity: 1; }
|
||||||
|
.cm-logo-row { display: flex; flex-wrap: wrap; align-items: center; gap: 2.5rem; }
|
||||||
|
|
||||||
|
/* --- Marketing: feature grid --- */
|
||||||
|
.cm-feature { border-top: 2px solid hsl(var(--accent)); padding-top: 1.25rem; }
|
||||||
|
.cm-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 --- */
|
||||||
|
.cm-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)); }
|
||||||
|
.cm-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)); }
|
||||||
|
.cm-quote-cite { font-size: 0.875rem; color: hsl(var(--muted-foreground)); margin-top: 0.75rem; }
|
||||||
|
.cm-avatar { width: 2.75rem; height: 2.75rem; border-radius: 9999px; object-fit: cover; background-color: hsl(var(--muted)); }
|
||||||
|
|
||||||
|
/* --- Marketing: pricing --- */
|
||||||
|
.cm-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; }
|
||||||
|
.cm-price-period { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 0.9375rem; color: hsl(var(--muted-foreground)); }
|
||||||
|
.cm-price-tier { display: flex; flex-direction: column; }
|
||||||
|
.cm-price-tier--featured { border-top: 4px solid hsl(var(--accent)); }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-check { color: hsl(var(--accent)); flex: none; }
|
||||||
|
|
||||||
|
/* --- Marketing: faq --- */
|
||||||
|
.cm-faq-item { border-top: 1px solid hsl(var(--border)); }
|
||||||
|
.cm-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)); }
|
||||||
|
.cm-faq-item summary::-webkit-details-marker { display: none; }
|
||||||
|
.cm-faq-item[open] summary .cm-faq-chevron { transform: rotate(180deg); }
|
||||||
|
.cm-faq-chevron { transition: transform 150ms ease; color: hsl(var(--accent)); }
|
||||||
|
.cm-faq-answer { padding: 0 0 1.25rem; color: hsl(var(--muted-foreground)); font-size: 1rem; line-height: 1.65; }
|
||||||
|
|
||||||
|
/* --- Marketing: timeline --- */
|
||||||
|
.cm-timeline-step { display: grid; grid-template-columns: auto 1fr; gap: 1.25rem; padding-bottom: 2rem; }
|
||||||
|
.cm-timeline-rail { position: relative; display: flex; flex-direction: column; align-items: center; }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-timeline-line { flex: 1 1 auto; width: 1px; background-color: hsl(var(--border)); margin-top: 0.5rem; }
|
||||||
|
|
||||||
|
/* --- Marketing: cta strip --- */
|
||||||
|
.cm-cta-strip { padding: 3.5rem 0; }
|
||||||
|
.cm-cta-strip--band { background-color: hsl(var(--foreground)); color: hsl(var(--background)); }
|
||||||
|
.cm-cta-strip--band .cm-btn-primary { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); border-color: hsl(var(--accent)); }
|
||||||
|
.cm-cta-strip--card { background-color: hsl(var(--muted)); }
|
||||||
|
.cm-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 --- */
|
||||||
|
.cm-count-unit { display: flex; flex-direction: column; align-items: center; }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-count-box { border: 1px solid hsl(var(--border)); padding: 1rem 1.25rem; border-radius: 4px; min-width: 4.5rem; }
|
||||||
|
|
||||||
|
/* --- Primitives: quote / divider / gallery / media --- */
|
||||||
|
.cm-divider-line { border: 0; border-top: 1px solid hsl(var(--border)); }
|
||||||
|
.cm-divider-thick { border-top-width: 2px; border-top-color: hsl(var(--accent)); }
|
||||||
|
.cm-divider-ornament { display: flex; align-items: center; justify-content: center; gap: 0.75rem; color: hsl(var(--muted-foreground)); }
|
||||||
|
.cm-gallery-grid { display: grid; gap: 0.75rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
|
@media (min-width: 768px) { .cm-gallery-grid.cm-gallery-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } .cm-gallery-grid.cm-gallery-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
|
||||||
|
.cm-video-frame { position: relative; display: block; width: 100%; background-color: hsl(var(--muted)); border: 1px solid hsl(var(--border)); overflow: hidden; }
|
||||||
|
.cm-video-play { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: hsl(var(--background)); background: none; border: 0; cursor: pointer; }
|
||||||
|
.cm-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 --- */
|
||||||
|
.cm-menu-item { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; padding: 0.75rem 0; border-bottom: 1px solid hsl(var(--border)); }
|
||||||
|
.cm-menu-price { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-variant-numeric: tabular-nums; color: hsl(var(--foreground)); }
|
||||||
|
.cm-channel { display: flex; align-items: flex-start; gap: 0.75rem; }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-hours-row { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; padding: 0.375rem 0; border-bottom: 1px solid hsl(var(--border)); }
|
||||||
|
.cm-social-row { display: flex; flex-wrap: wrap; gap: 0.75rem; }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-social-link:hover { border-color: hsl(var(--accent)); color: hsl(var(--accent)); }
|
||||||
|
|
||||||
|
/* --- Forms --- */
|
||||||
|
.cm-form { display: grid; gap: 1.25rem; }
|
||||||
|
.cm-form-grid { display: grid; gap: 1.25rem; grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
||||||
|
@media (min-width: 640px) { .cm-form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||||
|
.cm-field { display: flex; flex-direction: column; gap: 0.375rem; }
|
||||||
|
.cm-field--full { grid-column: 1 / -1; }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-input:focus { outline: 2px solid hsl(var(--ring)); outline-offset: 1px; border-color: hsl(var(--ring)); }
|
||||||
|
.cm-form-note { font-size: 0.8125rem; color: hsl(var(--muted-foreground)); }
|
||||||
|
.cm-alert-ok { border-left: 3px solid hsl(var(--accent)); background-color: hsl(var(--muted)); padding: 1rem 1.25rem; color: hsl(var(--foreground)); }
|
||||||
|
.cm-alert-err { border-left: 3px solid hsl(var(--destructive)); background-color: hsl(var(--muted)); padding: 1rem 1.25rem; color: hsl(var(--foreground)); }
|
||||||
|
|
||||||
|
/* --- Blog family --- */
|
||||||
|
.cm-post-card { display: flex; flex-direction: column; gap: 0.75rem; }
|
||||||
|
.cm-post-thumb { display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover; background-color: hsl(var(--muted)); border: 1px solid hsl(var(--border)); }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-post-title:hover { color: hsl(var(--accent)); }
|
||||||
|
.cm-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)); }
|
||||||
|
.cm-post-excerpt { color: hsl(var(--muted-foreground)); font-size: 0.9375rem; line-height: 1.6; }
|
||||||
|
.cm-post-grid { display: grid; gap: 2.5rem; grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
||||||
|
@media (min-width: 640px) { .cm-post-grid-2, .cm-post-grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||||
|
@media (min-width: 1024px) { .cm-post-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-tag:hover { border-color: hsl(var(--accent)); color: hsl(var(--accent)); }
|
||||||
|
.cm-crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; font-size: 0.8125rem; color: hsl(var(--muted-foreground)); }
|
||||||
|
.cm-crumbs a { color: hsl(var(--muted-foreground)); text-decoration: none; }
|
||||||
|
.cm-crumbs a:hover { color: hsl(var(--accent)); }
|
||||||
|
.cm-crumb-sep { color: hsl(var(--border)); }
|
||||||
|
.cm-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; }
|
||||||
|
.cm-sidebar-link:hover, .cm-sidebar-link[aria-current="page"] { border-left-color: hsl(var(--accent)); color: hsl(var(--accent)); }
|
||||||
|
.cm-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) --- */
|
||||||
|
.cm-reveal { opacity: 0; transform: translateY(0.5rem); animation: cm-reveal 500ms ease forwards; }
|
||||||
|
@keyframes cm-reveal { to { opacity: 1; transform: none; } }
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.cm-reveal { animation: none; opacity: 1; transform: none; }
|
||||||
|
.cm-btn-ghost, .cm-logo, .cm-nav-link, .cm-faq-chevron { transition: none; }
|
||||||
|
}
|
||||||
|
|||||||
@ -2,23 +2,31 @@
|
|||||||
{
|
{
|
||||||
"key": "corporate-modernist:default-master",
|
"key": "corporate-modernist:default-master",
|
||||||
"title": "Corporate Modernist Default Master",
|
"title": "Corporate Modernist Default Master",
|
||||||
"page_templates": ["default", "article"],
|
"page_templates": ["default", "article", "blog-index", "full-width"],
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{ "block_key": "navbar", "title": "Main Navigation", "content": { "menuName": "main", "style": "underline" }, "slot": "header", "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": "slot", "title": "Main Content Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
|
||||||
{ "block_key": "corporate-modernist:footer", "title": "Site Footer", "content": { "variant": "compact", "showLegal": true }, "slot": "footer", "sort_order": 0 }
|
{ "block_key": "footer", "title": "Site Footer", "content": { "layout": "columns", "showSocial": true, "companyName": "Meridian Advisory", "tagline": "Strategy, operations, and risk counsel for boards.", "copyright": "Meridian Advisory" }, "slot": "footer", "sort_order": 0 }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "corporate-modernist:landing-master",
|
"key": "corporate-modernist:landing-master",
|
||||||
"title": "Corporate Modernist Landing Master",
|
"title": "Corporate Modernist Landing Master",
|
||||||
"page_templates": ["landing", "full-width"],
|
"page_templates": ["landing"],
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{ "block_key": "navbar", "title": "Main Navigation", "content": { "menuName": "main", "style": "underline" }, "slot": "header", "sort_order": 0 },
|
{ "block_key": "navbar", "title": "Main Navigation", "content": { "variant": "standard", "menuName": "main" }, "slot": "header", "sort_order": 0 },
|
||||||
{ "block_key": "corporate-modernist:hero_statement", "title": "Landing Hero", "content": { "eyebrow": "Enterprise", "headline": "Trusted by 200+ boards" }, "slot": "hero", "sort_order": 0 },
|
{ "block_key": "hero", "title": "Landing Hero", "content": { "template": "left-aligned", "eyebrow": "B2B advisory", "headline": "Precise counsel for boards that move first", "subheadline": "Strategy, operations, and risk work delivered on a fixed scope." }, "slot": "hero", "sort_order": 0 },
|
||||||
{ "block_key": "slot", "title": "Main Content Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
|
{ "block_key": "slot", "title": "Main Content Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
|
||||||
{ "block_key": "corporate-modernist:cta_strip", "title": "Closing CTA", "content": { "variant": "book-call" }, "slot": "cta", "sort_order": 0 },
|
{ "block_key": "cta", "title": "Closing CTA", "content": { "background": "band", "heading": "Book a working session", "text": "Ninety minutes with a principal. No pitch deck.", "primaryLabel": "Request a session", "primaryUrl": "/contact" }, "slot": "cta", "sort_order": 0 },
|
||||||
{ "block_key": "corporate-modernist:footer", "title": "Site Footer", "content": { "variant": "full", "showLegal": true }, "slot": "footer", "sort_order": 0 }
|
{ "block_key": "footer", "title": "Site Footer", "content": { "layout": "columns", "showSocial": true, "companyName": "Meridian Advisory", "copyright": "Meridian Advisory" }, "slot": "footer", "sort_order": 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "corporate-modernist:auth-master",
|
||||||
|
"title": "Corporate Modernist Auth Master",
|
||||||
|
"page_templates": ["auth"],
|
||||||
|
"blocks": [
|
||||||
|
{ "block_key": "slot", "title": "Auth Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
name = "corporate-modernist"
|
name = "corporate-modernist"
|
||||||
display_name = "Corporate Modernist"
|
display_name = "Corporate Modernist"
|
||||||
scope = "@themes"
|
scope = "@themes"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
description = "Swiss-grid B2B theme with restrained typography, a single configurable accent, and trust-signal blocks for enterprise sites."
|
description = "Swiss-grid B2B theme with restrained typography, a single configurable accent, and trust-signal blocks for enterprise sites."
|
||||||
kind = "theme"
|
kind = "theme"
|
||||||
categories = ["templates"]
|
categories = ["templates"]
|
||||||
|
|||||||
104
presets.json
104
presets.json
@ -52,7 +52,7 @@
|
|||||||
{
|
{
|
||||||
"id": "forest-quiet",
|
"id": "forest-quiet",
|
||||||
"name": "Forest Quiet",
|
"name": "Forest Quiet",
|
||||||
"description": "Same chassis, deep green accent for sustainability, advisory, ESG.",
|
"description": "Same chassis, deep green accent for sustainability, advisory, and ESG.",
|
||||||
"theme": {
|
"theme": {
|
||||||
"mode": "both",
|
"mode": "both",
|
||||||
"lightColors": {
|
"lightColors": {
|
||||||
@ -102,7 +102,7 @@
|
|||||||
{
|
{
|
||||||
"id": "burgundy-tradition",
|
"id": "burgundy-tradition",
|
||||||
"name": "Burgundy Tradition",
|
"name": "Burgundy Tradition",
|
||||||
"description": "Heritage accent for old-line legal, private banking, partnerships.",
|
"description": "Heritage accent for old-line legal, private banking, and partnerships.",
|
||||||
"theme": {
|
"theme": {
|
||||||
"mode": "both",
|
"mode": "both",
|
||||||
"lightColors": {
|
"lightColors": {
|
||||||
@ -148,5 +148,105 @@
|
|||||||
"ring": "350 60% 60%"
|
"ring": "350 60% 60%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "graphite-mono",
|
||||||
|
"name": "Graphite Mono",
|
||||||
|
"description": "Achromatic ink on gallery white. Pure Swiss restraint with no hue.",
|
||||||
|
"theme": {
|
||||||
|
"mode": "both",
|
||||||
|
"lightColors": {
|
||||||
|
"background": "0 0% 100%",
|
||||||
|
"foreground": "0 0% 9%",
|
||||||
|
"card": "0 0% 100%",
|
||||||
|
"cardForeground": "0 0% 9%",
|
||||||
|
"popover": "0 0% 100%",
|
||||||
|
"popoverForeground": "0 0% 9%",
|
||||||
|
"primary": "0 0% 13%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "0 0% 96%",
|
||||||
|
"secondaryForeground": "0 0% 9%",
|
||||||
|
"muted": "0 0% 96%",
|
||||||
|
"mutedForeground": "0 0% 40%",
|
||||||
|
"accent": "0 0% 13%",
|
||||||
|
"accentForeground": "0 0% 100%",
|
||||||
|
"destructive": "0 72% 42%",
|
||||||
|
"destructiveForeground": "0 0% 100%",
|
||||||
|
"border": "0 0% 88%",
|
||||||
|
"input": "0 0% 88%",
|
||||||
|
"ring": "0 0% 13%"
|
||||||
|
},
|
||||||
|
"darkColors": {
|
||||||
|
"background": "0 0% 8%",
|
||||||
|
"foreground": "0 0% 96%",
|
||||||
|
"card": "0 0% 11%",
|
||||||
|
"cardForeground": "0 0% 96%",
|
||||||
|
"popover": "0 0% 11%",
|
||||||
|
"popoverForeground": "0 0% 96%",
|
||||||
|
"primary": "0 0% 92%",
|
||||||
|
"primaryForeground": "0 0% 8%",
|
||||||
|
"secondary": "0 0% 17%",
|
||||||
|
"secondaryForeground": "0 0% 96%",
|
||||||
|
"muted": "0 0% 15%",
|
||||||
|
"mutedForeground": "0 0% 64%",
|
||||||
|
"accent": "0 0% 92%",
|
||||||
|
"accentForeground": "0 0% 8%",
|
||||||
|
"destructive": "0 62% 50%",
|
||||||
|
"destructiveForeground": "0 0% 100%",
|
||||||
|
"border": "0 0% 22%",
|
||||||
|
"input": "0 0% 22%",
|
||||||
|
"ring": "0 0% 92%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "signal-red",
|
||||||
|
"name": "Signal Red",
|
||||||
|
"description": "Graphite ground with a single Bauhaus primary-red accent for bold consultancies.",
|
||||||
|
"theme": {
|
||||||
|
"mode": "both",
|
||||||
|
"lightColors": {
|
||||||
|
"background": "0 0% 100%",
|
||||||
|
"foreground": "0 0% 11%",
|
||||||
|
"card": "0 0% 100%",
|
||||||
|
"cardForeground": "0 0% 11%",
|
||||||
|
"popover": "0 0% 100%",
|
||||||
|
"popoverForeground": "0 0% 11%",
|
||||||
|
"primary": "4 74% 46%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "0 0% 96%",
|
||||||
|
"secondaryForeground": "0 0% 11%",
|
||||||
|
"muted": "0 0% 96%",
|
||||||
|
"mutedForeground": "0 0% 40%",
|
||||||
|
"accent": "4 74% 46%",
|
||||||
|
"accentForeground": "0 0% 100%",
|
||||||
|
"destructive": "0 72% 42%",
|
||||||
|
"destructiveForeground": "0 0% 100%",
|
||||||
|
"border": "0 0% 88%",
|
||||||
|
"input": "0 0% 88%",
|
||||||
|
"ring": "4 74% 46%"
|
||||||
|
},
|
||||||
|
"darkColors": {
|
||||||
|
"background": "0 0% 8%",
|
||||||
|
"foreground": "0 0% 96%",
|
||||||
|
"card": "0 0% 11%",
|
||||||
|
"cardForeground": "0 0% 96%",
|
||||||
|
"popover": "0 0% 11%",
|
||||||
|
"popoverForeground": "0 0% 96%",
|
||||||
|
"primary": "4 78% 58%",
|
||||||
|
"primaryForeground": "0 0% 8%",
|
||||||
|
"secondary": "0 0% 17%",
|
||||||
|
"secondaryForeground": "0 0% 96%",
|
||||||
|
"muted": "0 0% 15%",
|
||||||
|
"mutedForeground": "0 0% 64%",
|
||||||
|
"accent": "4 78% 58%",
|
||||||
|
"accentForeground": "0 0% 8%",
|
||||||
|
"destructive": "0 62% 50%",
|
||||||
|
"destructiveForeground": "0 0% 100%",
|
||||||
|
"border": "0 0% 22%",
|
||||||
|
"input": "0 0% 22%",
|
||||||
|
"ring": "4 78% 58%"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
122
seed/seed.json
Normal file
122
seed/seed.json
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
{
|
||||||
|
"demo": true,
|
||||||
|
"settings": {
|
||||||
|
"merge": {
|
||||||
|
"site_name": "Meridian Advisory",
|
||||||
|
"tagline": "Strategy, operations, and risk counsel for boards."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data_tables": [
|
||||||
|
{
|
||||||
|
"key": "contact_submissions",
|
||||||
|
"name": "Contact submissions",
|
||||||
|
"description": "Messages sent from the Meridian Advisory 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": "Meridian Advisory",
|
||||||
|
"template_key": "full-width",
|
||||||
|
"reconcile_blocks": true,
|
||||||
|
"blocks": [
|
||||||
|
{ "block_key": "hero", "title": "Hero", "slot": "main", "sort_order": 1, "content": { "template": "left-aligned", "eyebrow": { "text": "B2B advisory" }, "headline": { "text": "Precise counsel for boards that move first" }, "subheadline": { "text": "We do strategy, operations, and risk work on a fixed scope. No retainers you cannot read." }, "ctaPrimary": { "text": "Book a working session", "url": "/contact" }, "ctaSecondary": { "text": "See how we work", "url": "/about" } } },
|
||||||
|
{ "block_key": "corporate-modernist:credential_bar", "title": "Credentials", "slot": "main", "sort_order": 2, "content": { "items": [ { "figure": "18", "label": "Years in practice" }, { "figure": "6", "label": "Sectors served" }, { "figure": "40+", "label": "Board engagements" }, { "figure": "3", "label": "Continents" } ] } },
|
||||||
|
{ "block_key": "corporate-modernist:capability_matrix", "title": "Capabilities", "slot": "main", "sort_order": 3, "content": { "eyebrow": "What we do", "heading": "Three disciplines, one team", "intro": "Every engagement draws on the same three capabilities. We assign a principal to each.", "columns": 3, "capabilities": [ { "mark": "circle", "title": "Strategy", "text": "Market entry, portfolio choices, and the operating model that carries them." }, { "mark": "triangle", "title": "Operations", "text": "Cost structure, process design, and the metrics a board can hold." }, { "mark": "square", "title": "Risk", "text": "Controls, resilience, and the reporting that keeps regulators satisfied." } ] } },
|
||||||
|
{ "block_key": "stats", "title": "Outcomes", "slot": "main", "sort_order": 4, "content": { "heading": "Outcomes we measure", "columns": 3, "countUp": false, "items": [ { "value": "3.2", "suffix": "x", "label": "Median return on engagement" }, { "value": "90", "suffix": " days", "label": "Time to first decision" }, { "value": "12", "label": "Week average engagement" } ] } },
|
||||||
|
{ "block_key": "corporate-modernist:engagement_process", "title": "Process", "slot": "main", "sort_order": 5, "content": { "eyebrow": "How we work", "heading": "A fixed path from brief to handover", "steps": [ { "title": "Frame", "text": "We agree the question and the scope in one working session." }, { "title": "Diagnose", "text": "We gather evidence and test it against the board's own numbers." }, { "title": "Decide", "text": "We present options with a clear recommendation and the trade-offs." }, { "title": "Hand over", "text": "We leave the model, the plan, and the owners in place." } ] } },
|
||||||
|
{ "block_key": "cta", "title": "Closing CTA", "slot": "main", "sort_order": 6, "content": { "background": "band", "heading": "Book a working session", "text": "Ninety minutes with a principal. No pitch deck.", "primaryLabel": "Request a session", "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": "A small firm by design", "level": 1 } },
|
||||||
|
{ "block_key": "text", "title": "Intro", "slot": "main", "sort_order": 2, "content": { "text": "<p>Meridian Advisory keeps a short bench. Every engagement is led by a principal, not passed to a junior team. We take fewer clients so we can go deeper on each one.</p>" } },
|
||||||
|
{ "block_key": "corporate-modernist:capability_matrix", "title": "Values", "slot": "main", "sort_order": 3, "content": { "eyebrow": "How we operate", "heading": "What holds across every brief", "columns": 3, "capabilities": [ { "mark": "square", "title": "Fixed scope", "text": "You know the price and the deadline before we start." }, { "mark": "circle", "title": "Evidence first", "text": "We argue from your data, not from a template deck." }, { "mark": "triangle", "title": "Clean handover", "text": "We finish by making ourselves unnecessary." } ] } },
|
||||||
|
{ "block_key": "team", "title": "Team", "slot": "main", "sort_order": 4, "content": { "heading": "The principals", "layout": "grid", "columns": 3, "members": [ { "name": "A. Reyes", "role": "Strategy", "bio": "Two decades in corporate strategy and market entry." }, { "name": "K. Osei", "role": "Operations", "bio": "Process and cost work across manufacturing and services." }, { "name": "M. Lindqvist", "role": "Risk", "bio": "Controls and resilience for regulated industries." } ] } },
|
||||||
|
{ "block_key": "corporate-modernist:case_study_card", "title": "Case study", "slot": "main", "sort_order": 5, "content": { "client": "Regional bank", "metric": "31%", "label": "Reduction in cost to serve", "summary": "A twelve-week operations review that reset the branch operating model.", "href": { "label": "Read the case", "href": "/blog/operations-reset" } } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "/blog",
|
||||||
|
"title": "Insights",
|
||||||
|
"template_key": "blog-index",
|
||||||
|
"reconcile_blocks": true,
|
||||||
|
"blocks": [
|
||||||
|
{ "block_key": "card", "title": "Post 1", "slot": "main", "sort_order": 1, "content": { "title": "Resetting a branch operating model", "text": "<p>What changed when a regional bank moved from headcount targets to cost-to-serve.</p>", "link": "/blog/operations-reset", "linkLabel": "Read" } },
|
||||||
|
{ "block_key": "card", "title": "Post 2", "slot": "main", "sort_order": 2, "content": { "title": "Reading a risk register the board will use", "text": "<p>Most registers are written for auditors. Here is how to write one for directors.</p>", "link": "/blog/risk-register", "linkLabel": "Read" } },
|
||||||
|
{ "block_key": "card", "title": "Post 3", "slot": "main", "sort_order": 3, "content": { "title": "The ninety-day decision", "text": "<p>Why the first real decision should land inside three months, not six.</p>", "link": "/blog/ninety-day-decision", "linkLabel": "Read" } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "/blog/operations-reset",
|
||||||
|
"parent_slug": "/blog",
|
||||||
|
"title": "Resetting a branch operating model",
|
||||||
|
"template_key": "article",
|
||||||
|
"reconcile_blocks": true,
|
||||||
|
"blocks": [
|
||||||
|
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Resetting a branch operating model", "level": 1 } },
|
||||||
|
{ "block_key": "text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "text": "<p>A regional bank asked us to cut cost to serve without closing branches. We started with the numbers the board already trusted. Then we tested each assumption against a week of real transactions.</p><p>The answer was not fewer branches. It was fewer tasks per branch. We moved back-office work to a shared team and gave each branch a single metric it could hold.</p>" } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "/blog/risk-register",
|
||||||
|
"parent_slug": "/blog",
|
||||||
|
"title": "Reading a risk register the board will use",
|
||||||
|
"template_key": "article",
|
||||||
|
"reconcile_blocks": true,
|
||||||
|
"blocks": [
|
||||||
|
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Reading a risk register the board will use", "level": 1 } },
|
||||||
|
{ "block_key": "text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "text": "<p>A risk register written for auditors lists everything. A register written for directors ranks a few things and says who owns them. The second kind gets read.</p><p>We keep the top ten risks on one page. Each has an owner, a trend, and a next action. Everything else lives in the appendix.</p>" } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "/blog/ninety-day-decision",
|
||||||
|
"parent_slug": "/blog",
|
||||||
|
"title": "The ninety-day decision",
|
||||||
|
"template_key": "article",
|
||||||
|
"reconcile_blocks": true,
|
||||||
|
"blocks": [
|
||||||
|
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "The ninety-day decision", "level": 1 } },
|
||||||
|
{ "block_key": "text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "text": "<p>Long engagements drift because no decision anchors them. We set a real decision inside the first ninety days. It forces the evidence to be good enough to act on.</p><p>The decision does not have to be final. It has to be real. A board that decides once will decide again faster.</p>" } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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 working session", "description": "Tell us the question. We reply within one business day.", "submitLabel": "Send message", "successMessage": "Thank you. 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 can we help with?", "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": "hello@meridian.example", "link": "mailto:hello@meridian.example" }, { "icon": "lucide:phone", "label": "Phone", "value": "+61 2 5550 0100", "link": "tel:+61255500100" } ] } },
|
||||||
|
{ "block_key": "hours-location", "title": "Office hours", "slot": "aside", "sort_order": 2, "content": { "heading": "Office", "address": "Level 12, 100 Harbour Street, Sydney NSW 2000", "hours": [ { "day": "Monday to Friday", "hours": "9:00 to 17:30" }, { "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": "Insights", "page_slug": "/blog", "sort_order": 3 },
|
||||||
|
{ "menu": "main", "label": "Contact", "page_slug": "/contact", "sort_order": 4 }
|
||||||
|
]
|
||||||
|
}
|
||||||
19
seed/workflows.json
Normal file
19
seed/workflows.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"workflows": [
|
||||||
|
{
|
||||||
|
"key": "contact_notify_admins",
|
||||||
|
"name": "Notify admins on contact submission",
|
||||||
|
"description": "Emails site admins whenever the Meridian Advisory 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" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
17
templates/corporate-modernist/auth.ninjatpl
Normal file
17
templates/corporate-modernist/auth.ninjatpl
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
{{ head_html|safe }}
|
||||||
|
<body class="cm-body" style="margin: 0; min-height: 100vh; display: flex; flex-direction: column; background-color: hsl(var(--muted)); color: hsl(var(--foreground));">
|
||||||
|
{{ admin_banner_html|safe }}
|
||||||
|
<main style="flex: 1 1 auto; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 3rem 1.5rem;">
|
||||||
|
<div style="width: 100%; max-width: 26rem;">
|
||||||
|
<a href="/" aria-label="{{ site_settings.Title|default:"Home" }}" style="display: inline-flex; align-items: center; gap: 0.625rem; text-decoration: none; margin-bottom: 1.5rem;"><span class="cm-mark cm-mark-square" aria-hidden="true"></span><span class="cm-brand">{{ site_settings.Title|default:"Corporate Modernist" }}</span></a>
|
||||||
|
<div class="cm-card cm-card-accent cm-reveal" style="padding: 2rem;">
|
||||||
|
{% if slots.main %}{{ slots.main|safe }}{% else %}<p class="cm-stat-label">No form assigned to this page.</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if slots.footer %}<div class="cm-stat-label" style="text-align: center; margin-top: 1.5rem; text-transform: none; letter-spacing: 0;">{{ slots.footer|safe }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{{ body_end_html|safe }}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
17
templates/corporate-modernist/blog-index.ninjatpl
Normal file
17
templates/corporate-modernist/blog-index.ninjatpl
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
{{ head_html|safe }}
|
||||||
|
<body class="cm-body" style="margin: 0; min-height: 100vh; display: flex; flex-direction: column; background-color: hsl(var(--background)); color: hsl(var(--foreground));">
|
||||||
|
{{ admin_banner_html|safe }}
|
||||||
|
{% if slots.header %}<header class="cm-hairline-bottom" style="width: 100%;"><div class="cm-content-well">{{ slots.header|safe }}</div></header>{% endif %}
|
||||||
|
<main style="flex: 1 1 auto; width: 100%;" data-content-well>
|
||||||
|
<div class="cm-content-well cm-section">
|
||||||
|
{% if title %}<div class="cm-accent-rule" style="padding-top: 1.5rem; margin-bottom: 3rem;"><p class="cm-eyebrow">Insights</p><h1 class="cm-heading" style="font-size: clamp(2rem, 4vw, 3rem); line-height: 1.1; margin: 0;">{{ title }}</h1>{% if page_meta.MetaDescription %}<p class="cm-lede" style="max-width: 60ch; margin: 1rem 0 0;">{{ page_meta.MetaDescription }}</p>{% endif %}</div>{% endif %}
|
||||||
|
{% if slots.featured %}<section style="margin-bottom: 3rem;">{{ slots.featured|safe }}</section>{% endif %}
|
||||||
|
{% if slots.main %}<section>{{ slots.main|safe }}</section>{% else %}<p class="cm-stat-label">No posts published yet.</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% if slots.footer %}<footer style="width: 100%; margin-top: auto;">{{ slots.footer|safe }}</footer>{% endif %}
|
||||||
|
{{ body_end_html|safe }}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
23
templates/corporate-modernist/contact.ninjatpl
Normal file
23
templates/corporate-modernist/contact.ninjatpl
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
{{ head_html|safe }}
|
||||||
|
<body class="cm-body" style="margin: 0; min-height: 100vh; display: flex; flex-direction: column; background-color: hsl(var(--background)); color: hsl(var(--foreground));">
|
||||||
|
{{ admin_banner_html|safe }}
|
||||||
|
{% if slots.header %}<header class="cm-hairline-bottom" style="width: 100%;"><div class="cm-content-well">{{ slots.header|safe }}</div></header>{% endif %}
|
||||||
|
<main style="flex: 1 1 auto; width: 100%;" data-content-well>
|
||||||
|
<div class="cm-content-well cm-section">
|
||||||
|
{% if slots.aside %}
|
||||||
|
<div class="cm-swiss-12">
|
||||||
|
<div style="grid-column: span 12 / span 12;" class="cm-contact-main">{{ slots.main|safe }}</div>
|
||||||
|
<aside style="grid-column: span 12 / span 12;" class="cm-hairline-top cm-contact-aside">{{ slots.aside|safe }}</aside>
|
||||||
|
</div>
|
||||||
|
<style>@media (min-width: 1024px){.cm-contact-main{grid-column: span 7 / span 7 !important;} .cm-contact-aside{grid-column: 9 / span 4 !important; border-top: 0 !important; padding-left: 2rem; border-left: 1px solid hsl(var(--border));}}</style>
|
||||||
|
{% else %}
|
||||||
|
<div class="cm-swiss-12"><div style="grid-column: span 12 / span 12;" class="cm-measure">{% if slots.main %}{{ slots.main|safe }}{% else %}<p class="cm-stat-label">No content blocks assigned to this page.</p>{% endif %}</div></div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% if slots.footer %}<footer style="width: 100%; margin-top: auto;">{{ slots.footer|safe }}</footer>{% endif %}
|
||||||
|
{{ body_end_html|safe }}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
{# announcement-bar override — centered notice; carries data-bn-announcement / data-bn-annkey and a data-bn-ann-dismiss close button so the host dismissal JS binds. #}
|
||||||
|
{% if variant == "promo" %}{% set annvar = "cm-announce--promo" %}{% set annsty = "background:hsl(var(--primary));color:hsl(var(--primary-foreground));" %}{% elif variant == "alert" %}{% set annvar = "cm-announce--alert" %}{% set annsty = "background:hsl(var(--destructive));color:hsl(var(--destructive-foreground));" %}{% else %}{% set annvar = "cm-announce--info" %}{% set annsty = "background:hsl(var(--accent));color:hsl(var(--accent-foreground));" %}{% endif %}
|
||||||
|
<div data-bn-announcement data-bn-annkey="{{ variant|default:'info' }}:{{ message|slugify }}" class="cm-announce {{ annvar }} {{ class }}" style="{{ annsty }}">
|
||||||
|
<div class="cm-content-well cm-announce-row" style="position:relative;display:flex;align-items:center;justify-content:center;gap:0.75rem;padding-top:0.5rem;padding-bottom:0.5rem;font-size:0.875rem;text-align:center;">
|
||||||
|
<p style="margin:0;font-weight:500;">{{ message }}{% if linkText and linkUrl %} <a href="{{ linkUrl }}" style="color:inherit;text-decoration:underline;text-underline-offset:2px;">{{ linkText }}</a>{% endif %}</p>
|
||||||
|
{% if dismissable %}<button type="button" data-bn-ann-dismiss aria-label="Dismiss announcement" class="cm-announce-close" style="position:absolute;right:0.75rem;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;padding:0.25rem;background:none;border:0;color:inherit;cursor:pointer;">::lucide:x:sm::</button>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
14
templates/overrides/corporate-modernist/audio-embed.ninjatpl
Normal file
14
templates/overrides/corporate-modernist/audio-embed.ninjatpl
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{# audio-embed override — hairline card row; carries data-bn-audio / data-audio-url / data-audio-media + data-bn-audio-slot so the host facade JS binds. #}
|
||||||
|
<figure class="cm-card {{ class }}" data-bn-audio data-audio-url="{{ url|default:'' }}" data-audio-media="{% if media %}{{ media|mediaURL }}{% endif %}" style="margin:2rem 0;padding:0;overflow:hidden;">
|
||||||
|
<div style="display:flex;align-items:center;gap:1rem;padding:1rem;">
|
||||||
|
{% if artwork %}<div class="cm-media-frame" style="width:4rem;height:4rem;flex:0 0 auto;overflow:hidden;border:1px solid hsl(var(--border));border-radius:4px;">{% img artwork alt=title|default:"" class="cm-audio-art" %}</div>{% endif %}
|
||||||
|
<div style="min-width:0;flex:1;">
|
||||||
|
{% if title %}<p class="cm-display" style="margin:0;font-size:0.9375rem;color:hsl(var(--foreground));white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">{{ title }}</p>{% endif %}
|
||||||
|
{% if artist %}<p style="margin:0;font-size:0.8125rem;color:hsl(var(--muted-foreground));">{{ artist }}</p>{% endif %}
|
||||||
|
<div data-bn-audio-slot style="display:flex;align-items:center;gap:0.75rem;margin-top:0.5rem;">
|
||||||
|
<button type="button" data-bn-audio-play class="cm-btn-primary cm-btn-sm" aria-label="Play{% if title %}: {{ title }}{% endif %}" style="display:inline-flex;align-items:center;gap:0.375rem;">::lucide:play:sm:: Play</button>
|
||||||
|
{% if download %}<a href="{% if media %}{{ media|mediaURL }}{% else %}{{ url }}{% endif %}" download class="cm-btn-ghost" style="display:inline-flex;align-items:center;gap:0.25rem;font-size:0.8125rem;color:hsl(var(--muted-foreground));text-decoration:none;">::lucide:download:sm:: Download</a>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</figure><style>.cm-audio-art{display:block;width:100%;height:100%;object-fit:cover;}</style>
|
||||||
22
templates/overrides/corporate-modernist/auth-form.ninjatpl
Normal file
22
templates/overrides/corporate-modernist/auth-form.ninjatpl
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<div class="cm-stack {{ class }}" data-block="corporate-modernist:auth-form">
|
||||||
|
<input type="radio" name="cm-auth-tab" id="cm-auth-login" class="cm-auth-radio" style="display:none;"{% if default_tab != "register" %} checked{% endif %}>
|
||||||
|
<input type="radio" name="cm-auth-tab" id="cm-auth-register" class="cm-auth-radio" style="display:none;"{% if default_tab == "register" %} checked{% endif %}>
|
||||||
|
<div class="cm-auth-tabs" style="display:flex; gap:1.5rem; border-bottom:1px solid hsl(var(--border));">
|
||||||
|
<label for="cm-auth-login" class="cm-auth-tab cm-label" style="cursor:pointer; padding:0.75rem 0;">Sign in</label>
|
||||||
|
<label for="cm-auth-register" class="cm-auth-tab cm-label" style="cursor:pointer; padding:0.75rem 0;">Create account</label>
|
||||||
|
</div>
|
||||||
|
<div id="auth-message" role="status" aria-live="polite"></div>
|
||||||
|
<form class="cm-auth-panel cm-auth-panel-login cm-form" hx-post="/api/auth/login" hx-target="#auth-message" hx-swap="innerHTML">
|
||||||
|
<div class="cm-field cm-field--full"><label class="cm-label" for="cm-login-email">Email</label><input class="cm-input" type="email" name="email" id="cm-login-email" required></div>
|
||||||
|
<div class="cm-field cm-field--full"><label class="cm-label" for="cm-login-password">Password</label><input class="cm-input" type="password" name="password" id="cm-login-password" required></div>
|
||||||
|
<button type="submit" class="cm-btn-primary" hx-disabled-elt="this" style="width:100%; justify-content:center;">Sign in</button>
|
||||||
|
<p class="cm-form-note" style="text-align:center;"><a href="/forgot-password" style="color: hsl(var(--accent)); text-decoration: underline; text-underline-offset: 2px;">Forgot password?</a></p>
|
||||||
|
</form>
|
||||||
|
<form class="cm-auth-panel cm-auth-panel-register cm-form" hx-post="/api/auth/register" hx-target="#auth-message" hx-swap="innerHTML">
|
||||||
|
<div class="cm-field cm-field--full"><label class="cm-label" for="cm-reg-username">Username</label><input class="cm-input" type="text" name="username" id="cm-reg-username" required pattern="[a-zA-Z0-9_-]{3,30}"></div>
|
||||||
|
<div class="cm-field cm-field--full"><label class="cm-label" for="cm-reg-email">Email</label><input class="cm-input" type="email" name="email" id="cm-reg-email" required></div>
|
||||||
|
<div class="cm-field cm-field--full"><label class="cm-label" for="cm-reg-password">Password</label><input class="cm-input" type="password" name="password" id="cm-reg-password" required minlength="8"></div>
|
||||||
|
<button type="submit" class="cm-btn-primary" hx-disabled-elt="this" style="width:100%; justify-content:center;">Create account</button>
|
||||||
|
</form>
|
||||||
|
<style>.cm-auth-panel{display:none;} #cm-auth-login:checked ~ .cm-auth-panel-login{display:grid;} #cm-auth-register:checked ~ .cm-auth-panel-register{display:grid;}</style>
|
||||||
|
</div>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{% if context.isPublicLoggedIn %}<div class="cm-btn-row {{ class }}" data-block="corporate-modernist:auth-status" style="align-items: center;"><span class="cm-mono" style="color: hsl(var(--muted-foreground));">{{ context.publicDisplayName|default:context.publicUsername }}</span><a class="cm-btn-outline cm-btn-sm" href="/account">Account</a><a class="cm-btn-ghost cm-btn-sm" href="/api/auth/logout">Sign out</a></div>{% else %}<div class="cm-btn-row {{ class }}" data-block="corporate-modernist:auth-status" style="align-items: center;"><a class="cm-btn-outline cm-btn-sm" href="/login">Sign in</a><a class="cm-btn-primary cm-btn-sm" href="/login?tab=register">Create account</a></div>{% endif %}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{% if context.currentAuthor %}<section class="cm-section {{ class }}" data-block="corporate-modernist:author-bio-hero"><div class="cm-content-well"><div class="cm-measure cm-stack" style="text-align: center; margin-left: auto; margin-right: auto;">{% if showAvatar and context.currentAuthor.avatarUrl %}<img class="cm-avatar" src="{{ context.currentAuthor.avatarUrl }}" alt="{{ context.currentAuthor.displayName }}" style="width: 6rem; height: 6rem; border-radius: 9999px; object-fit: cover; margin-left: auto; margin-right: auto;">{% else %}<span class="cm-mark cm-mark-ring" aria-hidden="true" style="margin-left: auto; margin-right: auto;"></span>{% endif %}<h1 class="cm-display" style="margin: 0;">{{ context.currentAuthor.displayName }}</h1>{% if context.currentAuthor.bio %}<p class="cm-lede" style="margin: 0;">{{ context.currentAuthor.bio }}</p>{% endif %}{% if showSocial %}<div class="cm-btn-row" style="justify-content: center;">{% if context.currentAuthor.websiteUrl %}<a class="cm-btn-ghost cm-btn-sm" href="{{ context.currentAuthor.websiteUrl }}" target="_blank" rel="noopener noreferrer" title="Website"><svg class="cm-icon" aria-hidden="true"><use href="/icons/lucide.svg#globe"/></svg></a>{% endif %}{% if context.currentAuthor.twitterHandle %}<a class="cm-btn-ghost cm-btn-sm" href="https://twitter.com/{{ context.currentAuthor.twitterHandle|cut:"@" }}" target="_blank" rel="noopener noreferrer" title="X"><svg class="cm-icon" aria-hidden="true"><use href="/icons/simple-icons.svg#x"/></svg></a>{% endif %}{% if context.currentAuthor.linkedinUrl %}<a class="cm-btn-ghost cm-btn-sm" href="{{ context.currentAuthor.linkedinUrl }}" target="_blank" rel="noopener noreferrer" title="LinkedIn"><svg class="cm-icon" aria-hidden="true"><use href="/icons/simple-icons.svg#linkedin"/></svg></a>{% endif %}</div>{% endif %}</div></div></section>{% endif %}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{% if context.currentPost.authorName %}<aside class="cm-card cm-card-accent {{ class }}" data-block="corporate-modernist:author-bio">{% if heading %}<p class="cm-kicker" style="margin-top: 0;">{{ heading }}</p>{% endif %}<div style="display: flex; align-items: flex-start; gap: 1rem;">{% if showAvatar and context.currentPost.authorAvatarUrl %}<img class="cm-avatar" src="{{ context.currentPost.authorAvatarUrl }}" alt="{{ context.currentPost.authorName }}" style="width: 3.5rem; height: 3.5rem; border-radius: 9999px; object-fit: cover; flex-shrink: 0;">{% endif %}<div style="min-width: 0;"><h3 class="cm-heading" style="font-size: 1.125rem; margin: 0;">{% if context.currentPost.authorSlug %}<a href="/author/{{ context.currentPost.authorSlug }}">{{ context.currentPost.authorName }}</a>{% else %}{{ context.currentPost.authorName }}{% endif %}</h3>{% if context.currentAuthor.bio %}<p class="cm-text" style="margin: 0.5rem 0 0 0; font-size: 0.9375rem; color: hsl(var(--muted-foreground));">{{ context.currentAuthor.bio }}</p>{% endif %}</div></div></aside>{% endif %}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<section class="cm-section {{ class }}" data-block="corporate-modernist:blog-hero"><div class="cm-content-well"><div class="cm-stack">{% if title %}<h1 class="cm-heading" style="font-size: clamp(2rem, 4vw, 3rem); margin: 0;"><span class="cm-heading-h2-accent">{{ title }}</span></h1>{% endif %}{% if subtitle %}<p class="cm-lede" style="max-width: 60ch; margin: 0.75rem 0 0 0;">{{ subtitle }}</p>{% endif %}</div>{% if posts %}<div class="cm-post-grid cm-post-grid-3" style="margin-top: 2.5rem;">{% for post in posts %}<article class="cm-post-card{% if forloop.First %} cm-card-accent{% endif %}">{% if post.featured_image_url %}<a href="{{ post.url }}" class="cm-post-thumb cm-media-frame" style="display: block; aspect-ratio: 16 / 9;"><img src="{{ post.featured_image_url }}" alt="{{ post.title }}" style="width: 100%; height: 100%; object-fit: cover;"></a>{% endif %}<h2 class="cm-post-title"{% if forloop.First %} style="font-size: 1.5rem;"{% endif %}><a href="{{ post.url }}">{{ post.title }}</a></h2>{% if showExcerpt and post.excerpt %}<p class="cm-post-excerpt">{{ post.excerpt }}</p>{% endif %}<div class="cm-post-meta">{% if post.author_name %}<span>{{ post.author_name }}</span>{% endif %}{% if post.published_at %}<span class="cm-mono numeric-tabular">{{ post.published_at|date:"M j, Y" }}</span>{% endif %}</div></article>{% endfor %}</div>{% endif %}</div></section>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<nav aria-label="Breadcrumb" class="cm-crumbs {{ class }}" data-block="corporate-modernist:breadcrumbs"><ol style="display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem; list-style: none; margin: 0; padding: 0;">{% if showHome %}<li style="display: flex; align-items: center;"><a class="cm-sidebar-link" href="/" style="padding: 0;">{{ homeLabel|default:"Home" }}</a></li>{% endif %}{% for item in breadcrumbs %}<li style="display: flex; align-items: center;">{% if showHome or not forloop.First %}<span class="cm-crumb-sep" aria-hidden="true">{{ separator|default:"/" }}</span>{% endif %}{% if item.is_current %}{% if showCurrent %}<span aria-current="page" style="color: hsl(var(--foreground)); font-weight: 500;">{{ item.label }}</span>{% endif %}{% else %}<a class="cm-sidebar-link" href="{{ item.url }}" style="padding: 0;">{{ item.label }}</a>{% endif %}</li>{% endfor %}</ol></nav>
|
||||||
@ -1 +1,6 @@
|
|||||||
{% if variant|default:"primary" == "outline" or variant|default:"primary" == "secondary" %}<a class="cm-btn-outline" href="{{ href|default:url|default:"#" }}">{{ label|default:text|default:"Learn more" }}</a>{% else %}<a class="cm-btn-primary" href="{{ href|default:url|default:"#" }}">{{ label|default:text|default:"Learn more" }}</a>{% endif %}
|
{% set btnStyle = style|default:"primary" %}
|
||||||
|
{% set btnClass = "cm-btn-primary" %}
|
||||||
|
{% if btnStyle == "outline" or btnStyle == "secondary" %}{% set btnClass = "cm-btn-outline" %}{% elif btnStyle == "ghost" or btnStyle == "link" %}{% set btnClass = "cm-btn-ghost" %}{% endif %}
|
||||||
|
{% set sizeClass = "" %}
|
||||||
|
{% if size == "sm" %}{% set sizeClass = "cm-btn-sm" %}{% elif size == "lg" %}{% set sizeClass = "cm-btn-lg" %}{% endif %}
|
||||||
|
<div class="cm-btn-row {{ class }}" style="{% if align == "center" %}justify-content: center;{% elif align == "right" %}justify-content: flex-end;{% endif %}">{% if newTab %}<a class="{{ btnClass }} {{ sizeClass }}" href="{{ link|default:"#" }}" target="_blank" rel="noopener noreferrer">{% if icon and iconPosition != "right" %}::{{ icon }}:sm::{% endif %}<span>{{ label|default:"Learn more" }}</span>{% if icon and iconPosition == "right" %}::{{ icon }}:sm::{% endif %}</a>{% else %}<a class="{{ btnClass }} {{ sizeClass }}" href="{{ link|default:"#" }}">{% if icon and iconPosition != "right" %}::{{ icon }}:sm::{% endif %}<span>{{ label|default:"Learn more" }}</span>{% if icon and iconPosition == "right" %}::{{ icon }}:sm::{% endif %}</a>{% endif %}</div>
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
<div class="cm-card{% if accent == true or accent == "true" %} cm-card-accent{% endif %}">{% if title %}<div class="cm-display" style="font-size: 1.25rem; font-weight: 600; margin: 0 0 0.75rem 0;">{{ title }}</div>{% endif %}{% if body|default:text %}<div class="cm-text">{{ body|default:text|safe }}</div>{% endif %}</div>
|
<article class="cm-card cm-card-accent {{ class }}">{% if media %}<div class="cm-media-frame" style="aspect-ratio: 16 / 9; margin: -1.5rem -1.5rem 1.25rem -1.5rem; border: 0; border-bottom: 1px solid hsl(var(--border));">{% img media alt=title|default:"" class="cm-card-media" %}</div>{% endif %}{% if title %}<h3 class="cm-feature-title" style="margin-top: 0; font-size: 1.25rem;">{{ title }}</h3>{% endif %}{% if text %}<div class="cm-text" style="font-size: 0.9375rem; line-height: 1.6; color: hsl(var(--muted-foreground));">{{ text|safe }}</div>{% endif %}{% if linkLabel and link %}<a class="cm-btn-ghost" href="{{ link }}" style="margin-top: 1rem;">{{ linkLabel }}::lucide:arrow-right:sm::</a>{% endif %}</article>
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
<section class="cm-section-tight {{ class }}" data-block="corporate-modernist:category-list">{% if heading %}<h2 class="cm-kicker">{{ heading }}</h2>{% endif %}{% if style == "pills" %}<div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">{% for cat in categories %}<a href="{{ cat.url }}" class="cm-tag">{{ cat.name }}{% if showCount %} <span class="cm-mono numeric-tabular">{{ cat.post_count }}</span>{% endif %}</a>{% endfor %}</div>{% else %}<ul class="cm-stack-sm" style="list-style: none; margin: 0; padding: 0;">{% for cat in categories %}<li><a href="{{ cat.url }}" class="cm-sidebar-link" style="display: flex; align-items: center; justify-content: space-between;"><span>{{ cat.name }}</span>{% if showCount %}<span class="cm-mono numeric-tabular">{{ cat.post_count }}</span>{% endif %}</a></li>{% endfor %}</ul>{% endif %}</section>
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
{# contact-card override — grouped contact channels; each channel a hairline mark + label + value. #}
|
||||||
|
<section class="cm-section {{ class }}"><div class="cm-content-well">
|
||||||
|
{% if heading %}<h2 class="cm-heading" style="font-size:clamp(1.75rem, 3vw, 2.25rem);margin:0 0 0.75rem 0;"><span class="cm-heading-h2-accent">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if description %}<p class="cm-lede" style="max-width:60ch;margin:0 0 2.5rem 0;color:hsl(var(--muted-foreground));">{{ description }}</p>{% endif %}
|
||||||
|
<div class="cm-grid {% if columns == 3 %}cm-grid-3{% elif columns == 1 %}{% else %}cm-grid-2{% endif %}" style="display:grid;gap:1rem;{% if columns == 3 %}grid-template-columns:repeat(auto-fill, minmax(240px, 1fr));{% elif columns == 1 %}grid-template-columns:1fr;{% else %}grid-template-columns:repeat(auto-fill, minmax(280px, 1fr));{% endif %}">
|
||||||
|
{% for channel in channels %}<div class="cm-channel cm-card" style="display:flex;align-items:flex-start;gap:1rem;">
|
||||||
|
{% if channel.icon %}<span class="cm-channel-mark" style="display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;flex:0 0 auto;border:1px solid hsl(var(--border));border-radius:4px;color:hsl(var(--accent));">::{{ channel.icon }}:sm::</span>{% endif %}
|
||||||
|
<div style="min-width:0;">
|
||||||
|
{% if channel.label %}<div class="cm-stat-label" style="margin:0;">{{ channel.label }}</div>{% endif %}
|
||||||
|
{% if channel.value %}<div style="margin-top:0.25rem;font-weight:500;word-break:break-word;color:hsl(var(--foreground));">{% if channel.link %}<a href="{{ channel.link }}" style="color:inherit;text-decoration:none;">{{ channel.value }}</a>{% else %}{{ channel.value }}{% endif %}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div></section>
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
{# contact-form override — server-driven; block-id resolution, {% form %} wiring, honeypot, and field loop copied verbatim from the builtin, reskinned with cm-form primitives. #}
|
||||||
|
{% with bid=_block_id|default:context.blockId %}
|
||||||
|
<section id="contact-form-{{ bid }}" class="cm-section {{ class }}"><div class="cm-content-well" style="max-width:640px;">
|
||||||
|
{% if form.submitted %}
|
||||||
|
<div class="cm-card cm-card-accent cm-alert-ok" style="text-align:center;padding:2rem;">
|
||||||
|
<span class="cm-mark cm-mark-circle" aria-hidden="true" style="display:inline-block;margin-bottom:1rem;"></span>
|
||||||
|
<p class="cm-display" style="margin:0;font-size:1.125rem;">{{ form.message|default:successMessage|default:"Thanks — your message has been sent." }}</p>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% if heading %}<h2 class="cm-heading" style="font-size:clamp(1.75rem, 3vw, 2.25rem);margin:0 0 0.75rem 0;"><span class="cm-heading-h2-accent">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if description %}<p class="cm-lede" style="margin:0 0 2rem 0;color:hsl(var(--muted-foreground));">{{ description }}</p>{% endif %}
|
||||||
|
{% if form.errors._form %}<div class="cm-alert-err" style="margin-bottom:1.5rem;border:1px solid hsl(var(--destructive));border-radius:4px;padding:0.75rem 1rem;font-size:0.875rem;color:hsl(var(--destructive));background:hsl(var(--destructive) / 0.08);">{{ form.errors._form }}</div>{% endif %}
|
||||||
|
{% with formAction="/api/blocks/"|add:bid|add:"/submit" formTarget="#contact-form-"|add:bid %}
|
||||||
|
{% form action=formAction hx_target=formTarget hx_swap="outerHTML" class="cm-form-grid" %}
|
||||||
|
{% for field in fields %}
|
||||||
|
<div class="cm-field {% if field.width == "half" %}{% else %}cm-field--full{% endif %}">
|
||||||
|
{% if field.label %}<label class="cm-label" for="cf-{{ bid }}-{{ field.name }}">{{ field.label }}{% if field.required %} <span style="color:hsl(var(--destructive));">*</span>{% endif %}</label>{% endif %}
|
||||||
|
{% if field.type == "textarea" %}
|
||||||
|
<textarea id="cf-{{ bid }}-{{ field.name }}" name="{{ field.name }}" rows="5"{% if field.required %} required{% endif %}{% if field.placeholder %} placeholder="{{ field.placeholder }}"{% endif %} class="cm-input"></textarea>
|
||||||
|
{% elif field.type == "select" %}
|
||||||
|
<select id="cf-{{ bid }}-{{ field.name }}" name="{{ field.name }}"{% if field.required %} required{% endif %} class="cm-input">{% if field.placeholder %}<option value="">{{ field.placeholder }}</option>{% endif %}{% for opt in field.options %}<option value="{{ opt.value }}">{{ opt.label|default:opt.value }}</option>{% endfor %}</select>
|
||||||
|
{% else %}
|
||||||
|
<input id="cf-{{ bid }}-{{ field.name }}" name="{{ field.name }}" type="{{ field.type|default:"text" }}"{% if field.required %} required{% endif %}{% if field.placeholder %} placeholder="{{ field.placeholder }}"{% endif %} class="cm-input">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<div class="cm-field--full" aria-hidden="true" style="display:none;"><label>Leave this field empty<input type="text" name="website" tabindex="-1" autocomplete="off"></label></div>
|
||||||
|
<div class="cm-field--full"><button type="submit" class="cm-btn-primary">{{ submitLabel|default:"Send message" }}</button></div>
|
||||||
|
{% endform %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
</div></section>
|
||||||
|
<style>#contact-form-{{ bid }} .cm-form-grid{display:grid;grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.25rem;margin-top:2rem;}@media (min-width:640px){#contact-form-{{ bid }} .cm-form-grid{grid-template-columns:repeat(2, minmax(0, 1fr));}}#contact-form-{{ bid }} .cm-field--full{grid-column:1 / -1;}#contact-form-{{ bid }} .cm-label{display:block;margin-bottom:0.375rem;font-size:0.8125rem;font-weight:500;color:hsl(var(--foreground));font-family:var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif);}#contact-form-{{ bid }} .cm-input{width:100%;padding:0.5rem 0.75rem;font-size:0.9375rem;font-family:var(--font-body, "Inter", system-ui, sans-serif);color:hsl(var(--foreground));background:hsl(var(--background));border:1px solid hsl(var(--input));border-radius:4px;box-sizing:border-box;}#contact-form-{{ bid }} .cm-input:focus{outline:2px solid hsl(var(--ring));outline-offset:1px;}</style>
|
||||||
|
{% endwith %}
|
||||||
44
templates/overrides/corporate-modernist/countdown.ninjatpl
Normal file
44
templates/overrides/corporate-modernist/countdown.ninjatpl
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{# countdown override — oversized mono unit boxes; host countdown JS binds via data-* attrs. #}
|
||||||
|
<section class="cm-section {{ class }}"><div class="cm-content-well" style="text-align: center;">
|
||||||
|
{% if heading %}<h2 class="cm-heading" style="font-size: clamp(1.75rem, 3vw, 2.25rem); margin: 0 0 0.75rem 0;">{{ heading }}</h2>{% endif %}
|
||||||
|
{% if intro %}<p class="cm-lede" style="max-width: 60ch; margin: 0 auto; color: hsl(var(--muted-foreground));">{{ intro }}</p>{% endif %}
|
||||||
|
<div data-countdown style="margin-top: 2.5rem;">
|
||||||
|
<div data-countdown-timer data-target="{{ target }}" role="timer" aria-label="Time remaining" style="display: flex; align-items: flex-start; justify-content: center; gap: 1rem; flex-wrap: wrap;">
|
||||||
|
<div class="cm-count-unit {% if variant == "boxes" %}cm-count-box{% endif %}" style="display: flex; flex-direction: column; align-items: center; {% if variant == "boxes" %}min-width: 5rem; border: 1px solid hsl(var(--border)); background: hsl(var(--card)); padding: 1rem 0.75rem;{% endif %}"><span class="cm-count-figure numeric-tabular" style="font-family: var(--font-mono, ui-monospace, monospace); font-size: clamp(2rem, 5vw, 3rem); font-weight: 500; line-height: 1; color: hsl(var(--foreground));" data-unit="days">--</span><span class="cm-stat-label" style="margin-top: 0.5rem;">{{ labelDays|default:"Days" }}</span></div>
|
||||||
|
<div class="cm-count-unit {% if variant == "boxes" %}cm-count-box{% endif %}" style="display: flex; flex-direction: column; align-items: center; {% if variant == "boxes" %}min-width: 5rem; border: 1px solid hsl(var(--border)); background: hsl(var(--card)); padding: 1rem 0.75rem;{% endif %}"><span class="cm-count-figure numeric-tabular" style="font-family: var(--font-mono, ui-monospace, monospace); font-size: clamp(2rem, 5vw, 3rem); font-weight: 500; line-height: 1; color: hsl(var(--foreground));" data-unit="hours">--</span><span class="cm-stat-label" style="margin-top: 0.5rem;">{{ labelHours|default:"Hours" }}</span></div>
|
||||||
|
<div class="cm-count-unit {% if variant == "boxes" %}cm-count-box{% endif %}" style="display: flex; flex-direction: column; align-items: center; {% if variant == "boxes" %}min-width: 5rem; border: 1px solid hsl(var(--border)); background: hsl(var(--card)); padding: 1rem 0.75rem;{% endif %}"><span class="cm-count-figure numeric-tabular" style="font-family: var(--font-mono, ui-monospace, monospace); font-size: clamp(2rem, 5vw, 3rem); font-weight: 500; line-height: 1; color: hsl(var(--foreground));" data-unit="minutes">--</span><span class="cm-stat-label" style="margin-top: 0.5rem;">{{ labelMinutes|default:"Minutes" }}</span></div>
|
||||||
|
<div class="cm-count-unit {% if variant == "boxes" %}cm-count-box{% endif %}" style="display: flex; flex-direction: column; align-items: center; {% if variant == "boxes" %}min-width: 5rem; border: 1px solid hsl(var(--border)); background: hsl(var(--card)); padding: 1rem 0.75rem;{% endif %}"><span class="cm-count-figure numeric-tabular" style="font-family: var(--font-mono, ui-monospace, monospace); font-size: clamp(2rem, 5vw, 3rem); font-weight: 500; line-height: 1; color: hsl(var(--foreground));" data-unit="seconds">--</span><span class="cm-stat-label" style="margin-top: 0.5rem;">{{ labelSeconds|default:"Seconds" }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div data-countdown-expired style="display: none; margin-top: 2rem;"><p class="cm-heading" style="font-size: 1.25rem;">{{ expiredMessage|default:"This has started." }}</p>{% if expiredUrl and expiredLabel %}<a class="cm-btn-primary" href="{{ expiredUrl }}" style="margin-top: 1rem;">{{ expiredLabel }}</a>{% endif %}</div>
|
||||||
|
</div>
|
||||||
|
<noscript><p class="cm-text" style="margin-top: 1.5rem; color: hsl(var(--muted-foreground));">Counting down to <time datetime="{{ target }}">{{ target }}</time>.</p></noscript>
|
||||||
|
</div></section>
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
var timers = document.querySelectorAll('[data-countdown-timer]');
|
||||||
|
timers.forEach(function(el){
|
||||||
|
var target = new Date(el.getAttribute('data-target')).getTime();
|
||||||
|
if (isNaN(target)) return;
|
||||||
|
var wrap = el.closest('[data-countdown]');
|
||||||
|
var expired = wrap ? wrap.querySelector('[data-countdown-expired]') : null;
|
||||||
|
function pad(n){ return (n < 10 ? '0' : '') + n; }
|
||||||
|
function set(unit, value){ var node = el.querySelector('[data-unit="' + unit + '"]'); if (node) node.textContent = value; }
|
||||||
|
var interval;
|
||||||
|
function tick(){
|
||||||
|
var diff = target - Date.now();
|
||||||
|
if (diff <= 0){
|
||||||
|
el.style.display = 'none';
|
||||||
|
if (expired) expired.style.display = 'block';
|
||||||
|
if (interval) clearInterval(interval);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
set('days', Math.floor(diff / 86400000));
|
||||||
|
set('hours', pad(Math.floor(diff % 86400000 / 3600000)));
|
||||||
|
set('minutes', pad(Math.floor(diff % 3600000 / 60000)));
|
||||||
|
set('seconds', pad(Math.floor(diff % 60000 / 1000)));
|
||||||
|
}
|
||||||
|
tick();
|
||||||
|
interval = setInterval(tick, 1000);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
8
templates/overrides/corporate-modernist/cta.ninjatpl
Normal file
8
templates/overrides/corporate-modernist/cta.ninjatpl
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{# cta override — full-width strip; band / card / split variants. #}
|
||||||
|
<section class="cm-cta-strip {% if background == "band" %}cm-cta-strip--band{% elif background == "card" %}cm-cta-strip--card{% endif %} {{ class }}" style="padding-top: 4rem; padding-bottom: 4rem;"><div class="cm-content-well" style="display: flex; flex-wrap: wrap; align-items: center; gap: 2rem; {% if background == "split" %}justify-content: space-between;{% endif %}">
|
||||||
|
<div style="{% if background != "split" %}max-width: 60ch;{% endif %}">
|
||||||
|
{% if heading %}<h2 class="cm-cta-headline cm-heading" style="font-size: clamp(1.75rem, 4vw, 2.75rem); margin: 0; color: inherit;">{{ heading }}</h2>{% endif %}
|
||||||
|
{% if text %}<p class="cm-lede" style="margin: 0.75rem 0 0 0; max-width: 55ch; color: {% if background == "band" %}inherit{% else %}hsl(var(--muted-foreground)){% endif %};">{{ text }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if primaryLabel or secondaryLabel %}<div class="cm-btn-row" style="display: flex; flex-wrap: wrap; gap: 0.75rem; flex: none;">{% if primaryUrl and primaryLabel %}<a class="cm-btn-primary cm-btn-lg" href="{{ primaryUrl }}">{{ primaryLabel }}</a>{% endif %}{% if secondaryUrl and secondaryLabel %}<a class="cm-btn-outline cm-btn-lg" href="{{ secondaryUrl }}">{{ secondaryLabel }}</a>{% endif %}</div>{% endif %}
|
||||||
|
</div></section>
|
||||||
2
templates/overrides/corporate-modernist/divider.ninjatpl
Normal file
2
templates/overrides/corporate-modernist/divider.ninjatpl
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
{# divider override — plain hairline / thick spacer rule / centered ornament between two hairlines. #}
|
||||||
|
{% if variant == "ornament" %}<div class="cm-divider-ornament {{ class }}" role="separator" style="display:flex;align-items:center;gap:1rem;margin:2.5rem auto;{% if width == "narrow" %}max-width:200px;{% elif width == "wide" %}max-width:768px;{% endif %}"><span class="cm-hairline-top" style="flex:1;height:0;"></span><span style="display:inline-flex;color:hsl(var(--muted-foreground));">::{{ icon|default:"lucide:asterisk" }}:md::</span><span class="cm-hairline-top" style="flex:1;height:0;"></span></div>{% elif variant == "spacer-rule" %}<hr class="cm-divider-line cm-divider-thick {{ class }}" role="separator" style="border:0;border-top:{% if weight == "thick" %}4px{% elif weight == "medium" %}2px{% else %}1px{% endif %} solid hsl(var(--border));margin:3rem auto;{% if width == "narrow" %}max-width:200px;{% elif width == "wide" %}max-width:768px;{% endif %}">{% else %}<hr class="cm-divider-line {{ class }}" role="separator" style="border:0;border-top:{% if weight == "thick" %}4px{% elif weight == "medium" %}2px{% else %}1px{% endif %} solid hsl(var(--border));margin:2rem auto;{% if width == "narrow" %}max-width:200px;{% elif width == "wide" %}max-width:768px;{% endif %}">{% endif %}
|
||||||
31
templates/overrides/corporate-modernist/event-list.ninjatpl
Normal file
31
templates/overrides/corporate-modernist/event-list.ninjatpl
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{# event-list override — dates shown VERBATIM in each event's own timezone (never converted); list rows or card grid. #}
|
||||||
|
<section class="cm-section {{ class }}"><div class="cm-content-well">
|
||||||
|
{% if heading %}<h2 class="cm-heading" style="font-size:clamp(1.75rem, 3vw, 2.25rem);margin:0 0 0.75rem 0;"><span class="cm-heading-h2-accent">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if intro %}<p class="cm-lede" style="max-width:60ch;margin:0 0 2.5rem 0;color:hsl(var(--muted-foreground));">{{ intro }}</p>{% endif %}
|
||||||
|
{% if layout == "cards" %}
|
||||||
|
<div class="cm-grid cm-grid-3" style="margin-top:2.5rem;display:grid;grid-template-columns:repeat(auto-fill, minmax(260px, 1fr));gap:1.5rem;">
|
||||||
|
{% for event in events %}{% if not (hidePast and event.past) %}<article class="cm-card" style="display:flex;flex-direction:column;padding:0;overflow:hidden;{% if event.past %}opacity:0.6;{% endif %}">
|
||||||
|
{% if event.media %}<div class="cm-media-frame" style="aspect-ratio:16 / 9;overflow:hidden;border-bottom:1px solid hsl(var(--border));">{% img event.media alt=event.title|default:"" class="cm-event-media" %}</div>{% endif %}
|
||||||
|
<div style="display:flex;flex-direction:column;gap:0.5rem;padding:1.25rem;flex:1;">
|
||||||
|
<div class="cm-mono numeric-tabular" style="font-size:0.8125rem;color:hsl(var(--accent));"><time{% if event.datetime %} datetime="{{ event.datetime }}"{% endif %}>{{ event.start }}{% if event.end %} – {{ event.end }}{% endif %}</time>{% if event.timezone %} <span style="color:hsl(var(--muted-foreground));">{{ event.timezone }}</span>{% endif %}</div>
|
||||||
|
<h3 class="cm-feature-title" style="font-size:1.125rem;margin:0;">{{ event.title }}</h3>
|
||||||
|
{% if event.venue %}<p style="margin:0;font-size:0.875rem;color:hsl(var(--muted-foreground));">{{ event.venue }}</p>{% endif %}
|
||||||
|
{% if event.description %}<p style="margin:0;font-size:0.875rem;color:hsl(var(--muted-foreground));">{{ event.description }}</p>{% endif %}
|
||||||
|
{% if event.link %}<a class="cm-btn-ghost" href="{{ event.link }}" style="margin-top:auto;display:inline-flex;align-items:center;gap:0.375rem;">{{ event.linkLabel|default:"Details" }}::lucide:arrow-right:sm::</a>{% endif %}
|
||||||
|
</div>
|
||||||
|
</article>{% endif %}{% endfor %}
|
||||||
|
</div><style>.cm-event-media{display:block;width:100%;height:100%;object-fit:cover;}</style>
|
||||||
|
{% else %}
|
||||||
|
<ul class="cm-hairline-top" style="list-style:none;margin:2.5rem 0 0 0;padding:0;">
|
||||||
|
{% for event in events %}{% if not (hidePast and event.past) %}<li class="cm-hairline-bottom" style="display:flex;flex-wrap:wrap;align-items:baseline;gap:0.75rem 1.5rem;padding:1.25rem 0;{% if event.past %}opacity:0.6;{% endif %}">
|
||||||
|
<div class="cm-mono numeric-tabular" style="flex:0 0 12rem;font-size:0.875rem;"><time{% if event.datetime %} datetime="{{ event.datetime }}"{% endif %}>{{ event.start }}</time>{% if event.end %}<div style="font-size:0.75rem;color:hsl(var(--muted-foreground));">to {{ event.end }}</div>{% endif %}{% if event.timezone %}<div style="font-size:0.75rem;color:hsl(var(--muted-foreground));">{{ event.timezone }}</div>{% endif %}</div>
|
||||||
|
<div style="min-width:0;flex:1;">
|
||||||
|
<h3 class="cm-feature-title" style="font-size:1rem;margin:0;">{{ event.title }}</h3>
|
||||||
|
{% if event.venue %}<p style="margin:0.125rem 0 0 0;font-size:0.875rem;color:hsl(var(--muted-foreground));">{{ event.venue }}</p>{% endif %}
|
||||||
|
{% if event.description %}<p style="margin:0.25rem 0 0 0;font-size:0.875rem;color:hsl(var(--muted-foreground));">{{ event.description }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if event.link %}<a class="cm-btn-outline cm-btn-sm" href="{{ event.link }}" style="flex:0 0 auto;">{{ event.linkLabel|default:"Details" }}</a>{% endif %}
|
||||||
|
</li>{% endif %}{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div></section>
|
||||||
6
templates/overrides/corporate-modernist/faq.ninjatpl
Normal file
6
templates/overrides/corporate-modernist/faq.ninjatpl
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{# faq override — native hairline-divided disclosure list. #}
|
||||||
|
<section class="cm-section {{ class }}"><div class="cm-content-well cm-measure">
|
||||||
|
{% if heading %}<h2 class="cm-heading" style="font-size: clamp(1.75rem, 3vw, 2.25rem); margin: 0 0 0.75rem 0;"><span class="cm-heading-h2-accent">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if intro %}<p class="cm-lede" style="max-width: 60ch; margin: 0 0 2rem 0; color: hsl(var(--muted-foreground));">{{ intro }}</p>{% endif %}
|
||||||
|
{% if items %}<div class="cm-hairline-top" style="margin-top: 1.5rem;">{% for item in items %}<details class="cm-faq-item cm-hairline-bottom"{% if variant == "open-list" %} open{% endif %}><summary class="cm-heading" style="display: flex; justify-content: space-between; align-items: center; gap: 1rem; cursor: pointer; padding: 1rem 0; font-size: 1.125rem; list-style: none;">{{ item.question }}<span class="cm-faq-chevron" aria-hidden="true" style="color: hsl(var(--muted-foreground)); flex: none;">::lucide:chevron-down:sm::</span></summary><div class="cm-faq-answer cm-text" style="padding-bottom: 1rem; color: hsl(var(--muted-foreground));">{{ item.answer|safe }}</div></details>{% endfor %}</div>{% endif %}
|
||||||
|
</div></section>
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
{# feature-grid override — Swiss cells, each led by a Bauhaus square mark. #}
|
||||||
|
<section class="cm-section {{ class }}"><div class="cm-content-well">
|
||||||
|
{% if eyebrow %}<p class="cm-eyebrow">{{ eyebrow }}</p>{% endif %}
|
||||||
|
{% if heading %}<h2 class="cm-heading" style="font-size: clamp(1.75rem, 3vw, 2.25rem); margin: 0 0 0.75rem 0;"><span class="cm-heading-h2-accent">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if intro %}<p class="cm-lede" style="max-width: 60ch; margin: 0 0 2.5rem 0; color: hsl(var(--muted-foreground));">{{ intro }}</p>{% endif %}
|
||||||
|
{% if items %}<div class="cm-grid {% if columns == 2 %}cm-grid-2{% elif columns == 4 %}cm-grid-4{% else %}cm-grid-3{% endif %}">{% for item in items %}<div class="cm-feature">{% if item.icon %}<span class="cm-mark cm-mark-square" aria-hidden="true">::{{ item.icon }}:sm::</span>{% else %}<span class="cm-mark cm-mark-square" aria-hidden="true"></span>{% endif %}{% if item.title %}<h3 class="cm-feature-title">{% if item.linkUrl %}<a href="{{ item.linkUrl }}" style="color: inherit; text-decoration: none;">{{ item.title }}</a>{% else %}{{ item.title }}{% endif %}</h3>{% endif %}{% if item.text %}<p class="cm-text" style="font-size: 0.9375rem; line-height: 1.6; color: hsl(var(--muted-foreground));">{{ item.text }}</p>{% endif %}{% if item.linkUrl and item.linkLabel %}<a class="cm-btn-ghost" href="{{ item.linkUrl }}" style="margin-top: 0.75rem;">{{ item.linkLabel }}::lucide:arrow-right:sm::</a>{% endif %}</div>{% endfor %}</div>{% endif %}
|
||||||
|
</div></section>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<section class="cm-section {{ class }}" data-block="corporate-modernist:featured-posts"><div class="cm-content-well">{% if heading %}<h2 class="cm-heading" style="font-size: clamp(1.75rem, 3vw, 2.25rem); margin: 0 0 2rem 0;"><span class="cm-heading-h2-accent">{{ heading }}</span></h2>{% endif %}<div class="cm-post-grid {% if columns == 2 %}cm-post-grid-2{% else %}cm-post-grid-3{% endif %}">{% for post in posts %}<article class="cm-post-card">{% if showFeaturedImage and post.featured_image_url %}<a href="{{ post.url }}" class="cm-post-thumb cm-media-frame" style="display: block; aspect-ratio: 16 / 9;"><img src="{{ post.featured_image_url }}" alt="{{ post.title }}" style="width: 100%; height: 100%; object-fit: cover;"></a>{% endif %}<h3 class="cm-post-title"><a href="{{ post.url }}">{{ post.title }}</a></h3>{% if showExcerpt and post.excerpt %}<p class="cm-post-excerpt">{{ post.excerpt }}</p>{% endif %}<div class="cm-post-meta">{% if showAuthor and post.author_name %}<span>{{ post.author_name }}</span>{% endif %}{% if showDate and post.published_at %}<span class="cm-mono numeric-tabular">{{ post.published_at|date:"M j, Y" }}</span>{% endif %}{% if post.reading_time %}<span class="cm-mono numeric-tabular">{{ post.reading_time }} min</span>{% endif %}</div></article>{% endfor %}</div></div></section>
|
||||||
17
templates/overrides/corporate-modernist/footer.ninjatpl
Normal file
17
templates/overrides/corporate-modernist/footer.ninjatpl
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{# footer override — Swiss-grid footer chrome from CONTENT fields (brand, contact, newsletter, legal); menu columns / social / legal rows render from the footer providers (empty today, forward-correct). #}
|
||||||
|
<footer class="cm-footer {{ class }}"><div class="cm-content-well">
|
||||||
|
<div class="cm-footer-cols" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:2.5rem;align-items:start;">
|
||||||
|
<div class="cm-stack" style="grid-column:span 2;min-width:220px;display:flex;flex-direction:column;gap:1rem;">
|
||||||
|
<a href="/" style="display:inline-flex;align-items:center;gap:0.5rem;text-decoration:none;color:hsl(var(--foreground));">{% if logo %}{% img logo alt=companyName|default:"Home" class="cm-footer-logo" %}{% else %}<span class="cm-mark cm-mark-square" aria-hidden="true"></span><span class="cm-display" style="font-size:1.125rem;">{{ companyName|default:context.site.title|default:"Studio" }}</span>{% endif %}</a>
|
||||||
|
{% if tagline %}<p class="cm-text" style="font-size:0.9375rem;max-width:34ch;margin:0;color:hsl(var(--muted-foreground));">{{ tagline }}</p>{% endif %}
|
||||||
|
{% if address or email or phone %}<address class="cm-mono numeric-tabular" style="font-style:normal;font-size:0.8125rem;line-height:1.8;color:hsl(var(--muted-foreground));">{% if address %}<div>{{ address }}</div>{% endif %}{% if email %}<div><a href="mailto:{{ email }}" class="cm-footer-link" style="color:inherit;text-decoration:none;">{{ email }}</a></div>{% endif %}{% if phone %}<div><a href="tel:{{ phone }}" class="cm-footer-link" style="color:inherit;text-decoration:none;">{{ phone }}</a></div>{% endif %}</address>{% endif %}
|
||||||
|
{% if showNewsletter %}<div class="cm-stack-sm" style="margin-top:0.5rem;display:flex;flex-direction:column;gap:0.5rem;"><p class="cm-footer-head" style="margin:0;font-size:0.75rem;text-transform:uppercase;letter-spacing:0.08em;color:hsl(var(--foreground));">{{ newsletterTitle|default:"Stay in the loop" }}</p>{% if newsletterDescription %}<p class="cm-text" style="margin:0;font-size:0.8125rem;color:hsl(var(--muted-foreground));">{{ newsletterDescription }}</p>{% endif %}{% signup_form list=newsletterListSlug|default:"main" source="footer" submit=newsletterButtonText|default:"Subscribe" %}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% for col in columns %}<div class="cm-stack-sm"><p class="cm-footer-head" style="margin:0 0 0.75rem;font-size:0.75rem;text-transform:uppercase;letter-spacing:0.08em;color:hsl(var(--foreground));">{{ col.header }}</p><ul style="list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:0.5rem;">{% for item in col.items %}<li><a class="cm-footer-link" href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} style="font-size:0.875rem;color:hsl(var(--muted-foreground));text-decoration:none;">{{ item.label }}</a></li>{% endfor %}</ul></div>{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="cm-footer-legal" style="display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:1rem;">
|
||||||
|
<p style="margin:0;">{% if copyright %}© {{ copyright }}{% else %}© {{ companyName }}{% endif %}</p>
|
||||||
|
{% if legal_items %}<nav style="display:flex;flex-wrap:wrap;gap:1.25rem;">{% for l in legal_items %}<a href="{{ l.url|default:'#' }}"{% if l.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="cm-footer-link" style="font-size:0.8125rem;color:hsl(var(--muted-foreground));text-decoration:none;">{{ l.label }}</a>{% endfor %}</nav>{% endif %}
|
||||||
|
{% if showSocial and social_items %}<div class="cm-social-row" style="display:flex;align-items:center;gap:0.75rem;">{% for s in social_items %}<a href="{{ s.url }}" target="_blank" rel="noopener" title="{{ s.label }}" class="cm-social-link" style="display:inline-flex;color:hsl(var(--muted-foreground));">{% if s.icon %}::{{ s.icon }}:sm::{% endif %}</a>{% endfor %}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div><style>.cm-footer-logo{max-height:2rem;width:auto;display:block;}.cm-footer-link:hover{color:hsl(var(--foreground));}</style></footer>
|
||||||
6
templates/overrides/corporate-modernist/gallery.ninjatpl
Normal file
6
templates/overrides/corporate-modernist/gallery.ninjatpl
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{# gallery override — Swiss grid of hairline-framed images; carries data-bn-gallery / data-lightbox / data-bn-gallery-item so the host lightbox JS binds. #}
|
||||||
|
{% if columns == 4 %}{% set gcols = "cm-gallery-4" %}{% set gn = 4 %}{% elif columns == 2 %}{% set gcols = "" %}{% set gn = 2 %}{% else %}{% set gcols = "cm-gallery-3" %}{% set gn = 3 %}{% endif %}
|
||||||
|
{% if gap == "sm" %}{% set ggap = "0.5rem" %}{% elif gap == "lg" %}{% set ggap = "1.5rem" %}{% else %}{% set ggap = "1rem" %}{% endif %}
|
||||||
|
<div class="cm-gallery-grid {{ gcols }} {{ class }}" data-bn-gallery data-lightbox="{% if lightbox %}true{% else %}false{% endif %}" style="margin:1.5rem 0;display:grid;grid-template-columns:repeat({{ gn }}, minmax(0, 1fr));gap:{{ ggap }};">
|
||||||
|
{% for image in images %}<a href="{{ image.src|mediaURL }}" data-bn-gallery-item data-caption="{{ image.caption|default:'' }}" class="cm-media-frame" style="display:block;overflow:hidden;border:1px solid hsl(var(--border));border-radius:4px;background:hsl(var(--muted));">{% img image.src alt=image.alt|default:"" class="cm-gallery-img" %}</a>{% endfor %}
|
||||||
|
</div><style>.cm-gallery-grid .cm-gallery-img{display:block;width:100%;height:100%;aspect-ratio:1;object-fit:cover;}</style>
|
||||||
24
templates/overrides/corporate-modernist/hero.ninjatpl
Normal file
24
templates/overrides/corporate-modernist/hero.ninjatpl
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{# hero override — Swiss grid, oversized display headline, Bauhaus restraint. #}
|
||||||
|
{# styled-text fields accept a {text,color} object OR a bare string (.text|default:field). #}
|
||||||
|
{% set eyebrowText = eyebrow.text|default:eyebrow %}
|
||||||
|
{% set headlineText = headline.text|default:headline %}
|
||||||
|
{% set subText = subheadline.text|default:subheadline %}
|
||||||
|
{% set descText = description.text|default:description %}
|
||||||
|
{% set pText = ctaPrimary.text %}
|
||||||
|
{% set pUrl = ctaPrimary.url %}
|
||||||
|
{% set sText = ctaSecondary.text %}
|
||||||
|
{% set sUrl = ctaSecondary.url %}
|
||||||
|
{% set variant = template|default:"centered" %}
|
||||||
|
{% macro herobody() %}
|
||||||
|
{% if eyebrowText %}<p class="cm-eyebrow">{{ eyebrowText }}</p>{% endif %}
|
||||||
|
{% if headlineText %}<h1 class="cm-hero-headline cm-display" style="font-size: clamp(2.5rem, 6vw, 4.5rem); line-height: 1.02; letter-spacing: -0.03em; margin: 0; color: hsl(var(--foreground));">{{ headlineText }}</h1>{% endif %}
|
||||||
|
{% if subText %}<p class="cm-hero-sub cm-lede" style="margin: 1.25rem 0 0 0; font-size: 1.25rem; line-height: 1.5; color: hsl(var(--muted-foreground)); max-width: 60ch;">{{ subText }}</p>{% endif %}
|
||||||
|
{% if descText %}<p class="cm-text" style="margin: 1rem 0 0 0; color: hsl(var(--muted-foreground)); max-width: 60ch;">{{ descText }}</p>{% endif %}
|
||||||
|
{% if pText or sText %}<div class="cm-btn-row" style="display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 2rem;">{% if pText and pUrl %}<a class="cm-btn-primary cm-btn-lg" href="{{ pUrl }}">{{ pText }}</a>{% endif %}{% if sText and sUrl %}<a class="cm-btn-outline cm-btn-lg" href="{{ sUrl }}">{{ sText }}</a>{% endif %}</div>{% endif %}
|
||||||
|
{% if trustLogos %}<div class="cm-hairline-top" style="margin-top: 3rem; padding-top: 1.5rem;"><p class="cm-kicker cm-eyebrow" style="margin-bottom: 1rem; color: hsl(var(--muted-foreground));">Trusted by</p><div class="cm-logo-row" style="display: flex; flex-wrap: wrap; align-items: center; gap: 2rem;">{% for logo in trustLogos %}{% if logo.url %}<a href="{{ logo.url }}">{% img logo.image alt=logo.alt class="cm-logo" %}</a>{% else %}{% img logo.image alt=logo.alt class="cm-logo" %}{% endif %}{% endfor %}</div></div>{% endif %}
|
||||||
|
{% endmacro %}
|
||||||
|
{% if variant == "split" and backgroundImage %}
|
||||||
|
<section class="cm-hero cm-hero-split {{ class }}" style="padding-top: 3.5rem; padding-bottom: 3.5rem;"><div class="cm-content-well"><div class="cm-grid cm-grid-2" style="align-items: center;"><div>{{ herobody() }}</div><div class="cm-media-frame" style="aspect-ratio: 4 / 3; background: hsl(var(--muted)); border: 1px solid hsl(var(--border)); overflow: hidden; {% if mediaPosition == "left" %}order: -1;{% endif %}">{% img backgroundImage alt=headlineText class="cm-hero-media" %}</div></div></div></section>
|
||||||
|
{% else %}
|
||||||
|
<section class="cm-hero {{ class }}" style="padding-top: 3.5rem; padding-bottom: 3.5rem; {% if variant == "left-aligned" %}text-align: left;{% else %}text-align: center;{% endif %}"><div class="cm-content-well" style="{% if variant != "left-aligned" %}display: flex; flex-direction: column; align-items: center;{% endif %}"><div style="max-width: 60ch; width: 100%;">{{ herobody() }}</div></div></section>
|
||||||
|
{% endif %}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
{# hours-location override — hairline hours table + address; click-to-load map preserves the builtin data-map-src / onclick wiring. #}
|
||||||
|
<section class="cm-section {{ class }}"><div class="cm-content-well">
|
||||||
|
{% if heading %}<h2 class="cm-heading" style="font-size:clamp(1.75rem, 3vw, 2.25rem);margin:0 0 0.75rem 0;"><span class="cm-heading-h2-accent">{{ heading }}</span></h2>{% endif %}
|
||||||
|
<div class="cm-grid cm-grid-2" style="display:grid;grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));gap:2.5rem;margin-top:2.5rem;">
|
||||||
|
<div>
|
||||||
|
{% if hours %}<table style="width:100%;border-collapse:collapse;font-size:0.9375rem;"><tbody>{% for row in hours %}<tr class="cm-hours-row cm-hairline-bottom"><th scope="row" style="text-align:left;padding:0.5rem 1rem 0.5rem 0;font-weight:500;">{{ row.day }}</th><td class="numeric-tabular" style="text-align:right;padding:0.5rem 0;color:{% if row.closed %}hsl(var(--muted-foreground)){% else %}hsl(var(--foreground)){% endif %};">{% if row.closed %}Closed{% else %}{{ row.hours }}{% endif %}</td></tr>{% endfor %}</tbody></table>{% endif %}
|
||||||
|
{% if specialHoursNote %}<p class="cm-surface-muted" style="margin-top:1rem;border:1px solid hsl(var(--border));background:hsl(var(--muted));padding:0.5rem 0.75rem;font-size:0.875rem;color:hsl(var(--muted-foreground));">{{ specialHoursNote }}</p>{% endif %}
|
||||||
|
{% if address %}<div style="margin-top:1.5rem;"><div class="cm-stat-label" style="margin:0;">Address</div><address style="margin-top:0.25rem;white-space:pre-line;font-style:normal;color:hsl(var(--foreground));">{{ address }}</address></div>{% endif %}
|
||||||
|
{% if phone %}<div style="margin-top:1rem;"><div class="cm-stat-label" style="margin:0;">Phone</div><a href="tel:{{ phone }}" style="display:inline-block;margin-top:0.25rem;color:hsl(var(--foreground));text-decoration:none;">{{ phone }}</a></div>{% endif %}
|
||||||
|
{% if directionsUrl %}<a href="{{ directionsUrl }}" target="_blank" rel="noopener noreferrer" class="cm-btn-outline" style="margin-top:1.5rem;display:inline-flex;align-items:center;gap:0.5rem;">::lucide:navigation:sm::Get directions</a>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if mapEmbedUrl %}
|
||||||
|
<div class="cm-media-frame" style="width:100%;min-height:16rem;aspect-ratio:4 / 3;overflow:hidden;border:1px solid hsl(var(--border));border-radius:4px;background:hsl(var(--muted));">
|
||||||
|
<button type="button" data-map-src="{{ mapEmbedUrl }}" onclick="var f=document.createElement('iframe');f.src=this.getAttribute('data-map-src');f.setAttribute('style','width:100%;height:100%;border:0;');f.setAttribute('loading','lazy');f.setAttribute('referrerpolicy','no-referrer-when-downgrade');f.setAttribute('title','Map');this.parentNode.replaceChild(f,this);" style="display:flex;align-items:center;justify-content:center;gap:0.5rem;width:100%;height:100%;min-height:16rem;border:0;background:none;cursor:pointer;color:hsl(var(--foreground));font-family:var(--font-heading, 'Inter Tight', 'Inter', system-ui, sans-serif);font-weight:500;">::lucide:map-pin:md::{{ mapButtonLabel|default:"Load map" }}</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div></section>
|
||||||
5
templates/overrides/corporate-modernist/image.ninjatpl
Normal file
5
templates/overrides/corporate-modernist/image.ninjatpl
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{# image override — hairline-framed figure with alignment and an optional caption. #}
|
||||||
|
{% if src %}{% if align == "left" %}{% set ialign = "flex-start" %}{% elif align == "right" %}{% set ialign = "flex-end" %}{% else %}{% set ialign = "center" %}{% endif %}<figure class="{{ class }}" style="margin:1.5rem 0;display:flex;flex-direction:column;align-items:{{ ialign }};">
|
||||||
|
<div class="cm-media-frame" style="max-width:100%;overflow:hidden;border:1px solid hsl(var(--border));border-radius:4px;line-height:0;">{% img src alt=alt|default:"" class="cm-image-el" %}</div>
|
||||||
|
{% if caption %}<figcaption class="cm-stat-label" style="margin-top:0.5rem;text-align:{% if align == "center" %}center{% else %}left{% endif %};">{{ caption }}</figcaption>{% endif %}
|
||||||
|
</figure><style>.cm-media-frame > .cm-image-el,.cm-media-frame > img{display:block;max-width:100%;height:auto;}</style>{% endif %}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
{# logo-strip override — quiet greyscale client row under a kicker. #}
|
||||||
|
<section class="cm-section-tight {{ class }}" style="padding-top: 3rem; padding-bottom: 3rem;"><div class="cm-content-well">
|
||||||
|
{% if heading %}<p class="cm-kicker cm-eyebrow" style="text-align: center; color: hsl(var(--muted-foreground)); margin-bottom: 0;">{{ heading }}</p>{% endif %}
|
||||||
|
<div class="cm-logo-row" style="display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 2.5rem; margin-top: 1.5rem;">{% for logo in logos %}{% if logo.url %}<a href="{{ logo.url }}">{% img logo.image alt=logo.alt class="cm-logo" %}</a>{% else %}{% img logo.image alt=logo.alt class="cm-logo" %}{% endif %}{% endfor %}</div>
|
||||||
|
</div></section>
|
||||||
21
templates/overrides/corporate-modernist/menu-list.ninjatpl
Normal file
21
templates/overrides/corporate-modernist/menu-list.ninjatpl
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{# menu-list override — sectioned price list; tabular-numeral prices right-aligned, dietary badges as hairline pills. #}
|
||||||
|
<section class="cm-section {{ class }}"><div class="cm-content-well">
|
||||||
|
{% if heading %}<h2 class="cm-heading" style="font-size:clamp(1.75rem, 3vw, 2.25rem);margin:0 0 0.75rem 0;"><span class="cm-heading-h2-accent">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if intro %}<p class="cm-lede" style="max-width:60ch;margin:0 0 2.5rem 0;color:hsl(var(--muted-foreground));">{{ intro }}</p>{% endif %}
|
||||||
|
<div class="cm-grid {% if columns == 2 %}cm-grid-2{% endif %}" style="display:grid;gap:2.5rem 3rem;{% if columns == 2 %}grid-template-columns:repeat(2, minmax(0, 1fr));{% endif %}">
|
||||||
|
{% for section in sections %}<div class="cm-menu-section" style="break-inside:avoid;">
|
||||||
|
{% if section.title %}<h3 class="cm-display" style="font-size:1.25rem;border-bottom:1px solid hsl(var(--border));padding-bottom:0.5rem;margin:0 0 1rem 0;">{{ section.title }}</h3>{% endif %}
|
||||||
|
{% if section.note %}<p style="margin:0 0 1rem 0;font-size:0.875rem;color:hsl(var(--muted-foreground));">{{ section.note }}</p>{% endif %}
|
||||||
|
<ul style="list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:1.25rem;">
|
||||||
|
{% for item in section.items %}<li class="cm-menu-item" style="display:flex;align-items:baseline;gap:1rem;">
|
||||||
|
<div style="min-width:0;flex:1;">
|
||||||
|
<div style="display:flex;flex-wrap:wrap;align-items:baseline;gap:0.5rem;"><span class="cm-display" style="font-size:1rem;color:hsl(var(--foreground));">{{ item.name }}</span>{% for badge in item.badges %}{% if badge.label %}<span style="display:inline-block;border:1px solid hsl(var(--border));border-radius:4px;padding:0.05rem 0.4rem;font-size:0.6875rem;text-transform:uppercase;letter-spacing:0.05em;color:hsl(var(--muted-foreground));">{{ badge.label }}</span>{% endif %}{% endfor %}</div>
|
||||||
|
{% if item.description %}<p style="margin:0.25rem 0 0 0;font-size:0.875rem;color:hsl(var(--muted-foreground));">{{ item.description }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if item.price %}<span class="cm-menu-price cm-mono numeric-tabular" style="flex:0 0 auto;color:hsl(var(--foreground));">{% if currency %}{{ currency }}{% endif %}{{ item.price }}</span>{% endif %}
|
||||||
|
</li>{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% if footnote %}<p class="cm-stat-label" style="margin-top:2.5rem;">{{ footnote }}</p>{% endif %}
|
||||||
|
</div></section>
|
||||||
1
templates/overrides/corporate-modernist/navbar.ninjatpl
Normal file
1
templates/overrides/corporate-modernist/navbar.ninjatpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<nav class="cm-navbar {{ class }}" aria-label="Primary"><a class="cm-brand" href="/"><span class="cm-mark cm-mark-square" aria-hidden="true"></span>{% if logoType == "image" and logo %}{% img logo alt=alt|default:"Home" class="cm-brand-logo" %}{% else %}<span>{{ logoText.text|default:logoText|default:context.site.title|default:"Studio" }}</span>{% endif %}</a><div class="cm-nav-links">{% for item in menu.items %}<a class="cm-nav-link" href="{{ item.url }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}>{{ item.label|default:item.title }}</a>{% endfor %}{% if cta.text and cta.url %}<a class="cm-btn-primary cm-btn-sm" href="{{ cta.url }}">{{ cta.text }}</a>{% endif %}</div><input type="checkbox" id="cm-nav-toggle-cb" class="cm-nav-cb" style="display:none;"><label for="cm-nav-toggle-cb" class="cm-nav-toggle cm-btn-outline cm-btn-sm" aria-label="Toggle menu">::lucide:menu:sm::</label><div class="cm-nav-drawer">{% for item in menu.items %}<a href="{{ item.url }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}>{{ item.label|default:item.title }}</a>{% endfor %}{% if cta.text and cta.url %}<a href="{{ cta.url }}" class="cm-btn-primary cm-btn-sm" style="margin-top:0.75rem;">{{ cta.text }}</a>{% endif %}</div><style>.cm-nav-cb:checked ~ .cm-nav-drawer{display:flex;}</style></nav>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<section class="cm-section {{ class }}" data-block="corporate-modernist:newsletter-signup"><div class="cm-content-well"><div class="{% if layout == "inline" %}cm-panel{% else %}cm-card cm-card-accent{% endif %}" style="text-align: center;">{% if title %}<h2 class="cm-heading" style="font-size: clamp(1.5rem, 2.5vw, 2rem); margin: 0;"><span class="cm-heading-h2-accent">{{ title }}</span></h2>{% endif %}{% if description %}<p class="cm-lede" style="max-width: 52ch; margin: 0.75rem auto 0 auto;">{{ description }}</p>{% endif %}<div class="cm-form" style="max-width: 28rem; margin: 1.5rem auto 0 auto;">{% signup_form list=listSlug source=source submit=submitText success_message=successMessage placeholder=placeholder %}</div></div></div></section>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<section class="cm-section {{ class }}" data-block="corporate-modernist:page-suggestions"><div class="cm-content-well"><div class="cm-measure cm-stack"><span class="cm-mark cm-mark-triangle" aria-hidden="true"></span><h2 class="cm-heading" style="font-size: clamp(1.5rem, 2.5vw, 2rem); margin: 0;"><span class="cm-heading-h2-accent">{{ title|default:"Were you looking for..." }}</span></h2>{% if suggestions %}<ul class="cm-stack-sm" style="list-style: none; margin: 0; padding: 0;">{% for s in suggestions %}<li><a class="cm-sidebar-link" href="/{{ s.slug }}" style="display: block;"><span class="cm-post-title" style="font-size: 1rem;">{{ s.title }}</span>{% if showDescription and s.excerpt %}<span class="cm-text" style="display: block; font-size: 0.875rem; color: hsl(var(--muted-foreground));">{{ s.excerpt }}</span>{% endif %}</a></li>{% endfor %}</ul>{% else %}{% if emptyMessage %}<p class="cm-text" style="margin: 0; color: hsl(var(--muted-foreground));">{{ emptyMessage }}</p>{% endif %}<div class="cm-btn-row"><a class="cm-btn-outline cm-btn-sm" href="/">::lucide:home:sm::Home</a><a class="cm-btn-outline cm-btn-sm" href="/blog">::lucide:newspaper:sm::Blog</a></div>{% endif %}</div></div></section>
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
<div class="cm-stack {{ class }}" data-block="corporate-modernist:password-reset-form">
|
||||||
|
<span class="cm-mark cm-mark-ring" aria-hidden="true"></span>
|
||||||
|
<h2 class="cm-heading" style="font-size: 1.5rem; margin: 0;">Reset password</h2>
|
||||||
|
<p class="cm-text" style="margin: 0; font-size: 0.9375rem; color: hsl(var(--muted-foreground));">Enter your email and we will send a link to reset your password.</p>
|
||||||
|
<div id="reset-message" role="status" aria-live="polite"></div>
|
||||||
|
<form class="cm-form" hx-post="/api/auth/request-password-reset" hx-target="#reset-message" hx-swap="innerHTML">
|
||||||
|
<div class="cm-field cm-field--full"><label class="cm-label" for="cm-reset-email">Email</label><input class="cm-input" type="email" name="email" id="cm-reset-email" required></div>
|
||||||
|
<button type="submit" class="cm-btn-primary" hx-disabled-elt="this" style="width:100%; justify-content:center;">Send reset link</button>
|
||||||
|
</form>
|
||||||
|
<a class="cm-btn-ghost cm-btn-sm" href="/login" style="align-self: flex-start;">::lucide:arrow-left:sm::Back to sign in</a>
|
||||||
|
</div>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<section class="cm-section-tight {{ class }}" data-block="corporate-modernist:popular-posts">{% if heading %}<h2 class="cm-kicker">{{ heading }}</h2>{% endif %}<ol class="cm-stack-sm" style="list-style: none; margin: 0; padding: 0;">{% for post in posts %}<li style="display: flex; gap: 0.75rem; align-items: flex-start;">{% if showRank %}<span class="cm-rank numeric-tabular">{{ forloop.Counter }}</span>{% endif %}{% if showThumbnail and post.featured_image_url %}<a href="{{ post.url }}" class="cm-post-thumb cm-media-frame" style="width: 3rem; height: 3rem; flex-shrink: 0;"><img src="{{ post.featured_image_url }}" alt="{{ post.title }}" style="width: 100%; height: 100%; object-fit: cover;"></a>{% endif %}<div style="min-width: 0;"><h3 class="cm-post-title" style="font-size: 0.9375rem;"><a href="{{ post.url }}">{{ post.title }}</a></h3>{% if post.published_at %}<p class="cm-post-meta"><span class="cm-mono numeric-tabular">{{ post.published_at|date:"M j, Y" }}</span></p>{% endif %}</div></li>{% endfor %}</ol></section>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<section class="cm-section {{ class }}" data-block="corporate-modernist:post-hero"><div class="cm-content-well">{% if context.currentPost.featuredImageUrl %}<div class="cm-media-frame" style="aspect-ratio: 21 / 9; margin-bottom: 2.5rem;"><img src="{{ context.currentPost.featuredImageUrl }}" alt="{{ context.currentPost.featuredImageAlt|default:context.currentPost.title }}" style="width: 100%; height: 100%; object-fit: cover;"></div>{% endif %}<div class="cm-measure cm-stack" style="{% if align == "left" %}text-align: left;{% else %}margin-left: auto; margin-right: auto; text-align: center;{% endif %}"><span class="cm-mark cm-mark-square" aria-hidden="true"{% if align != "left" %} style="margin-left: auto; margin-right: auto;"{% endif %}></span><h1 class="cm-display" style="margin: 0;">{{ context.currentPost.title }}</h1>{% if showExcerpt and context.currentPost.excerpt %}<p class="cm-lede" style="margin: 0;">{{ context.currentPost.excerpt }}</p>{% endif %}{% if showMeta %}<div class="cm-post-meta"{% if align != "left" %} style="justify-content: center;"{% endif %}>{% if context.currentPost.authorName %}<span>{{ context.currentPost.authorName }}</span>{% endif %}{% if context.currentPost.publishedAt %}<span class="cm-mono numeric-tabular">{{ context.currentPost.publishedAt }}</span>{% endif %}{% if context.currentPost.readingTimeMinutes %}<span class="cm-mono numeric-tabular">{{ context.currentPost.readingTimeMinutes }} min read</span>{% endif %}</div>{% endif %}</div></div></section>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<div class="cm-post-meta {{ class }}" data-block="corporate-modernist:post-metadata">{% if showAuthor and context.currentPost.authorName %}<span style="display: inline-flex; align-items: center; gap: 0.5rem;">{% if showAvatar and context.currentPost.authorAvatarUrl %}<img class="cm-avatar" src="{{ context.currentPost.authorAvatarUrl }}" alt="{{ context.currentPost.authorName }}" style="width: 1.75rem; height: 1.75rem; border-radius: 9999px; object-fit: cover;">{% endif %}<span style="color: hsl(var(--foreground)); font-weight: 500;">{{ context.currentPost.authorName }}</span></span>{% endif %}{% if showDate and context.currentPost.publishedAt %}<time class="cm-mono numeric-tabular" datetime="{{ context.currentPost.publishedAtIso }}">{{ context.currentPost.publishedAt }}</time>{% endif %}{% if showReadingTime and context.currentPost.readingTimeMinutes %}<span class="cm-mono numeric-tabular">{{ context.currentPost.readingTimeMinutes }} min read</span>{% endif %}</div>
|
||||||
14
templates/overrides/corporate-modernist/pricing.ninjatpl
Normal file
14
templates/overrides/corporate-modernist/pricing.ninjatpl
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{# pricing override — flat hairline tiers, mono price figures, accent-top featured tier. #}
|
||||||
|
<section class="cm-section {{ class }}"><div class="cm-content-well">
|
||||||
|
{% if heading %}<h2 class="cm-heading" style="font-size: clamp(1.75rem, 3vw, 2.25rem); margin: 0 0 0.75rem 0;"><span class="cm-heading-h2-accent">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if intro %}<p class="cm-lede" style="max-width: 60ch; margin: 0 0 2.5rem 0; color: hsl(var(--muted-foreground));">{{ intro }}</p>{% endif %}
|
||||||
|
{% if tiers %}<div class="cm-grid cm-grid-3">{% for tier in tiers %}<div class="cm-card cm-price-tier {% if tier.highlighted %}cm-price-tier--featured cm-card-accent{% endif %}" style="display: flex; flex-direction: column;">
|
||||||
|
{% if tier.badge %}<span class="cm-badge" style="display: inline-block; align-self: flex-start; background: hsl(var(--accent)); color: hsl(var(--accent-foreground)); font-size: 0.6875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; padding: 0.25rem 0.5rem; border-radius: 4px; margin-bottom: 0.75rem;">{{ tier.badge }}</span>{% endif %}
|
||||||
|
{% if tier.name %}<h3 class="cm-feature-title" style="margin: 0;">{{ tier.name }}</h3>{% endif %}
|
||||||
|
<p style="display: flex; align-items: baseline; gap: 0.25rem; margin: 1rem 0 0 0;"><span class="cm-price numeric-tabular" style="font-family: var(--font-mono, ui-monospace, monospace); font-size: clamp(2rem, 4vw, 2.75rem); font-weight: 500; line-height: 1; color: hsl(var(--foreground));">{% if currency %}{{ currency }}{% else %}${% endif %}{{ tier.price }}</span>{% if tier.period %}<span class="cm-price-period cm-stat-label" style="margin-top: 0;">{{ tier.period }}</span>{% endif %}</p>
|
||||||
|
{% if tier.description %}<p class="cm-text" style="font-size: 0.9375rem; color: hsl(var(--muted-foreground)); margin-top: 0.75rem;">{{ tier.description }}</p>{% endif %}
|
||||||
|
{% if tier.features %}<ul class="cm-stack-sm" style="list-style: none; padding: 0; margin: 1.5rem 0 0 0; display: flex; flex-direction: column; gap: 0.625rem; flex: 1;">{% for feature in tier.features %}<li class="cm-check" style="display: flex; gap: 0.5rem; align-items: flex-start; font-size: 0.9375rem; color: hsl(var(--foreground));"><span style="color: hsl(var(--accent)); flex: none;">::lucide:check:sm::</span><span>{{ feature }}</span></li>{% endfor %}</ul>{% endif %}
|
||||||
|
{% if tier.ctaUrl and tier.ctaLabel %}{% if tier.highlighted %}<a class="cm-btn-primary" href="{{ tier.ctaUrl }}" style="margin-top: 1.5rem; width: 100%; box-sizing: border-box; text-align: center;">{{ tier.ctaLabel }}</a>{% else %}<a class="cm-btn-outline" href="{{ tier.ctaUrl }}" style="margin-top: 1.5rem; width: 100%; box-sizing: border-box; text-align: center;">{{ tier.ctaLabel }}</a>{% endif %}{% endif %}
|
||||||
|
</div>{% endfor %}</div>{% endif %}
|
||||||
|
{% if billingNote %}<p class="cm-stat-label" style="text-align: center; margin-top: 2rem;">{{ billingNote }}</p>{% endif %}
|
||||||
|
</div></section>
|
||||||
6
templates/overrides/corporate-modernist/quote.ninjatpl
Normal file
6
templates/overrides/corporate-modernist/quote.ninjatpl
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{# quote override — pull quote (accent top rule) / block quote (accent left rule) / inline card. #}
|
||||||
|
{% if style == "pull" %}{% set qcls = "cm-pull-quote" %}{% set qsty = "margin:2rem 0;" %}{% elif style == "inline-card" %}{% set qcls = "cm-quote-block cm-card cm-card-accent" %}{% set qsty = "margin:2rem 0;" %}{% else %}{% set qcls = "cm-quote-block" %}{% set qsty = "margin:2rem 0;border-left:2px solid hsl(var(--accent));padding-left:1.25rem;" %}{% endif %}
|
||||||
|
<figure class="{{ qcls }} {{ class }}" style="{{ qsty }}{% if align == "center" %}text-align:center;{% endif %}">
|
||||||
|
<blockquote{% if cite %} cite="{{ cite }}"{% endif %} style="margin:0;border:0;padding:0;{% if style != "pull" %}font-size:1.25rem;line-height:1.5;color:hsl(var(--foreground));{% endif %}">{{ quote|safe }}</blockquote>
|
||||||
|
{% if attribution or role or cite %}<figcaption class="cm-quote-cite" style="margin-top:1rem;font-size:0.875rem;color:hsl(var(--muted-foreground));">{% if attribution %}<span class="cm-display" style="font-size:0.9375rem;color:hsl(var(--foreground));">{{ attribution }}</span>{% endif %}{% if role %}<span style="display:block;">{{ role }}</span>{% endif %}{% if cite %}<a href="{{ cite }}" style="display:inline-block;margin-top:0.25rem;color:inherit;text-decoration:underline;text-underline-offset:2px;">Source</a>{% endif %}</figcaption>{% endif %}
|
||||||
|
</figure>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<section class="cm-section cm-hairline-top {{ class }}" data-block="corporate-modernist:related-posts"><div class="cm-content-well"><h2 class="cm-heading" style="font-size: clamp(1.5rem, 2.5vw, 2rem); margin: 0 0 2rem 0;"><span class="cm-heading-h2-accent">{{ heading|default:"Related posts" }}</span></h2><div class="cm-post-grid {% if columns == 2 %}cm-post-grid-2{% else %}cm-post-grid-3{% endif %}">{% for post in posts %}<article class="cm-post-card">{% if post.featured_image_url %}<a href="{{ post.url }}" class="cm-post-thumb cm-media-frame" style="display: block; aspect-ratio: 16 / 9;"><img src="{{ post.featured_image_url }}" alt="{{ post.title }}" style="width: 100%; height: 100%; object-fit: cover;"></a>{% endif %}<h3 class="cm-post-title"><a href="{{ post.url }}">{{ post.title }}</a></h3>{% if post.published_at %}<div class="cm-post-meta"><span class="cm-mono numeric-tabular">{{ post.published_at|date:"M j, Y" }}</span></div>{% endif %}</article>{% endfor %}</div></div></section>
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
{# rich-section override — two-column media + rich text, hairline surface options. #}
|
||||||
|
<section class="cm-section {% if background == "muted" %}cm-surface-muted{% elif background == "card" %}cm-surface-card{% endif %} {{ class }}"><div class="cm-content-well"><div class="cm-grid cm-grid-2" style="align-items: center;">
|
||||||
|
<div>
|
||||||
|
{% if eyebrow %}<p class="cm-eyebrow">{{ eyebrow }}</p>{% endif %}
|
||||||
|
{% if heading %}<h2 class="cm-heading" style="font-size: clamp(1.75rem, 3vw, 2.25rem); margin: 0 0 1rem 0;"><span class="cm-heading-h2-accent">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if body %}<div class="cm-text" style="color: hsl(var(--muted-foreground)); line-height: 1.7;">{{ body|safe }}</div>{% endif %}
|
||||||
|
{% if ctaUrl and ctaLabel %}<a class="cm-btn-ghost" href="{{ ctaUrl }}" style="margin-top: 1.5rem;">{{ ctaLabel }}::lucide:arrow-right:sm::</a>{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="cm-media-frame" style="aspect-ratio: 4 / 3; background: hsl(var(--muted)); border: 1px solid hsl(var(--border)); overflow: hidden; {% if mediaPosition == "left" %}order: -1;{% endif %}">{% if media %}{% img media alt=mediaAlt class="cm-rich-media" %}{% endif %}</div>
|
||||||
|
</div></div></section>
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
{# rich-text override — raw imported HTML inside the Swiss reading measure. #}
|
||||||
|
{% if html %}<div class="cm-text cm-measure {{ class }}" style="max-width:70ch;margin-left:auto;margin-right:auto;">{{ html|safe }}</div>{% endif %}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
<nav class="{{ class }}" data-block="corporate-modernist:sidebar-nav" style="{% if showBorder %}border-right: 1px solid hsl(var(--border)); padding-right: 1rem;{% endif %}">{% if title %}<h3 class="cm-kicker" style="margin-top: 0;">{{ title }}</h3>{% endif %}<ul class="cm-stack-sm" style="list-style: none; margin: 0; padding: 0;">{% for item in menu.items %}<li><a class="cm-sidebar-link" href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}{% if item.title %} title="{{ item.title }}"{% endif %} data-bn-sidebar-link>{% if item.icon %}<svg class="cm-icon" aria-hidden="true"><use href="/icons/{{ item.icon|split:":"|first }}.svg#{{ item.icon|split:":"|last }}"/></svg>{% endif %}{{ item.label }}</a></li>{% endfor %}</ul>{% if highlightCurrent %}<script>
|
||||||
|
(function(){var p=window.location.pathname;document.querySelectorAll('[data-bn-sidebar-link]').forEach(function(a){if(a.getAttribute('href')===p){a.setAttribute('aria-current','page');}});})();
|
||||||
|
</script>{% endif %}</nav>
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
{# social-links override — icon row or labelled stack; each link a hairline cm-social-link (rendered only when it has a URL). #}
|
||||||
|
<div class="{{ class }}">
|
||||||
|
{% if heading %}<p class="cm-stat-label" style="margin:0 0 0.75rem 0;">{{ heading }}</p>{% endif %}
|
||||||
|
<ul class="cm-social-row" style="list-style:none;margin:0;padding:0;{% if style == "stack" %}display:flex;flex-direction:column;gap:0.5rem;{% else %}display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem;{% endif %}">
|
||||||
|
{% for link in links %}{% if link.url %}<li><a href="{{ link.url }}" target="_blank" rel="noopener noreferrer" aria-label="{{ link.label|default:link.network }}" class="cm-social-link" style="display:inline-flex;align-items:center;gap:0.5rem;border:1px solid hsl(var(--border));border-radius:4px;color:hsl(var(--foreground));text-decoration:none;{% if style == "stack" %}padding:0.5rem 1rem;{% else %}padding:0.5rem;{% endif %}">{% if link.network %}::{{ link.network }}:sm::{% endif %}{% if style == "stack" and link.label %}<span style="font-size:0.875rem;font-weight:500;">{{ link.label }}</span>{% endif %}</a></li>{% endif %}{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
6
templates/overrides/corporate-modernist/stats.ninjatpl
Normal file
6
templates/overrides/corporate-modernist/stats.ninjatpl
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{# stats override — oversized tabular figures over a hairline, no count-up JS. #}
|
||||||
|
<section class="cm-section {{ class }}"><div class="cm-content-well">
|
||||||
|
{% if heading %}<h2 class="cm-heading" style="font-size: clamp(1.75rem, 3vw, 2.25rem); margin: 0 0 0.75rem 0;"><span class="cm-heading-h2-accent">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if intro %}<p class="cm-lede" style="max-width: 60ch; margin: 0 0 2.5rem 0; color: hsl(var(--muted-foreground));">{{ intro }}</p>{% endif %}
|
||||||
|
{% if items %}<div class="cm-grid {% if columns == 2 %}cm-grid-2{% elif columns == 3 %}cm-grid-3{% else %}cm-grid-4{% endif %}">{% for item in items %}<div class="cm-hairline-top" style="padding-top: 1.25rem;"><div class="cm-stat-figure numeric-tabular" style="font-size: clamp(2.5rem, 5vw, 3.5rem);">{% if item.prefix %}{{ item.prefix }}{% endif %}{{ item.value }}{% if item.suffix %}{{ item.suffix }}{% endif %}</div>{% if item.label %}<div class="cm-stat-label">{{ item.label }}</div>{% endif %}</div>{% endfor %}</div>{% endif %}
|
||||||
|
</div></section>
|
||||||
12
templates/overrides/corporate-modernist/team.ninjatpl
Normal file
12
templates/overrides/corporate-modernist/team.ninjatpl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{# team override — leadership grid, square-framed portraits, restrained social row. #}
|
||||||
|
<section class="cm-section {{ class }}"><div class="cm-content-well">
|
||||||
|
{% if heading %}<h2 class="cm-heading" style="font-size: clamp(1.75rem, 3vw, 2.25rem); margin: 0 0 0.75rem 0;"><span class="cm-heading-h2-accent">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if intro %}<p class="cm-lede" style="max-width: 60ch; margin: 0 0 2.5rem 0; color: hsl(var(--muted-foreground));">{{ intro }}</p>{% endif %}
|
||||||
|
{% if members %}<div class="{% if layout == "list" %}cm-stack{% elif layout == "single" %}cm-measure{% else %}cm-leadership-grid{% endif %}">{% for member in members %}<div class="cm-feature">
|
||||||
|
{% if member.photo %}<div class="cm-media-frame" style="aspect-ratio: 1 / 1; background: hsl(var(--muted)); border: 1px solid hsl(var(--border)); overflow: hidden; margin-bottom: 1rem;">{% img member.photo alt=member.name class="cm-team-photo" %}</div>{% endif %}
|
||||||
|
{% if member.name %}<h3 class="cm-feature-title" style="margin: 0;">{{ member.name }}</h3>{% endif %}
|
||||||
|
{% if member.role %}<div class="cm-stat-label" style="margin-top: 0.25rem;">{{ member.role }}</div>{% endif %}
|
||||||
|
{% if member.bio %}<p class="cm-text" style="font-size: 0.9375rem; line-height: 1.6; color: hsl(var(--muted-foreground)); margin-top: 0.75rem;">{{ member.bio }}</p>{% endif %}
|
||||||
|
{% if member.socials %}<div class="cm-btn-row" style="display: flex; gap: 0.75rem; margin-top: 1rem;">{% for social in member.socials %}{% if social.url %}<a href="{{ social.url }}" aria-label="Profile link" style="color: hsl(var(--muted-foreground));">::{{ social.icon }}:sm::</a>{% endif %}{% endfor %}</div>{% endif %}
|
||||||
|
</div>{% endfor %}</div>{% endif %}
|
||||||
|
</div></section>
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
{# testimonial override — accent-ruled pull quotes, optional 5-star scale. #}
|
||||||
|
<section class="cm-section {{ class }}"><div class="cm-content-well">
|
||||||
|
{% if heading %}<h2 class="cm-heading" style="font-size: clamp(1.75rem, 3vw, 2.25rem); margin: 0 0 2.5rem 0;"><span class="cm-heading-h2-accent">{{ heading }}</span></h2>{% endif %}
|
||||||
|
<div class="{% if layout == "single" %}cm-measure{% else %}cm-grid cm-grid-3{% endif %}">{% for q in quotes %}<figure class="cm-quote-block cm-card" style="margin: 0; display: flex; flex-direction: column;">
|
||||||
|
{% if q.rating %}<div class="cm-rating" aria-label="Rated {{ q.rating }} out of 5" style="display: flex; gap: 0.125rem; margin-bottom: 0.75rem;"><span style="color: {% if q.rating >= 1 %}hsl(var(--accent)){% else %}hsl(var(--muted-foreground) / 0.35){% endif %};">::lucide:star:sm::</span><span style="color: {% if q.rating >= 2 %}hsl(var(--accent)){% else %}hsl(var(--muted-foreground) / 0.35){% endif %};">::lucide:star:sm::</span><span style="color: {% if q.rating >= 3 %}hsl(var(--accent)){% else %}hsl(var(--muted-foreground) / 0.35){% endif %};">::lucide:star:sm::</span><span style="color: {% if q.rating >= 4 %}hsl(var(--accent)){% else %}hsl(var(--muted-foreground) / 0.35){% endif %};">::lucide:star:sm::</span><span style="color: {% if q.rating >= 5 %}hsl(var(--accent)){% else %}hsl(var(--muted-foreground) / 0.35){% endif %};">::lucide:star:sm::</span></div>{% endif %}
|
||||||
|
{% if q.quote %}<blockquote class="cm-pull-quote" style="flex: 1; margin: 0;">{{ q.quote }}</blockquote>{% endif %}
|
||||||
|
<figcaption class="cm-quote-cite" style="display: flex; align-items: center; gap: 0.75rem; margin-top: 1.25rem;">{% if q.avatar %}<span class="cm-avatar" style="width: 2.75rem; height: 2.75rem; border-radius: 9999px; overflow: hidden; background: hsl(var(--muted)); display: inline-flex; flex: none;">{% img q.avatar alt=q.name class="cm-avatar-img" %}</span>{% endif %}<span>{% if q.name %}<span class="cm-feature-title" style="display: block; font-size: 0.9375rem;">{{ q.name }}</span>{% endif %}{% if q.role %}<span class="cm-stat-label" style="margin-top: 0;">{{ q.role }}</span>{% endif %}</span></figcaption>
|
||||||
|
</figure>{% endfor %}</div>
|
||||||
|
</div></section>
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
{# timeline override — numbered Swiss rail (mirrors engagement_process). #}
|
||||||
|
<section class="cm-section {{ class }}"><div class="cm-content-well">
|
||||||
|
{% if heading %}<h2 class="cm-heading" style="font-size: clamp(1.75rem, 3vw, 2.25rem); margin: 0 0 0.75rem 0;"><span class="cm-heading-h2-accent">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if intro %}<p class="cm-lede" style="max-width: 60ch; margin: 0 0 2.5rem 0; color: hsl(var(--muted-foreground));">{{ intro }}</p>{% endif %}
|
||||||
|
{% if steps %}<div>{% for step in steps %}<div class="cm-timeline-step"><div class="cm-timeline-rail"><span class="cm-timeline-node numeric-tabular">{{ forloop.Counter }}</span>{% if not forloop.Last %}<span class="cm-timeline-line" aria-hidden="true"></span>{% endif %}</div><div style="padding-top: 0.375rem;">{% if step.label %}<p class="cm-eyebrow" style="margin-bottom: 0.25rem;">{{ step.label }}</p>{% endif %}{% if step.title %}<h3 class="cm-feature-title" style="margin-top: 0;">{{ step.title }}</h3>{% endif %}{% if step.text %}<p class="cm-text" style="font-size: 0.9375rem; line-height: 1.6; color: hsl(var(--muted-foreground));">{{ step.text }}</p>{% endif %}{% if step.media %}<div class="cm-media-frame" style="aspect-ratio: 16 / 9; border: 1px solid hsl(var(--border)); overflow: hidden; margin-top: 1rem;">{% img step.media alt=step.title class="cm-timeline-media" %}</div>{% endif %}</div></div>{% endfor %}</div>{% else %}<p class="cm-stat-label">No steps configured.</p>{% endif %}
|
||||||
|
</div></section>
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
{# utility-bar override — thin Swiss top strip; left/right item groups; icon-picker fields render via ::pack:name:: shorthand. #}
|
||||||
|
{% if variant == "primary" %}{% set ubvar = "cm-utility-bar--primary" %}{% set ubsty = "background:hsl(var(--primary));color:hsl(var(--primary-foreground));" %}{% elif variant == "dark" %}{% set ubvar = "cm-utility-bar--dark" %}{% set ubsty = "background:hsl(var(--foreground));color:hsl(var(--background));" %}{% else %}{% set ubvar = "cm-utility-bar--muted" %}{% set ubsty = "background:hsl(var(--muted));color:hsl(var(--muted-foreground));border-bottom:1px solid hsl(var(--border));" %}{% endif %}
|
||||||
|
{% macro ubitem(it) %}{% if it.link %}<a href="{{ it.link }}" class="cm-utility-item" style="display:inline-flex;align-items:center;gap:0.375rem;color:inherit;text-decoration:none;">{% if it.icon %}::{{ it.icon }}:sm::{% endif %}{% if it.label %}<span>{{ it.label }}</span>{% endif %}</a>{% else %}<span class="cm-utility-item" style="display:inline-flex;align-items:center;gap:0.375rem;">{% if it.icon %}::{{ it.icon }}:sm::{% endif %}{% if it.label %}<span>{{ it.label }}</span>{% endif %}</span>{% endif %}{% endmacro %}
|
||||||
|
<div class="cm-utility-bar {{ ubvar }} {{ class }}" style="{{ ubsty }}font-size:0.75rem;font-family:var(--font-body, 'Inter', system-ui, sans-serif);">
|
||||||
|
<div class="cm-content-well cm-utility-row" style="display:flex;align-items:center;justify-content:space-between;gap:1rem;min-height:2.25rem;">
|
||||||
|
<div class="cm-utility-group" style="display:flex;align-items:center;gap:1.25rem;flex-wrap:wrap;">{% for it in leftItems %}{{ ubitem(it) }}{% endfor %}</div>
|
||||||
|
<div class="cm-utility-group" style="display:flex;align-items:center;gap:1.25rem;flex-wrap:wrap;justify-content:flex-end;">{% for it in rightItems %}{{ ubitem(it) }}{% endfor %}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
11
templates/overrides/corporate-modernist/video-embed.ninjatpl
Normal file
11
templates/overrides/corporate-modernist/video-embed.ninjatpl
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{# video-embed override — click-to-load facade; carries data-bn-video / data-video-url / data-video-media so the host facade JS binds. #}
|
||||||
|
{% if aspect == "4:3" %}{% set vratio = "4 / 3" %}{% elif aspect == "1:1" %}{% set vratio = "1 / 1" %}{% elif aspect == "21:9" %}{% set vratio = "21 / 9" %}{% else %}{% set vratio = "16 / 9" %}{% endif %}
|
||||||
|
<figure class="{{ class }}" style="margin:2rem 0;">
|
||||||
|
<div class="cm-video-frame" data-bn-video data-video-url="{{ url|default:'' }}" data-video-media="{% if media %}{{ media|mediaURL }}{% endif %}" style="position:relative;width:100%;overflow:hidden;border:1px solid hsl(var(--border));border-radius:4px;background:hsl(var(--muted));aspect-ratio:{{ vratio }};">
|
||||||
|
<button type="button" data-bn-video-play class="cm-video-play" aria-label="Play video{% if title %}: {{ title }}{% endif %}" style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;width:100%;height:100%;border:0;background:none;cursor:pointer;">
|
||||||
|
{% if poster %}{% img poster alt=title|default:"" class="cm-video-poster" %}{% endif %}
|
||||||
|
<span class="cm-video-play-badge" style="position:relative;display:inline-flex;align-items:center;justify-content:center;width:4rem;height:4rem;border-radius:4px;background:hsl(var(--accent));color:hsl(var(--accent-foreground));">::lucide:play:lg::</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% if caption %}<figcaption class="cm-stat-label" style="margin-top:0.5rem;text-align:center;">{{ caption }}</figcaption>{% endif %}
|
||||||
|
</figure><style>.cm-video-frame .cm-video-poster{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;}</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user