Alex Dunmow 85d95d0545 fix(kindergarten): repair hero, grids, empty media + mobile whitespace
Remediate broken layout on the demo home page (persona intact):

- Hero rendered with no height: it relied on min-h-screen /
  responsive Tailwind that the host JIT drops on a cold provision.
  Drive hero sizing + type through compiled .kg-hero* CSS classes
  (input_css_append is compiled every build). Mascot repositioned to
  bottom-center behind readable content; confetti/ABC/reduced-motion
  fallback preserved. All builtin field reads unchanged.
- Landing header was position:absolute and overlapped the feature
  grid when the hero collapsed -> switch to the default's in-flow
  sticky header (collision gone).
- feature-grid columns never formed (lg:grid-cols-3 not compiled) ->
  route through compiled .kg-cols/.kg-cols-3 media-query grid.
- Empty art-wall polaroids ballooned into large blank squares ->
  graceful 4:3 capped placeholder with a playful crayon wash.
- Define .kg-section (was only in the dead assets/style.css, so
  persona sections had no padding) and add a consistent .kg-sect
  rhythm; trim oversized section padding.
- Alphabet strip now a single scrollable row (was flex-wrap) so the
  sticky header stays compact on mobile.
- Mobile: all layout-critical grids are compiled media-query CSS
  (mobile-first, 1 col), no fixed widths > viewport, tap targets ok.

No .reveal/scroll-hide pattern in this theme; whitespace was real
layout void. Colors stay semantic tokens; check-safety 32 checks OK.
No version bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:26:20 +08:00

86 lines
6.5 KiB
Plaintext

{# hero kindergarten override (GO-BIG showpiece): a bouncing mascot, wiggling ABC letters, and a dependency-free confetti canvas fill the panel when there is no backgroundImage. The canvas lazy-inits on view, honours prefers-reduced-motion, and degrades to a static confetti wash with JS off. Sizing + typography are driven by compiled .kg-hero* CSS (NOT min-h-screen / responsive Tailwind, which the host JIT drops on a cold provision). All builtin field reads are preserved from the default hero. #}
{% set eyebrowText = eyebrow.text|default:eyebrow %}
{% set headlineText = headline.text|default:headline %}
{% set subText = subheadline.text|default:subheadline %}
{% set descText = description.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 != "" %}
{% set align = "center" %}
{% if variant == "left-aligned" or variant == "split" %}{% set align = "left" %}{% endif %}
<section class="kg-hero {% if align == "center" %}kg-hero--center{% endif %}{% if class %} {{ class }}{% endif %}">
{% if hasImage %}<div class="kg-hero-media">{% img backgroundImage alt=headlineText class="h-full w-full object-cover" layout="hero" %}{% if showOverlay %}<div class="kg-hero-overlay"></div>{% endif %}</div>{% else %}<div class="kg-hero-media" aria-hidden="true"><div class="kg-confetti-wrap"><div class="kg-confetti-fallback"></div><canvas class="kg-confetti-canvas"></canvas></div><span class="kg-hero-mascot"><span class="kg-mascot inline-block"><svg viewBox="0 0 200 200" width="150" height="150" role="img" aria-label="Pip the mascot"><circle cx="100" cy="115" r="70" fill="hsl(var(--accent))" stroke="hsl(var(--foreground))" stroke-width="4"></circle><circle cx="78" cy="100" r="8" fill="hsl(var(--foreground))"></circle><circle cx="122" cy="100" r="8" fill="hsl(var(--foreground))"></circle><circle cx="78" cy="98" r="3" fill="hsl(var(--background))"></circle><circle cx="122" cy="98" r="3" fill="hsl(var(--background))"></circle><path d="M 75 130 Q 100 155 125 130" stroke="hsl(var(--foreground))" stroke-width="5" fill="none" stroke-linecap="round"></path><circle cx="60" cy="115" r="6" fill="hsl(var(--secondary))"></circle><circle cx="140" cy="115" r="6" fill="hsl(var(--secondary))"></circle><path d="M 50 60 L 60 35 L 80 50 Z" fill="hsl(var(--primary))"></path><path d="M 150 60 L 140 35 L 120 50 Z" fill="hsl(var(--primary))"></path></svg></span></span></div>{% endif %}
<div class="kg-hero-inner">
{% if eyebrowText %}<span class="kg-sticker">{{ eyebrowText }}</span>{% endif %}
{% if headlineText %}<h1 class="kg-display kg-hero-title"><span class="kg-crayon-underline">{{ headlineText }}</span></h1>{% endif %}
<span class="kg-hero-abc" aria-hidden="true"><span class="kg-wiggle-letter">A</span><span class="kg-wiggle-letter">B</span><span class="kg-wiggle-letter">C</span></span>
{% if subText %}<h2 class="kg-hero-sub">{{ subText }}</h2>{% endif %}
{% if descText %}<p class="kg-hero-desc">{{ descText }}</p>{% endif %}
{% if pText or sText %}<div class="kg-hero-cta">
{% if pText and pUrl %}<a href="{{ pUrl }}" class="kg-btn kg-btn-primary kg-pop">{{ pText }}</a>{% endif %}
{% if sText and sUrl %}<a href="{{ sUrl }}" class="kg-btn kg-btn-outline kg-pop">{{ sText }}</a>{% endif %}
</div>{% endif %}
{% if trustLogos %}<div class="kg-hero-trust">
<p class="kg-eyebrow">Loved by families at</p>
<div class="kg-hero-logos">{% for logo in trustLogos %}{% if logo.url %}<a href="{{ logo.url }}" target="_blank" rel="noopener noreferrer" class="opacity-70 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-70" %}{% endif %}{% endfor %}</div>
</div>{% endif %}
</div>
</section>
<script>
(function(){
if(window.__kgConfetti)return;window.__kgConfetti=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 cols=['hsl('+(raw('--primary')||'0 0% 50%')+')','hsl('+(raw('--secondary')||raw('--primary')||'0 0% 50%')+')','hsl('+(raw('--accent')||raw('--primary')||'0 0% 50%')+')'];
var W=0,H=0,dpr=1,pieces=[];
function make(){return {x:Math.random(),y:Math.random()*-1,r:6+Math.random()*8,rot:Math.random()*6.28,vr:(Math.random()-0.5)*0.15,sp:0.0016+Math.random()*0.0026,sway:0.3+Math.random()*0.7,c:cols[Math.floor(Math.random()*cols.length)],sq:Math.random()<0.5};}
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);
var n=Math.max(16,Math.min(70,Math.floor(W/16)));
pieces=[];for(var i=0;i<n;i++){var p=make();p.y=Math.random();pieces.push(p);}
}
resize();
var last=0,fps=1000/32;
function frame(t){
requestAnimationFrame(frame);
if(t-last<fps)return;last=t;
if(W===0||H===0){resize();return;}
ctx.clearRect(0,0,W,H);
for(var i=0;i<pieces.length;i++){
var p=pieces[i];
p.y+=p.sp;p.rot+=p.vr;
var px=(p.x*W)+Math.sin(p.y*6.28*p.sway)*14;
var py=p.y*H;
ctx.save();ctx.translate(px,py);ctx.rotate(p.rot);ctx.fillStyle=p.c;
if(p.sq){ctx.fillRect(-p.r/2,-p.r/2,p.r,p.r);}else{ctx.beginPath();ctx.arc(0,0,p.r/2,0,6.28);ctx.fill();}
ctx.restore();
if(py>H+20){pieces[i]=make();}
}
}
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('.kg-confetti-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>