fix(corporate-modernist): repair empty hero + tighten rhythm + mobile

Root cause of the missing home hero: the hero built-in's styled-text
fields (eyebrow/headline/subheadline, schema type=object x-editor=
styled-text) were seeded as BARE STRINGS. The override reads
`headline.text|default:headline`; ninjatpl hard-errors on `.text` field
access against a string ("can't access a field by name on type string")
BEFORE the |default filter runs, aborting the whole hero render → the
section rendered nothing, leaving a large void at the top of the page.
Fixed by seeding the object form `{ "text": "..." }` (matches the
built-in schema + sample). Same latent bug affects any theme that seeds
bare strings into hero styled-text fields.

Whitespace / layout:
- cm-section padding 4rem -> 3rem (3.5rem >=768px); cm-section-tight
  2.5rem -> 2rem. Kills the stacked ~8rem inter-section dead bands so the
  Swiss grid reads precise, not empty.
- hero padding 5rem -> 3.5rem.

Empty-media hardening:
- hero split variant only renders its media frame when backgroundImage
  is present (was an unconditional 4/3 muted box -> balloon).

Mobile:
- cm-form-grid now single-column base, 2-col >=640px (was fixed 2-col ->
  cramped half-fields at 375px); mirrored in the contact-form scoped
  style which wins on specificity.
- mobile nav toggle min 44x44 tap target.

No version bump. make + check-safety both exit 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Alex Dunmow 2026-07-05 17:28:06 +08:00
parent c0d9fd5420
commit a7a90ba3c1
5 changed files with 20 additions and 11 deletions

View File

@ -289,8 +289,15 @@
/* Section spacing. */
.cm-section {
padding-top: 4rem;
padding-bottom: 4rem;
padding-top: 3rem;
padding-bottom: 3rem;
}
@media (min-width: 768px) {
.cm-section {
padding-top: 3.5rem;
padding-bottom: 3.5rem;
}
}
/* Image empty-state — when media missing. */

View File

@ -128,8 +128,9 @@ css:
.cm-content-well { width: 100%; max-width: 1280px; margin-left: auto; margin-right: auto; padding-left: 1.5rem; padding-right: 1.5rem; box-sizing: border-box; }
.cm-measure { max-width: 70ch; }
.cm-swiss-12 { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: 1.5rem; row-gap: 1.5rem; }
.cm-section { padding-top: 4rem; padding-bottom: 4rem; }
.cm-section-tight { padding-top: 2.5rem; padding-bottom: 2.5rem; }
.cm-section { padding-top: 3rem; padding-bottom: 3rem; }
@media (min-width: 768px) { .cm-section { padding-top: 3.5rem; padding-bottom: 3.5rem; } }
.cm-section-tight { padding-top: 2rem; padding-bottom: 2rem; }
.cm-hairline { border: 1px solid hsl(var(--border)); }
.cm-hairline-top { border-top: 1px solid hsl(var(--border)); }
.cm-hairline-bottom { border-bottom: 1px solid hsl(var(--border)); }
@ -205,7 +206,7 @@ css:
@media (min-width: 768px) { .cm-nav-links { display: flex; } }
.cm-nav-link { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 0.9375rem; color: hsl(var(--foreground)); text-decoration: none; padding-bottom: 2px; border-bottom: 2px solid transparent; transition: border-color 150ms ease; }
.cm-nav-link:hover { border-bottom-color: hsl(var(--accent)); }
.cm-nav-toggle { display: inline-flex; }
.cm-nav-toggle { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; }
@media (min-width: 768px) { .cm-nav-toggle { display: none; } }
.cm-nav-drawer { display: none; flex-direction: column; gap: 0.25rem; padding: 0.5rem 0 1rem; border-top: 1px solid hsl(var(--border)); width: 100%; }
@media (min-width: 768px) { .cm-nav-drawer { display: none !important; } }
@ -315,7 +316,8 @@ css:
/* --- Forms --- */
.cm-form { display: grid; gap: 1.25rem; }
.cm-form-grid { display: grid; gap: 1.25rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.cm-form-grid { display: grid; gap: 1.25rem; grid-template-columns: repeat(1, minmax(0, 1fr)); }
@media (min-width: 640px) { .cm-form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.cm-field { display: flex; flex-direction: column; gap: 0.375rem; }
.cm-field--full { grid-column: 1 / -1; }
.cm-label { font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-size: 0.8125rem; font-weight: 600; color: hsl(var(--foreground)); text-transform: uppercase; letter-spacing: 0.04em; }

View File

@ -27,7 +27,7 @@
"template_key": "full-width",
"reconcile_blocks": true,
"blocks": [
{ "block_key": "hero", "title": "Hero", "slot": "main", "sort_order": 1, "content": { "template": "left-aligned", "eyebrow": "B2B advisory", "headline": "Precise counsel for boards that move first", "subheadline": "We do strategy, operations, and risk work on a fixed scope. No retainers you cannot read.", "ctaPrimary": { "text": "Book a working session", "url": "/contact" }, "ctaSecondary": { "text": "See how we work", "url": "/about" } } },
{ "block_key": "hero", "title": "Hero", "slot": "main", "sort_order": 1, "content": { "template": "left-aligned", "eyebrow": { "text": "B2B advisory" }, "headline": { "text": "Precise counsel for boards that move first" }, "subheadline": { "text": "We do strategy, operations, and risk work on a fixed scope. No retainers you cannot read." }, "ctaPrimary": { "text": "Book a working session", "url": "/contact" }, "ctaSecondary": { "text": "See how we work", "url": "/about" } } },
{ "block_key": "corporate-modernist:credential_bar", "title": "Credentials", "slot": "main", "sort_order": 2, "content": { "items": [ { "figure": "18", "label": "Years in practice" }, { "figure": "6", "label": "Sectors served" }, { "figure": "40+", "label": "Board engagements" }, { "figure": "3", "label": "Continents" } ] } },
{ "block_key": "corporate-modernist:capability_matrix", "title": "Capabilities", "slot": "main", "sort_order": 3, "content": { "eyebrow": "What we do", "heading": "Three disciplines, one team", "intro": "Every engagement draws on the same three capabilities. We assign a principal to each.", "columns": 3, "capabilities": [ { "mark": "circle", "title": "Strategy", "text": "Market entry, portfolio choices, and the operating model that carries them." }, { "mark": "triangle", "title": "Operations", "text": "Cost structure, process design, and the metrics a board can hold." }, { "mark": "square", "title": "Risk", "text": "Controls, resilience, and the reporting that keeps regulators satisfied." } ] } },
{ "block_key": "stats", "title": "Outcomes", "slot": "main", "sort_order": 4, "content": { "heading": "Outcomes we measure", "columns": 3, "countUp": false, "items": [ { "value": "3.2", "suffix": "x", "label": "Median return on engagement" }, { "value": "90", "suffix": " days", "label": "Time to first decision" }, { "value": "12", "label": "Week average engagement" } ] } },

View File

@ -30,5 +30,5 @@
{% endwith %}
{% endif %}
</div></section>
<style>#contact-form-{{ bid }} .cm-form-grid{display:grid;grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.25rem;margin-top:2rem;}#contact-form-{{ bid }} .cm-field--full{grid-column:1 / -1;}#contact-form-{{ bid }} .cm-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 }} .cm-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 }} .cm-input:focus{outline:2px solid hsl(var(--ring));outline-offset:1px;}</style>
<style>#contact-form-{{ bid }} .cm-form-grid{display:grid;grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.25rem;margin-top:2rem;}@media (min-width:640px){#contact-form-{{ bid }} .cm-form-grid{grid-template-columns:repeat(2, minmax(0, 1fr));}}#contact-form-{{ bid }} .cm-field--full{grid-column:1 / -1;}#contact-form-{{ bid }} .cm-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 }} .cm-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 }} .cm-input:focus{outline:2px solid hsl(var(--ring));outline-offset:1px;}</style>
{% endwith %}

View File

@ -17,8 +17,8 @@
{% if pText or sText %}<div class="cm-btn-row" style="display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 2rem;">{% if pText and pUrl %}<a class="cm-btn-primary cm-btn-lg" href="{{ pUrl }}">{{ pText }}</a>{% endif %}{% if sText and sUrl %}<a class="cm-btn-outline cm-btn-lg" href="{{ sUrl }}">{{ sText }}</a>{% endif %}</div>{% endif %}
{% if trustLogos %}<div class="cm-hairline-top" style="margin-top: 3rem; padding-top: 1.5rem;"><p class="cm-kicker cm-eyebrow" style="margin-bottom: 1rem; color: hsl(var(--muted-foreground));">Trusted by</p><div class="cm-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="cm-logo" %}</a>{% else %}{% img logo.image alt=logo.alt class="cm-logo" %}{% endif %}{% endfor %}</div></div>{% endif %}
{% endmacro %}
{% if variant == "split" %}
<section class="cm-hero cm-hero-split {{ class }}" style="padding-top: 4rem; padding-bottom: 4rem;"><div class="cm-content-well"><div class="cm-grid cm-grid-2" style="align-items: center;"><div>{{ herobody() }}</div><div class="cm-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 %}">{% if backgroundImage %}{% img backgroundImage alt=headlineText class="cm-hero-media" %}{% endif %}</div></div></div></section>
{% if variant == "split" and backgroundImage %}
<section class="cm-hero cm-hero-split {{ class }}" style="padding-top: 3.5rem; padding-bottom: 3.5rem;"><div class="cm-content-well"><div class="cm-grid cm-grid-2" style="align-items: center;"><div>{{ herobody() }}</div><div class="cm-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="cm-hero-media" %}</div></div></div></section>
{% else %}
<section class="cm-hero {{ class }}" style="padding-top: 5rem; padding-bottom: 5rem; {% if variant == "left-aligned" %}text-align: left;{% else %}text-align: center;{% endif %}"><div class="cm-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="cm-hero {{ class }}" style="padding-top: 3.5rem; padding-bottom: 3.5rem; {% if variant == "left-aligned" %}text-align: left;{% else %}text-align: center;{% endif %}"><div class="cm-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 %}