diff --git a/RECOMMENDED_FONTS.md b/RECOMMENDED_FONTS.md index f07e1e3..2a13e3e 100644 --- a/RECOMMENDED_FONTS.md +++ b/RECOMMENDED_FONTS.md @@ -1,47 +1,39 @@ -# Earthen — recommended fonts +# Earthen — fonts -This theme ships `fonts.json = []` per the wave-1 fonts policy -(see `themes/docs/FONTS.md`). The CSS uses -`var(--font-heading)` / `var(--font-body)` / `var(--font-mono)` with a -fallback stack so the page reads close to the intended aesthetic before -the admin assigns fonts. +Earthen **bundles** its display and body faces as `source=template` rows via +`fonts.json`, so the theme reads correctly on activation with no admin setup. +All CSS references fonts through `var(--font-heading)` / `var(--font-body)` / +`var(--font-mono)` with a fallback stack, so the admin can still override any +role from the Typography panel. -Configure these in the admin Typography panel to match the spec's -intended look. +## Bundled faces (ship with the theme) -## Display / heading — assign to `Heading` +| Role | Family | Weights | File | License | +|------|--------|---------|------|---------| +| Heading | **Fraunces** | 400, 600 | `assets/fonts/fraunces-latin-400.woff2`, `fraunces-latin-600.woff2` | SIL Open Font License 1.1 (`assets/fonts/OFL.txt`) | +| Body | **Work Sans** | 400, 600 | `assets/fonts/worksans-latin-400.woff2`, `worksans-latin-600.woff2` | SIL Open Font License 1.1 (`assets/fonts/OFL.txt`) | -Primary pick: +Fraunces gives the organic, slightly wobbly display voice the persona wants; +Work Sans keeps body copy warm but legible for donation and report pages. Both +are OFL, subset to the Latin range to stay inside the marketplace size gate. -- **Fraunces** — Source: `google:Fraunces`. - Open the Typography panel, switch to the Google Fonts tab, search - "Fraunces", click Add, then assign to Heading. (OFL, free to host.) +## Fallback stacks (used before/without admin fonts) -Spec-preferred display face (commercial): +- Heading: `var(--font-heading, "Fraunces", "Playfair Display", Georgia, serif)` +- Body: `var(--font-body, "Work Sans", ui-sans-serif, system-ui, sans-serif)` +- Mono: `var(--font-mono, "JetBrains Mono", ui-monospace, monospace)` -- **Recoleta** — Source: `upload:Recoleta`. - Recoleta is a commercial face from Latinotype. Purchase the desktop + - webfont licence, upload the woff2 in the Fonts panel, then assign to - Heading. Until then Fraunces is the fallback used by the theme CSS. +## Optional admin choices -## Body — assign to `Body` - -- **Spectral** — Source: `google:Spectral`. - Open the Typography panel, Google Fonts tab, search "Spectral", - click Add, assign to Body. (OFL, free to host.) - -## Mono / data captions — assign to `Mono` - -- **JetBrains Mono** — Source: `google:JetBrains Mono`. - Open the Typography panel, Google Fonts tab, search "JetBrains Mono", - click Add, assign to Mono. (OFL, free to host.) +- **Mono** is not bundled. If your content uses monospace (data captions, code), + add **JetBrains Mono** (`google:JetBrains Mono`, OFL) from the Typography panel + Google Fonts tab and assign it to Mono. Until then the system mono stack applies. +- **Alegreya** (`google:Alegreya`, OFL) is a good serif alternative for Body if you + want a more literary, long-read feel for field journals. Assign it to Body to swap. ## Notes -- All three Google-Font picks are part of the curated picker list. - No manual upload required for the OFL families. -- A future build pass may bundle Recoleta woff2s directly via a - populated `fonts.json` once the licence is confirmed. -- The CSS fallback stack inside the theme (`"Fraunces", "Playfair Display", - Georgia, serif` for headings; `"Spectral", Georgia, serif` for body) - keeps the moss/clay character visible during the cold-load window. +- Do not hardcode a `font-family` anywhere. Every family flows through the CSS + variables so the admin font picker keeps working. +- Bundled woff2 files are subset to Latin. Extend the subset only if you serve + non-Latin content, and keep the total artifact within the registry size gate. diff --git a/RECOMMENDED_ICONS.md b/RECOMMENDED_ICONS.md new file mode 100644 index 0000000..00238f3 --- /dev/null +++ b/RECOMMENDED_ICONS.md @@ -0,0 +1,23 @@ +# Earthen — icon packs + +Earthen renders icons through the sprite system only +(``, or the `::pack:name:size::` +shorthand in templates). It does **not** inline icon SVGs. The theme's botanical +line-art (fern, seed, root motifs) is decorative illustration, not icons, and is +drawn inline on purpose. + +## Required pack + +- **Lucide** — declared in `plugin.mod` `required_icon_packs = ["lucide"]`. + Lucide ships bundled with the CMS, so no action is normally needed. If it is + missing on your instance, open `/admin/icons`, install **Lucide**, and enable it. + +Block schemas that expose an icon picker default to Lucide names, so the theme +renders correctly out of the box. When the loader begins honoring +`required_icon_packs`, Lucide will be installed automatically on activation. + +## Optional + +No other packs are required. If you add icons from another pack in block content, +install that pack via `/admin/icons` first, then reference it as `":"` +in the block's icon field. diff --git a/assets/fonts/OFL.txt b/assets/fonts/OFL.txt new file mode 100644 index 0000000..0bc9995 --- /dev/null +++ b/assets/fonts/OFL.txt @@ -0,0 +1,111 @@ +Bundled fonts — SIL Open Font License 1.1 +========================================= + +This directory bundles latin-subset woff2 files for three families, all +licensed under the SIL Open Font License, Version 1.1 (OFL-1.1). + +Copyright notices +----------------- +Fraunces: Copyright 2018 The Fraunces Project Authors (https://github.com/undercasetype/Fraunces) +Work Sans: Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) +Caveat: Copyright 2019 The Caveat Project Authors (https://github.com/googlefonts/caveat) + +Files +----- +fraunces-latin-400.woff2, fraunces-latin-600.woff2 — Fraunces (heading) +worksans-latin-400.woff2, worksans-latin-600.woff2 — Work Sans (body) +caveat-latin-400.woff2, caveat-latin-700.woff2 — Caveat (hand-drawn accents) + +Full license text (SIL OFL 1.1) +------------------------------- +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/fonts/README.md b/assets/fonts/README.md index c8daa74..dd353ce 100644 --- a/assets/fonts/README.md +++ b/assets/fonts/README.md @@ -1,5 +1,9 @@ # Earthen fonts -Reserved for woff2 bundles in a future build pass. The wave-1 build ships -`fonts.json = []` per the FONTS.md policy; admins assign Fraunces / -Spectral / JetBrains Mono through the typography picker. +Bundled woff2 faces, served as `source=template` rows via `../../fonts.json`: + +- **Fraunces** 400/600 (`fraunces-latin-400.woff2`, `fraunces-latin-600.woff2`) — heading. +- **Work Sans** 400/600 (`worksans-latin-400.woff2`, `worksans-latin-600.woff2`) — body. + +Both are SIL Open Font License 1.1 (see `OFL.txt`), subset to Latin. See +`../../RECOMMENDED_FONTS.md` for admin assignment and the mono option. diff --git a/assets/fonts/fraunces-latin-400.woff2 b/assets/fonts/fraunces-latin-400.woff2 new file mode 100644 index 0000000..83cbd8f Binary files /dev/null and b/assets/fonts/fraunces-latin-400.woff2 differ diff --git a/assets/fonts/fraunces-latin-600.woff2 b/assets/fonts/fraunces-latin-600.woff2 new file mode 100644 index 0000000..58cee1f Binary files /dev/null and b/assets/fonts/fraunces-latin-600.woff2 differ diff --git a/assets/fonts/worksans-latin-400.woff2 b/assets/fonts/worksans-latin-400.woff2 new file mode 100644 index 0000000..02a6a42 Binary files /dev/null and b/assets/fonts/worksans-latin-400.woff2 differ diff --git a/assets/fonts/worksans-latin-600.woff2 b/assets/fonts/worksans-latin-600.woff2 new file mode 100644 index 0000000..1ec3048 Binary files /dev/null and b/assets/fonts/worksans-latin-600.woff2 differ diff --git a/assets/style.css b/assets/style.css index db57ef7..f6b99f4 100644 --- a/assets/style.css +++ b/assets/style.css @@ -102,9 +102,9 @@ color: hsl(var(--muted-foreground)); } -/* Ensure article main column uses the Spectral body fallback. */ +/* Ensure article main column uses the Work Sans body fallback. */ .earthen-article { - font-family: var(--font-body, "Spectral", Georgia, serif); + font-family: var(--font-body, "Work Sans", ui-sans-serif, system-ui, sans-serif); color: hsl(var(--foreground)); } @@ -112,3 +112,69 @@ .earthen-glyph { color: hsl(var(--primary)); } + +/* Organic blob — soft asymmetric corners for cards, media frames and hero art. + The signature earthen shape: hand-crafted, never a hard rectangle. */ +.earthen-blob { + border-radius: 42% 58% 62% 38% / 42% 44% 56% 58%; +} +.earthen-blob-soft { + border-radius: 1.75rem 2.25rem 1.75rem 2.25rem; +} + +/* Soft card / panel frame — cream surface with a hairline botanical border. */ +.earthen-panel { + background-color: hsl(var(--card)); + color: hsl(var(--card-foreground)); + border: 1px solid hsl(var(--border)); + box-shadow: inset 0 0 0 1px hsl(var(--border) / 0.35); +} + +/* Terracotta seedling button — the primary call to action across the theme. */ +.earthen-cta { + background-color: hsl(var(--accent)); + color: hsl(var(--accent-foreground)); + border-radius: 0.75rem; + transition: box-shadow 200ms ease, transform 120ms ease; +} +.earthen-cta:hover, +.earthen-cta:focus-visible { + box-shadow: 0 6px 0 -2px hsl(var(--accent) / 0.35); + transform: translateY(-1px); +} +.earthen-cta:focus-visible { + outline: 2px solid hsl(var(--ring)); + outline-offset: 2px; +} + +/* Sage link — dotted botanical underline that fills on hover. */ +.earthen-link { + color: hsl(var(--primary)); + text-decoration: none; + border-bottom: 1px dotted hsl(var(--primary) / 0.6); + transition: color 150ms ease, border-color 150ms ease; +} +.earthen-link:hover, +.earthen-link:focus-visible { + color: hsl(var(--accent)); + border-bottom-color: hsl(var(--accent)); +} + +/* Leaf line-draw — a hairline that draws in on scroll where motion is allowed. */ +@media (prefers-reduced-motion: no-preference) { + .earthen-draw { + stroke-dasharray: 240; + stroke-dashoffset: 240; + animation: earthen-draw-in 1.4s ease forwards; + } + @keyframes earthen-draw-in { + to { stroke-dashoffset: 0; } + } + .earthen-blob-morph { + animation: earthen-blob-morph 18s ease-in-out infinite; + } + @keyframes earthen-blob-morph { + 0%, 100% { border-radius: 42% 58% 62% 38% / 42% 44% 56% 58%; } + 50% { border-radius: 58% 42% 38% 62% / 56% 58% 42% 44%; } + } +} diff --git a/blocks/blocks.yaml b/blocks/blocks.yaml index c386792..dd5f0e2 100644 --- a/blocks/blocks.yaml +++ b/blocks/blocks.yaml @@ -1,42 +1,26 @@ -# Earthen theme block definitions (codeless .bnp, WO-WZ-021). -# Keys are fully qualified (earthen:) 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. +# Earthen theme block definitions (codeless .bnp, WO-WZ-021 / WO-TF-016). +# Only TRUE persona furniture remains as theme blocks. Everything that +# duplicated a builtin (footer, stats/impact-metrics, logo-strip/partner-logos, +# divider/botanical-divider) is now a template OVERRIDE of the builtin key +# (templates/overrides/earthen/). Keys are fully qualified (earthen:) — the +# codeless loader registers definitions verbatim; the qualified keys match the +# data-block attributes, master_pages.json and seed references. blocks: - key: earthen:donation_cta title: Donation CTA - description: Donation call-to-action with preset amount tiles and custom amount field. + description: Donation appeal with preset amount tiles and a custom amount field. category: content schema: donation_cta.schema.json template: donation_cta.ninjatpl - - key: earthen:impact_metrics - title: Impact Metrics - description: Numerical impact metrics with optional botanical illustration. - category: content - schema: impact_metrics.schema.json - template: impact_metrics.ninjatpl - - key: earthen:partner_logos - title: Partner Logos - description: Grayscale partner logo grid with hover-to-color treatment. - category: content - schema: partner_logos.schema.json - template: partner_logos.ninjatpl - key: earthen:field_note title: Field Note - description: Article-style dispatch from the field with byline and location. + description: Dispatch from the field with byline, location and a lede image. category: content schema: field_note.schema.json template: field_note.ninjatpl - - key: earthen:botanical_divider - title: Botanical Divider - description: Hand-illustrated SVG section break in fern, root or seed motif. - category: layout - schema: botanical_divider.schema.json - template: botanical_divider.ninjatpl - - key: earthen:footer - title: Site Footer - description: Cream-on-moss footer with optional newsletter and column links. - category: navigation - schema: footer.schema.json - template: footer.ninjatpl + - key: earthen:impact_report + title: Impact Report + description: Narrative impact report with a summary lede and outcome rows. + category: content + schema: impact_report.schema.json + template: impact_report.ninjatpl diff --git a/blocks/botanical_divider.ninjatpl b/blocks/botanical_divider.ninjatpl deleted file mode 100644 index 62e22b1..0000000 --- a/blocks/botanical_divider.ninjatpl +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/blocks/botanical_divider.schema.json b/blocks/botanical_divider.schema.json deleted file mode 100644 index c95d5db..0000000 --- a/blocks/botanical_divider.schema.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Botanical Divider", - "description": "SVG section break with hand-illustrated botanical motif", - "type": "object", - "properties": { - "motif": { - "type": "string", - "title": "Motif", - "description": "Botanical illustration used for the divider", - "x-editor": "select", - "enum": ["fern", "root", "seed"], - "default": "fern" - } - } -} diff --git a/blocks/donation_cta.ninjatpl b/blocks/donation_cta.ninjatpl index e3dd95d..b2ff58e 100644 --- a/blocks/donation_cta.ninjatpl +++ b/blocks/donation_cta.ninjatpl @@ -1,7 +1,7 @@
{% if headline %}

{{ headline }}

{% endif %} - {% if body %}
{{ body|safe }}
{% endif %} + {% if body %}
{{ body|safe }}
{% endif %}
{% if amounts %}{% for a in amounts %}{% endfor %}{% else %}{% endif %} diff --git a/blocks/field_note.ninjatpl b/blocks/field_note.ninjatpl index 74735e0..bcb9033 100644 --- a/blocks/field_note.ninjatpl +++ b/blocks/field_note.ninjatpl @@ -3,6 +3,6 @@ - {% if image %}
{% endif %} - {% if body %}
{{ body|safe }}
{% else %}

No dispatch yet — add a body to share what you saw in the field.

{% endif %} + {% if image %}
{% img image alt="" class="w-full h-auto" %}
{% endif %} + {% if body %}
{{ body|safe }}
{% else %}

No dispatch yet — add a body to share what you saw in the field.

{% endif %} diff --git a/blocks/footer.ninjatpl b/blocks/footer.ninjatpl deleted file mode 100644 index 96c4751..0000000 --- a/blocks/footer.ninjatpl +++ /dev/null @@ -1,17 +0,0 @@ - diff --git a/blocks/footer.schema.json b/blocks/footer.schema.json deleted file mode 100644 index 0cf2f95..0000000 --- a/blocks/footer.schema.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Site Footer", - "description": "Cream-on-moss footer with optional newsletter and botanical rule", - "type": "object", - "properties": { - "showNewsletter": { - "type": "boolean", - "title": "Show Newsletter", - "description": "Show the newsletter signup form", - "x-editor": "select", - "default": true - }, - "tagline": { - "type": "string", - "title": "Tagline", - "description": "Tagline above the columns", - "x-editor": "text" - }, - "columns": { - "type": "array", - "title": "Columns", - "description": "Footer link columns", - "x-editor": "collection", - "items": { - "type": "object", - "properties": { - "title": { - "type": "string", - "title": "Column Title", - "x-editor": "text" - }, - "links": { - "type": "array", - "title": "Links", - "x-editor": "collection", - "items": { - "type": "object", - "properties": { - "text": { - "type": "string", - "title": "Text", - "x-editor": "text" - }, - "url": { - "type": "string", - "title": "URL", - "x-editor": "link" - } - } - } - } - } - } - } - } -} diff --git a/blocks/impact_metrics.ninjatpl b/blocks/impact_metrics.ninjatpl deleted file mode 100644 index 1d46d59..0000000 --- a/blocks/impact_metrics.ninjatpl +++ /dev/null @@ -1,7 +0,0 @@ -
-
- {% if title %}

{{ title }}

{% endif %} - {% if illustration %}
{% elif title or metrics %}
{% endif %} - {% if metrics %}
{% for m in metrics %}
{{ m.value }}{{ m.suffix }}
{{ m.label }}
{% endfor %}
{% else %}

Add impact metrics to bring this section to life.

{% endif %} -
-
diff --git a/blocks/impact_metrics.schema.json b/blocks/impact_metrics.schema.json deleted file mode 100644 index f0489ce..0000000 --- a/blocks/impact_metrics.schema.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Impact Metrics", - "description": "Numerical impact metrics with optional illustration", - "type": "object", - "properties": { - "title": { - "type": "string", - "title": "Section Title", - "description": "Heading above the metric tiles", - "x-editor": "text" - }, - "metrics": { - "type": "array", - "title": "Metrics", - "description": "List of impact metrics", - "x-editor": "collection", - "items": { - "type": "object", - "properties": { - "value": { - "type": "string", - "title": "Value", - "description": "Numeric value (e.g. '12,000')", - "x-editor": "text" - }, - "label": { - "type": "string", - "title": "Label", - "description": "Description of the metric", - "x-editor": "text" - }, - "suffix": { - "type": "string", - "title": "Suffix", - "description": "Optional suffix (e.g. '+', '%')", - "x-editor": "text" - } - } - } - }, - "illustration": { - "type": "string", - "title": "Illustration", - "description": "Optional botanical illustration shown alongside metrics", - "x-editor": "media" - } - } -} diff --git a/blocks/impact_report.ninjatpl b/blocks/impact_report.ninjatpl new file mode 100644 index 0000000..785aa31 --- /dev/null +++ b/blocks/impact_report.ninjatpl @@ -0,0 +1,14 @@ +
+
+
+ +
+ {% if period %}

{{ period }}

{% endif %} + {% if title %}

{{ title }}

{% endif %} +
+
+ {% if summary %}
{{ summary|safe }}
{% endif %} + {% if outcomes %}
{% for o in outcomes %}
{{ o.value }}

{{ o.label }}

{% if o.detail %}

{{ o.detail }}

{% endif %}
{% endfor %}
{% else %}

Add outcomes to show what this season grew.

{% endif %} + {% if sourceNote %}

{{ sourceNote }}

{% endif %} +
+
diff --git a/blocks/impact_report.schema.json b/blocks/impact_report.schema.json new file mode 100644 index 0000000..e970237 --- /dev/null +++ b/blocks/impact_report.schema.json @@ -0,0 +1,62 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Impact Report", + "description": "Narrative impact report with a summary lede and a list of outcome rows", + "type": "object", + "properties": { + "period": { + "type": "string", + "title": "Period", + "description": "Reporting period label (e.g. 'Season 2025')", + "x-editor": "text" + }, + "title": { + "type": "string", + "title": "Title", + "description": "Report heading", + "x-editor": "text", + "default": "What we grew together" + }, + "summary": { + "type": "string", + "title": "Summary", + "description": "Opening paragraph of the report", + "x-editor": "richtext" + }, + "outcomes": { + "type": "array", + "title": "Outcomes", + "description": "Reported outcomes shown as rows", + "x-editor": "collection", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "title": "Value", + "description": "Headline figure or result (e.g. '840 acres')", + "x-editor": "text" + }, + "label": { + "type": "string", + "title": "Label", + "description": "What the figure counts", + "x-editor": "text" + }, + "detail": { + "type": "string", + "title": "Detail", + "description": "One line of context", + "x-editor": "text" + } + } + } + }, + "sourceNote": { + "type": "string", + "title": "Source Note", + "description": "Footnote crediting the source of the figures", + "x-editor": "text" + } + } +} diff --git a/blocks/partner_logos.ninjatpl b/blocks/partner_logos.ninjatpl deleted file mode 100644 index 0dbc24c..0000000 --- a/blocks/partner_logos.ninjatpl +++ /dev/null @@ -1,6 +0,0 @@ -
-
- {% if title %}

{{ title }}

{% endif %} - {% if logos %}
{% for logo in logos %}
{% if logo.url %}{% if logo.image %}{{ logo.name }}{% else %}
{% if logo.name %}{% for w in logo.name|split:" " %}{% if forloop.Counter <= 2 %}{{ w|first }}{% endif %}{% endfor %}{% else %}?{% endif %}
{% endif %}
{% else %}{% endif %}
{% endfor %}
{% else %}

Add partner logos to fill this grid.

{% endif %} -
-
diff --git a/blocks/partner_logos.schema.json b/blocks/partner_logos.schema.json deleted file mode 100644 index cc6877b..0000000 --- a/blocks/partner_logos.schema.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Partner Logos", - "description": "Grayscale partner logo grid with hover-to-color treatment", - "type": "object", - "properties": { - "title": { - "type": "string", - "title": "Section Title", - "description": "Heading above the logo grid", - "x-editor": "text" - }, - "logos": { - "type": "array", - "title": "Logos", - "description": "Partner logos", - "x-editor": "collection", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "title": "Partner Name", - "x-editor": "text" - }, - "image": { - "type": "string", - "title": "Logo Image", - "x-editor": "media" - }, - "url": { - "type": "string", - "title": "Partner URL", - "x-editor": "link" - } - } - } - } - } -} diff --git a/fonts.json b/fonts.json index fe51488..ad03c14 100644 --- a/fonts.json +++ b/fonts.json @@ -1 +1,18 @@ -[] +[ + { + "name": "Fraunces", + "family": "Fraunces", + "variants": [ + { "weight": "400", "style": "normal", "file": "fonts/fraunces-latin-400.woff2" }, + { "weight": "600", "style": "normal", "file": "fonts/fraunces-latin-600.woff2" } + ] + }, + { + "name": "Work Sans", + "family": "Work Sans", + "variants": [ + { "weight": "400", "style": "normal", "file": "fonts/worksans-latin-400.woff2" }, + { "weight": "600", "style": "normal", "file": "fonts/worksans-latin-600.woff2" } + ] + } +] diff --git a/manifest.yaml b/manifest.yaml index d936398..c4f9d67 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -3,6 +3,7 @@ theme_presets: presets.json bundled_fonts: fonts.json master_pages: master_pages.json +required_icon_packs: [lucide] system_templates: - key: earthen @@ -30,13 +31,79 @@ page_templates: title: Full Width description: "Edge-to-edge imagery for reports and galleries" slots: [header, main, footer] + - system: earthen + key: blog-index + title: Field Journal + description: "Blog index with a botanical masthead, featured and post-grid slots" + slots: [header, featured, main, footer] + - system: earthen + key: contact + title: Get in Touch + description: "Contact layout with an optional aside for hours and location" + slots: [header, main, aside, footer] + - system: earthen + key: auth + title: Members + description: "Centred auth shell with wordmark and a fern crest" + slots: [main, footer] template_overrides: + # Chrome (4) + - { template: earthen, block: navbar } + - { template: earthen, block: footer } + - { template: earthen, block: utility-bar } + - { template: earthen, block: announcement-bar } + # Marketing (12) + - { template: earthen, block: hero } + - { template: earthen, block: feature-grid } + - { template: earthen, block: rich-section } + - { template: earthen, block: stats } + - { template: earthen, block: testimonial } + - { template: earthen, block: logo-strip } + - { template: earthen, block: pricing } + - { template: earthen, block: team } + - { template: earthen, block: faq } + - { template: earthen, block: timeline } + - { template: earthen, block: cta } + - { template: earthen, block: countdown } + # Primitives (11) - { template: earthen, block: heading } - { template: earthen, block: text } - - { template: earthen, block: button } + - { template: earthen, block: rich-text } - { template: earthen, block: image } + - { template: earthen, block: quote } + - { template: earthen, block: divider } + - { template: earthen, block: button } - { template: earthen, block: card } + - { template: earthen, block: gallery } + - { template: earthen, block: video-embed } + - { template: earthen, block: audio-embed } + # Commerce / local (6) + - { template: earthen, block: menu-list } + - { template: earthen, block: event-list } + - { template: earthen, block: contact-form } + - { template: earthen, block: contact-card } + - { template: earthen, block: hours-location } + - { template: earthen, block: social-links } + # Blog family (12) + - { template: earthen, block: blog-hero } + - { template: earthen, block: featured-posts } + - { template: earthen, block: related-posts } + - { template: earthen, block: popular-posts } + - { template: earthen, block: category-list } + - { template: earthen, block: post-hero } + - { template: earthen, block: post-metadata } + - { template: earthen, block: author-bio } + - { template: earthen, block: author-bio-hero } + - { template: earthen, block: newsletter-signup } + - { template: earthen, block: breadcrumbs } + - { template: earthen, block: sidebar-nav } + # Auth surface (3) + - { template: earthen, block: auth-form } + - { template: earthen, block: password-reset-form } + - { template: earthen, block: auth-status } + # System (1) + - { template: earthen, block: page-suggestions } email_wrappers: - earthen diff --git a/master_pages.json b/master_pages.json index 90e8905..6aa1091 100644 --- a/master_pages.json +++ b/master_pages.json @@ -2,24 +2,31 @@ { "key": "earthen:default-master", "title": "Earthen Default Master", - "page_templates": ["default", "article"], + "page_templates": ["default", "article", "contact", "full-width", "blog-index"], "blocks": [ { "block_key": "navbar", "title": "Main Navigation", "content": { "menuName": "main" }, "slot": "header", "sort_order": 0 }, { "block_key": "slot", "title": "Main Content", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 }, - { "block_key": "earthen:donation_cta", "title": "Donation Rail", "content": { "amounts": [25, 50, 100], "headline": "Plant the next acre." }, "slot": "main", "sort_order": 100 }, - { "block_key": "earthen:footer", "title": "Site Footer", "content": { "showNewsletter": true, "tagline": "Rooted in place." }, "slot": "footer", "sort_order": 0 } + { "block_key": "footer", "title": "Site Footer", "content": { "layout": "columns", "showNewsletter": true, "newsletterTitle": "Field notes in your inbox", "companyName": "Rootline Trust", "tagline": "Rooted in place. We restore land with the people who live on it.", "menuName": "main" }, "slot": "footer", "sort_order": 0 } ] }, { "key": "earthen:landing-master", "title": "Earthen Landing Master", - "page_templates": ["landing", "full-width"], + "page_templates": ["landing"], "blocks": [ { "block_key": "navbar", "title": "Main Navigation", "content": { "menuName": "main" }, "slot": "hero", "sort_order": 0 }, { "block_key": "slot", "title": "Hero Slot", "content": { "slotName": "hero" }, "slot": "hero", "sort_order": 10 }, { "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 }, - { "block_key": "earthen:donation_cta", "title": "Donation CTA", "content": { "amounts": [25, 50, 100, 250] }, "slot": "cta", "sort_order": 0 }, - { "block_key": "earthen:footer", "title": "Footer", "content": { "showNewsletter": true }, "slot": "footer", "sort_order": 0 } + { "block_key": "earthen:donation_cta", "title": "Donation Appeal", "content": { "headline": "Plant the next acre.", "amounts": [25, 50, 100, 250] }, "slot": "cta", "sort_order": 0 }, + { "block_key": "footer", "title": "Site Footer", "content": { "layout": "columns", "showNewsletter": true, "newsletterTitle": "Field notes in your inbox", "companyName": "Rootline Trust", "tagline": "Rooted in place. We restore land with the people who live on it.", "menuName": "main" }, "slot": "footer", "sort_order": 0 } + ] + }, + { + "key": "earthen:auth-master", + "title": "Earthen Auth Master", + "page_templates": ["auth"], + "blocks": [ + { "block_key": "slot", "title": "Auth Form Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 } ] } ] diff --git a/plugin.mod b/plugin.mod index bfde9d3..642c2e5 100644 --- a/plugin.mod +++ b/plugin.mod @@ -7,3 +7,4 @@ description = "Botanical nonprofit theme in moss, clay, sage and cream — hand- kind = "theme" categories = ["templates"] tags = ["earth", "nonprofit", "sustainability", "outdoor", "farm", "cause", "botanical", "conservation", "ngo"] +required_icon_packs = ["lucide"] diff --git a/presets.json b/presets.json index efe1f23..3bfd2d5 100644 --- a/presets.json +++ b/presets.json @@ -106,5 +106,105 @@ "ring": "80 35% 55%" } } + }, + { + "id": "sage-morning", + "name": "Sage Morning", + "description": "Pale sage paper and honey light, misty forest dark — a softer, hopeful reading of the palette.", + "theme": { + "mode": "both", + "lightColors": { + "background": "70 25% 95%", + "foreground": "130 20% 16%", + "card": "70 22% 98%", + "cardForeground": "130 20% 16%", + "popover": "70 22% 98%", + "popoverForeground": "130 20% 16%", + "primary": "140 25% 34%", + "primaryForeground": "70 25% 96%", + "secondary": "75 18% 87%", + "secondaryForeground": "130 22% 20%", + "muted": "70 16% 90%", + "mutedForeground": "110 10% 40%", + "accent": "38 62% 48%", + "accentForeground": "40 30% 97%", + "destructive": "8 70% 45%", + "destructiveForeground": "40 30% 96%", + "border": "90 12% 80%", + "input": "90 12% 80%", + "ring": "140 25% 34%" + }, + "darkColors": { + "background": "140 15% 9%", + "foreground": "70 18% 90%", + "card": "140 15% 12%", + "cardForeground": "70 18% 90%", + "popover": "140 15% 12%", + "popoverForeground": "70 18% 90%", + "primary": "95 28% 58%", + "primaryForeground": "140 15% 9%", + "secondary": "140 12% 18%", + "secondaryForeground": "70 18% 90%", + "muted": "140 10% 15%", + "mutedForeground": "90 10% 58%", + "accent": "40 65% 56%", + "accentForeground": "140 15% 9%", + "destructive": "8 70% 50%", + "destructiveForeground": "70 18% 90%", + "border": "140 10% 22%", + "input": "140 10% 22%", + "ring": "95 28% 58%" + } + } + }, + { + "id": "ember-loam", + "name": "Ember Loam", + "description": "Terracotta-led warm clay in light, firelight ember over deep loam in dark — moss becomes the accent.", + "theme": { + "mode": "both", + "lightColors": { + "background": "28 38% 95%", + "foreground": "18 40% 15%", + "card": "28 32% 98%", + "cardForeground": "18 40% 15%", + "popover": "28 32% 98%", + "popoverForeground": "18 40% 15%", + "primary": "14 62% 44%", + "primaryForeground": "28 38% 96%", + "secondary": "32 26% 88%", + "secondaryForeground": "18 40% 16%", + "muted": "28 22% 91%", + "mutedForeground": "22 20% 38%", + "accent": "95 24% 34%", + "accentForeground": "28 38% 96%", + "destructive": "6 72% 44%", + "destructiveForeground": "28 38% 96%", + "border": "28 18% 81%", + "input": "28 18% 81%", + "ring": "14 62% 44%" + }, + "darkColors": { + "background": "20 22% 7%", + "foreground": "35 25% 90%", + "card": "20 22% 10%", + "cardForeground": "35 25% 90%", + "popover": "20 22% 10%", + "popoverForeground": "35 25% 90%", + "primary": "22 72% 56%", + "primaryForeground": "20 22% 7%", + "secondary": "20 16% 17%", + "secondaryForeground": "35 25% 90%", + "muted": "20 14% 14%", + "mutedForeground": "30 14% 58%", + "accent": "90 30% 52%", + "accentForeground": "20 22% 7%", + "destructive": "6 72% 52%", + "destructiveForeground": "35 25% 90%", + "border": "20 14% 21%", + "input": "20 14% 21%", + "ring": "22 72% 56%" + } + } } ] diff --git a/seed/seed.json b/seed/seed.json new file mode 100644 index 0000000..8f7e237 --- /dev/null +++ b/seed/seed.json @@ -0,0 +1,185 @@ +{ + "demo": true, + "settings": { + "merge": { + "companyName": "Rootline Trust", + "tagline": "We restore land with the people who live on it." + } + }, + "data_tables": [ + { + "key": "contact_submissions", + "name": "Contact Enquiries", + "description": "Seeded contact-form submissions for the Rootline Trust 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": "region", "label": "Region", "type": "text", "required": false }, + { "key": "message", "label": "Message", "type": "text", "required": true } + ] + } + ], + "pages": [ + { + "slug": "/", + "title": "Rootline Trust", + "template_key": "landing", + "blocks": [ + { + "block_key": "hero", + "title": "Hero", + "slot": "hero", + "sort_order": 1, + "content": { + "template": "centered", + "eyebrow": "Land restoration", + "headline": "Bring the land back to life.", + "subheadline": "We restore land with the people who live on it.", + "description": "Rootline Trust plants native cover, mends waterways and hands care of the land back to local hands. Small work, done every season, in places that need it.", + "ctaPrimary": { "text": "Donate", "url": "/contact" }, + "ctaSecondary": { "text": "Read a field note", "url": "/blog" } + } + }, + { + "block_key": "earthen:impact_report", + "title": "Impact Report", + "slot": "main", + "sort_order": 1, + "content": { + "period": "Season 2025", + "title": "What we grew together", + "summary": "

Every acre here started as a pledge. This is where those pledges went last season. The figures below are example figures for this demo site.

", + "outcomes": [ + { "value": "840 acres", "label": "Native cover planted", "detail": "Across twelve restoration sites." }, + { "value": "19 km", "label": "Creek bank mended", "detail": "Fenced, replanted and handed to local carers." }, + { "value": "1,200", "label": "Volunteer days", "detail": "Given by people who live near the work." } + ], + "sourceNote": "Example figures for demonstration." + } + }, + { + "block_key": "earthen:field_note", + "title": "Field Note", + "slot": "main", + "sort_order": 2, + "content": { + "author": "Mara Ellison", + "location": "Wirra Creek", + "dateline": "March 2025", + "body": "

The first rain after planting is the one that tells you. We walked the creek line at dawn and the tube stock had held. Small green points, thousands of them, holding the bank against the next flood.

Restoration is slow. You plant, you fence, you wait. Then one morning the land answers back.

" + } + } + ] + }, + { + "slug": "/about", + "title": "Our work", + "template_key": "default", + "blocks": [ + { "block_key": "heading", "title": "Heading", "slot": "main", "sort_order": 1, "content": { "text": "Our work", "level": 1 } }, + { "block_key": "rich-text", "title": "Story", "slot": "main", "sort_order": 2, "content": { "html": "

Rootline Trust began with one degraded paddock and a handful of neighbours who wanted it back. We plant native cover, mend waterways and fence stock out of creek lines. Then we hand the care of the land to the people who live beside it.

We work slowly and in public. Every site is chosen with local carers. Every season is reported to the people who fund it.

" } }, + { "block_key": "divider", "title": "Divider", "slot": "main", "sort_order": 3, "content": { "variant": "ornament", "icon": "lucide:sprout" } }, + { "block_key": "quote", "title": "Quote", "slot": "main", "sort_order": 4, "content": { "style": "block", "align": "center", "quote": "You plant, you fence, you wait. Then the land answers back.", "attribution": "Mara Ellison, Field Lead" } } + ] + }, + { + "slug": "/blog", + "title": "Field Journal", + "template_key": "blog-index", + "blocks": [ + { "block_key": "card", "title": "Post: First Rain", "slot": "main", "sort_order": 1, "content": { "layout": "vertical", "title": "The first rain after planting", "text": "

What a dawn walk along Wirra Creek tells you about a season of restoration.

", "link": "/blog/the-first-rain-after-planting", "linkLabel": "Read the dispatch" } }, + { "block_key": "card", "title": "Post: Fencing", "slot": "main", "sort_order": 2, "content": { "layout": "vertical", "title": "Why we fence before we plant", "text": "

The least glamorous work protects everything that comes after it.

", "link": "/blog/why-we-fence-before-we-plant", "linkLabel": "Read the dispatch" } }, + { "block_key": "card", "title": "Post: Handover", "slot": "main", "sort_order": 3, "content": { "layout": "vertical", "title": "Handing the land back", "text": "

Restoration is not finished when we leave. It is finished when local carers take it on.

", "link": "/blog/handing-the-land-back", "linkLabel": "Read the dispatch" } } + ] + }, + { + "slug": "/blog/the-first-rain-after-planting", + "title": "The first rain after planting", + "template_key": "article", + "parent_slug": "/blog", + "blocks": [ + { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "The first rain after planting", "level": 1 } }, + { "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "

We walked the creek line at dawn and the tube stock had held. Small green points, thousands of them, holding the bank against the next flood.

The first rain after planting is the one that tells you whether the season will take. This one took. The soil was dark and the roots had gripped.

Restoration is slow. You plant, you fence, you wait. Then one morning the land answers back.

" } } + ] + }, + { + "slug": "/blog/why-we-fence-before-we-plant", + "title": "Why we fence before we plant", + "template_key": "article", + "parent_slug": "/blog", + "blocks": [ + { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Why we fence before we plant", "level": 1 } }, + { "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "

Stock will find new growth before it has a chance to root. So we fence first. It is the least glamorous work we do and it protects everything that comes after.

A good fence buys a young planting three years of quiet. That is usually enough.

" } } + ] + }, + { + "slug": "/blog/handing-the-land-back", + "title": "Handing the land back", + "template_key": "article", + "parent_slug": "/blog", + "blocks": [ + { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Handing the land back", "level": 1 } }, + { "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "

Restoration is not finished when we leave. It is finished when local carers take it on. We plant with them from the first day so the knowledge stays on the land.

By the third season we step back. The site keeps going because the people beside it keep going.

" } } + ] + }, + { + "slug": "/contact", + "title": "Get in touch", + "template_key": "contact", + "blocks": [ + { + "block_key": "contact-form", + "title": "Contact Enquiry", + "slot": "main", + "sort_order": 1, + "content": { + "heading": "Work with us", + "description": "Tell us about your land, your region or how you would like to help. We read every message.", + "submitLabel": "Send message", + "successMessage": "Thank you. Your message has been received. We will reply by email.", + "fields": [ + { "name": "name", "label": "Your name", "type": "text", "required": true, "width": "half" }, + { "name": "email", "label": "Email", "type": "email", "required": true, "width": "half" }, + { "name": "region", "label": "Region", "type": "text", "required": false, "width": "half" }, + { "name": "message", "label": "How can we help?", "type": "textarea", "required": true, "width": "full" } + ], + "formConfig": { "enabled": true, "captchaEnabled": false, "targetTable": "contact_submissions" } + } + }, + { + "block_key": "hours-location", + "title": "Where to find us", + "slot": "aside", + "sort_order": 1, + "content": { + "heading": "Office hours", + "hours": [ + { "day": "Monday to Thursday", "hours": "9am to 5pm" }, + { "day": "Friday", "hours": "9am to 3pm" }, + { "day": "Saturday", "hours": "Field days only" }, + { "day": "Sunday", "closed": true } + ], + "specialHoursNote": "Planting days run most weekends in autumn. Check the field journal.", + "address": "3 Wattle Road\nWirra Creek", + "phone": "+1 555 0142" + } + } + ] + }, + { + "slug": "/login", + "title": "Members", + "template_key": "auth", + "blocks": [ + { "block_key": "auth-form", "title": "Sign in", "slot": "main", "sort_order": 1, "content": { "default_tab": "login", "show_social": false } }, + { "block_key": "text", "title": "Foot", "slot": "footer", "sort_order": 1, "content": { "text": "

Members track pledges and see each site's season report first.

" } } + ] + } + ], + "menu_items": [ + { "menu": "main", "label": "Home", "page_slug": "/", "sort_order": 1 }, + { "menu": "main", "label": "Our work", "page_slug": "/about", "sort_order": 2 }, + { "menu": "main", "label": "Field Journal", "page_slug": "/blog", "sort_order": 3 }, + { "menu": "main", "label": "Get in touch", "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/earthen/article.ninjatpl b/templates/earthen/article.ninjatpl index 65a3326..df2cde4 100644 --- a/templates/earthen/article.ninjatpl +++ b/templates/earthen/article.ninjatpl @@ -1,7 +1,7 @@ {{ head_html|safe }} - + {{ admin_banner_html|safe }}
{{ slots.header|safe }}
diff --git a/templates/earthen/auth.ninjatpl b/templates/earthen/auth.ninjatpl new file mode 100644 index 0000000..907338a --- /dev/null +++ b/templates/earthen/auth.ninjatpl @@ -0,0 +1,17 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} +
+
+ + + +
{% 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/earthen/blog-index.ninjatpl b/templates/earthen/blog-index.ninjatpl new file mode 100644 index 0000000..2d07897 --- /dev/null +++ b/templates/earthen/blog-index.ninjatpl @@ -0,0 +1,23 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} + {% if slots.header %}
{{ slots.header|safe }}
{% endif %} +
+
+ {% if title %} +
+ +

{{ 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 yet. New stories from the field will appear here soon.

{% endif %} +
+
+ {% if slots.footer %}
{{ slots.footer|safe }}
{% endif %} + {{ body_end_html|safe }} + + diff --git a/templates/earthen/contact.ninjatpl b/templates/earthen/contact.ninjatpl new file mode 100644 index 0000000..d798a7a --- /dev/null +++ b/templates/earthen/contact.ninjatpl @@ -0,0 +1,22 @@ + + +{{ 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 %}
{{ slots.footer|safe }}
{% endif %} + {{ body_end_html|safe }} + + diff --git a/templates/earthen/default.ninjatpl b/templates/earthen/default.ninjatpl index a19bc9f..4d2dd40 100644 --- a/templates/earthen/default.ninjatpl +++ b/templates/earthen/default.ninjatpl @@ -1,7 +1,7 @@ {{ 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 %}
diff --git a/templates/earthen/full-width.ninjatpl b/templates/earthen/full-width.ninjatpl index 4784c71..be5cf08 100644 --- a/templates/earthen/full-width.ninjatpl +++ b/templates/earthen/full-width.ninjatpl @@ -1,7 +1,7 @@ {{ 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 %}
diff --git a/templates/earthen/landing.ninjatpl b/templates/earthen/landing.ninjatpl index e53d65d..68d5e0b 100644 --- a/templates/earthen/landing.ninjatpl +++ b/templates/earthen/landing.ninjatpl @@ -1,7 +1,7 @@ {{ head_html|safe }} - + {{ admin_banner_html|safe }}
{{ slots.hero|safe }}
{% if slots.main %}
{{ slots.main|safe }}
{% endif %}
diff --git a/templates/email/earthen.ninjatpl b/templates/email/earthen.ninjatpl index 08abadb..94119e2 100644 --- a/templates/email/earthen.ninjatpl +++ b/templates/email/earthen.ninjatpl @@ -6,4 +6,4 @@ - {{ site_name }}{% if preview_text %}
{{ preview_text }}
{% endif %}
{% if logo_url %}{{ site_name }}{% elif site_name %}

{{ site_name }}

{% endif %}
{{ body|safe }}

{{ site_name }}

Registered charity. We respect your inbox.

{% if site_url %}

{{ site_url }}

{% endif %}{% if unsubscribe_url %}

Unsubscribe

{% else %}

Unsubscribe

{% endif %}
+ {{ site_name }}{% if preview_text %}
{{ preview_text }}
{% endif %}
{% if logo_url %}{{ site_name }}{% elif site_name %}

{{ site_name }}

{% endif %}
{{ body|safe }}

{{ site_name }}

Registered charity. We respect your inbox.

{% if site_url %}

{{ site_url }}

{% endif %}{% if unsubscribe_url %}

Unsubscribe

{% else %}

Unsubscribe

{% endif %}
diff --git a/templates/overrides/earthen/announcement-bar.ninjatpl b/templates/overrides/earthen/announcement-bar.ninjatpl new file mode 100644 index 0000000..9953782 --- /dev/null +++ b/templates/overrides/earthen/announcement-bar.ninjatpl @@ -0,0 +1,21 @@ +{# announcement-bar override — earthen banner on moss/clay tones. Dismiss {% endif %} diff --git a/templates/overrides/earthen/audio-embed.ninjatpl b/templates/overrides/earthen/audio-embed.ninjatpl new file mode 100644 index 0000000..3fe8f24 --- /dev/null +++ b/templates/overrides/earthen/audio-embed.ninjatpl @@ -0,0 +1,16 @@ +{# audio-embed override — cream paper player card. Fields + facade JS mirror builtin verbatim: url, media, artwork, title, artist, download. #} +
{% 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/earthen/auth-form.ninjatpl b/templates/overrides/earthen/auth-form.ninjatpl new file mode 100644 index 0000000..e8aa789 --- /dev/null +++ b/templates/overrides/earthen/auth-form.ninjatpl @@ -0,0 +1,33 @@ +{# auth-form override — earthen login/register on a paper panel. Wiring is UNTOUCHABLE and reproduced exactly from builtin auth_blocks.go: single shared #auth-message, /api/auth/login (email,password) and /api/auth/register (username,email,password), hx-target #auth-message. Captcha widget injected from _captcha_widget when captchaEnabled (server enforces). Fields: default_tab, show_social (social omitted — needs Go/site config; graceful). Only classes/styles/copy reskinned to earthen. #} +{% 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/earthen/auth-status.ninjatpl b/templates/overrides/earthen/auth-status.ninjatpl new file mode 100644 index 0000000..06adabc --- /dev/null +++ b/templates/overrides/earthen/auth-status.ninjatpl @@ -0,0 +1,31 @@ +{# auth-status override — header login state. Logged-out: Sign in / Join links. Logged-in: moss monogram + dropdown (logout posts /api/auth/logout). Wiring UNTOUCHABLE: context.isPublicLoggedIn / context.publicDisplayName / context.publicUsername reads, #auth-dropdown-menu, onclick toggle, outside-click +
+{% else %} + +{% endif %} diff --git a/templates/overrides/earthen/author-bio-hero.ninjatpl b/templates/overrides/earthen/author-bio-hero.ninjatpl new file mode 100644 index 0000000..f4b673f --- /dev/null +++ b/templates/overrides/earthen/author-bio-hero.ninjatpl @@ -0,0 +1,22 @@ +{# author-bio-hero (earthen) — author-page header from the `authors` provider. Guards, field reads, social icons (via ) and image wiring preserved; earthen skin only. #} +{% 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/earthen/author-bio.ninjatpl b/templates/overrides/earthen/author-bio.ninjatpl new file mode 100644 index 0000000..cb12a23 --- /dev/null +++ b/templates/overrides/earthen/author-bio.ninjatpl @@ -0,0 +1,13 @@ +{# author-bio (earthen) — byline card sourced from the `post` provider's author_* fields. Guards, field reads and image wiring preserved; earthen skin only. #} +{% if post.author_name %} + +{% endif %} diff --git a/templates/overrides/earthen/blog-hero.ninjatpl b/templates/overrides/earthen/blog-hero.ninjatpl new file mode 100644 index 0000000..e62a1bf --- /dev/null +++ b/templates/overrides/earthen/blog-hero.ninjatpl @@ -0,0 +1,24 @@ +{# blog-hero (earthen) — blog-index header + featured posts from the `posts` provider. Wiring, field reads and loops preserved from builtin; earthen skin only. #} +
+
+
+ +{% if title %}

{{ title }}

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

{{ subtitle }}

{% endif %} +
+{% if posts %} +
+{% for post in posts %} + +{% endfor %} +
+{% endif %} +
+
diff --git a/templates/overrides/earthen/breadcrumbs.ninjatpl b/templates/overrides/earthen/breadcrumbs.ninjatpl new file mode 100644 index 0000000..b681894 --- /dev/null +++ b/templates/overrides/earthen/breadcrumbs.ninjatpl @@ -0,0 +1,13 @@ +{# breadcrumbs (earthen) — trail from the `breadcrumbs` provider (page ancestry). showHome prepends a Home link; separator is text ("/") or a chevron for the arrows template; the trailing is_current item renders as plain text. Control flow and field reads preserved; earthen skin only. #} +{% set arrows = template == "arrows" %} + diff --git a/templates/overrides/earthen/button.ninjatpl b/templates/overrides/earthen/button.ninjatpl index 1fcfa81..266b5a7 100644 --- a/templates/overrides/earthen/button.ninjatpl +++ b/templates/overrides/earthen/button.ninjatpl @@ -1 +1,10 @@ -{{ label|default:text }} +{# button override — terracotta seedling CTA, or a moss botanical-underline link for quiet styles. Fields (new builtin contract, per OVERRIDE-CONTRACT §4): link, style, label, size, icon, iconPosition, newTab, align. #} +{% with align=align|default:"left" %} + +{% endwith %} diff --git a/templates/overrides/earthen/card.ninjatpl b/templates/overrides/earthen/card.ninjatpl index 445f8de..37a1a51 100644 --- a/templates/overrides/earthen/card.ninjatpl +++ b/templates/overrides/earthen/card.ninjatpl @@ -1 +1,4 @@ - +{# card override — cream paper panel with soft organic corners. Fields mirror builtin: layout, link, media, title, text, linkLabel. Media via {% img %}. #} +{% with layout=layout|default:"vertical" %} + +{% endwith %} diff --git a/templates/overrides/earthen/category-list.ninjatpl b/templates/overrides/earthen/category-list.ninjatpl new file mode 100644 index 0000000..a1da22b --- /dev/null +++ b/templates/overrides/earthen/category-list.ninjatpl @@ -0,0 +1,18 @@ +{# category-list (earthen) — from the `categories` provider. list or pills. Control flow and field reads preserved; earthen skin only. #} +{% 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/earthen/contact-card.ninjatpl b/templates/overrides/earthen/contact-card.ninjatpl new file mode 100644 index 0000000..fa48718 --- /dev/null +++ b/templates/overrides/earthen/contact-card.ninjatpl @@ -0,0 +1,18 @@ +{# contact-card override — cream contact tiles with moss icon chips. Fields mirror builtin: heading, description, columns, channels[].icon/.label/.value/.link. Icons via sprite . Wiring 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/earthen/contact-form.ninjatpl b/templates/overrides/earthen/contact-form.ninjatpl new file mode 100644 index 0000000..c7fb566 --- /dev/null +++ b/templates/overrides/earthen/contact-form.ninjatpl @@ -0,0 +1,43 @@ +{# contact-form override — SEED-CRITICAL. ALL form wiring preserved verbatim from builtin: bid, {% form %} action/hx attrs, honeypot input name, field loop, form.submitted/errors, submit target. Only inputs restyled and copy reworded to earthen. #} +{% with bid=_block_id|default:context.blockId %} +
+
+ {% if form.submitted %} +
+
::lucide:check:lg::
+

{{ form.message|default:successMessage|default:"Thank you. Your message has been received." }}

+
+ {% 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/earthen/countdown.ninjatpl b/templates/overrides/earthen/countdown.ninjatpl new file mode 100644 index 0000000..00d4dbc --- /dev/null +++ b/templates/overrides/earthen/countdown.ninjatpl @@ -0,0 +1,49 @@ +{# countdown override — earthen: paper ground, soft botanical unit boxes, terracotta digits. Field reads, every data-countdown/data-countdown-timer/data-target/data-unit/data-countdown-expired hook, the
+ {% endif %} + + +
diff --git a/templates/overrides/earthen/image.ninjatpl b/templates/overrides/earthen/image.ninjatpl index 9ab7ad2..12561e9 100644 --- a/templates/overrides/earthen/image.ninjatpl +++ b/templates/overrides/earthen/image.ninjatpl @@ -1 +1,11 @@ -
{% if src|default:url %}
{{ alt }}
{% else %}
Choose an image to display here
{% endif %}{% if caption %}
{{ caption }}
{% endif %}
+{# image override — recycled-paper frame with soft organic corners. Fields mirror builtin: src, alt, caption, size, align, aspectRatio, objectFit. Media resolved via {% img %} (media filter is not registered in the block render path). #} +{% with size=size|default:"full" align=align|default:"center" objectFit=objectFit|default:"cover" %} +
+
+
+ {% 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/earthen/logo-strip.ninjatpl b/templates/overrides/earthen/logo-strip.ninjatpl new file mode 100644 index 0000000..8ba1416 --- /dev/null +++ b/templates/overrides/earthen/logo-strip.ninjatpl @@ -0,0 +1,18 @@ +{# logo-strip override — earthen: grayscale-at-rest, colour-on-hover partner logos (partner_logos DNA via .earthen-partner-* classes). Field reads, the {% if logo.url %} anchor split, the {% if grayscale %} branch, marquee classes and the {% if marquee %}{% endif %} diff --git a/templates/overrides/earthen/menu-list.ninjatpl b/templates/overrides/earthen/menu-list.ninjatpl new file mode 100644 index 0000000..b673e9d --- /dev/null +++ b/templates/overrides/earthen/menu-list.ninjatpl @@ -0,0 +1,30 @@ +{# menu-list override — a field-kitchen menu card: Fraunces section titles, dotted leader rules, terracotta prices. Fields mirror builtin: 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/earthen/navbar.ninjatpl b/templates/overrides/earthen/navbar.ninjatpl new file mode 100644 index 0000000..d036dc6 --- /dev/null +++ b/templates/overrides/earthen/navbar.ninjatpl @@ -0,0 +1,104 @@ +{# navbar override — earthen botanical masthead. Fixed drawer ids (bn-navbar-toggle/drawer/overlay), focus-trap + scroll-lock JS and all data-bn-* wiring preserved verbatim from the builtin; only classes/style/fonts reskinned. Fields identical: variant/stickyBehavior/megaMenu/logoType/logoText/logo/companyName/cta/menu.items/... #} +{% set nv = variant %} +{% if not nv %}{% if template == "centered" %}{% set nv = "centered-logo" %}{% elif template == "transparent" %}{% set nv = "transparent-over-hero" %}{% else %}{% set nv = "standard" %}{% endif %}{% endif %} +{% set sb = stickyBehavior %} +{% if not sb %}{% if sticky %}{% set sb = "sticky" %}{% else %}{% set sb = "none" %}{% endif %}{% endif %} +{% set use_mega = megaMenu or template == "mega" %} +{% set is_transparent = nv == "transparent-over-hero" %} +{% set cta_text = cta.text|default:ctaLabel %} +{% set cta_url = cta.url|default:ctaUrl %} +{% set bg = backgroundColor|default:"bg-background" %} +{% macro navlink(item) %}{{ 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/earthen/newsletter-signup.ninjatpl b/templates/overrides/earthen/newsletter-signup.ninjatpl new file mode 100644 index 0000000..7454a7d --- /dev/null +++ b/templates/overrides/earthen/newsletter-signup.ninjatpl @@ -0,0 +1,11 @@ +{# newsletter-signup (earthen) — the {% signup_form %} tag carries the untouchable subscribe wiring (/api/lists/subscribe HTMX post + honeypot); this template supplies only the earthen 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/earthen/page-suggestions.ninjatpl b/templates/overrides/earthen/page-suggestions.ninjatpl new file mode 100644 index 0000000..6dee51a --- /dev/null +++ b/templates/overrides/earthen/page-suggestions.ninjatpl @@ -0,0 +1,21 @@ +{# page-suggestions override — themed 404 suggestions. Wiring UNTOUCHABLE: fields title, showDescription, emptyMessage; suggestions[] from the provider (.slug/.title/.excerpt). Only classes/styles/copy reskinned to earthen. #} +{% 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/earthen/password-reset-form.ninjatpl b/templates/overrides/earthen/password-reset-form.ninjatpl new file mode 100644 index 0000000..be0775c --- /dev/null +++ b/templates/overrides/earthen/password-reset-form.ninjatpl @@ -0,0 +1,14 @@ +{# password-reset-form override — request stage only, matching builtin. Wiring UNTOUCHABLE: /api/auth/request-password-reset (email), hx-target #reset-message, #request-reset-panel. Only classes/styles/copy reskinned to earthen. #} +
+
+
+ +

Reset password

+

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

+
+
+ +
+

Back to sign in

+
+
diff --git a/templates/overrides/earthen/popular-posts.ninjatpl b/templates/overrides/earthen/popular-posts.ninjatpl new file mode 100644 index 0000000..0bf4eec --- /dev/null +++ b/templates/overrides/earthen/popular-posts.ninjatpl @@ -0,0 +1,16 @@ +{# popular-posts (earthen) — compact ranked list from the `posts` provider. Loops, rank counter and image wiring preserved; earthen skin only. #} +
+{% 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/earthen/post-hero.ninjatpl b/templates/overrides/earthen/post-hero.ninjatpl new file mode 100644 index 0000000..78c4fce --- /dev/null +++ b/templates/overrides/earthen/post-hero.ninjatpl @@ -0,0 +1,22 @@ +{# post-hero (earthen) — current post from the `post` provider. Banner when a featured image exists, else a clean text hero. Branches, field reads and image wiring preserved; earthen skin only. #} +{% 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" %}
+
+{% if center %}{% endif %} +

{{ 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 %} +
+
+{% if center %}{% endif %} +

{{ 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/earthen/post-metadata.ninjatpl b/templates/overrides/earthen/post-metadata.ninjatpl new file mode 100644 index 0000000..f9a6473 --- /dev/null +++ b/templates/overrides/earthen/post-metadata.ninjatpl @@ -0,0 +1,9 @@ +{# post-metadata (earthen) — author/date/reading-time row from the `post` provider. Guards, field reads, loops and image wiring preserved; earthen skin only. #} +{% if post %} +
+{% if showAuthor and post.author_name %}{% if showAvatar and post.author_avatar %}{% img post.author_avatar alt=post.author_name class="earthen-blob-soft h-7 w-7 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/earthen/pricing.ninjatpl b/templates/overrides/earthen/pricing.ninjatpl new file mode 100644 index 0000000..0f9b069 --- /dev/null +++ b/templates/overrides/earthen/pricing.ninjatpl @@ -0,0 +1,26 @@ +{# pricing override — earthen: paper ground, soft botanical tier cards, terracotta prices. Field reads, highlighted/tier control flow, features loop and check-icon preserved verbatim from the builtin; only classes/style/fonts reskinned. #} +
+
+ {% 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/earthen/quote.ninjatpl b/templates/overrides/earthen/quote.ninjatpl new file mode 100644 index 0000000..18ff84e --- /dev/null +++ b/templates/overrides/earthen/quote.ninjatpl @@ -0,0 +1,8 @@ +{# quote override — warm pull-quote: large italic Work Sans, moss rail, a small fern flourish when centred. Fields mirror builtin: quote, style, align, cite, attribution, role. Wiring preserved. #} +{% with style=style|default:"block" align=align|default:"left" %} +
+ {% if align == "center" %}{% endif %} + {{ 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/earthen/related-posts.ninjatpl b/templates/overrides/earthen/related-posts.ninjatpl new file mode 100644 index 0000000..dff1b6a --- /dev/null +++ b/templates/overrides/earthen/related-posts.ninjatpl @@ -0,0 +1,17 @@ +{# related-posts (earthen) — grid from the `posts` provider. Guard, loops and image wiring preserved; earthen skin only. #} +{% if posts %} +
+
+

{{ heading|default:"More field notes" }}

+
+{% for post in posts %} + +{% endfor %} +
+
+
+{% endif %} diff --git a/templates/overrides/earthen/rich-section.ninjatpl b/templates/overrides/earthen/rich-section.ninjatpl new file mode 100644 index 0000000..5370f28 --- /dev/null +++ b/templates/overrides/earthen/rich-section.ninjatpl @@ -0,0 +1,14 @@ +{# rich-section override — earthen: paper ground, botanical eyebrow rule, Fraunces heading with crayon underline, organic media frame. Field reads, background/mediaPosition order control flow, body|safe and the {% img %} call preserved verbatim from the builtin; only classes/style/fonts reskinned. #} +
+
+
+ {% 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/earthen/rich-text.ninjatpl b/templates/overrides/earthen/rich-text.ninjatpl new file mode 100644 index 0000000..6358204 --- /dev/null +++ b/templates/overrides/earthen/rich-text.ninjatpl @@ -0,0 +1,2 @@ +{# rich-text override — long-form Work Sans editorial column, moss links + moss blockquote rail. Field mirrors builtin: html. Wiring preserved. #} +{% if html %}
{{ html|safe }}
{% endif %} diff --git a/templates/overrides/earthen/sidebar-nav.ninjatpl b/templates/overrides/earthen/sidebar-nav.ninjatpl new file mode 100644 index 0000000..925be9a --- /dev/null +++ b/templates/overrides/earthen/sidebar-nav.ninjatpl @@ -0,0 +1,13 @@ +{# sidebar-nav (earthen) — vertical menu from the `menus` provider. Variants default | compact. highlight-current carried as a progressive-enhancement script keyed on data-bn-sidebar-link. Control flow, icon and script preserved byte-for-byte; earthen skin only. #} +{% set compact = template == "compact" %} + +{% if highlightCurrent %}{% endif %} diff --git a/templates/overrides/earthen/social-links.ninjatpl b/templates/overrides/earthen/social-links.ninjatpl new file mode 100644 index 0000000..0a46042 --- /dev/null +++ b/templates/overrides/earthen/social-links.ninjatpl @@ -0,0 +1,14 @@ +{# social-links override — soft-cornered moss chips that warm to terracotta on hover. Fields mirror builtin: heading, style, links[].url/.label/.network. Icons via sprite . Wiring preserved. #} + diff --git a/templates/overrides/earthen/stats.ninjatpl b/templates/overrides/earthen/stats.ninjatpl new file mode 100644 index 0000000..a0f5f0e --- /dev/null +++ b/templates/overrides/earthen/stats.ninjatpl @@ -0,0 +1,50 @@ +{# stats override — earthen: big terracotta numbers with a botanical fern flourish (impact-metrics DNA). Field reads, columns control flow, countUp data-stats/data-countup-value hooks and the entire count-up {% endif %} diff --git a/templates/overrides/earthen/team.ninjatpl b/templates/overrides/earthen/team.ninjatpl new file mode 100644 index 0000000..e6bcbd0 --- /dev/null +++ b/templates/overrides/earthen/team.ninjatpl @@ -0,0 +1,22 @@ +{# team override — earthen: paper ground, soft botanical cards, organic member portraits. Field reads, layout/columns control flow, the {% img %} portrait call and the social-icon split preserved verbatim from the builtin; only classes/style/fonts reskinned. #} +
+
+ {% 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/earthen/testimonial.ninjatpl b/templates/overrides/earthen/testimonial.ninjatpl new file mode 100644 index 0000000..a7d30b9 --- /dev/null +++ b/templates/overrides/earthen/testimonial.ninjatpl @@ -0,0 +1,28 @@ +{# testimonial override — earthen: paper ground, soft botanical cards, organic avatar. Field reads, layout control flow, the five rating-star conditionals and the {% img %} avatar call preserved verbatim from the builtin; only classes/style/fonts reskinned. #} +
+
+ {% 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/earthen/text.ninjatpl b/templates/overrides/earthen/text.ninjatpl index 4937e26..5ed22db 100644 --- a/templates/overrides/earthen/text.ninjatpl +++ b/templates/overrides/earthen/text.ninjatpl @@ -1 +1,2 @@ -{% if variant == "lede" %}
{{ text|safe }}
{% else %}
{{ text|safe }}
{% endif %} +{# text override — Work Sans body prose with moss links that fill to terracotta on hover. Fields mirror builtin: text, proseClass. Wiring preserved. #} +
{{ text|safe }}
diff --git a/templates/overrides/earthen/timeline.ninjatpl b/templates/overrides/earthen/timeline.ninjatpl new file mode 100644 index 0000000..51f18ed --- /dev/null +++ b/templates/overrides/earthen/timeline.ninjatpl @@ -0,0 +1,22 @@ +{# timeline override — earthen: paper ground, soft botanical step markers, organic media. Field reads, orientation/marker control flow, forloop.counter, marker flag and the {% img %} call preserved verbatim from the builtin; only classes/style/fonts reskinned. #} +
+
+ {% 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/earthen/utility-bar.ninjatpl b/templates/overrides/earthen/utility-bar.ninjatpl new file mode 100644 index 0000000..3fd7069 --- /dev/null +++ b/templates/overrides/earthen/utility-bar.ninjatpl @@ -0,0 +1,10 @@ +{# utility-bar override — earthen thin contact strip on warm oat. Fields mirror builtin: variant, showOnMobile, leftItems/rightItems {icon,label,link}; icons via the ::pack:name:: sprite shorthand. Only classes reskinned. #} +{% 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/earthen/video-embed.ninjatpl b/templates/overrides/earthen/video-embed.ninjatpl new file mode 100644 index 0000000..6ed3094 --- /dev/null +++ b/templates/overrides/earthen/video-embed.ninjatpl @@ -0,0 +1,23 @@ +{# video-embed override — paper frame with soft organic corners; play button in a cream medallion. Fields + facade JS mirror builtin verbatim: aspect, url, media, poster, title, caption. #} +{% with aspect=aspect|default:"16:9" %}
{% if caption %}
{{ caption }}
{% endif %}
{% endwith %}