Deliver the full conservation-nonprofit theme per OVERRIDE-CONTRACT (Wave A).
- 49 mandatory builtin overrides in templates/overrides/earthen/ (chrome,
marketing, primitives, commerce, blog family, auth surface, page-suggestions),
re-skinned from the frozen builtins into botanical/paper-grain chrome with
all content fields, custom tags, provider vars, HTMX/auth wiring and JS
preserved verbatim. Stale button/card/image/text/heading overrides fixed to
the new field contract (link/style/label, media/text via {% img %}).
- 7 page templates: default, full-width, landing, article, blog-index, contact,
auth.
- Blocks rework: impact_metrics/partner_logos/botanical_divider/footer converted
to builtin overrides (deleted as theme blocks); donation_cta and field_note
kept; impact_report added. Persona blocks now 3.
- 5 presets (all 19 tokens; mossbed/sage-morning/ember-loam are dual-mode).
- Bundled Fraunces + Work Sans woff2 (OFL) in assets/fonts/; fonts.json,
RECOMMENDED_FONTS.md, RECOMMENDED_ICONS.md; required_icon_packs=[lucide].
- Seed: home/about/blog(3 posts)/contact/login as Rootline Trust, contact-form
formConfig.targetTable=contact_submissions + row_inserted email-admins workflow.
- Themed email-safe wrapper; on-theme master pages; CSS-first botanical motion.
Verified: make (build) exit 0, make archive-check exit 0, check-safety exit 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
105 lines
13 KiB
Plaintext
105 lines
13 KiB
Plaintext
{# 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) %}<a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}{% if item.title %} title="{{ item.title }}"{% endif %} class="px-3 py-2 text-sm font-medium text-foreground/75 hover:text-primary transition-colors {{ item.css_class }}" style="font-family:var(--font-body, 'Work Sans', ui-sans-serif, system-ui, sans-serif);">{{ item.label }}</a>{% endmacro %}
|
|
{% macro dropdown(item) %}<div class="relative group">
|
|
<button type="button" class="px-3 py-2 text-sm font-medium text-foreground/75 hover:text-primary transition-colors inline-flex items-center gap-1" style="font-family:var(--font-body, 'Work Sans', ui-sans-serif, system-ui, sans-serif);">{{ item.label }}<svg class="h-4 w-4 transition-transform group-hover:rotate-180" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg></button>
|
|
{% if use_mega %}<div class="absolute left-1/2 -translate-x-1/2 mt-2 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50"><div class="earthen-panel rounded-2xl p-6 grid gap-8" style="grid-template-columns:repeat({{ item.children|length|default:1 }},minmax(10rem,1fr));min-width:min(90vw,42rem)">{% for child in item.children %}<div class="space-y-1">{% if child.item_type == "header" %}<div class="px-2 py-1 text-[0.65rem] font-semibold uppercase tracking-wider text-primary border-b border-border mb-2">{{ child.label }}</div>{% else %}<a href="{{ child.url|default:'#' }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="block px-2 py-2 rounded-lg hover:bg-muted transition-colors {{ child.css_class }}">{% if child.icon %}<span class="mr-1.5 text-primary">::{{ child.icon }}::</span>{% endif %}<span class="text-sm font-medium text-foreground">{{ child.label }}</span>{% if child.description %}<span class="block text-xs text-muted-foreground mt-0.5">{{ child.description }}</span>{% endif %}</a>{% endif %}</div>{% endfor %}</div></div>
|
|
{% else %}<div class="absolute left-0 mt-2 w-56 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50"><div class="earthen-panel rounded-2xl py-2">{% for child in item.children %}{% if child.item_type == "header" %}<div class="px-4 py-2 text-[0.65rem] font-semibold uppercase tracking-wider text-primary">{{ child.label }}</div>{% else %}<a href="{{ child.url|default:'#' }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="block px-4 py-2 text-sm text-foreground hover:bg-muted transition-colors {{ child.css_class }}">{{ child.label }}</a>{% endif %}{% endfor %}</div></div>{% endif %}
|
|
</div>{% endmacro %}
|
|
{% macro brand(size) %}<a href="/" class="flex-shrink-0 flex items-center">{% if logoType == "text" and logoText.text %}<span class="text-xl font-semibold {{ logoText.color }}" style="font-family:var(--font-heading, 'Fraunces', 'Playfair Display', Georgia, serif);color:hsl(var(--primary));">{{ logoText.text }}</span>{% elif logo %}<img src="{{ logo }}" alt="{{ alt|default:companyName }}" class="{{ size }} w-auto"{% if logoMaxWidth or logoMaxHeight %} style="{% if logoMaxWidth %}max-width:{{ logoMaxWidth }};{% endif %}{% if logoMaxHeight %}max-height:{{ logoMaxHeight }};{% endif %}"{% endif %}/>{% elif companyName %}<span class="text-xl font-semibold" style="font-family:var(--font-heading, 'Fraunces', 'Playfair Display', Georgia, serif);color:hsl(var(--primary));">{{ companyName }}</span>{% endif %}</a>{% endmacro %}
|
|
{% macro ctabtn(extra) %}{% if cta_text %}<a href="{{ cta_url|default:'#' }}" class="earthen-cta inline-flex items-center px-5 py-2.5 text-sm font-semibold {{ extra }}">{{ cta_text }}</a>{% endif %}{% endmacro %}
|
|
<nav data-block-override="earthen:navbar" data-bn-navbar data-bn-nav-variant="{{ nv }}" data-bn-sticky="{{ sb }}"{% if is_transparent %} data-bn-transparent="1"{% endif %} class="bn-navbar w-full {% if sb == "sticky" or sb == "shrink-on-scroll" %}sticky top-0 z-40 {% endif %}{% if is_transparent %}absolute top-0 left-0 z-40 text-white{% else %}{{ bg }} border-b border-border{% endif %} {{ class }}">
|
|
{% if showUtilityBar %}<div class="w-full text-xs {% if not showOnMobile %}hidden sm:block{% endif %} bg-muted text-muted-foreground border-b border-border"><div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-between h-8"><div>{{ utilityBarText|safe }}</div><div class="flex items-center gap-4">{% if utilityBarPhone %}<a href="tel:{{ utilityBarPhone }}" class="hover:text-primary transition-colors">{{ utilityBarPhone }}</a>{% endif %}{% if utilityBarEmail %}<a href="mailto:{{ utilityBarEmail }}" class="hover:text-primary transition-colors">{{ utilityBarEmail }}</a>{% endif %}</div></div></div>{% endif %}
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="bn-navbar-row flex items-center justify-between h-16">
|
|
{% if nv == "centered-logo" %}
|
|
<div class="hidden md:flex items-center gap-1 flex-1">{% for item in menu.items %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endfor %}</div>
|
|
<div class="flex items-center justify-center">{{ brand("h-9") }}</div>
|
|
<div class="hidden md:flex items-center gap-3 flex-1 justify-end">{{ ctabtn("") }}</div>
|
|
{% elif nv == "split" %}
|
|
<div class="hidden md:flex items-center gap-1 flex-1">{% for item in menu.items %}{% if forloop.Counter0 * 2 < menu.items|length %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endif %}{% endfor %}</div>
|
|
<div class="flex items-center justify-center">{{ brand("h-9") }}</div>
|
|
<div class="hidden md:flex items-center gap-1 flex-1 justify-end">{% for item in menu.items %}{% if forloop.Counter0 * 2 >= menu.items|length %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endif %}{% endfor %}{{ ctabtn("ml-3") }}</div>
|
|
{% else %}
|
|
<div class="flex items-center">{{ brand("h-8") }}</div>
|
|
<div class="hidden md:flex items-center gap-1">{% for item in menu.items %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endfor %}{{ ctabtn("ml-3") }}</div>
|
|
{% endif %}
|
|
<div class="flex items-center md:hidden">
|
|
<label for="bn-navbar-toggle" data-bn-nav-open aria-controls="bn-navbar-drawer" aria-expanded="false" class="inline-flex items-center justify-center p-2 rounded-lg cursor-pointer hover:bg-muted transition-colors"><span class="sr-only">Open menu</span><svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg></label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{# --- Mobile off-canvas drawer: pure-CSS checkbox baseline (works with no JS) --- #}
|
|
<input type="checkbox" id="bn-navbar-toggle" class="bn-navbar-toggle" aria-hidden="true"/>
|
|
<label for="bn-navbar-toggle" class="bn-navbar-overlay fixed inset-0 bg-black/50 z-40 md:hidden" aria-hidden="true"></label>
|
|
<div id="bn-navbar-drawer" class="bn-navbar-drawer fixed top-0 right-0 h-full w-80 max-w-[85vw] bg-background text-foreground z-50 shadow-xl md:hidden flex flex-col border-l border-border" role="dialog" aria-modal="true" aria-label="Site menu">
|
|
<div class="flex items-center justify-between p-4 border-b border-border">{{ brand("h-8") }}<label for="bn-navbar-toggle" data-bn-nav-close class="p-2 rounded-lg cursor-pointer text-muted-foreground hover:text-primary hover:bg-muted"><span class="sr-only">Close menu</span><svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg></label></div>
|
|
<nav class="flex-1 overflow-y-auto py-4" aria-label="Mobile">{% for item in menu.items %}{% if item.children %}<details class="group/acc"><summary class="flex items-center justify-between px-6 py-3 text-base font-medium cursor-pointer hover:bg-muted transition-colors list-none">{{ item.label }}<svg class="h-4 w-4 transition-transform group-open/acc:rotate-180" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg></summary><div class="pl-8 pb-2">{% for child in item.children %}{% if child.item_type != "header" %}<a href="{{ child.url|default:'#' }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="block px-2 py-2 text-sm text-muted-foreground hover:text-primary transition-colors">{{ child.label }}</a>{% endif %}{% endfor %}</div></details>{% else %}<a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="block px-6 py-3 text-base font-medium hover:bg-muted transition-colors">{{ item.label }}</a>{% endif %}{% endfor %}</nav>
|
|
{% if cta_text %}<div class="p-4 border-t border-border"><a href="{{ cta_url|default:'#' }}" class="earthen-cta block w-full text-center px-4 py-3 text-base font-semibold">{{ cta_text }}</a></div>{% endif %}
|
|
</div>
|
|
</nav>
|
|
<style>
|
|
.bn-navbar-toggle{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;}
|
|
.bn-navbar-drawer{transform:translateX(100%);transition:transform .3s ease;}
|
|
.bn-navbar-overlay{opacity:0;pointer-events:none;transition:opacity .3s ease;}
|
|
.bn-navbar-toggle:checked ~ .bn-navbar-drawer{transform:translateX(0);}
|
|
.bn-navbar-toggle:checked ~ .bn-navbar-overlay{opacity:1;pointer-events:auto;}
|
|
.bn-navbar-drawer details > summary::-webkit-details-marker{display:none;}
|
|
[data-bn-sticky="shrink-on-scroll"].bn-navbar-shrunk .bn-navbar-row{height:3rem;}
|
|
[data-bn-sticky="shrink-on-scroll"] .bn-navbar-row{transition:height .2s ease;}
|
|
[data-bn-transparent].bn-navbar-solid{position:sticky;color:inherit;background:hsl(var(--background));border-bottom:1px solid hsl(var(--border));}
|
|
</style>
|
|
<script>
|
|
(function(){
|
|
if(window.__bnNavInit)return;window.__bnNavInit=1;
|
|
function ready(fn){if(document.readyState!='loading')fn();else document.addEventListener('DOMContentLoaded',fn);}
|
|
ready(function(){
|
|
document.querySelectorAll('[data-bn-navbar]').forEach(function(nav){
|
|
var toggle=nav.querySelector('.bn-navbar-toggle');
|
|
var drawer=nav.querySelector('.bn-navbar-drawer');
|
|
var opener=nav.querySelector('[data-bn-nav-open]');
|
|
if(toggle&&drawer){
|
|
function focusables(){return drawer.querySelectorAll('a[href],button,label[for],input,summary,[tabindex]:not([tabindex="-1"])');}
|
|
toggle.addEventListener('change',function(){
|
|
var open=toggle.checked;
|
|
document.documentElement.style.overflow=open?'hidden':'';
|
|
document.body.style.overflow=open?'hidden':'';
|
|
if(opener)opener.setAttribute('aria-expanded',open?'true':'false');
|
|
if(open){var f=focusables();if(f.length)f[0].focus();}
|
|
else if(opener&&opener.focus)opener.focus();
|
|
});
|
|
drawer.addEventListener('keydown',function(e){
|
|
if(e.key==='Escape'){toggle.checked=false;toggle.dispatchEvent(new Event('change'));return;}
|
|
if(e.key!=='Tab')return;
|
|
var f=focusables();if(!f.length)return;
|
|
var first=f[0],last=f[f.length-1];
|
|
if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus();}
|
|
else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus();}
|
|
});
|
|
drawer.querySelectorAll('a[href]').forEach(function(a){a.addEventListener('click',function(){toggle.checked=false;toggle.dispatchEvent(new Event('change'));});});
|
|
}
|
|
var sb=nav.getAttribute('data-bn-sticky');
|
|
var transparent=nav.getAttribute('data-bn-transparent')==='1';
|
|
if(sb==='shrink-on-scroll'||transparent){
|
|
var overHero=false;
|
|
if(transparent){var next=nav.nextElementSibling;overHero=!!next&&next.getBoundingClientRect().height>=240;if(!overHero)nav.classList.add('bn-navbar-solid');}
|
|
var onScroll=function(){
|
|
var y=window.scrollY||window.pageYOffset;
|
|
if(sb==='shrink-on-scroll')nav.classList.toggle('bn-navbar-shrunk',y>40);
|
|
if(transparent&&overHero)nav.classList.toggle('bn-navbar-solid',y>40);
|
|
};
|
|
window.addEventListener('scroll',onScroll,{passive:true});onScroll();
|
|
}
|
|
});
|
|
});
|
|
})();
|
|
</script>
|