Compare commits

..

No commits in common. "1bdb2cfe3e04418d22fd6ed584d1585da0856d28" and "f6e2c1dccc607551315e5696f639a225c4b1a075" have entirely different histories.

80 changed files with 494 additions and 2218 deletions

View File

@ -1,25 +0,0 @@
# Terminal
A command-line theme in phosphor green on near-black, with TTY window chrome and man-page article layouts. Monospace type runs throughout, and a paper-teletype light mode gives you an off-white, printer-ribbon alternative. It reads like a console a developer already trusts.
## Good for
We built Terminal for developer tools and open-source projects. Treat these as starting points, not limits. If the mood fits your project, use it:
- Dev tools and CLI utilities
- Open-source docs and changelogs
- Hacker and CTF communities
- Technical blogs and engineering handbooks
## What you get
- Five color presets, each with a tuned light mode and a tuned dark mode: Phosphor Green, Amber CRT, Paper TTY, Ice Terminal, and Magenta Hacker.
- JetBrains Mono and IBM Plex Mono, bundled and ready. Fonts stay admin-controlled, so you can swap them from the typography settings.
- Four persona blocks: a man-page section header, a code console, a keybind reference table, and a boot-log sequence.
- A typed-terminal hero that draws itself on a canvas at the top of the page. It respects reduced-motion and falls back to a static command block with JavaScript off.
- A demo site that installs when you activate the theme: a home page, an about page, a short blog, and a contact page whose form emails your admins.
- Every core block restyled to match, plus a themed login screen and a themed 404 page.
## Install
Install from the BlockNinja registry, then pick a preset and assign your fonts in the admin. Activate the demo content if you want a fully dressed starting point.

View File

@ -1,43 +1,31 @@
# Recommended fonts — Terminal
# Recommended fonts — Terminal theme
Every family flows through the admin typography picker via CSS variables. Templates never
hardcode a `font-family`; they read `var(--font-heading, ...)`, `var(--font-body, ...)`,
and `var(--font-mono, ...)`, each with a bundled fallback so the intended look survives
before the admin assigns a slot.
This theme ships `fonts.json = []` per the wave-1 implementation policy in `docs/FONTS.md`. The intended typography is **all monospace, no exceptions**. The CSS fallback chain ends in `monospace` so the theme remains legible before an admin picks fonts; the recommendations below match the spec's intended look.
## Bundled (ship as-is)
Fonts are admin-configured through the typography panel. Open the Google Fonts tab of the picker, add each family, then assign it to the appropriate slot.
The theme bundles two OFL monospace families (latin subset, ~69 KB total) as
`source=template` rows, already selectable in the picker. They back the fallbacks:
## Heading & mono — JetBrains Mono
| Slot | Fallback family | File |
|---|---|---|
| Heading | JetBrains Mono | `assets/fonts/jetbrains-mono-latin.woff2` |
| Mono | JetBrains Mono | `assets/fonts/jetbrains-mono-latin.woff2` |
| Body | IBM Plex Mono | `assets/fonts/ibm-plex-mono-400.woff2`, `ibm-plex-mono-500.woff2` |
- **Source recommendation:** `google:JetBrains Mono`
- **Slots:** Heading, Mono
- **How:** Open the typography panel → Google Fonts tab → search "JetBrains Mono" → click Add. Assign it to the **Heading** slot and the **Mono** slot.
A terminal theme is monospace top to bottom, so all three slots default to a mono family.
License record: `assets/fonts/LICENSES.md`.
## Body — IBM Plex Mono
## Recommended admin assignments (Google Fonts tab)
Assign these from Admin -> Appearance -> Typography if you want the full family (extended
glyph coverage) or the display accent the bundled subset omits:
| Slot | Assign | Why |
|---|---|---|
| Heading | JetBrains Mono or IBM Plex Mono | Sharp, even monospace headings that read as terminal chrome. |
| Body | IBM Plex Mono | Slightly wider, highly readable for long docs and man pages. |
| Mono | JetBrains Mono | Code blocks, prompts, keybind tables. |
| Display accent (optional) | VT323 | Chunky bitmap-CRT face for big phosphor hero display type. Not bundled; assign from the Google Fonts tab if you want the retro-CRT accent. |
All four are SIL Open Font License 1.1. Nothing here hardcodes a family, so any admin
assignment overrides the bundled fallback cleanly.
- **Source recommendation:** `google:IBM Plex Mono`
- **Slot:** Body
- **How:** Open the typography panel → Google Fonts tab → search "IBM Plex Mono" → click Add. Assign it to the **Body** slot.
## Fallback chain (already in theme CSS)
Before any admin selection the theme degrades through:
If the admin has not yet picked fonts, the theme degrades through:
```
"JetBrains Mono", "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace
```
This keeps the all-monospace aesthetic intact even without any admin font selection.
## Wave-2 follow-up
Bundling JetBrains Mono and IBM Plex Mono `.woff2` files (both OFL-licensed) is deferred to wave-2 along with the `LICENSES.md` entry. In this pass, fonts are admin-side via the Google Fonts picker.

View File

@ -1,24 +0,0 @@
# Recommended icons — Terminal
Icons render through the CMS sprite pipeline: `<svg><use href="/icons/<pack>.svg#<name>"/></svg>`
(or the `::pack:name:size::` shorthand). The theme's overrides reference two bundled packs.
## Required packs
Declared in `plugin.mod` as `required_icon_packs = ["lucide", "simple-icons"]`
(forward-declared — the loader does not yet auto-install; install manually until it does).
| Pack | Used for | Install |
|---|---|---|
| **lucide** | UI glyphs the builtin/override templates use: `arrow-right`, `check`, `chevron-down`, `chevron-left`, `chevron-right`, `x`, `terminal`, `cpu`, `mail`, `map-pin`, `download`, `play`, `radio`, `message-square` | Ships bundled with the CMS — no action needed. |
| **simple-icons** | Author/social brand glyphs: `github`, `x`, `linkedin`, `discord` | Ships bundled with the CMS — no action needed. |
Both packs ship bundled with the CMS, so a fresh instance renders every override icon with
no manual step. If you author blocks that reference other packs (e.g. `tabler`, `phosphor`),
install them from `/admin/icons` (Browse -> install -> enable) before activation.
## Note
Icon values in block content are stored as `"pack:name"` strings and split with
`|split:":"|first` / `|last`. Never inline raw SVG path data for content icons — that
bypasses the sprite system and the admin pack manager.

View File

@ -1,21 +0,0 @@
# Bundled font licenses — Terminal theme
Both families are bundled as latin-subset `.woff2` and are licensed under the SIL Open
Font License 1.1 (OFL). Redistribution inside this theme artifact is permitted.
| File | Family | Weights | License | Source |
|---|---|---|---|---|
| `jetbrains-mono-latin.woff2` | JetBrains Mono (variable) | 400 / 500 / 700 | OFL 1.1 | Google Fonts (fonts.gstatic.com), latin subset |
| `ibm-plex-mono-400.woff2` | IBM Plex Mono | 400 | OFL 1.1 | Google Fonts (fonts.gstatic.com), latin subset |
| `ibm-plex-mono-500.woff2` | IBM Plex Mono | 500 | OFL 1.1 | Google Fonts (fonts.gstatic.com), latin subset |
OFL 1.1 full text: https://openfontlicense.org
- JetBrains Mono (c) 2020 The JetBrains Mono Project Authors.
- IBM Plex Mono (c) 2017 IBM Corp., the IBM Plex Project Authors.
These are latin subsets (Latin + Latin-1 punctuation), chosen to keep the bundle small
(~69 KB total). They cover English content. Sites needing extended Latin, Cyrillic, Greek,
or Vietnamese glyphs should assign the full family from the admin Google Fonts picker.
The bundled families back the theme's `--font-heading`, `--font-body`, and `--font-mono`
fallbacks; the admin typography picker overrides them per slot.

Binary file not shown.

Binary file not shown.

View File

@ -23,10 +23,7 @@
font-family: var(--font-heading, "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
text-transform: uppercase;
letter-spacing: 0.08em;
/* Inherit colour from context so headings placed on an inverted band
(e.g. a bg-primary CTA) read as primary-foreground, not a hardcoded
foreground that goes dark-on-bright and fails contrast. */
color: inherit;
color: hsl(var(--foreground));
}
.terminal-mono {
@ -431,45 +428,3 @@
margin: 0 auto;
font-family: var(--font-body, ui-monospace, monospace);
}
/* --- Reliable responsive grids (see manifest.yaml input_css_append:
the host scans rendered block HTML, not theme override templates, so
responsive Tailwind grid utilities in overrides are purge-fragile;
define multi-column layout in CSS instead). minmax(0,1fr) prevents
long content forcing horizontal overflow on mobile. --- */
.tty-grid,
.tty-grid-2,
.tty-grid-3,
.tty-grid-4 { display: grid; gap: 1.5rem; grid-template-columns: repeat(1, minmax(0, 1fr)); }
@media (min-width: 640px) {
.tty-grid-2,
.tty-grid-3,
.tty-grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
.tty-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.tty-grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.tty-grid-stats2,
.tty-grid-stats3,
.tty-grid-stats4 { display: grid; gap: 2rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 768px) {
.tty-grid-stats3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.tty-grid-stats4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
/* --- Two-column form grid (col-span utilities purge-fragile) --- */
.tty-form-grid { display: grid; gap: 1.25rem; grid-template-columns: repeat(1, minmax(0, 1fr)); }
.tty-form-grid > .tty-col-full { grid-column: 1 / -1; }
@media (min-width: 640px) { .tty-form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* --- Contained console shell for full-width landing pages --- */
.tty-console-shell { width: 100%; max-width: 56rem; margin: 1.5rem auto; padding-inline: 1rem; }
/* --- Hero heights (purge-fragile utilities pinned in CSS) --- */
.tty-hero { position: relative; }
.tty-hero-screen { min-height: 100vh; }
.tty-hero-tall { min-height: 75vh; }
.tty-hero-half { min-height: 50vh; }
.tty-hero-auto { min-height: 24rem; }
.tty-hero-media { min-height: 20rem; }

View File

@ -0,0 +1,3 @@
{% set eff_title = title %}{% if not title and not asciiArt %}{% set eff_title = "~" %}{% endif %}<div class="ascii-header" data-block="terminal:ascii_header">{% if asciiArt %}<pre>{{ asciiArt }}</pre>{% else %}<pre>/*===========================================*/
/* {{ eff_title|slice:":41"|ljust:41 }} */
/*===========================================*/</pre>{% endif %}<div class="prompt-line"><span class="terminal-mono">{{ prompt|default:"$ " }}</span>{% if eff_title %}<span class="terminal-mono">{{ eff_title }}</span>{% endif %}</div></div>

View File

@ -0,0 +1,29 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ASCII Header",
"description": "TTY-style figlet banner with a prompt line",
"type": "object",
"properties": {
"title": {
"type": "string",
"title": "Title",
"description": "Title shown in the banner (e.g. ~/projects)",
"default": "~/projects",
"x-editor": "text"
},
"prompt": {
"type": "string",
"title": "Prompt",
"description": "Shell prompt prefix (e.g. $ )",
"default": "$ ",
"x-editor": "text"
},
"asciiArt": {
"type": "string",
"title": "ASCII Art",
"description": "Optional pre-baked figlet block. Leave blank to use the auto-generated banner.",
"default": "",
"x-editor": "textarea"
}
}
}

View File

@ -1,15 +1,27 @@
# Terminal theme block definitions (codeless .bnp, WO-TF-025).
# Only true persona furniture stays a theme block; blocks that duplicated a
# builtin (ascii masthead, footer) became template overrides, and the section
# rail (toc) became article page-template furniture. Keys are fully qualified
# (terminal:<key>) because the codeless loader registers definitions verbatim.
# Terminal theme block definitions (codeless .bnp, WO-WZ-021).
# Keys are fully qualified (terminal:<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 hardcoded data-block attributes and the master_pages.json references.
blocks:
- key: terminal:ascii_header
title: ASCII Header
description: Figlet-style TTY banner with a shell prompt line
category: layout
schema: ascii_header.schema.json
template: ascii_header.ninjatpl
- key: terminal:manpage_header
title: Man-page Header
description: NAME(section) ... center ... version row, like a man-page top line
category: layout
schema: manpage_header.schema.json
template: manpage_header.ninjatpl
- key: terminal:toc
title: Section TOC
description: Fixed left-rail table of contents for article pages
category: navigation
schema: toc.schema.json
template: toc.ninjatpl
- key: terminal:code_console
title: Console Block
description: Shell prompt + command + output, with language hint
@ -23,8 +35,14 @@ blocks:
schema: keybind_table.schema.json
template: keybind_table.ninjatpl
- key: terminal:boot_log
title: Boot Log
title: Boot Log Hero
description: Sequential dmesg-style boot lines revealed one after another
category: content
schema: boot_log.schema.json
template: boot_log.ninjatpl
- key: terminal:footer
title: TTY Footer
description: EOF rule, MOTD, optional newsletter signup
category: layout
schema: footer.schema.json
template: footer.ninjatpl

View File

@ -1 +1 @@
{% if lines %}<div class="tty-console-shell"><div class="boot-log" data-block="terminal:boot_log">{% for line in lines %}<span class="boot-line" data-line-index="{{ forloop.Counter0 }}" style="animation-delay: {{ forloop.Counter * 80 }}ms;">[ {{ forloop.Counter0 }} ] {{ line }}</span>{% endfor %}{% set cur = cursor|default:"block" %}{% if cur != "none" %}<span class="boot-cursor">{% if cur == "underscore" %}_{% elif cur == "pipe" %}|{% else %}█{% endif %}</span>{% endif %}</div></div>{% endif %}
{% if lines %}<div class="boot-log" data-block="terminal:boot_log">{% for line in lines %}<span class="boot-line" data-line-index="{{ forloop.Counter0 }}" style="animation-delay: {{ forloop.Counter * 80 }}ms;">[ {{ forloop.Counter0 }} ] {{ line }}</span>{% endfor %}{% set cur = cursor|default:"block" %}{% if cur != "none" %}<span class="boot-cursor">{% if cur == "underscore" %}_{% elif cur == "pipe" %}|{% else %}█{% endif %}</span>{% endif %}</div>{% endif %}

View File

@ -1 +1 @@
{% if command or output %}<div class="tty-console-shell"><div class="code-console" data-block="terminal:code_console" data-language="{{ language|default:"shell" }}">{% if command %}<div class="cmd-line"><span class="cmd-prompt">{{ prompt|default:"$" }}</span><span class="cmd-text">{{ command }}</span></div>{% endif %}{% if output %}<pre>{{ output }}</pre>{% endif %}</div></div>{% endif %}
{% if command or output %}<div class="code-console" data-block="terminal:code_console" data-language="{{ language|default:"shell" }}">{% if command %}<div class="cmd-line"><span class="cmd-prompt">{{ prompt|default:"$" }}</span><span class="cmd-text">{{ command }}</span></div>{% endif %}{% if output %}<pre>{{ output }}</pre>{% endif %}</div>{% endif %}

1
blocks/footer.ninjatpl Normal file
View File

@ -0,0 +1 @@
{% set m = motd|default:"connection closed." %}<div class="terminal-footer" data-block="terminal:footer"><div class="terminal-footer-rule">===========================================</div>{% if m %}<div class="terminal-footer-motd">-- {{ m }}</div>{% endif %}{% if links %}<div class="terminal-footer-links">{% for l in links %}<a href="{% if l.url %}{{ l.url }}{% else %}#{% endif %}">[ {{ l.label }} ]</a>{% endfor %}</div>{% endif %}{% if showSignup == true or showSignup == "true" or showSignup == "yes" or showSignup == "1" %}<form class="terminal-footer-signup" method="post" action="#"><label class="terminal-mono">$ subscribe <input type="email" name="email" placeholder="you@example.com" required></label></form>{% endif %}<div class="terminal-footer-rule">===========================================</div><div class="terminal-mono">EOF</div></div>

46
blocks/footer.schema.json Normal file
View File

@ -0,0 +1,46 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "TTY Footer",
"description": "EOF rule + motd + optional newsletter signup",
"type": "object",
"properties": {
"motd": {
"type": "string",
"title": "MOTD",
"description": "Message-of-the-day shown in the footer",
"default": "connection closed.",
"x-editor": "text"
},
"links": {
"type": "array",
"title": "Links",
"description": "Footer links",
"default": [],
"x-editor": "collection",
"items": {
"type": "object",
"properties": {
"label": {
"type": "string",
"title": "Label",
"x-editor": "text"
},
"url": {
"type": "string",
"title": "URL",
"x-editor": "link"
}
},
"required": ["label"]
}
},
"showSignup": {
"type": "string",
"title": "Show newsletter signup",
"description": "Render a minimal email signup row",
"default": "false",
"x-editor": "select",
"enum": ["true", "false"]
}
}
}

1
blocks/toc.ninjatpl Normal file
View File

@ -0,0 +1 @@
<nav class="terminal-toc" data-block="terminal:toc"><h4>{{ heading|default:"Sections" }}</h4>{% if items %}<ul>{% for item in items %}<li><a href="{% if item.anchor %}#{{ item.anchor }}{% else %}#{% endif %}">§ {{ item.label }}</a></li>{% endfor %}</ul>{% else %}<p class="terminal-mono">// no sections yet</p>{% endif %}</nav>

39
blocks/toc.schema.json Normal file
View File

@ -0,0 +1,39 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Section TOC",
"description": "Fixed left-rail table of contents for article pages",
"type": "object",
"properties": {
"heading": {
"type": "string",
"title": "Heading",
"description": "Heading shown above the list",
"default": "Sections",
"x-editor": "text"
},
"items": {
"type": "array",
"title": "Items",
"description": "Anchor links into the page body",
"default": [],
"x-editor": "collection",
"items": {
"type": "object",
"properties": {
"label": {
"type": "string",
"title": "Label",
"x-editor": "text"
},
"anchor": {
"type": "string",
"title": "Anchor",
"description": "Anchor slug (no leading '#')",
"x-editor": "slug"
}
},
"required": ["label"]
}
}
}
}

View File

@ -1,19 +1 @@
[
{
"name": "JetBrains Mono",
"family": "JetBrains Mono",
"variants": [
{ "weight": "400", "style": "normal", "file": "fonts/jetbrains-mono-latin.woff2" },
{ "weight": "500", "style": "normal", "file": "fonts/jetbrains-mono-latin.woff2" },
{ "weight": "700", "style": "normal", "file": "fonts/jetbrains-mono-latin.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" }
]
}
]
[]

View File

@ -1,109 +1,41 @@
# Terminal theme — codeless .bnp manifest (WO-TF-025, Wave A).
# Terminal theme — codeless .bnp manifest (WO-WZ-021 Phase B1).
# Synthesized into manifest.pb by `ninja plugin build` (no plugin.wasm).
theme_presets: presets.json
bundled_fonts: fonts.json
master_pages: master_pages.json
required_icon_packs: ["lucide", "simple-icons"]
system_templates:
- key: terminal
title: Terminal
description: "Phosphor-on-black TTY theme for dev tools and OSS docs — window chrome, man-page article layouts, keybind tables, boot logs, monospace type, and a typed-terminal canvas hero. Paper-teletype light, phosphor dark."
description: "Phosphor green on black terminal aesthetic with ASCII art and monospace type."
page_templates:
- system: terminal
key: default
title: Default
description: "Centered 80-col TTY page with window chrome and a sticky header"
slots: [header, main, footer]
- system: terminal
key: full-width
title: Full Width
description: "Edge-to-edge layout for tool dashboards and status pages"
description: "Standard 80-col centered TTY layout"
slots: [header, main, footer]
- system: terminal
key: landing
title: Landing
description: "Typed-terminal canvas hero, feature grid, boot log, and CTA for README-style project sites"
slots: [header, main, footer]
description: "ASCII hero + feature grid for project README sites"
slots: [hero, main, cta, footer]
- system: terminal
key: article
title: Article (man-page)
description: "Man-page two-column layout with a section rail, ideal for docs and writeups"
slots: [header, main, aside, footer]
description: "Man-page two-column with section nav, ideal for writeups"
slots: [header, toc, main, footer]
- system: terminal
key: blog-index
title: Blog Index
description: "Changelog-style post feed with a sidebar"
slots: [header, main, footer]
- system: terminal
key: contact
title: Contact
description: "Terminal contact console — form panel plus a contact card"
slots: [header, main, footer]
- system: terminal
key: auth
title: Auth
description: "Centered login/register/reset console in a TTY window"
key: full-width
title: Full Width
description: "Edge-to-edge for tool dashboards & status pages"
slots: [header, main, footer]
template_overrides:
# Chrome (4)
- { template: terminal, block: navbar }
- { template: terminal, block: footer }
- { template: terminal, block: utility-bar }
- { template: terminal, block: announcement-bar }
# Marketing (12)
- { template: terminal, block: hero }
- { template: terminal, block: feature-grid }
- { template: terminal, block: rich-section }
- { template: terminal, block: stats }
- { template: terminal, block: testimonial }
- { template: terminal, block: logo-strip }
- { template: terminal, block: pricing }
- { template: terminal, block: team }
- { template: terminal, block: faq }
- { template: terminal, block: timeline }
- { template: terminal, block: cta }
- { template: terminal, block: countdown }
# Primitives (11)
- { template: terminal, block: heading }
- { template: terminal, block: text }
- { template: terminal, block: rich-text }
- { template: terminal, block: image }
- { template: terminal, block: quote }
- { template: terminal, block: divider }
- { template: terminal, block: button }
- { template: terminal, block: card }
- { template: terminal, block: gallery }
- { template: terminal, block: video-embed }
- { template: terminal, block: audio-embed }
# Commerce / local (6)
- { template: terminal, block: menu-list }
- { template: terminal, block: event-list }
- { template: terminal, block: contact-form }
- { template: terminal, block: contact-card }
- { template: terminal, block: hours-location }
- { template: terminal, block: social-links }
# Blog family (12)
- { template: terminal, block: blog-hero }
- { template: terminal, block: featured-posts }
- { template: terminal, block: related-posts }
- { template: terminal, block: popular-posts }
- { template: terminal, block: category-list }
- { template: terminal, block: post-hero }
- { template: terminal, block: post-metadata }
- { template: terminal, block: author-bio }
- { template: terminal, block: author-bio-hero }
- { template: terminal, block: newsletter-signup }
- { template: terminal, block: breadcrumbs }
- { template: terminal, block: sidebar-nav }
# Auth surface (3)
- { template: terminal, block: auth-form }
- { template: terminal, block: password-reset-form }
- { template: terminal, block: auth-status }
# System (1)
- { template: terminal, block: page-suggestions }
- { template: terminal, block: image }
email_wrappers:
- terminal
@ -111,48 +43,42 @@ email_wrappers:
css:
input_css_append: |
/* ============================================================
Terminal — Phosphor-on-black TTY theme (WO-TF-025).
All colours consume the host shadcn HSL tokens via
hsl(var(--token)). No hardcoded hex / rgb / named colours.
Dual-mode: light = paper teletype, dark = phosphor CRT.
Terminal Theme — Phosphor green TTY aesthetic
============================================================ */
/* --- Globals: square corners, mono stack ------------------ */
/* --- Globals: square corners, mono stack --- */
:root { --radius: 0; }
:root {
--radius: 0;
}
.terminal-page {
font-family: var(--font-body, "IBM Plex Mono", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
letter-spacing: 0.01em;
}
.terminal-page * { border-radius: 0 !important; }
.terminal-page * {
border-radius: 0 !important;
}
/* --- Typography helpers ----------------------------------- */
/* --- Typography helpers --- */
.terminal-heading {
font-family: var(--font-heading, "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
text-transform: uppercase;
letter-spacing: 0.06em;
letter-spacing: 0.08em;
color: hsl(var(--foreground));
}
.terminal-mono {
font-family: var(--font-mono, "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
}
.terminal-eyebrow {
font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace);
text-transform: uppercase;
letter-spacing: 0.2em;
font-size: 0.75rem;
color: hsl(var(--accent));
}
.terminal-prose,
.terminal-prose p,
.terminal-prose li {
font-family: var(--font-body, "IBM Plex Mono", ui-monospace, monospace);
line-height: 1.7;
.terminal-prose {
font-family: var(--font-body, "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
color: hsl(var(--foreground));
line-height: 1.6;
max-width: 80ch;
}
.terminal-prose a {
@ -161,85 +87,7 @@ css:
text-underline-offset: 0.2em;
}
.terminal-prose code,
code.terminal-mono {
color: hsl(var(--accent));
background-color: hsl(var(--muted));
padding: 0.1rem 0.35rem;
border: 1px solid hsl(var(--border));
font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace);
font-size: 0.875em;
}
/* --- Caret blink (element opacity; used as <span class="caret-blink">_</span>) --- */
@keyframes caret-blink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }
.caret-blink {
display: inline-block;
animation: caret-blink 1s step-end infinite;
}
/* --- TTY window chrome (signature) ------------------------ */
.tty-window {
border: 1px solid hsl(var(--border));
background-color: hsl(var(--card));
color: hsl(var(--card-foreground));
}
.tty-titlebar {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.4rem 0.75rem;
border-bottom: 1px solid hsl(var(--border));
background-color: hsl(var(--muted));
color: hsl(var(--muted-foreground));
font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace);
font-size: 0.75rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.tty-dot {
display: inline-block;
width: 0.6rem;
height: 0.6rem;
border: 1px solid hsl(var(--border));
background-color: hsl(var(--secondary));
}
/* --- TTY frame: bordered panel with hover glow ------------ */
.tty-frame {
border: 1px solid hsl(var(--border));
background-color: hsl(var(--card));
transition: box-shadow 180ms ease, border-color 180ms ease;
}
.tty-frame:hover,
.tty-frame:focus-within {
border-color: hsl(var(--primary));
box-shadow: 0 0 0 1px hsl(var(--primary) / 0.5), 0 0 18px hsl(var(--primary) / 0.18);
}
/* --- ASCII corner brackets (accent + on the corners) ------ */
.tty-corners { position: relative; }
.tty-corners::before,
.tty-corners::after {
content: "+";
position: absolute;
color: hsl(var(--primary));
font-family: var(--font-mono, ui-monospace, monospace);
font-weight: 700;
line-height: 1;
pointer-events: none;
}
.tty-corners::before { top: -0.55em; left: -0.35em; }
.tty-corners::after { bottom: -0.55em; right: -0.35em; }
/* --- ASCII figure frame (image override) ------------------ */
/* --- ASCII frame (image override + figure wrappers) --- */
.ascii-frame {
display: inline-block;
@ -248,6 +96,26 @@ css:
background-color: hsl(var(--card));
position: relative;
}
.ascii-frame::before,
.ascii-frame::after {
content: "+";
position: absolute;
color: hsl(var(--border));
font-family: var(--font-mono, ui-monospace, monospace);
font-weight: 700;
}
.ascii-frame::before {
top: -0.5em;
left: -0.5em;
}
.ascii-frame::after {
bottom: -0.5em;
right: -0.5em;
}
.ascii-frame figcaption {
display: block;
margin-top: 0.5rem;
@ -257,25 +125,12 @@ css:
text-align: center;
}
/* --- TTY scanlines overlay element (hero/landing) --------- */
/* --- CRT scanlines overlay --- */
.tty-scanlines {
opacity: 0.06;
background-image: linear-gradient(
to bottom,
hsl(var(--foreground)) 0px,
hsl(var(--foreground)) 1px,
transparent 1px,
transparent 3px
);
background-size: 100% 3px;
animation: tty-scan-drift 6s linear infinite;
.crt-scanlines {
position: relative;
}
@keyframes tty-scan-drift { 0% { background-position: 0 0; } 100% { background-position: 0 3px; } }
/* --- Page-level CRT scanline wash (subtle, whole page) ---- */
.crt-scanlines { position: relative; }
.crt-scanlines::before {
content: "";
position: fixed;
@ -286,47 +141,102 @@ css:
to bottom,
transparent 0,
transparent 2px,
hsl(var(--foreground) / 0.04) 2px,
hsl(var(--foreground) / 0.04) 3px
hsl(var(--background) / 0.15) 2px,
hsl(var(--background) / 0.15) 3px
);
}
/* --- Status dot pulse ------------------------------------- */
/* --- Caret blink keyframe + utility --- */
.terminal-status-dot { animation: terminal-pulse 2.2s ease-in-out infinite; }
@keyframes terminal-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
/* --- Typed-terminal canvas showpiece (hero override only) - */
.tty-rain-wrap {
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
}
.tty-rain-canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
opacity: 0.45;
}
.tty-rain-fallback {
position: absolute;
inset: 0;
background:
radial-gradient(120% 80% at 50% 0%, hsl(var(--primary) / 0.16), transparent 60%),
linear-gradient(to bottom, transparent, hsl(var(--background) / 0.7));
@keyframes caret-blink {
0%, 50% { opacity: 1; }
50.01%, 100% { opacity: 0; }
}
/* --- Man-page header block -------------------------------- */
.caret-blink::after {
content: "_";
display: inline-block;
margin-left: 0.15em;
animation: caret-blink 1s step-end infinite;
animation-name: caret-blink;
color: hsl(var(--foreground));
}
/* --- Bracketed button (override) --- */
.terminal-button {
font-family: var(--font-mono, ui-monospace, monospace);
background: transparent;
color: hsl(var(--foreground));
border: 1px solid hsl(var(--border));
padding: 0.4rem 0.8rem;
text-decoration: none;
display: inline-block;
text-transform: uppercase;
letter-spacing: 0.08em;
position: relative;
}
.terminal-button:hover {
background: hsl(var(--foreground));
color: hsl(var(--background));
}
.terminal-button:hover::after {
content: "_";
position: absolute;
margin-left: 0.2em;
animation: caret-blink 1s step-end infinite;
animation-name: caret-blink;
}
/* --- Bracketed nav --- */
.terminal-nav {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
font-family: var(--font-mono, ui-monospace, monospace);
}
.terminal-nav a {
color: hsl(var(--foreground));
text-decoration: none;
white-space: nowrap;
}
.terminal-nav a:hover {
color: hsl(var(--accent));
}
/* --- ASCII header block --- */
.ascii-header {
font-family: var(--font-mono, ui-monospace, monospace);
color: hsl(var(--accent));
padding: 1rem 0;
border-bottom: 1px solid hsl(var(--border));
white-space: pre;
overflow-x: auto;
}
.ascii-header pre {
margin: 0;
font-family: inherit;
line-height: 1.1;
}
.ascii-header .prompt-line {
margin-top: 0.5rem;
color: hsl(var(--foreground));
}
/* --- Man-page header --- */
.manpage-header {
font-family: var(--font-mono, ui-monospace, monospace);
border-top: 1px solid hsl(var(--border));
border-bottom: 1px solid hsl(var(--border));
padding: 0.6rem 0;
padding: 0.75rem 0;
margin-bottom: 1rem;
display: flex;
justify-content: space-between;
@ -335,13 +245,17 @@ css:
letter-spacing: 0.1em;
color: hsl(var(--foreground));
}
.manpage-header .left,
.manpage-header .center,
.manpage-header .right { flex: 1; }
.manpage-header .right {
flex: 1;
}
.manpage-header .center { text-align: center; }
.manpage-header .right { text-align: right; }
/* --- Section rail (article aside furniture) --------------- */
/* --- TOC (left rail on article) --- */
.terminal-toc {
font-family: var(--font-mono, ui-monospace, monospace);
@ -349,9 +263,8 @@ css:
padding: 1rem;
border: 1px solid hsl(var(--border));
background: hsl(var(--card));
position: sticky;
top: 1rem;
}
.terminal-toc h4 {
text-transform: uppercase;
letter-spacing: 0.1em;
@ -359,13 +272,33 @@ css:
margin: 0 0 0.5rem;
font-size: 0.875rem;
}
.terminal-toc ul { list-style: none; margin: 0; padding: 0; }
.terminal-toc li { margin: 0.2rem 0; }
.terminal-toc a { color: hsl(var(--muted-foreground)); text-decoration: none; }
.terminal-toc a:hover { color: hsl(var(--foreground)); }
@media (max-width: 768px) { .terminal-toc { display: none; } }
/* --- Code console block ----------------------------------- */
.terminal-toc ul {
list-style: none;
margin: 0;
padding: 0;
}
.terminal-toc li {
margin: 0.2rem 0;
}
.terminal-toc a {
color: hsl(var(--muted-foreground));
text-decoration: none;
}
.terminal-toc a:hover {
color: hsl(var(--foreground));
}
@media (max-width: 768px) {
.terminal-toc {
display: none;
}
}
/* --- Code console block --- */
.code-console {
font-family: var(--font-mono, ui-monospace, monospace);
@ -376,9 +309,23 @@ css:
overflow-x: auto;
color: hsl(var(--card-foreground));
}
.code-console .cmd-line { display: flex; align-items: baseline; gap: 0.5em; margin: 0; }
.code-console .cmd-prompt { color: hsl(var(--accent)); }
.code-console .cmd-text { color: hsl(var(--foreground)); }
.code-console .cmd-line {
display: flex;
align-items: center;
gap: 0.5em;
color: hsl(var(--accent));
margin: 0;
}
.code-console .cmd-prompt {
color: hsl(var(--accent));
}
.code-console .cmd-text {
color: hsl(var(--foreground));
}
.code-console pre {
margin: 0.5rem 0 0;
white-space: pre-wrap;
@ -386,7 +333,7 @@ css:
font-family: inherit;
}
/* --- Keybinding table ------------------------------------- */
/* --- Keybinding table --- */
.keybind-table {
font-family: var(--font-mono, ui-monospace, monospace);
@ -396,6 +343,7 @@ css:
max-width: 80ch;
color: hsl(var(--foreground));
}
.keybind-table th,
.keybind-table td {
border-bottom: 1px dashed hsl(var(--border));
@ -403,12 +351,14 @@ css:
text-align: left;
vertical-align: top;
}
.keybind-table th {
text-transform: uppercase;
letter-spacing: 0.08em;
color: hsl(var(--accent));
font-weight: 600;
}
.keybind-table kbd {
font-family: inherit;
background: hsl(var(--muted));
@ -419,7 +369,7 @@ css:
font-size: 0.875em;
}
/* --- Boot log --------------------------------------------- */
/* --- Boot log --- */
.boot-log {
font-family: var(--font-mono, ui-monospace, monospace);
@ -431,106 +381,94 @@ css:
white-space: pre-wrap;
overflow-x: auto;
}
.boot-log .boot-line {
display: block;
opacity: 0;
animation: boot-line-in 0.2s ease-out forwards;
}
@keyframes boot-line-in {
from { opacity: 0; transform: translateY(2px); }
to { opacity: 1; transform: translateY(0); }
}
.boot-log .boot-cursor {
display: inline-block;
color: hsl(var(--accent));
animation: caret-blink 1s step-end infinite;
animation-name: caret-blink;
}
/* --- Layout helpers --------------------------------------- */
/* --- Footer --- */
.terminal-main-80 { max-width: 80ch; margin: 0 auto; }
.terminal-footer {
font-family: var(--font-mono, ui-monospace, monospace);
color: hsl(var(--muted-foreground));
border-top: 1px solid hsl(var(--border));
padding: 1rem 0;
margin-top: 2rem;
text-align: center;
}
.terminal-footer .terminal-footer-rule {
color: hsl(var(--border));
margin: 0.25rem 0;
letter-spacing: 0.2em;
}
.terminal-footer .terminal-footer-motd {
color: hsl(var(--foreground));
margin: 0.5rem 0;
}
.terminal-footer .terminal-footer-links {
margin: 0.5rem 0;
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
}
.terminal-footer a {
color: hsl(var(--muted-foreground));
text-decoration: none;
}
.terminal-footer a:hover {
color: hsl(var(--foreground));
}
.terminal-footer .terminal-footer-signup {
margin: 0.75rem auto;
max-width: 40ch;
}
.terminal-footer .terminal-footer-signup input[type="email"] {
font-family: inherit;
background: hsl(var(--input));
color: hsl(var(--foreground));
border: 1px solid hsl(var(--border));
padding: 0.4rem 0.6rem;
}
/* --- Article layout grid --- */
.terminal-article-grid {
display: grid;
grid-template-columns: 20ch 1fr;
grid-template-columns: 18ch 1fr;
gap: 2rem;
align-items: start;
}
@media (max-width: 768px) { .terminal-article-grid { grid-template-columns: 1fr; } }
/* --- Reliable responsive grids ----------------------------
The host scans rendered block HTML, not theme override
.ninjatpl files, so responsive Tailwind grid utilities used
only in overrides (lg:grid-cols-*, sm:grid-cols-2) are
purge-fragile and can silently collapse to one column on
desktop. Multi-column layout is therefore defined here as
plain CSS, which is compiled into the Tailwind input and can
never be purged. minmax(0,1fr) stops long/unbreakable content
from forcing horizontal overflow on mobile. */
.tty-grid,
.tty-grid-2,
.tty-grid-3,
.tty-grid-4 { display: grid; gap: 1.5rem; grid-template-columns: repeat(1, minmax(0, 1fr)); }
@media (min-width: 640px) {
.tty-grid-2,
.tty-grid-3,
.tty-grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
.tty-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.tty-grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.tty-grid-stats2,
.tty-grid-stats3,
.tty-grid-stats4 { display: grid; gap: 2rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 768px) {
.tty-grid-stats3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.tty-grid-stats4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
/* --- Two-column form grid (col-span utilities in overrides
are purge-fragile; pin the grid in CSS) ------------------- */
.tty-form-grid { display: grid; gap: 1.25rem; grid-template-columns: repeat(1, minmax(0, 1fr)); }
.tty-form-grid > .tty-col-full { grid-column: 1 / -1; }
@media (min-width: 640px) { .tty-form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* --- Contained console shell ------------------------------
The persona code-console / boot-log blocks ship no max-width
wrapper, so on a full-width landing page they run edge-to-edge
while the neighbouring sections are contained. This shell
centres them and keeps a mobile side gutter. */
.tty-console-shell { width: 100%; max-width: 56rem; margin: 1.5rem auto; padding-inline: 1rem; }
/* --- Hero heights (min-h-screen / arbitrary min-h-[..] are
purge-fragile utilities; pin them in CSS) ----------------- */
.tty-hero { position: relative; }
.tty-hero-screen { min-height: 100vh; }
.tty-hero-tall { min-height: 75vh; }
.tty-hero-half { min-height: 50vh; }
.tty-hero-auto { min-height: 24rem; }
.tty-hero-media { min-height: 20rem; }
/* --- Reduced motion: kill all motion ---------------------- */
@media (prefers-reduced-motion: reduce) {
.caret-blink,
.tty-scanlines,
.terminal-status-dot,
.tty-rain-canvas,
.boot-log .boot-line,
.boot-log .boot-cursor {
animation: none !important;
animation-duration: 0.01ms !important;
@media (max-width: 768px) {
.terminal-article-grid {
grid-template-columns: 1fr;
}
.boot-log .boot-line { opacity: 1; transform: none; }
.tty-scanlines { opacity: 0; }
.tty-rain-canvas { display: none; }
}
/* --- High contrast: drop the CRT overlays ----------------- */
@media (prefers-contrast: more) {
.tty-scanlines,
.crt-scanlines::before,
.tty-rain-canvas { display: none; }
.terminal-main-80 {
max-width: 80ch;
margin: 0 auto;
font-family: var(--font-body, ui-monospace, monospace);
}

View File

@ -1,22 +1,25 @@
[
{
"key": "terminal:site-master",
"title": "Terminal Site Master",
"page_templates": ["default", "full-width", "landing", "article", "blog-index", "contact"],
"key": "terminal:default-master",
"title": "Terminal Default Master",
"page_templates": ["default", "landing", "full-width"],
"blocks": [
{ "block_key": "navbar", "title": "TTY Navbar", "content": { "menuName": "main", "logoType": "text", "logoText": { "text": "grid" }, "companyName": "grid", "stickyBehavior": "sticky", "borderBottom": true, "cta": { "text": "Install", "url": "/contact" } }, "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": "TTY Footer", "content": { "layout": "columns", "companyName": "grid", "tagline": "A fast parallel task runner and build cache for the terminal.", "copyright": "grid contributors" }, "slot": "footer", "sort_order": 0 }
{ "block_key": "terminal:ascii_header", "title": "TTY Header", "content": { "title": "~/projects", "prompt": "$ " }, "slot": "header", "sort_order": 0 },
{ "block_key": "navbar", "title": "Main Nav", "content": { "menuName": "main", "style": "bracketed" }, "slot": "header", "sort_order": 1 },
{ "block_key": "slot", "title": "Main Content", "content": { "slotName": "main", "placeholder": "// content here" }, "slot": "main", "sort_order": 0 },
{ "block_key": "terminal:footer", "title": "TTY Footer", "content": { "motd": "connection closed.", "showSignup": true }, "slot": "footer", "sort_order": 0 }
]
},
{
"key": "terminal:auth-master",
"title": "Terminal Auth Master",
"page_templates": ["auth"],
"key": "terminal:article-master",
"title": "Terminal Article Master",
"page_templates": ["article"],
"blocks": [
{ "block_key": "navbar", "title": "TTY Navbar", "content": { "menuName": "main", "logoType": "text", "logoText": { "text": "grid" }, "companyName": "grid" }, "slot": "header", "sort_order": 0 },
{ "block_key": "slot", "title": "Auth Console", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
{ "block_key": "footer", "title": "TTY Footer", "content": { "layout": "minimal", "companyName": "grid", "tagline": "A fast parallel task runner and build cache for the terminal.", "copyright": "grid contributors" }, "slot": "footer", "sort_order": 0 }
{ "block_key": "terminal:manpage_header", "title": "Man-page Header", "content": { "name": "WRITEUP", "section": 1, "version": "terminal v0.1.0" }, "slot": "header", "sort_order": 0 },
{ "block_key": "navbar", "title": "Main Nav", "content": { "menuName": "main", "style": "bracketed" }, "slot": "header", "sort_order": 1 },
{ "block_key": "terminal:toc", "title": "Section TOC", "content": { "heading": "Sections", "items": [] }, "slot": "toc", "sort_order": 0 },
{ "block_key": "slot", "title": "Article Body", "content": { "slotName": "main", "placeholder": "// article body" }, "slot": "main", "sort_order": 0 },
{ "block_key": "terminal:footer", "title": "TTY Footer", "content": { "motd": "connection closed.", "showSignup": false }, "slot": "footer", "sort_order": 0 }
]
}
]

View File

@ -2,8 +2,8 @@
name = "terminal"
display_name = "Terminal"
scope = "@themes"
version = "0.2.1"
description = "Phosphor-on-black TTY theme for dev tools and OSS docs. Window chrome, man-page article layouts, keybind tables, boot logs, monospace type, and a typed-terminal canvas hero. Paper-teletype light mode, phosphor dark mode."
version = "0.2.0"
description = "Phosphor green on black terminal aesthetic with ASCII art, monospace type, and man-page article layouts. For indie dev sites, CTF writeups, and CLI tool docs."
kind = "theme"
categories = ["templates"]
tags = ["terminal", "hacker", "monospace", "dev", "ctf", "docs", "devtools", "oss"]
tags = ["terminal", "hacker", "monospace", "dev", "ctf", "security", "docs", "indie"]

View File

@ -2,30 +2,9 @@
{
"id": "phosphor-green",
"name": "Phosphor Green",
"description": "Green printer-ribbon on paper for light, VT220 green phosphor for dark. The flagship terminal look.",
"description": "Classic green-on-black VT220 phosphor.",
"theme": {
"mode": "both",
"lightColors": {
"background": "60 20% 97%",
"foreground": "120 30% 12%",
"card": "60 20% 99%",
"cardForeground": "120 30% 12%",
"popover": "60 20% 99%",
"popoverForeground": "120 30% 12%",
"primary": "130 60% 28%",
"primaryForeground": "60 20% 98%",
"secondary": "90 20% 90%",
"secondaryForeground": "120 30% 20%",
"muted": "60 15% 92%",
"mutedForeground": "120 12% 38%",
"accent": "140 65% 30%",
"accentForeground": "60 20% 98%",
"destructive": "0 70% 42%",
"destructiveForeground": "0 0% 100%",
"border": "90 15% 78%",
"input": "90 15% 84%",
"ring": "130 60% 28%"
},
"mode": "dark",
"darkColors": {
"background": "0 0% 4%",
"foreground": "120 100% 50%",
@ -50,32 +29,11 @@
}
},
{
"id": "amber-crt",
"name": "Amber CRT",
"description": "Warm cream paper with amber ribbon for light, amber phosphor on warm black for dark.",
"id": "amber-mono",
"name": "Amber Mono",
"description": "Warm amber phosphor terminal.",
"theme": {
"mode": "both",
"lightColors": {
"background": "40 30% 96%",
"foreground": "30 25% 15%",
"card": "40 30% 99%",
"cardForeground": "30 25% 15%",
"popover": "40 30% 99%",
"popoverForeground": "30 25% 15%",
"primary": "30 80% 40%",
"primaryForeground": "40 30% 98%",
"secondary": "38 25% 90%",
"secondaryForeground": "30 25% 22%",
"muted": "40 20% 91%",
"mutedForeground": "30 15% 40%",
"accent": "25 85% 43%",
"accentForeground": "40 30% 98%",
"destructive": "0 70% 45%",
"destructiveForeground": "0 0% 100%",
"border": "38 20% 78%",
"input": "38 20% 84%",
"ring": "30 80% 40%"
},
"mode": "dark",
"darkColors": {
"background": "30 20% 4%",
"foreground": "38 95% 60%",
@ -102,30 +60,9 @@
{
"id": "paper-tty",
"name": "Paper TTY",
"description": "Crisp black-on-white docs for light with a blue link accent, hard white on black for dark. Austere, no glow.",
"description": "Hard white on black; austere docs mode (no glow).",
"theme": {
"mode": "both",
"lightColors": {
"background": "0 0% 100%",
"foreground": "0 0% 10%",
"card": "0 0% 100%",
"cardForeground": "0 0% 10%",
"popover": "0 0% 100%",
"popoverForeground": "0 0% 10%",
"primary": "0 0% 12%",
"primaryForeground": "0 0% 98%",
"secondary": "0 0% 94%",
"secondaryForeground": "0 0% 12%",
"muted": "0 0% 95%",
"mutedForeground": "0 0% 40%",
"accent": "210 90% 42%",
"accentForeground": "0 0% 100%",
"destructive": "0 72% 46%",
"destructiveForeground": "0 0% 100%",
"border": "0 0% 85%",
"input": "0 0% 88%",
"ring": "210 90% 42%"
},
"mode": "dark",
"darkColors": {
"background": "0 0% 5%",
"foreground": "0 0% 95%",
@ -148,105 +85,5 @@
"ring": "0 0% 95%"
}
}
},
{
"id": "ice-terminal",
"name": "Ice Terminal",
"description": "Cool slate-on-white for light with a teal accent, cyan phosphor on blue-black for dark.",
"theme": {
"mode": "both",
"lightColors": {
"background": "200 30% 97%",
"foreground": "200 40% 14%",
"card": "200 30% 99%",
"cardForeground": "200 40% 14%",
"popover": "200 30% 99%",
"popoverForeground": "200 40% 14%",
"primary": "190 80% 30%",
"primaryForeground": "200 30% 98%",
"secondary": "195 25% 90%",
"secondaryForeground": "200 40% 20%",
"muted": "200 20% 92%",
"mutedForeground": "200 15% 40%",
"accent": "185 85% 32%",
"accentForeground": "200 30% 98%",
"destructive": "0 70% 45%",
"destructiveForeground": "0 0% 100%",
"border": "195 20% 78%",
"input": "195 20% 84%",
"ring": "190 80% 30%"
},
"darkColors": {
"background": "200 40% 5%",
"foreground": "185 90% 60%",
"card": "200 40% 8%",
"cardForeground": "185 90% 65%",
"popover": "200 40% 7%",
"popoverForeground": "185 90% 65%",
"primary": "185 90% 50%",
"primaryForeground": "200 40% 5%",
"secondary": "195 40% 15%",
"secondaryForeground": "185 90% 72%",
"muted": "200 30% 11%",
"mutedForeground": "185 40% 52%",
"accent": "175 95% 55%",
"accentForeground": "200 40% 5%",
"destructive": "0 85% 55%",
"destructiveForeground": "0 0% 100%",
"border": "190 60% 26%",
"input": "190 50% 18%",
"ring": "185 90% 50%"
}
}
},
{
"id": "magenta-hacker",
"name": "Magenta Hacker",
"description": "Ink-on-white with a magenta accent for light, magenta phosphor on black for dark.",
"theme": {
"mode": "both",
"lightColors": {
"background": "320 20% 98%",
"foreground": "320 30% 14%",
"card": "320 20% 99%",
"cardForeground": "320 30% 14%",
"popover": "320 20% 99%",
"popoverForeground": "320 30% 14%",
"primary": "320 70% 40%",
"primaryForeground": "320 20% 98%",
"secondary": "315 25% 92%",
"secondaryForeground": "320 30% 22%",
"muted": "320 15% 93%",
"mutedForeground": "320 12% 44%",
"accent": "300 75% 43%",
"accentForeground": "320 20% 98%",
"destructive": "0 70% 45%",
"destructiveForeground": "0 0% 100%",
"border": "315 18% 80%",
"input": "315 18% 85%",
"ring": "320 70% 40%"
},
"darkColors": {
"background": "300 25% 5%",
"foreground": "320 90% 65%",
"card": "300 25% 8%",
"cardForeground": "320 90% 70%",
"popover": "300 25% 7%",
"popoverForeground": "320 90% 70%",
"primary": "320 90% 55%",
"primaryForeground": "300 25% 5%",
"secondary": "310 35% 16%",
"secondaryForeground": "320 90% 76%",
"muted": "300 20% 12%",
"mutedForeground": "320 40% 55%",
"accent": "285 90% 62%",
"accentForeground": "300 25% 5%",
"destructive": "0 85% 58%",
"destructiveForeground": "0 0% 100%",
"border": "320 55% 28%",
"input": "320 45% 20%",
"ring": "320 90% 55%"
}
}
}
]

View File

@ -1,240 +0,0 @@
{
"demo": true,
"settings": {
"merge": {
"companyName": "grid",
"tagline": "A fast parallel task runner and build cache for the terminal."
}
},
"data_tables": [
{
"key": "contact_submissions",
"name": "Contact Enquiries",
"description": "Seeded contact-form submissions for the grid 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": "project", "label": "Project", "type": "text", "required": false },
{ "key": "message", "label": "Message", "type": "text", "required": true }
]
}
],
"pages": [
{
"slug": "/",
"title": "grid",
"template_key": "landing",
"blocks": [
{
"block_key": "hero",
"title": "Hero",
"slot": "main",
"sort_order": 1,
"content": {
"template": "centered",
"minHeight": "screen",
"eyebrow": "v1.4.0",
"headline": "Run every task in parallel",
"subheadline": "grid is a task runner and build cache for the terminal.",
"description": "Define tasks once. grid runs the ones that changed, in parallel, and caches the rest. No config server, no daemon.",
"ctaPrimary": { "text": "Install", "url": "/contact" },
"ctaSecondary": { "text": "Read the docs", "url": "/blog" }
}
},
{
"block_key": "feature-grid",
"title": "Features",
"slot": "main",
"sort_order": 2,
"content": {
"eyebrow": "What it does",
"heading": "One binary, three jobs",
"intro": "Runs tasks, caches output, and watches files. Nothing else to install.",
"layout": "card",
"columns": 3,
"items": [
{ "icon": "lucide:cpu", "title": "Parallel", "text": "Tasks that do not depend on each other run at the same time, up to your core count." },
{ "icon": "lucide:database", "title": "Cached", "text": "Every task output is keyed by its inputs. Unchanged inputs skip the work." },
{ "icon": "lucide:eye", "title": "Watch", "text": "Point grid at a file glob and it reruns the affected tasks on save." }
]
}
},
{
"block_key": "terminal:code_console",
"title": "Install",
"slot": "main",
"sort_order": 3,
"content": {
"prompt": "$",
"language": "shell",
"command": "curl -fsSL https://grid.dev/install.sh | sh",
"output": "installing grid v1.4.0 ...\nok: /usr/local/bin/grid\nrun `grid init` in your project to get started"
}
},
{
"block_key": "terminal:boot_log",
"title": "Run log",
"slot": "main",
"sort_order": 4,
"content": {
"cursor": "block",
"lines": [
"grid run build test lint",
"build cached 0.00s",
"lint ok 0.31s",
"test ok 1.12s",
"3 tasks, 1 cached, 0 failed"
]
}
},
{
"block_key": "cta",
"title": "CTA",
"slot": "main",
"sort_order": 5,
"content": {
"background": "band",
"heading": "Try it on your next build",
"text": "Install grid, run grid init, and watch your first cached run.",
"primaryLabel": "Install",
"primaryUrl": "/contact"
}
}
]
},
{
"slug": "/about",
"title": "About grid",
"template_key": "default",
"blocks": [
{ "block_key": "heading", "title": "Heading", "slot": "main", "sort_order": 1, "content": { "text": "About grid", "level": 1 } },
{ "block_key": "rich-text", "title": "Story", "slot": "main", "sort_order": 2, "content": { "html": "<p>grid started as a shell script that ran build steps in the background. The script grew until it needed a cache. Then it needed a dependency graph. At that point it became a real tool.</p><p>The goal has not changed. Run the work that changed, skip the work that did not, and stay out of the way. grid is open source and ships as a single static binary.</p>" } },
{
"block_key": "terminal:code_console",
"title": "Quickstart",
"slot": "main",
"sort_order": 3,
"content": { "prompt": "$", "language": "shell", "command": "grid init && grid run build", "output": "wrote grid.toml\nbuild ok 0.84s\n1 task, 0 cached, 0 failed" }
},
{
"block_key": "terminal:keybind_table",
"title": "Commands",
"slot": "main",
"sort_order": 4,
"content": {
"rows": [
{ "keys": "grid run", "action": "Run the named tasks and their dependencies" },
{ "keys": "grid watch", "action": "Rerun affected tasks when files change" },
{ "keys": "grid graph", "action": "Print the task dependency graph" },
{ "keys": "grid clean", "action": "Drop the local build cache" }
]
}
},
{ "block_key": "divider", "title": "Divider", "slot": "main", "sort_order": 5, "content": { "variant": "line", "weight": "medium" } },
{ "block_key": "quote", "title": "Quote", "slot": "main", "sort_order": 6, "content": { "style": "pull", "quote": "Do the work once. Cache the rest.", "attribution": "grid README" } }
]
},
{
"slug": "/blog",
"title": "Changelog",
"template_key": "blog-index",
"blocks": [
{ "block_key": "heading", "title": "Heading", "slot": "main", "sort_order": 1, "content": { "text": "Changelog", "level": 1 } },
{ "block_key": "card", "title": "Post: Cache keys", "slot": "main", "sort_order": 2, "content": { "layout": "vertical", "title": "How grid keys the cache", "text": "<p>Every task output is keyed by its inputs. Here is how the hash is built and why it is stable across machines.</p>", "link": "/blog/cache-keys", "linkLabel": "Read on" } },
{ "block_key": "card", "title": "Post: Watch mode", "slot": "main", "sort_order": 3, "content": { "layout": "vertical", "title": "Watch mode without a daemon", "text": "<p>grid watch reruns affected tasks on save with no background process. This is how the file watcher stays cheap.</p>", "link": "/blog/watch-mode", "linkLabel": "Read on" } },
{ "block_key": "card", "title": "Post: Parallel", "slot": "main", "sort_order": 4, "content": { "layout": "vertical", "title": "Scheduling tasks in parallel", "text": "<p>Independent tasks run together, up to your core count. The scheduler is a topological sort with a worker pool.</p>", "link": "/blog/parallel-scheduling", "linkLabel": "Read on" } }
]
},
{
"slug": "/blog/cache-keys",
"title": "How grid keys the cache",
"template_key": "article",
"parent_slug": "/blog",
"blocks": [
{ "block_key": "terminal:manpage_header", "title": "Man header", "slot": "main", "sort_order": 1, "content": { "name": "CACHE-KEYS", "section": 7, "version": "grid v1.4.0" } },
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 2, "content": { "text": "How grid keys the cache", "level": 1 } },
{ "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 3, "content": { "html": "<p>A cache is only useful if the key is exact. grid hashes the task command, its input files, and the tool versions it declares. Same inputs, same key, cached output.</p><p>The key is content based, not time based. Two machines that check out the same commit build the same key. That makes the cache shareable across a team.</p>" } },
{ "block_key": "terminal:code_console", "title": "Example", "slot": "main", "sort_order": 4, "content": { "prompt": "$", "language": "shell", "command": "grid run build --explain", "output": "build key: sha256:9f2c...\ninputs: src/**, grid.toml, go 1.26\nresult: cache hit" } }
]
},
{
"slug": "/blog/watch-mode",
"title": "Watch mode without a daemon",
"template_key": "article",
"parent_slug": "/blog",
"blocks": [
{ "block_key": "terminal:manpage_header", "title": "Man header", "slot": "main", "sort_order": 1, "content": { "name": "WATCH", "section": 1, "version": "grid v1.4.0" } },
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 2, "content": { "text": "Watch mode without a daemon", "level": 1 } },
{ "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 3, "content": { "html": "<p>Most watchers run a background process that never exits. grid does not. grid watch runs in the foreground and exits when you press q.</p><p>On each save it maps the changed file back to the tasks that read it, then reruns only those. The rest stay cached.</p>" } },
{ "block_key": "terminal:code_console", "title": "Example", "slot": "main", "sort_order": 4, "content": { "prompt": "$", "language": "shell", "command": "grid watch 'src/**/*.go'", "output": "watching 42 files\nchanged: src/api/handler.go\ntest ok 0.90s" } }
]
},
{
"slug": "/blog/parallel-scheduling",
"title": "Scheduling tasks in parallel",
"template_key": "article",
"parent_slug": "/blog",
"blocks": [
{ "block_key": "terminal:manpage_header", "title": "Man header", "slot": "main", "sort_order": 1, "content": { "name": "SCHEDULER", "section": 7, "version": "grid v1.4.0" } },
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 2, "content": { "text": "Scheduling tasks in parallel", "level": 1 } },
{ "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 3, "content": { "html": "<p>grid builds a graph of tasks and their dependencies. It runs a task the moment its dependencies finish. Independent branches run at the same time.</p><p>The worker pool is sized to your core count by default. You can cap it with a flag when a task is memory heavy.</p>" } },
{ "block_key": "terminal:code_console", "title": "Example", "slot": "main", "sort_order": 4, "content": { "prompt": "$", "language": "shell", "command": "grid run all --jobs 4", "output": "scheduled 9 tasks across 4 workers\nall ok 2.03s" } }
]
},
{
"slug": "/contact",
"title": "Contact",
"template_key": "contact",
"blocks": [
{
"block_key": "contact-form",
"title": "Contact form",
"slot": "main",
"sort_order": 1,
"content": {
"heading": "Get in touch",
"description": "Ask a question or report a bug. We reply within one business day.",
"submitLabel": "Send",
"successMessage": "Message received. We will reply within one business day.",
"fields": [
{ "name": "name", "label": "Your name", "type": "text", "required": true, "width": "half" },
{ "name": "email", "label": "Email", "type": "email", "required": true, "width": "half" },
{ "name": "project", "label": "Project", "type": "text", "required": false, "width": "full" },
{ "name": "message", "label": "Message", "type": "textarea", "required": true, "width": "full" }
],
"formConfig": { "enabled": true, "captchaEnabled": false, "targetTable": "contact_submissions" }
}
},
{
"block_key": "contact-card",
"title": "Reach us",
"slot": "main",
"sort_order": 2,
"content": {
"heading": "Other ways in",
"columns": 3,
"channels": [
{ "icon": "lucide:mail", "label": "Email", "value": "team@grid.dev", "link": "mailto:team@grid.dev" },
{ "icon": "simple-icons:github", "label": "Source", "value": "github.com/grid-dev/grid", "link": "https://github.com/grid-dev/grid" },
{ "icon": "lucide:message-square", "label": "Chat", "value": "Join the Discord", "link": "#" }
]
}
}
]
},
{
"slug": "/login",
"title": "Sign in",
"template_key": "auth",
"blocks": [
{ "block_key": "auth-form", "title": "Sign in", "slot": "main", "sort_order": 1, "content": { "default_tab": "login", "show_social": false } }
]
}
],
"menu_items": [
{ "menu": "main", "label": "Home", "page_slug": "/", "sort_order": 1 },
{ "menu": "main", "label": "About", "page_slug": "/about", "sort_order": 2 },
{ "menu": "main", "label": "Docs", "page_slug": "/blog", "sort_order": 3 },
{ "menu": "main", "label": "Contact", "page_slug": "/contact", "sort_order": 4 }
]
}

View File

@ -1,19 +0,0 @@
{
"workflows": [
{
"key": "contact_notify_admins",
"name": "Contact enquiry to admins",
"description": "On a contact-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" }
}
]
}
]
}

View File

@ -1,21 +0,0 @@
{# announcement_bar (WO-TF-006): dismissable banner; dismissal persists in localStorage keyed by a content hash (variant + slugified message) so it reappears when the message changes; no-JS fallback is a static non-dismissable strip. Terminal override: mono broadcast readout — data-* + script preserved. #}
{% set anncls = "bg-primary/10 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-bn-announcement data-bn-annkey="{{ variant|default:'info' }}:{{ message|slugify }}" class="bn-announcement w-full {{ 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="terminal-mono uppercase tracking-wide font-medium">{{ 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 %}

View File

@ -1,16 +0,0 @@
{# audio-embed terminal override: neon panel, mono meta. Facade script + data-bn-* attributes + {% img %} preserved byte-for-byte. #}
<figure class="bn-audio my-6 overflow-hidden rounded-md border border-primary/40 bg-card text-card-foreground tty-frame{% 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="h-16 w-16 shrink-0 overflow-hidden rounded-md border border-primary/30 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="terminal-heading truncate font-medium text-foreground">{{ title }}</p>{% endif %}{% if artist %}<p class="terminal-mono truncate text-sm text-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="terminal-mono inline-flex items-center gap-2 rounded-md bg-primary px-4 py-1.5 text-sm font-medium uppercase tracking-wide text-primary-foreground transition-colors hover:bg-primary/90 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring" 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="terminal-mono inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground">::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>

View File

@ -1,77 +0,0 @@
{# auth-form terminal override — reproduces the builtin's security-critical wiring: hardcoded HTMX endpoints /api/auth/login + /api/auth/register (target #auth-message), /forgot-password link, OAuth start URLs gated on context.site.*, and the exact Cap widget markup when captchaEnabled. Only the chrome is themed. #}
{% if context.isPublicLoggedIn %}
<div class="auth-form-logged-in mx-auto max-w-md text-center py-6">
<p class="terminal-mono text-sm uppercase tracking-wide text-muted-foreground">Session active as <strong class="text-primary">{{ context.publicUsername }}</strong></p>
<a href="/account" class="terminal-mono mt-3 inline-flex items-center rounded-md bg-primary px-4 py-2 text-sm font-medium uppercase tracking-wide text-primary-foreground hover:bg-primary/90">My Account</a>
</div>
{% else %}
{% set loginActive = "border-primary text-primary" %}
{% set registerActive = "border-transparent text-muted-foreground" %}
{% set loginHidden = "" %}
{% set registerHidden = "hidden" %}
{% if default_tab == "register" %}{% set loginActive = "border-transparent text-muted-foreground" %}{% set registerActive = "border-primary text-primary" %}{% set loginHidden = "hidden" %}{% set registerHidden = "" %}{% endif %}
{% set tabbase = "flex-1 py-2 text-center terminal-mono text-sm font-medium uppercase tracking-wider border-b-2 transition-colors" %}
{% macro captcha() %}<script>
window.CAP_CUSTOM_WASM_URL = "/assets/captcha/cap_wasm_bg.wasm";
window.CAP_PAKO_URL = "/assets/captcha/pako_inflate.min.js";
</script>
<cap-widget data-cap-api-endpoint="/api/captcha/" data-cap-worker-count="2"></cap-widget>
<script src="/assets/captcha/cap.min.js" defer></script>
<script>
(function(){
var s = document.currentScript;
var form = s && s.closest ? s.closest('form') : null;
if (!form) return;
form.addEventListener('htmx:afterRequest', function(){
var w = form.querySelector('cap-widget');
if (w && typeof w.reset === 'function') w.reset();
});
})();
</script>{% endmacro %}
{% macro social() %}{% if show_social != false and (context.site.google_oauth_client_id or context.site.facebook_app_id) %}<div class="auth-social my-4">
<div class="relative my-4"><hr class="border-border"/><span class="terminal-mono absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-background px-3 text-xs uppercase tracking-widest text-muted-foreground">or</span></div>
{% if context.site.google_oauth_client_id %}<a href="/api/auth/google/start" class="terminal-mono mb-2 flex w-full items-center justify-center gap-3 rounded-md border border-border px-4 py-2.5 uppercase tracking-wide transition-colors hover:bg-accent/10"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#google"></use></svg><span class="text-sm font-medium text-foreground/80">Continue with Google</span></a>{% endif %}
{% if context.site.facebook_app_id %}<a href="/api/auth/facebook/start" class="terminal-mono flex w-full items-center justify-center gap-3 rounded-md border border-border px-4 py-2.5 uppercase tracking-wide transition-colors hover:bg-accent/10"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#facebook"></use></svg><span class="text-sm font-medium text-foreground/80">Continue with Facebook</span></a>{% endif %}
</div>{% endif %}{% endmacro %}
<div class="auth-form tty-corners mx-auto max-w-md rounded-md border border-primary/40 bg-card p-6 text-card-foreground tty-frame">
<div id="auth-message"></div>
<div class="mb-5 flex border-b border-border">
<button type="button" data-auth-tab="login" data-tabbase="{{ tabbase }}" class="{{ tabbase }} {{ loginActive }}">Login</button>
<button type="button" data-auth-tab="register" data-tabbase="{{ tabbase }}" class="{{ tabbase }} {{ registerActive }}">Register</button>
</div>
<div id="login-panel" class="{{ loginHidden }}">
<form hx-post="/api/auth/login" hx-target="#auth-message" hx-swap="innerHTML">
<div class="mb-3"><label class="terminal-mono mb-1 block text-xs font-medium uppercase tracking-wide text-accent">Email</label><input type="email" name="email" required class="w-full rounded-md border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"/></div>
<div class="mb-4"><label class="terminal-mono mb-1 block text-xs font-medium uppercase tracking-wide text-accent">Password</label><input type="password" name="password" required class="w-full rounded-md border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"/></div>
{% if captchaEnabled %}{{ captcha() }}{% endif %}
<button type="submit" class="terminal-mono w-full rounded-md bg-primary px-4 py-2.5 font-semibold uppercase tracking-wider text-primary-foreground hover:bg-primary/90" hx-disabled-elt="this">Login</button>
<p class="mt-3 text-center text-sm"><a href="/forgot-password" class="terminal-mono uppercase tracking-wide text-primary hover:underline">Forgot password?</a></p>
</form>
{{ social() }}
</div>
<div id="register-panel" class="{{ registerHidden }}">
<form hx-post="/api/auth/register" hx-target="#auth-message" hx-swap="innerHTML">
<div class="mb-3"><label class="terminal-mono mb-1 block text-xs font-medium uppercase tracking-wide text-accent">Username</label><input type="text" name="username" required pattern="[a-zA-Z0-9_-]{3,30}" class="w-full rounded-md border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"/></div>
<div class="mb-3"><label class="terminal-mono mb-1 block text-xs font-medium uppercase tracking-wide text-accent">Email</label><input type="email" name="email" required class="w-full rounded-md border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"/></div>
<div class="mb-4"><label class="terminal-mono mb-1 block text-xs font-medium uppercase tracking-wide text-accent">Password</label><input type="password" name="password" required minlength="8" class="w-full rounded-md border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"/></div>
{% if captchaEnabled %}{{ captcha() }}{% endif %}
<button type="submit" class="terminal-mono w-full rounded-md bg-primary px-4 py-2.5 font-semibold uppercase tracking-wider text-primary-foreground hover:bg-primary/90" hx-disabled-elt="this">Create Account</button>
</form>
{{ social() }}
</div>
</div>
<script>
(function(){
var f=document.currentScript.closest('.auth-form')||document.querySelector('.auth-form');
if(!f)return;
var tabs=f.querySelectorAll('[data-auth-tab]');
tabs.forEach(function(btn){btn.addEventListener('click',function(){
var t=btn.getAttribute('data-auth-tab');
var lp=f.querySelector('#login-panel'),rp=f.querySelector('#register-panel');
if(lp)lp.classList.toggle('hidden',t!=='login');
if(rp)rp.classList.toggle('hidden',t!=='register');
tabs.forEach(function(b){var on=b===btn;b.className=b.getAttribute('data-tabbase')+(on?' border-primary text-primary':' border-transparent text-muted-foreground');});
});});
})();
</script>
{% endif %}

View File

@ -1,36 +0,0 @@
{# auth-status terminal override — reproduces the builtin's hardcoded wiring: /login + /login?tab=register when logged out; /account, /account/reviews, and hx-post /api/auth/logout dropdown when logged in; hx-post /api/auth/resend-verification banner when unverified. State from context.*. #}
{% if not context.isPublicLoggedIn %}
<div class="auth-status flex items-center gap-2">
<a href="/login" class="terminal-mono rounded-md px-3 py-1.5 text-sm font-medium uppercase tracking-wide opacity-80 transition-colors hover:bg-primary/10 hover:opacity-100">Login</a>
<a href="/login?tab=register" class="terminal-mono rounded-md bg-accent px-3 py-1.5 text-sm font-medium uppercase tracking-wide text-accent-foreground hover:bg-accent/90">Sign Up</a>
</div>
{% else %}
{% if not context.publicEmailVerified %}<div class="fixed top-16 inset-x-0 z-40 border-y border-destructive/50 bg-destructive/15 px-4 py-2 text-center text-sm text-destructive">
<span class="terminal-mono uppercase tracking-wide">Verify your email address.</span> <button hx-post="/api/auth/resend-verification" hx-swap="outerHTML" class="underline font-medium">Resend verification email</button>
</div>{% endif %}
<div class="auth-status relative">
<button type="button" onclick="this.nextElementSibling.classList.toggle('hidden')" class="flex items-center gap-2 rounded-md px-3 py-1.5 text-sm font-medium opacity-90 transition-colors hover:bg-primary/10 hover:opacity-100">
<span class="flex h-7 w-7 items-center justify-center rounded-md bg-accent text-accent-foreground"><svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#user"></use></svg></span>
<span class="terminal-mono uppercase tracking-wide">{{ context.publicDisplayName|default:context.publicUsername }}</span>
<svg class="h-4 w-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="tty-corners hidden absolute right-0 top-full mt-1 w-48 rounded-md border border-primary/40 bg-card py-1 text-card-foreground tty-frame z-50">
<a href="/account" class="terminal-mono block px-4 py-2 text-sm uppercase tracking-wide text-card-foreground hover:bg-primary/10">My Profile</a>
<a href="/account/reviews" class="terminal-mono block px-4 py-2 text-sm uppercase tracking-wide text-card-foreground hover:bg-primary/10">My Reviews</a>
<hr class="my-1 border-border"/>
<button hx-post="/api/auth/logout" hx-swap="none" class="terminal-mono block w-full px-4 py-2 text-left text-sm uppercase tracking-wide text-destructive hover:bg-primary/10">Logout</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>
{% endif %}

View File

@ -1,22 +0,0 @@
{# author-bio-hero (terminal override) — author-page header from the `authors` provider. #}
{% if author %}
<section class="relative overflow-hidden border-b border-primary/30 bg-background py-12 text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
<div class="tty-scanlines pointer-events-none absolute inset-0"></div>
<div class="relative z-10 mx-auto flex max-w-3xl flex-col items-center px-4 text-center">
{% if showAvatar and author.avatar_url %}<div class="mb-5 h-24 w-24 overflow-hidden rounded-full bg-muted ring-2 ring-primary tty-frame">{% img author.avatar_url alt=author.display_name class="h-full w-full object-cover" %}</div>{% endif %}
<h1 class="terminal-heading text-3xl font-bold tracking-tight md:text-4xl" data-text="{{ author.display_name }}">{{ author.display_name }}</h1>
{% if author.job_title or author.company %}<p class="terminal-mono mt-1 text-sm uppercase tracking-wide text-accent">{{ author.job_title }}{% if author.job_title and author.company %} · {% endif %}{{ author.company }}</p>{% endif %}
{% if author.bio %}<p class="terminal-page mt-4 max-w-2xl leading-relaxed text-muted-foreground">{{ author.bio }}</p>{% endif %}
<div class="mt-5 flex flex-wrap items-center justify-center gap-4 terminal-mono text-sm uppercase tracking-wide text-accent">
{% 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-5 flex items-center justify-center gap-3">
{% 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 %}

View File

@ -1,13 +0,0 @@
{# author-bio (terminal override) — byline card sourced from the `post` provider's author_* fields. #}
{% if post.author_name %}
<aside class="tty-corners rounded-md border border-dashed border-primary/40 bg-card p-6 text-card-foreground tty-frame{% if class %} {{ class }}{% endif %}">
{% if heading %}<p class="terminal-eyebrow terminal-mono mb-4 text-xs font-semibold uppercase tracking-widest text-accent">{{ 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 border border-primary/40 bg-muted">{% img post.author_avatar alt=post.author_name class="h-full w-full object-cover" %}</div>{% endif %}
<div class="min-w-0">
<h3 class="terminal-heading text-base font-semibold">{% 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>
{% if post.author_bio %}<p class="terminal-page mt-1 text-sm text-muted-foreground">{{ post.author_bio }}</p>{% endif %}
</div>
</div>
</aside>
{% endif %}

View File

@ -1,24 +0,0 @@
{# blog-hero (terminal override) — blog-index header + featured posts from the `posts` provider. #}
<section class="relative overflow-hidden bg-background py-12 text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
<div class="tty-scanlines pointer-events-none absolute inset-0"></div>
<div class="relative z-10 mx-auto max-w-6xl px-4">
<div class="max-w-2xl">
{% if title %}<h1 class="terminal-heading text-4xl font-bold tracking-tight md:text-5xl" data-text="{{ title }}">{{ title }}</h1>{% endif %}
{% if subtitle %}<p class="terminal-page mt-3 text-lg text-muted-foreground">{{ subtitle }}</p>{% endif %}
</div>
{% if posts %}
<div class="mt-10 grid grid-cols-1 gap-6 md:grid-cols-3">
{% for post in posts %}
<article class="tty-corners group flex flex-col rounded-md border border-dashed border-primary/40 bg-card p-4 text-card-foreground tty-frame{% if forloop.First %} md:col-span-3 md:flex-row md:items-stretch md:gap-6{% endif %}">
{% if post.featured_image_url %}<a href="{{ post.url }}" class="mb-3 block overflow-hidden rounded-md 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-300 group-hover:scale-105" %}</a>{% endif %}
<div class="flex flex-col justify-center{% if forloop.First %} md:w-1/2{% endif %}">
<h2 class="terminal-heading {% if forloop.First %}text-2xl{% else %}text-lg{% endif %} font-semibold leading-snug"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h2>
{% if showExcerpt and post.excerpt %}<p class="terminal-page mt-2 text-sm text-muted-foreground">{{ post.excerpt }}</p>{% endif %}
<div class="mt-3 flex flex-wrap items-center gap-x-3 terminal-mono text-xs uppercase tracking-wide text-accent">{% if post.author_name %}<span class="font-medium text-foreground">{{ 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>

View File

@ -1,15 +0,0 @@
{# breadcrumbs (terminal override) — 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" class="py-3{% if class %} {{ class }}{% endif %}">
<ol class="flex flex-wrap items-center gap-1 terminal-mono text-sm uppercase tracking-wide">
{% if showHome %}<li class="flex items-center"><a href="/" class="text-accent 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-4 w-4 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="font-medium text-foreground" aria-current="page">{{ item.label }}</span>{% endif %}{% else %}<a href="{{ item.url }}" class="text-accent transition-colors hover:text-primary">{{ item.label }}</a>{% endif %}
</li>
{% endfor %}
</ol>
</nav>

View File

@ -1,2 +1 @@
{# terminal button — keep {% button %} tag intact; terminal chrome on hugging wrapper #}
{% with align=align|default:"left" %}<div class="bn-button flex {% if align == "center" %}justify-center{% elif align == "right" %}justify-end{% else %}justify-start{% endif %}{% if class %} {{ class }}{% endif %}"><span class="terminal-mono terminal-mono inline-flex uppercase tracking-wider font-semibold">{% if newTab %}{% button style|default:"primary" text=label|default:"Learn more" href=link|default:"#" size=size|default:"md" icon=icon|default:"" icon_position=iconPosition|default:"left" target="_blank" %}{% else %}{% button style|default:"primary" text=label|default:"Learn more" href=link|default:"#" size=size|default:"md" icon=icon|default:"" icon_position=iconPosition|default:"left" %}{% endif %}</span></div>{% endwith %}
{% if text %}{% if target == "_blank" %}<a class="terminal-button caret-blink" href="{% if url %}{{ url }}{% else %}#{% endif %}" target="_blank" rel="noopener noreferrer">[ {{ text }} ]</a>{% else %}<a class="terminal-button caret-blink" href="{% if url %}{{ url }}{% else %}#{% endif %}">[ {{ text }} ]</a>{% endif %}{% endif %}

View File

@ -1,2 +0,0 @@
{# terminal card — dashed neon panel; fields layout/link/media/title/text/linkLabel #}
{% with layout=layout|default:"vertical" %}<article class="bn-card tty-frame overflow-hidden rounded-md border border-dashed border-primary/40 bg-card text-card-foreground tty-frame {% 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="text-lg font-semibold tracking-tight text-foreground terminal-heading">{{ title }}</h3>{% endif %}{% if text %}<div class="text-sm leading-relaxed text-muted-foreground terminal-page">{{ text|safe }}</div>{% endif %}{% if linkLabel and link %}<span class="mt-2 inline-flex items-center gap-1 text-sm font-medium text-primary terminal-mono uppercase tracking-wide">{{ linkLabel }}::lucide:arrow-right:sm::</span>{% endif %}</div>{% if link %}</a>{% endif %}</article>{% endwith %}

View File

@ -1,18 +0,0 @@
{# category-list (terminal override) — from the `categories` provider. list or pills. #}
{% set pills = style == "pills" %}
<section class="bg-background text-foreground{% if class %} {{ class }}{% endif %}">
{% if heading %}<h2 class="terminal-eyebrow terminal-mono mb-4 text-xs font-semibold uppercase tracking-widest text-accent">{{ heading }}</h2>{% endif %}
{% if pills %}
<div class="flex flex-wrap gap-2">
{% for cat in categories %}
<a href="{{ cat.url }}" class="inline-flex items-center gap-1.5 border border-primary/50 bg-card px-3 py-1 terminal-mono text-sm uppercase tracking-wide text-card-foreground tty-frame transition-colors 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="flex items-center justify-between rounded-md px-3 py-2 terminal-mono text-sm uppercase tracking-wide text-muted-foreground transition-colors hover:bg-accent hover:text-primary"><span>{{ cat.name }}</span>{% if showCount %}<span class="rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground">{{ cat.post_count }}</span>{% endif %}</a></li>
{% endfor %}
</ul>
{% endif %}
</section>

View File

@ -1,18 +0,0 @@
{# contact-card terminal override: neon channel panels. All field reads + control flow preserved. #}
<section class="py-8 md:py-12 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-4xl px-4">
{% if heading %}<h2 class="terminal-heading text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
{% if description %}<p class="mt-3 max-w-2xl text-lg text-muted-foreground">{{ description }}</p>{% endif %}
<div class="mt-8 {% if columns == 3 %}tty-grid-3{% elif columns == 1 %}tty-grid{% else %}tty-grid-2{% endif %}">
{% for channel in channels %}
<div class="tty-corners flex items-start gap-4 rounded-md border border-primary/40 bg-card p-5 text-card-foreground tty-frame">
{% if channel.icon %}<span class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-md 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="terminal-mono text-sm font-medium uppercase tracking-wide text-accent">{{ channel.label }}</div>{% endif %}
{% if channel.value %}<div class="mt-0.5 break-words font-medium">{% if channel.link %}<a href="{{ channel.link }}" class="transition-colors hover:text-primary">{{ channel.value }}</a>{% else %}{{ channel.value }}{% endif %}</div>{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
</section>

View File

@ -1,40 +0,0 @@
{# contact-form terminal override: terminal console chrome. The {% form %} block, honeypot, field loop, form.submitted/errors reads and _block_id/bid submit wiring are preserved byte-for-byte — only presentational classes change. #}
{% with bid=_block_id|default:context.blockId %}
<section id="contact-form-{{ bid }}" class="py-8 md:py-12 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-2xl px-4">
{% if form.submitted %}
<div class="tty-corners rounded-md border border-primary/50 bg-card p-8 text-center text-card-foreground tty-frame">
<div class="mx-auto mb-4 inline-flex h-12 w-12 items-center justify-center rounded-md bg-primary/10 text-primary"><svg class="h-6 w-6" aria-hidden="true"><use href="/icons/lucide.svg#check"></use></svg></div>
<p class="terminal-heading text-lg font-medium">{{ form.message|default:successMessage|default:"Thanks — your message has been sent." }}</p>
</div>
{% else %}
{% if heading %}<h2 class="terminal-heading text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
{% if description %}<p class="mt-3 text-lg text-muted-foreground">{{ description }}</p>{% endif %}
{% if form.errors._form %}<div class="mt-6 rounded-md 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 tty-form-grid" %}
{% for field in fields %}
<div class="{% if field.width == "half" %}tty-col-half{% else %}tty-col-full{% endif %}">
{% if field.label %}<label for="cf-{{ bid }}-{{ field.name }}" class="terminal-mono mb-1.5 block text-sm font-medium uppercase tracking-wide text-accent">{{ 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-md border border-primary/40 bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 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-md border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 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-md border border-primary/40 bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 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="tty-col-full">
<button type="submit" class="terminal-mono inline-flex items-center justify-center rounded-md bg-primary px-6 py-2.5 text-sm font-medium uppercase tracking-wider text-primary-foreground transition-colors hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2">{{ submitLabel|default:"Send message" }}</button>
</div>
{% endform %}
{% endwith %}
{% endif %}
</div>
</section>
{% endwith %}

View File

@ -1,50 +0,0 @@
{# countdown terminal override: neon HUD digit boxes, heading. Timer script + data attributes preserved byte-for-byte. #}
<section class="relative overflow-hidden py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div aria-hidden="true" class="tty-scanlines pointer-events-none absolute inset-0"></div>
<div class="relative z-10 mx-auto max-w-4xl px-4 text-center">
{% if heading %}<h2 class="terminal-heading text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-lg text-muted-foreground">{{ 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" %}min-w-[4.5rem] rounded-md border border-primary/50 bg-card px-3 py-4 text-card-foreground tty-frame{% endif %}"><span class="terminal-mono text-4xl font-bold tabular-nums sm:text-5xl text-primary" data-unit="days">--</span><span class="terminal-mono mt-1 text-xs font-medium uppercase tracking-widest text-muted-foreground">{{ labelDays|default:"Days" }}</span></div>
<div class="flex flex-col items-center {% if variant == "boxes" %}min-w-[4.5rem] rounded-md border border-primary/50 bg-card px-3 py-4 text-card-foreground tty-frame{% endif %}"><span class="terminal-mono text-4xl font-bold tabular-nums sm:text-5xl text-primary" data-unit="hours">--</span><span class="terminal-mono mt-1 text-xs font-medium uppercase tracking-widest text-muted-foreground">{{ labelHours|default:"Hours" }}</span></div>
<div class="flex flex-col items-center {% if variant == "boxes" %}min-w-[4.5rem] rounded-md border border-primary/50 bg-card px-3 py-4 text-card-foreground tty-frame{% endif %}"><span class="terminal-mono text-4xl font-bold tabular-nums sm:text-5xl text-primary" data-unit="minutes">--</span><span class="terminal-mono mt-1 text-xs font-medium uppercase tracking-widest text-muted-foreground">{{ labelMinutes|default:"Minutes" }}</span></div>
<div class="flex flex-col items-center {% if variant == "boxes" %}min-w-[4.5rem] rounded-md border border-primary/50 bg-card px-3 py-4 text-card-foreground tty-frame{% endif %}"><span class="terminal-mono text-4xl font-bold tabular-nums sm:text-5xl text-primary" data-unit="seconds">--</span><span class="terminal-mono mt-1 text-xs font-medium uppercase tracking-widest text-muted-foreground">{{ labelSeconds|default:"Seconds" }}</span></div>
</div>
<div class="mt-8 hidden" data-countdown-expired>
<p class="terminal-heading text-xl font-medium">{{ expiredMessage|default:"This has started." }}</p>
{% if expiredUrl and expiredLabel %}<a href="{{ expiredUrl }}" class="terminal-mono mt-4 inline-flex items-center gap-2 rounded-md bg-primary px-5 py-3 font-semibold uppercase tracking-wider text-primary-foreground transition-opacity hover:opacity-90">{{ 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>

View File

@ -1,16 +0,0 @@
{# cta terminal override: scanline overlay, heading, CTAs. All field reads preserved. #}
<section class="relative overflow-hidden py-12 md:py-20 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div aria-hidden="true" class="tty-scanlines pointer-events-none absolute inset-0"></div>
<div class="relative z-10 mx-auto max-w-6xl px-4">
<div class="tty-corners {% if background == "band" %}rounded-md bg-primary px-6 py-14 text-center text-primary-foreground md:px-12{% elif background == "card" %}rounded-md border border-primary/50 bg-card px-6 py-14 text-center text-card-foreground tty-frame md:px-12{% else %}flex flex-col items-center gap-8 rounded-md border border-primary/30 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="terminal-heading text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
{% if text %}<p class="mt-3 text-lg {% if background == "band" %}text-primary-foreground/80{% else %}text-muted-foreground{% endif %}">{{ 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="terminal-mono inline-flex items-center gap-2 rounded-md px-6 py-3 font-semibold uppercase tracking-wider transition-opacity hover:opacity-90 {% if background == "band" %}bg-background text-foreground{% else %}bg-primary text-primary-foreground{% endif %}">{{ 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="terminal-mono inline-flex items-center rounded-md border px-6 py-3 font-semibold uppercase tracking-wider transition-colors {% if background == "band" %}border-primary-foreground/40 text-primary-foreground hover:bg-primary-foreground/10{% else %}border-accent/60 text-accent hover:bg-accent/10{% endif %}">{{ secondaryLabel }}</a>{% endif %}
</div>
</div>
</div>
</section>

View File

@ -1,2 +0,0 @@
{# terminal divider — neon gradient rule / primary-tinted variants #}
{% with variant=variant|default:"line" weight=weight|default:"thin" width=width|default:"full" %}{% if variant == "ornament" %}<div class="bn-divider mx-auto my-8 flex items-center gap-4 {% if width == "narrow" %}max-w-[200px]{% elif width == "wide" %}max-w-3xl{% else %}w-full{% endif %}{% if class %} {{ class }}{% endif %}" role="separator"><span class="h-px flex-1 bg-gradient-to-r from-transparent via-primary to-transparent"></span><span class="shrink-0 text-primary">::{{ icon|default:"lucide:asterisk" }}:md text-primary::</span><span class="h-px flex-1 bg-gradient-to-l from-transparent via-primary to-transparent"></span></div>{% elif variant == "spacer-rule" %}<div class="bn-divider mx-auto py-10 {% if width == "narrow" %}max-w-[200px]{% elif width == "wide" %}max-w-3xl{% else %}w-full{% endif %}{% if class %} {{ class }}{% endif %}" role="separator"><hr class="mx-auto w-16 border-0 border-t {% if weight == "thick" %}border-t-4{% elif weight == "medium" %}border-t-2{% endif %} border-primary/60"></div>{% else %}<hr class="bn-divider mx-auto my-8 border-0 border-t {% if weight == "thick" %}border-t-4{% elif weight == "medium" %}border-t-2{% endif %} border-primary/60 {% if width == "narrow" %}max-w-[200px]{% elif width == "wide" %}max-w-3xl{% else %}w-full{% endif %}{% if class %} {{ class }}{% endif %}" role="separator">{% endif %}{% endwith %}

View File

@ -1,41 +0,0 @@
{# event-list terminal override: neon cards, mono datetime. All field reads + control flow preserved. #}
<section 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="terminal-heading text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mt-3 max-w-2xl text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
{% if layout == "cards" %}
<div class="mt-10 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{% for event in events %}{% if not (hidePast and event.past) %}
<article class="tty-corners flex flex-col overflow-hidden rounded-md border border-primary/40 bg-card text-card-foreground tty-frame{% if event.past %} opacity-60{% endif %}">
{% if event.media %}<div class="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-5">
<div class="terminal-mono text-sm font-medium uppercase tracking-wide text-primary"><time{% if event.datetime %} datetime="{{ event.datetime }}"{% endif %}>{{ event.start }}{% if event.end %} &ndash; {{ event.end }}{% endif %}</time>{% if event.timezone %} <span class="text-muted-foreground">{{ event.timezone }}</span>{% endif %}</div>
<h3 class="terminal-heading text-lg font-semibold">{{ event.title }}</h3>
{% if event.venue %}<p class="text-sm text-muted-foreground">{{ event.venue }}</p>{% endif %}
{% if event.description %}<p class="text-sm text-muted-foreground">{{ event.description }}</p>{% endif %}
{% if event.link %}<a href="{{ event.link }}" class="terminal-mono mt-auto inline-flex items-center gap-1 pt-2 text-sm font-medium uppercase tracking-wide text-primary hover:underline">{{ 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-10 divide-y divide-primary/30 border-y border-primary/30">
{% for event in events %}{% if not (hidePast and event.past) %}
<li class="flex flex-col gap-3 py-5 sm:flex-row sm:items-center sm:gap-6{% if event.past %} opacity-60{% endif %}">
<div class="shrink-0 sm:w-52">
<div class="terminal-mono text-sm font-semibold text-primary"><time{% if event.datetime %} datetime="{{ event.datetime }}"{% endif %}>{{ event.start }}</time></div>
{% if event.end %}<div class="terminal-mono text-xs text-muted-foreground">to {{ event.end }}</div>{% endif %}
{% if event.timezone %}<div class="terminal-mono text-xs text-muted-foreground">{{ event.timezone }}</div>{% endif %}
</div>
<div class="min-w-0 flex-1">
<h3 class="terminal-heading text-base font-semibold">{{ event.title }}</h3>
{% if event.venue %}<p class="text-sm text-muted-foreground">{{ event.venue }}</p>{% endif %}
{% if event.description %}<p class="mt-1 text-sm text-muted-foreground">{{ event.description }}</p>{% endif %}
</div>
{% if event.link %}<a href="{{ event.link }}" class="terminal-mono inline-flex shrink-0 items-center rounded-md border border-primary/50 px-3 py-1.5 text-sm font-medium uppercase tracking-wide text-primary hover:bg-primary/10">{{ event.linkLabel|default:"Details" }}</a>{% endif %}
</li>
{% endif %}{% endfor %}
</ul>
{% endif %}
</div>
</section>

View File

@ -1,18 +0,0 @@
{# faq terminal override: heading, neon-tinted dividers, display questions. Chevron svg, {% for %}, and answer prose preserved. #}
<section 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="terminal-heading text-center text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
<div class="mt-10 divide-y divide-primary/20 border-y border-primary/30">
{% 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">
<span class="terminal-heading">{{ 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">{{ item.answer|safe }}</div>
</details>
{% endfor %}
</div>
</div>
</section>

View File

@ -1,19 +0,0 @@
{# feature_grid terminal override: scanline hero overlay, mono eyebrow, heading, neon HUD cards. All field reads + control flow preserved. #}
<section class="relative overflow-hidden py-12 md:py-20 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div aria-hidden="true" class="tty-scanlines pointer-events-none absolute inset-0"></div>
<div class="relative z-10 mx-auto max-w-6xl px-4">
{% if eyebrow %}<p class="terminal-mono text-center text-xs font-semibold uppercase tracking-widest text-accent">{{ eyebrow }}</p>{% endif %}
{% if heading %}<h2 class="terminal-heading mt-2 text-center text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
<div class="mt-10 tty-grid-{% if columns == 2 %}2{% elif columns == 4 %}4{% else %}3{% endif %}">
{% for item in items %}
<div class="{% if layout == "card" %}tty-corners rounded-md border border-primary/40 bg-card p-6 text-card-foreground tty-frame transition-transform hover:-translate-y-0.5{% elif layout == "centered" %}text-center{% endif %}">
{% if item.icon %}<div class="mb-4 inline-flex h-11 w-11 items-center justify-center rounded-md 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="terminal-heading text-lg font-semibold">{% 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">{{ item.text }}</p>{% endif %}
{% if item.linkUrl and item.linkLabel %}<a href="{{ item.linkUrl }}" class="terminal-mono uppercase tracking-wide mt-3 inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline">{{ 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>

View File

@ -1,23 +0,0 @@
{# featured-posts (terminal override) — grid of cards from the `posts` provider #}
{# (params: limit, featured=true). Neon HUD card: image, title, excerpt, meta. #}
<section 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="terminal-heading mb-8 text-2xl font-semibold tracking-tight md:text-3xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
<div class="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 post in posts %}
<article class="tty-corners group flex flex-col overflow-hidden rounded-md border border-dashed border-primary/40 bg-card text-card-foreground tty-frame transition-transform hover:-translate-y-0.5">
{% 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-300 group-hover:scale-105" %}</a>{% endif %}
<div class="flex flex-1 flex-col p-5">
<h3 class="terminal-heading text-lg font-semibold leading-snug"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h3>
{% if showExcerpt and post.excerpt %}<p class="terminal-page mt-2 flex-1 text-sm text-muted-foreground">{{ post.excerpt }}</p>{% endif %}
<div class="mt-4 flex flex-wrap items-center gap-x-3 gap-y-1 terminal-mono text-xs uppercase tracking-wide text-accent">
{% if showAuthor and post.author_name %}<span class="font-medium text-foreground">{{ 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>

View File

@ -1,39 +0,0 @@
{# footer (WO-TF-006): layouts columns|minimal|centered; menu columns, legal bar, and social row from the `footer_menus` provider; newsletter section reuses the {% signup_form %} core (no duplicated form). Terminal override: mono column headers, neon top rule, display wordmark. #}
{% set fl = layout %}
{% if not fl %}{% if template == "centered" %}{% set fl = "centered" %}{% else %}{% set fl = "columns" %}{% endif %}{% endif %}
{% set fbg = backgroundColor|default:"bg-muted/50" %}
{% macro sociallinks(cls) %}{% if social_items %}<div class="flex items-center gap-3 {{ cls }}">{% for s in social_items %}<a href="{{ s.url }}" target="_blank" rel="noopener" title="{{ s.label }}" class="text-muted-foreground 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="terminal-mono uppercase tracking-widest text-xs font-semibold text-accent">{{ newsletterTitle|default:"Stay in the loop" }}</h3>{% if newsletterDescription %}<p class="mt-1 text-sm text-muted-foreground">{{ 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-primary/30 flex flex-col sm:flex-row items-center justify-between gap-3 text-sm text-muted-foreground"><p class="terminal-mono">{% if copyright %}&copy; {{ copyright }}{% else %}&copy; {{ 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 class="w-full border-t border-primary/30 {{ 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="terminal-heading text-lg font-bold text-foreground">{{ companyName }}</span>{% endif %}{% if tagline %}<span class="text-sm text-muted-foreground 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="terminal-heading text-xl font-bold text-foreground">{{ companyName }}</span>{% endif %}
{% if tagline %}<p class="max-w-md text-sm text-muted-foreground">{{ 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="terminal-mono uppercase tracking-wide text-xs text-muted-foreground 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="terminal-heading text-xl font-bold text-foreground">{{ companyName }}</span>{% endif %}
{% if tagline %}<p class="mt-3 max-w-xs text-sm text-muted-foreground">{{ tagline }}</p>{% endif %}
{% if address or email or phone %}<address class="mt-4 not-italic text-sm text-muted-foreground 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="terminal-mono uppercase tracking-widest text-xs font-semibold text-accent">{{ 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-muted-foreground 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>

View File

@ -1,52 +0,0 @@
{# gallery terminal override: bordered TTY tiles. Lightbox script + data-bn-* attributes + {% img %} preserved byte-for-byte. #}
{% with layout=layout|default:"grid" gap=gap|default:"md" cols=columns|default:3 %}<div class="bn-gallery my-6{% 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="group relative block overflow-hidden rounded-md border border-primary/30 bg-muted tty-frame 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 %}

View File

@ -1,7 +1 @@
{# terminal heading — terminal-heading everywhere; on h1/h2 only #}
{% if level == 1 %}<h1 class="text-4xl font-bold tracking-tight terminal-heading {{ textClass }} flex-1">{{ text }}</h1>
{% elif level == 3 %}<h3 class="text-2xl font-semibold terminal-heading {{ textClass }} flex-1">{{ text }}</h3>
{% elif level == 4 %}<h4 class="text-xl font-semibold terminal-heading {{ textClass }} flex-1">{{ text }}</h4>
{% elif level == 5 %}<h5 class="text-lg font-medium terminal-heading {{ textClass }} flex-1">{{ text }}</h5>
{% elif level == 6 %}<h6 class="text-base font-medium terminal-heading {{ textClass }} flex-1">{{ text }}</h6>
{% else %}<h2 class="text-3xl font-semibold tracking-tight terminal-heading {{ textClass }} flex-1">{{ text }}</h2>{% endif %}
{% set lvl = level|default:2|integer %}{% if lvl < 1 or lvl > 6 %}{% set lvl = 2 %}{% endif %}{% set t = text|upper %}{% if lvl == 1 %}<h1 class="terminal-heading terminal-heading-1 text-3xl font-bold {{ textClass }}" style="text-transform: uppercase;"># {{ t }}</h1>{% elif lvl == 3 %}<h3 class="terminal-heading terminal-heading-3 text-xl font-semibold {{ textClass }}" style="text-transform: uppercase;">### {{ t }}</h3>{% elif lvl == 4 %}<h4 class="terminal-heading terminal-heading-4 text-lg font-semibold {{ textClass }}" style="text-transform: uppercase;">#### {{ t }}</h4>{% elif lvl == 5 %}<h5 class="terminal-heading terminal-heading-5 text-base font-medium {{ textClass }}" style="text-transform: uppercase;">##### {{ t }}</h5>{% elif lvl == 6 %}<h6 class="terminal-heading terminal-heading-6 text-base font-medium {{ textClass }}" style="text-transform: uppercase;">###### {{ t }}</h6>{% else %}<h2 class="terminal-heading terminal-heading-2 text-2xl font-bold {{ textClass }}" style="text-transform: uppercase;">## {{ t }}</h2>{% endif %}

View File

@ -1,95 +0,0 @@
{# hero terminal override (GO-BIG showpiece): canvas ASCII glyph-rain background, TTY prompt eyebrow, scanline overlay. All builtin field reads + macro + branch structure preserved from the builtin default. The rain shows when there is no backgroundImage; it lazy-inits on view, honors prefers-reduced-motion, degrades to a static phosphor gradient with JS off, and is dependency-free. #}
{% set eyebrowText = eyebrow|get:"text"|default:eyebrow %}
{% set headlineText = headline|get:"text"|default:headline %}
{% set subText = subheadline|get:"text"|default:subheadline %}
{% set descText = description|get:"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 %}<span class="terminal-eyebrow inline-block mb-4 border border-border bg-muted/40 px-3 py-1">$ {{ eyebrowText }} <span aria-hidden="true" class="caret-blink">_</span></span>{% endif %}
{% if headlineText %}<h1 class="terminal-heading text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight text-foreground">{{ headlineText }}</h1>{% endif %}
{% if subText %}<h2 class="mt-4 text-xl sm:text-2xl text-muted-foreground max-w-2xl{% if align == "center" %} mx-auto{% endif %}">{{ subText }}</h2>{% endif %}
{% if descText %}<p class="mt-4 max-w-xl leading-relaxed text-muted-foreground{% if align == "center" %} mx-auto{% endif %}">{{ 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="terminal-mono inline-flex items-center justify-center gap-2 border border-primary bg-primary px-6 py-3 font-semibold uppercase tracking-wider text-primary-foreground transition-colors hover:bg-transparent hover:text-primary">{{ pText }}</a>{% endif %}
{% if sText and sUrl %}<a href="{{ sUrl }}" class="terminal-mono inline-flex items-center justify-center border border-border px-6 py-3 font-semibold uppercase tracking-wider text-foreground transition-colors hover:border-primary hover:text-primary">{{ sText }}</a>{% endif %}
</div>{% endif %}
{% if trustLogos %}<div class="mt-10">
<p class="terminal-mono text-xs uppercase tracking-widest text-muted-foreground mb-3">Trusted by</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 class="hero grid grid-cols-1 lg:grid-cols-2 overflow-hidden bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="relative tty-hero-media bg-muted {% 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" %}{% else %}<div class="tty-rain-wrap"><div class="tty-rain-fallback"></div><canvas class="tty-rain-canvas" aria-hidden="true"></canvas></div>{% endif %}<div aria-hidden="true" class="tty-scanlines pointer-events-none absolute inset-0"></div></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="max-w-lg text-left">{{ herocontent("left") }}</div></div>
</section>
{% else %}
{% set align = "center" %}
{% if variant == "left-aligned" %}{% set align = "left" %}{% endif %}
<section class="hero tty-hero flex flex-col overflow-hidden bg-background text-foreground {% if minHeight == "screen" %}tty-hero-screen{% elif minHeight == "50vh" %}tty-hero-half{% elif minHeight == "auto" %}tty-hero-auto{% else %}tty-hero-tall{% 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>{% else %}<div class="tty-rain-wrap z-0"><div class="tty-rain-fallback"></div><canvas class="tty-rain-canvas" aria-hidden="true"></canvas></div>{% endif %}
<div aria-hidden="true" class="tty-scanlines pointer-events-none absolute inset-0 z-0"></div>
<div class="relative z-10 mx-auto w-full max-w-4xl px-4 sm:px-6 lg:px-8 {% if variant == "left-aligned" %}text-left{% else %}text-center{% endif %}">{{ herocontent(align) }}</div>
</section>
{% endif %}
<script>
(function(){
if(window.__terminalRain)return;window.__terminalRain=1;
var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function initCanvas(cv){
if(cv.__init)return;cv.__init=1;
var ctx=cv.getContext&&cv.getContext('2d');if(!ctx)return;
var wrap=cv.parentElement;
var cs=getComputedStyle(document.documentElement);
function raw(n){return cs.getPropertyValue(n).trim();}
var fg=raw('--foreground');
var col1='hsl('+(raw('--primary')||fg)+')';
var col2='hsl('+(raw('--accent')||fg)+')';
var fade='hsl('+(raw('--background')||fg)+' / 0.12)';
var glyphs='01<>[]{}#$%&*+=/|_~^:;.,abcdefghijklmnopqrstuvwxyz'.split('');
var fontSize=14,cols=0,drops=[],W=0,H=0,dpr=1;
function resize(){
dpr=Math.min(window.devicePixelRatio||1,2);
W=wrap.clientWidth;H=wrap.clientHeight;
if(W===0||H===0)return;
cv.width=W*dpr;cv.height=H*dpr;ctx.setTransform(dpr,0,0,dpr,0,0);
cols=Math.max(1,Math.floor(W/fontSize));
drops=[];for(var i=0;i<cols;i++)drops[i]=Math.random()*-50;
}
resize();
var last=0,fps=1000/18;
function frame(t){
requestAnimationFrame(frame);
if(t-last<fps)return;last=t;
if(W===0||H===0){resize();return;}
ctx.fillStyle=fade;ctx.fillRect(0,0,W,H);
ctx.font=fontSize+'px monospace';
for(var i=0;i<cols;i++){
var ch=glyphs[Math.floor(Math.random()*glyphs.length)];
var x=i*fontSize,y=drops[i]*fontSize;
ctx.fillStyle=Math.random()<0.85?col1:col2;
ctx.fillText(ch,x,y);
if(y>H&&Math.random()>0.975)drops[i]=0;
drops[i]++;
}
}
if(window.ResizeObserver){var ro=new ResizeObserver(resize);ro.observe(wrap);}else{window.addEventListener('resize',resize);}
requestAnimationFrame(frame);
}
function boot(){
if(reduce)return;
var cvs=document.querySelectorAll('.tty-rain-canvas');
if(!cvs.length)return;
if(!('IntersectionObserver' in window)){cvs.forEach(initCanvas);return;}
var io=new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting){initCanvas(e.target);io.unobserve(e.target);}});},{threshold:0.05});
cvs.forEach(function(c){io.observe(c);});
}
if(document.readyState!='loading')boot();else document.addEventListener('DOMContentLoaded',boot);
})();
</script>

View File

@ -1,31 +0,0 @@
{# hours-location terminal override: mono readout table, neon map panel. Map lazy-load button + inline handler preserved. #}
<section 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="terminal-heading text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
<div class="mt-10 grid grid-cols-1 gap-10 md:grid-cols-2">
<div>
{% if hours %}
<table class="terminal-mono w-full text-sm">
<tbody class="divide-y divide-primary/30">
{% for row in hours %}
<tr>
<th scope="row" class="py-2 pr-4 text-left font-medium uppercase tracking-wide">{{ row.day }}</th>
<td class="py-2 text-right {% if row.closed %}text-muted-foreground{% else %}text-primary{% endif %} tabular-nums">{% if row.closed %}Closed{% else %}{{ row.hours }}{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if specialHoursNote %}<p class="mt-4 rounded-md border border-primary/40 bg-muted px-3 py-2 text-sm text-muted-foreground">{{ specialHoursNote }}</p>{% endif %}
{% if address %}<div class="mt-6"><div class="terminal-mono text-sm font-medium uppercase tracking-wide text-accent">Address</div><address class="mt-1 whitespace-pre-line not-italic">{{ address }}</address></div>{% endif %}
{% if phone %}<div class="mt-4"><div class="terminal-mono text-sm font-medium uppercase tracking-wide text-accent">Phone</div><a href="tel:{{ phone }}" class="terminal-mono mt-1 inline-block font-medium transition-colors hover:text-primary">{{ phone }}</a></div>{% endif %}
{% if directionsUrl %}<a href="{{ directionsUrl }}" target="_blank" rel="noopener noreferrer" class="terminal-mono mt-6 inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium uppercase tracking-wide text-primary-foreground transition-colors hover:bg-primary/90"><svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#navigation"></use></svg>Get directions</a>{% endif %}
</div>
{% if mapEmbedUrl %}
<div class="tty-corners aspect-square w-full overflow-hidden rounded-md border border-primary/40 bg-muted tty-frame 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="terminal-mono flex h-full min-h-[16rem] w-full items-center justify-center gap-2 text-sm font-medium uppercase tracking-wide transition-colors hover:bg-accent/20 hover:text-foreground"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/lucide.svg#map-pin"></use></svg>{{ mapButtonLabel|default:"Load map" }}</button>
</div>
{% endif %}
</div>
</div>
</section>

View File

@ -1,2 +1 @@
{# terminal image — bordered ASCII frame, mono caption; all size/fit branches preserved #}
{% with size=size|default:"full" align=align|default:"center" aspectRatio=aspectRatio|default:"auto" objectFit=objectFit|default:"cover" rounded=rounded|default:"lg" shadow=shadow|default:"none" %}<figure class="flex-1 {% if class %}{{ class }} {% endif %}{% if align == "left" %}flex justify-start{% elif align == "right" %}flex justify-end{% else %}flex justify-center{% endif %}"><div class="relative overflow-hidden tty-frame {% 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 %}{% if aspectRatio == "square" %} aspect-square{% elif aspectRatio == "video" %} aspect-video{% elif aspectRatio == "portrait" %} aspect-[3/4]{% elif aspectRatio == "wide" %} aspect-[21/9]{% endif %}{% if rounded == "sm" %} rounded-sm{% elif rounded == "md" %} rounded-md{% elif rounded == "lg" %} rounded-lg{% elif rounded == "xl" %} rounded-xl{% elif rounded == "full" %} rounded-full{% endif %}{% if shadow == "sm" %} shadow-sm{% elif shadow == "md" %} shadow-md{% elif shadow == "lg" %} shadow-lg{% elif shadow == "xl" %} shadow-xl{% 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>{% if caption %}<figcaption class="mt-2 text-sm text-muted-foreground text-center terminal-mono">{{ caption }}</figcaption>{% endif %}</figure>{% endwith %}
<figure class="ascii-frame">{% if src %}<img src="{{ src }}" alt="{{ alt|default:caption }}">{% else %}<div class="terminal-mono" style="padding: 1rem;">[ no image ]</div>{% endif %}<figcaption>[fig.{% if caption %} {{ caption }}{% endif %}]</figcaption></figure>

View File

@ -1,16 +0,0 @@
{# logo_strip terminal override: mono kicker. Marquee classes, {% img %}, and keyframes <style> preserved byte-for-byte. #}
<section 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 %}<p class="terminal-mono text-center text-sm font-semibold uppercase tracking-widest text-accent">{{ heading }}</p>{% 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 %}

View File

@ -1,30 +0,0 @@
{# menu-list terminal override: mono price readouts, neon section rules. All field reads + control flow preserved. #}
<section 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="terminal-heading text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mt-3 max-w-2xl text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
<div class="mt-10 grid grid-cols-1 gap-x-12 gap-y-10 {% if columns == 2 %}md:grid-cols-2{% endif %}">
{% for section in sections %}
<div class="menu-section">
{% if section.title %}<h3 class="terminal-heading border-b border-primary/40 pb-2 text-xl font-semibold">{{ section.title }}</h3>{% endif %}
{% if section.note %}<p class="mt-2 text-sm text-muted-foreground">{{ section.note }}</p>{% endif %}
<ul class="mt-4 space-y-5">
{% for item in section.items %}
<li class="flex items-baseline gap-4">
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
<span class="font-medium">{{ item.name }}</span>
{% for badge in item.badges %}{% if badge.label %}<span class="terminal-mono inline-block rounded border border-accent/50 px-2 py-0.5 text-xs uppercase tracking-wide text-accent">{{ badge.label }}</span>{% endif %}{% endfor %}
</div>
{% if item.description %}<p class="mt-0.5 text-sm text-muted-foreground">{{ item.description }}</p>{% endif %}
</div>
{% if item.price %}<span class="terminal-mono shrink-0 font-medium tabular-nums text-primary">{% if currency %}{{ currency }}{% endif %}{{ item.price }}</span>{% endif %}
</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
{% if footnote %}<p class="terminal-mono mt-10 text-xs text-muted-foreground">{{ footnote }}</p>{% endif %}
</div>
</section>

View File

@ -1,104 +0,0 @@
{# navbar (WO-TF-005): variants standard|centered-logo|split|transparent-over-hero; sticky none|sticky|shrink-on-scroll; dependency-free off-canvas drawer (pure-CSS baseline + progressive-JS focus-trap/Escape/scroll-lock); menu data from the `menus` provider. Terminal override: mono nav links, neon dropdown panels, CTA — drawer ids/style/script/data-bn-* preserved byte-for-byte. #}
{% 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="terminal-mono uppercase tracking-wider px-3 py-2 text-xs font-medium 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="terminal-mono uppercase tracking-wider px-3 py-2 text-xs font-medium 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="p-6 bg-card border border-primary/40 tty-frame rounded-md shadow-xl 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="terminal-mono px-2 py-1 text-xs font-semibold text-accent uppercase tracking-widest border-b border-primary/40 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 rounded 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="py-2 bg-card border border-primary/40 tty-frame rounded-md shadow-lg">{% for child in item.children %}{% if child.item_type == "header" %}<div class="terminal-mono px-4 py-2 text-xs font-semibold text-accent uppercase tracking-widest">{{ 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="terminal-heading text-xl font-bold text-foreground {{ logoText.color }}">{{ 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="terminal-heading text-xl font-bold text-foreground">{{ companyName }}</span>{% endif %}</a>{% endmacro %}
{% macro ctabtn(extra) %}{% if cta_text %}<a href="{{ cta_url|default:'#' }}" class="terminal-mono uppercase tracking-wider inline-flex items-center rounded-none bg-primary px-4 py-2 text-xs font-semibold text-primary-foreground hover:bg-primary/90 transition-colors {{ extra }}">{{ cta_text }}</a>{% endif %}{% endmacro %}
<nav 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 }}{% if borderBottom %} border-b border-primary/30{% endif %}{% endif %} {{ class }}">
{% if showUtilityBar %}<div class="terminal-mono w-full text-xs {% if not showOnMobile %}hidden sm:block{% endif %} bg-muted/60 text-muted-foreground border-b border-primary/20"><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" role="dialog" aria-modal="true" aria-label="Site menu">
<div class="flex items-center justify-between p-4 border-b border-primary/30">{{ 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="terminal-mono uppercase tracking-wide flex items-center justify-between px-6 py-3 text-sm font-medium 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="terminal-mono uppercase tracking-wide block px-6 py-3 text-sm font-medium hover:bg-accent transition-colors">{{ item.label }}</a>{% endif %}{% endfor %}</nav>
{% if cta_text %}<div class="p-4 border-t border-primary/30"><a href="{{ cta_url|default:'#' }}" class="terminal-mono uppercase tracking-wider block w-full text-center rounded-none bg-primary px-4 py-3 text-base font-semibold text-primary-foreground hover:bg-primary/90 transition-colors">{{ 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(--border));}
</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>

View File

@ -1,12 +0,0 @@
{# newsletter-signup (terminal override) — the {% signup_form %} tag carries the #}
{# untouchable subscribe wiring (/api/lists/subscribe HTMX post + honeypot); #}
{# this template supplies only the terminal chrome around it. #}
<section 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="tty-corners rounded-md {% if layout == "inline" %}bg-primary px-6 py-10 text-primary-foreground tty-frame md:px-12{% else %}border border-dashed border-primary/40 bg-card px-6 py-10 text-card-foreground tty-frame md:px-10{% endif %} text-center">
{% if title %}<h2 class="terminal-heading text-2xl font-semibold tracking-tight md:text-3xl" data-text="{{ title }}">{{ title }}</h2>{% endif %}
{% if description %}<p class="terminal-page mx-auto mt-3 max-w-xl {% if layout == "inline" %}text-primary-foreground/80{% else %}text-muted-foreground{% endif %}">{{ 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>

View File

@ -1,13 +0,0 @@
{# page-suggestions terminal override — themed 404. NOTE: the builtin builds its "did you mean" list from a Postgres full-text query (SearchSimilarPages), which a codeless template cannot run — so this override renders the on-theme 404 console (title / message / requested path / home CTA) without the dynamic suggestion list. Recorded as a known deviation in WO-TF-015. Content fields honored: title, emptyMessage. #}
<section class="relative overflow-hidden bg-background text-foreground py-20 md:py-28{% if class %} {{ class }}{% endif %}">
<div aria-hidden="true" class="tty-scanlines pointer-events-none absolute inset-0"></div>
<div class="relative z-10 mx-auto max-w-2xl px-4 text-center">
<p class="terminal-mono text-sm font-semibold uppercase tracking-[0.3em] text-accent">Error 404 <span aria-hidden="true" class="caret-blink">_</span></p>
<h1 class="terminal-heading mt-3 text-4xl font-bold tracking-tight md:text-5xl" data-text="{{ title|default:'Signal lost' }}">{{ title|default:"Signal lost" }}</h1>
<p class="mt-4 text-lg text-muted-foreground">{{ emptyMessage|default:"That route returned nothing. The page moved, shipped, or never existed." }}</p>
{% if context.path %}<p class="terminal-mono mt-3 text-sm text-muted-foreground">requested: <span class="text-primary">{{ context.path }}</span></p>{% endif %}
<div class="mt-8 flex flex-wrap items-center justify-center gap-3">
<a href="/" class="terminal-mono inline-flex items-center gap-2 rounded-md bg-primary px-6 py-3 font-semibold uppercase tracking-wider text-primary-foreground transition-opacity hover:opacity-90"><svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg>Back to home</a>
</div>
</div>
</section>

View File

@ -1,13 +0,0 @@
{# password-reset-form terminal override — reproduces the builtin's hardcoded wiring: hx-post /api/auth/request-password-reset (target #reset-message), back-to-login link /login. Only the chrome is themed. #}
<div class="password-reset-form tty-corners mx-auto max-w-md rounded-md border border-primary/40 bg-card p-6 text-card-foreground tty-frame">
<div id="reset-message"></div>
<div id="request-reset-panel">
<h2 class="terminal-heading text-2xl font-bold mb-2" data-text="Reset Password">Reset Password</h2>
<p class="text-muted-foreground mb-4">Enter your email address and we will send you a link to reset your password.</p>
<form hx-post="/api/auth/request-password-reset" hx-target="#reset-message" hx-swap="innerHTML">
<div class="mb-4"><label class="terminal-mono mb-1 block text-xs font-medium uppercase tracking-wide text-accent">Email</label><input type="email" name="email" required class="w-full rounded-md border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"/></div>
<button type="submit" class="terminal-mono w-full rounded-md bg-primary px-4 py-2.5 font-semibold uppercase tracking-wider text-primary-foreground hover:bg-primary/90" hx-disabled-elt="this">Send Reset Link</button>
</form>
<p class="mt-4 text-center text-sm"><a href="/login" class="terminal-mono uppercase tracking-wide text-primary hover:underline">Back to login</a></p>
</div>
</div>

View File

@ -1,16 +0,0 @@
{# popular-posts (terminal override) — compact ranked list from the `posts` provider. #}
<section class="bg-background text-foreground{% if class %} {{ class }}{% endif %}">
{% if heading %}<h2 class="terminal-eyebrow terminal-mono mb-4 text-xs font-semibold uppercase tracking-widest text-accent">{{ heading }}</h2>{% endif %}
<ol class="space-y-4">
{% for post in posts %}
<li class="flex items-start gap-3">
{% if showRank %}<span class="terminal-mono mt-0.5 text-lg font-bold tabular-nums text-primary" style="text-shadow: 0 0 8px hsl(var(--primary));">{{ forloop.Counter }}</span>{% endif %}
{% if showThumbnail and post.featured_image_url %}<a href="{{ post.url }}" class="h-12 w-12 flex-shrink-0 overflow-hidden rounded-md border border-primary/40 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="text-sm font-medium leading-snug"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h3>
{% if post.published_at %}<p class="terminal-mono mt-0.5 text-xs uppercase tracking-wide text-accent">{{ post.published_at|date:"M j, Y" }}</p>{% endif %}
</div>
</li>
{% endfor %}
</ol>
</section>

View File

@ -1,23 +0,0 @@
{# post-hero (terminal override) — 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 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/60"></div></div>
<div class="tty-scanlines pointer-events-none absolute inset-0 z-[1]"></div>
<div class="relative z-10 mx-auto w-full max-w-4xl px-4 py-12 sm:px-6 lg:px-8 {% if center %}text-center{% endif %}">
<h1 class="terminal-heading text-3xl font-bold leading-tight text-white sm:text-4xl lg:text-5xl" data-text="{{ post.title }}">{{ post.title }}</h1>
{% if showExcerpt and post.excerpt %}<p class="terminal-page mt-4 max-w-2xl text-lg text-white/80{% if center %} mx-auto{% endif %}">{{ post.excerpt }}</p>{% endif %}
{% if showMeta %}<div class="mt-6 flex flex-wrap items-center gap-x-3 gap-y-1 terminal-mono text-sm uppercase tracking-wide text-white/80{% if center %} justify-center{% endif %}">{% if post.author_name %}<span class="font-medium 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 class="relative overflow-hidden bg-background py-14 text-foreground md:py-20{% if class %} {{ class }}{% endif %}">
<div class="tty-scanlines pointer-events-none absolute inset-0"></div>
<div class="relative z-10 mx-auto w-full max-w-3xl px-4 {% if center %}text-center{% endif %}">
<h1 class="terminal-heading text-3xl font-bold leading-tight tracking-tight sm:text-4xl lg:text-5xl" data-text="{{ post.title }}">{{ post.title }}</h1>
{% if showExcerpt and post.excerpt %}<p class="terminal-page mt-4 text-lg text-muted-foreground{% if center %} mx-auto max-w-2xl{% endif %}">{{ post.excerpt }}</p>{% endif %}
{% if showMeta %}<div class="mt-6 flex flex-wrap items-center gap-x-3 gap-y-1 terminal-mono text-sm uppercase tracking-wide text-accent{% if center %} justify-center{% endif %}">{% if post.author_name %}<span class="font-medium text-foreground">{{ 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 %}

View File

@ -1,9 +0,0 @@
{# post-metadata (terminal override) — author/date/reading-time row from the `post` provider. #}
{% if post %}
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 terminal-mono text-sm uppercase tracking-wide text-accent{% 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 border border-primary/40 object-cover" %}{% endif %}<span class="font-medium text-foreground">{{ 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="border border-primary/40 bg-card px-2 py-0.5 text-xs text-card-foreground transition-colors hover:text-primary">{{ cat.name }}</a>{% endfor %}</span>{% endif %}
</div>
{% endif %}

View File

@ -1,26 +0,0 @@
{# pricing terminal override: scanline overlay, heading, HUD neon tier panels (magenta highlight / cyan standard), CTAs. Field reads + control flow preserved. #}
<section class="relative overflow-hidden py-12 md:py-20 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div aria-hidden="true" class="tty-scanlines pointer-events-none absolute inset-0"></div>
<div class="relative z-10 mx-auto max-w-6xl px-4">
{% if heading %}<h2 class="terminal-heading text-center text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
<div class="mt-10 tty-grid-3">
{% for tier in tiers %}
<div class="tty-corners relative flex flex-col rounded-md border p-6 {% if tier.highlighted %}border-primary bg-card text-card-foreground tty-frame ring-1 ring-primary{% else %}border-primary/40 bg-card text-card-foreground tty-frame{% endif %}">
{% if tier.badge %}<span class="terminal-mono absolute -top-3 left-1/2 -translate-x-1/2 rounded-none bg-primary px-3 py-1 text-xs font-semibold uppercase tracking-widest text-primary-foreground">{{ tier.badge }}</span>{% endif %}
<h3 class="terminal-heading text-lg font-semibold">{{ tier.name }}</h3>
<p class="mt-4 flex items-baseline gap-1">
<span class="terminal-heading text-4xl font-bold tracking-tight" style="font-family:var(--font-heading)">{% if currency %}{{ currency }}{% else %}${% endif %}{{ tier.price }}</span>
{% if tier.period %}<span class="terminal-mono text-sm text-muted-foreground">{{ tier.period }}</span>{% endif %}
</p>
{% if tier.description %}<p class="mt-3 text-sm text-muted-foreground">{{ 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 %}<a href="{{ tier.ctaUrl }}" class="terminal-mono uppercase tracking-wider mt-8 inline-flex w-full items-center justify-center rounded-none px-5 py-3 font-semibold transition-opacity hover:opacity-90 {% if tier.highlighted %}bg-primary text-primary-foreground{% else %}border border-primary/40 bg-background text-foreground hover:bg-muted{% endif %}">{{ tier.ctaLabel }}</a>{% endif %}
</div>
{% endfor %}
</div>
{% if billingNote %}<p class="terminal-mono mt-8 text-center text-sm text-muted-foreground">{{ billingNote }}</p>{% endif %}
</div>
</section>

View File

@ -1,2 +0,0 @@
{# terminal quote — dashed neon left border; terminal-heading for pull; mono figcaption #}
{% with style=style|default:"block" align=align|default:"left" %}<figure class="bn-quote my-6 {% if style == "inline-card" %}rounded-md border border-dashed border-primary/40 bg-card p-6 md:p-8 tty-frame{% elif style == "pull" %}border-l-4 border-dashed border-primary pl-6 md:pl-8 py-2{% else %}border-l-2 border-dashed border-primary/50 pl-5 py-1{% endif %}{% if align == "center" %} text-center{% endif %}{% if class %} {{ class }}{% endif %}"><blockquote{% if cite %} cite="{{ cite }}"{% endif %} class="{% if style == "pull" %}text-2xl md:text-3xl font-semibold leading-snug tracking-tight terminal-heading{% else %}text-lg md:text-xl leading-relaxed{% endif %} text-foreground">{{ quote|safe }}</blockquote>{% if attribution or role or cite %}<figcaption class="mt-4 text-sm text-muted-foreground terminal-mono{% if align == "center" %} flex flex-col items-center{% endif %}">{% if attribution %}<span class="font-medium text-foreground">{{ attribution }}</span>{% endif %}{% if role %}<span class="{% if attribution %}block {% endif %}text-muted-foreground">{{ role }}</span>{% endif %}{% if cite %}<a href="{{ cite }}" class="{% if attribution or role %}mt-1 {% endif %}inline-block underline underline-offset-2 hover:text-foreground">Source</a>{% endif %}</figcaption>{% endif %}</figure>{% endwith %}

View File

@ -1,17 +0,0 @@
{# related-posts (terminal override) — grid from the `posts` provider. #}
{% if posts %}
<section class="border-t border-primary/30 py-12 md:py-16 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-6xl px-4">
<h2 class="terminal-heading mb-8 text-2xl font-semibold tracking-tight" data-text="{{ heading|default:'Related posts' }}">{{ heading|default:"Related posts" }}</h2>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 {% if columns == 2 %}lg:grid-cols-2{% else %}lg:grid-cols-3{% endif %}">
{% for post in posts %}
<article class="tty-corners group flex flex-col rounded-md border border-dashed border-primary/40 bg-card p-4 text-card-foreground tty-frame transition-transform hover:-translate-y-0.5">
{% if post.featured_image_url %}<a href="{{ post.url }}" class="mb-3 block aspect-video overflow-hidden rounded-md bg-muted">{% img post.featured_image_url alt=post.title class="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105" %}</a>{% endif %}
<h3 class="terminal-heading text-base font-semibold leading-snug"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h3>
{% if post.published_at %}<p class="terminal-mono mt-1 text-xs uppercase tracking-wide text-accent">{{ post.published_at|date:"M j, Y" }}</p>{% endif %}
</article>
{% endfor %}
</div>
</div>
</section>
{% endif %}

View File

@ -1,14 +0,0 @@
{# rich_section terminal override: mono eyebrow, heading, HUD-framed media, CTA. Field reads + {% img %} preserved. #}
<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 %}<p class="terminal-mono text-xs font-semibold uppercase tracking-widest text-accent">{{ eyebrow }}</p>{% endif %}
{% if heading %}<h2 class="terminal-heading mt-2 text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ 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">{{ body|safe }}</div>{% endif %}
{% if ctaUrl and ctaLabel %}<a href="{{ ctaUrl }}" class="terminal-mono uppercase tracking-wider mt-6 inline-flex items-center gap-2 rounded-none bg-primary px-5 py-3 font-semibold text-primary-foreground transition-opacity hover:opacity-90">{{ 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="tty-corners overflow-hidden rounded-md border border-dashed border-primary/50 tty-frame">{% img media alt=mediaAlt class="w-full h-auto object-cover" %}</div>{% endif %}
</div>
</div>
</section>

View File

@ -1,2 +0,0 @@
{# terminal rich-text — body font via terminal-prose #}
{% if html %}<article class="rich-text prose prose-lg max-w-none terminal-prose terminal-page">{{ html|safe }}</article>{% endif %}

View File

@ -1,15 +0,0 @@
{# sidebar-nav (terminal override) — 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 class="flex-1 {% if compact %}w-56 py-4 pr-3{% else %}w-64 py-6 pr-4{% endif %}{% if showBorder %} border-r border-primary/30{% endif %}{% if class %} {{ class }}{% endif %}">
{% if title %}<h3 class="terminal-eyebrow terminal-mono {% if compact %}px-2 mb-3 text-xs uppercase tracking-widest text-accent{% else %}px-3 mb-4 text-sm uppercase tracking-widest text-accent{% endif %} font-semibold">{{ 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="flex items-center rounded-md terminal-mono text-sm uppercase tracking-wide 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" 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 %}

View File

@ -1,14 +0,0 @@
{# terminal social-links — mono label, sharp neon-bordered chips; icon sprites preserved #}
<div class="social-links{% if class %} {{ class }}{% endif %}">
{% if heading %}<p class="mb-3 text-sm font-medium text-muted-foreground terminal-mono uppercase tracking-widest">{{ 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="inline-flex items-center gap-2 rounded-none border border-primary/40 text-foreground transition-colors hover:bg-accent hover:text-accent-foreground {% if style == "stack" %}w-full px-4 py-2{% else %}p-2.5{% endif %}">
{% if link.network %}<svg class="h-5 w-5" 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="text-sm font-medium terminal-mono">{{ link.label }}</span>{% endif %}
</a>
</li>
{% endif %}{% endfor %}
</ul>
</div>

View File

@ -1,50 +0,0 @@
{# stats terminal override: scanline overlay, heading, HUD stat tiles, mono labels. data-stats + countUp script/attrs preserved byte-for-byte. #}
<section class="relative overflow-hidden py-12 md:py-20 bg-background text-foreground{% if class %} {{ class }}{% endif %}"{% if countUp %} data-stats{% endif %}>
<div aria-hidden="true" class="tty-scanlines pointer-events-none absolute inset-0"></div>
<div class="relative z-10 mx-auto max-w-6xl px-4">
{% if heading %}<h2 class="terminal-heading text-center text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
<dl class="mt-10 tty-grid-stats{% if columns == 2 %}2{% elif columns == 3 %}3{% else %}4{% endif %}">
{% for item in items %}
<div class="tty-corners text-center">
<dd class="terminal-heading text-4xl font-bold tracking-tight 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="terminal-mono mt-2 text-sm font-medium uppercase tracking-widest 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 %}

View File

@ -1,22 +0,0 @@
{# team terminal override: heading, dashed-neon HUD member panels (lime), mono roles. {% img %} + social icon svgs + control flow preserved. #}
<section 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="terminal-heading text-center text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ 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" %}tty-corners flex items-start gap-5 rounded-md border border-dashed border-primary/50 bg-card p-5 text-card-foreground tty-frame{% 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 rounded-full 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="terminal-heading text-lg font-semibold">{{ member.name }}</h3>{% endif %}
{% if member.role %}<p class="terminal-mono uppercase tracking-wide text-sm font-medium text-primary">{{ member.role }}</p>{% endif %}
{% if member.bio %}<p class="mt-2 text-sm text-muted-foreground">{{ 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>

View File

@ -1,27 +0,0 @@
{# testimonial terminal override: heading, dashed-neon HUD quote cards. Rating svgs, {% img %}, and control flow preserved. #}
<section 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="terminal-heading text-center text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ 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="tty-corners flex flex-col rounded-md border border-dashed border-primary/50 bg-card p-6 text-card-foreground tty-frame{% if layout == "carousel" %} min-w-[18rem] max-w-sm shrink-0 snap-start{% endif %}">
{% if q.rating %}<div class="mb-3 flex 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-lg leading-relaxed">{% if q.quote %}&ldquo;{{ q.quote }}&rdquo;{% endif %}</blockquote>
<figcaption class="mt-5 flex items-center gap-3">
{% if q.avatar %}<span class="h-10 w-10 shrink-0 overflow-hidden rounded-full bg-muted">{% img q.avatar alt=q.name class="h-full w-full object-cover" %}</span>{% endif %}
<span>
{% if q.name %}<span class="terminal-heading block font-semibold">{{ q.name }}</span>{% endif %}
{% if q.role %}<span class="terminal-mono uppercase tracking-wide block text-sm text-muted-foreground">{{ q.role }}</span>{% endif %}
</span>
</figcaption>
</figure>
{% endfor %}
</div>
</div>
</section>

View File

@ -1,2 +1 @@
{# terminal text — body font via terminal-prose #}
<div class="prose prose-gray max-w-none flex-1 terminal-prose terminal-page {{ proseClass }}">{{ text|safe }}</div>
<div class="terminal-prose {{ class }}">{{ text|safe }}</div>

View File

@ -1,22 +0,0 @@
{# timeline terminal override: heading, mono step labels, neon markers. All field reads + control flow preserved. #}
<section 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="terminal-heading text-center text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ 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-primary/50{% endif %}">
{% for step in steps %}
<li class="{% if orientation == "horizontal" %}text-center{% else %}relative pl-14{% endif %}">
<span class="flex h-8 w-8 items-center justify-center rounded-md bg-primary text-sm font-semibold text-primary-foreground terminal-mono {% 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 rounded-full 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="terminal-mono text-xs font-semibold uppercase tracking-widest text-accent">{{ step.label }}</p>{% endif %}
{% if step.title %}<h3 class="terminal-heading mt-1 text-lg font-semibold">{{ step.title }}</h3>{% endif %}
{% if step.text %}<p class="mt-1 text-muted-foreground">{{ step.text }}</p>{% endif %}
{% if step.media %}<div class="mt-3 overflow-hidden rounded-md border border-primary/40">{% img step.media alt=step.title class="w-full h-auto object-cover" %}</div>{% endif %}
</div>
</li>
{% endfor %}
</ol>
</div>
</section>

View File

@ -1,10 +0,0 @@
{# utility_bar (WO-TF-006): thin top contact strip; left/right item lists {icon,label,link}; icons render through the sprite pipeline via the ::pack:name:: shorthand. Terminal override: mono terminal readout, neon underline. #}
{% set ubcls = "bg-muted text-muted-foreground border-b border-primary/30" %}
{% 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="inline-flex items-center gap-1.5 hover:opacity-80 transition-opacity">{% if it.icon %}::{{ it.icon }}::{% endif %}{% if it.label %}<span>{{ it.label }}</span>{% endif %}</a>{% else %}<span class="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 class="terminal-mono uppercase tracking-wide w-full text-xs {% 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-4 flex-wrap">{% for it in leftItems %}{{ ubitem(it) }}{% endfor %}</div>
<div class="flex items-center gap-4 flex-wrap justify-end">{% for it in rightItems %}{{ ubitem(it) }}{% endfor %}</div>
</div>
</div>

View File

@ -1,23 +0,0 @@
{# video-embed terminal override: neon HUD frame, play button. Facade script + data-bn-* attributes + {% img %} preserved byte-for-byte. #}
{% with aspect=aspect|default:"16:9" %}<figure class="bn-video my-6{% if class %} {{ class }}{% endif %}"><div class="bn-video-frame tty-corners relative w-full overflow-hidden rounded-md border border-primary/40 bg-muted tty-frame {% if aspect == "4:3" %}aspect-[4/3]{% elif aspect == "1:1" %}aspect-square{% elif aspect == "21:9" %}aspect-[21/9]{% else %}aspect-video{% endif %}" data-bn-video data-video-url="{{ url|default:"" }}" data-video-media="{% if media %}{{ media|mediaURL }}{% endif %}"><button type="button" data-bn-video-play class="group absolute inset-0 flex h-full w-full items-center justify-center focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring" aria-label="Play video{% if title %}: {{ title }}{% endif %}">{% if poster %}{% img poster alt=title|default:"" class="absolute inset-0 h-full w-full object-cover" %}{% endif %}<span class="absolute inset-0 bg-primary/10 transition-colors group-hover:bg-primary/20" aria-hidden="true"></span><span class="relative inline-flex h-16 w-16 items-center justify-center rounded-md border border-primary/60 bg-background/90 text-primary shadow-lg transition-transform group-hover:scale-105 motion-reduce:transform-none">::lucide:play:xl::</span></button></div>{% if caption %}<figcaption class="terminal-mono mt-2 text-center text-sm text-muted-foreground">{{ caption }}</figcaption>{% endif %}</figure><script>
(function(){
if(window.__bnVideoFacade)return;window.__bnVideoFacade=true;
function yt(u){var m=u.match(/(?:youtube\.com\/(?:watch\?v=|embed\/|shorts\/)|youtu\.be\/)([\w-]{6,})/);return m?m[1]:null;}
function vim(u){var m=u.match(/vimeo\.com\/(?:video\/)?(\d+)/);return m?m[1]:null;}
document.addEventListener('click',function(e){
var btn=e.target.closest('[data-bn-video-play]');if(!btn)return;
var frame=btn.closest('[data-bn-video]');if(!frame)return;
e.preventDefault();
var url=frame.getAttribute('data-video-url')||'',media=frame.getAttribute('data-video-media')||'',node;
var vstyle='position:absolute;inset:0;width:100%;height:100%;object-fit:contain;background:rgba(0,0,0,1);';
if(media){node=document.createElement('video');node.src=media;node.controls=true;node.autoplay=true;node.setAttribute('playsinline','');node.style.cssText=vstyle;}
else{var y=yt(url),v=vim(url),embed;
if(y)embed='https://www.youtube-nocookie.com/embed/'+y+'?autoplay=1&rel=0';
else if(v)embed='https://player.vimeo.com/video/'+v+'?autoplay=1';
if(embed){node=document.createElement('iframe');node.src=embed;node.allow='autoplay; fullscreen; picture-in-picture';node.setAttribute('allowfullscreen','');node.setAttribute('title',btn.getAttribute('aria-label')||'Video');node.style.cssText='position:absolute;inset:0;width:100%;height:100%;border:0;';}
else if(url){node=document.createElement('video');node.src=url;node.controls=true;node.autoplay=true;node.setAttribute('playsinline','');node.style.cssText=vstyle;}
}
if(node){btn.remove();frame.appendChild(node);}
});
})();
</script>{% endwith %}

View File

@ -1,23 +1,22 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
<!DOCTYPE html>
<html lang="en" class="dark">
{{ head_html|safe }}
<body class="terminal-page bg-background text-foreground antialiased min-h-screen flex flex-col">
{{ admin_banner_html|safe }}
<div aria-hidden="true" class="tty-scanlines pointer-events-none fixed inset-0 z-50"></div>
<header class="w-full">{{ slots.header|safe }}</header>
<main class="flex-grow w-full px-4 py-10">
<div class="mx-auto w-full max-w-5xl">
{% if slots.aside %}
<div class="terminal-article-grid">
<aside class="w-full">{{ slots.aside|safe }}</aside>
<article class="w-full terminal-prose">{% if slots.main %}{{ slots.main|safe }}{% else %}<p class="terminal-mono text-muted-foreground">// article body</p>{% endif %}</article>
</div>
{% else %}
<article class="mx-auto w-full max-w-3xl terminal-prose">{% if slots.main %}{{ slots.main|safe }}{% else %}<p class="terminal-mono text-muted-foreground">// article body</p>{% endif %}</article>
{% endif %}
<header class="w-full">
<div class="terminal-main-80 px-4">{{ slots.header|safe }}</div>
</header>
<div class="terminal-main-80 w-full px-4 py-6 flex-grow">
<div class="terminal-article-grid">
<aside class="w-full">{{ slots.toc|safe }}</aside>
<main class="w-full">
{% if slots.main %}{{ slots.main|safe }}{% else %}<p class="terminal-mono">// no content blocks assigned to this page</p>{% endif %}
</main>
</div>
</main>
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
</div>
<footer class="w-full mt-auto">
<div class="terminal-main-80 px-4">{{ slots.footer|safe }}</div>
</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -1,21 +0,0 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
{{ head_html|safe }}
<body class="terminal-page bg-background text-foreground antialiased min-h-screen flex flex-col">
{{ admin_banner_html|safe }}
<div aria-hidden="true" class="tty-scanlines pointer-events-none fixed inset-0 z-50"></div>
<header class="w-full">{{ slots.header|safe }}</header>
<main class="flex-grow w-full flex items-center justify-center px-4 py-16">
<div class="w-full max-w-md">
<div class="tty-window">
<div class="tty-titlebar"><span class="tty-dot"></span><span class="tty-dot"></span><span class="tty-dot"></span><span class="ml-1 terminal-mono">login</span></div>
<div class="p-6 md:p-8">
{% if slots.main %}{{ slots.main|safe }}{% else %}<p class="terminal-mono text-center text-muted-foreground">// auth console</p>{% endif %}
</div>
</div>
</div>
</main>
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -1,16 +0,0 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
{{ head_html|safe }}
<body class="terminal-page bg-background text-foreground antialiased min-h-screen flex flex-col">
{{ admin_banner_html|safe }}
<div aria-hidden="true" class="tty-scanlines pointer-events-none fixed inset-0 z-50"></div>
<header class="w-full">{{ slots.header|safe }}</header>
<main class="flex-grow w-full px-4 py-10">
<div class="mx-auto w-full max-w-5xl">
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center"><p class="terminal-mono text-muted-foreground">// no posts yet</p></div>{% endif %}
</div>
</main>
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -1,21 +0,0 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
{{ head_html|safe }}
<body class="terminal-page bg-background text-foreground antialiased min-h-screen flex flex-col">
{{ admin_banner_html|safe }}
<div aria-hidden="true" class="tty-scanlines pointer-events-none fixed inset-0 z-50"></div>
<header class="w-full">{{ slots.header|safe }}</header>
<main class="flex-grow w-full px-4 py-12">
<div class="mx-auto w-full max-w-3xl space-y-8">
<div class="tty-window">
<div class="tty-titlebar"><span class="tty-dot"></span><span class="tty-dot"></span><span class="tty-dot"></span><span class="ml-1 terminal-mono">contact --send</span></div>
<div class="p-6 md:p-8">
{% if slots.main %}{{ slots.main|safe }}{% else %}<p class="terminal-mono text-muted-foreground">// add a contact-form block</p>{% endif %}
</div>
</div>
</div>
</main>
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -1,21 +1,17 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
<!DOCTYPE html>
<html lang="en" class="dark">
{{ head_html|safe }}
<body class="terminal-page bg-background text-foreground antialiased min-h-screen flex flex-col">
{{ admin_banner_html|safe }}
<div aria-hidden="true" class="tty-scanlines pointer-events-none fixed inset-0 z-50"></div>
<header class="w-full">{{ slots.header|safe }}</header>
<main class="flex-grow w-full px-4 py-10">
<div class="terminal-main-80">
<div class="tty-window">
<div class="tty-titlebar"><span class="tty-dot"></span><span class="tty-dot"></span><span class="tty-dot"></span><span class="ml-1 terminal-mono">man page</span></div>
<div class="terminal-prose p-6 md:p-8">
{% if slots.main %}{{ slots.main|safe }}{% else %}<p class="terminal-mono text-muted-foreground">// no content blocks assigned to this page</p>{% endif %}
</div>
</div>
</div>
<header class="w-full">
<div class="terminal-main-80 px-4">{{ slots.header|safe }}</div>
</header>
<main class="terminal-main-80 w-full px-4 py-8 flex-grow">
{% if slots.main %}{{ slots.main|safe }}{% else %}<p class="terminal-mono">// no content blocks assigned to this page</p>{% endif %}
</main>
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
<footer class="w-full mt-auto">
<div class="terminal-main-80 px-4">{{ slots.footer|safe }}</div>
</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -1,14 +1,13 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
<!DOCTYPE html>
<html lang="en" class="dark">
{{ head_html|safe }}
<body class="terminal-page bg-background text-foreground antialiased min-h-screen flex flex-col">
{{ admin_banner_html|safe }}
<div aria-hidden="true" class="tty-scanlines pointer-events-none fixed inset-0 z-50"></div>
<header class="w-full">{{ slots.header|safe }}</header>
<main class="flex-grow w-full">
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="px-4 py-16 text-center"><p class="terminal-mono text-muted-foreground">// no content blocks assigned to this page</p></div>{% endif %}
<header class="w-full px-4">{{ slots.header|safe }}</header>
<main class="flex-grow w-full px-4 py-6">
{% if slots.main %}{{ slots.main|safe }}{% else %}<p class="terminal-mono">// no content blocks assigned to this page</p>{% endif %}
</main>
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
<footer class="w-full mt-auto px-4">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -1,14 +1,20 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
<!DOCTYPE html>
<html lang="en" class="dark">
{{ head_html|safe }}
<body class="terminal-page bg-background text-foreground antialiased min-h-screen flex flex-col">
{{ admin_banner_html|safe }}
<div aria-hidden="true" class="tty-scanlines pointer-events-none fixed inset-0 z-50"></div>
<header class="w-full">{{ slots.header|safe }}</header>
<section class="w-full">
<div class="terminal-main-80 px-4 py-8">{{ slots.hero|safe }}</div>
</section>
<main class="flex-grow w-full">
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="px-4 py-16 text-center"><p class="terminal-mono text-muted-foreground">// add a hero block to start the boot sequence</p></div>{% endif %}
{% if slots.main %}<div class="terminal-main-80 px-4 py-8">{{ slots.main|safe }}</div>{% endif %}
</main>
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
<section class="w-full">
<div class="terminal-main-80 px-4 py-6">{{ slots.cta|safe }}</div>
</section>
<footer class="w-full mt-auto">
<div class="terminal-main-80 px-4">{{ slots.footer|safe }}</div>
</footer>
{{ body_end_html|safe }}
</body>
</html>