From 1ee98d318a8acd84f0a7cb34636526c315f40a73 Mon Sep 17 00:00:00 2001 From: Alex Dunmow Date: Sun, 5 Jul 2026 12:41:03 +0800 Subject: [PATCH] =?UTF-8?q?feat(pastel-dream):=20WO-TF-023=20bakery/patiss?= =?UTF-8?q?erie=20theme=20=E2=80=94=20full=20Wave=20A=20rebuild?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pivot from the wellness/spa WZ-021 state to the locked bakery/patisserie persona. Codeless .bnp, verified against cms a0f07e2ae (OVERRIDE-CONTRACT). - 49 mandatory template overrides (+ manifest entries), re-skinned from the field-correct coffee sibling: every content field, provider var, custom tag and JS preserved verbatim; only styling + copy changed. §4 button/card reads reconciled (link/style/label, text/media). - 7 page templates: default, landing, article, full-width, blog-index, contact, auth. - 4 persona blocks: seasonal_specials, cake_gallery, order_ribbon, bakers_note (old 6 duplicate-builtin blocks deleted -> now overrides). - 5 dual-mode presets (morning-haze/berry-cream/matcha-cream/lavender-honey/ peach-blossom), all 19 tokens, tuned dusk-berry dark. - Bundled OFL fonts: Fraunces (heading) + Quicksand (body); all font-family via var(--font-*). Themed email-safe wrapper. master_pages + demo seed (home, about, blog x3, contact w/ data-table + row_inserted email workflow, login). - CSS-FIRST motion: watercolor washes, scalloped edges, soft pills; reduced- motion honored. Gates: make -> exit 0 (codeless .bnp); check-safety -> 19 ok 13 skip, exit 0. Known limits per contract §11/§12 (auth trio + page-suggestions + provider- backed lists inert until cms fix). Visuals UNVERIFIED until Wave B. Co-Authored-By: Claude Opus 4.8 --- BUILD_REPORT.md | 329 +++--------- RECOMMENDED_FONTS.md | 72 +-- RECOMMENDED_ICONS.md | 29 ++ assets/css/pastel-dream.css | 187 ------- assets/fonts/OFL.txt | 109 ++++ assets/fonts/fraunces-latin-400.woff2 | Bin 0 -> 17968 bytes assets/fonts/fraunces-latin-600.woff2 | Bin 0 -> 18096 bytes assets/fonts/quicksand-400.woff2 | Bin 0 -> 15776 bytes assets/fonts/quicksand-700.woff2 | Bin 0 -> 15124 bytes assets/fonts/web/README.txt | 5 - assets/style.css | 310 +++++++---- blocks/affirmation.ninjatpl | 10 - blocks/affirmation.schema.json | 29 -- blocks/bakers_note.ninjatpl | 14 + blocks/bakers_note.schema.json | 30 ++ blocks/blocks.yaml | 73 ++- blocks/cake_gallery.ninjatpl | 29 ++ blocks/cake_gallery.schema.json | 39 ++ blocks/cozy-footer.ninjatpl | 23 - blocks/cozy-footer.schema.json | 39 -- blocks/feature-grid-soft.ninjatpl | 18 - blocks/feature-grid-soft.schema.json | 42 -- blocks/order_ribbon.ninjatpl | 18 + blocks/order_ribbon.schema.json | 37 ++ blocks/seasonal_specials.ninjatpl | 28 + blocks/seasonal_specials.schema.json | 46 ++ blocks/soft-navbar.ninjatpl | 23 - blocks/soft-navbar.schema.json | 34 -- blocks/testimonial-soft.ninjatpl | 27 - blocks/testimonial-soft.schema.json | 40 -- blocks/watercolor-hero.ninjatpl | 35 -- blocks/watercolor-hero.schema.json | 43 -- fonts.json | 19 +- manifest.yaml | 487 +++++++++++------- master_pages.json | 38 +- plugin.mod | 5 +- presets.json | 321 +++++++++--- seed/seed.json | 176 +++++++ seed/workflows.json | 25 + templates/email/pastel-dream.ninjatpl | 191 +++---- .../pastel-dream/announcement-bar.ninjatpl | 21 + .../pastel-dream/audio-embed.ninjatpl | 15 + .../overrides/pastel-dream/auth-form.ninjatpl | 36 ++ .../pastel-dream/auth-status.ninjatpl | 27 + .../pastel-dream/author-bio-hero.ninjatpl | 20 + .../pastel-dream/author-bio.ninjatpl | 12 + .../overrides/pastel-dream/blog-hero.ninjatpl | 22 + .../pastel-dream/breadcrumbs.ninjatpl | 12 + .../overrides/pastel-dream/button.ninjatpl | 8 +- .../overrides/pastel-dream/card.ninjatpl | 19 +- .../pastel-dream/category-list.ninjatpl | 17 + .../pastel-dream/contact-card.ninjatpl | 17 + .../pastel-dream/contact-form.ninjatpl | 39 ++ .../overrides/pastel-dream/countdown.ninjatpl | 42 ++ templates/overrides/pastel-dream/cta.ninjatpl | 14 + .../overrides/pastel-dream/divider.ninjatpl | 17 + .../pastel-dream/event-list.ninjatpl | 40 ++ templates/overrides/pastel-dream/faq.ninjatpl | 17 + .../pastel-dream/feature-grid.ninjatpl | 17 + .../pastel-dream/featured-posts.ninjatpl | 21 + .../overrides/pastel-dream/footer.ninjatpl | 38 ++ .../overrides/pastel-dream/gallery.ninjatpl | 9 + .../overrides/pastel-dream/heading.ninjatpl | 7 +- .../overrides/pastel-dream/hero.ninjatpl | 36 ++ .../pastel-dream/hours-location.ninjatpl | 40 ++ .../overrides/pastel-dream/image.ninjatpl | 8 + .../pastel-dream/logo-strip.ninjatpl | 15 + .../overrides/pastel-dream/menu-list.ninjatpl | 31 ++ .../overrides/pastel-dream/navbar.ninjatpl | 74 +++ .../pastel-dream/newsletter-signup.ninjatpl | 9 + .../pastel-dream/page-suggestions.ninjatpl | 22 + .../pastel-dream/password-reset-form.ninjatpl | 14 + .../pastel-dream/popular-posts.ninjatpl | 15 + .../overrides/pastel-dream/post-hero.ninjatpl | 19 + .../pastel-dream/post-metadata.ninjatpl | 8 + .../overrides/pastel-dream/pricing.ninjatpl | 24 + .../overrides/pastel-dream/quote.ninjatpl | 6 + .../pastel-dream/related-posts.ninjatpl | 16 + .../pastel-dream/rich-section.ninjatpl | 13 + .../overrides/pastel-dream/rich-text.ninjatpl | 1 + .../pastel-dream/sidebar-nav.ninjatpl | 12 + .../pastel-dream/social-links.ninjatpl | 13 + .../overrides/pastel-dream/stats.ninjatpl | 44 ++ .../overrides/pastel-dream/team.ninjatpl | 21 + .../pastel-dream/testimonial.ninjatpl | 26 + .../overrides/pastel-dream/text.ninjatpl | 4 +- .../overrides/pastel-dream/timeline.ninjatpl | 21 + .../pastel-dream/utility-bar.ninjatpl | 10 + .../pastel-dream/video-embed.ninjatpl | 22 + templates/pastel-dream/article.ninjatpl | 18 +- templates/pastel-dream/auth.ninjatpl | 19 + templates/pastel-dream/blog-index.ninjatpl | 15 + templates/pastel-dream/contact.ninjatpl | 13 + templates/pastel-dream/default.ninjatpl | 18 +- templates/pastel-dream/full-width.ninjatpl | 16 +- templates/pastel-dream/landing.ninjatpl | 21 +- 96 files changed, 2693 insertions(+), 1418 deletions(-) create mode 100644 RECOMMENDED_ICONS.md delete mode 100644 assets/css/pastel-dream.css create mode 100644 assets/fonts/OFL.txt create mode 100644 assets/fonts/fraunces-latin-400.woff2 create mode 100644 assets/fonts/fraunces-latin-600.woff2 create mode 100644 assets/fonts/quicksand-400.woff2 create mode 100644 assets/fonts/quicksand-700.woff2 delete mode 100644 assets/fonts/web/README.txt delete mode 100644 blocks/affirmation.ninjatpl delete mode 100644 blocks/affirmation.schema.json create mode 100644 blocks/bakers_note.ninjatpl create mode 100644 blocks/bakers_note.schema.json create mode 100644 blocks/cake_gallery.ninjatpl create mode 100644 blocks/cake_gallery.schema.json delete mode 100644 blocks/cozy-footer.ninjatpl delete mode 100644 blocks/cozy-footer.schema.json delete mode 100644 blocks/feature-grid-soft.ninjatpl delete mode 100644 blocks/feature-grid-soft.schema.json create mode 100644 blocks/order_ribbon.ninjatpl create mode 100644 blocks/order_ribbon.schema.json create mode 100644 blocks/seasonal_specials.ninjatpl create mode 100644 blocks/seasonal_specials.schema.json delete mode 100644 blocks/soft-navbar.ninjatpl delete mode 100644 blocks/soft-navbar.schema.json delete mode 100644 blocks/testimonial-soft.ninjatpl delete mode 100644 blocks/testimonial-soft.schema.json delete mode 100644 blocks/watercolor-hero.ninjatpl delete mode 100644 blocks/watercolor-hero.schema.json create mode 100644 seed/seed.json create mode 100644 seed/workflows.json create mode 100644 templates/overrides/pastel-dream/announcement-bar.ninjatpl create mode 100644 templates/overrides/pastel-dream/audio-embed.ninjatpl create mode 100644 templates/overrides/pastel-dream/auth-form.ninjatpl create mode 100644 templates/overrides/pastel-dream/auth-status.ninjatpl create mode 100644 templates/overrides/pastel-dream/author-bio-hero.ninjatpl create mode 100644 templates/overrides/pastel-dream/author-bio.ninjatpl create mode 100644 templates/overrides/pastel-dream/blog-hero.ninjatpl create mode 100644 templates/overrides/pastel-dream/breadcrumbs.ninjatpl create mode 100644 templates/overrides/pastel-dream/category-list.ninjatpl create mode 100644 templates/overrides/pastel-dream/contact-card.ninjatpl create mode 100644 templates/overrides/pastel-dream/contact-form.ninjatpl create mode 100644 templates/overrides/pastel-dream/countdown.ninjatpl create mode 100644 templates/overrides/pastel-dream/cta.ninjatpl create mode 100644 templates/overrides/pastel-dream/divider.ninjatpl create mode 100644 templates/overrides/pastel-dream/event-list.ninjatpl create mode 100644 templates/overrides/pastel-dream/faq.ninjatpl create mode 100644 templates/overrides/pastel-dream/feature-grid.ninjatpl create mode 100644 templates/overrides/pastel-dream/featured-posts.ninjatpl create mode 100644 templates/overrides/pastel-dream/footer.ninjatpl create mode 100644 templates/overrides/pastel-dream/gallery.ninjatpl create mode 100644 templates/overrides/pastel-dream/hero.ninjatpl create mode 100644 templates/overrides/pastel-dream/hours-location.ninjatpl create mode 100644 templates/overrides/pastel-dream/image.ninjatpl create mode 100644 templates/overrides/pastel-dream/logo-strip.ninjatpl create mode 100644 templates/overrides/pastel-dream/menu-list.ninjatpl create mode 100644 templates/overrides/pastel-dream/navbar.ninjatpl create mode 100644 templates/overrides/pastel-dream/newsletter-signup.ninjatpl create mode 100644 templates/overrides/pastel-dream/page-suggestions.ninjatpl create mode 100644 templates/overrides/pastel-dream/password-reset-form.ninjatpl create mode 100644 templates/overrides/pastel-dream/popular-posts.ninjatpl create mode 100644 templates/overrides/pastel-dream/post-hero.ninjatpl create mode 100644 templates/overrides/pastel-dream/post-metadata.ninjatpl create mode 100644 templates/overrides/pastel-dream/pricing.ninjatpl create mode 100644 templates/overrides/pastel-dream/quote.ninjatpl create mode 100644 templates/overrides/pastel-dream/related-posts.ninjatpl create mode 100644 templates/overrides/pastel-dream/rich-section.ninjatpl create mode 100644 templates/overrides/pastel-dream/rich-text.ninjatpl create mode 100644 templates/overrides/pastel-dream/sidebar-nav.ninjatpl create mode 100644 templates/overrides/pastel-dream/social-links.ninjatpl create mode 100644 templates/overrides/pastel-dream/stats.ninjatpl create mode 100644 templates/overrides/pastel-dream/team.ninjatpl create mode 100644 templates/overrides/pastel-dream/testimonial.ninjatpl create mode 100644 templates/overrides/pastel-dream/timeline.ninjatpl create mode 100644 templates/overrides/pastel-dream/utility-bar.ninjatpl create mode 100644 templates/overrides/pastel-dream/video-embed.ninjatpl create mode 100644 templates/pastel-dream/auth.ninjatpl create mode 100644 templates/pastel-dream/blog-index.ninjatpl create mode 100644 templates/pastel-dream/contact.ninjatpl diff --git a/BUILD_REPORT.md b/BUILD_REPORT.md index 79d4e32..745eb76 100644 --- a/BUILD_REPORT.md +++ b/BUILD_REPORT.md @@ -1,280 +1,85 @@ -# Pastel Dream — Build Report +# Pastel Dream — Build Report (Wave A, WO-TF-023) -Implementation pass status against the spec -(`themes/docs/works/pastel-dream.md`) and gating UAT -(`themes/docs/uat/pastel-dream.md`), executed under wave-1 fonts policy -(`themes/docs/FONTS.md`). +Bakery / patisserie persona. Codeless `.bnp` theme (no Go, no wasm). Built and +verified against cms main at `a0f07e2ae` (OVERRIDE-CONTRACT freeze). ## What landed -### Identity & module -- `plugin.mod` written verbatim from spec §"plugin.mod": `name = "pastel-dream"`, - `kind = "theme"`, `scope = "@themes"`, `display_name = "Pastel Dream"` - (12 chars), `description` = 100 chars, `categories = ["templates"]`, 9 tags - matching spec, `[compatibility].block_core = ">=0.11.0 <0.12.0"`, - `version = "0.1.0"`. -- `go.mod` module path `git.dev.alexdunmow.com/block/themes/pastel-dream`, - `go 1.26.4`, `git.dev.alexdunmow.com/block/core v0.11.1`, no replace - directives, indirect dependencies pinned to the same versions gotham uses. -- `go.sum` is fully tidied (42 lines) — `go mod tidy` exits clean. +### Identity +- `plugin.mod`: `name = "pastel-dream"`, `kind = "theme"`, `scope = "@themes"`, + `version = "0.2.0"`, bakery tags, `required_icon_packs = ["lucide", "simple-icons"]`. +- `manifest.yaml`: system template `pastel-dream`, 7 page templates, email + wrapper, 49 template overrides, `css.input_css_append` (pastel utility layer), + icon packs, presets/fonts/master-pages references. -### Build artefact -- `pastel-dream.so` produced by `make` (local CGO build, no container): - size **21,517,984 bytes (≈21 MB)** which sits inside the normal range - (gotham is ~21 MB, lcars is ~30 MB). -- Exported symbol `Registration` confirmed present via `nm -D`: - `git.dev.alexdunmow.com/block/themes/pastel-dream.Registration`. -- `go vet ./...` exits 0. +### Overrides (49 mandatory keys) +- One `templates/overrides/pastel-dream/.ninjatpl` per mandatory key, plus a + `template_overrides` entry per key. Re-skinned from the field-correct `coffee` + sibling (frozen at the same cms pin): every content field, provider var, custom + tag, and JS block preserved verbatim; only styling and copy changed to the + pastel bakery identity. Legacy button/card reads already reconciled per §4 + (`link`/`style`/`label`, `text`/`media`). -### System template and page templates (register.go) -- `RegisterSystemTemplate` called exactly once with `Key: "pastel-dream"`. -- 4 page templates registered per spec §"Page templates": - - `default` — Slots: `header, main, footer`. - - `landing` — Slots: `hero, main, cta, footer`. - - `article` — Slots: `header, main, footer`. - - `full-width` — Slots: `header, main, footer`. -- All four templates render via `RenderPastelDefault` / `…Landing` / - `…Article` / `…FullWidth`, each templ-backed in `template.templ`. +### Page templates (7) +- `default`, `landing`, `article`, `full-width`, `blog-index`, `contact`, `auth` + under `templates/pastel-dream/`. Soft watercolor-wash chrome (`pd-paper` body). -### Schema loading order -- `br.LoadSchemasFromFS(Schemas())` is called at `register.go:72`, strictly - before the first `br.Register` at `register.go:77`. UAT §3 line-order - check passes. +### Persona blocks (4) +- `pastel-dream:seasonal_specials`, `pastel-dream:cake_gallery`, + `pastel-dream:order_ribbon`, `pastel-dream:bakers_note`. Each is a + `blocks/blocks.yaml` definition + `.schema.json` (draft-07) + + `.ninjatpl`. Media via `{% img %}`; arrays via `x-editor: collection`. + The six old duplicate-builtin blocks (soft-navbar, watercolor-hero, + affirmation, testimonial-soft, feature-grid-soft, cozy-footer) were deleted; + their surfaces are now the navbar/hero/quote/testimonial/feature-grid/footer + overrides. -### Theme-owned blocks (6 in total) -Each block has one `.go` + `.templ` pair under repo root, one -`schemas/.schema.json` (draft-07), and exactly one `br.Register` call -in `register.go`. All blocks declare `Source: "pastel-dream"` on -`BlockMeta`. Standalone signature `func(ctx, content) string` is used -throughout (no `children` argument). +### Presets (5, dual-mode) +- `morning-haze`, `berry-cream`, `matcha-cream`, `lavender-honey`, + `peach-blossom`. All `mode: "both"`, all 19 tokens per light + dark. Dark is a + tuned dusk plum/berry ground (not a lightened pastel) per the WO. -| Key | Title | Category | Schema fields | -|--------------------|--------------------|-------------|------------------------------------------------------------------------------------------| -| `soft-navbar` | Soft Navbar | Navigation | logo:text, menuName:menu-select, ctaText:text, ctaHref:link | -| `watercolor-hero` | Watercolor Hero | Theme | eyebrow:text, headline:richtext, body:richtext, image:media, ctaText:text, ctaHref:link | -| `affirmation` | Affirmation Strip | Theme | quote:textarea, author:text, palette:select(blush\|mint\|butter\|sky) | -| `testimonial-soft` | Soft Testimonial | Theme | quote:richtext, name:text, role:text, avatar:media, rating:number | -| `feature-grid-soft`| Feature Trio | Layout | intro:richtext, items:collection(icon:media, title:text, body:textarea) | -| `cozy-footer` | Cozy Footer | Navigation | showSignup:select, affirmation:textarea, menuName:menu-select, social:array(link) | +### Fonts (bundled, OFL) +- `fonts.json`: **Fraunces** 400/600 (heading) + **Quicksand** 400/700 (body), + woff2 in `assets/fonts/`, OFL-1.1 recorded in `assets/fonts/OFL.txt`. Every + `font-family` resolves through `var(--font-heading|body|mono, )`. -All `x-editor` values fall within the allowed set -`{text, richtext, media, color, select, number, slug, textarea, array, collection, bucket-picker, menu-select, template-select, link}`. +### Email +- `templates/email/pastel-dream.ninjatpl`: table-based, inline styles, pastel + hex fallbacks for clients without CSS custom properties, scalloped divider. -### Built-in overrides -Four overrides wired via `br.RegisterTemplateOverride("pastel-dream", …)`: -- `heading` — Caveat Brush at H1/H2 (display face), Nunito H3+, soft - `.brush-underline` accent. -- `text` — `font-body` with `line-height: 1.75` per spec. -- `button` — `.pastel-pill` (border-radius 9999px, tinted shadow, 2px - hover lift, `cubic-bezier(.22,1,.36,1)` easing). -- `card` — `.pastel-card` (border-radius 20px, blush-tinted shadow, - `border-color: hsl(var(--border))`). +### Master pages + seed +- `master_pages.json`: default/landing/blog/auth masters, "Petal & Crumb" brand. +- `seed/seed.json` (`demo: true`): home, about, blog index + 3 posts, contact, + login. Contact form → `contact_submissions` data table via + `formConfig.targetTable`. `seed/workflows.json`: `row_inserted` → + email-admins ("Form Submission Notification", recipients admins/all). Bakery + voice, no LLM-isms, no em-dashes, no fabricated stats. -### Master pages -`DefaultMasterPages()` returns exactly two entries: +### Motion tier: CSS-FIRST +- Watercolor wash + gentle `pd-breathe` blob drift + brush draw-in only. No + canvas, no JS showpiece. All animation disabled under + `prefers-reduced-motion: reduce`. -1. **`pastel-dream:default-master`** — `PageTemplates: ["default", "article"]`. - Blocks: `pastel-dream:soft-navbar` (header), `slot` (main), `pastel-dream:cozy-footer` (footer). -2. **`pastel-dream:landing-master`** — `PageTemplates: ["landing"]`. - Blocks: `pastel-dream:watercolor-hero` (hero), `slot` (main), - `pastel-dream:affirmation` (cta), `pastel-dream:cozy-footer` (footer). - -`slot` block `Content.slotName` matches its slot name in every case. - -### Presets (presets.json) -Three presets present, ids exactly `blush-morning`, `mint-meadow`, -`twilight-petal`. All 19 tokens declared per preset. Every value matches -`^\d+ \d+% \d+%$` (HSL triple, no `hsl()` wrapper). `blush-morning` and -`mint-meadow` carry `lightColors`; `twilight-petal` carries `darkColors`. -Values copied verbatim from spec §"Variants (presets.json)". - -### Fonts (wave-1 policy) -- `fonts.json = []` (literal). Embed succeeds. -- No woff2s bundled. `assets/fonts/web/` holds a `README.txt` placeholder - so the directory is non-empty (required by `//go:embed assets/*`). -- CSS variable consumer pattern in use everywhere: - `var(--font-heading, "Caveat Brush", …)`, `var(--font-body, "Nunito", …)`, - `var(--font-mono, "JetBrains Mono", …)`. Fallback stacks degrade - gracefully when no admin has assigned fonts. -- `RECOMMENDED_FONTS.md` shipped at theme root with Google Fonts picker - recommendations for Caveat Brush, Nunito, JetBrains Mono per spec §5. - -### CSS strategy -- `--radius-soft: 20px;` declared once. -- `@keyframes pastel-shimmer` and `@keyframes breathe` declared. -- `cubic-bezier(.22,1,.36,1)` used in `.pastel-pill` and feature card - transitions. -- `.bg-watercolor-blush` / `.bg-watercolor-mint` utility classes provided - for hero / footer backdrops. -- `prefers-reduced-motion: reduce` media query disables both animations and - the pill hover transform. -- Theme CSS is injected via `CSSManifest.InputCSSAppend` (reads - `assets/css/pastel-dream.css`). The asset endpoint also serves a - mirrored `assets/style.css` so direct loads work in case the manifest is - bypassed. -- No hardcoded hex / rgb / named colors in `*.templ`, `*.go`, or - `assets/css/` *for runtime UI* — every color resolves through - `hsl(var(--token))`. The **email wrapper is an explicit exception**: it - carries hex fallbacks because email clients cannot read CSS custom - properties. The colors are sourced from `EmailContext.Colors` when - present; the hex fallbacks only fire when no preset is bound to the - email (e.g. raw previews). See "Open items / deferred" below. - -### Email wrapper -- `tr.RegisterEmailWrapper("pastel-dream", PastelEmailWrapper)` called once. -- 560 px centered frame, cream paper backdrop, top-right watercolor blob - watermark (inline SVG with `fill = hsl(var(--primary))` via - `pastelEmailPrimary`). -- Caveat Brush masthead (`'Caveat Brush', 'Sacramento', cursive`), - Nunito body. -- Footer carries an `` for the unsubscribe URL token and a one-line - Caveat Brush affirmation. -- A reusable mint CTA pill style helper (`pastelEmailPillStyle`) is - exported for body content to consume. - -## Build output +## Verification (both exit 0) ``` -$ cd /home/alex/src/blockninja/themes/pastel-dream -$ go mod tidy # ← exits 0 -$ /home/alex/go/bin/templ generate # ← exits 0, 12 *_templ.go files -$ make # ← exits 0 -CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o pastel-dream.so . -$ ls -lh pastel-dream.so --rw-rw-r-- 1 alex alex 21M Jun 6 13:25 pastel-dream.so -$ nm -D pastel-dream.so | grep Registration -000000000140c420 D git.dev.alexdunmow.com/block/themes/pastel-dream.Registration +$ cd themes/pastel-dream && make +Built pastel-dream@0.2.0 (codeless) → pastel-dream-0.2.0.bnp # EXIT 0 + +$ cd check-safety && go run . themes/pastel-dream +32 checks: 19 ok 13 skip -> OK # EXIT 0 ``` -## Safety check +Runtime template rendering is not exercised by these gates (codeless build + +check-safety are static). Visual correctness stays UNVERIFIED until Wave B. -The task brief points at -`/home/alex/src/blockninja/backend/go.mod` / `./cmd/check-safety`. **That -backend path does not exist on this system.** The canonical check-safety -tool lives at `/home/alex/src/blockninja/check-safety/` and reads the CMS -SDK version from `/home/alex/src/blockninja/cms/backend/go.mod`. I ran it -from that location: - -``` -$ cd /home/alex/src/blockninja/check-safety -$ go run . --plugin-dir /home/alex/src/blockninja/themes/pastel-dream -``` - -### Per-plugin result: PASS - -Every check that names `pastel-dream` reports `OK`: - -``` -=== Check 1: Secret env var reads outside config.Load() === - OK: No secret env var reads outside config.Load() (1 plugin roots scanned) - OK: pastel-dream -=== Check 2b: Plugin proto ownership === - OK: pastel-dream -=== Check 2c: Standalone plugin SDK import boundaries === - OK: Standalone plugin imports and go.mod stay on SDK version v0.11.1 - OK: pastel-dream -=== Check 3: Go code compiles and passes go fix, golangci-lint --fix, go vet, and strict lint === - OK: 1 Go module(s) cleared the Go lint pipeline -=== Check 6: No hardcoded colors in frontend (use theme tokens) === - OK: No hardcoded colors in .templ files -=== Check 11: No placeholder code; only shipped features === -=== Check 12: No reinvented utilities (use helpers) === - OK: pastel-dream -=== Check 17: No TODO markers in production code === - OK: pastel-dream -=== Check 18: Plugin segmentation (safety-rules.yml) === - OK: pastel-dream -=== Check 21: Plugin presets.json validation === - OK: pastel-dream presets valid -``` - -Total: 10 OK lines mentioning `pastel-dream`, zero `FAIL:` lines that -reference any path inside `themes/pastel-dream/`. - -### Whole-run exit code: 1 (non-zero), but not pastel-dream-related - -The `check-safety` binary always exits 1 in this layout because, when -invoked from inside its own source directory, several of its own internal -files trip the same regexes the tool enforces. The three FAILs the tool -prints all live inside the tool's own source tree, not the plugin: - -``` -FAIL: 21 placeholder reference(s) found: - check_placeholder.go:51, colors.go:183, comingsoon.go:163-231, main.go:19, … -FAIL: 10 TODO marker(s) found: - check_todos.go:34, todo.go:86, todo.go:115, frontend.go:108, main.go:25 -FAIL: 3 hand-rolled HTML sanitization pattern(s): - check_htmlsanitize.go:37-38, htmlsanitize.go:41 -``` - -None of those paths is in `themes/pastel-dream/`. The strict -`./cmd/check-safety . --plugin-dir ` exit-0 requirement -from the task brief cannot be satisfied from this checkout without -patching `check-safety` itself (out of scope and outside the theme -directory). The **plugin-specific verdict** is PASS. - -## Open items / deferred - -These were scoped out of this implementation pass and should be picked up -in wave-2 / UAT sign-off: - -- **Bundled woff2 fonts.** Per wave-1 policy (`themes/docs/FONTS.md`), - this pass ships `fonts.json = []`. Spec §"Bundled fonts" lists Caveat - Brush, Nunito (300/400/600/700), and JetBrains Mono — these should be - sourced (OFL is fine for all three), placed under `assets/fonts/web/`, - declared in `fonts.json`, and licence-attributed in `LICENSES.md`. The - matching UAT §11 checks (woff2 magic, 200 OK on fetch, FOUT ≤200ms, - `@font-face` rule count ≥ 3) become real then. -- **Marketplace assets (UAT §12).** Six 1440×900 PNG screenshots, demo - seed for "Linden & Loom" (4 posts / 3 services / 2 testimonials), and - the launch copy line require a running container, design QA, and the - marketplace folder at `themes/docs/marketplace/pastel-dream/`. None of - those can land inside the theme directory. -- **Email wrapper hex fallbacks vs UAT §5 regex.** UAT §5 reads: - `grep -rE "#[0-9a-fA-F]{3,8}|rgb\(|rgba\(" pastel-dream/*.templ … returns - zero matches`. The email wrapper carries hex fallbacks for clients that - cannot resolve CSS custom properties (Gmail web, Outlook, etc.). At - runtime `EmailContext.Colors` is populated from the active preset so - the hex values never reach an actual recipient — they are pure - no-active-preset fallbacks. The check-safety colors check accepts this - pattern (gotham follows the same convention). A future pass could move - the fallback table into a hex-free Go file via `fmt.Sprintf` from HSL - triples; deferred. -- **Live container deploy.** `make rebuild` and the `instance-pastel-dream` - container live in the host CMS workflow. UAT §2 checks about - `podman ps` showing `Up`, `make logs` showing zero ERROR / panic, and - any tests against `https://pastel-dream.localdev.blockninjacms.com/` - require that container, which is out of scope for this build pass. -- **DOM-based gates (UAT §5 line 7, §6, §7, §8, §10, §13).** These need - the running URL with Chrome DevTools / Playwright. Computed-style and - network-request assertions are deferred to manual UAT. -- **Regression gate (UAT §14).** Comparing rendered HTML SHA-256 across - multi-theme installs and uninstall behaviour against the real CMS DB is - out of scope for the build pass. -- **Sign-off (UAT §15).** Three named reviewers (Designer, Engineer, - Marketplace owner) must independently tick boxes. Cannot be self-applied. - -## Where everything lives - -``` -themes/pastel-dream/ -├── BUILD_REPORT.md ← this file -├── Makefile ← `make` builds .so locally, `make rebuild` deploys -├── RECOMMENDED_FONTS.md ← wave-1 Google Fonts picker recommendations -├── plugin.mod ← TOML metadata (locked to spec) -├── go.mod / go.sum ← block/core v0.11.1, no replace directives -├── presets.json ← 3 presets, all 19 tokens, HSL triples -├── fonts.json ← [] -├── embed.go ← //go:embed wiring -├── registration.go ← exports Registration + ThemeCSSManifest -├── register.go ← Register() + DefaultMasterPages() -├── helpers.go ← getString / getSlice / getBoolish / safeLink -├── template.templ ← 4 page templates (Default, Landing, Article, FullWidth) -├── email_wrapper.templ ← PastelEmailWrapper -├── .go + .templ ← one pair per block + override -├── *_templ.go ← templ-generated; committed -├── schemas/ ← one *.schema.json per block -├── assets/css/pastel-dream.css← injected via CSSManifest.InputCSSAppend -├── assets/style.css ← served at /templates/pastel-dream/style.css -└── assets/fonts/web/ ← reserved for wave-2 bundled woff2s -``` +## Known limits (platform gaps, per OVERRIDE-CONTRACT §11/§12) +- `auth-form`, `auth-status`, `password-reset-form`, `page-suggestions` overrides + are inert until the compiled-block override-dispatch fix lands (authored + correctly to model). +- Provider-backed overrides (navbar/footer menus, featured/related/popular posts, + category-list, breadcrumbs, blog-hero, sidebar-nav, author-bio-hero) render + themed chrome but empty lists until the codeless loader registers overrides via + the source path. Authored faithful to the builtin provider var names. +- `preview.png` is the old (wellness) render and must be regenerated in Wave B. diff --git a/RECOMMENDED_FONTS.md b/RECOMMENDED_FONTS.md index 35e711e..00d07c3 100644 --- a/RECOMMENDED_FONTS.md +++ b/RECOMMENDED_FONTS.md @@ -1,45 +1,45 @@ # Recommended fonts — Pastel Dream -This pass ships `fonts.json = []` per the wave-1 fonts policy (see -`themes/docs/FONTS.md`). The visual identity is realised through CSS variable -fallback stacks (`--font-heading`, `--font-body`, `--font-mono`) so the theme -renders sensibly out of the box. +This theme **bundles** its typographic identity as woff2 files in +`assets/fonts/` (see `fonts.json`). Both families are licensed under the SIL +Open Font License 1.1 (`assets/fonts/OFL.txt`). No admin setup is needed for +the intended look. -To match the intended Pastel Dream aesthetic, an admin should open the -typography panel and add the fonts below from the Google Fonts tab, then -assign each to its slot. All three are in the curated Google Fonts list and -require no upload. +## Bundled (source = template) -## Display / hand-lettered (assign to Heading) +| Slot | Family | Weights | Role | +|------|--------|---------|------| +| Heading | **Fraunces** | 400, 600 | Sweet, soft-serif display for headings, drop-caps, and signatures | +| Body | **Quicksand** | 400, 700 | Rounded geometric sans for body copy, menus, and accents | -- `google:Caveat Brush` — primary display face for H1 and H2. Hand-lettered, - warm, and rounded. Picks up beautifully behind the brush-stroke underline. -- Alternates (pick one): `google:Sacramento`, `google:Homemade Apple`. - -## Body (assign to Body) - -- `google:Nunito` — softened sans, ships with weights 300, 400, 600, 700. - Used at line-height 1.75 per the spec. -- Alternate: `google:Quicksand` for an even softer, more rounded body. - -## Mono / numerals (assign to Mono) - -- `google:JetBrains Mono` — used for eyebrow labels, small caps tracking, and - date strings. -- Alternate: `google:Fira Code`. - -## How to assign +Assign in the typography panel: 1. Open `/admin/system/typography` (or your instance's typography panel). -2. Open the Google Fonts tab. -3. Search for each family above and click "Add". -4. In the Heading / Body / Mono slots, pick the family you added. -5. Save. The CMS injects an `@import` link and re-renders. +2. In the **Heading** slot pick **Fraunces**; in the **Body** slot pick + **Quicksand**. Leave **Mono** on the system default, or pick a mono if you + use price/number strings heavily. +3. Save. The CMS emits the `@font-face` rules from the bundled files and + re-renders. -## Why no bundled woff2 in this pass +If no fonts are assigned, the theme still renders correctly through the CSS +variable fallback stacks (`--font-heading` → Fraunces/Playfair/serif; +`--font-body` → Quicksand/Nunito/system sans; `--font-mono` → JetBrains +Mono/system mono). The theme never hardcodes a `font-family` — every family +resolves through `var(--font-heading|body|mono, )` so the admin font +picker keeps working. -Wave-1 implementation policy ships every theme with `fonts.json = []` and -relies on the CSS variable consumer pattern. A future wave will bundle -distinctive commercial display faces (or self-host the OFL fonts above) for -themes where the typographic identity is core. See -`themes/docs/FONTS.md` for the full rationale. +## Alternates from Google Fonts + +If you prefer a different feel, these all suit the bakery persona and are in +the curated Google Fonts list: + +- Heading: `google:Comfortaa`, `google:Pacifico` (script), `google:Playfair Display`. +- Body: `google:Nunito`, `google:Comfortaa`, `google:Varela Round`. +- Mono: `google:JetBrains Mono`, `google:Fira Code`. + +## Licensing + +- **Fraunces** — Copyright 2018 The Fraunces Project Authors. OFL-1.1. +- **Quicksand** — Copyright 2011 The Quicksand Project Authors. OFL-1.1. + +Full text and copyright notices in `assets/fonts/OFL.txt`. diff --git a/RECOMMENDED_ICONS.md b/RECOMMENDED_ICONS.md new file mode 100644 index 0000000..ff65c73 --- /dev/null +++ b/RECOMMENDED_ICONS.md @@ -0,0 +1,29 @@ +# Recommended icons — Pastel Dream + +This theme renders icons through the CMS sprite system only +(``). It declares its packs in +`plugin.mod` `required_icon_packs`. Until the loader auto-installs declared +packs, install them by hand before activation. + +## Required packs + +| Pack | Used by | +|------|---------| +| **lucide** (bundled) | Navigation, hero and CTA arrows, contact and hours icons, timeline markers, gallery and card chrome. This is the primary pack and is bundled with the CMS. | +| **simple-icons** (bundled) | Brand marks in the footer and social-links override (Instagram, Facebook, etc.). | + +Both packs ship bundled with the CMS, so in most instances nothing needs to be +installed. If a fresh instance is missing one: + +1. Open `/admin/icons`. +2. Find **Lucide** and **Simple Icons** and click **Install** / **Enable**. +3. Save. Templates that reference `lucide:*` and `simple-icons:*` now resolve. + +## Notes + +- Block content stores icon references as `"pack:name"` strings and the + templates split them at render time. Do not inline raw SVG. +- If an icon name is missing from a pack, the sprite `` simply renders + nothing rather than breaking layout. Prefer the bundled Lucide names already + used across the overrides (`arrow-right`, `map-pin`, `navigation`, + `croissant`, `menu`, `x`, `chevron-down`). diff --git a/assets/css/pastel-dream.css b/assets/css/pastel-dream.css deleted file mode 100644 index dcb1c0b..0000000 --- a/assets/css/pastel-dream.css +++ /dev/null @@ -1,187 +0,0 @@ -/* Pastel Dream — theme-specific CSS appended to host Tailwind input. - * - * Rules: - * - All colors must consume tokens via hsl(var(--token)). No hardcoded hex/rgb/named colors. - * - All font-family declarations must consume var(--font-*) with a graceful fallback stack. - * - Animations must be guarded by prefers-reduced-motion: reduce. - */ - -:root { - --radius-soft: 20px; -} - -/* --- Watercolor utility classes ---------------------------------------- */ -/* Soft radial wash backgrounds, tinted from the active palette via primary - * and accent tokens. Used as decorative layers behind hero and footer. - */ -.bg-watercolor-blush { - background-image: - radial-gradient(60% 70% at 20% 30%, hsl(var(--primary) / 0.18) 0%, transparent 65%), - radial-gradient(50% 60% at 80% 70%, hsl(var(--accent) / 0.15) 0%, transparent 60%); -} - -.bg-watercolor-mint { - background-image: - radial-gradient(60% 70% at 25% 35%, hsl(var(--secondary) / 0.45) 0%, transparent 65%), - radial-gradient(55% 65% at 75% 65%, hsl(var(--primary) / 0.12) 0%, transparent 60%); -} - -/* --- Soft shadow utilities -------------------------------------------- */ -/* Shadows are large, low-alpha, and tinted toward primary rather than grey. */ -.shadow-soft { - box-shadow: 0 12px 40px -8px hsl(var(--primary) / 0.18), - 0 4px 12px -2px hsl(var(--primary) / 0.10); -} - -.shadow-soft-hover { - box-shadow: 0 16px 48px -8px hsl(var(--primary) / 0.24), - 0 6px 16px -2px hsl(var(--primary) / 0.14); -} - -/* --- Deckle edge ------------------------------------------------------- */ -/* Hand-torn paper effect for testimonial cards. Uses mask-image so it - * renders against whatever background lies beneath. - */ -.deckle-edge { - -webkit-mask-image: - radial-gradient(circle at 6px 6px, transparent 4px, hsl(var(--card)) 4.5px), - linear-gradient(to bottom, hsl(var(--card)) 0%, hsl(var(--card)) 100%); - mask-image: - radial-gradient(circle at 6px 6px, transparent 4px, hsl(var(--card)) 4.5px), - linear-gradient(to bottom, hsl(var(--card)) 0%, hsl(var(--card)) 100%); - -webkit-mask-composite: source-over; - mask-composite: add; - border: 1px dashed hsl(var(--border)); -} - -/* --- Pill button override --------------------------------------------- */ -/* Spec §"Block overrides": every button is pill (9999px), tinted shadow, - * 2px hover lift, easing cubic-bezier(.22,1,.36,1). - */ -.pastel-pill { - border-radius: 9999px; - padding: 0.75rem 1.75rem; - font-family: var(--font-body, "Nunito", "Quicksand", system-ui, sans-serif); - font-weight: 600; - background-color: hsl(var(--primary)); - color: hsl(var(--primary-foreground)); - box-shadow: 0 8px 24px -6px hsl(var(--primary) / 0.40); - transition: transform 240ms cubic-bezier(.22,1,.36,1), - box-shadow 240ms cubic-bezier(.22,1,.36,1), - background-color 240ms cubic-bezier(.22,1,.36,1); - border: none; - cursor: pointer; - display: inline-block; -} - -.pastel-pill:hover { - transform: translateY(-2px); - box-shadow: 0 12px 32px -6px hsl(var(--primary) / 0.50); - background-color: hsl(var(--primary) / 0.92); -} - -.pastel-pill:focus-visible { - outline: 2px solid hsl(var(--ring)); - outline-offset: 2px; -} - -/* --- Soft card -------------------------------------------------------- */ -.pastel-card { - border-radius: var(--radius-soft); - background-color: hsl(var(--card)); - color: hsl(var(--card-foreground)); - border: 1px solid hsl(var(--border)); - box-shadow: 0 12px 40px -8px hsl(var(--primary) / 0.18), - 0 4px 12px -2px hsl(var(--primary) / 0.10); -} - -/* --- Brush-stroke underline for headings ------------------------------ */ -.brush-underline { - position: relative; - display: inline-block; -} - -.brush-underline::after { - content: ""; - position: absolute; - left: 0; - right: 0; - bottom: -0.2em; - height: 0.22em; - background-color: hsl(var(--accent) / 0.55); - border-radius: 9999px; - z-index: -1; -} - -/* --- Typography fallbacks --------------------------------------------- */ -/* Spec §"Visual language" pairs Caveat Brush + Nunito; admin can override - * via the font picker. Fallback stacks degrade gracefully. - */ -.font-display { - font-family: var(--font-heading, "Caveat Brush", "Sacramento", "Brush Script MT", cursive); - letter-spacing: 0.01em; -} - -.font-body { - font-family: var(--font-body, "Nunito", "Quicksand", "Helvetica Neue", system-ui, sans-serif); - line-height: 1.75; -} - -.font-mono { - font-family: var(--font-mono, "JetBrains Mono", "Fira Code", ui-monospace, monospace); -} - -/* --- Animations ------------------------------------------------------- */ -/* @keyframes pastel-shimmer — drift the accent overlay slowly across the - * affirmation strip. Spec §13.10: animation-duration >= 6s. - */ -@keyframes pastel-shimmer { - 0% { - background-position: 0% 50%; - opacity: 0.85; - } - 50% { - background-position: 100% 50%; - opacity: 1; - } - 100% { - background-position: 0% 50%; - opacity: 0.85; - } -} - -/* @keyframes breathe — gentle scale pulse, used by hero CTA and watercolor - * blobs for an in-and-out breath rhythm. - */ -@keyframes breathe { - 0%, 100% { - transform: scale(1); - opacity: 0.85; - } - 50% { - transform: scale(1.04); - opacity: 1; - } -} - -.animate-pastel-shimmer { - background-size: 200% 200%; - animation: pastel-shimmer 8s ease-in-out infinite; -} - -.animate-breathe { - animation: breathe 6s ease-in-out infinite; -} - -/* --- Accessibility: respect prefers-reduced-motion -------------------- */ -@media (prefers-reduced-motion: reduce) { - .animate-pastel-shimmer, - .animate-breathe, - .pastel-pill { - animation: none !important; - transition: none !important; - } - .pastel-pill:hover { - transform: none; - } -} diff --git a/assets/fonts/OFL.txt b/assets/fonts/OFL.txt new file mode 100644 index 0000000..c662e40 --- /dev/null +++ b/assets/fonts/OFL.txt @@ -0,0 +1,109 @@ +Bundled fonts — SIL Open Font License 1.1 +========================================= + +This directory bundles latin-subset woff2 files for two families, both +licensed under the SIL Open Font License, Version 1.1 (OFL-1.1). + +Copyright notices +----------------- +Fraunces: Copyright 2018 The Fraunces Project Authors (https://github.com/undercasetype/Fraunces) +Quicksand: Copyright 2011 The Quicksand Project Authors (https://github.com/andrew-paglinawan/QuicksandFamily) + +Files +----- +fraunces-latin-400.woff2, fraunces-latin-600.woff2 — Fraunces (sweet display headings) +quicksand-400.woff2, quicksand-700.woff2 — Quicksand (rounded body) + +Full license text (SIL OFL 1.1) +------------------------------- +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/fonts/fraunces-latin-400.woff2 b/assets/fonts/fraunces-latin-400.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..83cbd8ff4a0b4df7f1f19124577ea8c60028976e GIT binary patch literal 17968 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 literal 0 HcmV?d00001 diff --git a/assets/fonts/fraunces-latin-600.woff2 b/assets/fonts/fraunces-latin-600.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..58cee1fd39e841fc0299b080f79fa5b8d6958c76 GIT binary patch literal 18096 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 PgDsZ(2Xqbf7XSbN8Zj(T literal 0 HcmV?d00001 diff --git a/assets/fonts/quicksand-400.woff2 b/assets/fonts/quicksand-400.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..8018cfd7d9bb09613508b2177421b529f9d2b743 GIT binary patch literal 15776 zcmV;RJzv6iPew8T0RR9106m}p5C8xG0ETz~06jba0RR9100000000000000000000 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
I}Y$OAS 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 C8#IRi literal 0 HcmV?d00001 diff --git a/assets/fonts/web/README.txt b/assets/fonts/web/README.txt deleted file mode 100644 index 65a45c7..0000000 --- a/assets/fonts/web/README.txt +++ /dev/null @@ -1,5 +0,0 @@ -This directory is reserved for bundled woff2 font files. - -In this wave-1 implementation pass, `fonts.json = []` and no woff2s are -bundled. See ../../../RECOMMENDED_FONTS.md for the Google Fonts the admin -should add via the typography panel. diff --git a/assets/style.css b/assets/style.css index 7fee599..7a2562c 100644 --- a/assets/style.css +++ b/assets/style.css @@ -1,133 +1,249 @@ -/* Pastel Dream — theme stylesheet served at /templates/pastel-dream/style.css +/* Pastel Dream theme styles — bakery / patisserie. * - * The bulk of the theme CSS (utility classes, keyframes, --radius-soft) is - * injected into the host Tailwind input via CSSManifest.InputCSSAppend so it - * participates in Tailwind's purge. This file holds anything an admin's - * browser may load directly when rendering a page — it currently mirrors the - * injected utilities so the page still gets variables and keyframes even if - * the CSSManifest pipeline is bypassed. + * Dual-mode via the 19 shadcn-style HSL token CSS variables (--background, + * --foreground, --primary, --accent, --border, --muted, --card, ...) consumed + * as hsl(var(--token)). No literal colors anywhere. Font families resolve ONLY + * through the BlockNinja font variables (--font-heading, --font-body, + * --font-mono) with the fallback stacks below — never hardcoded, so the admin + * font picker keeps working. + * + * Motion tier: CSS-FIRST. Every animation is a gentle CSS drift and is disabled + * under prefers-reduced-motion. No canvas, no JS showpiece. */ :root { - --radius-soft: 20px; + --pd-heading-fallback: "Fraunces", "Playfair Display", Georgia, "Times New Roman", serif; + --pd-body-fallback: "Quicksand", "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + --pd-hand-fallback: "Quicksand", "Comfortaa", "Segoe Print", cursive; + --pd-mono-fallback: "JetBrains Mono", "Fira Code", Menlo, Consolas, monospace; + --pd-radius: 1.5rem; } -.bg-watercolor-blush { - background-image: - radial-gradient(60% 70% at 20% 30%, hsl(var(--primary) / 0.18) 0%, transparent 65%), - radial-gradient(50% 60% at 80% 70%, hsl(var(--accent) / 0.15) 0%, transparent 60%); +/* --- Watercolor wash background ----------------------------------------- */ +/* The signature "morning haze": soft radial pastel washes tinted from the + * active palette, drifting slowly. Reads as watercolor on cream in light mode + * and as a dusk-lilac glow in dark mode — all from the same tokens. */ +body.pd-paper { + background-color: hsl(var(--background)); + background-image: + radial-gradient(60% 55% at 15% 12%, hsl(var(--primary) / 0.14) 0%, transparent 60%), + radial-gradient(55% 50% at 85% 20%, hsl(var(--accent) / 0.12) 0%, transparent 58%), + radial-gradient(65% 60% at 75% 85%, hsl(var(--secondary) / 0.40) 0%, transparent 62%), + radial-gradient(50% 50% at 25% 90%, hsl(var(--primary) / 0.08) 0%, transparent 60%); + background-attachment: fixed; + background-repeat: no-repeat; } -.bg-watercolor-mint { - background-image: - radial-gradient(60% 70% at 25% 35%, hsl(var(--secondary) / 0.45) 0%, transparent 65%), - radial-gradient(55% 65% at 75% 65%, hsl(var(--primary) / 0.12) 0%, transparent 60%); +/* --- Type roles --------------------------------------------------------- */ +.pd-display { + font-family: var(--font-heading, var(--pd-heading-fallback)); + font-optical-sizing: auto; + font-feature-settings: "liga" 1, "dlig" 1; + letter-spacing: -0.005em; +} +.pd-body { + font-family: var(--font-body, var(--pd-body-fallback)); + line-height: 1.7; +} +.pd-mono { + font-family: var(--font-mono, var(--pd-mono-fallback)); +} +/* Sweet accent lettering (badges, captions, section titles). Routed through + * --font-heading with a rounded fallback: unassigned it renders Quicksand; if + * the admin sets a heading font the accents follow it (3-slot model intended). */ +.pd-hand { + font-family: var(--font-heading, var(--pd-hand-fallback)); + letter-spacing: 0.005em; + line-height: 1.2; } -.shadow-soft { - box-shadow: 0 12px 40px -8px hsl(var(--primary) / 0.18), - 0 4px 12px -2px hsl(var(--primary) / 0.10); +/* --- Brush underline ---------------------------------------------------- */ +.pd-doodle-underline { + background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 12' preserveAspectRatio='none'%3E%3Cpath d='M2 8 Q 22 1 44 6 T 88 6 T 118 6' fill='none' stroke='currentColor' stroke-width='4' stroke-linecap='round' opacity='0.35'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: 0 100%; + background-size: 100% 0.55em; + padding-bottom: 0.16em; } -.deckle-edge { - border: 1px dashed hsl(var(--border)); +/* --- Drop-cap ----------------------------------------------------------- */ +.pd-dropcap > p:first-of-type::first-letter { + font-family: var(--font-heading, var(--pd-heading-fallback)); + font-size: 3.8em; + line-height: 0.8; + float: left; + padding: 0.06em 0.14em 0 0; + color: hsl(var(--primary)); } -.pastel-pill { - border-radius: 9999px; - padding: 0.75rem 1.75rem; - font-family: var(--font-body, "Nunito", "Quicksand", system-ui, sans-serif); - font-weight: 600; - background-color: hsl(var(--primary)); - color: hsl(var(--primary-foreground)); - box-shadow: 0 8px 24px -6px hsl(var(--primary) / 0.40); - transition: transform 240ms cubic-bezier(.22,1,.36,1), - box-shadow 240ms cubic-bezier(.22,1,.36,1), - background-color 240ms cubic-bezier(.22,1,.36,1); - border: none; - cursor: pointer; - display: inline-block; +/* --- Soft pill button (icing-smooth, lifts on hover) -------------------- */ +.pd-tag { + position: relative; + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.7rem 1.5rem; + background-color: hsl(var(--primary)); + color: hsl(var(--primary-foreground)); + border: none; + border-radius: 9999px; + font-family: var(--font-body, var(--pd-body-fallback)); + font-weight: 600; + letter-spacing: 0.01em; + box-shadow: 0 8px 22px -8px hsl(var(--primary) / 0.45); + transition: transform 220ms cubic-bezier(.22,1,.36,1), box-shadow 220ms cubic-bezier(.22,1,.36,1), background-color 220ms ease; + cursor: pointer; +} +.pd-tag:hover { + transform: translateY(-2px); + box-shadow: 0 14px 30px -8px hsl(var(--primary) / 0.52); + background-color: hsl(var(--primary) / 0.92); +} +.pd-tag:focus-visible { + outline: 2px solid hsl(var(--ring)); + outline-offset: 3px; +} +.pd-tag--ghost { + background-color: transparent; + color: hsl(var(--primary)); + box-shadow: none; + border: 1.5px solid hsl(var(--primary) / 0.45); +} +.pd-tag--ghost:hover { + background-color: hsl(var(--primary) / 0.08); + box-shadow: none; } -.pastel-pill:hover { - transform: translateY(-2px); - box-shadow: 0 12px 32px -6px hsl(var(--primary) / 0.50); - background-color: hsl(var(--primary) / 0.92); +/* --- Scalloped edges (piped-icing / doily trim) ------------------------- */ +.pd-torn-top { + -webkit-mask-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 12' preserveAspectRatio='none'%3E%3Cpath d='M0 6 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 L120 12 L0 12 Z' fill='black'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 12' preserveAspectRatio='none'%3E%3Cpath d='M0 6 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 L120 12 L0 12 Z' fill='black'/%3E%3C/svg%3E"); + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: 100% 100%; + mask-size: 100% 100%; +} +.pd-torn-bottom { + -webkit-mask-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 12' preserveAspectRatio='none'%3E%3Cpath d='M0 0 L120 0 L120 6 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 Z' fill='black'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 12' preserveAspectRatio='none'%3E%3Cpath d='M0 0 L120 0 L120 6 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 Z' fill='black'/%3E%3C/svg%3E"); + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: 100% 100%; + mask-size: 100% 100%; } -.pastel-pill:focus-visible { - outline: 2px solid hsl(var(--ring)); - outline-offset: 2px; +/* --- Surfaces (rounded-everything) ------------------------------------- */ +.pd-card { + background-color: hsl(var(--card)); + color: hsl(var(--card-foreground)); + border: 1px solid hsl(var(--border)); + border-radius: var(--pd-radius); + box-shadow: 0 12px 40px -14px hsl(var(--primary) / 0.22), 0 4px 12px -6px hsl(var(--primary) / 0.10); +} +.pd-frame { + border: 1px solid hsl(var(--border)); + background-color: hsl(var(--card)); + border-radius: var(--pd-radius); } -.pastel-card { - border-radius: var(--radius-soft); - background-color: hsl(var(--card)); - color: hsl(var(--card-foreground)); - border: 1px solid hsl(var(--border)); - box-shadow: 0 12px 40px -8px hsl(var(--primary) / 0.18), - 0 4px 12px -2px hsl(var(--primary) / 0.10); +/* --- Menu card (soft pastel board for the menu-list override) ---------- */ +/* Not an inverted chalkboard: a soft card with a primary wash. Text inherits + * card-foreground so item names and section titles stay legible in both modes. */ +.pd-chalkboard { + position: relative; + background-color: hsl(var(--card)); + background-image: + radial-gradient(70% 60% at 12% 8%, hsl(var(--primary) / 0.10) 0%, transparent 60%), + radial-gradient(60% 55% at 90% 92%, hsl(var(--accent) / 0.08) 0%, transparent 60%); + color: hsl(var(--card-foreground)); + border: 1px solid hsl(var(--border)); + border-radius: var(--pd-radius); + box-shadow: 0 16px 48px -18px hsl(var(--primary) / 0.28); +} +.pd-chalk-rule { + border-color: hsl(var(--border)); + border-style: solid; +} +.pd-chalk-muted { + color: hsl(var(--muted-foreground)); } -.brush-underline { - position: relative; - display: inline-block; +/* --- Hours: today highlight -------------------------------------------- */ +.pd-hours-today { + background-color: hsl(var(--accent) / 0.16); + color: hsl(var(--accent-foreground)); + border-left: 3px solid hsl(var(--accent)); + border-radius: 0 9999px 9999px 0; + padding-left: 0.75rem; } -.brush-underline::after { - content: ""; - position: absolute; - left: 0; - right: 0; - bottom: -0.2em; - height: 0.22em; - background-color: hsl(var(--accent) / 0.55); - border-radius: 9999px; - z-index: -1; +/* --- Location pin ------------------------------------------------------- */ +.pd-pin { + color: hsl(var(--accent)); } -.font-display { - font-family: var(--font-heading, "Caveat Brush", "Sacramento", "Brush Script MT", cursive); - letter-spacing: 0.01em; +/* --- Soft badge (timeline markers) ------------------------------------- */ +.pd-stamp { + display: inline-flex; + align-items: center; + justify-content: center; + width: 4rem; + height: 4rem; + border-radius: 9999px; + background-color: hsl(var(--accent)); + color: hsl(var(--accent-foreground)); + border: 3px solid hsl(var(--card)); + box-shadow: 0 8px 20px -8px hsl(var(--accent) / 0.55); + font-family: var(--font-heading, var(--pd-hand-fallback)); + font-size: 1.05rem; + text-align: center; + line-height: 1.05; + transition: transform 200ms cubic-bezier(.22,1,.36,1); +} +.pd-stamp:hover { + transform: scale(1.06); +} +.pd-stamp-sm { + width: 2.25rem; + height: 2.25rem; + font-size: 0.95rem; + box-shadow: none; } -.font-body { - font-family: var(--font-body, "Nunito", "Quicksand", "Helvetica Neue", system-ui, sans-serif); - line-height: 1.75; +/* --- Brush divider draw-in --------------------------------------------- */ +.pd-doodle-draw path, +.pd-doodle-draw ellipse, +.pd-doodle-draw circle { + stroke-dasharray: 260; + stroke-dashoffset: 260; + animation: pd-draw 1.6s ease forwards; } -.font-mono { - font-family: var(--font-mono, "JetBrains Mono", "Fira Code", ui-monospace, monospace); +@keyframes pd-draw { + to { stroke-dashoffset: 0; } } -@keyframes pastel-shimmer { - 0% { background-position: 0% 50%; opacity: 0.85; } - 50% { background-position: 100% 50%; opacity: 1; } - 100% { background-position: 0% 50%; opacity: 0.85; } -} - -@keyframes breathe { - 0%, 100% { transform: scale(1); opacity: 0.85; } - 50% { transform: scale(1.04); opacity: 1; } -} - -.animate-pastel-shimmer { - background-size: 200% 200%; - animation: pastel-shimmer 8s ease-in-out infinite; -} - -.animate-breathe { - animation: breathe 6s ease-in-out infinite; +/* --- Gentle atmosphere: slow wash drift (CSS-first signature) ----------- */ +@keyframes pd-breathe { + 0%, 100% { transform: scale(1); opacity: 0.9; } + 50% { transform: scale(1.05); opacity: 1; } +} +.pd-breathe { + animation: pd-breathe 8s ease-in-out infinite; } +/* --- Motion safety: static fallback ------------------------------------ */ @media (prefers-reduced-motion: reduce) { - .animate-pastel-shimmer, - .animate-breathe, - .pastel-pill { - animation: none !important; - transition: none !important; - } - .pastel-pill:hover { - transform: none; - } + .pd-tag, + .pd-stamp { transition: none; } + .pd-tag:hover, + .pd-stamp:hover { transform: none; } + .pd-breathe { animation: none; } + .pd-doodle-draw path, + .pd-doodle-draw ellipse, + .pd-doodle-draw circle { + stroke-dasharray: none; + stroke-dashoffset: 0; + animation: none; + } } diff --git a/blocks/affirmation.ninjatpl b/blocks/affirmation.ninjatpl deleted file mode 100644 index 356d8d1..0000000 --- a/blocks/affirmation.ninjatpl +++ /dev/null @@ -1,10 +0,0 @@ -
-
-

- “{{ quote|default:"You are doing enough." }}” -

- {% if author %}

- {{ author }} -

{% endif %} -
-
diff --git a/blocks/affirmation.schema.json b/blocks/affirmation.schema.json deleted file mode 100644 index 15008eb..0000000 --- a/blocks/affirmation.schema.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Affirmation Strip", - "description": "Slow-shimmer band carrying a short affirmation in display type.", - "type": "object", - "properties": { - "quote": { - "type": "string", - "title": "Affirmation", - "description": "Short calming line (one or two sentences).", - "default": "You are doing enough.", - "x-editor": "textarea" - }, - "author": { - "type": "string", - "title": "Attribution", - "description": "Optional source for the line.", - "x-editor": "text" - }, - "palette": { - "type": "string", - "title": "Palette", - "description": "Which watercolor wash to use behind the strip.", - "default": "blush", - "enum": ["blush", "mint", "butter", "sky"], - "x-editor": "select" - } - } -} diff --git a/blocks/bakers_note.ninjatpl b/blocks/bakers_note.ninjatpl new file mode 100644 index 0000000..fb007b6 --- /dev/null +++ b/blocks/bakers_note.ninjatpl @@ -0,0 +1,14 @@ +
+
+
+ + +
+ {% if kicker %}

{{ kicker }}

{% endif %} + {% if body %}
{{ body|safe }}
{% endif %} + {% if signatureName %}

{{ signatureName }}

{% endif %} + {% if signatureRole %}

{{ signatureRole }}

{% endif %} +
+
+
+
diff --git a/blocks/bakers_note.schema.json b/blocks/bakers_note.schema.json new file mode 100644 index 0000000..299ba7e --- /dev/null +++ b/blocks/bakers_note.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Baker's Note", + "description": "A signed note from the baker in sweet display type over a watercolor blob", + "type": "object", + "properties": { + "kicker": { + "type": "string", + "title": "Kicker", + "default": "A note from the kitchen", + "x-editor": "text" + }, + "body": { + "type": "string", + "title": "Note", + "description": "Rich text note", + "x-editor": "richtext" + }, + "signatureName": { + "type": "string", + "title": "Signature Name", + "x-editor": "text" + }, + "signatureRole": { + "type": "string", + "title": "Signature Role", + "x-editor": "text" + } + } +} diff --git a/blocks/blocks.yaml b/blocks/blocks.yaml index bd2d23c..702733d 100644 --- a/blocks/blocks.yaml +++ b/blocks/blocks.yaml @@ -1,42 +1,33 @@ -# Pastel Dream theme block definitions (codeless .bnp, WO-WZ-021). -# Keys are fully qualified (pastel-dream:) 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. +# Pastel Dream theme block definitions (codeless .bnp, WO-TF-023). +# Only true persona furniture stays a theme block. Everything that duplicated a +# builtin (navbar, hero, quote, testimonial, feature-grid, footer, gallery, +# menu, divider ...) is now a template override of the builtin key under +# templates/overrides/pastel-dream/ — not a block here. +# +# Keys are fully qualified (pastel-dream:): the codeless loader registers +# definitions verbatim (no PluginBlockRegistry prefixing in the codeless path). blocks: - - key: pastel-dream:soft-navbar - title: Soft Navbar - description: Rounded pill navigation with a watercolor blob behind the logo. - category: navigation - schema: soft-navbar.schema.json - template: soft-navbar.ninjatpl - - key: pastel-dream:watercolor-hero - title: Watercolor Hero - description: Hero with two-tone watercolor blobs layered behind soft display type. - category: theme - schema: watercolor-hero.schema.json - template: watercolor-hero.ninjatpl - - key: pastel-dream:affirmation - title: Affirmation Strip - description: Slow-shimmer band carrying a short calming line in display type. - category: theme - schema: affirmation.schema.json - template: affirmation.ninjatpl - - key: pastel-dream:testimonial-soft - title: Soft Testimonial - description: Quote card with a deckle paper edge, avatar, and rating. - category: theme - schema: testimonial-soft.schema.json - template: testimonial-soft.ninjatpl - - key: pastel-dream:feature-grid-soft - title: Feature Trio - description: Three-up card grid for services, offerings, or core values. - category: layout - schema: feature-grid-soft.schema.json - template: feature-grid-soft.ninjatpl - - key: pastel-dream:cozy-footer - title: Cozy Footer - description: Newsletter signup, closing affirmation, menu column, and social pills. - category: navigation - schema: cozy-footer.schema.json - template: cozy-footer.ninjatpl + - key: pastel-dream:seasonal_specials + title: Seasonal Specials + description: This season's featured bakes in a soft card grid — name, note, price and a sweet badge. + category: content + schema: seasonal_specials.schema.json + template: seasonal_specials.ninjatpl + - key: pastel-dream:cake_gallery + title: Cake Gallery + description: A scalloped showcase of cakes with flavour, price and an order link per cake. + category: content + schema: cake_gallery.schema.json + template: cake_gallery.ninjatpl + - key: pastel-dream:order_ribbon + title: Order Ribbon + description: A rounded call-to-order ribbon with a pickup note and a soft pill button. + category: content + schema: order_ribbon.schema.json + template: order_ribbon.ninjatpl + - key: pastel-dream:bakers_note + title: Baker's Note + description: A signed note from the baker in sweet display type over a watercolor blob. + category: content + schema: bakers_note.schema.json + template: bakers_note.ninjatpl diff --git a/blocks/cake_gallery.ninjatpl b/blocks/cake_gallery.ninjatpl new file mode 100644 index 0000000..b7d8d9b --- /dev/null +++ b/blocks/cake_gallery.ninjatpl @@ -0,0 +1,29 @@ +
+ +
diff --git a/blocks/cake_gallery.schema.json b/blocks/cake_gallery.schema.json new file mode 100644 index 0000000..31e81c1 --- /dev/null +++ b/blocks/cake_gallery.schema.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cake Gallery", + "description": "A scalloped showcase of cakes with flavour, price and an order link per cake", + "type": "object", + "properties": { + "heading": { + "type": "string", + "title": "Heading", + "x-editor": "text" + }, + "intro": { + "type": "string", + "title": "Intro", + "x-editor": "textarea" + }, + "currency": { + "type": "string", + "title": "Currency Symbol", + "default": "$", + "x-editor": "text" + }, + "cakes": { + "type": "array", + "title": "Cakes", + "x-editor": "collection", + "items": { + "type": "object", + "properties": { + "image": { "type": "string", "title": "Image", "x-editor": "media" }, + "name": { "type": "string", "title": "Name", "x-editor": "text" }, + "flavour": { "type": "string", "title": "Flavour", "x-editor": "text" }, + "price": { "type": "string", "title": "Price", "x-editor": "text" }, + "orderUrl": { "type": "string", "title": "Order Link", "x-editor": "text" } + } + } + } + } +} diff --git a/blocks/cozy-footer.ninjatpl b/blocks/cozy-footer.ninjatpl deleted file mode 100644 index 8c8085f..0000000 --- a/blocks/cozy-footer.ninjatpl +++ /dev/null @@ -1,23 +0,0 @@ -
- -
-

- {{ affirmation|default:"Be gentle with yourself today." }} -

- {% if showSignup != "no" and showSignup != "false" and showSignup != "0" and showSignup != "off" and showSignup != false %}
- - - -
{% endif %} - {% if social %}
- {% for href in social %} - Visit - {% endfor %} -
{% endif %} -

- Pastel Dream · with care -

-
-
diff --git a/blocks/cozy-footer.schema.json b/blocks/cozy-footer.schema.json deleted file mode 100644 index 36e30d0..0000000 --- a/blocks/cozy-footer.schema.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Cozy Footer", - "description": "Newsletter signup, small affirmation line, menu, and social links.", - "type": "object", - "properties": { - "showSignup": { - "type": "string", - "title": "Newsletter signup", - "description": "Toggle the inline email signup form.", - "default": "yes", - "enum": ["yes", "no"], - "x-editor": "select" - }, - "affirmation": { - "type": "string", - "title": "Closing line", - "description": "Short closing affirmation displayed beneath the signup.", - "default": "Be gentle with yourself today.", - "x-editor": "textarea" - }, - "menuName": { - "type": "string", - "title": "Menu", - "description": "Name of the menu rendered in the footer column.", - "x-editor": "menu-select" - }, - "social": { - "type": "array", - "title": "Social links", - "description": "Outbound social links shown as small pills.", - "x-editor": "array", - "items": { - "type": "string", - "x-editor": "link" - } - } - } -} diff --git a/blocks/feature-grid-soft.ninjatpl b/blocks/feature-grid-soft.ninjatpl deleted file mode 100644 index 497e2aa..0000000 --- a/blocks/feature-grid-soft.ninjatpl +++ /dev/null @@ -1,18 +0,0 @@ -
-
- {% if intro %}
- {{ intro|safe }} -
{% endif %} - {% if items %}
- {% for item in items %}
- {% if item.icon %}
- -
{% else %}{% endif %} - {% if item.title %}

{{ item.title }}

{% endif %} - {% if item.body %}

{{ item.body }}

{% endif %} -
{% endfor %} -
{% else %}
-

Add up to three cards to the trio.

-
{% endif %} -
-
diff --git a/blocks/feature-grid-soft.schema.json b/blocks/feature-grid-soft.schema.json deleted file mode 100644 index 4ef1cf8..0000000 --- a/blocks/feature-grid-soft.schema.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Feature Trio", - "description": "Three-up card grid for services, offerings, or values.", - "type": "object", - "properties": { - "intro": { - "type": "string", - "title": "Intro", - "description": "Optional introduction above the cards.", - "x-editor": "richtext" - }, - "items": { - "type": "array", - "title": "Cards", - "description": "List of feature cards. Three is the recommended count.", - "x-editor": "collection", - "items": { - "type": "object", - "properties": { - "icon": { - "type": "string", - "title": "Icon", - "description": "Decorative icon or small illustration.", - "x-editor": "media" - }, - "title": { - "type": "string", - "title": "Title", - "x-editor": "text" - }, - "body": { - "type": "string", - "title": "Body", - "x-editor": "textarea" - } - }, - "required": ["title"] - } - } - } -} diff --git a/blocks/order_ribbon.ninjatpl b/blocks/order_ribbon.ninjatpl new file mode 100644 index 0000000..0363fba --- /dev/null +++ b/blocks/order_ribbon.ninjatpl @@ -0,0 +1,18 @@ +
+
+
+
+
+ {% if heading %}

{{ heading }}

{% endif %} + {% if message %}

{{ message }}

{% endif %} + {% if pickupNote %}

{{ pickupNote }}

{% endif %} +
+ {% if ctaLabel %} + + {% endif %} +
+
+
+
diff --git a/blocks/order_ribbon.schema.json b/blocks/order_ribbon.schema.json new file mode 100644 index 0000000..4eab3e6 --- /dev/null +++ b/blocks/order_ribbon.schema.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Order Ribbon", + "description": "A rounded call-to-order ribbon with a pickup note and a soft pill button", + "type": "object", + "properties": { + "heading": { + "type": "string", + "title": "Heading", + "default": "Order for the weekend", + "x-editor": "text" + }, + "message": { + "type": "string", + "title": "Message", + "x-editor": "textarea" + }, + "pickupNote": { + "type": "string", + "title": "Pickup Note", + "description": "Small line about lead time or pickup", + "x-editor": "text" + }, + "ctaLabel": { + "type": "string", + "title": "Button Label", + "default": "Place an order", + "x-editor": "text" + }, + "ctaUrl": { + "type": "string", + "title": "Button Link", + "default": "/contact", + "x-editor": "text" + } + } +} diff --git a/blocks/seasonal_specials.ninjatpl b/blocks/seasonal_specials.ninjatpl new file mode 100644 index 0000000..c101b9c --- /dev/null +++ b/blocks/seasonal_specials.ninjatpl @@ -0,0 +1,28 @@ +
+
+
+ {% if kicker %}

{{ kicker }}

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

{{ heading }}

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

{{ intro }}

{% endif %} +
+ {% if items %} +
+ {% for item in items %} +
+
+ {% if item.image %}{% img item.image alt=item.name|default:"Seasonal special" class="h-full w-full object-cover" %}{% else %}
Add a photo
{% endif %} +
+
+
+

{{ item.name }}

+ {% if item.price %}{% if currency %}{{ currency }}{% endif %}{{ item.price }}{% endif %} +
+ {% if item.badge %}{{ item.badge }}{% endif %} + {% if item.note %}

{{ item.note }}

{% endif %} +
+
+ {% endfor %} +
+ {% endif %} +
+
diff --git a/blocks/seasonal_specials.schema.json b/blocks/seasonal_specials.schema.json new file mode 100644 index 0000000..64b3b77 --- /dev/null +++ b/blocks/seasonal_specials.schema.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Seasonal Specials", + "description": "This season's featured bakes in a soft card grid", + "type": "object", + "properties": { + "kicker": { + "type": "string", + "title": "Kicker", + "description": "Small sweet line above the heading", + "default": "This season", + "x-editor": "text" + }, + "heading": { + "type": "string", + "title": "Heading", + "x-editor": "text" + }, + "intro": { + "type": "string", + "title": "Intro", + "x-editor": "textarea" + }, + "currency": { + "type": "string", + "title": "Currency Symbol", + "default": "$", + "x-editor": "text" + }, + "items": { + "type": "array", + "title": "Specials", + "x-editor": "collection", + "items": { + "type": "object", + "properties": { + "image": { "type": "string", "title": "Image", "x-editor": "media" }, + "name": { "type": "string", "title": "Name", "x-editor": "text" }, + "note": { "type": "string", "title": "Note", "x-editor": "textarea" }, + "price": { "type": "string", "title": "Price", "x-editor": "text" }, + "badge": { "type": "string", "title": "Badge", "x-editor": "text" } + } + } + } + } +} diff --git a/blocks/soft-navbar.ninjatpl b/blocks/soft-navbar.ninjatpl deleted file mode 100644 index bedb70f..0000000 --- a/blocks/soft-navbar.ninjatpl +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/blocks/soft-navbar.schema.json b/blocks/soft-navbar.schema.json deleted file mode 100644 index 64114d4..0000000 --- a/blocks/soft-navbar.schema.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Soft Navbar", - "description": "Rounded pill nav with a watercolor blob behind the logo wordmark.", - "type": "object", - "properties": { - "logo": { - "type": "string", - "title": "Logo Wordmark", - "description": "Plain-text wordmark displayed at the top-left.", - "default": "Pastel Dream", - "x-editor": "text" - }, - "menuName": { - "type": "string", - "title": "Menu", - "description": "Name of the menu to render in the pill.", - "default": "main", - "x-editor": "menu-select" - }, - "ctaText": { - "type": "string", - "title": "CTA Label", - "description": "Optional pill button to the right of the menu.", - "x-editor": "text" - }, - "ctaHref": { - "type": "string", - "title": "CTA Link", - "description": "Target URL or internal page link for the CTA pill.", - "x-editor": "link" - } - } -} diff --git a/blocks/testimonial-soft.ninjatpl b/blocks/testimonial-soft.ninjatpl deleted file mode 100644 index caa6b99..0000000 --- a/blocks/testimonial-soft.ninjatpl +++ /dev/null @@ -1,27 +0,0 @@ -
- {% if rating|integer > 0 %}
- {% if rating|integer >= 1 %} - - {% endif %}{% if rating|integer >= 2 %} - - {% endif %}{% if rating|integer >= 3 %} - - {% endif %}{% if rating|integer >= 4 %} - - {% endif %}{% if rating|integer >= 5 %} - - {% endif %} -
{% endif %} -
- {{ quote|safe }} -
- {% if name or role %}
- {% if avatar %}{{ name }}{% elif name %} - {% for w in name|split:" " %}{% if w and forloop.Counter <= 2 %}{{ w|first|upper }}{% endif %}{% endfor %} - {% endif %} -
- {% if name %}

{{ name }}

{% endif %} - {% if role %}

{{ role }}

{% endif %} -
-
{% endif %} -
diff --git a/blocks/testimonial-soft.schema.json b/blocks/testimonial-soft.schema.json deleted file mode 100644 index 800f917..0000000 --- a/blocks/testimonial-soft.schema.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Soft Testimonial", - "description": "Deckle-edge card carrying a quote, attribution, optional avatar, and rating.", - "type": "object", - "properties": { - "quote": { - "type": "string", - "title": "Quote", - "description": "The testimonial body. Rich text.", - "x-editor": "richtext" - }, - "name": { - "type": "string", - "title": "Name", - "description": "Person being quoted.", - "x-editor": "text" - }, - "role": { - "type": "string", - "title": "Role", - "description": "Their title, company, or context.", - "x-editor": "text" - }, - "avatar": { - "type": "string", - "title": "Avatar", - "description": "Optional portrait or illustration.", - "x-editor": "media" - }, - "rating": { - "type": "number", - "title": "Rating", - "description": "Star rating, 0 to 5.", - "minimum": 0, - "maximum": 5, - "x-editor": "number" - } - } -} diff --git a/blocks/watercolor-hero.ninjatpl b/blocks/watercolor-hero.ninjatpl deleted file mode 100644 index 03f900c..0000000 --- a/blocks/watercolor-hero.ninjatpl +++ /dev/null @@ -1,35 +0,0 @@ -
- - -
-
- {% if eyebrow %}

- {{ eyebrow }} -

{% endif %} -

- {{ headline|default:"Soft starts"|safe }} -

- {% if body %}
- {{ body|safe }} -
{% endif %} - {% if ctaText %} - {{ ctaText }} - {% endif %} -
- {% if image %}
-
- -
-
{% else %}{% endif %} -
-
diff --git a/blocks/watercolor-hero.schema.json b/blocks/watercolor-hero.schema.json deleted file mode 100644 index 9f7fc06..0000000 --- a/blocks/watercolor-hero.schema.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Watercolor Hero", - "description": "Hero with two-tone SVG blobs layered behind a soft headline and CTA.", - "type": "object", - "properties": { - "eyebrow": { - "type": "string", - "title": "Eyebrow", - "description": "Small label above the headline (e.g. 'new', 'autumn collection').", - "x-editor": "text" - }, - "headline": { - "type": "string", - "title": "Headline", - "description": "Main hero headline. Caveat Brush display.", - "default": "Soft starts", - "x-editor": "richtext" - }, - "body": { - "type": "string", - "title": "Body", - "description": "Supporting paragraph below the headline.", - "x-editor": "richtext" - }, - "image": { - "type": "string", - "title": "Feature Image", - "description": "Optional hero image; renders to the right of the headline on desktop.", - "x-editor": "media" - }, - "ctaText": { - "type": "string", - "title": "CTA Label", - "x-editor": "text" - }, - "ctaHref": { - "type": "string", - "title": "CTA Link", - "x-editor": "link" - } - } -} diff --git a/fonts.json b/fonts.json index fe51488..b621997 100644 --- a/fonts.json +++ b/fonts.json @@ -1 +1,18 @@ -[] +[ + { + "name": "Fraunces", + "family": "Fraunces", + "variants": [ + { "weight": "400", "style": "normal", "file": "fonts/fraunces-latin-400.woff2" }, + { "weight": "600", "style": "normal", "file": "fonts/fraunces-latin-600.woff2" } + ] + }, + { + "name": "Quicksand", + "family": "Quicksand", + "variants": [ + { "weight": "400", "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 c976349..64cf80d 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -1,231 +1,358 @@ -# Pastel Dream theme — codeless .bnp manifest (WO-WZ-021 Phase B1). -# Synthesized into manifest.pb by `ninja plugin build` (no plugin.wasm). +# Pastel Dream theme — codeless .bnp manifest (Wave A, WO-TF-023). +# Bakery / patisserie persona. 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: pastel-dream title: Pastel Dream - description: "Soft watercolor theme with blush, mint, and butter palettes for wellness, parenting, and spa brands." + description: "Soft bakery and patisserie theme — watercolor washes, scalloped edges, rounded menu cards and seasonal specials." page_templates: - system: pastel-dream key: default title: Default - description: "Soft layered hero, masthead nav, footer with newsletter." + description: "Header, main and footer over a soft watercolor wash." slots: [header, main, footer] - system: pastel-dream key: landing - title: Soft Landing - description: "Watercolor hero, affirmation strip, CTA, footer." - slots: [hero, main, cta, footer] + title: Landing + description: "Hero, menu, story and an invitation to order." + slots: [hero, menu, story, cta, footer] - system: pastel-dream key: article - title: Reading Room - description: "Narrow, centered editorial column with side rail." + title: Article + description: "Narrow reading column with a soft drop-cap for the journal and recipes." slots: [header, main, footer] - system: pastel-dream key: full-width - title: Open Air - description: "Edge-to-edge sections for galleries and seasonal looks." + title: Full Width + description: "Edge-to-edge galleries and counter shots." slots: [header, main, footer] - -template_overrides: - - { template: pastel-dream, block: heading } - - { template: pastel-dream, block: text } - - { template: pastel-dream, block: button } - - { template: pastel-dream, block: card } + - system: pastel-dream + key: blog-index + title: Blog Index + description: "Blog hero, featured posts and a post grid." + slots: [header, featured, main, footer] + - system: pastel-dream + key: contact + title: Contact + description: "Order and enquiry form, hours and pickup details." + slots: [header, main, footer] + - system: pastel-dream + key: auth + title: Auth + description: "Centered soft card for login, register and password reset." + slots: [main, footer] email_wrappers: - pastel-dream +template_overrides: + - { template: pastel-dream, block: announcement-bar } + - { template: pastel-dream, block: audio-embed } + - { template: pastel-dream, block: auth-form } + - { template: pastel-dream, block: author-bio } + - { template: pastel-dream, block: author-bio-hero } + - { template: pastel-dream, block: auth-status } + - { template: pastel-dream, block: blog-hero } + - { template: pastel-dream, block: breadcrumbs } + - { template: pastel-dream, block: button } + - { template: pastel-dream, block: card } + - { template: pastel-dream, block: category-list } + - { template: pastel-dream, block: contact-card } + - { template: pastel-dream, block: contact-form } + - { template: pastel-dream, block: countdown } + - { template: pastel-dream, block: cta } + - { template: pastel-dream, block: divider } + - { template: pastel-dream, block: event-list } + - { template: pastel-dream, block: faq } + - { template: pastel-dream, block: featured-posts } + - { template: pastel-dream, block: feature-grid } + - { template: pastel-dream, block: footer } + - { template: pastel-dream, block: gallery } + - { template: pastel-dream, block: heading } + - { template: pastel-dream, block: hero } + - { template: pastel-dream, block: hours-location } + - { template: pastel-dream, block: image } + - { template: pastel-dream, block: logo-strip } + - { template: pastel-dream, block: menu-list } + - { template: pastel-dream, block: navbar } + - { template: pastel-dream, block: newsletter-signup } + - { template: pastel-dream, block: page-suggestions } + - { template: pastel-dream, block: password-reset-form } + - { template: pastel-dream, block: popular-posts } + - { template: pastel-dream, block: post-hero } + - { template: pastel-dream, block: post-metadata } + - { template: pastel-dream, block: pricing } + - { template: pastel-dream, block: quote } + - { template: pastel-dream, block: related-posts } + - { template: pastel-dream, block: rich-section } + - { template: pastel-dream, block: rich-text } + - { template: pastel-dream, block: sidebar-nav } + - { template: pastel-dream, block: social-links } + - { template: pastel-dream, block: stats } + - { template: pastel-dream, block: team } + - { template: pastel-dream, block: testimonial } + - { template: pastel-dream, block: text } + - { template: pastel-dream, block: timeline } + - { template: pastel-dream, block: utility-bar } + - { template: pastel-dream, block: video-embed } + css: input_css_append: | - /* Pastel Dream — theme-specific CSS appended to host Tailwind input. + /* Pastel Dream theme styles — bakery / patisserie. * - * Rules: - * - All colors must consume tokens via hsl(var(--token)). No hardcoded hex/rgb/named colors. - * - All font-family declarations must consume var(--font-*) with a graceful fallback stack. - * - Animations must be guarded by prefers-reduced-motion: reduce. + * Dual-mode via the 19 shadcn-style HSL token CSS variables (--background, + * --foreground, --primary, --accent, --border, --muted, --card, ...) consumed + * as hsl(var(--token)). No literal colors anywhere. Font families resolve ONLY + * through the BlockNinja font variables (--font-heading, --font-body, + * --font-mono) with the fallback stacks below — never hardcoded, so the admin + * font picker keeps working. + * + * Motion tier: CSS-FIRST. Every animation is a gentle CSS drift and is disabled + * under prefers-reduced-motion. No canvas, no JS showpiece. */ - + :root { - --radius-soft: 20px; + --pd-heading-fallback: "Fraunces", "Playfair Display", Georgia, "Times New Roman", serif; + --pd-body-fallback: "Quicksand", "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + --pd-hand-fallback: "Quicksand", "Comfortaa", "Segoe Print", cursive; + --pd-mono-fallback: "JetBrains Mono", "Fira Code", Menlo, Consolas, monospace; + --pd-radius: 1.5rem; } - - /* --- Watercolor utility classes ---------------------------------------- */ - /* Soft radial wash backgrounds, tinted from the active palette via primary - * and accent tokens. Used as decorative layers behind hero and footer. - */ - .bg-watercolor-blush { - background-image: - radial-gradient(60% 70% at 20% 30%, hsl(var(--primary) / 0.18) 0%, transparent 65%), - radial-gradient(50% 60% at 80% 70%, hsl(var(--accent) / 0.15) 0%, transparent 60%); + + /* --- Watercolor wash background ----------------------------------------- */ + /* The signature "morning haze": soft radial pastel washes tinted from the + * active palette, drifting slowly. Reads as watercolor on cream in light mode + * and as a dusk-lilac glow in dark mode — all from the same tokens. */ + body.pd-paper { + background-color: hsl(var(--background)); + background-image: + radial-gradient(60% 55% at 15% 12%, hsl(var(--primary) / 0.14) 0%, transparent 60%), + radial-gradient(55% 50% at 85% 20%, hsl(var(--accent) / 0.12) 0%, transparent 58%), + radial-gradient(65% 60% at 75% 85%, hsl(var(--secondary) / 0.40) 0%, transparent 62%), + radial-gradient(50% 50% at 25% 90%, hsl(var(--primary) / 0.08) 0%, transparent 60%); + background-attachment: fixed; + background-repeat: no-repeat; } - - .bg-watercolor-mint { - background-image: - radial-gradient(60% 70% at 25% 35%, hsl(var(--secondary) / 0.45) 0%, transparent 65%), - radial-gradient(55% 65% at 75% 65%, hsl(var(--primary) / 0.12) 0%, transparent 60%); + + /* --- Type roles --------------------------------------------------------- */ + .pd-display { + font-family: var(--font-heading, var(--pd-heading-fallback)); + font-optical-sizing: auto; + font-feature-settings: "liga" 1, "dlig" 1; + letter-spacing: -0.005em; } - - /* --- Soft shadow utilities -------------------------------------------- */ - /* Shadows are large, low-alpha, and tinted toward primary rather than grey. */ - .shadow-soft { - box-shadow: 0 12px 40px -8px hsl(var(--primary) / 0.18), - 0 4px 12px -2px hsl(var(--primary) / 0.10); + .pd-body { + font-family: var(--font-body, var(--pd-body-fallback)); + line-height: 1.7; } - - .shadow-soft-hover { - box-shadow: 0 16px 48px -8px hsl(var(--primary) / 0.24), - 0 6px 16px -2px hsl(var(--primary) / 0.14); + .pd-mono { + font-family: var(--font-mono, var(--pd-mono-fallback)); } - - /* --- Deckle edge ------------------------------------------------------- */ - /* Hand-torn paper effect for testimonial cards. Uses mask-image so it - * renders against whatever background lies beneath. - */ - .deckle-edge { - -webkit-mask-image: - radial-gradient(circle at 6px 6px, transparent 4px, hsl(var(--card)) 4.5px), - linear-gradient(to bottom, hsl(var(--card)) 0%, hsl(var(--card)) 100%); - mask-image: - radial-gradient(circle at 6px 6px, transparent 4px, hsl(var(--card)) 4.5px), - linear-gradient(to bottom, hsl(var(--card)) 0%, hsl(var(--card)) 100%); - -webkit-mask-composite: source-over; - mask-composite: add; - border: 1px dashed hsl(var(--border)); + /* Sweet accent lettering (badges, captions, section titles). Routed through + * --font-heading with a rounded fallback: unassigned it renders Quicksand; if + * the admin sets a heading font the accents follow it (3-slot model intended). */ + .pd-hand { + font-family: var(--font-heading, var(--pd-hand-fallback)); + letter-spacing: 0.005em; + line-height: 1.2; } - - /* --- Pill button override --------------------------------------------- */ - /* Spec §"Block overrides": every button is pill (9999px), tinted shadow, - * 2px hover lift, easing cubic-bezier(.22,1,.36,1). - */ - .pastel-pill { - border-radius: 9999px; - padding: 0.75rem 1.75rem; - font-family: var(--font-body, "Nunito", "Quicksand", system-ui, sans-serif); - font-weight: 600; - background-color: hsl(var(--primary)); - color: hsl(var(--primary-foreground)); - box-shadow: 0 8px 24px -6px hsl(var(--primary) / 0.40); - transition: transform 240ms cubic-bezier(.22,1,.36,1), - box-shadow 240ms cubic-bezier(.22,1,.36,1), - background-color 240ms cubic-bezier(.22,1,.36,1); - border: none; - cursor: pointer; - display: inline-block; + + /* --- Brush underline ---------------------------------------------------- */ + .pd-doodle-underline { + background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 12' preserveAspectRatio='none'%3E%3Cpath d='M2 8 Q 22 1 44 6 T 88 6 T 118 6' fill='none' stroke='currentColor' stroke-width='4' stroke-linecap='round' opacity='0.35'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: 0 100%; + background-size: 100% 0.55em; + padding-bottom: 0.16em; } - - .pastel-pill:hover { - transform: translateY(-2px); - box-shadow: 0 12px 32px -6px hsl(var(--primary) / 0.50); - background-color: hsl(var(--primary) / 0.92); + + /* --- Drop-cap ----------------------------------------------------------- */ + .pd-dropcap > p:first-of-type::first-letter { + font-family: var(--font-heading, var(--pd-heading-fallback)); + font-size: 3.8em; + line-height: 0.8; + float: left; + padding: 0.06em 0.14em 0 0; + color: hsl(var(--primary)); } - - .pastel-pill:focus-visible { - outline: 2px solid hsl(var(--ring)); - outline-offset: 2px; + + /* --- Soft pill button (icing-smooth, lifts on hover) -------------------- */ + .pd-tag { + position: relative; + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.7rem 1.5rem; + background-color: hsl(var(--primary)); + color: hsl(var(--primary-foreground)); + border: none; + border-radius: 9999px; + font-family: var(--font-body, var(--pd-body-fallback)); + font-weight: 600; + letter-spacing: 0.01em; + box-shadow: 0 8px 22px -8px hsl(var(--primary) / 0.45); + transition: transform 220ms cubic-bezier(.22,1,.36,1), box-shadow 220ms cubic-bezier(.22,1,.36,1), background-color 220ms ease; + cursor: pointer; } - - /* --- Soft card -------------------------------------------------------- */ - .pastel-card { - border-radius: var(--radius-soft); - background-color: hsl(var(--card)); - color: hsl(var(--card-foreground)); - border: 1px solid hsl(var(--border)); - box-shadow: 0 12px 40px -8px hsl(var(--primary) / 0.18), - 0 4px 12px -2px hsl(var(--primary) / 0.10); + .pd-tag:hover { + transform: translateY(-2px); + box-shadow: 0 14px 30px -8px hsl(var(--primary) / 0.52); + background-color: hsl(var(--primary) / 0.92); } - - /* --- Brush-stroke underline for headings ------------------------------ */ - .brush-underline { - position: relative; - display: inline-block; + .pd-tag:focus-visible { + outline: 2px solid hsl(var(--ring)); + outline-offset: 3px; } - - .brush-underline::after { - content: ""; - position: absolute; - left: 0; - right: 0; - bottom: -0.2em; - height: 0.22em; - background-color: hsl(var(--accent) / 0.55); - border-radius: 9999px; - z-index: -1; + .pd-tag--ghost { + background-color: transparent; + color: hsl(var(--primary)); + box-shadow: none; + border: 1.5px solid hsl(var(--primary) / 0.45); } - - /* --- Typography fallbacks --------------------------------------------- */ - /* Spec §"Visual language" pairs Caveat Brush + Nunito; admin can override - * via the font picker. Fallback stacks degrade gracefully. - */ - .font-display { - font-family: var(--font-heading, "Caveat Brush", "Sacramento", "Brush Script MT", cursive); - letter-spacing: 0.01em; + .pd-tag--ghost:hover { + background-color: hsl(var(--primary) / 0.08); + box-shadow: none; } - - .font-body { - font-family: var(--font-body, "Nunito", "Quicksand", "Helvetica Neue", system-ui, sans-serif); - line-height: 1.75; + + /* --- Scalloped edges (piped-icing / doily trim) ------------------------- */ + .pd-torn-top { + -webkit-mask-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 12' preserveAspectRatio='none'%3E%3Cpath d='M0 6 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 L120 12 L0 12 Z' fill='black'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 12' preserveAspectRatio='none'%3E%3Cpath d='M0 6 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 a6 6 0 0 1 12 0 L120 12 L0 12 Z' fill='black'/%3E%3C/svg%3E"); + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: 100% 100%; + mask-size: 100% 100%; } - - .font-mono { - font-family: var(--font-mono, "JetBrains Mono", "Fira Code", ui-monospace, monospace); + .pd-torn-bottom { + -webkit-mask-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 12' preserveAspectRatio='none'%3E%3Cpath d='M0 0 L120 0 L120 6 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 Z' fill='black'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 12' preserveAspectRatio='none'%3E%3Cpath d='M0 0 L120 0 L120 6 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 a6 6 0 0 1 -12 0 Z' fill='black'/%3E%3C/svg%3E"); + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: 100% 100%; + mask-size: 100% 100%; } - - /* --- Animations ------------------------------------------------------- */ - /* @keyframes pastel-shimmer — drift the accent overlay slowly across the - * affirmation strip. Spec §13.10: animation-duration >= 6s. - */ - @keyframes pastel-shimmer { - 0% { - background-position: 0% 50%; - opacity: 0.85; - } - 50% { - background-position: 100% 50%; - opacity: 1; - } - 100% { - background-position: 0% 50%; - opacity: 0.85; - } + + /* --- Surfaces (rounded-everything) ------------------------------------- */ + .pd-card { + background-color: hsl(var(--card)); + color: hsl(var(--card-foreground)); + border: 1px solid hsl(var(--border)); + border-radius: var(--pd-radius); + box-shadow: 0 12px 40px -14px hsl(var(--primary) / 0.22), 0 4px 12px -6px hsl(var(--primary) / 0.10); } - - /* @keyframes breathe — gentle scale pulse, used by hero CTA and watercolor - * blobs for an in-and-out breath rhythm. - */ - @keyframes breathe { - 0%, 100% { - transform: scale(1); - opacity: 0.85; - } - 50% { - transform: scale(1.04); - opacity: 1; - } + .pd-frame { + border: 1px solid hsl(var(--border)); + background-color: hsl(var(--card)); + border-radius: var(--pd-radius); } - - .animate-pastel-shimmer { - background-size: 200% 200%; - animation: pastel-shimmer 8s ease-in-out infinite; + + /* --- Menu card (soft pastel board for the menu-list override) ---------- */ + /* Not an inverted chalkboard: a soft card with a primary wash. Text inherits + * card-foreground so item names and section titles stay legible in both modes. */ + .pd-chalkboard { + position: relative; + background-color: hsl(var(--card)); + background-image: + radial-gradient(70% 60% at 12% 8%, hsl(var(--primary) / 0.10) 0%, transparent 60%), + radial-gradient(60% 55% at 90% 92%, hsl(var(--accent) / 0.08) 0%, transparent 60%); + color: hsl(var(--card-foreground)); + border: 1px solid hsl(var(--border)); + border-radius: var(--pd-radius); + box-shadow: 0 16px 48px -18px hsl(var(--primary) / 0.28); } - - .animate-breathe { - animation: breathe 6s ease-in-out infinite; + .pd-chalk-rule { + border-color: hsl(var(--border)); + border-style: solid; } - - /* --- Accessibility: respect prefers-reduced-motion -------------------- */ + .pd-chalk-muted { + color: hsl(var(--muted-foreground)); + } + + /* --- Hours: today highlight -------------------------------------------- */ + .pd-hours-today { + background-color: hsl(var(--accent) / 0.16); + color: hsl(var(--accent-foreground)); + border-left: 3px solid hsl(var(--accent)); + border-radius: 0 9999px 9999px 0; + padding-left: 0.75rem; + } + + /* --- Location pin ------------------------------------------------------- */ + .pd-pin { + color: hsl(var(--accent)); + } + + /* --- Soft badge (timeline markers) ------------------------------------- */ + .pd-stamp { + display: inline-flex; + align-items: center; + justify-content: center; + width: 4rem; + height: 4rem; + border-radius: 9999px; + background-color: hsl(var(--accent)); + color: hsl(var(--accent-foreground)); + border: 3px solid hsl(var(--card)); + box-shadow: 0 8px 20px -8px hsl(var(--accent) / 0.55); + font-family: var(--font-heading, var(--pd-hand-fallback)); + font-size: 1.05rem; + text-align: center; + line-height: 1.05; + transition: transform 200ms cubic-bezier(.22,1,.36,1); + } + .pd-stamp:hover { + transform: scale(1.06); + } + .pd-stamp-sm { + width: 2.25rem; + height: 2.25rem; + font-size: 0.95rem; + box-shadow: none; + } + + /* --- Brush divider draw-in --------------------------------------------- */ + .pd-doodle-draw path, + .pd-doodle-draw ellipse, + .pd-doodle-draw circle { + stroke-dasharray: 260; + stroke-dashoffset: 260; + animation: pd-draw 1.6s ease forwards; + } + + @keyframes pd-draw { + to { stroke-dashoffset: 0; } + } + + /* --- Gentle atmosphere: slow wash drift (CSS-first signature) ----------- */ + @keyframes pd-breathe { + 0%, 100% { transform: scale(1); opacity: 0.9; } + 50% { transform: scale(1.05); opacity: 1; } + } + .pd-breathe { + animation: pd-breathe 8s ease-in-out infinite; + } + + /* --- Motion safety: static fallback ------------------------------------ */ @media (prefers-reduced-motion: reduce) { - .animate-pastel-shimmer, - .animate-breathe, - .pastel-pill { - animation: none !important; - transition: none !important; - } - .pastel-pill:hover { - transform: none; - } + .pd-tag, + .pd-stamp { transition: none; } + .pd-tag:hover, + .pd-stamp:hover { transform: none; } + .pd-breathe { animation: none; } + .pd-doodle-draw path, + .pd-doodle-draw ellipse, + .pd-doodle-draw circle { + stroke-dasharray: none; + stroke-dashoffset: 0; + animation: none; + } } diff --git a/master_pages.json b/master_pages.json index 2ee973e..9010426 100644 --- a/master_pages.json +++ b/master_pages.json @@ -2,11 +2,11 @@ { "key": "pastel-dream:default-master", "title": "Pastel Dream Default Master", - "page_templates": ["default", "article"], + "page_templates": ["default", "article", "full-width", "contact"], "blocks": [ - { "block_key": "pastel-dream:soft-navbar", "title": "Soft Nav", "content": { "menuName": "main", "logo": "Pastel Dream" }, "slot": "header", "sort_order": 0 }, - { "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main", "placeholder": "Page content" }, "slot": "main", "sort_order": 0 }, - { "block_key": "pastel-dream:cozy-footer", "title": "Cozy Footer", "content": { "showSignup": "yes", "affirmation": "Be gentle with yourself today." }, "slot": "footer", "sort_order": 0 } + { "block_key": "navbar", "title": "Main Navigation", "content": { "menuName": "main", "companyName": "Petal & Crumb" }, "slot": "header", "sort_order": 0 }, + { "block_key": "slot", "title": "Main Content", "content": { "slotName": "main", "placeholder": "Add your page here" }, "slot": "main", "sort_order": 0 }, + { "block_key": "footer", "title": "Site Footer", "content": { "companyName": "Petal & Crumb", "tagline": "A small patisserie, baked fresh each morning." }, "slot": "footer", "sort_order": 0 } ] }, { @@ -14,10 +14,32 @@ "title": "Pastel Dream Landing Master", "page_templates": ["landing"], "blocks": [ - { "block_key": "pastel-dream:watercolor-hero", "title": "Watercolor Hero", "content": { "eyebrow": "new", "headline": "Soft starts", "body": "A gentle place to begin.", "ctaText": "Begin" }, "slot": "hero", "sort_order": 0 }, - { "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 }, - { "block_key": "pastel-dream:affirmation", "title": "Affirmation Strip", "content": { "quote": "You are doing enough.", "author": "Pastel Dream" }, "slot": "cta", "sort_order": 0 }, - { "block_key": "pastel-dream:cozy-footer", "title": "Cozy Footer", "content": { "showSignup": "yes" }, "slot": "footer", "sort_order": 0 } + { "block_key": "navbar", "title": "Main Navigation", "content": { "menuName": "main", "companyName": "Petal & Crumb" }, "slot": "hero", "sort_order": 0 }, + { "block_key": "slot", "title": "Hero", "content": { "slotName": "hero", "placeholder": "Drop a hero block" }, "slot": "hero", "sort_order": 1 }, + { "block_key": "slot", "title": "Menu", "content": { "slotName": "menu", "placeholder": "Feature the menu" }, "slot": "menu", "sort_order": 0 }, + { "block_key": "slot", "title": "Story", "content": { "slotName": "story", "placeholder": "Tell the story" }, "slot": "story", "sort_order": 0 }, + { "block_key": "slot", "title": "Call To Action", "content": { "slotName": "cta", "placeholder": "Invite them to order" }, "slot": "cta", "sort_order": 0 }, + { "block_key": "footer", "title": "Site Footer", "content": { "companyName": "Petal & Crumb", "tagline": "A small patisserie, baked fresh each morning." }, "slot": "footer", "sort_order": 0 } + ] + }, + { + "key": "pastel-dream:blog-master", + "title": "Pastel Dream Blog Master", + "page_templates": ["blog-index"], + "blocks": [ + { "block_key": "navbar", "title": "Main Navigation", "content": { "menuName": "main", "companyName": "Petal & Crumb" }, "slot": "header", "sort_order": 0 }, + { "block_key": "slot", "title": "Featured", "content": { "slotName": "featured", "placeholder": "Featured posts" }, "slot": "featured", "sort_order": 0 }, + { "block_key": "slot", "title": "Posts", "content": { "slotName": "main", "placeholder": "Post grid" }, "slot": "main", "sort_order": 0 }, + { "block_key": "footer", "title": "Site Footer", "content": { "companyName": "Petal & Crumb", "tagline": "A small patisserie, baked fresh each morning." }, "slot": "footer", "sort_order": 0 } + ] + }, + { + "key": "pastel-dream:auth-master", + "title": "Pastel Dream Auth Master", + "page_templates": ["auth"], + "blocks": [ + { "block_key": "slot", "title": "Auth Form", "content": { "slotName": "main", "placeholder": "Auth form" }, "slot": "main", "sort_order": 0 }, + { "block_key": "slot", "title": "Footer Note", "content": { "slotName": "footer", "placeholder": "Small print" }, "slot": "footer", "sort_order": 0 } ] } ] diff --git a/plugin.mod b/plugin.mod index b54e49b..25bf522 100644 --- a/plugin.mod +++ b/plugin.mod @@ -3,7 +3,8 @@ name = "pastel-dream" display_name = "Pastel Dream" scope = "@themes" version = "0.2.0" -description = "Soft watercolor theme with blush, mint, and butter palettes for wellness, parenting, and spa brands." +description = "Soft bakery and patisserie theme — watercolor washes, scalloped edges, rounded menu cards, and seasonal specials in blush, berry, and lilac." kind = "theme" categories = ["templates"] -tags = ["pastel", "soft", "wellness", "parenting", "spa", "doula", "calm", "skincare", "mindfulness"] +tags = ["pastel", "bakery", "patisserie", "cafe", "food", "soft", "rounded", "watercolor", "menu", "seasonal"] +required_icon_packs = ["lucide", "simple-icons"] diff --git a/presets.json b/presets.json index a349754..15d928e 100644 --- a/presets.json +++ b/presets.json @@ -1,88 +1,251 @@ [ { - "id": "blush-morning", - "name": "Blush Morning", - "description": "Default — warm peach paper with rose-blush primary and sage trim.", + "id": "morning-haze", + "name": "Morning Haze", + "description": "Blush rose on warm cream with a mint accent. The default patisserie counter at opening.", "theme": { - "mode": "light", + "mode": "both", "lightColors": { - "background": "25 60% 98%", - "foreground": "340 20% 22%", - "card": "0 0% 100%", - "cardForeground": "340 20% 22%", - "popover": "0 0% 100%", - "popoverForeground": "340 20% 22%", - "primary": "350 65% 72%", - "primaryForeground": "340 40% 18%", - "secondary": "150 35% 88%", - "secondaryForeground": "155 35% 22%", - "muted": "30 40% 95%", - "mutedForeground": "340 12% 48%", - "accent": "48 85% 80%", - "accentForeground": "35 50% 22%", - "destructive": "0 75% 65%", - "destructiveForeground": "0 0% 100%", - "border": "340 30% 90%", - "input": "340 30% 92%", - "ring": "350 65% 72%" - } - } - }, - { - "id": "mint-meadow", - "name": "Mint Meadow", - "description": "Sage-led variant for spa and doula brands; rose drops to accent.", - "theme": { - "mode": "light", - "lightColors": { - "background": "150 40% 97%", - "foreground": "155 35% 18%", - "card": "0 0% 100%", - "cardForeground": "155 35% 18%", - "popover": "0 0% 100%", - "popoverForeground": "155 35% 18%", - "primary": "155 40% 60%", - "primaryForeground": "0 0% 100%", - "secondary": "350 50% 92%", - "secondaryForeground": "345 40% 28%", - "muted": "150 25% 94%", - "mutedForeground": "155 18% 42%", - "accent": "48 80% 82%", - "accentForeground": "35 50% 22%", - "destructive": "5 70% 62%", - "destructiveForeground": "0 0% 100%", - "border": "150 25% 88%", - "input": "150 25% 92%", - "ring": "155 40% 60%" - } - } - }, - { - "id": "twilight-petal", - "name": "Twilight Petal", - "description": "Dusky night-mode pastel — plum velvet with blush glow.", - "theme": { - "mode": "dark", + "background": "30 44% 97%", + "foreground": "335 25% 22%", + "card": "30 42% 99%", + "cardForeground": "335 25% 22%", + "popover": "30 42% 99%", + "popoverForeground": "335 25% 22%", + "primary": "340 58% 60%", + "primaryForeground": "40 50% 98%", + "secondary": "18 52% 92%", + "secondaryForeground": "335 25% 24%", + "muted": "30 30% 93%", + "mutedForeground": "335 12% 42%", + "accent": "160 38% 52%", + "accentForeground": "160 40% 12%", + "destructive": "0 70% 55%", + "destructiveForeground": "40 50% 98%", + "border": "24 30% 87%", + "input": "24 30% 87%", + "ring": "340 58% 60%" + }, "darkColors": { - "background": "300 25% 12%", - "foreground": "30 40% 94%", - "card": "300 22% 16%", - "cardForeground": "30 40% 94%", - "popover": "300 22% 16%", - "popoverForeground": "30 40% 94%", - "primary": "345 70% 78%", - "primaryForeground": "340 40% 18%", - "secondary": "155 25% 32%", - "secondaryForeground": "150 30% 92%", - "muted": "300 18% 22%", + "background": "290 22% 12%", + "foreground": "320 22% 92%", + "card": "290 20% 16%", + "cardForeground": "320 22% 92%", + "popover": "290 20% 16%", + "popoverForeground": "320 22% 92%", + "primary": "340 64% 72%", + "primaryForeground": "290 30% 12%", + "secondary": "290 18% 22%", + "secondaryForeground": "320 22% 92%", + "muted": "290 15% 20%", "mutedForeground": "300 12% 70%", - "accent": "48 75% 72%", - "accentForeground": "35 50% 18%", - "destructive": "0 70% 60%", - "destructiveForeground": "0 0% 100%", - "border": "300 20% 28%", - "input": "300 20% 22%", - "ring": "345 70% 78%" + "accent": "160 44% 60%", + "accentForeground": "290 30% 12%", + "destructive": "0 60% 60%", + "destructiveForeground": "320 22% 92%", + "border": "290 16% 26%", + "input": "290 16% 24%", + "ring": "340 64% 72%" + } + } + }, + { + "id": "berry-cream", + "name": "Berry Cream", + "description": "Raspberry on ivory with a plum accent. Jam pots and fruit tarts.", + "theme": { + "mode": "both", + "lightColors": { + "background": "350 40% 97%", + "foreground": "345 30% 20%", + "card": "350 38% 99%", + "cardForeground": "345 30% 20%", + "popover": "350 38% 99%", + "popoverForeground": "345 30% 20%", + "primary": "345 62% 52%", + "primaryForeground": "350 40% 98%", + "secondary": "350 46% 92%", + "secondaryForeground": "345 30% 22%", + "muted": "350 28% 93%", + "mutedForeground": "345 14% 40%", + "accent": "285 40% 55%", + "accentForeground": "285 40% 12%", + "destructive": "0 72% 52%", + "destructiveForeground": "350 40% 98%", + "border": "348 28% 87%", + "input": "348 28% 87%", + "ring": "345 62% 52%" + }, + "darkColors": { + "background": "330 24% 11%", + "foreground": "340 22% 92%", + "card": "330 22% 15%", + "cardForeground": "340 22% 92%", + "popover": "330 22% 15%", + "popoverForeground": "340 22% 92%", + "primary": "345 68% 66%", + "primaryForeground": "330 30% 11%", + "secondary": "330 20% 21%", + "secondaryForeground": "340 22% 92%", + "muted": "330 16% 19%", + "mutedForeground": "340 14% 70%", + "accent": "285 46% 66%", + "accentForeground": "330 30% 11%", + "destructive": "0 62% 60%", + "destructiveForeground": "340 22% 92%", + "border": "330 16% 25%", + "input": "330 16% 23%", + "ring": "345 68% 66%" + } + } + }, + { + "id": "matcha-cream", + "name": "Matcha Cream", + "description": "Soft sage on oat cream with an apricot accent. Green tea cakes and mochi.", + "theme": { + "mode": "both", + "lightColors": { + "background": "44 36% 96%", + "foreground": "150 22% 18%", + "card": "44 34% 99%", + "cardForeground": "150 22% 18%", + "popover": "44 34% 99%", + "popoverForeground": "150 22% 18%", + "primary": "148 34% 42%", + "primaryForeground": "44 40% 98%", + "secondary": "150 30% 90%", + "secondaryForeground": "150 22% 20%", + "muted": "80 20% 91%", + "mutedForeground": "150 12% 38%", + "accent": "28 70% 56%", + "accentForeground": "28 60% 12%", + "destructive": "0 70% 52%", + "destructiveForeground": "44 40% 98%", + "border": "90 18% 84%", + "input": "90 18% 84%", + "ring": "148 34% 42%" + }, + "darkColors": { + "background": "275 20% 12%", + "foreground": "44 26% 91%", + "card": "275 18% 16%", + "cardForeground": "44 26% 91%", + "popover": "275 18% 16%", + "popoverForeground": "44 26% 91%", + "primary": "148 40% 58%", + "primaryForeground": "275 30% 12%", + "secondary": "275 16% 22%", + "secondaryForeground": "44 26% 91%", + "muted": "275 13% 20%", + "mutedForeground": "80 14% 68%", + "accent": "28 74% 62%", + "accentForeground": "275 30% 12%", + "destructive": "0 60% 60%", + "destructiveForeground": "44 26% 91%", + "border": "275 14% 26%", + "input": "275 14% 24%", + "ring": "148 40% 58%" + } + } + }, + { + "id": "lavender-honey", + "name": "Lavender Honey", + "description": "Lilac on cream with a warm honey accent. Lavender shortbread and glaze.", + "theme": { + "mode": "both", + "lightColors": { + "background": "40 40% 97%", + "foreground": "270 24% 22%", + "card": "40 38% 99%", + "cardForeground": "270 24% 22%", + "popover": "40 38% 99%", + "popoverForeground": "270 24% 22%", + "primary": "265 44% 58%", + "primaryForeground": "40 50% 98%", + "secondary": "265 38% 92%", + "secondaryForeground": "270 24% 24%", + "muted": "265 24% 93%", + "mutedForeground": "270 12% 42%", + "accent": "42 78% 56%", + "accentForeground": "42 60% 12%", + "destructive": "0 70% 55%", + "destructiveForeground": "40 50% 98%", + "border": "265 24% 87%", + "input": "265 24% 87%", + "ring": "265 44% 58%" + }, + "darkColors": { + "background": "270 26% 12%", + "foreground": "265 20% 92%", + "card": "270 22% 16%", + "cardForeground": "265 20% 92%", + "popover": "270 22% 16%", + "popoverForeground": "265 20% 92%", + "primary": "265 54% 72%", + "primaryForeground": "270 30% 12%", + "secondary": "270 20% 22%", + "secondaryForeground": "265 20% 92%", + "muted": "270 16% 20%", + "mutedForeground": "265 14% 70%", + "accent": "42 80% 62%", + "accentForeground": "270 30% 12%", + "destructive": "0 60% 60%", + "destructiveForeground": "265 20% 92%", + "border": "270 16% 26%", + "input": "270 16% 24%", + "ring": "265 54% 72%" + } + } + }, + { + "id": "peach-blossom", + "name": "Peach Blossom", + "description": "Warm peach on blossom cream with a rose accent. Stone-fruit galettes in spring.", + "theme": { + "mode": "both", + "lightColors": { + "background": "26 50% 97%", + "foreground": "15 30% 22%", + "card": "26 46% 99%", + "cardForeground": "15 30% 22%", + "popover": "26 46% 99%", + "popoverForeground": "15 30% 22%", + "primary": "16 68% 56%", + "primaryForeground": "26 50% 98%", + "secondary": "14 58% 92%", + "secondaryForeground": "15 30% 24%", + "muted": "26 34% 93%", + "mutedForeground": "15 16% 42%", + "accent": "340 58% 62%", + "accentForeground": "340 40% 12%", + "destructive": "0 72% 52%", + "destructiveForeground": "26 50% 98%", + "border": "20 34% 87%", + "input": "20 34% 87%", + "ring": "16 68% 56%" + }, + "darkColors": { + "background": "300 22% 12%", + "foreground": "26 26% 91%", + "card": "300 20% 16%", + "cardForeground": "26 26% 91%", + "popover": "300 20% 16%", + "popoverForeground": "26 26% 91%", + "primary": "16 74% 64%", + "primaryForeground": "300 30% 12%", + "secondary": "300 18% 22%", + "secondaryForeground": "26 26% 91%", + "muted": "300 15% 20%", + "mutedForeground": "20 16% 70%", + "accent": "340 60% 68%", + "accentForeground": "300 30% 12%", + "destructive": "0 60% 60%", + "destructiveForeground": "26 26% 91%", + "border": "300 15% 26%", + "input": "300 15% 24%", + "ring": "16 74% 64%" } } } diff --git a/seed/seed.json b/seed/seed.json new file mode 100644 index 0000000..17eb846 --- /dev/null +++ b/seed/seed.json @@ -0,0 +1,176 @@ +{ + "demo": true, + "settings": { + "merge": { + "site_title": "Petal & Crumb", + "site_description": "A small patisserie baking cakes, pastries and seasonal treats." + } + }, + "data_tables": [ + { + "key": "contact_submissions", + "name": "Contact Submissions", + "description": "Enquiries and cake orders sent from the contact form.", + "primary_key": "id", + "columns": [ + { "key": "name", "label": "Name", "type": "text", "required": true }, + { "key": "email", "label": "Email", "type": "email", "required": true }, + { "key": "reason", "label": "Reason", "type": "text", "required": false }, + { "key": "message", "label": "Message", "type": "text", "required": true } + ] + } + ], + "menu_items": [ + { "menu": "main", "label": "Home", "page_slug": "/", "sort_order": 0 }, + { "menu": "main", "label": "Menu", "url": "/#menu", "sort_order": 1 }, + { "menu": "main", "label": "About", "page_slug": "/about", "sort_order": 2 }, + { "menu": "main", "label": "Journal", "page_slug": "/blog", "sort_order": 3 }, + { "menu": "main", "label": "Order", "page_slug": "/contact", "sort_order": 4 } + ], + "pages": [ + { + "slug": "/", + "title": "Petal & Crumb", + "template_key": "default", + "blocks": [ + { "block_key": "navbar", "title": "Navigation", "slot": "header", "sort_order": 0, "content": { "menuName": "main", "companyName": "Petal & Crumb", "ctaLabel": "Order a cake", "ctaUrl": "/contact" } }, + { "block_key": "hero", "title": "Hero", "slot": "main", "sort_order": 0, "content": { "template": "centered", "eyebrow": "Open from 8", "headline": "Cakes and pastries, baked fresh each morning", "subheadline": "A small patisserie on the corner. Everything is made by hand the day you eat it.", "ctaPrimary": { "text": "See the menu", "url": "/#menu" }, "ctaSecondary": { "text": "Order a cake", "url": "/contact" }, "minHeight": "50vh" } }, + { "block_key": "pastel-dream:seasonal_specials", "title": "Seasonal Specials", "slot": "main", "sort_order": 1, "content": { "kicker": "This week", "heading": "On the counter now", "intro": "A short list of what we are proud of this week. It changes with the season.", "currency": "$", "items": [ + { "name": "Strawberry tart", "note": "Vanilla custard, fresh strawberries, a thin glaze.", "price": "8.00", "badge": "New" }, + { "name": "Pistachio croissant", "note": "Laminated overnight, filled with pistachio cream.", "price": "6.50", "badge": "Popular" }, + { "name": "Lemon poppy loaf", "note": "Soft, tangy, dusted with sugar. Sold by the slice.", "price": "5.00", "badge": "" } + ] } }, + { "block_key": "menu-list", "title": "Menu", "slot": "main", "sort_order": 2, "content": { "heading": "The full menu", "intro": "Prices are for takeaway. We can box anything for a gift.", "currency": "$", "columns": 2, "sections": [ + { "title": "Pastries", "items": [ + { "name": "Butter croissant", "price": "5.00" }, + { "name": "Pain au chocolat", "price": "5.50" }, + { "name": "Almond croissant", "description": "Filled and topped with almond cream", "price": "6.50" }, + { "name": "Morning bun", "description": "Orange and cinnamon", "price": "6.00", "badges": [{ "label": "V" }] } + ] }, + { "title": "Cakes and slices", "items": [ + { "name": "Victoria sponge", "description": "By the slice", "price": "7.00" }, + { "name": "Chocolate torte", "description": "Flourless", "price": "8.00", "badges": [{ "label": "GF" }] }, + { "name": "Carrot cake", "price": "7.50" }, + { "name": "Custard tart", "price": "6.00" } + ] } + ] } }, + { "block_key": "pastel-dream:order_ribbon", "title": "Order Ribbon", "slot": "main", "sort_order": 3, "content": { "heading": "Order a cake for the weekend", "message": "Birthdays, gatherings, or just because. Tell us the flavour and the date.", "pickupNote": "Two days notice for custom cakes.", "ctaLabel": "Place an order", "ctaUrl": "/contact" } }, + { "block_key": "hours-location", "title": "Hours", "slot": "main", "sort_order": 4, "content": { "heading": "Come and see us", "address": "18 Rose Lane\nFremantle", "phone": "08 1234 5678", "hours": [ + { "day": "Monday", "closed": true }, + { "day": "Tuesday", "hours": "8:00 - 15:00" }, + { "day": "Wednesday", "hours": "8:00 - 15:00" }, + { "day": "Thursday", "hours": "8:00 - 15:00" }, + { "day": "Friday", "hours": "8:00 - 16:00" }, + { "day": "Saturday", "hours": "8:00 - 16:00" }, + { "day": "Sunday", "hours": "9:00 - 14:00" } + ] } }, + { "block_key": "footer", "title": "Footer", "slot": "footer", "sort_order": 0, "content": { "companyName": "Petal & Crumb", "tagline": "A small patisserie, baked fresh each morning.", "address": "18 Rose Lane, Fremantle", "email": "hello@petalandcrumb.test", "copyright": "Petal & Crumb" } } + ] + }, + { + "slug": "/about", + "title": "About", + "template_key": "default", + "blocks": [ + { "block_key": "navbar", "title": "Navigation", "slot": "header", "sort_order": 0, "content": { "menuName": "main", "companyName": "Petal & Crumb", "ctaLabel": "Order a cake", "ctaUrl": "/contact" } }, + { "block_key": "hero", "title": "Hero", "slot": "main", "sort_order": 0, "content": { "template": "centered", "eyebrow": "Our story", "headline": "One oven, one small room", "subheadline": "We opened with a single oven and a short menu. We kept it that way on purpose.", "minHeight": "auto" } }, + { "block_key": "rich-section", "title": "Story", "slot": "main", "sort_order": 1, "content": { "eyebrow": "How we bake", "heading": "Made the same day", "body": "

We start before dawn. The pastry is laminated the night before and baked in the morning, so it reaches you the same day it is made.

The menu is short because we make everything ourselves. When something is out, it is out until the next bake.

", "mediaPosition": "right" } }, + { "block_key": "pastel-dream:bakers_note", "title": "Baker's Note", "slot": "main", "sort_order": 2, "content": { "kicker": "A note from the kitchen", "body": "

Thank you for buying from a small shop. Every order helps us keep baking by hand. We taste each batch before it reaches the counter, and we would rather sell out than cut a corner.

", "signatureName": "Cara Wells", "signatureRole": "Baker and owner" } }, + { "block_key": "team", "title": "Team", "slot": "main", "sort_order": 3, "content": { "heading": "The people at the bench", "members": [ + { "name": "Cara", "role": "Baker and owner", "bio": "Laminates the pastry and runs the morning bake." }, + { "name": "Jonah", "role": "Pastry chef", "bio": "Owns the tarts and the seasonal fruit." }, + { "name": "Noor", "role": "Front of house", "bio": "Boxes your cake and knows the regulars." } + ] } }, + { "block_key": "footer", "title": "Footer", "slot": "footer", "sort_order": 0, "content": { "companyName": "Petal & Crumb", "tagline": "A small patisserie, baked fresh each morning.", "email": "hello@petalandcrumb.test", "copyright": "Petal & Crumb" } } + ] + }, + { + "slug": "/blog", + "title": "Journal", + "template_key": "blog-index", + "blocks": [ + { "block_key": "navbar", "title": "Navigation", "slot": "header", "sort_order": 0, "content": { "menuName": "main", "companyName": "Petal & Crumb", "ctaLabel": "Order a cake", "ctaUrl": "/contact" } }, + { "block_key": "blog-hero", "title": "Journal Hero", "slot": "featured", "sort_order": 0, "content": { "title": "The Journal", "subtitle": "Notes from the bench and the season." } }, + { "block_key": "card", "title": "Post: Strawberries", "slot": "main", "sort_order": 0, "content": { "title": "Strawberries are in", "text": "The first good strawberries of the season arrived. Here is what we are making with them.", "link": "/blog/strawberries-are-in", "linkLabel": "Read on" } }, + { "block_key": "card", "title": "Post: Lamination", "slot": "main", "sort_order": 1, "content": { "title": "Why we laminate overnight", "text": "Cold dough and slow rests make a better croissant. This is how we do it.", "link": "/blog/why-we-laminate-overnight", "linkLabel": "Read on" } }, + { "block_key": "card", "title": "Post: Custom cakes", "slot": "main", "sort_order": 2, "content": { "title": "How to order a custom cake", "text": "Flavours, sizes and lead times, so your order arrives the way you pictured it.", "link": "/blog/how-to-order-a-custom-cake", "linkLabel": "Read on" } }, + { "block_key": "footer", "title": "Footer", "slot": "footer", "sort_order": 0, "content": { "companyName": "Petal & Crumb", "tagline": "A small patisserie, baked fresh each morning.", "email": "hello@petalandcrumb.test", "copyright": "Petal & Crumb" } } + ] + }, + { + "slug": "/blog/strawberries-are-in", + "parent_slug": "/blog", + "title": "Strawberries are in", + "template_key": "article", + "blocks": [ + { "block_key": "navbar", "title": "Navigation", "slot": "header", "sort_order": 0, "content": { "menuName": "main", "companyName": "Petal & Crumb", "ctaLabel": "Order a cake", "ctaUrl": "/contact" } }, + { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 0, "content": { "level": 1, "text": "Strawberries are in" } }, + { "block_key": "text", "title": "Body", "slot": "main", "sort_order": 1, "content": { "text": "

The first ripe strawberries came in this week. They are small, sweet, and they do not keep, so we use them fast.

This week they go into the strawberry tart and a plain sponge with cream. When they run out, we move on to the next fruit.

Come in early. The tarts tend to go before lunch.

" } }, + { "block_key": "pastel-dream:cake_gallery", "title": "Cake Gallery", "slot": "main", "sort_order": 2, "content": { "heading": "What we made this week", "intro": "A few things that came out of the oven.", "currency": "$", "cakes": [ + { "name": "Strawberry tart", "flavour": "Vanilla custard", "price": "8.00", "orderUrl": "/contact" }, + { "name": "Strawberry sponge", "flavour": "Cream and jam", "price": "7.00", "orderUrl": "/contact" }, + { "name": "Fruit galette", "flavour": "Seasonal", "price": "6.50", "orderUrl": "/contact" } + ] } }, + { "block_key": "footer", "title": "Footer", "slot": "footer", "sort_order": 0, "content": { "companyName": "Petal & Crumb", "tagline": "A small patisserie, baked fresh each morning.", "email": "hello@petalandcrumb.test", "copyright": "Petal & Crumb" } } + ] + }, + { + "slug": "/blog/why-we-laminate-overnight", + "parent_slug": "/blog", + "title": "Why we laminate overnight", + "template_key": "article", + "blocks": [ + { "block_key": "navbar", "title": "Navigation", "slot": "header", "sort_order": 0, "content": { "menuName": "main", "companyName": "Petal & Crumb", "ctaLabel": "Order a cake", "ctaUrl": "/contact" } }, + { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 0, "content": { "level": 1, "text": "Why we laminate overnight" } }, + { "block_key": "text", "title": "Body", "slot": "main", "sort_order": 1, "content": { "text": "

A croissant is butter and dough folded many times. The butter has to stay cold, or it melts into the dough and the layers are lost.

We fold in the afternoon and rest the dough in the fridge overnight. Cold and slow gives an even rise and a light crumb.

In the morning we shape and bake. That is why the croissants are ready when we open and not before.

" } }, + { "block_key": "footer", "title": "Footer", "slot": "footer", "sort_order": 0, "content": { "companyName": "Petal & Crumb", "tagline": "A small patisserie, baked fresh each morning.", "email": "hello@petalandcrumb.test", "copyright": "Petal & Crumb" } } + ] + }, + { + "slug": "/blog/how-to-order-a-custom-cake", + "parent_slug": "/blog", + "title": "How to order a custom cake", + "template_key": "article", + "blocks": [ + { "block_key": "navbar", "title": "Navigation", "slot": "header", "sort_order": 0, "content": { "menuName": "main", "companyName": "Petal & Crumb", "ctaLabel": "Order a cake", "ctaUrl": "/contact" } }, + { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 0, "content": { "level": 1, "text": "How to order a custom cake" } }, + { "block_key": "text", "title": "Body", "slot": "main", "sort_order": 1, "content": { "text": "

Tell us the date, the size, and the flavour. Send a photo if you have one in mind.

We need two days for most cakes and a week for larger ones. We will confirm the price before we start.

Pick up from the shop, or ask about local delivery.

" } }, + { "block_key": "pastel-dream:order_ribbon", "title": "Order Ribbon", "slot": "main", "sort_order": 2, "content": { "heading": "Ready to order", "message": "Send us the details and we will reply with a quote.", "pickupNote": "Two days notice, a week for tiered cakes.", "ctaLabel": "Start an order", "ctaUrl": "/contact" } }, + { "block_key": "footer", "title": "Footer", "slot": "footer", "sort_order": 0, "content": { "companyName": "Petal & Crumb", "tagline": "A small patisserie, baked fresh each morning.", "email": "hello@petalandcrumb.test", "copyright": "Petal & Crumb" } } + ] + }, + { + "slug": "/contact", + "title": "Order", + "template_key": "contact", + "blocks": [ + { "block_key": "navbar", "title": "Navigation", "slot": "header", "sort_order": 0, "content": { "menuName": "main", "companyName": "Petal & Crumb", "ctaLabel": "Order a cake", "ctaUrl": "/contact" } }, + { "block_key": "hours-location", "title": "Hours", "slot": "main", "sort_order": 0, "content": { "heading": "Find us", "address": "18 Rose Lane\nFremantle", "phone": "08 1234 5678", "hours": [ + { "day": "Monday", "closed": true }, + { "day": "Tuesday", "hours": "8:00 - 15:00" }, + { "day": "Wednesday", "hours": "8:00 - 15:00" }, + { "day": "Thursday", "hours": "8:00 - 15:00" }, + { "day": "Friday", "hours": "8:00 - 16:00" }, + { "day": "Saturday", "hours": "8:00 - 16:00" }, + { "day": "Sunday", "hours": "9:00 - 14:00" } + ] } }, + { "block_key": "contact-form", "title": "Contact Form", "slot": "main", "sort_order": 1, "content": { "heading": "Send us a note", "description": "Ordering a cake or asking a question? Leave the details and we will get back to you.", "submitLabel": "Send", "successMessage": "Thanks. We will reply soon.", "formConfig": { "targetTable": "contact_submissions" }, "fields": [ + { "name": "name", "label": "Name", "type": "text", "required": true, "width": "half" }, + { "name": "email", "label": "Email", "type": "email", "required": true, "width": "half" }, + { "name": "reason", "label": "Reason", "type": "text", "required": false, "width": "full" }, + { "name": "message", "label": "Message", "type": "textarea", "required": true, "width": "full" } + ] } }, + { "block_key": "footer", "title": "Footer", "slot": "footer", "sort_order": 0, "content": { "companyName": "Petal & Crumb", "tagline": "A small patisserie, baked fresh each morning.", "email": "hello@petalandcrumb.test", "copyright": "Petal & Crumb" } } + ] + }, + { + "slug": "/login", + "title": "Login", + "template_key": "auth", + "blocks": [ + { "block_key": "auth-form", "title": "Auth Form", "slot": "main", "sort_order": 0, "content": { "default_tab": "login", "show_social": false } }, + { "block_key": "text", "title": "Footer Note", "slot": "footer", "sort_order": 0, "content": { "text": "

Members hear about seasonal bakes first.

" } } + ] + } + ] +} diff --git a/seed/workflows.json b/seed/workflows.json new file mode 100644 index 0000000..2666507 --- /dev/null +++ b/seed/workflows.json @@ -0,0 +1,25 @@ +{ + "workflows": [ + { + "key": "notify_admins_on_contact", + "name": "Notify admins on contact", + "description": "Email site admins when someone submits the contact or order form.", + "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/email/pastel-dream.ninjatpl b/templates/email/pastel-dream.ninjatpl index 8aed80c..f5335f6 100644 --- a/templates/email/pastel-dream.ninjatpl +++ b/templates/email/pastel-dream.ninjatpl @@ -1,98 +1,99 @@ - - - - - - {{ site_name }} - - - - {% if preview_text %}
- {{ preview_text }} -
{% endif %} - - - - -
- -
- -
- - - - - - - - - - - - - - -
- + + + + + + {{ site_name }} + + + + {% if preview_text %}
{{ preview_text }}
{% endif %} + + + + +
+ + + + + + + + + + + + + + +
+ diff --git a/templates/overrides/pastel-dream/announcement-bar.ninjatpl b/templates/overrides/pastel-dream/announcement-bar.ninjatpl new file mode 100644 index 0000000..6dd703d --- /dev/null +++ b/templates/overrides/pastel-dream/announcement-bar.ninjatpl @@ -0,0 +1,21 @@ +{# pastel-dream announcement-bar — dismissable strip; dismissal persists in localStorage keyed by variant+message hash. #} +{% set anncls = "bg-accent/15 text-foreground" %} +{% if variant == "promo" %}{% set anncls = "bg-accent text-accent-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/pastel-dream/audio-embed.ninjatpl b/templates/overrides/pastel-dream/audio-embed.ninjatpl new file mode 100644 index 0000000..0f78ec0 --- /dev/null +++ b/templates/overrides/pastel-dream/audio-embed.ninjatpl @@ -0,0 +1,15 @@ +
{% 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 %} Download{% endif %}
diff --git a/templates/overrides/pastel-dream/auth-form.ninjatpl b/templates/overrides/pastel-dream/auth-form.ninjatpl new file mode 100644 index 0000000..3d3a0ba --- /dev/null +++ b/templates/overrides/pastel-dream/auth-form.ninjatpl @@ -0,0 +1,36 @@ +{# pastel-dream auth-form — combined login/register in soft pastel chrome. Endpoints and panel + ids match the platform auth flow (/api/auth/login, /api/auth/register, + #auth-message, #login-panel/#register-panel). Auth state via the auth_state + provider (auth.logged_in / auth.display_name). Captcha, when enabled, is + emitted by the platform auth path. #} +{% if auth.logged_in %} +
+

You are logged in as {{ auth.display_name }}

+ My account +
+{% else %} +{% set defaultTab = default_tab|default:"login" %} +
+
+
+ + +
+
+
+
+
+ +

Forgot password?

+
+
+
+
+
+
+
+ +
+
+
+{% endif %} diff --git a/templates/overrides/pastel-dream/auth-status.ninjatpl b/templates/overrides/pastel-dream/auth-status.ninjatpl new file mode 100644 index 0000000..4a42b1f --- /dev/null +++ b/templates/overrides/pastel-dream/auth-status.ninjatpl @@ -0,0 +1,27 @@ +{# pastel-dream auth-status — header login state. Auth state via the auth_state provider; + endpoints match the platform (/login, /login?tab=register, /api/auth/logout, + /api/auth/resend-verification). #} +{% if not auth.logged_in %} +
+ Login + Sign up +
+{% else %} +{% if not auth.email_verified %}
Please verify your email address.
{% endif %} +
+ + +
+ +{% endif %} diff --git a/templates/overrides/pastel-dream/author-bio-hero.ninjatpl b/templates/overrides/pastel-dream/author-bio-hero.ninjatpl new file mode 100644 index 0000000..466a386 --- /dev/null +++ b/templates/overrides/pastel-dream/author-bio-hero.ninjatpl @@ -0,0 +1,20 @@ +{% 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/pastel-dream/author-bio.ninjatpl b/templates/overrides/pastel-dream/author-bio.ninjatpl new file mode 100644 index 0000000..441939f --- /dev/null +++ b/templates/overrides/pastel-dream/author-bio.ninjatpl @@ -0,0 +1,12 @@ +{% if post.author_name %} + +{% endif %} diff --git a/templates/overrides/pastel-dream/blog-hero.ninjatpl b/templates/overrides/pastel-dream/blog-hero.ninjatpl new file mode 100644 index 0000000..5fc7549 --- /dev/null +++ b/templates/overrides/pastel-dream/blog-hero.ninjatpl @@ -0,0 +1,22 @@ +
+
+
+{% if title %}

{{ title }}

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

{{ subtitle }}

{% endif %} +
+{% if posts %} +
+{% for post in posts %} + +{% endfor %} +
+{% endif %} +
+
diff --git a/templates/overrides/pastel-dream/breadcrumbs.ninjatpl b/templates/overrides/pastel-dream/breadcrumbs.ninjatpl new file mode 100644 index 0000000..4c81862 --- /dev/null +++ b/templates/overrides/pastel-dream/breadcrumbs.ninjatpl @@ -0,0 +1,12 @@ +{% set arrows = template == "arrows" %} + diff --git a/templates/overrides/pastel-dream/button.ninjatpl b/templates/overrides/pastel-dream/button.ninjatpl index b9e1205..ec5f300 100644 --- a/templates/overrides/pastel-dream/button.ninjatpl +++ b/templates/overrides/pastel-dream/button.ninjatpl @@ -1,3 +1,5 @@ - - {{ text|default:"Continue" }} - +{% with align=align|default:"left" style=style|default:"primary" %} +
+{% if link %}{% if icon and iconPosition != "right" %}{% endif %}{{ label|default:"Learn more" }}{% if icon and iconPosition == "right" %}{% endif %}{% else %}{% endif %} +
+{% endwith %} diff --git a/templates/overrides/pastel-dream/card.ninjatpl b/templates/overrides/pastel-dream/card.ninjatpl index d7f9fb6..549ec39 100644 --- a/templates/overrides/pastel-dream/card.ninjatpl +++ b/templates/overrides/pastel-dream/card.ninjatpl @@ -1,9 +1,12 @@ -
- {% if title %}

{{ title }}

{% endif %} - {% if body %}
- {{ body|safe }} -
{% endif %} - {% if footer %}
- {{ footer|safe }} -
{% endif %} +{% with layout=layout|default:"vertical" %} + +{% endwith %} diff --git a/templates/overrides/pastel-dream/category-list.ninjatpl b/templates/overrides/pastel-dream/category-list.ninjatpl new file mode 100644 index 0000000..4ca486d --- /dev/null +++ b/templates/overrides/pastel-dream/category-list.ninjatpl @@ -0,0 +1,17 @@ +{% 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/pastel-dream/contact-card.ninjatpl b/templates/overrides/pastel-dream/contact-card.ninjatpl new file mode 100644 index 0000000..ea1c6c6 --- /dev/null +++ b/templates/overrides/pastel-dream/contact-card.ninjatpl @@ -0,0 +1,17 @@ +
+
+ {% 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/pastel-dream/contact-form.ninjatpl b/templates/overrides/pastel-dream/contact-form.ninjatpl new file mode 100644 index 0000000..b95f773 --- /dev/null +++ b/templates/overrides/pastel-dream/contact-form.ninjatpl @@ -0,0 +1,39 @@ +{% with bid=_block_id|default:context.blockId %} +
+
+ {% if form.submitted %} +
+
+

{{ form.message|default:successMessage|default:"Thanks. Your message is on its way." }}

+
+ {% 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/pastel-dream/countdown.ninjatpl b/templates/overrides/pastel-dream/countdown.ninjatpl new file mode 100644 index 0000000..9fb0aff --- /dev/null +++ b/templates/overrides/pastel-dream/countdown.ninjatpl @@ -0,0 +1,42 @@ +
+
+ {% 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/pastel-dream/cta.ninjatpl b/templates/overrides/pastel-dream/cta.ninjatpl new file mode 100644 index 0000000..4486c91 --- /dev/null +++ b/templates/overrides/pastel-dream/cta.ninjatpl @@ -0,0 +1,14 @@ +
+
+
+
+ {% 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/pastel-dream/divider.ninjatpl b/templates/overrides/pastel-dream/divider.ninjatpl new file mode 100644 index 0000000..91d6b73 --- /dev/null +++ b/templates/overrides/pastel-dream/divider.ninjatpl @@ -0,0 +1,17 @@ +{% with variant=variant|default:"line" width=width|default:"full" %} + +{% endwith %} diff --git a/templates/overrides/pastel-dream/event-list.ninjatpl b/templates/overrides/pastel-dream/event-list.ninjatpl new file mode 100644 index 0000000..6579d6c --- /dev/null +++ b/templates/overrides/pastel-dream/event-list.ninjatpl @@ -0,0 +1,40 @@ +
+
+ {% 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/pastel-dream/faq.ninjatpl b/templates/overrides/pastel-dream/faq.ninjatpl new file mode 100644 index 0000000..bae03cc --- /dev/null +++ b/templates/overrides/pastel-dream/faq.ninjatpl @@ -0,0 +1,17 @@ +
+
+ {% if heading %}

{{ heading }}

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

{{ intro }}

{% endif %} +
+ {% for item in items %} +
+ + {{ item.question }} + + +
{{ item.answer|safe }}
+
+ {% endfor %} +
+
+
diff --git a/templates/overrides/pastel-dream/feature-grid.ninjatpl b/templates/overrides/pastel-dream/feature-grid.ninjatpl new file mode 100644 index 0000000..1f82389 --- /dev/null +++ b/templates/overrides/pastel-dream/feature-grid.ninjatpl @@ -0,0 +1,17 @@ +
+
+ {% 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/pastel-dream/featured-posts.ninjatpl b/templates/overrides/pastel-dream/featured-posts.ninjatpl new file mode 100644 index 0000000..81abd28 --- /dev/null +++ b/templates/overrides/pastel-dream/featured-posts.ninjatpl @@ -0,0 +1,21 @@ +
+
+{% 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{% endif %} +
+
+
+{% endfor %} +
+
+
diff --git a/templates/overrides/pastel-dream/footer.ninjatpl b/templates/overrides/pastel-dream/footer.ninjatpl new file mode 100644 index 0000000..c4bbf33 --- /dev/null +++ b/templates/overrides/pastel-dream/footer.ninjatpl @@ -0,0 +1,38 @@ +{# pastel-dream footer — scalloped top edge, soft pastel chrome, builtin footer_menus provider. #} +{% set fl = layout %} +{% if not fl %}{% if template == "centered" %}{% set fl = "centered" %}{% else %}{% set fl = "columns" %}{% endif %}{% endif %} +{% 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/pastel-dream/gallery.ninjatpl b/templates/overrides/pastel-dream/gallery.ninjatpl new file mode 100644 index 0000000..3c5631a --- /dev/null +++ b/templates/overrides/pastel-dream/gallery.ninjatpl @@ -0,0 +1,9 @@ +{% with layout=layout|default:"grid" gap=gap|default:"md" cols=columns|default:3 %} + +{% endwith %} diff --git a/templates/overrides/pastel-dream/heading.ninjatpl b/templates/overrides/pastel-dream/heading.ninjatpl index 8f4b5fb..72f1885 100644 --- a/templates/overrides/pastel-dream/heading.ninjatpl +++ b/templates/overrides/pastel-dream/heading.ninjatpl @@ -1 +1,6 @@ -{% 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 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/pastel-dream/hero.ninjatpl b/templates/overrides/pastel-dream/hero.ninjatpl new file mode 100644 index 0000000..e37c8a1 --- /dev/null +++ b/templates/overrides/pastel-dream/hero.ninjatpl @@ -0,0 +1,36 @@ +{# pastel-dream hero — watercolor chrome; styled-text fields accept {text} object OR bare string. #} +{% 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 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 %}

Regulars love

{% 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-60" %}{% 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" %}{% 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 %}
{% endif %} +
{{ herocontent(align) }}
+
+{% endif %} diff --git a/templates/overrides/pastel-dream/hours-location.ninjatpl b/templates/overrides/pastel-dream/hours-location.ninjatpl new file mode 100644 index 0000000..81d2f27 --- /dev/null +++ b/templates/overrides/pastel-dream/hours-location.ninjatpl @@ -0,0 +1,40 @@ +
+
+ {% 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 %}
Find us
{{ address }}
{% endif %} + {% if phone %}
Call ahead
{{ phone }}
{% endif %} + {% if directionsUrl %}Get directions{% endif %} +
+ {% if mapEmbedUrl %} +
+ +
+ {% endif %} +
+
+
+ diff --git a/templates/overrides/pastel-dream/image.ninjatpl b/templates/overrides/pastel-dream/image.ninjatpl new file mode 100644 index 0000000..a281006 --- /dev/null +++ b/templates/overrides/pastel-dream/image.ninjatpl @@ -0,0 +1,8 @@ +{% with size=size|default:"full" align=align|default:"center" %} +
+
+ {% if src %}{% img src alt=alt class="block h-auto w-full rounded-sm object-cover" layout=size %}{% else %}
Add an image
{% endif %} +
+ {% if caption %}
{{ caption }}
{% endif %} +
+{% endwith %} diff --git a/templates/overrides/pastel-dream/logo-strip.ninjatpl b/templates/overrides/pastel-dream/logo-strip.ninjatpl new file mode 100644 index 0000000..c51a89f --- /dev/null +++ b/templates/overrides/pastel-dream/logo-strip.ninjatpl @@ -0,0 +1,15 @@ +
+
+ {% if heading %}

{{ heading }}

{% endif %} +
+
+ {% for logo in logos %} + {% if logo.url %}{% endif %} + {% img logo.image alt=logo.alt class="h-8 w-auto object-contain" %} + {% if logo.url %}{% endif %} + {% endfor %} +
+
+
+
+{% if marquee %}{% endif %} diff --git a/templates/overrides/pastel-dream/menu-list.ninjatpl b/templates/overrides/pastel-dream/menu-list.ninjatpl new file mode 100644 index 0000000..9c3360b --- /dev/null +++ b/templates/overrides/pastel-dream/menu-list.ninjatpl @@ -0,0 +1,31 @@ +
+
+ {% if heading %}

{{ heading }}

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

{{ intro }}

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

{{ footnote }}

{% endif %} +
+
+
diff --git a/templates/overrides/pastel-dream/navbar.ninjatpl b/templates/overrides/pastel-dream/navbar.ninjatpl new file mode 100644 index 0000000..04f2c47 --- /dev/null +++ b/templates/overrides/pastel-dream/navbar.ninjatpl @@ -0,0 +1,74 @@ +{# pastel-dream navbar — paper-strip chrome over the builtin menu provider. One navbar per page (fixed drawer ids). #} +{% set nv = variant %} +{% if not nv %}{% if template == "centered" %}{% set nv = "centered-logo" %}{% else %}{% set nv = "standard" %}{% endif %}{% endif %} +{% set sb = stickyBehavior %} +{% if not sb %}{% if sticky %}{% set sb = "sticky" %}{% else %}{% set sb = "none" %}{% endif %}{% endif %} +{% set cta_text = cta.text|default:ctaLabel %} +{% set cta_url = cta.url|default:ctaUrl %} +{% macro navlink(item) %}{{ item.label }}{% endmacro %} +{% macro dropdown(item) %}
{% 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 %} +