fix(lcars): remediate hero blank, empty-media balloon, CTA void, band padding

Layout/whitespace remediation (no redesign; LCARS persona intact):
- hero: styled-text reads used `field.text|default:field`, which HARD-ERRORS
  in ninjatpl when the field is a bare string (resolveIdentifier errors on
  reflect.String, and nodeFilteredVariable returns before the default filter
  runs). The demo seed passes bare strings for eyebrow/headline/subheadline,
  so the hero aborted on its first {% set %} and rendered nothing — a dead
  band on the home page. Switch to `field|get:"text"|default:field` (the
  global jutsu `get` filter tolerates non-maps), which resolves both the
  {text,color} object and bare-string forms. Same defensive fix in navbar
  logoText.
- hero split: guard the empty media frame so an image-less split hero drops to
  a single column instead of leaving a blank muted panel.
- episode_console: empty cover rendered a full 1:1 square that ballooned to
  ~600px and, with align-items:stretch, stretched the text column to match,
  leaving a large void beside the copy. Switch to align-items:start and render
  a compact, capped, on-brand LCARS readout placeholder (.lcars-console-art)
  when no cover ships.
- cta: band variant left a large empty region right of the button; use
  justify-content:space-between and trim the oversized 4rem band padding to 3rem.
- manifest css: add .lcars-console-art (explicit CSS, semantic tokens) + cap
  .lcars-img-empty height.

Mobile responsiveness audited from CSS/templates: all grids/rails collapse via
explicit @media rules in manifest css.input_css_append (not Tailwind JIT-scanned
theme classes); no horizontal overflow at 375-390px. Content is never gated by
IntersectionObserver (canvas IO drives only the decorative hero background;
.lcars-reveal is a load-time CSS animation with a reduced-motion opacity:1
fallback), so no reveal-hidden dead bands.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Alex Dunmow 2026-07-05 17:26:12 +08:00
parent 800c8904d6
commit d919a967cf
5 changed files with 20 additions and 10 deletions

View File

@ -1,7 +1,7 @@
<section class="lcars-section {{ class }}" data-block="lcars:episode_console"><div class="lcars-well"> <section class="lcars-section {{ class }}" data-block="lcars:episode_console"><div class="lcars-well">
<div class="lcars-panel lcars-panel-accent" style="border-radius:0 0.75rem 0.75rem 0;padding:1.5rem;"> <div class="lcars-panel lcars-panel-accent" style="border-radius:0 0.75rem 0.75rem 0;padding:1.5rem;">
<div class="lcars-hero-split" style="gap:1.75rem;align-items:stretch;"> <div class="lcars-hero-split" style="gap:1.75rem;align-items:start;">
<div class="lcars-media-frame" style="aspect-ratio:1 / 1;align-self:start;">{% if cover %}{% img cover alt=title|default:"Episode cover" %}{% else %}<div class="lcars-img-empty" style="aspect-ratio:1 / 1;"></div>{% endif %}</div> {% if cover %}<div class="lcars-media-frame" style="aspect-ratio:1 / 1;max-height:22rem;align-self:start;">{% img cover alt=title|default:"Episode cover" %}</div>{% else %}<div class="lcars-console-art" aria-hidden="true"><span class="lcars-console-art-scope"><i></i><i></i><i></i><i></i><i></i></span></div>{% endif %}
<div class="lcars-stack-sm" style="min-width:0;"> <div class="lcars-stack-sm" style="min-width:0;">
<div class="lcars-panel-header"><span class="lcars-pill lcars-pill-accent" aria-hidden="true"></span>{% if eyebrow %}<span class="lcars-panel-title">{{ eyebrow }}</span>{% else %}<span class="lcars-panel-title">Now Broadcasting</span>{% endif %}<span class="lcars-panel-header-bar"></span></div> <div class="lcars-panel-header"><span class="lcars-pill lcars-pill-accent" aria-hidden="true"></span>{% if eyebrow %}<span class="lcars-panel-title">{{ eyebrow }}</span>{% else %}<span class="lcars-panel-title">Now Broadcasting</span>{% endif %}<span class="lcars-panel-header-bar"></span></div>
<div style="display:flex;flex-wrap:wrap;gap:0.75rem;align-items:center;">{% if episodeNumber %}<span class="lcars-badge">EP {{ episodeNumber }}</span>{% endif %}{% if stardate %}<span class="lcars-stardate">Stardate {{ stardate }}</span>{% endif %}{% if duration %}<span class="lcars-readout">{{ duration }}</span>{% endif %}</div> <div style="display:flex;flex-wrap:wrap;gap:0.75rem;align-items:center;">{% if episodeNumber %}<span class="lcars-badge">EP {{ episodeNumber }}</span>{% endif %}{% if stardate %}<span class="lcars-stardate">Stardate {{ stardate }}</span>{% endif %}{% if duration %}<span class="lcars-readout">{{ duration }}</span>{% endif %}</div>

View File

@ -212,7 +212,17 @@ css:
.lcars-surface-card { background-color: hsl(var(--card)); } .lcars-surface-card { background-color: hsl(var(--card)); }
.lcars-media-frame { display: block; width: 100%; overflow: hidden; background-color: hsl(var(--muted)); border-radius: 0.5rem; } .lcars-media-frame { display: block; width: 100%; overflow: hidden; background-color: hsl(var(--muted)); border-radius: 0.5rem; }
.lcars-media-frame img { display: block; width: 100%; height: 100%; object-fit: cover; } .lcars-media-frame img { display: block; width: 100%; height: 100%; object-fit: cover; }
.lcars-img-empty { display: block; width: 100%; aspect-ratio: 16 / 9; background-color: hsl(var(--muted)); border-radius: 0.5rem; } .lcars-img-empty { display: block; width: 100%; aspect-ratio: 16 / 9; max-height: 20rem; background-color: hsl(var(--muted)); border-radius: 0.5rem; }
/* Graceful empty-media state: an on-brand LCARS readout panel that stays
compact instead of ballooning into a blank square when no cover ships. */
.lcars-console-art { display: flex; align-items: center; justify-content: center; align-self: start; width: 100%; aspect-ratio: 4 / 3; max-height: 15rem; padding: 1.5rem; background-color: hsl(var(--muted)); border-radius: 0.5rem; box-sizing: border-box; }
.lcars-console-art-scope { display: flex; flex-direction: column; gap: 0.5rem; width: 100%; max-width: 16rem; }
.lcars-console-art-scope i { display: block; height: 0.625rem; border-radius: 0.3125rem; opacity: 0.55; }
.lcars-console-art-scope i:nth-child(1) { width: 100%; background-color: hsl(var(--primary)); }
.lcars-console-art-scope i:nth-child(2) { width: 72%; background-color: hsl(var(--secondary)); }
.lcars-console-art-scope i:nth-child(3) { width: 88%; background-color: hsl(var(--accent)); }
.lcars-console-art-scope i:nth-child(4) { width: 60%; background-color: hsl(var(--secondary)); }
.lcars-console-art-scope i:nth-child(5) { width: 80%; background-color: hsl(var(--primary)); }
/* --- Responsive grids --- */ /* --- Responsive grids --- */
.lcars-grid { display: grid; gap: 1.25rem; grid-template-columns: repeat(1, minmax(0, 1fr)); } .lcars-grid { display: grid; gap: 1.25rem; grid-template-columns: repeat(1, minmax(0, 1fr)); }

View File

@ -1,5 +1,5 @@
{# cta override — full-width strip; band / card / split variants. #} {# cta override — full-width strip; band / card / split variants. #}
<section class="lcars-cta-strip {% if background == "band" %}lcars-cta-strip--band{% elif background == "card" %}lcars-cta-strip--card{% endif %} {{ class }}" style="padding-top: 4rem; padding-bottom: 4rem;"><div class="lcars-content-well" style="display: flex; flex-wrap: wrap; align-items: center; gap: 2rem; {% if background == "split" %}justify-content: space-between;{% endif %}"> <section class="lcars-cta-strip {% if background == "band" %}lcars-cta-strip--band{% elif background == "card" %}lcars-cta-strip--card{% endif %} {{ class }}" style="padding-top: 3rem; padding-bottom: 3rem;"><div class="lcars-content-well" style="display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1.5rem 2.5rem;">
<div style="{% if background != "split" %}max-width: 60ch;{% endif %}"> <div style="{% if background != "split" %}max-width: 60ch;{% endif %}">
{% if heading %}<h2 class="lcars-cta-headline lcars-heading" style="font-size: clamp(1.75rem, 4vw, 2.75rem); margin: 0; color: inherit;">{{ heading }}</h2>{% endif %} {% if heading %}<h2 class="lcars-cta-headline lcars-heading" style="font-size: clamp(1.75rem, 4vw, 2.75rem); margin: 0; color: inherit;">{{ heading }}</h2>{% endif %}
{% if text %}<p class="lcars-lede" style="margin: 0.75rem 0 0 0; max-width: 55ch; color: {% if background == "band" %}inherit{% else %}hsl(var(--muted-foreground)){% endif %};">{{ text }}</p>{% endif %} {% if text %}<p class="lcars-lede" style="margin: 0.75rem 0 0 0; max-width: 55ch; color: {% if background == "band" %}inherit{% else %}hsl(var(--muted-foreground)){% endif %};">{{ text }}</p>{% endif %}

View File

@ -1,8 +1,8 @@
{# hero override — LCARS command-deck hero. GO BIG motion tier: one dependency-free canvas panel-sweep behind the content, lazy-inited, prefers-reduced-motion honored, with the fully-styled hero as the static no-JS fallback. All content fields preserved verbatim from the builtin. Styled-text fields accept {text,color} OR a bare string. #} {# hero override — LCARS command-deck hero. GO BIG motion tier: one dependency-free canvas panel-sweep behind the content, lazy-inited, prefers-reduced-motion honored, with the fully-styled hero as the static no-JS fallback. All content fields preserved verbatim from the builtin. Styled-text fields accept {text,color} OR a bare string. #}
{% set eyebrowText = eyebrow.text|default:eyebrow %} {% set eyebrowText = eyebrow|get:"text"|default:eyebrow %}
{% set headlineText = headline.text|default:headline %} {% set headlineText = headline|get:"text"|default:headline %}
{% set subText = subheadline.text|default:subheadline %} {% set subText = subheadline|get:"text"|default:subheadline %}
{% set descText = description.text|default:description %} {% set descText = description|get:"text"|default:description %}
{% set pText = ctaPrimary.text %} {% set pText = ctaPrimary.text %}
{% set pUrl = ctaPrimary.url %} {% set pUrl = ctaPrimary.url %}
{% set sText = ctaSecondary.text %} {% set sText = ctaSecondary.text %}
@ -17,7 +17,7 @@
{% if trustLogos %}<div class="lcars-hairline-top" style="margin-top: 3rem; padding-top: 1.5rem;"><p class="lcars-kicker lcars-eyebrow" style="margin-bottom: 1rem; color: hsl(var(--muted-foreground));">Trusted by</p><div class="lcars-logo-row">{% for logo in trustLogos %}{% if logo.url %}<a href="{{ logo.url }}">{% img logo.image alt=logo.alt class="lcars-logo" %}</a>{% else %}{% img logo.image alt=logo.alt class="lcars-logo" %}{% endif %}{% endfor %}</div></div>{% endif %} {% if trustLogos %}<div class="lcars-hairline-top" style="margin-top: 3rem; padding-top: 1.5rem;"><p class="lcars-kicker lcars-eyebrow" style="margin-bottom: 1rem; color: hsl(var(--muted-foreground));">Trusted by</p><div class="lcars-logo-row">{% for logo in trustLogos %}{% if logo.url %}<a href="{{ logo.url }}">{% img logo.image alt=logo.alt class="lcars-logo" %}</a>{% else %}{% img logo.image alt=logo.alt class="lcars-logo" %}{% endif %}{% endfor %}</div></div>{% endif %}
{% endmacro %} {% endmacro %}
{% if variant == "split" %} {% if variant == "split" %}
<section class="lcars-hero lcars-hero-split lcars-hero-canvas-host {{ class }}"><canvas class="lcars-hero-canvas" data-lcars-hero-canvas aria-hidden="true"></canvas><div class="lcars-content-well"><div class="lcars-grid lcars-grid-2" style="align-items: center;"><div>{{ herobody() }}</div><div class="lcars-media-frame" style="aspect-ratio: 4 / 3; {% if mediaPosition == "left" %}order: -1;{% endif %}">{% if backgroundImage %}{% img backgroundImage alt=headlineText class="lcars-hero-media" %}{% endif %}</div></div></div></section> <section class="lcars-hero lcars-hero-split lcars-hero-canvas-host {{ class }}"><canvas class="lcars-hero-canvas" data-lcars-hero-canvas aria-hidden="true"></canvas><div class="lcars-content-well">{% if backgroundImage %}<div class="lcars-grid lcars-grid-2" style="align-items: center;"><div>{{ herobody() }}</div><div class="lcars-media-frame" style="aspect-ratio: 4 / 3; max-height: 26rem; {% if mediaPosition == "left" %}order: -1;{% endif %}">{% img backgroundImage alt=headlineText class="lcars-hero-media" %}</div></div>{% else %}<div style="max-width: 60ch;">{{ herobody() }}</div>{% endif %}</div></section>
{% else %} {% else %}
<section class="lcars-hero lcars-hero-canvas-host {{ class }}" style="{% if variant == "left-aligned" %}text-align: left;{% else %}text-align: center;{% endif %}"><canvas class="lcars-hero-canvas" data-lcars-hero-canvas aria-hidden="true"></canvas><div class="lcars-content-well" style="{% if variant != "left-aligned" %}display: flex; flex-direction: column; align-items: center;{% endif %}"><div style="max-width: 60ch; width: 100%;">{{ herobody() }}</div></div></section> <section class="lcars-hero lcars-hero-canvas-host {{ class }}" style="{% if variant == "left-aligned" %}text-align: left;{% else %}text-align: center;{% endif %}"><canvas class="lcars-hero-canvas" data-lcars-hero-canvas aria-hidden="true"></canvas><div class="lcars-content-well" style="{% if variant != "left-aligned" %}display: flex; flex-direction: column; align-items: center;{% endif %}"><div style="max-width: 60ch; width: 100%;">{{ herobody() }}</div></div></section>
{% endif %} {% endif %}

View File

@ -1 +1 @@
<nav class="lcars-navbar {{ class }}" aria-label="Primary"><a class="lcars-brand" href="/"><span class="lcars-mark lcars-mark-square" aria-hidden="true"></span>{% if logoType == "image" and logo %}{% img logo alt=alt|default:"Home" class="lcars-brand-logo" %}{% else %}<span>{{ logoText.text|default:logoText|default:context.site.title|default:"Studio" }}</span>{% endif %}</a><div class="lcars-nav-links">{% for item in menu.items %}<a class="lcars-nav-link" href="{{ item.url }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}>{{ item.label|default:item.title }}</a>{% endfor %}{% if cta.text and cta.url %}<a class="lcars-btn-primary lcars-btn-sm" href="{{ cta.url }}">{{ cta.text }}</a>{% endif %}</div><input type="checkbox" id="lcars-nav-toggle-cb" class="lcars-nav-cb" style="display:none;"><label for="lcars-nav-toggle-cb" class="lcars-nav-toggle lcars-btn-outline lcars-btn-sm" aria-label="Toggle menu">::lucide:menu:sm::</label><div class="lcars-nav-drawer">{% for item in menu.items %}<a href="{{ item.url }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}>{{ item.label|default:item.title }}</a>{% endfor %}{% if cta.text and cta.url %}<a href="{{ cta.url }}" class="lcars-btn-primary lcars-btn-sm" style="margin-top:0.75rem;">{{ cta.text }}</a>{% endif %}</div><style>.lcars-nav-cb:checked ~ .lcars-nav-drawer{display:flex;}</style></nav> <nav class="lcars-navbar {{ class }}" aria-label="Primary"><a class="lcars-brand" href="/"><span class="lcars-mark lcars-mark-square" aria-hidden="true"></span>{% if logoType == "image" and logo %}{% img logo alt=alt|default:"Home" class="lcars-brand-logo" %}{% else %}<span>{{ logoText|get:"text"|default:logoText|default:context.site.title|default:"Studio" }}</span>{% endif %}</a><div class="lcars-nav-links">{% for item in menu.items %}<a class="lcars-nav-link" href="{{ item.url }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}>{{ item.label|default:item.title }}</a>{% endfor %}{% if cta.text and cta.url %}<a class="lcars-btn-primary lcars-btn-sm" href="{{ cta.url }}">{{ cta.text }}</a>{% endif %}</div><input type="checkbox" id="lcars-nav-toggle-cb" class="lcars-nav-cb" style="display:none;"><label for="lcars-nav-toggle-cb" class="lcars-nav-toggle lcars-btn-outline lcars-btn-sm" aria-label="Toggle menu">::lucide:menu:sm::</label><div class="lcars-nav-drawer">{% for item in menu.items %}<a href="{{ item.url }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}>{{ item.label|default:item.title }}</a>{% endfor %}{% if cta.text and cta.url %}<a href="{{ cta.url }}" class="lcars-btn-primary lcars-btn-sm" style="margin-top:0.75rem;">{{ cta.text }}</a>{% endif %}</div><style>.lcars-nav-cb:checked ~ .lcars-nav-drawer{display:flex;}</style></nav>