feat(pastel-dream): WO-TF-023 bakery/patisserie theme — full Wave A rebuild
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 <noreply@anthropic.com>
This commit is contained in:
parent
5dfc1cd4be
commit
1ee98d318a
329
BUILD_REPORT.md
329
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
|
Bakery / patisserie persona. Codeless `.bnp` theme (no Go, no wasm). Built and
|
||||||
(`themes/docs/works/pastel-dream.md`) and gating UAT
|
verified against cms main at `a0f07e2ae` (OVERRIDE-CONTRACT freeze).
|
||||||
(`themes/docs/uat/pastel-dream.md`), executed under wave-1 fonts policy
|
|
||||||
(`themes/docs/FONTS.md`).
|
|
||||||
|
|
||||||
## What landed
|
## What landed
|
||||||
|
|
||||||
### Identity & module
|
### Identity
|
||||||
- `plugin.mod` written verbatim from spec §"plugin.mod": `name = "pastel-dream"`,
|
- `plugin.mod`: `name = "pastel-dream"`, `kind = "theme"`, `scope = "@themes"`,
|
||||||
`kind = "theme"`, `scope = "@themes"`, `display_name = "Pastel Dream"`
|
`version = "0.2.0"`, bakery tags, `required_icon_packs = ["lucide", "simple-icons"]`.
|
||||||
(12 chars), `description` = 100 chars, `categories = ["templates"]`, 9 tags
|
- `manifest.yaml`: system template `pastel-dream`, 7 page templates, email
|
||||||
matching spec, `[compatibility].block_core = ">=0.11.0 <0.12.0"`,
|
wrapper, 49 template overrides, `css.input_css_append` (pastel utility layer),
|
||||||
`version = "0.1.0"`.
|
icon packs, presets/fonts/master-pages references.
|
||||||
- `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.
|
|
||||||
|
|
||||||
### Build artefact
|
### Overrides (49 mandatory keys)
|
||||||
- `pastel-dream.so` produced by `make` (local CGO build, no container):
|
- One `templates/overrides/pastel-dream/<key>.ninjatpl` per mandatory key, plus a
|
||||||
size **21,517,984 bytes (≈21 MB)** which sits inside the normal range
|
`template_overrides` entry per key. Re-skinned from the field-correct `coffee`
|
||||||
(gotham is ~21 MB, lcars is ~30 MB).
|
sibling (frozen at the same cms pin): every content field, provider var, custom
|
||||||
- Exported symbol `Registration` confirmed present via `nm -D`:
|
tag, and JS block preserved verbatim; only styling and copy changed to the
|
||||||
`git.dev.alexdunmow.com/block/themes/pastel-dream.Registration`.
|
pastel bakery identity. Legacy button/card reads already reconciled per §4
|
||||||
- `go vet ./...` exits 0.
|
(`link`/`style`/`label`, `text`/`media`).
|
||||||
|
|
||||||
### System template and page templates (register.go)
|
### Page templates (7)
|
||||||
- `RegisterSystemTemplate` called exactly once with `Key: "pastel-dream"`.
|
- `default`, `landing`, `article`, `full-width`, `blog-index`, `contact`, `auth`
|
||||||
- 4 page templates registered per spec §"Page templates":
|
under `templates/pastel-dream/`. Soft watercolor-wash chrome (`pd-paper` body).
|
||||||
- `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`.
|
|
||||||
|
|
||||||
### Schema loading order
|
### Persona blocks (4)
|
||||||
- `br.LoadSchemasFromFS(Schemas())` is called at `register.go:72`, strictly
|
- `pastel-dream:seasonal_specials`, `pastel-dream:cake_gallery`,
|
||||||
before the first `br.Register` at `register.go:77`. UAT §3 line-order
|
`pastel-dream:order_ribbon`, `pastel-dream:bakers_note`. Each is a
|
||||||
check passes.
|
`blocks/blocks.yaml` definition + `<key>.schema.json` (draft-07) +
|
||||||
|
`<key>.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)
|
### Presets (5, dual-mode)
|
||||||
Each block has one `<key>.go` + `<key>.templ` pair under repo root, one
|
- `morning-haze`, `berry-cream`, `matcha-cream`, `lavender-honey`,
|
||||||
`schemas/<key>.schema.json` (draft-07), and exactly one `br.Register` call
|
`peach-blossom`. All `mode: "both"`, all 19 tokens per light + dark. Dark is a
|
||||||
in `register.go`. All blocks declare `Source: "pastel-dream"` on
|
tuned dusk plum/berry ground (not a lightened pastel) per the WO.
|
||||||
`BlockMeta`. Standalone signature `func(ctx, content) string` is used
|
|
||||||
throughout (no `children` argument).
|
|
||||||
|
|
||||||
| Key | Title | Category | Schema fields |
|
### Fonts (bundled, OFL)
|
||||||
|--------------------|--------------------|-------------|------------------------------------------------------------------------------------------|
|
- `fonts.json`: **Fraunces** 400/600 (heading) + **Quicksand** 400/700 (body),
|
||||||
| `soft-navbar` | Soft Navbar | Navigation | logo:text, menuName:menu-select, ctaText:text, ctaHref:link |
|
woff2 in `assets/fonts/`, OFL-1.1 recorded in `assets/fonts/OFL.txt`. Every
|
||||||
| `watercolor-hero` | Watercolor Hero | Theme | eyebrow:text, headline:richtext, body:richtext, image:media, ctaText:text, ctaHref:link |
|
`font-family` resolves through `var(--font-heading|body|mono, <fallback>)`.
|
||||||
| `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) |
|
|
||||||
|
|
||||||
All `x-editor` values fall within the allowed set
|
### Email
|
||||||
`{text, richtext, media, color, select, number, slug, textarea, array, collection, bucket-picker, menu-select, template-select, link}`.
|
- `templates/email/pastel-dream.ninjatpl`: table-based, inline styles, pastel
|
||||||
|
hex fallbacks for clients without CSS custom properties, scalloped divider.
|
||||||
|
|
||||||
### Built-in overrides
|
### Master pages + seed
|
||||||
Four overrides wired via `br.RegisterTemplateOverride("pastel-dream", …)`:
|
- `master_pages.json`: default/landing/blog/auth masters, "Petal & Crumb" brand.
|
||||||
- `heading` — Caveat Brush at H1/H2 (display face), Nunito H3+, soft
|
- `seed/seed.json` (`demo: true`): home, about, blog index + 3 posts, contact,
|
||||||
`.brush-underline` accent.
|
login. Contact form → `contact_submissions` data table via
|
||||||
- `text` — `font-body` with `line-height: 1.75` per spec.
|
`formConfig.targetTable`. `seed/workflows.json`: `row_inserted` →
|
||||||
- `button` — `.pastel-pill` (border-radius 9999px, tinted shadow, 2px
|
email-admins ("Form Submission Notification", recipients admins/all). Bakery
|
||||||
hover lift, `cubic-bezier(.22,1,.36,1)` easing).
|
voice, no LLM-isms, no em-dashes, no fabricated stats.
|
||||||
- `card` — `.pastel-card` (border-radius 20px, blush-tinted shadow,
|
|
||||||
`border-color: hsl(var(--border))`).
|
|
||||||
|
|
||||||
### Master pages
|
### Motion tier: CSS-FIRST
|
||||||
`DefaultMasterPages()` returns exactly two entries:
|
- 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"]`.
|
## Verification (both exit 0)
|
||||||
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 `<a>` 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
|
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cd /home/alex/src/blockninja/themes/pastel-dream
|
$ cd themes/pastel-dream && make
|
||||||
$ go mod tidy # ← exits 0
|
Built pastel-dream@0.2.0 (codeless) → pastel-dream-0.2.0.bnp # EXIT 0
|
||||||
$ /home/alex/go/bin/templ generate # ← exits 0, 12 *_templ.go files
|
|
||||||
$ make # ← exits 0
|
$ cd check-safety && go run . themes/pastel-dream
|
||||||
CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o pastel-dream.so .
|
32 checks: 19 ok 13 skip -> OK # EXIT 0
|
||||||
$ 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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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
|
## Known limits (platform gaps, per OVERRIDE-CONTRACT §11/§12)
|
||||||
`/home/alex/src/blockninja/backend/go.mod` / `./cmd/check-safety`. **That
|
- `auth-form`, `auth-status`, `password-reset-form`, `page-suggestions` overrides
|
||||||
backend path does not exist on this system.** The canonical check-safety
|
are inert until the compiled-block override-dispatch fix lands (authored
|
||||||
tool lives at `/home/alex/src/blockninja/check-safety/` and reads the CMS
|
correctly to model).
|
||||||
SDK version from `/home/alex/src/blockninja/cms/backend/go.mod`. I ran it
|
- Provider-backed overrides (navbar/footer menus, featured/related/popular posts,
|
||||||
from that location:
|
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.
|
||||||
$ cd /home/alex/src/blockninja/check-safety
|
- `preview.png` is the old (wellness) render and must be regenerated in Wave B.
|
||||||
$ 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 <pastel-dream>` 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
|
|
||||||
├── <block>.go + <block>.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
|
|
||||||
```
|
|
||||||
|
|||||||
@ -1,45 +1,45 @@
|
|||||||
# Recommended fonts — Pastel Dream
|
# Recommended fonts — Pastel Dream
|
||||||
|
|
||||||
This pass ships `fonts.json = []` per the wave-1 fonts policy (see
|
This theme **bundles** its typographic identity as woff2 files in
|
||||||
`themes/docs/FONTS.md`). The visual identity is realised through CSS variable
|
`assets/fonts/` (see `fonts.json`). Both families are licensed under the SIL
|
||||||
fallback stacks (`--font-heading`, `--font-body`, `--font-mono`) so the theme
|
Open Font License 1.1 (`assets/fonts/OFL.txt`). No admin setup is needed for
|
||||||
renders sensibly out of the box.
|
the intended look.
|
||||||
|
|
||||||
To match the intended Pastel Dream aesthetic, an admin should open the
|
## Bundled (source = template)
|
||||||
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.
|
|
||||||
|
|
||||||
## 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,
|
Assign in the typography panel:
|
||||||
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
|
|
||||||
|
|
||||||
1. Open `/admin/system/typography` (or your instance's typography panel).
|
1. Open `/admin/system/typography` (or your instance's typography panel).
|
||||||
2. Open the Google Fonts tab.
|
2. In the **Heading** slot pick **Fraunces**; in the **Body** slot pick
|
||||||
3. Search for each family above and click "Add".
|
**Quicksand**. Leave **Mono** on the system default, or pick a mono if you
|
||||||
4. In the Heading / Body / Mono slots, pick the family you added.
|
use price/number strings heavily.
|
||||||
5. Save. The CMS injects an `@import` link and re-renders.
|
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, <fallback>)` so the admin font
|
||||||
|
picker keeps working.
|
||||||
|
|
||||||
Wave-1 implementation policy ships every theme with `fonts.json = []` and
|
## Alternates from Google Fonts
|
||||||
relies on the CSS variable consumer pattern. A future wave will bundle
|
|
||||||
distinctive commercial display faces (or self-host the OFL fonts above) for
|
If you prefer a different feel, these all suit the bakery persona and are in
|
||||||
themes where the typographic identity is core. See
|
the curated Google Fonts list:
|
||||||
`themes/docs/FONTS.md` for the full rationale.
|
|
||||||
|
- 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`.
|
||||||
|
|||||||
29
RECOMMENDED_ICONS.md
Normal file
29
RECOMMENDED_ICONS.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Recommended icons — Pastel Dream
|
||||||
|
|
||||||
|
This theme renders icons through the CMS sprite system only
|
||||||
|
(`<svg><use href="/icons/<pack>.svg#<name>"/></svg>`). 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 `<use>` 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`).
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
109
assets/fonts/OFL.txt
Normal file
109
assets/fonts/OFL.txt
Normal file
@ -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.
|
||||||
BIN
assets/fonts/fraunces-latin-400.woff2
Normal file
BIN
assets/fonts/fraunces-latin-400.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/fraunces-latin-600.woff2
Normal file
BIN
assets/fonts/fraunces-latin-600.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/quicksand-400.woff2
Normal file
BIN
assets/fonts/quicksand-400.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/quicksand-700.woff2
Normal file
BIN
assets/fonts/quicksand-700.woff2
Normal file
Binary file not shown.
@ -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.
|
|
||||||
310
assets/style.css
310
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
|
* Dual-mode via the 19 shadcn-style HSL token CSS variables (--background,
|
||||||
* injected into the host Tailwind input via CSSManifest.InputCSSAppend so it
|
* --foreground, --primary, --accent, --border, --muted, --card, ...) consumed
|
||||||
* participates in Tailwind's purge. This file holds anything an admin's
|
* as hsl(var(--token)). No literal colors anywhere. Font families resolve ONLY
|
||||||
* browser may load directly when rendering a page — it currently mirrors the
|
* through the BlockNinja font variables (--font-heading, --font-body,
|
||||||
* injected utilities so the page still gets variables and keyframes even if
|
* --font-mono) with the fallback stacks below — never hardcoded, so the admin
|
||||||
* the CSSManifest pipeline is bypassed.
|
* 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 {
|
: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 {
|
/* --- Watercolor wash background ----------------------------------------- */
|
||||||
background-image:
|
/* The signature "morning haze": soft radial pastel washes tinted from the
|
||||||
radial-gradient(60% 70% at 20% 30%, hsl(var(--primary) / 0.18) 0%, transparent 65%),
|
* active palette, drifting slowly. Reads as watercolor on cream in light mode
|
||||||
radial-gradient(50% 60% at 80% 70%, hsl(var(--accent) / 0.15) 0%, transparent 60%);
|
* 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 {
|
/* --- Type roles --------------------------------------------------------- */
|
||||||
background-image:
|
.pd-display {
|
||||||
radial-gradient(60% 70% at 25% 35%, hsl(var(--secondary) / 0.45) 0%, transparent 65%),
|
font-family: var(--font-heading, var(--pd-heading-fallback));
|
||||||
radial-gradient(55% 65% at 75% 65%, hsl(var(--primary) / 0.12) 0%, transparent 60%);
|
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 {
|
/* --- Brush underline ---------------------------------------------------- */
|
||||||
box-shadow: 0 12px 40px -8px hsl(var(--primary) / 0.18),
|
.pd-doodle-underline {
|
||||||
0 4px 12px -2px hsl(var(--primary) / 0.10);
|
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 {
|
/* --- Drop-cap ----------------------------------------------------------- */
|
||||||
border: 1px dashed hsl(var(--border));
|
.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 {
|
/* --- Soft pill button (icing-smooth, lifts on hover) -------------------- */
|
||||||
border-radius: 9999px;
|
.pd-tag {
|
||||||
padding: 0.75rem 1.75rem;
|
position: relative;
|
||||||
font-family: var(--font-body, "Nunito", "Quicksand", system-ui, sans-serif);
|
display: inline-flex;
|
||||||
font-weight: 600;
|
align-items: center;
|
||||||
background-color: hsl(var(--primary));
|
gap: 0.5rem;
|
||||||
color: hsl(var(--primary-foreground));
|
padding: 0.7rem 1.5rem;
|
||||||
box-shadow: 0 8px 24px -6px hsl(var(--primary) / 0.40);
|
background-color: hsl(var(--primary));
|
||||||
transition: transform 240ms cubic-bezier(.22,1,.36,1),
|
color: hsl(var(--primary-foreground));
|
||||||
box-shadow 240ms cubic-bezier(.22,1,.36,1),
|
border: none;
|
||||||
background-color 240ms cubic-bezier(.22,1,.36,1);
|
border-radius: 9999px;
|
||||||
border: none;
|
font-family: var(--font-body, var(--pd-body-fallback));
|
||||||
cursor: pointer;
|
font-weight: 600;
|
||||||
display: inline-block;
|
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 {
|
/* --- Scalloped edges (piped-icing / doily trim) ------------------------- */
|
||||||
transform: translateY(-2px);
|
.pd-torn-top {
|
||||||
box-shadow: 0 12px 32px -6px hsl(var(--primary) / 0.50);
|
-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");
|
||||||
background-color: hsl(var(--primary) / 0.92);
|
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 {
|
/* --- Surfaces (rounded-everything) ------------------------------------- */
|
||||||
outline: 2px solid hsl(var(--ring));
|
.pd-card {
|
||||||
outline-offset: 2px;
|
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 {
|
/* --- Menu card (soft pastel board for the menu-list override) ---------- */
|
||||||
border-radius: var(--radius-soft);
|
/* Not an inverted chalkboard: a soft card with a primary wash. Text inherits
|
||||||
background-color: hsl(var(--card));
|
* card-foreground so item names and section titles stay legible in both modes. */
|
||||||
color: hsl(var(--card-foreground));
|
.pd-chalkboard {
|
||||||
border: 1px solid hsl(var(--border));
|
position: relative;
|
||||||
box-shadow: 0 12px 40px -8px hsl(var(--primary) / 0.18),
|
background-color: hsl(var(--card));
|
||||||
0 4px 12px -2px hsl(var(--primary) / 0.10);
|
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 {
|
/* --- Hours: today highlight -------------------------------------------- */
|
||||||
position: relative;
|
.pd-hours-today {
|
||||||
display: inline-block;
|
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 {
|
/* --- Location pin ------------------------------------------------------- */
|
||||||
content: "";
|
.pd-pin {
|
||||||
position: absolute;
|
color: hsl(var(--accent));
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: -0.2em;
|
|
||||||
height: 0.22em;
|
|
||||||
background-color: hsl(var(--accent) / 0.55);
|
|
||||||
border-radius: 9999px;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-display {
|
/* --- Soft badge (timeline markers) ------------------------------------- */
|
||||||
font-family: var(--font-heading, "Caveat Brush", "Sacramento", "Brush Script MT", cursive);
|
.pd-stamp {
|
||||||
letter-spacing: 0.01em;
|
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 {
|
/* --- Brush divider draw-in --------------------------------------------- */
|
||||||
font-family: var(--font-body, "Nunito", "Quicksand", "Helvetica Neue", system-ui, sans-serif);
|
.pd-doodle-draw path,
|
||||||
line-height: 1.75;
|
.pd-doodle-draw ellipse,
|
||||||
|
.pd-doodle-draw circle {
|
||||||
|
stroke-dasharray: 260;
|
||||||
|
stroke-dashoffset: 260;
|
||||||
|
animation: pd-draw 1.6s ease forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-mono {
|
@keyframes pd-draw {
|
||||||
font-family: var(--font-mono, "JetBrains Mono", "Fira Code", ui-monospace, monospace);
|
to { stroke-dashoffset: 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pastel-shimmer {
|
/* --- Gentle atmosphere: slow wash drift (CSS-first signature) ----------- */
|
||||||
0% { background-position: 0% 50%; opacity: 0.85; }
|
@keyframes pd-breathe {
|
||||||
50% { background-position: 100% 50%; opacity: 1; }
|
0%, 100% { transform: scale(1); opacity: 0.9; }
|
||||||
100% { background-position: 0% 50%; opacity: 0.85; }
|
50% { transform: scale(1.05); opacity: 1; }
|
||||||
}
|
}
|
||||||
|
.pd-breathe {
|
||||||
@keyframes breathe {
|
animation: pd-breathe 8s ease-in-out infinite;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Motion safety: static fallback ------------------------------------ */
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.animate-pastel-shimmer,
|
.pd-tag,
|
||||||
.animate-breathe,
|
.pd-stamp { transition: none; }
|
||||||
.pastel-pill {
|
.pd-tag:hover,
|
||||||
animation: none !important;
|
.pd-stamp:hover { transform: none; }
|
||||||
transition: none !important;
|
.pd-breathe { animation: none; }
|
||||||
}
|
.pd-doodle-draw path,
|
||||||
.pastel-pill:hover {
|
.pd-doodle-draw ellipse,
|
||||||
transform: none;
|
.pd-doodle-draw circle {
|
||||||
}
|
stroke-dasharray: none;
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
<section class="relative py-16 overflow-hidden animate-pastel-shimmer" style="background-image: linear-gradient(90deg, hsl(var(--background)) 0%, {% if palette == "mint" %}hsl(var(--secondary) / 0.55){% elif palette == "butter" %}hsl(var(--accent) / 0.55){% elif palette == "sky" %}hsl(var(--secondary) / 0.40){% else %}hsl(var(--primary) / 0.35){% endif %} 50%, hsl(var(--background)) 100%);" data-block="pastel-dream:affirmation" data-palette="{% if palette == "mint" or palette == "butter" or palette == "sky" %}{{ palette }}{% else %}blush{% endif %}">
|
|
||||||
<div class="relative z-10 max-w-3xl mx-auto px-6 text-center">
|
|
||||||
<p class="font-display text-3xl md:text-4xl leading-snug" style="color: hsl(var(--foreground));">
|
|
||||||
“{{ quote|default:"You are doing enough." }}”
|
|
||||||
</p>
|
|
||||||
{% if author %}<p class="font-body mt-4 text-sm uppercase tracking-[0.2em]" style="color: hsl(var(--muted-foreground));">
|
|
||||||
{{ author }}
|
|
||||||
</p>{% endif %}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
14
blocks/bakers_note.ninjatpl
Normal file
14
blocks/bakers_note.ninjatpl
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<section data-block="pastel-dream:bakers_note" class="py-14 md:py-20 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-3xl px-4">
|
||||||
|
<div class="pd-card relative overflow-hidden p-8 text-center md:p-12">
|
||||||
|
<div class="pointer-events-none absolute -right-16 -top-16 h-48 w-48 rounded-full bg-accent/15 pd-breathe" aria-hidden="true"></div>
|
||||||
|
<div class="pointer-events-none absolute -bottom-20 -left-12 h-56 w-56 rounded-full bg-primary/10 pd-breathe" aria-hidden="true"></div>
|
||||||
|
<div class="relative">
|
||||||
|
{% if kicker %}<p class="pd-hand text-2xl text-accent">{{ kicker }}</p>{% endif %}
|
||||||
|
{% if body %}<div class="pd-body mx-auto mt-4 max-w-xl text-lg text-foreground [&_strong]:text-primary">{{ body|safe }}</div>{% endif %}
|
||||||
|
{% if signatureName %}<p class="pd-display mt-6 text-2xl text-primary">{{ signatureName }}</p>{% endif %}
|
||||||
|
{% if signatureRole %}<p class="pd-body text-sm text-muted-foreground">{{ signatureRole }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
30
blocks/bakers_note.schema.json
Normal file
30
blocks/bakers_note.schema.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,42 +1,33 @@
|
|||||||
# Pastel Dream theme block definitions (codeless .bnp, WO-WZ-021).
|
# Pastel Dream theme block definitions (codeless .bnp, WO-TF-023).
|
||||||
# Keys are fully qualified (pastel-dream:<key>) because the codeless loader
|
# Only true persona furniture stays a theme block. Everything that duplicated a
|
||||||
# registers definitions verbatim — there is no PluginBlockRegistry prefixing
|
# builtin (navbar, hero, quote, testimonial, feature-grid, footer, gallery,
|
||||||
# in the codeless path (unlike the wasm/templ path). The qualified keys match
|
# menu, divider ...) is now a template override of the builtin key under
|
||||||
# the hardcoded data-block attributes and the master_pages.json references.
|
# templates/overrides/pastel-dream/ — not a block here.
|
||||||
|
#
|
||||||
|
# Keys are fully qualified (pastel-dream:<key>): the codeless loader registers
|
||||||
|
# definitions verbatim (no PluginBlockRegistry prefixing in the codeless path).
|
||||||
blocks:
|
blocks:
|
||||||
- key: pastel-dream:soft-navbar
|
- key: pastel-dream:seasonal_specials
|
||||||
title: Soft Navbar
|
title: Seasonal Specials
|
||||||
description: Rounded pill navigation with a watercolor blob behind the logo.
|
description: This season's featured bakes in a soft card grid — name, note, price and a sweet badge.
|
||||||
category: navigation
|
category: content
|
||||||
schema: soft-navbar.schema.json
|
schema: seasonal_specials.schema.json
|
||||||
template: soft-navbar.ninjatpl
|
template: seasonal_specials.ninjatpl
|
||||||
- key: pastel-dream:watercolor-hero
|
- key: pastel-dream:cake_gallery
|
||||||
title: Watercolor Hero
|
title: Cake Gallery
|
||||||
description: Hero with two-tone watercolor blobs layered behind soft display type.
|
description: A scalloped showcase of cakes with flavour, price and an order link per cake.
|
||||||
category: theme
|
category: content
|
||||||
schema: watercolor-hero.schema.json
|
schema: cake_gallery.schema.json
|
||||||
template: watercolor-hero.ninjatpl
|
template: cake_gallery.ninjatpl
|
||||||
- key: pastel-dream:affirmation
|
- key: pastel-dream:order_ribbon
|
||||||
title: Affirmation Strip
|
title: Order Ribbon
|
||||||
description: Slow-shimmer band carrying a short calming line in display type.
|
description: A rounded call-to-order ribbon with a pickup note and a soft pill button.
|
||||||
category: theme
|
category: content
|
||||||
schema: affirmation.schema.json
|
schema: order_ribbon.schema.json
|
||||||
template: affirmation.ninjatpl
|
template: order_ribbon.ninjatpl
|
||||||
- key: pastel-dream:testimonial-soft
|
- key: pastel-dream:bakers_note
|
||||||
title: Soft Testimonial
|
title: Baker's Note
|
||||||
description: Quote card with a deckle paper edge, avatar, and rating.
|
description: A signed note from the baker in sweet display type over a watercolor blob.
|
||||||
category: theme
|
category: content
|
||||||
schema: testimonial-soft.schema.json
|
schema: bakers_note.schema.json
|
||||||
template: testimonial-soft.ninjatpl
|
template: bakers_note.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
|
|
||||||
|
|||||||
29
blocks/cake_gallery.ninjatpl
Normal file
29
blocks/cake_gallery.ninjatpl
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<section data-block="pastel-dream:cake_gallery" class="py-14 md:py-20 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-6xl px-4">
|
||||||
|
<div class="text-center">
|
||||||
|
{% if heading %}<h2 class="pd-display text-3xl text-primary md:text-4xl"><span class="pd-doodle-underline">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if intro %}<p class="pd-body mx-auto mt-3 max-w-2xl text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if cakes %}
|
||||||
|
<div class="mt-10 grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{% for cake in cakes %}
|
||||||
|
<figure class="pd-frame overflow-hidden">
|
||||||
|
<div class="pd-torn-bottom aspect-square w-full overflow-hidden bg-secondary">
|
||||||
|
{% if cake.image %}{% img cake.image alt=cake.name|default:"Cake" class="h-full w-full object-cover" %}{% else %}<div class="flex h-full w-full items-center justify-center pd-hand text-xl text-muted-foreground">Add a photo</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
<figcaption class="p-5">
|
||||||
|
<div class="flex items-start justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
{% if cake.name %}<h3 class="pd-display text-xl text-primary">{{ cake.name }}</h3>{% endif %}
|
||||||
|
{% if cake.flavour %}<p class="pd-hand text-base text-accent">{{ cake.flavour }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if cake.price %}<span class="pd-mono shrink-0 text-lg text-accent tabular-nums">{% if currency %}{{ currency }}{% endif %}{{ cake.price }}</span>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if cake.orderUrl %}<a href="{{ cake.orderUrl }}" class="pd-tag pd-tag--ghost mt-4 text-sm">Order this cake<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
39
blocks/cake_gallery.schema.json
Normal file
39
blocks/cake_gallery.schema.json
Normal file
@ -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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,23 +0,0 @@
|
|||||||
<section class="relative py-16 md:py-20 overflow-hidden bg-watercolor-mint" data-block="pastel-dream:cozy-footer">
|
|
||||||
<svg class="absolute -top-8 -right-8 w-64 h-64 -z-0" viewBox="0 0 300 300" aria-hidden="true">
|
|
||||||
<path d="M150,30 C220,30 270,90 270,150 C270,220 200,270 140,270 C80,270 30,210 30,150 C30,90 80,30 150,30 Z" fill="hsl(var(--primary) / 0.16)"></path>
|
|
||||||
</svg>
|
|
||||||
<div class="relative z-10 max-w-4xl mx-auto px-4 text-center">
|
|
||||||
<p class="font-display text-3xl mb-6" style="color: hsl(var(--foreground));">
|
|
||||||
{{ affirmation|default:"Be gentle with yourself today." }}
|
|
||||||
</p>
|
|
||||||
{% if showSignup != "no" and showSignup != "false" and showSignup != "0" and showSignup != "off" and showSignup != false %}<form class="flex flex-col sm:flex-row gap-3 max-w-md mx-auto mb-8" data-pastel-signup="true" onsubmit="event.preventDefault();">
|
|
||||||
<label class="sr-only" for="pastel-newsletter-email">Email</label>
|
|
||||||
<input id="pastel-newsletter-email" type="email" placeholder="you@calm.day" class="flex-1 px-5 py-3 font-body rounded-full" style="background-color: hsl(var(--input)); color: hsl(var(--foreground)); border: 1px solid hsl(var(--border));">
|
|
||||||
<button type="submit" class="pastel-pill">Stay in touch</button>
|
|
||||||
</form>{% endif %}
|
|
||||||
{% if social %}<div class="flex justify-center gap-3 flex-wrap mt-6">
|
|
||||||
{% for href in social %}<a href="{{ href }}" class="px-4 py-2 font-body text-xs uppercase tracking-[0.2em] rounded-full" style="background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px solid hsl(var(--border));">
|
|
||||||
Visit
|
|
||||||
</a>{% endfor %}
|
|
||||||
</div>{% endif %}
|
|
||||||
<p class="font-mono text-xs mt-10" style="color: hsl(var(--muted-foreground));">
|
|
||||||
Pastel Dream · with care
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
<section class="py-16 md:py-20" data-block="pastel-dream:feature-grid-soft">
|
|
||||||
<div class="max-w-6xl mx-auto px-4">
|
|
||||||
{% if intro %}<div class="font-body text-center max-w-2xl mx-auto mb-12 text-lg" style="color: hsl(var(--foreground) / 0.78);">
|
|
||||||
{{ intro|safe }}
|
|
||||||
</div>{% endif %}
|
|
||||||
{% if items %}<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
||||||
{% for item in items %}<article class="pastel-card p-8 transition-transform" style="transition-timing-function: cubic-bezier(.22,1,.36,1); transition-duration: 240ms;">
|
|
||||||
{% if item.icon %}<div class="mb-4">
|
|
||||||
<img src="{{ item.icon }}" alt="" class="w-12 h-12 object-contain">
|
|
||||||
</div>{% else %}<div class="mb-4 w-12 h-12 rounded-full" style="background-color: hsl(var(--accent) / 0.5);" aria-hidden="true"></div>{% endif %}
|
|
||||||
{% if item.title %}<h3 class="font-display text-2xl mb-3" style="color: hsl(var(--foreground));">{{ item.title }}</h3>{% endif %}
|
|
||||||
{% if item.body %}<p class="font-body text-base" style="color: hsl(var(--foreground) / 0.78); line-height: 1.75;">{{ item.body }}</p>{% endif %}
|
|
||||||
</article>{% endfor %}
|
|
||||||
</div>{% else %}<div class="text-center font-body text-base" style="color: hsl(var(--muted-foreground));">
|
|
||||||
<p>Add up to three cards to the trio.</p>
|
|
||||||
</div>{% endif %}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
@ -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"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
18
blocks/order_ribbon.ninjatpl
Normal file
18
blocks/order_ribbon.ninjatpl
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<section data-block="pastel-dream:order_ribbon" class="py-14 md:py-20 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-5xl px-4">
|
||||||
|
<div class="pd-chalkboard relative overflow-hidden p-8 md:p-12">
|
||||||
|
<div class="relative flex flex-col items-start gap-6 md:flex-row md:items-center md:justify-between">
|
||||||
|
<div class="max-w-2xl">
|
||||||
|
{% if heading %}<h2 class="pd-display text-3xl text-primary md:text-4xl">{{ heading }}</h2>{% endif %}
|
||||||
|
{% if message %}<p class="pd-body mt-3 text-lg text-muted-foreground">{{ message }}</p>{% endif %}
|
||||||
|
{% if pickupNote %}<p class="pd-hand mt-3 text-xl text-accent">{{ pickupNote }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if ctaLabel %}
|
||||||
|
<div class="shrink-0">
|
||||||
|
<a href="{{ ctaUrl|default:"/contact" }}" class="pd-tag text-base">{{ ctaLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
37
blocks/order_ribbon.schema.json
Normal file
37
blocks/order_ribbon.schema.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
blocks/seasonal_specials.ninjatpl
Normal file
28
blocks/seasonal_specials.ninjatpl
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<section data-block="pastel-dream:seasonal_specials" class="py-14 md:py-20 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-6xl px-4">
|
||||||
|
<div class="max-w-2xl">
|
||||||
|
{% if kicker %}<p class="pd-hand text-xl text-accent">{{ kicker }}</p>{% endif %}
|
||||||
|
{% if heading %}<h2 class="pd-display mt-1 text-3xl text-primary md:text-4xl"><span class="pd-doodle-underline">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if intro %}<p class="pd-body mt-3 text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if items %}
|
||||||
|
<div class="mt-10 grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{% for item in items %}
|
||||||
|
<article class="pd-card overflow-hidden">
|
||||||
|
<div class="aspect-[4/3] w-full overflow-hidden bg-secondary">
|
||||||
|
{% if item.image %}{% img item.image alt=item.name|default:"Seasonal special" class="h-full w-full object-cover" %}{% else %}<div class="flex h-full w-full items-center justify-center pd-hand text-xl text-muted-foreground">Add a photo</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="p-5">
|
||||||
|
<div class="flex items-start justify-between gap-3">
|
||||||
|
<h3 class="pd-display text-xl text-primary">{{ item.name }}</h3>
|
||||||
|
{% if item.price %}<span class="pd-mono shrink-0 text-lg text-accent tabular-nums">{% if currency %}{{ currency }}{% endif %}{{ item.price }}</span>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if item.badge %}<span class="pd-hand mt-2 inline-flex rounded-full bg-accent/15 px-3 py-0.5 text-sm text-accent">{{ item.badge }}</span>{% endif %}
|
||||||
|
{% if item.note %}<p class="pd-body mt-3 text-sm text-muted-foreground">{{ item.note }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
46
blocks/seasonal_specials.schema.json
Normal file
46
blocks/seasonal_specials.schema.json
Normal file
@ -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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,23 +0,0 @@
|
|||||||
<nav class="w-full px-4 py-4" data-block="pastel-dream:soft-navbar" data-menu="{{ menuName }}">
|
|
||||||
<div class="max-w-6xl mx-auto flex items-center justify-between gap-4 px-2 py-2 rounded-full bg-watercolor-blush" style="border-radius: 9999px;">
|
|
||||||
<a href="/" class="font-display text-2xl tracking-tight px-4 py-2 rounded-full" style="color: hsl(var(--primary-foreground)); background-color: hsl(var(--primary) / 0.15);">
|
|
||||||
{{ logo|default:"Pastel Dream" }}
|
|
||||||
</a>
|
|
||||||
<div class="hidden md:flex items-center gap-6 font-body text-sm" data-pastel-menu="{{ menuName }}">
|
|
||||||
<a href="/" style="color: hsl(var(--foreground));" class="hover:opacity-70 transition-opacity">Home</a>
|
|
||||||
<a href="/about" style="color: hsl(var(--foreground));" class="hover:opacity-70 transition-opacity">About</a>
|
|
||||||
<a href="/journal" style="color: hsl(var(--foreground));" class="hover:opacity-70 transition-opacity">Journal</a>
|
|
||||||
<a href="/contact" style="color: hsl(var(--foreground));" class="hover:opacity-70 transition-opacity">Contact</a>
|
|
||||||
</div>
|
|
||||||
{% if ctaText %}<a href="{{ ctaHref|default:"#" }}" class="pastel-pill text-sm">
|
|
||||||
{{ ctaText }}
|
|
||||||
</a>{% endif %}
|
|
||||||
<button class="md:hidden p-2 rounded-full" aria-label="Open menu" style="color: hsl(var(--foreground));">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<line x1="3" y1="6" x2="21" y2="6"></line>
|
|
||||||
<line x1="3" y1="12" x2="21" y2="12"></line>
|
|
||||||
<line x1="3" y1="18" x2="21" y2="18"></line>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
<figure class="pastel-card deckle-edge p-8 max-w-xl mx-auto" data-block="pastel-dream:testimonial-soft">
|
|
||||||
{% if rating|integer > 0 %}<div class="flex gap-1 mb-4" aria-label="Rating">
|
|
||||||
{% if rating|integer >= 1 %}<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: hsl(var(--accent));">
|
|
||||||
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
|
|
||||||
</svg>{% endif %}{% if rating|integer >= 2 %}<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: hsl(var(--accent));">
|
|
||||||
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
|
|
||||||
</svg>{% endif %}{% if rating|integer >= 3 %}<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: hsl(var(--accent));">
|
|
||||||
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
|
|
||||||
</svg>{% endif %}{% if rating|integer >= 4 %}<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: hsl(var(--accent));">
|
|
||||||
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
|
|
||||||
</svg>{% endif %}{% if rating|integer >= 5 %}<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: hsl(var(--accent));">
|
|
||||||
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
|
|
||||||
</svg>{% endif %}
|
|
||||||
</div>{% endif %}
|
|
||||||
<blockquote class="font-body text-lg leading-relaxed" style="color: hsl(var(--card-foreground));">
|
|
||||||
{{ quote|safe }}
|
|
||||||
</blockquote>
|
|
||||||
{% if name or role %}<figcaption class="flex items-center gap-4 mt-6 pt-6 border-t" style="border-color: hsl(var(--border));">
|
|
||||||
{% if avatar %}<img src="{{ avatar }}" alt="{{ name }}" class="w-12 h-12 rounded-full object-cover">{% elif name %}<span class="w-12 h-12 rounded-full flex items-center justify-center font-display text-lg" style="background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground));">
|
|
||||||
{% for w in name|split:" " %}{% if w and forloop.Counter <= 2 %}{{ w|first|upper }}{% endif %}{% endfor %}
|
|
||||||
</span>{% endif %}
|
|
||||||
<div>
|
|
||||||
{% if name %}<p class="font-display text-xl leading-tight" style="color: hsl(var(--foreground));">{{ name }}</p>{% endif %}
|
|
||||||
{% if role %}<p class="font-body text-sm" style="color: hsl(var(--muted-foreground));">{{ role }}</p>{% endif %}
|
|
||||||
</div>
|
|
||||||
</figcaption>{% endif %}
|
|
||||||
</figure>
|
|
||||||
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
<section class="relative overflow-hidden py-20 md:py-28 bg-watercolor-blush" data-block="pastel-dream:watercolor-hero">
|
|
||||||
<svg class="absolute -top-12 -left-12 w-[28rem] h-[28rem] -z-0 animate-breathe" viewBox="0 0 400 400" aria-hidden="true">
|
|
||||||
<path d="M200,40 C290,40 360,120 360,200 C360,290 280,360 200,360 C110,360 40,280 40,200 C40,110 110,40 200,40 Z" fill="hsl(var(--primary) / 0.22)"></path>
|
|
||||||
</svg>
|
|
||||||
<svg class="absolute -bottom-16 -right-8 w-[24rem] h-[24rem] -z-0 animate-breathe" viewBox="0 0 400 400" aria-hidden="true">
|
|
||||||
<path d="M120,60 C200,40 320,80 360,180 C400,280 320,360 220,360 C120,360 40,300 40,220 C40,160 60,80 120,60 Z" fill="hsl(var(--accent) / 0.30)"></path>
|
|
||||||
</svg>
|
|
||||||
<div class="relative z-10 max-w-6xl mx-auto px-4 grid md:grid-cols-2 gap-12 items-center">
|
|
||||||
<div>
|
|
||||||
{% if eyebrow %}<p class="font-mono text-xs uppercase tracking-[0.2em] mb-4" style="color: hsl(var(--muted-foreground));">
|
|
||||||
{{ eyebrow }}
|
|
||||||
</p>{% endif %}
|
|
||||||
<h1 class="font-display brush-underline text-5xl md:text-6xl leading-tight mb-6" style="color: hsl(var(--foreground));">
|
|
||||||
{{ headline|default:"Soft starts"|safe }}
|
|
||||||
</h1>
|
|
||||||
{% if body %}<div class="font-body text-lg max-w-prose mb-8" style="color: hsl(var(--foreground) / 0.78);">
|
|
||||||
{{ body|safe }}
|
|
||||||
</div>{% endif %}
|
|
||||||
{% if ctaText %}<a href="{{ ctaHref|default:"#" }}" class="pastel-pill text-base animate-breathe">
|
|
||||||
{{ ctaText }}
|
|
||||||
</a>{% endif %}
|
|
||||||
</div>
|
|
||||||
{% if image %}<div class="relative">
|
|
||||||
<div class="pastel-card overflow-hidden">
|
|
||||||
<img src="{{ image }}" alt="" class="w-full h-auto block">
|
|
||||||
</div>
|
|
||||||
</div>{% else %}<div class="relative h-64 md:h-80" aria-hidden="true">
|
|
||||||
<svg viewBox="0 0 300 240" class="w-full h-full">
|
|
||||||
<ellipse cx="150" cy="120" rx="110" ry="80" fill="hsl(var(--secondary) / 0.55)"></ellipse>
|
|
||||||
<ellipse cx="120" cy="100" rx="70" ry="50" fill="hsl(var(--accent) / 0.45)"></ellipse>
|
|
||||||
<ellipse cx="180" cy="140" rx="60" ry="44" fill="hsl(var(--primary) / 0.40)"></ellipse>
|
|
||||||
</svg>
|
|
||||||
</div>{% endif %}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
19
fonts.json
19
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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|||||||
487
manifest.yaml
487
manifest.yaml
@ -1,231 +1,358 @@
|
|||||||
# Pastel Dream theme — codeless .bnp manifest (WO-WZ-021 Phase B1).
|
# Pastel Dream theme — codeless .bnp manifest (Wave A, WO-TF-023).
|
||||||
# Synthesized into manifest.pb by `ninja plugin build` (no plugin.wasm).
|
# Bakery / patisserie persona. Synthesized into manifest.pb by
|
||||||
|
# `ninja plugin build` (no plugin.wasm).
|
||||||
theme_presets: presets.json
|
theme_presets: presets.json
|
||||||
bundled_fonts: fonts.json
|
bundled_fonts: fonts.json
|
||||||
master_pages: master_pages.json
|
master_pages: master_pages.json
|
||||||
|
|
||||||
|
required_icon_packs:
|
||||||
|
- lucide
|
||||||
|
- simple-icons
|
||||||
|
|
||||||
system_templates:
|
system_templates:
|
||||||
- key: pastel-dream
|
- key: pastel-dream
|
||||||
title: 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:
|
page_templates:
|
||||||
- system: pastel-dream
|
- system: pastel-dream
|
||||||
key: default
|
key: default
|
||||||
title: 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]
|
slots: [header, main, footer]
|
||||||
- system: pastel-dream
|
- system: pastel-dream
|
||||||
key: landing
|
key: landing
|
||||||
title: Soft Landing
|
title: Landing
|
||||||
description: "Watercolor hero, affirmation strip, CTA, footer."
|
description: "Hero, menu, story and an invitation to order."
|
||||||
slots: [hero, main, cta, footer]
|
slots: [hero, menu, story, cta, footer]
|
||||||
- system: pastel-dream
|
- system: pastel-dream
|
||||||
key: article
|
key: article
|
||||||
title: Reading Room
|
title: Article
|
||||||
description: "Narrow, centered editorial column with side rail."
|
description: "Narrow reading column with a soft drop-cap for the journal and recipes."
|
||||||
slots: [header, main, footer]
|
slots: [header, main, footer]
|
||||||
- system: pastel-dream
|
- system: pastel-dream
|
||||||
key: full-width
|
key: full-width
|
||||||
title: Open Air
|
title: Full Width
|
||||||
description: "Edge-to-edge sections for galleries and seasonal looks."
|
description: "Edge-to-edge galleries and counter shots."
|
||||||
slots: [header, main, footer]
|
slots: [header, main, footer]
|
||||||
|
- system: pastel-dream
|
||||||
template_overrides:
|
key: blog-index
|
||||||
- { template: pastel-dream, block: heading }
|
title: Blog Index
|
||||||
- { template: pastel-dream, block: text }
|
description: "Blog hero, featured posts and a post grid."
|
||||||
- { template: pastel-dream, block: button }
|
slots: [header, featured, main, footer]
|
||||||
- { template: pastel-dream, block: card }
|
- 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:
|
email_wrappers:
|
||||||
- pastel-dream
|
- 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:
|
css:
|
||||||
input_css_append: |
|
input_css_append: |
|
||||||
/* Pastel Dream — theme-specific CSS appended to host Tailwind input.
|
/* Pastel Dream theme styles — bakery / patisserie.
|
||||||
*
|
*
|
||||||
* Rules:
|
* Dual-mode via the 19 shadcn-style HSL token CSS variables (--background,
|
||||||
* - All colors must consume tokens via hsl(var(--token)). No hardcoded hex/rgb/named colors.
|
* --foreground, --primary, --accent, --border, --muted, --card, ...) consumed
|
||||||
* - All font-family declarations must consume var(--font-*) with a graceful fallback stack.
|
* as hsl(var(--token)). No literal colors anywhere. Font families resolve ONLY
|
||||||
* - Animations must be guarded by prefers-reduced-motion: reduce.
|
* 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 {
|
: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 ---------------------------------------- */
|
/* --- Watercolor wash background ----------------------------------------- */
|
||||||
/* Soft radial wash backgrounds, tinted from the active palette via primary
|
/* The signature "morning haze": soft radial pastel washes tinted from the
|
||||||
* and accent tokens. Used as decorative layers behind hero and footer.
|
* 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. */
|
||||||
.bg-watercolor-blush {
|
body.pd-paper {
|
||||||
background-image:
|
background-color: hsl(var(--background));
|
||||||
radial-gradient(60% 70% at 20% 30%, hsl(var(--primary) / 0.18) 0%, transparent 65%),
|
background-image:
|
||||||
radial-gradient(50% 60% at 80% 70%, hsl(var(--accent) / 0.15) 0%, transparent 60%);
|
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 {
|
/* --- Type roles --------------------------------------------------------- */
|
||||||
background-image:
|
.pd-display {
|
||||||
radial-gradient(60% 70% at 25% 35%, hsl(var(--secondary) / 0.45) 0%, transparent 65%),
|
font-family: var(--font-heading, var(--pd-heading-fallback));
|
||||||
radial-gradient(55% 65% at 75% 65%, hsl(var(--primary) / 0.12) 0%, transparent 60%);
|
font-optical-sizing: auto;
|
||||||
|
font-feature-settings: "liga" 1, "dlig" 1;
|
||||||
|
letter-spacing: -0.005em;
|
||||||
}
|
}
|
||||||
|
.pd-body {
|
||||||
/* --- Soft shadow utilities -------------------------------------------- */
|
font-family: var(--font-body, var(--pd-body-fallback));
|
||||||
/* Shadows are large, low-alpha, and tinted toward primary rather than grey. */
|
line-height: 1.7;
|
||||||
.shadow-soft {
|
|
||||||
box-shadow: 0 12px 40px -8px hsl(var(--primary) / 0.18),
|
|
||||||
0 4px 12px -2px hsl(var(--primary) / 0.10);
|
|
||||||
}
|
}
|
||||||
|
.pd-mono {
|
||||||
.shadow-soft-hover {
|
font-family: var(--font-mono, var(--pd-mono-fallback));
|
||||||
box-shadow: 0 16px 48px -8px hsl(var(--primary) / 0.24),
|
|
||||||
0 6px 16px -2px hsl(var(--primary) / 0.14);
|
|
||||||
}
|
}
|
||||||
|
/* Sweet accent lettering (badges, captions, section titles). Routed through
|
||||||
/* --- Deckle edge ------------------------------------------------------- */
|
* --font-heading with a rounded fallback: unassigned it renders Quicksand; if
|
||||||
/* Hand-torn paper effect for testimonial cards. Uses mask-image so it
|
* the admin sets a heading font the accents follow it (3-slot model intended). */
|
||||||
* renders against whatever background lies beneath.
|
.pd-hand {
|
||||||
*/
|
font-family: var(--font-heading, var(--pd-hand-fallback));
|
||||||
.deckle-edge {
|
letter-spacing: 0.005em;
|
||||||
-webkit-mask-image:
|
line-height: 1.2;
|
||||||
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 --------------------------------------------- */
|
/* --- Brush underline ---------------------------------------------------- */
|
||||||
/* Spec §"Block overrides": every button is pill (9999px), tinted shadow,
|
.pd-doodle-underline {
|
||||||
* 2px hover lift, easing cubic-bezier(.22,1,.36,1).
|
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;
|
||||||
.pastel-pill {
|
background-position: 0 100%;
|
||||||
border-radius: 9999px;
|
background-size: 100% 0.55em;
|
||||||
padding: 0.75rem 1.75rem;
|
padding-bottom: 0.16em;
|
||||||
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 {
|
/* --- Drop-cap ----------------------------------------------------------- */
|
||||||
transform: translateY(-2px);
|
.pd-dropcap > p:first-of-type::first-letter {
|
||||||
box-shadow: 0 12px 32px -6px hsl(var(--primary) / 0.50);
|
font-family: var(--font-heading, var(--pd-heading-fallback));
|
||||||
background-color: hsl(var(--primary) / 0.92);
|
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 {
|
/* --- Soft pill button (icing-smooth, lifts on hover) -------------------- */
|
||||||
outline: 2px solid hsl(var(--ring));
|
.pd-tag {
|
||||||
outline-offset: 2px;
|
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 {
|
||||||
/* --- Soft card -------------------------------------------------------- */
|
transform: translateY(-2px);
|
||||||
.pastel-card {
|
box-shadow: 0 14px 30px -8px hsl(var(--primary) / 0.52);
|
||||||
border-radius: var(--radius-soft);
|
background-color: hsl(var(--primary) / 0.92);
|
||||||
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:focus-visible {
|
||||||
/* --- Brush-stroke underline for headings ------------------------------ */
|
outline: 2px solid hsl(var(--ring));
|
||||||
.brush-underline {
|
outline-offset: 3px;
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
}
|
||||||
|
.pd-tag--ghost {
|
||||||
.brush-underline::after {
|
background-color: transparent;
|
||||||
content: "";
|
color: hsl(var(--primary));
|
||||||
position: absolute;
|
box-shadow: none;
|
||||||
left: 0;
|
border: 1.5px solid hsl(var(--primary) / 0.45);
|
||||||
right: 0;
|
|
||||||
bottom: -0.2em;
|
|
||||||
height: 0.22em;
|
|
||||||
background-color: hsl(var(--accent) / 0.55);
|
|
||||||
border-radius: 9999px;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
}
|
||||||
|
.pd-tag--ghost:hover {
|
||||||
/* --- Typography fallbacks --------------------------------------------- */
|
background-color: hsl(var(--primary) / 0.08);
|
||||||
/* Spec §"Visual language" pairs Caveat Brush + Nunito; admin can override
|
box-shadow: none;
|
||||||
* 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 {
|
/* --- Scalloped edges (piped-icing / doily trim) ------------------------- */
|
||||||
font-family: var(--font-body, "Nunito", "Quicksand", "Helvetica Neue", system-ui, sans-serif);
|
.pd-torn-top {
|
||||||
line-height: 1.75;
|
-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 {
|
||||||
.font-mono {
|
-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");
|
||||||
font-family: var(--font-mono, "JetBrains Mono", "Fira Code", ui-monospace, monospace);
|
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 ------------------------------------------------------- */
|
/* --- Surfaces (rounded-everything) ------------------------------------- */
|
||||||
/* @keyframes pastel-shimmer — drift the accent overlay slowly across the
|
.pd-card {
|
||||||
* affirmation strip. Spec §13.10: animation-duration >= 6s.
|
background-color: hsl(var(--card));
|
||||||
*/
|
color: hsl(var(--card-foreground));
|
||||||
@keyframes pastel-shimmer {
|
border: 1px solid hsl(var(--border));
|
||||||
0% {
|
border-radius: var(--pd-radius);
|
||||||
background-position: 0% 50%;
|
box-shadow: 0 12px 40px -14px hsl(var(--primary) / 0.22), 0 4px 12px -6px hsl(var(--primary) / 0.10);
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
background-position: 100% 50%;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-position: 0% 50%;
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.pd-frame {
|
||||||
/* @keyframes breathe — gentle scale pulse, used by hero CTA and watercolor
|
border: 1px solid hsl(var(--border));
|
||||||
* blobs for an in-and-out breath rhythm.
|
background-color: hsl(var(--card));
|
||||||
*/
|
border-radius: var(--pd-radius);
|
||||||
@keyframes breathe {
|
|
||||||
0%, 100% {
|
|
||||||
transform: scale(1);
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: scale(1.04);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.animate-pastel-shimmer {
|
/* --- Menu card (soft pastel board for the menu-list override) ---------- */
|
||||||
background-size: 200% 200%;
|
/* Not an inverted chalkboard: a soft card with a primary wash. Text inherits
|
||||||
animation: pastel-shimmer 8s ease-in-out infinite;
|
* 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 {
|
||||||
.animate-breathe {
|
border-color: hsl(var(--border));
|
||||||
animation: breathe 6s ease-in-out infinite;
|
border-style: solid;
|
||||||
}
|
}
|
||||||
|
.pd-chalk-muted {
|
||||||
/* --- Accessibility: respect prefers-reduced-motion -------------------- */
|
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) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.animate-pastel-shimmer,
|
.pd-tag,
|
||||||
.animate-breathe,
|
.pd-stamp { transition: none; }
|
||||||
.pastel-pill {
|
.pd-tag:hover,
|
||||||
animation: none !important;
|
.pd-stamp:hover { transform: none; }
|
||||||
transition: none !important;
|
.pd-breathe { animation: none; }
|
||||||
}
|
.pd-doodle-draw path,
|
||||||
.pastel-pill:hover {
|
.pd-doodle-draw ellipse,
|
||||||
transform: none;
|
.pd-doodle-draw circle {
|
||||||
}
|
stroke-dasharray: none;
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,11 @@
|
|||||||
{
|
{
|
||||||
"key": "pastel-dream:default-master",
|
"key": "pastel-dream:default-master",
|
||||||
"title": "Pastel Dream Default Master",
|
"title": "Pastel Dream Default Master",
|
||||||
"page_templates": ["default", "article"],
|
"page_templates": ["default", "article", "full-width", "contact"],
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{ "block_key": "pastel-dream:soft-navbar", "title": "Soft Nav", "content": { "menuName": "main", "logo": "Pastel Dream" }, "slot": "header", "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 Slot", "content": { "slotName": "main", "placeholder": "Page content" }, "slot": "main", "sort_order": 0 },
|
{ "block_key": "slot", "title": "Main Content", "content": { "slotName": "main", "placeholder": "Add your page here" }, "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": "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",
|
"title": "Pastel Dream Landing Master",
|
||||||
"page_templates": ["landing"],
|
"page_templates": ["landing"],
|
||||||
"blocks": [
|
"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": "navbar", "title": "Main Navigation", "content": { "menuName": "main", "companyName": "Petal & Crumb" }, "slot": "hero", "sort_order": 0 },
|
||||||
{ "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
|
{ "block_key": "slot", "title": "Hero", "content": { "slotName": "hero", "placeholder": "Drop a hero block" }, "slot": "hero", "sort_order": 1 },
|
||||||
{ "block_key": "pastel-dream:affirmation", "title": "Affirmation Strip", "content": { "quote": "You are doing enough.", "author": "Pastel Dream" }, "slot": "cta", "sort_order": 0 },
|
{ "block_key": "slot", "title": "Menu", "content": { "slotName": "menu", "placeholder": "Feature the menu" }, "slot": "menu", "sort_order": 0 },
|
||||||
{ "block_key": "pastel-dream:cozy-footer", "title": "Cozy Footer", "content": { "showSignup": "yes" }, "slot": "footer", "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 }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -3,7 +3,8 @@ name = "pastel-dream"
|
|||||||
display_name = "Pastel Dream"
|
display_name = "Pastel Dream"
|
||||||
scope = "@themes"
|
scope = "@themes"
|
||||||
version = "0.2.0"
|
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"
|
kind = "theme"
|
||||||
categories = ["templates"]
|
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"]
|
||||||
|
|||||||
321
presets.json
321
presets.json
@ -1,88 +1,251 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "blush-morning",
|
"id": "morning-haze",
|
||||||
"name": "Blush Morning",
|
"name": "Morning Haze",
|
||||||
"description": "Default — warm peach paper with rose-blush primary and sage trim.",
|
"description": "Blush rose on warm cream with a mint accent. The default patisserie counter at opening.",
|
||||||
"theme": {
|
"theme": {
|
||||||
"mode": "light",
|
"mode": "both",
|
||||||
"lightColors": {
|
"lightColors": {
|
||||||
"background": "25 60% 98%",
|
"background": "30 44% 97%",
|
||||||
"foreground": "340 20% 22%",
|
"foreground": "335 25% 22%",
|
||||||
"card": "0 0% 100%",
|
"card": "30 42% 99%",
|
||||||
"cardForeground": "340 20% 22%",
|
"cardForeground": "335 25% 22%",
|
||||||
"popover": "0 0% 100%",
|
"popover": "30 42% 99%",
|
||||||
"popoverForeground": "340 20% 22%",
|
"popoverForeground": "335 25% 22%",
|
||||||
"primary": "350 65% 72%",
|
"primary": "340 58% 60%",
|
||||||
"primaryForeground": "340 40% 18%",
|
"primaryForeground": "40 50% 98%",
|
||||||
"secondary": "150 35% 88%",
|
"secondary": "18 52% 92%",
|
||||||
"secondaryForeground": "155 35% 22%",
|
"secondaryForeground": "335 25% 24%",
|
||||||
"muted": "30 40% 95%",
|
"muted": "30 30% 93%",
|
||||||
"mutedForeground": "340 12% 48%",
|
"mutedForeground": "335 12% 42%",
|
||||||
"accent": "48 85% 80%",
|
"accent": "160 38% 52%",
|
||||||
"accentForeground": "35 50% 22%",
|
"accentForeground": "160 40% 12%",
|
||||||
"destructive": "0 75% 65%",
|
"destructive": "0 70% 55%",
|
||||||
"destructiveForeground": "0 0% 100%",
|
"destructiveForeground": "40 50% 98%",
|
||||||
"border": "340 30% 90%",
|
"border": "24 30% 87%",
|
||||||
"input": "340 30% 92%",
|
"input": "24 30% 87%",
|
||||||
"ring": "350 65% 72%"
|
"ring": "340 58% 60%"
|
||||||
}
|
},
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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",
|
|
||||||
"darkColors": {
|
"darkColors": {
|
||||||
"background": "300 25% 12%",
|
"background": "290 22% 12%",
|
||||||
"foreground": "30 40% 94%",
|
"foreground": "320 22% 92%",
|
||||||
"card": "300 22% 16%",
|
"card": "290 20% 16%",
|
||||||
"cardForeground": "30 40% 94%",
|
"cardForeground": "320 22% 92%",
|
||||||
"popover": "300 22% 16%",
|
"popover": "290 20% 16%",
|
||||||
"popoverForeground": "30 40% 94%",
|
"popoverForeground": "320 22% 92%",
|
||||||
"primary": "345 70% 78%",
|
"primary": "340 64% 72%",
|
||||||
"primaryForeground": "340 40% 18%",
|
"primaryForeground": "290 30% 12%",
|
||||||
"secondary": "155 25% 32%",
|
"secondary": "290 18% 22%",
|
||||||
"secondaryForeground": "150 30% 92%",
|
"secondaryForeground": "320 22% 92%",
|
||||||
"muted": "300 18% 22%",
|
"muted": "290 15% 20%",
|
||||||
"mutedForeground": "300 12% 70%",
|
"mutedForeground": "300 12% 70%",
|
||||||
"accent": "48 75% 72%",
|
"accent": "160 44% 60%",
|
||||||
"accentForeground": "35 50% 18%",
|
"accentForeground": "290 30% 12%",
|
||||||
"destructive": "0 70% 60%",
|
"destructive": "0 60% 60%",
|
||||||
"destructiveForeground": "0 0% 100%",
|
"destructiveForeground": "320 22% 92%",
|
||||||
"border": "300 20% 28%",
|
"border": "290 16% 26%",
|
||||||
"input": "300 20% 22%",
|
"input": "290 16% 24%",
|
||||||
"ring": "345 70% 78%"
|
"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%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
176
seed/seed.json
Normal file
176
seed/seed.json
Normal file
@ -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": "<p>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.</p><p>The menu is short because we make everything ourselves. When something is out, it is out until the next bake.</p>", "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": "<p>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.</p>", "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": "<p>The first ripe strawberries came in this week. They are small, sweet, and they do not keep, so we use them fast.</p><p>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.</p><p>Come in early. The tarts tend to go before lunch.</p>" } },
|
||||||
|
{ "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": "<p>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.</p><p>We fold in the afternoon and rest the dough in the fridge overnight. Cold and slow gives an even rise and a light crumb.</p><p>In the morning we shape and bake. That is why the croissants are ready when we open and not before.</p>" } },
|
||||||
|
{ "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": "<p>Tell us the date, the size, and the flavour. Send a photo if you have one in mind.</p><p>We need two days for most cakes and a week for larger ones. We will confirm the price before we start.</p><p>Pick up from the shop, or ask about local delivery.</p>" } },
|
||||||
|
{ "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": "<p>Members hear about seasonal bakes first.</p>" } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
25
seed/workflows.json
Normal file
25
seed/workflows.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1,98 +1,99 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="x-apple-disable-message-reformatting">
|
<meta name="x-apple-disable-message-reformatting">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<title>{{ site_name }}</title>
|
<title>{{ site_name }}</title>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
body, table, td, p, a, li {
|
body, table, td, p, a, li, blockquote {
|
||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
-ms-text-size-adjust: 100%;
|
-ms-text-size-adjust: 100%;
|
||||||
}
|
}
|
||||||
table, td {
|
table, td {
|
||||||
mso-table-lspace: 0pt;
|
mso-table-lspace: 0pt;
|
||||||
mso-table-rspace: 0pt;
|
mso-table-rspace: 0pt;
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
-ms-interpolation-mode: bicubic;
|
-ms-interpolation-mode: bicubic;
|
||||||
border: 0;
|
border: 0;
|
||||||
height: auto;
|
height: auto;
|
||||||
line-height: 100%;
|
line-height: 100%;
|
||||||
outline: none;
|
outline: none;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
.pastel-email-frame {
|
a[x-apple-data-detectors] {
|
||||||
max-width: 560px;
|
color: inherit !important;
|
||||||
margin: 0 auto;
|
text-decoration: none !important;
|
||||||
}
|
}
|
||||||
@media only screen and (max-width: 600px) {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
.pastel-email-frame {
|
font-family: "Fraunces", "Playfair Display", Georgia, "Times New Roman", serif;
|
||||||
width: 100% !important;
|
font-weight: 600;
|
||||||
max-width: 100% !important;
|
}
|
||||||
}
|
@media only screen and (max-width: 620px) {
|
||||||
.pastel-pad {
|
.pd-email-container {
|
||||||
padding-left: 24px !important;
|
width: 100% !important;
|
||||||
padding-right: 24px !important;
|
max-width: 100% !important;
|
||||||
}
|
}
|
||||||
}
|
.pd-email-padding {
|
||||||
</style>
|
padding-left: 24px !important;
|
||||||
</head>
|
padding-right: 24px !important;
|
||||||
<body style="background-color: {{ colors.background|default:"#fdf7f1" }}; margin: 0; padding: 0; font-family: &#39;Nunito&#39;, &#39;Quicksand&#39;, Helvetica, Arial, sans-serif;">
|
}
|
||||||
{% if preview_text %}<div style="display: none; max-height: 0; overflow: hidden;">
|
}
|
||||||
{{ preview_text }}
|
</style>
|
||||||
</div>{% endif %}
|
</head>
|
||||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background-color: {{ colors.background|default:"#fdf7f1" }};">
|
<body style="background-color: {{ colors.background|default:"#faf4ed" }}; margin: 0; padding: 0; font-family: &#39;Quicksand&#39;, &#39;Nunito&#39;, -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, sans-serif;">
|
||||||
<tr>
|
{% if preview_text %}<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">{{ preview_text }}</div>{% endif %}
|
||||||
<td align="center" style="padding: 40px 12px; position: relative;">
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
|
||||||
<!-- Watermark blob (top-right). Inline SVG so any client that supports it renders the wash. -->
|
<tr>
|
||||||
<div style="position: absolute; top: 0; right: 0; width: 200px; height: 200px; opacity: 0.35; pointer-events: none;">
|
<td align="center" style="padding: 40px 10px; background-color: {{ colors.background|default:"#faf4ed" }};">
|
||||||
<svg viewBox="0 0 200 200" width="200" height="200" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
<table role="presentation" class="pd-email-container" width="560" cellspacing="0" cellpadding="0" border="0" style="max-width: 560px; background-color: {{ colors.card|default:"#fdf9f4" }}; border: 1px solid {{ colors.border|default:"#e5d6c9" }}; border-radius: 4px;">
|
||||||
<path d="M100,20 C160,20 180,80 180,120 C180,170 130,180 90,180 C40,180 20,140 20,100 C20,60 50,20 100,20 Z" fill="{{ colors.primary|default:"#e9a1ad" }}"></path>
|
<tr>
|
||||||
</svg>
|
<td align="center" style="padding: 32px 40px 16px; border-bottom: 1px dashed {{ colors.border|default:"#e5d6c9" }};">
|
||||||
</div>
|
{% if logo_url %}<img src="{{ logo_url }}" alt="{{ site_name }}" style="max-height: 48px; width: auto; display: block;">{% elif site_name %}<h1 style="margin: 0; font-size: 24px; letter-spacing: -0.01em; color: {{ colors.primary|default:"#d05a83" }};">{{ site_name }}</h1>{% endif %}
|
||||||
<table role="presentation" class="pastel-email-frame" width="560" cellspacing="0" cellpadding="0" border="0" style="max-width: 560px; background-color: {{ colors.card|default:"#ffffff" }}; border-radius: 20px; border: 1px solid {{ colors.border|default:"#f0d8de" }};">
|
</td>
|
||||||
<!-- Masthead -->
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" class="pastel-pad" style="padding: 36px 40px 8px; border-bottom: 1px solid {{ colors.border|default:"#f0d8de" }};">
|
<td class="pd-email-padding" style="padding: 32px 40px; color: {{ colors.foreground|default:"#47323c" }}; font-size: 16px; line-height: 1.65;">{{ body|safe }}</td>
|
||||||
{% if logo_url %}<img src="{{ logo_url }}" alt="{{ site_name }}" style="max-height: 56px; width: auto; display: block;">{% elif site_name %}<h1 style="margin: 0; font-family: &#39;Caveat Brush&#39;, &#39;Sacramento&#39;, cursive; font-size: 36px; font-weight: 400; color: {{ colors.foreground|default:"#432e36" }};">
|
</tr>
|
||||||
{{ site_name }}
|
<tr>
|
||||||
</h1>{% endif %}
|
<td align="center" style="padding: 16px 40px 8px;">
|
||||||
</td>
|
<!-- inline SVG doodle divider, survives Outlook -->
|
||||||
</tr>
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0"><tr><td>
|
||||||
<!-- Body -->
|
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="32" viewBox="0 0 200 32" fill="none" stroke="{{ colors.primary|default:"#d05a83" }}" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
<tr>
|
<path d="M4 18 L80 18"></path>
|
||||||
<td class="pastel-pad" style="padding: 32px 40px 24px; color: {{ colors.foreground|default:"#432e36" }}; font-size: 16px; line-height: 1.75;">
|
<path d="M120 18 L196 18"></path>
|
||||||
{{ body|safe }}
|
<ellipse cx="95" cy="18" rx="6" ry="9"></ellipse>
|
||||||
</td>
|
<path d="M95 9 L95 27"></path>
|
||||||
</tr>
|
<ellipse cx="108" cy="18" rx="6" ry="9"></ellipse>
|
||||||
<!-- Footer -->
|
<path d="M108 9 L108 27"></path>
|
||||||
<tr>
|
</svg>
|
||||||
<td class="pastel-pad" align="center" style="padding: 24px 40px 36px; border-top: 1px solid {{ colors.border|default:"#f0d8de" }};">
|
</td></tr></table>
|
||||||
<p style="margin: 0 0 12px; font-family: &#39;Caveat Brush&#39;, &#39;Sacramento&#39;, cursive; font-size: 18px; color: {{ colors.mutedForeground|default:"#80707a" }};">
|
</td>
|
||||||
Be gentle with yourself today.
|
</tr>
|
||||||
</p>
|
<tr>
|
||||||
{% if site_url %}<p style="margin: 0 0 12px; font-size: 13px; color: {{ colors.mutedForeground|default:"#80707a" }};">
|
<td style="padding: 16px 40px 32px; color: {{ colors.mutedForeground|default:"#79626c" }}; font-size: 12px; line-height: 1.6;">
|
||||||
<a href="{{ site_url }}" style="color: {{ colors.primary|default:"#e9a1ad" }}; text-decoration: none;">
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
|
||||||
{{ site_url }}
|
<tr>
|
||||||
</a>
|
<td align="center">
|
||||||
</p>{% endif %}
|
{% if site_name %}<p style="margin: 0 0 6px; font-size: 13px; color: {{ colors.foreground|default:"#47323c" }};">{{ site_name }}</p>{% endif %}
|
||||||
{% if unsubscribe_url %}<p style="margin: 0; font-size: 11px; color: {{ colors.mutedForeground|default:"#80707a" }};">
|
<p style="margin: 0 0 6px;">Fresh bakes from the morning. Cakes to order all week.</p>
|
||||||
<a href="{{ unsubscribe_url }}" style="color: {{ colors.mutedForeground|default:"#80707a" }}; text-decoration: underline;">
|
{% if site_url %}<p style="margin: 0 0 8px;"><a href="{{ site_url }}" style="color: {{ colors.primary|default:"#d05a83" }}; text-decoration: none; border-bottom: 1px dashed {{ colors.primary|default:"#d05a83" }};">{{ site_url }}</a></p>{% endif %}
|
||||||
Unsubscribe
|
{% if unsubscribe_url %}<p style="margin: 0; font-size: 11px;"><a href="{{ unsubscribe_url }}" style="color: {{ colors.mutedForeground|default:"#79626c" }}; text-decoration: none;">Unsubscribe</a></p>{% endif %}
|
||||||
</a>
|
</td>
|
||||||
</p>{% endif %}
|
</tr>
|
||||||
</td>
|
</table>
|
||||||
</tr>
|
</td>
|
||||||
</table>
|
</tr>
|
||||||
</td>
|
</table>
|
||||||
</tr>
|
</td>
|
||||||
</table>
|
</tr>
|
||||||
</body>
|
</table>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
21
templates/overrides/pastel-dream/announcement-bar.ninjatpl
Normal file
21
templates/overrides/pastel-dream/announcement-bar.ninjatpl
Normal file
@ -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 %}
|
||||||
|
<div data-bn-announcement data-bn-annkey="{{ variant|default:'info' }}:{{ message|slugify }}" class="bn-announcement w-full {{ anncls }} {{ class }}">
|
||||||
|
<div class="relative mx-auto flex max-w-7xl items-center justify-center gap-2 px-10 py-2 text-center text-sm">
|
||||||
|
<p class="pd-body font-medium">{{ message }}{% if linkText and linkUrl %} <a href="{{ linkUrl }}" class="underline decoration-dashed underline-offset-2 hover:opacity-80">{{ linkText }}</a>{% endif %}</p>
|
||||||
|
{% if dismissable %}<button type="button" data-bn-ann-dismiss aria-label="Dismiss announcement" class="absolute right-3 top-1/2 inline-flex -translate-y-1/2 items-center justify-center rounded p-1 transition-colors hover:bg-black/10"><svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg></button>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if dismissable %}<script>
|
||||||
|
(function(){
|
||||||
|
var bars=document.querySelectorAll('[data-bn-announcement]:not([data-bn-ann-wired])');
|
||||||
|
bars.forEach(function(bar){
|
||||||
|
bar.setAttribute('data-bn-ann-wired','1');
|
||||||
|
var key='bnann:'+bar.getAttribute('data-bn-annkey');
|
||||||
|
try{if(localStorage.getItem(key)==='1'){bar.style.display='none';return;}}catch(e){}
|
||||||
|
var btn=bar.querySelector('[data-bn-ann-dismiss]');
|
||||||
|
if(btn)btn.addEventListener('click',function(){bar.style.display='none';try{localStorage.setItem(key,'1');}catch(e){}});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>{% endif %}
|
||||||
15
templates/overrides/pastel-dream/audio-embed.ninjatpl
Normal file
15
templates/overrides/pastel-dream/audio-embed.ninjatpl
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<figure class="bn-audio pd-card my-8 overflow-hidden{% if class %} {{ class }}{% endif %}" data-bn-audio data-audio-url="{{ url|default:"" }}" data-audio-media="{% if media %}{{ media|mediaURL }}{% endif %}"><div class="flex items-center gap-4 p-4">{% if artwork %}<div class="h-16 w-16 shrink-0 overflow-hidden rounded-sm bg-secondary">{% img artwork alt=title|default:"" class="h-full w-full object-cover" %}</div>{% endif %}<div class="min-w-0 flex-1">{% if title %}<p class="pd-display truncate text-lg text-primary">{{ title }}</p>{% endif %}{% if artist %}<p class="pd-hand truncate text-lg text-accent">{{ artist }}</p>{% endif %}<div class="mt-2 flex items-center gap-3" data-bn-audio-slot><button type="button" data-bn-audio-play class="pd-tag text-xs" aria-label="Play{% if title %}: {{ title }}{% endif %}"><svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#play"></use></svg> Play</button>{% if download %}<a href="{% if media %}{{ media|mediaURL }}{% else %}{{ url }}{% endif %}" download class="pd-body inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"><svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#download"></use></svg> Download</a>{% endif %}</div></div></div></figure><script>
|
||||||
|
(function(){
|
||||||
|
if(window.__bnAudioFacade)return;window.__bnAudioFacade=true;
|
||||||
|
document.addEventListener('click',function(e){
|
||||||
|
var btn=e.target.closest('[data-bn-audio-play]');if(!btn)return;
|
||||||
|
var fig=btn.closest('[data-bn-audio]');if(!fig)return;
|
||||||
|
e.preventDefault();
|
||||||
|
var src=fig.getAttribute('data-audio-media')||fig.getAttribute('data-audio-url')||'';
|
||||||
|
if(!src)return;
|
||||||
|
var slot=btn.closest('[data-bn-audio-slot]');
|
||||||
|
var a=document.createElement('audio');a.src=src;a.controls=true;a.autoplay=true;a.preload='none';a.style.cssText='width:100%;min-width:12rem;height:2.25rem;';
|
||||||
|
btn.remove();slot.insertBefore(a,slot.firstChild);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
36
templates/overrides/pastel-dream/auth-form.ninjatpl
Normal file
36
templates/overrides/pastel-dream/auth-form.ninjatpl
Normal file
@ -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 %}
|
||||||
|
<div class="auth-form-logged-in pd-card mx-auto max-w-md p-6 text-center">
|
||||||
|
<p class="pd-body text-muted-foreground">You are logged in as <strong class="text-primary">{{ auth.display_name }}</strong></p>
|
||||||
|
<a href="/account" class="pd-tag mt-4">My account</a>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% set defaultTab = default_tab|default:"login" %}
|
||||||
|
<div class="auth-form pd-card mx-auto max-w-md p-6">
|
||||||
|
<div id="auth-message"></div>
|
||||||
|
<div class="mb-5 flex border-b border-dashed border-border">
|
||||||
|
<button type="button" onclick="document.getElementById('login-panel').classList.remove('hidden');document.getElementById('register-panel').classList.add('hidden');this.className='pd-display flex-1 py-2 text-center border-b-2 border-primary text-primary';this.nextElementSibling.className='pd-display flex-1 py-2 text-center border-b-2 border-transparent text-muted-foreground'" class="pd-display flex-1 py-2 text-center border-b-2 {% if defaultTab == "register" %}border-transparent text-muted-foreground{% else %}border-primary text-primary{% endif %}">Login</button>
|
||||||
|
<button type="button" onclick="document.getElementById('register-panel').classList.remove('hidden');document.getElementById('login-panel').classList.add('hidden');this.className='pd-display flex-1 py-2 text-center border-b-2 border-primary text-primary';this.previousElementSibling.className='pd-display flex-1 py-2 text-center border-b-2 border-transparent text-muted-foreground'" class="pd-display flex-1 py-2 text-center border-b-2 {% if defaultTab == "register" %}border-primary text-primary{% else %}border-transparent text-muted-foreground{% endif %}">Register</button>
|
||||||
|
</div>
|
||||||
|
<div id="login-panel" class="{% if defaultTab == "register" %}hidden{% endif %}">
|
||||||
|
<form hx-post="/api/auth/login" hx-target="#auth-message" hx-swap="innerHTML">
|
||||||
|
<div class="mb-3"><label class="pd-hand mb-1 block text-lg text-accent">Email</label><input type="email" name="email" required class="pd-body w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"></div>
|
||||||
|
<div class="mb-4"><label class="pd-hand mb-1 block text-lg text-accent">Password</label><input type="password" name="password" required class="pd-body w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"></div>
|
||||||
|
<button type="submit" class="pd-tag w-full justify-center" hx-disabled-elt="this">Login</button>
|
||||||
|
<p class="pd-body mt-3 text-center text-sm"><a href="/forgot-password" class="text-accent underline decoration-dashed">Forgot password?</a></p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="register-panel" class="{% if defaultTab == "register" %}{% else %}hidden{% endif %}">
|
||||||
|
<form hx-post="/api/auth/register" hx-target="#auth-message" hx-swap="innerHTML">
|
||||||
|
<div class="mb-3"><label class="pd-hand mb-1 block text-lg text-accent">Username</label><input type="text" name="username" required pattern="[a-zA-Z0-9_-]{3,30}" class="pd-body w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"></div>
|
||||||
|
<div class="mb-3"><label class="pd-hand mb-1 block text-lg text-accent">Email</label><input type="email" name="email" required class="pd-body w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"></div>
|
||||||
|
<div class="mb-4"><label class="pd-hand mb-1 block text-lg text-accent">Password</label><input type="password" name="password" required minlength="8" class="pd-body w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"></div>
|
||||||
|
<button type="submit" class="pd-tag w-full justify-center" hx-disabled-elt="this">Create account</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
27
templates/overrides/pastel-dream/auth-status.ninjatpl
Normal file
27
templates/overrides/pastel-dream/auth-status.ninjatpl
Normal file
@ -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 %}
|
||||||
|
<div class="auth-status flex items-center gap-2">
|
||||||
|
<a href="/login" class="pd-body rounded-lg px-3 py-1.5 text-sm font-medium opacity-80 transition-colors hover:bg-muted hover:opacity-100">Login</a>
|
||||||
|
<a href="/login?tab=register" class="pd-tag text-sm">Sign up</a>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% if not auth.email_verified %}<div class="fixed inset-x-0 top-16 z-40 bg-accent px-4 py-2 text-center text-sm text-accent-foreground">Please verify your email address. <button hx-post="/api/auth/resend-verification" hx-swap="outerHTML" class="font-medium underline">Resend verification email</button></div>{% endif %}
|
||||||
|
<div class="auth-status relative">
|
||||||
|
<button type="button" onclick="this.nextElementSibling.classList.toggle('hidden')" class="pd-body flex items-center gap-2 rounded-lg px-3 py-1.5 text-sm font-medium opacity-90 transition-colors hover:bg-muted hover:opacity-100">
|
||||||
|
<span class="pd-hand flex h-7 w-7 items-center justify-center rounded-full bg-accent text-sm text-accent-foreground">{{ auth.display_name|first|upper }}</span>
|
||||||
|
<span>{{ auth.display_name }}</span>
|
||||||
|
<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#chevron-down"></use></svg>
|
||||||
|
</button>
|
||||||
|
<div id="auth-dropdown-menu" class="pd-card absolute right-0 top-full z-50 mt-1 hidden w-48 py-1 shadow-lg">
|
||||||
|
<a href="/account" class="pd-body block px-4 py-2 text-sm text-foreground hover:bg-muted">My profile</a>
|
||||||
|
<a href="/account/reviews" class="pd-body block px-4 py-2 text-sm text-foreground hover:bg-muted">My reviews</a>
|
||||||
|
<hr class="my-1 border-dashed border-border">
|
||||||
|
<button hx-post="/api/auth/logout" hx-swap="none" class="pd-body block w-full px-4 py-2 text-left text-sm text-destructive hover:bg-muted">Logout</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
(function(){document.addEventListener('click',function(e){var menu=document.getElementById('auth-dropdown-menu');var btn=document.querySelector('.auth-status > button');if(!menu||!btn)return;if(!btn.contains(e.target)&&!menu.contains(e.target)){menu.classList.add('hidden');}});})();
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
20
templates/overrides/pastel-dream/author-bio-hero.ninjatpl
Normal file
20
templates/overrides/pastel-dream/author-bio-hero.ninjatpl
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{% if author %}
|
||||||
|
<section class="border-b border-dashed border-border bg-background py-12 text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto flex max-w-3xl flex-col items-center px-4 text-center">
|
||||||
|
{% if showAvatar and author.avatar_url %}<div class="mb-5 h-24 w-24 overflow-hidden rounded-full bg-secondary ring-2 ring-border">{% img author.avatar_url alt=author.display_name class="h-full w-full object-cover" %}</div>{% endif %}
|
||||||
|
<h1 class="pd-display text-3xl text-primary md:text-4xl">{{ author.display_name }}</h1>
|
||||||
|
{% if author.job_title or author.company %}<p class="pd-hand mt-1 text-xl text-accent">{{ author.job_title }}{% if author.job_title and author.company %} · {% endif %}{{ author.company }}</p>{% endif %}
|
||||||
|
{% if author.bio %}<p class="pd-body mt-4 max-w-2xl leading-relaxed text-muted-foreground">{{ author.bio }}</p>{% endif %}
|
||||||
|
<div class="pd-body mt-5 flex flex-wrap items-center justify-center gap-4 text-sm text-muted-foreground">
|
||||||
|
{% if showPostCount %}<span>{{ author.post_count|default:0 }} post{% if author.post_count != 1 %}s{% endif %}</span>{% endif %}
|
||||||
|
{% if author.location %}<span>{{ author.location }}</span>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if showSocial %}<div class="mt-5 flex items-center justify-center gap-3">
|
||||||
|
{% if author.website_url %}<a href="{{ author.website_url }}" target="_blank" rel="noopener" class="text-muted-foreground transition-colors hover:text-accent" title="Website"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/lucide.svg#globe"></use></svg></a>{% endif %}
|
||||||
|
{% if author.twitter_handle %}<a href="https://twitter.com/{{ author.twitter_handle|cut:"@" }}" target="_blank" rel="noopener" class="text-muted-foreground transition-colors hover:text-accent" title="Twitter"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#x"></use></svg></a>{% endif %}
|
||||||
|
{% if author.linkedin_url %}<a href="{{ author.linkedin_url }}" target="_blank" rel="noopener" class="text-muted-foreground transition-colors hover:text-accent" title="LinkedIn"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#linkedin"></use></svg></a>{% endif %}
|
||||||
|
{% if author.github_url %}<a href="{{ author.github_url }}" target="_blank" rel="noopener" class="text-muted-foreground transition-colors hover:text-accent" title="GitHub"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#github"></use></svg></a>{% endif %}
|
||||||
|
</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
12
templates/overrides/pastel-dream/author-bio.ninjatpl
Normal file
12
templates/overrides/pastel-dream/author-bio.ninjatpl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{% if post.author_name %}
|
||||||
|
<aside class="pd-card p-6{% if class %} {{ class }}{% endif %}">
|
||||||
|
{% if heading %}<p class="pd-hand mb-4 text-xl text-accent">{{ heading }}</p>{% endif %}
|
||||||
|
<div class="flex items-start gap-4">
|
||||||
|
{% if showAvatar and post.author_avatar %}<div class="h-14 w-14 flex-shrink-0 overflow-hidden rounded-full bg-secondary ring-2 ring-border">{% img post.author_avatar alt=post.author_name class="h-full w-full object-cover" %}</div>{% endif %}
|
||||||
|
<div class="min-w-0">
|
||||||
|
<h3 class="pd-display text-base text-primary">{% if post.author_slug %}<a href="/author/{{ post.author_slug }}" class="transition-colors hover:text-accent">{{ post.author_name }}</a>{% else %}{{ post.author_name }}{% endif %}</h3>
|
||||||
|
{% if post.author_bio %}<p class="pd-body mt-1 text-sm text-muted-foreground">{{ post.author_bio }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
{% endif %}
|
||||||
22
templates/overrides/pastel-dream/blog-hero.ninjatpl
Normal file
22
templates/overrides/pastel-dream/blog-hero.ninjatpl
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<section class="bg-background py-12 text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-6xl px-4">
|
||||||
|
<div class="max-w-2xl">
|
||||||
|
{% if title %}<h1 class="pd-display text-4xl text-primary md:text-5xl"><span class="pd-doodle-underline">{{ title }}</span></h1>{% endif %}
|
||||||
|
{% if subtitle %}<p class="pd-body mt-3 text-lg text-muted-foreground">{{ subtitle }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if posts %}
|
||||||
|
<div class="mt-10 grid grid-cols-1 gap-6 md:grid-cols-3">
|
||||||
|
{% for post in posts %}
|
||||||
|
<article class="group flex flex-col{% if forloop.First %} md:col-span-3 md:flex-row md:items-stretch md:gap-6{% endif %}">
|
||||||
|
{% if post.featured_image_url %}<a href="{{ post.url }}" class="pd-frame mb-3 block overflow-hidden rounded-sm p-1.5{% if forloop.First %} md:mb-0 md:w-1/2{% endif %}">{% img post.featured_image_url alt=post.title class="aspect-video h-full w-full rounded-sm object-cover transition-transform duration-300 group-hover:scale-105" %}</a>{% endif %}
|
||||||
|
<div class="flex flex-col justify-center{% if forloop.First %} md:w-1/2{% endif %}">
|
||||||
|
<h2 class="pd-display {% if forloop.First %}text-2xl{% else %}text-lg{% endif %} text-primary"><a href="{{ post.url }}" class="transition-colors hover:text-accent">{{ post.title }}</a></h2>
|
||||||
|
{% if showExcerpt and post.excerpt %}<p class="pd-body mt-2 text-sm text-muted-foreground">{{ post.excerpt }}</p>{% endif %}
|
||||||
|
<div class="pd-hand mt-3 flex flex-wrap items-center gap-x-3 text-lg text-accent">{% if post.author_name %}<span>{{ post.author_name }}</span>{% endif %}{% if post.published_at %}<span class="text-muted-foreground">{{ post.published_at|date:"M j, Y" }}</span>{% endif %}</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
12
templates/overrides/pastel-dream/breadcrumbs.ninjatpl
Normal file
12
templates/overrides/pastel-dream/breadcrumbs.ninjatpl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{% set arrows = template == "arrows" %}
|
||||||
|
<nav aria-label="Breadcrumb" class="py-3{% if class %} {{ class }}{% endif %}">
|
||||||
|
<ol class="pd-body flex flex-wrap items-center gap-1 text-sm">
|
||||||
|
{% if showHome %}<li class="flex items-center"><a href="/" class="text-muted-foreground transition-colors hover:text-accent">{{ homeLabel|default:"Home" }}</a></li>{% endif %}
|
||||||
|
{% for item in breadcrumbs %}
|
||||||
|
<li class="flex items-center">
|
||||||
|
{% if showHome or not forloop.First %}{% if arrows %}<svg class="mx-2 h-4 w-4 text-accent" aria-hidden="true"><use href="/icons/lucide.svg#chevron-right"></use></svg>{% else %}<span class="mx-2 text-accent">{{ separator|default:"/" }}</span>{% endif %}{% endif %}
|
||||||
|
{% if item.is_current %}{% if showCurrent %}<span class="pd-display text-foreground" aria-current="page">{{ item.label }}</span>{% endif %}{% else %}<a href="{{ item.url }}" class="text-muted-foreground transition-colors hover:text-accent">{{ item.label }}</a>{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
@ -1,3 +1,5 @@
|
|||||||
<a href="{{ href|default:"#" }}" class="pastel-pill" data-block-type="button" data-variant="{{ variant|default:"primary" }}">
|
{% with align=align|default:"left" style=style|default:"primary" %}
|
||||||
{{ text|default:"Continue" }}
|
<div class="bn-button flex {% if align == "center" %}justify-center{% elif align == "right" %}justify-end{% else %}justify-start{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||||
</a>
|
{% if link %}<a href="{{ link }}"{% if newTab %} target="_blank" rel="noopener"{% endif %} class="pd-tag {% if style == "secondary" %}bg-secondary text-secondary-foreground{% elif style == "outline" or style == "ghost" %}pd-tag--ghost{% elif style == "destructive" %}bg-destructive text-destructive-foreground{% endif %} {% if size == "lg" %}text-base px-6 py-3{% elif size == "sm" %}text-xs px-3 py-1.5{% endif %}">{% if icon and iconPosition != "right" %}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/{{ icon|split:":"|first }}.svg#{{ icon|split:":"|last }}"></use></svg>{% endif %}<span>{{ label|default:"Learn more" }}</span>{% if icon and iconPosition == "right" %}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/{{ icon|split:":"|first }}.svg#{{ icon|split:":"|last }}"></use></svg>{% endif %}</a>{% else %}<button type="button" class="pd-tag {% if style == "secondary" %}bg-secondary text-secondary-foreground{% elif style == "outline" or style == "ghost" %}pd-tag--ghost{% elif style == "destructive" %}bg-destructive text-destructive-foreground{% endif %} {% if size == "lg" %}text-base px-6 py-3{% elif size == "sm" %}text-xs px-3 py-1.5{% endif %}">{% if icon and iconPosition != "right" %}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/{{ icon|split:":"|first }}.svg#{{ icon|split:":"|last }}"></use></svg>{% endif %}<span>{{ label|default:"Learn more" }}</span>{% if icon and iconPosition == "right" %}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/{{ icon|split:":"|first }}.svg#{{ icon|split:":"|last }}"></use></svg>{% endif %}</button>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
<div class="pastel-card p-6" data-block-type="card" style="border-radius: 20px;">
|
{% with layout=layout|default:"vertical" %}
|
||||||
{% if title %}<h3 class="font-display text-2xl mb-3" style="color: hsl(var(--card-foreground));">{{ title }}</h3>{% endif %}
|
<article class="bn-card pd-card overflow-hidden transition-shadow hover:shadow-md {% if layout == "horizontal" %}flex flex-col sm:flex-row{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||||
{% if body %}<div class="font-body" style="color: hsl(var(--card-foreground) / 0.85); line-height: 1.75;">
|
{% if link %}<a href="{{ link }}" class="contents">{% endif %}
|
||||||
{{ body|safe }}
|
{% if media %}<div class="overflow-hidden bg-secondary {% if layout == "horizontal" %}sm:w-2/5 sm:shrink-0{% endif %}">{% if layout == "horizontal" %}{% img media alt=title|default:"" class="h-full w-full object-cover" %}{% else %}{% img media alt=title|default:"" class="aspect-video h-full w-full object-cover" %}{% endif %}</div>{% endif %}
|
||||||
</div>{% endif %}
|
<div class="flex flex-col gap-2 p-5 md:p-6 {% if layout == "horizontal" %}justify-center{% endif %}">
|
||||||
{% if footer %}<div class="mt-6 pt-4 border-t font-body text-sm" style="border-color: hsl(var(--border)); color: hsl(var(--muted-foreground));">
|
{% if title %}<h3 class="pd-display text-xl text-primary">{{ title }}</h3>{% endif %}
|
||||||
{{ footer|safe }}
|
{% if text %}<div class="pd-body text-sm leading-relaxed text-muted-foreground">{{ text|safe }}</div>{% endif %}
|
||||||
</div>{% endif %}
|
{% if linkLabel and link %}<span class="pd-hand mt-2 inline-flex items-center gap-1 text-lg text-accent">{{ linkLabel }} <svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></span>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if link %}</a>{% endif %}
|
||||||
|
</article>
|
||||||
|
{% endwith %}
|
||||||
|
|||||||
17
templates/overrides/pastel-dream/category-list.ninjatpl
Normal file
17
templates/overrides/pastel-dream/category-list.ninjatpl
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{% set pills = style == "pills" %}
|
||||||
|
<section class="bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
{% if heading %}<h2 class="pd-hand mb-4 text-2xl text-accent">{{ heading }}</h2>{% endif %}
|
||||||
|
{% if pills %}
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
{% for cat in categories %}
|
||||||
|
<a href="{{ cat.url }}" class="pd-body inline-flex items-center gap-1.5 rounded-full border border-dashed border-border bg-card px-3 py-1 text-sm text-card-foreground transition-colors hover:border-accent hover:text-accent">{{ cat.name }}{% if showCount %}<span class="text-xs text-muted-foreground">{{ cat.post_count }}</span>{% endif %}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<ul class="space-y-1">
|
||||||
|
{% for cat in categories %}
|
||||||
|
<li><a href="{{ cat.url }}" class="pd-body flex items-center justify-between rounded-md px-3 py-2 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"><span>{{ cat.name }}</span>{% if showCount %}<span class="rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground">{{ cat.post_count }}</span>{% endif %}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</section>
|
||||||
17
templates/overrides/pastel-dream/contact-card.ninjatpl
Normal file
17
templates/overrides/pastel-dream/contact-card.ninjatpl
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-4xl px-4">
|
||||||
|
{% if heading %}<h2 class="pd-display text-3xl text-primary md:text-4xl"><span class="pd-doodle-underline">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if description %}<p class="pd-body mt-3 max-w-2xl text-lg text-muted-foreground">{{ description }}</p>{% endif %}
|
||||||
|
<div class="mt-10 grid grid-cols-1 gap-4 {% if columns == 3 %}sm:grid-cols-2 lg:grid-cols-3{% elif columns == 1 %}{% else %}sm:grid-cols-2{% endif %}">
|
||||||
|
{% for channel in channels %}
|
||||||
|
<div class="pd-card flex items-start gap-4 p-5">
|
||||||
|
{% if channel.icon %}<span class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-accent/15 text-accent"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/{{ channel.icon|split:":"|first }}.svg#{{ channel.icon|split:":"|last }}"></use></svg></span>{% endif %}
|
||||||
|
<div class="min-w-0">
|
||||||
|
{% if channel.label %}<div class="pd-hand text-lg text-accent">{{ channel.label }}</div>{% endif %}
|
||||||
|
{% if channel.value %}<div class="pd-body mt-0.5 break-words font-medium">{% if channel.link %}<a href="{{ channel.link }}" class="transition-colors hover:text-accent">{{ channel.value }}</a>{% else %}{{ channel.value }}{% endif %}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
39
templates/overrides/pastel-dream/contact-form.ninjatpl
Normal file
39
templates/overrides/pastel-dream/contact-form.ninjatpl
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{% with bid=_block_id|default:context.blockId %}
|
||||||
|
<section id="contact-form-{{ bid }}" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-2xl px-4">
|
||||||
|
{% if form.submitted %}
|
||||||
|
<div class="pd-card pd-torn-top pd-torn-bottom p-8 text-center">
|
||||||
|
<div class="mx-auto mb-4 inline-flex h-12 w-12 items-center justify-center rounded-full bg-accent/15 text-accent"><svg class="h-6 w-6" aria-hidden="true"><use href="/icons/lucide.svg#check"></use></svg></div>
|
||||||
|
<p class="pd-display text-xl text-primary">{{ form.message|default:successMessage|default:"Thanks. Your message is on its way." }}</p>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% if heading %}<h2 class="pd-display text-3xl text-primary md:text-4xl"><span class="pd-doodle-underline">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if description %}<p class="pd-body mt-3 text-lg text-muted-foreground">{{ description }}</p>{% endif %}
|
||||||
|
{% if form.errors._form %}<div class="pd-body mt-6 rounded-md border border-destructive/50 bg-destructive/10 px-4 py-3 text-sm text-destructive">{{ form.errors._form }}</div>{% endif %}
|
||||||
|
{% with formAction="/api/blocks/"|add:bid|add:"/submit" formTarget="#contact-form-"|add:bid %}
|
||||||
|
{% form action=formAction hx_target=formTarget hx_swap="outerHTML" class="mt-8 grid grid-cols-1 gap-5 sm:grid-cols-2" %}
|
||||||
|
{% for field in fields %}
|
||||||
|
<div class="{% if field.width == "half" %}sm:col-span-1{% else %}sm:col-span-2{% endif %}">
|
||||||
|
{% if field.label %}<label for="cf-{{ bid }}-{{ field.name }}" class="pd-hand mb-1.5 block text-lg text-accent">{{ field.label }}{% if field.required %} <span class="text-destructive">*</span>{% endif %}</label>{% endif %}
|
||||||
|
{% if field.type == "textarea" %}
|
||||||
|
<textarea id="cf-{{ bid }}-{{ field.name }}" name="{{ field.name }}" rows="5"{% if field.required %} required{% endif %}{% if field.placeholder %} placeholder="{{ field.placeholder }}"{% endif %} class="pd-body w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"></textarea>
|
||||||
|
{% elif field.type == "select" %}
|
||||||
|
<select id="cf-{{ bid }}-{{ field.name }}" name="{{ field.name }}"{% if field.required %} required{% endif %} class="pd-body w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring">
|
||||||
|
{% if field.placeholder %}<option value="">{{ field.placeholder }}</option>{% endif %}
|
||||||
|
{% for opt in field.options %}<option value="{{ opt.value }}">{{ opt.label|default:opt.value }}</option>{% endfor %}
|
||||||
|
</select>
|
||||||
|
{% else %}
|
||||||
|
<input id="cf-{{ bid }}-{{ field.name }}" name="{{ field.name }}" type="{{ field.type|default:"text" }}"{% if field.required %} required{% endif %}{% if field.placeholder %} placeholder="{{ field.placeholder }}"{% endif %} class="pd-body w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<div class="hidden" aria-hidden="true"><label>Leave this field empty<input type="text" name="website" tabindex="-1" autocomplete="off"></label></div>
|
||||||
|
<div class="sm:col-span-2">
|
||||||
|
<button type="submit" class="pd-tag">{{ submitLabel|default:"Send message" }}</button>
|
||||||
|
</div>
|
||||||
|
{% endform %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endwith %}
|
||||||
42
templates/overrides/pastel-dream/countdown.ninjatpl
Normal file
42
templates/overrides/pastel-dream/countdown.ninjatpl
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-4xl px-4 text-center">
|
||||||
|
{% if heading %}<h2 class="pd-display text-3xl text-primary md:text-4xl">{{ heading }}</h2>{% endif %}
|
||||||
|
{% if intro %}<p class="pd-body mx-auto mt-4 max-w-2xl text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
|
||||||
|
<div class="mt-10" data-countdown>
|
||||||
|
<div class="flex items-start justify-center gap-3 sm:gap-6" data-countdown-timer data-target="{{ target }}" role="timer" aria-label="Time remaining">
|
||||||
|
<div class="flex flex-col items-center {% if variant == "boxes" %}pd-card min-w-[4.5rem] px-3 py-4{% endif %}"><span class="pd-mono text-4xl font-bold tabular-nums text-accent sm:text-5xl" data-unit="days">--</span><span class="pd-hand mt-1 text-lg text-muted-foreground">{{ labelDays|default:"Days" }}</span></div>
|
||||||
|
<div class="flex flex-col items-center {% if variant == "boxes" %}pd-card min-w-[4.5rem] px-3 py-4{% endif %}"><span class="pd-mono text-4xl font-bold tabular-nums text-accent sm:text-5xl" data-unit="hours">--</span><span class="pd-hand mt-1 text-lg text-muted-foreground">{{ labelHours|default:"Hours" }}</span></div>
|
||||||
|
<div class="flex flex-col items-center {% if variant == "boxes" %}pd-card min-w-[4.5rem] px-3 py-4{% endif %}"><span class="pd-mono text-4xl font-bold tabular-nums text-accent sm:text-5xl" data-unit="minutes">--</span><span class="pd-hand mt-1 text-lg text-muted-foreground">{{ labelMinutes|default:"Minutes" }}</span></div>
|
||||||
|
<div class="flex flex-col items-center {% if variant == "boxes" %}pd-card min-w-[4.5rem] px-3 py-4{% endif %}"><span class="pd-mono text-4xl font-bold tabular-nums text-accent sm:text-5xl" data-unit="seconds">--</span><span class="pd-hand mt-1 text-lg text-muted-foreground">{{ labelSeconds|default:"Seconds" }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-8 hidden" data-countdown-expired>
|
||||||
|
<p class="pd-display text-xl text-primary">{{ expiredMessage|default:"This has started." }}</p>
|
||||||
|
{% if expiredUrl and expiredLabel %}<a href="{{ expiredUrl }}" class="pd-tag mt-4 text-base px-5 py-3">{{ expiredLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<noscript><p class="pd-body mt-6 text-muted-foreground">Counting down to <time datetime="{{ target }}">{{ target }}</time>.</p></noscript>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
document.querySelectorAll('[data-countdown-timer]').forEach(function(el){
|
||||||
|
var target = new Date(el.getAttribute('data-target')).getTime();
|
||||||
|
if (isNaN(target)) return;
|
||||||
|
var wrap = el.closest('[data-countdown]');
|
||||||
|
var expired = wrap ? wrap.querySelector('[data-countdown-expired]') : null;
|
||||||
|
function pad(n){ return (n < 10 ? '0' : '') + n; }
|
||||||
|
function set(unit, value){ var node = el.querySelector('[data-unit="' + unit + '"]'); if (node) node.textContent = value; }
|
||||||
|
var interval;
|
||||||
|
function tick(){
|
||||||
|
var diff = target - Date.now();
|
||||||
|
if (diff <= 0){ el.style.display = 'none'; if (expired) expired.classList.remove('hidden'); if (interval) clearInterval(interval); return; }
|
||||||
|
set('days', Math.floor(diff / 86400000));
|
||||||
|
set('hours', pad(Math.floor(diff % 86400000 / 3600000)));
|
||||||
|
set('minutes', pad(Math.floor(diff % 3600000 / 60000)));
|
||||||
|
set('seconds', pad(Math.floor(diff % 60000 / 1000)));
|
||||||
|
}
|
||||||
|
tick();
|
||||||
|
interval = setInterval(tick, 1000);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
14
templates/overrides/pastel-dream/cta.ninjatpl
Normal file
14
templates/overrides/pastel-dream/cta.ninjatpl
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-6xl px-4">
|
||||||
|
<div class="{% if background == "band" %}pd-torn-top pd-torn-bottom rounded-2xl bg-primary px-6 py-14 text-center text-primary-foreground md:px-12{% elif background == "card" %}pd-card px-6 py-14 text-center md:px-12{% else %}flex flex-col items-center gap-8 rounded-2xl bg-muted px-6 py-12 text-foreground md:flex-row md:justify-between md:px-12 md:text-left{% endif %}">
|
||||||
|
<div class="{% if background != "split" %}mx-auto max-w-2xl{% endif %}">
|
||||||
|
{% if heading %}<h2 class="pd-display text-3xl md:text-4xl {% if background == "band" %}text-primary-foreground{% else %}text-primary{% endif %}">{{ heading }}</h2>{% endif %}
|
||||||
|
{% if text %}<p class="pd-body mt-3 text-lg {% if background == "band" %}text-primary-foreground/80{% else %}text-muted-foreground{% endif %}">{{ text }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap items-center justify-center gap-3 {% if background != "split" %}mt-8{% else %}shrink-0{% endif %}">
|
||||||
|
{% if primaryUrl and primaryLabel %}<a href="{{ primaryUrl }}" class="pd-tag text-base px-6 py-3 {% if background == "band" %}bg-background text-foreground{% endif %}">{{ primaryLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||||
|
{% if secondaryUrl and secondaryLabel %}<a href="{{ secondaryUrl }}" class="pd-tag pd-tag--ghost text-base px-6 py-3 {% if background == "band" %}border-primary-foreground/40 text-primary-foreground{% endif %}">{{ secondaryLabel }}</a>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
17
templates/overrides/pastel-dream/divider.ninjatpl
Normal file
17
templates/overrides/pastel-dream/divider.ninjatpl
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{% with variant=variant|default:"line" width=width|default:"full" %}
|
||||||
|
<div class="bn-divider pd-doodle-draw mx-auto my-8 flex items-center justify-center {% if width == "narrow" %}max-w-[200px]{% elif width == "wide" %}max-w-3xl{% else %}w-full{% endif %}{% if class %} {{ class }}{% endif %}" role="separator">
|
||||||
|
{% if variant == "ornament" or variant == "spacer-rule" %}
|
||||||
|
<span class="h-px flex-1 bg-border"></span>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="72" height="26" viewBox="0 0 72 26" fill="none" stroke="hsl(var(--accent))" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" class="mx-4 shrink-0" aria-hidden="true">
|
||||||
|
<ellipse cx="30" cy="13" rx="6" ry="9"></ellipse><path d="M30 4 L30 22"></path>
|
||||||
|
<ellipse cx="42" cy="13" rx="6" ry="9"></ellipse><path d="M42 4 L42 22"></path>
|
||||||
|
<path d="M4 13 L18 13"></path><path d="M54 13 L68 13"></path>
|
||||||
|
</svg>
|
||||||
|
<span class="h-px flex-1 bg-border"></span>
|
||||||
|
{% else %}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 12" preserveAspectRatio="none" class="h-3 w-full" fill="none" stroke="hsl(var(--border))" stroke-width="2" stroke-linecap="round" aria-hidden="true">
|
||||||
|
<path d="M2 7 Q 40 2 80 7 T 160 6 T 240 7 T 298 6"></path>
|
||||||
|
</svg>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
40
templates/overrides/pastel-dream/event-list.ninjatpl
Normal file
40
templates/overrides/pastel-dream/event-list.ninjatpl
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-6xl px-4">
|
||||||
|
{% if heading %}<h2 class="pd-display text-3xl text-primary md:text-4xl"><span class="pd-doodle-underline">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if intro %}<p class="pd-body mt-3 max-w-2xl text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
|
||||||
|
{% if layout == "cards" %}
|
||||||
|
<div class="mt-10 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{% for event in events %}{% if not (hidePast and event.past) %}
|
||||||
|
<article class="pd-card flex flex-col overflow-hidden{% if event.past %} opacity-60{% endif %}">
|
||||||
|
{% if event.media %}<div class="aspect-video overflow-hidden bg-secondary">{% img event.media alt=event.title class="h-full w-full object-cover" %}</div>{% endif %}
|
||||||
|
<div class="flex flex-1 flex-col gap-2 p-5">
|
||||||
|
<div class="pd-hand text-lg text-accent"><time{% if event.datetime %} datetime="{{ event.datetime }}"{% endif %}>{{ event.start }}{% if event.end %} – {{ event.end }}{% endif %}</time>{% if event.timezone %} <span class="text-muted-foreground">{{ event.timezone }}</span>{% endif %}</div>
|
||||||
|
<h3 class="pd-display text-lg text-primary">{{ event.title }}</h3>
|
||||||
|
{% if event.venue %}<p class="pd-body text-sm text-muted-foreground">{{ event.venue }}</p>{% endif %}
|
||||||
|
{% if event.description %}<p class="pd-body text-sm text-muted-foreground">{{ event.description }}</p>{% endif %}
|
||||||
|
{% if event.link %}<a href="{{ event.link }}" class="pd-hand mt-auto inline-flex items-center gap-1 pt-2 text-lg text-accent hover:underline">{{ event.linkLabel|default:"Details" }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% endif %}{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<ul class="mt-10 divide-y divide-dashed divide-border border-y border-dashed border-border">
|
||||||
|
{% for event in events %}{% if not (hidePast and event.past) %}
|
||||||
|
<li class="flex flex-col gap-3 py-5 sm:flex-row sm:items-center sm:gap-6{% if event.past %} opacity-60{% endif %}">
|
||||||
|
<div class="shrink-0 sm:w-52">
|
||||||
|
<div class="pd-display text-sm text-primary"><time{% if event.datetime %} datetime="{{ event.datetime }}"{% endif %}>{{ event.start }}</time></div>
|
||||||
|
{% if event.end %}<div class="pd-body text-xs text-muted-foreground">to {{ event.end }}</div>{% endif %}
|
||||||
|
{% if event.timezone %}<div class="pd-body text-xs text-muted-foreground">{{ event.timezone }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<h3 class="pd-display text-base text-primary">{{ event.title }}</h3>
|
||||||
|
{% if event.venue %}<p class="pd-body text-sm text-muted-foreground">{{ event.venue }}</p>{% endif %}
|
||||||
|
{% if event.description %}<p class="pd-body mt-1 text-sm text-muted-foreground">{{ event.description }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if event.link %}<a href="{{ event.link }}" class="pd-tag pd-tag--ghost shrink-0 text-xs">{{ event.linkLabel|default:"Details" }}</a>{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endif %}{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
17
templates/overrides/pastel-dream/faq.ninjatpl
Normal file
17
templates/overrides/pastel-dream/faq.ninjatpl
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-3xl px-4">
|
||||||
|
{% if heading %}<h2 class="pd-display text-center text-3xl text-primary md:text-4xl">{{ heading }}</h2>{% endif %}
|
||||||
|
{% if intro %}<p class="pd-body mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
|
||||||
|
<div class="mt-10 divide-y divide-dashed divide-border border-y border-dashed border-border">
|
||||||
|
{% for item in items %}
|
||||||
|
<details class="group py-2"{% if variant == "open-list" %} open{% endif %}>
|
||||||
|
<summary class="pd-display flex cursor-pointer list-none items-center justify-between gap-4 py-3 text-lg text-primary marker:content-none [&::-webkit-details-marker]:hidden">
|
||||||
|
<span>{{ item.question }}</span>
|
||||||
|
<svg class="h-5 w-5 shrink-0 text-accent transition-transform duration-200 group-open:rotate-180" aria-hidden="true"><use href="/icons/lucide.svg#chevron-down"></use></svg>
|
||||||
|
</summary>
|
||||||
|
<div class="pd-body pb-4 text-muted-foreground [&_a]:text-accent [&_a]:underline [&_a]:decoration-dashed">{{ item.answer|safe }}</div>
|
||||||
|
</details>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
17
templates/overrides/pastel-dream/feature-grid.ninjatpl
Normal file
17
templates/overrides/pastel-dream/feature-grid.ninjatpl
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-6xl px-4">
|
||||||
|
{% if eyebrow %}<p class="pd-hand text-center text-2xl text-accent">{{ eyebrow }}</p>{% endif %}
|
||||||
|
{% if heading %}<h2 class="pd-display mt-1 text-center text-3xl text-primary md:text-4xl"><span class="pd-doodle-underline">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if intro %}<p class="pd-body mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
|
||||||
|
<div class="mt-12 grid grid-cols-1 gap-6 sm:grid-cols-2 {% if columns == 2 %}lg:grid-cols-2{% elif columns == 4 %}lg:grid-cols-4{% else %}lg:grid-cols-3{% endif %}">
|
||||||
|
{% for item in items %}
|
||||||
|
<div class="{% if layout == "card" %}pd-card p-6 transition-shadow hover:shadow-md{% elif layout == "centered" %}text-center{% endif %}">
|
||||||
|
{% if item.icon %}<div class="mb-4 inline-flex h-11 w-11 items-center justify-center rounded-full bg-accent/15 text-accent{% if layout == "centered" %} mx-auto{% endif %}"><svg class="h-6 w-6" aria-hidden="true"><use href="/icons/{{ item.icon|split:":"|first }}.svg#{{ item.icon|split:":"|last }}"></use></svg></div>{% endif %}
|
||||||
|
<h3 class="pd-display text-xl text-primary">{% if item.linkUrl %}<a href="{{ item.linkUrl }}" class="transition-colors hover:text-accent">{{ item.title }}</a>{% else %}{{ item.title }}{% endif %}</h3>
|
||||||
|
{% if item.text %}<p class="pd-body mt-2 text-muted-foreground">{{ item.text }}</p>{% endif %}
|
||||||
|
{% if item.linkUrl and item.linkLabel %}<a href="{{ item.linkUrl }}" class="pd-hand mt-3 inline-flex items-center gap-1 text-lg text-accent hover:underline">{{ item.linkLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
21
templates/overrides/pastel-dream/featured-posts.ninjatpl
Normal file
21
templates/overrides/pastel-dream/featured-posts.ninjatpl
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<section class="py-12 md:py-16 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-6xl px-4">
|
||||||
|
{% if heading %}<h2 class="pd-display mb-8 text-2xl text-primary md:text-3xl"><span class="pd-doodle-underline">{{ heading }}</span></h2>{% endif %}
|
||||||
|
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 {% if columns == 2 %}lg:grid-cols-2{% elif columns == 4 %}lg:grid-cols-4{% else %}lg:grid-cols-3{% endif %}">
|
||||||
|
{% for post in posts %}
|
||||||
|
<article class="pd-card group flex flex-col overflow-hidden transition-shadow hover:shadow-md">
|
||||||
|
{% if showFeaturedImage and post.featured_image_url %}<a href="{{ post.url }}" class="block aspect-video overflow-hidden bg-secondary">{% img post.featured_image_url alt=post.title class="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105" %}</a>{% endif %}
|
||||||
|
<div class="flex flex-1 flex-col p-5">
|
||||||
|
<h3 class="pd-display text-lg text-primary"><a href="{{ post.url }}" class="transition-colors hover:text-accent">{{ post.title }}</a></h3>
|
||||||
|
{% if showExcerpt and post.excerpt %}<p class="pd-body mt-2 flex-1 text-sm text-muted-foreground">{{ post.excerpt }}</p>{% endif %}
|
||||||
|
<div class="pd-hand mt-4 flex flex-wrap items-center gap-x-3 gap-y-1 text-base text-accent">
|
||||||
|
{% if showAuthor and post.author_name %}<span>{{ post.author_name }}</span>{% endif %}
|
||||||
|
{% if showDate and post.published_at %}<span class="text-muted-foreground">{{ post.published_at|date:"M j, Y" }}</span>{% endif %}
|
||||||
|
{% if post.reading_time %}<span class="text-muted-foreground">{{ post.reading_time }} min</span>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
38
templates/overrides/pastel-dream/footer.ninjatpl
Normal file
38
templates/overrides/pastel-dream/footer.ninjatpl
Normal file
@ -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 %}<div class="flex items-center gap-3 {{ cls }}">{% for s in social_items %}<a href="{{ s.url }}" target="_blank" rel="noopener" title="{{ s.label }}" class="text-muted-foreground transition-colors hover:text-accent"><span class="sr-only">{{ s.label }}</span>{% if s.icon %}<svg class="h-5 w-5" aria-hidden="true"><use href="/icons/{{ s.icon|split:":"|first }}.svg#{{ s.icon|split:":"|last }}"></use></svg>{% else %}{{ s.label }}{% endif %}</a>{% endfor %}</div>{% endif %}{% endmacro %}
|
||||||
|
{% macro newsletter() %}{% if showNewsletter %}<div class="max-w-sm"><h3 class="pd-hand text-2xl text-accent">{{ newsletterTitle|default:"Stay in the loop" }}</h3>{% if newsletterDescription %}<p class="pd-body mt-1 text-sm text-muted-foreground">{{ newsletterDescription }}</p>{% endif %}<div class="mt-3">{% signup_form list=newsletterListSlug|default:"main" source="footer" submit=newsletterButtonText|default:"Subscribe" %}</div></div>{% endif %}{% endmacro %}
|
||||||
|
{% macro legalbar() %}<div class="mt-10 flex flex-col items-center justify-between gap-3 border-t border-dashed border-border pt-6 text-sm text-muted-foreground sm:flex-row"><p class="pd-body">{% if copyright %}© {{ copyright }}{% else %}© {{ companyName }}{% endif %}</p>{% if legal_items %}<nav class="flex flex-wrap items-center gap-x-4 gap-y-1">{% for l in legal_items %}<a href="{{ l.url|default:'#' }}"{% if l.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="pd-body transition-colors hover:text-foreground">{{ l.label }}</a>{% endfor %}</nav>{% endif %}</div>{% endmacro %}
|
||||||
|
<footer class="pd-torn-top w-full bg-muted/50 text-foreground {{ class }}">
|
||||||
|
<div class="{% if fullWidth %}w-full px-4 sm:px-6 lg:px-8{% else %}mx-auto max-w-7xl px-4 sm:px-6 lg:px-8{% endif %} py-16">
|
||||||
|
{% if fl == "minimal" %}
|
||||||
|
<div class="flex flex-col items-center justify-between gap-6 sm:flex-row">
|
||||||
|
<div class="flex items-center gap-3">{% if logo %}<img src="{{ logo }}" alt="{{ companyName }}" class="h-8 w-auto"/>{% elif companyName %}<span class="pd-display text-xl text-primary">{{ companyName }}</span>{% endif %}{% if tagline %}<span class="pd-hand hidden text-lg text-accent sm:inline">{{ tagline }}</span>{% endif %}</div>
|
||||||
|
{{ sociallinks("") }}
|
||||||
|
</div>
|
||||||
|
{{ legalbar() }}
|
||||||
|
{% elif fl == "centered" %}
|
||||||
|
<div class="flex flex-col items-center gap-5 text-center">
|
||||||
|
{% if logo %}<img src="{{ logo }}" alt="{{ companyName }}" class="h-10 w-auto"/>{% elif companyName %}<span class="pd-display text-2xl text-primary">{{ companyName }}</span>{% endif %}
|
||||||
|
{% if tagline %}<p class="pd-hand max-w-md text-xl text-accent">{{ tagline }}</p>{% endif %}
|
||||||
|
{% if columns %}<nav class="flex flex-wrap items-center justify-center gap-x-6 gap-y-2">{% for col in columns %}{% for item in col.items %}<a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="pd-body text-sm text-muted-foreground transition-colors hover:text-foreground">{{ item.label }}</a>{% endfor %}{% endfor %}</nav>{% endif %}
|
||||||
|
{{ newsletter() }}
|
||||||
|
{{ sociallinks("justify-center") }}
|
||||||
|
</div>
|
||||||
|
{{ legalbar() }}
|
||||||
|
{% else %}
|
||||||
|
<div class="grid grid-cols-2 gap-8 md:grid-cols-4 lg:grid-cols-5">
|
||||||
|
<div class="col-span-2 lg:col-span-2">
|
||||||
|
{% if logo %}<img src="{{ logo }}" alt="{{ companyName }}" class="h-9 w-auto"/>{% elif companyName %}<span class="pd-display text-2xl text-primary">{{ companyName }}</span>{% endif %}
|
||||||
|
{% if tagline %}<p class="pd-hand mt-2 max-w-xs text-xl text-accent">{{ tagline }}</p>{% endif %}
|
||||||
|
{% if address or email or phone %}<address class="pd-body mt-4 space-y-1 text-sm not-italic text-muted-foreground">{% if address %}<div>{{ address }}</div>{% endif %}{% if email %}<div><a href="mailto:{{ email }}" class="transition-colors hover:text-foreground">{{ email }}</a></div>{% endif %}{% if phone %}<div><a href="tel:{{ phone }}" class="transition-colors hover:text-foreground">{{ phone }}</a></div>{% endif %}</address>{% endif %}
|
||||||
|
{{ sociallinks("mt-5") }}
|
||||||
|
</div>
|
||||||
|
{% for col in columns %}<div><h3 class="pd-hand text-xl text-accent">{{ col.header }}</h3><ul class="mt-3 space-y-2">{% for item in col.items %}<li><a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="pd-body text-sm text-muted-foreground transition-colors hover:text-foreground">{{ item.label }}</a></li>{% endfor %}</ul></div>{% endfor %}
|
||||||
|
{% if showNewsletter %}<div class="col-span-2 lg:col-span-1">{{ newsletter() }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
{{ legalbar() }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
9
templates/overrides/pastel-dream/gallery.ninjatpl
Normal file
9
templates/overrides/pastel-dream/gallery.ninjatpl
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{% with layout=layout|default:"grid" gap=gap|default:"md" cols=columns|default:3 %}
|
||||||
|
<div class="bn-gallery my-8{% if class %} {{ class }}{% endif %}" data-bn-gallery data-lightbox="{% if lightbox %}true{% else %}false{% endif %}">
|
||||||
|
<div class="{% if layout == "carousel" %}flex snap-x snap-mandatory overflow-x-auto pb-2 {% if gap == "sm" %}gap-2{% elif gap == "lg" %}gap-6{% else %}gap-4{% endif %}{% elif layout == "masonry" %}columns-2 {% if cols == 4 %}md:columns-4{% elif cols == 2 %}md:columns-2{% else %}md:columns-3{% endif %} {% if gap == "sm" %}gap-2{% elif gap == "lg" %}gap-6{% else %}gap-4{% endif %}{% else %}grid grid-cols-2 {% if cols == 4 %}md:grid-cols-4{% elif cols == 2 %}md:grid-cols-2{% else %}md:grid-cols-3{% endif %} {% if gap == "sm" %}gap-2{% elif gap == "lg" %}gap-6{% else %}gap-4{% endif %}{% endif %}">
|
||||||
|
{% for image in images %}
|
||||||
|
<a href="{{ image.src|mediaURL }}" data-bn-gallery-item data-caption="{{ image.caption|default:"" }}" class="group relative block overflow-hidden rounded-sm pd-frame p-1.5 bg-card focus:outline-none focus-visible:ring-2 focus-visible:ring-ring {% if layout == "carousel" %}w-64 shrink-0 snap-start sm:w-80{% elif layout == "masonry" %}mb-4 w-full break-inside-avoid{% endif %}">{% if layout == "grid" %}{% img image.src alt=image.alt|default:"" class="aspect-square w-full rounded-sm object-cover" %}{% else %}{% img image.src alt=image.alt|default:"" class="h-auto w-full rounded-sm" %}{% endif %}{% if image.caption %}<span class="sr-only">{{ image.caption }}</span>{% endif %}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
@ -1 +1,6 @@
|
|||||||
{% if level|integer == 1 %}<h1 class="font-display text-5xl md:text-6xl leading-tight brush-underline {{ textClass }}" style="color: hsl(var(--foreground));">{{ text }}</h1>{% elif level|integer == 3 %}<h3 class="font-body text-3xl font-semibold tracking-tight {{ textClass }}" style="color: hsl(var(--foreground));">{{ text }}</h3>{% elif level|integer == 4 %}<h4 class="font-body text-2xl font-semibold {{ textClass }}" style="color: hsl(var(--foreground));">{{ text }}</h4>{% elif level|integer == 5 %}<h5 class="font-body text-xl font-medium {{ textClass }}" style="color: hsl(var(--foreground));">{{ text }}</h5>{% elif level|integer == 6 %}<h6 class="font-body text-lg font-medium {{ textClass }}" style="color: hsl(var(--foreground));">{{ text }}</h6>{% else %}<h2 class="font-display text-4xl md:text-5xl leading-tight brush-underline {{ textClass }}" style="color: hsl(var(--foreground));">{{ text }}</h2>{% endif %}
|
{% if level == 1 %}<h1 class="pd-display text-4xl md:text-5xl leading-tight text-primary flex-1 {{ textClass }}">{{ text }}</h1>
|
||||||
|
{% elif level == 3 %}<h3 class="pd-display text-2xl text-primary flex-1 {{ textClass }}"><span class="pd-doodle-underline">{{ text }}</span></h3>
|
||||||
|
{% elif level == 4 %}<h4 class="pd-display text-xl text-primary flex-1 {{ textClass }}">{{ text }}</h4>
|
||||||
|
{% elif level == 5 %}<h5 class="pd-display text-lg font-medium text-primary flex-1 {{ textClass }}">{{ text }}</h5>
|
||||||
|
{% elif level == 6 %}<h6 class="pd-hand text-xl text-accent flex-1 {{ textClass }}">{{ text }}</h6>
|
||||||
|
{% else %}<h2 class="pd-display text-3xl md:text-4xl text-primary flex-1 {{ textClass }}"><span class="pd-doodle-underline">{{ text }}</span></h2>{% endif %}
|
||||||
|
|||||||
36
templates/overrides/pastel-dream/hero.ninjatpl
Normal file
36
templates/overrides/pastel-dream/hero.ninjatpl
Normal file
@ -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 %}<span class="pd-hand mb-2 inline-block text-2xl text-accent">{{ eyebrowText }}</span>{% endif %}
|
||||||
|
{% if headlineText %}<h1 class="pd-display text-4xl leading-tight text-primary sm:text-5xl lg:text-6xl">{{ headlineText }}</h1>{% endif %}
|
||||||
|
{% if subText %}<h2 class="pd-body mt-4 max-w-2xl text-xl text-muted-foreground sm:text-2xl{% if align == "center" %} mx-auto{% endif %}">{{ subText }}</h2>{% endif %}
|
||||||
|
{% if descText %}<p class="pd-body mt-4 max-w-xl leading-relaxed text-muted-foreground{% if align == "center" %} mx-auto{% endif %}">{{ descText }}</p>{% endif %}
|
||||||
|
{% if pText or sText %}<div class="mt-8 flex flex-col gap-4 sm:flex-row{% if align == "center" %} justify-center{% endif %}">
|
||||||
|
{% if pText and pUrl %}<a href="{{ pUrl }}" class="pd-tag text-base px-6 py-3">{{ pText }}</a>{% endif %}
|
||||||
|
{% if sText and sUrl %}<a href="{{ sUrl }}" class="pd-tag pd-tag--ghost text-base px-6 py-3">{{ sText }}</a>{% endif %}
|
||||||
|
</div>{% endif %}
|
||||||
|
{% if trustLogos %}<div class="mt-10"><p class="pd-hand mb-3 text-lg text-accent">Regulars love</p><div class="flex flex-wrap items-center gap-6{% if align == "center" %} justify-center{% endif %}">{% for logo in trustLogos %}{% if logo.url %}<a href="{{ logo.url }}" target="_blank" rel="noopener" class="opacity-60 transition-opacity hover:opacity-100">{% img logo.image alt=logo.alt class="h-8 w-auto" %}</a>{% else %}{% img logo.image alt=logo.alt class="h-8 w-auto opacity-60" %}{% endif %}{% endfor %}</div></div>{% endif %}
|
||||||
|
{% endmacro %}
|
||||||
|
{% if variant == "split" %}
|
||||||
|
<section class="hero grid grid-cols-1 overflow-hidden bg-background text-foreground lg:grid-cols-2{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="relative min-h-[320px] bg-secondary {% if mediaPosition == "left" %}order-1 lg:order-1{% else %}order-1 lg:order-2{% endif %}">{% if hasImage %}{% img backgroundImage alt=headlineText class="absolute inset-0 h-full w-full object-cover" layout="hero" %}{% endif %}</div>
|
||||||
|
<div class="flex flex-col justify-center px-6 py-16 lg:px-12 {% if mediaPosition == "left" %}order-2 lg:order-2{% else %}order-2 lg:order-1{% endif %}"><div class="max-w-lg text-left">{{ herocontent("left") }}</div></div>
|
||||||
|
</section>
|
||||||
|
{% else %}
|
||||||
|
{% set align = "center" %}
|
||||||
|
{% if variant == "left-aligned" %}{% set align = "left" %}{% endif %}
|
||||||
|
<section class="hero relative flex flex-col overflow-hidden bg-background text-foreground {% if minHeight == "screen" %}min-h-screen{% elif minHeight == "50vh" %}min-h-[50vh]{% elif minHeight == "auto" %}min-h-[400px]{% else %}min-h-[75vh]{% endif %} {% if verticalAlign == "top" %}justify-start pt-20{% elif verticalAlign == "bottom" %}justify-end pb-20{% else %}justify-center{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||||
|
{% if hasImage %}<div class="absolute inset-0 z-0">{% img backgroundImage alt=headlineText class="h-full w-full object-cover" layout="hero" %}{% if showOverlay %}<div class="absolute inset-0 {% if overlayColor == "light" %}bg-background/60{% elif overlayColor == "primary" %}bg-primary/50{% elif overlayOpacity <= 30 %}bg-black/30{% elif overlayOpacity <= 50 %}bg-black/50{% elif overlayOpacity <= 70 %}bg-black/70{% else %}bg-black/80{% endif %}"></div>{% endif %}</div>{% endif %}
|
||||||
|
<div class="relative z-10 mx-auto w-full max-w-4xl px-4 sm:px-6 lg:px-8 {% if variant == "left-aligned" %}text-left{% else %}text-center{% endif %}">{{ herocontent(align) }}</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
40
templates/overrides/pastel-dream/hours-location.ninjatpl
Normal file
40
templates/overrides/pastel-dream/hours-location.ninjatpl
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-5xl px-4">
|
||||||
|
{% if heading %}<h2 class="pd-display text-3xl text-primary md:text-4xl"><span class="pd-doodle-underline">{{ heading }}</span></h2>{% endif %}
|
||||||
|
<div class="mt-10 grid grid-cols-1 gap-10 md:grid-cols-2">
|
||||||
|
<div>
|
||||||
|
{% if hours %}
|
||||||
|
<table class="pd-body w-full text-sm" data-pd-hours>
|
||||||
|
<tbody class="divide-y divide-dashed divide-border">
|
||||||
|
{% for row in hours %}
|
||||||
|
<tr data-day="{{ row.day }}">
|
||||||
|
<th scope="row" class="pd-display py-2 pr-4 text-left text-base text-primary">{{ row.day }}</th>
|
||||||
|
<td class="py-2 text-right {% if row.closed %}text-muted-foreground{% else %}text-foreground{% endif %} tabular-nums">{% if row.closed %}Closed{% else %}{{ row.hours }}{% endif %}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
{% if specialHoursNote %}<p class="pd-body mt-4 rounded-md border border-dashed border-border bg-muted px-3 py-2 text-sm text-muted-foreground">{{ specialHoursNote }}</p>{% endif %}
|
||||||
|
{% if address %}<div class="mt-6"><div class="pd-hand text-lg text-accent">Find us</div><address class="pd-body mt-1 whitespace-pre-line not-italic">{{ address }}</address></div>{% endif %}
|
||||||
|
{% if phone %}<div class="mt-4"><div class="pd-hand text-lg text-accent">Call ahead</div><a href="tel:{{ phone }}" class="pd-body mt-1 inline-block font-medium transition-colors hover:text-accent">{{ phone }}</a></div>{% endif %}
|
||||||
|
{% if directionsUrl %}<a href="{{ directionsUrl }}" target="_blank" rel="noopener" class="pd-tag mt-6 text-sm"><svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#navigation"></use></svg>Get directions</a>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if mapEmbedUrl %}
|
||||||
|
<div class="pd-frame aspect-square w-full overflow-hidden rounded-sm bg-secondary md:aspect-auto">
|
||||||
|
<button type="button" data-map-src="{{ mapEmbedUrl }}" onclick="var f=document.createElement('iframe');f.src=this.getAttribute('data-map-src');f.setAttribute('class','h-full w-full border-0');f.setAttribute('loading','lazy');f.setAttribute('referrerpolicy','no-referrer-when-downgrade');f.setAttribute('title','Map');this.parentNode.replaceChild(f,this);" class="pd-body flex h-full min-h-[16rem] w-full items-center justify-center gap-2 text-sm font-medium transition-colors hover:bg-muted"><svg class="h-5 w-5 pd-pin" aria-hidden="true"><use href="/icons/lucide.svg#map-pin"></use></svg>{{ mapButtonLabel|default:"Load map" }}</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
var days=['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
|
||||||
|
var today=days[new Date().getDay()];
|
||||||
|
document.querySelectorAll('[data-pd-hours] tr[data-day]').forEach(function(tr){
|
||||||
|
var d=(tr.getAttribute('data-day')||'').trim();
|
||||||
|
if(d.toLowerCase().indexOf(today.toLowerCase())===0){tr.querySelector('th').classList.add('pd-hours-today');}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
8
templates/overrides/pastel-dream/image.ninjatpl
Normal file
8
templates/overrides/pastel-dream/image.ninjatpl
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{% with size=size|default:"full" align=align|default:"center" %}
|
||||||
|
<figure class="my-8 flex flex-1 flex-col {% if align == "left" %}items-start{% elif align == "right" %}items-end{% else %}items-center{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="pd-frame pd-torn-bottom relative overflow-hidden p-2 {% if size == "xs" %}max-w-[200px] w-full{% elif size == "sm" %}max-w-[400px] w-full{% elif size == "md" %}max-w-[600px] w-full{% elif size == "lg" %}max-w-[800px] w-full{% else %}w-full{% endif %}">
|
||||||
|
{% if src %}{% img src alt=alt class="block h-auto w-full rounded-sm object-cover" layout=size %}{% else %}<div class="flex h-48 w-full items-center justify-center pd-hand text-2xl text-muted-foreground">Add an image</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if caption %}<figcaption class="pd-hand mt-3 text-center text-xl text-accent">{{ caption }}</figcaption>{% endif %}
|
||||||
|
</figure>
|
||||||
|
{% endwith %}
|
||||||
15
templates/overrides/pastel-dream/logo-strip.ninjatpl
Normal file
15
templates/overrides/pastel-dream/logo-strip.ninjatpl
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<section class="py-12 md:py-16 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-6xl px-4">
|
||||||
|
{% if heading %}<p class="pd-hand text-center text-2xl text-accent">{{ heading }}</p>{% endif %}
|
||||||
|
<div class="mt-8 {% if marquee %}group relative overflow-hidden [mask-image:linear-gradient(to_right,transparent,black_8%,black_92%,transparent)]{% endif %}">
|
||||||
|
<div class="flex flex-wrap items-center justify-center gap-x-10 gap-y-6 {% if marquee %}flex-nowrap [animation:logo-marquee_30s_linear_infinite] motion-reduce:[animation:none] group-hover:[animation-play-state:paused]{% endif %}">
|
||||||
|
{% for logo in logos %}
|
||||||
|
{% if logo.url %}<a href="{{ logo.url }}" class="block shrink-0 opacity-80 transition hover:opacity-100">{% endif %}
|
||||||
|
<span class="inline-flex h-8 items-center{% if grayscale %} grayscale transition-[filter] hover:grayscale-0{% endif %}">{% img logo.image alt=logo.alt class="h-8 w-auto object-contain" %}</span>
|
||||||
|
{% if logo.url %}</a>{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% if marquee %}<style>@keyframes logo-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}</style>{% endif %}
|
||||||
31
templates/overrides/pastel-dream/menu-list.ninjatpl
Normal file
31
templates/overrides/pastel-dream/menu-list.ninjatpl
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-5xl px-4">
|
||||||
|
{% if heading %}<h2 class="pd-display text-3xl text-primary md:text-4xl"><span class="pd-doodle-underline">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if intro %}<p class="pd-body mt-3 max-w-2xl text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
|
||||||
|
<div class="pd-chalkboard mt-10 p-6 md:p-10">
|
||||||
|
<div class="grid grid-cols-1 gap-x-12 gap-y-10 {% if columns == 2 %}md:grid-cols-2{% endif %}">
|
||||||
|
{% for section in sections %}
|
||||||
|
<div class="menu-section">
|
||||||
|
{% if section.title %}<h3 class="pd-hand border-b pd-chalk-rule pb-2 text-3xl">{{ section.title }}</h3>{% endif %}
|
||||||
|
{% if section.note %}<p class="pd-body pd-chalk-muted mt-2 text-sm">{{ section.note }}</p>{% endif %}
|
||||||
|
<ul class="mt-4 space-y-5">
|
||||||
|
{% for item in section.items %}
|
||||||
|
<li class="flex items-baseline gap-4">
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
|
||||||
|
<span class="pd-display text-lg">{{ item.name }}</span>
|
||||||
|
{% for badge in item.badges %}{% if badge.label %}<span class="pd-hand rounded-full border pd-chalk-rule px-2 py-0.5 text-sm pd-chalk-muted">{{ badge.label }}</span>{% endif %}{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% if item.description %}<p class="pd-body pd-chalk-muted mt-0.5 text-sm">{{ item.description }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if item.price %}<span class="pd-mono shrink-0 tabular-nums">{% if currency %}{{ currency }}{% endif %}{{ item.price }}</span>{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% if footnote %}<p class="pd-body pd-chalk-muted mt-10 text-xs">{{ footnote }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
74
templates/overrides/pastel-dream/navbar.ninjatpl
Normal file
74
templates/overrides/pastel-dream/navbar.ninjatpl
Normal file
@ -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) %}<a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}{% if item.title %} title="{{ item.title }}"{% endif %} class="pd-body px-3 py-2 text-sm font-medium text-foreground/80 transition-colors hover:text-accent {{ item.css_class }}">{{ item.label }}</a>{% endmacro %}
|
||||||
|
{% macro dropdown(item) %}<div class="relative group"><button type="button" class="pd-body inline-flex items-center gap-1 px-3 py-2 text-sm font-medium text-foreground/80 transition-colors hover:text-accent">{{ item.label }}<svg class="h-4 w-4 transition-transform group-hover:rotate-180" aria-hidden="true"><use href="/icons/lucide.svg#chevron-down"></use></svg></button><div class="absolute left-0 z-50 mt-1 w-56 invisible opacity-0 transition-all duration-200 group-hover:visible group-hover:opacity-100"><div class="pd-card py-2 shadow-lg">{% for child in item.children %}{% if child.item_type == "header" %}<div class="pd-hand px-4 py-2 text-base text-accent">{{ child.label }}</div>{% else %}<a href="{{ child.url|default:'#' }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="pd-body block px-4 py-2 text-sm text-foreground transition-colors hover:bg-muted {{ child.css_class }}">{{ child.label }}</a>{% endif %}{% endfor %}</div></div></div>{% endmacro %}
|
||||||
|
{% macro brand(size) %}<a href="/" class="flex flex-shrink-0 items-center gap-2">{% if logoType == "text" and logoText.text %}<span class="pd-display text-2xl text-primary {{ logoText.color }}">{{ logoText.text }}</span>{% elif logo %}<img src="{{ logo }}" alt="{{ alt|default:companyName }}" class="{{ size }} w-auto"{% if logoMaxWidth or logoMaxHeight %} style="{% if logoMaxWidth %}max-width:{{ logoMaxWidth }};{% endif %}{% if logoMaxHeight %}max-height:{{ logoMaxHeight }};{% endif %}"{% endif %}/>{% elif companyName %}<span class="pd-display text-2xl text-primary">{{ companyName }}</span>{% endif %}</a>{% endmacro %}
|
||||||
|
{% macro ctabtn(extra) %}{% if cta_text %}<a href="{{ cta_url|default:'#' }}" class="pd-tag text-sm {{ extra }}">{{ cta_text }}</a>{% endif %}{% endmacro %}
|
||||||
|
<nav data-bn-navbar data-bn-sticky="{{ sb }}" class="bn-navbar w-full {% if sb == "sticky" or sb == "shrink-on-scroll" %}sticky top-0 z-40 {% endif %}bg-background/95 backdrop-blur border-b border-dashed border-border {{ class }}">
|
||||||
|
{% if showUtilityBar %}<div class="w-full border-b border-border/60 bg-muted/60 text-xs text-muted-foreground {% if not showOnMobile %}hidden sm:block{% endif %}"><div class="mx-auto flex h-8 max-w-7xl items-center justify-between px-4 sm:px-6 lg:px-8"><div class="pd-body">{{ utilityBarText|safe }}</div><div class="flex items-center gap-4">{% if utilityBarPhone %}<a href="tel:{{ utilityBarPhone }}" class="hover:text-foreground">{{ utilityBarPhone }}</a>{% endif %}{% if utilityBarEmail %}<a href="mailto:{{ utilityBarEmail }}" class="hover:text-foreground">{{ utilityBarEmail }}</a>{% endif %}</div></div></div>{% endif %}
|
||||||
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="bn-navbar-row flex h-16 items-center justify-between">
|
||||||
|
{% if nv == "centered-logo" %}
|
||||||
|
<div class="hidden flex-1 items-center gap-1 md:flex">{% for item in menu.items %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endfor %}</div>
|
||||||
|
<div class="flex items-center justify-center">{{ brand("h-9") }}</div>
|
||||||
|
<div class="hidden flex-1 items-center justify-end gap-3 md:flex">{{ ctabtn("") }}</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="flex items-center">{{ brand("h-9") }}</div>
|
||||||
|
<div class="hidden items-center gap-1 md:flex">{% for item in menu.items %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endfor %}{{ ctabtn("ml-3") }}</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="flex items-center md:hidden">
|
||||||
|
<label for="bn-navbar-toggle" data-bn-nav-open aria-controls="bn-navbar-drawer" aria-expanded="false" class="inline-flex cursor-pointer items-center justify-center rounded-md p-2 transition-colors hover:bg-muted"><span class="sr-only">Open menu</span><svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="checkbox" id="bn-navbar-toggle" class="bn-navbar-toggle" aria-hidden="true"/>
|
||||||
|
<label for="bn-navbar-toggle" class="bn-navbar-overlay fixed inset-0 z-40 bg-black/50 md:hidden" aria-hidden="true"></label>
|
||||||
|
<div id="bn-navbar-drawer" class="bn-navbar-drawer fixed right-0 top-0 z-50 flex h-full w-80 max-w-[85vw] flex-col bg-background text-foreground shadow-xl md:hidden" role="dialog" aria-modal="true" aria-label="Site menu">
|
||||||
|
<div class="flex items-center justify-between border-b border-dashed border-border p-4">{{ brand("h-8") }}<label for="bn-navbar-toggle" data-bn-nav-close class="cursor-pointer rounded-md p-2 text-muted-foreground hover:bg-muted hover:text-foreground"><span class="sr-only">Close menu</span><svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg></label></div>
|
||||||
|
<nav class="flex-1 overflow-y-auto py-4" aria-label="Mobile">{% for item in menu.items %}{% if item.children %}<details class="group/acc"><summary class="pd-body flex cursor-pointer list-none items-center justify-between px-6 py-3 text-base font-medium transition-colors hover:bg-muted">{{ item.label }}<svg class="h-4 w-4 transition-transform group-open/acc:rotate-180" aria-hidden="true"><use href="/icons/lucide.svg#chevron-down"></use></svg></summary><div class="pb-2 pl-8">{% for child in item.children %}{% if child.item_type != "header" %}<a href="{{ child.url|default:'#' }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="pd-body block px-2 py-2 text-sm text-muted-foreground transition-colors hover:text-foreground">{{ child.label }}</a>{% endif %}{% endfor %}</div></details>{% else %}<a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="pd-body block px-6 py-3 text-base font-medium transition-colors hover:bg-muted">{{ item.label }}</a>{% endif %}{% endfor %}</nav>
|
||||||
|
{% if cta_text %}<div class="border-t border-dashed border-border p-4"><a href="{{ cta_url|default:'#' }}" class="pd-tag w-full justify-center">{{ cta_text }}</a></div>{% endif %}
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.bn-navbar-toggle{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;}
|
||||||
|
.bn-navbar-drawer{transform:translateX(100%);transition:transform .3s ease;}
|
||||||
|
.bn-navbar-overlay{opacity:0;pointer-events:none;transition:opacity .3s ease;}
|
||||||
|
.bn-navbar-toggle:checked ~ .bn-navbar-drawer{transform:translateX(0);}
|
||||||
|
.bn-navbar-toggle:checked ~ .bn-navbar-overlay{opacity:1;pointer-events:auto;}
|
||||||
|
.bn-navbar-drawer details > summary::-webkit-details-marker{display:none;}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
if(window.__bnPastelNavInit)return;window.__bnPastelNavInit=1;
|
||||||
|
function ready(fn){if(document.readyState!='loading')fn();else document.addEventListener('DOMContentLoaded',fn);}
|
||||||
|
ready(function(){
|
||||||
|
document.querySelectorAll('[data-bn-navbar]').forEach(function(nav){
|
||||||
|
var toggle=nav.querySelector('.bn-navbar-toggle');
|
||||||
|
var drawer=nav.querySelector('.bn-navbar-drawer');
|
||||||
|
var opener=nav.querySelector('[data-bn-nav-open]');
|
||||||
|
if(!toggle||!drawer)return;
|
||||||
|
function focusables(){return drawer.querySelectorAll('a[href],button,label[for],input,summary,[tabindex]:not([tabindex="-1"])');}
|
||||||
|
toggle.addEventListener('change',function(){
|
||||||
|
var open=toggle.checked;
|
||||||
|
document.documentElement.style.overflow=open?'hidden':'';
|
||||||
|
document.body.style.overflow=open?'hidden':'';
|
||||||
|
if(opener)opener.setAttribute('aria-expanded',open?'true':'false');
|
||||||
|
if(open){var f=focusables();if(f.length)f[0].focus();}else if(opener&&opener.focus)opener.focus();
|
||||||
|
});
|
||||||
|
drawer.addEventListener('keydown',function(e){
|
||||||
|
if(e.key==='Escape'){toggle.checked=false;toggle.dispatchEvent(new Event('change'));return;}
|
||||||
|
if(e.key!=='Tab')return;
|
||||||
|
var f=focusables();if(!f.length)return;
|
||||||
|
var first=f[0],last=f[f.length-1];
|
||||||
|
if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus();}
|
||||||
|
else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus();}
|
||||||
|
});
|
||||||
|
drawer.querySelectorAll('a[href]').forEach(function(a){a.addEventListener('click',function(){toggle.checked=false;toggle.dispatchEvent(new Event('change'));});});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
<section class="py-12 bg-background text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-3xl px-4">
|
||||||
|
<div class="pd-torn-top pd-torn-bottom rounded-2xl {% if layout == "inline" %}bg-primary px-6 py-10 text-primary-foreground md:px-12{% else %}pd-card px-6 py-10 md:px-10{% endif %} text-center">
|
||||||
|
{% if title %}<h2 class="pd-display text-2xl md:text-3xl {% if layout == "inline" %}text-primary-foreground{% else %}text-primary{% endif %}">{{ title }}</h2>{% endif %}
|
||||||
|
{% if description %}<p class="pd-body mx-auto mt-3 max-w-xl {% if layout == "inline" %}text-primary-foreground/80{% else %}text-muted-foreground{% endif %}">{{ description }}</p>{% endif %}
|
||||||
|
<div class="mx-auto mt-6 max-w-md">{% signup_form list=listSlug source=source submit=submitText success_message=successMessage placeholder=placeholder %}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
22
templates/overrides/pastel-dream/page-suggestions.ninjatpl
Normal file
22
templates/overrides/pastel-dream/page-suggestions.ninjatpl
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{# pastel-dream page-suggestions — themed 404 helper. Suggestions (when the platform
|
||||||
|
provides them) list {url|slug, title, excerpt}; otherwise a friendly
|
||||||
|
hand-drawn "lost your way" panel with a route home. #}
|
||||||
|
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-3xl px-4 text-center">
|
||||||
|
<p class="pd-hand text-3xl text-accent">Lost your way?</p>
|
||||||
|
<h2 class="pd-display mt-1 text-3xl text-primary md:text-4xl">{{ title|default:"Were you looking for..." }}</h2>
|
||||||
|
{% if suggestions %}
|
||||||
|
<ul class="mt-8 space-y-3 text-left">
|
||||||
|
{% for s in suggestions %}
|
||||||
|
<li class="pd-card p-4">
|
||||||
|
<a href="{{ s.url|default:s.slug }}" class="pd-display text-lg text-primary transition-colors hover:text-accent">{{ s.title }}</a>
|
||||||
|
{% if showDescription and s.excerpt %}<p class="pd-body mt-1 text-sm text-muted-foreground">{{ s.excerpt }}</p>{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<p class="pd-body mx-auto mt-4 max-w-xl text-muted-foreground">{{ emptyMessage|default:"That page has wandered off. Head back to the counter and start again." }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<a href="/" class="pd-tag mt-8"><svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#home"></use></svg>Back home</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
{# pastel-dream password-reset-form — request panel in soft pastel chrome. Endpoint matches
|
||||||
|
the platform (/api/auth/request-password-reset, #reset-message). #}
|
||||||
|
<div class="password-reset-form pd-card mx-auto max-w-md p-6">
|
||||||
|
<div id="reset-message"></div>
|
||||||
|
<div id="request-reset-panel">
|
||||||
|
<h2 class="pd-display text-2xl text-primary"><span class="pd-doodle-underline">Reset password</span></h2>
|
||||||
|
<p class="pd-body mt-2 text-muted-foreground">Enter your email address and we will send you a link to reset your password.</p>
|
||||||
|
<form hx-post="/api/auth/request-password-reset" hx-target="#reset-message" hx-swap="innerHTML" class="mt-4">
|
||||||
|
<div class="mb-4"><label class="pd-hand mb-1 block text-lg text-accent">Email</label><input type="email" name="email" required class="pd-body w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"></div>
|
||||||
|
<button type="submit" class="pd-tag w-full justify-center" hx-disabled-elt="this">Send reset link</button>
|
||||||
|
</form>
|
||||||
|
<p class="pd-body mt-4 text-center text-sm"><a href="/login" class="text-accent underline decoration-dashed">Back to login</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
15
templates/overrides/pastel-dream/popular-posts.ninjatpl
Normal file
15
templates/overrides/pastel-dream/popular-posts.ninjatpl
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<section class="bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
{% if heading %}<h2 class="pd-hand mb-4 text-2xl text-accent">{{ heading }}</h2>{% endif %}
|
||||||
|
<ol class="space-y-4">
|
||||||
|
{% for post in posts %}
|
||||||
|
<li class="flex items-start gap-3">
|
||||||
|
{% if showRank %}<span class="pd-display mt-0.5 text-2xl text-accent/60">{{ forloop.Counter }}</span>{% endif %}
|
||||||
|
{% if showThumbnail and post.featured_image_url %}<a href="{{ post.url }}" class="pd-frame h-12 w-12 flex-shrink-0 overflow-hidden rounded-sm p-0.5">{% img post.featured_image_url alt=post.title class="h-full w-full rounded-sm object-cover" %}</a>{% endif %}
|
||||||
|
<div class="min-w-0">
|
||||||
|
<h3 class="pd-display text-sm text-primary"><a href="{{ post.url }}" class="transition-colors hover:text-accent">{{ post.title }}</a></h3>
|
||||||
|
{% if post.published_at %}<p class="pd-body mt-0.5 text-xs text-muted-foreground">{{ post.published_at|date:"M j, Y" }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
19
templates/overrides/pastel-dream/post-hero.ninjatpl
Normal file
19
templates/overrides/pastel-dream/post-hero.ninjatpl
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{% set center = align != "left" %}
|
||||||
|
{% if post.featured_image_url %}
|
||||||
|
<section class="hero relative flex min-h-[50vh] flex-col justify-end overflow-hidden bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="absolute inset-0 z-0">{% img post.featured_image_url alt=post.featured_image_alt class="h-full w-full object-cover" layout="hero" %}<div class="absolute inset-0 bg-black/60"></div></div>
|
||||||
|
<div class="relative z-10 mx-auto w-full max-w-4xl px-4 py-12 sm:px-6 lg:px-8 {% if center %}text-center{% endif %}">
|
||||||
|
<h1 class="pd-display text-3xl leading-tight text-white sm:text-4xl lg:text-5xl">{{ post.title }}</h1>
|
||||||
|
{% if showExcerpt and post.excerpt %}<p class="pd-body mt-4 max-w-2xl text-lg text-white/80{% if center %} mx-auto{% endif %}">{{ post.excerpt }}</p>{% endif %}
|
||||||
|
{% if showMeta %}<div class="pd-hand mt-6 flex flex-wrap items-center gap-x-3 gap-y-1 text-lg text-white/90{% if center %} justify-center{% endif %}">{% if post.author_name %}<span>{{ post.author_name }}</span>{% endif %}{% if post.published_at %}<span>{{ post.published_at|date:"F j, Y" }}</span>{% endif %}{% if post.reading_time %}<span>{{ post.reading_time }} min</span>{% endif %}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% else %}
|
||||||
|
<section class="bg-background py-14 text-foreground md:py-20{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto w-full max-w-3xl px-4 {% if center %}text-center{% endif %}">
|
||||||
|
<h1 class="pd-display text-3xl leading-tight text-primary sm:text-4xl lg:text-5xl">{{ post.title }}</h1>
|
||||||
|
{% if showExcerpt and post.excerpt %}<p class="pd-body mt-4 text-lg text-muted-foreground{% if center %} mx-auto max-w-2xl{% endif %}">{{ post.excerpt }}</p>{% endif %}
|
||||||
|
{% if showMeta %}<div class="pd-hand mt-6 flex flex-wrap items-center gap-x-3 gap-y-1 text-lg text-accent{% if center %} justify-center{% endif %}">{% if post.author_name %}<span>{{ post.author_name }}</span>{% endif %}{% if post.published_at %}<span class="text-muted-foreground">{{ post.published_at|date:"F j, Y" }}</span>{% endif %}{% if post.reading_time %}<span class="text-muted-foreground">{{ post.reading_time }} min</span>{% endif %}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
8
templates/overrides/pastel-dream/post-metadata.ninjatpl
Normal file
8
templates/overrides/pastel-dream/post-metadata.ninjatpl
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{% if post %}
|
||||||
|
<div class="pd-body flex flex-wrap items-center gap-x-4 gap-y-2 text-sm text-muted-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
{% if showAuthor and post.author_name %}<span class="flex items-center gap-2">{% if showAvatar and post.author_avatar %}{% img post.author_avatar alt=post.author_name class="h-7 w-7 rounded-full object-cover" %}{% endif %}<span class="pd-display text-foreground">{{ post.author_name }}</span></span>{% endif %}
|
||||||
|
{% if showDate and post.published_at %}<time datetime="{{ post.published_at|date:"Y-m-d" }}">{{ post.published_at|date:"F j, Y" }}</time>{% endif %}
|
||||||
|
{% if showReadingTime and post.reading_time %}<span>{{ post.reading_time }} min read</span>{% endif %}
|
||||||
|
{% if post.categories %}<span class="flex flex-wrap gap-1.5">{% for cat in post.categories %}<a href="{{ cat.url|default:'#' }}" class="rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground transition-colors hover:text-accent">{{ cat.name }}</a>{% endfor %}</span>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
24
templates/overrides/pastel-dream/pricing.ninjatpl
Normal file
24
templates/overrides/pastel-dream/pricing.ninjatpl
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-6xl px-4">
|
||||||
|
{% if heading %}<h2 class="pd-display text-center text-3xl text-primary md:text-4xl">{{ heading }}</h2>{% endif %}
|
||||||
|
{% if intro %}<p class="pd-body mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
|
||||||
|
<div class="mt-12 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{% for tier in tiers %}
|
||||||
|
<div class="pd-card relative flex flex-col p-6 {% if tier.highlighted %}ring-2 ring-accent{% endif %}">
|
||||||
|
{% if tier.badge %}<span class="pd-hand absolute -top-4 left-1/2 -translate-x-1/2 rotate-[-3deg] rounded-full bg-accent px-4 py-1 text-lg text-accent-foreground shadow-sm">{{ tier.badge }}</span>{% endif %}
|
||||||
|
<h3 class="pd-display text-xl text-primary">{{ tier.name }}</h3>
|
||||||
|
<p class="mt-4 flex items-baseline gap-1">
|
||||||
|
<span class="pd-display text-4xl text-accent">{% if currency %}{{ currency }}{% else %}${% endif %}{{ tier.price }}</span>
|
||||||
|
{% if tier.period %}<span class="pd-body text-sm text-muted-foreground">{{ tier.period }}</span>{% endif %}
|
||||||
|
</p>
|
||||||
|
{% if tier.description %}<p class="pd-body mt-3 text-sm text-muted-foreground">{{ tier.description }}</p>{% endif %}
|
||||||
|
{% if tier.features %}<ul class="mt-6 flex-1 space-y-3 text-sm">
|
||||||
|
{% for feature in tier.features %}<li class="pd-body flex items-start gap-2"><svg class="mt-0.5 h-4 w-4 shrink-0 text-accent" aria-hidden="true"><use href="/icons/lucide.svg#check"></use></svg><span>{{ feature }}</span></li>{% endfor %}
|
||||||
|
</ul>{% endif %}
|
||||||
|
{% if tier.ctaUrl and tier.ctaLabel %}<a href="{{ tier.ctaUrl }}" class="pd-tag mt-8 w-full justify-center {% if not tier.highlighted %}pd-tag--ghost{% endif %}">{{ tier.ctaLabel }}</a>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% if billingNote %}<p class="pd-body mt-8 text-center text-sm text-muted-foreground">{{ billingNote }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
6
templates/overrides/pastel-dream/quote.ninjatpl
Normal file
6
templates/overrides/pastel-dream/quote.ninjatpl
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{% with style=style|default:"block" align=align|default:"left" %}
|
||||||
|
<figure class="bn-quote my-8 {% if style == "inline-card" %}pd-card p-6 md:p-8{% elif style == "pull" %}border-l-4 border-accent pl-6 md:pl-8 py-2{% else %}border-l-2 border-dashed border-border pl-5 py-1{% endif %}{% if align == "center" %} text-center{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||||
|
<blockquote{% if cite %} cite="{{ cite }}"{% endif %} class="pd-display {% if style == "pull" %}text-2xl md:text-3xl leading-snug{% else %}text-xl md:text-2xl leading-relaxed{% endif %} text-primary">{{ quote|safe }}</blockquote>
|
||||||
|
{% if attribution or role or cite %}<figcaption class="pd-hand mt-4 text-xl text-accent{% if align == "center" %} flex flex-col items-center{% endif %}">{% if attribution %}<span>{{ attribution }}</span>{% endif %}{% if role %}<span class="{% if attribution %}block {% endif %}text-lg text-muted-foreground">{{ role }}</span>{% endif %}{% if cite %}<a href="{{ cite }}" class="{% if attribution or role %}mt-1 {% endif %}pd-body inline-block text-sm underline decoration-dashed underline-offset-2 hover:text-foreground">Source</a>{% endif %}</figcaption>{% endif %}
|
||||||
|
</figure>
|
||||||
|
{% endwith %}
|
||||||
16
templates/overrides/pastel-dream/related-posts.ninjatpl
Normal file
16
templates/overrides/pastel-dream/related-posts.ninjatpl
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{% if posts %}
|
||||||
|
<section class="border-t border-dashed border-border py-12 md:py-16 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-6xl px-4">
|
||||||
|
<h2 class="pd-display mb-8 text-2xl text-primary">{{ heading|default:"More from the counter" }}</h2>
|
||||||
|
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 {% if columns == 2 %}lg:grid-cols-2{% else %}lg:grid-cols-3{% endif %}">
|
||||||
|
{% for post in posts %}
|
||||||
|
<article class="group flex flex-col">
|
||||||
|
{% if post.featured_image_url %}<a href="{{ post.url }}" class="pd-frame mb-3 block aspect-video overflow-hidden rounded-sm p-1.5">{% img post.featured_image_url alt=post.title class="h-full w-full rounded-sm object-cover transition-transform duration-300 group-hover:scale-105" %}</a>{% endif %}
|
||||||
|
<h3 class="pd-display text-base text-primary"><a href="{{ post.url }}" class="transition-colors hover:text-accent">{{ post.title }}</a></h3>
|
||||||
|
{% if post.published_at %}<p class="pd-hand mt-1 text-base text-accent">{{ post.published_at|date:"M j, Y" }}</p>{% endif %}
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
13
templates/overrides/pastel-dream/rich-section.ninjatpl
Normal file
13
templates/overrides/pastel-dream/rich-section.ninjatpl
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<section class="py-16 md:py-24 {% if background == "muted" %}bg-muted text-foreground{% elif background == "card" %}bg-card text-card-foreground{% else %}bg-background text-foreground{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto grid max-w-6xl items-center gap-10 px-4 md:grid-cols-2 md:gap-16">
|
||||||
|
<div class="{% if mediaPosition == "left" %}md:order-2{% else %}md:order-1{% endif %}">
|
||||||
|
{% if eyebrow %}<p class="pd-hand text-2xl text-accent">{{ eyebrow }}</p>{% endif %}
|
||||||
|
{% if heading %}<h2 class="pd-display mt-1 text-3xl text-primary md:text-4xl"><span class="pd-doodle-underline">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if body %}<div class="pd-body mt-4 space-y-4 text-lg text-muted-foreground [&_a]:text-accent [&_a]:underline [&_a]:decoration-dashed [&_strong]:text-primary">{{ body|safe }}</div>{% endif %}
|
||||||
|
{% if ctaUrl and ctaLabel %}<a href="{{ ctaUrl }}" class="pd-tag mt-6 text-base px-5 py-3">{{ ctaLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="{% if mediaPosition == "left" %}md:order-1{% else %}md:order-2{% endif %}">
|
||||||
|
{% if media %}<div class="pd-frame pd-torn-bottom overflow-hidden rounded-sm p-2">{% img media alt=mediaAlt class="h-auto w-full rounded-sm object-cover" %}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
1
templates/overrides/pastel-dream/rich-text.ninjatpl
Normal file
1
templates/overrides/pastel-dream/rich-text.ninjatpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
{% if html %}<article class="rich-text pd-body prose prose-lg max-w-none text-foreground [&_h1]:pd-display [&_h2]:pd-display [&_h3]:pd-display [&_h1]:text-primary [&_h2]:text-primary [&_h3]:text-primary [&_a]:text-accent [&_a]:decoration-dashed [&_blockquote]:border-l-2 [&_blockquote]:border-accent [&_blockquote]:italic">{{ html|safe }}</article>{% endif %}
|
||||||
12
templates/overrides/pastel-dream/sidebar-nav.ninjatpl
Normal file
12
templates/overrides/pastel-dream/sidebar-nav.ninjatpl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{% set compact = template == "compact" %}
|
||||||
|
<nav class="flex-1 {% if compact %}w-56 py-4 pr-3{% else %}w-64 py-6 pr-4{% endif %}{% if showBorder %} border-r border-dashed border-border{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||||
|
{% if title %}<h3 class="pd-hand {% if compact %}px-2 mb-3 text-lg{% else %}px-3 mb-4 text-xl{% endif %} text-accent">{{ title }}</h3>{% endif %}
|
||||||
|
<ul class="{% if compact %}space-y-0.5{% else %}space-y-1{% endif %}">
|
||||||
|
{% for item in menu.items %}
|
||||||
|
<li><a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}{% if item.title %} title="{{ item.title }}"{% endif %} data-bn-sidebar-link class="pd-body flex items-center rounded-md text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground {% if compact %}px-2 py-1.5{% else %}px-3 py-2{% endif %} {{ item.css_class }}">{% if item.icon %}<svg class="mr-2 h-4 w-4" aria-hidden="true"><use href="/icons/{{ item.icon|split:":"|first }}.svg#{{ item.icon|split:":"|last }}"></use></svg>{% endif %}{{ item.label }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{% if highlightCurrent %}<script>
|
||||||
|
(function(){var p=window.location.pathname;document.querySelectorAll('[data-bn-sidebar-link]').forEach(function(a){if(a.getAttribute('href')===p){a.classList.remove('text-muted-foreground');a.classList.add('text-foreground','bg-muted','font-medium');}});})();
|
||||||
|
</script>{% endif %}
|
||||||
13
templates/overrides/pastel-dream/social-links.ninjatpl
Normal file
13
templates/overrides/pastel-dream/social-links.ninjatpl
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<div class="social-links{% if class %} {{ class }}{% endif %}">
|
||||||
|
{% if heading %}<p class="pd-hand mb-3 text-xl text-accent">{{ heading }}</p>{% endif %}
|
||||||
|
<ul class="flex {% if style == "stack" %}flex-col gap-2{% else %}flex-wrap items-center gap-3{% endif %}">
|
||||||
|
{% for link in links %}{% if link.url %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ link.url }}" target="_blank" rel="noopener" aria-label="{{ link.label|default:link.network }}" class="inline-flex items-center gap-2 rounded-full border border-dashed border-border text-foreground transition-colors hover:border-accent hover:text-accent {% if style == "stack" %}w-full px-4 py-2{% else %}p-2.5{% endif %}">
|
||||||
|
{% if link.network %}<svg class="h-5 w-5" aria-hidden="true"><use href="/icons/{{ link.network|split:":"|first }}.svg#{{ link.network|split:":"|last }}"></use></svg>{% endif %}
|
||||||
|
{% if style == "stack" and link.label %}<span class="pd-body text-sm font-medium">{{ link.label }}</span>{% endif %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
44
templates/overrides/pastel-dream/stats.ninjatpl
Normal file
44
templates/overrides/pastel-dream/stats.ninjatpl
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}"{% if countUp %} data-stats{% endif %}>
|
||||||
|
<div class="mx-auto max-w-6xl px-4">
|
||||||
|
{% if heading %}<h2 class="pd-display text-center text-3xl text-primary md:text-4xl">{{ heading }}</h2>{% endif %}
|
||||||
|
{% if intro %}<p class="pd-body mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
|
||||||
|
<dl class="mt-12 grid grid-cols-2 gap-8 {% if columns == 2 %}md:grid-cols-2{% elif columns == 3 %}md:grid-cols-3{% else %}md:grid-cols-4{% endif %}">
|
||||||
|
{% for item in items %}
|
||||||
|
<div class="text-center">
|
||||||
|
<dd class="pd-display text-4xl text-accent md:text-5xl">{% if item.prefix %}{{ item.prefix }}{% endif %}<span{% if countUp %} data-countup-value="{{ item.value }}"{% endif %}>{{ item.value }}</span>{% if item.suffix %}{{ item.suffix }}{% endif %}</dd>
|
||||||
|
{% if item.label %}<dt class="pd-hand mt-2 text-xl text-muted-foreground">{{ item.label }}</dt>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% if countUp %}<script>
|
||||||
|
(function(){
|
||||||
|
var sections = document.querySelectorAll('[data-stats]');
|
||||||
|
var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
function animate(section){
|
||||||
|
section.querySelectorAll('[data-countup-value]').forEach(function(node){
|
||||||
|
var raw = node.getAttribute('data-countup-value') || '';
|
||||||
|
var target = parseFloat(raw.replace(/[^0-9.\-]/g, ''));
|
||||||
|
if (isNaN(target)) return;
|
||||||
|
var decimals = (raw.split('.')[1] || '').length;
|
||||||
|
var start = null, dur = 1200;
|
||||||
|
function step(ts){
|
||||||
|
if (start === null) start = ts;
|
||||||
|
var p = Math.min((ts - start) / dur, 1);
|
||||||
|
var eased = 1 - Math.pow(1 - p, 3);
|
||||||
|
node.textContent = (target * eased).toFixed(decimals);
|
||||||
|
if (p < 1) requestAnimationFrame(step); else node.textContent = target.toFixed(decimals);
|
||||||
|
}
|
||||||
|
requestAnimationFrame(step);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sections.forEach(function(section){
|
||||||
|
if (reduce || !('IntersectionObserver' in window)) return;
|
||||||
|
var io = new IntersectionObserver(function(entries){
|
||||||
|
entries.forEach(function(e){ if (e.isIntersecting){ animate(section); io.disconnect(); } });
|
||||||
|
}, { threshold: 0.3 });
|
||||||
|
io.observe(section);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>{% endif %}
|
||||||
21
templates/overrides/pastel-dream/team.ninjatpl
Normal file
21
templates/overrides/pastel-dream/team.ninjatpl
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-6xl px-4">
|
||||||
|
{% if heading %}<h2 class="pd-display text-center text-3xl text-primary md:text-4xl"><span class="pd-doodle-underline">{{ heading }}</span></h2>{% endif %}
|
||||||
|
{% if intro %}<p class="pd-body mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
|
||||||
|
<div class="mt-12 {% if layout == "list" %}mx-auto max-w-3xl space-y-6{% elif layout == "single" %}mx-auto max-w-xl{% else %}grid grid-cols-1 gap-8 sm:grid-cols-2 {% if columns == 2 %}lg:grid-cols-2{% elif columns == 4 %}lg:grid-cols-4{% else %}lg:grid-cols-3{% endif %}{% endif %}">
|
||||||
|
{% for member in members %}
|
||||||
|
<div class="{% if layout == "list" %}pd-card flex items-start gap-5 p-5{% else %}text-center{% endif %}">
|
||||||
|
{% if member.photo %}<span class="{% if layout == "list" %}h-20 w-20{% else %}mx-auto h-28 w-28{% endif %} block shrink-0 overflow-hidden rounded-full bg-secondary ring-2 ring-border">{% img member.photo alt=member.name class="h-full w-full object-cover" %}</span>{% endif %}
|
||||||
|
<div class="{% if layout != "list" %}mt-4{% endif %}">
|
||||||
|
{% if member.name %}<h3 class="pd-display text-lg text-primary">{{ member.name }}</h3>{% endif %}
|
||||||
|
{% if member.role %}<p class="pd-hand text-lg text-accent">{{ member.role }}</p>{% endif %}
|
||||||
|
{% if member.bio %}<p class="pd-body mt-2 text-sm text-muted-foreground">{{ member.bio }}</p>{% endif %}
|
||||||
|
{% if member.socials %}<div class="mt-3 flex gap-3 {% if layout != "list" %}justify-center{% endif %}">
|
||||||
|
{% for social in member.socials %}{% if social.url %}<a href="{{ social.url }}" class="text-muted-foreground transition-colors hover:text-accent" aria-label="Social link"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/{{ social.icon|split:":"|first }}.svg#{{ social.icon|split:":"|last }}"></use></svg></a>{% endif %}{% endfor %}
|
||||||
|
</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
26
templates/overrides/pastel-dream/testimonial.ninjatpl
Normal file
26
templates/overrides/pastel-dream/testimonial.ninjatpl
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-6xl px-4">
|
||||||
|
{% if heading %}<h2 class="pd-display text-center text-3xl text-primary md:text-4xl"><span class="pd-doodle-underline">{{ heading }}</span></h2>{% endif %}
|
||||||
|
<div class="mt-12 {% if layout == "single" %}mx-auto max-w-3xl{% elif layout == "carousel" %}flex snap-x snap-mandatory gap-6 overflow-x-auto pb-4{% else %}grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3{% endif %}">
|
||||||
|
{% for q in quotes %}
|
||||||
|
<figure class="pd-card flex flex-col p-6{% if layout == "carousel" %} min-w-[18rem] max-w-sm shrink-0 snap-start{% endif %}">
|
||||||
|
{% if q.rating %}<div class="mb-3 flex gap-0.5" aria-label="Rated {{ q.rating }} out of 5">
|
||||||
|
<svg class="h-4 w-4 {% if q.rating >= 1 %}text-accent{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||||
|
<svg class="h-4 w-4 {% if q.rating >= 2 %}text-accent{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||||
|
<svg class="h-4 w-4 {% if q.rating >= 3 %}text-accent{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||||
|
<svg class="h-4 w-4 {% if q.rating >= 4 %}text-accent{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||||
|
<svg class="h-4 w-4 {% if q.rating >= 5 %}text-accent{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||||
|
</div>{% endif %}
|
||||||
|
<blockquote class="pd-body flex-1 text-lg leading-relaxed text-foreground">{% if q.quote %}“{{ q.quote }}”{% endif %}</blockquote>
|
||||||
|
<figcaption class="mt-5 flex items-center gap-3">
|
||||||
|
{% if q.avatar %}<span class="h-10 w-10 shrink-0 overflow-hidden rounded-full bg-secondary">{% img q.avatar alt=q.name class="h-full w-full object-cover" %}</span>{% endif %}
|
||||||
|
<span>
|
||||||
|
{% if q.name %}<span class="pd-display block text-base text-primary">{{ q.name }}</span>{% endif %}
|
||||||
|
{% if q.role %}<span class="pd-hand block text-lg text-accent">{{ q.role }}</span>{% endif %}
|
||||||
|
</span>
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
@ -1,3 +1 @@
|
|||||||
<div class="font-body prose max-w-none {{ class }}" style="color: hsl(var(--foreground)); line-height: 1.75;">
|
<div class="pd-body prose max-w-none flex-1 text-foreground [&_a]:text-accent [&_a]:underline [&_a]:decoration-dashed [&_strong]:text-primary {{ proseClass }}">{{ text|safe }}</div>
|
||||||
{{ text|safe }}
|
|
||||||
</div>
|
|
||||||
|
|||||||
21
templates/overrides/pastel-dream/timeline.ninjatpl
Normal file
21
templates/overrides/pastel-dream/timeline.ninjatpl
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||||
|
<div class="mx-auto max-w-4xl px-4">
|
||||||
|
{% if heading %}<h2 class="pd-display text-center text-3xl text-primary md:text-4xl">{{ heading }}</h2>{% endif %}
|
||||||
|
{% if intro %}<p class="pd-body mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
|
||||||
|
<ol class="mt-12 {% if orientation == "horizontal" %}grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-4{% else %}relative space-y-8 before:absolute before:left-4 before:top-2 before:h-[calc(100%-1rem)] before:w-px before:bg-border{% endif %}">
|
||||||
|
{% for step in steps %}
|
||||||
|
<li class="{% if orientation == "horizontal" %}text-center{% else %}relative pl-14{% endif %}">
|
||||||
|
<span class="pd-stamp pd-stamp-sm {% if orientation == "horizontal" %}mx-auto{% else %}absolute left-0 top-0 ring-4 ring-background{% endif %}">
|
||||||
|
{% if marker == "numbered" %}{{ forloop.counter }}{% elif marker == "dot" %}<span class="h-2 w-2 rounded-full bg-accent-foreground"></span>{% else %}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#croissant"></use></svg>{% endif %}
|
||||||
|
</span>
|
||||||
|
<div class="{% if orientation == "horizontal" %}mt-4{% else %}pt-1{% endif %}">
|
||||||
|
{% if step.label %}<p class="pd-hand text-lg text-accent">{{ step.label }}</p>{% endif %}
|
||||||
|
{% if step.title %}<h3 class="pd-display mt-1 text-lg text-primary">{{ step.title }}</h3>{% endif %}
|
||||||
|
{% if step.text %}<p class="pd-body mt-1 text-muted-foreground">{{ step.text }}</p>{% endif %}
|
||||||
|
{% if step.media %}<div class="pd-frame mt-3 overflow-hidden rounded-sm p-1.5">{% img step.media alt=step.title class="h-auto w-full rounded-sm object-cover" %}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
10
templates/overrides/pastel-dream/utility-bar.ninjatpl
Normal file
10
templates/overrides/pastel-dream/utility-bar.ninjatpl
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{# pastel-dream utility-bar — thin contact strip; left/right item lists {icon,label,link}. #}
|
||||||
|
{% set ubcls = "bg-muted text-muted-foreground border-b border-dashed border-border" %}
|
||||||
|
{% if variant == "primary" %}{% set ubcls = "bg-primary text-primary-foreground" %}{% elif variant == "dark" %}{% set ubcls = "bg-foreground text-background" %}{% endif %}
|
||||||
|
{% macro ubitem(it) %}{% if it.link %}<a href="{{ it.link }}" class="inline-flex items-center gap-1.5 transition-opacity hover:opacity-80">{% if it.icon %}<svg class="h-3.5 w-3.5" aria-hidden="true"><use href="/icons/{{ it.icon|split:":"|first }}.svg#{{ it.icon|split:":"|last }}"></use></svg>{% endif %}{% if it.label %}<span>{{ it.label }}</span>{% endif %}</a>{% else %}<span class="inline-flex items-center gap-1.5">{% if it.icon %}<svg class="h-3.5 w-3.5" aria-hidden="true"><use href="/icons/{{ it.icon|split:":"|first }}.svg#{{ it.icon|split:":"|last }}"></use></svg>{% endif %}{% if it.label %}<span>{{ it.label }}</span>{% endif %}</span>{% endif %}{% endmacro %}
|
||||||
|
<div class="pd-body w-full text-xs {% if not showOnMobile %}hidden sm:block{% endif %} {{ ubcls }} {{ class }}">
|
||||||
|
<div class="mx-auto flex h-9 max-w-7xl items-center justify-between gap-4 px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="flex flex-wrap items-center gap-4">{% for it in leftItems %}{{ ubitem(it) }}{% endfor %}</div>
|
||||||
|
<div class="flex flex-wrap items-center justify-end gap-4">{% for it in rightItems %}{{ ubitem(it) }}{% endfor %}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
22
templates/overrides/pastel-dream/video-embed.ninjatpl
Normal file
22
templates/overrides/pastel-dream/video-embed.ninjatpl
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{% with aspect=aspect|default:"16:9" %}<figure class="bn-video my-8{% if class %} {{ class }}{% endif %}"><div class="bn-video-frame pd-frame relative w-full overflow-hidden rounded-sm bg-secondary p-1.5 {% if aspect == "4:3" %}aspect-[4/3]{% elif aspect == "1:1" %}aspect-square{% elif aspect == "21:9" %}aspect-[21/9]{% else %}aspect-video{% endif %}" data-bn-video data-video-url="{{ url|default:"" }}" data-video-media="{% if media %}{{ media|mediaURL }}{% endif %}"><button type="button" data-bn-video-play class="group absolute inset-0 flex h-full w-full items-center justify-center focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring" aria-label="Play video{% if title %}: {{ title }}{% endif %}">{% if poster %}{% img poster alt=title|default:"" class="absolute inset-0 h-full w-full rounded-sm object-cover" %}{% endif %}<span class="absolute inset-0 bg-foreground/10 transition-colors group-hover:bg-foreground/20" aria-hidden="true"></span><span class="relative inline-flex h-16 w-16 items-center justify-center rounded-full bg-accent text-accent-foreground shadow-lg transition-transform group-hover:scale-105 motion-reduce:transform-none"><svg class="h-7 w-7" aria-hidden="true"><use href="/icons/lucide.svg#play"></use></svg></span></button></div>{% if caption %}<figcaption class="pd-hand mt-3 text-center text-xl text-accent">{{ caption }}</figcaption>{% endif %}</figure><script>
|
||||||
|
(function(){
|
||||||
|
if(window.__bnVideoFacade)return;window.__bnVideoFacade=true;
|
||||||
|
function yt(u){var m=u.match(/(?:youtube\.com\/(?:watch\?v=|embed\/|shorts\/)|youtu\.be\/)([\w-]{6,})/);return m?m[1]:null;}
|
||||||
|
function vim(u){var m=u.match(/vimeo\.com\/(?:video\/)?(\d+)/);return m?m[1]:null;}
|
||||||
|
document.addEventListener('click',function(e){
|
||||||
|
var btn=e.target.closest('[data-bn-video-play]');if(!btn)return;
|
||||||
|
var frame=btn.closest('[data-bn-video]');if(!frame)return;
|
||||||
|
e.preventDefault();
|
||||||
|
var url=frame.getAttribute('data-video-url')||'',media=frame.getAttribute('data-video-media')||'',node;
|
||||||
|
var vstyle='position:absolute;inset:0;width:100%;height:100%;object-fit:contain;background:rgba(0,0,0,1);';
|
||||||
|
if(media){node=document.createElement('video');node.src=media;node.controls=true;node.autoplay=true;node.setAttribute('playsinline','');node.style.cssText=vstyle;}
|
||||||
|
else{var y=yt(url),v=vim(url),embed;
|
||||||
|
if(y)embed='https://www.youtube-nocookie.com/embed/'+y+'?autoplay=1&rel=0';
|
||||||
|
else if(v)embed='https://player.vimeo.com/video/'+v+'?autoplay=1';
|
||||||
|
if(embed){node=document.createElement('iframe');node.src=embed;node.allow='autoplay; fullscreen; picture-in-picture';node.setAttribute('allowfullscreen','');node.setAttribute('title',btn.getAttribute('aria-label')||'Video');node.style.cssText='position:absolute;inset:0;width:100%;height:100%;border:0;';}
|
||||||
|
else if(url){node=document.createElement('video');node.src=url;node.controls=true;node.autoplay=true;node.setAttribute('playsinline','');node.style.cssText=vstyle;}
|
||||||
|
}
|
||||||
|
if(node){btn.remove();frame.appendChild(node);}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>{% endwith %}
|
||||||
@ -1,21 +1,15 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||||
{{ head_html|safe }}
|
{{ head_html|safe }}
|
||||||
<body class="font-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
|
<body class="pd-paper pd-body bg-background text-foreground antialiased min-h-screen flex flex-col">
|
||||||
{{ admin_banner_html|safe }}
|
{{ admin_banner_html|safe }}
|
||||||
<header class="w-full">
|
<header class="w-full">
|
||||||
{{ slots.header|safe }}
|
<div class="max-w-3xl mx-auto px-4">{{ slots.header|safe }}</div>
|
||||||
</header>
|
</header>
|
||||||
<main class="flex-grow w-full max-w-2xl mx-auto px-4 py-16">
|
<main class="flex-grow max-w-2xl mx-auto w-full px-4 py-12 pd-dropcap">
|
||||||
{% if slots.main %}<article class="font-body prose max-w-none" style="line-height: 1.75; color: hsl(var(--foreground));">
|
{% if slots.main %}<article class="prose prose-lg max-w-none">{{ slots.main|safe }}</article>{% else %}<div class="py-20 text-center pd-body text-muted-foreground italic"><p>Write something honest here.</p></div>{% endif %}
|
||||||
{{ slots.main|safe }}
|
|
||||||
</article>{% else %}<div class="py-20 text-center" style="color: hsl(var(--muted-foreground));">
|
|
||||||
<p>This page is still resting. Add a block to begin.</p>
|
|
||||||
</div>{% endif %}
|
|
||||||
</main>
|
</main>
|
||||||
<footer class="w-full mt-auto">
|
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
|
||||||
{{ slots.footer|safe }}
|
|
||||||
</footer>
|
|
||||||
{{ body_end_html|safe }}
|
{{ body_end_html|safe }}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
19
templates/pastel-dream/auth.ninjatpl
Normal file
19
templates/pastel-dream/auth.ninjatpl
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||||
|
{{ head_html|safe }}
|
||||||
|
<body class="pd-paper pd-body bg-background text-foreground antialiased min-h-screen flex flex-col">
|
||||||
|
{{ admin_banner_html|safe }}
|
||||||
|
<main class="flex flex-grow w-full flex-col items-center justify-center px-4 py-12">
|
||||||
|
<div class="w-full max-w-md">
|
||||||
|
<a href="/" class="mb-6 flex items-center justify-center" aria-label="Home">
|
||||||
|
{% if site_settings.Logo %}<img src="{{ site_settings.Logo }}" alt="{{ site_settings.LogoAlt|default:site_settings.Title }}" class="h-10 w-auto">{% else %}<span class="pd-display text-3xl text-primary">{{ site_settings.Title|default:"Petal & Crumb" }}</span>{% endif %}
|
||||||
|
</a>
|
||||||
|
<div>
|
||||||
|
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="pd-card p-6 text-center pd-body text-muted-foreground"><p>No form assigned to this page.</p></div>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if slots.footer %}<div class="pd-body mt-6 text-center text-sm text-muted-foreground">{{ slots.footer|safe }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{{ body_end_html|safe }}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
15
templates/pastel-dream/blog-index.ninjatpl
Normal file
15
templates/pastel-dream/blog-index.ninjatpl
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||||
|
{{ head_html|safe }}
|
||||||
|
<body class="pd-paper pd-body bg-background text-foreground antialiased min-h-screen flex flex-col">
|
||||||
|
{{ admin_banner_html|safe }}
|
||||||
|
<header class="w-full">{{ slots.header|safe }}</header>
|
||||||
|
<main class="flex-grow w-full">
|
||||||
|
{% if title %}<div class="mx-auto max-w-6xl px-4 pt-12"><h1 class="pd-display text-4xl text-primary md:text-5xl"><span class="pd-doodle-underline">{{ title }}</span></h1>{% if page_meta.MetaDescription %}<p class="pd-body mt-3 max-w-2xl text-lg text-muted-foreground">{{ page_meta.MetaDescription }}</p>{% endif %}</div>{% endif %}
|
||||||
|
{% if slots.featured %}<section class="w-full">{{ slots.featured|safe }}</section>{% endif %}
|
||||||
|
{% if slots.main %}<section class="w-full">{{ slots.main|safe }}</section>{% else %}<div class="py-20 text-center pd-hand text-2xl text-muted-foreground"><p>No stories brewed yet.</p></div>{% endif %}
|
||||||
|
</main>
|
||||||
|
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
|
||||||
|
{{ body_end_html|safe }}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
13
templates/pastel-dream/contact.ninjatpl
Normal file
13
templates/pastel-dream/contact.ninjatpl
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||||
|
{{ head_html|safe }}
|
||||||
|
<body class="pd-paper pd-body bg-background text-foreground antialiased min-h-screen flex flex-col">
|
||||||
|
{{ admin_banner_html|safe }}
|
||||||
|
<header class="w-full">{{ slots.header|safe }}</header>
|
||||||
|
<main class="flex-grow w-full">
|
||||||
|
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center pd-hand text-2xl text-muted-foreground"><p>Add a contact form and your hours.</p></div>{% endif %}
|
||||||
|
</main>
|
||||||
|
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
|
||||||
|
{{ body_end_html|safe }}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -1,19 +1,13 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||||
{{ head_html|safe }}
|
{{ head_html|safe }}
|
||||||
<body class="font-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
|
<body class="pd-paper pd-body bg-background text-foreground antialiased min-h-screen flex flex-col">
|
||||||
{{ admin_banner_html|safe }}
|
{{ admin_banner_html|safe }}
|
||||||
<header class="w-full">
|
<header class="w-full">{{ slots.header|safe }}</header>
|
||||||
{{ slots.header|safe }}
|
<main class="flex-grow w-full">
|
||||||
</header>
|
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center pd-hand text-2xl text-muted-foreground"><p>Pour something in here.</p></div>{% endif %}
|
||||||
<main class="flex-grow w-full max-w-4xl mx-auto px-4 py-12">
|
|
||||||
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center font-body" style="color: hsl(var(--muted-foreground));">
|
|
||||||
<p>This page is still resting. Add a block to begin.</p>
|
|
||||||
</div>{% endif %}
|
|
||||||
</main>
|
</main>
|
||||||
<footer class="w-full mt-auto">
|
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
|
||||||
{{ slots.footer|safe }}
|
|
||||||
</footer>
|
|
||||||
{{ body_end_html|safe }}
|
{{ body_end_html|safe }}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,19 +1,13 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||||
{{ head_html|safe }}
|
{{ head_html|safe }}
|
||||||
<body class="font-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
|
<body class="pd-paper pd-body bg-background text-foreground antialiased min-h-screen flex flex-col">
|
||||||
{{ admin_banner_html|safe }}
|
{{ admin_banner_html|safe }}
|
||||||
<header class="w-full">
|
<header class="w-full">{{ slots.header|safe }}</header>
|
||||||
{{ slots.header|safe }}
|
|
||||||
</header>
|
|
||||||
<main class="flex-grow w-full">
|
<main class="flex-grow w-full">
|
||||||
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="max-w-4xl mx-auto py-20 px-4 text-center" style="color: hsl(var(--muted-foreground));">
|
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="max-w-3xl mx-auto py-20 px-4 text-center pd-body text-muted-foreground italic"><p>Add a gallery, a wide photo, a confession.</p></div>{% endif %}
|
||||||
<p>This page is still resting. Add a block to begin.</p>
|
|
||||||
</div>{% endif %}
|
|
||||||
</main>
|
</main>
|
||||||
<footer class="w-full mt-auto">
|
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
|
||||||
{{ slots.footer|safe }}
|
|
||||||
</footer>
|
|
||||||
{{ body_end_html|safe }}
|
{{ body_end_html|safe }}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,22 +1,19 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||||
{{ head_html|safe }}
|
{{ head_html|safe }}
|
||||||
<body class="font-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
|
<body class="pd-paper pd-body bg-background text-foreground antialiased min-h-screen flex flex-col">
|
||||||
{{ admin_banner_html|safe }}
|
{{ admin_banner_html|safe }}
|
||||||
|
<section class="w-full">{{ slots.hero|safe }}</section>
|
||||||
<section class="w-full">
|
<section class="w-full">
|
||||||
{{ slots.hero|safe }}
|
<div class="max-w-5xl mx-auto px-4">{{ slots.menu|safe }}</div>
|
||||||
</section>
|
</section>
|
||||||
<main class="flex-grow w-full">
|
|
||||||
{% if slots.main %}<div class="max-w-6xl mx-auto px-4 py-20">
|
|
||||||
{{ slots.main|safe }}
|
|
||||||
</div>{% endif %}
|
|
||||||
</main>
|
|
||||||
<section class="w-full">
|
<section class="w-full">
|
||||||
{{ slots.cta|safe }}
|
<div class="max-w-3xl mx-auto px-4 py-12">{{ slots.story|safe }}</div>
|
||||||
</section>
|
</section>
|
||||||
<footer class="w-full mt-auto">
|
<section class="w-full">
|
||||||
{{ slots.footer|safe }}
|
<div class="max-w-5xl mx-auto px-4">{{ slots.cta|safe }}</div>
|
||||||
</footer>
|
</section>
|
||||||
|
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
|
||||||
{{ body_end_html|safe }}
|
{{ body_end_html|safe }}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user