diff --git a/RECOMMENDED_FONTS.md b/RECOMMENDED_FONTS.md index 3abfe16..ab81536 100644 --- a/RECOMMENDED_FONTS.md +++ b/RECOMMENDED_FONTS.md @@ -1,43 +1,39 @@ -# Recommended fonts for Kindergarten +# Recommended fonts — Kindergarten -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). +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. -Until the admin makes a choice, the theme falls back to its system stack: +| 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` | -- `--font-heading` → `Quicksand`, `Sniglet`, `Nunito`, system sans -- `--font-body` → `Nunito`, `Quicksand`, system sans -- `--font-mono` → `Fira Code`, `JetBrains Mono`, system monospace +## How the theme consumes them -## How to apply +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: -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) +- `.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. -## Recommendations +## To assign (optional) -| 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. | +Open the typography panel and assign **Baloo 2 -> Heading** and **Quicksand -> Body**. Both +appear in the picker under the theme's bundled fonts. -## Why these picks +## Alternate direction -- **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. +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. -## Wave-2 follow-up +## Licensing -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. +Both are OFL 1.1 and cleared for redistribution inside this artifact. Details and +attribution: `assets/fonts/LICENSES.md`. diff --git a/RECOMMENDED_ICONS.md b/RECOMMENDED_ICONS.md new file mode 100644 index 0000000..4ca94a2 --- /dev/null +++ b/RECOMMENDED_ICONS.md @@ -0,0 +1,26 @@ +# Recommended icons — Kindergarten + +Icons render through the CMS sprite pipeline: `` +(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.) diff --git a/assets/fonts/LICENSES.md b/assets/fonts/LICENSES.md new file mode 100644 index 0000000..97c8d32 --- /dev/null +++ b/assets/fonts/LICENSES.md @@ -0,0 +1,20 @@ +# 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. diff --git a/assets/fonts/baloo2-700.woff2 b/assets/fonts/baloo2-700.woff2 new file mode 100644 index 0000000..ceb751a Binary files /dev/null and b/assets/fonts/baloo2-700.woff2 differ diff --git a/assets/fonts/quicksand-400.woff2 b/assets/fonts/quicksand-400.woff2 new file mode 100644 index 0000000..8018cfd Binary files /dev/null and b/assets/fonts/quicksand-400.woff2 differ diff --git a/assets/fonts/quicksand-700.woff2 b/assets/fonts/quicksand-700.woff2 new file mode 100644 index 0000000..42f7a0b Binary files /dev/null and b/assets/fonts/quicksand-700.woff2 differ diff --git a/blocks/alphabet_strip.ninjatpl b/blocks/alphabet_strip.ninjatpl index 02a95bb..c79355f 100644 --- a/blocks/alphabet_strip.ninjatpl +++ b/blocks/alphabet_strip.ninjatpl @@ -1 +1 @@ - + diff --git a/blocks/alphabet_strip.schema.json b/blocks/alphabet_strip.schema.json index 84b57b1..0e86f4c 100644 --- a/blocks/alphabet_strip.schema.json +++ b/blocks/alphabet_strip.schema.json @@ -5,11 +5,22 @@ "type": "object", "properties": { "letters": { - "type": "string", + "type": "array", "title": "Letters", - "description": "Sequence of letters to render. Each character becomes one tile.", - "default": "ABCDEFGHIJ", - "x-editor": "text" + "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" + } + } + } }, "colorMode": { "type": "string", diff --git a/blocks/art_wall.ninjatpl b/blocks/art_wall.ninjatpl new file mode 100644 index 0000000..2a99f71 --- /dev/null +++ b/blocks/art_wall.ninjatpl @@ -0,0 +1 @@ +
{% set showNames = true %}{% if showChildName == false %}{% set showNames = false %}{% endif %}{% if title %}

{{ title }}

{% endif %}{% if not items %}
No artworks yet — pin a few pieces to fill the wall.
{% else %}{% endif %}
diff --git a/blocks/art_wall.schema.json b/blocks/art_wall.schema.json new file mode 100644 index 0000000..2c76c8e --- /dev/null +++ b/blocks/art_wall.schema.json @@ -0,0 +1,51 @@ +{ + "$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" + } + } + } + } + } +} diff --git a/blocks/big_cta.ninjatpl b/blocks/big_cta.ninjatpl deleted file mode 100644 index 7150b32..0000000 --- a/blocks/big_cta.ninjatpl +++ /dev/null @@ -1 +0,0 @@ -
{% if label %}{{ label }}{% else %}Add a label and link to publish this call-to-action.{% endif %}
diff --git a/blocks/big_cta.schema.json b/blocks/big_cta.schema.json deleted file mode 100644 index b8d11e6..0000000 --- a/blocks/big_cta.schema.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "$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" - } - } -} diff --git a/blocks/blocks.yaml b/blocks/blocks.yaml index a070299..ede4ee6 100644 --- a/blocks/blocks.yaml +++ b/blocks/blocks.yaml @@ -1,54 +1,34 @@ -# Kindergarten theme block definitions (codeless .bnp, WO-WZ-021). +# Kindergarten theme persona block definitions (codeless .bnp, WO-TF-019). # Keys are fully qualified (kindergarten:) 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 hardcoded data-block attributes and the master_pages.json references. +# 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. 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: Decorative letter band; jumps to anchors when used as TOC. + description: A-Z learning band of colorful crayon letter tiles. category: navigation schema: alphabet_strip.schema.json template: alphabet_strip.ninjatpl - key: kindergarten:schedule title: Day Schedule - description: Daycare/classroom day plan with crayon time-pills. + description: Daily class plan with crayon time-pills and activity icons. category: content schema: schedule.schema.json template: schedule.ninjatpl - - key: kindergarten:gallery_of_art - title: Gallery of Art - description: Polaroid-style grid for kid artwork. + - key: kindergarten:art_wall + title: Art Wall + description: Polaroid wall of children's artwork with artist names. category: content - 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'). + 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. category: content - 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 + schema: story_time.schema.json + template: story_time.ninjatpl diff --git a/blocks/footer.ninjatpl b/blocks/footer.ninjatpl deleted file mode 100644 index c0c2186..0000000 --- a/blocks/footer.ninjatpl +++ /dev/null @@ -1 +0,0 @@ - diff --git a/blocks/footer.schema.json b/blocks/footer.schema.json deleted file mode 100644 index 4bdefa2..0000000 --- a/blocks/footer.schema.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "$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" - } - } - } - } - } -} diff --git a/blocks/gallery_of_art.ninjatpl b/blocks/gallery_of_art.ninjatpl deleted file mode 100644 index f304e23..0000000 --- a/blocks/gallery_of_art.ninjatpl +++ /dev/null @@ -1 +0,0 @@ -
{% set showNames = true %}{% if showChildName == false %}{% set showNames = false %}{% endif %}{% if title %}

{{ title }}

{% endif %}{% if not items %}
No artworks yet — add one to fill the gallery wall.
{% else %}{% endif %}
diff --git a/blocks/gallery_of_art.schema.json b/blocks/gallery_of_art.schema.json deleted file mode 100644 index 863e631..0000000 --- a/blocks/gallery_of_art.schema.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "$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" - } - } - } - } - } -} diff --git a/blocks/mascot_hero.ninjatpl b/blocks/mascot_hero.ninjatpl deleted file mode 100644 index d1a7de3..0000000 --- a/blocks/mascot_hero.ninjatpl +++ /dev/null @@ -1 +0,0 @@ -
{% if headline %}

{{ headline }}

{% else %}

Welcome!

{% endif %}{% if tagline %}

{{ tagline }}

{% endif %}{% if ctaLabel and ctaHref %}{% endif %}
{% if mascot == "blocks" %}ABC{% elif mascot == "star" %}{% elif mascot == "balloon" %}{% else %}{% endif %}
diff --git a/blocks/mascot_hero.schema.json b/blocks/mascot_hero.schema.json deleted file mode 100644 index 45eb3d2..0000000 --- a/blocks/mascot_hero.schema.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$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" - } - } -} diff --git a/blocks/numbers_counter.ninjatpl b/blocks/numbers_counter.ninjatpl deleted file mode 100644 index c2037eb..0000000 --- a/blocks/numbers_counter.ninjatpl +++ /dev/null @@ -1 +0,0 @@ -
{% if not items %}
No numbers yet — add a few stats to celebrate.
{% else %}
{% for item in items %}
{{ item.number|integer }}{% if item.label %}{{ item.label }}{% endif %}
{% endfor %}
{% endif %}
diff --git a/blocks/numbers_counter.schema.json b/blocks/numbers_counter.schema.json deleted file mode 100644 index 10757a2..0000000 --- a/blocks/numbers_counter.schema.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$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" - } - } - } - } - } -} diff --git a/blocks/story_time.ninjatpl b/blocks/story_time.ninjatpl new file mode 100644 index 0000000..436e4fb --- /dev/null +++ b/blocks/story_time.ninjatpl @@ -0,0 +1 @@ +
{% if cover %}Cover of {{ bookTitle|default:'the story' }}{% else %}{% endif %}
Story time{% if bookTitle %}

{{ bookTitle }}

{% endif %}{% if line %}

"{{ line }}"

{% else %}

Add a line from the story to read together.

{% endif %}{% if author %}
by {{ author }}
{% endif %}
diff --git a/blocks/story_time.schema.json b/blocks/story_time.schema.json new file mode 100644 index 0000000..9f2f7b8 --- /dev/null +++ b/blocks/story_time.schema.json @@ -0,0 +1,32 @@ +{ + "$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" + } + } +} diff --git a/blocks/storybook_quote.ninjatpl b/blocks/storybook_quote.ninjatpl deleted file mode 100644 index 998a266..0000000 --- a/blocks/storybook_quote.ninjatpl +++ /dev/null @@ -1 +0,0 @@ -
{% if quote %}

"{{ quote }}"

{% else %}

Add a quote to inspire your readers.

{% endif %}{% if author %}
— {{ author }}
{% endif %}
{% if illustration %}Illustration accompanying the quote{% else %}{% endif %}
diff --git a/blocks/storybook_quote.schema.json b/blocks/storybook_quote.schema.json deleted file mode 100644 index 4c9bac6..0000000 --- a/blocks/storybook_quote.schema.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$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" - } - } -} diff --git a/fonts.json b/fonts.json index fe51488..f866d93 100644 --- a/fonts.json +++ b/fonts.json @@ -1 +1,18 @@ -[] +[ + { + "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" } + ] + } +] diff --git a/manifest.yaml b/manifest.yaml index bc49032..6be2b51 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -1,41 +1,110 @@ -# Kindergarten theme — codeless .bnp manifest (WO-WZ-021 Phase B). +# Kindergarten theme — codeless .bnp manifest (WO-TF-019, Wave A). # Synthesized into manifest.pb by `ninja plugin build` (no plugin.wasm). theme_presets: presets.json bundled_fonts: fonts.json master_pages: master_pages.json +required_icon_packs: ["lucide", "simple-icons"] system_templates: - key: kindergarten title: Kindergarten - description: "Primary-colored, hand-lettered theme for schools, daycares, toy shops, museums, and kids' publishers." + description: "Preschool / daycare theme — crayon-drawn borders, sticker badges, confetti, and a bouncing-mascot canvas hero. Warm, safe, and playful, in a bright classroom by day and a quiet-time navy by night." page_templates: - system: kindergarten key: default title: Default - description: "Standard rounded layout with header, main, footer" + description: "Standard rounded page — dotted-paper background, header, main, footer" + slots: [header, main, footer] + - system: kindergarten + key: full-width + title: Full Width + description: "Edge-to-edge classroom / gallery showcase, no max-width" slots: [header, main, footer] - system: kindergarten key: landing title: Landing - description: "Mascot-led hero plus stacked feature sections" - slots: [hero, main, cta, footer] + description: "Bouncing-mascot canvas hero, sticker feature cards, big confetti CTA" + slots: [header, main, footer] - system: kindergarten key: article title: Story Page - description: "Picture-book reading layout for blog posts and stories" + description: "Picture-book reading layout — prose column plus a sticker note aside" slots: [header, main, aside, footer] - system: kindergarten - key: full-width - title: Full Width - description: "Edge-to-edge gallery / classroom showcase" + key: blog-index + title: Blog Index + description: "Story-wall feed of rounded post cards" + slots: [header, main, footer] + - system: kindergarten + key: contact + title: Contact + description: "Friendly enrol / contact page — form card plus a contact sticker card" + slots: [header, main, footer] + - system: kindergarten + key: auth + title: Auth + description: "Centered rounded sign-in card for login / register / reset" slots: [header, main, footer] +# All 49 mandatory-tier builtin overrides (OVERRIDE-CONTRACT §1), dashed keys. template_overrides: + # Chrome (4) + - { template: kindergarten, block: navbar } + - { template: kindergarten, block: footer } + - { template: kindergarten, block: utility-bar } + - { template: kindergarten, block: announcement-bar } + # Marketing (12) + - { template: kindergarten, block: hero } + - { template: kindergarten, block: feature-grid } + - { template: kindergarten, block: rich-section } + - { template: kindergarten, block: stats } + - { template: kindergarten, block: testimonial } + - { template: kindergarten, block: logo-strip } + - { template: kindergarten, block: pricing } + - { template: kindergarten, block: team } + - { template: kindergarten, block: faq } + - { template: kindergarten, block: timeline } + - { template: kindergarten, block: cta } + - { template: kindergarten, block: countdown } + # Primitives (11) - { template: kindergarten, block: heading } - { template: kindergarten, block: text } + - { template: kindergarten, block: rich-text } + - { template: kindergarten, block: image } + - { template: kindergarten, block: quote } + - { template: kindergarten, block: divider } - { template: kindergarten, block: button } - { template: kindergarten, block: card } + - { template: kindergarten, block: gallery } + - { template: kindergarten, block: video-embed } + - { template: kindergarten, block: audio-embed } + # Commerce / local (6) + - { template: kindergarten, block: menu-list } + - { template: kindergarten, block: event-list } + - { template: kindergarten, block: contact-form } + - { template: kindergarten, block: contact-card } + - { template: kindergarten, block: hours-location } + - { template: kindergarten, block: social-links } + # Blog family (12) + - { template: kindergarten, block: blog-hero } + - { template: kindergarten, block: featured-posts } + - { template: kindergarten, block: related-posts } + - { template: kindergarten, block: popular-posts } + - { template: kindergarten, block: category-list } + - { template: kindergarten, block: post-hero } + - { template: kindergarten, block: post-metadata } + - { template: kindergarten, block: author-bio } + - { template: kindergarten, block: author-bio-hero } + - { template: kindergarten, block: newsletter-signup } + - { template: kindergarten, block: breadcrumbs } + - { template: kindergarten, block: sidebar-nav } + # Auth surface (3) + - { template: kindergarten, block: auth-form } + - { template: kindergarten, block: password-reset-form } + - { template: kindergarten, block: auth-status } + # System (1) + - { template: kindergarten, block: page-suggestions } email_wrappers: - kindergarten @@ -43,490 +112,370 @@ email_wrappers: css: input_css_append: | /* ============================================================ - Kindergarten — Primary-colored, hand-lettered theme - ------------------------------------------------------------ - Token strategy: every color reference goes through - `hsl(var(--))` and font-family goes through - `var(--font-{heading|body|mono}, )`. + Kindergarten — Preschool / daycare theme (WO-TF-019, Wave A). + Crayon borders, sticker badges, confetti, primary-colour joy, + and a GO-BIG bouncing-mascot canvas hero (hero/landing only). + Every colour goes through hsl(var(--token)); every font-family + goes through var(--font-{heading|body|mono}, ). + No hardcoded hex / rgb / named colours. ============================================================ */ - - /* ---- Typography (CSS-variable consumers + fallbacks) ------- */ - - .kg-page { - font-family: var(--font-body, "Nunito", "Quicksand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif); - line-height: 1.7; - background-color: hsl(var(--background)); - color: hsl(var(--foreground)); + + /* ---- Typography -------------------------------------------- */ + /* Families flow through the admin-bound CSS variables; the + fallback leads with the bundled OFL family so the intended look + survives before the admin assigns a slot. @font-face for the + bundled families is auto-emitted from fonts.json. */ + + .kg-body { + font-family: var(--font-body, "Quicksand", "Nunito", system-ui, -apple-system, "Segoe UI", sans-serif); } - - .kg-display, - .kg-page h1, - .kg-page h2, - .kg-page h3, - .kg-page h4, - .kg-page h5, - .kg-page h6 { - font-family: var(--font-heading, "Quicksand", "Sniglet", "Nunito", -apple-system, sans-serif); - font-weight: 700; - letter-spacing: -0.01em; + .kg-display { + font-family: var(--font-heading, "Baloo 2", "Fredoka", "Quicksand", system-ui, sans-serif); + font-weight: 700; + letter-spacing: -0.01em; } - .kg-mono { - font-family: var(--font-mono, "Fira Code", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace); + font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace); } - - /* ---- Layout utilities -------------------------------------- */ - - .kg-section { - padding-block: 4rem; + .kg-prose, + .kg-prose p, + .kg-prose li { + font-family: var(--font-body, "Quicksand", "Nunito", sans-serif); + line-height: 1.75; } - - .kg-container { - max-width: 72rem; - margin-inline: auto; - padding-inline: 1.5rem; + .kg-prose code { + color: hsl(var(--primary)); + background-color: hsl(var(--muted)); + padding: 0.125rem 0.4rem; + border-radius: 0.5rem; + font-family: var(--font-mono, ui-monospace, Menlo, monospace); + font-size: 0.875em; } - - /* ---- Rounded primitives ------------------------------------ */ - - .kg-chip { - border-radius: 24px; - padding: 0.5rem 1rem; - display: inline-flex; - align-items: center; - gap: 0.5rem; + /* Friendly kicker: rounded, bold, primary — carries enough on its + own that it is never a silent no-op used alone. */ + .kg-eyebrow { + font-family: var(--font-heading, "Baloo 2", "Quicksand", sans-serif); + font-weight: 700; + font-size: 0.85rem; + letter-spacing: 0.02em; + color: hsl(var(--primary)); + display: inline-flex; + align-items: center; + gap: 0.4rem; } - - .kg-card { - border-radius: 32px; - background-color: hsl(var(--card)); - color: hsl(var(--card-foreground, var(--foreground))); - padding: 2rem; - border: 1px solid hsl(var(--border)); - position: relative; - overflow: hidden; - } - - .kg-card::before { - content: ""; - position: absolute; - inset: 0 0 auto 0; - height: 4px; - background-color: hsl(var(--primary)); - } - - .kg-hero-panel { - border-radius: 48px; - padding: clamp(2rem, 6vw, 4rem); - position: relative; - overflow: hidden; - background-color: hsl(var(--accent) / 0.15); - } - - .kg-pill { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - padding: 1.25rem 2.5rem; - border-radius: 9999px; - font-family: var(--font-heading, "Quicksand", "Sniglet", "Nunito", sans-serif); - font-weight: 700; - font-size: 1.125rem; - background-color: hsl(var(--primary)); - color: hsl(var(--primary-foreground)); - border: 0; - cursor: pointer; - box-shadow: 0 4px 0 0 hsl(var(--foreground) / 0.15); - text-decoration: none; - transition: transform 120ms ease, box-shadow 120ms ease; - } - - .kg-pill:hover { - transform: translateY(-1px); - box-shadow: 0 5px 0 0 hsl(var(--foreground) / 0.15); - } - - .kg-pill:active { - transform: translateY(2px); - box-shadow: 0 2px 0 0 hsl(var(--foreground) / 0.15); - } - - .kg-pill:focus-visible { - outline: 3px solid hsl(var(--ring)); - outline-offset: 3px; - } - - /* CTA colorways */ - .kg-pill-red { background-color: hsl(var(--destructive)); color: hsl(var(--destructive-foreground)); } - .kg-pill-blue { background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground)); } - .kg-pill-yellow { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); } - .kg-pill-green { background-color: hsl(145 65% 45%); color: hsl(0 0% 100%); } - - /* ---- Crayon underline (utility on headings) ---------------- */ - + + /* ---- Crayon underline (decorative heading accent) ---------- */ + .kg-crayon-underline { - position: relative; - display: inline-block; + position: relative; + display: inline-block; } - .kg-crayon-underline::after { - content: ""; - position: absolute; - left: 0; - right: 0; - bottom: -6px; - height: 8px; - background: linear-gradient(90deg, - hsl(var(--primary)) 0 22%, - hsl(var(--accent)) 22% 50%, - hsl(var(--secondary)) 50% 78%, - currentColor 78% 100%); - border-radius: 4px; - transform: rotate(-1deg); - z-index: -1; + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: -6px; + height: 8px; + background: linear-gradient(90deg, + hsl(var(--primary)) 0 26%, + hsl(var(--accent)) 26% 52%, + hsl(var(--secondary)) 52% 78%, + hsl(var(--primary)) 78% 100%); + border-radius: 6px; + transform: rotate(-1deg); + z-index: -1; } - - /* ---- Numeral badges ---------------------------------------- */ - - .kg-numeral { - display: inline-flex; - align-items: center; - justify-content: center; - width: 96px; - height: 96px; - border-radius: 50%; - aspect-ratio: 1 / 1; - background-color: hsl(var(--accent)); - color: hsl(var(--accent-foreground)); - font-family: var(--font-heading, "Quicksand", "Sniglet", sans-serif); - font-weight: 700; - font-size: 2.5rem; - box-shadow: 0 4px 0 0 hsl(var(--foreground) / 0.15); + + /* ---- Crayon / sticker primitives (drop-in surface chrome) -- */ + /* kg-crayon-shadow: chunky hand-cut offset shadow + playful lift. + Pairs with Tailwind border-* / rounded-* on the same element. */ + + .kg-crayon-shadow { + box-shadow: 0 4px 0 0 hsl(var(--foreground) / 0.12); + transition: transform 160ms ease, box-shadow 160ms ease; } - - /* ---- Polaroid frame for gallery_of_art --------------------- */ - + .kg-crayon-shadow:hover, + .kg-crayon-shadow:focus-within { + transform: translateY(-2px); + box-shadow: 0 8px 0 0 hsl(var(--foreground) / 0.14); + } + + /* kg-drop: chunky flat button shadow (the crayon "sticker" edge). */ + .kg-drop { + box-shadow: 0 4px 0 0 hsl(var(--foreground) / 0.15); + } + + /* kg-pop: playful hover bounce for buttons / interactive chips. */ + .kg-pop { + transition: transform 140ms cubic-bezier(0.34, 1.56, 0.64, 1); + } + .kg-pop:hover, + .kg-pop:focus-visible { + transform: translateY(-2px) scale(1.03); + } + .kg-pop:active { + transform: translateY(1px) scale(0.98); + } + + /* kg-sticker: peel-sticker badge — rounded, chunky, slight tilt. */ + .kg-sticker { + display: inline-flex; + align-items: center; + gap: 0.35rem; + padding: 0.3rem 0.85rem; + border-radius: 9999px; + font-family: var(--font-heading, "Baloo 2", "Quicksand", sans-serif); + font-weight: 700; + font-size: 0.8rem; + background-color: hsl(var(--accent)); + color: hsl(var(--accent-foreground)); + box-shadow: 0 3px 0 0 hsl(var(--foreground) / 0.12); + transform: rotate(-2deg); + } + + /* Global focus-visible ring (accessibility). */ + .kg-page a:focus-visible, + .kg-page button:focus-visible, + .kg-page input:focus-visible, + .kg-page textarea:focus-visible, + .kg-page select:focus-visible { + outline: 3px solid hsl(var(--ring)); + outline-offset: 2px; + } + + /* ---- Page shell ------------------------------------------- */ + + .kg-page { + font-family: var(--font-body, "Quicksand", "Nunito", system-ui, sans-serif); + line-height: 1.7; + background-color: hsl(var(--background)); + color: hsl(var(--foreground)); + } + .kg-page h1, .kg-page h2, .kg-page h3, + .kg-page h4, .kg-page h5, .kg-page h6 { + font-family: var(--font-heading, "Baloo 2", "Quicksand", sans-serif); + font-weight: 700; + letter-spacing: -0.01em; + } + .kg-container { + max-width: 72rem; + margin-inline: auto; + padding-inline: 1.5rem; + } + .kg-empty { + text-align: center; + padding: 2rem; + border-radius: 1.5rem; + border: 3px dashed hsl(var(--border)); + color: hsl(var(--muted-foreground)); + font-family: var(--font-body, "Quicksand", sans-serif); + } + /* Dotted-paper page background layer. */ + .kg-dotted-paper { + background-color: hsl(var(--background)); + background-image: radial-gradient(circle, hsl(var(--muted-foreground) / 0.14) 1px, transparent 1.6px); + background-size: 26px 26px; + } + + /* ---- Persona-block furniture ------------------------------ */ + + .kg-hero-panel { + border-radius: 2.5rem; + padding: clamp(2rem, 6vw, 4rem); + position: relative; + overflow: hidden; + background-color: hsl(var(--accent) / 0.15); + } + .kg-card { + border-radius: 2rem; + background-color: hsl(var(--card)); + color: hsl(var(--card-foreground)); + padding: 2rem; + border: 3px solid hsl(var(--border)); + position: relative; + overflow: hidden; + } + + /* Alphabet strip (persona block). */ + .kg-alphabet-strip { + display: flex; + flex-wrap: wrap; + gap: 0.6rem; + padding: 1rem; + } + .kg-alphabet-letter { + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + width: 56px; + height: 56px; + border-radius: 1.25rem; + font-family: var(--font-heading, "Baloo 2", "Quicksand", sans-serif); + font-weight: 700; + font-size: 1.5rem; + box-shadow: 0 4px 0 0 hsl(var(--foreground) / 0.12); + } + .kg-alphabet-letter:nth-child(4n+1) { background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground)); } + .kg-alphabet-letter:nth-child(4n+2) { background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground)); } + .kg-alphabet-letter:nth-child(4n+3) { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); } + .kg-alphabet-letter:nth-child(4n+4) { background-color: hsl(var(--destructive)); color: hsl(var(--destructive-foreground)); } + .kg-alphabet-mono .kg-alphabet-letter { background-color: hsl(var(--muted)); color: hsl(var(--muted-foreground)); } + .kg-alphabet-primary .kg-alphabet-letter { background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground)); } + + /* Day schedule (persona block). */ + .kg-schedule-item { + display: grid; + grid-template-columns: auto 1fr auto; + gap: 1rem; + align-items: center; + padding: 1rem 1.25rem; + border-radius: 1.5rem; + background-color: hsl(var(--card)); + border: 3px solid hsl(var(--border)); + margin-bottom: 0.75rem; + } + .kg-schedule-time { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.25rem 0.85rem; + border-radius: 9999px; + background-color: hsl(var(--accent)); + color: hsl(var(--accent-foreground)); + font-family: var(--font-mono, ui-monospace, monospace); + font-weight: 700; + } + .kg-schedule-activity { color: hsl(var(--foreground)); } + .kg-schedule-icon { color: hsl(var(--primary)); } + + /* Art-wall polaroid (persona block). */ + .kg-gallery-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1.5rem; + } + @media (min-width: 640px) { .kg-gallery-grid { grid-template-columns: repeat(2, 1fr); } } + @media (min-width: 1024px) { .kg-gallery-grid { grid-template-columns: repeat(3, 1fr); } } .kg-polaroid { - background-color: hsl(var(--card)); - border-radius: 16px; - padding-top: 12px; - padding-inline: 12px; - padding-bottom: 36px; - box-shadow: 0 6px 24px hsl(var(--foreground) / 0.12); - transform: rotate(-1deg); - transition: transform 200ms ease; + background-color: hsl(var(--card)); + border-radius: 1rem; + padding: 12px 12px 36px; + box-shadow: 0 6px 24px hsl(var(--foreground) / 0.12); + transform: rotate(-1.5deg); + transition: transform 200ms ease; } - - .kg-polaroid:nth-child(2n) { transform: rotate(1.5deg); } - .kg-polaroid:nth-child(3n) { transform: rotate(-0.5deg); } - .kg-polaroid:hover { transform: rotate(0deg); } - + .kg-polaroid:nth-child(2n) { transform: rotate(1.5deg); } + .kg-polaroid:nth-child(3n) { transform: rotate(-0.5deg); } + .kg-polaroid:hover { transform: rotate(0deg); } .kg-polaroid > img, .kg-polaroid > .kg-polaroid-image { - display: block; - width: 100%; - aspect-ratio: 1 / 1; - object-fit: cover; - border-radius: 8px; - background-color: hsl(var(--muted)); + display: block; + width: 100%; + aspect-ratio: 1 / 1; + object-fit: cover; + border-radius: 0.5rem; + background-color: hsl(var(--muted)); } - .kg-polaroid-caption { - text-align: center; - margin-top: 0.75rem; - font-family: var(--font-heading, "Quicksand", "Sniglet", sans-serif); - color: hsl(var(--foreground)); + text-align: center; + margin-top: 0.75rem; + font-family: var(--font-heading, "Baloo 2", "Quicksand", sans-serif); + color: hsl(var(--foreground)); } - - /* ---- Alphabet strip ---------------------------------------- */ - - .kg-alphabet-strip { - display: flex; - overflow-x: auto; - gap: 0.5rem; - padding: 1rem; - scrollbar-width: thin; - } - - .kg-alphabet-letter { - flex: 0 0 auto; - display: inline-flex; - align-items: center; - justify-content: center; - width: 56px; - height: 56px; - border-radius: 24px; - background-color: hsl(var(--primary)); - color: hsl(var(--primary-foreground)); - font-family: var(--font-heading, "Quicksand", "Sniglet", sans-serif); - font-weight: 700; - font-size: 1.5rem; - box-shadow: 0 4px 0 0 hsl(var(--foreground) / 0.12); - } - - .kg-alphabet-letter:nth-child(4n+1) { background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground)); } - .kg-alphabet-letter:nth-child(4n+2) { background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground)); } - .kg-alphabet-letter:nth-child(4n+3) { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); } - .kg-alphabet-letter:nth-child(4n+4) { background-color: hsl(145 65% 45%); color: hsl(0 0% 100%); } - - .kg-alphabet-mono .kg-alphabet-letter { - background-color: hsl(var(--muted)); - color: hsl(var(--muted-foreground)); - } - - .kg-alphabet-primary .kg-alphabet-letter { - background-color: hsl(var(--primary)); - color: hsl(var(--primary-foreground)); - } - - /* ---- Day schedule pills ------------------------------------ */ - - .kg-schedule-item { - display: grid; - grid-template-columns: auto 1fr auto; - gap: 1rem; - align-items: center; - padding: 1rem; - border-radius: 24px; - background-color: hsl(var(--card)); - border: 1px solid hsl(var(--border)); - margin-bottom: 0.75rem; - } - - .kg-schedule-time { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0.25rem 0.75rem; - border-radius: 9999px; - background-color: hsl(var(--accent)); - color: hsl(var(--accent-foreground)); - font-family: var(--font-mono, "Fira Code", monospace); - font-weight: 700; - } - - .kg-schedule-activity { - font-family: var(--font-body, "Nunito", sans-serif); - color: hsl(var(--foreground)); - } - - .kg-schedule-icon { - color: hsl(var(--primary)); - } - - /* ---- Storybook quote --------------------------------------- */ - + + /* Story-time open book (persona block). */ .kg-storybook { - display: grid; - grid-template-columns: 1fr; - gap: 2rem; - padding: 2rem; - border-radius: 32px; - background-color: hsl(var(--card)); - border: 1px solid hsl(var(--border)); + display: grid; + grid-template-columns: 1fr; + gap: 2rem; + padding: 2rem; + border-radius: 2rem; + background-color: hsl(var(--card)); + border: 3px solid hsl(var(--border)); } - @media (min-width: 768px) { - .kg-storybook { - grid-template-columns: 1fr 1fr; - align-items: center; - } + .kg-storybook { grid-template-columns: 1fr 1fr; align-items: center; } } - .kg-storybook-quote { - font-family: var(--font-heading, "Quicksand", "Sniglet", serif); - font-size: clamp(1.25rem, 3vw, 2rem); - line-height: 1.4; - color: hsl(var(--foreground)); + font-family: var(--font-heading, "Baloo 2", "Quicksand", sans-serif); + font-size: clamp(1.25rem, 3vw, 2rem); + line-height: 1.4; + color: hsl(var(--foreground)); } - - .kg-storybook-author { - margin-top: 1rem; - color: hsl(var(--muted-foreground)); - font-family: var(--font-body, "Nunito", sans-serif); - } - .kg-storybook-illustration { - width: 100%; - aspect-ratio: 4 / 3; - object-fit: cover; - border-radius: 24px; - background-color: hsl(var(--muted)); + width: 100%; + aspect-ratio: 4 / 3; + object-fit: cover; + border-radius: 1.25rem; + background-color: hsl(var(--muted)); } - - /* ---- Friendly footer --------------------------------------- */ - - .kg-footer { - padding: 3rem 1.5rem; - background-color: hsl(var(--muted)); - color: hsl(var(--foreground)); - border-top: 6px dashed hsl(var(--primary)); - border-radius: 48px 48px 0 0; + + /* ---- GO-BIG motion: bouncing mascot + wiggling letters ----- */ + /* The canvas confetti/mascot showpiece lives ONLY on the hero + override (hero/landing). It lazy-inits on view, honours + prefers-reduced-motion, and degrades to a static confetti wash + with JS off. No canvas anywhere else in the theme. */ + + .kg-mascot { + transform-origin: center bottom; + animation: kg-bounce 2.4s ease-in-out infinite; } - - .kg-footer-mascot { - display: inline-flex; - align-items: center; - gap: 0.5rem; - margin-bottom: 1rem; - font-family: var(--font-heading, "Quicksand", sans-serif); + @keyframes kg-bounce { + 0%, 100% { transform: translateY(0) rotate(-1deg); } + 50% { transform: translateY(-16px) rotate(1deg); } } - - .kg-footer-signup { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; - margin-block: 1.5rem; - max-width: 32rem; + .kg-wiggle-letter { + display: inline-block; + animation: kg-wiggle 2.2s ease-in-out infinite; } - - .kg-footer-signup input { - flex: 1 1 12rem; - padding: 0.75rem 1rem; - border-radius: 9999px; - border: 2px solid hsl(var(--border)); - background-color: hsl(var(--input)); - color: hsl(var(--foreground)); - font-family: var(--font-body, "Nunito", sans-serif); + .kg-wiggle-letter:nth-child(2n) { animation-delay: 0.15s; } + .kg-wiggle-letter:nth-child(3n) { animation-delay: 0.3s; } + .kg-wiggle-letter:nth-child(4n) { animation-delay: 0.45s; } + @keyframes kg-wiggle { + 0%, 100% { transform: rotate(-5deg); } + 50% { transform: rotate(5deg); } } - - .kg-footer-signup input:focus-visible { - outline: 3px solid hsl(var(--ring)); - outline-offset: 2px; + + /* Confetti canvas + static no-JS fallback. */ + .kg-confetti-wrap { + position: absolute; + inset: 0; + overflow: hidden; + pointer-events: none; } - - .kg-social-row { - display: flex; - gap: 0.75rem; - margin-top: 1rem; + .kg-confetti-canvas { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + display: block; } - - .kg-social-link { - display: inline-flex; - align-items: center; - justify-content: center; - width: 40px; - height: 40px; - border-radius: 9999px; - background-color: hsl(var(--card)); - color: hsl(var(--primary)); - border: 1px solid hsl(var(--border)); + .kg-confetti-fallback { + position: absolute; + inset: 0; + pointer-events: none; + background-image: + radial-gradient(circle, hsl(var(--primary) / 0.55) 8px, transparent 9px), + radial-gradient(circle, hsl(var(--secondary) / 0.55) 6px, transparent 7px), + radial-gradient(circle, hsl(var(--accent) / 0.6) 5px, transparent 6px); + background-size: 90px 90px, 60px 60px, 40px 40px; + background-position: 12% 18%, 72% 28%, 42% 78%; + opacity: 0.55; } - - .kg-social-link:focus-visible { - outline: 3px solid hsl(var(--ring)); - outline-offset: 2px; - } - - /* ---- Mascot hero confetti dots ----------------------------- */ - - .kg-confetti { - position: absolute; - inset: 0; - pointer-events: none; - background-image: - radial-gradient(circle, hsl(var(--primary) / 0.5) 8px, transparent 9px), - radial-gradient(circle, hsl(var(--secondary) / 0.5) 6px, transparent 7px), - radial-gradient(circle, hsl(var(--accent) / 0.5) 5px, transparent 6px); - background-size: 80px 80px, 50px 50px, 30px 30px; - background-position: 10% 20%, 70% 30%, 40% 80%; - opacity: 0.6; - } - - /* ---- Dotted paper layer (page background) ------------------ */ - - .kg-dotted-paper { - background-color: hsl(var(--background)); - background-image: radial-gradient(circle, hsl(var(--muted-foreground) / 0.15) 1px, transparent 1.5px); - background-size: 24px 24px; - } - - /* ---- Heading override (numeral badge variant) -------------- */ - - .kg-heading-stepped { - display: flex; - align-items: center; - gap: 1rem; - } - - .kg-heading-step-badge { - display: inline-flex; - align-items: center; - justify-content: center; - width: 48px; - height: 48px; - border-radius: 50%; - background-color: hsl(var(--accent)); - color: hsl(var(--accent-foreground)); - font-family: var(--font-heading, "Quicksand", "Sniglet", sans-serif); - font-weight: 700; - font-size: 1.5rem; - flex: 0 0 auto; - } - - /* ---- Text override: rounded selection highlight ------------ */ - - .kg-text ::selection { - background-color: hsl(var(--accent)); - color: hsl(var(--accent-foreground)); - } - - .kg-text { - font-family: var(--font-body, "Nunito", sans-serif); - line-height: 1.7; - color: hsl(var(--foreground)); - } - - /* ---- Numbers counter grid ---------------------------------- */ - - .kg-numbers-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: 2rem; - text-align: center; - } - - .kg-numbers-item { - display: flex; - flex-direction: column; - align-items: center; - gap: 0.75rem; - } - - .kg-numbers-label { - font-family: var(--font-body, "Nunito", sans-serif); - color: hsl(var(--muted-foreground)); - text-transform: uppercase; - letter-spacing: 0.05em; - font-size: 0.875rem; - } - - /* ---- Gallery grid ------------------------------------------ */ - - .kg-gallery-grid { - display: grid; - grid-template-columns: 1fr; - gap: 1.5rem; - } - - @media (min-width: 640px) { .kg-gallery-grid { grid-template-columns: repeat(2, 1fr); } } - @media (min-width: 1024px) { .kg-gallery-grid { grid-template-columns: repeat(3, 1fr); } } - - /* ---- Big CTA panel (spec §9 button override) --------------- */ - - .kg-big-cta { - text-align: center; - padding: 3rem 1.5rem; - } - - /* ---- Empty-state placeholder ------------------------------- */ - - .kg-empty { - text-align: center; - padding: 2rem; - border-radius: 24px; - border: 2px dashed hsl(var(--border)); - color: hsl(var(--muted-foreground)); - font-family: var(--font-body, "Nunito", sans-serif); + + /* ---- Reduced motion: freeze everything -------------------- */ + + @media (prefers-reduced-motion: reduce) { + .kg-mascot, + .kg-wiggle-letter, + .kg-confetti-canvas { + animation: none !important; + } + .kg-confetti-canvas { display: none; } + .kg-pop:hover, + .kg-pop:focus-visible, + .kg-crayon-shadow:hover, + .kg-crayon-shadow:focus-within { transform: none; } } diff --git a/master_pages.json b/master_pages.json index fca0431..ea359a1 100644 --- a/master_pages.json +++ b/master_pages.json @@ -1,25 +1,23 @@ [ { - "key": "kindergarten:default-master", - "title": "Kindergarten Default Master", - "page_templates": ["default", "article"], + "key": "kindergarten:site-master", + "title": "Kindergarten Site Master", + "page_templates": ["default", "full-width", "landing", "article", "blog-index", "contact"], "blocks": [ - { "block_key": "navbar", "title": "Crayon Nav", "content": { "menuName": "main" }, "slot": "header", "sort_order": 0 }, - { "block_key": "kindergarten:alphabet_strip", "title": "ABC Strip", "content": { "letters": "ABCDEFGHIJ", "colorMode": "rainbow" }, "slot": "header", "sort_order": 1 }, - { "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main", "placeholder": "Page content" }, "slot": "main", "sort_order": 0 }, - { "block_key": "kindergarten:footer", "title": "Friendly Footer", "content": { "showSignup": "true", "mascotName": "Pip" }, "slot": "footer", "sort_order": 0 } + { "block_key": "navbar", "title": "Crayon Navbar", "content": { "menuName": "main", "logoType": "text", "logoText": { "text": "Little Sprouts" }, "companyName": "Little Sprouts", "stickyBehavior": "sticky", "cta": { "text": "Book a visit", "url": "/contact" } }, "slot": "header", "sort_order": 0 }, + { "block_key": "kindergarten:alphabet_strip", "title": "ABC Band", "content": { "colorMode": "rainbow", "letters": [ { "letter": "A" }, { "letter": "B" }, { "letter": "C" }, { "letter": "D" }, { "letter": "E" }, { "letter": "F" }, { "letter": "G" }, { "letter": "H" } ] }, "slot": "header", "sort_order": 1 }, + { "block_key": "slot", "title": "Main Content", "content": { "slotName": "main", "placeholder": "Page content" }, "slot": "main", "sort_order": 0 }, + { "block_key": "footer", "title": "Friendly Footer", "content": { "layout": "columns", "companyName": "Little Sprouts", "tagline": "A warm, playful place to learn and grow.", "copyright": "Little Sprouts Preschool", "showNewsletter": true, "newsletterTitle": "Stay in the loop", "newsletterDescription": "Notes home, event dates, and the odd finger-painting photo." }, "slot": "footer", "sort_order": 0 } ] }, { - "key": "kindergarten:landing-master", - "title": "Kindergarten Landing Master", - "page_templates": ["landing", "full-width"], + "key": "kindergarten:auth-master", + "title": "Kindergarten Auth Master", + "page_templates": ["auth"], "blocks": [ - { "block_key": "navbar", "title": "Crayon Nav", "content": { "menuName": "main" }, "slot": "header", "sort_order": 0 }, - { "block_key": "kindergarten:mascot_hero", "title": "Mascot Hero", "content": { "mascot": "pip", "headline": "Welcome to school!", "tagline": "Big fun, small humans." }, "slot": "hero", "sort_order": 0 }, - { "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 }, - { "block_key": "kindergarten:big_cta", "title": "Big Yellow CTA", "content": { "label": "Enrol today", "href": "/enrol", "colorVariant": "yellow" }, "slot": "cta", "sort_order": 0 }, - { "block_key": "kindergarten:footer", "title": "Friendly Footer", "content": { "showSignup": "true" }, "slot": "footer", "sort_order": 0 } + { "block_key": "navbar", "title": "Crayon Navbar", "content": { "menuName": "main", "logoType": "text", "logoText": { "text": "Little Sprouts" }, "companyName": "Little Sprouts" }, "slot": "header", "sort_order": 0 }, + { "block_key": "slot", "title": "Auth Content", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 }, + { "block_key": "footer", "title": "Friendly Footer", "content": { "layout": "minimal", "companyName": "Little Sprouts", "tagline": "A warm, playful place to learn and grow.", "copyright": "Little Sprouts Preschool" }, "slot": "footer", "sort_order": 0 } ] } ] diff --git a/plugin.mod b/plugin.mod index 9e6f6fc..6c5722d 100644 --- a/plugin.mod +++ b/plugin.mod @@ -3,7 +3,8 @@ name = "kindergarten" display_name = "Kindergarten" scope = "@themes" version = "0.2.0" -description = "Primary-colored, hand-lettered theme for schools, daycares, toy shops, museums, and kids' publishers." +description = "Primary-colored, hand-lettered theme for preschools and daycares — crayon borders, sticker badges, confetti, and a bouncing mascot showpiece." kind = "theme" categories = ["templates"] -tags = ["kids", "education", "playful", "family", "school", "museum", "friendly", "daycare", "publisher"] +tags = ["kids", "education", "playful", "family", "school", "daycare", "preschool", "friendly", "colorful"] +required_icon_packs = ["lucide", "simple-icons"] diff --git a/presets.json b/presets.json index 7b80111..aafb3f4 100644 --- a/presets.json +++ b/presets.json @@ -1,110 +1,252 @@ [ - { - "id": "recess", - "name": "Crayon Recess", - "description": "Light, sunny, primary-on-cream — for daytime classrooms.", - "theme": { - "lightColors": { - "background": "48 60% 97%", - "foreground": "220 40% 18%", - "card": "0 0% 100%", - "cardForeground": "220 40% 18%", - "popover": "0 0% 100%", - "popoverForeground": "220 40% 18%", - "primary": "4 80% 56%", - "primaryForeground": "0 0% 100%", - "secondary": "220 75% 56%", - "secondaryForeground": "0 0% 100%", - "muted": "48 40% 92%", - "mutedForeground": "220 25% 38%", - "accent": "48 95% 58%", - "accentForeground": "220 50% 14%", - "destructive": "0 84% 55%", - "destructiveForeground": "0 0% 100%", - "border": "220 30% 86%", - "input": "48 30% 90%", - "ring": "4 80% 56%" - }, - "mode": "light" - } - }, - { - "id": "chalkboard", - "name": "Chalkboard Night", - "description": "Deep navy chalkboard with crayon-bright accents.", - "theme": { - "darkColors": { - "background": "220 50% 10%", - "foreground": "48 60% 96%", - "card": "220 45% 14%", - "cardForeground": "48 60% 96%", - "popover": "220 45% 14%", - "popoverForeground": "48 60% 96%", - "primary": "48 95% 58%", - "primaryForeground": "220 50% 10%", - "secondary": "4 80% 60%", - "secondaryForeground": "0 0% 100%", - "muted": "220 35% 18%", - "mutedForeground": "220 20% 70%", - "accent": "145 70% 50%", - "accentForeground": "220 50% 10%", - "destructive": "0 84% 60%", - "destructiveForeground": "0 0% 100%", - "border": "220 35% 24%", - "input": "220 35% 20%", - "ring": "48 95% 58%" - }, - "mode": "dark" - } - }, { "id": "crayon-box", "name": "Crayon Box", - "description": "Full primary quartet — red, blue, yellow, green — in both light and dark.", + "description": "The default — crayon blue, red, and yellow. Bright classroom by day, quiet-time navy by night.", "theme": { + "mode": "both", "lightColors": { "background": "48 55% 98%", - "foreground": "220 45% 15%", + "foreground": "222 47% 16%", "card": "0 0% 100%", - "cardForeground": "220 45% 15%", + "cardForeground": "222 47% 16%", "popover": "0 0% 100%", - "popoverForeground": "220 45% 15%", - "primary": "220 78% 52%", + "popoverForeground": "222 47% 16%", + "primary": "222 80% 54%", "primaryForeground": "0 0% 100%", - "secondary": "4 78% 56%", + "secondary": "4 78% 57%", "secondaryForeground": "0 0% 100%", - "muted": "48 35% 93%", - "mutedForeground": "220 22% 38%", - "accent": "145 65% 45%", + "muted": "48 40% 93%", + "mutedForeground": "222 20% 40%", + "accent": "45 96% 56%", + "accentForeground": "222 50% 14%", + "destructive": "0 84% 55%", + "destructiveForeground": "0 0% 100%", + "border": "222 25% 86%", + "input": "48 30% 90%", + "ring": "222 80% 54%" + }, + "darkColors": { + "background": "224 46% 9%", + "foreground": "48 55% 95%", + "card": "224 42% 13%", + "cardForeground": "48 55% 95%", + "popover": "224 42% 13%", + "popoverForeground": "48 55% 95%", + "primary": "218 90% 66%", + "primaryForeground": "224 46% 9%", + "secondary": "4 84% 64%", + "secondaryForeground": "0 0% 100%", + "muted": "224 34% 17%", + "mutedForeground": "220 20% 70%", + "accent": "45 96% 62%", + "accentForeground": "224 46% 9%", + "destructive": "0 84% 62%", + "destructiveForeground": "0 0% 100%", + "border": "224 32% 22%", + "input": "224 32% 19%", + "ring": "45 96% 62%" + } + } + }, + { + "id": "sunny-recess", + "name": "Sunny Recess", + "description": "Warm red-and-sun classroom by day, soft navy with a glow-star yellow for nap time.", + "theme": { + "mode": "both", + "lightColors": { + "background": "40 70% 97%", + "foreground": "20 40% 18%", + "card": "0 0% 100%", + "cardForeground": "20 40% 18%", + "popover": "0 0% 100%", + "popoverForeground": "20 40% 18%", + "primary": "8 82% 56%", + "primaryForeground": "0 0% 100%", + "secondary": "205 75% 52%", + "secondaryForeground": "0 0% 100%", + "muted": "40 45% 92%", + "mutedForeground": "20 20% 40%", + "accent": "42 96% 55%", + "accentForeground": "20 45% 16%", + "destructive": "0 84% 55%", + "destructiveForeground": "0 0% 100%", + "border": "32 35% 85%", + "input": "40 35% 90%", + "ring": "8 82% 56%" + }, + "darkColors": { + "background": "222 45% 9%", + "foreground": "40 60% 95%", + "card": "222 40% 13%", + "cardForeground": "40 60% 95%", + "popover": "222 40% 13%", + "popoverForeground": "40 60% 95%", + "primary": "10 88% 64%", + "primaryForeground": "0 0% 100%", + "secondary": "205 82% 62%", + "secondaryForeground": "222 45% 9%", + "muted": "222 32% 17%", + "mutedForeground": "40 22% 72%", + "accent": "44 96% 62%", + "accentForeground": "222 45% 9%", + "destructive": "0 84% 62%", + "destructiveForeground": "0 0% 100%", + "border": "222 30% 22%", + "input": "222 30% 19%", + "ring": "44 96% 62%" + } + } + }, + { + "id": "bluebird", + "name": "Bluebird", + "description": "Calm blue-and-teal for reading corners. Deep quiet navy with a teal glow after dark.", + "theme": { + "mode": "both", + "lightColors": { + "background": "205 60% 98%", + "foreground": "218 45% 16%", + "card": "0 0% 100%", + "cardForeground": "218 45% 16%", + "popover": "0 0% 100%", + "popoverForeground": "218 45% 16%", + "primary": "212 84% 52%", + "primaryForeground": "0 0% 100%", + "secondary": "45 95% 55%", + "secondaryForeground": "218 50% 14%", + "muted": "205 40% 93%", + "mutedForeground": "218 22% 42%", + "accent": "175 70% 42%", "accentForeground": "0 0% 100%", "destructive": "0 84% 55%", "destructiveForeground": "0 0% 100%", - "border": "220 25% 85%", - "input": "48 25% 88%", - "ring": "48 95% 55%" + "border": "210 30% 86%", + "input": "205 30% 90%", + "ring": "212 84% 52%" }, "darkColors": { - "background": "220 40% 8%", - "foreground": "48 55% 95%", - "card": "220 40% 12%", - "cardForeground": "48 55% 95%", - "popover": "220 40% 12%", - "popoverForeground": "48 55% 95%", - "primary": "220 80% 60%", - "primaryForeground": "0 0% 100%", - "secondary": "4 80% 58%", - "secondaryForeground": "0 0% 100%", - "muted": "220 30% 16%", - "mutedForeground": "220 18% 68%", - "accent": "145 70% 50%", - "accentForeground": "220 40% 8%", - "destructive": "0 84% 60%", + "background": "220 48% 9%", + "foreground": "205 55% 95%", + "card": "220 44% 13%", + "cardForeground": "205 55% 95%", + "popover": "220 44% 13%", + "popoverForeground": "205 55% 95%", + "primary": "210 92% 66%", + "primaryForeground": "220 48% 9%", + "secondary": "45 96% 62%", + "secondaryForeground": "220 48% 9%", + "muted": "220 34% 17%", + "mutedForeground": "205 20% 72%", + "accent": "172 80% 52%", + "accentForeground": "220 48% 9%", + "destructive": "0 84% 62%", "destructiveForeground": "0 0% 100%", - "border": "220 30% 22%", - "input": "220 30% 18%", - "ring": "48 95% 60%" + "border": "220 32% 22%", + "input": "220 32% 19%", + "ring": "172 80% 52%" + } + } + }, + { + "id": "meadow", + "name": "Meadow Play", + "description": "Green-and-orange nature play by day, deep teal-navy with a warm glow for winding down.", + "theme": { + "mode": "both", + "lightColors": { + "background": "90 45% 97%", + "foreground": "140 35% 15%", + "card": "0 0% 100%", + "cardForeground": "140 35% 15%", + "popover": "0 0% 100%", + "popoverForeground": "140 35% 15%", + "primary": "145 62% 40%", + "primaryForeground": "0 0% 100%", + "secondary": "25 88% 55%", + "secondaryForeground": "0 0% 100%", + "muted": "90 30% 92%", + "mutedForeground": "140 18% 38%", + "accent": "48 95% 55%", + "accentForeground": "140 40% 14%", + "destructive": "0 84% 55%", + "destructiveForeground": "0 0% 100%", + "border": "110 25% 84%", + "input": "90 25% 89%", + "ring": "145 62% 40%" }, - "mode": "both" + "darkColors": { + "background": "200 40% 9%", + "foreground": "90 45% 94%", + "card": "200 36% 13%", + "cardForeground": "90 45% 94%", + "popover": "200 36% 13%", + "popoverForeground": "90 45% 94%", + "primary": "145 66% 52%", + "primaryForeground": "200 40% 9%", + "secondary": "25 90% 62%", + "secondaryForeground": "0 0% 100%", + "muted": "200 30% 17%", + "mutedForeground": "90 20% 70%", + "accent": "48 96% 62%", + "accentForeground": "200 40% 9%", + "destructive": "0 84% 62%", + "destructiveForeground": "0 0% 100%", + "border": "200 28% 22%", + "input": "200 28% 19%", + "ring": "48 96% 62%" + } + } + }, + { + "id": "bubblegum", + "name": "Bubblegum", + "description": "Playful pink-and-purple for toy shops and party rooms. Plum-navy with a soft glow at night.", + "theme": { + "mode": "both", + "lightColors": { + "background": "330 50% 98%", + "foreground": "300 35% 18%", + "card": "0 0% 100%", + "cardForeground": "300 35% 18%", + "popover": "0 0% 100%", + "popoverForeground": "300 35% 18%", + "primary": "330 80% 58%", + "primaryForeground": "0 0% 100%", + "secondary": "265 70% 60%", + "secondaryForeground": "0 0% 100%", + "muted": "330 35% 94%", + "mutedForeground": "300 18% 42%", + "accent": "42 95% 56%", + "accentForeground": "300 40% 16%", + "destructive": "0 84% 55%", + "destructiveForeground": "0 0% 100%", + "border": "320 30% 88%", + "input": "330 30% 92%", + "ring": "330 80% 58%" + }, + "darkColors": { + "background": "285 40% 9%", + "foreground": "330 45% 95%", + "card": "285 36% 13%", + "cardForeground": "330 45% 95%", + "popover": "285 36% 13%", + "popoverForeground": "330 45% 95%", + "primary": "330 88% 68%", + "primaryForeground": "285 40% 9%", + "secondary": "265 82% 72%", + "secondaryForeground": "285 40% 9%", + "muted": "285 30% 17%", + "mutedForeground": "330 20% 72%", + "accent": "45 96% 64%", + "accentForeground": "285 40% 9%", + "destructive": "0 84% 62%", + "destructiveForeground": "0 0% 100%", + "border": "285 28% 23%", + "input": "285 28% 20%", + "ring": "45 96% 64%" + } } } ] diff --git a/seed/seed.json b/seed/seed.json new file mode 100644 index 0000000..617bae7 --- /dev/null +++ b/seed/seed.json @@ -0,0 +1,228 @@ +{ + "demo": true, + "settings": { + "merge": { + "companyName": "Little Sprouts", + "tagline": "A warm, playful place to learn and grow." + } + }, + "data_tables": [ + { + "key": "contact_submissions", + "name": "Visit Requests", + "description": "Seeded contact-form submissions for the Little Sprouts demo. A submission becomes a row here and emails the site admins.", + "columns": [ + { "key": "name", "label": "Parent name", "type": "text", "required": true }, + { "key": "email", "label": "Email", "type": "email", "required": true }, + { "key": "childAge", "label": "Child's age", "type": "text", "required": false }, + { "key": "message", "label": "Message", "type": "text", "required": true } + ] + } + ], + "pages": [ + { + "slug": "/", + "title": "Little Sprouts", + "template_key": "landing", + "blocks": [ + { + "block_key": "hero", + "title": "Hero", + "slot": "main", + "sort_order": 1, + "content": { + "template": "centered", + "minHeight": "screen", + "eyebrow": "Now enrolling", + "headline": "A happy place to start", + "subheadline": "Play-based preschool for ages 2 to 5.", + "description": "Small groups, warm teachers, and a garden to dig in. Come and see a normal day.", + "ctaPrimary": { "text": "Book a visit", "url": "/contact" }, + "ctaSecondary": { "text": "See our day", "url": "/about" } + } + }, + { + "block_key": "feature-grid", + "title": "Why families choose us", + "slot": "main", + "sort_order": 2, + "content": { + "eyebrow": "Why families choose us", + "heading": "What your child gets", + "intro": "Every day has room to move, make, and rest.", + "layout": "card", + "columns": 3, + "items": [ + { "icon": "lucide:sun", "title": "Time outside every day", "text": "Rain or shine, we head to the garden and the yard." }, + { "icon": "lucide:book-open", "title": "Stories and songs", "text": "We read together, sing together, and make up our own tales." }, + { "icon": "lucide:palette", "title": "Room to make a mess", "text": "Paint, clay, glue, and glitter. Aprons on, big ideas out." } + ] + } + }, + { + "block_key": "kindergarten:schedule", + "title": "A day in the Sunflower Room", + "slot": "main", + "sort_order": 3, + "content": { + "title": "A day in the Sunflower Room", + "items": [ + { "time": "09:00", "activity": "Arrival and free play", "icon": "play" }, + { "time": "10:00", "activity": "Morning circle and songs", "icon": "book" }, + { "time": "10:30", "activity": "Snack and fruit", "icon": "snack" }, + { "time": "11:00", "activity": "Garden and outside play", "icon": "sun" }, + { "time": "12:00", "activity": "Lunch together", "icon": "snack" }, + { "time": "13:00", "activity": "Quiet time and rest", "icon": "nap" }, + { "time": "14:00", "activity": "Painting and making", "icon": "paint" } + ] + } + }, + { + "block_key": "kindergarten:art_wall", + "title": "Fresh off the easel", + "slot": "main", + "sort_order": 4, + "content": { + "title": "Fresh off the easel", + "showChildName": true, + "items": [ + { "childName": "Mia", "age": "4" }, + { "childName": "Noah", "age": "3" }, + { "childName": "Ada", "age": "5" } + ] + } + }, + { + "block_key": "cta", + "title": "CTA", + "slot": "main", + "sort_order": 5, + "content": { + "background": "band", + "heading": "Come and see for yourself", + "text": "Visits run most mornings. We will show you around and answer everything.", + "primaryLabel": "Book a visit", + "primaryUrl": "/contact" + } + } + ] + }, + { + "slug": "/about", + "title": "About us", + "template_key": "default", + "blocks": [ + { "block_key": "heading", "title": "Heading", "slot": "main", "sort_order": 1, "content": { "text": "About Little Sprouts", "level": 1 } }, + { "block_key": "rich-text", "title": "Story", "slot": "main", "sort_order": 2, "content": { "html": "

Little Sprouts started in a church hall with six children and a box of blocks. Families kept asking for more days, so we grew into a proper little school with a garden out the back.

Our rooms are small on purpose. Teachers learn every child by name in the first week. We follow the children's lead, keep the day gentle, and send them home tired and happy.

" } }, + { + "block_key": "kindergarten:story_time", + "title": "Story time", + "slot": "main", + "sort_order": 3, + "content": { + "bookTitle": "The Very Sleepy Bear", + "author": "our reading corner", + "line": "The bear yawned, and the whole forest yawned back." + } + }, + { "block_key": "divider", "title": "Divider", "slot": "main", "sort_order": 4, "content": { "variant": "line", "weight": "medium" } }, + { "block_key": "quote", "title": "Quote", "slot": "main", "sort_order": 5, "content": { "style": "pull", "quote": "My daughter runs to the door every morning.", "attribution": "A Little Sprouts parent" } } + ] + }, + { + "slug": "/blog", + "title": "Stories", + "template_key": "blog-index", + "blocks": [ + { "block_key": "card", "title": "Post: Settling in", "slot": "main", "sort_order": 1, "content": { "layout": "vertical", "title": "Settling in: the first week", "text": "

Little tips that help a new child feel at home, from the drop-off wave to the goodbye routine.

", "link": "/blog/settling-in", "linkLabel": "Read on" } }, + { "block_key": "card", "title": "Post: What to pack", "slot": "main", "sort_order": 2, "content": { "layout": "vertical", "title": "What to pack for a preschool day", "text": "

A short list of what to bring, what to label, and what to leave at home.

", "link": "/blog/what-to-pack", "linkLabel": "Read on" } }, + { "block_key": "card", "title": "Post: Reading at home", "slot": "main", "sort_order": 3, "content": { "layout": "vertical", "title": "Ways to read at home", "text": "

Reading together does not need to be fancy. Here is how we keep it easy and fun.

", "link": "/blog/reading-at-home", "linkLabel": "Read on" } } + ] + }, + { + "slug": "/blog/settling-in", + "title": "Settling in: the first week", + "template_key": "article", + "parent_slug": "/blog", + "blocks": [ + { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Settling in: the first week", "level": 1 } }, + { "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "

The first week is a big change for everyone. Some children walk straight in. Others hold on tight at the door. Both are normal.

Keep goodbyes short and warm. Tell your child when you will be back, then go. We will send a photo once they settle, and most do within minutes.

" } } + ] + }, + { + "slug": "/blog/what-to-pack", + "title": "What to pack for a preschool day", + "template_key": "article", + "parent_slug": "/blog", + "blocks": [ + { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "What to pack for a preschool day", "level": 1 } }, + { "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "

Pack a spare set of clothes, a hat, and a water bottle. Label everything with your child's name.

Leave toys from home in the car. They tend to go missing, and sharing them is hard on a new friend. We have plenty here to play with.

" } } + ] + }, + { + "slug": "/blog/reading-at-home", + "title": "Ways to read at home", + "template_key": "article", + "parent_slug": "/blog", + "blocks": [ + { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Ways to read at home", "level": 1 } }, + { "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "

Read the same book as often as they ask. Repetition is how young children learn the shape of a story.

Point at the pictures and ask what happens next. Let them turn the pages. The goal is a warm few minutes together, not a perfect reading.

" } } + ] + }, + { + "slug": "/contact", + "title": "Book a visit", + "template_key": "contact", + "blocks": [ + { + "block_key": "contact-form", + "title": "Book a visit", + "slot": "main", + "sort_order": 1, + "content": { + "heading": "Book a visit", + "description": "Tell us a little about your family. We will call to set up a time.", + "submitLabel": "Send", + "successMessage": "Thanks. We will be in touch very soon.", + "fields": [ + { "name": "name", "label": "Your name", "type": "text", "required": true, "width": "half" }, + { "name": "email", "label": "Email", "type": "email", "required": true, "width": "half" }, + { "name": "childAge", "label": "Your child's age", "type": "text", "required": false, "width": "full" }, + { "name": "message", "label": "Anything you would like us to know", "type": "textarea", "required": true, "width": "full" } + ], + "formConfig": { "enabled": true, "captchaEnabled": false, "targetTable": "contact_submissions" } + } + }, + { + "block_key": "contact-card", + "title": "Find us", + "slot": "main", + "sort_order": 2, + "content": { + "heading": "Find us", + "columns": 3, + "channels": [ + { "icon": "lucide:mail", "label": "Email", "value": "hello@littlesprouts.example", "link": "mailto:hello@littlesprouts.example" }, + { "icon": "lucide:phone", "label": "Phone", "value": "(02) 5550 1234", "link": "tel:+61255501234" }, + { "icon": "lucide:map-pin", "label": "Visit", "value": "14 Garden Lane, Riverside" } + ] + } + } + ] + }, + { + "slug": "/login", + "title": "Sign in", + "template_key": "auth", + "blocks": [ + { "block_key": "auth-form", "title": "Sign in", "slot": "main", "sort_order": 1, "content": { "default_tab": "login", "show_social": false } } + ] + } + ], + "menu_items": [ + { "menu": "main", "label": "Home", "page_slug": "/", "sort_order": 1 }, + { "menu": "main", "label": "About", "page_slug": "/about", "sort_order": 2 }, + { "menu": "main", "label": "Stories", "page_slug": "/blog", "sort_order": 3 }, + { "menu": "main", "label": "Visit", "page_slug": "/contact", "sort_order": 4 } + ] +} diff --git a/seed/workflows.json b/seed/workflows.json new file mode 100644 index 0000000..879dbfb --- /dev/null +++ b/seed/workflows.json @@ -0,0 +1,19 @@ +{ + "workflows": [ + { + "key": "contact_notify_admins", + "name": "Visit request to admins", + "description": "On a contact-form submission, email the site admins.", + "enabled": true, + "trigger": { "type": "row_inserted", "table": "contact_submissions" }, + "steps": [ + { + "type": "email", + "name": "Email admins", + "template": "Form Submission Notification", + "recipients": { "mode": "admins", "scope": "all" } + } + ] + } + ] +} diff --git a/templates/kindergarten/article.ninjatpl b/templates/kindergarten/article.ninjatpl index 7f8e017..74bbd99 100644 --- a/templates/kindergarten/article.ninjatpl +++ b/templates/kindergarten/article.ninjatpl @@ -1,16 +1,14 @@ - + {{ head_html|safe }} - + {{ admin_banner_html|safe }} -
{{ slots.header|safe }}
-
-
- {% if slots.main %}
{{ slots.main|safe }}
{% else %}
No content yet — start a story.
{% endif %} -
- {% if slots.aside %}{% endif %} -
-
{{ slots.footer|safe }}
+
{{ slots.header|safe }}
+
+
{{ slots.main|safe }}
+ {% if slots.aside %}{% endif %} +
+
{{ slots.footer|safe }}
{{ body_end_html|safe }} diff --git a/templates/kindergarten/auth.ninjatpl b/templates/kindergarten/auth.ninjatpl new file mode 100644 index 0000000..ea85e22 --- /dev/null +++ b/templates/kindergarten/auth.ninjatpl @@ -0,0 +1,13 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} +
{{ slots.header|safe }}
+
+
{% if slots.main %}{{ slots.main|safe }}{% else %}

Sign-in form goes here.

{% endif %}
+
+
{{ slots.footer|safe }}
+ {{ body_end_html|safe }} + + diff --git a/templates/kindergarten/blog-index.ninjatpl b/templates/kindergarten/blog-index.ninjatpl new file mode 100644 index 0000000..c31f398 --- /dev/null +++ b/templates/kindergarten/blog-index.ninjatpl @@ -0,0 +1,11 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} +
{{ slots.header|safe }}
+
{% if slots.main %}{{ slots.main|safe }}{% else %}
No stories yet — check back soon.
{% endif %}
+
{{ slots.footer|safe }}
+ {{ body_end_html|safe }} + + diff --git a/templates/kindergarten/contact.ninjatpl b/templates/kindergarten/contact.ninjatpl new file mode 100644 index 0000000..3087ad7 --- /dev/null +++ b/templates/kindergarten/contact.ninjatpl @@ -0,0 +1,11 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} +
{{ slots.header|safe }}
+
{% if slots.main %}{{ slots.main|safe }}{% else %}
Add a contact form to this page.
{% endif %}
+
{{ slots.footer|safe }}
+ {{ body_end_html|safe }} + + diff --git a/templates/kindergarten/default.ninjatpl b/templates/kindergarten/default.ninjatpl index 9c074c2..f49caad 100644 --- a/templates/kindergarten/default.ninjatpl +++ b/templates/kindergarten/default.ninjatpl @@ -1,13 +1,11 @@ - + {{ head_html|safe }} - + {{ admin_banner_html|safe }} -
{{ slots.header|safe }}
-
- {% if slots.main %}{{ slots.main|safe }}{% else %}
No content blocks assigned to this page.
{% endif %} -
-
{{ slots.footer|safe }}
+
{{ slots.header|safe }}
+
{% if slots.main %}{{ slots.main|safe }}{% else %}
No content blocks assigned to this page yet.
{% endif %}
+
{{ slots.footer|safe }}
{{ body_end_html|safe }} diff --git a/templates/kindergarten/full-width.ninjatpl b/templates/kindergarten/full-width.ninjatpl index ff0bce7..c97dde0 100644 --- a/templates/kindergarten/full-width.ninjatpl +++ b/templates/kindergarten/full-width.ninjatpl @@ -1,13 +1,11 @@ - + {{ head_html|safe }} - + {{ admin_banner_html|safe }} -
{{ slots.header|safe }}
-
- {% if slots.main %}{{ slots.main|safe }}{% else %}
No content blocks assigned to this page.
{% endif %} -
-
{{ slots.footer|safe }}
+
{{ slots.header|safe }}
+
{% if slots.main %}{{ slots.main|safe }}{% else %}
No content blocks assigned to this page yet.
{% endif %}
+
{{ slots.footer|safe }}
{{ body_end_html|safe }} diff --git a/templates/kindergarten/landing.ninjatpl b/templates/kindergarten/landing.ninjatpl index 18d3b7f..220ec3d 100644 --- a/templates/kindergarten/landing.ninjatpl +++ b/templates/kindergarten/landing.ninjatpl @@ -1,15 +1,11 @@ - + {{ head_html|safe }} - + {{ admin_banner_html|safe }} -
{{ slots.header|safe }}
-
{{ slots.hero|safe }}
-
- {% if slots.main %}{{ slots.main|safe }}{% endif %} -
-
{{ slots.cta|safe }}
-
{{ slots.footer|safe }}
+
{{ slots.header|safe }}
+
{% if slots.main %}{{ slots.main|safe }}{% else %}
No content blocks assigned to this page yet.
{% endif %}
+
{{ slots.footer|safe }}
{{ body_end_html|safe }} diff --git a/templates/overrides/kindergarten/announcement-bar.ninjatpl b/templates/overrides/kindergarten/announcement-bar.ninjatpl new file mode 100644 index 0000000..4ffb8c6 --- /dev/null +++ b/templates/overrides/kindergarten/announcement-bar.ninjatpl @@ -0,0 +1,21 @@ +{# announcement_bar (WO-TF-006): dismissable banner; dismissal persists in localStorage keyed by a content hash (variant + slugified message) so it reappears when the message changes; no-JS fallback is a static non-dismissable strip. Kindergarten override: mono broadcast readout — data-* + script preserved. #} +{% set anncls = "bg-primary/10 text-foreground" %} +{% if variant == "promo" %}{% set anncls = "bg-primary text-primary-foreground" %}{% elif variant == "alert" %}{% set anncls = "bg-destructive text-destructive-foreground" %}{% endif %} +
+
+

{{ message }}{% if linkText and linkUrl %} {{ linkText }}{% endif %}

+{% if dismissable %}{% endif %} +
+
+{% if dismissable %}{% endif %} diff --git a/templates/overrides/kindergarten/audio-embed.ninjatpl b/templates/overrides/kindergarten/audio-embed.ninjatpl new file mode 100644 index 0000000..f4689ab --- /dev/null +++ b/templates/overrides/kindergarten/audio-embed.ninjatpl @@ -0,0 +1,16 @@ +{# audio-embed kindergarten override: neon panel, mono meta. Facade script + data-bn-* attributes + {% img %} preserved byte-for-byte. #} +
{% if artwork %}
{% img artwork alt=title|default:"" class="h-full w-full object-cover" %}
{% endif %}
{% if title %}

{{ title }}

{% endif %}{% if artist %}

{{ artist }}

{% endif %}
{% if download %}::lucide:download:sm:: Download{% endif %}
diff --git a/templates/overrides/kindergarten/auth-form.ninjatpl b/templates/overrides/kindergarten/auth-form.ninjatpl new file mode 100644 index 0000000..c8a870c --- /dev/null +++ b/templates/overrides/kindergarten/auth-form.ninjatpl @@ -0,0 +1,77 @@ +{# auth-form kindergarten override — reproduces the builtin's security-critical wiring: hardcoded HTMX endpoints /api/auth/login + /api/auth/register (target #auth-message), /forgot-password link, OAuth start URLs gated on context.site.*, and the exact Cap widget markup when captchaEnabled. Only the chrome is themed. #} +{% if context.isPublicLoggedIn %} +
+

Session active as {{ context.publicUsername }}

+ My Account +
+{% else %} +{% set loginActive = "border-primary text-primary" %} +{% set registerActive = "border-transparent text-muted-foreground" %} +{% set loginHidden = "" %} +{% set registerHidden = "hidden" %} +{% if default_tab == "register" %}{% set loginActive = "border-transparent text-muted-foreground" %}{% set registerActive = "border-primary text-primary" %}{% set loginHidden = "hidden" %}{% set registerHidden = "" %}{% endif %} +{% set tabbase = "flex-1 py-2 text-center kg-mono text-sm font-medium border-b-2 transition-colors" %} +{% macro captcha() %} + + +{% endmacro %} +{% macro social() %}{% if show_social != false and (context.site.google_oauth_client_id or context.site.facebook_app_id) %}
+

or
+ {% if context.site.google_oauth_client_id %}Continue with Google{% endif %} + {% if context.site.facebook_app_id %}Continue with Facebook{% endif %} +
{% endif %}{% endmacro %} +
+
+
+ + +
+
+
+
+
+ {% if captchaEnabled %}{{ captcha() }}{% endif %} + +

Forgot password?

+
+ {{ social() }} +
+
+
+
+
+
+ {% if captchaEnabled %}{{ captcha() }}{% endif %} + +
+ {{ social() }} +
+
+ +{% endif %} diff --git a/templates/overrides/kindergarten/auth-status.ninjatpl b/templates/overrides/kindergarten/auth-status.ninjatpl new file mode 100644 index 0000000..752cf51 --- /dev/null +++ b/templates/overrides/kindergarten/auth-status.ninjatpl @@ -0,0 +1,36 @@ +{# auth-status kindergarten override — reproduces the builtin's hardcoded wiring: /login + /login?tab=register when logged out; /account, /account/reviews, and hx-post /api/auth/logout dropdown when logged in; hx-post /api/auth/resend-verification banner when unverified. State from context.*. #} +{% if not context.isPublicLoggedIn %} +
+ Login + Sign Up +
+{% else %} +{% if not context.publicEmailVerified %}
+ Verify your email address. +
{% endif %} +
+ + + +
+{% endif %} diff --git a/templates/overrides/kindergarten/author-bio-hero.ninjatpl b/templates/overrides/kindergarten/author-bio-hero.ninjatpl new file mode 100644 index 0000000..3d26615 --- /dev/null +++ b/templates/overrides/kindergarten/author-bio-hero.ninjatpl @@ -0,0 +1,21 @@ +{# author-bio-hero (kindergarten override) — author-page header from the `authors` provider. #} +{% if author %} +
+
+{% if showAvatar and author.avatar_url %}
{% img author.avatar_url alt=author.display_name class="h-full w-full object-cover" %}
{% endif %} +

{{ author.display_name }}

+{% if author.job_title or author.company %}

{{ author.job_title }}{% if author.job_title and author.company %} · {% endif %}{{ author.company }}

{% endif %} +{% if author.bio %}

{{ author.bio }}

{% endif %} +
+{% if showPostCount %}{{ author.post_count|default:0 }} post{% if author.post_count != 1 %}s{% endif %}{% endif %} +{% if author.location %}{{ author.location }}{% endif %} +
+{% if showSocial %}
+{% if author.website_url %}{% endif %} +{% if author.twitter_handle %}{% endif %} +{% if author.linkedin_url %}{% endif %} +{% if author.github_url %}{% endif %} +
{% endif %} +
+
+{% endif %} diff --git a/templates/overrides/kindergarten/author-bio.ninjatpl b/templates/overrides/kindergarten/author-bio.ninjatpl new file mode 100644 index 0000000..8705078 --- /dev/null +++ b/templates/overrides/kindergarten/author-bio.ninjatpl @@ -0,0 +1,13 @@ +{# author-bio (kindergarten override) — byline card sourced from the `post` provider's author_* fields. #} +{% if post.author_name %} + +{% endif %} diff --git a/templates/overrides/kindergarten/blog-hero.ninjatpl b/templates/overrides/kindergarten/blog-hero.ninjatpl new file mode 100644 index 0000000..6cb83c2 --- /dev/null +++ b/templates/overrides/kindergarten/blog-hero.ninjatpl @@ -0,0 +1,23 @@ +{# blog-hero (kindergarten override) — blog-index header + featured posts from the `posts` provider. #} +
+
+
+{% if title %}

{{ title }}

{% endif %} +{% if subtitle %}

{{ subtitle }}

{% endif %} +
+{% if posts %} +
+{% for post in posts %} + +{% endfor %} +
+{% endif %} +
+
diff --git a/templates/overrides/kindergarten/breadcrumbs.ninjatpl b/templates/overrides/kindergarten/breadcrumbs.ninjatpl new file mode 100644 index 0000000..e6980af --- /dev/null +++ b/templates/overrides/kindergarten/breadcrumbs.ninjatpl @@ -0,0 +1,15 @@ +{# breadcrumbs (kindergarten override) — trail from the `breadcrumbs` provider (page ancestry). #} +{# showHome prepends a Home link; separator is text ("/") for default or a chevron #} +{# for the arrows template. The trailing is_current item renders as plain text. #} +{% set arrows = template == "arrows" %} + diff --git a/templates/overrides/kindergarten/button.ninjatpl b/templates/overrides/kindergarten/button.ninjatpl index 3dae610..0990930 100644 --- a/templates/overrides/kindergarten/button.ninjatpl +++ b/templates/overrides/kindergarten/button.ninjatpl @@ -1 +1,2 @@ -{% if text %}{% set lbl = text %}{% else %}{% set lbl = label %}{% endif %}{% if lbl %}{{ lbl }}{% else %}Add label and link to render this button.{% endif %} +{# kindergarten button — keep the {% button %} tag intact (new fields: label / link / style / size / icon / iconPosition / newTab); wrap in a rounded kg-pop bounce so the host-rendered button gets the playful hover. #} +{% with align=align|default:"left" %}
{% if newTab %}{% button style|default:"primary" text=label|default:"Let's go" href=link|default:"#" size=size|default:"md" icon=icon|default:"" icon_position=iconPosition|default:"left" target="_blank" %}{% else %}{% button style|default:"primary" text=label|default:"Let's go" href=link|default:"#" size=size|default:"md" icon=icon|default:"" icon_position=iconPosition|default:"left" %}{% endif %}
{% endwith %} diff --git a/templates/overrides/kindergarten/card.ninjatpl b/templates/overrides/kindergarten/card.ninjatpl index e2e2d1e..6783733 100644 --- a/templates/overrides/kindergarten/card.ninjatpl +++ b/templates/overrides/kindergarten/card.ninjatpl @@ -1 +1,2 @@ -
{% if title %}

{{ title }}

{% endif %}{% if body %}
{{ body|safe }}
{% endif %}{% if not title and not body %}
Add a title or body to fill this card.
{% endif %}
+{# kindergarten card — crayon-bordered sticker panel; new builtin fields layout / link / media / title / text / linkLabel (reconciled from legacy body/image). #} +{% with layout=layout|default:"vertical" %}{% endwith %} diff --git a/templates/overrides/kindergarten/category-list.ninjatpl b/templates/overrides/kindergarten/category-list.ninjatpl new file mode 100644 index 0000000..fa93587 --- /dev/null +++ b/templates/overrides/kindergarten/category-list.ninjatpl @@ -0,0 +1,18 @@ +{# category-list (kindergarten override) — from the `categories` provider. list or pills. #} +{% set pills = style == "pills" %} +
+{% if heading %}

{{ heading }}

{% endif %} +{% if pills %} +
+{% for cat in categories %} +{{ cat.name }}{% if showCount %}{{ cat.post_count }}{% endif %} +{% endfor %} +
+{% else %} + +{% endif %} +
diff --git a/templates/overrides/kindergarten/contact-card.ninjatpl b/templates/overrides/kindergarten/contact-card.ninjatpl new file mode 100644 index 0000000..3f7e837 --- /dev/null +++ b/templates/overrides/kindergarten/contact-card.ninjatpl @@ -0,0 +1,18 @@ +{# contact-card kindergarten override: neon channel panels. All field reads + control flow preserved. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if description %}

{{ description }}

{% endif %} +
+ {% for channel in channels %} +
+ {% if channel.icon %}{% endif %} +
+ {% if channel.label %}
{{ channel.label }}
{% endif %} + {% if channel.value %}
{% if channel.link %}{{ channel.value }}{% else %}{{ channel.value }}{% endif %}
{% endif %} +
+
+ {% endfor %} +
+
+
diff --git a/templates/overrides/kindergarten/contact-form.ninjatpl b/templates/overrides/kindergarten/contact-form.ninjatpl new file mode 100644 index 0000000..5885984 --- /dev/null +++ b/templates/overrides/kindergarten/contact-form.ninjatpl @@ -0,0 +1,40 @@ +{# contact-form kindergarten override: terminal console chrome. The {% form %} block, honeypot, field loop, form.submitted/errors reads and _block_id/bid submit wiring are preserved byte-for-byte — only presentational classes change. #} +{% with bid=_block_id|default:context.blockId %} +
+
+ {% if form.submitted %} +
+
+

{{ form.message|default:successMessage|default:"Thanks — your message has been sent." }}

+
+ {% else %} + {% if heading %}

{{ heading }}

{% endif %} + {% if description %}

{{ description }}

{% endif %} + {% if form.errors._form %}
{{ form.errors._form }}
{% endif %} + {% with formAction="/api/blocks/"|add:bid|add:"/submit" formTarget="#contact-form-"|add:bid %} + {% form action=formAction hx_target=formTarget hx_swap="outerHTML" class="mt-8 grid grid-cols-1 gap-5 sm:grid-cols-2" %} + {% for field in fields %} +
+ {% if field.label %}{% endif %} + {% if field.type == "textarea" %} + + {% elif field.type == "select" %} + + {% else %} + + {% endif %} +
+ {% endfor %} + +
+ +
+ {% endform %} + {% endwith %} + {% endif %} +
+
+{% endwith %} diff --git a/templates/overrides/kindergarten/countdown.ninjatpl b/templates/overrides/kindergarten/countdown.ninjatpl new file mode 100644 index 0000000..964f3aa --- /dev/null +++ b/templates/overrides/kindergarten/countdown.ninjatpl @@ -0,0 +1,49 @@ +{# countdown kindergarten override: neon HUD digit boxes, heading. Timer script + data attributes preserved byte-for-byte. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} +
+
+
--{{ labelDays|default:"Days" }}
+
--{{ labelHours|default:"Hours" }}
+
--{{ labelMinutes|default:"Minutes" }}
+
--{{ labelSeconds|default:"Seconds" }}
+
+ +
+ +
+
+ diff --git a/templates/overrides/kindergarten/cta.ninjatpl b/templates/overrides/kindergarten/cta.ninjatpl new file mode 100644 index 0000000..8426816 --- /dev/null +++ b/templates/overrides/kindergarten/cta.ninjatpl @@ -0,0 +1,15 @@ +{# cta kindergarten override: scanline overlay, heading, kg-pop CTAs. All field reads preserved. #} +
+
+
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if text %}

{{ text }}

{% endif %} +
+
+ {% if primaryUrl and primaryLabel %}{{ primaryLabel }}{% endif %} + {% if secondaryUrl and secondaryLabel %}{{ secondaryLabel }}{% endif %} +
+
+
+
diff --git a/templates/overrides/kindergarten/divider.ninjatpl b/templates/overrides/kindergarten/divider.ninjatpl new file mode 100644 index 0000000..74fdfb7 --- /dev/null +++ b/templates/overrides/kindergarten/divider.ninjatpl @@ -0,0 +1,2 @@ +{# kindergarten divider — neon gradient rule / primary-tinted variants #} +{% with variant=variant|default:"line" weight=weight|default:"thin" width=width|default:"full" %}{% if variant == "ornament" %}{% elif variant == "spacer-rule" %}{% else %}{% endif %}{% endwith %} diff --git a/templates/overrides/kindergarten/event-list.ninjatpl b/templates/overrides/kindergarten/event-list.ninjatpl new file mode 100644 index 0000000..b430c91 --- /dev/null +++ b/templates/overrides/kindergarten/event-list.ninjatpl @@ -0,0 +1,41 @@ +{# event-list kindergarten override: neon cards, mono datetime. All field reads + control flow preserved. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} + {% if layout == "cards" %} +
+ {% for event in events %}{% if not (hidePast and event.past) %} +
+ {% if event.media %}
{% img event.media alt=event.title class="h-full w-full object-cover" %}
{% endif %} +
+
{{ event.start }}{% if event.end %} – {{ event.end }}{% endif %}{% if event.timezone %} {{ event.timezone }}{% endif %}
+

{{ event.title }}

+ {% if event.venue %}

{{ event.venue }}

{% endif %} + {% if event.description %}

{{ event.description }}

{% endif %} + {% if event.link %}{{ event.linkLabel|default:"Details" }}{% endif %} +
+
+ {% endif %}{% endfor %} +
+ {% else %} +
    + {% for event in events %}{% if not (hidePast and event.past) %} +
  • +
    +
    {{ event.start }}
    + {% if event.end %}
    to {{ event.end }}
    {% endif %} + {% if event.timezone %}
    {{ event.timezone }}
    {% endif %} +
    +
    +

    {{ event.title }}

    + {% if event.venue %}

    {{ event.venue }}

    {% endif %} + {% if event.description %}

    {{ event.description }}

    {% endif %} +
    + {% if event.link %}{{ event.linkLabel|default:"Details" }}{% endif %} +
  • + {% endif %}{% endfor %} +
+ {% endif %} +
+
diff --git a/templates/overrides/kindergarten/faq.ninjatpl b/templates/overrides/kindergarten/faq.ninjatpl new file mode 100644 index 0000000..73ba402 --- /dev/null +++ b/templates/overrides/kindergarten/faq.ninjatpl @@ -0,0 +1,18 @@ +{# faq kindergarten override: heading, neon-tinted dividers, display questions. Chevron svg, {% for %}, and answer prose preserved. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} +
+ {% for item in items %} +
+ + {{ item.question }} + + +
{{ item.answer|safe }}
+
+ {% endfor %} +
+
+
diff --git a/templates/overrides/kindergarten/feature-grid.ninjatpl b/templates/overrides/kindergarten/feature-grid.ninjatpl new file mode 100644 index 0000000..dd46d53 --- /dev/null +++ b/templates/overrides/kindergarten/feature-grid.ninjatpl @@ -0,0 +1,18 @@ +{# feature_grid kindergarten override: scanline hero overlay, mono eyebrow, heading, neon HUD cards. All field reads + control flow preserved. #} +
+
+ {% if eyebrow %}

{{ eyebrow }}

{% endif %} + {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} +
+ {% for item in items %} +
+ {% if item.icon %}
{% endif %} +

{% if item.linkUrl %}{{ item.title }}{% else %}{{ item.title }}{% endif %}

+ {% if item.text %}

{{ item.text }}

{% endif %} + {% if item.linkUrl and item.linkLabel %}{{ item.linkLabel }}{% endif %} +
+ {% endfor %} +
+
+
diff --git a/templates/overrides/kindergarten/featured-posts.ninjatpl b/templates/overrides/kindergarten/featured-posts.ninjatpl new file mode 100644 index 0000000..fbddb4d --- /dev/null +++ b/templates/overrides/kindergarten/featured-posts.ninjatpl @@ -0,0 +1,23 @@ +{# featured-posts (kindergarten override) — grid of cards from the `posts` provider #} +{# (params: limit, featured=true). Neon HUD card: image, title, excerpt, meta. #} +
+
+{% if heading %}

{{ heading }}

{% endif %} +
+{% for post in posts %} +
+{% if showFeaturedImage and post.featured_image_url %}{% img post.featured_image_url alt=post.title class="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105" %}{% endif %} +
+

{{ post.title }}

+{% if showExcerpt and post.excerpt %}

{{ post.excerpt }}

{% endif %} +
+{% if showAuthor and post.author_name %}{{ post.author_name }}{% endif %} +{% if showDate and post.published_at %}{{ post.published_at|date:"M j, Y" }}{% endif %} +{% if post.reading_time %}{{ post.reading_time }} min read{% endif %} +
+
+
+{% endfor %} +
+
+
diff --git a/templates/overrides/kindergarten/footer.ninjatpl b/templates/overrides/kindergarten/footer.ninjatpl new file mode 100644 index 0000000..6e55255 --- /dev/null +++ b/templates/overrides/kindergarten/footer.ninjatpl @@ -0,0 +1,39 @@ +{# footer (WO-TF-006): layouts columns|minimal|centered; menu columns, legal bar, and social row from the `footer_menus` provider; newsletter section reuses the {% signup_form %} core (no duplicated form). Kindergarten override: mono column headers, neon top rule, display wordmark. #} +{% set fl = layout %} +{% if not fl %}{% if template == "centered" %}{% set fl = "centered" %}{% else %}{% set fl = "columns" %}{% endif %}{% endif %} +{% set fbg = backgroundColor|default:"bg-muted/50" %} +{% macro sociallinks(cls) %}{% if social_items %}{% endif %}{% endmacro %} +{% macro newsletter() %}{% if showNewsletter %}

{{ newsletterTitle|default:"Stay in the loop" }}

{% if newsletterDescription %}

{{ newsletterDescription }}

{% endif %}
{% signup_form list=newsletterListSlug|default:"main" source="footer" submit=newsletterButtonText|default:"Subscribe" %}
{% endif %}{% endmacro %} +{% macro legalbar() %}

{% if copyright %}© {{ copyright }}{% else %}© {{ companyName }}{% endif %}

{% if legal_items %}{% endif %}
{% endmacro %} +
+
+{% if fl == "minimal" %} +
+
{% if logo %}{{ companyName }}{% elif companyName %}{{ companyName }}{% endif %}{% if tagline %}{% endif %}
+{{ sociallinks("") }} +
+{{ legalbar() }} +{% elif fl == "centered" %} +
+{% if logo %}{{ companyName }}{% elif companyName %}{{ companyName }}{% endif %} +{% if tagline %}

{{ tagline }}

{% endif %} +{% if columns %}{% endif %} +{{ newsletter() }} +{{ sociallinks("justify-center") }} +
+{{ legalbar() }} +{% else %} +
+
+{% if logo %}{{ companyName }}{% elif companyName %}{{ companyName }}{% endif %} +{% if tagline %}

{{ tagline }}

{% endif %} +{% if address or email or phone %}
{% if address %}
{{ address }}
{% endif %}{% if email %}{% endif %}{% if phone %}{% endif %}
{% endif %} +{{ sociallinks("mt-5") }} +
+{% for col in columns %}

{{ col.header }}

{% endfor %} +{% if showNewsletter %}
{{ newsletter() }}
{% endif %} +
+{{ legalbar() }} +{% endif %} +
+
diff --git a/templates/overrides/kindergarten/gallery.ninjatpl b/templates/overrides/kindergarten/gallery.ninjatpl new file mode 100644 index 0000000..6293d4e --- /dev/null +++ b/templates/overrides/kindergarten/gallery.ninjatpl @@ -0,0 +1,52 @@ +{# gallery kindergarten override: crayon-bordered tiles. Lightbox script + data-bn-* attributes + {% img %} preserved byte-for-byte. #} +{% with layout=layout|default:"grid" gap=gap|default:"md" cols=columns|default:3 %}{% if lightbox %}{% endif %}{% endwith %} diff --git a/templates/overrides/kindergarten/heading.ninjatpl b/templates/overrides/kindergarten/heading.ninjatpl index aaa106d..0ae1de0 100644 --- a/templates/overrides/kindergarten/heading.ninjatpl +++ b/templates/overrides/kindergarten/heading.ninjatpl @@ -1 +1,7 @@ -{% if step|integer >= 1 and step|integer <= 9 %}
{% endif %}{% if level|integer == 1 %}

{{ text }}

{% elif level|integer == 3 %}

{{ text }}

{% elif level|integer == 4 %}

{{ text }}

{% elif level|integer == 5 %}
{{ text }}
{% elif level|integer == 6 %}
{{ text }}
{% else %}

{{ text }}

{% endif %}{% if step|integer >= 1 and step|integer <= 9 %}
{% endif %} +{# kindergarten heading — kg-display everywhere; on h1/h2 only #} +{% if level == 1 %}

{{ text }}

+{% elif level == 3 %}

{{ text }}

+{% elif level == 4 %}

{{ text }}

+{% elif level == 5 %}
{{ text }}
+{% elif level == 6 %}
{{ text }}
+{% else %}

{{ text }}

{% endif %} diff --git a/templates/overrides/kindergarten/hero.ninjatpl b/templates/overrides/kindergarten/hero.ninjatpl new file mode 100644 index 0000000..4694957 --- /dev/null +++ b/templates/overrides/kindergarten/hero.ninjatpl @@ -0,0 +1,94 @@ +{# hero kindergarten override (GO-BIG showpiece): a bouncing mascot, wiggling ABC letters, and a dependency-free confetti canvas fill the panel when there is no backgroundImage. The canvas lazy-inits on view, honours prefers-reduced-motion, and degrades to a static confetti wash with JS off. All builtin field reads + macro + branch structure are preserved from the default hero — only the styling changes. #} +{% set eyebrowText = eyebrow.text|default:eyebrow %} +{% set headlineText = headline.text|default:headline %} +{% set subText = subheadline.text|default:subheadline %} +{% set descText = description.text|default:description %} +{% set pText = ctaPrimary.text %} +{% set pUrl = ctaPrimary.url %} +{% set sText = ctaSecondary.text %} +{% set sUrl = ctaSecondary.url %} +{% set variant = template|default:"centered" %} +{% set hasImage = backgroundImage %} +{% set showOverlay = hasImage and overlayColor != "none" and overlayColor != "" %} +{% macro playmascot() %}{% endmacro %} +{% macro herocontent(align) %} +{% if eyebrowText %}{{ eyebrowText }}{% endif %} +{% if headlineText %}

{{ headlineText }}

{% endif %} + +{% if subText %}

{{ subText }}

{% endif %} +{% if descText %}

{{ descText }}

{% endif %} +{% if pText or sText %}
+{% if pText and pUrl %}{{ pText }}{% endif %} +{% if sText and sUrl %}{{ sText }}{% endif %} +
{% endif %} +{% if trustLogos %}
+

Loved by families at

+
{% for logo in trustLogos %}{% if logo.url %}{% img logo.image alt=logo.alt class="h-8 w-auto" %}{% else %}{% img logo.image alt=logo.alt class="h-8 w-auto opacity-70" %}{% endif %}{% endfor %}
+
{% endif %} +{% endmacro %} +{% if variant == "split" %} +
+
{% if hasImage %}{% img backgroundImage alt=headlineText class="absolute inset-0 h-full w-full object-cover" layout="hero" %}{% else %}{{ playmascot() }}{% endif %}
+
{{ herocontent("left") }}
+
+{% else %} +{% set align = "center" %} +{% if variant == "left-aligned" %}{% set align = "left" %}{% endif %} +
+{% if hasImage %}
{% img backgroundImage alt=headlineText class="h-full w-full object-cover" layout="hero" %}{% if showOverlay %}
{% endif %}
{% else %}
{{ playmascot() }}
{% endif %} +
{{ herocontent(align) }}
+
+{% endif %} + diff --git a/templates/overrides/kindergarten/hours-location.ninjatpl b/templates/overrides/kindergarten/hours-location.ninjatpl new file mode 100644 index 0000000..843f723 --- /dev/null +++ b/templates/overrides/kindergarten/hours-location.ninjatpl @@ -0,0 +1,31 @@ +{# hours-location kindergarten override: mono readout table, neon map panel. Map lazy-load button + inline handler preserved. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} +
+
+ {% if hours %} + + + {% for row in hours %} + + + + + {% endfor %} + +
{{ row.day }}{% if row.closed %}Closed{% else %}{{ row.hours }}{% endif %}
+ {% endif %} + {% if specialHoursNote %}

{{ specialHoursNote }}

{% endif %} + {% if address %}
Address
{{ address }}
{% endif %} + {% if phone %}{% endif %} + {% if directionsUrl %}Get directions{% endif %} +
+ {% if mapEmbedUrl %} +
+ +
+ {% endif %} +
+
+
diff --git a/templates/overrides/kindergarten/image.ninjatpl b/templates/overrides/kindergarten/image.ninjatpl new file mode 100644 index 0000000..2baa284 --- /dev/null +++ b/templates/overrides/kindergarten/image.ninjatpl @@ -0,0 +1,2 @@ +{# kindergarten image — crayon-bordered frame, mono caption; all size/fit branches preserved #} +{% with size=size|default:"full" align=align|default:"center" aspectRatio=aspectRatio|default:"auto" objectFit=objectFit|default:"cover" rounded=rounded|default:"lg" shadow=shadow|default:"none" %}
{% if objectFit == "contain" %}{% img src alt=alt class="w-full h-full object-contain" layout=size %}{% elif objectFit == "fill" %}{% img src alt=alt class="w-full h-full object-fill" layout=size %}{% elif objectFit == "none" %}{% img src alt=alt class="w-full h-full object-none" layout=size %}{% else %}{% img src alt=alt class="w-full h-full object-cover" layout=size %}{% endif %}
{% if caption %}
{{ caption }}
{% endif %}
{% endwith %} diff --git a/templates/overrides/kindergarten/logo-strip.ninjatpl b/templates/overrides/kindergarten/logo-strip.ninjatpl new file mode 100644 index 0000000..813df74 --- /dev/null +++ b/templates/overrides/kindergarten/logo-strip.ninjatpl @@ -0,0 +1,16 @@ +{# logo_strip kindergarten override: mono kicker. Marquee classes, {% img %}, and keyframes {% endif %} diff --git a/templates/overrides/kindergarten/menu-list.ninjatpl b/templates/overrides/kindergarten/menu-list.ninjatpl new file mode 100644 index 0000000..b40d3fc --- /dev/null +++ b/templates/overrides/kindergarten/menu-list.ninjatpl @@ -0,0 +1,30 @@ +{# menu-list kindergarten override: mono price readouts, neon section rules. All field reads + control flow preserved. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} +
+ {% for section in sections %} + + {% endfor %} +
+ {% if footnote %}

{{ footnote }}

{% endif %} +
+
diff --git a/templates/overrides/kindergarten/navbar.ninjatpl b/templates/overrides/kindergarten/navbar.ninjatpl new file mode 100644 index 0000000..3d5c90c --- /dev/null +++ b/templates/overrides/kindergarten/navbar.ninjatpl @@ -0,0 +1,104 @@ +{# navbar (WO-TF-005): variants standard|centered-logo|split|transparent-over-hero; sticky none|sticky|shrink-on-scroll; dependency-free off-canvas drawer (pure-CSS baseline + progressive-JS focus-trap/Escape/scroll-lock); menu data from the `menus` provider. Kindergarten override: mono nav links, neon dropdown panels, CTA — drawer ids/style/script/data-bn-* preserved byte-for-byte. #} +{% set nv = variant %} +{% if not nv %}{% if template == "centered" %}{% set nv = "centered-logo" %}{% elif template == "transparent" %}{% set nv = "transparent-over-hero" %}{% else %}{% set nv = "standard" %}{% endif %}{% endif %} +{% set sb = stickyBehavior %} +{% if not sb %}{% if sticky %}{% set sb = "sticky" %}{% else %}{% set sb = "none" %}{% endif %}{% endif %} +{% set use_mega = megaMenu or template == "mega" %} +{% set is_transparent = nv == "transparent-over-hero" %} +{% set cta_text = cta.text|default:ctaLabel %} +{% set cta_url = cta.url|default:ctaUrl %} +{% set bg = backgroundColor|default:"bg-background" %} +{% macro navlink(item) %}{{ item.label }}{% endmacro %} +{% macro dropdown(item) %}
+ +{% if use_mega %} +{% else %}{% endif %} +
{% endmacro %} +{% macro brand(size) %}{% if logoType == "text" and logoText.text %}{{ logoText.text }}{% elif logo %}{{ alt|default:companyName }}{% elif companyName %}{{ companyName }}{% endif %}{% endmacro %} +{% macro ctabtn(extra) %}{% if cta_text %}{{ cta_text }}{% endif %}{% endmacro %} + + + diff --git a/templates/overrides/kindergarten/newsletter-signup.ninjatpl b/templates/overrides/kindergarten/newsletter-signup.ninjatpl new file mode 100644 index 0000000..3fa5a5d --- /dev/null +++ b/templates/overrides/kindergarten/newsletter-signup.ninjatpl @@ -0,0 +1,12 @@ +{# newsletter-signup (kindergarten override) — the {% signup_form %} tag carries the #} +{# untouchable subscribe wiring (/api/lists/subscribe HTMX post + honeypot); #} +{# this template supplies only the kindergarten chrome around it. #} +
+
+
+{% if title %}

{{ title }}

{% endif %} +{% if description %}

{{ description }}

{% endif %} +
{% signup_form list=listSlug source=source submit=submitText success_message=successMessage placeholder=placeholder %}
+
+
+
diff --git a/templates/overrides/kindergarten/page-suggestions.ninjatpl b/templates/overrides/kindergarten/page-suggestions.ninjatpl new file mode 100644 index 0000000..8142c92 --- /dev/null +++ b/templates/overrides/kindergarten/page-suggestions.ninjatpl @@ -0,0 +1,12 @@ +{# page-suggestions kindergarten override — themed 404. NOTE: the builtin builds its "did you mean" list from a Postgres full-text query (SearchSimilarPages), which a codeless template cannot run — so this override renders the on-theme 404 (title / message / requested path / home CTA) without the dynamic suggestion list. Recorded as a known deviation in WO-TF-019. Content fields honored: title, emptyMessage. #} +
+
+ Oops +

{{ title|default:"This page went to play outside" }}

+

{{ emptyMessage|default:"We could not find that page. It may have moved, or maybe it never existed. Let us take you back to the start." }}

+ {% if context.path %}

You asked for: {{ context.path }}

{% endif %} +
+ Back home +
+
+
diff --git a/templates/overrides/kindergarten/password-reset-form.ninjatpl b/templates/overrides/kindergarten/password-reset-form.ninjatpl new file mode 100644 index 0000000..21521c0 --- /dev/null +++ b/templates/overrides/kindergarten/password-reset-form.ninjatpl @@ -0,0 +1,13 @@ +{# password-reset-form kindergarten override — reproduces the builtin's hardcoded wiring: hx-post /api/auth/request-password-reset (target #reset-message), back-to-login link /login. Only the chrome is themed. #} +
+
+
+

Reset Password

+

Enter your email address and we will send you a link to reset your password.

+
+
+ +
+

Back to login

+
+
diff --git a/templates/overrides/kindergarten/popular-posts.ninjatpl b/templates/overrides/kindergarten/popular-posts.ninjatpl new file mode 100644 index 0000000..9618561 --- /dev/null +++ b/templates/overrides/kindergarten/popular-posts.ninjatpl @@ -0,0 +1,16 @@ +{# popular-posts (kindergarten override) — compact ranked list from the `posts` provider. #} +
+{% if heading %}

{{ heading }}

{% endif %} +
    +{% for post in posts %} +
  1. +{% if showRank %}{{ forloop.Counter }}{% endif %} +{% if showThumbnail and post.featured_image_url %}{% img post.featured_image_url alt=post.title class="h-full w-full object-cover" %}{% endif %} +
    +

    {{ post.title }}

    +{% if post.published_at %}

    {{ post.published_at|date:"M j, Y" }}

    {% endif %} +
    +
  2. +{% endfor %} +
+
diff --git a/templates/overrides/kindergarten/post-hero.ninjatpl b/templates/overrides/kindergarten/post-hero.ninjatpl new file mode 100644 index 0000000..eec514a --- /dev/null +++ b/templates/overrides/kindergarten/post-hero.ninjatpl @@ -0,0 +1,21 @@ +{# post-hero (kindergarten override) — current post from the `post` provider. When the post #} +{# has a featured image it becomes an overlaid banner; otherwise a clean text hero. #} +{% set center = align != "left" %} +{% if post.featured_image_url %} +
+
{% img post.featured_image_url alt=post.featured_image_alt class="h-full w-full object-cover" layout="hero" %}
+
+

{{ post.title }}

+{% if showExcerpt and post.excerpt %}

{{ post.excerpt }}

{% endif %} +{% if showMeta %}
{% if post.author_name %}{{ post.author_name }}{% endif %}{% if post.published_at %}{{ post.published_at|date:"F j, Y" }}{% endif %}{% if post.reading_time %}{{ post.reading_time }} min read{% endif %}
{% endif %} +
+
+{% else %} +
+
+

{{ post.title }}

+{% if showExcerpt and post.excerpt %}

{{ post.excerpt }}

{% endif %} +{% if showMeta %}
{% if post.author_name %}{{ post.author_name }}{% endif %}{% if post.published_at %}{{ post.published_at|date:"F j, Y" }}{% endif %}{% if post.reading_time %}{{ post.reading_time }} min read{% endif %}
{% endif %} +
+
+{% endif %} diff --git a/templates/overrides/kindergarten/post-metadata.ninjatpl b/templates/overrides/kindergarten/post-metadata.ninjatpl new file mode 100644 index 0000000..484c5af --- /dev/null +++ b/templates/overrides/kindergarten/post-metadata.ninjatpl @@ -0,0 +1,9 @@ +{# post-metadata (kindergarten override) — author/date/reading-time row from the `post` provider. #} +{% if post %} +
+{% if showAuthor and post.author_name %}{% if showAvatar and post.author_avatar %}{% img post.author_avatar alt=post.author_name class="h-7 w-7 rounded-full border border-primary/40 object-cover" %}{% endif %}{{ post.author_name }}{% endif %} +{% if showDate and post.published_at %}{% endif %} +{% if showReadingTime and post.reading_time %}{{ post.reading_time }} min read{% endif %} +{% if post.categories %}{% for cat in post.categories %}{{ cat.name }}{% endfor %}{% endif %} +
+{% endif %} diff --git a/templates/overrides/kindergarten/pricing.ninjatpl b/templates/overrides/kindergarten/pricing.ninjatpl new file mode 100644 index 0000000..3ee297a --- /dev/null +++ b/templates/overrides/kindergarten/pricing.ninjatpl @@ -0,0 +1,25 @@ +{# pricing kindergarten override: scanline overlay, heading, HUD neon tier panels (magenta highlight / cyan standard), CTAs. Field reads + control flow preserved. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} +
+ {% for tier in tiers %} +
+ {% if tier.badge %}{{ tier.badge }}{% endif %} +

{{ tier.name }}

+

+ {% if currency %}{{ currency }}{% else %}${% endif %}{{ tier.price }} + {% if tier.period %}{{ tier.period }}{% endif %} +

+ {% if tier.description %}

{{ tier.description }}

{% endif %} + {% if tier.features %}
    + {% for feature in tier.features %}
  • {{ feature }}
  • {% endfor %} +
{% endif %} + {% if tier.ctaUrl and tier.ctaLabel %}{{ tier.ctaLabel }}{% endif %} +
+ {% endfor %} +
+ {% if billingNote %}

{{ billingNote }}

{% endif %} +
+
diff --git a/templates/overrides/kindergarten/quote.ninjatpl b/templates/overrides/kindergarten/quote.ninjatpl new file mode 100644 index 0000000..3ed8108 --- /dev/null +++ b/templates/overrides/kindergarten/quote.ninjatpl @@ -0,0 +1,2 @@ +{# kindergarten quote — dashed neon left border; kg-display for pull; mono figcaption #} +{% with style=style|default:"block" align=align|default:"left" %}
{{ quote|safe }}{% if attribution or role or cite %}
{% if attribution %}{{ attribution }}{% endif %}{% if role %}{{ role }}{% endif %}{% if cite %}Source{% endif %}
{% endif %}
{% endwith %} diff --git a/templates/overrides/kindergarten/related-posts.ninjatpl b/templates/overrides/kindergarten/related-posts.ninjatpl new file mode 100644 index 0000000..052bb94 --- /dev/null +++ b/templates/overrides/kindergarten/related-posts.ninjatpl @@ -0,0 +1,17 @@ +{# related-posts (kindergarten override) — grid from the `posts` provider. #} +{% if posts %} +
+
+

{{ heading|default:"Related posts" }}

+
+{% for post in posts %} + +{% endfor %} +
+
+
+{% endif %} diff --git a/templates/overrides/kindergarten/rich-section.ninjatpl b/templates/overrides/kindergarten/rich-section.ninjatpl new file mode 100644 index 0000000..89cc2b8 --- /dev/null +++ b/templates/overrides/kindergarten/rich-section.ninjatpl @@ -0,0 +1,14 @@ +{# rich_section kindergarten override: mono eyebrow, heading, HUD-framed media, CTA. Field reads + {% img %} preserved. #} +
+
+
+ {% if eyebrow %}

{{ eyebrow }}

{% endif %} + {% if heading %}

{{ heading }}

{% endif %} + {% if body %}
{{ body|safe }}
{% endif %} + {% if ctaUrl and ctaLabel %}{{ ctaLabel }}{% endif %} +
+
+ {% if media %}
{% img media alt=mediaAlt class="w-full h-auto object-cover" %}
{% endif %} +
+
+
diff --git a/templates/overrides/kindergarten/rich-text.ninjatpl b/templates/overrides/kindergarten/rich-text.ninjatpl new file mode 100644 index 0000000..690d369 --- /dev/null +++ b/templates/overrides/kindergarten/rich-text.ninjatpl @@ -0,0 +1,2 @@ +{# kindergarten rich-text — body font via kg-prose #} +{% if html %}
{{ html|safe }}
{% endif %} diff --git a/templates/overrides/kindergarten/sidebar-nav.ninjatpl b/templates/overrides/kindergarten/sidebar-nav.ninjatpl new file mode 100644 index 0000000..fa31303 --- /dev/null +++ b/templates/overrides/kindergarten/sidebar-nav.ninjatpl @@ -0,0 +1,15 @@ +{# sidebar-nav (kindergarten override) — vertical menu from the `menus` provider. Variants #} +{# default | compact. highlight-current carried as a progressive-enhancement #} +{# script keyed on data-bn-sidebar-link (marks the anchor matching location.pathname). #} +{% set compact = template == "compact" %} + +{% if highlightCurrent %}{% endif %} diff --git a/templates/overrides/kindergarten/social-links.ninjatpl b/templates/overrides/kindergarten/social-links.ninjatpl new file mode 100644 index 0000000..1bae3e2 --- /dev/null +++ b/templates/overrides/kindergarten/social-links.ninjatpl @@ -0,0 +1,14 @@ +{# kindergarten social-links — mono label, sharp neon-bordered chips; icon sprites preserved #} + diff --git a/templates/overrides/kindergarten/stats.ninjatpl b/templates/overrides/kindergarten/stats.ninjatpl new file mode 100644 index 0000000..a619e4e --- /dev/null +++ b/templates/overrides/kindergarten/stats.ninjatpl @@ -0,0 +1,49 @@ +{# stats kindergarten override: scanline overlay, heading, HUD stat tiles, mono labels. data-stats + countUp script/attrs preserved byte-for-byte. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} +
+ {% for item in items %} +
+
{% if item.prefix %}{{ item.prefix }}{% endif %}{{ item.value }}{% if item.suffix %}{{ item.suffix }}{% endif %}
+ {% if item.label %}
{{ item.label }}
{% endif %} +
+ {% endfor %} +
+
+
+{% if countUp %}{% endif %} diff --git a/templates/overrides/kindergarten/team.ninjatpl b/templates/overrides/kindergarten/team.ninjatpl new file mode 100644 index 0000000..c1f163e --- /dev/null +++ b/templates/overrides/kindergarten/team.ninjatpl @@ -0,0 +1,22 @@ +{# team kindergarten override: heading, dashed-neon HUD member panels (lime), mono roles. {% img %} + social icon svgs + control flow preserved. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} +
+ {% for member in members %} +
+ {% if member.photo %}{% img member.photo alt=member.name class="h-full w-full object-cover" %}{% endif %} +
+ {% if member.name %}

{{ member.name }}

{% endif %} + {% if member.role %}

{{ member.role }}

{% endif %} + {% if member.bio %}

{{ member.bio }}

{% endif %} + {% if member.socials %}
+ {% for social in member.socials %}{% if social.url %}{% endif %}{% endfor %} +
{% endif %} +
+
+ {% endfor %} +
+
+
diff --git a/templates/overrides/kindergarten/testimonial.ninjatpl b/templates/overrides/kindergarten/testimonial.ninjatpl new file mode 100644 index 0000000..266f1eb --- /dev/null +++ b/templates/overrides/kindergarten/testimonial.ninjatpl @@ -0,0 +1,27 @@ +{# testimonial kindergarten override: heading, dashed-neon HUD quote cards. Rating svgs, {% img %}, and control flow preserved. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} +
+ {% for q in quotes %} +
+ {% if q.rating %}
+ + + + + +
{% endif %} +
{% if q.quote %}“{{ q.quote }}”{% endif %}
+
+ {% if q.avatar %}{% img q.avatar alt=q.name class="h-full w-full object-cover" %}{% endif %} + + {% if q.name %}{{ q.name }}{% endif %} + {% if q.role %}{{ q.role }}{% endif %} + +
+
+ {% endfor %} +
+
+
diff --git a/templates/overrides/kindergarten/text.ninjatpl b/templates/overrides/kindergarten/text.ninjatpl index 2e1cfd7..1ba6bad 100644 --- a/templates/overrides/kindergarten/text.ninjatpl +++ b/templates/overrides/kindergarten/text.ninjatpl @@ -1 +1,2 @@ -
{{ text|safe }}
+{# kindergarten text — body font via kg-prose #} +
{{ text|safe }}
diff --git a/templates/overrides/kindergarten/timeline.ninjatpl b/templates/overrides/kindergarten/timeline.ninjatpl new file mode 100644 index 0000000..36e7ad1 --- /dev/null +++ b/templates/overrides/kindergarten/timeline.ninjatpl @@ -0,0 +1,22 @@ +{# timeline kindergarten override: heading, mono step labels, neon markers. All field reads + control flow preserved. #} +
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if intro %}

{{ intro }}

{% endif %} +
    + {% for step in steps %} +
  1. + + {% if marker == "numbered" %}{{ forloop.counter }}{% elif marker == "dot" %}{% else %}{% endif %} + +
    + {% if step.label %}

    {{ step.label }}

    {% endif %} + {% if step.title %}

    {{ step.title }}

    {% endif %} + {% if step.text %}

    {{ step.text }}

    {% endif %} + {% if step.media %}
    {% img step.media alt=step.title class="w-full h-auto object-cover" %}
    {% endif %} +
    +
  2. + {% endfor %} +
+
+
diff --git a/templates/overrides/kindergarten/utility-bar.ninjatpl b/templates/overrides/kindergarten/utility-bar.ninjatpl new file mode 100644 index 0000000..bc7ab00 --- /dev/null +++ b/templates/overrides/kindergarten/utility-bar.ninjatpl @@ -0,0 +1,10 @@ +{# utility_bar (WO-TF-006): thin top contact strip; left/right item lists {icon,label,link}; icons render through the sprite pipeline via the ::pack:name:: shorthand. Kindergarten override: mono terminal readout, neon underline. #} +{% set ubcls = "bg-muted text-muted-foreground border-b border-primary/30" %} +{% if variant == "primary" %}{% set ubcls = "bg-primary text-primary-foreground" %}{% elif variant == "dark" %}{% set ubcls = "bg-foreground text-background" %}{% endif %} +{% macro ubitem(it) %}{% if it.link %}{% if it.icon %}::{{ it.icon }}::{% endif %}{% if it.label %}{{ it.label }}{% endif %}{% else %}{% if it.icon %}::{{ it.icon }}::{% endif %}{% if it.label %}{{ it.label }}{% endif %}{% endif %}{% endmacro %} +
+
+
{% for it in leftItems %}{{ ubitem(it) }}{% endfor %}
+
{% for it in rightItems %}{{ ubitem(it) }}{% endfor %}
+
+
diff --git a/templates/overrides/kindergarten/video-embed.ninjatpl b/templates/overrides/kindergarten/video-embed.ninjatpl new file mode 100644 index 0000000..0694c78 --- /dev/null +++ b/templates/overrides/kindergarten/video-embed.ninjatpl @@ -0,0 +1,23 @@ +{# video-embed kindergarten override: neon HUD frame, play button. Facade script + data-bn-* attributes + {% img %} preserved byte-for-byte. #} +{% with aspect=aspect|default:"16:9" %}
{% if caption %}
{{ caption }}
{% endif %}
{% endwith %}