Hero blank-hero bug: eyebrow/headline/subheadline/description read
field.text|default:field, which hard-errors in the ninjatpl engine on
bare-string seed values (attribute access on a string), blanking the whole
hero. Switch to field|get:"text"|default:field — the process-global `get`
filter degrades gracefully on both {text,...} objects and bare strings.
Responsive layout back to Tailwind (supersedes the prior CSS-class approach
in 6787a01): the host Tailwind build now scans theme .ninjatpl, so the
cp-grid / cp-cols / cp-span / cp-split / cp-article-grid classes were exact
reimplementations of stock utilities. Restore plain Tailwind in the
templates (grid grid-cols-1 sm:/md:/lg:grid-cols-*, gap-*, columns-*,
col-span-*, before:* timeline connector) across feature-grid, pricing,
stats, team, testimonial, contact-card, featured/related-posts, blog-hero,
menu-list, event-list, hours-location, timeline, footer, gallery,
rich-section, roster_grid, article + hero split section, and delete the
duplicate CSS from manifest css.input_css_append.
Kept as genuinely bespoke design CSS: cp-hero-* bounded clamp() min-heights
(the anti-balloon whitespace fix — not a stock min-h-* utility; restoring
min-h-screen would reintroduce the empty neon void) and the reduced-motion /
high-contrast blocks.
Scroll-reveal: cyberpunk ships no opacity-hidden .reveal content — its two
IntersectionObservers (hero digital-rain, stats countUp) fire on the IO
initial callback when already in view and both have static, visible no-JS /
reduced-motion fallbacks, so content is never hidden behind JS.
make (codeless .bnp) OK; check-safety 32 checks 19 ok 13 skip -> OK.
No version bump. No push.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
96 lines
7.6 KiB
Plaintext
96 lines
7.6 KiB
Plaintext
{# hero cyberpunk override (GO-BIG showpiece): canvas digital-rain background, glitch headline, scanline overlay. All builtin field reads + macro + branch structure preserved. The rain shows when there is no backgroundImage; it lazy-inits on view, honors prefers-reduced-motion, degrades to a static neon gradient with JS off, and is dependency-free. #}
|
|
{% set eyebrowText = eyebrow|get:"text"|default:eyebrow %}
|
|
{% set headlineText = headline|get:"text"|default:headline %}
|
|
{% set subText = subheadline|get:"text"|default:subheadline %}
|
|
{% set descText = description|get:"text"|default:description %}
|
|
{% set pText = ctaPrimary.text %}
|
|
{% set pUrl = ctaPrimary.url %}
|
|
{% set sText = ctaSecondary.text %}
|
|
{% set sUrl = ctaSecondary.url %}
|
|
{% set variant = template|default:"centered" %}
|
|
{% set hasImage = backgroundImage %}
|
|
{% set showOverlay = hasImage and overlayColor != "none" and overlayColor != "" %}
|
|
{% macro herocontent(align) %}
|
|
{% if eyebrowText %}<span class="cyberpunk-mono clip-cut inline-block mb-4 px-3 py-1 rounded-md border border-primary/50 bg-primary/10 text-primary text-xs font-semibold uppercase tracking-widest">{{ eyebrowText }} <span aria-hidden="true" class="caret-blink">_</span></span>{% endif %}
|
|
{% if headlineText %}<h1 class="cyberpunk-display glitch text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight text-foreground" data-text="{{ headlineText }}">{{ headlineText }}</h1>{% endif %}
|
|
{% if subText %}<h2 class="mt-4 text-xl sm:text-2xl text-muted-foreground max-w-2xl{% if align == "center" %} mx-auto{% endif %}">{{ subText }}</h2>{% endif %}
|
|
{% if descText %}<p class="mt-4 max-w-xl leading-relaxed text-muted-foreground{% if align == "center" %} mx-auto{% endif %}">{{ descText }}</p>{% endif %}
|
|
{% if pText or sText %}<div class="mt-8 flex flex-col sm:flex-row gap-4{% if align == "center" %} justify-center{% endif %}">
|
|
{% if pText and pUrl %}<a href="{{ pUrl }}" class="cyberpunk-mono clip-cut rgb-split inline-flex items-center justify-center gap-2 rounded-md bg-primary px-6 py-3 font-semibold uppercase tracking-wider text-primary-foreground transition-opacity hover:opacity-90">{{ pText }}</a>{% endif %}
|
|
{% if sText and sUrl %}<a href="{{ sUrl }}" class="cyberpunk-mono inline-flex items-center justify-center rounded-md border border-accent/60 px-6 py-3 font-semibold uppercase tracking-wider text-accent transition-colors hover:bg-accent/10">{{ sText }}</a>{% endif %}
|
|
</div>{% endif %}
|
|
{% if trustLogos %}<div class="mt-10">
|
|
<p class="cyberpunk-mono text-xs uppercase tracking-widest text-muted-foreground mb-3">Trusted by</p>
|
|
<div class="flex flex-wrap items-center gap-6{% if align == "center" %} justify-center{% endif %}">{% for logo in trustLogos %}{% if logo.url %}<a href="{{ logo.url }}" target="_blank" rel="noopener noreferrer" class="opacity-60 transition-opacity hover:opacity-100">{% img logo.image alt=logo.alt class="h-8 w-auto" %}</a>{% else %}{% img logo.image alt=logo.alt class="h-8 w-auto opacity-60" %}{% endif %}{% endfor %}</div>
|
|
</div>{% endif %}
|
|
{% endmacro %}
|
|
{% if variant == "split" %}
|
|
<section class="hero grid grid-cols-1 lg:grid-cols-2 overflow-hidden bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
|
<div class="relative cp-hero-split bg-muted {% if mediaPosition == "left" %}order-1 lg:order-1{% else %}order-1 lg:order-2{% endif %}">{% if hasImage %}{% img backgroundImage alt=headlineText class="absolute inset-0 h-full w-full object-cover" layout="hero" %}{% else %}<div class="cyberpunk-rain-wrap"><div class="cyberpunk-rain-fallback"></div><canvas class="cyberpunk-rain-canvas" aria-hidden="true"></canvas></div>{% endif %}<div aria-hidden="true" class="scanlines pointer-events-none absolute inset-0"></div></div>
|
|
<div class="flex flex-col justify-center px-6 py-16 lg:px-12 {% if mediaPosition == "left" %}order-2 lg:order-2{% else %}order-2 lg:order-1{% endif %}"><div class="max-w-lg text-left">{{ herocontent("left") }}</div></div>
|
|
</section>
|
|
{% else %}
|
|
{% set align = "center" %}
|
|
{% if variant == "left-aligned" %}{% set align = "left" %}{% endif %}
|
|
<section class="hero relative flex flex-col overflow-hidden bg-background text-foreground {% if minHeight == "screen" %}cp-hero-tall{% elif minHeight == "50vh" %}cp-hero-short{% elif minHeight == "auto" %}cp-hero-auto{% else %}cp-hero-mid{% endif %} {% if verticalAlign == "top" %}justify-start pt-20{% elif verticalAlign == "bottom" %}justify-end pb-20{% else %}justify-center{% endif %}{% if class %} {{ class }}{% endif %}">
|
|
{% if hasImage %}<div class="absolute inset-0 z-0">{% img backgroundImage alt=headlineText class="h-full w-full object-cover" layout="hero" %}{% if showOverlay %}<div class="absolute inset-0 {% if overlayColor == "light" %}bg-background/60{% elif overlayColor == "primary" %}bg-primary/50{% elif overlayOpacity <= 30 %}bg-black/30{% elif overlayOpacity <= 50 %}bg-black/50{% elif overlayOpacity <= 70 %}bg-black/70{% else %}bg-black/80{% endif %}"></div>{% endif %}</div>{% else %}<div class="cyberpunk-rain-wrap z-0"><div class="cyberpunk-rain-fallback"></div><canvas class="cyberpunk-rain-canvas" aria-hidden="true"></canvas></div>{% endif %}
|
|
<div aria-hidden="true" class="scanlines pointer-events-none absolute inset-0 z-0"></div>
|
|
<div class="relative z-10 mx-auto w-full max-w-4xl px-4 sm:px-6 lg:px-8 {% if variant == "left-aligned" %}text-left{% else %}text-center{% endif %}">{{ herocontent(align) }}</div>
|
|
</section>
|
|
{% endif %}
|
|
<script>
|
|
(function(){
|
|
if(window.__cyberpunkRain)return;window.__cyberpunkRain=1;
|
|
var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
function initCanvas(cv){
|
|
if(cv.__init)return;cv.__init=1;
|
|
var ctx=cv.getContext&&cv.getContext('2d');if(!ctx)return;
|
|
var wrap=cv.parentElement;
|
|
var cs=getComputedStyle(document.documentElement);
|
|
function raw(n){return cs.getPropertyValue(n).trim();}
|
|
var fg=raw('--foreground');
|
|
var col1='hsl('+(raw('--primary')||fg)+')';
|
|
var col2='hsl('+(raw('--accent')||fg)+')';
|
|
var fade='hsl('+(raw('--background')||fg)+' / 0.14)';
|
|
var glyphs='01<>[]{}#$%&*+=/|=abcdefghijklmnopqrstuvwxyz'.split('');
|
|
var fontSize=14,cols=0,drops=[],W=0,H=0,dpr=1;
|
|
function resize(){
|
|
dpr=Math.min(window.devicePixelRatio||1,2);
|
|
W=wrap.clientWidth;H=wrap.clientHeight;
|
|
if(W===0||H===0)return;
|
|
cv.width=W*dpr;cv.height=H*dpr;ctx.setTransform(dpr,0,0,dpr,0,0);
|
|
cols=Math.max(1,Math.floor(W/fontSize));
|
|
drops=[];for(var i=0;i<cols;i++)drops[i]=Math.random()*-50;
|
|
}
|
|
resize();
|
|
var last=0,fps=1000/18;
|
|
function frame(t){
|
|
requestAnimationFrame(frame);
|
|
if(t-last<fps)return;last=t;
|
|
if(W===0||H===0){resize();return;}
|
|
ctx.fillStyle=fade;ctx.fillRect(0,0,W,H);
|
|
ctx.font=fontSize+'px monospace';
|
|
for(var i=0;i<cols;i++){
|
|
var ch=glyphs[Math.floor(Math.random()*glyphs.length)];
|
|
var x=i*fontSize,y=drops[i]*fontSize;
|
|
ctx.fillStyle=Math.random()<0.5?col1:col2;
|
|
ctx.fillText(ch,x,y);
|
|
if(y>H&&Math.random()>0.975)drops[i]=0;
|
|
drops[i]++;
|
|
}
|
|
}
|
|
if(window.ResizeObserver){var ro=new ResizeObserver(resize);ro.observe(wrap);}else{window.addEventListener('resize',resize);}
|
|
requestAnimationFrame(frame);
|
|
}
|
|
function boot(){
|
|
if(reduce)return;
|
|
var cvs=document.querySelectorAll('.cyberpunk-rain-canvas');
|
|
if(!cvs.length)return;
|
|
if(!('IntersectionObserver' in window)){cvs.forEach(initCanvas);return;}
|
|
var io=new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting){initCanvas(e.target);io.unobserve(e.target);}});},{threshold:0.05});
|
|
cvs.forEach(function(c){io.observe(c);});
|
|
}
|
|
if(document.readyState!='loading')boot();else document.addEventListener('DOMContentLoaded',boot);
|
|
})();
|
|
</script>
|