diff --git a/RECOMMENDED_FONTS.md b/RECOMMENDED_FONTS.md index 69c41e5..b26b574 100644 --- a/RECOMMENDED_FONTS.md +++ b/RECOMMENDED_FONTS.md @@ -1,31 +1,43 @@ -# Recommended fonts — Terminal theme +# Recommended fonts — Terminal -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. +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. -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. +## Bundled (ship as-is) -## Heading & mono — JetBrains Mono +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: -- **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. +| 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` | -## Body — IBM Plex Mono +A terminal theme is monospace top to bottom, so all three slots default to a mono family. +License record: `assets/fonts/LICENSES.md`. -- **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. +## 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. ## Fallback chain (already in theme CSS) -If the admin has not yet picked fonts, the theme degrades through: +Before any admin selection 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. diff --git a/RECOMMENDED_ICONS.md b/RECOMMENDED_ICONS.md new file mode 100644 index 0000000..2025b83 --- /dev/null +++ b/RECOMMENDED_ICONS.md @@ -0,0 +1,24 @@ +# Recommended icons — Terminal + +Icons render through the CMS sprite pipeline: `` +(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. diff --git a/assets/fonts/LICENSES.md b/assets/fonts/LICENSES.md new file mode 100644 index 0000000..3c0a999 --- /dev/null +++ b/assets/fonts/LICENSES.md @@ -0,0 +1,21 @@ +# 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. diff --git a/assets/fonts/ibm-plex-mono-400.woff2 b/assets/fonts/ibm-plex-mono-400.woff2 new file mode 100644 index 0000000..0804aaf Binary files /dev/null and b/assets/fonts/ibm-plex-mono-400.woff2 differ diff --git a/assets/fonts/ibm-plex-mono-500.woff2 b/assets/fonts/ibm-plex-mono-500.woff2 new file mode 100644 index 0000000..090f82f Binary files /dev/null and b/assets/fonts/ibm-plex-mono-500.woff2 differ diff --git a/assets/fonts/jetbrains-mono-latin.woff2 b/assets/fonts/jetbrains-mono-latin.woff2 new file mode 100644 index 0000000..cd5102a Binary files /dev/null and b/assets/fonts/jetbrains-mono-latin.woff2 differ diff --git a/blocks/ascii_header.ninjatpl b/blocks/ascii_header.ninjatpl deleted file mode 100644 index 0d77fd7..0000000 --- a/blocks/ascii_header.ninjatpl +++ /dev/null @@ -1,3 +0,0 @@ -{% set eff_title = title %}{% if not title and not asciiArt %}{% set eff_title = "~" %}{% endif %}
{% if asciiArt %}
{{ asciiArt }}
{% else %}
/*===========================================*/
-/* {{ eff_title|slice:":41"|ljust:41 }} */
-/*===========================================*/
{% endif %}
{{ prompt|default:"$ " }}{% if eff_title %}{{ eff_title }}{% endif %}
diff --git a/blocks/ascii_header.schema.json b/blocks/ascii_header.schema.json deleted file mode 100644 index 365d6c1..0000000 --- a/blocks/ascii_header.schema.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "$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" - } - } -} diff --git a/blocks/blocks.yaml b/blocks/blocks.yaml index 361df0b..4b1508d 100644 --- a/blocks/blocks.yaml +++ b/blocks/blocks.yaml @@ -1,27 +1,15 @@ -# Terminal theme block definitions (codeless .bnp, WO-WZ-021). -# Keys are fully qualified (terminal:) 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. +# 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:) because the codeless loader registers definitions verbatim. 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 @@ -35,14 +23,8 @@ blocks: schema: keybind_table.schema.json template: keybind_table.ninjatpl - key: terminal:boot_log - title: Boot Log Hero + title: Boot Log 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 diff --git a/blocks/footer.ninjatpl b/blocks/footer.ninjatpl deleted file mode 100644 index 6be565b..0000000 --- a/blocks/footer.ninjatpl +++ /dev/null @@ -1 +0,0 @@ -{% set m = motd|default:"connection closed." %} diff --git a/blocks/footer.schema.json b/blocks/footer.schema.json deleted file mode 100644 index f859b02..0000000 --- a/blocks/footer.schema.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$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"] - } - } -} diff --git a/blocks/toc.ninjatpl b/blocks/toc.ninjatpl deleted file mode 100644 index 20afef6..0000000 --- a/blocks/toc.ninjatpl +++ /dev/null @@ -1 +0,0 @@ - diff --git a/blocks/toc.schema.json b/blocks/toc.schema.json deleted file mode 100644 index 5e670c9..0000000 --- a/blocks/toc.schema.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$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"] - } - } - } -} diff --git a/fonts.json b/fonts.json index fe51488..69ab1b1 100644 --- a/fonts.json +++ b/fonts.json @@ -1 +1,19 @@ -[] +[ + { + "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" } + ] + } +] diff --git a/manifest.yaml b/manifest.yaml index decb8b1..b0b7ddb 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -1,41 +1,109 @@ -# Terminal theme — codeless .bnp manifest (WO-WZ-021 Phase B1). +# Terminal theme — codeless .bnp manifest (WO-TF-025, Wave A). # 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 green on black terminal aesthetic with ASCII art and monospace type." + 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." page_templates: - system: terminal key: default title: Default - description: "Standard 80-col centered TTY layout" + 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" slots: [header, main, footer] - system: terminal key: landing title: Landing - description: "ASCII hero + feature grid for project README sites" - slots: [hero, main, cta, footer] + description: "Typed-terminal canvas hero, feature grid, boot log, and CTA for README-style project sites" + slots: [header, main, footer] - system: terminal key: article title: Article (man-page) - description: "Man-page two-column with section nav, ideal for writeups" - slots: [header, toc, main, footer] + description: "Man-page two-column layout with a section rail, ideal for docs and writeups" + slots: [header, main, aside, footer] - system: terminal - key: full-width - title: Full Width - description: "Edge-to-edge for tool dashboards & status pages" + 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" 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: button } + - { 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 } email_wrappers: - terminal @@ -43,42 +111,48 @@ email_wrappers: css: input_css_append: | /* ============================================================ - Terminal Theme — Phosphor green TTY aesthetic + 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. ============================================================ */ - /* --- 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.08em; - color: hsl(var(--foreground)); + letter-spacing: 0.06em; } .terminal-mono { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace); } - .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-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 a { @@ -87,7 +161,85 @@ css: text-underline-offset: 0.2em; } - /* --- ASCII frame (image override + figure wrappers) --- */ + .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 _) --- */ + + @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 { display: inline-block; @@ -96,26 +248,6 @@ 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; @@ -125,12 +257,25 @@ css: text-align: center; } - /* --- CRT scanlines overlay --- */ + /* --- TTY scanlines overlay element (hero/landing) --------- */ - .crt-scanlines { - position: relative; + .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; } + @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; @@ -141,102 +286,47 @@ css: to bottom, transparent 0, transparent 2px, - hsl(var(--background) / 0.15) 2px, - hsl(var(--background) / 0.15) 3px + hsl(var(--foreground) / 0.04) 2px, + hsl(var(--foreground) / 0.04) 3px ); } - /* --- Caret blink keyframe + utility --- */ + /* --- Status dot pulse ------------------------------------- */ - @keyframes caret-blink { - 0%, 50% { opacity: 1; } - 50.01%, 100% { opacity: 0; } - } + .terminal-status-dot { animation: terminal-pulse 2.2s ease-in-out infinite; } + @keyframes terminal-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } } - .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)); - } + /* --- Typed-terminal canvas showpiece (hero override only) - */ - /* --- 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: "_"; + .tty-rain-wrap { position: absolute; - margin-left: 0.2em; - animation: caret-blink 1s step-end infinite; - animation-name: caret-blink; + 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)); } - /* --- 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 --- */ + /* --- Man-page header block -------------------------------- */ .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.75rem 0; + padding: 0.6rem 0; margin-bottom: 1rem; display: flex; justify-content: space-between; @@ -245,17 +335,13 @@ 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; } - /* --- TOC (left rail on article) --- */ + /* --- Section rail (article aside furniture) --------------- */ .terminal-toc { font-family: var(--font-mono, ui-monospace, monospace); @@ -263,8 +349,9 @@ 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; @@ -272,33 +359,13 @@ 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; } } - .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 block ----------------------------------- */ .code-console { font-family: var(--font-mono, ui-monospace, monospace); @@ -309,23 +376,9 @@ css: overflow-x: auto; color: hsl(var(--card-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 .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 pre { margin: 0.5rem 0 0; white-space: pre-wrap; @@ -333,7 +386,7 @@ css: font-family: inherit; } - /* --- Keybinding table --- */ + /* --- Keybinding table ------------------------------------- */ .keybind-table { font-family: var(--font-mono, ui-monospace, monospace); @@ -343,7 +396,6 @@ css: max-width: 80ch; color: hsl(var(--foreground)); } - .keybind-table th, .keybind-table td { border-bottom: 1px dashed hsl(var(--border)); @@ -351,14 +403,12 @@ 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)); @@ -369,7 +419,7 @@ css: font-size: 0.875em; } - /* --- Boot log --- */ + /* --- Boot log --------------------------------------------- */ .boot-log { font-family: var(--font-mono, ui-monospace, monospace); @@ -381,94 +431,54 @@ 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; } - /* --- Footer --- */ + /* --- Layout helpers --------------------------------------- */ - .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-main-80 { max-width: 80ch; margin: 0 auto; } .terminal-article-grid { display: grid; - grid-template-columns: 18ch 1fr; + grid-template-columns: 20ch 1fr; gap: 2rem; align-items: start; } + @media (max-width: 768px) { .terminal-article-grid { grid-template-columns: 1fr; } } - @media (max-width: 768px) { - .terminal-article-grid { - grid-template-columns: 1fr; + /* --- 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; } + .boot-log .boot-line { opacity: 1; transform: none; } + .tty-scanlines { opacity: 0; } + .tty-rain-canvas { display: none; } } - .terminal-main-80 { - max-width: 80ch; - margin: 0 auto; - font-family: var(--font-body, ui-monospace, monospace); + /* --- High contrast: drop the CRT overlays ----------------- */ + + @media (prefers-contrast: more) { + .tty-scanlines, + .crt-scanlines::before, + .tty-rain-canvas { display: none; } } diff --git a/master_pages.json b/master_pages.json index 7acd5c3..0b8a25c 100644 --- a/master_pages.json +++ b/master_pages.json @@ -1,25 +1,22 @@ [ { - "key": "terminal:default-master", - "title": "Terminal Default Master", - "page_templates": ["default", "landing", "full-width"], + "key": "terminal:site-master", + "title": "Terminal Site Master", + "page_templates": ["default", "full-width", "landing", "article", "blog-index", "contact"], "blocks": [ - { "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 } + { "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 } ] }, { - "key": "terminal:article-master", - "title": "Terminal Article Master", - "page_templates": ["article"], + "key": "terminal:auth-master", + "title": "Terminal Auth Master", + "page_templates": ["auth"], "blocks": [ - { "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 } + { "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 } ] } ] diff --git a/plugin.mod b/plugin.mod index 36f482d..7ebf4d8 100644 --- a/plugin.mod +++ b/plugin.mod @@ -3,7 +3,8 @@ name = "terminal" display_name = "Terminal" scope = "@themes" 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." +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." kind = "theme" categories = ["templates"] -tags = ["terminal", "hacker", "monospace", "dev", "ctf", "security", "docs", "indie"] +tags = ["terminal", "hacker", "monospace", "dev", "ctf", "docs", "devtools", "oss"] +required_icon_packs = ["lucide", "simple-icons"] diff --git a/presets.json b/presets.json index d2942a7..a7c30b9 100644 --- a/presets.json +++ b/presets.json @@ -2,9 +2,30 @@ { "id": "phosphor-green", "name": "Phosphor Green", - "description": "Classic green-on-black VT220 phosphor.", + "description": "Green printer-ribbon on paper for light, VT220 green phosphor for dark. The flagship terminal look.", "theme": { - "mode": "dark", + "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%" + }, "darkColors": { "background": "0 0% 4%", "foreground": "120 100% 50%", @@ -29,11 +50,32 @@ } }, { - "id": "amber-mono", - "name": "Amber Mono", - "description": "Warm amber phosphor terminal.", + "id": "amber-crt", + "name": "Amber CRT", + "description": "Warm cream paper with amber ribbon for light, amber phosphor on warm black for dark.", "theme": { - "mode": "dark", + "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%" + }, "darkColors": { "background": "30 20% 4%", "foreground": "38 95% 60%", @@ -60,9 +102,30 @@ { "id": "paper-tty", "name": "Paper TTY", - "description": "Hard white on black; austere docs mode (no glow).", + "description": "Crisp black-on-white docs for light with a blue link accent, hard white on black for dark. Austere, no glow.", "theme": { - "mode": "dark", + "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%" + }, "darkColors": { "background": "0 0% 5%", "foreground": "0 0% 95%", @@ -85,5 +148,105 @@ "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%" + } + } } ] diff --git a/seed/seed.json b/seed/seed.json new file mode 100644 index 0000000..1555349 --- /dev/null +++ b/seed/seed.json @@ -0,0 +1,240 @@ +{ + "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": "

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.

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.

" } }, + { + "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": "

Every task output is keyed by its inputs. Here is how the hash is built and why it is stable across machines.

", "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": "

grid watch reruns affected tasks on save with no background process. This is how the file watcher stays cheap.

", "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": "

Independent tasks run together, up to your core count. The scheduler is a topological sort with a worker pool.

", "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": "

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.

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.

" } }, + { "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": "

Most watchers run a background process that never exits. grid does not. grid watch runs in the foreground and exits when you press q.

On each save it maps the changed file back to the tasks that read it, then reruns only those. The rest stay cached.

" } }, + { "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": "

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.

The worker pool is sized to your core count by default. You can cap it with a flag when a task is memory heavy.

" } }, + { "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 } + ] +} diff --git a/seed/workflows.json b/seed/workflows.json new file mode 100644 index 0000000..c277456 --- /dev/null +++ b/seed/workflows.json @@ -0,0 +1,19 @@ +{ + "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" } + } + ] + } + ] +} diff --git a/templates/overrides/terminal/announcement-bar.ninjatpl b/templates/overrides/terminal/announcement-bar.ninjatpl new file mode 100644 index 0000000..bc46c0e --- /dev/null +++ b/templates/overrides/terminal/announcement-bar.ninjatpl @@ -0,0 +1,21 @@ +{# 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 %} +
+
+

{{ message }}{% if linkText and linkUrl %} {{ linkText }}{% endif %}

+{% if dismissable %}{% endif %} +
+
+{% if dismissable %}{% endif %} diff --git a/templates/overrides/terminal/audio-embed.ninjatpl b/templates/overrides/terminal/audio-embed.ninjatpl new file mode 100644 index 0000000..6a9944c --- /dev/null +++ b/templates/overrides/terminal/audio-embed.ninjatpl @@ -0,0 +1,16 @@ +{# audio-embed terminal override: neon panel, mono meta. Facade script + data-bn-* attributes + {% img %} preserved byte-for-byte. #} +
{% if artwork %}
{% img artwork alt=title|default:"" class="h-full w-full object-cover" %}
{% endif %}
{% if title %}

{{ title }}

{% endif %}{% if artist %}

{{ artist }}

{% endif %}
{% if download %}::lucide:download:sm:: Download{% endif %}
diff --git a/templates/overrides/terminal/auth-form.ninjatpl b/templates/overrides/terminal/auth-form.ninjatpl new file mode 100644 index 0000000..bab56d7 --- /dev/null +++ b/templates/overrides/terminal/auth-form.ninjatpl @@ -0,0 +1,77 @@ +{# 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 %} +
+

Session active as {{ context.publicUsername }}

+ My Account +
+{% 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() %} + + +{% endmacro %} +{% macro social() %}{% if show_social != false and (context.site.google_oauth_client_id or context.site.facebook_app_id) %}
+

or
+ {% if context.site.google_oauth_client_id %}Continue with Google{% endif %} + {% if context.site.facebook_app_id %}Continue with Facebook{% endif %} +
{% endif %}{% endmacro %} +
+
+
+ + +
+
+
+
+
+ {% if captchaEnabled %}{{ captcha() }}{% endif %} + +

Forgot password?

+
+ {{ social() }} +
+
+
+
+
+
+ {% if captchaEnabled %}{{ captcha() }}{% endif %} + +
+ {{ social() }} +
+
+ +{% endif %} diff --git a/templates/overrides/terminal/auth-status.ninjatpl b/templates/overrides/terminal/auth-status.ninjatpl new file mode 100644 index 0000000..0a302d2 --- /dev/null +++ b/templates/overrides/terminal/auth-status.ninjatpl @@ -0,0 +1,36 @@ +{# 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 %} +
+ Login + Sign Up +
+{% else %} +{% if not context.publicEmailVerified %}
+ Verify your email address. +
{% endif %} +
+ + + +
+{% endif %} diff --git a/templates/overrides/terminal/author-bio-hero.ninjatpl b/templates/overrides/terminal/author-bio-hero.ninjatpl new file mode 100644 index 0000000..ec376d8 --- /dev/null +++ b/templates/overrides/terminal/author-bio-hero.ninjatpl @@ -0,0 +1,22 @@ +{# author-bio-hero (terminal override) — author-page header from the `authors` provider. #} +{% if author %} +
+
+
+{% if showAvatar and author.avatar_url %}
{% img author.avatar_url alt=author.display_name class="h-full w-full object-cover" %}
{% endif %} +

{{ author.display_name }}

+{% if author.job_title or author.company %}

{{ author.job_title }}{% if author.job_title and author.company %} · {% endif %}{{ author.company }}

{% endif %} +{% if author.bio %}

{{ author.bio }}

{% endif %} +
+{% if showPostCount %}{{ author.post_count|default:0 }} post{% if author.post_count != 1 %}s{% endif %}{% endif %} +{% if author.location %}{{ author.location }}{% endif %} +
+{% if showSocial %}
+{% if author.website_url %}{% endif %} +{% if author.twitter_handle %}{% endif %} +{% if author.linkedin_url %}{% endif %} +{% if author.github_url %}{% endif %} +
{% endif %} +
+
+{% endif %} diff --git a/templates/overrides/terminal/author-bio.ninjatpl b/templates/overrides/terminal/author-bio.ninjatpl new file mode 100644 index 0000000..63edf63 --- /dev/null +++ b/templates/overrides/terminal/author-bio.ninjatpl @@ -0,0 +1,13 @@ +{# author-bio (terminal override) — byline card sourced from the `post` provider's author_* fields. #} +{% if post.author_name %} + +{% endif %} diff --git a/templates/overrides/terminal/blog-hero.ninjatpl b/templates/overrides/terminal/blog-hero.ninjatpl new file mode 100644 index 0000000..8ba19b6 --- /dev/null +++ b/templates/overrides/terminal/blog-hero.ninjatpl @@ -0,0 +1,24 @@ +{# blog-hero (terminal override) — blog-index header + featured posts from the `posts` provider. #} +
+
+
+
+{% if title %}

{{ title }}

{% endif %} +{% if subtitle %}

{{ subtitle }}

{% endif %} +
+{% if posts %} +
+{% for post in posts %} + +{% endfor %} +
+{% endif %} +
+
diff --git a/templates/overrides/terminal/breadcrumbs.ninjatpl b/templates/overrides/terminal/breadcrumbs.ninjatpl new file mode 100644 index 0000000..e93509f --- /dev/null +++ b/templates/overrides/terminal/breadcrumbs.ninjatpl @@ -0,0 +1,15 @@ +{# 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" %} + diff --git a/templates/overrides/terminal/button.ninjatpl b/templates/overrides/terminal/button.ninjatpl index 115475b..d8079ec 100644 --- a/templates/overrides/terminal/button.ninjatpl +++ b/templates/overrides/terminal/button.ninjatpl @@ -1 +1,2 @@ -{% if text %}{% if target == "_blank" %}[ {{ text }} ]{% else %}[ {{ text }} ]{% endif %}{% endif %} +{# terminal button — keep {% button %} tag intact; terminal chrome on hugging wrapper #} +{% with align=align|default:"left" %}
{% 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 %}
{% endwith %} diff --git a/templates/overrides/terminal/card.ninjatpl b/templates/overrides/terminal/card.ninjatpl new file mode 100644 index 0000000..a88420a --- /dev/null +++ b/templates/overrides/terminal/card.ninjatpl @@ -0,0 +1,2 @@ +{# terminal card — dashed neon panel; fields layout/link/media/title/text/linkLabel #} +{% with layout=layout|default:"vertical" %}{% endwith %} diff --git a/templates/overrides/terminal/category-list.ninjatpl b/templates/overrides/terminal/category-list.ninjatpl new file mode 100644 index 0000000..156271d --- /dev/null +++ b/templates/overrides/terminal/category-list.ninjatpl @@ -0,0 +1,18 @@ +{# category-list (terminal override) — from the `categories` provider. list or pills. #} +{% set pills = style == "pills" %} +
+{% if heading %}

{{ heading }}

{% endif %} +{% if pills %} +
+{% for cat in categories %} +{{ cat.name }}{% if showCount %}{{ cat.post_count }}{% endif %} +{% endfor %} +
+{% else %} + +{% endif %} +
diff --git a/templates/overrides/terminal/contact-card.ninjatpl b/templates/overrides/terminal/contact-card.ninjatpl new file mode 100644 index 0000000..3cc7fa5 --- /dev/null +++ b/templates/overrides/terminal/contact-card.ninjatpl @@ -0,0 +1,18 @@ +{# contact-card terminal override: neon channel panels. All field reads + control flow preserved. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if description %}

{{ description }}

{% endif %} +
+ {% for channel in channels %} +
+ {% if channel.icon %}{% endif %} +
+ {% if channel.label %}
{{ channel.label }}
{% endif %} + {% if channel.value %}
{% if channel.link %}{{ channel.value }}{% else %}{{ channel.value }}{% endif %}
{% endif %} +
+
+ {% endfor %} +
+
+
diff --git a/templates/overrides/terminal/contact-form.ninjatpl b/templates/overrides/terminal/contact-form.ninjatpl new file mode 100644 index 0000000..de44b05 --- /dev/null +++ b/templates/overrides/terminal/contact-form.ninjatpl @@ -0,0 +1,40 @@ +{# 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 %} +
+
+ {% if form.submitted %} +
+
+

{{ form.message|default:successMessage|default:"Thanks — your message has been sent." }}

+
+ {% else %} + {% if heading %}

{{ heading }}

{% endif %} + {% if description %}

{{ description }}

{% endif %} + {% if form.errors._form %}
{{ form.errors._form }}
{% endif %} + {% with formAction="/api/blocks/"|add:bid|add:"/submit" formTarget="#contact-form-"|add:bid %} + {% form action=formAction hx_target=formTarget hx_swap="outerHTML" class="mt-8 grid grid-cols-1 gap-5 sm:grid-cols-2" %} + {% for field in fields %} +
+ {% if field.label %}{% endif %} + {% if field.type == "textarea" %} + + {% elif field.type == "select" %} + + {% else %} + + {% endif %} +
+ {% endfor %} + +
+ +
+ {% endform %} + {% endwith %} + {% endif %} +
+
+{% endwith %} diff --git a/templates/overrides/terminal/countdown.ninjatpl b/templates/overrides/terminal/countdown.ninjatpl new file mode 100644 index 0000000..c3a888f --- /dev/null +++ b/templates/overrides/terminal/countdown.ninjatpl @@ -0,0 +1,50 @@ +{# countdown terminal override: neon HUD digit boxes, heading. Timer script + data attributes preserved byte-for-byte. #} +
+ +
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} +
+
+
--{{ labelDays|default:"Days" }}
+
--{{ labelHours|default:"Hours" }}
+
--{{ labelMinutes|default:"Minutes" }}
+
--{{ labelSeconds|default:"Seconds" }}
+
+ +
+ +
+
+ diff --git a/templates/overrides/terminal/cta.ninjatpl b/templates/overrides/terminal/cta.ninjatpl new file mode 100644 index 0000000..db7643b --- /dev/null +++ b/templates/overrides/terminal/cta.ninjatpl @@ -0,0 +1,16 @@ +{# cta terminal override: scanline overlay, heading, CTAs. All field reads preserved. #} +
+ +
+
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if text %}

{{ text }}

{% endif %} +
+
+ {% if primaryUrl and primaryLabel %}{{ primaryLabel }}{% endif %} + {% if secondaryUrl and secondaryLabel %}{{ secondaryLabel }}{% endif %} +
+
+
+
diff --git a/templates/overrides/terminal/divider.ninjatpl b/templates/overrides/terminal/divider.ninjatpl new file mode 100644 index 0000000..2088b0f --- /dev/null +++ b/templates/overrides/terminal/divider.ninjatpl @@ -0,0 +1,2 @@ +{# terminal divider — neon gradient rule / primary-tinted variants #} +{% with variant=variant|default:"line" weight=weight|default:"thin" width=width|default:"full" %}{% if variant == "ornament" %}{% elif variant == "spacer-rule" %}{% else %}{% endif %}{% endwith %} diff --git a/templates/overrides/terminal/event-list.ninjatpl b/templates/overrides/terminal/event-list.ninjatpl new file mode 100644 index 0000000..2fa975e --- /dev/null +++ b/templates/overrides/terminal/event-list.ninjatpl @@ -0,0 +1,41 @@ +{# event-list terminal override: neon cards, mono datetime. All field reads + control flow preserved. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} + {% if layout == "cards" %} +
+ {% for event in events %}{% if not (hidePast and event.past) %} +
+ {% if event.media %}
{% img event.media alt=event.title class="h-full w-full object-cover" %}
{% endif %} +
+
{{ event.start }}{% if event.end %} – {{ event.end }}{% endif %}{% if event.timezone %} {{ event.timezone }}{% endif %}
+

{{ event.title }}

+ {% if event.venue %}

{{ event.venue }}

{% endif %} + {% if event.description %}

{{ event.description }}

{% endif %} + {% if event.link %}{{ event.linkLabel|default:"Details" }}{% endif %} +
+
+ {% endif %}{% endfor %} +
+ {% else %} +
    + {% for event in events %}{% if not (hidePast and event.past) %} +
  • +
    +
    {{ event.start }}
    + {% if event.end %}
    to {{ event.end }}
    {% endif %} + {% if event.timezone %}
    {{ event.timezone }}
    {% endif %} +
    +
    +

    {{ event.title }}

    + {% if event.venue %}

    {{ event.venue }}

    {% endif %} + {% if event.description %}

    {{ event.description }}

    {% endif %} +
    + {% if event.link %}{{ event.linkLabel|default:"Details" }}{% endif %} +
  • + {% endif %}{% endfor %} +
+ {% endif %} +
+
diff --git a/templates/overrides/terminal/faq.ninjatpl b/templates/overrides/terminal/faq.ninjatpl new file mode 100644 index 0000000..f3a8a9a --- /dev/null +++ b/templates/overrides/terminal/faq.ninjatpl @@ -0,0 +1,18 @@ +{# faq terminal override: heading, neon-tinted dividers, display questions. Chevron svg, {% for %}, and answer prose preserved. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} +
+ {% for item in items %} +
+ + {{ item.question }} + + +
{{ item.answer|safe }}
+
+ {% endfor %} +
+
+
diff --git a/templates/overrides/terminal/feature-grid.ninjatpl b/templates/overrides/terminal/feature-grid.ninjatpl new file mode 100644 index 0000000..638981e --- /dev/null +++ b/templates/overrides/terminal/feature-grid.ninjatpl @@ -0,0 +1,19 @@ +{# feature_grid terminal override: scanline hero overlay, mono eyebrow, heading, neon HUD cards. All field reads + control flow preserved. #} +
+ +
+ {% if eyebrow %}

{{ eyebrow }}

{% endif %} + {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} +
+ {% for item in items %} +
+ {% if item.icon %}
{% endif %} +

{% if item.linkUrl %}{{ item.title }}{% else %}{{ item.title }}{% endif %}

+ {% if item.text %}

{{ item.text }}

{% endif %} + {% if item.linkUrl and item.linkLabel %}{{ item.linkLabel }}{% endif %} +
+ {% endfor %} +
+
+
diff --git a/templates/overrides/terminal/featured-posts.ninjatpl b/templates/overrides/terminal/featured-posts.ninjatpl new file mode 100644 index 0000000..db32552 --- /dev/null +++ b/templates/overrides/terminal/featured-posts.ninjatpl @@ -0,0 +1,23 @@ +{# featured-posts (terminal override) — grid of cards from the `posts` provider #} +{# (params: limit, featured=true). Neon HUD card: image, title, excerpt, meta. #} +
+
+{% if heading %}

{{ heading }}

{% endif %} +
+{% for post in posts %} +
+{% if showFeaturedImage and post.featured_image_url %}{% img post.featured_image_url alt=post.title class="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105" %}{% endif %} +
+

{{ post.title }}

+{% if showExcerpt and post.excerpt %}

{{ post.excerpt }}

{% endif %} +
+{% if showAuthor and post.author_name %}{{ post.author_name }}{% endif %} +{% if showDate and post.published_at %}{{ post.published_at|date:"M j, Y" }}{% endif %} +{% if post.reading_time %}{{ post.reading_time }} min read{% endif %} +
+
+
+{% endfor %} +
+
+
diff --git a/templates/overrides/terminal/footer.ninjatpl b/templates/overrides/terminal/footer.ninjatpl new file mode 100644 index 0000000..50ed4ff --- /dev/null +++ b/templates/overrides/terminal/footer.ninjatpl @@ -0,0 +1,39 @@ +{# 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 %}{% endif %}{% endmacro %} +{% macro newsletter() %}{% if showNewsletter %}

{{ newsletterTitle|default:"Stay in the loop" }}

{% if newsletterDescription %}

{{ newsletterDescription }}

{% endif %}
{% signup_form list=newsletterListSlug|default:"main" source="footer" submit=newsletterButtonText|default:"Subscribe" %}
{% endif %}{% endmacro %} +{% macro legalbar() %}

{% if copyright %}© {{ copyright }}{% else %}© {{ companyName }}{% endif %}

{% if legal_items %}{% endif %}
{% endmacro %} +
+
+{% if fl == "minimal" %} +
+
{% if logo %}{{ companyName }}{% elif companyName %}{{ companyName }}{% endif %}{% if tagline %}{% endif %}
+{{ sociallinks("") }} +
+{{ legalbar() }} +{% elif fl == "centered" %} +
+{% if logo %}{{ companyName }}{% elif companyName %}{{ companyName }}{% endif %} +{% if tagline %}

{{ tagline }}

{% endif %} +{% if columns %}{% endif %} +{{ newsletter() }} +{{ sociallinks("justify-center") }} +
+{{ legalbar() }} +{% else %} +
+
+{% if logo %}{{ companyName }}{% elif companyName %}{{ companyName }}{% endif %} +{% if tagline %}

{{ tagline }}

{% endif %} +{% if address or email or phone %}
{% if address %}
{{ address }}
{% endif %}{% if email %}{% endif %}{% if phone %}{% endif %}
{% endif %} +{{ sociallinks("mt-5") }} +
+{% for col in columns %}

{{ col.header }}

{% endfor %} +{% if showNewsletter %}
{{ newsletter() }}
{% endif %} +
+{{ legalbar() }} +{% endif %} +
+
diff --git a/templates/overrides/terminal/gallery.ninjatpl b/templates/overrides/terminal/gallery.ninjatpl new file mode 100644 index 0000000..9ef3f15 --- /dev/null +++ b/templates/overrides/terminal/gallery.ninjatpl @@ -0,0 +1,52 @@ +{# 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 %}{% if lightbox %}{% endif %}{% endwith %} diff --git a/templates/overrides/terminal/heading.ninjatpl b/templates/overrides/terminal/heading.ninjatpl index ce5c430..8ada4aa 100644 --- a/templates/overrides/terminal/heading.ninjatpl +++ b/templates/overrides/terminal/heading.ninjatpl @@ -1 +1,7 @@ -{% set lvl = level|default:2|integer %}{% if lvl < 1 or lvl > 6 %}{% set lvl = 2 %}{% endif %}{% set t = text|upper %}{% if lvl == 1 %}

# {{ t }}

{% elif lvl == 3 %}

### {{ t }}

{% elif lvl == 4 %}

#### {{ t }}

{% elif lvl == 5 %}
##### {{ t }}
{% elif lvl == 6 %}
###### {{ t }}
{% else %}

## {{ t }}

{% endif %} +{# terminal heading — terminal-heading everywhere; on h1/h2 only #} +{% if level == 1 %}

{{ text }}

+{% elif level == 3 %}

{{ text }}

+{% elif level == 4 %}

{{ text }}

+{% elif level == 5 %}
{{ text }}
+{% elif level == 6 %}
{{ text }}
+{% else %}

{{ text }}

{% endif %} diff --git a/templates/overrides/terminal/hero.ninjatpl b/templates/overrides/terminal/hero.ninjatpl new file mode 100644 index 0000000..7135fb1 --- /dev/null +++ b/templates/overrides/terminal/hero.ninjatpl @@ -0,0 +1,95 @@ +{# 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.text|default:eyebrow %} +{% set headlineText = headline.text|default:headline %} +{% set subText = subheadline.text|default:subheadline %} +{% set descText = description.text|default:description %} +{% set pText = ctaPrimary.text %} +{% set pUrl = ctaPrimary.url %} +{% set sText = ctaSecondary.text %} +{% set sUrl = ctaSecondary.url %} +{% set variant = template|default:"centered" %} +{% set hasImage = backgroundImage %} +{% set showOverlay = hasImage and overlayColor != "none" and overlayColor != "" %} +{% macro herocontent(align) %} +{% if eyebrowText %}$ {{ eyebrowText }} {% endif %} +{% if headlineText %}

{{ headlineText }}

{% endif %} +{% if subText %}

{{ subText }}

{% endif %} +{% if descText %}

{{ descText }}

{% endif %} +{% if pText or sText %}
+{% if pText and pUrl %}{{ pText }}{% endif %} +{% if sText and sUrl %}{{ sText }}{% endif %} +
{% endif %} +{% if trustLogos %}
+

Trusted by

+
{% for logo in trustLogos %}{% if logo.url %}{% img logo.image alt=logo.alt class="h-8 w-auto" %}{% else %}{% img logo.image alt=logo.alt class="h-8 w-auto opacity-60" %}{% endif %}{% endfor %}
+
{% endif %} +{% endmacro %} +{% if variant == "split" %} +
+
{% if hasImage %}{% img backgroundImage alt=headlineText class="absolute inset-0 h-full w-full object-cover" layout="hero" %}{% else %}
{% endif %}
+
{{ herocontent("left") }}
+
+{% else %} +{% set align = "center" %} +{% if variant == "left-aligned" %}{% set align = "left" %}{% endif %} +
+{% if hasImage %}
{% img backgroundImage alt=headlineText class="h-full w-full object-cover" layout="hero" %}{% if showOverlay %}
{% endif %}
{% else %}
{% endif %} + +
{{ herocontent(align) }}
+
+{% endif %} + diff --git a/templates/overrides/terminal/hours-location.ninjatpl b/templates/overrides/terminal/hours-location.ninjatpl new file mode 100644 index 0000000..51700b0 --- /dev/null +++ b/templates/overrides/terminal/hours-location.ninjatpl @@ -0,0 +1,31 @@ +{# hours-location terminal override: mono readout table, neon map panel. Map lazy-load button + inline handler preserved. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} +
+
+ {% if hours %} + + + {% for row in hours %} + + + + + {% endfor %} + +
{{ row.day }}{% if row.closed %}Closed{% else %}{{ row.hours }}{% endif %}
+ {% endif %} + {% if specialHoursNote %}

{{ specialHoursNote }}

{% endif %} + {% if address %}
Address
{{ address }}
{% endif %} + {% if phone %}{% endif %} + {% if directionsUrl %}Get directions{% endif %} +
+ {% if mapEmbedUrl %} +
+ +
+ {% endif %} +
+
+
diff --git a/templates/overrides/terminal/image.ninjatpl b/templates/overrides/terminal/image.ninjatpl index 95d92ce..9390389 100644 --- a/templates/overrides/terminal/image.ninjatpl +++ b/templates/overrides/terminal/image.ninjatpl @@ -1 +1,2 @@ -
{% if src %}{{ alt|default:caption }}{% else %}
[ no image ]
{% endif %}
[fig.{% if caption %} {{ caption }}{% endif %}]
+{# 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" %}
{% 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 %}
{% if caption %}
{{ caption }}
{% endif %}
{% endwith %} diff --git a/templates/overrides/terminal/logo-strip.ninjatpl b/templates/overrides/terminal/logo-strip.ninjatpl new file mode 100644 index 0000000..706bc4d --- /dev/null +++ b/templates/overrides/terminal/logo-strip.ninjatpl @@ -0,0 +1,16 @@ +{# logo_strip terminal override: mono kicker. Marquee classes, {% img %}, and keyframes {% endif %} diff --git a/templates/overrides/terminal/menu-list.ninjatpl b/templates/overrides/terminal/menu-list.ninjatpl new file mode 100644 index 0000000..4913029 --- /dev/null +++ b/templates/overrides/terminal/menu-list.ninjatpl @@ -0,0 +1,30 @@ +{# menu-list terminal override: mono price readouts, neon section rules. All field reads + control flow preserved. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} +
+ {% for section in sections %} + + {% endfor %} +
+ {% if footnote %}

{{ footnote }}

{% endif %} +
+
diff --git a/templates/overrides/terminal/navbar.ninjatpl b/templates/overrides/terminal/navbar.ninjatpl new file mode 100644 index 0000000..95d8c21 --- /dev/null +++ b/templates/overrides/terminal/navbar.ninjatpl @@ -0,0 +1,104 @@ +{# 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) %}{{ item.label }}{% endmacro %} +{% macro dropdown(item) %}
+ +{% if use_mega %} +{% else %}{% endif %} +
{% endmacro %} +{% macro brand(size) %}{% if logoType == "text" and logoText.text %}{{ logoText.text }}{% elif logo %}{{ alt|default:companyName }}{% elif companyName %}{{ companyName }}{% endif %}{% endmacro %} +{% macro ctabtn(extra) %}{% if cta_text %}{{ cta_text }}{% endif %}{% endmacro %} + + + diff --git a/templates/overrides/terminal/newsletter-signup.ninjatpl b/templates/overrides/terminal/newsletter-signup.ninjatpl new file mode 100644 index 0000000..e5863ca --- /dev/null +++ b/templates/overrides/terminal/newsletter-signup.ninjatpl @@ -0,0 +1,12 @@ +{# 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. #} +
+
+
+{% if title %}

{{ title }}

{% endif %} +{% if description %}

{{ description }}

{% endif %} +
{% signup_form list=listSlug source=source submit=submitText success_message=successMessage placeholder=placeholder %}
+
+
+
diff --git a/templates/overrides/terminal/page-suggestions.ninjatpl b/templates/overrides/terminal/page-suggestions.ninjatpl new file mode 100644 index 0000000..7c1c9c1 --- /dev/null +++ b/templates/overrides/terminal/page-suggestions.ninjatpl @@ -0,0 +1,13 @@ +{# 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. #} +
+ +
+

Error 404

+

{{ title|default:"Signal lost" }}

+

{{ emptyMessage|default:"That route returned nothing. The page moved, shipped, or never existed." }}

+ {% if context.path %}

requested: {{ context.path }}

{% endif %} + +
+
diff --git a/templates/overrides/terminal/password-reset-form.ninjatpl b/templates/overrides/terminal/password-reset-form.ninjatpl new file mode 100644 index 0000000..26b7d8c --- /dev/null +++ b/templates/overrides/terminal/password-reset-form.ninjatpl @@ -0,0 +1,13 @@ +{# 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. #} +
+
+
+

Reset Password

+

Enter your email address and we will send you a link to reset your password.

+
+
+ +
+

Back to login

+
+
diff --git a/templates/overrides/terminal/popular-posts.ninjatpl b/templates/overrides/terminal/popular-posts.ninjatpl new file mode 100644 index 0000000..458bb24 --- /dev/null +++ b/templates/overrides/terminal/popular-posts.ninjatpl @@ -0,0 +1,16 @@ +{# popular-posts (terminal override) — compact ranked list from the `posts` provider. #} +
+{% if heading %}

{{ heading }}

{% endif %} +
    +{% for post in posts %} +
  1. +{% if showRank %}{{ forloop.Counter }}{% endif %} +{% if showThumbnail and post.featured_image_url %}{% img post.featured_image_url alt=post.title class="h-full w-full object-cover" %}{% endif %} +
    +

    {{ post.title }}

    +{% if post.published_at %}

    {{ post.published_at|date:"M j, Y" }}

    {% endif %} +
    +
  2. +{% endfor %} +
+
diff --git a/templates/overrides/terminal/post-hero.ninjatpl b/templates/overrides/terminal/post-hero.ninjatpl new file mode 100644 index 0000000..ce97464 --- /dev/null +++ b/templates/overrides/terminal/post-hero.ninjatpl @@ -0,0 +1,23 @@ +{# 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 %} +
+
{% img post.featured_image_url alt=post.featured_image_alt class="h-full w-full object-cover" layout="hero" %}
+
+
+

{{ post.title }}

+{% if showExcerpt and post.excerpt %}

{{ post.excerpt }}

{% endif %} +{% if showMeta %}
{% if post.author_name %}{{ post.author_name }}{% endif %}{% if post.published_at %}{{ post.published_at|date:"F j, Y" }}{% endif %}{% if post.reading_time %}{{ post.reading_time }} min read{% endif %}
{% endif %} +
+
+{% else %} +
+
+
+

{{ post.title }}

+{% if showExcerpt and post.excerpt %}

{{ post.excerpt }}

{% endif %} +{% if showMeta %}
{% if post.author_name %}{{ post.author_name }}{% endif %}{% if post.published_at %}{{ post.published_at|date:"F j, Y" }}{% endif %}{% if post.reading_time %}{{ post.reading_time }} min read{% endif %}
{% endif %} +
+
+{% endif %} diff --git a/templates/overrides/terminal/post-metadata.ninjatpl b/templates/overrides/terminal/post-metadata.ninjatpl new file mode 100644 index 0000000..0f7029e --- /dev/null +++ b/templates/overrides/terminal/post-metadata.ninjatpl @@ -0,0 +1,9 @@ +{# post-metadata (terminal override) — author/date/reading-time row from the `post` provider. #} +{% if post %} +
+{% if showAuthor and post.author_name %}{% 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 %}{{ post.author_name }}{% endif %} +{% if showDate and post.published_at %}{% endif %} +{% if showReadingTime and post.reading_time %}{{ post.reading_time }} min read{% endif %} +{% if post.categories %}{% for cat in post.categories %}{{ cat.name }}{% endfor %}{% endif %} +
+{% endif %} diff --git a/templates/overrides/terminal/pricing.ninjatpl b/templates/overrides/terminal/pricing.ninjatpl new file mode 100644 index 0000000..108923b --- /dev/null +++ b/templates/overrides/terminal/pricing.ninjatpl @@ -0,0 +1,26 @@ +{# pricing terminal override: scanline overlay, heading, HUD neon tier panels (magenta highlight / cyan standard), CTAs. Field reads + control flow preserved. #} +
+ +
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} +
+ {% for tier in tiers %} +
+ {% if tier.badge %}{{ tier.badge }}{% endif %} +

{{ tier.name }}

+

+ {% if currency %}{{ currency }}{% else %}${% endif %}{{ tier.price }} + {% if tier.period %}{{ tier.period }}{% endif %} +

+ {% if tier.description %}

{{ tier.description }}

{% endif %} + {% if tier.features %}
    + {% for feature in tier.features %}
  • {{ feature }}
  • {% endfor %} +
{% endif %} + {% if tier.ctaUrl and tier.ctaLabel %}{{ tier.ctaLabel }}{% endif %} +
+ {% endfor %} +
+ {% if billingNote %}

{{ billingNote }}

{% endif %} +
+
diff --git a/templates/overrides/terminal/quote.ninjatpl b/templates/overrides/terminal/quote.ninjatpl new file mode 100644 index 0000000..1e803a4 --- /dev/null +++ b/templates/overrides/terminal/quote.ninjatpl @@ -0,0 +1,2 @@ +{# terminal quote — dashed neon left border; terminal-heading for pull; mono figcaption #} +{% with style=style|default:"block" align=align|default:"left" %}
{{ quote|safe }}{% if attribution or role or cite %}
{% if attribution %}{{ attribution }}{% endif %}{% if role %}{{ role }}{% endif %}{% if cite %}Source{% endif %}
{% endif %}
{% endwith %} diff --git a/templates/overrides/terminal/related-posts.ninjatpl b/templates/overrides/terminal/related-posts.ninjatpl new file mode 100644 index 0000000..5c962af --- /dev/null +++ b/templates/overrides/terminal/related-posts.ninjatpl @@ -0,0 +1,17 @@ +{# related-posts (terminal override) — grid from the `posts` provider. #} +{% if posts %} +
+
+

{{ heading|default:"Related posts" }}

+
+{% for post in posts %} + +{% endfor %} +
+
+
+{% endif %} diff --git a/templates/overrides/terminal/rich-section.ninjatpl b/templates/overrides/terminal/rich-section.ninjatpl new file mode 100644 index 0000000..a696cc4 --- /dev/null +++ b/templates/overrides/terminal/rich-section.ninjatpl @@ -0,0 +1,14 @@ +{# rich_section terminal override: mono eyebrow, heading, HUD-framed media, CTA. Field reads + {% img %} preserved. #} +
+
+
+ {% if eyebrow %}

{{ eyebrow }}

{% endif %} + {% if heading %}

{{ heading }}

{% endif %} + {% if body %}
{{ body|safe }}
{% endif %} + {% if ctaUrl and ctaLabel %}{{ ctaLabel }}{% endif %} +
+
+ {% if media %}
{% img media alt=mediaAlt class="w-full h-auto object-cover" %}
{% endif %} +
+
+
diff --git a/templates/overrides/terminal/rich-text.ninjatpl b/templates/overrides/terminal/rich-text.ninjatpl new file mode 100644 index 0000000..f8aae0c --- /dev/null +++ b/templates/overrides/terminal/rich-text.ninjatpl @@ -0,0 +1,2 @@ +{# terminal rich-text — body font via terminal-prose #} +{% if html %}
{{ html|safe }}
{% endif %} diff --git a/templates/overrides/terminal/sidebar-nav.ninjatpl b/templates/overrides/terminal/sidebar-nav.ninjatpl new file mode 100644 index 0000000..3168491 --- /dev/null +++ b/templates/overrides/terminal/sidebar-nav.ninjatpl @@ -0,0 +1,15 @@ +{# 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" %} + +{% if highlightCurrent %}{% endif %} diff --git a/templates/overrides/terminal/social-links.ninjatpl b/templates/overrides/terminal/social-links.ninjatpl new file mode 100644 index 0000000..89599e1 --- /dev/null +++ b/templates/overrides/terminal/social-links.ninjatpl @@ -0,0 +1,14 @@ +{# terminal social-links — mono label, sharp neon-bordered chips; icon sprites preserved #} + diff --git a/templates/overrides/terminal/stats.ninjatpl b/templates/overrides/terminal/stats.ninjatpl new file mode 100644 index 0000000..a7050e1 --- /dev/null +++ b/templates/overrides/terminal/stats.ninjatpl @@ -0,0 +1,50 @@ +{# stats terminal override: scanline overlay, heading, HUD stat tiles, mono labels. data-stats + countUp script/attrs preserved byte-for-byte. #} +
+ +
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} +
+ {% for item in items %} +
+
{% if item.prefix %}{{ item.prefix }}{% endif %}{{ item.value }}{% if item.suffix %}{{ item.suffix }}{% endif %}
+ {% if item.label %}
{{ item.label }}
{% endif %} +
+ {% endfor %} +
+
+
+{% if countUp %}{% endif %} diff --git a/templates/overrides/terminal/team.ninjatpl b/templates/overrides/terminal/team.ninjatpl new file mode 100644 index 0000000..2ccc8cb --- /dev/null +++ b/templates/overrides/terminal/team.ninjatpl @@ -0,0 +1,22 @@ +{# team terminal override: heading, dashed-neon HUD member panels (lime), mono roles. {% img %} + social icon svgs + control flow preserved. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} +
+ {% for member in members %} +
+ {% if member.photo %}{% img member.photo alt=member.name class="h-full w-full object-cover" %}{% endif %} +
+ {% if member.name %}

{{ member.name }}

{% endif %} + {% if member.role %}

{{ member.role }}

{% endif %} + {% if member.bio %}

{{ member.bio }}

{% endif %} + {% if member.socials %}
+ {% for social in member.socials %}{% if social.url %}{% endif %}{% endfor %} +
{% endif %} +
+
+ {% endfor %} +
+
+
diff --git a/templates/overrides/terminal/testimonial.ninjatpl b/templates/overrides/terminal/testimonial.ninjatpl new file mode 100644 index 0000000..8e4ca41 --- /dev/null +++ b/templates/overrides/terminal/testimonial.ninjatpl @@ -0,0 +1,27 @@ +{# testimonial terminal override: heading, dashed-neon HUD quote cards. Rating svgs, {% img %}, and control flow preserved. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} +
+ {% for q in quotes %} +
+ {% if q.rating %}
+ + + + + +
{% endif %} +
{% if q.quote %}“{{ q.quote }}”{% endif %}
+
+ {% if q.avatar %}{% img q.avatar alt=q.name class="h-full w-full object-cover" %}{% endif %} + + {% if q.name %}{{ q.name }}{% endif %} + {% if q.role %}{{ q.role }}{% endif %} + +
+
+ {% endfor %} +
+
+
diff --git a/templates/overrides/terminal/text.ninjatpl b/templates/overrides/terminal/text.ninjatpl index 7d0a8fb..410277d 100644 --- a/templates/overrides/terminal/text.ninjatpl +++ b/templates/overrides/terminal/text.ninjatpl @@ -1 +1,2 @@ -
{{ text|safe }}
+{# terminal text — body font via terminal-prose #} +
{{ text|safe }}
diff --git a/templates/overrides/terminal/timeline.ninjatpl b/templates/overrides/terminal/timeline.ninjatpl new file mode 100644 index 0000000..6660e13 --- /dev/null +++ b/templates/overrides/terminal/timeline.ninjatpl @@ -0,0 +1,22 @@ +{# timeline terminal override: heading, mono step labels, neon markers. All field reads + control flow preserved. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} +
    + {% for step in steps %} +
  1. + + {% if marker == "numbered" %}{{ forloop.counter }}{% elif marker == "dot" %}{% else %}{% endif %} + +
    + {% if step.label %}

    {{ step.label }}

    {% endif %} + {% if step.title %}

    {{ step.title }}

    {% endif %} + {% if step.text %}

    {{ step.text }}

    {% endif %} + {% if step.media %}
    {% img step.media alt=step.title class="w-full h-auto object-cover" %}
    {% endif %} +
    +
  2. + {% endfor %} +
+
+
diff --git a/templates/overrides/terminal/utility-bar.ninjatpl b/templates/overrides/terminal/utility-bar.ninjatpl new file mode 100644 index 0000000..4e6cc80 --- /dev/null +++ b/templates/overrides/terminal/utility-bar.ninjatpl @@ -0,0 +1,10 @@ +{# 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 %}{% if it.icon %}::{{ it.icon }}::{% endif %}{% if it.label %}{{ it.label }}{% endif %}{% else %}{% if it.icon %}::{{ it.icon }}::{% endif %}{% if it.label %}{{ it.label }}{% endif %}{% endif %}{% endmacro %} +
+
+
{% for it in leftItems %}{{ ubitem(it) }}{% endfor %}
+
{% for it in rightItems %}{{ ubitem(it) }}{% endfor %}
+
+
diff --git a/templates/overrides/terminal/video-embed.ninjatpl b/templates/overrides/terminal/video-embed.ninjatpl new file mode 100644 index 0000000..0111384 --- /dev/null +++ b/templates/overrides/terminal/video-embed.ninjatpl @@ -0,0 +1,23 @@ +{# 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" %}
{% if caption %}
{{ caption }}
{% endif %}
{% endwith %} diff --git a/templates/terminal/article.ninjatpl b/templates/terminal/article.ninjatpl index 747e2f3..7032ba4 100644 --- a/templates/terminal/article.ninjatpl +++ b/templates/terminal/article.ninjatpl @@ -1,22 +1,23 @@ - - + + {{ head_html|safe }} {{ admin_banner_html|safe }} -
-
{{ slots.header|safe }}
-
-
-
- -
- {% if slots.main %}{{ slots.main|safe }}{% else %}

// no content blocks assigned to this page

{% endif %} -
+ +
{{ slots.header|safe }}
+
+
+ {% if slots.aside %} +
+ +
{% if slots.main %}{{ slots.main|safe }}{% else %}

// article body

{% endif %}
+
+ {% else %} +
{% if slots.main %}{{ slots.main|safe }}{% else %}

// article body

{% endif %}
+ {% endif %}
-
-
-
{{ slots.footer|safe }}
-
+ +
{{ slots.footer|safe }}
{{ body_end_html|safe }} diff --git a/templates/terminal/auth.ninjatpl b/templates/terminal/auth.ninjatpl new file mode 100644 index 0000000..c43ca0f --- /dev/null +++ b/templates/terminal/auth.ninjatpl @@ -0,0 +1,21 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} + +
{{ slots.header|safe }}
+
+
+
+
login
+
+ {% if slots.main %}{{ slots.main|safe }}{% else %}

// auth console

{% endif %} +
+
+
+
+
{{ slots.footer|safe }}
+ {{ body_end_html|safe }} + + diff --git a/templates/terminal/blog-index.ninjatpl b/templates/terminal/blog-index.ninjatpl new file mode 100644 index 0000000..4959993 --- /dev/null +++ b/templates/terminal/blog-index.ninjatpl @@ -0,0 +1,16 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} + +
{{ slots.header|safe }}
+
+
+ {% if slots.main %}{{ slots.main|safe }}{% else %}

// no posts yet

{% endif %} +
+
+
{{ slots.footer|safe }}
+ {{ body_end_html|safe }} + + diff --git a/templates/terminal/contact.ninjatpl b/templates/terminal/contact.ninjatpl new file mode 100644 index 0000000..6a7c145 --- /dev/null +++ b/templates/terminal/contact.ninjatpl @@ -0,0 +1,21 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} + +
{{ slots.header|safe }}
+
+
+
+
contact --send
+
+ {% if slots.main %}{{ slots.main|safe }}{% else %}

// add a contact-form block

{% endif %} +
+
+
+
+
{{ slots.footer|safe }}
+ {{ body_end_html|safe }} + + diff --git a/templates/terminal/default.ninjatpl b/templates/terminal/default.ninjatpl index 4cd03ce..79c4682 100644 --- a/templates/terminal/default.ninjatpl +++ b/templates/terminal/default.ninjatpl @@ -1,17 +1,21 @@ - - + + {{ head_html|safe }} {{ admin_banner_html|safe }} -
-
{{ slots.header|safe }}
-
-
- {% if slots.main %}{{ slots.main|safe }}{% else %}

// no content blocks assigned to this page

{% endif %} + +
{{ slots.header|safe }}
+
+
+
+
man page
+
+ {% if slots.main %}{{ slots.main|safe }}{% else %}

// no content blocks assigned to this page

{% endif %} +
+
+
-
-
{{ slots.footer|safe }}
-
+
{{ slots.footer|safe }}
{{ body_end_html|safe }} diff --git a/templates/terminal/full-width.ninjatpl b/templates/terminal/full-width.ninjatpl index 5c400b9..165acbf 100644 --- a/templates/terminal/full-width.ninjatpl +++ b/templates/terminal/full-width.ninjatpl @@ -1,13 +1,14 @@ - - + + {{ head_html|safe }} {{ admin_banner_html|safe }} -
{{ slots.header|safe }}
-
- {% if slots.main %}{{ slots.main|safe }}{% else %}

// no content blocks assigned to this page

{% endif %} + +
{{ slots.header|safe }}
+
+ {% if slots.main %}{{ slots.main|safe }}{% else %}

// no content blocks assigned to this page

{% endif %}
-
{{ slots.footer|safe }}
+
{{ slots.footer|safe }}
{{ body_end_html|safe }} diff --git a/templates/terminal/landing.ninjatpl b/templates/terminal/landing.ninjatpl index 97bf031..dfe3eba 100644 --- a/templates/terminal/landing.ninjatpl +++ b/templates/terminal/landing.ninjatpl @@ -1,20 +1,14 @@ - - + + {{ head_html|safe }} {{ admin_banner_html|safe }} -
-
{{ slots.hero|safe }}
-
+ +
{{ slots.header|safe }}
- {% if slots.main %}
{{ slots.main|safe }}
{% endif %} + {% if slots.main %}{{ slots.main|safe }}{% else %}

// add a hero block to start the boot sequence

{% endif %}
-
-
{{ slots.cta|safe }}
-
-
-
{{ slots.footer|safe }}
-
+ {{ body_end_html|safe }}