Compare commits

..

No commits in common. "ca4e132f41a04e43c98ca9b90a74b7d3787ff776" and "dccd269ab2805f11dd3e03164d0ad6f95eb8f661" have entirely different histories.

89 changed files with 912 additions and 2459 deletions

View File

@ -1,26 +0,0 @@
# Kindergarten
A bright, hand-lettered theme built on primary colors and crayon. Wobbly cut-out shapes, sticker badges, and dotted paper give it the warmth of a classroom wall. It feels playful and safe, the kind of page a parent trusts and a child recognizes. Rounded type keeps everything friendly and easy to read.
## Good for
We built Kindergarten with early learning in mind. Treat these as starting points, not limits. If the mood fits your brand, use it:
- Preschools and daycare centres
- After-school and holiday programs
- Children's activity classes
- Family and parenting groups
- Kids' party and play venues
## What you get
- Five color presets, each with a tuned light mode and a tuned dark mode: Crayon Box, Sunny Recess, Bluebird, Meadow Play, and Bubblegum.
- Two bundled fonts, ready to assign: Baloo 2 for rounded headings and Quicksand for body text. Fonts stay admin-controlled, so you can swap them from the typography settings.
- A GO BIG hero with an animated canvas: a bouncing mascot, wiggling letters, and a confetti burst. It honors reduced-motion settings and falls back to a static scene when scripts are off.
- Four persona blocks: an alphabet strip, a daily class schedule, an art wall of child-artist polaroids, and a story-time open book.
- A demo site that installs when you activate the theme: a home page, an about page, a blog with three posts, and a contact page whose form emails your admins.
- Every core block restyled to match, plus a themed login screen and a themed 404 page.
## Install
Install from the BlockNinja registry, then pick a preset and assign your fonts in the admin. Activate the demo content if you want a fully dressed starting point.

View File

@ -1,39 +1,43 @@
# Recommended fonts Kindergarten
# Recommended fonts for Kindergarten
This theme **bundles** two OFL (SIL Open Font License 1.1) latin-subset webfonts in
`assets/fonts/` and declares them in `fonts.json`. On install they become
`source=template` rows in the `fonts` table, so their `@font-face` declarations are
auto-emitted and they are selectable in the admin typography picker. No manual font setup
is required to get the intended look.
The kindergarten theme ships `fonts.json = []` per the wave-1 fonts policy
(`themes/docs/FONTS.md`). Admins assign fonts via the typography panel; this
file lists the picker recommendations sourced from the spec (`docs/works/kindergarten.md` §3).
| Slot | Bundled family | File | Fallback stack (before assignment) |
|---|---|---|---|
| Heading (`--font-heading`) | **Baloo 2** | `fonts/baloo2-700.woff2` | `"Baloo 2", "Fredoka", "Quicksand", system-ui, sans-serif` |
| Body (`--font-body`) | **Quicksand** | `fonts/quicksand-400.woff2`, `fonts/quicksand-700.woff2` | `"Quicksand", "Nunito", system-ui, -apple-system, "Segoe UI", sans-serif` |
| Mono (`--font-mono`) | *(not bundled — system fallback)* | — | `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace` |
Until the admin makes a choice, the theme falls back to its system stack:
## How the theme consumes them
- `--font-heading``Quicksand`, `Sniglet`, `Nunito`, system sans
- `--font-body``Nunito`, `Quicksand`, system sans
- `--font-mono``Fira Code`, `JetBrains Mono`, system monospace
Every `font-family` in the theme flows through a CSS variable with a bundled-family
fallback — nothing is hardcoded, so the admin picker always wins:
## How to apply
- `.kg-display` -> `var(--font-heading, "Baloo 2", ...)` — headlines, hero, section titles, sticker numerals.
- `.kg-body` / `.kg-prose` -> `var(--font-body, "Quicksand", ...)` — body copy, forms, cards.
- `.kg-mono` -> `var(--font-mono, ...)` — schedule times and small numeric readouts.
1. Open **Settings → Typography** in the BlockNinja admin.
2. Click the **Google Fonts** tab in the font picker.
3. Add each font below using the listed picker source.
4. Assign Heading / Body / Mono in the typography panel:
- Heading slot → `google:Quicksand` (700)
- Body slot → `google:Nunito` (400, 600)
- Mono slot → `google:Fira Code` (400)
## To assign (optional)
## Recommendations
Open the typography panel and assign **Baloo 2 -> Heading** and **Quicksand -> Body**. Both
appear in the picker under the theme's bundled fonts.
| Slot | Source | Family | Weights / styles | Notes |
|---------|--------------------|-------------|---------------------|-------|
| Heading | `google:Quicksand` | Quicksand | 400, 600, 700 | Primary display face — rounded, friendly. |
| Heading (alt) | `google:Sniglet` | Sniglet | 400, 800 | Alt display face for extra-chunky banners. Confirm SIL OFL compatibility before bundling. |
| Body | `google:Nunito` | Nunito | 400, 600, 700 | Famously rounded body text. Pairs with Quicksand. |
| Mono | `google:Fira Code` | Fira Code | 400 | Use the regular weight; ligatures look great in classroom code samples. |
## Alternate direction
## Why these picks
The locked WO-TF-019 direction pairs **Baloo 2** (rounded display) with **Quicksand**
(body). **Fredoka** (OFL) is an equally good rounded display alternative to Baloo 2 — both
are in the curated Google Fonts tab. **Nunito** (OFL) is a warmer-weight body alternative to
Quicksand.
- **Quicksand** and **Nunito** are SIL OFL — safe to recommend and free to use.
- **Sniglet** is also SIL OFL with the original "Sniglet" family available via Google Fonts as a sibling of Comfortaa. The spec calls out Sniglet as the alternate display face; ship Comfortaa as a backup if your admin prefers something with a similar feel but slightly more conservative letterforms.
- **Fira Code** is SIL OFL. The spec calls for "Fira Code Rounded" — Google Fonts ships only the regular Fira Code variants today; use the regular as the closest available stand-in.
## Licensing
## Wave-2 follow-up
Both are OFL 1.1 and cleared for redistribution inside this artifact. Details and
attribution: `assets/fonts/LICENSES.md`.
If the brand requires Sniglet rounded everywhere, bundle the woff2 files
inside `assets/fonts/web/` and add the entries to `fonts.json` per the
schema in `themes/docs/FONTS.md`. Record the licence in `LICENSES.md` at the
theme root.

View File

@ -1,26 +0,0 @@
# Recommended icons — Kindergarten
Icons render through the CMS sprite pipeline: `<svg><use href="/icons/<pack>.svg#<name>"/></svg>`
(or the `::pack:name:size::` shorthand). The theme's overrides reference two bundled packs.
## Required packs
Declared in `plugin.mod` as `required_icon_packs = ["lucide", "simple-icons"]`
(forward-declared — the loader does not yet auto-install; install manually until it does).
| Pack | Used for | Install |
|---|---|---|
| **lucide** | UI glyphs the builtin/override templates use: `arrow-right`, `check`, `chevron-down`, `chevron-left`, `chevron-right`, `x`, `star`, `flag`, `navigation`, `map-pin`, `download`, `play`, `sun`, `book-open`, `palette`, `user`, `globe` | Ships bundled with the CMS — no action needed. |
| **simple-icons** | Author/social brand glyphs: `x` (Twitter), `linkedin`, `github`, `facebook`, `instagram`, `youtube` | Ships bundled with the CMS — no action needed. |
Both packs ship bundled with the CMS, so a fresh instance renders every override icon with
no manual step. If you author blocks that reference other packs (e.g. `tabler`, `phosphor`),
install them from `/admin/icons` (Browse -> install -> enable) before activation.
## Note
Icon values in block content are stored as `"pack:name"` strings and split with
`|split:":"|first` / `|last`. Never inline raw SVG path data for content icons — that
bypasses the sprite system and the admin pack manager. (The theme's decorative mascot,
schedule glyphs, and social marks in persona blocks are intrinsic illustrations, not
content-icon fields.)

View File

@ -1,20 +0,0 @@
# Bundled font licenses — Kindergarten theme
Both families are bundled as latin-subset `.woff2` and are licensed under the
SIL Open Font License 1.1 (OFL). Redistribution inside this theme artifact is permitted.
| File | Family | License | Source |
|---|---|---|---|
| `baloo2-700.woff2` | Baloo 2 (700) | OFL 1.1 | Google Fonts (fonts.gstatic.com, v23), latin subset |
| `quicksand-400.woff2` | Quicksand (400) | OFL 1.1 | Google Fonts (fonts.gstatic.com, v37), latin subset |
| `quicksand-700.woff2` | Quicksand (700) | OFL 1.1 | Google Fonts (fonts.gstatic.com, v37), latin subset |
OFL 1.1 full text: https://openfontlicense.org
- Baloo 2 © The Baloo 2 Project Authors (ektype).
- Quicksand © The Quicksand Project Authors (Andrew Paglinawan).
These are the Google-hosted latin subsets (Latin + Latin-1 punctuation), chosen to keep
the bundle small (~50 KB total). They cover English content; sites needing extended Latin,
Devanagari, Cyrillic, Greek, or Vietnamese glyphs should assign the full family from the
admin Google Fonts picker instead.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1 +1 @@
<nav class="kg-alphabet-strip {% if colorMode == "primary" %}kg-alphabet-primary{% elif colorMode == "mono" %}kg-alphabet-mono{% endif %}" aria-label="Alphabet strip" data-block="kindergarten:alphabet_strip">{% if not letters %}<span class="kg-empty" data-empty="true">Add a few letters to build the strip.</span>{% endif %}{% for item in letters %}<span class="kg-alphabet-letter">{{ item.letter }}</span>{% endfor %}</nav>
<nav class="kg-alphabet-strip {% if colorMode == "primary" %}kg-alphabet-primary{% elif colorMode == "mono" %}kg-alphabet-mono{% endif %}" aria-label="Alphabet strip" data-block="kindergarten:alphabet_strip">{% if not letters|cut:" " %}<span class="kg-empty" data-empty="true">No letters configured</span>{% endif %}{% for letter in letters|cut:" " %}<span class="kg-alphabet-letter">{{ letter }}</span>{% endfor %}</nav>

View File

@ -5,22 +5,11 @@
"type": "object",
"properties": {
"letters": {
"type": "array",
"type": "string",
"title": "Letters",
"description": "Ordered list of letter tiles.",
"default": [],
"x-editor": "array",
"items": {
"type": "object",
"properties": {
"letter": {
"type": "string",
"title": "Letter",
"description": "A single letter or character for this tile.",
"x-editor": "text"
}
}
}
"description": "Sequence of letters to render. Each character becomes one tile.",
"default": "ABCDEFGHIJ",
"x-editor": "text"
},
"colorMode": {
"type": "string",

View File

@ -1 +0,0 @@
<section class="kg-section" data-block="kindergarten:art_wall"><div class="kg-container">{% set showNames = true %}{% if showChildName == false %}{% set showNames = false %}{% endif %}{% if title %}<h2 class="kg-display mb-6" style="font-size: 2rem;"><span class="kg-crayon-underline">{{ title }}</span></h2>{% endif %}{% if not items %}<div class="kg-empty" data-empty="true">No artworks yet — pin a few pieces to fill the wall.</div>{% else %}<div class="kg-gallery-grid">{% for art in items %}<figure class="kg-polaroid">{% if art.image %}<img src="{{ art.image|mediaURL }}" alt="{% if showNames and art.childName %}Artwork by {{ art.childName }}{% else %}Children's artwork{% endif %}">{% else %}<span class="kg-polaroid-image" role="img" aria-label="Placeholder for a child's artwork"></span>{% endif %}<figcaption class="kg-polaroid-caption">{% if showNames and art.childName %}<span>{{ art.childName }}</span>{% endif %}{% if showNames and art.childName and art.age %}, {% endif %}{% if art.age %}<span>age {{ art.age }}</span>{% endif %}</figcaption></figure>{% endfor %}</div>{% endif %}</div></section>

View File

@ -1,51 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Art Wall",
"description": "Polaroid-style wall of children's artwork with artist names.",
"type": "object",
"properties": {
"title": {
"type": "string",
"title": "Title",
"description": "Optional heading above the wall (e.g. 'Our art wall').",
"x-editor": "text"
},
"showChildName": {
"type": "boolean",
"title": "Show artist name",
"description": "Show each child's name and age under their artwork.",
"default": true,
"x-editor": "select"
},
"items": {
"type": "array",
"title": "Artworks",
"description": "Each piece of artwork.",
"default": [],
"x-editor": "array",
"items": {
"type": "object",
"properties": {
"image": {
"type": "string",
"title": "Artwork image",
"description": "The scanned or photographed artwork.",
"x-editor": "media"
},
"childName": {
"type": "string",
"title": "Artist name",
"description": "First name of the child who made it.",
"x-editor": "text"
},
"age": {
"type": "string",
"title": "Age",
"description": "The child's age (optional).",
"x-editor": "text"
}
}
}
}
}
}

1
blocks/big_cta.ninjatpl Normal file
View File

@ -0,0 +1 @@
<section class="kg-big-cta" data-block="kindergarten:big_cta"><div class="kg-container">{% if label %}<a href="{{ href|default:'#' }}" class="kg-pill kg-crayon-underline {% if colorVariant == "red" %}kg-pill-red{% elif colorVariant == "blue" %}kg-pill-blue{% elif colorVariant == "green" %}kg-pill-green{% else %}kg-pill-yellow{% endif %}">{{ label }}</a>{% else %}<span class="kg-empty" data-empty="true">Add a label and link to publish this call-to-action.</span>{% endif %}</div></section>

View File

@ -0,0 +1,28 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Big CTA",
"description": "Oversized pill button with sticker drop-shadow and crayon underline.",
"type": "object",
"properties": {
"label": {
"type": "string",
"title": "Label",
"description": "Button copy (e.g. 'Enrol today').",
"x-editor": "text"
},
"href": {
"type": "string",
"title": "Link",
"description": "Where the button points.",
"x-editor": "link"
},
"colorVariant": {
"type": "string",
"title": "Color Variant",
"description": "Which primary colorway the pill uses.",
"x-editor": "select",
"enum": ["red", "blue", "yellow", "green"],
"default": "yellow"
}
}
}

View File

@ -1,34 +1,54 @@
# Kindergarten theme persona block definitions (codeless .bnp, WO-TF-019).
# Kindergarten theme block definitions (codeless .bnp, WO-WZ-021).
# Keys are fully qualified (kindergarten:<key>) because the codeless loader
# registers definitions verbatim — there is no PluginBlockRegistry prefixing
# in the codeless path (unlike the wasm/templ path). The qualified keys match
# the data-block attributes and the master_pages.json / seed references.
#
# Builtin-duplicating blocks (hero, cta, footer, gallery, stats, quote) are NOT
# theme blocks — they are template overrides in templates/overrides/kindergarten/.
# Only true preschool furniture lives here.
# the hardcoded data-block attributes and the master_pages.json references.
blocks:
- key: kindergarten:mascot_hero
title: Mascot Hero
description: Big rounded panel with mascot SVG and primary-colored confetti.
category: content
schema: mascot_hero.schema.json
template: mascot_hero.ninjatpl
- key: kindergarten:alphabet_strip
title: Alphabet Strip
description: A-Z learning band of colorful crayon letter tiles.
description: Decorative letter band; jumps to anchors when used as TOC.
category: navigation
schema: alphabet_strip.schema.json
template: alphabet_strip.ninjatpl
- key: kindergarten:schedule
title: Day Schedule
description: Daily class plan with crayon time-pills and activity icons.
description: Daycare/classroom day plan with crayon time-pills.
category: content
schema: schedule.schema.json
template: schedule.ninjatpl
- key: kindergarten:art_wall
title: Art Wall
description: Polaroid wall of children's artwork with artist names.
- key: kindergarten:gallery_of_art
title: Gallery of Art
description: Polaroid-style grid for kid artwork.
category: content
schema: art_wall.schema.json
template: art_wall.ninjatpl
- key: kindergarten:story_time
title: Story Time
description: Picture-book read-aloud panel — cover, title, and a line to read together.
schema: gallery_of_art.schema.json
template: gallery_of_art.ninjatpl
- key: kindergarten:numbers_counter
title: Numbers Counter
description: Big circular numeral badges for stats (e.g. '12 teachers').
category: content
schema: story_time.schema.json
template: story_time.ninjatpl
schema: numbers_counter.schema.json
template: numbers_counter.ninjatpl
- key: kindergarten:storybook_quote
title: Storybook Quote
description: Picture-book page treatment for a quote with optional illustration.
category: content
schema: storybook_quote.schema.json
template: storybook_quote.ninjatpl
- key: kindergarten:big_cta
title: Big CTA
description: Oversized pill button with crayon underline.
category: content
schema: big_cta.schema.json
template: big_cta.ninjatpl
- key: kindergarten:footer
title: Friendly Footer
description: Mascot wave, signup, contact, crayon-rule divider.
category: layout
schema: footer.schema.json
template: footer.ninjatpl

1
blocks/footer.ninjatpl Normal file
View File

@ -0,0 +1 @@
<div class="kg-footer" data-block="kindergarten:footer"><div class="kg-container"><div class="kg-footer-mascot"><span aria-hidden="true">👋</span><span>{{ mascotName|default:'Pip' }} says hi!</span></div>{% if showSignup != false %}<form class="kg-footer-signup" aria-label="Newsletter signup" method="post" action="/subscribe"><input type="email" name="email" placeholder="Your email" aria-label="Email address" required><button type="submit" class="kg-pill kg-pill-yellow">Sign up</button></form>{% endif %}{% if socialLinks %}<div class="kg-social-row" aria-label="Social media">{% for link in socialLinks %}<a href="{{ link.href }}" class="kg-social-link" aria-label="{% if link.platform == "facebook" %}Visit our Facebook{% elif link.platform == "instagram" %}Visit our Instagram{% elif link.platform == "youtube" %}Visit our YouTube{% elif link.platform == "tiktok" %}Visit our TikTok{% elif link.platform == "email" %}Send us an email{% else %}Open social link{% endif %}">{% if link.platform == "facebook" %}<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path></svg>{% elif link.platform == "instagram" %}<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="20" rx="5" ry="5"></rect><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"></path><line x1="17.5" y1="6.5" x2="17.51" y2="6.5"></line></svg>{% elif link.platform == "youtube" %}<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22.54 6.42a2.78 2.78 0 0 0-1.94-2C18.88 4 12 4 12 4s-6.88 0-8.6.46a2.78 2.78 0 0 0-1.94 2A29 29 0 0 0 1 11.75a29 29 0 0 0 .46 5.33A2.78 2.78 0 0 0 3.4 19c1.72.46 8.6.46 8.6.46s6.88 0 8.6-.46a2.78 2.78 0 0 0 1.94-2 29 29 0 0 0 .46-5.25 29 29 0 0 0-.46-5.33z"></path><polygon points="9.75 15.02 15.5 11.75 9.75 8.48 9.75 15.02"></polygon></svg>{% elif link.platform == "tiktok" %}<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 12a4 4 0 1 0 4 4V4a5 5 0 0 0 5 5"></path></svg>{% elif link.platform == "email" %}<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>{% else %}<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle></svg>{% endif %}</a>{% endfor %}</div>{% endif %}</div></div>

48
blocks/footer.schema.json Normal file
View File

@ -0,0 +1,48 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Friendly Footer",
"description": "Mascot wave, optional signup, contact, social links, and crayon-rule divider.",
"type": "object",
"properties": {
"showSignup": {
"type": "string",
"title": "Show Newsletter Signup",
"description": "Render the small newsletter signup form.",
"x-editor": "select",
"enum": ["true", "false"],
"default": "true"
},
"mascotName": {
"type": "string",
"title": "Mascot Name",
"description": "Name of the mascot displayed in the footer wave (e.g. 'Pip').",
"default": "Pip",
"x-editor": "text"
},
"socialLinks": {
"type": "array",
"title": "Social Links",
"description": "Friendly icon links for social platforms.",
"default": [],
"x-editor": "array",
"items": {
"type": "object",
"properties": {
"platform": {
"type": "string",
"title": "Platform",
"description": "Which platform this link points to.",
"x-editor": "select",
"enum": ["facebook", "instagram", "youtube", "tiktok", "email"]
},
"href": {
"type": "string",
"title": "URL",
"description": "Link target.",
"x-editor": "link"
}
}
}
}
}
}

View File

@ -0,0 +1 @@
<section class="kg-section" data-block="kindergarten:gallery_of_art"><div class="kg-container">{% set showNames = true %}{% if showChildName == false %}{% set showNames = false %}{% endif %}{% if title %}<h2 class="kg-display kg-crayon-underline" style="font-size: 2rem; margin-bottom: 1.5rem;">{{ title }}</h2>{% endif %}{% if not items %}<div class="kg-empty" data-empty="true">No artworks yet — add one to fill the gallery wall.</div>{% else %}<div class="kg-gallery-grid">{% for art in items %}<figure class="kg-polaroid">{% if art.image %}<img src="{{ art.image|mediaURL }}" alt="{% if showNames and art.childName %}Artwork by {{ art.childName }}{% else %}Children&#39;s artwork{% endif %}">{% else %}<span class="kg-polaroid-image" role="img" aria-label="Placeholder for child artwork"></span>{% endif %}<figcaption class="kg-polaroid-caption">{% if showNames and art.childName %}<span>{{ art.childName }}</span>{% endif %}{% if showNames and art.childName and art.age|integer > 0 %}, {% endif %}{% if art.age|integer > 0 %}<span>age {{ art.age|integer }}</span>{% endif %}</figcaption></figure>{% endfor %}</div>{% endif %}</div></section>

View File

@ -0,0 +1,52 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Gallery of Art",
"description": "Polaroid-style grid for kid artwork with privacy toggle for child names.",
"type": "object",
"properties": {
"title": {
"type": "string",
"title": "Title",
"description": "Optional heading above the gallery.",
"x-editor": "text"
},
"showChildName": {
"type": "string",
"title": "Show Child Names",
"description": "Privacy toggle. When 'false', child names are omitted from the rendered gallery.",
"x-editor": "select",
"enum": ["true", "false"],
"default": "true"
},
"items": {
"type": "array",
"title": "Artworks",
"description": "Collection of artworks. Each entry has an image, child name, and age.",
"default": [],
"x-editor": "collection",
"items": {
"type": "object",
"properties": {
"image": {
"type": "string",
"title": "Image",
"description": "Artwork image (media reference).",
"x-editor": "media"
},
"childName": {
"type": "string",
"title": "Child Name",
"description": "Artist's first name. Hidden when 'Show Child Names' is off.",
"x-editor": "text"
},
"age": {
"type": "integer",
"title": "Age",
"description": "Artist's age (years).",
"x-editor": "number"
}
}
}
}
}
}

View File

@ -0,0 +1 @@
<section class="kg-section" data-block="kindergarten:mascot_hero"><div class="kg-container"><div class="kg-hero-panel"><div class="kg-confetti" aria-hidden="true"></div><div style="position: relative; z-index: 1; display: grid; gap: 2rem; grid-template-columns: 1fr; align-items: center;" class="kg-mascot-hero-grid"><div>{% if headline %}<h1 class="kg-display kg-crayon-underline" style="font-size: clamp(2.5rem, 6vw, 4rem); margin: 0;">{{ headline }}</h1>{% else %}<h1 class="kg-display" style="font-size: clamp(2.5rem, 6vw, 4rem); margin: 0;" data-empty="true">Welcome!</h1>{% endif %}{% if tagline %}<p class="kg-text" style="font-size: 1.25rem; margin-top: 1.5rem;">{{ tagline }}</p>{% endif %}{% if ctaLabel and ctaHref %}<div style="margin-top: 2rem;"><a href="{{ ctaHref }}" class="kg-pill">{{ ctaLabel }}</a></div>{% endif %}</div><div style="display: flex; justify-content: center; align-items: center;">{% if mascot == "blocks" %}<svg viewBox="0 0 200 200" width="200" height="200" role="img" aria-label="Alphabet blocks mascot"><rect x="20" y="60" width="60" height="60" rx="12" fill="hsl(var(--primary))"></rect><rect x="90" y="40" width="60" height="60" rx="12" fill="hsl(var(--secondary))"></rect><rect x="60" y="120" width="60" height="60" rx="12" fill="hsl(var(--accent))"></rect><text x="50" y="98" text-anchor="middle" font-family="var(--font-heading, Quicksand)" font-weight="700" font-size="32" fill="hsl(var(--primary-foreground))">A</text><text x="120" y="78" text-anchor="middle" font-family="var(--font-heading, Quicksand)" font-weight="700" font-size="32" fill="hsl(var(--secondary-foreground))">B</text><text x="90" y="158" text-anchor="middle" font-family="var(--font-heading, Quicksand)" font-weight="700" font-size="32" fill="hsl(var(--accent-foreground))">C</text></svg>{% elif mascot == "star" %}<svg viewBox="0 0 200 200" width="200" height="200" role="img" aria-label="Friendly star mascot"><polygon points="100,10 122,75 190,75 135,115 155,180 100,140 45,180 65,115 10,75 78,75" fill="hsl(var(--accent))" stroke="hsl(var(--foreground))" stroke-width="4" stroke-linejoin="round"></polygon><circle cx="80" cy="95" r="6" fill="hsl(var(--foreground))"></circle><circle cx="120" cy="95" r="6" fill="hsl(var(--foreground))"></circle><path d="M 80 120 Q 100 135 120 120" stroke="hsl(var(--foreground))" stroke-width="4" fill="none" stroke-linecap="round"></path></svg>{% elif mascot == "balloon" %}<svg viewBox="0 0 200 200" width="200" height="200" role="img" aria-label="Balloon mascot"><ellipse cx="100" cy="80" rx="50" ry="60" fill="hsl(var(--primary))" stroke="hsl(var(--foreground))" stroke-width="4"></ellipse><path d="M 100 140 L 95 150 L 100 145 L 105 150 Z" fill="hsl(var(--foreground))"></path><path d="M 100 150 Q 90 175 100 195" stroke="hsl(var(--foreground))" stroke-width="2" fill="none"></path><circle cx="85" cy="65" r="6" fill="hsl(var(--foreground))"></circle><circle cx="115" cy="65" r="6" fill="hsl(var(--foreground))"></circle><path d="M 85 90 Q 100 105 115 90" stroke="hsl(var(--foreground))" stroke-width="4" fill="none" stroke-linecap="round"></path></svg>{% else %}<svg viewBox="0 0 200 200" width="200" height="200" role="img" aria-label="Pip the mascot"><circle cx="100" cy="115" r="70" fill="hsl(var(--accent))" stroke="hsl(var(--foreground))" stroke-width="4"></circle><circle cx="78" cy="100" r="8" fill="hsl(var(--foreground))"></circle><circle cx="122" cy="100" r="8" fill="hsl(var(--foreground))"></circle><circle cx="78" cy="98" r="3" fill="hsl(var(--background))"></circle><circle cx="122" cy="98" r="3" fill="hsl(var(--background))"></circle><path d="M 75 130 Q 100 155 125 130" stroke="hsl(var(--foreground))" stroke-width="5" fill="none" stroke-linecap="round"></path><circle cx="60" cy="115" r="6" fill="hsl(var(--secondary))"></circle><circle cx="140" cy="115" r="6" fill="hsl(var(--secondary))"></circle><path d="M 50 60 L 60 35 L 80 50 Z" fill="hsl(var(--primary))"></path><path d="M 150 60 L 140 35 L 120 50 Z" fill="hsl(var(--primary))"></path></svg>{% endif %}</div></div></div></div></section>

View File

@ -0,0 +1,46 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Mascot Hero",
"description": "Big rounded panel with mascot SVG, headline, tagline, and a primary-colored CTA.",
"type": "object",
"properties": {
"mascot": {
"type": "string",
"title": "Mascot",
"description": "Mascot character to render in the hero panel.",
"x-editor": "select",
"enum": ["pip", "blocks", "star", "balloon"],
"default": "pip"
},
"headline": {
"type": "string",
"title": "Headline",
"description": "Big, hand-lettered headline.",
"x-editor": "text"
},
"tagline": {
"type": "string",
"title": "Tagline",
"description": "Friendly one-liner under the headline.",
"x-editor": "text"
},
"ctaLabel": {
"type": "string",
"title": "CTA Label",
"description": "Pill button label (e.g. 'Enrol today').",
"x-editor": "text"
},
"ctaHref": {
"type": "string",
"title": "CTA Link",
"description": "Where the pill button points.",
"x-editor": "link"
},
"bgColor": {
"type": "string",
"title": "Background Wash",
"description": "Optional HSL triple (e.g. '48 95% 58%') to tint the hero wash. Leave blank to use theme accent.",
"x-editor": "color"
}
}
}

View File

@ -0,0 +1 @@
<section class="kg-section" data-block="kindergarten:numbers_counter"><div class="kg-container">{% if not items %}<div class="kg-empty" data-empty="true">No numbers yet — add a few stats to celebrate.</div>{% else %}<div class="kg-numbers-grid">{% for item in items %}<div class="kg-numbers-item"><span class="kg-numeral" data-number="{{ item.number|integer }}">{{ item.number|integer }}</span>{% if item.label %}<span class="kg-numbers-label">{{ item.label }}</span>{% endif %}</div>{% endfor %}</div>{% endif %}</div></section>

View File

@ -0,0 +1,32 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Numbers Counter",
"description": "Big circular numeral badges for stats (e.g. '12 teachers').",
"type": "object",
"properties": {
"items": {
"type": "array",
"title": "Numbers",
"description": "Collection of (number, label) pairs.",
"default": [],
"x-editor": "collection",
"items": {
"type": "object",
"properties": {
"number": {
"type": "integer",
"title": "Number",
"description": "Numeral rendered as a circular badge.",
"x-editor": "number"
},
"label": {
"type": "string",
"title": "Label",
"description": "Plain-language description (e.g. 'teachers').",
"x-editor": "text"
}
}
}
}
}
}

View File

@ -1 +0,0 @@
<section class="kg-section" data-block="kindergarten:story_time"><div class="kg-container"><figure class="kg-storybook">{% if cover %}<img class="kg-storybook-illustration" src="{{ cover|mediaURL }}" alt="Cover of {{ bookTitle|default:'the story' }}">{% else %}<svg viewBox="0 0 200 150" width="100%" height="auto" class="kg-storybook-illustration" role="img" aria-label="Open storybook"><rect width="200" height="150" rx="18" fill="hsl(var(--muted))"></rect><path d="M100 28 C 78 20 46 20 30 28 L 30 122 C 46 114 78 114 100 122 C 122 114 154 114 170 122 L 170 28 C 154 20 122 20 100 28 Z" fill="hsl(var(--card))" stroke="hsl(var(--foreground))" stroke-width="4" stroke-linejoin="round"></path><line x1="100" y1="28" x2="100" y2="122" stroke="hsl(var(--foreground))" stroke-width="4"></line><circle cx="60" cy="60" r="12" fill="hsl(var(--accent))"></circle><rect x="46" y="82" width="30" height="6" rx="3" fill="hsl(var(--primary))"></rect><rect x="122" y="52" width="34" height="6" rx="3" fill="hsl(var(--secondary))"></rect><rect x="122" y="70" width="26" height="6" rx="3" fill="hsl(var(--primary))"></rect></svg>{% endif %}<div><span class="kg-sticker">Story time</span>{% if bookTitle %}<h2 class="kg-display mt-3" style="font-size: clamp(1.5rem, 3.5vw, 2.25rem);">{{ bookTitle }}</h2>{% endif %}{% if line %}<p class="kg-storybook-quote mt-3">"{{ line }}"</p>{% else %}<p class="kg-storybook-quote mt-3" data-empty="true">Add a line from the story to read together.</p>{% endif %}{% if author %}<figcaption class="kg-body mt-3 text-muted-foreground">by {{ author }}</figcaption>{% endif %}</div></figure></div></section>

View File

@ -1,32 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Story Time",
"description": "Picture-book read-aloud panel — cover, book title, and a line to read together.",
"type": "object",
"properties": {
"bookTitle": {
"type": "string",
"title": "Book title",
"description": "The name of the book or story.",
"x-editor": "text"
},
"author": {
"type": "string",
"title": "Author",
"description": "Who wrote it (optional).",
"x-editor": "text"
},
"line": {
"type": "string",
"title": "A line to read",
"description": "A short line from the story to read together.",
"x-editor": "textarea"
},
"cover": {
"type": "string",
"title": "Cover image",
"description": "The book cover or an illustration (optional — a friendly open book shows if empty).",
"x-editor": "media"
}
}
}

View File

@ -0,0 +1 @@
<section class="kg-section" data-block="kindergarten:storybook_quote"><div class="kg-container"><figure class="kg-storybook"><blockquote style="margin: 0;">{% if quote %}<p class="kg-storybook-quote">"{{ quote }}"</p>{% else %}<p class="kg-storybook-quote" data-empty="true">Add a quote to inspire your readers.</p>{% endif %}{% if author %}<figcaption class="kg-storybook-author">— {{ author }}</figcaption>{% endif %}</blockquote><div>{% if illustration %}<img class="kg-storybook-illustration" src="{{ illustration|mediaURL }}" alt="Illustration accompanying the quote">{% else %}<svg viewBox="0 0 200 150" width="100%" height="auto" class="kg-storybook-illustration" role="img" aria-label="Storybook illustration"><rect width="200" height="150" rx="24" fill="hsl(var(--muted))"></rect><circle cx="60" cy="60" r="20" fill="hsl(var(--accent))"></circle><rect x="100" y="40" width="60" height="40" rx="8" fill="hsl(var(--primary))"></rect><path d="M 30 110 Q 100 90 170 110 L 170 130 L 30 130 Z" fill="hsl(var(--secondary))"></path></svg>{% endif %}</div></figure></div></section>

View File

@ -0,0 +1,26 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Storybook Quote",
"description": "Picture-book page treatment for a quote with optional illustration.",
"type": "object",
"properties": {
"quote": {
"type": "string",
"title": "Quote",
"description": "The quoted text.",
"x-editor": "textarea"
},
"author": {
"type": "string",
"title": "Author",
"description": "Who said it.",
"x-editor": "text"
},
"illustration": {
"type": "string",
"title": "Illustration",
"description": "Optional picture-book illustration.",
"x-editor": "media"
}
}
}

View File

@ -1,18 +1 @@
[
{
"name": "Baloo 2",
"family": "Baloo 2",
"variants": [
{ "weight": "700", "style": "normal", "file": "fonts/baloo2-700.woff2" }
]
},
{
"name": "Quicksand",
"family": "Quicksand",
"variants": [
{ "weight": "400", "style": "normal", "file": "fonts/quicksand-400.woff2" },
{ "weight": "500", "style": "normal", "file": "fonts/quicksand-400.woff2" },
{ "weight": "700", "style": "normal", "file": "fonts/quicksand-700.woff2" }
]
}
]
[]

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,25 @@
[
{
"key": "kindergarten:site-master",
"title": "Kindergarten Site Master",
"page_templates": ["default", "full-width", "landing", "article", "blog-index", "contact"],
"key": "kindergarten:default-master",
"title": "Kindergarten Default Master",
"page_templates": ["default", "article"],
"blocks": [
{ "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 }
{ "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 }
]
},
{
"key": "kindergarten:auth-master",
"title": "Kindergarten Auth Master",
"page_templates": ["auth"],
"key": "kindergarten:landing-master",
"title": "Kindergarten Landing Master",
"page_templates": ["landing", "full-width"],
"blocks": [
{ "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 }
{ "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 }
]
}
]

View File

@ -2,8 +2,8 @@
name = "kindergarten"
display_name = "Kindergarten"
scope = "@themes"
version = "0.2.1"
description = "Primary-colored, hand-lettered theme for preschools and daycares — crayon borders, sticker badges, confetti, and a bouncing mascot showpiece."
version = "0.2.0"
description = "Primary-colored, hand-lettered theme for schools, daycares, toy shops, museums, and kids' publishers."
kind = "theme"
categories = ["templates"]
tags = ["kids", "education", "playful", "family", "school", "daycare", "preschool", "friendly", "colorful"]
tags = ["kids", "education", "playful", "family", "school", "museum", "friendly", "daycare", "publisher"]

View File

@ -1,252 +1,110 @@
[
{
"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": "The default — crayon blue, red, and yellow. Bright classroom by day, quiet-time navy by night.",
"description": "Full primary quartet — red, blue, yellow, green — in both light and dark.",
"theme": {
"mode": "both",
"lightColors": {
"background": "48 55% 98%",
"foreground": "222 47% 16%",
"foreground": "220 45% 15%",
"card": "0 0% 100%",
"cardForeground": "222 47% 16%",
"cardForeground": "220 45% 15%",
"popover": "0 0% 100%",
"popoverForeground": "222 47% 16%",
"primary": "222 80% 54%",
"popoverForeground": "220 45% 15%",
"primary": "220 78% 52%",
"primaryForeground": "0 0% 100%",
"secondary": "4 78% 57%",
"secondary": "4 78% 56%",
"secondaryForeground": "0 0% 100%",
"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%",
"muted": "48 35% 93%",
"mutedForeground": "220 22% 38%",
"accent": "145 65% 45%",
"accentForeground": "0 0% 100%",
"destructive": "0 84% 55%",
"destructiveForeground": "0 0% 100%",
"border": "210 30% 86%",
"input": "205 30% 90%",
"ring": "212 84% 52%"
"border": "220 25% 85%",
"input": "48 25% 88%",
"ring": "48 95% 55%"
},
"darkColors": {
"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 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%",
"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": "25 88% 55%",
"secondary": "4 80% 58%",
"secondaryForeground": "0 0% 100%",
"muted": "90 30% 92%",
"mutedForeground": "140 18% 38%",
"accent": "48 95% 55%",
"accentForeground": "140 40% 14%",
"destructive": "0 84% 55%",
"muted": "220 30% 16%",
"mutedForeground": "220 18% 68%",
"accent": "145 70% 50%",
"accentForeground": "220 40% 8%",
"destructive": "0 84% 60%",
"destructiveForeground": "0 0% 100%",
"border": "110 25% 84%",
"input": "90 25% 89%",
"ring": "145 62% 40%"
"border": "220 30% 22%",
"input": "220 30% 18%",
"ring": "48 95% 60%"
},
"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%"
}
"mode": "both"
}
}
]

View File

@ -1,228 +0,0 @@
{
"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": "<p>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.</p><p>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.</p>" } },
{
"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": "<p>Little tips that help a new child feel at home, from the drop-off wave to the goodbye routine.</p>", "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": "<p>A short list of what to bring, what to label, and what to leave at home.</p>", "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": "<p>Reading together does not need to be fancy. Here is how we keep it easy and fun.</p>", "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": "<p>The first week is a big change for everyone. Some children walk straight in. Others hold on tight at the door. Both are normal.</p><p>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.</p>" } }
]
},
{
"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": "<p>Pack a spare set of clothes, a hat, and a water bottle. Label everything with your child's name.</p><p>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.</p>" } }
]
},
{
"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": "<p>Read the same book as often as they ask. Repetition is how young children learn the shape of a story.</p><p>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.</p>" } }
]
},
{
"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 }
]
}

View File

@ -1,19 +0,0 @@
{
"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" }
}
]
}
]
}

View File

@ -1,14 +1,16 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
<html lang="en">
{{ head_html|safe }}
<body class="kg-page kg-dotted-paper min-h-screen flex flex-col antialiased">
<body class="kg-page kg-dotted-paper">
{{ admin_banner_html|safe }}
<header class="w-full sticky top-0 z-40" style="background-color: hsl(var(--background) / 0.9); backdrop-filter: blur(6px); border-bottom: 3px solid hsl(var(--primary) / 0.25);">{{ slots.header|safe }}</header>
<main class="flex-grow w-full max-w-5xl mx-auto px-4 py-12 grid gap-10 lg:grid-cols-[1fr_280px]">
<article class="kg-prose prose max-w-none min-w-0">{{ slots.main|safe }}</article>
{% if slots.aside %}<aside class="kg-crayon-shadow rounded-3xl border-[3px] border-primary/30 bg-card text-card-foreground p-6 text-sm h-max">{{ slots.aside|safe }}</aside>{% endif %}
</main>
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
<header class="kg-section" style="padding-block: 1.5rem;">{{ slots.header|safe }}</header>
<div class="kg-container" style="display: grid; grid-template-columns: 1fr; gap: 2rem; padding-block: 2rem;">
<main>
{% if slots.main %}<article class="kg-text">{{ slots.main|safe }}</article>{% else %}<div class="kg-empty">No content yet — start a story.</div>{% endif %}
</main>
{% if slots.aside %}<aside class="kg-card">{{ slots.aside|safe }}</aside>{% endif %}
</div>
<footer style="margin-top: auto;">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -1,13 +0,0 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
{{ head_html|safe }}
<body class="kg-page kg-dotted-paper min-h-screen flex flex-col antialiased">
{{ admin_banner_html|safe }}
<header class="w-full" style="border-bottom: 3px solid hsl(var(--primary) / 0.25);">{{ slots.header|safe }}</header>
<main class="flex-grow w-full flex items-center justify-center px-4 py-16">
<div class="w-full max-w-md">{% if slots.main %}{{ slots.main|safe }}{% else %}<p class="kg-body text-center text-muted-foreground">Sign-in form goes here.</p>{% endif %}</div>
</main>
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -1,11 +0,0 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
{{ head_html|safe }}
<body class="kg-page kg-dotted-paper min-h-screen flex flex-col antialiased">
{{ admin_banner_html|safe }}
<header class="w-full sticky top-0 z-40" style="background-color: hsl(var(--background) / 0.9); backdrop-filter: blur(6px); border-bottom: 3px solid hsl(var(--primary) / 0.25);">{{ slots.header|safe }}</header>
<main class="flex-grow w-full max-w-6xl mx-auto px-4 py-12">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="kg-empty">No stories yet — check back soon.</div>{% endif %}</main>
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -1,11 +0,0 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
{{ head_html|safe }}
<body class="kg-page kg-dotted-paper min-h-screen flex flex-col antialiased">
{{ admin_banner_html|safe }}
<header class="w-full sticky top-0 z-40" style="background-color: hsl(var(--background) / 0.9); backdrop-filter: blur(6px); border-bottom: 3px solid hsl(var(--primary) / 0.25);">{{ slots.header|safe }}</header>
<main class="flex-grow w-full max-w-5xl mx-auto px-4 py-12">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="kg-empty">Add a contact form to this page.</div>{% endif %}</main>
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -1,11 +1,13 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
<html lang="en">
{{ head_html|safe }}
<body class="kg-page kg-dotted-paper min-h-screen flex flex-col antialiased">
<body class="kg-page kg-dotted-paper">
{{ admin_banner_html|safe }}
<header class="w-full sticky top-0 z-40" style="background-color: hsl(var(--background) / 0.9); backdrop-filter: blur(6px); border-bottom: 3px solid hsl(var(--primary) / 0.25);">{{ slots.header|safe }}</header>
<main class="flex-grow w-full max-w-6xl mx-auto px-4 py-12">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="kg-empty">No content blocks assigned to this page yet.</div>{% endif %}</main>
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
<header class="kg-section" style="padding-block: 1.5rem;">{{ slots.header|safe }}</header>
<main class="kg-container" style="padding-block: 2rem; flex: 1;">
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="kg-empty">No content blocks assigned to this page.</div>{% endif %}
</main>
<footer style="margin-top: auto;">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -1,11 +1,13 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
<html lang="en">
{{ head_html|safe }}
<body class="kg-page kg-dotted-paper min-h-screen flex flex-col antialiased">
<body class="kg-page kg-dotted-paper">
{{ admin_banner_html|safe }}
<header class="w-full sticky top-0 z-40" style="background-color: hsl(var(--background) / 0.9); backdrop-filter: blur(6px); border-bottom: 3px solid hsl(var(--primary) / 0.25);">{{ slots.header|safe }}</header>
<main class="flex-grow w-full">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="max-w-6xl mx-auto px-4 py-12"><div class="kg-empty">No content blocks assigned to this page yet.</div></div>{% endif %}</main>
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
<header class="kg-section" style="padding-block: 1.5rem;">{{ slots.header|safe }}</header>
<main style="flex: 1;">
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="kg-container"><div class="kg-empty">No content blocks assigned to this page.</div></div>{% endif %}
</main>
<footer style="margin-top: auto;">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -1,11 +1,15 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
<html lang="en">
{{ head_html|safe }}
<body class="kg-page kg-dotted-paper min-h-screen flex flex-col antialiased">
<body class="kg-page kg-dotted-paper">
{{ admin_banner_html|safe }}
<header class="w-full sticky top-0 z-40" style="background-color: hsl(var(--background) / 0.9); backdrop-filter: blur(6px); border-bottom: 3px solid hsl(var(--primary) / 0.25);">{{ slots.header|safe }}</header>
<main class="flex-grow w-full">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="max-w-6xl mx-auto px-4 py-12"><div class="kg-empty">No content blocks assigned to this page yet.</div></div>{% endif %}</main>
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
<header class="kg-section" style="padding-block: 1.5rem;">{{ slots.header|safe }}</header>
<section class="kg-section" style="padding-top: 0;">{{ slots.hero|safe }}</section>
<main class="kg-container">
{% if slots.main %}{{ slots.main|safe }}{% endif %}
</main>
<section>{{ slots.cta|safe }}</section>
<footer style="margin-top: auto;">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -1,21 +0,0 @@
{# 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 %}
<div data-bn-announcement data-bn-annkey="{{ variant|default:'info' }}:{{ message|slugify }}" class="bn-announcement w-full {{ anncls }} {{ class }}">
<div class="max-w-7xl mx-auto px-10 py-2 relative flex items-center justify-center gap-2 text-sm text-center">
<p class="kg-mono font-medium">{{ message }}{% if linkText and linkUrl %} <a href="{{ linkUrl }}" class="underline underline-offset-2 hover:opacity-80">{{ linkText }}</a>{% endif %}</p>
{% if dismissable %}<button type="button" data-bn-ann-dismiss aria-label="Dismiss announcement" class="absolute right-3 top-1/2 -translate-y-1/2 inline-flex items-center justify-center p-1 rounded hover:bg-black/10 transition-colors"><svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg></button>{% endif %}
</div>
</div>
{% if dismissable %}<script>
(function(){
var bars=document.querySelectorAll('[data-bn-announcement]:not([data-bn-ann-wired])');
bars.forEach(function(bar){
bar.setAttribute('data-bn-ann-wired','1');
var key='bnann:'+bar.getAttribute('data-bn-annkey');
try{if(localStorage.getItem(key)==='1'){bar.style.display='none';return;}}catch(e){}
var btn=bar.querySelector('[data-bn-ann-dismiss]');
if(btn)btn.addEventListener('click',function(){bar.style.display='none';try{localStorage.setItem(key,'1');}catch(e){}});
});
})();
</script>{% endif %}

View File

@ -1,16 +0,0 @@
{# audio-embed kindergarten override: neon panel, mono meta. Facade script + data-bn-* attributes + {% img %} preserved byte-for-byte. #}
<figure class="bn-audio my-6 overflow-hidden rounded-2xl border border-primary/40 bg-card text-card-foreground kg-crayon-shadow{% if class %} {{ class }}{% endif %}" data-bn-audio data-audio-url="{{ url|default:"" }}" data-audio-media="{% if media %}{{ media|mediaURL }}{% endif %}"><div class="flex items-center gap-4 p-4">{% if artwork %}<div class="h-16 w-16 shrink-0 overflow-hidden rounded-2xl border border-primary/30 bg-muted">{% img artwork alt=title|default:"" class="h-full w-full object-cover" %}</div>{% endif %}<div class="min-w-0 flex-1">{% if title %}<p class="kg-display truncate font-medium text-foreground">{{ title }}</p>{% endif %}{% if artist %}<p class="kg-mono truncate text-sm text-muted-foreground">{{ artist }}</p>{% endif %}<div class="mt-2 flex items-center gap-3" data-bn-audio-slot><button type="button" data-bn-audio-play class="kg-mono inline-flex items-center gap-2 rounded-2xl bg-primary px-4 py-1.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring" aria-label="Play{% if title %}: {{ title }}{% endif %}">::lucide:play:sm:: Play</button>{% if download %}<a href="{% if media %}{{ media|mediaURL }}{% else %}{{ url }}{% endif %}" download class="kg-mono inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground">::lucide:download:sm:: Download</a>{% endif %}</div></div></div></figure><script>
(function(){
if(window.__bnAudioFacade)return;window.__bnAudioFacade=true;
document.addEventListener('click',function(e){
var btn=e.target.closest('[data-bn-audio-play]');if(!btn)return;
var fig=btn.closest('[data-bn-audio]');if(!fig)return;
e.preventDefault();
var src=fig.getAttribute('data-audio-media')||fig.getAttribute('data-audio-url')||'';
if(!src)return;
var slot=btn.closest('[data-bn-audio-slot]');
var a=document.createElement('audio');a.src=src;a.controls=true;a.autoplay=true;a.preload='none';a.style.cssText='width:100%;min-width:12rem;height:2.25rem;';
btn.remove();slot.insertBefore(a,slot.firstChild);
});
})();
</script>

View File

@ -1,77 +0,0 @@
{# 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 %}
<div class="auth-form-logged-in mx-auto max-w-md text-center py-6">
<p class="kg-mono text-sm text-muted-foreground">Session active as <strong class="text-primary">{{ context.publicUsername }}</strong></p>
<a href="/account" class="kg-mono mt-3 inline-flex items-center rounded-2xl bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90">My Account</a>
</div>
{% 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() %}<script>
window.CAP_CUSTOM_WASM_URL = "/assets/captcha/cap_wasm_bg.wasm";
window.CAP_PAKO_URL = "/assets/captcha/pako_inflate.min.js";
</script>
<cap-widget data-cap-api-endpoint="/api/captcha/" data-cap-worker-count="2"></cap-widget>
<script src="/assets/captcha/cap.min.js" defer></script>
<script>
(function(){
var s = document.currentScript;
var form = s && s.closest ? s.closest('form') : null;
if (!form) return;
form.addEventListener('htmx:afterRequest', function(){
var w = form.querySelector('cap-widget');
if (w && typeof w.reset === 'function') w.reset();
});
})();
</script>{% endmacro %}
{% macro social() %}{% if show_social != false and (context.site.google_oauth_client_id or context.site.facebook_app_id) %}<div class="auth-social my-4">
<div class="relative my-4"><hr class="border-border"/><span class="kg-mono absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-background px-3 text-xs text-muted-foreground">or</span></div>
{% if context.site.google_oauth_client_id %}<a href="/api/auth/google/start" class="kg-mono mb-2 flex w-full items-center justify-center gap-3 rounded-2xl border border-border px-4 py-2.5 transition-colors hover:bg-accent/10"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#google"></use></svg><span class="text-sm font-medium text-foreground/80">Continue with Google</span></a>{% endif %}
{% if context.site.facebook_app_id %}<a href="/api/auth/facebook/start" class="kg-mono flex w-full items-center justify-center gap-3 rounded-2xl border border-border px-4 py-2.5 transition-colors hover:bg-accent/10"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#facebook"></use></svg><span class="text-sm font-medium text-foreground/80">Continue with Facebook</span></a>{% endif %}
</div>{% endif %}{% endmacro %}
<div class="auth-form mx-auto max-w-md rounded-2xl border border-primary/40 bg-card p-6 text-card-foreground kg-crayon-shadow">
<div id="auth-message"></div>
<div class="mb-5 flex border-b border-border">
<button type="button" data-auth-tab="login" data-tabbase="{{ tabbase }}" class="{{ tabbase }} {{ loginActive }}">Login</button>
<button type="button" data-auth-tab="register" data-tabbase="{{ tabbase }}" class="{{ tabbase }} {{ registerActive }}">Register</button>
</div>
<div id="login-panel" class="{{ loginHidden }}">
<form hx-post="/api/auth/login" hx-target="#auth-message" hx-swap="innerHTML">
<div class="mb-3"><label class="kg-mono mb-1 block text-xs font-medium text-accent">Email</label><input type="email" name="email" required class="w-full rounded-2xl border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"/></div>
<div class="mb-4"><label class="kg-mono mb-1 block text-xs font-medium text-accent">Password</label><input type="password" name="password" required class="w-full rounded-2xl border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"/></div>
{% if captchaEnabled %}{{ captcha() }}{% endif %}
<button type="submit" class="kg-mono kg-pop w-full rounded-2xl bg-primary px-4 py-2.5 font-semibold text-primary-foreground hover:bg-primary/90" hx-disabled-elt="this">Login</button>
<p class="mt-3 text-center text-sm"><a href="/forgot-password" class="kg-mono text-primary hover:underline">Forgot password?</a></p>
</form>
{{ social() }}
</div>
<div id="register-panel" class="{{ registerHidden }}">
<form hx-post="/api/auth/register" hx-target="#auth-message" hx-swap="innerHTML">
<div class="mb-3"><label class="kg-mono mb-1 block text-xs font-medium text-accent">Username</label><input type="text" name="username" required pattern="[a-zA-Z0-9_-]{3,30}" class="w-full rounded-2xl border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"/></div>
<div class="mb-3"><label class="kg-mono mb-1 block text-xs font-medium text-accent">Email</label><input type="email" name="email" required class="w-full rounded-2xl border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"/></div>
<div class="mb-4"><label class="kg-mono mb-1 block text-xs font-medium text-accent">Password</label><input type="password" name="password" required minlength="8" class="w-full rounded-2xl border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"/></div>
{% if captchaEnabled %}{{ captcha() }}{% endif %}
<button type="submit" class="kg-mono kg-pop w-full rounded-2xl bg-primary px-4 py-2.5 font-semibold text-primary-foreground hover:bg-primary/90" hx-disabled-elt="this">Create Account</button>
</form>
{{ social() }}
</div>
</div>
<script>
(function(){
var f=document.currentScript.closest('.auth-form')||document.querySelector('.auth-form');
if(!f)return;
var tabs=f.querySelectorAll('[data-auth-tab]');
tabs.forEach(function(btn){btn.addEventListener('click',function(){
var t=btn.getAttribute('data-auth-tab');
var lp=f.querySelector('#login-panel'),rp=f.querySelector('#register-panel');
if(lp)lp.classList.toggle('hidden',t!=='login');
if(rp)rp.classList.toggle('hidden',t!=='register');
tabs.forEach(function(b){var on=b===btn;b.className=b.getAttribute('data-tabbase')+(on?' border-primary text-primary':' border-transparent text-muted-foreground');});
});});
})();
</script>
{% endif %}

View File

@ -1,36 +0,0 @@
{# 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 %}
<div class="auth-status flex items-center gap-2">
<a href="/login" class="kg-mono rounded-2xl px-3 py-1.5 text-sm font-medium opacity-80 transition-colors hover:bg-primary/10 hover:opacity-100">Login</a>
<a href="/login?tab=register" class="kg-mono rounded-2xl bg-accent px-3 py-1.5 text-sm font-medium text-accent-foreground hover:bg-accent/90">Sign Up</a>
</div>
{% else %}
{% if not context.publicEmailVerified %}<div class="fixed top-16 inset-x-0 z-40 border-y border-destructive/50 bg-destructive/15 px-4 py-2 text-center text-sm text-destructive">
<span class="kg-mono">Verify your email address.</span> <button hx-post="/api/auth/resend-verification" hx-swap="outerHTML" class="underline font-medium">Resend verification email</button>
</div>{% endif %}
<div class="auth-status relative">
<button type="button" onclick="this.nextElementSibling.classList.toggle('hidden')" class="flex items-center gap-2 rounded-2xl px-3 py-1.5 text-sm font-medium opacity-90 transition-colors hover:bg-primary/10 hover:opacity-100">
<span class="flex h-7 w-7 items-center justify-center rounded-2xl bg-accent text-accent-foreground"><svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#user"></use></svg></span>
<span class="kg-mono">{{ context.publicDisplayName|default:context.publicUsername }}</span>
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div id="auth-dropdown-menu" class="hidden absolute right-0 top-full mt-1 w-48 rounded-2xl border border-primary/40 bg-card py-1 text-card-foreground kg-crayon-shadow z-50">
<a href="/account" class="kg-mono block px-4 py-2 text-sm text-card-foreground hover:bg-primary/10">My Profile</a>
<a href="/account/reviews" class="kg-mono block px-4 py-2 text-sm text-card-foreground hover:bg-primary/10">My Reviews</a>
<hr class="my-1 border-border"/>
<button hx-post="/api/auth/logout" hx-swap="none" class="kg-mono block w-full px-4 py-2 text-left text-sm text-destructive hover:bg-primary/10">Logout</button>
</div>
<script>
(function(){
document.addEventListener('click', function(e) {
var menu = document.getElementById('auth-dropdown-menu');
var btn = document.querySelector('.auth-status > button');
if (!menu || !btn) return;
if (!btn.contains(e.target) && !menu.contains(e.target)) {
menu.classList.add('hidden');
}
});
})();
</script>
</div>
{% endif %}

View File

@ -1,21 +0,0 @@
{# author-bio-hero (kindergarten override) — author-page header from the `authors` provider. #}
{% if author %}
<section class="relative overflow-hidden border-b border-primary/30 bg-background py-12 text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
<div class="relative z-10 mx-auto flex max-w-3xl flex-col items-center px-4 text-center">
{% if showAvatar and author.avatar_url %}<div class="mb-5 h-24 w-24 overflow-hidden rounded-full bg-muted ring-2 ring-primary kg-crayon-shadow">{% img author.avatar_url alt=author.display_name class="h-full w-full object-cover" %}</div>{% endif %}
<h1 class="kg-display text-3xl font-bold tracking-tight md:text-4xl">{{ author.display_name }}</h1>
{% if author.job_title or author.company %}<p class="kg-mono mt-1 text-sm text-accent">{{ author.job_title }}{% if author.job_title and author.company %} · {% endif %}{{ author.company }}</p>{% endif %}
{% if author.bio %}<p class="kg-body mt-4 max-w-2xl leading-relaxed text-muted-foreground">{{ author.bio }}</p>{% endif %}
<div class="mt-5 flex flex-wrap items-center justify-center gap-4 kg-mono text-sm text-accent">
{% if showPostCount %}<span>{{ author.post_count|default:0 }} post{% if author.post_count != 1 %}s{% endif %}</span>{% endif %}
{% if author.location %}<span>{{ author.location }}</span>{% endif %}
</div>
{% if showSocial %}<div class="mt-5 flex items-center justify-center gap-3">
{% if author.website_url %}<a href="{{ author.website_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="Website"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/lucide.svg#globe"></use></svg></a>{% endif %}
{% if author.twitter_handle %}<a href="https://twitter.com/{{ author.twitter_handle|cut:"@" }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="Twitter"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#x"></use></svg></a>{% endif %}
{% if author.linkedin_url %}<a href="{{ author.linkedin_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="LinkedIn"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#linkedin"></use></svg></a>{% endif %}
{% if author.github_url %}<a href="{{ author.github_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="GitHub"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#github"></use></svg></a>{% endif %}
</div>{% endif %}
</div>
</section>
{% endif %}

View File

@ -1,13 +0,0 @@
{# author-bio (kindergarten override) — byline card sourced from the `post` provider's author_* fields. #}
{% if post.author_name %}
<aside class="rounded-2xl border border-dashed border-primary/40 bg-card p-6 text-card-foreground kg-crayon-shadow{% if class %} {{ class }}{% endif %}">
{% if heading %}<p class="kg-eyebrow kg-mono mb-4 text-xs font-semibold text-accent">{{ heading }}</p>{% endif %}
<div class="flex items-start gap-4">
{% if showAvatar and post.author_avatar %}<div class="h-14 w-14 flex-shrink-0 overflow-hidden rounded-full border border-primary/40 bg-muted">{% img post.author_avatar alt=post.author_name class="h-full w-full object-cover" %}</div>{% endif %}
<div class="min-w-0">
<h3 class="kg-display text-base font-semibold">{% if post.author_slug %}<a href="/author/{{ post.author_slug }}" class="transition-colors hover:text-primary">{{ post.author_name }}</a>{% else %}{{ post.author_name }}{% endif %}</h3>
{% if post.author_bio %}<p class="kg-body mt-1 text-sm text-muted-foreground">{{ post.author_bio }}</p>{% endif %}
</div>
</div>
</aside>
{% endif %}

View File

@ -1,23 +0,0 @@
{# blog-hero (kindergarten override) — blog-index header + featured posts from the `posts` provider. #}
<section class="relative overflow-hidden bg-background py-12 text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
<div class="relative z-10 mx-auto max-w-6xl px-4">
<div class="max-w-2xl">
{% if title %}<h1 class="kg-display text-4xl font-bold tracking-tight md:text-5xl">{{ title }}</h1>{% endif %}
{% if subtitle %}<p class="kg-body mt-3 text-lg text-muted-foreground">{{ subtitle }}</p>{% endif %}
</div>
{% if posts %}
<div class="mt-10 grid grid-cols-1 gap-6 md:grid-cols-3">
{% for post in posts %}
<article class="group flex flex-col rounded-2xl border border-dashed border-primary/40 bg-card p-4 text-card-foreground kg-crayon-shadow{% if forloop.First %} md:col-span-3 md:flex-row md:items-stretch md:gap-6{% endif %}">
{% if post.featured_image_url %}<a href="{{ post.url }}" class="mb-3 block overflow-hidden rounded-2xl bg-muted{% if forloop.First %} md:mb-0 md:w-1/2{% endif %}">{% img post.featured_image_url alt=post.title class="aspect-video h-full w-full object-cover transition-transform duration-300 group-hover:scale-105" %}</a>{% endif %}
<div class="flex flex-col justify-center{% if forloop.First %} md:w-1/2{% endif %}">
<h2 class="kg-display {% if forloop.First %}text-2xl{% else %}text-lg{% endif %} font-semibold leading-snug"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h2>
{% if showExcerpt and post.excerpt %}<p class="kg-body mt-2 text-sm text-muted-foreground">{{ post.excerpt }}</p>{% endif %}
<div class="mt-3 flex flex-wrap items-center gap-x-3 kg-mono text-xs text-accent">{% if post.author_name %}<span class="font-medium text-foreground">{{ post.author_name }}</span>{% endif %}{% if post.published_at %}<span>{{ post.published_at|date:"M j, Y" }}</span>{% endif %}</div>
</div>
</article>
{% endfor %}
</div>
{% endif %}
</div>
</section>

View File

@ -1,15 +0,0 @@
{# 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" %}
<nav aria-label="Breadcrumb" class="py-3{% if class %} {{ class }}{% endif %}">
<ol class="flex flex-wrap items-center gap-1 kg-mono text-sm">
{% if showHome %}<li class="flex items-center"><a href="/" class="text-accent transition-colors hover:text-primary">{{ homeLabel|default:"Home" }}</a></li>{% endif %}
{% for item in breadcrumbs %}
<li class="flex items-center">
{% if showHome or not forloop.First %}{% if arrows %}<svg class="mx-2 h-4 w-4 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>{% else %}<span class="mx-2 text-primary">{{ separator|default:"/" }}</span>{% endif %}{% endif %}
{% if item.is_current %}{% if showCurrent %}<span class="font-medium text-foreground" aria-current="page">{{ item.label }}</span>{% endif %}{% else %}<a href="{{ item.url }}" class="text-accent transition-colors hover:text-primary">{{ item.label }}</a>{% endif %}
</li>
{% endfor %}
</ol>
</nav>

View File

@ -1,2 +1 @@
{# 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" %}<div class="bn-button flex {% if align == "center" %}justify-center{% elif align == "right" %}justify-end{% else %}justify-start{% endif %}{% if class %} {{ class }}{% endif %}"><span class="kg-btn kg-display kg-pop inline-flex [&_a]:rounded-full [&_button]:rounded-full">{% 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 %}</span></div>{% endwith %}
{% if text %}{% set lbl = text %}{% else %}{% set lbl = label %}{% endif %}{% if lbl %}<a href="{{ href|default:'#' }}" class="kg-pill {% if variant == "red" %}kg-pill-red{% elif variant == "blue" %}kg-pill-blue{% elif variant == "green" %}kg-pill-green{% else %}kg-pill-yellow{% endif %}">{{ lbl }}</a>{% else %}<span class="kg-empty" data-empty="true">Add label and link to render this button.</span>{% endif %}

View File

@ -1,2 +1 @@
{# 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" %}<article class="bn-card kg-crayon-shadow overflow-hidden rounded-3xl border-[3px] border-primary/40 bg-card text-card-foreground {% if layout == "horizontal" %}flex flex-col sm:flex-row{% endif %}{% if class %} {{ class }}{% endif %}">{% if link %}<a href="{{ link }}" class="contents">{% endif %}{% if media %}<div class="overflow-hidden bg-muted {% if layout == "horizontal" %}sm:w-2/5 sm:shrink-0{% endif %}">{% 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 %}</div>{% endif %}<div class="flex flex-col gap-2 p-6 {% if layout == "horizontal" %}justify-center{% endif %}">{% if title %}<h3 class="kg-display text-lg font-bold tracking-tight text-foreground">{{ title }}</h3>{% endif %}{% if text %}<div class="kg-body text-sm leading-relaxed text-muted-foreground">{{ text|safe }}</div>{% endif %}{% if linkLabel and link %}<span class="kg-display mt-2 inline-flex items-center gap-1 text-sm font-bold text-primary">{{ linkLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></span>{% endif %}</div>{% if link %}</a>{% endif %}</article>{% endwith %}
<div class="kg-card {{ class }}">{% if title %}<h3 class="kg-display" style="margin-top: 0; margin-bottom: 1rem; font-size: 1.5rem;">{{ title }}</h3>{% endif %}{% if body %}<div class="kg-text">{{ body|safe }}</div>{% endif %}{% if not title and not body %}<div data-empty="true" class="kg-empty">Add a title or body to fill this card.</div>{% endif %}</div>

View File

@ -1,18 +0,0 @@
{# category-list (kindergarten override) — from the `categories` provider. list or pills. #}
{% set pills = style == "pills" %}
<section class="bg-background text-foreground{% if class %} {{ class }}{% endif %}">
{% if heading %}<h2 class="kg-eyebrow kg-mono mb-4 text-xs font-semibold text-accent">{{ heading }}</h2>{% endif %}
{% if pills %}
<div class="flex flex-wrap gap-2">
{% for cat in categories %}
<a href="{{ cat.url }}" class="inline-flex items-center gap-1.5 border border-primary/50 bg-card px-3 py-1 kg-mono text-sm text-card-foreground kg-crayon-shadow transition-colors hover:text-primary">{{ cat.name }}{% if showCount %}<span class="text-xs text-muted-foreground">{{ cat.post_count }}</span>{% endif %}</a>
{% endfor %}
</div>
{% else %}
<ul class="space-y-1">
{% for cat in categories %}
<li><a href="{{ cat.url }}" class="flex items-center justify-between rounded-2xl px-3 py-2 kg-mono text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-primary"><span>{{ cat.name }}</span>{% if showCount %}<span class="rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground">{{ cat.post_count }}</span>{% endif %}</a></li>
{% endfor %}
</ul>
{% endif %}
</section>

View File

@ -1,18 +0,0 @@
{# contact-card kindergarten override: neon channel panels. All field reads + control flow preserved. #}
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-4xl px-4">
{% if heading %}<h2 class="kg-display text-3xl font-semibold tracking-tight md:text-4xl">{{ heading }}</h2>{% endif %}
{% if description %}<p class="mt-3 max-w-2xl text-lg text-muted-foreground">{{ description }}</p>{% endif %}
<div class="mt-10 grid grid-cols-1 gap-4 {% if columns == 3 %}sm:grid-cols-2 lg:grid-cols-3{% elif columns == 1 %}{% else %}sm:grid-cols-2{% endif %}">
{% for channel in channels %}
<div class="flex items-start gap-4 rounded-2xl border border-primary/40 bg-card p-5 text-card-foreground kg-crayon-shadow">
{% if channel.icon %}<span class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl bg-primary/10 text-primary"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/{{ channel.icon|split:":"|first }}.svg#{{ channel.icon|split:":"|last }}"></use></svg></span>{% endif %}
<div class="min-w-0">
{% if channel.label %}<div class="kg-mono text-sm font-medium text-accent">{{ channel.label }}</div>{% endif %}
{% if channel.value %}<div class="mt-0.5 break-words font-medium">{% if channel.link %}<a href="{{ channel.link }}" class="transition-colors hover:text-primary">{{ channel.value }}</a>{% else %}{{ channel.value }}{% endif %}</div>{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
</section>

View File

@ -1,40 +0,0 @@
{# 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 %}
<section id="contact-form-{{ bid }}" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-2xl px-4">
{% if form.submitted %}
<div class="rounded-2xl border border-primary/50 bg-card p-8 text-center text-card-foreground kg-crayon-shadow">
<div class="mx-auto mb-4 inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10 text-primary"><svg class="h-6 w-6" aria-hidden="true"><use href="/icons/lucide.svg#check"></use></svg></div>
<p class="kg-display text-lg font-medium">{{ form.message|default:successMessage|default:"Thanks — your message has been sent." }}</p>
</div>
{% else %}
{% if heading %}<h2 class="kg-display text-3xl font-semibold tracking-tight md:text-4xl">{{ heading }}</h2>{% endif %}
{% if description %}<p class="mt-3 text-lg text-muted-foreground">{{ description }}</p>{% endif %}
{% if form.errors._form %}<div class="mt-6 rounded-2xl border border-destructive/50 bg-destructive/10 px-4 py-3 text-sm text-destructive">{{ form.errors._form }}</div>{% 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 %}
<div class="{% if field.width == "half" %}sm:col-span-1{% else %}sm:col-span-2{% endif %}">
{% if field.label %}<label for="cf-{{ bid }}-{{ field.name }}" class="kg-mono mb-1.5 block text-sm font-medium text-accent">{{ field.label }}{% if field.required %} <span class="text-destructive">*</span>{% endif %}</label>{% endif %}
{% if field.type == "textarea" %}
<textarea id="cf-{{ bid }}-{{ field.name }}" name="{{ field.name }}" rows="5"{% if field.required %} required{% endif %}{% if field.placeholder %} placeholder="{{ field.placeholder }}"{% endif %} class="w-full rounded-2xl border border-primary/40 bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"></textarea>
{% elif field.type == "select" %}
<select id="cf-{{ bid }}-{{ field.name }}" name="{{ field.name }}"{% if field.required %} required{% endif %} class="w-full rounded-2xl border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring">
{% if field.placeholder %}<option value="">{{ field.placeholder }}</option>{% endif %}
{% for opt in field.options %}<option value="{{ opt.value }}">{{ opt.label|default:opt.value }}</option>{% endfor %}
</select>
{% else %}
<input id="cf-{{ bid }}-{{ field.name }}" name="{{ field.name }}" type="{{ field.type|default:"text" }}"{% if field.required %} required{% endif %}{% if field.placeholder %} placeholder="{{ field.placeholder }}"{% endif %} class="w-full rounded-2xl border border-primary/40 bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring">
{% endif %}
</div>
{% endfor %}
<div class="hidden" aria-hidden="true"><label>Leave this field empty<input type="text" name="website" tabindex="-1" autocomplete="off"></label></div>
<div class="sm:col-span-2">
<button type="submit" class="kg-mono kg-pop inline-flex items-center justify-center rounded-2xl bg-primary px-6 py-2.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2">{{ submitLabel|default:"Send message" }}</button>
</div>
{% endform %}
{% endwith %}
{% endif %}
</div>
</section>
{% endwith %}

View File

@ -1,49 +0,0 @@
{# countdown kindergarten override: neon HUD digit boxes, heading. Timer script + data attributes preserved byte-for-byte. #}
<section class="relative overflow-hidden py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="relative z-10 mx-auto max-w-4xl px-4 text-center">
{% if heading %}<h2 class="kg-display text-3xl font-semibold tracking-tight md:text-4xl">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
<div class="mt-10" data-countdown>
<div class="flex items-start justify-center gap-3 sm:gap-6" data-countdown-timer data-target="{{ target }}" role="timer" aria-label="Time remaining">
<div class="flex flex-col items-center {% if variant == "boxes" %}min-w-[4.5rem] rounded-2xl border border-primary/50 bg-card px-3 py-4 text-card-foreground kg-crayon-shadow{% endif %}"><span class="kg-mono text-4xl font-bold tabular-nums sm:text-5xl text-primary" data-unit="days">--</span><span class="kg-mono mt-1 text-xs font-medium text-muted-foreground">{{ labelDays|default:"Days" }}</span></div>
<div class="flex flex-col items-center {% if variant == "boxes" %}min-w-[4.5rem] rounded-2xl border border-primary/50 bg-card px-3 py-4 text-card-foreground kg-crayon-shadow{% endif %}"><span class="kg-mono text-4xl font-bold tabular-nums sm:text-5xl text-primary" data-unit="hours">--</span><span class="kg-mono mt-1 text-xs font-medium text-muted-foreground">{{ labelHours|default:"Hours" }}</span></div>
<div class="flex flex-col items-center {% if variant == "boxes" %}min-w-[4.5rem] rounded-2xl border border-primary/50 bg-card px-3 py-4 text-card-foreground kg-crayon-shadow{% endif %}"><span class="kg-mono text-4xl font-bold tabular-nums sm:text-5xl text-primary" data-unit="minutes">--</span><span class="kg-mono mt-1 text-xs font-medium text-muted-foreground">{{ labelMinutes|default:"Minutes" }}</span></div>
<div class="flex flex-col items-center {% if variant == "boxes" %}min-w-[4.5rem] rounded-2xl border border-primary/50 bg-card px-3 py-4 text-card-foreground kg-crayon-shadow{% endif %}"><span class="kg-mono text-4xl font-bold tabular-nums sm:text-5xl text-primary" data-unit="seconds">--</span><span class="kg-mono mt-1 text-xs font-medium text-muted-foreground">{{ labelSeconds|default:"Seconds" }}</span></div>
</div>
<div class="mt-8 hidden" data-countdown-expired>
<p class="kg-display text-xl font-medium">{{ expiredMessage|default:"This has started." }}</p>
{% if expiredUrl and expiredLabel %}<a href="{{ expiredUrl }}" class="kg-mono kg-pop mt-4 inline-flex items-center gap-2 rounded-2xl bg-primary px-5 py-3 font-semibold text-primary-foreground transition-opacity hover:opacity-90">{{ expiredLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
</div>
</div>
<noscript><p class="mt-6 text-muted-foreground">Counting down to <time datetime="{{ target }}">{{ target }}</time>.</p></noscript>
</div>
</section>
<script>
(function(){
var timers = document.querySelectorAll('[data-countdown-timer]');
timers.forEach(function(el){
var target = new Date(el.getAttribute('data-target')).getTime();
if (isNaN(target)) return;
var wrap = el.closest('[data-countdown]');
var expired = wrap ? wrap.querySelector('[data-countdown-expired]') : null;
function pad(n){ return (n < 10 ? '0' : '') + n; }
function set(unit, value){ var node = el.querySelector('[data-unit="' + unit + '"]'); if (node) node.textContent = value; }
var interval;
function tick(){
var diff = target - Date.now();
if (diff <= 0){
el.style.display = 'none';
if (expired) expired.classList.remove('hidden');
if (interval) clearInterval(interval);
return;
}
set('days', Math.floor(diff / 86400000));
set('hours', pad(Math.floor(diff % 86400000 / 3600000)));
set('minutes', pad(Math.floor(diff % 3600000 / 60000)));
set('seconds', pad(Math.floor(diff % 60000 / 1000)));
}
tick();
interval = setInterval(tick, 1000);
});
})();
</script>

View File

@ -1,15 +0,0 @@
{# cta kindergarten override: scanline overlay, heading, kg-pop CTAs. All field reads preserved. #}
<section class="kg-sect overflow-hidden bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="relative z-10 mx-auto max-w-6xl px-4">
<div class="{% if background == "band" %}rounded-2xl bg-primary px-6 py-14 text-center text-primary-foreground md:px-12{% elif background == "card" %}rounded-2xl border border-primary/50 bg-card px-6 py-14 text-center text-card-foreground kg-crayon-shadow md:px-12{% else %}flex flex-col items-center gap-8 rounded-2xl border border-primary/30 bg-muted px-6 py-12 text-foreground md:flex-row md:justify-between md:px-12 md:text-left{% endif %}">
<div class="{% if background != "split" %}mx-auto max-w-2xl{% endif %}">
{% if heading %}<h2 class="kg-display text-3xl font-semibold tracking-tight md:text-4xl">{{ heading }}</h2>{% endif %}
{% if text %}<p class="mt-3 text-lg {% if background == "band" %}text-primary-foreground/80{% else %}text-muted-foreground{% endif %}">{{ text }}</p>{% endif %}
</div>
<div class="flex flex-wrap items-center justify-center gap-3 {% if background != "split" %}mt-8{% else %}shrink-0{% endif %}">
{% if primaryUrl and primaryLabel %}<a href="{{ primaryUrl }}" class="kg-mono kg-pop inline-flex items-center gap-2 rounded-2xl px-6 py-3 font-semibold transition-opacity hover:opacity-90 {% if background == "band" %}bg-background text-foreground{% else %}bg-primary text-primary-foreground{% endif %}">{{ primaryLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
{% if secondaryUrl and secondaryLabel %}<a href="{{ secondaryUrl }}" class="kg-mono inline-flex items-center rounded-2xl border px-6 py-3 font-semibold transition-colors {% if background == "band" %}border-primary-foreground/40 text-primary-foreground hover:bg-primary-foreground/10{% else %}border-accent/60 text-accent hover:bg-accent/10{% endif %}">{{ secondaryLabel }}</a>{% endif %}
</div>
</div>
</div>
</section>

View File

@ -1,2 +0,0 @@
{# kindergarten divider — neon gradient rule / primary-tinted variants #}
{% with variant=variant|default:"line" weight=weight|default:"thin" width=width|default:"full" %}{% if variant == "ornament" %}<div class="bn-divider mx-auto my-8 flex items-center gap-4 {% if width == "narrow" %}max-w-[200px]{% elif width == "wide" %}max-w-3xl{% else %}w-full{% endif %}{% if class %} {{ class }}{% endif %}" role="separator"><span class="h-px flex-1 bg-gradient-to-r from-transparent via-primary to-transparent"></span><span class="shrink-0 text-primary">::{{ icon|default:"lucide:asterisk" }}:md text-primary::</span><span class="h-px flex-1 bg-gradient-to-l from-transparent via-primary to-transparent"></span></div>{% elif variant == "spacer-rule" %}<div class="bn-divider mx-auto py-10 {% if width == "narrow" %}max-w-[200px]{% elif width == "wide" %}max-w-3xl{% else %}w-full{% endif %}{% if class %} {{ class }}{% endif %}" role="separator"><hr class="mx-auto w-16 border-0 border-t {% if weight == "thick" %}border-t-4{% elif weight == "medium" %}border-t-2{% endif %} border-primary/60"></div>{% else %}<hr class="bn-divider mx-auto my-8 border-0 border-t {% if weight == "thick" %}border-t-4{% elif weight == "medium" %}border-t-2{% endif %} border-primary/60 {% if width == "narrow" %}max-w-[200px]{% elif width == "wide" %}max-w-3xl{% else %}w-full{% endif %}{% if class %} {{ class }}{% endif %}" role="separator">{% endif %}{% endwith %}

View File

@ -1,41 +0,0 @@
{# event-list kindergarten override: neon cards, mono datetime. All field reads + control flow preserved. #}
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-6xl px-4">
{% if heading %}<h2 class="kg-display text-3xl font-semibold tracking-tight md:text-4xl">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mt-3 max-w-2xl text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
{% if layout == "cards" %}
<div class="mt-10 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{% for event in events %}{% if not (hidePast and event.past) %}
<article class="flex flex-col overflow-hidden rounded-2xl border border-primary/40 bg-card text-card-foreground kg-crayon-shadow{% if event.past %} opacity-60{% endif %}">
{% if event.media %}<div class="aspect-video overflow-hidden bg-muted">{% img event.media alt=event.title class="h-full w-full object-cover" %}</div>{% endif %}
<div class="flex flex-1 flex-col gap-2 p-5">
<div class="kg-mono text-sm font-medium text-primary"><time{% if event.datetime %} datetime="{{ event.datetime }}"{% endif %}>{{ event.start }}{% if event.end %} &ndash; {{ event.end }}{% endif %}</time>{% if event.timezone %} <span class="text-muted-foreground">{{ event.timezone }}</span>{% endif %}</div>
<h3 class="kg-display text-lg font-semibold">{{ event.title }}</h3>
{% if event.venue %}<p class="text-sm text-muted-foreground">{{ event.venue }}</p>{% endif %}
{% if event.description %}<p class="text-sm text-muted-foreground">{{ event.description }}</p>{% endif %}
{% if event.link %}<a href="{{ event.link }}" class="kg-mono mt-auto inline-flex items-center gap-1 pt-2 text-sm font-medium text-primary hover:underline">{{ event.linkLabel|default:"Details" }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
</div>
</article>
{% endif %}{% endfor %}
</div>
{% else %}
<ul class="mt-10 divide-y divide-primary/30 border-y border-primary/30">
{% for event in events %}{% if not (hidePast and event.past) %}
<li class="flex flex-col gap-3 py-5 sm:flex-row sm:items-center sm:gap-6{% if event.past %} opacity-60{% endif %}">
<div class="shrink-0 sm:w-52">
<div class="kg-mono text-sm font-semibold text-primary"><time{% if event.datetime %} datetime="{{ event.datetime }}"{% endif %}>{{ event.start }}</time></div>
{% if event.end %}<div class="kg-mono text-xs text-muted-foreground">to {{ event.end }}</div>{% endif %}
{% if event.timezone %}<div class="kg-mono text-xs text-muted-foreground">{{ event.timezone }}</div>{% endif %}
</div>
<div class="min-w-0 flex-1">
<h3 class="kg-display text-base font-semibold">{{ event.title }}</h3>
{% if event.venue %}<p class="text-sm text-muted-foreground">{{ event.venue }}</p>{% endif %}
{% if event.description %}<p class="mt-1 text-sm text-muted-foreground">{{ event.description }}</p>{% endif %}
</div>
{% if event.link %}<a href="{{ event.link }}" class="kg-mono inline-flex shrink-0 items-center rounded-2xl border border-primary/50 px-3 py-1.5 text-sm font-medium text-primary hover:bg-primary/10">{{ event.linkLabel|default:"Details" }}</a>{% endif %}
</li>
{% endif %}{% endfor %}
</ul>
{% endif %}
</div>
</section>

View File

@ -1,18 +0,0 @@
{# faq kindergarten override: heading, neon-tinted dividers, display questions. Chevron svg, {% for %}, and answer prose preserved. #}
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-3xl px-4">
{% if heading %}<h2 class="kg-display text-center text-3xl font-semibold tracking-tight md:text-4xl">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
<div class="mt-10 divide-y divide-primary/20 border-y border-primary/30">
{% for item in items %}
<details class="group py-2"{% if variant == "open-list" %} open{% endif %}>
<summary class="flex cursor-pointer list-none items-center justify-between gap-4 py-3 text-lg font-medium marker:content-none [&::-webkit-details-marker]:hidden">
<span class="kg-display">{{ item.question }}</span>
<svg class="h-5 w-5 shrink-0 text-primary transition-transform duration-200 group-open:rotate-180" aria-hidden="true"><use href="/icons/lucide.svg#chevron-down"></use></svg>
</summary>
<div class="pb-4 text-muted-foreground [&_a]:text-primary [&_a]:underline">{{ item.answer|safe }}</div>
</details>
{% endfor %}
</div>
</div>
</section>

View File

@ -1,18 +0,0 @@
{# feature_grid kindergarten override: scanline hero overlay, mono eyebrow, heading, neon HUD cards. All field reads + control flow preserved. #}
<section class="kg-sect overflow-hidden bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="relative z-10 mx-auto max-w-6xl px-4">
{% if eyebrow %}<p class="kg-mono text-center text-xs font-semibold text-accent">{{ eyebrow }}</p>{% endif %}
{% if heading %}<h2 class="kg-display mt-2 text-center text-3xl font-semibold tracking-tight md:text-4xl">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
<div class="mt-10 kg-cols {% if columns == 2 %}kg-cols-2{% elif columns == 4 %}kg-cols-4{% else %}kg-cols-3{% endif %}">
{% for item in items %}
<div class="{% if layout == "card" %}rounded-2xl border border-primary/40 bg-card p-6 text-card-foreground kg-crayon-shadow transition-transform hover:-translate-y-0.5{% elif layout == "centered" %}text-center{% endif %}">
{% if item.icon %}<div class="mb-4 inline-flex h-11 w-11 items-center justify-center rounded-2xl bg-primary/10 text-primary{% if layout == "centered" %} mx-auto{% endif %}"><svg class="h-6 w-6" aria-hidden="true"><use href="/icons/{{ item.icon|split:":"|first }}.svg#{{ item.icon|split:":"|last }}"></use></svg></div>{% endif %}
<h3 class="kg-display text-lg font-semibold">{% if item.linkUrl %}<a href="{{ item.linkUrl }}" class="transition-colors hover:text-primary">{{ item.title }}</a>{% else %}{{ item.title }}{% endif %}</h3>
{% if item.text %}<p class="mt-2 text-muted-foreground">{{ item.text }}</p>{% endif %}
{% if item.linkUrl and item.linkLabel %}<a href="{{ item.linkUrl }}" class="kg-mono mt-3 inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline">{{ item.linkLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
</div>
{% endfor %}
</div>
</div>
</section>

View File

@ -1,23 +0,0 @@
{# featured-posts (kindergarten override) — grid of cards from the `posts` provider #}
{# (params: limit, featured=true). Neon HUD card: image, title, excerpt, meta. #}
<section class="py-12 md:py-16 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-6xl px-4">
{% if heading %}<h2 class="kg-display mb-8 text-2xl font-semibold tracking-tight md:text-3xl">{{ heading }}</h2>{% endif %}
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 {% if columns == 2 %}lg:grid-cols-2{% elif columns == 4 %}lg:grid-cols-4{% else %}lg:grid-cols-3{% endif %}">
{% for post in posts %}
<article class="group flex flex-col overflow-hidden rounded-2xl border border-dashed border-primary/40 bg-card text-card-foreground kg-crayon-shadow transition-transform hover:-translate-y-0.5">
{% if showFeaturedImage and post.featured_image_url %}<a href="{{ post.url }}" class="block aspect-video overflow-hidden bg-muted">{% img post.featured_image_url alt=post.title class="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105" %}</a>{% endif %}
<div class="flex flex-1 flex-col p-5">
<h3 class="kg-display text-lg font-semibold leading-snug"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h3>
{% if showExcerpt and post.excerpt %}<p class="kg-body mt-2 flex-1 text-sm text-muted-foreground">{{ post.excerpt }}</p>{% endif %}
<div class="mt-4 flex flex-wrap items-center gap-x-3 gap-y-1 kg-mono text-xs text-accent">
{% if showAuthor and post.author_name %}<span class="font-medium text-foreground">{{ post.author_name }}</span>{% endif %}
{% if showDate and post.published_at %}<span>{{ post.published_at|date:"M j, Y" }}</span>{% endif %}
{% if post.reading_time %}<span>{{ post.reading_time }} min read</span>{% endif %}
</div>
</div>
</article>
{% endfor %}
</div>
</div>
</section>

View File

@ -1,39 +0,0 @@
{# 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 %}<div class="flex items-center gap-3 {{ cls }}">{% for s in social_items %}<a href="{{ s.url }}" target="_blank" rel="noopener" title="{{ s.label }}" class="text-muted-foreground hover:text-primary transition-colors"><span class="sr-only">{{ s.label }}</span>{% if s.icon %}::{{ s.icon }}::{% else %}{{ s.label }}{% endif %}</a>{% endfor %}</div>{% endif %}{% endmacro %}
{% macro newsletter() %}{% if showNewsletter %}<div class="max-w-sm"><h3 class="kg-mono text-xs font-semibold text-accent">{{ newsletterTitle|default:"Stay in the loop" }}</h3>{% if newsletterDescription %}<p class="mt-1 text-sm text-muted-foreground">{{ newsletterDescription }}</p>{% endif %}<div class="mt-3">{% signup_form list=newsletterListSlug|default:"main" source="footer" submit=newsletterButtonText|default:"Subscribe" %}</div></div>{% endif %}{% endmacro %}
{% macro legalbar() %}<div class="mt-10 pt-6 border-t border-primary/30 flex flex-col sm:flex-row items-center justify-between gap-3 text-sm text-muted-foreground"><p class="kg-mono">{% if copyright %}&copy; {{ copyright }}{% else %}&copy; {{ companyName }}{% endif %}</p>{% if legal_items %}<nav class="flex flex-wrap items-center gap-x-4 gap-y-1">{% for l in legal_items %}<a href="{{ l.url|default:'#' }}"{% if l.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="hover:text-primary transition-colors">{{ l.label }}</a>{% endfor %}</nav>{% endif %}</div>{% endmacro %}
<footer class="w-full border-t border-primary/30 {{ fbg }} {{ class }}">
<div class="{% if fullWidth %}w-full px-4 sm:px-6 lg:px-8{% else %}max-w-7xl mx-auto px-4 sm:px-6 lg:px-8{% endif %} py-16">
{% if fl == "minimal" %}
<div class="flex flex-col sm:flex-row items-center justify-between gap-6">
<div class="flex items-center gap-3">{% if logo %}<img src="{{ logo }}" alt="{{ companyName }}" class="h-8 w-auto"/>{% elif companyName %}<span class="kg-display text-lg font-bold text-foreground">{{ companyName }}</span>{% endif %}{% if tagline %}<span class="text-sm text-muted-foreground hidden sm:inline">{{ tagline }}</span>{% endif %}</div>
{{ sociallinks("") }}
</div>
{{ legalbar() }}
{% elif fl == "centered" %}
<div class="flex flex-col items-center text-center gap-5">
{% if logo %}<img src="{{ logo }}" alt="{{ companyName }}" class="h-10 w-auto"/>{% elif companyName %}<span class="kg-display text-xl font-bold text-foreground">{{ companyName }}</span>{% endif %}
{% if tagline %}<p class="max-w-md text-sm text-muted-foreground">{{ tagline }}</p>{% endif %}
{% if columns %}<nav class="flex flex-wrap items-center justify-center gap-x-6 gap-y-2">{% for col in columns %}{% for item in col.items %}<a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="kg-mono text-xs text-muted-foreground hover:text-primary transition-colors">{{ item.label }}</a>{% endfor %}{% endfor %}</nav>{% endif %}
{{ newsletter() }}
{{ sociallinks("justify-center") }}
</div>
{{ legalbar() }}
{% else %}
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-8">
<div class="col-span-2 lg:col-span-2">
{% if logo %}<img src="{{ logo }}" alt="{{ companyName }}" class="h-9 w-auto"/>{% elif companyName %}<span class="kg-display text-xl font-bold text-foreground">{{ companyName }}</span>{% endif %}
{% if tagline %}<p class="mt-3 max-w-xs text-sm text-muted-foreground">{{ tagline }}</p>{% endif %}
{% if address or email or phone %}<address class="mt-4 not-italic text-sm text-muted-foreground space-y-1">{% if address %}<div>{{ address }}</div>{% endif %}{% if email %}<div><a href="mailto:{{ email }}" class="hover:text-primary transition-colors">{{ email }}</a></div>{% endif %}{% if phone %}<div><a href="tel:{{ phone }}" class="hover:text-primary transition-colors">{{ phone }}</a></div>{% endif %}</address>{% endif %}
{{ sociallinks("mt-5") }}
</div>
{% for col in columns %}<div><h3 class="kg-mono text-xs font-semibold text-accent">{{ col.header }}</h3><ul class="mt-3 space-y-2">{% for item in col.items %}<li><a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="text-sm text-muted-foreground hover:text-primary transition-colors">{{ item.label }}</a></li>{% endfor %}</ul></div>{% endfor %}
{% if showNewsletter %}<div class="col-span-2 lg:col-span-1">{{ newsletter() }}</div>{% endif %}
</div>
{{ legalbar() }}
{% endif %}
</div>
</footer>

View File

@ -1,52 +0,0 @@
{# 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 %}<div class="bn-gallery my-6{% if class %} {{ class }}{% endif %}" data-bn-gallery data-lightbox="{% if lightbox %}true{% else %}false{% endif %}"><div class="{% if layout == "carousel" %}flex snap-x snap-mandatory overflow-x-auto pb-2 {% if gap == "sm" %}gap-2{% elif gap == "lg" %}gap-6{% else %}gap-4{% endif %}{% elif layout == "masonry" %}columns-2 {% if cols == 4 %}md:columns-4{% elif cols == 2 %}md:columns-2{% else %}md:columns-3{% endif %} {% if gap == "sm" %}gap-2{% elif gap == "lg" %}gap-6{% else %}gap-4{% endif %}{% else %}grid grid-cols-2 {% if cols == 4 %}md:grid-cols-4{% elif cols == 2 %}md:grid-cols-2{% else %}md:grid-cols-3{% endif %} {% if gap == "sm" %}gap-2{% elif gap == "lg" %}gap-6{% else %}gap-4{% endif %}{% endif %}">{% for image in images %}<a href="{{ image.src|mediaURL }}" data-bn-gallery-item data-caption="{{ image.caption|default:"" }}" class="group relative block overflow-hidden rounded-2xl border border-primary/30 bg-muted kg-crayon-shadow focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 {% if layout == "carousel" %}w-64 shrink-0 snap-start sm:w-80{% elif layout == "masonry" %}mb-4 w-full break-inside-avoid{% endif %}">{% if layout == "grid" %}{% img image.src alt=image.alt|default:"" class="aspect-square w-full object-cover" %}{% else %}{% img image.src alt=image.alt|default:"" class="h-auto w-full" %}{% endif %}{% if image.caption %}<span class="sr-only">{{ image.caption }}</span>{% endif %}</a>{% endfor %}</div></div>{% if lightbox %}<script>
(function(){
if(window.__bnLightbox)return;window.__bnLightbox=true;
var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var ov,imgEl,capEl,items=[],idx=0,lastFocus=null;
function build(){
ov=document.createElement('div');
ov.setAttribute('role','dialog');ov.setAttribute('aria-modal','true');ov.setAttribute('aria-label','Image viewer');ov.tabIndex=-1;
ov.style.cssText='position:fixed;inset:0;z-index:9999;display:none;align-items:center;justify-content:center;background:rgba(0,0,0,0.9);padding:2rem;';
var btn='position:absolute;width:2.75rem;height:2.75rem;display:inline-flex;align-items:center;justify-content:center;border-radius:9999px;border:none;background:hsl(var(--background));color:hsl(var(--foreground));cursor:pointer;';
ov.innerHTML=
'<button type="button" data-lb-close aria-label="Close" style="'+btn+'top:1rem;right:1rem;"><svg style="width:1.25rem;height:1.25rem" aria-hidden="true"><use href="/icons/lucide.svg#x"/></svg></button>'+
'<button type="button" data-lb-prev aria-label="Previous image" style="'+btn+'left:1rem;top:50%;transform:translateY(-50%);"><svg style="width:1.25rem;height:1.25rem" aria-hidden="true"><use href="/icons/lucide.svg#chevron-left"/></svg></button>'+
'<figure style="margin:0;max-width:92vw;max-height:92vh;display:flex;flex-direction:column;align-items:center;gap:0.75rem;"><img data-lb-img alt="" style="max-width:92vw;max-height:82vh;object-fit:contain;border-radius:0.5rem;'+(reduce?'':'transition:opacity .2s ease;')+'"><figcaption data-lb-cap style="color:hsl(var(--background));opacity:.85;font-size:.875rem;text-align:center;max-width:60ch;"></figcaption></figure>'+
'<button type="button" data-lb-next aria-label="Next image" style="'+btn+'right:1rem;top:50%;transform:translateY(-50%);"><svg style="width:1.25rem;height:1.25rem" aria-hidden="true"><use href="/icons/lucide.svg#chevron-right"/></svg></button>';
document.body.appendChild(ov);
imgEl=ov.querySelector('[data-lb-img]');capEl=ov.querySelector('[data-lb-cap]');
ov.addEventListener('click',function(e){if(e.target===ov||e.target.closest('[data-lb-close]'))close();});
ov.querySelector('[data-lb-prev]').addEventListener('click',function(e){e.stopPropagation();show(idx-1);});
ov.querySelector('[data-lb-next]').addEventListener('click',function(e){e.stopPropagation();show(idx+1);});
}
function show(i){
if(!items.length)return;
idx=(i+items.length)%items.length;
var a=items[idx];var im=a.querySelector('img');
imgEl.src=(im&&(im.currentSrc||im.src))||a.getAttribute('href');
imgEl.alt=im?im.alt:'';
var cap=a.getAttribute('data-caption')||'';capEl.textContent=cap;capEl.style.display=cap?'block':'none';
}
function open(g,link){
if(!ov)build();
items=Array.prototype.slice.call(g.querySelectorAll('[data-bn-gallery-item]'));
lastFocus=document.activeElement;ov.style.display='flex';
var multi=items.length>1;
ov.querySelector('[data-lb-prev]').style.display=multi?'inline-flex':'none';
ov.querySelector('[data-lb-next]').style.display=multi?'inline-flex':'none';
show(items.indexOf(link));ov.focus();
}
function close(){if(ov)ov.style.display='none';if(lastFocus&&lastFocus.focus)lastFocus.focus();}
document.addEventListener('click',function(e){
var link=e.target.closest('[data-bn-gallery][data-lightbox="true"] [data-bn-gallery-item]');
if(!link)return;e.preventDefault();open(link.closest('[data-bn-gallery]'),link);
});
document.addEventListener('keydown',function(e){
if(!ov||ov.style.display==='none')return;
if(e.key==='Escape'){e.preventDefault();close();}
else if(e.key==='ArrowLeft')show(idx-1);
else if(e.key==='ArrowRight')show(idx+1);
});
})();
</script>{% endif %}{% endwith %}

View File

@ -1,7 +1 @@
{# kindergarten heading — kg-display everywhere; on h1/h2 only #}
{% if level == 1 %}<h1 class="text-4xl font-bold tracking-tight kg-display {{ textClass }} flex-1">{{ text }}</h1>
{% elif level == 3 %}<h3 class="text-2xl font-semibold kg-display {{ textClass }} flex-1">{{ text }}</h3>
{% elif level == 4 %}<h4 class="text-xl font-semibold kg-display {{ textClass }} flex-1">{{ text }}</h4>
{% elif level == 5 %}<h5 class="text-lg font-medium kg-display {{ textClass }} flex-1">{{ text }}</h5>
{% elif level == 6 %}<h6 class="text-base font-medium kg-display {{ textClass }} flex-1">{{ text }}</h6>
{% else %}<h2 class="text-3xl font-semibold tracking-tight kg-display {{ textClass }} flex-1">{{ text }}</h2>{% endif %}
{% if step|integer >= 1 and step|integer <= 9 %}<div class="kg-heading-stepped"><span class="kg-heading-step-badge" aria-hidden="true">{{ step|integer }}</span>{% endif %}{% if level|integer == 1 %}<h1 class="kg-display kg-crayon-underline {{ textClass }}" style="font-size: clamp(2.5rem, 6vw, 4rem); margin: 0;">{{ text }}</h1>{% elif level|integer == 3 %}<h3 class="kg-display {{ textClass }}" style="font-size: 1.75rem; margin: 0;">{{ text }}</h3>{% elif level|integer == 4 %}<h4 class="kg-display {{ textClass }}" style="font-size: 1.5rem; margin: 0;">{{ text }}</h4>{% elif level|integer == 5 %}<h5 class="kg-display {{ textClass }}" style="font-size: 1.25rem; margin: 0;">{{ text }}</h5>{% elif level|integer == 6 %}<h6 class="kg-display {{ textClass }}" style="font-size: 1.125rem; margin: 0;">{{ text }}</h6>{% else %}<h2 class="kg-display kg-crayon-underline {{ textClass }}" style="font-size: 2.25rem; margin: 0;">{{ text }}</h2>{% endif %}{% if step|integer >= 1 and step|integer <= 9 %}</div>{% endif %}

View File

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

View File

@ -1,31 +0,0 @@
{# hours-location kindergarten override: mono readout table, neon map panel. Map lazy-load button + inline handler preserved. #}
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-5xl px-4">
{% if heading %}<h2 class="kg-display text-3xl font-semibold tracking-tight md:text-4xl">{{ heading }}</h2>{% endif %}
<div class="mt-10 grid grid-cols-1 gap-10 md:grid-cols-2">
<div>
{% if hours %}
<table class="kg-mono w-full text-sm">
<tbody class="divide-y divide-primary/30">
{% for row in hours %}
<tr>
<th scope="row" class="py-2 pr-4 text-left font-medium">{{ row.day }}</th>
<td class="py-2 text-right {% if row.closed %}text-muted-foreground{% else %}text-primary{% endif %} tabular-nums">{% if row.closed %}Closed{% else %}{{ row.hours }}{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if specialHoursNote %}<p class="mt-4 rounded-2xl border border-primary/40 bg-muted px-3 py-2 text-sm text-muted-foreground">{{ specialHoursNote }}</p>{% endif %}
{% if address %}<div class="mt-6"><div class="kg-mono text-sm font-medium text-accent">Address</div><address class="mt-1 whitespace-pre-line not-italic">{{ address }}</address></div>{% endif %}
{% if phone %}<div class="mt-4"><div class="kg-mono text-sm font-medium text-accent">Phone</div><a href="tel:{{ phone }}" class="kg-mono mt-1 inline-block font-medium transition-colors hover:text-primary">{{ phone }}</a></div>{% endif %}
{% if directionsUrl %}<a href="{{ directionsUrl }}" target="_blank" rel="noopener noreferrer" class="kg-mono mt-6 inline-flex items-center gap-2 rounded-2xl bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"><svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#navigation"></use></svg>Get directions</a>{% endif %}
</div>
{% if mapEmbedUrl %}
<div class="aspect-square w-full overflow-hidden rounded-2xl border border-primary/40 bg-muted kg-crayon-shadow md:aspect-auto">
<button type="button" data-map-src="{{ mapEmbedUrl }}" onclick="var f=document.createElement('iframe');f.src=this.getAttribute('data-map-src');f.setAttribute('class','h-full w-full border-0');f.setAttribute('loading','lazy');f.setAttribute('referrerpolicy','no-referrer-when-downgrade');f.setAttribute('title','Map');this.parentNode.replaceChild(f,this);" class="kg-mono flex h-full min-h-[16rem] w-full items-center justify-center gap-2 text-sm font-medium transition-colors hover:bg-accent/20 hover:text-foreground"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/lucide.svg#map-pin"></use></svg>{{ mapButtonLabel|default:"Load map" }}</button>
</div>
{% endif %}
</div>
</div>
</section>

View File

@ -1,2 +0,0 @@
{# 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" %}<figure class="flex-1 {% if class %}{{ class }} {% endif %}{% if align == "left" %}flex justify-start{% elif align == "right" %}flex justify-end{% else %}flex justify-center{% endif %}"><div class="relative overflow-hidden kg-crayon-shadow {% if size == "xs" %}max-w-[200px] w-full{% elif size == "sm" %}max-w-[400px] w-full{% elif size == "md" %}max-w-[600px] w-full{% elif size == "lg" %}max-w-[800px] w-full{% else %}w-full{% endif %}{% if aspectRatio == "square" %} aspect-square{% elif aspectRatio == "video" %} aspect-video{% elif aspectRatio == "portrait" %} aspect-[3/4]{% elif aspectRatio == "wide" %} aspect-[21/9]{% endif %}{% if rounded == "sm" %} rounded-sm{% elif rounded == "md" %} rounded-2xl{% elif rounded == "lg" %} rounded-lg{% elif rounded == "xl" %} rounded-xl{% elif rounded == "full" %} rounded-full{% endif %}{% if shadow == "sm" %} shadow-sm{% elif shadow == "md" %} shadow-md{% elif shadow == "lg" %} shadow-lg{% elif shadow == "xl" %} shadow-xl{% endif %}">{% 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 %}</div>{% if caption %}<figcaption class="mt-2 text-sm text-muted-foreground text-center kg-mono">{{ caption }}</figcaption>{% endif %}</figure>{% endwith %}

View File

@ -1,16 +0,0 @@
{# logo_strip kindergarten override: mono kicker. Marquee classes, {% img %}, and keyframes <style> preserved byte-for-byte. #}
<section class="py-12 md:py-16 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-6xl px-4">
{% if heading %}<p class="kg-mono text-center text-sm font-semibold text-accent">{{ heading }}</p>{% endif %}
<div class="mt-8 {% if marquee %}group relative overflow-hidden [mask-image:linear-gradient(to_right,transparent,black_8%,black_92%,transparent)]{% endif %}">
<div class="flex flex-wrap items-center justify-center gap-x-10 gap-y-6 {% if marquee %}flex-nowrap [animation:logo-marquee_30s_linear_infinite] motion-reduce:[animation:none] group-hover:[animation-play-state:paused]{% endif %}">
{% for logo in logos %}
{% if logo.url %}<a href="{{ logo.url }}" class="block shrink-0 opacity-80 transition hover:opacity-100">{% endif %}
<span class="inline-flex h-8 items-center{% if grayscale %} grayscale transition-[filter] hover:grayscale-0{% endif %}">{% img logo.image alt=logo.alt class="h-8 w-auto object-contain" %}</span>
{% if logo.url %}</a>{% endif %}
{% endfor %}
</div>
</div>
</div>
</section>
{% if marquee %}<style>@keyframes logo-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}</style>{% endif %}

View File

@ -1,30 +0,0 @@
{# menu-list kindergarten override: mono price readouts, neon section rules. All field reads + control flow preserved. #}
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-5xl px-4">
{% if heading %}<h2 class="kg-display text-3xl font-semibold tracking-tight md:text-4xl">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mt-3 max-w-2xl text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
<div class="mt-10 grid grid-cols-1 gap-x-12 gap-y-10 {% if columns == 2 %}md:grid-cols-2{% endif %}">
{% for section in sections %}
<div class="menu-section">
{% if section.title %}<h3 class="kg-display border-b border-primary/40 pb-2 text-xl font-semibold">{{ section.title }}</h3>{% endif %}
{% if section.note %}<p class="mt-2 text-sm text-muted-foreground">{{ section.note }}</p>{% endif %}
<ul class="mt-4 space-y-5">
{% for item in section.items %}
<li class="flex items-baseline gap-4">
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
<span class="font-medium">{{ item.name }}</span>
{% for badge in item.badges %}{% if badge.label %}<span class="kg-mono inline-block rounded border border-accent/50 px-2 py-0.5 text-xs text-accent">{{ badge.label }}</span>{% endif %}{% endfor %}
</div>
{% if item.description %}<p class="mt-0.5 text-sm text-muted-foreground">{{ item.description }}</p>{% endif %}
</div>
{% if item.price %}<span class="kg-mono shrink-0 font-medium tabular-nums text-primary">{% if currency %}{{ currency }}{% endif %}{{ item.price }}</span>{% endif %}
</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
{% if footnote %}<p class="kg-mono mt-10 text-xs text-muted-foreground">{{ footnote }}</p>{% endif %}
</div>
</section>

View File

@ -1,104 +0,0 @@
{# 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|get:"text"|default:ctaLabel %}
{% set cta_url = cta.url|default:ctaUrl %}
{% set bg = backgroundColor|default:"bg-background" %}
{% macro navlink(item) %}<a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}{% if item.title %} title="{{ item.title }}"{% endif %} class="kg-mono px-3 py-2 text-xs font-medium text-foreground/80 hover:text-primary transition-colors {{ item.css_class }}">{{ item.label }}</a>{% endmacro %}
{% macro dropdown(item) %}<div class="relative group">
<button type="button" class="kg-mono px-3 py-2 text-xs font-medium text-foreground/80 hover:text-primary transition-colors inline-flex items-center gap-1">{{ item.label }}<svg class="h-4 w-4 transition-transform group-hover:rotate-180" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg></button>
{% if use_mega %}<div class="absolute left-1/2 -translate-x-1/2 mt-1 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50"><div class="p-6 bg-card border border-primary/40 kg-crayon-shadow rounded-2xl shadow-xl grid gap-8" style="grid-template-columns:repeat({{ item.children|length|default:1 }},minmax(10rem,1fr));min-width:min(90vw,42rem)">{% for child in item.children %}<div class="space-y-1">{% if child.item_type == "header" %}<div class="kg-mono px-2 py-1 text-xs font-semibold text-accent border-b border-primary/40 mb-2">{{ child.label }}</div>{% else %}<a href="{{ child.url|default:'#' }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="block px-2 py-2 rounded hover:bg-accent transition-colors {{ child.css_class }}">{% if child.icon %}<span class="mr-1.5 text-primary">::{{ child.icon }}::</span>{% endif %}<span class="text-sm font-medium text-foreground">{{ child.label }}</span>{% if child.description %}<span class="block text-xs text-muted-foreground mt-0.5">{{ child.description }}</span>{% endif %}</a>{% endif %}</div>{% endfor %}</div></div>
{% else %}<div class="absolute left-0 mt-1 w-56 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50"><div class="py-2 bg-card border border-primary/40 kg-crayon-shadow rounded-2xl shadow-lg">{% for child in item.children %}{% if child.item_type == "header" %}<div class="kg-mono px-4 py-2 text-xs font-semibold text-accent">{{ child.label }}</div>{% else %}<a href="{{ child.url|default:'#' }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="block px-4 py-2 text-sm text-foreground hover:bg-accent transition-colors {{ child.css_class }}">{{ child.label }}</a>{% endif %}{% endfor %}</div></div>{% endif %}
</div>{% endmacro %}
{% macro brand(size) %}<a href="/" class="flex-shrink-0 flex items-center">{% if logoType == "text" and logoText.text %}<span class="kg-display text-xl font-bold text-foreground {{ logoText.color }}">{{ logoText.text }}</span>{% elif logo %}<img src="{{ logo }}" alt="{{ alt|default:companyName }}" class="{{ size }} w-auto"{% if logoMaxWidth or logoMaxHeight %} style="{% if logoMaxWidth %}max-width:{{ logoMaxWidth }};{% endif %}{% if logoMaxHeight %}max-height:{{ logoMaxHeight }};{% endif %}"{% endif %}/>{% elif companyName %}<span class="kg-display text-xl font-bold text-foreground">{{ companyName }}</span>{% endif %}</a>{% endmacro %}
{% macro ctabtn(extra) %}{% if cta_text %}<a href="{{ cta_url|default:'#' }}" class="kg-mono kg-pop inline-flex items-center rounded-full bg-primary px-4 py-2 text-xs font-semibold text-primary-foreground hover:bg-primary/90 transition-colors {{ extra }}">{{ cta_text }}</a>{% endif %}{% endmacro %}
<nav data-bn-navbar data-bn-nav-variant="{{ nv }}" data-bn-sticky="{{ sb }}"{% if is_transparent %} data-bn-transparent="1"{% endif %} class="bn-navbar w-full {% if sb == "sticky" or sb == "shrink-on-scroll" %}sticky top-0 z-40 {% endif %}{% if is_transparent %}absolute top-0 left-0 z-40 text-white{% else %}{{ bg }}{% if borderBottom %} border-b border-primary/30{% endif %}{% endif %} {{ class }}">
{% if showUtilityBar %}<div class="kg-mono w-full text-xs {% if not showOnMobile %}hidden sm:block{% endif %} bg-muted/60 text-muted-foreground border-b border-primary/20"><div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-between h-8"><div>{{ utilityBarText|safe }}</div><div class="flex items-center gap-4">{% if utilityBarPhone %}<a href="tel:{{ utilityBarPhone }}" class="hover:text-primary transition-colors">{{ utilityBarPhone }}</a>{% endif %}{% if utilityBarEmail %}<a href="mailto:{{ utilityBarEmail }}" class="hover:text-primary transition-colors">{{ utilityBarEmail }}</a>{% endif %}</div></div></div>{% endif %}
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="bn-navbar-row flex items-center justify-between h-16">
{% if nv == "centered-logo" %}
<div class="hidden md:flex items-center gap-1 flex-1">{% for item in menu.items %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endfor %}</div>
<div class="flex items-center justify-center">{{ brand("h-9") }}</div>
<div class="hidden md:flex items-center gap-3 flex-1 justify-end">{{ ctabtn("") }}</div>
{% elif nv == "split" %}
<div class="hidden md:flex items-center gap-1 flex-1">{% for item in menu.items %}{% if forloop.Counter0 * 2 < menu.items|length %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endif %}{% endfor %}</div>
<div class="flex items-center justify-center">{{ brand("h-9") }}</div>
<div class="hidden md:flex items-center gap-1 flex-1 justify-end">{% 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") }}</div>
{% else %}
<div class="flex items-center">{{ brand("h-8") }}</div>
<div class="hidden md:flex items-center gap-1">{% for item in menu.items %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endfor %}{{ ctabtn("ml-3") }}</div>
{% endif %}
<div class="flex items-center md:hidden">
<label for="bn-navbar-toggle" data-bn-nav-open aria-controls="bn-navbar-drawer" aria-expanded="false" class="inline-flex items-center justify-center p-2 rounded-2xl cursor-pointer hover:bg-accent/50 transition-colors"><span class="sr-only">Open menu</span><svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg></label>
</div>
</div>
</div>
{# --- Mobile off-canvas drawer: pure-CSS checkbox baseline (works with no JS) --- #}
<input type="checkbox" id="bn-navbar-toggle" class="bn-navbar-toggle" aria-hidden="true"/>
<label for="bn-navbar-toggle" class="bn-navbar-overlay fixed inset-0 bg-black/50 z-40 md:hidden" aria-hidden="true"></label>
<div id="bn-navbar-drawer" class="bn-navbar-drawer fixed top-0 right-0 h-full w-80 max-w-[85vw] bg-background text-foreground z-50 shadow-xl md:hidden flex flex-col" role="dialog" aria-modal="true" aria-label="Site menu">
<div class="flex items-center justify-between p-4 border-b border-primary/30">{{ brand("h-8") }}<label for="bn-navbar-toggle" data-bn-nav-close class="p-2 rounded-2xl cursor-pointer text-muted-foreground hover:text-primary hover:bg-accent"><span class="sr-only">Close menu</span><svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg></label></div>
<nav class="flex-1 overflow-y-auto py-4" aria-label="Mobile">{% for item in menu.items %}{% if item.children %}<details class="group/acc"><summary class="kg-mono flex items-center justify-between px-6 py-3 text-sm font-medium cursor-pointer hover:bg-accent transition-colors list-none">{{ item.label }}<svg class="h-4 w-4 transition-transform group-open/acc:rotate-180" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg></summary><div class="pl-8 pb-2">{% for child in item.children %}{% if child.item_type != "header" %}<a href="{{ child.url|default:'#' }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="block px-2 py-2 text-sm text-muted-foreground hover:text-primary transition-colors">{{ child.label }}</a>{% endif %}{% endfor %}</div></details>{% else %}<a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="kg-mono block px-6 py-3 text-sm font-medium hover:bg-accent transition-colors">{{ item.label }}</a>{% endif %}{% endfor %}</nav>
{% if cta_text %}<div class="p-4 border-t border-primary/30"><a href="{{ cta_url|default:'#' }}" class="kg-mono block w-full text-center rounded-full bg-primary px-4 py-3 text-base font-semibold text-primary-foreground hover:bg-primary/90 transition-colors">{{ cta_text }}</a></div>{% endif %}
</div>
</nav>
<style>
.bn-navbar-toggle{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;}
.bn-navbar-drawer{transform:translateX(100%);transition:transform .3s ease;}
.bn-navbar-overlay{opacity:0;pointer-events:none;transition:opacity .3s ease;}
.bn-navbar-toggle:checked ~ .bn-navbar-drawer{transform:translateX(0);}
.bn-navbar-toggle:checked ~ .bn-navbar-overlay{opacity:1;pointer-events:auto;}
.bn-navbar-drawer details > summary::-webkit-details-marker{display:none;}
[data-bn-sticky="shrink-on-scroll"].bn-navbar-shrunk .bn-navbar-row{height:3rem;}
[data-bn-sticky="shrink-on-scroll"] .bn-navbar-row{transition:height .2s ease;}
[data-bn-transparent].bn-navbar-solid{position:sticky;color:inherit;background:hsl(var(--background));border-bottom:1px solid hsl(var(--border));}
</style>
<script>
(function(){
if(window.__bnNavInit)return;window.__bnNavInit=1;
function ready(fn){if(document.readyState!='loading')fn();else document.addEventListener('DOMContentLoaded',fn);}
ready(function(){
document.querySelectorAll('[data-bn-navbar]').forEach(function(nav){
var toggle=nav.querySelector('.bn-navbar-toggle');
var drawer=nav.querySelector('.bn-navbar-drawer');
var opener=nav.querySelector('[data-bn-nav-open]');
if(toggle&&drawer){
function focusables(){return drawer.querySelectorAll('a[href],button,label[for],input,summary,[tabindex]:not([tabindex="-1"])');}
toggle.addEventListener('change',function(){
var open=toggle.checked;
document.documentElement.style.overflow=open?'hidden':'';
document.body.style.overflow=open?'hidden':'';
if(opener)opener.setAttribute('aria-expanded',open?'true':'false');
if(open){var f=focusables();if(f.length)f[0].focus();}
else if(opener&&opener.focus)opener.focus();
});
drawer.addEventListener('keydown',function(e){
if(e.key==='Escape'){toggle.checked=false;toggle.dispatchEvent(new Event('change'));return;}
if(e.key!=='Tab')return;
var f=focusables();if(!f.length)return;
var first=f[0],last=f[f.length-1];
if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus();}
else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus();}
});
drawer.querySelectorAll('a[href]').forEach(function(a){a.addEventListener('click',function(){toggle.checked=false;toggle.dispatchEvent(new Event('change'));});});
}
var sb=nav.getAttribute('data-bn-sticky');
var transparent=nav.getAttribute('data-bn-transparent')==='1';
if(sb==='shrink-on-scroll'||transparent){
var overHero=false;
if(transparent){var next=nav.nextElementSibling;overHero=!!next&&next.getBoundingClientRect().height>=240;if(!overHero)nav.classList.add('bn-navbar-solid');}
var onScroll=function(){
var y=window.scrollY||window.pageYOffset;
if(sb==='shrink-on-scroll')nav.classList.toggle('bn-navbar-shrunk',y>40);
if(transparent&&overHero)nav.classList.toggle('bn-navbar-solid',y>40);
};
window.addEventListener('scroll',onScroll,{passive:true});onScroll();
}
});
});
})();
</script>

View File

@ -1,12 +0,0 @@
{# 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. #}
<section class="py-12 bg-background text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-3xl px-4">
<div class="rounded-2xl {% if layout == "inline" %}bg-primary px-6 py-10 text-primary-foreground kg-crayon-shadow md:px-12{% else %}border border-dashed border-primary/40 bg-card px-6 py-10 text-card-foreground kg-crayon-shadow md:px-10{% endif %} text-center">
{% if title %}<h2 class="kg-display text-2xl font-semibold tracking-tight md:text-3xl">{{ title }}</h2>{% endif %}
{% if description %}<p class="kg-body mx-auto mt-3 max-w-xl {% if layout == "inline" %}text-primary-foreground/80{% else %}text-muted-foreground{% endif %}">{{ description }}</p>{% endif %}
<div class="mx-auto mt-6 max-w-md">{% signup_form list=listSlug source=source submit=submitText success_message=successMessage placeholder=placeholder %}</div>
</div>
</div>
</section>

View File

@ -1,12 +0,0 @@
{# 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. #}
<section class="relative overflow-hidden bg-background text-foreground py-20 md:py-28{% if class %} {{ class }}{% endif %}">
<div class="relative z-10 mx-auto max-w-2xl px-4 text-center">
<span class="kg-sticker">Oops</span>
<h1 class="kg-display mt-4 text-4xl font-bold tracking-tight md:text-5xl"><span class="kg-crayon-underline">{{ title|default:"This page went to play outside" }}</span></h1>
<p class="mt-5 text-lg text-muted-foreground">{{ 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." }}</p>
{% if context.path %}<p class="kg-mono mt-3 text-sm text-muted-foreground">You asked for: <span class="text-primary">{{ context.path }}</span></p>{% endif %}
<div class="mt-8 flex flex-wrap items-center justify-center gap-3">
<a href="/" class="kg-display kg-pop inline-flex items-center gap-2 rounded-full bg-primary px-7 py-3.5 text-lg font-bold text-primary-foreground kg-drop"><svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg>Back home</a>
</div>
</div>
</section>

View File

@ -1,13 +0,0 @@
{# 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. #}
<div class="password-reset-form mx-auto max-w-md rounded-2xl border border-primary/40 bg-card p-6 text-card-foreground kg-crayon-shadow">
<div id="reset-message"></div>
<div id="request-reset-panel">
<h2 class="kg-display text-2xl font-bold mb-2">Reset Password</h2>
<p class="text-muted-foreground mb-4">Enter your email address and we will send you a link to reset your password.</p>
<form hx-post="/api/auth/request-password-reset" hx-target="#reset-message" hx-swap="innerHTML">
<div class="mb-4"><label class="kg-mono mb-1 block text-xs font-medium text-accent">Email</label><input type="email" name="email" required class="w-full rounded-2xl border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"/></div>
<button type="submit" class="kg-mono kg-pop w-full rounded-2xl bg-primary px-4 py-2.5 font-semibold text-primary-foreground hover:bg-primary/90" hx-disabled-elt="this">Send Reset Link</button>
</form>
<p class="mt-4 text-center text-sm"><a href="/login" class="kg-mono text-primary hover:underline">Back to login</a></p>
</div>
</div>

View File

@ -1,16 +0,0 @@
{# popular-posts (kindergarten override) — compact ranked list from the `posts` provider. #}
<section class="bg-background text-foreground{% if class %} {{ class }}{% endif %}">
{% if heading %}<h2 class="kg-eyebrow kg-mono mb-4 text-xs font-semibold text-accent">{{ heading }}</h2>{% endif %}
<ol class="space-y-4">
{% for post in posts %}
<li class="flex items-start gap-3">
{% if showRank %}<span class="kg-mono mt-0.5 text-lg font-bold tabular-nums text-primary" style="text-shadow: 0 0 8px hsl(var(--primary));">{{ forloop.Counter }}</span>{% endif %}
{% if showThumbnail and post.featured_image_url %}<a href="{{ post.url }}" class="h-12 w-12 flex-shrink-0 overflow-hidden rounded-2xl border border-primary/40 bg-muted">{% img post.featured_image_url alt=post.title class="h-full w-full object-cover" %}</a>{% endif %}
<div class="min-w-0">
<h3 class="text-sm font-medium leading-snug"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h3>
{% if post.published_at %}<p class="kg-mono mt-0.5 text-xs text-accent">{{ post.published_at|date:"M j, Y" }}</p>{% endif %}
</div>
</li>
{% endfor %}
</ol>
</section>

View File

@ -1,21 +0,0 @@
{# 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 %}
<section class="hero relative flex min-h-[50vh] flex-col justify-end overflow-hidden bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="absolute inset-0 z-0">{% img post.featured_image_url alt=post.featured_image_alt class="h-full w-full object-cover" layout="hero" %}<div class="absolute inset-0 bg-black/60"></div></div>
<div class="relative z-10 mx-auto w-full max-w-4xl px-4 py-12 sm:px-6 lg:px-8 {% if center %}text-center{% endif %}">
<h1 class="kg-display text-3xl font-bold leading-tight text-white sm:text-4xl lg:text-5xl">{{ post.title }}</h1>
{% if showExcerpt and post.excerpt %}<p class="kg-body mt-4 max-w-2xl text-lg text-white/80{% if center %} mx-auto{% endif %}">{{ post.excerpt }}</p>{% endif %}
{% if showMeta %}<div class="mt-6 flex flex-wrap items-center gap-x-3 gap-y-1 kg-mono text-sm text-white/80{% if center %} justify-center{% endif %}">{% if post.author_name %}<span class="font-medium text-white">{{ post.author_name }}</span>{% endif %}{% if post.published_at %}<span>{{ post.published_at|date:"F j, Y" }}</span>{% endif %}{% if post.reading_time %}<span>{{ post.reading_time }} min read</span>{% endif %}</div>{% endif %}
</div>
</section>
{% else %}
<section class="relative overflow-hidden bg-background py-14 text-foreground md:py-20{% if class %} {{ class }}{% endif %}">
<div class="relative z-10 mx-auto w-full max-w-3xl px-4 {% if center %}text-center{% endif %}">
<h1 class="kg-display text-3xl font-bold leading-tight tracking-tight sm:text-4xl lg:text-5xl">{{ post.title }}</h1>
{% if showExcerpt and post.excerpt %}<p class="kg-body mt-4 text-lg text-muted-foreground{% if center %} mx-auto max-w-2xl{% endif %}">{{ post.excerpt }}</p>{% endif %}
{% if showMeta %}<div class="mt-6 flex flex-wrap items-center gap-x-3 gap-y-1 kg-mono text-sm text-accent{% if center %} justify-center{% endif %}">{% if post.author_name %}<span class="font-medium text-foreground">{{ post.author_name }}</span>{% endif %}{% if post.published_at %}<span>{{ post.published_at|date:"F j, Y" }}</span>{% endif %}{% if post.reading_time %}<span>{{ post.reading_time }} min read</span>{% endif %}</div>{% endif %}
</div>
</section>
{% endif %}

View File

@ -1,9 +0,0 @@
{# post-metadata (kindergarten override) — author/date/reading-time row from the `post` provider. #}
{% if post %}
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 kg-mono text-sm text-accent{% if class %} {{ class }}{% endif %}">
{% if showAuthor and post.author_name %}<span class="flex items-center gap-2">{% 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 %}<span class="font-medium text-foreground">{{ post.author_name }}</span></span>{% endif %}
{% if showDate and post.published_at %}<time datetime="{{ post.published_at|date:"Y-m-d" }}">{{ post.published_at|date:"F j, Y" }}</time>{% endif %}
{% if showReadingTime and post.reading_time %}<span>{{ post.reading_time }} min read</span>{% endif %}
{% if post.categories %}<span class="flex flex-wrap gap-1.5">{% for cat in post.categories %}<a href="{{ cat.url|default:'#' }}" class="border border-primary/40 bg-card px-2 py-0.5 text-xs text-card-foreground transition-colors hover:text-primary">{{ cat.name }}</a>{% endfor %}</span>{% endif %}
</div>
{% endif %}

View File

@ -1,25 +0,0 @@
{# pricing kindergarten override: scanline overlay, heading, HUD neon tier panels (magenta highlight / cyan standard), CTAs. Field reads + control flow preserved. #}
<section class="relative overflow-hidden py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="relative z-10 mx-auto max-w-6xl px-4">
{% if heading %}<h2 class="kg-display text-center text-3xl font-semibold tracking-tight md:text-4xl">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
<div class="mt-12 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{% for tier in tiers %}
<div class="relative flex flex-col rounded-2xl border p-6 {% if tier.highlighted %}border-primary bg-card text-card-foreground kg-crayon-shadow ring-1 ring-primary{% else %}border-primary/40 bg-card text-card-foreground kg-crayon-shadow{% endif %}">
{% if tier.badge %}<span class="kg-mono absolute -top-3 left-1/2 -translate-x-1/2 rounded-full bg-primary px-3 py-1 text-xs font-semibold text-primary-foreground">{{ tier.badge }}</span>{% endif %}
<h3 class="kg-display text-lg font-semibold">{{ tier.name }}</h3>
<p class="mt-4 flex items-baseline gap-1">
<span class="kg-display text-4xl font-bold tracking-tight" style="font-family:var(--font-heading)">{% if currency %}{{ currency }}{% else %}${% endif %}{{ tier.price }}</span>
{% if tier.period %}<span class="kg-mono text-sm text-muted-foreground">{{ tier.period }}</span>{% endif %}
</p>
{% if tier.description %}<p class="mt-3 text-sm text-muted-foreground">{{ tier.description }}</p>{% endif %}
{% if tier.features %}<ul class="mt-6 flex-1 space-y-3 text-sm">
{% for feature in tier.features %}<li class="flex items-start gap-2"><svg class="mt-0.5 h-4 w-4 shrink-0 text-primary" aria-hidden="true"><use href="/icons/lucide.svg#check"></use></svg><span>{{ feature }}</span></li>{% endfor %}
</ul>{% endif %}
{% if tier.ctaUrl and tier.ctaLabel %}<a href="{{ tier.ctaUrl }}" class="kg-mono mt-8 inline-flex w-full items-center justify-center rounded-full px-5 py-3 font-semibold transition-opacity hover:opacity-90 {% if tier.highlighted %}kg-pop bg-primary text-primary-foreground{% else %}border border-primary/40 bg-background text-foreground hover:bg-muted{% endif %}">{{ tier.ctaLabel }}</a>{% endif %}
</div>
{% endfor %}
</div>
{% if billingNote %}<p class="kg-mono mt-8 text-center text-sm text-muted-foreground">{{ billingNote }}</p>{% endif %}
</div>
</section>

View File

@ -1,2 +0,0 @@
{# kindergarten quote — dashed neon left border; kg-display for pull; mono figcaption #}
{% with style=style|default:"block" align=align|default:"left" %}<figure class="bn-quote my-6 {% if style == "inline-card" %}rounded-2xl border border-dashed border-primary/40 bg-card p-6 md:p-8 kg-crayon-shadow{% elif style == "pull" %}border-l-4 border-dashed border-primary pl-6 md:pl-8 py-2{% else %}border-l-2 border-dashed border-primary/50 pl-5 py-1{% endif %}{% if align == "center" %} text-center{% endif %}{% if class %} {{ class }}{% endif %}"><blockquote{% if cite %} cite="{{ cite }}"{% endif %} class="{% if style == "pull" %}text-2xl md:text-3xl font-semibold leading-snug tracking-tight kg-display{% else %}text-lg md:text-xl leading-relaxed{% endif %} text-foreground">{{ quote|safe }}</blockquote>{% if attribution or role or cite %}<figcaption class="mt-4 text-sm text-muted-foreground kg-mono{% if align == "center" %} flex flex-col items-center{% endif %}">{% if attribution %}<span class="font-medium text-foreground">{{ attribution }}</span>{% endif %}{% if role %}<span class="{% if attribution %}block {% endif %}text-muted-foreground">{{ role }}</span>{% endif %}{% if cite %}<a href="{{ cite }}" class="{% if attribution or role %}mt-1 {% endif %}inline-block underline underline-offset-2 hover:text-foreground">Source</a>{% endif %}</figcaption>{% endif %}</figure>{% endwith %}

View File

@ -1,17 +0,0 @@
{# related-posts (kindergarten override) — grid from the `posts` provider. #}
{% if posts %}
<section class="border-t border-primary/30 py-12 md:py-16 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-6xl px-4">
<h2 class="kg-display mb-8 text-2xl font-semibold tracking-tight">{{ heading|default:"Related posts" }}</h2>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 {% if columns == 2 %}lg:grid-cols-2{% else %}lg:grid-cols-3{% endif %}">
{% for post in posts %}
<article class="group flex flex-col rounded-2xl border border-dashed border-primary/40 bg-card p-4 text-card-foreground kg-crayon-shadow transition-transform hover:-translate-y-0.5">
{% if post.featured_image_url %}<a href="{{ post.url }}" class="mb-3 block aspect-video overflow-hidden rounded-2xl bg-muted">{% img post.featured_image_url alt=post.title class="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105" %}</a>{% endif %}
<h3 class="kg-display text-base font-semibold leading-snug"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h3>
{% if post.published_at %}<p class="kg-mono mt-1 text-xs text-accent">{{ post.published_at|date:"M j, Y" }}</p>{% endif %}
</article>
{% endfor %}
</div>
</div>
</section>
{% endif %}

View File

@ -1,14 +0,0 @@
{# rich_section kindergarten override: mono eyebrow, heading, HUD-framed media, CTA. Field reads + {% img %} preserved. #}
<section class="py-16 md:py-24 {% if background == "muted" %}bg-muted text-foreground{% elif background == "card" %}bg-card text-card-foreground{% else %}bg-background text-foreground{% endif %}{% if class %} {{ class }}{% endif %}">
<div class="mx-auto grid max-w-6xl items-center gap-10 px-4 md:grid-cols-2 md:gap-16">
<div class="{% if mediaPosition == "left" %}md:order-2{% else %}md:order-1{% endif %}">
{% if eyebrow %}<p class="kg-mono text-xs font-semibold text-accent">{{ eyebrow }}</p>{% endif %}
{% if heading %}<h2 class="kg-display mt-2 text-3xl font-semibold tracking-tight md:text-4xl">{{ heading }}</h2>{% endif %}
{% if body %}<div class="mt-4 space-y-4 text-lg text-muted-foreground [&_a]:text-primary [&_a]:underline [&_strong]:text-foreground">{{ body|safe }}</div>{% endif %}
{% if ctaUrl and ctaLabel %}<a href="{{ ctaUrl }}" class="kg-mono kg-pop mt-6 inline-flex items-center gap-2 rounded-full bg-primary px-5 py-3 font-semibold text-primary-foreground transition-opacity hover:opacity-90">{{ ctaLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
</div>
<div class="{% if mediaPosition == "left" %}md:order-1{% else %}md:order-2{% endif %}">
{% if media %}<div class="overflow-hidden rounded-2xl border border-dashed border-primary/50 kg-crayon-shadow">{% img media alt=mediaAlt class="w-full h-auto object-cover" %}</div>{% endif %}
</div>
</div>
</section>

View File

@ -1,2 +0,0 @@
{# kindergarten rich-text — body font via kg-prose #}
{% if html %}<article class="rich-text prose prose-lg max-w-none kg-prose kg-body">{{ html|safe }}</article>{% endif %}

View File

@ -1,15 +0,0 @@
{# 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" %}
<nav class="flex-1 {% if compact %}w-56 py-4 pr-3{% else %}w-64 py-6 pr-4{% endif %}{% if showBorder %} border-r border-primary/30{% endif %}{% if class %} {{ class }}{% endif %}">
{% if title %}<h3 class="kg-eyebrow kg-mono {% if compact %}px-2 mb-3 text-xs text-accent{% else %}px-3 mb-4 text-sm text-accent{% endif %} font-semibold">{{ title }}</h3>{% endif %}
<ul class="{% if compact %}space-y-0.5{% else %}space-y-1{% endif %}">
{% for item in menu.items %}
<li><a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}{% if item.title %} title="{{ item.title }}"{% endif %} data-bn-sidebar-link class="flex items-center rounded-2xl kg-mono text-sm text-muted-foreground transition-colors hover:text-primary hover:bg-accent {% if compact %}px-2 py-1.5{% else %}px-3 py-2{% endif %} {{ item.css_class }}">{% if item.icon %}<svg class="mr-2 h-4 w-4" aria-hidden="true"><use href="/icons/{{ item.icon|split:":"|first }}.svg#{{ item.icon|split:":"|last }}"></use></svg>{% endif %}{{ item.label }}</a></li>
{% endfor %}
</ul>
</nav>
{% if highlightCurrent %}<script>
(function(){var p=window.location.pathname;document.querySelectorAll('[data-bn-sidebar-link]').forEach(function(a){if(a.getAttribute('href')===p){a.classList.remove('text-muted-foreground');a.classList.add('text-foreground','bg-accent','font-medium');}});})();
</script>{% endif %}

View File

@ -1,14 +0,0 @@
{# kindergarten social-links — mono label, sharp neon-bordered chips; icon sprites preserved #}
<div class="social-links{% if class %} {{ class }}{% endif %}">
{% if heading %}<p class="mb-3 text-sm font-medium text-muted-foreground kg-mono">{{ heading }}</p>{% endif %}
<ul class="flex {% if style == "stack" %}flex-col gap-2{% else %}flex-wrap items-center gap-3{% endif %}">
{% for link in links %}{% if link.url %}
<li>
<a href="{{ link.url }}" target="_blank" rel="noopener noreferrer" aria-label="{{ link.label|default:link.network }}" class="inline-flex items-center gap-2 rounded-full border border-primary/40 text-foreground transition-colors hover:bg-accent hover:text-accent-foreground {% if style == "stack" %}w-full px-4 py-2{% else %}p-2.5{% endif %}">
{% if link.network %}<svg class="h-5 w-5" aria-hidden="true"><use href="/icons/{{ link.network|split:":"|first }}.svg#{{ link.network|split:":"|last }}"></use></svg>{% endif %}
{% if style == "stack" and link.label %}<span class="text-sm font-medium kg-mono">{{ link.label }}</span>{% endif %}
</a>
</li>
{% endif %}{% endfor %}
</ul>
</div>

View File

@ -1,49 +0,0 @@
{# stats kindergarten override: scanline overlay, heading, HUD stat tiles, mono labels. data-stats + countUp script/attrs preserved byte-for-byte. #}
<section class="relative overflow-hidden py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}"{% if countUp %} data-stats{% endif %}>
<div class="relative z-10 mx-auto max-w-6xl px-4">
{% if heading %}<h2 class="kg-display text-center text-3xl font-semibold tracking-tight md:text-4xl">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
<dl class="mt-12 grid grid-cols-2 gap-8 {% if columns == 2 %}md:grid-cols-2{% elif columns == 3 %}md:grid-cols-3{% else %}md:grid-cols-4{% endif %}">
{% for item in items %}
<div class="text-center">
<dd class="kg-display text-4xl font-bold tracking-tight md:text-5xl" style="font-family:var(--font-heading)">{% if item.prefix %}{{ item.prefix }}{% endif %}<span{% if countUp %} data-countup-value="{{ item.value }}"{% endif %}>{{ item.value }}</span>{% if item.suffix %}{{ item.suffix }}{% endif %}</dd>
{% if item.label %}<dt class="kg-mono mt-2 text-sm font-medium text-muted-foreground">{{ item.label }}</dt>{% endif %}
</div>
{% endfor %}
</dl>
</div>
</section>
{% if countUp %}<script>
(function(){
var sections = document.querySelectorAll('[data-stats]');
var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function animate(section){
var nodes = section.querySelectorAll('[data-countup-value]');
nodes.forEach(function(node){
var raw = node.getAttribute('data-countup-value') || '';
var target = parseFloat(raw.replace(/[^0-9.\-]/g, ''));
if (isNaN(target)) return;
var decimals = (raw.split('.')[1] || '').length;
var start = null, dur = 1200;
function step(ts){
if (start === null) start = ts;
var p = Math.min((ts - start) / dur, 1);
var eased = 1 - Math.pow(1 - p, 3);
node.textContent = (target * eased).toFixed(decimals);
if (p < 1) requestAnimationFrame(step);
else node.textContent = target.toFixed(decimals);
}
requestAnimationFrame(step);
});
}
sections.forEach(function(section){
if (reduce || !('IntersectionObserver' in window)) return;
var io = new IntersectionObserver(function(entries){
entries.forEach(function(e){
if (e.isIntersecting){ animate(section); io.disconnect(); }
});
}, { threshold: 0.3 });
io.observe(section);
});
})();
</script>{% endif %}

View File

@ -1,22 +0,0 @@
{# team kindergarten override: heading, dashed-neon HUD member panels (lime), mono roles. {% img %} + social icon svgs + control flow preserved. #}
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-6xl px-4">
{% if heading %}<h2 class="kg-display text-center text-3xl font-semibold tracking-tight md:text-4xl">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
<div class="mt-12 {% if layout == "list" %}mx-auto max-w-3xl space-y-6{% elif layout == "single" %}mx-auto max-w-xl{% else %}grid grid-cols-1 gap-8 sm:grid-cols-2 {% if columns == 2 %}lg:grid-cols-2{% elif columns == 4 %}lg:grid-cols-4{% else %}lg:grid-cols-3{% endif %}{% endif %}">
{% for member in members %}
<div class="{% if layout == "list" %}flex items-start gap-5 rounded-2xl border border-dashed border-primary/50 bg-card p-5 text-card-foreground kg-crayon-shadow{% else %}text-center{% endif %}">
{% if member.photo %}<span class="{% if layout == "list" %}h-20 w-20{% else %}mx-auto h-28 w-28{% endif %} block shrink-0 overflow-hidden rounded-full bg-muted">{% img member.photo alt=member.name class="h-full w-full object-cover" %}</span>{% endif %}
<div class="{% if layout != "list" %}mt-4{% endif %}">
{% if member.name %}<h3 class="kg-display text-lg font-semibold">{{ member.name }}</h3>{% endif %}
{% if member.role %}<p class="kg-mono text-sm font-medium text-primary">{{ member.role }}</p>{% endif %}
{% if member.bio %}<p class="mt-2 text-sm text-muted-foreground">{{ member.bio }}</p>{% endif %}
{% if member.socials %}<div class="mt-3 flex gap-3 {% if layout != "list" %}justify-center{% endif %}">
{% for social in member.socials %}{% if social.url %}<a href="{{ social.url }}" class="text-muted-foreground transition-colors hover:text-primary" aria-label="Social link"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/{{ social.icon|split:":"|first }}.svg#{{ social.icon|split:":"|last }}"></use></svg></a>{% endif %}{% endfor %}
</div>{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
</section>

View File

@ -1,27 +0,0 @@
{# testimonial kindergarten override: heading, dashed-neon HUD quote cards. Rating svgs, {% img %}, and control flow preserved. #}
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-6xl px-4">
{% if heading %}<h2 class="kg-display text-center text-3xl font-semibold tracking-tight md:text-4xl">{{ heading }}</h2>{% endif %}
<div class="mt-12 {% if layout == "single" %}mx-auto max-w-3xl{% elif layout == "carousel" %}flex snap-x snap-mandatory gap-6 overflow-x-auto pb-4{% else %}grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3{% endif %}">
{% for q in quotes %}
<figure class="flex flex-col rounded-2xl border border-dashed border-primary/50 bg-card p-6 text-card-foreground kg-crayon-shadow{% if layout == "carousel" %} min-w-[18rem] max-w-sm shrink-0 snap-start{% endif %}">
{% if q.rating %}<div class="mb-3 flex gap-0.5" aria-label="Rated {{ q.rating }} out of 5">
<svg class="h-4 w-4 {% if q.rating >= 1 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
<svg class="h-4 w-4 {% if q.rating >= 2 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
<svg class="h-4 w-4 {% if q.rating >= 3 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
<svg class="h-4 w-4 {% if q.rating >= 4 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
<svg class="h-4 w-4 {% if q.rating >= 5 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
</div>{% endif %}
<blockquote class="flex-1 text-lg leading-relaxed">{% if q.quote %}&ldquo;{{ q.quote }}&rdquo;{% endif %}</blockquote>
<figcaption class="mt-5 flex items-center gap-3">
{% if q.avatar %}<span class="h-10 w-10 shrink-0 overflow-hidden rounded-full bg-muted">{% img q.avatar alt=q.name class="h-full w-full object-cover" %}</span>{% endif %}
<span>
{% if q.name %}<span class="kg-display block font-semibold">{{ q.name }}</span>{% endif %}
{% if q.role %}<span class="kg-mono block text-sm text-muted-foreground">{{ q.role }}</span>{% endif %}
</span>
</figcaption>
</figure>
{% endfor %}
</div>
</div>
</section>

View File

@ -1,2 +1 @@
{# kindergarten text — body font via kg-prose #}
<div class="prose prose-gray max-w-none flex-1 kg-prose kg-body {{ proseClass }}">{{ text|safe }}</div>
<div class="kg-text {{ class }}">{{ text|safe }}</div>

View File

@ -1,22 +0,0 @@
{# timeline kindergarten override: heading, mono step labels, neon markers. All field reads + control flow preserved. #}
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-4xl px-4">
{% if heading %}<h2 class="kg-display text-center text-3xl font-semibold tracking-tight md:text-4xl">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
<ol class="mt-12 {% if orientation == "horizontal" %}grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-4{% else %}relative space-y-8 before:absolute before:left-4 before:top-2 before:h-[calc(100%-1rem)] before:w-px before:bg-primary/50{% endif %}">
{% for step in steps %}
<li class="{% if orientation == "horizontal" %}text-center{% else %}relative pl-14{% endif %}">
<span class="flex h-8 w-8 items-center justify-center rounded-2xl bg-primary text-sm font-semibold text-primary-foreground kg-mono {% if orientation == "horizontal" %}mx-auto{% else %}absolute left-0 top-0 ring-4 ring-background{% endif %}">
{% if marker == "numbered" %}{{ forloop.counter }}{% elif marker == "dot" %}<span class="h-2 w-2 rounded-full bg-primary-foreground"></span>{% else %}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#flag"></use></svg>{% endif %}
</span>
<div class="{% if orientation == "horizontal" %}mt-4{% else %}pt-1{% endif %}">
{% if step.label %}<p class="kg-mono text-xs font-semibold text-accent">{{ step.label }}</p>{% endif %}
{% if step.title %}<h3 class="kg-display mt-1 text-lg font-semibold">{{ step.title }}</h3>{% endif %}
{% if step.text %}<p class="mt-1 text-muted-foreground">{{ step.text }}</p>{% endif %}
{% if step.media %}<div class="mt-3 overflow-hidden rounded-2xl border border-primary/40">{% img step.media alt=step.title class="w-full h-auto object-cover" %}</div>{% endif %}
</div>
</li>
{% endfor %}
</ol>
</div>
</section>

View File

@ -1,10 +0,0 @@
{# 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 %}<a href="{{ it.link }}" class="inline-flex items-center gap-1.5 hover:opacity-80 transition-opacity">{% if it.icon %}::{{ it.icon }}::{% endif %}{% if it.label %}<span>{{ it.label }}</span>{% endif %}</a>{% else %}<span class="inline-flex items-center gap-1.5">{% if it.icon %}::{{ it.icon }}::{% endif %}{% if it.label %}<span>{{ it.label }}</span>{% endif %}</span>{% endif %}{% endmacro %}
<div class="kg-mono w-full text-xs {% if not showOnMobile %}hidden sm:block{% endif %} {{ ubcls }} {{ class }}">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-9 flex items-center justify-between gap-4">
<div class="flex items-center gap-4 flex-wrap">{% for it in leftItems %}{{ ubitem(it) }}{% endfor %}</div>
<div class="flex items-center gap-4 flex-wrap justify-end">{% for it in rightItems %}{{ ubitem(it) }}{% endfor %}</div>
</div>
</div>

View File

@ -1,23 +0,0 @@
{# 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" %}<figure class="bn-video my-6{% if class %} {{ class }}{% endif %}"><div class="bn-video-frame relative w-full overflow-hidden rounded-2xl border border-primary/40 bg-muted kg-crayon-shadow {% if aspect == "4:3" %}aspect-[4/3]{% elif aspect == "1:1" %}aspect-square{% elif aspect == "21:9" %}aspect-[21/9]{% else %}aspect-video{% endif %}" data-bn-video data-video-url="{{ url|default:"" }}" data-video-media="{% if media %}{{ media|mediaURL }}{% endif %}"><button type="button" data-bn-video-play class="group absolute inset-0 flex h-full w-full items-center justify-center focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring" aria-label="Play video{% if title %}: {{ title }}{% endif %}">{% if poster %}{% img poster alt=title|default:"" class="absolute inset-0 h-full w-full object-cover" %}{% endif %}<span class="absolute inset-0 bg-primary/10 transition-colors group-hover:bg-primary/20" aria-hidden="true"></span><span class="relative inline-flex h-16 w-16 items-center justify-center rounded-2xl border border-primary/60 bg-background/90 text-primary shadow-lg transition-transform group-hover:scale-105 motion-reduce:transform-none">::lucide:play:xl::</span></button></div>{% if caption %}<figcaption class="kg-mono mt-2 text-center text-sm text-muted-foreground">{{ caption }}</figcaption>{% endif %}</figure><script>
(function(){
if(window.__bnVideoFacade)return;window.__bnVideoFacade=true;
function yt(u){var m=u.match(/(?:youtube\.com\/(?:watch\?v=|embed\/|shorts\/)|youtu\.be\/)([\w-]{6,})/);return m?m[1]:null;}
function vim(u){var m=u.match(/vimeo\.com\/(?:video\/)?(\d+)/);return m?m[1]:null;}
document.addEventListener('click',function(e){
var btn=e.target.closest('[data-bn-video-play]');if(!btn)return;
var frame=btn.closest('[data-bn-video]');if(!frame)return;
e.preventDefault();
var url=frame.getAttribute('data-video-url')||'',media=frame.getAttribute('data-video-media')||'',node;
var vstyle='position:absolute;inset:0;width:100%;height:100%;object-fit:contain;background:rgba(0,0,0,1);';
if(media){node=document.createElement('video');node.src=media;node.controls=true;node.autoplay=true;node.setAttribute('playsinline','');node.style.cssText=vstyle;}
else{var y=yt(url),v=vim(url),embed;
if(y)embed='https://www.youtube-nocookie.com/embed/'+y+'?autoplay=1&rel=0';
else if(v)embed='https://player.vimeo.com/video/'+v+'?autoplay=1';
if(embed){node=document.createElement('iframe');node.src=embed;node.allow='autoplay; fullscreen; picture-in-picture';node.setAttribute('allowfullscreen','');node.setAttribute('title',btn.getAttribute('aria-label')||'Video');node.style.cssText='position:absolute;inset:0;width:100%;height:100%;border:0;';}
else if(url){node=document.createElement('video');node.src=url;node.controls=true;node.autoplay=true;node.setAttribute('playsinline','');node.style.cssText=vstyle;}
}
if(node){btn.remove();frame.appendChild(node);}
});
})();
</script>{% endwith %}