).
+ No hardcoded hex / rgb / named colours.
============================================================ */
-
- /* ---- Typography (CSS-variable consumers + fallbacks) ------- */
-
- .kg-page {
- font-family: var(--font-body, "Nunito", "Quicksand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
- line-height: 1.7;
- background-color: hsl(var(--background));
- color: hsl(var(--foreground));
+
+ /* ---- Typography -------------------------------------------- */
+ /* Families flow through the admin-bound CSS variables; the
+ fallback leads with the bundled OFL family so the intended look
+ survives before the admin assigns a slot. @font-face for the
+ bundled families is auto-emitted from fonts.json. */
+
+ .kg-body {
+ font-family: var(--font-body, "Quicksand", "Nunito", system-ui, -apple-system, "Segoe UI", sans-serif);
}
-
- .kg-display,
- .kg-page h1,
- .kg-page h2,
- .kg-page h3,
- .kg-page h4,
- .kg-page h5,
- .kg-page h6 {
- font-family: var(--font-heading, "Quicksand", "Sniglet", "Nunito", -apple-system, sans-serif);
- font-weight: 700;
- letter-spacing: -0.01em;
+ .kg-display {
+ font-family: var(--font-heading, "Baloo 2", "Fredoka", "Quicksand", system-ui, sans-serif);
+ font-weight: 700;
+ letter-spacing: -0.01em;
}
-
.kg-mono {
- font-family: var(--font-mono, "Fira Code", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace);
+ font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace);
}
-
- /* ---- Layout utilities -------------------------------------- */
-
- .kg-section {
- padding-block: 4rem;
+ .kg-prose,
+ .kg-prose p,
+ .kg-prose li {
+ font-family: var(--font-body, "Quicksand", "Nunito", sans-serif);
+ line-height: 1.75;
}
-
- .kg-container {
- max-width: 72rem;
- margin-inline: auto;
- padding-inline: 1.5rem;
+ .kg-prose code {
+ color: hsl(var(--primary));
+ background-color: hsl(var(--muted));
+ padding: 0.125rem 0.4rem;
+ border-radius: 0.5rem;
+ font-family: var(--font-mono, ui-monospace, Menlo, monospace);
+ font-size: 0.875em;
}
-
- /* ---- Rounded primitives ------------------------------------ */
-
- .kg-chip {
- border-radius: 24px;
- padding: 0.5rem 1rem;
- display: inline-flex;
- align-items: center;
- gap: 0.5rem;
+ /* Friendly kicker: rounded, bold, primary — carries enough on its
+ own that it is never a silent no-op used alone. */
+ .kg-eyebrow {
+ font-family: var(--font-heading, "Baloo 2", "Quicksand", sans-serif);
+ font-weight: 700;
+ font-size: 0.85rem;
+ letter-spacing: 0.02em;
+ color: hsl(var(--primary));
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
}
-
- .kg-card {
- border-radius: 32px;
- background-color: hsl(var(--card));
- color: hsl(var(--card-foreground, var(--foreground)));
- padding: 2rem;
- border: 1px solid hsl(var(--border));
- position: relative;
- overflow: hidden;
- }
-
- .kg-card::before {
- content: "";
- position: absolute;
- inset: 0 0 auto 0;
- height: 4px;
- background-color: hsl(var(--primary));
- }
-
- .kg-hero-panel {
- border-radius: 48px;
- padding: clamp(2rem, 6vw, 4rem);
- position: relative;
- overflow: hidden;
- background-color: hsl(var(--accent) / 0.15);
- }
-
- .kg-pill {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 0.5rem;
- padding: 1.25rem 2.5rem;
- border-radius: 9999px;
- font-family: var(--font-heading, "Quicksand", "Sniglet", "Nunito", sans-serif);
- font-weight: 700;
- font-size: 1.125rem;
- background-color: hsl(var(--primary));
- color: hsl(var(--primary-foreground));
- border: 0;
- cursor: pointer;
- box-shadow: 0 4px 0 0 hsl(var(--foreground) / 0.15);
- text-decoration: none;
- transition: transform 120ms ease, box-shadow 120ms ease;
- }
-
- .kg-pill:hover {
- transform: translateY(-1px);
- box-shadow: 0 5px 0 0 hsl(var(--foreground) / 0.15);
- }
-
- .kg-pill:active {
- transform: translateY(2px);
- box-shadow: 0 2px 0 0 hsl(var(--foreground) / 0.15);
- }
-
- .kg-pill:focus-visible {
- outline: 3px solid hsl(var(--ring));
- outline-offset: 3px;
- }
-
- /* CTA colorways */
- .kg-pill-red { background-color: hsl(var(--destructive)); color: hsl(var(--destructive-foreground)); }
- .kg-pill-blue { background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground)); }
- .kg-pill-yellow { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); }
- .kg-pill-green { background-color: hsl(145 65% 45%); color: hsl(0 0% 100%); }
-
- /* ---- Crayon underline (utility on headings) ---------------- */
-
+
+ /* ---- Crayon underline (decorative heading accent) ---------- */
+
.kg-crayon-underline {
- position: relative;
- display: inline-block;
+ position: relative;
+ display: inline-block;
}
-
.kg-crayon-underline::after {
- content: "";
- position: absolute;
- left: 0;
- right: 0;
- bottom: -6px;
- height: 8px;
- background: linear-gradient(90deg,
- hsl(var(--primary)) 0 22%,
- hsl(var(--accent)) 22% 50%,
- hsl(var(--secondary)) 50% 78%,
- currentColor 78% 100%);
- border-radius: 4px;
- transform: rotate(-1deg);
- z-index: -1;
+ content: "";
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: -6px;
+ height: 8px;
+ background: linear-gradient(90deg,
+ hsl(var(--primary)) 0 26%,
+ hsl(var(--accent)) 26% 52%,
+ hsl(var(--secondary)) 52% 78%,
+ hsl(var(--primary)) 78% 100%);
+ border-radius: 6px;
+ transform: rotate(-1deg);
+ z-index: -1;
}
-
- /* ---- Numeral badges ---------------------------------------- */
-
- .kg-numeral {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 96px;
- height: 96px;
- border-radius: 50%;
- aspect-ratio: 1 / 1;
- background-color: hsl(var(--accent));
- color: hsl(var(--accent-foreground));
- font-family: var(--font-heading, "Quicksand", "Sniglet", sans-serif);
- font-weight: 700;
- font-size: 2.5rem;
- box-shadow: 0 4px 0 0 hsl(var(--foreground) / 0.15);
+
+ /* ---- Crayon / sticker primitives (drop-in surface chrome) -- */
+ /* kg-crayon-shadow: chunky hand-cut offset shadow + playful lift.
+ Pairs with Tailwind border-* / rounded-* on the same element. */
+
+ .kg-crayon-shadow {
+ box-shadow: 0 4px 0 0 hsl(var(--foreground) / 0.12);
+ transition: transform 160ms ease, box-shadow 160ms ease;
}
-
- /* ---- Polaroid frame for gallery_of_art --------------------- */
-
+ .kg-crayon-shadow:hover,
+ .kg-crayon-shadow:focus-within {
+ transform: translateY(-2px);
+ box-shadow: 0 8px 0 0 hsl(var(--foreground) / 0.14);
+ }
+
+ /* kg-drop: chunky flat button shadow (the crayon "sticker" edge). */
+ .kg-drop {
+ box-shadow: 0 4px 0 0 hsl(var(--foreground) / 0.15);
+ }
+
+ /* kg-pop: playful hover bounce for buttons / interactive chips. */
+ .kg-pop {
+ transition: transform 140ms cubic-bezier(0.34, 1.56, 0.64, 1);
+ }
+ .kg-pop:hover,
+ .kg-pop:focus-visible {
+ transform: translateY(-2px) scale(1.03);
+ }
+ .kg-pop:active {
+ transform: translateY(1px) scale(0.98);
+ }
+
+ /* kg-sticker: peel-sticker badge — rounded, chunky, slight tilt. */
+ .kg-sticker {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ padding: 0.3rem 0.85rem;
+ border-radius: 9999px;
+ font-family: var(--font-heading, "Baloo 2", "Quicksand", sans-serif);
+ font-weight: 700;
+ font-size: 0.8rem;
+ background-color: hsl(var(--accent));
+ color: hsl(var(--accent-foreground));
+ box-shadow: 0 3px 0 0 hsl(var(--foreground) / 0.12);
+ transform: rotate(-2deg);
+ }
+
+ /* Global focus-visible ring (accessibility). */
+ .kg-page a:focus-visible,
+ .kg-page button:focus-visible,
+ .kg-page input:focus-visible,
+ .kg-page textarea:focus-visible,
+ .kg-page select:focus-visible {
+ outline: 3px solid hsl(var(--ring));
+ outline-offset: 2px;
+ }
+
+ /* ---- Page shell ------------------------------------------- */
+
+ .kg-page {
+ font-family: var(--font-body, "Quicksand", "Nunito", system-ui, sans-serif);
+ line-height: 1.7;
+ background-color: hsl(var(--background));
+ color: hsl(var(--foreground));
+ }
+ .kg-page h1, .kg-page h2, .kg-page h3,
+ .kg-page h4, .kg-page h5, .kg-page h6 {
+ font-family: var(--font-heading, "Baloo 2", "Quicksand", sans-serif);
+ font-weight: 700;
+ letter-spacing: -0.01em;
+ }
+ .kg-container {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: 1.5rem;
+ }
+ .kg-empty {
+ text-align: center;
+ padding: 2rem;
+ border-radius: 1.5rem;
+ border: 3px dashed hsl(var(--border));
+ color: hsl(var(--muted-foreground));
+ font-family: var(--font-body, "Quicksand", sans-serif);
+ }
+ /* Dotted-paper page background layer. */
+ .kg-dotted-paper {
+ background-color: hsl(var(--background));
+ background-image: radial-gradient(circle, hsl(var(--muted-foreground) / 0.14) 1px, transparent 1.6px);
+ background-size: 26px 26px;
+ }
+
+ /* ---- Persona-block furniture ------------------------------ */
+
+ .kg-hero-panel {
+ border-radius: 2.5rem;
+ padding: clamp(2rem, 6vw, 4rem);
+ position: relative;
+ overflow: hidden;
+ background-color: hsl(var(--accent) / 0.15);
+ }
+ .kg-card {
+ border-radius: 2rem;
+ background-color: hsl(var(--card));
+ color: hsl(var(--card-foreground));
+ padding: 2rem;
+ border: 3px solid hsl(var(--border));
+ position: relative;
+ overflow: hidden;
+ }
+
+ /* Alphabet strip (persona block). */
+ .kg-alphabet-strip {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.6rem;
+ padding: 1rem;
+ }
+ .kg-alphabet-letter {
+ flex: 0 0 auto;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 56px;
+ height: 56px;
+ border-radius: 1.25rem;
+ font-family: var(--font-heading, "Baloo 2", "Quicksand", sans-serif);
+ font-weight: 700;
+ font-size: 1.5rem;
+ box-shadow: 0 4px 0 0 hsl(var(--foreground) / 0.12);
+ }
+ .kg-alphabet-letter:nth-child(4n+1) { background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground)); }
+ .kg-alphabet-letter:nth-child(4n+2) { background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground)); }
+ .kg-alphabet-letter:nth-child(4n+3) { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); }
+ .kg-alphabet-letter:nth-child(4n+4) { background-color: hsl(var(--destructive)); color: hsl(var(--destructive-foreground)); }
+ .kg-alphabet-mono .kg-alphabet-letter { background-color: hsl(var(--muted)); color: hsl(var(--muted-foreground)); }
+ .kg-alphabet-primary .kg-alphabet-letter { background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground)); }
+
+ /* Day schedule (persona block). */
+ .kg-schedule-item {
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ gap: 1rem;
+ align-items: center;
+ padding: 1rem 1.25rem;
+ border-radius: 1.5rem;
+ background-color: hsl(var(--card));
+ border: 3px solid hsl(var(--border));
+ margin-bottom: 0.75rem;
+ }
+ .kg-schedule-time {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0.25rem 0.85rem;
+ border-radius: 9999px;
+ background-color: hsl(var(--accent));
+ color: hsl(var(--accent-foreground));
+ font-family: var(--font-mono, ui-monospace, monospace);
+ font-weight: 700;
+ }
+ .kg-schedule-activity { color: hsl(var(--foreground)); }
+ .kg-schedule-icon { color: hsl(var(--primary)); }
+
+ /* Art-wall polaroid (persona block). */
+ .kg-gallery-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 1.5rem;
+ }
+ @media (min-width: 640px) { .kg-gallery-grid { grid-template-columns: repeat(2, 1fr); } }
+ @media (min-width: 1024px) { .kg-gallery-grid { grid-template-columns: repeat(3, 1fr); } }
.kg-polaroid {
- background-color: hsl(var(--card));
- border-radius: 16px;
- padding-top: 12px;
- padding-inline: 12px;
- padding-bottom: 36px;
- box-shadow: 0 6px 24px hsl(var(--foreground) / 0.12);
- transform: rotate(-1deg);
- transition: transform 200ms ease;
+ background-color: hsl(var(--card));
+ border-radius: 1rem;
+ padding: 12px 12px 36px;
+ box-shadow: 0 6px 24px hsl(var(--foreground) / 0.12);
+ transform: rotate(-1.5deg);
+ transition: transform 200ms ease;
}
-
- .kg-polaroid:nth-child(2n) { transform: rotate(1.5deg); }
- .kg-polaroid:nth-child(3n) { transform: rotate(-0.5deg); }
- .kg-polaroid:hover { transform: rotate(0deg); }
-
+ .kg-polaroid:nth-child(2n) { transform: rotate(1.5deg); }
+ .kg-polaroid:nth-child(3n) { transform: rotate(-0.5deg); }
+ .kg-polaroid:hover { transform: rotate(0deg); }
.kg-polaroid > img,
.kg-polaroid > .kg-polaroid-image {
- display: block;
- width: 100%;
- aspect-ratio: 1 / 1;
- object-fit: cover;
- border-radius: 8px;
- background-color: hsl(var(--muted));
+ display: block;
+ width: 100%;
+ aspect-ratio: 1 / 1;
+ object-fit: cover;
+ border-radius: 0.5rem;
+ background-color: hsl(var(--muted));
}
-
.kg-polaroid-caption {
- text-align: center;
- margin-top: 0.75rem;
- font-family: var(--font-heading, "Quicksand", "Sniglet", sans-serif);
- color: hsl(var(--foreground));
+ text-align: center;
+ margin-top: 0.75rem;
+ font-family: var(--font-heading, "Baloo 2", "Quicksand", sans-serif);
+ color: hsl(var(--foreground));
}
-
- /* ---- Alphabet strip ---------------------------------------- */
-
- .kg-alphabet-strip {
- display: flex;
- overflow-x: auto;
- gap: 0.5rem;
- padding: 1rem;
- scrollbar-width: thin;
- }
-
- .kg-alphabet-letter {
- flex: 0 0 auto;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 56px;
- height: 56px;
- border-radius: 24px;
- background-color: hsl(var(--primary));
- color: hsl(var(--primary-foreground));
- font-family: var(--font-heading, "Quicksand", "Sniglet", sans-serif);
- font-weight: 700;
- font-size: 1.5rem;
- box-shadow: 0 4px 0 0 hsl(var(--foreground) / 0.12);
- }
-
- .kg-alphabet-letter:nth-child(4n+1) { background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground)); }
- .kg-alphabet-letter:nth-child(4n+2) { background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground)); }
- .kg-alphabet-letter:nth-child(4n+3) { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); }
- .kg-alphabet-letter:nth-child(4n+4) { background-color: hsl(145 65% 45%); color: hsl(0 0% 100%); }
-
- .kg-alphabet-mono .kg-alphabet-letter {
- background-color: hsl(var(--muted));
- color: hsl(var(--muted-foreground));
- }
-
- .kg-alphabet-primary .kg-alphabet-letter {
- background-color: hsl(var(--primary));
- color: hsl(var(--primary-foreground));
- }
-
- /* ---- Day schedule pills ------------------------------------ */
-
- .kg-schedule-item {
- display: grid;
- grid-template-columns: auto 1fr auto;
- gap: 1rem;
- align-items: center;
- padding: 1rem;
- border-radius: 24px;
- background-color: hsl(var(--card));
- border: 1px solid hsl(var(--border));
- margin-bottom: 0.75rem;
- }
-
- .kg-schedule-time {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- padding: 0.25rem 0.75rem;
- border-radius: 9999px;
- background-color: hsl(var(--accent));
- color: hsl(var(--accent-foreground));
- font-family: var(--font-mono, "Fira Code", monospace);
- font-weight: 700;
- }
-
- .kg-schedule-activity {
- font-family: var(--font-body, "Nunito", sans-serif);
- color: hsl(var(--foreground));
- }
-
- .kg-schedule-icon {
- color: hsl(var(--primary));
- }
-
- /* ---- Storybook quote --------------------------------------- */
-
+
+ /* Story-time open book (persona block). */
.kg-storybook {
- display: grid;
- grid-template-columns: 1fr;
- gap: 2rem;
- padding: 2rem;
- border-radius: 32px;
- background-color: hsl(var(--card));
- border: 1px solid hsl(var(--border));
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 2rem;
+ padding: 2rem;
+ border-radius: 2rem;
+ background-color: hsl(var(--card));
+ border: 3px solid hsl(var(--border));
}
-
@media (min-width: 768px) {
- .kg-storybook {
- grid-template-columns: 1fr 1fr;
- align-items: center;
- }
+ .kg-storybook { grid-template-columns: 1fr 1fr; align-items: center; }
}
-
.kg-storybook-quote {
- font-family: var(--font-heading, "Quicksand", "Sniglet", serif);
- font-size: clamp(1.25rem, 3vw, 2rem);
- line-height: 1.4;
- color: hsl(var(--foreground));
+ font-family: var(--font-heading, "Baloo 2", "Quicksand", sans-serif);
+ font-size: clamp(1.25rem, 3vw, 2rem);
+ line-height: 1.4;
+ color: hsl(var(--foreground));
}
-
- .kg-storybook-author {
- margin-top: 1rem;
- color: hsl(var(--muted-foreground));
- font-family: var(--font-body, "Nunito", sans-serif);
- }
-
.kg-storybook-illustration {
- width: 100%;
- aspect-ratio: 4 / 3;
- object-fit: cover;
- border-radius: 24px;
- background-color: hsl(var(--muted));
+ width: 100%;
+ aspect-ratio: 4 / 3;
+ object-fit: cover;
+ border-radius: 1.25rem;
+ background-color: hsl(var(--muted));
}
-
- /* ---- Friendly footer --------------------------------------- */
-
- .kg-footer {
- padding: 3rem 1.5rem;
- background-color: hsl(var(--muted));
- color: hsl(var(--foreground));
- border-top: 6px dashed hsl(var(--primary));
- border-radius: 48px 48px 0 0;
+
+ /* ---- GO-BIG motion: bouncing mascot + wiggling letters ----- */
+ /* The canvas confetti/mascot showpiece lives ONLY on the hero
+ override (hero/landing). It lazy-inits on view, honours
+ prefers-reduced-motion, and degrades to a static confetti wash
+ with JS off. No canvas anywhere else in the theme. */
+
+ .kg-mascot {
+ transform-origin: center bottom;
+ animation: kg-bounce 2.4s ease-in-out infinite;
}
-
- .kg-footer-mascot {
- display: inline-flex;
- align-items: center;
- gap: 0.5rem;
- margin-bottom: 1rem;
- font-family: var(--font-heading, "Quicksand", sans-serif);
+ @keyframes kg-bounce {
+ 0%, 100% { transform: translateY(0) rotate(-1deg); }
+ 50% { transform: translateY(-16px) rotate(1deg); }
}
-
- .kg-footer-signup {
- display: flex;
- flex-wrap: wrap;
- gap: 0.5rem;
- margin-block: 1.5rem;
- max-width: 32rem;
+ .kg-wiggle-letter {
+ display: inline-block;
+ animation: kg-wiggle 2.2s ease-in-out infinite;
}
-
- .kg-footer-signup input {
- flex: 1 1 12rem;
- padding: 0.75rem 1rem;
- border-radius: 9999px;
- border: 2px solid hsl(var(--border));
- background-color: hsl(var(--input));
- color: hsl(var(--foreground));
- font-family: var(--font-body, "Nunito", sans-serif);
+ .kg-wiggle-letter:nth-child(2n) { animation-delay: 0.15s; }
+ .kg-wiggle-letter:nth-child(3n) { animation-delay: 0.3s; }
+ .kg-wiggle-letter:nth-child(4n) { animation-delay: 0.45s; }
+ @keyframes kg-wiggle {
+ 0%, 100% { transform: rotate(-5deg); }
+ 50% { transform: rotate(5deg); }
}
-
- .kg-footer-signup input:focus-visible {
- outline: 3px solid hsl(var(--ring));
- outline-offset: 2px;
+
+ /* Confetti canvas + static no-JS fallback. */
+ .kg-confetti-wrap {
+ position: absolute;
+ inset: 0;
+ overflow: hidden;
+ pointer-events: none;
}
-
- .kg-social-row {
- display: flex;
- gap: 0.75rem;
- margin-top: 1rem;
+ .kg-confetti-canvas {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ display: block;
}
-
- .kg-social-link {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 40px;
- height: 40px;
- border-radius: 9999px;
- background-color: hsl(var(--card));
- color: hsl(var(--primary));
- border: 1px solid hsl(var(--border));
+ .kg-confetti-fallback {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ background-image:
+ radial-gradient(circle, hsl(var(--primary) / 0.55) 8px, transparent 9px),
+ radial-gradient(circle, hsl(var(--secondary) / 0.55) 6px, transparent 7px),
+ radial-gradient(circle, hsl(var(--accent) / 0.6) 5px, transparent 6px);
+ background-size: 90px 90px, 60px 60px, 40px 40px;
+ background-position: 12% 18%, 72% 28%, 42% 78%;
+ opacity: 0.55;
}
-
- .kg-social-link:focus-visible {
- outline: 3px solid hsl(var(--ring));
- outline-offset: 2px;
- }
-
- /* ---- Mascot hero confetti dots ----------------------------- */
-
- .kg-confetti {
- position: absolute;
- inset: 0;
- pointer-events: none;
- background-image:
- radial-gradient(circle, hsl(var(--primary) / 0.5) 8px, transparent 9px),
- radial-gradient(circle, hsl(var(--secondary) / 0.5) 6px, transparent 7px),
- radial-gradient(circle, hsl(var(--accent) / 0.5) 5px, transparent 6px);
- background-size: 80px 80px, 50px 50px, 30px 30px;
- background-position: 10% 20%, 70% 30%, 40% 80%;
- opacity: 0.6;
- }
-
- /* ---- Dotted paper layer (page background) ------------------ */
-
- .kg-dotted-paper {
- background-color: hsl(var(--background));
- background-image: radial-gradient(circle, hsl(var(--muted-foreground) / 0.15) 1px, transparent 1.5px);
- background-size: 24px 24px;
- }
-
- /* ---- Heading override (numeral badge variant) -------------- */
-
- .kg-heading-stepped {
- display: flex;
- align-items: center;
- gap: 1rem;
- }
-
- .kg-heading-step-badge {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 48px;
- height: 48px;
- border-radius: 50%;
- background-color: hsl(var(--accent));
- color: hsl(var(--accent-foreground));
- font-family: var(--font-heading, "Quicksand", "Sniglet", sans-serif);
- font-weight: 700;
- font-size: 1.5rem;
- flex: 0 0 auto;
- }
-
- /* ---- Text override: rounded selection highlight ------------ */
-
- .kg-text ::selection {
- background-color: hsl(var(--accent));
- color: hsl(var(--accent-foreground));
- }
-
- .kg-text {
- font-family: var(--font-body, "Nunito", sans-serif);
- line-height: 1.7;
- color: hsl(var(--foreground));
- }
-
- /* ---- Numbers counter grid ---------------------------------- */
-
- .kg-numbers-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
- gap: 2rem;
- text-align: center;
- }
-
- .kg-numbers-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 0.75rem;
- }
-
- .kg-numbers-label {
- font-family: var(--font-body, "Nunito", sans-serif);
- color: hsl(var(--muted-foreground));
- text-transform: uppercase;
- letter-spacing: 0.05em;
- font-size: 0.875rem;
- }
-
- /* ---- Gallery grid ------------------------------------------ */
-
- .kg-gallery-grid {
- display: grid;
- grid-template-columns: 1fr;
- gap: 1.5rem;
- }
-
- @media (min-width: 640px) { .kg-gallery-grid { grid-template-columns: repeat(2, 1fr); } }
- @media (min-width: 1024px) { .kg-gallery-grid { grid-template-columns: repeat(3, 1fr); } }
-
- /* ---- Big CTA panel (spec §9 button override) --------------- */
-
- .kg-big-cta {
- text-align: center;
- padding: 3rem 1.5rem;
- }
-
- /* ---- Empty-state placeholder ------------------------------- */
-
- .kg-empty {
- text-align: center;
- padding: 2rem;
- border-radius: 24px;
- border: 2px dashed hsl(var(--border));
- color: hsl(var(--muted-foreground));
- font-family: var(--font-body, "Nunito", sans-serif);
+
+ /* ---- Reduced motion: freeze everything -------------------- */
+
+ @media (prefers-reduced-motion: reduce) {
+ .kg-mascot,
+ .kg-wiggle-letter,
+ .kg-confetti-canvas {
+ animation: none !important;
+ }
+ .kg-confetti-canvas { display: none; }
+ .kg-pop:hover,
+ .kg-pop:focus-visible,
+ .kg-crayon-shadow:hover,
+ .kg-crayon-shadow:focus-within { transform: none; }
}
diff --git a/master_pages.json b/master_pages.json
index fca0431..ea359a1 100644
--- a/master_pages.json
+++ b/master_pages.json
@@ -1,25 +1,23 @@
[
{
- "key": "kindergarten:default-master",
- "title": "Kindergarten Default Master",
- "page_templates": ["default", "article"],
+ "key": "kindergarten:site-master",
+ "title": "Kindergarten Site Master",
+ "page_templates": ["default", "full-width", "landing", "article", "blog-index", "contact"],
"blocks": [
- { "block_key": "navbar", "title": "Crayon Nav", "content": { "menuName": "main" }, "slot": "header", "sort_order": 0 },
- { "block_key": "kindergarten:alphabet_strip", "title": "ABC Strip", "content": { "letters": "ABCDEFGHIJ", "colorMode": "rainbow" }, "slot": "header", "sort_order": 1 },
- { "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main", "placeholder": "Page content" }, "slot": "main", "sort_order": 0 },
- { "block_key": "kindergarten:footer", "title": "Friendly Footer", "content": { "showSignup": "true", "mascotName": "Pip" }, "slot": "footer", "sort_order": 0 }
+ { "block_key": "navbar", "title": "Crayon Navbar", "content": { "menuName": "main", "logoType": "text", "logoText": { "text": "Little Sprouts" }, "companyName": "Little Sprouts", "stickyBehavior": "sticky", "cta": { "text": "Book a visit", "url": "/contact" } }, "slot": "header", "sort_order": 0 },
+ { "block_key": "kindergarten:alphabet_strip", "title": "ABC Band", "content": { "colorMode": "rainbow", "letters": [ { "letter": "A" }, { "letter": "B" }, { "letter": "C" }, { "letter": "D" }, { "letter": "E" }, { "letter": "F" }, { "letter": "G" }, { "letter": "H" } ] }, "slot": "header", "sort_order": 1 },
+ { "block_key": "slot", "title": "Main Content", "content": { "slotName": "main", "placeholder": "Page content" }, "slot": "main", "sort_order": 0 },
+ { "block_key": "footer", "title": "Friendly Footer", "content": { "layout": "columns", "companyName": "Little Sprouts", "tagline": "A warm, playful place to learn and grow.", "copyright": "Little Sprouts Preschool", "showNewsletter": true, "newsletterTitle": "Stay in the loop", "newsletterDescription": "Notes home, event dates, and the odd finger-painting photo." }, "slot": "footer", "sort_order": 0 }
]
},
{
- "key": "kindergarten:landing-master",
- "title": "Kindergarten Landing Master",
- "page_templates": ["landing", "full-width"],
+ "key": "kindergarten:auth-master",
+ "title": "Kindergarten Auth Master",
+ "page_templates": ["auth"],
"blocks": [
- { "block_key": "navbar", "title": "Crayon Nav", "content": { "menuName": "main" }, "slot": "header", "sort_order": 0 },
- { "block_key": "kindergarten:mascot_hero", "title": "Mascot Hero", "content": { "mascot": "pip", "headline": "Welcome to school!", "tagline": "Big fun, small humans." }, "slot": "hero", "sort_order": 0 },
- { "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
- { "block_key": "kindergarten:big_cta", "title": "Big Yellow CTA", "content": { "label": "Enrol today", "href": "/enrol", "colorVariant": "yellow" }, "slot": "cta", "sort_order": 0 },
- { "block_key": "kindergarten:footer", "title": "Friendly Footer", "content": { "showSignup": "true" }, "slot": "footer", "sort_order": 0 }
+ { "block_key": "navbar", "title": "Crayon Navbar", "content": { "menuName": "main", "logoType": "text", "logoText": { "text": "Little Sprouts" }, "companyName": "Little Sprouts" }, "slot": "header", "sort_order": 0 },
+ { "block_key": "slot", "title": "Auth Content", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
+ { "block_key": "footer", "title": "Friendly Footer", "content": { "layout": "minimal", "companyName": "Little Sprouts", "tagline": "A warm, playful place to learn and grow.", "copyright": "Little Sprouts Preschool" }, "slot": "footer", "sort_order": 0 }
]
}
]
diff --git a/plugin.mod b/plugin.mod
index 9e6f6fc..6c5722d 100644
--- a/plugin.mod
+++ b/plugin.mod
@@ -3,7 +3,8 @@ name = "kindergarten"
display_name = "Kindergarten"
scope = "@themes"
version = "0.2.0"
-description = "Primary-colored, hand-lettered theme for schools, daycares, toy shops, museums, and kids' publishers."
+description = "Primary-colored, hand-lettered theme for preschools and daycares — crayon borders, sticker badges, confetti, and a bouncing mascot showpiece."
kind = "theme"
categories = ["templates"]
-tags = ["kids", "education", "playful", "family", "school", "museum", "friendly", "daycare", "publisher"]
+tags = ["kids", "education", "playful", "family", "school", "daycare", "preschool", "friendly", "colorful"]
+required_icon_packs = ["lucide", "simple-icons"]
diff --git a/presets.json b/presets.json
index 7b80111..aafb3f4 100644
--- a/presets.json
+++ b/presets.json
@@ -1,110 +1,252 @@
[
- {
- "id": "recess",
- "name": "Crayon Recess",
- "description": "Light, sunny, primary-on-cream — for daytime classrooms.",
- "theme": {
- "lightColors": {
- "background": "48 60% 97%",
- "foreground": "220 40% 18%",
- "card": "0 0% 100%",
- "cardForeground": "220 40% 18%",
- "popover": "0 0% 100%",
- "popoverForeground": "220 40% 18%",
- "primary": "4 80% 56%",
- "primaryForeground": "0 0% 100%",
- "secondary": "220 75% 56%",
- "secondaryForeground": "0 0% 100%",
- "muted": "48 40% 92%",
- "mutedForeground": "220 25% 38%",
- "accent": "48 95% 58%",
- "accentForeground": "220 50% 14%",
- "destructive": "0 84% 55%",
- "destructiveForeground": "0 0% 100%",
- "border": "220 30% 86%",
- "input": "48 30% 90%",
- "ring": "4 80% 56%"
- },
- "mode": "light"
- }
- },
- {
- "id": "chalkboard",
- "name": "Chalkboard Night",
- "description": "Deep navy chalkboard with crayon-bright accents.",
- "theme": {
- "darkColors": {
- "background": "220 50% 10%",
- "foreground": "48 60% 96%",
- "card": "220 45% 14%",
- "cardForeground": "48 60% 96%",
- "popover": "220 45% 14%",
- "popoverForeground": "48 60% 96%",
- "primary": "48 95% 58%",
- "primaryForeground": "220 50% 10%",
- "secondary": "4 80% 60%",
- "secondaryForeground": "0 0% 100%",
- "muted": "220 35% 18%",
- "mutedForeground": "220 20% 70%",
- "accent": "145 70% 50%",
- "accentForeground": "220 50% 10%",
- "destructive": "0 84% 60%",
- "destructiveForeground": "0 0% 100%",
- "border": "220 35% 24%",
- "input": "220 35% 20%",
- "ring": "48 95% 58%"
- },
- "mode": "dark"
- }
- },
{
"id": "crayon-box",
"name": "Crayon Box",
- "description": "Full primary quartet — red, blue, yellow, green — in both light and dark.",
+ "description": "The default — crayon blue, red, and yellow. Bright classroom by day, quiet-time navy by night.",
"theme": {
+ "mode": "both",
"lightColors": {
"background": "48 55% 98%",
- "foreground": "220 45% 15%",
+ "foreground": "222 47% 16%",
"card": "0 0% 100%",
- "cardForeground": "220 45% 15%",
+ "cardForeground": "222 47% 16%",
"popover": "0 0% 100%",
- "popoverForeground": "220 45% 15%",
- "primary": "220 78% 52%",
+ "popoverForeground": "222 47% 16%",
+ "primary": "222 80% 54%",
"primaryForeground": "0 0% 100%",
- "secondary": "4 78% 56%",
+ "secondary": "4 78% 57%",
"secondaryForeground": "0 0% 100%",
- "muted": "48 35% 93%",
- "mutedForeground": "220 22% 38%",
- "accent": "145 65% 45%",
+ "muted": "48 40% 93%",
+ "mutedForeground": "222 20% 40%",
+ "accent": "45 96% 56%",
+ "accentForeground": "222 50% 14%",
+ "destructive": "0 84% 55%",
+ "destructiveForeground": "0 0% 100%",
+ "border": "222 25% 86%",
+ "input": "48 30% 90%",
+ "ring": "222 80% 54%"
+ },
+ "darkColors": {
+ "background": "224 46% 9%",
+ "foreground": "48 55% 95%",
+ "card": "224 42% 13%",
+ "cardForeground": "48 55% 95%",
+ "popover": "224 42% 13%",
+ "popoverForeground": "48 55% 95%",
+ "primary": "218 90% 66%",
+ "primaryForeground": "224 46% 9%",
+ "secondary": "4 84% 64%",
+ "secondaryForeground": "0 0% 100%",
+ "muted": "224 34% 17%",
+ "mutedForeground": "220 20% 70%",
+ "accent": "45 96% 62%",
+ "accentForeground": "224 46% 9%",
+ "destructive": "0 84% 62%",
+ "destructiveForeground": "0 0% 100%",
+ "border": "224 32% 22%",
+ "input": "224 32% 19%",
+ "ring": "45 96% 62%"
+ }
+ }
+ },
+ {
+ "id": "sunny-recess",
+ "name": "Sunny Recess",
+ "description": "Warm red-and-sun classroom by day, soft navy with a glow-star yellow for nap time.",
+ "theme": {
+ "mode": "both",
+ "lightColors": {
+ "background": "40 70% 97%",
+ "foreground": "20 40% 18%",
+ "card": "0 0% 100%",
+ "cardForeground": "20 40% 18%",
+ "popover": "0 0% 100%",
+ "popoverForeground": "20 40% 18%",
+ "primary": "8 82% 56%",
+ "primaryForeground": "0 0% 100%",
+ "secondary": "205 75% 52%",
+ "secondaryForeground": "0 0% 100%",
+ "muted": "40 45% 92%",
+ "mutedForeground": "20 20% 40%",
+ "accent": "42 96% 55%",
+ "accentForeground": "20 45% 16%",
+ "destructive": "0 84% 55%",
+ "destructiveForeground": "0 0% 100%",
+ "border": "32 35% 85%",
+ "input": "40 35% 90%",
+ "ring": "8 82% 56%"
+ },
+ "darkColors": {
+ "background": "222 45% 9%",
+ "foreground": "40 60% 95%",
+ "card": "222 40% 13%",
+ "cardForeground": "40 60% 95%",
+ "popover": "222 40% 13%",
+ "popoverForeground": "40 60% 95%",
+ "primary": "10 88% 64%",
+ "primaryForeground": "0 0% 100%",
+ "secondary": "205 82% 62%",
+ "secondaryForeground": "222 45% 9%",
+ "muted": "222 32% 17%",
+ "mutedForeground": "40 22% 72%",
+ "accent": "44 96% 62%",
+ "accentForeground": "222 45% 9%",
+ "destructive": "0 84% 62%",
+ "destructiveForeground": "0 0% 100%",
+ "border": "222 30% 22%",
+ "input": "222 30% 19%",
+ "ring": "44 96% 62%"
+ }
+ }
+ },
+ {
+ "id": "bluebird",
+ "name": "Bluebird",
+ "description": "Calm blue-and-teal for reading corners. Deep quiet navy with a teal glow after dark.",
+ "theme": {
+ "mode": "both",
+ "lightColors": {
+ "background": "205 60% 98%",
+ "foreground": "218 45% 16%",
+ "card": "0 0% 100%",
+ "cardForeground": "218 45% 16%",
+ "popover": "0 0% 100%",
+ "popoverForeground": "218 45% 16%",
+ "primary": "212 84% 52%",
+ "primaryForeground": "0 0% 100%",
+ "secondary": "45 95% 55%",
+ "secondaryForeground": "218 50% 14%",
+ "muted": "205 40% 93%",
+ "mutedForeground": "218 22% 42%",
+ "accent": "175 70% 42%",
"accentForeground": "0 0% 100%",
"destructive": "0 84% 55%",
"destructiveForeground": "0 0% 100%",
- "border": "220 25% 85%",
- "input": "48 25% 88%",
- "ring": "48 95% 55%"
+ "border": "210 30% 86%",
+ "input": "205 30% 90%",
+ "ring": "212 84% 52%"
},
"darkColors": {
- "background": "220 40% 8%",
- "foreground": "48 55% 95%",
- "card": "220 40% 12%",
- "cardForeground": "48 55% 95%",
- "popover": "220 40% 12%",
- "popoverForeground": "48 55% 95%",
- "primary": "220 80% 60%",
- "primaryForeground": "0 0% 100%",
- "secondary": "4 80% 58%",
- "secondaryForeground": "0 0% 100%",
- "muted": "220 30% 16%",
- "mutedForeground": "220 18% 68%",
- "accent": "145 70% 50%",
- "accentForeground": "220 40% 8%",
- "destructive": "0 84% 60%",
+ "background": "220 48% 9%",
+ "foreground": "205 55% 95%",
+ "card": "220 44% 13%",
+ "cardForeground": "205 55% 95%",
+ "popover": "220 44% 13%",
+ "popoverForeground": "205 55% 95%",
+ "primary": "210 92% 66%",
+ "primaryForeground": "220 48% 9%",
+ "secondary": "45 96% 62%",
+ "secondaryForeground": "220 48% 9%",
+ "muted": "220 34% 17%",
+ "mutedForeground": "205 20% 72%",
+ "accent": "172 80% 52%",
+ "accentForeground": "220 48% 9%",
+ "destructive": "0 84% 62%",
"destructiveForeground": "0 0% 100%",
- "border": "220 30% 22%",
- "input": "220 30% 18%",
- "ring": "48 95% 60%"
+ "border": "220 32% 22%",
+ "input": "220 32% 19%",
+ "ring": "172 80% 52%"
+ }
+ }
+ },
+ {
+ "id": "meadow",
+ "name": "Meadow Play",
+ "description": "Green-and-orange nature play by day, deep teal-navy with a warm glow for winding down.",
+ "theme": {
+ "mode": "both",
+ "lightColors": {
+ "background": "90 45% 97%",
+ "foreground": "140 35% 15%",
+ "card": "0 0% 100%",
+ "cardForeground": "140 35% 15%",
+ "popover": "0 0% 100%",
+ "popoverForeground": "140 35% 15%",
+ "primary": "145 62% 40%",
+ "primaryForeground": "0 0% 100%",
+ "secondary": "25 88% 55%",
+ "secondaryForeground": "0 0% 100%",
+ "muted": "90 30% 92%",
+ "mutedForeground": "140 18% 38%",
+ "accent": "48 95% 55%",
+ "accentForeground": "140 40% 14%",
+ "destructive": "0 84% 55%",
+ "destructiveForeground": "0 0% 100%",
+ "border": "110 25% 84%",
+ "input": "90 25% 89%",
+ "ring": "145 62% 40%"
},
- "mode": "both"
+ "darkColors": {
+ "background": "200 40% 9%",
+ "foreground": "90 45% 94%",
+ "card": "200 36% 13%",
+ "cardForeground": "90 45% 94%",
+ "popover": "200 36% 13%",
+ "popoverForeground": "90 45% 94%",
+ "primary": "145 66% 52%",
+ "primaryForeground": "200 40% 9%",
+ "secondary": "25 90% 62%",
+ "secondaryForeground": "0 0% 100%",
+ "muted": "200 30% 17%",
+ "mutedForeground": "90 20% 70%",
+ "accent": "48 96% 62%",
+ "accentForeground": "200 40% 9%",
+ "destructive": "0 84% 62%",
+ "destructiveForeground": "0 0% 100%",
+ "border": "200 28% 22%",
+ "input": "200 28% 19%",
+ "ring": "48 96% 62%"
+ }
+ }
+ },
+ {
+ "id": "bubblegum",
+ "name": "Bubblegum",
+ "description": "Playful pink-and-purple for toy shops and party rooms. Plum-navy with a soft glow at night.",
+ "theme": {
+ "mode": "both",
+ "lightColors": {
+ "background": "330 50% 98%",
+ "foreground": "300 35% 18%",
+ "card": "0 0% 100%",
+ "cardForeground": "300 35% 18%",
+ "popover": "0 0% 100%",
+ "popoverForeground": "300 35% 18%",
+ "primary": "330 80% 58%",
+ "primaryForeground": "0 0% 100%",
+ "secondary": "265 70% 60%",
+ "secondaryForeground": "0 0% 100%",
+ "muted": "330 35% 94%",
+ "mutedForeground": "300 18% 42%",
+ "accent": "42 95% 56%",
+ "accentForeground": "300 40% 16%",
+ "destructive": "0 84% 55%",
+ "destructiveForeground": "0 0% 100%",
+ "border": "320 30% 88%",
+ "input": "330 30% 92%",
+ "ring": "330 80% 58%"
+ },
+ "darkColors": {
+ "background": "285 40% 9%",
+ "foreground": "330 45% 95%",
+ "card": "285 36% 13%",
+ "cardForeground": "330 45% 95%",
+ "popover": "285 36% 13%",
+ "popoverForeground": "330 45% 95%",
+ "primary": "330 88% 68%",
+ "primaryForeground": "285 40% 9%",
+ "secondary": "265 82% 72%",
+ "secondaryForeground": "285 40% 9%",
+ "muted": "285 30% 17%",
+ "mutedForeground": "330 20% 72%",
+ "accent": "45 96% 64%",
+ "accentForeground": "285 40% 9%",
+ "destructive": "0 84% 62%",
+ "destructiveForeground": "0 0% 100%",
+ "border": "285 28% 23%",
+ "input": "285 28% 20%",
+ "ring": "45 96% 64%"
+ }
}
}
]
diff --git a/seed/seed.json b/seed/seed.json
new file mode 100644
index 0000000..617bae7
--- /dev/null
+++ b/seed/seed.json
@@ -0,0 +1,228 @@
+{
+ "demo": true,
+ "settings": {
+ "merge": {
+ "companyName": "Little Sprouts",
+ "tagline": "A warm, playful place to learn and grow."
+ }
+ },
+ "data_tables": [
+ {
+ "key": "contact_submissions",
+ "name": "Visit Requests",
+ "description": "Seeded contact-form submissions for the Little Sprouts demo. A submission becomes a row here and emails the site admins.",
+ "columns": [
+ { "key": "name", "label": "Parent name", "type": "text", "required": true },
+ { "key": "email", "label": "Email", "type": "email", "required": true },
+ { "key": "childAge", "label": "Child's age", "type": "text", "required": false },
+ { "key": "message", "label": "Message", "type": "text", "required": true }
+ ]
+ }
+ ],
+ "pages": [
+ {
+ "slug": "/",
+ "title": "Little Sprouts",
+ "template_key": "landing",
+ "blocks": [
+ {
+ "block_key": "hero",
+ "title": "Hero",
+ "slot": "main",
+ "sort_order": 1,
+ "content": {
+ "template": "centered",
+ "minHeight": "screen",
+ "eyebrow": "Now enrolling",
+ "headline": "A happy place to start",
+ "subheadline": "Play-based preschool for ages 2 to 5.",
+ "description": "Small groups, warm teachers, and a garden to dig in. Come and see a normal day.",
+ "ctaPrimary": { "text": "Book a visit", "url": "/contact" },
+ "ctaSecondary": { "text": "See our day", "url": "/about" }
+ }
+ },
+ {
+ "block_key": "feature-grid",
+ "title": "Why families choose us",
+ "slot": "main",
+ "sort_order": 2,
+ "content": {
+ "eyebrow": "Why families choose us",
+ "heading": "What your child gets",
+ "intro": "Every day has room to move, make, and rest.",
+ "layout": "card",
+ "columns": 3,
+ "items": [
+ { "icon": "lucide:sun", "title": "Time outside every day", "text": "Rain or shine, we head to the garden and the yard." },
+ { "icon": "lucide:book-open", "title": "Stories and songs", "text": "We read together, sing together, and make up our own tales." },
+ { "icon": "lucide:palette", "title": "Room to make a mess", "text": "Paint, clay, glue, and glitter. Aprons on, big ideas out." }
+ ]
+ }
+ },
+ {
+ "block_key": "kindergarten:schedule",
+ "title": "A day in the Sunflower Room",
+ "slot": "main",
+ "sort_order": 3,
+ "content": {
+ "title": "A day in the Sunflower Room",
+ "items": [
+ { "time": "09:00", "activity": "Arrival and free play", "icon": "play" },
+ { "time": "10:00", "activity": "Morning circle and songs", "icon": "book" },
+ { "time": "10:30", "activity": "Snack and fruit", "icon": "snack" },
+ { "time": "11:00", "activity": "Garden and outside play", "icon": "sun" },
+ { "time": "12:00", "activity": "Lunch together", "icon": "snack" },
+ { "time": "13:00", "activity": "Quiet time and rest", "icon": "nap" },
+ { "time": "14:00", "activity": "Painting and making", "icon": "paint" }
+ ]
+ }
+ },
+ {
+ "block_key": "kindergarten:art_wall",
+ "title": "Fresh off the easel",
+ "slot": "main",
+ "sort_order": 4,
+ "content": {
+ "title": "Fresh off the easel",
+ "showChildName": true,
+ "items": [
+ { "childName": "Mia", "age": "4" },
+ { "childName": "Noah", "age": "3" },
+ { "childName": "Ada", "age": "5" }
+ ]
+ }
+ },
+ {
+ "block_key": "cta",
+ "title": "CTA",
+ "slot": "main",
+ "sort_order": 5,
+ "content": {
+ "background": "band",
+ "heading": "Come and see for yourself",
+ "text": "Visits run most mornings. We will show you around and answer everything.",
+ "primaryLabel": "Book a visit",
+ "primaryUrl": "/contact"
+ }
+ }
+ ]
+ },
+ {
+ "slug": "/about",
+ "title": "About us",
+ "template_key": "default",
+ "blocks": [
+ { "block_key": "heading", "title": "Heading", "slot": "main", "sort_order": 1, "content": { "text": "About Little Sprouts", "level": 1 } },
+ { "block_key": "rich-text", "title": "Story", "slot": "main", "sort_order": 2, "content": { "html": "Little Sprouts started in a church hall with six children and a box of blocks. Families kept asking for more days, so we grew into a proper little school with a garden out the back.
Our rooms are small on purpose. Teachers learn every child by name in the first week. We follow the children's lead, keep the day gentle, and send them home tired and happy.
" } },
+ {
+ "block_key": "kindergarten:story_time",
+ "title": "Story time",
+ "slot": "main",
+ "sort_order": 3,
+ "content": {
+ "bookTitle": "The Very Sleepy Bear",
+ "author": "our reading corner",
+ "line": "The bear yawned, and the whole forest yawned back."
+ }
+ },
+ { "block_key": "divider", "title": "Divider", "slot": "main", "sort_order": 4, "content": { "variant": "line", "weight": "medium" } },
+ { "block_key": "quote", "title": "Quote", "slot": "main", "sort_order": 5, "content": { "style": "pull", "quote": "My daughter runs to the door every morning.", "attribution": "A Little Sprouts parent" } }
+ ]
+ },
+ {
+ "slug": "/blog",
+ "title": "Stories",
+ "template_key": "blog-index",
+ "blocks": [
+ { "block_key": "card", "title": "Post: Settling in", "slot": "main", "sort_order": 1, "content": { "layout": "vertical", "title": "Settling in: the first week", "text": "Little tips that help a new child feel at home, from the drop-off wave to the goodbye routine.
", "link": "/blog/settling-in", "linkLabel": "Read on" } },
+ { "block_key": "card", "title": "Post: What to pack", "slot": "main", "sort_order": 2, "content": { "layout": "vertical", "title": "What to pack for a preschool day", "text": "A short list of what to bring, what to label, and what to leave at home.
", "link": "/blog/what-to-pack", "linkLabel": "Read on" } },
+ { "block_key": "card", "title": "Post: Reading at home", "slot": "main", "sort_order": 3, "content": { "layout": "vertical", "title": "Ways to read at home", "text": "Reading together does not need to be fancy. Here is how we keep it easy and fun.
", "link": "/blog/reading-at-home", "linkLabel": "Read on" } }
+ ]
+ },
+ {
+ "slug": "/blog/settling-in",
+ "title": "Settling in: the first week",
+ "template_key": "article",
+ "parent_slug": "/blog",
+ "blocks": [
+ { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Settling in: the first week", "level": 1 } },
+ { "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "The first week is a big change for everyone. Some children walk straight in. Others hold on tight at the door. Both are normal.
Keep goodbyes short and warm. Tell your child when you will be back, then go. We will send a photo once they settle, and most do within minutes.
" } }
+ ]
+ },
+ {
+ "slug": "/blog/what-to-pack",
+ "title": "What to pack for a preschool day",
+ "template_key": "article",
+ "parent_slug": "/blog",
+ "blocks": [
+ { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "What to pack for a preschool day", "level": 1 } },
+ { "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "Pack a spare set of clothes, a hat, and a water bottle. Label everything with your child's name.
Leave toys from home in the car. They tend to go missing, and sharing them is hard on a new friend. We have plenty here to play with.
" } }
+ ]
+ },
+ {
+ "slug": "/blog/reading-at-home",
+ "title": "Ways to read at home",
+ "template_key": "article",
+ "parent_slug": "/blog",
+ "blocks": [
+ { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Ways to read at home", "level": 1 } },
+ { "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "Read the same book as often as they ask. Repetition is how young children learn the shape of a story.
Point at the pictures and ask what happens next. Let them turn the pages. The goal is a warm few minutes together, not a perfect reading.
" } }
+ ]
+ },
+ {
+ "slug": "/contact",
+ "title": "Book a visit",
+ "template_key": "contact",
+ "blocks": [
+ {
+ "block_key": "contact-form",
+ "title": "Book a visit",
+ "slot": "main",
+ "sort_order": 1,
+ "content": {
+ "heading": "Book a visit",
+ "description": "Tell us a little about your family. We will call to set up a time.",
+ "submitLabel": "Send",
+ "successMessage": "Thanks. We will be in touch very soon.",
+ "fields": [
+ { "name": "name", "label": "Your name", "type": "text", "required": true, "width": "half" },
+ { "name": "email", "label": "Email", "type": "email", "required": true, "width": "half" },
+ { "name": "childAge", "label": "Your child's age", "type": "text", "required": false, "width": "full" },
+ { "name": "message", "label": "Anything you would like us to know", "type": "textarea", "required": true, "width": "full" }
+ ],
+ "formConfig": { "enabled": true, "captchaEnabled": false, "targetTable": "contact_submissions" }
+ }
+ },
+ {
+ "block_key": "contact-card",
+ "title": "Find us",
+ "slot": "main",
+ "sort_order": 2,
+ "content": {
+ "heading": "Find us",
+ "columns": 3,
+ "channels": [
+ { "icon": "lucide:mail", "label": "Email", "value": "hello@littlesprouts.example", "link": "mailto:hello@littlesprouts.example" },
+ { "icon": "lucide:phone", "label": "Phone", "value": "(02) 5550 1234", "link": "tel:+61255501234" },
+ { "icon": "lucide:map-pin", "label": "Visit", "value": "14 Garden Lane, Riverside" }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "slug": "/login",
+ "title": "Sign in",
+ "template_key": "auth",
+ "blocks": [
+ { "block_key": "auth-form", "title": "Sign in", "slot": "main", "sort_order": 1, "content": { "default_tab": "login", "show_social": false } }
+ ]
+ }
+ ],
+ "menu_items": [
+ { "menu": "main", "label": "Home", "page_slug": "/", "sort_order": 1 },
+ { "menu": "main", "label": "About", "page_slug": "/about", "sort_order": 2 },
+ { "menu": "main", "label": "Stories", "page_slug": "/blog", "sort_order": 3 },
+ { "menu": "main", "label": "Visit", "page_slug": "/contact", "sort_order": 4 }
+ ]
+}
diff --git a/seed/workflows.json b/seed/workflows.json
new file mode 100644
index 0000000..879dbfb
--- /dev/null
+++ b/seed/workflows.json
@@ -0,0 +1,19 @@
+{
+ "workflows": [
+ {
+ "key": "contact_notify_admins",
+ "name": "Visit request to admins",
+ "description": "On a contact-form submission, email the site admins.",
+ "enabled": true,
+ "trigger": { "type": "row_inserted", "table": "contact_submissions" },
+ "steps": [
+ {
+ "type": "email",
+ "name": "Email admins",
+ "template": "Form Submission Notification",
+ "recipients": { "mode": "admins", "scope": "all" }
+ }
+ ]
+ }
+ ]
+}
diff --git a/templates/kindergarten/article.ninjatpl b/templates/kindergarten/article.ninjatpl
index 7f8e017..74bbd99 100644
--- a/templates/kindergarten/article.ninjatpl
+++ b/templates/kindergarten/article.ninjatpl
@@ -1,16 +1,14 @@
-
+
{{ head_html|safe }}
-
+
{{ admin_banner_html|safe }}
-
-
-
- {% if slots.main %}{{ slots.main|safe }} {% else %}No content yet — start a story.
{% endif %}
-
- {% if slots.aside %}
{% endif %}
-
-
+
+
+ {{ slots.main|safe }}
+ {% if slots.aside %}{% endif %}
+
+
{{ body_end_html|safe }}
diff --git a/templates/kindergarten/auth.ninjatpl b/templates/kindergarten/auth.ninjatpl
new file mode 100644
index 0000000..ea85e22
--- /dev/null
+++ b/templates/kindergarten/auth.ninjatpl
@@ -0,0 +1,13 @@
+
+
+{{ head_html|safe }}
+
+ {{ admin_banner_html|safe }}
+
+
+ {% if slots.main %}{{ slots.main|safe }}{% else %}
Sign-in form goes here.
{% endif %}
+
+
+ {{ body_end_html|safe }}
+
+
diff --git a/templates/kindergarten/blog-index.ninjatpl b/templates/kindergarten/blog-index.ninjatpl
new file mode 100644
index 0000000..c31f398
--- /dev/null
+++ b/templates/kindergarten/blog-index.ninjatpl
@@ -0,0 +1,11 @@
+
+
+{{ head_html|safe }}
+
+ {{ admin_banner_html|safe }}
+
+ {% if slots.main %}{{ slots.main|safe }}{% else %}No stories yet — check back soon.
{% endif %}
+
+ {{ body_end_html|safe }}
+
+
diff --git a/templates/kindergarten/contact.ninjatpl b/templates/kindergarten/contact.ninjatpl
new file mode 100644
index 0000000..3087ad7
--- /dev/null
+++ b/templates/kindergarten/contact.ninjatpl
@@ -0,0 +1,11 @@
+
+
+{{ head_html|safe }}
+
+ {{ admin_banner_html|safe }}
+
+ {% if slots.main %}{{ slots.main|safe }}{% else %}Add a contact form to this page.
{% endif %}
+
+ {{ body_end_html|safe }}
+
+
diff --git a/templates/kindergarten/default.ninjatpl b/templates/kindergarten/default.ninjatpl
index 9c074c2..f49caad 100644
--- a/templates/kindergarten/default.ninjatpl
+++ b/templates/kindergarten/default.ninjatpl
@@ -1,13 +1,11 @@
-
+
{{ head_html|safe }}
-
+
{{ admin_banner_html|safe }}
-
-
- {% if slots.main %}{{ slots.main|safe }}{% else %}No content blocks assigned to this page.
{% endif %}
-
-
+
+ {% if slots.main %}{{ slots.main|safe }}{% else %}No content blocks assigned to this page yet.
{% endif %}
+
{{ body_end_html|safe }}
diff --git a/templates/kindergarten/full-width.ninjatpl b/templates/kindergarten/full-width.ninjatpl
index ff0bce7..c97dde0 100644
--- a/templates/kindergarten/full-width.ninjatpl
+++ b/templates/kindergarten/full-width.ninjatpl
@@ -1,13 +1,11 @@
-
+
{{ head_html|safe }}
-
+
{{ admin_banner_html|safe }}
-
-
- {% if slots.main %}{{ slots.main|safe }}{% else %}No content blocks assigned to this page.
{% endif %}
-
-
+
+ {% if slots.main %}{{ slots.main|safe }}{% else %}No content blocks assigned to this page yet.
{% endif %}
+
{{ body_end_html|safe }}
diff --git a/templates/kindergarten/landing.ninjatpl b/templates/kindergarten/landing.ninjatpl
index 18d3b7f..220ec3d 100644
--- a/templates/kindergarten/landing.ninjatpl
+++ b/templates/kindergarten/landing.ninjatpl
@@ -1,15 +1,11 @@
-
+
{{ head_html|safe }}
-
+
{{ admin_banner_html|safe }}
-
-
-
- {% if slots.main %}{{ slots.main|safe }}{% endif %}
-
-
-
+
+ {% if slots.main %}{{ slots.main|safe }}{% else %}No content blocks assigned to this page yet.
{% endif %}
+
{{ body_end_html|safe }}
diff --git a/templates/overrides/kindergarten/announcement-bar.ninjatpl b/templates/overrides/kindergarten/announcement-bar.ninjatpl
new file mode 100644
index 0000000..4ffb8c6
--- /dev/null
+++ b/templates/overrides/kindergarten/announcement-bar.ninjatpl
@@ -0,0 +1,21 @@
+{# announcement_bar (WO-TF-006): dismissable banner; dismissal persists in localStorage keyed by a content hash (variant + slugified message) so it reappears when the message changes; no-JS fallback is a static non-dismissable strip. Kindergarten override: mono broadcast readout — data-* + script preserved. #}
+{% set anncls = "bg-primary/10 text-foreground" %}
+{% if variant == "promo" %}{% set anncls = "bg-primary text-primary-foreground" %}{% elif variant == "alert" %}{% set anncls = "bg-destructive text-destructive-foreground" %}{% endif %}
+
+
+
{{ message }}{% if linkText and linkUrl %} {{ linkText }} {% endif %}
+{% if dismissable %}
{% endif %}
+
+
+{% if dismissable %}{% endif %}
diff --git a/templates/overrides/kindergarten/audio-embed.ninjatpl b/templates/overrides/kindergarten/audio-embed.ninjatpl
new file mode 100644
index 0000000..f4689ab
--- /dev/null
+++ b/templates/overrides/kindergarten/audio-embed.ninjatpl
@@ -0,0 +1,16 @@
+{# audio-embed kindergarten override: neon panel, mono meta. Facade script + data-bn-* attributes + {% img %} preserved byte-for-byte. #}
+{% if artwork %}
{% img artwork alt=title|default:"" class="h-full w-full object-cover" %}
{% endif %}
{% if title %}
{{ title }}
{% endif %}{% if artist %}
{{ artist }}
{% endif %}
diff --git a/templates/overrides/kindergarten/auth-form.ninjatpl b/templates/overrides/kindergarten/auth-form.ninjatpl
new file mode 100644
index 0000000..c8a870c
--- /dev/null
+++ b/templates/overrides/kindergarten/auth-form.ninjatpl
@@ -0,0 +1,77 @@
+{# auth-form kindergarten override — reproduces the builtin's security-critical wiring: hardcoded HTMX endpoints /api/auth/login + /api/auth/register (target #auth-message), /forgot-password link, OAuth start URLs gated on context.site.*, and the exact Cap widget markup when captchaEnabled. Only the chrome is themed. #}
+{% if context.isPublicLoggedIn %}
+
+
Session active as {{ context.publicUsername }}
+
My Account
+
+{% else %}
+{% set loginActive = "border-primary text-primary" %}
+{% set registerActive = "border-transparent text-muted-foreground" %}
+{% set loginHidden = "" %}
+{% set registerHidden = "hidden" %}
+{% if default_tab == "register" %}{% set loginActive = "border-transparent text-muted-foreground" %}{% set registerActive = "border-primary text-primary" %}{% set loginHidden = "hidden" %}{% set registerHidden = "" %}{% endif %}
+{% set tabbase = "flex-1 py-2 text-center kg-mono text-sm font-medium border-b-2 transition-colors" %}
+{% macro captcha() %}
+
+
+{% endmacro %}
+{% macro social() %}{% if show_social != false and (context.site.google_oauth_client_id or context.site.facebook_app_id) %}{% endif %}{% endmacro %}
+
+
+
+ Login
+ Register
+
+
+
+
+
+{% endif %}
diff --git a/templates/overrides/kindergarten/auth-status.ninjatpl b/templates/overrides/kindergarten/auth-status.ninjatpl
new file mode 100644
index 0000000..752cf51
--- /dev/null
+++ b/templates/overrides/kindergarten/auth-status.ninjatpl
@@ -0,0 +1,36 @@
+{# auth-status kindergarten override — reproduces the builtin's hardcoded wiring: /login + /login?tab=register when logged out; /account, /account/reviews, and hx-post /api/auth/logout dropdown when logged in; hx-post /api/auth/resend-verification banner when unverified. State from context.*. #}
+{% if not context.isPublicLoggedIn %}
+
+{% else %}
+{% if not context.publicEmailVerified %}
+ Verify your email address. Resend verification email
+
{% endif %}
+
+
+
+ {{ context.publicDisplayName|default:context.publicUsername }}
+
+
+
+
+
+{% endif %}
diff --git a/templates/overrides/kindergarten/author-bio-hero.ninjatpl b/templates/overrides/kindergarten/author-bio-hero.ninjatpl
new file mode 100644
index 0000000..3d26615
--- /dev/null
+++ b/templates/overrides/kindergarten/author-bio-hero.ninjatpl
@@ -0,0 +1,21 @@
+{# author-bio-hero (kindergarten override) — author-page header from the `authors` provider. #}
+{% if author %}
+
+
+{% if showAvatar and author.avatar_url %}
{% img author.avatar_url alt=author.display_name class="h-full w-full object-cover" %}
{% endif %}
+
{{ author.display_name }}
+{% if author.job_title or author.company %}
{{ author.job_title }}{% if author.job_title and author.company %} · {% endif %}{{ author.company }}
{% endif %}
+{% if author.bio %}
{{ author.bio }}
{% endif %}
+
+{% if showPostCount %}{{ author.post_count|default:0 }} post{% if author.post_count != 1 %}s{% endif %} {% endif %}
+{% if author.location %}{{ author.location }} {% endif %}
+
+{% if showSocial %}
+{% if author.website_url %}
{% endif %}
+{% if author.twitter_handle %}
{% endif %}
+{% if author.linkedin_url %}
{% endif %}
+{% if author.github_url %}
{% endif %}
+
{% endif %}
+
+
+{% endif %}
diff --git a/templates/overrides/kindergarten/author-bio.ninjatpl b/templates/overrides/kindergarten/author-bio.ninjatpl
new file mode 100644
index 0000000..8705078
--- /dev/null
+++ b/templates/overrides/kindergarten/author-bio.ninjatpl
@@ -0,0 +1,13 @@
+{# author-bio (kindergarten override) — byline card sourced from the `post` provider's author_* fields. #}
+{% if post.author_name %}
+
+{% if heading %}{{ heading }}
{% endif %}
+
+{% if showAvatar and post.author_avatar %}
{% img post.author_avatar alt=post.author_name class="h-full w-full object-cover" %}
{% endif %}
+
+
{% if post.author_slug %}{{ post.author_name }} {% else %}{{ post.author_name }}{% endif %}
+{% if post.author_bio %}
{{ post.author_bio }}
{% endif %}
+
+
+
+{% endif %}
diff --git a/templates/overrides/kindergarten/blog-hero.ninjatpl b/templates/overrides/kindergarten/blog-hero.ninjatpl
new file mode 100644
index 0000000..6cb83c2
--- /dev/null
+++ b/templates/overrides/kindergarten/blog-hero.ninjatpl
@@ -0,0 +1,23 @@
+{# blog-hero (kindergarten override) — blog-index header + featured posts from the `posts` provider. #}
+
+
+
+{% if title %}
{{ title }} {% endif %}
+{% if subtitle %}
{{ subtitle }}
{% endif %}
+
+{% if posts %}
+
+{% endif %}
+
+
diff --git a/templates/overrides/kindergarten/breadcrumbs.ninjatpl b/templates/overrides/kindergarten/breadcrumbs.ninjatpl
new file mode 100644
index 0000000..e6980af
--- /dev/null
+++ b/templates/overrides/kindergarten/breadcrumbs.ninjatpl
@@ -0,0 +1,15 @@
+{# breadcrumbs (kindergarten override) — trail from the `breadcrumbs` provider (page ancestry). #}
+{# showHome prepends a Home link; separator is text ("/") for default or a chevron #}
+{# for the arrows template. The trailing is_current item renders as plain text. #}
+{% set arrows = template == "arrows" %}
+
+
+{% if showHome %}{{ homeLabel|default:"Home" }} {% endif %}
+{% for item in breadcrumbs %}
+
+{% if showHome or not forloop.First %}{% if arrows %} {% else %}{{ separator|default:"/" }} {% endif %}{% endif %}
+{% if item.is_current %}{% if showCurrent %}{{ item.label }} {% endif %}{% else %}{{ item.label }} {% endif %}
+
+{% endfor %}
+
+
diff --git a/templates/overrides/kindergarten/button.ninjatpl b/templates/overrides/kindergarten/button.ninjatpl
index 3dae610..0990930 100644
--- a/templates/overrides/kindergarten/button.ninjatpl
+++ b/templates/overrides/kindergarten/button.ninjatpl
@@ -1 +1,2 @@
-{% if text %}{% set lbl = text %}{% else %}{% set lbl = label %}{% endif %}{% if lbl %}{{ lbl }} {% else %}Add label and link to render this button. {% endif %}
+{# kindergarten button — keep the {% button %} tag intact (new fields: label / link / style / size / icon / iconPosition / newTab); wrap in a rounded kg-pop bounce so the host-rendered button gets the playful hover. #}
+{% with align=align|default:"left" %}{% if newTab %}{% button style|default:"primary" text=label|default:"Let's go" href=link|default:"#" size=size|default:"md" icon=icon|default:"" icon_position=iconPosition|default:"left" target="_blank" %}{% else %}{% button style|default:"primary" text=label|default:"Let's go" href=link|default:"#" size=size|default:"md" icon=icon|default:"" icon_position=iconPosition|default:"left" %}{% endif %}
{% endwith %}
diff --git a/templates/overrides/kindergarten/card.ninjatpl b/templates/overrides/kindergarten/card.ninjatpl
index e2e2d1e..6783733 100644
--- a/templates/overrides/kindergarten/card.ninjatpl
+++ b/templates/overrides/kindergarten/card.ninjatpl
@@ -1 +1,2 @@
-{% if title %}
{{ title }} {% endif %}{% if body %}
{{ body|safe }}
{% endif %}{% if not title and not body %}
Add a title or body to fill this card.
{% endif %}
+{# kindergarten card — crayon-bordered sticker panel; new builtin fields layout / link / media / title / text / linkLabel (reconciled from legacy body/image). #}
+{% with layout=layout|default:"vertical" %}{% if link %}{% endif %}{% if media %}{% if layout == "horizontal" %}{% img media alt=title|default:"" class="h-full w-full object-cover" %}{% else %}{% img media alt=title|default:"" class="aspect-video h-full w-full object-cover" %}{% endif %}
{% endif %}{% if title %}
{{ title }} {% endif %}{% if text %}
{{ text|safe }}
{% endif %}{% if linkLabel and link %}
{{ linkLabel }} {% endif %}
{% if link %} {% endif %} {% endwith %}
diff --git a/templates/overrides/kindergarten/category-list.ninjatpl b/templates/overrides/kindergarten/category-list.ninjatpl
new file mode 100644
index 0000000..fa93587
--- /dev/null
+++ b/templates/overrides/kindergarten/category-list.ninjatpl
@@ -0,0 +1,18 @@
+{# category-list (kindergarten override) — from the `categories` provider. list or pills. #}
+{% set pills = style == "pills" %}
+
+{% if heading %}{{ heading }} {% endif %}
+{% if pills %}
+
+{% else %}
+
+{% endif %}
+
diff --git a/templates/overrides/kindergarten/contact-card.ninjatpl b/templates/overrides/kindergarten/contact-card.ninjatpl
new file mode 100644
index 0000000..3f7e837
--- /dev/null
+++ b/templates/overrides/kindergarten/contact-card.ninjatpl
@@ -0,0 +1,18 @@
+{# contact-card kindergarten override: neon channel panels. All field reads + control flow preserved. #}
+
+
+ {% if heading %}
{{ heading }} {% endif %}
+ {% if description %}
{{ description }}
{% endif %}
+
+ {% for channel in channels %}
+
+ {% if channel.icon %}
{% endif %}
+
+ {% if channel.label %}
{{ channel.label }}
{% endif %}
+ {% if channel.value %}
{% endif %}
+
+
+ {% endfor %}
+
+
+
diff --git a/templates/overrides/kindergarten/contact-form.ninjatpl b/templates/overrides/kindergarten/contact-form.ninjatpl
new file mode 100644
index 0000000..5885984
--- /dev/null
+++ b/templates/overrides/kindergarten/contact-form.ninjatpl
@@ -0,0 +1,40 @@
+{# contact-form kindergarten override: terminal console chrome. The {% form %} block, honeypot, field loop, form.submitted/errors reads and _block_id/bid submit wiring are preserved byte-for-byte — only presentational classes change. #}
+{% with bid=_block_id|default:context.blockId %}
+
+
+ {% if form.submitted %}
+
+
+
{{ form.message|default:successMessage|default:"Thanks — your message has been sent." }}
+
+ {% else %}
+ {% if heading %}
{{ heading }} {% endif %}
+ {% if description %}
{{ description }}
{% endif %}
+ {% if form.errors._form %}
{{ form.errors._form }}
{% endif %}
+ {% with formAction="/api/blocks/"|add:bid|add:"/submit" formTarget="#contact-form-"|add:bid %}
+ {% form action=formAction hx_target=formTarget hx_swap="outerHTML" class="mt-8 grid grid-cols-1 gap-5 sm:grid-cols-2" %}
+ {% for field in fields %}
+
+ {% if field.label %}{{ field.label }}{% if field.required %} * {% endif %} {% endif %}
+ {% if field.type == "textarea" %}
+
+ {% elif field.type == "select" %}
+
+ {% if field.placeholder %}{{ field.placeholder }} {% endif %}
+ {% for opt in field.options %}{{ opt.label|default:opt.value }} {% endfor %}
+
+ {% else %}
+
+ {% endif %}
+
+ {% endfor %}
+
Leave this field empty
+
+ {{ submitLabel|default:"Send message" }}
+
+ {% endform %}
+ {% endwith %}
+ {% endif %}
+
+
+{% endwith %}
diff --git a/templates/overrides/kindergarten/countdown.ninjatpl b/templates/overrides/kindergarten/countdown.ninjatpl
new file mode 100644
index 0000000..964f3aa
--- /dev/null
+++ b/templates/overrides/kindergarten/countdown.ninjatpl
@@ -0,0 +1,49 @@
+{# countdown kindergarten override: neon HUD digit boxes, heading. Timer script + data attributes preserved byte-for-byte. #}
+
+
+ {% if heading %}
{{ heading }} {% endif %}
+ {% if intro %}
{{ intro }}
{% endif %}
+
+
+
-- {{ labelDays|default:"Days" }}
+
-- {{ labelHours|default:"Hours" }}
+
-- {{ labelMinutes|default:"Minutes" }}
+
-- {{ labelSeconds|default:"Seconds" }}
+
+
+
{{ expiredMessage|default:"This has started." }}
+ {% if expiredUrl and expiredLabel %}
{{ expiredLabel }} {% endif %}
+
+
+
Counting down to {{ target }} .
+
+
+
diff --git a/templates/overrides/kindergarten/cta.ninjatpl b/templates/overrides/kindergarten/cta.ninjatpl
new file mode 100644
index 0000000..8426816
--- /dev/null
+++ b/templates/overrides/kindergarten/cta.ninjatpl
@@ -0,0 +1,15 @@
+{# cta kindergarten override: scanline overlay, heading, kg-pop CTAs. All field reads preserved. #}
+
+
+
+
+ {% if heading %}
{{ heading }} {% endif %}
+ {% if text %}
{{ text }}
{% endif %}
+
+
+
+
+
diff --git a/templates/overrides/kindergarten/divider.ninjatpl b/templates/overrides/kindergarten/divider.ninjatpl
new file mode 100644
index 0000000..74fdfb7
--- /dev/null
+++ b/templates/overrides/kindergarten/divider.ninjatpl
@@ -0,0 +1,2 @@
+{# kindergarten divider — neon gradient rule / primary-tinted variants #}
+{% with variant=variant|default:"line" weight=weight|default:"thin" width=width|default:"full" %}{% if variant == "ornament" %}::{{ icon|default:"lucide:asterisk" }}:md text-primary::
{% elif variant == "spacer-rule" %}
{% else %} {% endif %}{% endwith %}
diff --git a/templates/overrides/kindergarten/event-list.ninjatpl b/templates/overrides/kindergarten/event-list.ninjatpl
new file mode 100644
index 0000000..b430c91
--- /dev/null
+++ b/templates/overrides/kindergarten/event-list.ninjatpl
@@ -0,0 +1,41 @@
+{# event-list kindergarten override: neon cards, mono datetime. All field reads + control flow preserved. #}
+
+
+ {% if heading %}
{{ heading }} {% endif %}
+ {% if intro %}
{{ intro }}
{% endif %}
+ {% if layout == "cards" %}
+
+ {% for event in events %}{% if not (hidePast and event.past) %}
+
+ {% if event.media %}{% img event.media alt=event.title class="h-full w-full object-cover" %}
{% endif %}
+
+
{{ event.start }}{% if event.end %} – {{ event.end }}{% endif %} {% if event.timezone %} {{ event.timezone }} {% endif %}
+
{{ event.title }}
+ {% if event.venue %}
{{ event.venue }}
{% endif %}
+ {% if event.description %}
{{ event.description }}
{% endif %}
+ {% if event.link %}
{{ event.linkLabel|default:"Details" }} {% endif %}
+
+
+ {% endif %}{% endfor %}
+
+ {% else %}
+
+ {% for event in events %}{% if not (hidePast and event.past) %}
+
+
+
{{ event.start }}
+ {% if event.end %}
to {{ event.end }}
{% endif %}
+ {% if event.timezone %}
{{ event.timezone }}
{% endif %}
+
+
+
{{ event.title }}
+ {% if event.venue %}
{{ event.venue }}
{% endif %}
+ {% if event.description %}
{{ event.description }}
{% endif %}
+
+ {% if event.link %}{{ event.linkLabel|default:"Details" }} {% endif %}
+
+ {% endif %}{% endfor %}
+
+ {% endif %}
+
+
diff --git a/templates/overrides/kindergarten/faq.ninjatpl b/templates/overrides/kindergarten/faq.ninjatpl
new file mode 100644
index 0000000..73ba402
--- /dev/null
+++ b/templates/overrides/kindergarten/faq.ninjatpl
@@ -0,0 +1,18 @@
+{# faq kindergarten override: heading, neon-tinted dividers, display questions. Chevron svg, {% for %}, and answer prose preserved. #}
+
+
+ {% if heading %}
{{ heading }} {% endif %}
+ {% if intro %}
{{ intro }}
{% endif %}
+
+ {% for item in items %}
+
+
+ {{ item.question }}
+
+
+ {{ item.answer|safe }}
+
+ {% endfor %}
+
+
+
diff --git a/templates/overrides/kindergarten/feature-grid.ninjatpl b/templates/overrides/kindergarten/feature-grid.ninjatpl
new file mode 100644
index 0000000..dd46d53
--- /dev/null
+++ b/templates/overrides/kindergarten/feature-grid.ninjatpl
@@ -0,0 +1,18 @@
+{# feature_grid kindergarten override: scanline hero overlay, mono eyebrow, heading, neon HUD cards. All field reads + control flow preserved. #}
+
+
+ {% if eyebrow %}
{{ eyebrow }}
{% endif %}
+ {% if heading %}
{{ heading }} {% endif %}
+ {% if intro %}
{{ intro }}
{% endif %}
+
+ {% for item in items %}
+
+ {% if item.icon %}
{% endif %}
+
{% if item.linkUrl %}{{ item.title }} {% else %}{{ item.title }}{% endif %}
+ {% if item.text %}
{{ item.text }}
{% endif %}
+ {% if item.linkUrl and item.linkLabel %}
{{ item.linkLabel }} {% endif %}
+
+ {% endfor %}
+
+
+
diff --git a/templates/overrides/kindergarten/featured-posts.ninjatpl b/templates/overrides/kindergarten/featured-posts.ninjatpl
new file mode 100644
index 0000000..fbddb4d
--- /dev/null
+++ b/templates/overrides/kindergarten/featured-posts.ninjatpl
@@ -0,0 +1,23 @@
+{# featured-posts (kindergarten override) — grid of cards from the `posts` provider #}
+{# (params: limit, featured=true). Neon HUD card: image, title, excerpt, meta. #}
+
+
+{% if heading %}
{{ heading }} {% endif %}
+
+
+
diff --git a/templates/overrides/kindergarten/footer.ninjatpl b/templates/overrides/kindergarten/footer.ninjatpl
new file mode 100644
index 0000000..6e55255
--- /dev/null
+++ b/templates/overrides/kindergarten/footer.ninjatpl
@@ -0,0 +1,39 @@
+{# footer (WO-TF-006): layouts columns|minimal|centered; menu columns, legal bar, and social row from the `footer_menus` provider; newsletter section reuses the {% signup_form %} core (no duplicated form). Kindergarten override: mono column headers, neon top rule, display wordmark. #}
+{% set fl = layout %}
+{% if not fl %}{% if template == "centered" %}{% set fl = "centered" %}{% else %}{% set fl = "columns" %}{% endif %}{% endif %}
+{% set fbg = backgroundColor|default:"bg-muted/50" %}
+{% macro sociallinks(cls) %}{% if social_items %}{% endif %}{% endmacro %}
+{% macro newsletter() %}{% if showNewsletter %}{{ newsletterTitle|default:"Stay in the loop" }} {% if newsletterDescription %}
{{ newsletterDescription }}
{% endif %}
{% signup_form list=newsletterListSlug|default:"main" source="footer" submit=newsletterButtonText|default:"Subscribe" %}
{% endif %}{% endmacro %}
+{% macro legalbar() %}{% if copyright %}© {{ copyright }}{% else %}© {{ companyName }}{% endif %}
{% if legal_items %}
{% for l in legal_items %}{{ l.label }} {% endfor %} {% endif %}
{% endmacro %}
+
diff --git a/templates/overrides/kindergarten/gallery.ninjatpl b/templates/overrides/kindergarten/gallery.ninjatpl
new file mode 100644
index 0000000..6293d4e
--- /dev/null
+++ b/templates/overrides/kindergarten/gallery.ninjatpl
@@ -0,0 +1,52 @@
+{# gallery kindergarten override: crayon-bordered tiles. Lightbox script + data-bn-* attributes + {% img %} preserved byte-for-byte. #}
+{% with layout=layout|default:"grid" gap=gap|default:"md" cols=columns|default:3 %}{% if lightbox %}{% endif %}{% endwith %}
diff --git a/templates/overrides/kindergarten/heading.ninjatpl b/templates/overrides/kindergarten/heading.ninjatpl
index aaa106d..0ae1de0 100644
--- a/templates/overrides/kindergarten/heading.ninjatpl
+++ b/templates/overrides/kindergarten/heading.ninjatpl
@@ -1 +1,7 @@
-{% if step|integer >= 1 and step|integer <= 9 %}{{ step|integer }} {% endif %}{% if level|integer == 1 %}
{{ text }} {% elif level|integer == 3 %}{{ text }} {% elif level|integer == 4 %}{{ text }} {% elif level|integer == 5 %}{{ text }} {% elif level|integer == 6 %}{{ text }} {% else %}{{ text }} {% endif %}{% if step|integer >= 1 and step|integer <= 9 %}{% endif %}
+{# kindergarten heading — kg-display everywhere; on h1/h2 only #}
+{% if level == 1 %}{{ text }}
+{% elif level == 3 %}{{ text }}
+{% elif level == 4 %}{{ text }}
+{% elif level == 5 %}{{ text }}
+{% elif level == 6 %}{{ text }}
+{% else %}{{ text }} {% endif %}
diff --git a/templates/overrides/kindergarten/hero.ninjatpl b/templates/overrides/kindergarten/hero.ninjatpl
new file mode 100644
index 0000000..4694957
--- /dev/null
+++ b/templates/overrides/kindergarten/hero.ninjatpl
@@ -0,0 +1,94 @@
+{# 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() %}{% endmacro %}
+{% macro herocontent(align) %}
+{% if eyebrowText %}{{ eyebrowText }} {% endif %}
+{% if headlineText %}{{ headlineText }} {% endif %}
+A B C
+{% if subText %}{{ subText }} {% endif %}
+{% if descText %}{{ descText }}
{% endif %}
+{% if pText or sText %}{% endif %}
+{% if trustLogos %}
+
Loved by families at
+
+
{% endif %}
+{% endmacro %}
+{% if variant == "split" %}
+
+{% if hasImage %}{% img backgroundImage alt=headlineText class="absolute inset-0 h-full w-full object-cover" layout="hero" %}{% else %}{{ playmascot() }}{% endif %}
+
+
+{% else %}
+{% set align = "center" %}
+{% if variant == "left-aligned" %}{% set align = "left" %}{% endif %}
+
+{% if hasImage %}{% img backgroundImage alt=headlineText class="h-full w-full object-cover" layout="hero" %}{% if showOverlay %}
{% endif %}
{% else %}{{ playmascot() }}
{% endif %}
+{{ herocontent(align) }}
+
+{% endif %}
+
diff --git a/templates/overrides/kindergarten/hours-location.ninjatpl b/templates/overrides/kindergarten/hours-location.ninjatpl
new file mode 100644
index 0000000..843f723
--- /dev/null
+++ b/templates/overrides/kindergarten/hours-location.ninjatpl
@@ -0,0 +1,31 @@
+{# hours-location kindergarten override: mono readout table, neon map panel. Map lazy-load button + inline handler preserved. #}
+
+
+ {% if heading %}
{{ heading }} {% endif %}
+
+
+ {% if hours %}
+
+
+ {% for row in hours %}
+
+ {{ row.day }}
+ {% if row.closed %}Closed{% else %}{{ row.hours }}{% endif %}
+
+ {% endfor %}
+
+
+ {% endif %}
+ {% if specialHoursNote %}
{{ specialHoursNote }}
{% endif %}
+ {% if address %}
{% endif %}
+ {% if phone %}
{% endif %}
+ {% if directionsUrl %}
Get directions{% endif %}
+
+ {% if mapEmbedUrl %}
+
+ {{ mapButtonLabel|default:"Load map" }}
+
+ {% endif %}
+
+
+
diff --git a/templates/overrides/kindergarten/image.ninjatpl b/templates/overrides/kindergarten/image.ninjatpl
new file mode 100644
index 0000000..2baa284
--- /dev/null
+++ b/templates/overrides/kindergarten/image.ninjatpl
@@ -0,0 +1,2 @@
+{# kindergarten image — crayon-bordered frame, mono caption; all size/fit branches preserved #}
+{% with size=size|default:"full" align=align|default:"center" aspectRatio=aspectRatio|default:"auto" objectFit=objectFit|default:"cover" rounded=rounded|default:"lg" shadow=shadow|default:"none" %}{% if objectFit == "contain" %}{% img src alt=alt class="w-full h-full object-contain" layout=size %}{% elif objectFit == "fill" %}{% img src alt=alt class="w-full h-full object-fill" layout=size %}{% elif objectFit == "none" %}{% img src alt=alt class="w-full h-full object-none" layout=size %}{% else %}{% img src alt=alt class="w-full h-full object-cover" layout=size %}{% endif %}
{% if caption %}{{ caption }} {% endif %} {% endwith %}
diff --git a/templates/overrides/kindergarten/logo-strip.ninjatpl b/templates/overrides/kindergarten/logo-strip.ninjatpl
new file mode 100644
index 0000000..813df74
--- /dev/null
+++ b/templates/overrides/kindergarten/logo-strip.ninjatpl
@@ -0,0 +1,16 @@
+{# logo_strip kindergarten override: mono kicker. Marquee classes, {% img %}, and keyframes {% endif %}
diff --git a/templates/overrides/kindergarten/menu-list.ninjatpl b/templates/overrides/kindergarten/menu-list.ninjatpl
new file mode 100644
index 0000000..b40d3fc
--- /dev/null
+++ b/templates/overrides/kindergarten/menu-list.ninjatpl
@@ -0,0 +1,30 @@
+{# menu-list kindergarten override: mono price readouts, neon section rules. All field reads + control flow preserved. #}
+
+
+ {% if heading %}
{{ heading }} {% endif %}
+ {% if intro %}
{{ intro }}
{% endif %}
+
+ {% for section in sections %}
+
+ {% endfor %}
+
+ {% if footnote %}
{{ footnote }}
{% endif %}
+
+
diff --git a/templates/overrides/kindergarten/navbar.ninjatpl b/templates/overrides/kindergarten/navbar.ninjatpl
new file mode 100644
index 0000000..3d5c90c
--- /dev/null
+++ b/templates/overrides/kindergarten/navbar.ninjatpl
@@ -0,0 +1,104 @@
+{# navbar (WO-TF-005): variants standard|centered-logo|split|transparent-over-hero; sticky none|sticky|shrink-on-scroll; dependency-free off-canvas drawer (pure-CSS baseline + progressive-JS focus-trap/Escape/scroll-lock); menu data from the `menus` provider. Kindergarten override: mono nav links, neon dropdown panels, CTA — drawer ids/style/script/data-bn-* preserved byte-for-byte. #}
+{% set nv = variant %}
+{% if not nv %}{% if template == "centered" %}{% set nv = "centered-logo" %}{% elif template == "transparent" %}{% set nv = "transparent-over-hero" %}{% else %}{% set nv = "standard" %}{% endif %}{% endif %}
+{% set sb = stickyBehavior %}
+{% if not sb %}{% if sticky %}{% set sb = "sticky" %}{% else %}{% set sb = "none" %}{% endif %}{% endif %}
+{% set use_mega = megaMenu or template == "mega" %}
+{% set is_transparent = nv == "transparent-over-hero" %}
+{% set cta_text = cta.text|default:ctaLabel %}
+{% set cta_url = cta.url|default:ctaUrl %}
+{% set bg = backgroundColor|default:"bg-background" %}
+{% macro navlink(item) %}{{ item.label }} {% endmacro %}
+{% macro dropdown(item) %}
+
{{ item.label }}
+{% if use_mega %}
{% for child in item.children %}
{% endfor %}
+{% else %}
{% for child in item.children %}{% if child.item_type == "header" %}
{{ child.label }}
{% else %}
{{ child.label }} {% endif %}{% endfor %}
{% endif %}
+
{% endmacro %}
+{% macro brand(size) %}{% if logoType == "text" and logoText.text %}{{ logoText.text }} {% elif logo %} {% elif companyName %}{{ companyName }} {% endif %} {% endmacro %}
+{% macro ctabtn(extra) %}{% if cta_text %}{% endif %}{% endmacro %}
+
+{% if showUtilityBar %}{{ utilityBarText|safe }}
{% endif %}
+
+
+{% if nv == "centered-logo" %}
+
{% for item in menu.items %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endfor %}
+
{{ brand("h-9") }}
+
{{ ctabtn("") }}
+{% elif nv == "split" %}
+
{% for item in menu.items %}{% if forloop.Counter0 * 2 < menu.items|length %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endif %}{% endfor %}
+
{{ brand("h-9") }}
+
{% for item in menu.items %}{% if forloop.Counter0 * 2 >= menu.items|length %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endif %}{% endfor %}{{ ctabtn("ml-3") }}
+{% else %}
+
{{ brand("h-8") }}
+
{% for item in menu.items %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endfor %}{{ ctabtn("ml-3") }}
+{% endif %}
+
+
+
+{# --- Mobile off-canvas drawer: pure-CSS checkbox baseline (works with no JS) --- #}
+
+
+
+
{{ brand("h-8") }}
Close menu
+
{% for item in menu.items %}{% if item.children %}{{ item.label }} {% for child in item.children %}{% if child.item_type != "header" %}
{{ child.label }} {% endif %}{% endfor %}
{% else %}{{ item.label }} {% endif %}{% endfor %}
+{% if cta_text %}
{% endif %}
+
+
+
+
diff --git a/templates/overrides/kindergarten/newsletter-signup.ninjatpl b/templates/overrides/kindergarten/newsletter-signup.ninjatpl
new file mode 100644
index 0000000..3fa5a5d
--- /dev/null
+++ b/templates/overrides/kindergarten/newsletter-signup.ninjatpl
@@ -0,0 +1,12 @@
+{# newsletter-signup (kindergarten override) — the {% signup_form %} tag carries the #}
+{# untouchable subscribe wiring (/api/lists/subscribe HTMX post + honeypot); #}
+{# this template supplies only the kindergarten chrome around it. #}
+
+
+
+{% if title %}
{{ title }} {% endif %}
+{% if description %}
{{ description }}
{% endif %}
+
{% signup_form list=listSlug source=source submit=submitText success_message=successMessage placeholder=placeholder %}
+
+
+
diff --git a/templates/overrides/kindergarten/page-suggestions.ninjatpl b/templates/overrides/kindergarten/page-suggestions.ninjatpl
new file mode 100644
index 0000000..8142c92
--- /dev/null
+++ b/templates/overrides/kindergarten/page-suggestions.ninjatpl
@@ -0,0 +1,12 @@
+{# page-suggestions kindergarten override — themed 404. NOTE: the builtin builds its "did you mean" list from a Postgres full-text query (SearchSimilarPages), which a codeless template cannot run — so this override renders the on-theme 404 (title / message / requested path / home CTA) without the dynamic suggestion list. Recorded as a known deviation in WO-TF-019. Content fields honored: title, emptyMessage. #}
+
+
+
Oops
+
{{ title|default:"This page went to play outside" }}
+
{{ emptyMessage|default:"We could not find that page. It may have moved, or maybe it never existed. Let us take you back to the start." }}
+ {% if context.path %}
You asked for: {{ context.path }}
{% endif %}
+
+
+
diff --git a/templates/overrides/kindergarten/password-reset-form.ninjatpl b/templates/overrides/kindergarten/password-reset-form.ninjatpl
new file mode 100644
index 0000000..21521c0
--- /dev/null
+++ b/templates/overrides/kindergarten/password-reset-form.ninjatpl
@@ -0,0 +1,13 @@
+{# password-reset-form kindergarten override — reproduces the builtin's hardcoded wiring: hx-post /api/auth/request-password-reset (target #reset-message), back-to-login link /login. Only the chrome is themed. #}
+
+
+
+
Reset Password
+
Enter your email address and we will send you a link to reset your password.
+
+
Back to login
+
+
diff --git a/templates/overrides/kindergarten/popular-posts.ninjatpl b/templates/overrides/kindergarten/popular-posts.ninjatpl
new file mode 100644
index 0000000..9618561
--- /dev/null
+++ b/templates/overrides/kindergarten/popular-posts.ninjatpl
@@ -0,0 +1,16 @@
+{# popular-posts (kindergarten override) — compact ranked list from the `posts` provider. #}
+
+{% if heading %}{{ heading }} {% endif %}
+
+{% for post in posts %}
+
+{% if showRank %}{{ forloop.Counter }} {% endif %}
+{% if showThumbnail and post.featured_image_url %}{% img post.featured_image_url alt=post.title class="h-full w-full object-cover" %} {% endif %}
+
+
+{% if post.published_at %}
{{ post.published_at|date:"M j, Y" }}
{% endif %}
+
+
+{% endfor %}
+
+
diff --git a/templates/overrides/kindergarten/post-hero.ninjatpl b/templates/overrides/kindergarten/post-hero.ninjatpl
new file mode 100644
index 0000000..eec514a
--- /dev/null
+++ b/templates/overrides/kindergarten/post-hero.ninjatpl
@@ -0,0 +1,21 @@
+{# post-hero (kindergarten override) — current post from the `post` provider. When the post #}
+{# has a featured image it becomes an overlaid banner; otherwise a clean text hero. #}
+{% set center = align != "left" %}
+{% if post.featured_image_url %}
+
+{% img post.featured_image_url alt=post.featured_image_alt class="h-full w-full object-cover" layout="hero" %}
+
+
{{ post.title }}
+{% if showExcerpt and post.excerpt %}
{{ post.excerpt }}
{% endif %}
+{% if showMeta %}
{% if post.author_name %}{{ post.author_name }} {% endif %}{% if post.published_at %}{{ post.published_at|date:"F j, Y" }} {% endif %}{% if post.reading_time %}{{ post.reading_time }} min read {% endif %}
{% endif %}
+
+
+{% else %}
+
+
+
{{ post.title }}
+{% if showExcerpt and post.excerpt %}
{{ post.excerpt }}
{% endif %}
+{% if showMeta %}
{% if post.author_name %}{{ post.author_name }} {% endif %}{% if post.published_at %}{{ post.published_at|date:"F j, Y" }} {% endif %}{% if post.reading_time %}{{ post.reading_time }} min read {% endif %}
{% endif %}
+
+
+{% endif %}
diff --git a/templates/overrides/kindergarten/post-metadata.ninjatpl b/templates/overrides/kindergarten/post-metadata.ninjatpl
new file mode 100644
index 0000000..484c5af
--- /dev/null
+++ b/templates/overrides/kindergarten/post-metadata.ninjatpl
@@ -0,0 +1,9 @@
+{# post-metadata (kindergarten override) — author/date/reading-time row from the `post` provider. #}
+{% if post %}
+
+{% if showAuthor and post.author_name %}
{% if showAvatar and post.author_avatar %}{% img post.author_avatar alt=post.author_name class="h-7 w-7 rounded-full border border-primary/40 object-cover" %}{% endif %}{{ post.author_name }} {% endif %}
+{% if showDate and post.published_at %}
{{ post.published_at|date:"F j, Y" }} {% endif %}
+{% if showReadingTime and post.reading_time %}
{{ post.reading_time }} min read {% endif %}
+{% if post.categories %}
{% for cat in post.categories %}{{ cat.name }} {% endfor %} {% endif %}
+
+{% endif %}
diff --git a/templates/overrides/kindergarten/pricing.ninjatpl b/templates/overrides/kindergarten/pricing.ninjatpl
new file mode 100644
index 0000000..3ee297a
--- /dev/null
+++ b/templates/overrides/kindergarten/pricing.ninjatpl
@@ -0,0 +1,25 @@
+{# pricing kindergarten override: scanline overlay, heading, HUD neon tier panels (magenta highlight / cyan standard), CTAs. Field reads + control flow preserved. #}
+
+
+ {% if heading %}
{{ heading }} {% endif %}
+ {% if intro %}
{{ intro }}
{% endif %}
+
+ {% for tier in tiers %}
+
+ {% if tier.badge %}
{{ tier.badge }} {% endif %}
+
{{ tier.name }}
+
+ {% if currency %}{{ currency }}{% else %}${% endif %}{{ tier.price }}
+ {% if tier.period %}{{ tier.period }} {% endif %}
+
+ {% if tier.description %}
{{ tier.description }}
{% endif %}
+ {% if tier.features %}
+ {% for feature in tier.features %}{{ feature }} {% endfor %}
+ {% endif %}
+ {% if tier.ctaUrl and tier.ctaLabel %}
{{ tier.ctaLabel }} {% endif %}
+
+ {% endfor %}
+
+ {% if billingNote %}
{{ billingNote }}
{% endif %}
+
+
diff --git a/templates/overrides/kindergarten/quote.ninjatpl b/templates/overrides/kindergarten/quote.ninjatpl
new file mode 100644
index 0000000..3ed8108
--- /dev/null
+++ b/templates/overrides/kindergarten/quote.ninjatpl
@@ -0,0 +1,2 @@
+{# kindergarten quote — dashed neon left border; kg-display for pull; mono figcaption #}
+{% with style=style|default:"block" align=align|default:"left" %}{{ quote|safe }} {% if attribution or role or cite %}{% if attribution %}{{ attribution }} {% endif %}{% if role %}{{ role }} {% endif %}{% if cite %}Source {% endif %} {% endif %} {% endwith %}
diff --git a/templates/overrides/kindergarten/related-posts.ninjatpl b/templates/overrides/kindergarten/related-posts.ninjatpl
new file mode 100644
index 0000000..052bb94
--- /dev/null
+++ b/templates/overrides/kindergarten/related-posts.ninjatpl
@@ -0,0 +1,17 @@
+{# related-posts (kindergarten override) — grid from the `posts` provider. #}
+{% if posts %}
+
+
+
{{ heading|default:"Related posts" }}
+
+
+
+{% endif %}
diff --git a/templates/overrides/kindergarten/rich-section.ninjatpl b/templates/overrides/kindergarten/rich-section.ninjatpl
new file mode 100644
index 0000000..89cc2b8
--- /dev/null
+++ b/templates/overrides/kindergarten/rich-section.ninjatpl
@@ -0,0 +1,14 @@
+{# rich_section kindergarten override: mono eyebrow, heading, HUD-framed media, CTA. Field reads + {% img %} preserved. #}
+
diff --git a/templates/overrides/kindergarten/rich-text.ninjatpl b/templates/overrides/kindergarten/rich-text.ninjatpl
new file mode 100644
index 0000000..690d369
--- /dev/null
+++ b/templates/overrides/kindergarten/rich-text.ninjatpl
@@ -0,0 +1,2 @@
+{# kindergarten rich-text — body font via kg-prose #}
+{% if html %}{{ html|safe }} {% endif %}
diff --git a/templates/overrides/kindergarten/sidebar-nav.ninjatpl b/templates/overrides/kindergarten/sidebar-nav.ninjatpl
new file mode 100644
index 0000000..fa31303
--- /dev/null
+++ b/templates/overrides/kindergarten/sidebar-nav.ninjatpl
@@ -0,0 +1,15 @@
+{# sidebar-nav (kindergarten override) — vertical menu from the `menus` provider. Variants #}
+{# default | compact. highlight-current carried as a progressive-enhancement #}
+{# script keyed on data-bn-sidebar-link (marks the anchor matching location.pathname). #}
+{% set compact = template == "compact" %}
+
+{% if title %}{{ title }} {% endif %}
+
+
+{% if highlightCurrent %}{% endif %}
diff --git a/templates/overrides/kindergarten/social-links.ninjatpl b/templates/overrides/kindergarten/social-links.ninjatpl
new file mode 100644
index 0000000..1bae3e2
--- /dev/null
+++ b/templates/overrides/kindergarten/social-links.ninjatpl
@@ -0,0 +1,14 @@
+{# kindergarten social-links — mono label, sharp neon-bordered chips; icon sprites preserved #}
+
+ {% if heading %}
{{ heading }}
{% endif %}
+
+
diff --git a/templates/overrides/kindergarten/stats.ninjatpl b/templates/overrides/kindergarten/stats.ninjatpl
new file mode 100644
index 0000000..a619e4e
--- /dev/null
+++ b/templates/overrides/kindergarten/stats.ninjatpl
@@ -0,0 +1,49 @@
+{# stats kindergarten override: scanline overlay, heading, HUD stat tiles, mono labels. data-stats + countUp script/attrs preserved byte-for-byte. #}
+
+
+ {% if heading %}
{{ heading }} {% endif %}
+ {% if intro %}
{{ intro }}
{% endif %}
+
+ {% for item in items %}
+
+
{% if item.prefix %}{{ item.prefix }}{% endif %}{{ item.value }} {% if item.suffix %}{{ item.suffix }}{% endif %}
+ {% if item.label %}{{ item.label }} {% endif %}
+
+ {% endfor %}
+
+
+
+{% if countUp %}{% endif %}
diff --git a/templates/overrides/kindergarten/team.ninjatpl b/templates/overrides/kindergarten/team.ninjatpl
new file mode 100644
index 0000000..c1f163e
--- /dev/null
+++ b/templates/overrides/kindergarten/team.ninjatpl
@@ -0,0 +1,22 @@
+{# team kindergarten override: heading, dashed-neon HUD member panels (lime), mono roles. {% img %} + social icon svgs + control flow preserved. #}
+
+
+ {% if heading %}
{{ heading }} {% endif %}
+ {% if intro %}
{{ intro }}
{% endif %}
+
+ {% for member in members %}
+
+ {% if member.photo %}
{% img member.photo alt=member.name class="h-full w-full object-cover" %} {% endif %}
+
+ {% if member.name %}
{{ member.name }} {% endif %}
+ {% if member.role %}
{{ member.role }}
{% endif %}
+ {% if member.bio %}
{{ member.bio }}
{% endif %}
+ {% if member.socials %}
+ {% for social in member.socials %}{% if social.url %}
{% endif %}{% endfor %}
+
{% endif %}
+
+
+ {% endfor %}
+
+
+
diff --git a/templates/overrides/kindergarten/testimonial.ninjatpl b/templates/overrides/kindergarten/testimonial.ninjatpl
new file mode 100644
index 0000000..266f1eb
--- /dev/null
+++ b/templates/overrides/kindergarten/testimonial.ninjatpl
@@ -0,0 +1,27 @@
+{# testimonial kindergarten override: heading, dashed-neon HUD quote cards. Rating svgs, {% img %}, and control flow preserved. #}
+
+
+ {% if heading %}
{{ heading }} {% endif %}
+
+ {% for q in quotes %}
+
+ {% if q.rating %}
+
+
+
+
+
+
{% endif %}
+ {% if q.quote %}“{{ q.quote }}”{% endif %}
+
+ {% if q.avatar %}{% img q.avatar alt=q.name class="h-full w-full object-cover" %} {% endif %}
+
+ {% if q.name %}{{ q.name }} {% endif %}
+ {% if q.role %}{{ q.role }} {% endif %}
+
+
+
+ {% endfor %}
+
+
+
diff --git a/templates/overrides/kindergarten/text.ninjatpl b/templates/overrides/kindergarten/text.ninjatpl
index 2e1cfd7..1ba6bad 100644
--- a/templates/overrides/kindergarten/text.ninjatpl
+++ b/templates/overrides/kindergarten/text.ninjatpl
@@ -1 +1,2 @@
-{{ text|safe }}
+{# kindergarten text — body font via kg-prose #}
+{{ text|safe }}
diff --git a/templates/overrides/kindergarten/timeline.ninjatpl b/templates/overrides/kindergarten/timeline.ninjatpl
new file mode 100644
index 0000000..36e7ad1
--- /dev/null
+++ b/templates/overrides/kindergarten/timeline.ninjatpl
@@ -0,0 +1,22 @@
+{# timeline kindergarten override: heading, mono step labels, neon markers. All field reads + control flow preserved. #}
+
+
+ {% if heading %}
{{ heading }} {% endif %}
+ {% if intro %}
{{ intro }}
{% endif %}
+
+ {% for step in steps %}
+
+
+ {% if marker == "numbered" %}{{ forloop.counter }}{% elif marker == "dot" %} {% else %} {% endif %}
+
+
+ {% if step.label %}
{{ step.label }}
{% endif %}
+ {% if step.title %}
{{ step.title }} {% endif %}
+ {% if step.text %}
{{ step.text }}
{% endif %}
+ {% if step.media %}
{% img step.media alt=step.title class="w-full h-auto object-cover" %}
{% endif %}
+
+
+ {% endfor %}
+
+
+
diff --git a/templates/overrides/kindergarten/utility-bar.ninjatpl b/templates/overrides/kindergarten/utility-bar.ninjatpl
new file mode 100644
index 0000000..bc7ab00
--- /dev/null
+++ b/templates/overrides/kindergarten/utility-bar.ninjatpl
@@ -0,0 +1,10 @@
+{# utility_bar (WO-TF-006): thin top contact strip; left/right item lists {icon,label,link}; icons render through the sprite pipeline via the ::pack:name:: shorthand. Kindergarten override: mono terminal readout, neon underline. #}
+{% set ubcls = "bg-muted text-muted-foreground border-b border-primary/30" %}
+{% if variant == "primary" %}{% set ubcls = "bg-primary text-primary-foreground" %}{% elif variant == "dark" %}{% set ubcls = "bg-foreground text-background" %}{% endif %}
+{% macro ubitem(it) %}{% if it.link %}{% if it.icon %}::{{ it.icon }}::{% endif %}{% if it.label %}{{ it.label }} {% endif %} {% else %}{% if it.icon %}::{{ it.icon }}::{% endif %}{% if it.label %}{{ it.label }} {% endif %} {% endif %}{% endmacro %}
+
+
+
{% for it in leftItems %}{{ ubitem(it) }}{% endfor %}
+
{% for it in rightItems %}{{ ubitem(it) }}{% endfor %}
+
+
diff --git a/templates/overrides/kindergarten/video-embed.ninjatpl b/templates/overrides/kindergarten/video-embed.ninjatpl
new file mode 100644
index 0000000..0694c78
--- /dev/null
+++ b/templates/overrides/kindergarten/video-embed.ninjatpl
@@ -0,0 +1,23 @@
+{# video-embed kindergarten override: neon HUD frame, play button. Facade script + data-bn-* attributes + {% img %} preserved byte-for-byte. #}
+{% with aspect=aspect|default:"16:9" %}{% if poster %}{% img poster alt=title|default:"" class="absolute inset-0 h-full w-full object-cover" %}{% endif %}::lucide:play:xl::
{% if caption %}{{ caption }} {% endif %} {% endwith %}