Alex Dunmow 6af6950f23 fix(scifi-clean): repair blank hero + whitespace/layout + mobile responsiveness
Layout / whitespace:
- Root-cause blank hero: seed passed hero styled-text (eyebrow/headline/
  subheadline) as bare strings, but the hero template does field.text|default:field
  and the ninjatpl engine throws a hard error on attribute-access-on-string, so the
  whole hero (and the builtin fallback) errored and rendered as an empty ~160px band.
  Seed now passes {text:...} objects (the builtin-sample / admin-editor format).
- tech_spec table: value + unit were both right-aligned, leaving a cavernous mid-gap;
  label column now absorbs slack and value+unit group together on the right. Box
  narrowed (34rem) with tighter padding so the HUD corners frame a deliberate panel.
- Tightened section rhythm (sc-section 4->3.5rem, sc-section-tight 2.5->2.75rem) and
  hero bottom padding to remove oversized inter-section gaps.
- Defined sc-article-measure (was referenced but undefined -> unbounded line length).

Empty-media graceful state (seed ships no images):
- rich-section and hero (split) rendered an empty aspect-ratio muted box when media
  was absent; media frame is now guarded and the grid collapses to one column.

Mobile responsiveness (explicit sc-* classes + @media, not JIT-scanned Tailwind):
- sc-form-grid (global + contact-form scoped) collapses to 1 column <=640px.
- gallery dropped inline grid-template-columns that overrode its responsive classes,
  so 3/4-col galleries now collapse on small screens.
- nav toggle tap target bumped to 42x42px.

Motion robustness:
- Added a safety-net full-reveal timeout to the scroll-reveal script so below-fold
  [data-sc-reveal] content is guaranteed visible on no-scroll screenshot captures and
  IntersectionObserver edge cases. Reduced-motion / no-JS already render fully.

Semantic tokens only; check-safety 32 checks 19 ok 13 skip -> OK; codeless build OK.
No version bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:27:27 +08:00

27 lines
3.3 KiB
Plaintext

{# hero override — schematic HUD headline over blueprint restraint. #}
{# styled-text fields accept a {text,color} object OR a bare string (.text|default:field). #}
{# MID motion: .sc-schematic line draws in via the page-template script; no-JS static. #}
{% 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" %}
{% macro herobody() %}
{% if eyebrowText %}<p class="sc-eyebrow">{{ eyebrowText }}</p>{% endif %}
<div class="sc-schematic" data-sc-reveal style="max-width: 11rem; margin: 0 0 1.25rem 0;"><div class="sc-schematic-line"></div></div>
{% if headlineText %}<h1 class="sc-hero-headline sc-display" style="font-size: clamp(2.5rem, 6vw, 4.5rem); line-height: 1.02; letter-spacing: -0.03em; margin: 0; color: hsl(var(--foreground));">{{ headlineText }}</h1>{% endif %}
{% if subText %}<p class="sc-hero-sub sc-lede" style="margin: 1.25rem 0 0 0; font-size: 1.25rem; line-height: 1.5; color: hsl(var(--muted-foreground)); max-width: 60ch;">{{ subText }}</p>{% endif %}
{% if descText %}<p class="sc-text" style="margin: 1rem 0 0 0; color: hsl(var(--muted-foreground)); max-width: 60ch;">{{ descText }}</p>{% endif %}
{% if pText or sText %}<div class="sc-btn-row" style="display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 2rem;">{% if pText and pUrl %}<a class="sc-btn-primary sc-btn-lg" href="{{ pUrl }}">{{ pText }}</a>{% endif %}{% if sText and sUrl %}<a class="sc-btn-outline sc-btn-lg" href="{{ sUrl }}">{{ sText }}</a>{% endif %}</div>{% endif %}
{% if trustLogos %}<div class="sc-hairline-top" style="margin-top: 3rem; padding-top: 1.5rem;"><p class="sc-kicker sc-eyebrow" style="margin-bottom: 1rem; color: hsl(var(--muted-foreground));">Trusted by</p><div class="sc-logo-row" style="display: flex; flex-wrap: wrap; align-items: center; gap: 2rem;">{% for logo in trustLogos %}{% if logo.url %}<a href="{{ logo.url }}">{% img logo.image alt=logo.alt class="sc-logo" %}</a>{% else %}{% img logo.image alt=logo.alt class="sc-logo" %}{% endif %}{% endfor %}</div></div>{% endif %}
{% endmacro %}
{% if variant == "split" %}
<section class="sc-hero sc-hero-split {{ class }}" style="padding-top: 4rem; padding-bottom: 4rem;"><div class="sc-content-well"><div class="sc-grid {% if backgroundImage %}sc-grid-2{% endif %}" style="align-items: center;"><div>{{ herobody() }}</div>{% if backgroundImage %}<div class="sc-media-frame" style="aspect-ratio: 4 / 3; background: hsl(var(--muted)); border: 1px solid hsl(var(--border)); overflow: hidden; {% if mediaPosition == "left" %}order: -1;{% endif %}">{% img backgroundImage alt=headlineText class="sc-hero-media" %}</div>{% endif %}</div></div></section>
{% else %}
<section class="sc-hero {{ class }}" style="padding-top: 4.5rem; padding-bottom: 3.5rem; {% if variant == "left-aligned" %}text-align: left;{% else %}text-align: center;{% endif %}"><div class="sc-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 %}