diff --git a/BUILD_REPORT.md b/BUILD_REPORT.md index 829fd5a..b449d29 100644 --- a/BUILD_REPORT.md +++ b/BUILD_REPORT.md @@ -1,143 +1,65 @@ # Noir — Build Report -Implementation pass: wave-1 scaffold of the `noir` theme plugin. +Wave A pass (WO-TF-022): re-persona from a photography/portfolio theme to the +LOCKED **detective / true-crime dossier** direction. Codeless `.bnp`. -Plugin slug: `noir` -Module path: `git.dev.alexdunmow.com/block/themes/noir` -SDK pinned to: `git.dev.alexdunmow.com/block/core v0.11.1` -Go directive: `go 1.26.4` -Tech style: templ (per spec §11) +Plugin slug: `noir` · kind: `theme` · version: `0.2.0` -## What landed +## Identity -### Metadata & build glue -- `plugin.mod` — name, display_name, scope `@themes`, kind `theme`, version `0.1.0`, categories `["templates", "media"]`, tags array (8 entries, includes the four spec-required minimums), `[compatibility] block_core = ">=0.11.0 <0.12.0"`. -- `go.mod` — pins `block/core v0.11.1` and `templ v0.3.1020`, mirrors gotham's indirect set, no `replace` directives. -- `Makefile` — local-only targets (`all`, `templ`, `clean`). No `rebuild` target. Default builds `noir.so` via `CGO_ENABLED=1 go build -buildmode=plugin`. -- `embed.go` — five canonical `//go:embed` directives plus a `ThemeCSSManifest()` that surfaces `assets/style.css` through `CSSManifest.InputCSSAppend` so the custom utilities (`.tracked-mono`, `.bleed`, `.hairline`, sprocket motif, lightbox overlay) survive Tailwind's content scanner. -- `registration.go` — exports `var Registration plugin.PluginRegistration` with all functions wired. +Case-file panels, typewriter type, redaction bars, evidence board and film +grain. High-key gallery by day (ink type, evidence-red accent); darkroom black by +night (bone type, amber safelight accent). Motion tier MID: JS scroll-reveal +(reduced-motion honored, static no-JS fallback) plus the builtin gallery lightbox +showpiece (no canvas). -### System & page templates -- `tr.RegisterSystemTemplate({Key: "noir", …})` — exactly once. -- Four `tr.RegisterPageTemplate("noir", …)` calls with the spec's slot sets: - - `default` — `["header", "main", "footer"]` - - `landing` — `["hero", "main", "cta", "footer"]` - - `article` — `["header", "main", "aside", "footer"]` - - `full-width` — `["header", "main", "footer"]` -- Each page renderer lives in `template.templ` and consumes the shared `bn.Head` / `bn.AdminBypassBanner` / `bn.BodyEnd` helpers, mirroring gotham. +## What shipped -### Blocks (6 theme-specific) -- `noir:lightbox_gallery` — grid + vanilla keyboard-aware lightbox overlay (Esc + Enter/Space, click-outside dismiss, ARIA-modal). -- `noir:contact_sheet` — sprocket-framed numbered grid; `data-frame-number` attribute on each frame for UAT §13.9. -- `noir:case_study` — sticky meta rail (client / year / credits) + image stack. -- `noir:caption_strip` — 10px mono full-width strip. -- `noir:image_pair` — 50/50 diptych with shared caption. -- `noir:footer` — dissolved-rail footer with optional social links. +- **49 mandatory template overrides** in `templates/overrides/noir/` + a + `template_overrides` entry per key in `manifest.yaml`. Each re-skins the frozen + builtin default, preserving every content field, provider var, custom tag and + ` +
+
+ {% if slots.main %}{{ slots.main|safe }}{% else %}

No content blocks assigned to this page.

{% endif %} +
+ +
+ {{ body_end_html|safe }} + diff --git a/templates/noir/auth.ninjatpl b/templates/noir/auth.ninjatpl new file mode 100644 index 0000000..34b77a2 --- /dev/null +++ b/templates/noir/auth.ninjatpl @@ -0,0 +1,18 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} +
+
+ + {% if site_settings.Logo %}{{ site_settings.LogoAlt }}{% else %}{{ site_settings.Title|default:"The Cold File" }}{% endif %} + +
Restricted access
+
{% if slots.main %}{{ slots.main|safe }}{% else %}

No form assigned to this page.

{% endif %}
+ {% if slots.footer %}
{{ slots.footer|safe }}
{% endif %} +
+
+ {{ body_end_html|safe }} + + diff --git a/templates/noir/blog-index.ninjatpl b/templates/noir/blog-index.ninjatpl new file mode 100644 index 0000000..886e88a --- /dev/null +++ b/templates/noir/blog-index.ninjatpl @@ -0,0 +1,35 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} + {% if slots.header %}
{{ slots.header|safe }}
{% endif %} +
+
+ {% if title %} +
+
Case index
+

{{ title }}

+ {% if page_meta.MetaDescription %}

{{ page_meta.MetaDescription }}

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

No posts published yet.

{% endif %} +
+
+ {% if slots.footer %}{% endif %} + {{ body_end_html|safe }} + + + diff --git a/templates/noir/contact.ninjatpl b/templates/noir/contact.ninjatpl new file mode 100644 index 0000000..97dc14e --- /dev/null +++ b/templates/noir/contact.ninjatpl @@ -0,0 +1,33 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} + {% if slots.header %}
{{ slots.header|safe }}
{% endif %} +
+
+ {% if slots.aside %} +
+
{% if slots.main %}{{ slots.main|safe }}{% endif %}
+ +
+ {% else %} +
{% if slots.main %}{{ slots.main|safe }}{% else %}

No content blocks assigned to this page.

{% endif %}
+ {% endif %} +
+
+ {% if slots.footer %}{% endif %} + {{ body_end_html|safe }} + + + diff --git a/templates/noir/default.ninjatpl b/templates/noir/default.ninjatpl index bc6e39d..29aceb0 100644 --- a/templates/noir/default.ninjatpl +++ b/templates/noir/default.ninjatpl @@ -1,65 +1,30 @@ - + {{ head_html|safe }} - + {{ admin_banner_html|safe }} -
-
{{ slots.header|safe }}
+
+
{{ slots.header|safe }}
-
- {% if slots.main %}{{ slots.main|safe }}{% else %}

No photographs assigned to this page.

{% endif %} +
+
+ {% 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/noir/full-width.ninjatpl b/templates/noir/full-width.ninjatpl index 3d017d3..c02cb61 100644 --- a/templates/noir/full-width.ninjatpl +++ b/templates/noir/full-width.ninjatpl @@ -1,65 +1,26 @@ - + {{ head_html|safe }} - + {{ admin_banner_html|safe }} -
-
{{ slots.header|safe }}
-
+
{{ slots.header|safe }}
- {% if slots.main %}{{ slots.main|safe }}{% else %}

No photographs assigned to this page.

{% endif %} + {% 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/noir/landing.ninjatpl b/templates/noir/landing.ninjatpl index 042fb21..cdaee02 100644 --- a/templates/noir/landing.ninjatpl +++ b/templates/noir/landing.ninjatpl @@ -1,64 +1,27 @@ - + {{ head_html|safe }} - + {{ admin_banner_html|safe }} -
{{ slots.hero|safe }}
+
{{ slots.hero|safe }}
- {% if slots.main %}
{{ slots.main|safe }}
{% endif %} + {% if slots.main %}
{{ slots.main|safe }}
{% endif %}
{{ slots.cta|safe }}
-
{{ slots.footer|safe }}
- +
+
{{ slots.footer|safe }}
+
{{ body_end_html|safe }} + diff --git a/templates/overrides/noir/announcement-bar.ninjatpl b/templates/overrides/noir/announcement-bar.ninjatpl new file mode 100644 index 0000000..8cc94ed --- /dev/null +++ b/templates/overrides/noir/announcement-bar.ninjatpl @@ -0,0 +1,21 @@ +{# noir override — announcement-bar; preserves variant, message, linkText, linkUrl, dismissable, data-bn-annkey/localStorage dismiss {% endif %} diff --git a/templates/overrides/noir/audio-embed.ninjatpl b/templates/overrides/noir/audio-embed.ninjatpl new file mode 100644 index 0000000..f22a728 --- /dev/null +++ b/templates/overrides/noir/audio-embed.ninjatpl @@ -0,0 +1,16 @@ +{# noir override — audio-embed; preserves url, media, artwork, title, artist, download, data-bn-audio, data-audio-url, data-audio-media, {% img %} call, and the facade IIFE verbatim. Only the player card restyled (square evidence panel). #} +
{% 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/noir/auth-form.ninjatpl b/templates/overrides/noir/auth-form.ninjatpl new file mode 100644 index 0000000..1199e0d --- /dev/null +++ b/templates/overrides/noir/auth-form.ninjatpl @@ -0,0 +1,33 @@ +{# noir override — auth-form; preserves context.isPublicLoggedIn/publicUsername, #auth-message, tab onclick toggles, #login-panel/#register-panel, /api/auth/login + /api/auth/register HTMX wiring, _captcha_widget, hx-disabled-elt, forgot-password link, default_tab. #} +{% if context.isPublicLoggedIn %} +
+

You are signed in as {{ context.publicUsername }}

+ My account +
+{% else %} +
+
+
+ + +
+
+
+
+
+ {% if _captcha_widget %}{{ _captcha_widget|safe }}{% endif %} + +

Forgot password?

+
+
+
+
+
+
+
+ {% if _captcha_widget %}{{ _captcha_widget|safe }}{% endif %} + +
+
+
+{% endif %} diff --git a/templates/overrides/noir/auth-status.ninjatpl b/templates/overrides/noir/auth-status.ninjatpl new file mode 100644 index 0000000..feb8a66 --- /dev/null +++ b/templates/overrides/noir/auth-status.ninjatpl @@ -0,0 +1,31 @@ +{# noir override — auth-status; preserves context.isPublicLoggedIn/publicDisplayName/publicUsername, dropdown onclick, #auth-dropdown-menu, /api/auth/logout, outside-click + +{% else %} + +{% endif %} diff --git a/templates/overrides/noir/author-bio-hero.ninjatpl b/templates/overrides/noir/author-bio-hero.ninjatpl new file mode 100644 index 0000000..a23b9ef --- /dev/null +++ b/templates/overrides/noir/author-bio-hero.ninjatpl @@ -0,0 +1,21 @@ +{# noir override — author-bio-hero; preserves author.* fields, showAvatar/showPostCount/showSocial, {% img %}, all social links (website/twitter/linkedin/github). #} +{% 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/noir/author-bio.ninjatpl b/templates/overrides/noir/author-bio.ninjatpl new file mode 100644 index 0000000..efddca6 --- /dev/null +++ b/templates/overrides/noir/author-bio.ninjatpl @@ -0,0 +1,14 @@ +{# noir override — author-bio; preserves post.author_name/author_slug/author_avatar/author_bio, heading/showAvatar, {% img %}. #} +{% if post.author_name %} + +{% endif %} diff --git a/templates/overrides/noir/blog-hero.ninjatpl b/templates/overrides/noir/blog-hero.ninjatpl new file mode 100644 index 0000000..3bb49be --- /dev/null +++ b/templates/overrides/noir/blog-hero.ninjatpl @@ -0,0 +1,24 @@ +{# noir override — blog-hero; preserves title, subtitle, posts provider loop, post.featured_image_url/.url/.title/.excerpt/.author_name/.published_at, showExcerpt, forloop.First. {% img %} kept verbatim. #} +
+
+
+{% if title %}

{{ title }}

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

{{ subtitle }}

{% endif %} +
+{% if posts %} +
+{% for post in posts %} + +{% endfor %} +
+{% endif %} +
+
diff --git a/templates/overrides/noir/breadcrumbs.ninjatpl b/templates/overrides/noir/breadcrumbs.ninjatpl new file mode 100644 index 0000000..201aa92 --- /dev/null +++ b/templates/overrides/noir/breadcrumbs.ninjatpl @@ -0,0 +1,13 @@ +{# noir override — breadcrumbs; preserves breadcrumbs provider loop, showHome/homeLabel/separator/template/showCurrent, item.is_current/url/label. #} +{% set arrows = template == "arrows" %} + diff --git a/templates/overrides/noir/button.ninjatpl b/templates/overrides/noir/button.ninjatpl index 7c9096a..0fb0743 100644 --- a/templates/overrides/noir/button.ninjatpl +++ b/templates/overrides/noir/button.ninjatpl @@ -1 +1,12 @@ -{% if url or href %}{% if text %}{{ text }}{% else %}{{ label }}{% endif %}{% else %}{% endif %} +{# button override — square case-file button. Fields (new builtin contract, OVERRIDE-CONTRACT §4): link, style, label, size, icon, iconPosition, newTab, align. The builtin {% button %} tag is replaced by a styled so the noir square-outline look applies. #} +{% with align=align|default:"left" %} + +{% endwith %} diff --git a/templates/overrides/noir/card.ninjatpl b/templates/overrides/noir/card.ninjatpl index 2a665b5..c4eceb1 100644 --- a/templates/overrides/noir/card.ninjatpl +++ b/templates/overrides/noir/card.ninjatpl @@ -1 +1,4 @@ -
{% if title %}

{{ title }}

{% endif %}{% if body %}
{{ body|safe }}
{% elif text %}
{{ text|safe }}
{% endif %}
+{# card override — manila case-file panel. Fields mirror builtin: layout, link, media, title, text, linkLabel. Media via {% img %}. #} +{% with layout=layout|default:"vertical" %} + +{% endwith %} diff --git a/templates/overrides/noir/category-list.ninjatpl b/templates/overrides/noir/category-list.ninjatpl new file mode 100644 index 0000000..4fde5a4 --- /dev/null +++ b/templates/overrides/noir/category-list.ninjatpl @@ -0,0 +1,18 @@ +{# noir override — category-list; preserves categories provider loop, style/pills, heading, showCount, cat.url/.name/.post_count. #} +{% 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/noir/contact-card.ninjatpl b/templates/overrides/noir/contact-card.ninjatpl new file mode 100644 index 0000000..fd0ad63 --- /dev/null +++ b/templates/overrides/noir/contact-card.ninjatpl @@ -0,0 +1,18 @@ +{# noir override — contact-card; preserves heading, description, columns, channels[].icon/.label/.value/.link. Icon split markup kept verbatim. #} +
+
+ {% 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/noir/contact-form.ninjatpl b/templates/overrides/noir/contact-form.ninjatpl new file mode 100644 index 0000000..82fdcef --- /dev/null +++ b/templates/overrides/noir/contact-form.ninjatpl @@ -0,0 +1,40 @@ +{# noir override — contact-form; ALL form wiring preserved verbatim from builtin: bid, #contact-form-{{ bid }}, form.submitted/errors/message, {% form %} action/hx attrs, field loop (textarea/select/input), honeypot website input, submit target. Only inputs restyled to noir. #} +{% 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/noir/countdown.ninjatpl b/templates/overrides/noir/countdown.ninjatpl new file mode 100644 index 0000000..0885fb5 --- /dev/null +++ b/templates/overrides/noir/countdown.ninjatpl @@ -0,0 +1,49 @@ +{# noir override — countdown; preserves heading, intro, target, variant, labelDays/Hours/Minutes/Seconds, expiredMessage, expiredUrl, expiredLabel, data-countdown/-timer/-expired, data-target, data-unit, arrow svg, and the timer IIFE verbatim. Only boxes/labels restyled. #} +
+
+ {% 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/noir/cta.ninjatpl b/templates/overrides/noir/cta.ninjatpl new file mode 100644 index 0000000..1bfd8f4 --- /dev/null +++ b/templates/overrides/noir/cta.ninjatpl @@ -0,0 +1,15 @@ +{# noir override — cta; preserves heading, text, background (band/card/split), primaryUrl/primaryLabel, secondaryUrl/secondaryLabel. Arrow svg markup verbatim; band adds .noir-grain. #} +
+
+
+
+ {% 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/noir/divider.ninjatpl b/templates/overrides/noir/divider.ninjatpl new file mode 100644 index 0000000..80884aa --- /dev/null +++ b/templates/overrides/noir/divider.ninjatpl @@ -0,0 +1,10 @@ +{# noir override — divider; preserves variant (line/ornament/spacer-rule), weight, width, icon. Ornament icon via ::{{ icon }}:sm::. #} +{% 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/noir/event-list.ninjatpl b/templates/overrides/noir/event-list.ninjatpl new file mode 100644 index 0000000..8502e9b --- /dev/null +++ b/templates/overrides/noir/event-list.ninjatpl @@ -0,0 +1,41 @@ +{# noir override — event-list; preserves heading, intro, layout, events[].past/.media/.datetime/.start/.end/.timezone/.title/.venue/.description/.link/.linkLabel, hidePast. Icon markup kept verbatim. #} +
+
+ {% 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/noir/faq.ninjatpl b/templates/overrides/noir/faq.ninjatpl new file mode 100644 index 0000000..0b2af0b --- /dev/null +++ b/templates/overrides/noir/faq.ninjatpl @@ -0,0 +1,18 @@ +{# noir override — faq; preserves heading, intro, variant, items[].question/.answer. Chevron svg + details/summary markup verbatim. #} +
+
+ {% if heading %}

{{ heading }}

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

{{ intro }}

{% endif %} +
+ {% for item in items %} +
+ + {{ item.question }} + + +
{{ item.answer|safe }}
+
+ {% endfor %} +
+
+
diff --git a/templates/overrides/noir/feature-grid.ninjatpl b/templates/overrides/noir/feature-grid.ninjatpl new file mode 100644 index 0000000..225e1d6 --- /dev/null +++ b/templates/overrides/noir/feature-grid.ninjatpl @@ -0,0 +1,18 @@ +{# noir override — feature-grid; preserves eyebrow, heading, intro, columns, layout, items[].icon/.title/.text/.linkUrl/.linkLabel, icon split markup verbatim. #} +
+
+ {% 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/noir/featured-posts.ninjatpl b/templates/overrides/noir/featured-posts.ninjatpl new file mode 100644 index 0000000..da91912 --- /dev/null +++ b/templates/overrides/noir/featured-posts.ninjatpl @@ -0,0 +1,22 @@ +{# noir override — featured-posts; preserves heading, columns, posts provider loop, post.featured_image_url/.url/.title/.excerpt/.author_name/.published_at/.reading_time, showFeaturedImage/showExcerpt/showAuthor/showDate. {% img %} kept verbatim. #} +
+
+{% 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/noir/footer.ninjatpl b/templates/overrides/noir/footer.ninjatpl new file mode 100644 index 0000000..f5bd045 --- /dev/null +++ b/templates/overrides/noir/footer.ninjatpl @@ -0,0 +1,40 @@ +{# noir override — footer; preserves layout, columns, social_items, legal_items, {% signup_form %}, sociallinks/newsletter/legalbar macros, all provider vars verbatim. #} +{% 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/noir/gallery.ninjatpl b/templates/overrides/noir/gallery.ninjatpl new file mode 100644 index 0000000..eac267a --- /dev/null +++ b/templates/overrides/noir/gallery.ninjatpl @@ -0,0 +1,52 @@ +{# noir override — gallery; preserves layout, gap, columns, lightbox, images[].src/.alt/.caption, data-bn-gallery, data-lightbox, data-bn-gallery-item, data-caption, {% img %} calls, and the full lightbox IIFE verbatim. Only grid tiles restyled (square hairline frames, grayscale). #} +{% with layout=layout|default:"grid" gap=gap|default:"md" cols=columns|default:3 %}{% if lightbox %}{% endif %}{% endwith %} diff --git a/templates/overrides/noir/heading.ninjatpl b/templates/overrides/noir/heading.ninjatpl index de2ff99..f310cb2 100644 --- a/templates/overrides/noir/heading.ninjatpl +++ b/templates/overrides/noir/heading.ninjatpl @@ -1 +1,7 @@ -{% if level|integer == 1 %}

{{ text }}

{% elif level|integer == 3 %}

{{ text }}

{% elif level|integer == 4 %}

{{ text }}

{% elif level|integer == 5 %}
{{ text }}
{% elif level|integer == 6 %}
{{ text }}
{% else %}

{{ text }}

{% endif %} +{# heading override — typed case-file header. Fields mirror builtin: text, level, textClass. #} +{% 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/noir/hero.ninjatpl b/templates/overrides/noir/hero.ninjatpl new file mode 100644 index 0000000..ddf1a4a --- /dev/null +++ b/templates/overrides/noir/hero.ninjatpl @@ -0,0 +1,39 @@ +{# noir override — hero; preserves styled-text {% set %} lines, herocontent macro, split/centered branches, {% img %} background calls + overlay conditionals, trustLogos loop verbatim. #} +{% 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" %}{% 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 %}
{% endif %} +
{{ herocontent(align) }}
+
+{% endif %} diff --git a/templates/overrides/noir/hours-location.ninjatpl b/templates/overrides/noir/hours-location.ninjatpl new file mode 100644 index 0000000..9a6e385 --- /dev/null +++ b/templates/overrides/noir/hours-location.ninjatpl @@ -0,0 +1,31 @@ +{# noir override — hours-location; preserves heading, hours[].day/.hours/.closed, specialHoursNote, address, phone, directionsUrl, mapEmbedUrl, mapButtonLabel. Map facade + + {% endif %} + + + diff --git a/templates/overrides/noir/image.ninjatpl b/templates/overrides/noir/image.ninjatpl index b5195de..9f2b7aa 100644 --- a/templates/overrides/noir/image.ninjatpl +++ b/templates/overrides/noir/image.ninjatpl @@ -1 +1,11 @@ -
{% if src %}{{ alt }}{% endif %}{% if caption %}
{{ caption }}
{% elif title %}
{{ title }}
{% endif %}
+{# image override — square evidence frame with a typed mono caption. Fields mirror builtin: src, alt, caption, size, align, aspectRatio, objectFit. Media resolved via {% img %} (the media filter is not registered in the block render path). #} +{% with size=size|default:"full" align=align|default:"center" objectFit=objectFit|default:"cover" %} +
+
+
+ {% 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/noir/logo-strip.ninjatpl b/templates/overrides/noir/logo-strip.ninjatpl new file mode 100644 index 0000000..95d6ca4 --- /dev/null +++ b/templates/overrides/noir/logo-strip.ninjatpl @@ -0,0 +1,16 @@ +{# noir override — logo-strip; preserves heading, marquee, grayscale, logos[].url/.image/.alt, {% img %} calls, marquee keyframes {% endif %} diff --git a/templates/overrides/noir/menu-list.ninjatpl b/templates/overrides/noir/menu-list.ninjatpl new file mode 100644 index 0000000..632355a --- /dev/null +++ b/templates/overrides/noir/menu-list.ninjatpl @@ -0,0 +1,29 @@ +{# noir override — menu-list; preserves heading, intro, columns, sections[].title/.note/.items[].name/.badges/.description/.price, currency, footnote. #} +
+
+ {% if heading %}

{{ heading }}

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

{{ intro }}

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

{{ footnote }}

{% endif %} +
+
diff --git a/templates/overrides/noir/navbar.ninjatpl b/templates/overrides/noir/navbar.ninjatpl new file mode 100644 index 0000000..567a3ce --- /dev/null +++ b/templates/overrides/noir/navbar.ninjatpl @@ -0,0 +1,104 @@ +{# noir override — navbar; preserves variant/stickyBehavior/megaMenu/logoType/logoText/menu.items/cta vars, fixed drawer ids (bn-navbar-toggle/-drawer/-overlay), all data-* attrs, + diff --git a/templates/overrides/noir/newsletter-signup.ninjatpl b/templates/overrides/noir/newsletter-signup.ninjatpl new file mode 100644 index 0000000..0536703 --- /dev/null +++ b/templates/overrides/noir/newsletter-signup.ninjatpl @@ -0,0 +1,10 @@ +{# noir override — newsletter-signup; preserves {% signup_form %} wiring verbatim (list/source/submit/success_message/placeholder), layout/title/description. #} +
+
+
+{% 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/noir/page-suggestions.ninjatpl b/templates/overrides/noir/page-suggestions.ninjatpl new file mode 100644 index 0000000..bd4f04d --- /dev/null +++ b/templates/overrides/noir/page-suggestions.ninjatpl @@ -0,0 +1,21 @@ +{# noir override — page-suggestions; preserves suggestions[].slug/.title/.excerpt, title/showDescription/emptyMessage. #} +{% if suggestions %} +
+ {% if title %}

{{ title }}

{% endif %} +
    + {% for suggestion in suggestions %} +
  • + ::lucide:chevron-right:sm:: +
    + {{ suggestion.title }} + {% if showDescription and suggestion.excerpt %}

    {{ suggestion.excerpt }}

    {% endif %} +
    +
  • + {% endfor %} +
+
+{% elif emptyMessage %} +
+

{{ emptyMessage }}

+
+{% endif %} diff --git a/templates/overrides/noir/password-reset-form.ninjatpl b/templates/overrides/noir/password-reset-form.ninjatpl new file mode 100644 index 0000000..961bd45 --- /dev/null +++ b/templates/overrides/noir/password-reset-form.ninjatpl @@ -0,0 +1,13 @@ +{# noir override — password-reset-form; preserves #reset-message, /api/auth/request-password-reset (email) HTMX wiring, #request-reset-panel, hx-disabled-elt, back-to-sign-in link. #} +
+
+
+

Reset password

+

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

+
+
+ +
+

Back to sign in

+
+
diff --git a/templates/overrides/noir/popular-posts.ninjatpl b/templates/overrides/noir/popular-posts.ninjatpl new file mode 100644 index 0000000..0239b34 --- /dev/null +++ b/templates/overrides/noir/popular-posts.ninjatpl @@ -0,0 +1,16 @@ +{# noir override — popular-posts; preserves posts provider loop, heading, showRank/showThumbnail, forloop.Counter, post.featured_image_url/.url/.title/.published_at. {% img %} kept verbatim. #} +
+{% 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/noir/post-hero.ninjatpl b/templates/overrides/noir/post-hero.ninjatpl new file mode 100644 index 0000000..ac481e8 --- /dev/null +++ b/templates/overrides/noir/post-hero.ninjatpl @@ -0,0 +1,20 @@ +{# noir override — post-hero; preserves post.* reads, {% img %}, showExcerpt/showMeta/align, featured-image + no-image branches. #} +{% 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 grayscale" 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/noir/post-metadata.ninjatpl b/templates/overrides/noir/post-metadata.ninjatpl new file mode 100644 index 0000000..176cea1 --- /dev/null +++ b/templates/overrides/noir/post-metadata.ninjatpl @@ -0,0 +1,9 @@ +{# noir override — post-metadata; preserves post.author_name/author_avatar/published_at/reading_time/categories, showAuthor/showAvatar/showDate/showReadingTime, {% img %}. #} +{% 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 object-cover grayscale" %}{% 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/noir/pricing.ninjatpl b/templates/overrides/noir/pricing.ninjatpl new file mode 100644 index 0000000..5278c47 --- /dev/null +++ b/templates/overrides/noir/pricing.ninjatpl @@ -0,0 +1,26 @@ +{# noir override — pricing; preserves heading, intro, tiers[].highlighted/.badge/.name/.price/.period/.description/.features/.ctaUrl/.ctaLabel, currency, billingNote. Check icon markup verbatim. #} +
+
+ {% 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 %}{% if tier.highlighted %}{{ tier.ctaLabel }}{% else %}{{ tier.ctaLabel }}{% endif %}{% endif %} +
+ {% endfor %} +
+ {% if billingNote %}

{{ billingNote }}

{% endif %} +
+
diff --git a/templates/overrides/noir/quote.ninjatpl b/templates/overrides/noir/quote.ninjatpl new file mode 100644 index 0000000..cb2e28d --- /dev/null +++ b/templates/overrides/noir/quote.ninjatpl @@ -0,0 +1,2 @@ +{# noir override — quote; preserves quote (quote|safe), style (block/pull/inline-card), align, cite, attribution, role. #} +{% 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/noir/related-posts.ninjatpl b/templates/overrides/noir/related-posts.ninjatpl new file mode 100644 index 0000000..734b332 --- /dev/null +++ b/templates/overrides/noir/related-posts.ninjatpl @@ -0,0 +1,17 @@ +{# noir override — related-posts; preserves posts provider loop, heading, columns, post.featured_image_url/.url/.title/.published_at. {% img %} kept verbatim. #} +{% if posts %} +
+
+

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

+
+{% for post in posts %} + +{% endfor %} +
+
+
+{% endif %} diff --git a/templates/overrides/noir/rich-section.ninjatpl b/templates/overrides/noir/rich-section.ninjatpl new file mode 100644 index 0000000..b14764e --- /dev/null +++ b/templates/overrides/noir/rich-section.ninjatpl @@ -0,0 +1,14 @@ +{# noir override — rich-section; preserves background, mediaPosition, eyebrow, heading, body, ctaUrl/ctaLabel, media/mediaAlt, {% img %} + icon markup verbatim. #} +
+
+
+ {% 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/noir/rich-text.ninjatpl b/templates/overrides/noir/rich-text.ninjatpl new file mode 100644 index 0000000..b330efe --- /dev/null +++ b/templates/overrides/noir/rich-text.ninjatpl @@ -0,0 +1,2 @@ +{# noir override — rich-text; preserves field html (html|safe). Source Serif prose column with evidence-red [&_a] links. #} +{% if html %}
{{ html|safe }}
{% endif %} diff --git a/templates/overrides/noir/sidebar-nav.ninjatpl b/templates/overrides/noir/sidebar-nav.ninjatpl new file mode 100644 index 0000000..9489ff4 --- /dev/null +++ b/templates/overrides/noir/sidebar-nav.ninjatpl @@ -0,0 +1,13 @@ +{# noir override — sidebar-nav; preserves menu.items loop, item.* fields, data-bn-sidebar-link, highlight-current {% endif %} diff --git a/templates/overrides/noir/social-links.ninjatpl b/templates/overrides/noir/social-links.ninjatpl new file mode 100644 index 0000000..1142704 --- /dev/null +++ b/templates/overrides/noir/social-links.ninjatpl @@ -0,0 +1,14 @@ +{# noir override — social-links; preserves heading, style, links[].url/.label/.network. Icon split markup kept verbatim. #} + diff --git a/templates/overrides/noir/stats.ninjatpl b/templates/overrides/noir/stats.ninjatpl new file mode 100644 index 0000000..0be5444 --- /dev/null +++ b/templates/overrides/noir/stats.ninjatpl @@ -0,0 +1,49 @@ +{# noir override — stats; preserves heading, intro, columns, countUp, items[].prefix/.value/.suffix/.label, data-stats/data-countup-value + count-up {% endif %} diff --git a/templates/overrides/noir/team.ninjatpl b/templates/overrides/noir/team.ninjatpl new file mode 100644 index 0000000..bb0d0f6 --- /dev/null +++ b/templates/overrides/noir/team.ninjatpl @@ -0,0 +1,22 @@ +{# noir override — team; preserves heading, intro, layout, columns, members[].photo/.name/.role/.bio/.socials[].url/.icon. Avatars keep rounded-full; social svg use markup verbatim. #} +
+
+ {% 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/noir/testimonial.ninjatpl b/templates/overrides/noir/testimonial.ninjatpl new file mode 100644 index 0000000..c134206 --- /dev/null +++ b/templates/overrides/noir/testimonial.ninjatpl @@ -0,0 +1,28 @@ +{# noir override — testimonial; preserves heading, layout, quotes[].rating/.quote/.avatar/.name/.role, star markup + {% img %} avatar verbatim. #} +
+
+ {% 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/noir/text.ninjatpl b/templates/overrides/noir/text.ninjatpl index abd9ea0..9515102 100644 --- a/templates/overrides/noir/text.ninjatpl +++ b/templates/overrides/noir/text.ninjatpl @@ -1 +1,2 @@ -
{{ text|safe }}
+{# text override — Source Serif body prose with evidence-red links. Fields: text, proseClass (mirror builtin). #} +
{{ text|safe }}
diff --git a/templates/overrides/noir/timeline.ninjatpl b/templates/overrides/noir/timeline.ninjatpl new file mode 100644 index 0000000..23c4123 --- /dev/null +++ b/templates/overrides/noir/timeline.ninjatpl @@ -0,0 +1,22 @@ +{# noir override — timeline; preserves heading, intro, orientation, marker, steps[].label/.title/.text/.media. Marker rank-circles keep rounded-full; flag svg + {% img %} verbatim. #} +
+
+ {% 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/noir/utility-bar.ninjatpl b/templates/overrides/noir/utility-bar.ninjatpl new file mode 100644 index 0000000..ece084d --- /dev/null +++ b/templates/overrides/noir/utility-bar.ninjatpl @@ -0,0 +1,10 @@ +{# noir override — utility-bar; preserves variant, showOnMobile, leftItems/rightItems {icon,label,link}, ubitem macro + ::pack:name:: icon shorthand. #} +{% set ubcls = "bg-muted text-muted-foreground border-b border-border" %} +{% 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/noir/video-embed.ninjatpl b/templates/overrides/noir/video-embed.ninjatpl new file mode 100644 index 0000000..48f63f7 --- /dev/null +++ b/templates/overrides/noir/video-embed.ninjatpl @@ -0,0 +1,23 @@ +{# noir override — video-embed; preserves aspect, url, media, poster, title, caption, data-bn-video, data-video-url, data-video-media, {% img %} calls, and the facade IIFE verbatim. Only frame/button restyled (square evidence frame, grayscale poster). #} +{% with aspect=aspect|default:"16:9" %}
{% if caption %}
{{ caption }}
{% endif %}
{% endwith %}