Preschool / daycare identity: crayon borders, sticker badges, confetti, and a dependency-free bouncing-mascot + confetti-canvas hero showpiece (hero/landing only, lazy-init, prefers-reduced-motion honored, static no-JS fallback). - All 49 mandatory builtin overrides (OVERRIDE-CONTRACT §1), field-correct against the frozen cms builtins, re-skinned to the crayon design system. Legacy button (label/link/style) and card (text/media) field reads reconciled. - 7 page templates: default, full-width, landing, article, blog-index, contact, auth — standard header/main/footer slots, dotted-paper chrome, dual-mode. - Blocks reworked: mascot_hero/big_cta/gallery_of_art/numbers_counter/ storybook_quote/footer converted to builtin overrides; 4 persona blocks kept/ added — alphabet_strip (array-shaped, gotcha-safe), schedule, art_wall, story_time. - 5 presets, all mode "both", bright-classroom light + quiet-time navy dark. - Bundled OFL woff2 (Baloo 2 700, Quicksand 400/700) in assets/fonts + LICENSES; all font-family via var(--font-*); RECOMMENDED_FONTS/ICONS updated; required_icon_packs declared. - Themed email-safe wrapper; master pages (site + auth); seed demo site (home/about/blog+3 posts/contact form+row_inserted email workflow/login), preschool voice. Known platform limit (OVERRIDE-CONTRACT §11): auth-form/auth-status/ password-reset-form/page-suggestions overrides are authored correct-to-model but do not dispatch (compiled BlockFuncs). page-suggestions also omits the dynamic suggestion list (no FTS in codeless). Verified: `make` (ninja plugin build) exit 0; check-safety exit 0. Visual quality UNVERIFIED until Wave B. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
95 lines
8.6 KiB
Plaintext
95 lines
8.6 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. All builtin field reads + macro + branch structure are preserved from the default hero — only the styling changes. #}
|
|
{% 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 != "" %}
|
|
{% macro playmascot() %}<div class="kg-confetti-wrap" aria-hidden="true"><div class="kg-confetti-fallback"></div><canvas class="kg-confetti-canvas"></canvas></div><div aria-hidden="true" class="pointer-events-none absolute inset-0 z-[1] flex items-center justify-center"><span class="kg-mascot inline-block"><svg viewBox="0 0 200 200" width="180" height="180" 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></div>{% endmacro %}
|
|
{% macro herocontent(align) %}
|
|
{% if eyebrowText %}<span class="kg-sticker mb-4">{{ eyebrowText }}</span>{% endif %}
|
|
{% if headlineText %}<h1 class="kg-display text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight text-foreground"><span class="kg-crayon-underline">{{ headlineText }}</span></h1>{% endif %}
|
|
<span class="mt-4 inline-flex gap-1 text-3xl" aria-hidden="true"><span class="kg-wiggle-letter text-primary">A</span><span class="kg-wiggle-letter text-secondary">B</span><span class="kg-wiggle-letter text-accent">C</span></span>
|
|
{% 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="kg-display kg-pop inline-flex items-center justify-center gap-2 rounded-full bg-primary px-7 py-3.5 text-lg font-bold text-primary-foreground kg-drop">{{ pText }}</a>{% endif %}
|
|
{% if sText and sUrl %}<a href="{{ sUrl }}" class="kg-display kg-pop inline-flex items-center justify-center rounded-full border-[3px] border-primary bg-background px-7 py-3.5 text-lg font-bold text-primary">{{ sText }}</a>{% endif %}
|
|
</div>{% endif %}
|
|
{% if trustLogos %}<div class="mt-10">
|
|
<p class="kg-eyebrow mb-3">Loved by families at</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-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 %}
|
|
{% 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 min-h-[340px] bg-accent/15 {% 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 %}{{ playmascot() }}{% endif %}</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" %}min-h-screen{% elif minHeight == "50vh" %}min-h-[50vh]{% elif minHeight == "auto" %}min-h-[400px]{% else %}min-h-[75vh]{% 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="absolute inset-0 z-0 bg-accent/15">{{ playmascot() }}</div>{% endif %}
|
|
<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.__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>
|