Alex Dunmow 72f1a7c8f4 fix(brutalist): Wave B design-review remediation + fresh gallery captures (0.6.0)
- Article title no longer rendered twice: seed article pages drop the duplicate
  level-1 heading block; the article template's page <h1> is the sole title.
- Home dead gap resolved: landing sections divided by a hard 4px rule. The CMS
  wraps each block in its own <div data-block-id>, so the divider adjacency now
  targets the block wrappers ([data-block-id]+[data-block-id] .bru-section), not
  the (non-sibling) sections; landing section padding reduced 4rem->3rem.
- SVG geometry guard completed: explicit width/height on every <use> icon SVG,
  including the 5 testimonial star ratings the earlier regex missed (the ">="
  in their class conditional).
- Seed quality: HARDSET site title hardset; article expanded to ~9 hard-edged
  paragraphs to kill the below-article void.
- Fresh 2880x1800 light+dark captures for home/about/article; preview = home-light.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:53:35 +08:00

70 lines
8.5 KiB
Plaintext

{# brutalist navbar override — thick-ruled bar, uppercase wordmark, mono links, off-canvas drawer. One navbar per page (fixed drawer ids). Fields: companyName, logo, logoType, logoText.text, alt, menu.items[], cta.text|ctaLabel, cta.url|ctaUrl, stickyBehavior, sticky, showUtilityBar, utilityBar*, class. #}
{% set sb = stickyBehavior %}{% if not sb %}{% if sticky %}{% set sb = "sticky" %}{% else %}{% set sb = "none" %}{% endif %}{% endif %}
{% set cta_text = cta.text|default:ctaLabel %}
{% set cta_url = cta.url|default:ctaUrl %}
{% macro brand() %}<a href="/" class="flex-shrink-0 flex items-center">{% if logoType == "text" and logoText.text %}<span class="bru-kicker" style="font-size:1.4rem">{{ logoText.text }}</span>{% elif logo %}<img src="{{ logo }}" alt="{{ alt|default:companyName }}" class="h-8 w-auto"/>{% else %}<span class="bru-kicker" style="font-size:1.4rem">{{ companyName|default:"STUDIO" }}</span>{% endif %}</a>{% endmacro %}
{% 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="bru-meta px-3 py-2 text-foreground hover:text-accent transition-colors {{ item.css_class }}">{{ item.label }}</a>{% endmacro %}
{% macro dropdown(item) %}<div class="relative group"><button type="button" class="bru-meta px-3 py-2 text-foreground hover:text-accent transition-colors inline-flex items-center gap-1">{{ item.label }}<svg width="12" height="12" class="h-3 w-3" aria-hidden="true"><use href="/icons/lucide.svg#chevron-down"></use></svg></button><div class="absolute left-0 mt-0 w-56 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-150 z-50"><div class="mt-2 bru-slab bru-shadow py-2">{% for child in item.children %}{% if child.item_type == "header" %}<div class="bru-meta px-4 py-2 text-muted-foreground">{{ child.label }}</div>{% else %}<a href="{{ child.url|default:"#" }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="block px-4 py-2 text-sm text-foreground hover:bg-accent hover:text-accent-foreground transition-colors {{ child.css_class }}">{{ child.label }}</a>{% endif %}{% endfor %}</div></div></div>{% endmacro %}
{% macro ctabtn(extra) %}{% if cta_text %}<a href="{{ cta_url|default:"#" }}" class="bru-btn bru-btn-solid {{ extra }}" style="padding:0.5rem 1rem">{{ cta_text }}</a>{% endif %}{% endmacro %}
<nav data-bn-navbar data-bn-sticky="{{ sb }}" class="bn-navbar w-full bg-background text-foreground {% if sb == "sticky" or sb == "shrink-on-scroll" %}sticky top-0 z-40 {% endif %}{{ class }}" style="border-bottom:4px solid hsl(var(--border))">
{% if showUtilityBar %}<div class="w-full bg-foreground text-background {% if not showOnMobile %}hidden sm:block{% endif %}"><div class="bru-wrap flex items-center justify-between h-8 bru-meta" style="color:hsl(var(--background))"><div>{{ utilityBarText|safe }}</div><div class="flex items-center gap-4">{% if utilityBarPhone %}<a href="tel:{{ utilityBarPhone }}" class="hover:text-accent">{{ utilityBarPhone }}</a>{% endif %}{% if utilityBarEmail %}<a href="mailto:{{ utilityBarEmail }}" class="hover:text-accent">{{ utilityBarEmail }}</a>{% endif %}</div></div></div>{% endif %}
<div class="bru-wrap"><div class="bn-navbar-row flex items-center justify-between h-16">
<div class="flex items-center">{{ brand() }}</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>
<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 cursor-pointer" style="border:2px solid hsl(var(--border))"><span class="sr-only">Open menu</span><svg width="24" height="24" class="h-6 w-6" aria-hidden="true"><use href="/icons/lucide.svg#menu"></use></svg></label></div>
</div></div>
<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 md:hidden flex flex-col" style="border-left:4px solid hsl(var(--border))" role="dialog" aria-modal="true" aria-label="Site menu">
<div class="flex items-center justify-between p-4" style="border-bottom:4px solid hsl(var(--border))">{{ brand() }}<label for="bn-navbar-toggle" data-bn-nav-close class="p-2 cursor-pointer text-foreground" style="border:2px solid hsl(var(--border))"><span class="sr-only">Close menu</span><svg width="24" height="24" class="h-6 w-6" aria-hidden="true"><use href="/icons/lucide.svg#x"></use></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 bru-meta cursor-pointer hover:bg-accent hover:text-accent-foreground list-none">{{ item.label }}<svg width="12" height="12" class="h-3 w-3 transition-transform group-open/acc:rotate-180" aria-hidden="true"><use href="/icons/lucide.svg#chevron-down"></use></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-foreground">{{ 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 bru-meta hover:bg-accent hover:text-accent-foreground">{{ item.label }}</a>{% endif %}{% endfor %}</nav>
{% if cta_text %}<div class="p-4" style="border-top:4px solid hsl(var(--border))"><a href="{{ cta_url|default:"#" }}" class="bru-btn bru-btn-solid w-full">{{ cta_text }}</a></div>{% endif %}
</div>
</nav>
<style>
.bn-navbar-toggle{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;}
/* Drawer + overlay geometry lives here, not on Tailwind classes: fixed/top-0/
right-0/h-full/w-80/max-w-[85vw]/inset-0/z-* are not in the host safelist and
a runtime-installed theme's classes aren't JIT-scanned, so relying on them
would leave the mobile drawer unpositioned. display stays on the safelisted
flex/flex-col/md:hidden classes so the drawer is desktop-hidden. */
.bn-navbar-drawer{position:fixed;top:0;right:0;height:100%;width:20rem;max-width:85vw;z-index:50;transform:translateX(100%);transition:transform .3s ease;}
.bn-navbar-overlay{position:fixed;inset:0;z-index:40;background:rgba(0,0,0,0.55);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;}
</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'));});});
}
});
});
})();
</script>