WO-TF-012. Neo-brutalist creative-studio theme brought up to the Wave A OVERRIDE-CONTRACT (frozen against cms a0f07e2ae). - Override all 49 mandatory builtin keys in templates/overrides/brutalist/ (chrome, marketing, primitives, commerce, blog family, auth surface, 404), each carrying the brutalist chrome (chunky borders, hard offset shadows, hover snap, mono uppercase meta, candy accents) via a raw-CSS "bru-*" kit shipped in manifest.yaml. Load-bearing wiring (HTMX auth endpoints, signup form, contact-form submit + honeypot, facade scripts, lightbox) preserved verbatim. - 7 page templates: default, full-width, landing, article, blog-index, contact, auth (fixed a bug where templates omitted the `dark` class so dark presets never activated). - Blocks rework: removed 6 blocks that duplicated builtins (masthead, concrete_hero, meta_strip, caption_image, pull_quote, colophon) — now overrides; kept project_ledger; added persona blocks sticker_wall, changelog, spec_sheet (4 total). - 4 dual-mode presets (all mode "both", 19 tokens light + dark): Concrete & Cadmium, Hazard, Riso, Acid. - Bundled 8 latin-subset OFL woff2 (Space Grotesk, Archivo Black, Inter, IBM Plex Mono) in assets/fonts/ + LICENSES.md; fonts.json populated; all font-family via var(--font-*). RECOMMENDED_FONTS/ICONS updated; required_icon_packs declared. - Email-safe themed wrapper (tables, inline styles) cleaned up. - master_pages.json reworked to builtin navbar/hero/cta/footer. - Demo seed (seed/seed.json + workflows.json, demo:true): home, about, blog (3 posts), contact (form + contact_submissions data table + row_inserted → email-admins workflow), login — studio voice. Known codeless limitation: page-suggestions renders themed 404 chrome but the similar-pages list (server-side DB query, no template provider) can't be reproduced; auth-form captcha widget is server-generated and can't be emitted (seed leaves captchaEnabled=false). Verified: make exit 0; check-safety exit 0. Visual quality UNVERIFIED until Wave B. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
53 lines
5.6 KiB
Plaintext
53 lines
5.6 KiB
Plaintext
{# brutalist gallery override — hard-bordered tiles + dependency-free lightbox. Fields: images[.src,.alt,.caption], layout, columns, gap, lightbox. #}
|
|
{% with layout=layout|default:"grid" gap=gap|default:"md" cols=columns|default:3 %}<div class="bn-gallery my-8{% if class %} {{ class }}{% endif %}" data-bn-gallery data-lightbox="{% if lightbox %}true{% else %}false{% endif %}"><div class="{% if layout == "carousel" %}flex snap-x snap-mandatory overflow-x-auto pb-2 {% if gap == "sm" %}gap-2{% elif gap == "lg" %}gap-6{% else %}gap-4{% endif %}{% elif layout == "masonry" %}columns-2 {% if cols == 4 %}md:columns-4{% elif cols == 2 %}md:columns-2{% else %}md:columns-3{% endif %} {% if gap == "sm" %}gap-2{% elif gap == "lg" %}gap-6{% else %}gap-4{% endif %}{% else %}grid grid-cols-2 {% if cols == 4 %}md:grid-cols-4{% elif cols == 2 %}md:grid-cols-2{% else %}md:grid-cols-3{% endif %} {% if gap == "sm" %}gap-2{% elif gap == "lg" %}gap-6{% else %}gap-4{% endif %}{% endif %}">{% for image in images %}<a href="{{ image.src|mediaURL }}" data-bn-gallery-item data-caption="{{ image.caption|default:"" }}" class="group relative block overflow-hidden bru-slab bru-hover bru-shadow-sm focus:outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring {% if layout == "carousel" %}w-64 shrink-0 snap-start sm:w-80{% elif layout == "masonry" %}mb-4 w-full break-inside-avoid{% endif %}">{% if layout == "grid" %}{% img image.src alt=image.alt|default:"" class="aspect-square w-full object-cover" %}{% else %}{% img image.src alt=image.alt|default:"" class="h-auto w-full" %}{% endif %}{% if image.caption %}<span class="sr-only">{{ image.caption }}</span>{% endif %}</a>{% endfor %}</div></div>{% if lightbox %}<script>
|
|
(function(){
|
|
if(window.__bnLightbox)return;window.__bnLightbox=true;
|
|
var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
var ov,imgEl,capEl,items=[],idx=0,lastFocus=null;
|
|
function build(){
|
|
ov=document.createElement('div');
|
|
ov.setAttribute('role','dialog');ov.setAttribute('aria-modal','true');ov.setAttribute('aria-label','Image viewer');ov.tabIndex=-1;
|
|
ov.style.cssText='position:fixed;inset:0;z-index:9999;display:none;align-items:center;justify-content:center;background:rgba(0,0,0,0.9);padding:2rem;';
|
|
var btn='position:absolute;width:2.75rem;height:2.75rem;display:inline-flex;align-items:center;justify-content:center;border:2px solid hsl(var(--foreground));background:hsl(var(--background));color:hsl(var(--foreground));cursor:pointer;';
|
|
ov.innerHTML=
|
|
'<button type="button" data-lb-close aria-label="Close" style="'+btn+'top:1rem;right:1rem;"><svg style="width:1.25rem;height:1.25rem" aria-hidden="true"><use href="/icons/lucide.svg#x"/></svg></button>'+
|
|
'<button type="button" data-lb-prev aria-label="Previous image" style="'+btn+'left:1rem;top:50%;transform:translateY(-50%);"><svg style="width:1.25rem;height:1.25rem" aria-hidden="true"><use href="/icons/lucide.svg#chevron-left"/></svg></button>'+
|
|
'<figure style="margin:0;max-width:92vw;max-height:92vh;display:flex;flex-direction:column;align-items:center;gap:0.75rem;"><img data-lb-img alt="" style="max-width:92vw;max-height:82vh;object-fit:contain;border:3px solid hsl(var(--background));'+(reduce?'':'transition:opacity .2s ease;')+'"><figcaption data-lb-cap style="color:hsl(var(--background));opacity:.85;font-family:var(--font-mono,ui-monospace,monospace);font-size:.75rem;letter-spacing:.08em;text-transform:uppercase;text-align:center;max-width:60ch;"></figcaption></figure>'+
|
|
'<button type="button" data-lb-next aria-label="Next image" style="'+btn+'right:1rem;top:50%;transform:translateY(-50%);"><svg style="width:1.25rem;height:1.25rem" aria-hidden="true"><use href="/icons/lucide.svg#chevron-right"/></svg></button>';
|
|
document.body.appendChild(ov);
|
|
imgEl=ov.querySelector('[data-lb-img]');capEl=ov.querySelector('[data-lb-cap]');
|
|
ov.addEventListener('click',function(e){if(e.target===ov||e.target.closest('[data-lb-close]'))close();});
|
|
ov.querySelector('[data-lb-prev]').addEventListener('click',function(e){e.stopPropagation();show(idx-1);});
|
|
ov.querySelector('[data-lb-next]').addEventListener('click',function(e){e.stopPropagation();show(idx+1);});
|
|
}
|
|
function show(i){
|
|
if(!items.length)return;
|
|
idx=(i+items.length)%items.length;
|
|
var a=items[idx];var im=a.querySelector('img');
|
|
imgEl.src=(im&&(im.currentSrc||im.src))||a.getAttribute('href');
|
|
imgEl.alt=im?im.alt:'';
|
|
var cap=a.getAttribute('data-caption')||'';capEl.textContent=cap;capEl.style.display=cap?'block':'none';
|
|
}
|
|
function open(g,link){
|
|
if(!ov)build();
|
|
items=Array.prototype.slice.call(g.querySelectorAll('[data-bn-gallery-item]'));
|
|
lastFocus=document.activeElement;ov.style.display='flex';
|
|
var multi=items.length>1;
|
|
ov.querySelector('[data-lb-prev]').style.display=multi?'inline-flex':'none';
|
|
ov.querySelector('[data-lb-next]').style.display=multi?'inline-flex':'none';
|
|
show(items.indexOf(link));ov.focus();
|
|
}
|
|
function close(){if(ov)ov.style.display='none';if(lastFocus&&lastFocus.focus)lastFocus.focus();}
|
|
document.addEventListener('click',function(e){
|
|
var link=e.target.closest('[data-bn-gallery][data-lightbox="true"] [data-bn-gallery-item]');
|
|
if(!link)return;e.preventDefault();open(link.closest('[data-bn-gallery]'),link);
|
|
});
|
|
document.addEventListener('keydown',function(e){
|
|
if(!ov||ov.style.display==='none')return;
|
|
if(e.key==='Escape'){e.preventDefault();close();}
|
|
else if(e.key==='ArrowLeft')show(idx-1);
|
|
else if(e.key==='ArrowRight')show(idx+1);
|
|
});
|
|
})();
|
|
</script>{% endif %}{% endwith %}
|