fix(pastel-dream): reliable layout via theme CSS + graceful empty media

Host Tailwind (v4, source(none)) does not scan installed .bnp theme
templates, so grid/aspect/gap/padding utilities used only in this theme
were silently dropped: custom persona blocks collapsed to a single
full-width column, media boxes rendered as thin strips, and p-5 loss
clipped the first glyph of every description. Move the theme's layout
primitives into css.input_css_append as plain CSS with explicit
media queries (mobile-first, collapse to 1 column) so the render no
longer depends on the JIT content scan.

- Add .pd-section / .pd-grid(--2/--3/--lg-2/--lg-4) / .pd-menu-cols /
  .pd-menu-list / .pd-media(--square) / .pd-card-body helpers to both
  manifest.yaml (served) and assets/style.css. Semantic tokens only.
- seasonal_specials + cake_gallery: reliable responsive grid, OMIT the
  media box when no image is set (no ballooning empty boxes / thin
  strips), real body padding.
- menu-list: honor columns=2 as a true two-up board; reliable item spacing.
- hours-location / feature-grid: reliable responsive grids.
- Tighten section rhythm (py-14/py-24 -> .pd-section) to remove dead bands.
- navbar drawer width pinned in its inline <style>; body overflow-x: clip
  guards against horizontal scroll on small screens.

No .reveal/scroll-reveal content-hiding in this theme (stats count-up
only, values render by default). No version bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Alex Dunmow 2026-07-05 17:26:33 +08:00
parent 8eaf3a68aa
commit e6348edd10
11 changed files with 108 additions and 24 deletions

View File

@ -32,6 +32,10 @@ body.pd-paper {
radial-gradient(50% 50% at 25% 90%, hsl(var(--primary) / 0.08) 0%, transparent 60%); radial-gradient(50% 50% at 25% 90%, hsl(var(--primary) / 0.08) 0%, transparent 60%);
background-attachment: fixed; background-attachment: fixed;
background-repeat: no-repeat; background-repeat: no-repeat;
/* Guard against horizontal scroll on small screens from any off-canvas
* chrome (mobile drawer). `clip` does not create a scroll container, so
* it will not break the sticky navbar the way `hidden` can. */
overflow-x: clip;
} }
/* --- Type roles --------------------------------------------------------- */ /* --- Type roles --------------------------------------------------------- */
@ -247,3 +251,43 @@ body.pd-paper {
animation: none; animation: none;
} }
} }
/* --- Layout helpers (theme-owned, semantic tokens only) -----------------
* Installed .bnp themes live outside the host Tailwind @source globs, so the
* host build does NOT compile grid / aspect-ratio / gap / padding utilities
* that appear only in this theme's own templates. The theme therefore owns
* those layout primitives here as plain CSS: mobile-first, collapsing to a
* single column on small screens, with explicit media queries so the render
* is reliable regardless of the JIT content scan. No colors layout only. */
.pd-section { padding-top: 2.75rem; padding-bottom: 2.75rem; }
@media (min-width: 768px) {
.pd-section { padding-top: 4rem; padding-bottom: 4rem; }
}
.pd-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2rem; }
@media (min-width: 640px) {
.pd-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
.pd-grid--lg-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.pd-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.pd-grid--lg-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
/* Two-up menu board: one column on phones, two side by side from md. */
.pd-menu-cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2.5rem 3rem; }
@media (min-width: 768px) {
.pd-menu-cols--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.pd-menu-list { margin-top: 1rem; }
.pd-menu-list > li + li { margin-top: 1.25rem; }
/* Media frames: a fixed aspect box so a photo fills gracefully. The EMPTY
* state is omitted in-template (no ballooning boxes when no photo is set). */
.pd-media { position: relative; width: 100%; overflow: hidden; aspect-ratio: 4 / 3; }
.pd-media--square { aspect-ratio: 1 / 1; }
.pd-media img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* Card body padding (host may not emit p-5 for theme templates). */
.pd-card-body { padding: 1.25rem 1.25rem 1.5rem; }
@media (min-width: 768px) { .pd-card-body { padding: 1.5rem; } }

View File

@ -1,4 +1,4 @@
<section data-block="pastel-dream:bakers_note" class="py-14 md:py-20 bg-background text-foreground{% if class %} {{ class }}{% endif %}"> <section data-block="pastel-dream:bakers_note" class="pd-section bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-3xl px-4"> <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="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 -right-16 -top-16 h-48 w-48 rounded-full bg-accent/15 pd-breathe" aria-hidden="true"></div>

View File

@ -1,17 +1,15 @@
<section data-block="pastel-dream:cake_gallery" class="py-14 md:py-20 bg-background text-foreground{% if class %} {{ class }}{% endif %}"> <section data-block="pastel-dream:cake_gallery" class="pd-section bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-6xl px-4"> <div class="mx-auto max-w-6xl px-4">
<div class="text-center"> <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 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 %} {% if intro %}<p class="pd-body mx-auto mt-3 max-w-2xl text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
</div> </div>
{% if cakes %} {% if cakes %}
<div class="mt-10 grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3"> <div class="mt-10 pd-grid pd-grid--2 pd-grid--3">
{% for cake in cakes %} {% for cake in cakes %}
<figure class="pd-frame overflow-hidden"> <figure class="pd-frame overflow-hidden flex flex-col">
<div class="pd-torn-bottom aspect-square w-full overflow-hidden bg-secondary"> {% if cake.image %}<div class="pd-media pd-media--square pd-torn-bottom bg-secondary">{% img cake.image alt=cake.name|default:"Cake" class="h-full w-full object-cover" %}</div>{% endif %}
{% 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 %} <figcaption class="pd-card-body">
</div>
<figcaption class="p-5">
<div class="flex items-start justify-between gap-3"> <div class="flex items-start justify-between gap-3">
<div> <div>
{% if cake.name %}<h3 class="pd-display text-xl text-primary">{{ cake.name }}</h3>{% endif %} {% if cake.name %}<h3 class="pd-display text-xl text-primary">{{ cake.name }}</h3>{% endif %}

View File

@ -1,4 +1,4 @@
<section data-block="pastel-dream:order_ribbon" class="py-14 md:py-20 bg-background text-foreground{% if class %} {{ class }}{% endif %}"> <section data-block="pastel-dream:order_ribbon" class="pd-section bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-5xl px-4"> <div class="mx-auto max-w-5xl px-4">
<div class="pd-chalkboard relative overflow-hidden p-8 md:p-12"> <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="relative flex flex-col items-start gap-6 md:flex-row md:items-center md:justify-between">

View File

@ -1,4 +1,4 @@
<section data-block="pastel-dream:seasonal_specials" class="py-14 md:py-20 bg-background text-foreground{% if class %} {{ class }}{% endif %}"> <section data-block="pastel-dream:seasonal_specials" class="pd-section bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-6xl px-4"> <div class="mx-auto max-w-6xl px-4">
<div class="max-w-2xl"> <div class="max-w-2xl">
{% if kicker %}<p class="pd-hand text-xl text-accent">{{ kicker }}</p>{% endif %} {% if kicker %}<p class="pd-hand text-xl text-accent">{{ kicker }}</p>{% endif %}
@ -6,13 +6,11 @@
{% if intro %}<p class="pd-body mt-3 text-lg text-muted-foreground">{{ intro }}</p>{% endif %} {% if intro %}<p class="pd-body mt-3 text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
</div> </div>
{% if items %} {% if items %}
<div class="mt-10 grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3"> <div class="mt-10 pd-grid pd-grid--2 pd-grid--3">
{% for item in items %} {% for item in items %}
<article class="pd-card overflow-hidden"> <article class="pd-card overflow-hidden flex flex-col">
<div class="aspect-[4/3] w-full overflow-hidden bg-secondary"> {% if item.image %}<div class="pd-media bg-secondary">{% img item.image alt=item.name|default:"Seasonal special" class="h-full w-full object-cover" %}</div>{% endif %}
{% 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 class="pd-card-body">
</div>
<div class="p-5">
<div class="flex items-start justify-between gap-3"> <div class="flex items-start justify-between gap-3">
<h3 class="pd-display text-xl text-primary">{{ item.name }}</h3> <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 %} {% if item.price %}<span class="pd-mono shrink-0 text-lg text-accent tabular-nums">{% if currency %}{{ currency }}{% endif %}{{ item.price }}</span>{% endif %}

View File

@ -141,6 +141,10 @@ css:
radial-gradient(50% 50% at 25% 90%, hsl(var(--primary) / 0.08) 0%, transparent 60%); radial-gradient(50% 50% at 25% 90%, hsl(var(--primary) / 0.08) 0%, transparent 60%);
background-attachment: fixed; background-attachment: fixed;
background-repeat: no-repeat; background-repeat: no-repeat;
/* Guard against horizontal scroll on small screens from any off-canvas
* chrome (mobile drawer). `clip` does not create a scroll container, so
* it will not break the sticky navbar the way `hidden` can. */
overflow-x: clip;
} }
/* --- Type roles --------------------------------------------------------- */ /* --- Type roles --------------------------------------------------------- */
@ -356,3 +360,43 @@ css:
animation: none; animation: none;
} }
} }
/* --- Layout helpers (theme-owned, semantic tokens only) -----------------
* Installed .bnp themes live outside the host Tailwind @source globs, so the
* host build does NOT compile grid / aspect-ratio / gap / padding utilities
* that appear only in this theme's own templates. The theme therefore owns
* those layout primitives here as plain CSS: mobile-first, collapsing to a
* single column on small screens, with explicit media queries so the render
* is reliable regardless of the JIT content scan. No colors — layout only. */
.pd-section { padding-top: 2.75rem; padding-bottom: 2.75rem; }
@media (min-width: 768px) {
.pd-section { padding-top: 4rem; padding-bottom: 4rem; }
}
.pd-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2rem; }
@media (min-width: 640px) {
.pd-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
.pd-grid--lg-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.pd-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.pd-grid--lg-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
/* Two-up menu board: one column on phones, two side by side from md. */
.pd-menu-cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2.5rem 3rem; }
@media (min-width: 768px) {
.pd-menu-cols--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.pd-menu-list { margin-top: 1rem; }
.pd-menu-list > li + li { margin-top: 1.25rem; }
/* Media frames: a fixed aspect box so a photo fills gracefully. The EMPTY
* state is omitted in-template (no ballooning boxes when no photo is set). */
.pd-media { position: relative; width: 100%; overflow: hidden; aspect-ratio: 4 / 3; }
.pd-media--square { aspect-ratio: 1 / 1; }
.pd-media img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* Card body padding (host may not emit p-5 for theme templates). */
.pd-card-body { padding: 1.25rem 1.25rem 1.5rem; }
@media (min-width: 768px) { .pd-card-body { padding: 1.5rem; } }

View File

@ -1,4 +1,4 @@
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}"> <section class="pd-section bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-6xl px-4"> <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 == "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 %}"> <div class="{% if background != "split" %}mx-auto max-w-2xl{% endif %}">

View File

@ -3,7 +3,7 @@
{% if eyebrow %}<p class="pd-hand text-center text-2xl text-accent">{{ eyebrow }}</p>{% endif %} {% 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 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 %} {% 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 %}"> <div class="mt-12 pd-grid pd-grid--2 {% if columns == 2 %}pd-grid--lg-2{% elif columns == 4 %}pd-grid--lg-4{% else %}pd-grid--3{% endif %}">
{% for item in items %} {% for item in items %}
<div class="{% if layout == "card" %}pd-card p-6 transition-shadow hover:shadow-md{% elif layout == "centered" %}text-center{% endif %}"> <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 %} {% 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 %}

View File

@ -1,7 +1,7 @@
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}"> <section class="pd-section bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-5xl px-4"> <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 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 class="mt-10 pd-grid pd-grid--2">
<div> <div>
{% if hours %} {% if hours %}
<table class="pd-body w-full text-sm" data-pd-hours> <table class="pd-body w-full text-sm" data-pd-hours>

View File

@ -1,14 +1,14 @@
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}"> <section class="pd-section bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-5xl px-4"> <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 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 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="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 %}"> <div class="pd-menu-cols {% if columns == 2 %}pd-menu-cols--2{% endif %}">
{% for section in sections %} {% for section in sections %}
<div class="menu-section"> <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.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 %} {% 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"> <ul class="pd-menu-list">
{% for item in section.items %} {% for item in section.items %}
<li class="flex items-baseline gap-4"> <li class="flex items-baseline gap-4">
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">

View File

@ -35,7 +35,7 @@
</div> </div>
<style> <style>
.bn-navbar-toggle{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;} .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-drawer{width:20rem;max-width:85vw;transform:translateX(100%);transition:transform .3s ease;}
.bn-navbar-overlay{opacity:0;pointer-events:none;transition:opacity .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-drawer{transform:translateX(0);}
.bn-navbar-toggle:checked ~ .bn-navbar-overlay{opacity:1;pointer-events:auto;} .bn-navbar-toggle:checked ~ .bn-navbar-overlay{opacity:1;pointer-events:auto;}