Compare commits
4 Commits
9955f5f002
...
486d3a283e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
486d3a283e | ||
|
|
c341fb9645 | ||
|
|
0911a76f2e | ||
|
|
8da60d7d5a |
@ -1,74 +0,0 @@
|
||||
# Gotham — templ → codeless `.bnp` port notes
|
||||
|
||||
This theme was ported from the bundled Go/templ `gotham` plugin (source kept in
|
||||
`port-source/` for reference) to a **codeless** `.bnp` (no Go, no `plugin.wasm`),
|
||||
matching the `art-deco` reference theme's structure. Every block, page template,
|
||||
override and the email wrapper is now a host-rendered `.ninjatpl`.
|
||||
|
||||
## What maps 1:1 (faithful, no behavior change)
|
||||
|
||||
| Source (templ) | Codeless equivalent | Notes |
|
||||
|---|---|---|
|
||||
| `RenderGotham` (default) | `templates/gotham/default.ninjatpl` | `bn.Head`/`bn.BodyEnd`/`bn.AdminBypassBanner` replaced by host-injected `{{ head_html }}` / `{{ body_end_html }}` / `{{ admin_banner_html }}`. `<html class="dark">` hardcoded exactly as source (Gotham is always dark). |
|
||||
| `RenderGothamLanding` | `templates/gotham/landing.ninjatpl` | hero/main/cta/footer slots preserved. |
|
||||
| `RenderGothamFullWidth` | `templates/gotham/full-width.ninjatpl` | |
|
||||
| `RenderGothamCentered` | `templates/gotham/centered.ninjatpl` | |
|
||||
| `FeaturesBlock` + `featuresComponent` | `blocks/features.ninjatpl` | Inline icon SVG switch (chart/users/clock/star/default) ported verbatim as `{% if %}` chains. `columns` grid logic preserved. |
|
||||
| `FooterBlock` + `footerComponent` | `blocks/footer.ninjatpl` | Column grid + link-URL logic preserved (see PageID note below). |
|
||||
| `StatsBlock` + `statsComponent` / `statsEmptyComponent` | `blocks/stats.ninjatpl` | Uses the `items` content array (the only path `StatsBlock` ever took). Empty-state placeholder preserved. |
|
||||
| `GothamHeadingBlock` override | `templates/overrides/gotham/heading.ninjatpl` | Per-level base classes + `text-accent` preserved. |
|
||||
| `GothamTextBlock` override | `templates/overrides/gotham/text.ninjatpl` | `prose prose-invert prose-amber` preserved. |
|
||||
| `GothamEmailWrapper` | `templates/email/gotham.ninjatpl` | Var names (`site_name`, `site_url`, `colors.background/card/foreground/muted/mutedForeground/border/primary`, `preview_text`, `logo_url`, `unsubscribe_url`) match the host email-context contract — identical to the `art-deco` reference wrapper. |
|
||||
| `assets/style.css` | `assets/style.css` | Byte-identical (`gotham-accent`, `gotham-accent-bg`, `gotham-card`, `gotham-glow`, …). Host serves it as a static and links it via `head_html`. |
|
||||
| `DefaultMasterPages()` | `master_pages.json` | `gotham:default-master` with navbar / slot / `gotham:footer`. |
|
||||
| `presets.json`, `fonts.json` | unchanged | Carried over as manifest JSON refs. |
|
||||
|
||||
## Behavior gaps / decisions the parent should review
|
||||
|
||||
1. **`hero` block was NOT registered in the source.** `port-source/hero.go` /
|
||||
`hero.templ` / `schemas/hero.schema.json` exist, but `register.go` never calls
|
||||
`br.Register(HeroBlockMeta, ...)`, and `HeroBlock` has the CMS-internal
|
||||
builtin signature `(ctx, content, _ []string)` — it was **dead code**. Because
|
||||
the task explicitly asked for a hero port and the `landing` template exposes a
|
||||
`hero` slot, I ported it faithfully to `blocks/hero.ninjatpl` +
|
||||
`hero.schema.json` and registered it as `gotham:hero`. **If the parent wants to
|
||||
match the original registered surface exactly, delete those two files and the
|
||||
`gotham:hero` entry in `blocks/blocks.yaml`** — nothing else references it.
|
||||
|
||||
2. **`stat_item` block dropped (intentionally).** `port-source/stat_item.go`
|
||||
(`StatItemBlockMeta`, `Hidden: true`) and `statItemComponent` were also never
|
||||
registered in `register.go`, and `StatsBlock` renders its items inline (never
|
||||
via a `stat_item` child block). The child-block container path
|
||||
(`statsContainerComponent` / `gridCols(childCount)`) was likewise unused. No
|
||||
codeless equivalent was created; the inline stats rendering is fully preserved.
|
||||
|
||||
3. **Footer internal-page links are a placeholder, same as source.** The templ
|
||||
`getLinkURL` emitted `/pages/<page_id>` for a link with `page_id` set, with a
|
||||
source comment "In production, this would be resolved to the actual page
|
||||
slug." The codeless template reproduces `/pages/{{ link.page_id }}` verbatim —
|
||||
it does **not** resolve `page_id` → real slug (the codeless block declares no
|
||||
data providers, and the source didn't resolve it either). Behavior is
|
||||
identical to the original; flagging only because it was already a known
|
||||
limitation.
|
||||
|
||||
4. **No computing logic required code.** All templ helper functions
|
||||
(`gridCols`, `featureGridCols`, `footerGridCols`, `gothamHeadingBaseClass`,
|
||||
`parseHeadingLevel`, the email `gotham*Color` fallbacks) are pure
|
||||
presentational branching and were fully expressed with `{% if %}` /
|
||||
`|default:` in ninjatpl. Nothing in the source needed a wasm guest, so the
|
||||
artifact is genuinely codeless.
|
||||
|
||||
## Not ported (obsolete)
|
||||
|
||||
`port-source/` also contains `embed.go`, `registration.go`, `register.go`,
|
||||
`text_override.go`, `heading_override.go` — Go glue for the bundled/`.so` era.
|
||||
None have a codeless equivalent (their concerns are now declarations in
|
||||
`manifest.yaml` / `blocks.yaml` / `master_pages.json`). Delete `port-source/`
|
||||
once the port is verified.
|
||||
|
||||
## Scope / version
|
||||
|
||||
- `version` starts at **1.0.0** (graduation from bundled).
|
||||
- `scope` set to **@ninja** per the port task. NOTE: the sibling reference themes
|
||||
(`art-deco`, `brutalist`) use `@themes`, and the dev registry has both scopes.
|
||||
Change `plugin.mod` if `@themes` is preferred.
|
||||
42
README.md
42
README.md
@ -1,36 +1,24 @@
|
||||
# Gotham
|
||||
|
||||
Dark, modern BlockNinja theme plugin. Bold typography, high-contrast accents,
|
||||
and a small set of theme-specific blocks (stats, features, footer) plus
|
||||
overrides for the built-in `heading` and `text` blocks when the Gotham
|
||||
template is active.
|
||||
An after-dark theme in near-black with brass hairline accents. Tall condensed headlines sit over letterboxed hero bars, so pages carry the mood of a private members' club after midnight. It reads exclusive and filmic. The look is cinematic luxe, the kind of page that feels like an invitation.
|
||||
|
||||
## Page templates
|
||||
## Good for
|
||||
|
||||
- `default` — header / main / footer
|
||||
- `landing` — hero / main / cta / footer
|
||||
- `full-width` — edge-to-edge header / main / footer
|
||||
- `centered` — narrow centered content for articles and docs
|
||||
We built Gotham for nightlife and hospitality. Treat these as starting points, not limits. If the mood fits your brand, use it:
|
||||
|
||||
## Blocks
|
||||
- Nightclubs and late-night lounges
|
||||
- Private members' clubs
|
||||
- Cocktail bars and speakeasies
|
||||
- Event spaces and ticketed shows
|
||||
|
||||
- `gotham:stats` — stat row with configurable items
|
||||
- `gotham:features` — feature grid
|
||||
- `gotham:footer` — branded footer with optional signup form
|
||||
## What you get
|
||||
|
||||
## Build
|
||||
- Five color presets, each with a tuned light mode and a tuned dark mode: Velvet Black, Midnight Brass, Noir Rose, Smoke & Silver, and Bourbon.
|
||||
- Four bundled fonts, ready to assign: Antonio for condensed display headings, Cormorant Garamond for elegant serif text, Inter for interface copy, and IBM Plex Mono. Fonts stay admin-controlled, so you can swap them from the typography settings.
|
||||
- Four persona blocks: a door strip for guest-list and entry details, an events marquee, a membership-tiers section, and a bottle-service menu.
|
||||
- A demo site that installs when you activate the theme: a home page, an about page, a short blog with three posts, 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.
|
||||
|
||||
```
|
||||
make # builds gotham.so locally
|
||||
make rebuild # rebuilds plugin inside an instance container
|
||||
```
|
||||
## Install
|
||||
|
||||
## Versioning
|
||||
|
||||
```
|
||||
make bump-patch # 0.1.0 -> 0.1.1
|
||||
make bump-minor # 0.1.0 -> 0.2.0
|
||||
make bump-major # 0.1.0 -> 1.0.0
|
||||
```
|
||||
|
||||
Then `git push --tags` and `ninja plugin publish` to release.
|
||||
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 dressed starting point.
|
||||
|
||||
29
RECOMMENDED_FONTS.md
Normal file
29
RECOMMENDED_FONTS.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Recommended fonts — Gotham
|
||||
|
||||
Gotham is admin-font-controlled. Every family is consumed through
|
||||
`var(--font-heading|body|mono, <fallback>)`, so nothing breaks if you assign a
|
||||
different family. These are the intended stacks.
|
||||
|
||||
Four OFL families ship bundled in `assets/fonts/` (see `LICENSES.md`) and appear
|
||||
in the admin font picker as `source=template` rows. Assign them in
|
||||
Admin -> Appearance -> Typography.
|
||||
|
||||
| Role | Variable | Assign | Bundled | Fallback |
|
||||
|------|----------|--------|---------|----------|
|
||||
| Headings | `--font-heading` | **Antonio** (or Oswald / Bebas Neue) | yes | `'Antonio', 'Oswald', 'Bebas Neue', sans-serif` |
|
||||
| Body | `--font-body` | **Cormorant Garamond** | yes | `'Cormorant Garamond', 'Cormorant', Georgia, serif` |
|
||||
| Meta / labels | `--font-body` accents | **Inter** | yes | `Inter, system-ui, sans-serif` |
|
||||
| Mono | `--font-mono` | **IBM Plex Mono** | yes | `'IBM Plex Mono', ui-monospace, monospace` |
|
||||
|
||||
## Why these
|
||||
|
||||
- **Antonio** is a tall condensed sans. It carries the letterboxed marquee
|
||||
headlines. Oswald and Bebas Neue are the intended substitutes if you prefer to
|
||||
assign from the Google Fonts tab.
|
||||
- **Cormorant Garamond** is the elegant serif body voice — the invitation card,
|
||||
not the neon sign.
|
||||
- **Inter** handles small meta: labels, eyebrows, form fields.
|
||||
- **IBM Plex Mono** covers any monospace surface (times, codes, run sheets).
|
||||
|
||||
Keep the condensed display and the serif body paired. That contrast is the
|
||||
theme's typographic signature.
|
||||
21
RECOMMENDED_ICONS.md
Normal file
21
RECOMMENDED_ICONS.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Recommended icons — Gotham
|
||||
|
||||
Gotham renders icons from the sprite system only, as
|
||||
`<svg><use href="/icons/<pack>.svg#<name>"/></svg>` (or the `::pack:name:size::`
|
||||
template shorthand). It never inlines SVG paths.
|
||||
|
||||
## Required pack
|
||||
|
||||
- **Lucide** (`lucide`) — the theme's default pack. Declared in
|
||||
`plugin.mod` `required_icon_packs = ["lucide"]`.
|
||||
|
||||
Lucide ships bundled with the CMS, so no manual install is normally needed. If a
|
||||
theme icon renders empty, open `/admin/icons`, confirm **Lucide** is installed
|
||||
and enabled, then reactivate the theme.
|
||||
|
||||
## Icons the theme references
|
||||
|
||||
`lucide:diamond`, `lucide:arrow-right`, `lucide:check`, `lucide:chevron-right`
|
||||
appear in seed content and block defaults. All resolve from the bundled Lucide
|
||||
pack. Any icon-picker field defaults to a Lucide glyph so the theme renders
|
||||
without further setup.
|
||||
16
assets/fonts/LICENSES.md
Normal file
16
assets/fonts/LICENSES.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Bundled font licenses
|
||||
|
||||
All bundled woff2 files in this directory are latin-subset builds redistributed
|
||||
under the SIL Open Font License 1.1 (OFL). No modifications were made beyond
|
||||
subsetting to the latin range.
|
||||
|
||||
| Family | Files | License | Source |
|
||||
|--------|-------|---------|--------|
|
||||
| Antonio | antonio-latin.woff2, antonio-latin-ext.woff2 | SIL OFL 1.1 | Vernon Adams / Google Fonts |
|
||||
| Cormorant Garamond | cormorant-garamond-400.woff2, cormorant-garamond-400-italic.woff2, cormorant-garamond-600.woff2, cormorant-garamond-700.woff2 | SIL OFL 1.1 | Christian Thalmann / Google Fonts |
|
||||
| Inter | inter-400.woff2, inter-500.woff2, inter-700.woff2 | SIL OFL 1.1 | Rasmus Andersson / Google Fonts |
|
||||
| IBM Plex Mono | ibm-plex-mono-400.woff2, ibm-plex-mono-500.woff2 | SIL OFL 1.1 | IBM / Google Fonts |
|
||||
|
||||
Full OFL text: https://openfontlicense.org/. Font-family is still admin-controlled;
|
||||
these bundled families appear as `source=template` rows in the fonts picker and
|
||||
are consumed only via `var(--font-heading|body|mono, <fallback>)`.
|
||||
BIN
assets/fonts/antonio-latin-ext.woff2
Normal file
BIN
assets/fonts/antonio-latin-ext.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/antonio-latin.woff2
Normal file
BIN
assets/fonts/antonio-latin.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/cormorant-garamond-400-italic.woff2
Normal file
BIN
assets/fonts/cormorant-garamond-400-italic.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/cormorant-garamond-400.woff2
Normal file
BIN
assets/fonts/cormorant-garamond-400.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/cormorant-garamond-600.woff2
Normal file
BIN
assets/fonts/cormorant-garamond-600.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/cormorant-garamond-700.woff2
Normal file
BIN
assets/fonts/cormorant-garamond-700.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/ibm-plex-mono-400.woff2
Normal file
BIN
assets/fonts/ibm-plex-mono-400.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/ibm-plex-mono-500.woff2
Normal file
BIN
assets/fonts/ibm-plex-mono-500.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/inter-400.woff2
Normal file
BIN
assets/fonts/inter-400.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/inter-500.woff2
Normal file
BIN
assets/fonts/inter-500.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/inter-700.woff2
Normal file
BIN
assets/fonts/inter-700.woff2
Normal file
Binary file not shown.
@ -1 +0,0 @@
|
||||
/* placeholder for fonts */
|
||||
@ -1,33 +0,0 @@
|
||||
/* Gotham template styles - uses theme CSS variables */
|
||||
|
||||
/* Accent color classes - mapped to theme primary */
|
||||
.gotham-accent {
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.gotham-accent-bg {
|
||||
background-color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.gotham-accent-bg:hover {
|
||||
background-color: hsl(var(--primary) / 0.9);
|
||||
}
|
||||
|
||||
/* Border accent */
|
||||
.gotham-border-accent {
|
||||
border-color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
/* Additional Gotham-specific utilities */
|
||||
.gotham-gradient {
|
||||
background: linear-gradient(180deg, hsl(var(--background)) 0%, hsl(var(--card)) 100%);
|
||||
}
|
||||
|
||||
.gotham-card {
|
||||
background-color: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.gotham-glow {
|
||||
box-shadow: 0 0 20px hsl(var(--primary) / 0.3);
|
||||
}
|
||||
@ -1,26 +1,32 @@
|
||||
# Gotham theme block definitions (codeless .bnp, WO-WZ-021).
|
||||
# Keys are fully qualified (gotham:<key>) because the codeless loader registers
|
||||
# definitions verbatim — there is no PluginBlockRegistry prefixing in the
|
||||
# codeless path (unlike the wasm/templ path). The qualified keys match the
|
||||
# master_pages.json references.
|
||||
# Gotham theme block definitions (codeless .bnp, WO-TF-018).
|
||||
# Only TRUE persona furniture remains as theme blocks. Everything that
|
||||
# duplicated a builtin (hero, stats, feature grid, footer, gallery, divider,
|
||||
# quote, testimonial...) is now a template OVERRIDE of the builtin key under
|
||||
# templates/overrides/gotham/. Keys are fully qualified (gotham:<key>) — the
|
||||
# codeless loader registers definitions verbatim; the qualified keys match the
|
||||
# data-block attributes, master_pages.json and seed references.
|
||||
blocks:
|
||||
- key: gotham:hero
|
||||
title: Hero Section
|
||||
description: Large hero section with headline and optional CTA
|
||||
schema: hero.schema.json
|
||||
template: hero.ninjatpl
|
||||
- key: gotham:stats
|
||||
title: Stats Counter
|
||||
description: Display statistics with labels and values
|
||||
schema: stats.schema.json
|
||||
template: stats.ninjatpl
|
||||
- key: gotham:features
|
||||
title: Feature Cards
|
||||
description: Grid of feature cards with icons and descriptions
|
||||
schema: features.schema.json
|
||||
template: features.ninjatpl
|
||||
- key: gotham:footer
|
||||
title: Footer
|
||||
description: Multi-column footer with links and copyright
|
||||
schema: footer.schema.json
|
||||
template: footer.ninjatpl
|
||||
- key: gotham:door_strip
|
||||
title: Door Strip
|
||||
description: Sticky near-black door strip with brass rules — reservations line, guest-list CTA and tonight's hours.
|
||||
category: navigation
|
||||
schema: door_strip.schema.json
|
||||
template: door_strip.ninjatpl
|
||||
- key: gotham:events_marquee
|
||||
title: Events Marquee
|
||||
description: Letterboxed line-up of nights and residencies, each with date, room and a booking link.
|
||||
category: content
|
||||
schema: events_marquee.schema.json
|
||||
template: events_marquee.ninjatpl
|
||||
- key: gotham:membership_tiers
|
||||
title: Membership Tiers
|
||||
description: Side-by-side membership cards with a brass-framed feature list and an apply link.
|
||||
category: content
|
||||
schema: membership_tiers.schema.json
|
||||
template: membership_tiers.ninjatpl
|
||||
- key: gotham:bottle_menu
|
||||
title: Bottle Menu
|
||||
description: Cellar and bottle-service list grouped into sections, each pour with a note and price.
|
||||
category: content
|
||||
schema: bottle_menu.schema.json
|
||||
template: bottle_menu.ninjatpl
|
||||
|
||||
29
blocks/bottle_menu.ninjatpl
Normal file
29
blocks/bottle_menu.ninjatpl
Normal file
@ -0,0 +1,29 @@
|
||||
{# bottle_menu — cellar list. sections[] and section.items[] are arrays of maps. #}
|
||||
<section data-block="gotham:bottle_menu" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-4xl px-4">
|
||||
<div class="text-center reveal">
|
||||
{% if eyebrow %}<div class="mb-4 flex items-center justify-center gap-4"><span class="reel-rule w-10"></span><span class="reel-caps text-xs text-primary">{{ eyebrow }}</span><span class="reel-rule w-10"></span></div>{% endif %}
|
||||
{% if title %}<h2 class="reel-caps text-3xl md:text-4xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ title }}</h2>{% endif %}
|
||||
</div>
|
||||
{% if sections %}
|
||||
<div class="mt-12 space-y-12">
|
||||
{% for section in sections %}
|
||||
<div class="reveal">
|
||||
{% if section.title %}<h3 class="reel-caps reel-underline w-fit text-lg text-primary" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);">{{ section.title }}</h3>{% endif %}
|
||||
<ul class="mt-5 space-y-4">
|
||||
{% for pour in section.items %}
|
||||
<li class="flex items-baseline gap-4">
|
||||
<span class="shrink-0" style="font-family:var(--font-body, 'Cormorant Garamond', Georgia, serif);color:hsl(var(--foreground));font-size:1.05rem;">{{ pour.name }}</span>
|
||||
<span class="reel-rule flex-1 translate-y-[-2px]" style="opacity:0.4;"></span>
|
||||
{% if pour.price %}<span class="shrink-0 reel-caps text-sm text-primary">{{ pour.price }}</span>{% endif %}
|
||||
{% if pour.note %}<span class="hidden shrink-0 basis-full text-sm italic sm:block sm:basis-auto" style="color:hsl(var(--muted-foreground));">{{ pour.note }}</span>{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if footnote %}<p class="mt-10 text-center text-sm italic" style="color:hsl(var(--muted-foreground));font-family:var(--font-body, 'Cormorant Garamond', Georgia, serif);">{{ footnote }}</p>{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
36
blocks/bottle_menu.schema.json
Normal file
36
blocks/bottle_menu.schema.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Bottle Menu",
|
||||
"description": "Cellar and bottle-service list grouped into sections.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"eyebrow": { "type": "string", "title": "Eyebrow", "x-editor": "text", "default": "The cellar" },
|
||||
"title": { "type": "string", "title": "Section title", "x-editor": "text", "default": "Bottle service" },
|
||||
"footnote": { "type": "string", "title": "Footnote", "x-editor": "text", "default": "" },
|
||||
"sections": {
|
||||
"type": "array",
|
||||
"title": "Sections",
|
||||
"x-editor": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": { "type": "string", "title": "Section title", "x-editor": "text" },
|
||||
"items": {
|
||||
"type": "array",
|
||||
"title": "Pours",
|
||||
"x-editor": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "title": "Name", "x-editor": "text" },
|
||||
"note": { "type": "string", "title": "Note", "x-editor": "text" },
|
||||
"price": { "type": "string", "title": "Price", "x-editor": "text" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
blocks/door_strip.ninjatpl
Normal file
11
blocks/door_strip.ninjatpl
Normal file
@ -0,0 +1,11 @@
|
||||
{# door_strip — sticky brass-on-black door strip. Iterates hours[] (array of strings). #}
|
||||
<div data-block="gotham:door_strip" class="sticky top-0 z-30 w-full film-grain reel-strip" style="background-color: hsl(var(--foreground)); color: hsl(var(--background));">
|
||||
<div class="max-w-6xl mx-auto px-4 py-2 flex flex-wrap items-center justify-between gap-3 text-sm">
|
||||
<div class="flex items-center gap-3 reel-caps text-xs" style="font-family: var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif); color: hsl(var(--primary));">
|
||||
<span>{{ label|default:"The Door" }}</span>
|
||||
{% if phone %}<span aria-hidden="true" style="color: hsl(var(--primary) / 0.5);">/</span><a href="tel:{{ phone }}" class="reel-link" style="border-bottom-color: hsl(var(--primary));">{{ phone }}</a>{% endif %}
|
||||
</div>
|
||||
{% if hours %}<div class="hidden sm:flex flex-wrap items-center gap-3" style="color: hsl(var(--background));">{% for h in hours %}{% if not forloop.First %}<span aria-hidden="true" style="color: hsl(var(--primary) / 0.5);">/</span>{% endif %}<span>{{ h.line }}</span>{% endfor %}</div>{% endif %}
|
||||
{% if reserveUrl %}<a href="{{ reserveUrl }}" class="reel-btn" style="background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground)); border-color: hsl(var(--primary));">{{ reserveLabel|default:"Guest list" }}</a>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
48
blocks/door_strip.schema.json
Normal file
48
blocks/door_strip.schema.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Door Strip",
|
||||
"description": "Sticky near-black door strip with brass rules, reservations line, guest-list CTA and tonight's hours.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string",
|
||||
"title": "Strip label",
|
||||
"description": "Short label at the head of the strip, e.g. 'The Door'.",
|
||||
"x-editor": "text",
|
||||
"default": "The Door"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string",
|
||||
"title": "Reservations phone",
|
||||
"description": "International-format number for the reservations desk.",
|
||||
"x-editor": "text",
|
||||
"default": ""
|
||||
},
|
||||
"reserveLabel": {
|
||||
"type": "string",
|
||||
"title": "Reserve button label",
|
||||
"x-editor": "text",
|
||||
"default": "Guest list"
|
||||
},
|
||||
"reserveUrl": {
|
||||
"type": "string",
|
||||
"title": "Reserve / guest-list URL",
|
||||
"description": "Link to the booking or guest-list page.",
|
||||
"x-editor": "link",
|
||||
"default": ""
|
||||
},
|
||||
"hours": {
|
||||
"type": "array",
|
||||
"title": "Door hours",
|
||||
"description": "Free-form lines such as 'Thu-Sat - 9pm till late'.",
|
||||
"x-editor": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"line": { "type": "string", "title": "Line", "x-editor": "text" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
blocks/events_marquee.ninjatpl
Normal file
24
blocks/events_marquee.ninjatpl
Normal file
@ -0,0 +1,24 @@
|
||||
{# events_marquee — letterboxed line-up. Iterates events[] (array of maps). #}
|
||||
<section data-block="gotham:events_marquee" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-5xl px-4">
|
||||
<div class="text-center reveal">
|
||||
{% if eyebrow %}<div class="mb-4 flex items-center justify-center gap-4"><span class="reel-rule w-10"></span><span class="reel-caps text-xs text-primary">{{ eyebrow }}</span><span class="reel-rule w-10"></span></div>{% endif %}
|
||||
{% if title %}<h2 class="reel-caps text-3xl md:text-4xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ title }}</h2>{% endif %}
|
||||
</div>
|
||||
{% if events %}
|
||||
<ul class="mt-10 divide-y divide-[hsl(var(--border))] border-y border-[hsl(var(--border))]">
|
||||
{% for event in events %}
|
||||
<li class="reveal grid grid-cols-1 gap-1 py-5 sm:grid-cols-[8rem_1fr_auto] sm:items-baseline sm:gap-6">
|
||||
<span class="reel-caps text-sm text-primary" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);">{{ event.date }}</span>
|
||||
<div>
|
||||
<span class="reel-caps text-lg" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ event.name }}</span>
|
||||
{% if event.room %}<span class="ml-3 text-sm" style="color:hsl(var(--muted-foreground));">{{ event.room }}</span>{% endif %}
|
||||
{% if event.note %}<p class="mt-1 text-sm italic" style="color:hsl(var(--muted-foreground));font-family:var(--font-body, 'Cormorant Garamond', Georgia, serif);">{{ event.note }}</p>{% endif %}
|
||||
</div>
|
||||
{% if event.url %}<a href="{{ event.url }}" class="reel-link inline-flex items-center gap-1 justify-self-start text-sm sm:justify-self-end">Book::lucide:arrow-right:sm::</a>{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
36
blocks/events_marquee.schema.json
Normal file
36
blocks/events_marquee.schema.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Events Marquee",
|
||||
"description": "Letterboxed line-up of nights and residencies.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"eyebrow": {
|
||||
"type": "string",
|
||||
"title": "Eyebrow",
|
||||
"x-editor": "text",
|
||||
"default": "The programme"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"title": "Section title",
|
||||
"x-editor": "text",
|
||||
"default": "Tonight and after"
|
||||
},
|
||||
"events": {
|
||||
"type": "array",
|
||||
"title": "Events",
|
||||
"x-editor": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"date": { "type": "string", "title": "Date", "x-editor": "text" },
|
||||
"name": { "type": "string", "title": "Name", "x-editor": "text" },
|
||||
"room": { "type": "string", "title": "Room", "x-editor": "text" },
|
||||
"note": { "type": "string", "title": "Note", "x-editor": "text" },
|
||||
"url": { "type": "string", "title": "Booking URL", "x-editor": "link" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
{% if features %}<section class="py-20 flex-1"><div class="max-w-6xl mx-auto px-4">{% if section_title %}<h2 class="text-3xl font-bold text-center mb-12 gotham-accent">{{ section_title }}</h2>{% endif %}<div class="grid gap-8 {% if columns|integer == 1 %}grid-cols-1{% elif columns|integer == 2 %}grid-cols-1 md:grid-cols-2{% elif columns|integer == 4 %}grid-cols-1 md:grid-cols-2 lg:grid-cols-4{% else %}grid-cols-1 md:grid-cols-2 lg:grid-cols-3{% endif %}">{% for feature in features %}<div class="p-6 gotham-surface rounded-lg gotham-border border hover:border-primary/30 transition-colors">{% if feature.icon %}<div class="mb-4 gotham-accent">{% if feature.icon == "chart" %}<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path></svg>{% elif feature.icon == "users" %}<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"></path></svg>{% elif feature.icon == "clock" %}<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>{% elif feature.icon == "star" %}<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"></path></svg>{% else %}<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>{% endif %}</div>{% endif %}<h3 class="text-xl font-semibold text-foreground mb-3">{{ feature.title }}</h3>{% if feature.description %}<p class="text-muted-foreground leading-relaxed">{{ feature.description }}</p>{% endif %}</div>{% endfor %}</div></div></section>{% endif %}
|
||||
@ -1,55 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Features Block",
|
||||
"description": "Grid of feature cards with icons and descriptions",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"section_title": {
|
||||
"type": "string",
|
||||
"title": "Section Title",
|
||||
"description": "Optional title above the feature cards",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"columns": {
|
||||
"type": "integer",
|
||||
"title": "Columns",
|
||||
"description": "Number of columns (1-4)",
|
||||
"x-editor": "number",
|
||||
"minimum": 1,
|
||||
"maximum": 4,
|
||||
"default": 3
|
||||
},
|
||||
"features": {
|
||||
"type": "array",
|
||||
"title": "Features",
|
||||
"description": "List of feature cards",
|
||||
"default": [],
|
||||
"x-editor": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"title": "Icon",
|
||||
"description": "Icon name (chart, users, clock, star)",
|
||||
"x-editor": "select",
|
||||
"enum": ["", "chart", "users", "clock", "star"]
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"title": "Title",
|
||||
"description": "Feature title",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"title": "Description",
|
||||
"description": "Feature description",
|
||||
"x-editor": "textarea"
|
||||
}
|
||||
},
|
||||
"required": ["title"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
<div class="max-w-6xl mx-auto px-4">{% if columns %}<div class="grid gap-8 mb-12 {% if columns|length == 1 %}grid-cols-1{% elif columns|length == 2 %}grid-cols-1 md:grid-cols-2{% elif columns|length == 3 %}grid-cols-1 md:grid-cols-3{% else %}grid-cols-1 md:grid-cols-2 lg:grid-cols-4{% endif %}">{% for col in columns %}<div>{% if col.heading %}<h4 class="text-lg font-semibold gotham-accent mb-4">{{ col.heading }}</h4>{% endif %}{% if col.links %}<ul class="space-y-2">{% for link in col.links %}<li><a href="{% if link.page_id %}/pages/{{ link.page_id }}{% elif link.url %}{{ link.url }}{% else %}#{% endif %}" class="text-muted-foreground hover:text-foreground transition-colors">{{ link.text }}</a></li>{% endfor %}</ul>{% endif %}</div>{% endfor %}</div>{% endif %}{% if copyright %}<div class="pt-8 gotham-border border-t text-center"><p class="text-muted-foreground text-sm">{{ copyright }}</p></div>{% endif %}</div>
|
||||
@ -1,63 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Footer Block",
|
||||
"description": "Multi-column footer with links and copyright",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"copyright": {
|
||||
"type": "string",
|
||||
"title": "Copyright Text",
|
||||
"description": "Copyright notice displayed at the bottom",
|
||||
"default": "© 2025 Your Company. All rights reserved.",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"columns": {
|
||||
"type": "array",
|
||||
"title": "Footer Columns",
|
||||
"description": "Columns with headings and links",
|
||||
"x-editor": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"heading": {
|
||||
"type": "string",
|
||||
"title": "Column Heading",
|
||||
"description": "Title for this column",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"links": {
|
||||
"type": "array",
|
||||
"title": "Links",
|
||||
"description": "Links in this column",
|
||||
"x-editor": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"title": "Link Text",
|
||||
"description": "Display text for the link",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"page_id": {
|
||||
"type": "string",
|
||||
"title": "Page",
|
||||
"description": "Select an internal page (overrides URL)",
|
||||
"x-editor": "page-select"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"title": "External URL",
|
||||
"description": "External URL (used if no page selected)",
|
||||
"x-editor": "url"
|
||||
}
|
||||
},
|
||||
"required": ["text"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["heading"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
<section class="relative py-32 overflow-hidden">{% if background_url %}<div class="absolute inset-0 z-0"><img src="{{ background_url }}" alt="" class="w-full h-full object-cover opacity-20"><div class="absolute inset-0 bg-gradient-to-b from-background/80 via-background/60 to-background"></div></div>{% endif %}<div class="relative z-10 max-w-4xl mx-auto px-4 text-center"><h1 class="text-5xl md:text-6xl font-bold mb-6"><span class="gotham-accent">{{ headline }}</span></h1>{% if subheadline %}<p class="text-xl md:text-2xl text-muted-foreground mb-10 max-w-2xl mx-auto">{{ subheadline }}</p>{% endif %}{% if cta_text and cta_url %}<a href="{{ cta_url }}" class="inline-block px-8 py-4 gotham-accent-bg text-primary-foreground font-semibold rounded-lg transition-colors text-lg">{{ cta_text }}</a>{% endif %}</div></section>
|
||||
@ -1,39 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Hero Block",
|
||||
"description": "Large hero section with headline and optional CTA",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"headline": {
|
||||
"type": "string",
|
||||
"title": "Headline",
|
||||
"description": "Main headline text",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"subheadline": {
|
||||
"type": "string",
|
||||
"title": "Subheadline",
|
||||
"description": "Supporting text below the headline",
|
||||
"x-editor": "textarea"
|
||||
},
|
||||
"background_url": {
|
||||
"type": "string",
|
||||
"title": "Background Image URL",
|
||||
"description": "URL for the background image",
|
||||
"x-editor": "url"
|
||||
},
|
||||
"cta_text": {
|
||||
"type": "string",
|
||||
"title": "CTA Button Text",
|
||||
"description": "Text for the call-to-action button",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"cta_url": {
|
||||
"type": "string",
|
||||
"title": "CTA Button URL",
|
||||
"description": "Link for the call-to-action button",
|
||||
"x-editor": "url"
|
||||
}
|
||||
},
|
||||
"required": ["headline"]
|
||||
}
|
||||
35
blocks/membership_tiers.ninjatpl
Normal file
35
blocks/membership_tiers.ninjatpl
Normal file
@ -0,0 +1,35 @@
|
||||
{# membership_tiers — brass-framed membership cards. tiers[] and tier.features[] are arrays of maps. #}
|
||||
<section data-block="gotham:membership_tiers" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-6xl px-4">
|
||||
<div class="text-center reveal">
|
||||
{% if eyebrow %}<div class="mb-4 flex items-center justify-center gap-4"><span class="reel-rule w-10"></span><span class="reel-caps text-xs text-primary">{{ eyebrow }}</span><span class="reel-rule w-10"></span></div>{% endif %}
|
||||
{% if title %}<h2 class="reel-caps text-3xl md:text-4xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ title }}</h2>{% endif %}
|
||||
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-lg italic" style="color:hsl(var(--muted-foreground));font-family:var(--font-body, 'Cormorant Garamond', Georgia, serif);">{{ intro }}</p>{% endif %}
|
||||
</div>
|
||||
{% if tiers %}
|
||||
<div class="mt-12 grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{% for tier in tiers %}
|
||||
<article class="reveal reel-frame flex flex-col bg-card text-card-foreground {% if tier.featured %}reel-foil{% endif %}">
|
||||
<div class="flex items-baseline justify-between gap-2">
|
||||
<h3 class="reel-caps text-xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ tier.name }}</h3>
|
||||
{% if tier.featured %}<span class="reel-caps text-[0.6rem] text-primary">House pick</span>{% endif %}
|
||||
</div>
|
||||
<div class="mt-3 flex items-baseline gap-1">
|
||||
<span class="text-3xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--primary));">{{ tier.price }}</span>
|
||||
{% if tier.cadence %}<span class="text-sm" style="color:hsl(var(--muted-foreground));">{{ tier.cadence }}</span>{% endif %}
|
||||
</div>
|
||||
{% if tier.description %}<p class="mt-3 text-sm leading-relaxed" style="color:hsl(var(--muted-foreground));font-family:var(--font-body, 'Cormorant Garamond', Georgia, serif);">{{ tier.description }}</p>{% endif %}
|
||||
{% if tier.features %}
|
||||
<ul class="mt-5 space-y-2 border-t border-[hsl(var(--border))] pt-5 text-sm">
|
||||
{% for feature in tier.features %}
|
||||
<li class="flex items-start gap-2"><span class="mt-0.5 shrink-0 text-primary">::lucide:check:sm::</span><span style="color:hsl(var(--foreground));">{{ feature.label }}</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if tier.applyUrl %}<a href="{{ tier.applyUrl }}" class="reel-btn mt-6 justify-center">{{ tier.applyLabel|default:"Apply" }}</a>{% endif %}
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
40
blocks/membership_tiers.schema.json
Normal file
40
blocks/membership_tiers.schema.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Membership Tiers",
|
||||
"description": "Side-by-side membership cards with a brass-framed feature list.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"eyebrow": { "type": "string", "title": "Eyebrow", "x-editor": "text", "default": "Membership" },
|
||||
"title": { "type": "string", "title": "Section title", "x-editor": "text", "default": "Join the house" },
|
||||
"intro": { "type": "string", "title": "Intro", "x-editor": "textarea", "default": "" },
|
||||
"tiers": {
|
||||
"type": "array",
|
||||
"title": "Tiers",
|
||||
"x-editor": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "title": "Name", "x-editor": "text" },
|
||||
"price": { "type": "string", "title": "Price", "x-editor": "text" },
|
||||
"cadence": { "type": "string", "title": "Cadence", "x-editor": "text" },
|
||||
"description": { "type": "string", "title": "Description", "x-editor": "textarea" },
|
||||
"featured": { "type": "boolean", "title": "Featured", "x-editor": "boolean", "default": false },
|
||||
"applyLabel": { "type": "string", "title": "Apply label", "x-editor": "text" },
|
||||
"applyUrl": { "type": "string", "title": "Apply URL", "x-editor": "link" },
|
||||
"features": {
|
||||
"type": "array",
|
||||
"title": "Features",
|
||||
"x-editor": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": { "type": "string", "title": "Label", "x-editor": "text" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
{% if items %}<section class="py-16 gotham-surface flex-1"><div class="max-w-6xl mx-auto px-4"><div class="grid gap-8 {% if items|length == 1 %}grid-cols-1{% elif items|length == 2 %}grid-cols-1 md:grid-cols-2{% elif items|length == 3 %}grid-cols-1 md:grid-cols-3{% else %}grid-cols-1 md:grid-cols-2 lg:grid-cols-4{% endif %}">{% for stat in items %}<div class="text-center p-6 rounded-lg gotham-border border">{% if stat.icon %}<div class="mb-3 gotham-accent">{% if stat.icon == "chart" %}<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path></svg>{% elif stat.icon == "users" %}<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"></path></svg>{% elif stat.icon == "clock" %}<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>{% elif stat.icon == "star" %}<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"></path></svg>{% else %}<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>{% endif %}</div>{% endif %}<div class="text-4xl font-bold gotham-accent mb-2">{{ stat.value }}</div><div class="text-muted-foreground uppercase text-sm tracking-wider">{{ stat.label }}</div></div>{% endfor %}</div></div></section>{% else %}<section class="py-16 gotham-surface flex-1"><div class="max-w-6xl mx-auto px-4"><div class="text-center p-8 border border-dashed border-border rounded-lg"><p class="text-muted-foreground">Click + to add stat items</p></div></div></section>{% endif %}
|
||||
@ -1,40 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Stats Block",
|
||||
"description": "Display statistics with labels and values",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"title": "Statistics",
|
||||
"description": "List of statistic items to display",
|
||||
"x-editor": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "string",
|
||||
"title": "Value",
|
||||
"description": "The statistic value (e.g., '100+', '99%')",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"title": "Label",
|
||||
"description": "Description of the statistic",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"title": "Icon",
|
||||
"description": "Icon name (chart, users, clock, star)",
|
||||
"x-editor": "select",
|
||||
"enum": ["", "chart", "users", "clock", "star"]
|
||||
}
|
||||
},
|
||||
"required": ["value", "label"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
114
fonts.json
114
fonts.json
@ -1,88 +1,38 @@
|
||||
[
|
||||
{
|
||||
"name": "Gotham",
|
||||
"family": "Gotham",
|
||||
"name": "Antonio",
|
||||
"family": "Antonio",
|
||||
"variants": [
|
||||
{
|
||||
"weight": "100",
|
||||
"style": "normal",
|
||||
"file": "fonts/web/gothamthin-webfont.woff2"
|
||||
},
|
||||
{
|
||||
"weight": "100",
|
||||
"style": "italic",
|
||||
"file": "fonts/web/gothamthinitalic-webfont.woff2"
|
||||
},
|
||||
{
|
||||
"weight": "200",
|
||||
"style": "normal",
|
||||
"file": "fonts/web/gothamxlight-webfont.woff2"
|
||||
},
|
||||
{
|
||||
"weight": "200",
|
||||
"style": "italic",
|
||||
"file": "fonts/web/gothamxlightitalic-webfont.woff2"
|
||||
},
|
||||
{
|
||||
"weight": "300",
|
||||
"style": "normal",
|
||||
"file": "fonts/web/gothamlight-webfont.woff2"
|
||||
},
|
||||
{
|
||||
"weight": "300",
|
||||
"style": "italic",
|
||||
"file": "fonts/web/gothamlightitalic-webfont.woff2"
|
||||
},
|
||||
{
|
||||
"weight": "400",
|
||||
"style": "normal",
|
||||
"file": "fonts/web/gothambook-webfont.woff2"
|
||||
},
|
||||
{
|
||||
"weight": "400",
|
||||
"style": "italic",
|
||||
"file": "fonts/web/gothambookitalic-webfont.woff2"
|
||||
},
|
||||
{
|
||||
"weight": "500",
|
||||
"style": "normal",
|
||||
"file": "fonts/web/gothammedium-webfont.woff2"
|
||||
},
|
||||
{
|
||||
"weight": "500",
|
||||
"style": "italic",
|
||||
"file": "fonts/web/gothammediumitalic-webfont.woff2"
|
||||
},
|
||||
{
|
||||
"weight": "700",
|
||||
"style": "normal",
|
||||
"file": "fonts/web/gothambold-webfont.woff2"
|
||||
},
|
||||
{
|
||||
"weight": "700",
|
||||
"style": "italic",
|
||||
"file": "fonts/web/gothambolditalic-webfont.woff2"
|
||||
},
|
||||
{
|
||||
"weight": "800",
|
||||
"style": "normal",
|
||||
"file": "fonts/web/gothamblack-webfont.woff2"
|
||||
},
|
||||
{
|
||||
"weight": "800",
|
||||
"style": "italic",
|
||||
"file": "fonts/web/gothamblackitalic-webfont.woff2"
|
||||
},
|
||||
{
|
||||
"weight": "900",
|
||||
"style": "normal",
|
||||
"file": "fonts/web/gothamultra-webfont.woff2"
|
||||
},
|
||||
{
|
||||
"weight": "900",
|
||||
"style": "italic",
|
||||
"file": "fonts/web/gothamultraitalic-webfont.woff2"
|
||||
}
|
||||
{ "weight": "400", "style": "normal", "file": "fonts/antonio-latin.woff2" },
|
||||
{ "weight": "600", "style": "normal", "file": "fonts/antonio-latin.woff2" },
|
||||
{ "weight": "700", "style": "normal", "file": "fonts/antonio-latin.woff2" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Cormorant Garamond",
|
||||
"family": "Cormorant Garamond",
|
||||
"variants": [
|
||||
{ "weight": "400", "style": "normal", "file": "fonts/cormorant-garamond-400.woff2" },
|
||||
{ "weight": "400", "style": "italic", "file": "fonts/cormorant-garamond-400-italic.woff2" },
|
||||
{ "weight": "600", "style": "normal", "file": "fonts/cormorant-garamond-600.woff2" },
|
||||
{ "weight": "700", "style": "normal", "file": "fonts/cormorant-garamond-700.woff2" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Inter",
|
||||
"family": "Inter",
|
||||
"variants": [
|
||||
{ "weight": "400", "style": "normal", "file": "fonts/inter-400.woff2" },
|
||||
{ "weight": "500", "style": "normal", "file": "fonts/inter-500.woff2" },
|
||||
{ "weight": "700", "style": "normal", "file": "fonts/inter-700.woff2" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "IBM Plex Mono",
|
||||
"family": "IBM Plex Mono",
|
||||
"variants": [
|
||||
{ "weight": "400", "style": "normal", "file": "fonts/ibm-plex-mono-400.woff2" },
|
||||
{ "weight": "500", "style": "normal", "file": "fonts/ibm-plex-mono-500.woff2" }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
223
manifest.yaml
223
manifest.yaml
@ -1,9 +1,10 @@
|
||||
# Gotham theme — codeless .bnp manifest (WO-WZ-021 Phase B).
|
||||
# Gotham theme — codeless .bnp manifest (WO-TF-018, Wave A).
|
||||
# Synthesized into manifest.pb by `ninja plugin build` (no plugin.wasm).
|
||||
#
|
||||
# No `css:` section: the wasm/templ Gotham shipped no CSSManifest — its utility
|
||||
# CSS lives in assets/style.css, which the host serves as a static asset and
|
||||
# links into <head> automatically (head_html) when assets/style.css exists.
|
||||
# Nightlife / private-club identity: cinematic near-black luxe, brass hairline
|
||||
# accents, letterboxed heroes, tall condensed display type. Utility CSS lives in
|
||||
# `css.input_css_append` below (host Tailwind pipeline) — colour is always
|
||||
# hsl(var(--token)) and font-family always var(--font-heading|body|mono, ...).
|
||||
theme_presets: presets.json
|
||||
bundled_fonts: fonts.json
|
||||
master_pages: master_pages.json
|
||||
@ -11,33 +12,231 @@ master_pages: master_pages.json
|
||||
system_templates:
|
||||
- key: gotham
|
||||
title: Gotham
|
||||
description: "Dark, modern theme with bold typography"
|
||||
description: "After-dark identity for a nightlife venue or private members' club — cinematic near-black luxe, brass accents and condensed display type."
|
||||
|
||||
page_templates:
|
||||
- system: gotham
|
||||
key: default
|
||||
title: Default
|
||||
description: "Standard dark page layout with header, main content, and footer"
|
||||
description: "Centred near-black page with brass rails, header, main and footer."
|
||||
slots: [header, main, footer]
|
||||
- system: gotham
|
||||
key: landing
|
||||
title: Landing Page
|
||||
description: "Full-width hero sections, ideal for marketing pages"
|
||||
title: Marquee Landing
|
||||
description: "Letterboxed hero, door strip and a closing CTA for the main room."
|
||||
slots: [hero, main, cta, footer]
|
||||
- system: gotham
|
||||
key: article
|
||||
title: Feature
|
||||
description: "Narrow editorial column for press, listings and the house journal."
|
||||
slots: [header, main, aside, footer]
|
||||
- system: gotham
|
||||
key: full-width
|
||||
title: Full Width
|
||||
description: "Edge-to-edge content without max-width constraints"
|
||||
title: Grand Room
|
||||
description: "Edge-to-edge gallery layout for the room, lookbooks and galleries."
|
||||
slots: [header, main, footer]
|
||||
- system: gotham
|
||||
key: blog-index
|
||||
title: The Journal
|
||||
description: "Blog index with a brass masthead, featured and post-grid slots."
|
||||
slots: [header, featured, main, footer]
|
||||
- system: gotham
|
||||
key: contact
|
||||
title: Reservations
|
||||
description: "Contact layout with an optional aside for hours and the door."
|
||||
slots: [header, main, aside, footer]
|
||||
- system: gotham
|
||||
key: auth
|
||||
title: Members
|
||||
description: "Centred members' shell with wordmark and a spotlight crest."
|
||||
slots: [main, footer]
|
||||
- system: gotham
|
||||
key: centered
|
||||
title: Centered Content
|
||||
description: "Narrow, centered content for articles and documentation"
|
||||
title: Centered
|
||||
description: "Narrow centred column for documents and single statements."
|
||||
slots: [header, main, footer]
|
||||
|
||||
template_overrides:
|
||||
# Chrome (4)
|
||||
- { template: gotham, block: navbar }
|
||||
- { template: gotham, block: footer }
|
||||
- { template: gotham, block: utility-bar }
|
||||
- { template: gotham, block: announcement-bar }
|
||||
# Marketing (12)
|
||||
- { template: gotham, block: hero }
|
||||
- { template: gotham, block: feature-grid }
|
||||
- { template: gotham, block: rich-section }
|
||||
- { template: gotham, block: stats }
|
||||
- { template: gotham, block: testimonial }
|
||||
- { template: gotham, block: logo-strip }
|
||||
- { template: gotham, block: pricing }
|
||||
- { template: gotham, block: team }
|
||||
- { template: gotham, block: faq }
|
||||
- { template: gotham, block: timeline }
|
||||
- { template: gotham, block: cta }
|
||||
- { template: gotham, block: countdown }
|
||||
# Primitives (11)
|
||||
- { template: gotham, block: heading }
|
||||
- { template: gotham, block: text }
|
||||
- { template: gotham, block: rich-text }
|
||||
- { template: gotham, block: image }
|
||||
- { template: gotham, block: quote }
|
||||
- { template: gotham, block: divider }
|
||||
- { template: gotham, block: button }
|
||||
- { template: gotham, block: card }
|
||||
- { template: gotham, block: gallery }
|
||||
- { template: gotham, block: video-embed }
|
||||
- { template: gotham, block: audio-embed }
|
||||
# Commerce / local (6)
|
||||
- { template: gotham, block: menu-list }
|
||||
- { template: gotham, block: event-list }
|
||||
- { template: gotham, block: contact-form }
|
||||
- { template: gotham, block: contact-card }
|
||||
- { template: gotham, block: hours-location }
|
||||
- { template: gotham, block: social-links }
|
||||
# Blog family (12)
|
||||
- { template: gotham, block: blog-hero }
|
||||
- { template: gotham, block: featured-posts }
|
||||
- { template: gotham, block: related-posts }
|
||||
- { template: gotham, block: popular-posts }
|
||||
- { template: gotham, block: category-list }
|
||||
- { template: gotham, block: post-hero }
|
||||
- { template: gotham, block: post-metadata }
|
||||
- { template: gotham, block: author-bio }
|
||||
- { template: gotham, block: author-bio-hero }
|
||||
- { template: gotham, block: newsletter-signup }
|
||||
- { template: gotham, block: breadcrumbs }
|
||||
- { template: gotham, block: sidebar-nav }
|
||||
# Auth surface (3)
|
||||
- { template: gotham, block: auth-form }
|
||||
- { template: gotham, block: password-reset-form }
|
||||
- { template: gotham, block: auth-status }
|
||||
# System (1)
|
||||
- { template: gotham, block: page-suggestions }
|
||||
|
||||
email_wrappers:
|
||||
- gotham
|
||||
|
||||
css:
|
||||
input_css_append: |
|
||||
/* Gotham theme — cinematic near-black utility CSS.
|
||||
*
|
||||
* Colour is always consumed via hsl(var(--token)); font families always
|
||||
* come from var(--font-heading|body|mono, <fallback>). No literal colours
|
||||
* except black scrims layered over IMAGES (letterbox effect).
|
||||
*/
|
||||
@layer utilities {
|
||||
/* reel-rule — brass hairline, 1px ascending to 2px. */
|
||||
.reel-rule { border: none; border-top: 1px solid hsl(var(--primary)); margin: 0; }
|
||||
.reel-rule-thick { border: none; border-top: 2px solid hsl(var(--primary)); margin: 0; }
|
||||
|
||||
/* reel-frame — thin film frame with brass corner ticks. */
|
||||
.reel-frame { position: relative; border: 1px solid hsl(var(--border)); padding: 1.25rem 1.5rem; }
|
||||
.reel-frame::before, .reel-frame::after {
|
||||
content: ""; position: absolute; width: 14px; height: 14px;
|
||||
border-color: hsl(var(--primary)); border-style: solid; pointer-events: none;
|
||||
}
|
||||
.reel-frame::before { top: -1px; left: -1px; border-width: 1px 0 0 1px; }
|
||||
.reel-frame::after { bottom: -1px; right: -1px; border-width: 0 1px 1px 0; }
|
||||
|
||||
/* reel-foil — inset brass glow used on hover / featured states. */
|
||||
.reel-foil {
|
||||
box-shadow:
|
||||
inset 0 0 0 1px hsl(var(--primary)),
|
||||
inset 0 1px 0 hsl(var(--primary) / 0.5),
|
||||
inset 0 -1px 0 hsl(var(--primary) / 0.3);
|
||||
}
|
||||
|
||||
/* reel-caps — condensed caps tracking for display headings. */
|
||||
.reel-caps { text-transform: uppercase; letter-spacing: 0.12em; }
|
||||
|
||||
/* reel-underline — brass hairline underline on headings. */
|
||||
.reel-underline { position: relative; padding-bottom: 0.35em; }
|
||||
.reel-underline::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: hsl(var(--primary)); }
|
||||
|
||||
/* reel-btn — the invitation. Brass on near-black, inverts on hover. */
|
||||
.reel-btn {
|
||||
display: inline-flex; align-items: center; gap: 0.5rem;
|
||||
border: 1px solid hsl(var(--primary));
|
||||
background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground));
|
||||
padding: 0.7rem 1.7rem; border-radius: 2px;
|
||||
text-transform: uppercase; letter-spacing: 0.14em; font-size: 0.8rem; line-height: 1;
|
||||
transition: background-color 200ms ease, color 200ms ease, box-shadow 200ms ease;
|
||||
}
|
||||
.reel-btn:hover, .reel-btn:focus-visible {
|
||||
background-color: transparent; color: hsl(var(--primary));
|
||||
box-shadow: inset 0 0 0 1px hsl(var(--primary));
|
||||
}
|
||||
.reel-btn:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 3px; }
|
||||
|
||||
/* reel-link — brass underline link. */
|
||||
.reel-link { color: hsl(var(--primary)); text-decoration: none; border-bottom: 1px solid hsl(var(--primary) / 0.5); transition: color 200ms ease, border-color 200ms ease; }
|
||||
.reel-link:hover, .reel-link:focus-visible { color: hsl(var(--accent)); border-bottom-color: hsl(var(--accent)); }
|
||||
|
||||
/* film-grain — 4% film-grain noise, dark surfaces. */
|
||||
.film-grain { position: relative; isolation: isolate; }
|
||||
.film-grain::after {
|
||||
content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none; opacity: 0.04;
|
||||
background-image:
|
||||
repeating-radial-gradient(circle at 17% 23%, hsl(var(--foreground)) 0 1px, transparent 1px 3px),
|
||||
repeating-radial-gradient(circle at 73% 61%, hsl(var(--foreground)) 0 1px, transparent 1px 3px);
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
|
||||
/* spotlight — a radial pool of brass light, hero/crest overlay. */
|
||||
.spotlight { background-image: radial-gradient(ellipse 60% 55% at 50% 40%, hsl(var(--primary) / 0.16), transparent 70%); }
|
||||
|
||||
/* reel-strip — brass double-rule band (crest / marquee accent). */
|
||||
.reel-strip {
|
||||
background-image:
|
||||
linear-gradient(hsl(var(--primary)), hsl(var(--primary))),
|
||||
linear-gradient(hsl(var(--primary)), hsl(var(--primary)));
|
||||
background-size: 100% 1px, 100% 1px;
|
||||
background-position: top, bottom;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
/* reel-mat — brass inner-mat around framed media. */
|
||||
.reel-mat { position: relative; padding: 4px; box-shadow: inset 0 0 0 1px hsl(var(--border)); }
|
||||
.reel-mat > * { display: block; width: 100%; height: 100%; }
|
||||
|
||||
/* reel-dropcap — condensed brass dropcap on first paragraph. */
|
||||
.reel-dropcap[data-deco-dropcap]::first-letter,
|
||||
.reel-dropcap[data-reel-dropcap]::first-letter {
|
||||
font-family: var(--font-heading, "Antonio", "Oswald", "Bebas Neue", sans-serif);
|
||||
font-size: 3.2em; line-height: 0.9; float: left; padding: 0.02em 0.14em 0 0; color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
/* letterbox-scrim — black cinematic bars over hero imagery (image overlay). */
|
||||
.letterbox-scrim {
|
||||
background-image: linear-gradient(to bottom,
|
||||
hsl(0 0% 0% / 0.78) 0%, hsl(0 0% 0% / 0.15) 16%,
|
||||
transparent 40%, transparent 60%,
|
||||
hsl(0 0% 0% / 0.15) 84%, hsl(0 0% 0% / 0.78) 100%);
|
||||
}
|
||||
|
||||
/* reveal — MID-tier scroll reveal. No JS = visible; reduced-motion honoured. */
|
||||
.reveal { opacity: 1; }
|
||||
.gotham-js .reveal { opacity: 0; transform: translateY(16px); transition: opacity .5s ease, transform .5s ease; }
|
||||
.gotham-js .reveal.is-visible { opacity: 1; transform: none; }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.gotham-js .reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
|
||||
}
|
||||
}
|
||||
|
||||
/* Raw CSS (outside @layer, JIT-scan-independent) — layout guarantees the
|
||||
* host Tailwind scan may not reliably emit from theme .ninjatpl files.
|
||||
* Imageless hero: a compact letterboxed text plate, never a 75vh void. */
|
||||
.gotham-hero-plate { min-height: 520px; }
|
||||
@media (max-width: 640px) { .gotham-hero-plate { min-height: 380px; } }
|
||||
|
||||
/* Mobile collapse safety net — guarantee persona/override grids fall to a
|
||||
* single column at ~<=640px even if the responsive prefix classes are not
|
||||
* compiled from the theme templates. Base is already 1-col in the markup;
|
||||
* this hard-forces it so a dropped sm:/md:/lg: prefix can never strand a
|
||||
* wide multi-column grid on a phone (no horizontal overflow). */
|
||||
@media (max-width: 640px) {
|
||||
[data-block="gotham:events_marquee"] ul > li { grid-template-columns: 1fr !important; }
|
||||
[data-block="gotham:membership_tiers"] .grid,
|
||||
[data-block-override="gotham:feature-grid"] .grid { grid-template-columns: 1fr !important; }
|
||||
}
|
||||
|
||||
@ -2,11 +2,22 @@
|
||||
{
|
||||
"key": "gotham:default-master",
|
||||
"title": "Gotham Default Master",
|
||||
"page_templates": ["default", "centered"],
|
||||
"page_templates": ["default", "article", "blog-index", "contact", "centered", "full-width"],
|
||||
"blocks": [
|
||||
{ "block_key": "navbar", "title": "Main Navigation", "content": { "menuName": "main" }, "slot": "header", "sort_order": 0 },
|
||||
{ "block_key": "slot", "title": "Main Content Slot", "content": { "slotName": "main", "placeholder": "Page content will appear here" }, "slot": "main", "sort_order": 0 },
|
||||
{ "block_key": "gotham:footer", "title": "Site Footer", "content": { "showSignup": true }, "slot": "footer", "sort_order": 0 }
|
||||
{ "block_key": "navbar", "title": "House Navigation", "content": { "menuName": "main", "logoType": "text", "logoText": { "text": "Gotham House" } }, "slot": "header", "sort_order": 0 },
|
||||
{ "block_key": "slot", "title": "Main Content", "content": { "slotName": "main", "placeholder": "Page content" }, "slot": "main", "sort_order": 0 },
|
||||
{ "block_key": "footer", "title": "Site Footer", "content": { "layout": "centered", "companyName": "Gotham House", "tagline": "A members' club after dark." }, "slot": "footer", "sort_order": 0 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "gotham:marquee-master",
|
||||
"title": "Gotham Marquee Master",
|
||||
"page_templates": ["landing"],
|
||||
"blocks": [
|
||||
{ "block_key": "navbar", "title": "House Navigation", "content": { "menuName": "main", "logoType": "text", "logoText": { "text": "Gotham House" } }, "slot": "hero", "sort_order": 0 },
|
||||
{ "block_key": "gotham:door_strip", "title": "Door Strip", "content": { "label": "The Door", "phone": "", "reserveLabel": "Guest list", "reserveUrl": "/contact", "hours": [] }, "slot": "hero", "sort_order": 1 },
|
||||
{ "block_key": "slot", "title": "Main Content", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
|
||||
{ "block_key": "footer", "title": "Site Footer", "content": { "layout": "centered", "companyName": "Gotham House", "tagline": "A members' club after dark." }, "slot": "footer", "sort_order": 0 }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
name = "gotham"
|
||||
display_name = "Gotham"
|
||||
scope = "@themes"
|
||||
version = "1.0.0"
|
||||
description = "Dark, high-contrast BlockNinja theme with bold typography and modern accent panels."
|
||||
version = "1.0.1"
|
||||
description = "After-dark identity for a nightlife venue or private members' club. Cinematic near-black luxe, brass hairline accents, letterboxed heroes and tall condensed display type."
|
||||
kind = "theme"
|
||||
categories = ["templates"]
|
||||
tags = ["dark", "high-contrast", "agency", "blog", "professional", "modern", "serif"]
|
||||
tags = ["dark", "nightlife", "luxury", "club", "cinematic", "brass", "condensed", "hospitality", "events"]
|
||||
|
||||
680
presets.json
680
presets.json
@ -1,502 +1,252 @@
|
||||
[
|
||||
{
|
||||
"id": "dark-knight",
|
||||
"name": "The Dark Knight",
|
||||
"description": "Classic Gotham - dark with amber accents",
|
||||
"id": "velvet-black",
|
||||
"name": "Velvet Black",
|
||||
"description": "Deep matte black lifted by brass gilt. The house palette. Silver-screen platinum by day, velvet black after dark.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"lightColors": {
|
||||
"background": "0 0% 100%",
|
||||
"foreground": "0 0% 3.9%",
|
||||
"card": "0 0% 100%",
|
||||
"cardForeground": "0 0% 3.9%",
|
||||
"popover": "0 0% 100%",
|
||||
"popoverForeground": "0 0% 3.9%",
|
||||
"primary": "45 93% 47%",
|
||||
"primaryForeground": "0 0% 0%",
|
||||
"secondary": "0 0% 96%",
|
||||
"secondaryForeground": "0 0% 9%",
|
||||
"muted": "0 0% 96%",
|
||||
"mutedForeground": "0 0% 45%",
|
||||
"accent": "45 93% 47%",
|
||||
"accentForeground": "0 0% 0%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "0 0% 90%",
|
||||
"input": "0 0% 90%",
|
||||
"ring": "45 93% 47%"
|
||||
"background": "40 22% 95%",
|
||||
"foreground": "30 10% 12%",
|
||||
"card": "40 24% 98%",
|
||||
"cardForeground": "30 10% 12%",
|
||||
"popover": "40 24% 98%",
|
||||
"popoverForeground": "30 10% 12%",
|
||||
"primary": "38 58% 42%",
|
||||
"primaryForeground": "40 30% 96%",
|
||||
"secondary": "40 12% 88%",
|
||||
"secondaryForeground": "30 12% 16%",
|
||||
"muted": "40 12% 90%",
|
||||
"mutedForeground": "30 8% 40%",
|
||||
"accent": "36 62% 48%",
|
||||
"accentForeground": "40 30% 96%",
|
||||
"destructive": "0 68% 44%",
|
||||
"destructiveForeground": "40 26% 96%",
|
||||
"border": "40 18% 78%",
|
||||
"input": "40 16% 82%",
|
||||
"ring": "38 58% 44%"
|
||||
},
|
||||
"darkColors": {
|
||||
"background": "0 0% 3.9%",
|
||||
"foreground": "0 0% 98%",
|
||||
"card": "0 0% 6%",
|
||||
"cardForeground": "0 0% 98%",
|
||||
"popover": "0 0% 6%",
|
||||
"popoverForeground": "0 0% 98%",
|
||||
"primary": "45 93% 47%",
|
||||
"primaryForeground": "0 0% 0%",
|
||||
"secondary": "0 0% 10%",
|
||||
"secondaryForeground": "0 0% 98%",
|
||||
"muted": "0 0% 10%",
|
||||
"mutedForeground": "0 0% 60%",
|
||||
"accent": "45 93% 47%",
|
||||
"accentForeground": "0 0% 0%",
|
||||
"destructive": "0 62% 30%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "0 0% 14%",
|
||||
"input": "0 0% 14%",
|
||||
"ring": "45 93% 47%"
|
||||
},
|
||||
"mode": "dark"
|
||||
"background": "30 9% 5%",
|
||||
"foreground": "40 28% 90%",
|
||||
"card": "30 9% 8%",
|
||||
"cardForeground": "40 28% 90%",
|
||||
"popover": "30 9% 8%",
|
||||
"popoverForeground": "40 28% 90%",
|
||||
"primary": "40 66% 54%",
|
||||
"primaryForeground": "30 22% 6%",
|
||||
"secondary": "30 8% 14%",
|
||||
"secondaryForeground": "40 24% 86%",
|
||||
"muted": "30 7% 12%",
|
||||
"mutedForeground": "40 12% 62%",
|
||||
"accent": "38 74% 60%",
|
||||
"accentForeground": "30 22% 6%",
|
||||
"destructive": "0 70% 50%",
|
||||
"destructiveForeground": "40 26% 95%",
|
||||
"border": "40 24% 20%",
|
||||
"input": "40 18% 16%",
|
||||
"ring": "40 66% 54%"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "joker",
|
||||
"name": "The Joker",
|
||||
"description": "Chaotic purple and toxic green",
|
||||
"id": "midnight-brass",
|
||||
"name": "Midnight Brass",
|
||||
"description": "Cool blue-black steel with bright brass. A colder, later room.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"lightColors": {
|
||||
"background": "0 0% 100%",
|
||||
"foreground": "270 50% 15%",
|
||||
"card": "0 0% 98%",
|
||||
"cardForeground": "270 50% 15%",
|
||||
"popover": "0 0% 98%",
|
||||
"popoverForeground": "270 50% 15%",
|
||||
"primary": "120 80% 35%",
|
||||
"primaryForeground": "0 0% 100%",
|
||||
"secondary": "270 40% 90%",
|
||||
"secondaryForeground": "270 50% 15%",
|
||||
"muted": "270 20% 95%",
|
||||
"mutedForeground": "270 30% 40%",
|
||||
"accent": "300 100% 70%",
|
||||
"accentForeground": "0 0% 0%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "270 20% 85%",
|
||||
"input": "270 20% 85%",
|
||||
"ring": "120 80% 35%"
|
||||
"background": "220 16% 95%",
|
||||
"foreground": "222 18% 14%",
|
||||
"card": "220 20% 98%",
|
||||
"cardForeground": "222 18% 14%",
|
||||
"popover": "220 20% 98%",
|
||||
"popoverForeground": "222 18% 14%",
|
||||
"primary": "40 60% 44%",
|
||||
"primaryForeground": "220 20% 96%",
|
||||
"secondary": "220 12% 88%",
|
||||
"secondaryForeground": "222 16% 18%",
|
||||
"muted": "220 12% 90%",
|
||||
"mutedForeground": "220 8% 40%",
|
||||
"accent": "40 66% 50%",
|
||||
"accentForeground": "222 20% 8%",
|
||||
"destructive": "0 68% 46%",
|
||||
"destructiveForeground": "220 20% 96%",
|
||||
"border": "220 14% 78%",
|
||||
"input": "220 12% 82%",
|
||||
"ring": "40 60% 46%"
|
||||
},
|
||||
"darkColors": {
|
||||
"background": "270 30% 8%",
|
||||
"foreground": "120 100% 80%",
|
||||
"card": "270 30% 12%",
|
||||
"cardForeground": "120 100% 80%",
|
||||
"popover": "270 30% 12%",
|
||||
"popoverForeground": "120 100% 80%",
|
||||
"primary": "120 100% 40%",
|
||||
"primaryForeground": "270 30% 8%",
|
||||
"secondary": "270 50% 20%",
|
||||
"secondaryForeground": "120 100% 80%",
|
||||
"muted": "270 30% 15%",
|
||||
"mutedForeground": "270 20% 60%",
|
||||
"accent": "300 100% 50%",
|
||||
"accentForeground": "0 0% 0%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "270 30% 20%",
|
||||
"input": "270 30% 20%",
|
||||
"ring": "120 100% 40%"
|
||||
},
|
||||
"mode": "dark"
|
||||
"background": "222 20% 6%",
|
||||
"foreground": "215 22% 90%",
|
||||
"card": "222 20% 9%",
|
||||
"cardForeground": "215 22% 90%",
|
||||
"popover": "222 20% 9%",
|
||||
"popoverForeground": "215 22% 90%",
|
||||
"primary": "42 72% 58%",
|
||||
"primaryForeground": "222 24% 8%",
|
||||
"secondary": "222 16% 15%",
|
||||
"secondaryForeground": "215 20% 86%",
|
||||
"muted": "222 14% 13%",
|
||||
"mutedForeground": "215 12% 62%",
|
||||
"accent": "40 78% 62%",
|
||||
"accentForeground": "222 24% 8%",
|
||||
"destructive": "0 72% 52%",
|
||||
"destructiveForeground": "215 22% 95%",
|
||||
"border": "220 20% 22%",
|
||||
"input": "220 16% 18%",
|
||||
"ring": "42 72% 58%"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "riddler",
|
||||
"name": "The Riddler",
|
||||
"description": "Bright neon green with purple mystery",
|
||||
"id": "noir-rose",
|
||||
"name": "Noir Rose",
|
||||
"description": "Near-black warmed with rose-gold. The velvet-rope room.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"lightColors": {
|
||||
"background": "0 0% 100%",
|
||||
"foreground": "0 0% 5%",
|
||||
"card": "0 0% 98%",
|
||||
"cardForeground": "0 0% 5%",
|
||||
"popover": "0 0% 98%",
|
||||
"popoverForeground": "0 0% 5%",
|
||||
"primary": "120 70% 35%",
|
||||
"primaryForeground": "0 0% 100%",
|
||||
"secondary": "280 50% 85%",
|
||||
"secondaryForeground": "280 80% 20%",
|
||||
"muted": "0 0% 96%",
|
||||
"mutedForeground": "120 30% 35%",
|
||||
"accent": "280 80% 55%",
|
||||
"accentForeground": "0 0% 100%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "120 30% 85%",
|
||||
"input": "0 0% 90%",
|
||||
"ring": "120 70% 35%"
|
||||
"background": "20 20% 95%",
|
||||
"foreground": "18 14% 14%",
|
||||
"card": "20 24% 98%",
|
||||
"cardForeground": "18 14% 14%",
|
||||
"popover": "20 24% 98%",
|
||||
"popoverForeground": "18 14% 14%",
|
||||
"primary": "16 52% 50%",
|
||||
"primaryForeground": "20 30% 97%",
|
||||
"secondary": "20 14% 88%",
|
||||
"secondaryForeground": "18 14% 18%",
|
||||
"muted": "20 12% 90%",
|
||||
"mutedForeground": "18 10% 40%",
|
||||
"accent": "12 60% 56%",
|
||||
"accentForeground": "20 30% 97%",
|
||||
"destructive": "0 68% 46%",
|
||||
"destructiveForeground": "20 26% 96%",
|
||||
"border": "18 18% 78%",
|
||||
"input": "18 16% 82%",
|
||||
"ring": "16 52% 50%"
|
||||
},
|
||||
"darkColors": {
|
||||
"background": "0 0% 4%",
|
||||
"foreground": "120 100% 50%",
|
||||
"card": "0 0% 8%",
|
||||
"cardForeground": "120 100% 50%",
|
||||
"popover": "0 0% 8%",
|
||||
"popoverForeground": "120 100% 50%",
|
||||
"primary": "120 100% 45%",
|
||||
"primaryForeground": "0 0% 0%",
|
||||
"secondary": "280 80% 30%",
|
||||
"secondaryForeground": "120 100% 50%",
|
||||
"muted": "0 0% 12%",
|
||||
"mutedForeground": "120 50% 40%",
|
||||
"accent": "280 100% 60%",
|
||||
"accentForeground": "0 0% 100%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "120 100% 20%",
|
||||
"input": "0 0% 15%",
|
||||
"ring": "120 100% 45%"
|
||||
},
|
||||
"mode": "dark"
|
||||
"background": "16 12% 6%",
|
||||
"foreground": "22 24% 90%",
|
||||
"card": "16 12% 9%",
|
||||
"cardForeground": "22 24% 90%",
|
||||
"popover": "16 12% 9%",
|
||||
"popoverForeground": "22 24% 90%",
|
||||
"primary": "18 64% 60%",
|
||||
"primaryForeground": "16 24% 8%",
|
||||
"secondary": "16 12% 15%",
|
||||
"secondaryForeground": "22 22% 86%",
|
||||
"muted": "16 10% 13%",
|
||||
"mutedForeground": "22 14% 62%",
|
||||
"accent": "10 70% 64%",
|
||||
"accentForeground": "16 24% 8%",
|
||||
"destructive": "0 72% 54%",
|
||||
"destructiveForeground": "22 24% 95%",
|
||||
"border": "18 20% 22%",
|
||||
"input": "18 16% 18%",
|
||||
"ring": "18 64% 60%"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "harley-quinn",
|
||||
"name": "Harley Quinn",
|
||||
"description": "Bold red, black, and playful pink",
|
||||
"id": "smoke-silver",
|
||||
"name": "Smoke & Silver",
|
||||
"description": "Graphite and cool silver. Restrained, low on gold. The quiet booth.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"lightColors": {
|
||||
"background": "0 0% 100%",
|
||||
"foreground": "0 0% 5%",
|
||||
"card": "350 50% 98%",
|
||||
"cardForeground": "0 0% 5%",
|
||||
"popover": "350 50% 98%",
|
||||
"popoverForeground": "0 0% 5%",
|
||||
"primary": "350 85% 50%",
|
||||
"primaryForeground": "0 0% 100%",
|
||||
"secondary": "210 80% 90%",
|
||||
"secondaryForeground": "210 100% 20%",
|
||||
"muted": "0 0% 96%",
|
||||
"mutedForeground": "350 20% 40%",
|
||||
"accent": "330 100% 75%",
|
||||
"accentForeground": "0 0% 0%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "350 30% 88%",
|
||||
"input": "0 0% 90%",
|
||||
"ring": "350 85% 50%"
|
||||
"background": "210 8% 95%",
|
||||
"foreground": "210 10% 14%",
|
||||
"card": "210 10% 98%",
|
||||
"cardForeground": "210 10% 14%",
|
||||
"popover": "210 10% 98%",
|
||||
"popoverForeground": "210 10% 14%",
|
||||
"primary": "210 8% 38%",
|
||||
"primaryForeground": "210 12% 97%",
|
||||
"secondary": "210 8% 88%",
|
||||
"secondaryForeground": "210 10% 18%",
|
||||
"muted": "210 8% 90%",
|
||||
"mutedForeground": "210 6% 42%",
|
||||
"accent": "42 40% 52%",
|
||||
"accentForeground": "210 12% 10%",
|
||||
"destructive": "0 66% 46%",
|
||||
"destructiveForeground": "210 12% 96%",
|
||||
"border": "210 8% 78%",
|
||||
"input": "210 8% 82%",
|
||||
"ring": "210 8% 42%"
|
||||
},
|
||||
"darkColors": {
|
||||
"background": "0 0% 5%",
|
||||
"foreground": "0 0% 95%",
|
||||
"card": "350 80% 10%",
|
||||
"cardForeground": "0 0% 95%",
|
||||
"popover": "350 80% 10%",
|
||||
"popoverForeground": "0 0% 95%",
|
||||
"primary": "350 100% 55%",
|
||||
"primaryForeground": "0 0% 100%",
|
||||
"secondary": "210 100% 50%",
|
||||
"secondaryForeground": "0 0% 100%",
|
||||
"muted": "0 0% 12%",
|
||||
"mutedForeground": "350 30% 60%",
|
||||
"accent": "330 100% 65%",
|
||||
"accentForeground": "0 0% 0%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "350 50% 20%",
|
||||
"input": "0 0% 15%",
|
||||
"ring": "350 100% 55%"
|
||||
},
|
||||
"mode": "dark"
|
||||
"background": "210 10% 7%",
|
||||
"foreground": "210 12% 90%",
|
||||
"card": "210 10% 10%",
|
||||
"cardForeground": "210 12% 90%",
|
||||
"popover": "210 10% 10%",
|
||||
"popoverForeground": "210 12% 90%",
|
||||
"primary": "210 14% 74%",
|
||||
"primaryForeground": "210 12% 8%",
|
||||
"secondary": "210 10% 15%",
|
||||
"secondaryForeground": "210 12% 86%",
|
||||
"muted": "210 8% 13%",
|
||||
"mutedForeground": "210 8% 60%",
|
||||
"accent": "42 56% 60%",
|
||||
"accentForeground": "210 12% 8%",
|
||||
"destructive": "0 70% 52%",
|
||||
"destructiveForeground": "210 12% 95%",
|
||||
"border": "210 12% 22%",
|
||||
"input": "210 10% 18%",
|
||||
"ring": "210 14% 74%"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mr-freeze",
|
||||
"name": "Mr. Freeze",
|
||||
"description": "Cold ice blue and frozen white",
|
||||
"id": "bourbon",
|
||||
"name": "Bourbon",
|
||||
"description": "Espresso black and whisky amber. The last-pour palette.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"lightColors": {
|
||||
"background": "0 0% 100%",
|
||||
"foreground": "200 60% 10%",
|
||||
"card": "200 40% 98%",
|
||||
"cardForeground": "200 60% 10%",
|
||||
"popover": "200 40% 98%",
|
||||
"popoverForeground": "200 60% 10%",
|
||||
"primary": "195 90% 45%",
|
||||
"primaryForeground": "0 0% 100%",
|
||||
"secondary": "200 40% 92%",
|
||||
"secondaryForeground": "200 60% 10%",
|
||||
"muted": "200 30% 95%",
|
||||
"mutedForeground": "200 30% 40%",
|
||||
"accent": "180 80% 60%",
|
||||
"accentForeground": "200 60% 10%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "200 30% 88%",
|
||||
"input": "200 30% 85%",
|
||||
"ring": "195 90% 45%"
|
||||
"background": "36 24% 94%",
|
||||
"foreground": "28 18% 13%",
|
||||
"card": "36 26% 98%",
|
||||
"cardForeground": "28 18% 13%",
|
||||
"popover": "36 26% 98%",
|
||||
"popoverForeground": "28 18% 13%",
|
||||
"primary": "30 62% 42%",
|
||||
"primaryForeground": "36 30% 96%",
|
||||
"secondary": "36 16% 87%",
|
||||
"secondaryForeground": "28 16% 17%",
|
||||
"muted": "36 14% 89%",
|
||||
"mutedForeground": "28 12% 40%",
|
||||
"accent": "26 66% 48%",
|
||||
"accentForeground": "36 30% 96%",
|
||||
"destructive": "0 68% 44%",
|
||||
"destructiveForeground": "36 26% 96%",
|
||||
"border": "34 20% 76%",
|
||||
"input": "34 18% 80%",
|
||||
"ring": "30 62% 44%"
|
||||
},
|
||||
"darkColors": {
|
||||
"background": "200 50% 8%",
|
||||
"foreground": "200 100% 95%",
|
||||
"card": "200 50% 12%",
|
||||
"cardForeground": "200 100% 95%",
|
||||
"popover": "200 50% 12%",
|
||||
"popoverForeground": "200 100% 95%",
|
||||
"primary": "195 100% 50%",
|
||||
"primaryForeground": "200 50% 8%",
|
||||
"secondary": "200 60% 20%",
|
||||
"secondaryForeground": "200 100% 95%",
|
||||
"muted": "200 40% 15%",
|
||||
"mutedForeground": "200 40% 60%",
|
||||
"accent": "180 100% 70%",
|
||||
"accentForeground": "200 50% 8%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "200 40% 25%",
|
||||
"input": "200 40% 20%",
|
||||
"ring": "195 100% 50%"
|
||||
},
|
||||
"mode": "dark"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "poison-ivy",
|
||||
"name": "Poison Ivy",
|
||||
"description": "Deep forest green with seductive red",
|
||||
"theme": {
|
||||
"lightColors": {
|
||||
"background": "0 0% 100%",
|
||||
"foreground": "140 50% 12%",
|
||||
"card": "140 30% 98%",
|
||||
"cardForeground": "140 50% 12%",
|
||||
"popover": "140 30% 98%",
|
||||
"popoverForeground": "140 50% 12%",
|
||||
"primary": "140 60% 35%",
|
||||
"primaryForeground": "0 0% 100%",
|
||||
"secondary": "0 50% 92%",
|
||||
"secondaryForeground": "0 70% 25%",
|
||||
"muted": "140 20% 95%",
|
||||
"mutedForeground": "140 25% 40%",
|
||||
"accent": "0 70% 55%",
|
||||
"accentForeground": "0 0% 100%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "140 25% 88%",
|
||||
"input": "140 20% 85%",
|
||||
"ring": "140 60% 35%"
|
||||
},
|
||||
"darkColors": {
|
||||
"background": "140 40% 6%",
|
||||
"foreground": "140 60% 90%",
|
||||
"card": "140 40% 10%",
|
||||
"cardForeground": "140 60% 90%",
|
||||
"popover": "140 40% 10%",
|
||||
"popoverForeground": "140 60% 90%",
|
||||
"primary": "140 70% 40%",
|
||||
"primaryForeground": "0 0% 100%",
|
||||
"secondary": "0 70% 40%",
|
||||
"secondaryForeground": "0 0% 100%",
|
||||
"muted": "140 30% 15%",
|
||||
"mutedForeground": "140 30% 55%",
|
||||
"accent": "0 80% 50%",
|
||||
"accentForeground": "0 0% 100%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "140 30% 20%",
|
||||
"input": "140 30% 15%",
|
||||
"ring": "140 70% 40%"
|
||||
},
|
||||
"mode": "dark"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "two-face",
|
||||
"name": "Two-Face",
|
||||
"description": "Split personality - half dark, half light",
|
||||
"theme": {
|
||||
"lightColors": {
|
||||
"background": "0 0% 100%",
|
||||
"foreground": "0 0% 5%",
|
||||
"card": "0 0% 98%",
|
||||
"cardForeground": "0 0% 5%",
|
||||
"popover": "0 0% 98%",
|
||||
"popoverForeground": "0 0% 5%",
|
||||
"primary": "0 0% 10%",
|
||||
"primaryForeground": "0 0% 100%",
|
||||
"secondary": "0 0% 92%",
|
||||
"secondaryForeground": "0 0% 10%",
|
||||
"muted": "0 0% 96%",
|
||||
"mutedForeground": "0 0% 45%",
|
||||
"accent": "45 100% 50%",
|
||||
"accentForeground": "0 0% 0%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "0 0% 88%",
|
||||
"input": "0 0% 85%",
|
||||
"ring": "0 0% 10%"
|
||||
},
|
||||
"darkColors": {
|
||||
"background": "0 0% 5%",
|
||||
"foreground": "0 0% 95%",
|
||||
"card": "0 0% 10%",
|
||||
"cardForeground": "0 0% 95%",
|
||||
"popover": "0 0% 10%",
|
||||
"popoverForeground": "0 0% 95%",
|
||||
"primary": "0 0% 95%",
|
||||
"primaryForeground": "0 0% 5%",
|
||||
"secondary": "0 0% 20%",
|
||||
"secondaryForeground": "0 0% 95%",
|
||||
"muted": "0 0% 15%",
|
||||
"mutedForeground": "0 0% 55%",
|
||||
"accent": "45 100% 50%",
|
||||
"accentForeground": "0 0% 0%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "0 0% 25%",
|
||||
"input": "0 0% 20%",
|
||||
"ring": "0 0% 95%"
|
||||
},
|
||||
"mode": "dark"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "catwoman",
|
||||
"name": "Catwoman",
|
||||
"description": "Sleek purple and black elegance",
|
||||
"theme": {
|
||||
"lightColors": {
|
||||
"background": "0 0% 100%",
|
||||
"foreground": "280 30% 15%",
|
||||
"card": "280 20% 98%",
|
||||
"cardForeground": "280 30% 15%",
|
||||
"popover": "280 20% 98%",
|
||||
"popoverForeground": "280 30% 15%",
|
||||
"primary": "280 50% 45%",
|
||||
"primaryForeground": "0 0% 100%",
|
||||
"secondary": "280 25% 92%",
|
||||
"secondaryForeground": "280 30% 15%",
|
||||
"muted": "280 15% 96%",
|
||||
"mutedForeground": "280 15% 40%",
|
||||
"accent": "320 70% 60%",
|
||||
"accentForeground": "0 0% 100%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "280 20% 88%",
|
||||
"input": "280 15% 85%",
|
||||
"ring": "280 50% 45%"
|
||||
},
|
||||
"darkColors": {
|
||||
"background": "280 20% 6%",
|
||||
"foreground": "280 10% 90%",
|
||||
"card": "280 20% 10%",
|
||||
"cardForeground": "280 10% 90%",
|
||||
"popover": "280 20% 10%",
|
||||
"popoverForeground": "280 10% 90%",
|
||||
"primary": "280 60% 55%",
|
||||
"primaryForeground": "0 0% 100%",
|
||||
"secondary": "280 30% 20%",
|
||||
"secondaryForeground": "280 10% 90%",
|
||||
"muted": "280 15% 15%",
|
||||
"mutedForeground": "280 10% 55%",
|
||||
"accent": "320 80% 60%",
|
||||
"accentForeground": "0 0% 100%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "280 20% 22%",
|
||||
"input": "280 20% 18%",
|
||||
"ring": "280 60% 55%"
|
||||
},
|
||||
"mode": "dark"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "scarecrow",
|
||||
"name": "Scarecrow",
|
||||
"description": "Fear-inducing orange and decayed brown",
|
||||
"theme": {
|
||||
"lightColors": {
|
||||
"background": "0 0% 100%",
|
||||
"foreground": "30 40% 15%",
|
||||
"card": "35 30% 98%",
|
||||
"cardForeground": "30 40% 15%",
|
||||
"popover": "35 30% 98%",
|
||||
"popoverForeground": "30 40% 15%",
|
||||
"primary": "25 85% 45%",
|
||||
"primaryForeground": "0 0% 100%",
|
||||
"secondary": "30 30% 92%",
|
||||
"secondaryForeground": "30 40% 15%",
|
||||
"muted": "30 20% 95%",
|
||||
"mutedForeground": "30 25% 40%",
|
||||
"accent": "15 90% 50%",
|
||||
"accentForeground": "0 0% 100%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "30 25% 88%",
|
||||
"input": "30 20% 85%",
|
||||
"ring": "25 85% 45%"
|
||||
},
|
||||
"darkColors": {
|
||||
"background": "30 30% 6%",
|
||||
"foreground": "35 60% 85%",
|
||||
"card": "30 30% 10%",
|
||||
"cardForeground": "35 60% 85%",
|
||||
"popover": "30 30% 10%",
|
||||
"popoverForeground": "35 60% 85%",
|
||||
"primary": "25 90% 50%",
|
||||
"primaryForeground": "0 0% 0%",
|
||||
"secondary": "30 40% 20%",
|
||||
"secondaryForeground": "35 60% 85%",
|
||||
"muted": "30 25% 15%",
|
||||
"mutedForeground": "30 20% 50%",
|
||||
"accent": "15 100% 45%",
|
||||
"accentForeground": "0 0% 100%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "30 30% 20%",
|
||||
"input": "30 30% 15%",
|
||||
"ring": "25 90% 50%"
|
||||
},
|
||||
"mode": "dark"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bane",
|
||||
"name": "Bane",
|
||||
"description": "Military tactical green and black",
|
||||
"theme": {
|
||||
"lightColors": {
|
||||
"background": "0 0% 100%",
|
||||
"foreground": "100 30% 15%",
|
||||
"card": "100 20% 98%",
|
||||
"cardForeground": "100 30% 15%",
|
||||
"popover": "100 20% 98%",
|
||||
"popoverForeground": "100 30% 15%",
|
||||
"primary": "100 45% 30%",
|
||||
"primaryForeground": "0 0% 100%",
|
||||
"secondary": "100 20% 92%",
|
||||
"secondaryForeground": "100 30% 15%",
|
||||
"muted": "100 15% 95%",
|
||||
"mutedForeground": "100 20% 40%",
|
||||
"accent": "45 75% 50%",
|
||||
"accentForeground": "0 0% 0%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "100 20% 88%",
|
||||
"input": "100 15% 85%",
|
||||
"ring": "100 45% 30%"
|
||||
},
|
||||
"darkColors": {
|
||||
"background": "100 20% 5%",
|
||||
"foreground": "100 10% 85%",
|
||||
"card": "100 20% 9%",
|
||||
"cardForeground": "100 10% 85%",
|
||||
"popover": "100 20% 9%",
|
||||
"popoverForeground": "100 10% 85%",
|
||||
"primary": "100 50% 35%",
|
||||
"primaryForeground": "0 0% 100%",
|
||||
"secondary": "100 25% 18%",
|
||||
"secondaryForeground": "100 10% 85%",
|
||||
"muted": "100 15% 14%",
|
||||
"mutedForeground": "100 10% 50%",
|
||||
"accent": "45 80% 45%",
|
||||
"accentForeground": "0 0% 0%",
|
||||
"destructive": "0 84% 60%",
|
||||
"destructiveForeground": "0 0% 98%",
|
||||
"border": "100 20% 18%",
|
||||
"input": "100 20% 14%",
|
||||
"ring": "100 50% 35%"
|
||||
},
|
||||
"mode": "dark"
|
||||
"background": "28 16% 6%",
|
||||
"foreground": "36 28% 89%",
|
||||
"card": "28 16% 9%",
|
||||
"cardForeground": "36 28% 89%",
|
||||
"popover": "28 16% 9%",
|
||||
"popoverForeground": "36 28% 89%",
|
||||
"primary": "32 72% 54%",
|
||||
"primaryForeground": "28 24% 7%",
|
||||
"secondary": "28 14% 15%",
|
||||
"secondaryForeground": "36 24% 85%",
|
||||
"muted": "28 12% 13%",
|
||||
"mutedForeground": "36 14% 60%",
|
||||
"accent": "24 80% 58%",
|
||||
"accentForeground": "28 24% 7%",
|
||||
"destructive": "0 72% 52%",
|
||||
"destructiveForeground": "36 26% 95%",
|
||||
"border": "30 22% 22%",
|
||||
"input": "30 18% 18%",
|
||||
"ring": "32 72% 54%"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
255
seed/seed.json
Normal file
255
seed/seed.json
Normal file
@ -0,0 +1,255 @@
|
||||
{
|
||||
"demo": true,
|
||||
"settings": {
|
||||
"merge": {
|
||||
"companyName": "Gotham House",
|
||||
"tagline": "A members' club after dark."
|
||||
}
|
||||
},
|
||||
"data_tables": [
|
||||
{
|
||||
"key": "contact_submissions",
|
||||
"name": "Door Enquiries",
|
||||
"description": "Seeded contact-form submissions for the Gotham House demo. A submission becomes a row here and emails the site admins.",
|
||||
"columns": [
|
||||
{ "key": "name", "label": "Name", "type": "text", "required": true },
|
||||
{ "key": "email", "label": "Email", "type": "email", "required": true },
|
||||
{ "key": "party", "label": "Party size", "type": "text", "required": false },
|
||||
{ "key": "message", "label": "Message", "type": "text", "required": true }
|
||||
]
|
||||
}
|
||||
],
|
||||
"pages": [
|
||||
{
|
||||
"slug": "/",
|
||||
"title": "Gotham House",
|
||||
"template_key": "landing",
|
||||
"blocks": [
|
||||
{
|
||||
"block_key": "hero",
|
||||
"title": "Hero",
|
||||
"slot": "main",
|
||||
"sort_order": 1,
|
||||
"content": {
|
||||
"template": "centered",
|
||||
"minHeight": "75vh",
|
||||
"eyebrow": "Members only",
|
||||
"headline": "Gotham House",
|
||||
"subheadline": "A members' club after dark.",
|
||||
"description": "Late rooms, a long bar and a short guest list. Doors from nine, Thursday to Sunday. Bring your card.",
|
||||
"ctaPrimary": { "text": "Request the guest list", "url": "/contact" },
|
||||
"ctaSecondary": { "text": "See the programme", "url": "#programme" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "gotham:events_marquee",
|
||||
"title": "Programme",
|
||||
"slot": "main",
|
||||
"sort_order": 2,
|
||||
"content": {
|
||||
"eyebrow": "The programme",
|
||||
"title": "Tonight and after",
|
||||
"events": [
|
||||
{ "date": "Thu", "name": "Late Set", "room": "Main Room", "note": "Resident selectors. Slow start, long finish.", "url": "/contact" },
|
||||
{ "date": "Fri", "name": "The Gilt Sessions", "room": "The Brass Room", "note": "House and disco on the good system.", "url": "/contact" },
|
||||
{ "date": "Sat", "name": "Members' Night", "room": "Whole House", "note": "Card on the door. No list, no entry.", "url": "/contact" },
|
||||
{ "date": "Sun", "name": "Last Pour", "room": "The Cellar Bar", "note": "Quiet room. Records and a nightcap.", "url": "/contact" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "gotham:membership_tiers",
|
||||
"title": "Membership",
|
||||
"slot": "main",
|
||||
"sort_order": 3,
|
||||
"content": {
|
||||
"eyebrow": "Membership",
|
||||
"title": "Join the house",
|
||||
"intro": "Two ways in. Both start at the door.",
|
||||
"tiers": [
|
||||
{
|
||||
"name": "House",
|
||||
"price": "$40",
|
||||
"cadence": "/ month",
|
||||
"description": "The standard card. Enough for most nights.",
|
||||
"featured": false,
|
||||
"applyLabel": "Apply",
|
||||
"applyUrl": "/contact",
|
||||
"features": [
|
||||
{ "label": "Priority on the guest list" },
|
||||
{ "label": "The main room and the cellar bar" },
|
||||
{ "label": "Two guests a night" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Brass",
|
||||
"price": "$120",
|
||||
"cadence": "/ month",
|
||||
"description": "The full card. Every room, every night.",
|
||||
"featured": true,
|
||||
"applyLabel": "Apply",
|
||||
"applyUrl": "/contact",
|
||||
"features": [
|
||||
{ "label": "Everything in House" },
|
||||
{ "label": "The Brass Room and private booths" },
|
||||
{ "label": "First call on bottle service" },
|
||||
{ "label": "Four guests a night" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "gotham:bottle_menu",
|
||||
"title": "Bottle service",
|
||||
"slot": "main",
|
||||
"sort_order": 4,
|
||||
"content": {
|
||||
"eyebrow": "The cellar",
|
||||
"title": "Bottle service",
|
||||
"footnote": "Booths hold six. Ask the host for the full list.",
|
||||
"sections": [
|
||||
{
|
||||
"title": "Champagne",
|
||||
"items": [
|
||||
{ "name": "House brut", "note": "By the glass or the bottle.", "price": "$95" },
|
||||
{ "name": "Vintage rose", "note": "A short shelf. Ask first.", "price": "$180" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Spirits",
|
||||
"items": [
|
||||
{ "name": "Aged rye", "note": "Neat, one large cube.", "price": "$140" },
|
||||
{ "name": "Single malt", "note": "Islay. Bring water.", "price": "$160" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "cta",
|
||||
"title": "Closing CTA",
|
||||
"slot": "cta",
|
||||
"sort_order": 1,
|
||||
"content": {
|
||||
"background": "band",
|
||||
"heading": "The list closes at nine",
|
||||
"text": "Send your name and party. We reply the same day.",
|
||||
"primaryLabel": "Request the guest list",
|
||||
"primaryUrl": "/contact"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "/about",
|
||||
"title": "The House",
|
||||
"template_key": "default",
|
||||
"blocks": [
|
||||
{ "block_key": "heading", "title": "Heading", "slot": "main", "sort_order": 1, "content": { "text": "The House", "level": 1 } },
|
||||
{ "block_key": "rich-text", "title": "Story", "slot": "main", "sort_order": 2, "content": { "html": "<p>Gotham House keeps late hours behind an unmarked door. We run four nights a week and hold the rest of the week back.</p><p>The rooms are dark by design. The bar is long. The list is short. Members come first, and their guests come with them.</p>" } },
|
||||
{ "block_key": "divider", "title": "Divider", "slot": "main", "sort_order": 3, "content": { "variant": "ornament", "icon": "lucide:diamond" } },
|
||||
{ "block_key": "quote", "title": "Quote", "slot": "main", "sort_order": 4, "content": { "style": "block", "align": "center", "quote": "Come late. Stay later.", "attribution": "The House" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "/blog",
|
||||
"title": "The Journal",
|
||||
"template_key": "blog-index",
|
||||
"blocks": [
|
||||
{ "block_key": "card", "title": "Post: The Door", "slot": "main", "sort_order": 1, "content": { "layout": "vertical", "title": "How the door works", "text": "<p>The list, the card and the guests. What to send and when.</p>", "link": "/blog/how-the-door-works", "linkLabel": "Read on" } },
|
||||
{ "block_key": "card", "title": "Post: A Night", "slot": "main", "sort_order": 2, "content": { "layout": "vertical", "title": "A night at the house", "text": "<p>From the first pour to the last record. How the evening runs.</p>", "link": "/blog/a-night-at-the-house", "linkLabel": "Read on" } },
|
||||
{ "block_key": "card", "title": "Post: The Cellar", "slot": "main", "sort_order": 3, "content": { "layout": "vertical", "title": "Notes from the cellar", "text": "<p>What we pour after midnight and why the list stays short.</p>", "link": "/blog/notes-from-the-cellar", "linkLabel": "Read on" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "/blog/how-the-door-works",
|
||||
"title": "How the door works",
|
||||
"template_key": "article",
|
||||
"parent_slug": "/blog",
|
||||
"blocks": [
|
||||
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "How the door works", "level": 1 } },
|
||||
{ "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "<p>Send your name and party by nine. We hold the list until then and reply the same day.</p><p>Members lead. Guests come with them, and there is a cap. Bring your card. The door checks it.</p>" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "/blog/a-night-at-the-house",
|
||||
"title": "A night at the house",
|
||||
"template_key": "article",
|
||||
"parent_slug": "/blog",
|
||||
"blocks": [
|
||||
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "A night at the house", "level": 1 } },
|
||||
{ "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "<p>Doors open at nine. The first set starts slow. The main room fills after eleven and holds until the lights come up.</p><p>The cellar bar stays quiet. That is the point. Records, a nightcap and the last pour.</p>" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "/blog/notes-from-the-cellar",
|
||||
"title": "Notes from the cellar",
|
||||
"template_key": "article",
|
||||
"parent_slug": "/blog",
|
||||
"blocks": [
|
||||
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Notes from the cellar", "level": 1 } },
|
||||
{ "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "<p>The list is short by choice. We pour champagne by the glass, keep a bench of rye and hold a small shelf for members who ask.</p><p>Tell the host what you are drinking. The rest follows.</p>" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "/contact",
|
||||
"title": "The Door",
|
||||
"template_key": "contact",
|
||||
"blocks": [
|
||||
{
|
||||
"block_key": "contact-form",
|
||||
"title": "Guest List Enquiry",
|
||||
"slot": "main",
|
||||
"sort_order": 1,
|
||||
"content": {
|
||||
"heading": "Request the guest list",
|
||||
"description": "Send your name, the night and the party. We reply the same day.",
|
||||
"submitLabel": "Send to the door",
|
||||
"successMessage": "Thank you. Your name is with the door. We will reply by email.",
|
||||
"fields": [
|
||||
{ "name": "name", "label": "Your name", "type": "text", "required": true, "width": "half" },
|
||||
{ "name": "email", "label": "Email", "type": "email", "required": true, "width": "half" },
|
||||
{ "name": "party", "label": "Party size", "type": "text", "required": false, "width": "half" },
|
||||
{ "name": "message", "label": "Which night, and any notes", "type": "textarea", "required": true, "width": "full" }
|
||||
],
|
||||
"formConfig": { "enabled": true, "captchaEnabled": false, "targetTable": "contact_submissions" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "hours-location",
|
||||
"title": "Door & Hours",
|
||||
"slot": "aside",
|
||||
"sort_order": 1,
|
||||
"content": {
|
||||
"heading": "The Door",
|
||||
"hours": [
|
||||
{ "day": "Thursday", "hours": "9pm to late" },
|
||||
{ "day": "Friday to Saturday", "hours": "9pm to 4am" },
|
||||
{ "day": "Sunday", "hours": "9pm to 2am" },
|
||||
{ "day": "Monday to Wednesday", "closed": true }
|
||||
],
|
||||
"specialHoursNote": "Members' Night runs Saturday. Card on the door.",
|
||||
"address": "1 Vane Court\nThe Old Quarter",
|
||||
"phone": "+1 555 0139"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "/login",
|
||||
"title": "Members",
|
||||
"template_key": "auth",
|
||||
"blocks": [
|
||||
{ "block_key": "auth-form", "title": "Sign in", "slot": "main", "sort_order": 1, "content": { "default_tab": "login", "show_social": false } },
|
||||
{ "block_key": "text", "title": "Foot", "slot": "footer", "sort_order": 1, "content": { "text": "<p>Members book priority nights and private booths.</p>" } }
|
||||
]
|
||||
}
|
||||
],
|
||||
"menu_items": [
|
||||
{ "menu": "main", "label": "Home", "page_slug": "/", "sort_order": 1 },
|
||||
{ "menu": "main", "label": "The House", "page_slug": "/about", "sort_order": 2 },
|
||||
{ "menu": "main", "label": "The Journal", "page_slug": "/blog", "sort_order": 3 },
|
||||
{ "menu": "main", "label": "The Door", "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": "Door enquiry to admins",
|
||||
"description": "On a guest-list form submission, email the site admins.",
|
||||
"enabled": true,
|
||||
"trigger": { "type": "row_inserted", "table": "contact_submissions" },
|
||||
"steps": [
|
||||
{
|
||||
"type": "email",
|
||||
"name": "Email admins",
|
||||
"template": "Form Submission Notification",
|
||||
"recipients": { "mode": "admins", "scope": "all" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,99 +1,64 @@
|
||||
<!doctype html>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="x-apple-disable-message-reformatting">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<!--[if mso]>
|
||||
<noscript>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
</noscript>
|
||||
<![endif]-->
|
||||
<title>{{ site_name }}</title>
|
||||
<style type="text/css">
|
||||
/* CSS Reset for Email */
|
||||
body, table, td, p, a, li, blockquote {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
table, td {
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
img {
|
||||
-ms-interpolation-mode: bicubic;
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
a[x-apple-data-detectors] {
|
||||
color: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
/* Gotham-specific styles */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
font-weight: 700;
|
||||
}
|
||||
/* Responsive styles */
|
||||
@media only screen and (max-width: 620px) {
|
||||
.email-container {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
.content-padding {
|
||||
padding-left: 24px !important;
|
||||
padding-right: 24px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="background-color: {{ colors.background|default:"#0a0a0a" }}; margin: 0; padding: 0; font-family: &#39;Inter&#39;, -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, sans-serif;">
|
||||
<!-- Preview text (hidden) -->
|
||||
{% if preview_text %}<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">{{ preview_text }}</div><div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;"> ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div>{% endif %}
|
||||
<!-- Main Container -->
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
|
||||
<tr>
|
||||
<td align="center" style="padding: 48px 10px; background-color: {{ colors.background|default:"#0a0a0a" }};">
|
||||
<!-- Email Container -->
|
||||
<table role="presentation" class="email-container" width="600" cellspacing="0" cellpadding="0" border="0" style="max-width: 600px; background-color: {{ colors.card|default:"#141414" }}; border: 1px solid {{ colors.border|default:"#262626" }};">
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td align="center" style="padding: 32px 40px; background-color: {{ colors.card|default:"#141414" }}; border-bottom: 1px solid {{ colors.border|default:"#262626" }};">{% if logo_url %}<img src="{{ logo_url }}" alt="{{ site_name }}" style="max-height: 48px; width: auto; display: block;">{% elif site_name %}<h1 style="margin: 0; font-size: 24px; font-weight: 800; color: {{ colors.foreground|default:"#fafafa" }}; letter-spacing: -0.5px; text-transform: uppercase;">{{ site_name }}</h1>{% endif %}</td>
|
||||
</tr>
|
||||
<!-- Body Content -->
|
||||
<tr>
|
||||
<td class="content-padding" style="padding: 40px 48px; color: {{ colors.foreground|default:"#fafafa" }}; font-size: 16px; line-height: 1.75; letter-spacing: 0.01em;">{{ body|safe }}</td>
|
||||
</tr>
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="padding: 32px 48px; background-color: {{ colors.muted|default:"#1a1a1a" }}; border-top: 1px solid {{ colors.border|default:"#262626" }};">
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<p style="margin: 0 0 8px; font-size: 13px; font-weight: 700; color: {{ colors.foreground|default:"#fafafa" }}; text-transform: uppercase; letter-spacing: 0.1em;">{{ site_name }}</p>
|
||||
{% if site_url %}<p style="margin: 0 0 16px; font-size: 13px; color: {{ colors.mutedForeground|default:"#737373" }};"><a href="{{ site_url }}" style="color: {{ colors.primary|default:"#fafafa" }}; text-decoration: none; border-bottom: 1px solid {{ colors.primary|default:"#fafafa" }};">{{ site_url }}</a></p>{% endif %}
|
||||
{% if unsubscribe_url %}<p style="margin: 0; font-size: 11px; color: {{ colors.mutedForeground|default:"#737373" }};"><a href="{{ unsubscribe_url }}" style="color: {{ colors.mutedForeground|default:"#737373" }}; text-decoration: none;">Unsubscribe</a></p>{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="x-apple-disable-message-reformatting">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<!--[if mso]>
|
||||
<noscript>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
</noscript>
|
||||
<![endif]-->
|
||||
<title>{{ site_name }}</title>
|
||||
<style type="text/css">
|
||||
body, table, td, p, a, li, blockquote {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
|
||||
img { -ms-interpolation-mode: bicubic; border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; }
|
||||
body { margin: 0 !important; padding: 0 !important; width: 100% !important; }
|
||||
a[x-apple-data-detectors] { color: inherit !important; text-decoration: none !important; }
|
||||
@media only screen and (max-width: 620px) {
|
||||
.email-container { width: 100% !important; max-width: 100% !important; }
|
||||
.content-padding { padding-left: 24px !important; padding-right: 24px !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="background-color: {{ colors.background }}; margin: 0; padding: 0; font-family: &#39;Cormorant Garamond&#39;, &#39;Cormorant&#39;, Georgia, serif;">
|
||||
{% if preview_text %}<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">{{ preview_text }}</div>{% endif %}
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
|
||||
<tr>
|
||||
<td align="center" style="padding: 32px 10px; background-color: {{ colors.background }};">
|
||||
<table role="presentation" class="email-container" width="600" cellspacing="0" cellpadding="0" border="0" style="max-width: 600px; background-color: {{ colors.background }}; border: 1px solid {{ colors.border }};">
|
||||
<tr>
|
||||
<td align="center" style="padding: 0; background-color: {{ colors.foreground }};">
|
||||
<img src="data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20600%2080%22%3E%3Crect%20width%3D%22600%22%20height%3D%2280%22%20fill%3D%22%23191510%22%2F%3E%3Cg%20stroke%3D%22%23{{ colors.primary|default:"#c9a14a"|cut:"#" }}%22%20stroke-width%3D%221%22%20fill%3D%22none%22%3E%3Cpath%20d%3D%22M300%2080%20L%20100%2010%22%2F%3E%3Cpath%20d%3D%22M300%2080%20L%20180%2010%22%2F%3E%3Cpath%20d%3D%22M300%2080%20L%20260%2010%22%2F%3E%3Cpath%20d%3D%22M300%2080%20L%20340%2010%22%2F%3E%3Cpath%20d%3D%22M300%2080%20L%20420%2010%22%2F%3E%3Cpath%20d%3D%22M300%2080%20L%20500%2010%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E" alt="" width="600" height="80" style="display: block; width: 100%; max-width: 600px; height: 80px; background-color: {{ colors.foreground }};">
|
||||
{% if site_name %}<h1 style="margin: 0; padding: 24px 16px; font-family: &#39;Oswald&#39;, &#39;Antonio&#39;, Georgia, serif; font-size: 28px; text-transform: uppercase; letter-spacing: 0.18em; color: {{ colors.primary }};">{{ site_name }}</h1>{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="content-padding" style="padding: 40px 48px; color: {{ colors.foreground }}; font-size: 16px; line-height: 1.7;">{{ body|safe }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 0 48px;"><hr style="border: none; border-top: 1px solid {{ colors.primary }}; margin: 0;"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="padding: 24px 48px; color: {{ colors.mutedForeground }}; font-size: 13px;">
|
||||
<p style="margin: 0 0 8px; font-family: &#39;Oswald&#39;, &#39;Antonio&#39;, Georgia, serif; text-transform: uppercase; letter-spacing: 0.18em; color: {{ colors.foreground }};">{{ site_name }}</p>
|
||||
{% if site_url %}<p style="margin: 0 0 8px;"><a href="{{ site_url }}" style="color: {{ colors.primary }}; text-decoration: none; border-bottom: 1px solid {{ colors.primary }};">{{ site_url }}</a></p>{% endif %}
|
||||
{% if support_email %}<p style="margin: 12px 0 0; font-style: italic; color: {{ colors.mutedForeground }};">P.S. Reservations & enquiries: {{ support_email }}</p>{% endif %}
|
||||
{% if unsubscribe_url %}<p style="margin: 16px 0 0; font-size: 11px;"><a href="{{ unsubscribe_url }}" style="color: {{ colors.mutedForeground }}; text-decoration: none;">Unsubscribe</a></p>{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
23
templates/gotham/article.ninjatpl
Normal file
23
templates/gotham/article.ninjatpl
Normal file
@ -0,0 +1,23 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||
{{ head_html|safe }}
|
||||
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col film-grain" style="font-family: var(--font-body, "Cormorant Garamond", "Cormorant", Georgia, serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<header class="w-full">
|
||||
<div class="max-w-3xl mx-auto px-4">{{ slots.header|safe }}</div>
|
||||
</header>
|
||||
<div class="flex-grow grid gap-12 max-w-5xl mx-auto w-full px-4 py-12 md:py-16 md:grid-cols-[2fr_1fr]">
|
||||
<main class="prose max-w-none">
|
||||
{% if slots.main %}<article class="reel-dropcap" data-reel-dropcap="true">{{ slots.main|safe }}</article>{% else %}<div class="py-20 text-center"><p class="italic" style="color:hsl(var(--muted-foreground));">No content blocks assigned to this page.</p></div>{% endif %}
|
||||
</main>
|
||||
<aside class="w-full md:border-l md:border-[hsl(var(--primary)/0.3)] md:pl-10">{{ slots.aside|safe }}</aside>
|
||||
</div>
|
||||
<footer class="w-full mt-auto border-t border-[hsl(var(--primary)/0.35)]">
|
||||
<div class="max-w-5xl mx-auto px-4">{{ slots.footer|safe }}</div>
|
||||
</footer>
|
||||
{{ body_end_html|safe }}
|
||||
<script>
|
||||
(function(){if(window.__gothamMotion)return;window.__gothamMotion=1;var d=document,root=d.documentElement;root.classList.add('gotham-js');var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;function pending(){return d.querySelectorAll('.reveal:not(.is-visible)');}function reveal(el){el.classList.add('is-visible');}function inView(){var h=window.innerHeight||root.clientHeight;pending().forEach(function(el){var r=el.getBoundingClientRect();if(r.top<h*1.05&&r.bottom>-h*0.25)reveal(el);});}function all(){pending().forEach(reveal);}function run(){if(reduce){all();return;}inView();window.addEventListener('scroll',inView,{passive:true});window.addEventListener('resize',inView,{passive:true});window.addEventListener('load',function(){inView();setTimeout(inView,120);},{once:true});setTimeout(inView,400);setTimeout(all,2600);d.querySelectorAll('[data-spotlight]').forEach(function(s){s.addEventListener('pointermove',function(ev){var r=s.getBoundingClientRect();s.style.setProperty('--sx',((ev.clientX-r.left)/r.width*100)+'%');s.style.setProperty('--sy',((ev.clientY-r.top)/r.height*100)+'%');});});}if(d.readyState!='loading')run();else d.addEventListener('DOMContentLoaded',run);})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
21
templates/gotham/auth.ninjatpl
Normal file
21
templates/gotham/auth.ninjatpl
Normal file
@ -0,0 +1,21 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||
{{ head_html|safe }}
|
||||
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col film-grain" style="font-family: var(--font-body, "Cormorant Garamond", "Cormorant", Georgia, serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<main class="flex-grow w-full flex flex-col items-center justify-center px-4 py-12" data-spotlight style="background-image:radial-gradient(circle at var(--sx,50%) var(--sy,42%), hsl(var(--primary)/0.10), transparent 60%);">
|
||||
<div class="w-full max-w-md">
|
||||
<a href="/" class="block text-center" aria-label="{{ site_settings.Title }} home">
|
||||
{% if site_settings.Logo %}<img src="{{ site_settings.Logo }}" alt="{{ site_settings.LogoAlt }}" class="mx-auto h-12 w-auto">{% else %}<span class="reel-caps text-2xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ site_settings.Title|default:"BlockNinja" }}</span>{% endif %}
|
||||
</a>
|
||||
<div class="mx-auto mt-4 mb-6 h-4 w-32 reel-strip" aria-hidden="true"></div>
|
||||
<div>{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center"><p class="italic" style="color:hsl(var(--muted-foreground));">No form assigned to this page.</p></div>{% endif %}</div>
|
||||
{% if slots.footer %}<div class="mt-6 text-center text-sm" style="color:hsl(var(--muted-foreground));">{{ slots.footer|safe }}</div>{% endif %}
|
||||
</div>
|
||||
</main>
|
||||
{{ body_end_html|safe }}
|
||||
<script>
|
||||
(function(){if(window.__gothamMotion)return;window.__gothamMotion=1;var d=document,root=d.documentElement;root.classList.add('gotham-js');var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;function pending(){return d.querySelectorAll('.reveal:not(.is-visible)');}function reveal(el){el.classList.add('is-visible');}function inView(){var h=window.innerHeight||root.clientHeight;pending().forEach(function(el){var r=el.getBoundingClientRect();if(r.top<h*1.05&&r.bottom>-h*0.25)reveal(el);});}function all(){pending().forEach(reveal);}function run(){if(reduce){all();return;}inView();window.addEventListener('scroll',inView,{passive:true});window.addEventListener('resize',inView,{passive:true});window.addEventListener('load',function(){inView();setTimeout(inView,120);},{once:true});setTimeout(inView,400);setTimeout(all,2600);d.querySelectorAll('[data-spotlight]').forEach(function(s){s.addEventListener('pointermove',function(ev){var r=s.getBoundingClientRect();s.style.setProperty('--sx',((ev.clientX-r.left)/r.width*100)+'%');s.style.setProperty('--sy',((ev.clientY-r.top)/r.height*100)+'%');});});}if(d.readyState!='loading')run();else d.addEventListener('DOMContentLoaded',run);})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
26
templates/gotham/blog-index.ninjatpl
Normal file
26
templates/gotham/blog-index.ninjatpl
Normal file
@ -0,0 +1,26 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||
{{ head_html|safe }}
|
||||
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col film-grain" style="font-family: var(--font-body, "Cormorant Garamond", "Cormorant", Georgia, serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
{% if slots.header %}<header class="w-full">{{ slots.header|safe }}</header>{% endif %}
|
||||
<main class="flex-grow w-full">
|
||||
<div class="max-w-6xl mx-auto px-4 py-12 md:py-16">
|
||||
{% if title %}
|
||||
<div class="text-center reveal">
|
||||
<h1 class="reel-caps reel-underline mx-auto w-fit text-4xl md:text-5xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);">{{ title }}</h1>
|
||||
{% if page_meta.MetaDescription %}<p class="mt-4 text-lg italic" style="color:hsl(var(--muted-foreground));">{{ page_meta.MetaDescription }}</p>{% endif %}
|
||||
<div class="mx-auto mt-6 h-4 w-40 reel-strip" aria-hidden="true"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if slots.featured %}<section class="mt-12">{{ slots.featured|safe }}</section>{% endif %}
|
||||
{% if slots.main %}<section class="mt-12">{{ slots.main|safe }}</section>{% else %}<div class="py-20 text-center"><p class="italic" style="color:hsl(var(--muted-foreground));">No posts published yet.</p></div>{% endif %}
|
||||
</div>
|
||||
</main>
|
||||
{% if slots.footer %}<footer class="w-full mt-auto border-t border-[hsl(var(--primary)/0.35)]">{{ slots.footer|safe }}</footer>{% endif %}
|
||||
{{ body_end_html|safe }}
|
||||
<script>
|
||||
(function(){if(window.__gothamMotion)return;window.__gothamMotion=1;var d=document,root=d.documentElement;root.classList.add('gotham-js');var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;function pending(){return d.querySelectorAll('.reveal:not(.is-visible)');}function reveal(el){el.classList.add('is-visible');}function inView(){var h=window.innerHeight||root.clientHeight;pending().forEach(function(el){var r=el.getBoundingClientRect();if(r.top<h*1.05&&r.bottom>-h*0.25)reveal(el);});}function all(){pending().forEach(reveal);}function run(){if(reduce){all();return;}inView();window.addEventListener('scroll',inView,{passive:true});window.addEventListener('resize',inView,{passive:true});window.addEventListener('load',function(){inView();setTimeout(inView,120);},{once:true});setTimeout(inView,400);setTimeout(all,2600);d.querySelectorAll('[data-spotlight]').forEach(function(s){s.addEventListener('pointermove',function(ev){var r=s.getBoundingClientRect();s.style.setProperty('--sx',((ev.clientX-r.left)/r.width*100)+'%');s.style.setProperty('--sy',((ev.clientY-r.top)/r.height*100)+'%');});});}if(d.readyState!='loading')run();else d.addEventListener('DOMContentLoaded',run);})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,11 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||
{{ head_html|safe }}
|
||||
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col">
|
||||
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col film-grain" style="font-family: var(--font-body, "Cormorant Garamond", "Cormorant", Georgia, serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<header class="w-full border-b border-border"><div class="max-w-2xl mx-auto px-4">{{ slots.header|safe }}</div></header>
|
||||
<main class="flex-grow max-w-2xl mx-auto w-full px-4 py-12">{% if slots.main %}<article class="prose prose-invert prose-lg max-w-none">{{ slots.main|safe }}</article>{% else %}<div class="py-20 text-center"><p class="text-muted-foreground">No content blocks assigned to this page.</p></div>{% endif %}</main>
|
||||
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
|
||||
<header class="w-full border-b border-[hsl(var(--primary)/0.35)]"><div class="max-w-2xl mx-auto px-4">{{ slots.header|safe }}</div></header>
|
||||
<main class="flex-grow max-w-2xl mx-auto w-full px-4 py-12 md:py-16">
|
||||
{% if slots.main %}<article class="prose prose-lg max-w-none">{{ slots.main|safe }}</article>{% else %}<div class="py-20 text-center"><p class="italic" style="color:hsl(var(--muted-foreground));">No content blocks assigned to this page.</p></div>{% endif %}
|
||||
</main>
|
||||
<footer class="w-full mt-auto border-t border-[hsl(var(--primary)/0.35)]"><div class="max-w-2xl mx-auto px-4">{{ slots.footer|safe }}</div></footer>
|
||||
{{ body_end_html|safe }}
|
||||
<script>
|
||||
(function(){if(window.__gothamMotion)return;window.__gothamMotion=1;var d=document,root=d.documentElement;root.classList.add('gotham-js');var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;function pending(){return d.querySelectorAll('.reveal:not(.is-visible)');}function reveal(el){el.classList.add('is-visible');}function inView(){var h=window.innerHeight||root.clientHeight;pending().forEach(function(el){var r=el.getBoundingClientRect();if(r.top<h*1.05&&r.bottom>-h*0.25)reveal(el);});}function all(){pending().forEach(reveal);}function run(){if(reduce){all();return;}inView();window.addEventListener('scroll',inView,{passive:true});window.addEventListener('resize',inView,{passive:true});window.addEventListener('load',function(){inView();setTimeout(inView,120);},{once:true});setTimeout(inView,400);setTimeout(all,2600);d.querySelectorAll('[data-spotlight]').forEach(function(s){s.addEventListener('pointermove',function(ev){var r=s.getBoundingClientRect();s.style.setProperty('--sx',((ev.clientX-r.left)/r.width*100)+'%');s.style.setProperty('--sy',((ev.clientY-r.top)/r.height*100)+'%');});});}if(d.readyState!='loading')run();else d.addEventListener('DOMContentLoaded',run);})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
25
templates/gotham/contact.ninjatpl
Normal file
25
templates/gotham/contact.ninjatpl
Normal file
@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||
{{ head_html|safe }}
|
||||
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col film-grain" style="font-family: var(--font-body, "Cormorant Garamond", "Cormorant", Georgia, serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
{% if slots.header %}<header class="w-full">{{ slots.header|safe }}</header>{% endif %}
|
||||
<main class="flex-grow w-full">
|
||||
<div class="max-w-6xl mx-auto px-4 py-12 md:py-16">
|
||||
{% if slots.aside %}
|
||||
<div class="grid gap-12 md:grid-cols-[2fr_1fr]">
|
||||
<div>{% if slots.main %}{{ slots.main|safe }}{% endif %}</div>
|
||||
<aside class="w-full md:border-l md:border-[hsl(var(--primary)/0.3)] md:pl-10">{{ slots.aside|safe }}</aside>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="max-w-3xl mx-auto">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center"><p class="italic" style="color:hsl(var(--muted-foreground));">No content blocks assigned to this page.</p></div>{% endif %}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</main>
|
||||
{% if slots.footer %}<footer class="w-full mt-auto border-t border-[hsl(var(--primary)/0.35)]">{{ slots.footer|safe }}</footer>{% endif %}
|
||||
{{ body_end_html|safe }}
|
||||
<script>
|
||||
(function(){if(window.__gothamMotion)return;window.__gothamMotion=1;var d=document,root=d.documentElement;root.classList.add('gotham-js');var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;function pending(){return d.querySelectorAll('.reveal:not(.is-visible)');}function reveal(el){el.classList.add('is-visible');}function inView(){var h=window.innerHeight||root.clientHeight;pending().forEach(function(el){var r=el.getBoundingClientRect();if(r.top<h*1.05&&r.bottom>-h*0.25)reveal(el);});}function all(){pending().forEach(reveal);}function run(){if(reduce){all();return;}inView();window.addEventListener('scroll',inView,{passive:true});window.addEventListener('resize',inView,{passive:true});window.addEventListener('load',function(){inView();setTimeout(inView,120);},{once:true});setTimeout(inView,400);setTimeout(all,2600);d.querySelectorAll('[data-spotlight]').forEach(function(s){s.addEventListener('pointermove',function(ev){var r=s.getBoundingClientRect();s.style.setProperty('--sx',((ev.clientX-r.left)/r.width*100)+'%');s.style.setProperty('--sy',((ev.clientY-r.top)/r.height*100)+'%');});});}if(d.readyState!='loading')run();else d.addEventListener('DOMContentLoaded',run);})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,11 +1,20 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||
{{ head_html|safe }}
|
||||
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col">
|
||||
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col film-grain" style="font-family: var(--font-body, "Cormorant Garamond", "Cormorant", Georgia, serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<header class="w-full">{{ slots.header|safe }}</header>
|
||||
<main class="flex-grow max-w-4xl mx-auto w-full px-4 py-8">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center"><p class="text-muted-foreground">No content blocks assigned to this page.</p></div>{% endif %}</main>
|
||||
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
|
||||
<header class="w-full">
|
||||
<div class="max-w-5xl mx-auto px-4">{{ slots.header|safe }}</div>
|
||||
</header>
|
||||
<main class="flex-grow max-w-3xl mx-auto w-full px-4 py-12 md:py-16">
|
||||
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center"><p class="italic" style="color:hsl(var(--muted-foreground));">No content blocks assigned to this page.</p></div>{% endif %}
|
||||
</main>
|
||||
<footer class="w-full mt-auto border-t border-[hsl(var(--primary)/0.35)]">
|
||||
<div class="max-w-5xl mx-auto px-4">{{ slots.footer|safe }}</div>
|
||||
</footer>
|
||||
{{ body_end_html|safe }}
|
||||
<script>
|
||||
(function(){if(window.__gothamMotion)return;window.__gothamMotion=1;var d=document,root=d.documentElement;root.classList.add('gotham-js');var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;function pending(){return d.querySelectorAll('.reveal:not(.is-visible)');}function reveal(el){el.classList.add('is-visible');}function inView(){var h=window.innerHeight||root.clientHeight;pending().forEach(function(el){var r=el.getBoundingClientRect();if(r.top<h*1.05&&r.bottom>-h*0.25)reveal(el);});}function all(){pending().forEach(reveal);}function run(){if(reduce){all();return;}inView();window.addEventListener('scroll',inView,{passive:true});window.addEventListener('resize',inView,{passive:true});window.addEventListener('load',function(){inView();setTimeout(inView,120);},{once:true});setTimeout(inView,400);setTimeout(all,2600);d.querySelectorAll('[data-spotlight]').forEach(function(s){s.addEventListener('pointermove',function(ev){var r=s.getBoundingClientRect();s.style.setProperty('--sx',((ev.clientX-r.left)/r.width*100)+'%');s.style.setProperty('--sy',((ev.clientY-r.top)/r.height*100)+'%');});});}if(d.readyState!='loading')run();else d.addEventListener('DOMContentLoaded',run);})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,11 +1,18 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||
{{ head_html|safe }}
|
||||
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col">
|
||||
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col film-grain" style="font-family: var(--font-body, "Cormorant Garamond", "Cormorant", Georgia, serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<header class="w-full">{{ slots.header|safe }}</header>
|
||||
<main class="flex-grow w-full">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="max-w-4xl mx-auto py-20 px-4 text-center"><p class="text-muted-foreground">No content blocks assigned to this page.</p></div>{% endif %}</main>
|
||||
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
|
||||
<main class="flex-grow w-full">
|
||||
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="max-w-4xl mx-auto py-20 px-4 text-center"><p class="italic" style="color:hsl(var(--muted-foreground));">No content blocks assigned to this page.</p></div>{% endif %}
|
||||
</main>
|
||||
<footer class="w-full mt-auto border-t border-[hsl(var(--primary)/0.35)]">
|
||||
<div class="max-w-5xl mx-auto px-4">{{ slots.footer|safe }}</div>
|
||||
</footer>
|
||||
{{ body_end_html|safe }}
|
||||
<script>
|
||||
(function(){if(window.__gothamMotion)return;window.__gothamMotion=1;var d=document,root=d.documentElement;root.classList.add('gotham-js');var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;function pending(){return d.querySelectorAll('.reveal:not(.is-visible)');}function reveal(el){el.classList.add('is-visible');}function inView(){var h=window.innerHeight||root.clientHeight;pending().forEach(function(el){var r=el.getBoundingClientRect();if(r.top<h*1.05&&r.bottom>-h*0.25)reveal(el);});}function all(){pending().forEach(reveal);}function run(){if(reduce){all();return;}inView();window.addEventListener('scroll',inView,{passive:true});window.addEventListener('resize',inView,{passive:true});window.addEventListener('load',function(){inView();setTimeout(inView,120);},{once:true});setTimeout(inView,400);setTimeout(all,2600);d.querySelectorAll('[data-spotlight]').forEach(function(s){s.addEventListener('pointermove',function(ev){var r=s.getBoundingClientRect();s.style.setProperty('--sx',((ev.clientX-r.left)/r.width*100)+'%');s.style.setProperty('--sy',((ev.clientY-r.top)/r.height*100)+'%');});});}if(d.readyState!='loading')run();else d.addEventListener('DOMContentLoaded',run);})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||
{{ head_html|safe }}
|
||||
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col">
|
||||
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col film-grain" style="font-family: var(--font-body, "Cormorant Garamond", "Cormorant", Georgia, serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<section class="w-full">{{ slots.hero|safe }}</section>
|
||||
<main class="flex-grow">{% if slots.main %}<div class="max-w-6xl mx-auto px-4 py-16">{{ slots.main|safe }}</div>{% endif %}</main>
|
||||
<main class="flex-grow">
|
||||
{% if slots.main %}{{ slots.main|safe }}{% endif %}
|
||||
</main>
|
||||
<section class="w-full">{{ slots.cta|safe }}</section>
|
||||
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
|
||||
<footer class="w-full mt-auto border-t border-[hsl(var(--primary)/0.35)]">
|
||||
<div class="max-w-5xl mx-auto px-4">{{ slots.footer|safe }}</div>
|
||||
</footer>
|
||||
{{ body_end_html|safe }}
|
||||
<script>
|
||||
(function(){if(window.__gothamMotion)return;window.__gothamMotion=1;var d=document,root=d.documentElement;root.classList.add('gotham-js');var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;function pending(){return d.querySelectorAll('.reveal:not(.is-visible)');}function reveal(el){el.classList.add('is-visible');}function inView(){var h=window.innerHeight||root.clientHeight;pending().forEach(function(el){var r=el.getBoundingClientRect();if(r.top<h*1.05&&r.bottom>-h*0.25)reveal(el);});}function all(){pending().forEach(reveal);}function run(){if(reduce){all();return;}inView();window.addEventListener('scroll',inView,{passive:true});window.addEventListener('resize',inView,{passive:true});window.addEventListener('load',function(){inView();setTimeout(inView,120);},{once:true});setTimeout(inView,400);setTimeout(all,2600);d.querySelectorAll('[data-spotlight]').forEach(function(s){s.addEventListener('pointermove',function(ev){var r=s.getBoundingClientRect();s.style.setProperty('--sx',((ev.clientX-r.left)/r.width*100)+'%');s.style.setProperty('--sy',((ev.clientY-r.top)/r.height*100)+'%');});});}if(d.readyState!='loading')run();else d.addEventListener('DOMContentLoaded',run);})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
21
templates/overrides/gotham/announcement-bar.ninjatpl
Normal file
21
templates/overrides/gotham/announcement-bar.ninjatpl
Normal file
@ -0,0 +1,21 @@
|
||||
{# announcement-bar override — gilded banner. Dismiss <script>, data-bn-annkey and localStorage keying preserved verbatim. Fields mirror builtin: variant, message, linkText, linkUrl, dismissable. #}
|
||||
{% set anncls = "bg-primary/15 text-foreground" %}
|
||||
{% if variant == "promo" %}{% set anncls = "bg-primary text-primary-foreground" %}{% elif variant == "alert" %}{% set anncls = "bg-destructive text-destructive-foreground" %}{% endif %}
|
||||
<div data-block-override="gotham:announcement-bar" data-bn-announcement data-bn-annkey="{{ variant|default:'info' }}:{{ message|slugify }}" class="bn-announcement w-full border-b border-[hsl(var(--primary)/0.4)] {{ anncls }} {{ class }}">
|
||||
<div class="max-w-7xl mx-auto px-10 py-2 relative flex items-center justify-center gap-2 text-sm text-center">
|
||||
<p class="reel-caps text-xs">{{ message }}{% if linkText and linkUrl %} <a href="{{ linkUrl }}" class="underline underline-offset-2 hover:opacity-80">{{ linkText }}</a>{% endif %}</p>
|
||||
{% if dismissable %}<button type="button" data-bn-ann-dismiss aria-label="Dismiss announcement" class="absolute right-3 top-1/2 -translate-y-1/2 inline-flex items-center justify-center p-1 rounded hover:bg-black/10 transition-colors"><svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg></button>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if dismissable %}<script>
|
||||
(function(){
|
||||
var bars=document.querySelectorAll('[data-bn-announcement]:not([data-bn-ann-wired])');
|
||||
bars.forEach(function(bar){
|
||||
bar.setAttribute('data-bn-ann-wired','1');
|
||||
var key='bnann:'+bar.getAttribute('data-bn-annkey');
|
||||
try{if(localStorage.getItem(key)==='1'){bar.style.display='none';return;}}catch(e){}
|
||||
var btn=bar.querySelector('[data-bn-ann-dismiss]');
|
||||
if(btn)btn.addEventListener('click',function(){bar.style.display='none';try{localStorage.setItem(key,'1');}catch(e){}});
|
||||
});
|
||||
})();
|
||||
</script>{% endif %}
|
||||
16
templates/overrides/gotham/audio-embed.ninjatpl
Normal file
16
templates/overrides/gotham/audio-embed.ninjatpl
Normal file
@ -0,0 +1,16 @@
|
||||
{# audio-embed override — gold-framed player card. Fields + facade JS mirror builtin verbatim: url, media, artwork, title, artist, download. #}
|
||||
<figure data-block-override="gotham:audio-embed" class="bn-audio reel-frame my-8 overflow-hidden bg-card text-card-foreground{% if class %} {{ class }}{% endif %}" data-bn-audio data-audio-url="{{ url|default:"" }}" data-audio-media="{% if media %}{{ media|mediaURL }}{% endif %}"><div class="flex items-center gap-4 p-4">{% if artwork %}<div class="reel-mat h-16 w-16 shrink-0 overflow-hidden bg-muted">{% img artwork alt=title|default:"" class="h-full w-full object-cover" %}</div>{% endif %}<div class="min-w-0 flex-1">{% if title %}<p class="reel-caps truncate text-sm" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ title }}</p>{% endif %}{% if artist %}<p class="truncate text-sm italic" style="color:hsl(var(--muted-foreground));">{{ artist }}</p>{% endif %}<div class="mt-2 flex items-center gap-3" data-bn-audio-slot><button type="button" data-bn-audio-play class="reel-btn inline-flex items-center gap-2 text-sm" aria-label="Play{% if title %}: {{ title }}{% endif %}">::lucide:play:sm:: Play</button>{% if download %}<a href="{% if media %}{{ media|mediaURL }}{% else %}{{ url }}{% endif %}" download class="reel-link inline-flex items-center gap-1 text-sm">::lucide:download:sm:: Download</a>{% endif %}</div></div></div></figure><script>
|
||||
(function(){
|
||||
if(window.__bnAudioFacade)return;window.__bnAudioFacade=true;
|
||||
document.addEventListener('click',function(e){
|
||||
var btn=e.target.closest('[data-bn-audio-play]');if(!btn)return;
|
||||
var fig=btn.closest('[data-bn-audio]');if(!fig)return;
|
||||
e.preventDefault();
|
||||
var src=fig.getAttribute('data-audio-media')||fig.getAttribute('data-audio-url')||'';
|
||||
if(!src)return;
|
||||
var slot=btn.closest('[data-bn-audio-slot]');
|
||||
var a=document.createElement('audio');a.src=src;a.controls=true;a.autoplay=true;a.preload='none';a.style.cssText='width:100%;min-width:12rem;height:2.25rem;';
|
||||
btn.remove();slot.insertBefore(a,slot.firstChild);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
33
templates/overrides/gotham/auth-form.ninjatpl
Normal file
33
templates/overrides/gotham/auth-form.ninjatpl
Normal file
@ -0,0 +1,33 @@
|
||||
{# auth-form override — gilded login/register. Wiring is UNTOUCHABLE and reproduced exactly from builtin auth_blocks.go: single shared #auth-message, /api/auth/login (email,password) and /api/auth/register (username,email,password), hx-target #auth-message. Captcha widget injected from _captcha_widget when captchaEnabled (server enforces). Fields: default_tab, show_social (social omitted — needs Go/site config; graceful). #}
|
||||
{% if context.isPublicLoggedIn %}
|
||||
<div data-block-override="gotham:auth-form" class="auth-form-logged-in mx-auto max-w-md text-center py-8">
|
||||
<p class="italic" style="color:hsl(var(--muted-foreground));font-family:var(--font-body, 'Cormorant Garamond', Georgia, serif);">You are signed in as <strong style="color:hsl(var(--foreground));">{{ context.publicUsername }}</strong></p>
|
||||
<a href="/account" class="reel-link mt-3 inline-block">My account</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div data-block-override="gotham:auth-form" class="auth-form reel-frame mx-auto max-w-md bg-card p-8">
|
||||
<div id="auth-message"></div>
|
||||
<div class="mb-6 flex border-b border-[hsl(var(--primary)/0.3)]">
|
||||
<button type="button" onclick="document.getElementById('login-panel').classList.remove('hidden');document.getElementById('register-panel').classList.add('hidden');this.setAttribute('data-active','1');this.nextElementSibling.removeAttribute('data-active');" data-auth-tab {% if default_tab != "register" %}data-active="1"{% endif %} class="reel-caps flex-1 py-2 text-center text-xs border-b-2 border-transparent data-[active]:border-[hsl(var(--primary))] data-[active]:text-primary">Sign in</button>
|
||||
<button type="button" onclick="document.getElementById('register-panel').classList.remove('hidden');document.getElementById('login-panel').classList.add('hidden');this.setAttribute('data-active','1');this.previousElementSibling.removeAttribute('data-active');" data-auth-tab {% if default_tab == "register" %}data-active="1"{% endif %} class="reel-caps flex-1 py-2 text-center text-xs border-b-2 border-transparent data-[active]:border-[hsl(var(--primary))] data-[active]:text-primary">Register</button>
|
||||
</div>
|
||||
<div id="login-panel" class="{% if default_tab == "register" %}hidden{% endif %}">
|
||||
<form hx-post="/api/auth/login" hx-target="#auth-message" hx-swap="innerHTML">
|
||||
<div class="mb-3"><label class="reel-caps block text-xs mb-1" style="color:hsl(var(--primary));">Email</label><input type="email" name="email" required class="w-full rounded-none border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:border-[hsl(var(--primary))] focus:ring-1 focus:ring-ring" /></div>
|
||||
<div class="mb-4"><label class="reel-caps block text-xs mb-1" style="color:hsl(var(--primary));">Password</label><input type="password" name="password" required class="w-full rounded-none border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:border-[hsl(var(--primary))] focus:ring-1 focus:ring-ring" /></div>
|
||||
{% if _captcha_widget %}{{ _captcha_widget|safe }}{% endif %}
|
||||
<button type="submit" class="reel-btn w-full" hx-disabled-elt="this">Sign in</button>
|
||||
<p class="text-center mt-3 text-sm"><a href="/forgot-password" class="reel-link">Forgot password?</a></p>
|
||||
</form>
|
||||
</div>
|
||||
<div id="register-panel" class="{% if default_tab != "register" %}hidden{% endif %}">
|
||||
<form hx-post="/api/auth/register" hx-target="#auth-message" hx-swap="innerHTML">
|
||||
<div class="mb-3"><label class="reel-caps block text-xs mb-1" style="color:hsl(var(--primary));">Username</label><input type="text" name="username" required pattern="[a-zA-Z0-9_-]{3,30}" class="w-full rounded-none border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:border-[hsl(var(--primary))] focus:ring-1 focus:ring-ring" /></div>
|
||||
<div class="mb-3"><label class="reel-caps block text-xs mb-1" style="color:hsl(var(--primary));">Email</label><input type="email" name="email" required class="w-full rounded-none border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:border-[hsl(var(--primary))] focus:ring-1 focus:ring-ring" /></div>
|
||||
<div class="mb-4"><label class="reel-caps block text-xs mb-1" style="color:hsl(var(--primary));">Password</label><input type="password" name="password" required minlength="8" class="w-full rounded-none border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:border-[hsl(var(--primary))] focus:ring-1 focus:ring-ring" /></div>
|
||||
{% if _captcha_widget %}{{ _captcha_widget|safe }}{% endif %}
|
||||
<button type="submit" class="reel-btn w-full" hx-disabled-elt="this">Create account</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
31
templates/overrides/gotham/auth-status.ninjatpl
Normal file
31
templates/overrides/gotham/auth-status.ninjatpl
Normal file
@ -0,0 +1,31 @@
|
||||
{# auth-status override — header login state. Logged-out: Sign in / Reserve links. Logged-in: gold monogram + dropdown (logout posts /api/auth/logout). Driven by context.isPublicLoggedIn / context.publicDisplayName / context.publicUsername. #}
|
||||
{% if context.isPublicLoggedIn %}
|
||||
<div data-block-override="gotham:auth-status" class="auth-status relative">
|
||||
<button type="button" onclick="this.nextElementSibling.classList.toggle('hidden')" class="reel-caps flex items-center gap-2 text-xs opacity-90 hover:opacity-100 px-3 py-1.5 transition-colors">
|
||||
<span class="inline-flex h-7 w-7 items-center justify-center rounded-full text-[0.7rem] font-bold" style="background-color:hsl(var(--primary));color:hsl(var(--primary-foreground));">{{ context.publicDisplayName|default:context.publicUsername|first|upper }}</span>
|
||||
<span>{{ context.publicDisplayName|default:context.publicUsername }}</span>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div id="auth-dropdown-menu" class="hidden absolute right-0 top-full mt-1 w-48 reel-frame bg-card py-1 z-50">
|
||||
<a href="/account" class="block px-4 py-2 text-sm text-foreground hover:bg-accent">My profile</a>
|
||||
<a href="/account/reviews" class="block px-4 py-2 text-sm text-foreground hover:bg-accent">My reviews</a>
|
||||
<hr class="my-1 reel-rule"/>
|
||||
<button hx-post="/api/auth/logout" hx-swap="none" class="block w-full text-left px-4 py-2 text-sm text-destructive hover:bg-accent">Sign out</button>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
document.addEventListener('click', function(e) {
|
||||
var menu = document.getElementById('auth-dropdown-menu');
|
||||
var btn = document.querySelector('.auth-status > button');
|
||||
if (!menu || !btn) return;
|
||||
if (!btn.contains(e.target) && !menu.contains(e.target)) { menu.classList.add('hidden'); }
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
{% else %}
|
||||
<div data-block-override="gotham:auth-status" class="auth-status flex items-center gap-2">
|
||||
<a href="/login" class="reel-caps text-xs px-3 py-1.5 opacity-80 hover:opacity-100 hover:text-primary transition-colors">Sign in</a>
|
||||
<a href="/login?tab=register" class="reel-btn text-xs">Reserve</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
22
templates/overrides/gotham/author-bio-hero.ninjatpl
Normal file
22
templates/overrides/gotham/author-bio-hero.ninjatpl
Normal file
@ -0,0 +1,22 @@
|
||||
{# author-bio-hero (gotham) — author-page header from the `authors` provider. #}
|
||||
{% if author %}
|
||||
<section data-block-override="gotham:author-bio-hero" class="reel-rule-thick bg-background py-12 text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto flex max-w-3xl flex-col items-center px-4 text-center">
|
||||
{% if showAvatar and author.avatar_url %}<div class="mb-6 h-24 w-24 overflow-hidden rounded-full bg-muted ring-2 ring-primary">{% img author.avatar_url alt=author.display_name class="h-full w-full object-cover" %}</div>{% endif %}
|
||||
<div class="mb-4 flex items-center justify-center gap-4"><span class="reel-rule w-12"></span><span class="reel-rule w-12"></span></div>
|
||||
<h1 class="reel-caps text-3xl md:text-4xl text-foreground" style="font-family:var(--font-heading, "Antonio", "Oswald", Georgia, serif)">{{ author.display_name }}</h1>
|
||||
{% if author.job_title or author.company %}<p class="reel-caps mt-2 text-sm text-primary">{{ author.job_title }}{% if author.job_title and author.company %} · {% endif %}{{ author.company }}</p>{% endif %}
|
||||
{% if author.bio %}<p class="mt-5 max-w-2xl leading-relaxed text-muted-foreground" style="font-family:var(--font-body, "Cormorant Garamond", "Cormorant", Georgia, serif)">{{ author.bio }}</p>{% endif %}
|
||||
<div class="mt-6 flex flex-wrap items-center justify-center gap-4 text-sm text-muted-foreground">
|
||||
{% if showPostCount %}<span>{{ author.post_count|default:0 }} post{% if author.post_count != 1 %}s{% endif %}</span>{% endif %}
|
||||
{% if author.location %}<span>{{ author.location }}</span>{% endif %}
|
||||
</div>
|
||||
{% if showSocial %}<div class="mt-6 flex items-center justify-center gap-4">
|
||||
{% if author.website_url %}<a href="{{ author.website_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="Website"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/lucide.svg#globe"></use></svg></a>{% endif %}
|
||||
{% if author.twitter_handle %}<a href="https://twitter.com/{{ author.twitter_handle|cut:"@" }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="Twitter"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#x"></use></svg></a>{% endif %}
|
||||
{% if author.linkedin_url %}<a href="{{ author.linkedin_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="LinkedIn"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#linkedin"></use></svg></a>{% endif %}
|
||||
{% if author.github_url %}<a href="{{ author.github_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="GitHub"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#github"></use></svg></a>{% endif %}
|
||||
</div>{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
14
templates/overrides/gotham/author-bio.ninjatpl
Normal file
14
templates/overrides/gotham/author-bio.ninjatpl
Normal file
@ -0,0 +1,14 @@
|
||||
{# author-bio (gotham) — byline card sourced from the `post` provider's author_* fields. #}
|
||||
{% if post.author_name %}
|
||||
<aside data-block-override="gotham:author-bio" class="reel-frame bg-card p-6 text-card-foreground{% if class %} {{ class }}{% endif %}">
|
||||
{% if heading %}<p class="reel-caps mb-4 text-xs text-primary">{{ heading }}</p>{% endif %}
|
||||
<div class="flex items-start gap-4">
|
||||
{% if showAvatar and post.author_avatar %}<div class="h-14 w-14 flex-shrink-0 overflow-hidden rounded-full bg-muted ring-1 ring-[hsl(var(--primary)/0.5)]">{% img post.author_avatar alt=post.author_name class="h-full w-full object-cover" %}</div>{% endif %}
|
||||
<div class="min-w-0">
|
||||
<h3 class="reel-caps text-base text-card-foreground" style="font-family:var(--font-heading, "Antonio", "Oswald", Georgia, serif)">{% if post.author_slug %}<a href="/author/{{ post.author_slug }}" class="transition-colors hover:text-primary">{{ post.author_name }}</a>{% else %}{{ post.author_name }}{% endif %}</h3>
|
||||
<span class="reel-rule mt-2 block w-10"></span>
|
||||
{% if post.author_bio %}<p class="mt-2 text-sm text-muted-foreground" style="font-family:var(--font-body, "Cormorant Garamond", "Cormorant", Georgia, serif)">{{ post.author_bio }}</p>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
{% endif %}
|
||||
24
templates/overrides/gotham/blog-hero.ninjatpl
Normal file
24
templates/overrides/gotham/blog-hero.ninjatpl
Normal file
@ -0,0 +1,24 @@
|
||||
{# blog-hero (gotham) — blog-index header + featured posts from the `posts` provider. #}
|
||||
<section data-block-override="gotham:blog-hero" class="bg-background py-12 text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-6xl px-4">
|
||||
<div class="mx-auto max-w-2xl text-center">
|
||||
{% if title %}<h1 class="reel-caps reel-underline text-4xl md:text-5xl text-foreground" style="font-family:var(--font-heading, "Antonio", "Oswald", Georgia, serif)">{{ title }}</h1>{% endif %}
|
||||
{% if subtitle %}<p class="mt-4 text-lg text-muted-foreground" style="font-family:var(--font-body, "Cormorant Garamond", "Cormorant", Georgia, serif)">{{ subtitle }}</p>{% endif %}
|
||||
</div>
|
||||
{% if posts %}
|
||||
<div class="mt-12 grid grid-cols-1 gap-8 md:grid-cols-3">
|
||||
{% for post in posts %}
|
||||
<article class="group flex flex-col{% if forloop.First %} md:col-span-3 md:flex-row md:items-stretch md:gap-8{% endif %}">
|
||||
{% if post.featured_image_url %}<a href="{{ post.url }}" class="reel-mat mb-4 block overflow-hidden bg-muted{% if forloop.First %} md:mb-0 md:w-1/2{% endif %}">{% img post.featured_image_url alt=post.title class="aspect-video h-full w-full object-cover transition-transform duration-500 group-hover:scale-105" %}</a>{% endif %}
|
||||
<div class="flex flex-col justify-center{% if forloop.First %} md:w-1/2{% endif %}">
|
||||
<span class="reel-rule mb-4 block w-12"></span>
|
||||
<h2 class="reel-caps {% if forloop.First %}text-2xl{% else %}text-lg{% endif %} leading-snug text-foreground" style="font-family:var(--font-heading, "Antonio", "Oswald", Georgia, serif)"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h2>
|
||||
{% if showExcerpt and post.excerpt %}<p class="mt-3 text-sm text-muted-foreground" style="font-family:var(--font-body, "Cormorant Garamond", "Cormorant", Georgia, serif)">{{ post.excerpt }}</p>{% endif %}
|
||||
<div class="mt-4 flex flex-wrap items-center gap-x-3 text-xs text-muted-foreground">{% if post.author_name %}<span class="reel-caps text-primary">{{ post.author_name }}</span>{% endif %}{% if post.published_at %}<span>{{ post.published_at|date:"M j, Y" }}</span>{% endif %}</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
15
templates/overrides/gotham/breadcrumbs.ninjatpl
Normal file
15
templates/overrides/gotham/breadcrumbs.ninjatpl
Normal file
@ -0,0 +1,15 @@
|
||||
{# breadcrumbs (gotham) — trail from the `breadcrumbs` provider (page ancestry). #}
|
||||
{# showHome prepends a Home link; separator is text ("/") for default or a chevron #}
|
||||
{# for the arrows template. The trailing is_current item renders as plain text. #}
|
||||
{% set arrows = template == "arrows" %}
|
||||
<nav aria-label="Breadcrumb" data-block-override="gotham:breadcrumbs" class="py-3{% if class %} {{ class }}{% endif %}">
|
||||
<ol class="flex flex-wrap items-center gap-1 text-xs">
|
||||
{% if showHome %}<li class="flex items-center"><a href="/" class="reel-caps text-muted-foreground transition-colors hover:text-primary">{{ homeLabel|default:"Home" }}</a></li>{% endif %}
|
||||
{% for item in breadcrumbs %}
|
||||
<li class="flex items-center">
|
||||
{% if showHome or not forloop.First %}{% if arrows %}<svg class="mx-2 h-3.5 w-3.5 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>{% else %}<span class="mx-2 text-primary">{{ separator|default:"/" }}</span>{% endif %}{% endif %}
|
||||
{% if item.is_current %}{% if showCurrent %}<span class="reel-caps text-foreground" aria-current="page">{{ item.label }}</span>{% endif %}{% else %}<a href="{{ item.url }}" class="reel-caps text-muted-foreground transition-colors hover:text-primary">{{ item.label }}</a>{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</nav>
|
||||
10
templates/overrides/gotham/button.ninjatpl
Normal file
10
templates/overrides/gotham/button.ninjatpl
Normal file
@ -0,0 +1,10 @@
|
||||
{# button override — gold-ringed lacquer pill. Fields (new builtin contract, per OVERRIDE-CONTRACT §4): link, style, label, size, icon, iconPosition, newTab, align. #}
|
||||
{% with align=align|default:"left" %}
|
||||
<div data-block-override="gotham:button" class="bn-button flex {% if align == "center" %}justify-center{% elif align == "right" %}justify-end{% else %}justify-start{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||
{% if style == "ghost" or style == "link" or style == "secondary" %}
|
||||
<a href="{{ link|default:'#' }}"{% if newTab %} target="_blank" rel="noopener"{% endif %} class="reel-link inline-flex items-center gap-2 text-sm">{% if icon and iconPosition != "right" %}::{{ icon }}:sm::{% endif %}{{ label|default:"Learn more" }}{% if icon and iconPosition == "right" %}::{{ icon }}:sm::{% endif %}</a>
|
||||
{% else %}
|
||||
<a href="{{ link|default:'#' }}"{% if newTab %} target="_blank" rel="noopener"{% endif %} class="reel-btn inline-flex items-center gap-2">{% if icon and iconPosition != "right" %}::{{ icon }}:sm::{% endif %}{{ label|default:"Learn more" }}{% if icon and iconPosition == "right" %}::{{ icon }}:sm::{% endif %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endwith %}
|
||||
4
templates/overrides/gotham/card.ninjatpl
Normal file
4
templates/overrides/gotham/card.ninjatpl
Normal file
@ -0,0 +1,4 @@
|
||||
{# card override — angular gold Deco panel. Fields mirror builtin: layout, link, media, title, text, linkLabel. Media via {% img %}. #}
|
||||
{% with layout=layout|default:"vertical" %}
|
||||
<article data-block-override="gotham:card" class="bn-card reel-frame overflow-hidden bg-card text-card-foreground transition-shadow hover:reel-foil {% if layout == "horizontal" %}flex flex-col sm:flex-row{% endif %}{% if class %} {{ class }}{% endif %}">{% if link %}<a href="{{ link }}" class="contents">{% endif %}{% if media %}<div class="overflow-hidden bg-muted {% if layout == "horizontal" %}sm:w-2/5 sm:shrink-0{% endif %}">{% if layout == "horizontal" %}{% img media alt=title|default:"" class="h-full w-full object-cover" %}{% else %}{% img media alt=title|default:"" class="aspect-video h-full w-full object-cover" %}{% endif %}</div>{% endif %}<div class="flex flex-col gap-2 p-5 md:p-6 {% if layout == "horizontal" %}justify-center{% endif %}">{% if title %}<h3 class="reel-caps text-lg" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ title }}</h3>{% endif %}{% if text %}<div class="text-sm leading-relaxed" style="color:hsl(var(--muted-foreground));font-family:var(--font-body, 'Cormorant Garamond', Georgia, serif);">{{ text|safe }}</div>{% endif %}{% if linkLabel and link %}<span class="mt-2 inline-flex items-center gap-1 text-sm reel-link">{{ linkLabel }}::lucide:arrow-right:sm::</span>{% endif %}</div>{% if link %}</a>{% endif %}</article>
|
||||
{% endwith %}
|
||||
18
templates/overrides/gotham/category-list.ninjatpl
Normal file
18
templates/overrides/gotham/category-list.ninjatpl
Normal file
@ -0,0 +1,18 @@
|
||||
{# category-list (gotham) — from the `categories` provider. list or pills. #}
|
||||
{% set pills = style == "pills" %}
|
||||
<section data-block-override="gotham:category-list" class="bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
{% if heading %}<h2 class="reel-caps mb-5 text-xs text-primary">{{ heading }}</h2>{% endif %}
|
||||
{% if pills %}
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% for cat in categories %}
|
||||
<a href="{{ cat.url }}" class="reel-caps inline-flex items-center gap-1.5 rounded-none border border-[hsl(var(--primary)/0.35)] bg-card px-4 py-1.5 text-xs text-card-foreground transition-colors hover:border-primary hover:text-primary">{{ cat.name }}{% if showCount %}<span class="text-xs text-muted-foreground">{{ cat.post_count }}</span>{% endif %}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<ul class="space-y-1">
|
||||
{% for cat in categories %}
|
||||
<li><a href="{{ cat.url }}" class="reel-rule flex items-center justify-between px-3 py-2.5 text-sm text-muted-foreground transition-colors hover:text-primary"><span class="reel-caps">{{ cat.name }}</span>{% if showCount %}<span class="rounded-none border border-[hsl(var(--primary)/0.35)] px-2 py-0.5 text-xs text-muted-foreground">{{ cat.post_count }}</span>{% endif %}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</section>
|
||||
17
templates/overrides/gotham/contact-card.ninjatpl
Normal file
17
templates/overrides/gotham/contact-card.ninjatpl
Normal file
@ -0,0 +1,17 @@
|
||||
<section data-block-override="gotham:contact-card" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-4xl px-4 text-center">
|
||||
{% if heading %}<h2 class="reel-caps reel-underline text-3xl md:text-4xl text-foreground" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ heading }}</h2>{% endif %}
|
||||
{% if description %}<p class="mt-4 mx-auto max-w-2xl text-lg text-muted-foreground" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ description }}</p>{% endif %}
|
||||
<div class="mt-12 grid grid-cols-1 gap-6 text-left {% if columns == 3 %}sm:grid-cols-2 lg:grid-cols-3{% elif columns == 1 %}{% else %}sm:grid-cols-2{% endif %}">
|
||||
{% for channel in channels %}
|
||||
<div class="reel-frame reel-foil flex items-start gap-4 rounded-none bg-card p-6 text-card-foreground">
|
||||
{% if channel.icon %}<span class="inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-none border border-[hsl(var(--primary)/0.45)] bg-primary/10 text-primary"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/{{ channel.icon|split:":"|first }}.svg#{{ channel.icon|split:":"|last }}"></use></svg></span>{% endif %}
|
||||
<div class="min-w-0">
|
||||
{% if channel.label %}<div class="reel-caps text-xs tracking-[0.16em] text-muted-foreground">{{ channel.label }}</div>{% endif %}
|
||||
{% if channel.value %}<div class="mt-1 break-words text-lg text-foreground" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{% if channel.link %}<a href="{{ channel.link }}" class="reel-link transition-colors hover:text-primary">{{ channel.value }}</a>{% else %}{{ channel.value }}{% endif %}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
43
templates/overrides/gotham/contact-form.ninjatpl
Normal file
43
templates/overrides/gotham/contact-form.ninjatpl
Normal file
@ -0,0 +1,43 @@
|
||||
{# contact-form override — reservations enquiry. ALL form wiring preserved verbatim from builtin: bid, {% form %} action/hx attrs, honeypot, field loop, form.submitted/errors, submit target. Only inputs restyled to Art Deco. #}
|
||||
{% with bid=_block_id|default:context.blockId %}
|
||||
<section data-block-override="gotham:contact-form" id="contact-form-{{ bid }}" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-2xl px-4">
|
||||
{% if form.submitted %}
|
||||
<div class="reel-frame bg-card p-10 text-center">
|
||||
<div class="mx-auto mb-4 inline-flex h-12 w-12 items-center justify-center rounded-full text-primary" style="box-shadow:inset 0 0 0 1px hsl(var(--primary));">::lucide:check:lg::</div>
|
||||
<p class="text-lg italic" style="font-family:var(--font-body, 'Cormorant Garamond', Georgia, serif);">{{ form.message|default:successMessage|default:"Thank you. Your table request has been received." }}</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center">
|
||||
{% if heading %}<h2 class="reel-caps text-3xl md:text-4xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ heading }}</h2>{% endif %}
|
||||
{% if description %}<p class="mt-3 text-lg italic" style="color:hsl(var(--muted-foreground));font-family:var(--font-body, 'Cormorant Garamond', Georgia, serif);">{{ description }}</p>{% endif %}
|
||||
<div class="mx-auto mt-5 mb-2 h-6 w-40 reel-strip" aria-hidden="true"></div>
|
||||
</div>
|
||||
{% if form.errors._form %}<div class="mt-6 border border-destructive/50 bg-destructive/10 px-4 py-3 text-sm text-destructive">{{ 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="mt-8 grid grid-cols-1 gap-5 sm:grid-cols-2" %}
|
||||
{% for field in fields %}
|
||||
<div class="{% if field.width == "half" %}sm:col-span-1{% else %}sm:col-span-2{% endif %}">
|
||||
{% if field.label %}<label for="cf-{{ bid }}-{{ field.name }}" class="reel-caps mb-1.5 block text-xs" style="color:hsl(var(--primary));">{{ field.label }}{% if field.required %} <span class="text-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="w-full rounded-none border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:border-[hsl(var(--primary))] focus:ring-1 focus:ring-ring"></textarea>
|
||||
{% elif field.type == "select" %}
|
||||
<select id="cf-{{ bid }}-{{ field.name }}" name="{{ field.name }}"{% if field.required %} required{% endif %} class="w-full rounded-none border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:border-[hsl(var(--primary))] focus:ring-1 focus:ring-ring">
|
||||
{% 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="w-full rounded-none border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:border-[hsl(var(--primary))] focus:ring-1 focus:ring-ring">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="hidden" aria-hidden="true"><label>Leave this field empty<input type="text" name="website" tabindex="-1" autocomplete="off"></label></div>
|
||||
<div class="sm:col-span-2 flex justify-center">
|
||||
<button type="submit" class="reel-btn inline-flex items-center justify-center">{{ submitLabel|default:"Request a table" }}</button>
|
||||
</div>
|
||||
{% endform %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endwith %}
|
||||
48
templates/overrides/gotham/countdown.ninjatpl
Normal file
48
templates/overrides/gotham/countdown.ninjatpl
Normal file
@ -0,0 +1,48 @@
|
||||
<section data-block-override="gotham:countdown" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-4xl px-4 text-center">
|
||||
{% if heading %}<h2 class="reel-caps text-3xl tracking-tight md:text-4xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ heading }}</h2>{% endif %}
|
||||
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-lg text-muted-foreground" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ intro }}</p>{% endif %}
|
||||
<div class="mt-10" data-countdown>
|
||||
<div class="flex items-start justify-center gap-3 sm:gap-6" data-countdown-timer data-target="{{ target }}" role="timer" aria-label="Time remaining">
|
||||
<div class="flex flex-col items-center {% if variant == "boxes" %}reel-frame min-w-[4.5rem] bg-card px-3 py-4 text-card-foreground{% endif %}"><span class="text-4xl font-bold tabular-nums text-primary sm:text-5xl" style="font-family:var(--font-mono)" data-unit="days">--</span><span class="reel-caps mt-1 text-xs text-muted-foreground">{{ labelDays|default:"Days" }}</span></div>
|
||||
<div class="flex flex-col items-center {% if variant == "boxes" %}reel-frame min-w-[4.5rem] bg-card px-3 py-4 text-card-foreground{% endif %}"><span class="text-4xl font-bold tabular-nums text-primary sm:text-5xl" style="font-family:var(--font-mono)" data-unit="hours">--</span><span class="reel-caps mt-1 text-xs text-muted-foreground">{{ labelHours|default:"Hours" }}</span></div>
|
||||
<div class="flex flex-col items-center {% if variant == "boxes" %}reel-frame min-w-[4.5rem] bg-card px-3 py-4 text-card-foreground{% endif %}"><span class="text-4xl font-bold tabular-nums text-primary sm:text-5xl" style="font-family:var(--font-mono)" data-unit="minutes">--</span><span class="reel-caps mt-1 text-xs text-muted-foreground">{{ labelMinutes|default:"Minutes" }}</span></div>
|
||||
<div class="flex flex-col items-center {% if variant == "boxes" %}reel-frame min-w-[4.5rem] bg-card px-3 py-4 text-card-foreground{% endif %}"><span class="text-4xl font-bold tabular-nums text-primary sm:text-5xl" style="font-family:var(--font-mono)" data-unit="seconds">--</span><span class="reel-caps mt-1 text-xs text-muted-foreground">{{ labelSeconds|default:"Seconds" }}</span></div>
|
||||
</div>
|
||||
<div class="mt-8 hidden" data-countdown-expired>
|
||||
<p class="reel-caps text-xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ expiredMessage|default:"This has started." }}</p>
|
||||
{% if expiredUrl and expiredLabel %}<a href="{{ expiredUrl }}" class="reel-btn mt-4 inline-flex items-center gap-2">{{ expiredLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<noscript><p class="mt-6 text-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.classList.remove('hidden');
|
||||
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>
|
||||
14
templates/overrides/gotham/cta.ninjatpl
Normal file
14
templates/overrides/gotham/cta.ninjatpl
Normal file
@ -0,0 +1,14 @@
|
||||
<section data-block-override="gotham:cta" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-6xl px-4">
|
||||
<div class="{% if background == "band" %}reel-frame film-grain bg-primary px-6 py-14 text-center text-primary-foreground md:px-12{% elif background == "card" %}reel-frame bg-card px-6 py-14 text-center text-card-foreground md:px-12{% else %}reel-frame flex flex-col items-center gap-8 bg-muted px-6 py-12 text-foreground md:flex-row md:justify-between md:px-12 md:text-left{% endif %}">
|
||||
<div class="{% if background != "split" %}mx-auto max-w-2xl{% endif %}">
|
||||
{% if heading %}<h2 class="reel-caps text-3xl tracking-tight md:text-4xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ heading }}</h2>{% endif %}
|
||||
{% if text %}<p class="mt-3 text-lg {% if background == "band" %}text-primary-foreground/80{% else %}text-muted-foreground{% endif %}" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ text }}</p>{% endif %}
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center justify-center gap-3 {% if background != "split" %}mt-8{% else %}shrink-0{% endif %}">
|
||||
{% if primaryUrl and primaryLabel %}<a href="{{ primaryUrl }}" class="reel-btn inline-flex items-center gap-2">{{ primaryLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||
{% if secondaryUrl and secondaryLabel %}<a href="{{ secondaryUrl }}" class="reel-link inline-flex items-center">{{ secondaryLabel }}</a>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
13
templates/overrides/gotham/divider.ninjatpl
Normal file
13
templates/overrides/gotham/divider.ninjatpl
Normal file
@ -0,0 +1,13 @@
|
||||
{# divider override — folds in the old divider_fan: gold geometric rules. Fields mirror builtin: variant (line|ornament|spacer-rule), weight, width, icon. ornament renders a sunburst fan crest; spacer-rule a short centred gold rule; line a hairline gold rule. #}
|
||||
{% with variant=variant|default:"line" weight=weight|default:"thin" width=width|default:"full" %}
|
||||
{% if variant == "ornament" %}
|
||||
<div data-block-override="gotham:divider" class="bn-divider mx-auto my-10 flex flex-col items-center gap-2 {% if width == "narrow" %}max-w-[200px]{% elif width == "wide" %}max-w-3xl{% else %}w-full{% endif %}{% if class %} {{ class }}{% endif %}" role="separator">
|
||||
<div class="h-8 w-40 reel-strip" aria-hidden="true"></div>
|
||||
<div class="flex w-full items-center gap-4"><span class="reel-rule flex-1"></span><span style="color:hsl(var(--primary));">::{{ icon|default:"lucide:diamond" }}:sm::</span><span class="reel-rule flex-1"></span></div>
|
||||
</div>
|
||||
{% elif variant == "spacer-rule" %}
|
||||
<div data-block-override="gotham:divider" class="bn-divider mx-auto py-10 flex justify-center {% if width == "narrow" %}max-w-[200px]{% elif width == "wide" %}max-w-3xl{% else %}w-full{% endif %}{% if class %} {{ class }}{% endif %}" role="separator"><span class="{% if weight == "thick" %}reel-rule-thick{% else %}reel-rule{% endif %} w-16"></span></div>
|
||||
{% else %}
|
||||
<div data-block-override="gotham:divider" class="bn-divider mx-auto my-8 flex justify-center {% if width == "narrow" %}max-w-[200px]{% elif width == "wide" %}max-w-3xl{% else %}w-full{% endif %}{% if class %} {{ class }}{% endif %}" role="separator"><span class="{% if weight == "thick" %}reel-rule-thick{% else %}reel-rule{% endif %} w-full"></span></div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
40
templates/overrides/gotham/event-list.ninjatpl
Normal file
40
templates/overrides/gotham/event-list.ninjatpl
Normal file
@ -0,0 +1,40 @@
|
||||
<section data-block-override="gotham:event-list" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-6xl px-4 text-center">
|
||||
{% if heading %}<h2 class="reel-caps reel-underline text-3xl md:text-4xl text-foreground" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ heading }}</h2>{% endif %}
|
||||
{% if intro %}<p class="mt-4 mx-auto max-w-2xl text-lg text-muted-foreground" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ intro }}</p>{% endif %}
|
||||
{% if layout == "cards" %}
|
||||
<div class="mt-12 grid grid-cols-1 gap-8 text-left sm:grid-cols-2 lg:grid-cols-3">
|
||||
{% for event in events %}{% if not (hidePast and event.past) %}
|
||||
<article class="reel-frame reel-foil flex flex-col overflow-hidden rounded-none bg-card text-card-foreground{% if event.past %} opacity-60{% endif %}">
|
||||
{% if event.media %}<div class="reel-mat aspect-video overflow-hidden bg-muted">{% img event.media alt=event.title class="h-full w-full object-cover" %}</div>{% endif %}
|
||||
<div class="flex flex-1 flex-col gap-2 p-6">
|
||||
<div class="reel-caps text-xs tracking-[0.18em] text-primary"><time{% if event.datetime %} datetime="{{ event.datetime }}"{% endif %}>{{ event.start }}{% if event.end %} – {{ event.end }}{% endif %}</time>{% if event.timezone %} <span class="text-muted-foreground">{{ event.timezone }}</span>{% endif %}</div>
|
||||
<h3 class="text-xl text-foreground" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ event.title }}</h3>
|
||||
{% if event.venue %}<p class="reel-caps text-xs tracking-[0.14em] text-muted-foreground">{{ event.venue }}</p>{% endif %}
|
||||
{% if event.description %}<p class="text-sm text-muted-foreground" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ event.description }}</p>{% endif %}
|
||||
{% if event.link %}<a href="{{ event.link }}" class="reel-link mt-auto inline-flex items-center gap-2 pt-3 text-sm text-primary">{{ event.linkLabel|default:"Details" }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
{% endif %}{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<ul class="mt-12 divide-y divide-[hsl(var(--primary)/0.35)] border-y-2 border-[hsl(var(--primary)/0.35)] text-left">
|
||||
{% for event in events %}{% if not (hidePast and event.past) %}
|
||||
<li class="flex flex-col gap-3 py-6 sm:flex-row sm:items-center sm:gap-8{% if event.past %} opacity-60{% endif %}">
|
||||
<div class="shrink-0 sm:w-52">
|
||||
<div class="reel-caps text-sm tracking-[0.16em] text-primary"><time{% if event.datetime %} datetime="{{ event.datetime }}"{% endif %}>{{ event.start }}</time></div>
|
||||
{% if event.end %}<div class="text-xs text-muted-foreground">to {{ event.end }}</div>{% endif %}
|
||||
{% if event.timezone %}<div class="text-xs text-muted-foreground">{{ event.timezone }}</div>{% endif %}
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<h3 class="text-lg text-foreground" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ event.title }}</h3>
|
||||
{% if event.venue %}<p class="reel-caps text-xs tracking-[0.14em] text-muted-foreground">{{ event.venue }}</p>{% endif %}
|
||||
{% if event.description %}<p class="mt-1 text-sm text-muted-foreground" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ event.description }}</p>{% endif %}
|
||||
</div>
|
||||
{% if event.link %}<a href="{{ event.link }}" class="reel-btn inline-flex shrink-0 items-center gap-2">{{ event.linkLabel|default:"Details" }}</a>{% endif %}
|
||||
</li>
|
||||
{% endif %}{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
17
templates/overrides/gotham/faq.ninjatpl
Normal file
17
templates/overrides/gotham/faq.ninjatpl
Normal file
@ -0,0 +1,17 @@
|
||||
<section data-block-override="gotham:faq" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-3xl px-4">
|
||||
{% if heading %}<h2 class="reel-caps text-center text-3xl text-foreground md:text-4xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ heading }}</h2>{% endif %}
|
||||
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ intro }}</p>{% endif %}
|
||||
<div class="mt-10 divide-y divide-[hsl(var(--primary)/0.25)] border-y border-[hsl(var(--primary)/0.4)]">
|
||||
{% for item in items %}
|
||||
<details class="group py-2"{% if variant == "open-list" %} open{% endif %}>
|
||||
<summary class="flex cursor-pointer list-none items-center justify-between gap-4 py-3 text-lg font-medium marker:content-none [&::-webkit-details-marker]:hidden" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">
|
||||
<span>{{ item.question }}</span>
|
||||
<svg class="h-5 w-5 shrink-0 text-primary transition-transform duration-200 group-open:rotate-180" aria-hidden="true"><use href="/icons/lucide.svg#chevron-down"></use></svg>
|
||||
</summary>
|
||||
<div class="pb-4 text-muted-foreground [&_a]:text-primary [&_a]:underline" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ item.answer|safe }}</div>
|
||||
</details>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
17
templates/overrides/gotham/feature-grid.ninjatpl
Normal file
17
templates/overrides/gotham/feature-grid.ninjatpl
Normal file
@ -0,0 +1,17 @@
|
||||
<section data-block-override="gotham:feature-grid" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-6xl px-4">
|
||||
{% if eyebrow %}<div class="mb-3 flex items-center justify-center gap-4"><span class="reel-rule w-12"></span><p class="reel-caps text-xs text-primary">{{ eyebrow }}</p><span class="reel-rule w-12"></span></div>{% endif %}
|
||||
{% if heading %}<h2 class="reel-caps mt-2 text-center text-3xl text-foreground md:text-4xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ heading }}</h2>{% endif %}
|
||||
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ intro }}</p>{% endif %}
|
||||
<div class="mt-12 grid grid-cols-1 gap-6 sm:grid-cols-2 {% if columns == 2 %}lg:grid-cols-2{% elif columns == 4 %}lg:grid-cols-4{% else %}lg:grid-cols-3{% endif %}">
|
||||
{% for item in items %}
|
||||
<div class="{% if layout == "card" %}reel-frame bg-card p-6 text-card-foreground reel-foil transition-shadow{% elif layout == "centered" %}text-center{% endif %}">
|
||||
{% if item.icon %}<div class="mb-4 inline-flex h-11 w-11 items-center justify-center border border-[hsl(var(--primary)/0.35)] bg-primary/10 text-primary{% if layout == "centered" %} mx-auto{% endif %}"><svg class="h-6 w-6" aria-hidden="true"><use href="/icons/{{ item.icon|split:":"|first }}.svg#{{ item.icon|split:":"|last }}"></use></svg></div>{% endif %}
|
||||
<h3 class="reel-caps text-lg text-foreground" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{% if item.linkUrl %}<a href="{{ item.linkUrl }}" class="transition-colors hover:text-primary">{{ item.title }}</a>{% else %}{{ item.title }}{% endif %}</h3>
|
||||
{% if item.text %}<p class="mt-2 text-muted-foreground" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ item.text }}</p>{% endif %}
|
||||
{% if item.linkUrl and item.linkLabel %}<a href="{{ item.linkUrl }}" class="reel-link mt-3 inline-flex items-center gap-1 text-sm">{{ item.linkLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
24
templates/overrides/gotham/featured-posts.ninjatpl
Normal file
24
templates/overrides/gotham/featured-posts.ninjatpl
Normal file
@ -0,0 +1,24 @@
|
||||
{# featured-posts (gotham) — grid of cards from the `posts` provider #}
|
||||
{# (params: limit, featured=true). Card: image, title, excerpt, meta. #}
|
||||
<section data-block-override="gotham:featured-posts" class="py-12 md:py-16 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-6xl px-4">
|
||||
{% if heading %}<h2 class="reel-caps reel-underline mb-10 text-center text-2xl md:text-3xl text-foreground" style="font-family:var(--font-heading, "Antonio", "Oswald", Georgia, serif)">{{ heading }}</h2>{% endif %}
|
||||
<div class="grid grid-cols-1 gap-8 sm:grid-cols-2 {% if columns == 2 %}lg:grid-cols-2{% elif columns == 4 %}lg:grid-cols-4{% else %}lg:grid-cols-3{% endif %}">
|
||||
{% for post in posts %}
|
||||
<article class="reel-frame group flex flex-col overflow-hidden bg-card text-card-foreground transition-shadow">
|
||||
{% if showFeaturedImage and post.featured_image_url %}<a href="{{ post.url }}" class="block aspect-video overflow-hidden bg-muted">{% img post.featured_image_url alt=post.title class="h-full w-full object-cover transition-transform duration-500 group-hover:scale-105" %}</a>{% endif %}
|
||||
<div class="flex flex-1 flex-col p-6 text-center">
|
||||
<h3 class="reel-caps text-lg leading-snug text-card-foreground" style="font-family:var(--font-heading, "Antonio", "Oswald", Georgia, serif)"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h3>
|
||||
<span class="reel-rule mx-auto mt-3 block w-10"></span>
|
||||
{% if showExcerpt and post.excerpt %}<p class="mt-3 flex-1 text-sm text-muted-foreground" style="font-family:var(--font-body, "Cormorant Garamond", "Cormorant", Georgia, serif)">{{ post.excerpt }}</p>{% endif %}
|
||||
<div class="mt-4 flex flex-wrap items-center justify-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
|
||||
{% if showAuthor and post.author_name %}<span class="reel-caps text-primary">{{ post.author_name }}</span>{% endif %}
|
||||
{% if showDate and post.published_at %}<span>{{ post.published_at|date:"M j, Y" }}</span>{% endif %}
|
||||
{% if post.reading_time %}<span>{{ post.reading_time }} min read</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
40
templates/overrides/gotham/footer.ninjatpl
Normal file
40
templates/overrides/gotham/footer.ninjatpl
Normal file
@ -0,0 +1,40 @@
|
||||
{# footer override — folds in the old footer block: centred gilded footer with gold rule, address, reservations, social. Structure/fields/signup_form preserved from builtin; reskinned to Art Deco. #}
|
||||
{% set fl = layout %}
|
||||
{% if not fl %}{% if template == "centered" %}{% set fl = "centered" %}{% else %}{% set fl = "columns" %}{% endif %}{% endif %}
|
||||
{% set fbg = backgroundColor|default:"bg-foreground text-background" %}
|
||||
{% macro sociallinks(cls) %}{% if social_items %}<div class="flex items-center gap-4 {{ cls }}">{% for s in social_items %}<a href="{{ s.url }}" target="_blank" rel="noopener" title="{{ s.label }}" class="text-background/70 hover:text-primary transition-colors"><span class="sr-only">{{ s.label }}</span>{% if s.icon %}::{{ s.icon }}::{% else %}{{ s.label }}{% endif %}</a>{% endfor %}</div>{% endif %}{% endmacro %}
|
||||
{% macro newsletter() %}{% if showNewsletter %}<div class="max-w-sm"><h3 class="reel-caps text-sm text-primary">{{ newsletterTitle|default:"Join the guest list" }}</h3>{% if newsletterDescription %}<p class="mt-1 text-sm italic text-background/70">{{ newsletterDescription }}</p>{% endif %}<div class="mt-3">{% signup_form list=newsletterListSlug|default:"main" source="footer" submit=newsletterButtonText|default:"Subscribe" %}</div></div>{% endif %}{% endmacro %}
|
||||
{% macro legalbar() %}<div class="mt-10 pt-6 border-t border-[hsl(var(--primary)/0.4)] flex flex-col sm:flex-row items-center justify-between gap-3 text-sm text-background/60"><p>{% if copyright %}© {{ copyright }}{% else %}© {{ companyName }}{% endif %}</p>{% if legal_items %}<nav class="flex flex-wrap items-center gap-x-4 gap-y-1">{% for l in legal_items %}<a href="{{ l.url|default:'#' }}"{% if l.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="hover:text-primary transition-colors">{{ l.label }}</a>{% endfor %}</nav>{% endif %}</div>{% endmacro %}
|
||||
<footer data-block-override="gotham:footer" class="w-full border-t-2 border-[hsl(var(--primary))] {{ fbg }} {{ class }}">
|
||||
<div class="{% if fullWidth %}w-full px-4 sm:px-6 lg:px-8{% else %}max-w-7xl mx-auto px-4 sm:px-6 lg:px-8{% endif %} py-16">
|
||||
{% if fl == "minimal" %}
|
||||
<div class="flex flex-col sm:flex-row items-center justify-between gap-6">
|
||||
<div class="flex items-center gap-3">{% if logo %}<img src="{{ logo }}" alt="{{ companyName }}" class="h-8 w-auto"/>{% elif companyName %}<span class="reel-caps text-lg" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);">{{ companyName }}</span>{% endif %}{% if tagline %}<span class="text-sm italic text-background/70 hidden sm:inline">{{ tagline }}</span>{% endif %}</div>
|
||||
{{ sociallinks("") }}
|
||||
</div>
|
||||
{{ legalbar() }}
|
||||
{% elif fl == "centered" %}
|
||||
<div class="flex flex-col items-center text-center gap-5">
|
||||
{% if logo %}<img src="{{ logo }}" alt="{{ companyName }}" class="h-10 w-auto"/>{% elif companyName %}<span class="reel-caps text-2xl text-primary" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);">{{ companyName }}</span>{% endif %}
|
||||
<div class="h-6 w-40 reel-strip" aria-hidden="true"></div>
|
||||
{% if tagline %}<p class="max-w-md text-sm italic text-background/70">{{ tagline }}</p>{% endif %}
|
||||
{% if columns %}<nav class="flex flex-wrap items-center justify-center gap-x-6 gap-y-2">{% for col in columns %}{% for item in col.items %}<a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="reel-caps text-xs text-background/80 hover:text-primary transition-colors">{{ item.label }}</a>{% endfor %}{% endfor %}</nav>{% endif %}
|
||||
{{ newsletter() }}
|
||||
{{ sociallinks("justify-center") }}
|
||||
</div>
|
||||
{{ legalbar() }}
|
||||
{% else %}
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-8">
|
||||
<div class="col-span-2 lg:col-span-2">
|
||||
{% if logo %}<img src="{{ logo }}" alt="{{ companyName }}" class="h-9 w-auto"/>{% elif companyName %}<span class="reel-caps text-xl text-primary" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);">{{ companyName }}</span>{% endif %}
|
||||
{% if tagline %}<p class="mt-3 max-w-xs text-sm italic text-background/70">{{ tagline }}</p>{% endif %}
|
||||
{% if address or email or phone %}<address class="mt-4 not-italic text-sm text-background/70 space-y-1">{% if address %}<div>{{ address }}</div>{% endif %}{% if email %}<div><a href="mailto:{{ email }}" class="hover:text-primary transition-colors">{{ email }}</a></div>{% endif %}{% if phone %}<div><a href="tel:{{ phone }}" class="hover:text-primary transition-colors">{{ phone }}</a></div>{% endif %}</address>{% endif %}
|
||||
{{ sociallinks("mt-5") }}
|
||||
</div>
|
||||
{% for col in columns %}<div><h3 class="reel-caps text-xs text-primary">{{ col.header }}</h3><ul class="mt-3 space-y-2">{% for item in col.items %}<li><a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="text-sm text-background/70 hover:text-primary transition-colors">{{ item.label }}</a></li>{% endfor %}</ul></div>{% endfor %}
|
||||
{% if showNewsletter %}<div class="col-span-2 lg:col-span-1">{{ newsletter() }}</div>{% endif %}
|
||||
</div>
|
||||
{{ legalbar() }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</footer>
|
||||
52
templates/overrides/gotham/gallery.ninjatpl
Normal file
52
templates/overrides/gotham/gallery.ninjatpl
Normal file
@ -0,0 +1,52 @@
|
||||
{# gallery override — folds in the old gallery_fan: symmetric gold-framed grid. Fields + lightbox JS mirror builtin verbatim: layout, gap, columns, lightbox, images[].src/.alt/.caption. #}
|
||||
{% with layout=layout|default:"grid" gap=gap|default:"md" cols=columns|default:3 %}<div data-block-override="gotham:gallery" class="bn-gallery my-8{% if class %} {{ class }}{% endif %}" data-bn-gallery data-lightbox="{% if lightbox %}true{% else %}false{% endif %}"><div class="{% if layout == "carousel" %}flex snap-x snap-mandatory overflow-x-auto pb-2 {% if gap == "sm" %}gap-2{% elif gap == "lg" %}gap-6{% else %}gap-4{% endif %}{% elif layout == "masonry" %}columns-2 {% if cols == 4 %}md:columns-4{% elif cols == 2 %}md:columns-2{% else %}md:columns-3{% endif %} {% if gap == "sm" %}gap-2{% elif gap == "lg" %}gap-6{% else %}gap-4{% endif %}{% else %}grid grid-cols-2 {% if cols == 4 %}md:grid-cols-4{% elif cols == 2 %}md:grid-cols-2{% else %}md:grid-cols-3{% endif %} {% if gap == "sm" %}gap-2{% elif gap == "lg" %}gap-6{% else %}gap-4{% endif %}{% endif %}">{% for image in images %}<a href="{{ image.src|mediaURL }}" data-bn-gallery-item data-caption="{{ image.caption|default:"" }}" class="reel-mat group relative block overflow-hidden bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 {% if layout == "carousel" %}w-64 shrink-0 snap-start sm:w-80{% elif layout == "masonry" %}mb-4 w-full break-inside-avoid{% endif %}">{% if layout == "grid" %}{% img image.src alt=image.alt|default:"" class="aspect-square w-full object-cover" %}{% else %}{% img image.src alt=image.alt|default:"" class="h-auto w-full" %}{% endif %}{% if image.caption %}<span class="sr-only">{{ image.caption }}</span>{% endif %}</a>{% endfor %}</div></div>{% if lightbox %}<script>
|
||||
(function(){
|
||||
if(window.__bnLightbox)return;window.__bnLightbox=true;
|
||||
var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
var ov,imgEl,capEl,items=[],idx=0,lastFocus=null;
|
||||
function build(){
|
||||
ov=document.createElement('div');
|
||||
ov.setAttribute('role','dialog');ov.setAttribute('aria-modal','true');ov.setAttribute('aria-label','Image viewer');ov.tabIndex=-1;
|
||||
ov.style.cssText='position:fixed;inset:0;z-index:9999;display:none;align-items:center;justify-content:center;background:rgba(0,0,0,0.9);padding:2rem;';
|
||||
var btn='position:absolute;width:2.75rem;height:2.75rem;display:inline-flex;align-items:center;justify-content:center;border-radius:9999px;border:none;background:hsl(var(--background));color:hsl(var(--foreground));cursor:pointer;';
|
||||
ov.innerHTML=
|
||||
'<button type="button" data-lb-close aria-label="Close" style="'+btn+'top:1rem;right:1rem;"><svg style="width:1.25rem;height:1.25rem" aria-hidden="true"><use href="/icons/lucide.svg#x"/></svg></button>'+
|
||||
'<button type="button" data-lb-prev aria-label="Previous image" style="'+btn+'left:1rem;top:50%;transform:translateY(-50%);"><svg style="width:1.25rem;height:1.25rem" aria-hidden="true"><use href="/icons/lucide.svg#chevron-left"/></svg></button>'+
|
||||
'<figure style="margin:0;max-width:92vw;max-height:92vh;display:flex;flex-direction:column;align-items:center;gap:0.75rem;"><img data-lb-img alt="" style="max-width:92vw;max-height:82vh;object-fit:contain;border-radius:0.5rem;'+(reduce?'':'transition:opacity .2s ease;')+'"><figcaption data-lb-cap style="color:hsl(var(--background));opacity:.85;font-size:.875rem;text-align:center;max-width:60ch;"></figcaption></figure>'+
|
||||
'<button type="button" data-lb-next aria-label="Next image" style="'+btn+'right:1rem;top:50%;transform:translateY(-50%);"><svg style="width:1.25rem;height:1.25rem" aria-hidden="true"><use href="/icons/lucide.svg#chevron-right"/></svg></button>';
|
||||
document.body.appendChild(ov);
|
||||
imgEl=ov.querySelector('[data-lb-img]');capEl=ov.querySelector('[data-lb-cap]');
|
||||
ov.addEventListener('click',function(e){if(e.target===ov||e.target.closest('[data-lb-close]'))close();});
|
||||
ov.querySelector('[data-lb-prev]').addEventListener('click',function(e){e.stopPropagation();show(idx-1);});
|
||||
ov.querySelector('[data-lb-next]').addEventListener('click',function(e){e.stopPropagation();show(idx+1);});
|
||||
}
|
||||
function show(i){
|
||||
if(!items.length)return;
|
||||
idx=(i+items.length)%items.length;
|
||||
var a=items[idx];var im=a.querySelector('img');
|
||||
imgEl.src=(im&&(im.currentSrc||im.src))||a.getAttribute('href');
|
||||
imgEl.alt=im?im.alt:'';
|
||||
var cap=a.getAttribute('data-caption')||'';capEl.textContent=cap;capEl.style.display=cap?'block':'none';
|
||||
}
|
||||
function open(g,link){
|
||||
if(!ov)build();
|
||||
items=Array.prototype.slice.call(g.querySelectorAll('[data-bn-gallery-item]'));
|
||||
lastFocus=document.activeElement;ov.style.display='flex';
|
||||
var multi=items.length>1;
|
||||
ov.querySelector('[data-lb-prev]').style.display=multi?'inline-flex':'none';
|
||||
ov.querySelector('[data-lb-next]').style.display=multi?'inline-flex':'none';
|
||||
show(items.indexOf(link));ov.focus();
|
||||
}
|
||||
function close(){if(ov)ov.style.display='none';if(lastFocus&&lastFocus.focus)lastFocus.focus();}
|
||||
document.addEventListener('click',function(e){
|
||||
var link=e.target.closest('[data-bn-gallery][data-lightbox="true"] [data-bn-gallery-item]');
|
||||
if(!link)return;e.preventDefault();open(link.closest('[data-bn-gallery]'),link);
|
||||
});
|
||||
document.addEventListener('keydown',function(e){
|
||||
if(!ov||ov.style.display==='none')return;
|
||||
if(e.key==='Escape'){e.preventDefault();close();}
|
||||
else if(e.key==='ArrowLeft')show(idx-1);
|
||||
else if(e.key==='ArrowRight')show(idx+1);
|
||||
});
|
||||
})();
|
||||
</script>{% endif %}{% endwith %}
|
||||
@ -1 +1,7 @@
|
||||
{% if level|integer == 1 %}<h1 class="text-4xl font-bold tracking-tight text-accent {{ textClass }}">{{ text }}</h1>{% elif level|integer == 3 %}<h3 class="text-2xl font-semibold text-accent {{ textClass }}">{{ text }}</h3>{% elif level|integer == 4 %}<h4 class="text-xl font-semibold text-accent {{ textClass }}">{{ text }}</h4>{% elif level|integer == 5 %}<h5 class="text-lg font-medium text-accent {{ textClass }}">{{ text }}</h5>{% elif level|integer == 6 %}<h6 class="text-base font-medium text-accent {{ textClass }}">{{ text }}</h6>{% else %}<h2 class="text-3xl font-semibold tracking-tight text-accent {{ textClass }}">{{ text }}</h2>{% endif %}
|
||||
{# heading override — gilded Antonio display caps with a hairline gold underline. Fields: text, level, textClass (mirrors builtin). #}
|
||||
{% if level == 1 %}<h1 data-block-override="gotham:heading" class="reel-caps reel-underline text-4xl md:text-5xl flex-1 {{ textClass }}" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ text }}</h1>
|
||||
{% elif level == 3 %}<h3 data-block-override="gotham:heading" class="reel-caps reel-underline text-2xl md:text-3xl flex-1 {{ textClass }}" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ text }}</h3>
|
||||
{% elif level == 4 %}<h4 data-block-override="gotham:heading" class="reel-caps reel-underline text-xl md:text-2xl flex-1 {{ textClass }}" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ text }}</h4>
|
||||
{% elif level == 5 %}<h5 data-block-override="gotham:heading" class="reel-caps reel-underline text-lg md:text-xl flex-1 {{ textClass }}" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ text }}</h5>
|
||||
{% elif level == 6 %}<h6 data-block-override="gotham:heading" class="reel-caps reel-underline text-base md:text-lg flex-1 {{ textClass }}" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ text }}</h6>
|
||||
{% else %}<h2 data-block-override="gotham:heading" class="reel-caps reel-underline text-3xl md:text-4xl flex-1 {{ textClass }}" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ text }}</h2>{% endif %}
|
||||
|
||||
42
templates/overrides/gotham/hero.ninjatpl
Normal file
42
templates/overrides/gotham/hero.ninjatpl
Normal file
@ -0,0 +1,42 @@
|
||||
{# hero override — folds in the old marquee_hero: sunburst overlay + stepped ziggurat framing, gold-foil Antonio headline. Structure/fields/{% img %} calls preserved from builtin. Styled-text fields accept {text,color} or a bare string. #}
|
||||
{% 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" %}
|
||||
{% set hasImage = backgroundImage %}
|
||||
{% set showOverlay = hasImage and overlayColor != "none" and overlayColor != "" %}
|
||||
{% macro herocontent(align) %}
|
||||
{% if eyebrowText %}<div class="mb-5 flex items-center gap-4 {% if align == "center" %}justify-center{% endif %}"><span class="reel-rule w-10"></span><span class="reel-caps text-xs text-primary">{{ eyebrowText }}</span><span class="reel-rule w-10"></span></div>{% endif %}
|
||||
{% if headlineText %}<h1 class="reel-caps text-4xl sm:text-5xl lg:text-6xl leading-tight" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ headlineText }}</h1>{% endif %}
|
||||
{% if subText %}<h2 class="mt-5 text-xl sm:text-2xl italic max-w-2xl{% if align == "center" %} mx-auto{% endif %}" style="color:hsl(var(--muted-foreground));font-family:var(--font-body, 'Cormorant Garamond', Georgia, serif);">{{ subText }}</h2>{% endif %}
|
||||
{% if descText %}<p class="mt-4 max-w-xl leading-relaxed{% if align == "center" %} mx-auto{% endif %}" style="color:hsl(var(--muted-foreground));">{{ descText }}</p>{% endif %}
|
||||
{% if pText or sText %}<div class="mt-8 flex flex-col sm:flex-row gap-4{% if align == "center" %} justify-center{% endif %}">
|
||||
{% if pText and pUrl %}<a href="{{ pUrl }}" class="reel-btn inline-flex items-center justify-center gap-2">{{ pText }}</a>{% endif %}
|
||||
{% if sText and sUrl %}<a href="{{ sUrl }}" class="reel-link inline-flex items-center justify-center">{{ sText }}</a>{% endif %}
|
||||
</div>{% endif %}
|
||||
{% if trustLogos %}<div class="mt-10">
|
||||
<p class="reel-caps text-xs text-muted-foreground mb-3">Acclaim</p>
|
||||
<div class="flex flex-wrap items-center gap-6{% if align == "center" %} justify-center{% endif %}">{% for logo in trustLogos %}{% if logo.url %}<a href="{{ logo.url }}" target="_blank" rel="noopener noreferrer" class="opacity-60 transition-opacity hover:opacity-100">{% img logo.image alt=logo.alt class="h-8 w-auto" %}</a>{% else %}{% img logo.image alt=logo.alt class="h-8 w-auto opacity-60" %}{% endif %}{% endfor %}</div>
|
||||
</div>{% endif %}
|
||||
{% endmacro %}
|
||||
{% if variant == "split" %}
|
||||
<section data-block-override="gotham:hero" class="hero grid grid-cols-1 lg:grid-cols-2 overflow-hidden bg-background text-foreground border-y border-[hsl(var(--primary)/0.5)]{% if class %} {{ class }}{% endif %}">
|
||||
<div class="relative min-h-[320px] bg-muted film-grain {% if mediaPosition == "left" %}order-1 lg:order-1{% else %}order-1 lg:order-2{% endif %}">{% if hasImage %}{% img backgroundImage alt=headlineText class="absolute inset-0 h-full w-full object-cover" layout="hero" %}<div class="absolute inset-0 letterbox-scrim" aria-hidden="true"></div>{% endif %}</div>
|
||||
<div class="flex flex-col justify-center px-6 py-16 lg:px-12 {% if mediaPosition == "left" %}order-2 lg:order-2{% else %}order-2 lg:order-1{% endif %}"><div class="reveal max-w-lg text-left">{{ herocontent("left") }}</div></div>
|
||||
</section>
|
||||
{% else %}
|
||||
{% set align = "center" %}
|
||||
{% if variant == "left-aligned" %}{% set align = "left" %}{% endif %}
|
||||
<section data-block-override="gotham:hero" data-spotlight class="hero relative flex flex-col overflow-hidden bg-background text-foreground film-grain {% if not hasImage %}spotlight gotham-hero-plate{% elif minHeight == "screen" %}min-h-screen{% elif minHeight == "50vh" %}min-h-[50vh]{% elif minHeight == "auto" %}min-h-[400px]{% else %}min-h-[75vh]{% endif %} {% if verticalAlign == "top" %}justify-start pt-20{% elif verticalAlign == "bottom" %}justify-end pb-20{% else %}justify-center{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||
{% if hasImage %}<div class="absolute inset-0 z-0">{% img backgroundImage alt=headlineText class="h-full w-full object-cover" layout="hero" %}{% if showOverlay %}<div class="absolute inset-0 {% if overlayColor == "light" %}bg-background/60{% elif overlayColor == "primary" %}bg-primary/50{% elif overlayOpacity <= 30 %}bg-black/30{% elif overlayOpacity <= 50 %}bg-black/50{% elif overlayOpacity <= 70 %}bg-black/70{% else %}bg-black/80{% endif %}"></div>{% endif %}<div class="absolute inset-0 letterbox-scrim" aria-hidden="true"></div></div>{% endif %}
|
||||
<div class="pointer-events-none absolute inset-x-0 top-0 z-[2] h-px bg-[hsl(var(--primary))]" aria-hidden="true"></div>
|
||||
<div class="pointer-events-none absolute inset-x-0 bottom-0 z-[2] h-px bg-[hsl(var(--primary))]" aria-hidden="true"></div>
|
||||
<div class="pointer-events-none absolute inset-0 z-[1]" aria-hidden="true" style="background-image:radial-gradient(circle at var(--sx,50%) var(--sy,42%), hsl(var(--primary) / 0.14), transparent 60%);"></div>
|
||||
<div class="reveal relative z-10 mx-auto w-full max-w-4xl px-6 py-16 sm:px-10 sm:py-20 {% if variant == "left-aligned" %}text-left{% else %}text-center{% endif %}">{{ herocontent(align) }}</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
31
templates/overrides/gotham/hours-location.ninjatpl
Normal file
31
templates/overrides/gotham/hours-location.ninjatpl
Normal file
@ -0,0 +1,31 @@
|
||||
{# hours-location override — gilded hours ledger + framed map. Map facade <button onclick> preserved verbatim. Fields mirror builtin: heading, hours[].day/.hours/.closed, specialHoursNote, address, phone, directionsUrl, mapEmbedUrl, mapButtonLabel. #}
|
||||
<section data-block-override="gotham:hours-location" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-5xl px-4">
|
||||
{% if heading %}<h2 class="reel-caps text-3xl md:text-4xl text-center" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ heading }}</h2>{% endif %}
|
||||
<div class="mt-10 grid grid-cols-1 gap-10 md:grid-cols-2">
|
||||
<div class="reel-frame bg-card p-6 md:p-8">
|
||||
{% if hours %}
|
||||
<table class="w-full text-sm">
|
||||
<tbody class="divide-y divide-[hsl(var(--primary)/0.2)]">
|
||||
{% for row in hours %}
|
||||
<tr>
|
||||
<th scope="row" class="reel-caps py-2 pr-4 text-left text-xs font-medium" style="color:hsl(var(--foreground));">{{ row.day }}</th>
|
||||
<td class="py-2 text-right tabular-nums {% if row.closed %}italic{% endif %}" style="{% if row.closed %}color:hsl(var(--muted-foreground));{% else %}color:hsl(var(--primary));{% endif %}">{% if row.closed %}Closed{% else %}{{ row.hours }}{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% if specialHoursNote %}<p class="mt-4 border-t border-[hsl(var(--primary)/0.3)] pt-3 text-sm italic" style="color:hsl(var(--muted-foreground));">{{ specialHoursNote }}</p>{% endif %}
|
||||
{% if address %}<div class="mt-6"><div class="reel-caps text-xs" style="color:hsl(var(--primary));">Address</div><address class="mt-1 whitespace-pre-line not-italic" style="font-family:var(--font-body, 'Cormorant Garamond', Georgia, serif);">{{ address }}</address></div>{% endif %}
|
||||
{% if phone %}<div class="mt-4"><div class="reel-caps text-xs" style="color:hsl(var(--primary));">Reservations</div><a href="tel:{{ phone }}" class="reel-link mt-1 inline-block">{{ phone }}</a></div>{% endif %}
|
||||
{% if directionsUrl %}<a href="{{ directionsUrl }}" target="_blank" rel="noopener noreferrer" class="reel-btn mt-6 inline-flex items-center gap-2">::lucide:navigation:sm:: Get directions</a>{% endif %}
|
||||
</div>
|
||||
{% if mapEmbedUrl %}
|
||||
<div class="reel-mat aspect-square w-full overflow-hidden bg-muted md:aspect-auto">
|
||||
<button type="button" data-map-src="{{ mapEmbedUrl }}" onclick="var f=document.createElement('iframe');f.src=this.getAttribute('data-map-src');f.setAttribute('class','h-full w-full border-0');f.setAttribute('loading','lazy');f.setAttribute('referrerpolicy','no-referrer-when-downgrade');f.setAttribute('title','Map');this.parentNode.replaceChild(f,this);" class="reel-caps flex h-full min-h-[16rem] w-full items-center justify-center gap-2 text-sm transition-colors hover:bg-accent hover:text-accent-foreground" style="color:hsl(var(--primary));">::lucide:map-pin:md:: {{ mapButtonLabel|default:"Load map" }}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
11
templates/overrides/gotham/image.ninjatpl
Normal file
11
templates/overrides/gotham/image.ninjatpl
Normal file
@ -0,0 +1,11 @@
|
||||
{# image override — stepped gold Deco frame. Fields mirror builtin: src, alt, caption, size, align, aspectRatio, objectFit. Media resolved via {% img %} (media filter is not registered in the block render path). #}
|
||||
{% with size=size|default:"full" align=align|default:"center" objectFit=objectFit|default:"cover" %}
|
||||
<figure data-block-override="gotham:image" class="my-8 flex-1 {% if align == "left" %}flex justify-start{% elif align == "right" %}flex justify-end{% else %}flex justify-center{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||
<div class="reel-mat {% if size == "xs" %}max-w-[200px] w-full{% elif size == "sm" %}max-w-[400px] w-full{% elif size == "md" %}max-w-[600px] w-full{% elif size == "lg" %}max-w-[800px] w-full{% else %}w-full{% endif %}">
|
||||
<div class="relative overflow-hidden bg-muted {% if aspectRatio == "square" %}aspect-square{% elif aspectRatio == "video" %}aspect-video{% elif aspectRatio == "portrait" %}aspect-[3/4]{% elif aspectRatio == "wide" %}aspect-[21/9]{% endif %}">
|
||||
{% if objectFit == "contain" %}{% img src alt=alt class="w-full h-full object-contain" layout=size %}{% elif objectFit == "fill" %}{% img src alt=alt class="w-full h-full object-fill" layout=size %}{% elif objectFit == "none" %}{% img src alt=alt class="w-full h-full object-none" layout=size %}{% else %}{% img src alt=alt class="w-full h-full object-cover" layout=size %}{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if caption %}<figcaption class="mt-3 text-center text-sm italic" style="color:hsl(var(--muted-foreground));font-family:var(--font-body, 'Cormorant Garamond', Georgia, serif);">{{ caption }}</figcaption>{% endif %}
|
||||
</figure>
|
||||
{% endwith %}
|
||||
15
templates/overrides/gotham/logo-strip.ninjatpl
Normal file
15
templates/overrides/gotham/logo-strip.ninjatpl
Normal file
@ -0,0 +1,15 @@
|
||||
<section data-block-override="gotham:logo-strip" class="py-12 md:py-16 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-6xl px-4">
|
||||
{% if heading %}<div class="mb-8 flex items-center justify-center gap-4"><span class="reel-rule w-12"></span><p class="reel-caps text-xs text-muted-foreground">{{ heading }}</p><span class="reel-rule w-12"></span></div>{% endif %}
|
||||
<div class="mt-8 {% if marquee %}group relative overflow-hidden [mask-image:linear-gradient(to_right,transparent,black_8%,black_92%,transparent)]{% endif %}">
|
||||
<div class="flex flex-wrap items-center justify-center gap-x-10 gap-y-6 {% if marquee %}flex-nowrap [animation:logo-marquee_30s_linear_infinite] motion-reduce:[animation:none] group-hover:[animation-play-state:paused]{% endif %}">
|
||||
{% for logo in logos %}
|
||||
{% if logo.url %}<a href="{{ logo.url }}" class="block shrink-0 opacity-80 transition hover:opacity-100">{% endif %}
|
||||
<span class="inline-flex h-8 items-center{% if grayscale %} grayscale transition-[filter] hover:grayscale-0{% endif %}">{% img logo.image alt=logo.alt class="h-8 w-auto object-contain" %}</span>
|
||||
{% if logo.url %}</a>{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% if marquee %}<style>@keyframes logo-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}</style>{% endif %}
|
||||
30
templates/overrides/gotham/menu-list.ninjatpl
Normal file
30
templates/overrides/gotham/menu-list.ninjatpl
Normal file
@ -0,0 +1,30 @@
|
||||
{# menu-list override — the supper-club menu card: centred Antonio section titles, gold leader rules, symmetric columns. Fields mirror builtin: heading, intro, columns, sections[].title/.note/.items[].name/.badges/.description/.price, currency, footnote. #}
|
||||
<section data-block-override="gotham:menu-list" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-5xl px-4 text-center">
|
||||
{% if heading %}<h2 class="reel-caps text-3xl md:text-4xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ heading }}</h2>{% endif %}
|
||||
{% if intro %}<p class="mx-auto mt-3 max-w-2xl text-lg italic" style="color:hsl(var(--muted-foreground));font-family:var(--font-body, 'Cormorant Garamond', Georgia, serif);">{{ intro }}</p>{% endif %}
|
||||
<div class="mx-auto mt-6 mb-4 h-6 w-40 reel-strip" aria-hidden="true"></div>
|
||||
<div class="mt-6 grid grid-cols-1 gap-x-14 gap-y-12 text-left {% if columns == 2 %}md:grid-cols-2{% endif %}">
|
||||
{% for section in sections %}
|
||||
<div class="menu-section">
|
||||
{% if section.title %}<h3 class="reel-caps reel-underline pb-2 text-center text-xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--primary));">{{ section.title }}</h3>{% endif %}
|
||||
{% if section.note %}<p class="mt-2 text-center text-sm italic" style="color:hsl(var(--muted-foreground));">{{ section.note }}</p>{% endif %}
|
||||
<ul class="mt-5 space-y-5">
|
||||
{% for item in section.items %}
|
||||
<li>
|
||||
<div class="flex items-baseline gap-3">
|
||||
<span class="shrink-0 font-medium" style="color:hsl(var(--foreground));">{{ item.name }}</span>
|
||||
<span class="min-w-6 flex-1 translate-y-[-2px] border-b border-dotted border-[hsl(var(--primary)/0.5)]" aria-hidden="true"></span>
|
||||
{% if item.price %}<span class="shrink-0 tabular-nums" style="color:hsl(var(--primary));font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);">{% if currency %}{{ currency }}{% endif %}{{ item.price }}</span>{% endif %}
|
||||
</div>
|
||||
{% if item.badges %}<div class="mt-1 flex flex-wrap gap-1.5">{% for badge in item.badges %}{% if badge.label %}<span class="reel-caps inline-block border border-[hsl(var(--primary)/0.4)] px-2 py-0.5 text-[0.65rem]" style="color:hsl(var(--muted-foreground));">{{ badge.label }}</span>{% endif %}{% endfor %}</div>{% endif %}
|
||||
{% if item.description %}<p class="mt-1 text-sm italic" style="color:hsl(var(--muted-foreground));font-family:var(--font-body, 'Cormorant Garamond', Georgia, serif);">{{ item.description }}</p>{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if footnote %}<p class="mt-12 text-xs" style="color:hsl(var(--muted-foreground));">{{ footnote }}</p>{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
104
templates/overrides/gotham/navbar.ninjatpl
Normal file
104
templates/overrides/gotham/navbar.ninjatpl
Normal file
@ -0,0 +1,104 @@
|
||||
{# navbar override — gilded masthead. Structure, fixed drawer ids, focus-trap JS and all data-* preserved verbatim from the builtin; presentation reskinned to Art Deco. Fields identical: variant/stickyBehavior/megaMenu/logoType/logoText/logo/companyName/cta/menu.items/... #}
|
||||
{% set nv = variant %}
|
||||
{% if not nv %}{% if template == "centered" %}{% set nv = "centered-logo" %}{% elif template == "transparent" %}{% set nv = "transparent-over-hero" %}{% else %}{% set nv = "standard" %}{% endif %}{% endif %}
|
||||
{% set sb = stickyBehavior %}
|
||||
{% if not sb %}{% if sticky %}{% set sb = "sticky" %}{% else %}{% set sb = "none" %}{% endif %}{% endif %}
|
||||
{% set use_mega = megaMenu or template == "mega" %}
|
||||
{% set is_transparent = nv == "transparent-over-hero" %}
|
||||
{% set cta_text = cta.text|default:ctaLabel %}
|
||||
{% set cta_url = cta.url|default:ctaUrl %}
|
||||
{% set bg = backgroundColor|default:"bg-background" %}
|
||||
{% macro navlink(item) %}<a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}{% if item.title %} title="{{ item.title }}"{% endif %} class="reel-caps px-3 py-2 text-xs text-foreground/80 hover:text-primary transition-colors {{ item.css_class }}">{{ item.label }}</a>{% endmacro %}
|
||||
{% macro dropdown(item) %}<div class="relative group">
|
||||
<button type="button" class="reel-caps px-3 py-2 text-xs text-foreground/80 hover:text-primary transition-colors inline-flex items-center gap-1">{{ item.label }}<svg class="h-4 w-4 transition-transform group-hover:rotate-180" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg></button>
|
||||
{% if use_mega %}<div class="absolute left-1/2 -translate-x-1/2 mt-1 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50"><div class="reel-frame p-6 bg-card grid gap-8" style="grid-template-columns:repeat({{ item.children|length|default:1 }},minmax(10rem,1fr));min-width:min(90vw,42rem)">{% for child in item.children %}<div class="space-y-1">{% if child.item_type == "header" %}<div class="reel-caps px-2 py-1 text-[0.65rem] text-primary border-b border-[hsl(var(--primary)/0.3)] mb-2">{{ child.label }}</div>{% else %}<a href="{{ child.url|default:'#' }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="block px-2 py-2 hover:bg-accent transition-colors {{ child.css_class }}">{% if child.icon %}<span class="mr-1.5 text-primary">::{{ child.icon }}::</span>{% endif %}<span class="text-sm font-medium text-foreground">{{ child.label }}</span>{% if child.description %}<span class="block text-xs text-muted-foreground mt-0.5">{{ child.description }}</span>{% endif %}</a>{% endif %}</div>{% endfor %}</div></div>
|
||||
{% else %}<div class="absolute left-0 mt-1 w-56 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50"><div class="reel-frame py-2 bg-card">{% for child in item.children %}{% if child.item_type == "header" %}<div class="reel-caps px-4 py-2 text-[0.65rem] text-primary">{{ child.label }}</div>{% else %}<a href="{{ child.url|default:'#' }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="block px-4 py-2 text-sm text-foreground hover:bg-accent transition-colors {{ child.css_class }}">{{ child.label }}</a>{% endif %}{% endfor %}</div></div>{% endif %}
|
||||
</div>{% endmacro %}
|
||||
{% macro brand(size) %}<a href="/" class="flex-shrink-0 flex items-center">{% if logoType == "text" and logoText.text %}<span class="reel-caps text-xl {{ logoText.color }}" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ logoText.text }}</span>{% elif logo %}<img src="{{ logo }}" alt="{{ alt|default:companyName }}" class="{{ size }} w-auto"{% if logoMaxWidth or logoMaxHeight %} style="{% if logoMaxWidth %}max-width:{{ logoMaxWidth }};{% endif %}{% if logoMaxHeight %}max-height:{{ logoMaxHeight }};{% endif %}"{% endif %}/>{% elif companyName %}<span class="reel-caps text-xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ companyName }}</span>{% endif %}</a>{% endmacro %}
|
||||
{% macro ctabtn(extra) %}{% if cta_text %}<a href="{{ cta_url|default:'#' }}" class="reel-btn inline-flex items-center {{ extra }}">{{ cta_text }}</a>{% endif %}{% endmacro %}
|
||||
<nav data-block-override="gotham:navbar" data-bn-navbar data-bn-nav-variant="{{ nv }}" data-bn-sticky="{{ sb }}"{% if is_transparent %} data-bn-transparent="1"{% endif %} class="bn-navbar w-full {% if sb == "sticky" or sb == "shrink-on-scroll" %}sticky top-0 z-40 {% endif %}{% if is_transparent %}absolute top-0 left-0 z-40 text-white{% else %}{{ bg }} border-b border-[hsl(var(--primary)/0.4)]{% endif %} {{ class }}">
|
||||
{% if showUtilityBar %}<div class="w-full text-xs {% if not showOnMobile %}hidden sm:block{% endif %} bg-foreground text-background"><div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-between h-8"><div>{{ utilityBarText|safe }}</div><div class="flex items-center gap-4">{% if utilityBarPhone %}<a href="tel:{{ utilityBarPhone }}" class="hover:text-primary transition-colors">{{ utilityBarPhone }}</a>{% endif %}{% if utilityBarEmail %}<a href="mailto:{{ utilityBarEmail }}" class="hover:text-primary transition-colors">{{ utilityBarEmail }}</a>{% endif %}</div></div></div>{% endif %}
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bn-navbar-row flex items-center justify-between h-16">
|
||||
{% if nv == "centered-logo" %}
|
||||
<div class="hidden md:flex items-center gap-1 flex-1">{% for item in menu.items %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endfor %}</div>
|
||||
<div class="flex items-center justify-center">{{ brand("h-9") }}</div>
|
||||
<div class="hidden md:flex items-center gap-3 flex-1 justify-end">{{ ctabtn("") }}</div>
|
||||
{% elif nv == "split" %}
|
||||
<div class="hidden md:flex items-center gap-1 flex-1">{% for item in menu.items %}{% if forloop.Counter0 * 2 < menu.items|length %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endif %}{% endfor %}</div>
|
||||
<div class="flex items-center justify-center">{{ brand("h-9") }}</div>
|
||||
<div class="hidden md:flex items-center gap-1 flex-1 justify-end">{% for item in menu.items %}{% if forloop.Counter0 * 2 >= menu.items|length %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endif %}{% endfor %}{{ ctabtn("ml-3") }}</div>
|
||||
{% else %}
|
||||
<div class="flex items-center">{{ brand("h-8") }}</div>
|
||||
<div class="hidden md:flex items-center gap-1">{% for item in menu.items %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endfor %}{{ ctabtn("ml-3") }}</div>
|
||||
{% endif %}
|
||||
<div class="flex items-center md:hidden">
|
||||
<label for="bn-navbar-toggle" data-bn-nav-open aria-controls="bn-navbar-drawer" aria-expanded="false" class="inline-flex items-center justify-center p-2 rounded-md cursor-pointer hover:bg-accent/50 transition-colors"><span class="sr-only">Open menu</span><svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{# --- Mobile off-canvas drawer: pure-CSS checkbox baseline (works with no JS) --- #}
|
||||
<input type="checkbox" id="bn-navbar-toggle" class="bn-navbar-toggle" aria-hidden="true"/>
|
||||
<label for="bn-navbar-toggle" class="bn-navbar-overlay fixed inset-0 bg-black/50 z-40 md:hidden" aria-hidden="true"></label>
|
||||
<div id="bn-navbar-drawer" class="bn-navbar-drawer fixed top-0 right-0 h-full w-80 max-w-[85vw] bg-background text-foreground z-50 shadow-xl md:hidden flex flex-col border-l border-[hsl(var(--primary)/0.4)]" role="dialog" aria-modal="true" aria-label="Site menu">
|
||||
<div class="flex items-center justify-between p-4 border-b border-[hsl(var(--primary)/0.3)]">{{ brand("h-8") }}<label for="bn-navbar-toggle" data-bn-nav-close class="p-2 rounded-md cursor-pointer text-muted-foreground hover:text-primary hover:bg-accent"><span class="sr-only">Close menu</span><svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg></label></div>
|
||||
<nav class="flex-1 overflow-y-auto py-4" aria-label="Mobile">{% for item in menu.items %}{% if item.children %}<details class="group/acc"><summary class="reel-caps flex items-center justify-between px-6 py-3 text-sm cursor-pointer hover:bg-accent transition-colors list-none">{{ item.label }}<svg class="h-4 w-4 transition-transform group-open/acc:rotate-180" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg></summary><div class="pl-8 pb-2">{% for child in item.children %}{% if child.item_type != "header" %}<a href="{{ child.url|default:'#' }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="block px-2 py-2 text-sm text-muted-foreground hover:text-primary transition-colors">{{ child.label }}</a>{% endif %}{% endfor %}</div></details>{% else %}<a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="reel-caps block px-6 py-3 text-sm hover:bg-accent transition-colors">{{ item.label }}</a>{% endif %}{% endfor %}</nav>
|
||||
{% if cta_text %}<div class="p-4 border-t border-[hsl(var(--primary)/0.3)]"><a href="{{ cta_url|default:'#' }}" class="reel-btn block w-full text-center">{{ cta_text }}</a></div>{% endif %}
|
||||
</div>
|
||||
</nav>
|
||||
<style>
|
||||
.bn-navbar-toggle{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;}
|
||||
.bn-navbar-drawer{transform:translateX(100%);transition:transform .3s ease;}
|
||||
.bn-navbar-overlay{opacity:0;pointer-events:none;transition:opacity .3s ease;}
|
||||
.bn-navbar-toggle:checked ~ .bn-navbar-drawer{transform:translateX(0);}
|
||||
.bn-navbar-toggle:checked ~ .bn-navbar-overlay{opacity:1;pointer-events:auto;}
|
||||
.bn-navbar-drawer details > summary::-webkit-details-marker{display:none;}
|
||||
[data-bn-sticky="shrink-on-scroll"].bn-navbar-shrunk .bn-navbar-row{height:3rem;}
|
||||
[data-bn-sticky="shrink-on-scroll"] .bn-navbar-row{transition:height .2s ease;}
|
||||
[data-bn-transparent].bn-navbar-solid{position:sticky;color:inherit;background:hsl(var(--background));border-bottom:1px solid hsl(var(--primary) / 0.4);}
|
||||
</style>
|
||||
<script>
|
||||
(function(){
|
||||
if(window.__bnNavInit)return;window.__bnNavInit=1;
|
||||
function ready(fn){if(document.readyState!='loading')fn();else document.addEventListener('DOMContentLoaded',fn);}
|
||||
ready(function(){
|
||||
document.querySelectorAll('[data-bn-navbar]').forEach(function(nav){
|
||||
var toggle=nav.querySelector('.bn-navbar-toggle');
|
||||
var drawer=nav.querySelector('.bn-navbar-drawer');
|
||||
var opener=nav.querySelector('[data-bn-nav-open]');
|
||||
if(toggle&&drawer){
|
||||
function focusables(){return drawer.querySelectorAll('a[href],button,label[for],input,summary,[tabindex]:not([tabindex="-1"])');}
|
||||
toggle.addEventListener('change',function(){
|
||||
var open=toggle.checked;
|
||||
document.documentElement.style.overflow=open?'hidden':'';
|
||||
document.body.style.overflow=open?'hidden':'';
|
||||
if(opener)opener.setAttribute('aria-expanded',open?'true':'false');
|
||||
if(open){var f=focusables();if(f.length)f[0].focus();}
|
||||
else if(opener&&opener.focus)opener.focus();
|
||||
});
|
||||
drawer.addEventListener('keydown',function(e){
|
||||
if(e.key==='Escape'){toggle.checked=false;toggle.dispatchEvent(new Event('change'));return;}
|
||||
if(e.key!=='Tab')return;
|
||||
var f=focusables();if(!f.length)return;
|
||||
var first=f[0],last=f[f.length-1];
|
||||
if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus();}
|
||||
else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus();}
|
||||
});
|
||||
drawer.querySelectorAll('a[href]').forEach(function(a){a.addEventListener('click',function(){toggle.checked=false;toggle.dispatchEvent(new Event('change'));});});
|
||||
}
|
||||
var sb=nav.getAttribute('data-bn-sticky');
|
||||
var transparent=nav.getAttribute('data-bn-transparent')==='1';
|
||||
if(sb==='shrink-on-scroll'||transparent){
|
||||
var overHero=false;
|
||||
if(transparent){var next=nav.nextElementSibling;overHero=!!next&&next.getBoundingClientRect().height>=240;if(!overHero)nav.classList.add('bn-navbar-solid');}
|
||||
var onScroll=function(){
|
||||
var y=window.scrollY||window.pageYOffset;
|
||||
if(sb==='shrink-on-scroll')nav.classList.toggle('bn-navbar-shrunk',y>40);
|
||||
if(transparent&&overHero)nav.classList.toggle('bn-navbar-solid',y>40);
|
||||
};
|
||||
window.addEventListener('scroll',onScroll,{passive:true});onScroll();
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
13
templates/overrides/gotham/newsletter-signup.ninjatpl
Normal file
13
templates/overrides/gotham/newsletter-signup.ninjatpl
Normal file
@ -0,0 +1,13 @@
|
||||
{# newsletter-signup (gotham) — the {% signup_form %} tag carries the #}
|
||||
{# untouchable subscribe wiring (/api/lists/subscribe HTMX post + honeypot); #}
|
||||
{# this template supplies only the gotham chrome around it. #}
|
||||
<section data-block-override="gotham:newsletter-signup" class="py-12 bg-background text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-3xl px-4">
|
||||
<div class="reel-frame {% if layout == "inline" %}film-grain bg-primary px-6 py-12 text-primary-foreground md:px-12{% else %}bg-card px-6 py-12 text-card-foreground md:px-10{% endif %} text-center">
|
||||
<div class="mb-4 flex items-center justify-center gap-4"><span class="reel-rule w-12"></span><span class="reel-rule w-12"></span></div>
|
||||
{% if title %}<h2 class="reel-caps text-2xl md:text-3xl" style="font-family:var(--font-heading, "Antonio", "Oswald", Georgia, serif)">{{ title }}</h2>{% endif %}
|
||||
{% if description %}<p class="mx-auto mt-4 max-w-xl {% if layout == "inline" %}text-primary-foreground/80{% else %}text-muted-foreground{% endif %}" style="font-family:var(--font-body, "Cormorant Garamond", "Cormorant", Georgia, serif)">{{ description }}</p>{% endif %}
|
||||
<div class="mx-auto mt-6 max-w-md">{% signup_form list=listSlug source=source submit=submitText success_message=successMessage placeholder=placeholder %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
21
templates/overrides/gotham/page-suggestions.ninjatpl
Normal file
21
templates/overrides/gotham/page-suggestions.ninjatpl
Normal file
@ -0,0 +1,21 @@
|
||||
{# page-suggestions override — themed 404 suggestions. Fields: title, showDescription, emptyMessage; suggestions[] from the provider (.slug/.title/.excerpt). #}
|
||||
{% if suggestions %}
|
||||
<div data-block-override="gotham:page-suggestions" class="page-suggestions py-10">
|
||||
{% if title %}<h2 class="reel-caps reel-underline mx-auto w-fit text-xl text-center" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">{{ title }}</h2>{% endif %}
|
||||
<ul class="mt-6 space-y-3 max-w-lg mx-auto">
|
||||
{% for suggestion in suggestions %}
|
||||
<li class="flex items-start gap-3">
|
||||
<span class="mt-0.5 shrink-0" style="color:hsl(var(--primary));">::lucide:chevron-right:sm::</span>
|
||||
<div>
|
||||
<a href="{{ suggestion.slug }}" class="reel-link font-medium">{{ suggestion.title }}</a>
|
||||
{% if showDescription and suggestion.excerpt %}<p class="mt-1 text-sm italic" style="color:hsl(var(--muted-foreground));">{{ suggestion.excerpt }}</p>{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% elif emptyMessage %}
|
||||
<div data-block-override="gotham:page-suggestions" class="page-suggestions py-10 text-center">
|
||||
<p class="italic" style="color:hsl(var(--muted-foreground));font-family:var(--font-body, 'Cormorant Garamond', Georgia, serif);">{{ emptyMessage }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
13
templates/overrides/gotham/password-reset-form.ninjatpl
Normal file
13
templates/overrides/gotham/password-reset-form.ninjatpl
Normal file
@ -0,0 +1,13 @@
|
||||
{# password-reset-form override — request stage only, matching builtin. Wiring UNTOUCHABLE: /api/auth/request-password-reset (email), hx-target #reset-message. Reskinned to Art Deco. #}
|
||||
<div data-block-override="gotham:password-reset-form" class="password-reset-form reel-frame mx-auto max-w-md bg-card p-8">
|
||||
<div id="reset-message"></div>
|
||||
<div id="request-reset-panel" class="text-center">
|
||||
<h2 class="reel-caps text-2xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif);color:hsl(var(--foreground));">Reset password</h2>
|
||||
<p class="mt-3 text-sm italic" style="color:hsl(var(--muted-foreground));font-family:var(--font-body, 'Cormorant Garamond', Georgia, serif);">Enter your email and we will send a link to reset your password.</p>
|
||||
<form hx-post="/api/auth/request-password-reset" hx-target="#reset-message" hx-swap="innerHTML" class="mt-6 text-left">
|
||||
<div class="mb-4"><label class="reel-caps block text-xs mb-1" style="color:hsl(var(--primary));">Email</label><input type="email" name="email" required class="w-full rounded-none border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:border-[hsl(var(--primary))] focus:ring-1 focus:ring-ring" /></div>
|
||||
<button type="submit" class="reel-btn w-full" hx-disabled-elt="this">Send reset link</button>
|
||||
</form>
|
||||
<p class="text-center mt-4 text-sm"><a href="/login" class="reel-link">Back to sign in</a></p>
|
||||
</div>
|
||||
</div>
|
||||
16
templates/overrides/gotham/popular-posts.ninjatpl
Normal file
16
templates/overrides/gotham/popular-posts.ninjatpl
Normal file
@ -0,0 +1,16 @@
|
||||
{# popular-posts (gotham) — compact ranked list from the `posts` provider. #}
|
||||
<section data-block-override="gotham:popular-posts" class="bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
{% if heading %}<h2 class="reel-caps mb-5 text-xs text-primary">{{ heading }}</h2>{% endif %}
|
||||
<ol class="space-y-4">
|
||||
{% for post in posts %}
|
||||
<li class="reel-rule flex items-start gap-3 pt-4">
|
||||
{% if showRank %}<span class="mt-0.5 text-lg font-bold tabular-nums text-primary" style="font-family:var(--font-heading, "Antonio", "Oswald", Georgia, serif)">{{ forloop.Counter }}</span>{% endif %}
|
||||
{% if showThumbnail and post.featured_image_url %}<a href="{{ post.url }}" class="reel-mat h-12 w-12 flex-shrink-0 overflow-hidden bg-muted">{% img post.featured_image_url alt=post.title class="h-full w-full object-cover" %}</a>{% endif %}
|
||||
<div class="min-w-0">
|
||||
<h3 class="reel-caps text-sm leading-snug text-foreground" style="font-family:var(--font-heading, "Antonio", "Oswald", Georgia, serif)"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h3>
|
||||
{% if post.published_at %}<p class="mt-1 text-xs text-muted-foreground">{{ post.published_at|date:"M j, Y" }}</p>{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</section>
|
||||
23
templates/overrides/gotham/post-hero.ninjatpl
Normal file
23
templates/overrides/gotham/post-hero.ninjatpl
Normal file
@ -0,0 +1,23 @@
|
||||
{# post-hero (gotham) — current post from the `post` provider. When the post #}
|
||||
{# has a featured image it becomes an overlaid banner; otherwise a clean text hero. #}
|
||||
{% set center = align != "left" %}
|
||||
{% if post.featured_image_url %}
|
||||
<section data-block-override="gotham:post-hero" class="hero relative flex min-h-[50vh] flex-col justify-end overflow-hidden bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="absolute inset-0 z-0">{% img post.featured_image_url alt=post.featured_image_alt class="h-full w-full object-cover" layout="hero" %}<div class="absolute inset-0 bg-black/70"></div><div class="film-grain absolute inset-0"></div></div>
|
||||
<div class="relative z-10 mx-auto w-full max-w-4xl px-4 py-14 sm:px-6 lg:px-8 {% if center %}text-center{% endif %}">
|
||||
{% if center %}<div class="mb-4 flex items-center justify-center gap-4"><span class="reel-rule w-12"></span><span class="reel-rule w-12"></span></div>{% endif %}
|
||||
<h1 class="reel-caps text-3xl leading-tight text-white sm:text-4xl lg:text-5xl" style="font-family:var(--font-heading, "Antonio", "Oswald", Georgia, serif)">{{ post.title }}</h1>
|
||||
{% if showExcerpt and post.excerpt %}<p class="mt-5 max-w-2xl text-lg text-white/80{% if center %} mx-auto{% endif %}" style="font-family:var(--font-body, "Cormorant Garamond", "Cormorant", Georgia, serif)">{{ post.excerpt }}</p>{% endif %}
|
||||
{% if showMeta %}<div class="mt-6 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-white/80{% if center %} justify-center{% endif %}">{% if post.author_name %}<span class="reel-caps text-white">{{ post.author_name }}</span>{% endif %}{% if post.published_at %}<span>{{ post.published_at|date:"F j, Y" }}</span>{% endif %}{% if post.reading_time %}<span>{{ post.reading_time }} min read</span>{% endif %}</div>{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% else %}
|
||||
<section data-block-override="gotham:post-hero" class="bg-background py-16 text-foreground md:py-20{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto w-full max-w-3xl px-4 {% if center %}text-center{% endif %}">
|
||||
{% if center %}<div class="mb-4 flex items-center justify-center gap-4"><span class="reel-rule w-12"></span><span class="reel-rule w-12"></span></div>{% endif %}
|
||||
<h1 class="reel-caps reel-underline text-3xl leading-tight sm:text-4xl lg:text-5xl text-foreground" style="font-family:var(--font-heading, "Antonio", "Oswald", Georgia, serif)">{{ post.title }}</h1>
|
||||
{% if showExcerpt and post.excerpt %}<p class="mt-5 text-lg text-muted-foreground{% if center %} mx-auto max-w-2xl{% endif %}" style="font-family:var(--font-body, "Cormorant Garamond", "Cormorant", Georgia, serif)">{{ post.excerpt }}</p>{% endif %}
|
||||
{% if showMeta %}<div class="mt-6 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-muted-foreground{% if center %} justify-center{% endif %}">{% if post.author_name %}<span class="reel-caps text-primary">{{ post.author_name }}</span>{% endif %}{% if post.published_at %}<span>{{ post.published_at|date:"F j, Y" }}</span>{% endif %}{% if post.reading_time %}<span>{{ post.reading_time }} min read</span>{% endif %}</div>{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
9
templates/overrides/gotham/post-metadata.ninjatpl
Normal file
9
templates/overrides/gotham/post-metadata.ninjatpl
Normal file
@ -0,0 +1,9 @@
|
||||
{# post-metadata (gotham) — author/date/reading-time row from the `post` provider. #}
|
||||
{% if post %}
|
||||
<div data-block-override="gotham:post-metadata" class="flex flex-wrap items-center gap-x-4 gap-y-2 text-sm text-muted-foreground{% if class %} {{ class }}{% endif %}">
|
||||
{% if showAuthor and post.author_name %}<span class="flex items-center gap-2">{% if showAvatar and post.author_avatar %}{% img post.author_avatar alt=post.author_name class="h-7 w-7 rounded-full object-cover ring-1 ring-[hsl(var(--primary)/0.5)]" %}{% endif %}<span class="reel-caps text-primary">{{ post.author_name }}</span></span>{% endif %}
|
||||
{% if showDate and post.published_at %}<time datetime="{{ post.published_at|date:"Y-m-d" }}">{{ post.published_at|date:"F j, Y" }}</time>{% endif %}
|
||||
{% if showReadingTime and post.reading_time %}<span>{{ post.reading_time }} min read</span>{% endif %}
|
||||
{% if post.categories %}<span class="flex flex-wrap gap-1.5">{% for cat in post.categories %}<a href="{{ cat.url|default:'#' }}" class="reel-caps rounded-none border border-[hsl(var(--primary)/0.35)] px-2 py-0.5 text-xs text-muted-foreground transition-colors hover:text-primary">{{ cat.name }}</a>{% endfor %}</span>{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
25
templates/overrides/gotham/pricing.ninjatpl
Normal file
25
templates/overrides/gotham/pricing.ninjatpl
Normal file
@ -0,0 +1,25 @@
|
||||
<section data-block-override="gotham:pricing" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-6xl px-4">
|
||||
{% if heading %}<h2 class="reel-caps text-center text-3xl text-foreground md:text-4xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ heading }}</h2>{% endif %}
|
||||
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ intro }}</p>{% endif %}
|
||||
<div class="mt-12 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{% for tier in tiers %}
|
||||
<div class="relative flex flex-col p-6 {% if tier.highlighted %}reel-frame bg-card text-card-foreground reel-foil{% else %}rounded-none border border-[hsl(var(--primary)/0.35)] bg-card text-card-foreground{% endif %}">
|
||||
{% if tier.badge %}<span class="reel-caps absolute -top-3 left-1/2 -translate-x-1/2 border border-primary bg-primary px-3 py-1 text-xs text-primary-foreground">{{ tier.badge }}</span>{% endif %}
|
||||
<h3 class="reel-caps text-center text-lg text-foreground" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ tier.name }}</h3>
|
||||
<div class="mx-auto my-3 flex items-center justify-center"><span class="reel-rule w-12"></span></div>
|
||||
<p class="flex items-baseline justify-center gap-1">
|
||||
<span class="text-4xl font-bold tracking-tight text-primary" style="font-family:var(--font-heading)">{% if currency %}{{ currency }}{% else %}${% endif %}{{ tier.price }}</span>
|
||||
{% if tier.period %}<span class="text-sm text-muted-foreground">{{ tier.period }}</span>{% endif %}
|
||||
</p>
|
||||
{% if tier.description %}<p class="mt-3 text-center text-sm text-muted-foreground" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ tier.description }}</p>{% endif %}
|
||||
{% if tier.features %}<ul class="mt-6 flex-1 space-y-3 text-sm">
|
||||
{% for feature in tier.features %}<li class="flex items-start gap-2"><svg class="mt-0.5 h-4 w-4 shrink-0 text-primary" aria-hidden="true"><use href="/icons/lucide.svg#check"></use></svg><span>{{ feature }}</span></li>{% endfor %}
|
||||
</ul>{% endif %}
|
||||
{% if tier.ctaUrl and tier.ctaLabel %}{% if tier.highlighted %}<a href="{{ tier.ctaUrl }}" class="reel-btn mt-8 inline-flex w-full items-center justify-center">{{ tier.ctaLabel }}</a>{% else %}<a href="{{ tier.ctaUrl }}" class="reel-link mt-8 inline-flex w-full items-center justify-center">{{ tier.ctaLabel }}</a>{% endif %}{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if billingNote %}<p class="mt-8 text-center text-sm text-muted-foreground">{{ billingNote }}</p>{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
8
templates/overrides/gotham/quote.ninjatpl
Normal file
8
templates/overrides/gotham/quote.ninjatpl
Normal file
@ -0,0 +1,8 @@
|
||||
{# quote override — supper-club press pull-quote: large italic Cormorant flanked by gold sunburst crests. Fields mirror builtin: quote, style, align, cite, attribution, role. #}
|
||||
{% with style=style|default:"block" align=align|default:"left" %}
|
||||
<figure data-block-override="gotham:quote" class="bn-quote my-10 {% if style == "inline-card" %}reel-frame bg-card p-8 md:p-10{% elif style == "pull" %}border-l-2 border-[hsl(var(--primary))] pl-6 md:pl-8 py-2{% else %}px-2 py-1{% endif %}{% if align == "center" %} text-center{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||
{% if align == "center" %}<div class="mb-5 h-6 reel-strip" aria-hidden="true"></div>{% endif %}
|
||||
<blockquote{% if cite %} cite="{{ cite }}"{% endif %} class="{% if style == "pull" %}text-2xl md:text-3xl leading-snug{% else %}text-xl md:text-2xl leading-relaxed{% endif %} italic" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif);color:hsl(var(--foreground));">{{ quote|safe }}</blockquote>
|
||||
{% if attribution or role or cite %}<figcaption class="mt-4 text-sm{% if align == "center" %} flex flex-col items-center{% endif %}" style="color:hsl(var(--muted-foreground));">{% if attribution %}<span class="reel-caps text-xs" style="color:hsl(var(--primary));">{{ attribution }}</span>{% endif %}{% if role %}<span class="{% if attribution %}block {% endif %}italic">{{ role }}</span>{% endif %}{% if cite %}<a href="{{ cite }}" class="reel-link {% if attribution or role %}mt-1 {% endif %}inline-block text-xs">Source</a>{% endif %}</figcaption>{% endif %}
|
||||
</figure>
|
||||
{% endwith %}
|
||||
17
templates/overrides/gotham/related-posts.ninjatpl
Normal file
17
templates/overrides/gotham/related-posts.ninjatpl
Normal file
@ -0,0 +1,17 @@
|
||||
{# related-posts (gotham) — grid from the `posts` provider. #}
|
||||
{% if posts %}
|
||||
<section data-block-override="gotham:related-posts" class="reel-rule-thick py-12 md:py-16 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-6xl px-4">
|
||||
<h2 class="reel-caps reel-underline mb-10 text-center text-2xl md:text-3xl text-foreground" style="font-family:var(--font-heading, "Antonio", "Oswald", Georgia, serif)">{{ heading|default:"Related posts" }}</h2>
|
||||
<div class="grid grid-cols-1 gap-8 sm:grid-cols-2 {% if columns == 2 %}lg:grid-cols-2{% else %}lg:grid-cols-3{% endif %}">
|
||||
{% for post in posts %}
|
||||
<article class="group flex flex-col text-center">
|
||||
{% if post.featured_image_url %}<a href="{{ post.url }}" class="reel-mat mb-4 block aspect-video overflow-hidden bg-muted">{% img post.featured_image_url alt=post.title class="h-full w-full object-cover transition-transform duration-500 group-hover:scale-105" %}</a>{% endif %}
|
||||
<h3 class="reel-caps text-base leading-snug text-foreground" style="font-family:var(--font-heading, "Antonio", "Oswald", Georgia, serif)"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h3>
|
||||
{% if post.published_at %}<p class="mt-2 text-xs text-muted-foreground">{{ post.published_at|date:"M j, Y" }}</p>{% endif %}
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
13
templates/overrides/gotham/rich-section.ninjatpl
Normal file
13
templates/overrides/gotham/rich-section.ninjatpl
Normal file
@ -0,0 +1,13 @@
|
||||
<section data-block-override="gotham:rich-section" class="py-16 md:py-24 {% if background == "muted" %}bg-muted text-foreground{% elif background == "card" %}bg-card text-card-foreground{% else %}bg-background text-foreground{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto grid max-w-6xl items-center gap-10 px-4 md:grid-cols-2 md:gap-16">
|
||||
<div class="{% if mediaPosition == "left" %}md:order-2{% else %}md:order-1{% endif %}">
|
||||
{% if eyebrow %}<div class="mb-3 flex items-center gap-4"><span class="reel-rule w-12"></span><p class="reel-caps text-xs text-primary">{{ eyebrow }}</p></div>{% endif %}
|
||||
{% if heading %}<h2 class="reel-caps reel-underline mt-2 text-3xl text-foreground md:text-4xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ heading }}</h2>{% endif %}
|
||||
{% if body %}<div class="mt-4 space-y-4 text-lg text-muted-foreground [&_a]:text-primary [&_a]:underline [&_strong]:text-foreground" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ body|safe }}</div>{% endif %}
|
||||
{% if ctaUrl and ctaLabel %}<a href="{{ ctaUrl }}" class="reel-btn mt-6 inline-flex items-center gap-2">{{ ctaLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||
</div>
|
||||
<div class="{% if mediaPosition == "left" %}md:order-1{% else %}md:order-2{% endif %}">
|
||||
{% if media %}<div class="reel-mat overflow-hidden">{% img media alt=mediaAlt class="w-full h-auto object-cover" %}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
2
templates/overrides/gotham/rich-text.ninjatpl
Normal file
2
templates/overrides/gotham/rich-text.ninjatpl
Normal file
@ -0,0 +1,2 @@
|
||||
{# rich-text override — long-form Cormorant editorial column with gilded links. Field: html (mirrors builtin). #}
|
||||
{% if html %}<article data-block-override="gotham:rich-text" class="rich-text prose prose-lg max-w-none [&_a]:text-primary [&_a]:no-underline [&_a]:border-b [&_a:hover]:text-accent [&_h1]:reel-caps [&_h2]:reel-caps [&_h3]:reel-caps [&_blockquote]:border-l-2 [&_blockquote]:border-[hsl(var(--primary))] [&_blockquote]:italic" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif);color:hsl(var(--foreground));font-size:1.1rem;line-height:1.75;">{{ html|safe }}</article>{% endif %}
|
||||
15
templates/overrides/gotham/sidebar-nav.ninjatpl
Normal file
15
templates/overrides/gotham/sidebar-nav.ninjatpl
Normal file
@ -0,0 +1,15 @@
|
||||
{# sidebar-nav (gotham) — vertical menu from the `menus` provider. Variants #}
|
||||
{# default | compact. highlight-current carried as a progressive-enhancement #}
|
||||
{# script keyed on data-bn-sidebar-link (marks the anchor matching location.pathname). #}
|
||||
{% set compact = template == "compact" %}
|
||||
<nav data-block-override="gotham:sidebar-nav" class="flex-1 {% if compact %}w-56 py-4 pr-3{% else %}w-64 py-6 pr-4{% endif %}{% if showBorder %} border-r border-[hsl(var(--primary)/0.35)]{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||
{% if title %}<h3 class="reel-caps reel-underline {% if compact %}px-2 mb-4 text-xs text-primary{% else %}px-3 mb-5 text-sm text-foreground{% endif %}" style="font-family:var(--font-heading, "Antonio", "Oswald", Georgia, serif)">{{ title }}</h3>{% endif %}
|
||||
<ul class="{% if compact %}space-y-0.5{% else %}space-y-1{% endif %}">
|
||||
{% for item in menu.items %}
|
||||
<li><a 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 class="reel-caps flex items-center rounded-none text-sm text-muted-foreground transition-colors hover:text-primary hover:bg-accent {% if compact %}px-2 py-1.5{% else %}px-3 py-2{% endif %} {{ item.css_class }}">{% if item.icon %}<svg class="mr-2 h-4 w-4 text-primary" aria-hidden="true"><use href="/icons/{{ item.icon|split:":"|first }}.svg#{{ item.icon|split:":"|last }}"></use></svg>{% endif %}{{ item.label }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% if highlightCurrent %}<script>
|
||||
(function(){var p=window.location.pathname;document.querySelectorAll('[data-bn-sidebar-link]').forEach(function(a){if(a.getAttribute('href')===p){a.classList.remove('text-muted-foreground');a.classList.add('text-foreground','bg-accent','font-medium');}});})();
|
||||
</script>{% endif %}
|
||||
13
templates/overrides/gotham/social-links.ninjatpl
Normal file
13
templates/overrides/gotham/social-links.ninjatpl
Normal file
@ -0,0 +1,13 @@
|
||||
<div data-block-override="gotham:social-links" class="social-links{% if class %} {{ class }}{% endif %}">
|
||||
{% if heading %}<p class="reel-caps mb-4 text-xs tracking-[0.18em] text-primary">{{ heading }}</p>{% endif %}
|
||||
<ul class="flex {% if style == "stack" %}flex-col gap-2{% else %}flex-wrap items-center gap-3{% 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="reel-foil inline-flex items-center gap-2 rounded-none border border-[hsl(var(--primary)/0.45)] text-foreground transition-colors hover:bg-primary/10 hover:text-primary {% if style == "stack" %}w-full px-4 py-2.5{% else %}p-2.5{% endif %}">
|
||||
{% if link.network %}<svg class="h-5 w-5 text-primary" aria-hidden="true"><use href="/icons/{{ link.network|split:":"|first }}.svg#{{ link.network|split:":"|last }}"></use></svg>{% endif %}
|
||||
{% if style == "stack" and link.label %}<span class="reel-caps text-xs tracking-[0.14em]">{{ link.label }}</span>{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
48
templates/overrides/gotham/stats.ninjatpl
Normal file
48
templates/overrides/gotham/stats.ninjatpl
Normal file
@ -0,0 +1,48 @@
|
||||
<section data-block-override="gotham:stats" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}"{% if countUp %} data-stats{% endif %}>
|
||||
<div class="mx-auto max-w-6xl px-4">
|
||||
{% if heading %}<h2 class="reel-caps text-center text-3xl text-foreground md:text-4xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ heading }}</h2>{% endif %}
|
||||
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ intro }}</p>{% endif %}
|
||||
<dl class="mt-12 grid grid-cols-2 gap-8 divide-x divide-[hsl(var(--primary)/0.25)] {% if columns == 2 %}md:grid-cols-2{% elif columns == 3 %}md:grid-cols-3{% else %}md:grid-cols-4{% endif %}">
|
||||
{% for item in items %}
|
||||
<div class="px-2 text-center">
|
||||
<dd class="text-4xl font-bold tracking-tight text-primary md:text-5xl" style="font-family:var(--font-heading)">{% if item.prefix %}{{ item.prefix }}{% endif %}<span{% if countUp %} data-countup-value="{{ item.value }}"{% endif %}>{{ item.value }}</span>{% if item.suffix %}{{ item.suffix }}{% endif %}</dd>
|
||||
{% if item.label %}<dt class="reel-caps mt-2 text-xs text-muted-foreground">{{ item.label }}</dt>{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
{% if countUp %}<script>
|
||||
(function(){
|
||||
var sections = document.querySelectorAll('[data-stats]');
|
||||
var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
function animate(section){
|
||||
var nodes = section.querySelectorAll('[data-countup-value]');
|
||||
nodes.forEach(function(node){
|
||||
var raw = node.getAttribute('data-countup-value') || '';
|
||||
var target = parseFloat(raw.replace(/[^0-9.\-]/g, ''));
|
||||
if (isNaN(target)) return;
|
||||
var decimals = (raw.split('.')[1] || '').length;
|
||||
var start = null, dur = 1200;
|
||||
function step(ts){
|
||||
if (start === null) start = ts;
|
||||
var p = Math.min((ts - start) / dur, 1);
|
||||
var eased = 1 - Math.pow(1 - p, 3);
|
||||
node.textContent = (target * eased).toFixed(decimals);
|
||||
if (p < 1) requestAnimationFrame(step);
|
||||
else node.textContent = target.toFixed(decimals);
|
||||
}
|
||||
requestAnimationFrame(step);
|
||||
});
|
||||
}
|
||||
sections.forEach(function(section){
|
||||
if (reduce || !('IntersectionObserver' in window)) return;
|
||||
var io = new IntersectionObserver(function(entries){
|
||||
entries.forEach(function(e){
|
||||
if (e.isIntersecting){ animate(section); io.disconnect(); }
|
||||
});
|
||||
}, { threshold: 0.3 });
|
||||
io.observe(section);
|
||||
});
|
||||
})();
|
||||
</script>{% endif %}
|
||||
21
templates/overrides/gotham/team.ninjatpl
Normal file
21
templates/overrides/gotham/team.ninjatpl
Normal file
@ -0,0 +1,21 @@
|
||||
<section data-block-override="gotham:team" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-6xl px-4">
|
||||
{% if heading %}<h2 class="reel-caps text-center text-3xl text-foreground md:text-4xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ heading }}</h2>{% endif %}
|
||||
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ intro }}</p>{% endif %}
|
||||
<div class="mt-12 {% if layout == "list" %}mx-auto max-w-3xl space-y-6{% elif layout == "single" %}mx-auto max-w-xl{% else %}grid grid-cols-1 gap-8 sm:grid-cols-2 {% if columns == 2 %}lg:grid-cols-2{% elif columns == 4 %}lg:grid-cols-4{% else %}lg:grid-cols-3{% endif %}{% endif %}">
|
||||
{% for member in members %}
|
||||
<div class="{% if layout == "list" %}reel-frame flex items-start gap-5 bg-card p-5 text-card-foreground{% else %}text-center{% endif %}">
|
||||
{% if member.photo %}<span class="{% if layout == "list" %}h-20 w-20{% else %}mx-auto h-28 w-28{% endif %} block shrink-0 overflow-hidden border border-[hsl(var(--primary)/0.4)] bg-muted">{% img member.photo alt=member.name class="h-full w-full object-cover" %}</span>{% endif %}
|
||||
<div class="{% if layout != "list" %}mt-4{% endif %}">
|
||||
{% if member.name %}<h3 class="reel-caps text-lg text-foreground" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ member.name }}</h3>{% endif %}
|
||||
{% if member.role %}<p class="reel-caps text-xs text-primary">{{ member.role }}</p>{% endif %}
|
||||
{% if member.bio %}<p class="mt-2 text-sm text-muted-foreground" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ member.bio }}</p>{% endif %}
|
||||
{% if member.socials %}<div class="mt-3 flex gap-3 {% if layout != "list" %}justify-center{% endif %}">
|
||||
{% for social in member.socials %}{% if social.url %}<a href="{{ social.url }}" class="text-muted-foreground transition-colors hover:text-primary" aria-label="Social link"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/{{ social.icon|split:":"|first }}.svg#{{ social.icon|split:":"|last }}"></use></svg></a>{% endif %}{% endfor %}
|
||||
</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
27
templates/overrides/gotham/testimonial.ninjatpl
Normal file
27
templates/overrides/gotham/testimonial.ninjatpl
Normal file
@ -0,0 +1,27 @@
|
||||
<section data-block-override="gotham:testimonial" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-6xl px-4">
|
||||
{% if heading %}<h2 class="reel-caps text-center text-3xl text-foreground md:text-4xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ heading }}</h2>{% endif %}
|
||||
<div class="mt-12 {% if layout == "single" %}mx-auto max-w-3xl{% elif layout == "carousel" %}flex snap-x snap-mandatory gap-6 overflow-x-auto pb-4{% else %}grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3{% endif %}">
|
||||
{% for q in quotes %}
|
||||
<figure class="reel-frame flex flex-col bg-card p-6 text-card-foreground{% if layout == "carousel" %} min-w-[18rem] max-w-sm shrink-0 snap-start{% endif %}">
|
||||
{% if q.rating %}<div class="mb-3 flex justify-center gap-0.5" aria-label="Rated {{ q.rating }} out of 5">
|
||||
<svg class="h-4 w-4 {% if q.rating >= 1 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||
<svg class="h-4 w-4 {% if q.rating >= 2 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||
<svg class="h-4 w-4 {% if q.rating >= 3 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||
<svg class="h-4 w-4 {% if q.rating >= 4 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||
<svg class="h-4 w-4 {% if q.rating >= 5 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||
</div>{% endif %}
|
||||
<blockquote class="flex-1 text-center text-lg leading-relaxed" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{% if q.quote %}“{{ q.quote }}”{% endif %}</blockquote>
|
||||
<div class="mx-auto my-4 flex items-center justify-center"><span class="reel-rule w-16"></span></div>
|
||||
<figcaption class="flex items-center justify-center gap-3">
|
||||
{% if q.avatar %}<span class="h-10 w-10 shrink-0 overflow-hidden border border-[hsl(var(--primary)/0.4)] bg-muted">{% img q.avatar alt=q.name class="h-full w-full object-cover" %}</span>{% endif %}
|
||||
<span class="text-center">
|
||||
{% if q.name %}<span class="reel-caps block text-sm text-foreground">{{ q.name }}</span>{% endif %}
|
||||
{% if q.role %}<span class="block text-sm text-muted-foreground">{{ q.role }}</span>{% endif %}
|
||||
</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -1 +1,2 @@
|
||||
<div class="prose prose-invert prose-amber max-w-none flex-1 {{ class }}">{{ text|safe }}</div>
|
||||
{# text override — Cormorant body prose with gilded links. Fields: text, proseClass (mirrors builtin). Dropcap is reserved for the article template, not forced on every text block. #}
|
||||
<div data-block-override="gotham:text" class="prose max-w-none flex-1 [&_a]:text-primary [&_a]:no-underline [&_a]:border-b [&_a]:border-[hsl(var(--primary))] [&_a:hover]:text-accent {{ proseClass }}" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif);color:hsl(var(--foreground));font-size:1.075rem;line-height:1.75;">{{ text|safe }}</div>
|
||||
|
||||
21
templates/overrides/gotham/timeline.ninjatpl
Normal file
21
templates/overrides/gotham/timeline.ninjatpl
Normal file
@ -0,0 +1,21 @@
|
||||
<section data-block-override="gotham:timeline" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-4xl px-4">
|
||||
{% if heading %}<h2 class="reel-caps text-center text-3xl text-foreground md:text-4xl" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ heading }}</h2>{% endif %}
|
||||
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ intro }}</p>{% endif %}
|
||||
<ol class="mt-12 {% if orientation == "horizontal" %}grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-4{% else %}relative space-y-8 before:absolute before:left-4 before:top-2 before:h-[calc(100%-1rem)] before:w-px before:bg-[hsl(var(--primary)/0.4)]{% endif %}">
|
||||
{% for step in steps %}
|
||||
<li class="{% if orientation == "horizontal" %}text-center{% else %}relative pl-14{% endif %}">
|
||||
<span class="reel-caps flex h-8 w-8 items-center justify-center border border-primary bg-primary text-sm text-primary-foreground {% if orientation == "horizontal" %}mx-auto{% else %}absolute left-0 top-0 ring-4 ring-background{% endif %}">
|
||||
{% if marker == "numbered" %}{{ forloop.counter }}{% elif marker == "dot" %}<span class="h-2 w-2 bg-primary-foreground"></span>{% else %}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#flag"></use></svg>{% endif %}
|
||||
</span>
|
||||
<div class="{% if orientation == "horizontal" %}mt-4{% else %}pt-1{% endif %}">
|
||||
{% if step.label %}<p class="reel-caps text-xs text-primary">{{ step.label }}</p>{% endif %}
|
||||
{% if step.title %}<h3 class="reel-caps mt-1 text-lg text-foreground" style="font-family:var(--font-heading, 'Antonio', 'Oswald', 'Bebas Neue', sans-serif)">{{ step.title }}</h3>{% endif %}
|
||||
{% if step.text %}<p class="mt-1 text-muted-foreground" style="font-family:var(--font-body, 'Cormorant Garamond', 'Cormorant', Georgia, serif)">{{ step.text }}</p>{% endif %}
|
||||
{% if step.media %}<div class="reel-mat mt-3 overflow-hidden">{% img step.media alt=step.title class="w-full h-auto object-cover" %}</div>{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
10
templates/overrides/gotham/utility-bar.ninjatpl
Normal file
10
templates/overrides/gotham/utility-bar.ninjatpl
Normal file
@ -0,0 +1,10 @@
|
||||
{# utility-bar override — thin lacquer contact strip, gold text. Fields mirror builtin: variant, showOnMobile, leftItems/rightItems {icon,label,link}. #}
|
||||
{% set ubcls = "bg-foreground text-background" %}
|
||||
{% if variant == "primary" %}{% set ubcls = "bg-primary text-primary-foreground" %}{% elif variant == "dark" %}{% set ubcls = "bg-foreground text-background" %}{% endif %}
|
||||
{% macro ubitem(it) %}{% if it.link %}<a href="{{ it.link }}" class="reel-caps inline-flex items-center gap-1.5 hover:text-primary transition-colors">{% if it.icon %}::{{ it.icon }}::{% endif %}{% if it.label %}<span>{{ it.label }}</span>{% endif %}</a>{% else %}<span class="reel-caps inline-flex items-center gap-1.5">{% if it.icon %}::{{ it.icon }}::{% endif %}{% if it.label %}<span>{{ it.label }}</span>{% endif %}</span>{% endif %}{% endmacro %}
|
||||
<div data-block-override="gotham:utility-bar" class="w-full text-[0.7rem] {% if not showOnMobile %}hidden sm:block{% endif %} {{ ubcls }} {{ class }}">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-9 flex items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-5 flex-wrap">{% for it in leftItems %}{{ ubitem(it) }}{% endfor %}</div>
|
||||
<div class="flex items-center gap-5 flex-wrap justify-end">{% for it in rightItems %}{{ ubitem(it) }}{% endfor %}</div>
|
||||
</div>
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user