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

35 lines
4.0 KiB
Plaintext

{# contact-form override — server-driven; block-id resolution, {% form %} wiring, honeypot, and field loop copied verbatim from the builtin, reskinned with sc-form primitives. #}
{% with bid=_block_id|default:context.blockId %}
<section id="contact-form-{{ bid }}" class="sc-section {{ class }}"><div class="sc-content-well" style="max-width:640px;">
{% if form.submitted %}
<div class="sc-card sc-card-accent sc-alert-ok" style="text-align:center;padding:2rem;">
<span class="sc-mark sc-mark-circle" aria-hidden="true" style="display:inline-block;margin-bottom:1rem;"></span>
<p class="sc-display" style="margin:0;font-size:1.125rem;">{{ form.message|default:successMessage|default:"Thanks — your message has been sent." }}</p>
</div>
{% else %}
{% if heading %}<h2 class="sc-heading" style="font-size:clamp(1.75rem, 3vw, 2.25rem);margin:0 0 0.75rem 0;"><span class="sc-heading-h2-accent">{{ heading }}</span></h2>{% endif %}
{% if description %}<p class="sc-lede" style="margin:0 0 2rem 0;color:hsl(var(--muted-foreground));">{{ description }}</p>{% endif %}
{% if form.errors._form %}<div class="sc-alert-err" style="margin-bottom:1.5rem;border:1px solid hsl(var(--destructive));border-radius:4px;padding:0.75rem 1rem;font-size:0.875rem;color:hsl(var(--destructive));background:hsl(var(--destructive) / 0.08);">{{ 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="sc-form-grid" %}
{% for field in fields %}
<div class="sc-field {% if field.width == "half" %}{% else %}sc-field--full{% endif %}">
{% if field.label %}<label class="sc-label" for="cf-{{ bid }}-{{ field.name }}">{{ field.label }}{% if field.required %} <span style="color:hsl(var(--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="sc-input"></textarea>
{% elif field.type == "select" %}
<select id="cf-{{ bid }}-{{ field.name }}" name="{{ field.name }}"{% if field.required %} required{% endif %} class="sc-input">{% 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="sc-input">
{% endif %}
</div>
{% endfor %}
<div class="sc-field--full" aria-hidden="true" style="display:none;"><label>Leave this field empty<input type="text" name="website" tabindex="-1" autocomplete="off"></label></div>
<div class="sc-field--full"><button type="submit" class="sc-btn-primary">{{ submitLabel|default:"Send message" }}</button></div>
{% endform %}
{% endwith %}
{% endif %}
</div></section>
<style>#contact-form-{{ bid }} .sc-form-grid{display:grid;grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.25rem;margin-top:2rem;}@media (max-width:640px){#contact-form-{{ bid }} .sc-form-grid{grid-template-columns:1fr;}}#contact-form-{{ bid }} .sc-field--full{grid-column:1 / -1;}#contact-form-{{ bid }} .sc-label{display:block;margin-bottom:0.375rem;font-size:0.8125rem;font-weight:500;color:hsl(var(--foreground));font-family:var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif);}#contact-form-{{ bid }} .sc-input{width:100%;padding:0.5rem 0.75rem;font-size:0.9375rem;font-family:var(--font-body, "Inter", system-ui, sans-serif);color:hsl(var(--foreground));background:hsl(var(--background));border:1px solid hsl(var(--input));border-radius:4px;box-sizing:border-box;}#contact-form-{{ bid }} .sc-input:focus{outline:2px solid hsl(var(--ring));outline-offset:1px;}</style>
{% endwith %}