From 0e54864687193c3a8e1cb61396ac50fde9282860 Mon Sep 17 00:00:00 2001 From: Alex Dunmow Date: Sun, 5 Jul 2026 17:26:40 +0800 Subject: [PATCH] fix(terminal): remediate layout/whitespace + mobile responsiveness Layout/whitespace (home page): - feature-grid rendered as 3 full-width ballooning rows because the host Tailwind scan covers rendered block HTML, not theme override .ninjatpl files, so lg:grid-cols-* utilities in overrides are purge-fragile and collapsed to one column. Move multi-column layout to plain CSS in css.input_css_append (.tty-grid*, never purged) and apply to feature-grid, stats, pricing, contact-card + a .tty-form-grid for contact-form (col-span utilities were equally fragile). - code_console / boot_log persona blocks shipped no max-width wrapper and ran edge-to-edge on the full-width landing page while sibling sections were contained; wrap both in a centred .tty-console-shell with a mobile side gutter. - CTA heading read dark-on-green (fails contrast): .terminal-heading in assets/style.css hardcoded color:foreground, overriding the band's primary-foreground. Change to color:inherit so headings on inverted bands read correctly. - Trim oversized section padding (py-16 md:py-24 -> py-12 md:py-20; the window-nested contact sections -> py-8 md:py-12). Mobile: - All multi-column grids now collapse to 1 column via CSS media queries (minmax(0,1fr) prevents long/monospace content forcing horizontal overflow); console blocks keep overflow-x scroll inside their own box. - Pin hero min-heights to CSS (.tty-hero*) since min-h-screen / arbitrary min-h-[..] utilities are also purge-fragile. Persona (phosphor TTY, window chrome, man-page, canvas hero) unchanged. No version bump. make + check-safety both exit 0. Co-Authored-By: Claude Opus 4.8 --- assets/style.css | 47 ++++++++++++++++- blocks/boot_log.ninjatpl | 2 +- blocks/code_console.ninjatpl | 2 +- manifest.yaml | 52 +++++++++++++++++++ .../overrides/terminal/contact-card.ninjatpl | 4 +- .../overrides/terminal/contact-form.ninjatpl | 8 +-- templates/overrides/terminal/cta.ninjatpl | 2 +- .../overrides/terminal/feature-grid.ninjatpl | 4 +- templates/overrides/terminal/hero.ninjatpl | 4 +- templates/overrides/terminal/pricing.ninjatpl | 4 +- templates/overrides/terminal/stats.ninjatpl | 4 +- 11 files changed, 115 insertions(+), 18 deletions(-) diff --git a/assets/style.css b/assets/style.css index fdc52f9..94862d7 100644 --- a/assets/style.css +++ b/assets/style.css @@ -23,7 +23,10 @@ font-family: var(--font-heading, "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace); text-transform: uppercase; letter-spacing: 0.08em; - color: hsl(var(--foreground)); + /* Inherit colour from context so headings placed on an inverted band + (e.g. a bg-primary CTA) read as primary-foreground, not a hardcoded + foreground that goes dark-on-bright and fails contrast. */ + color: inherit; } .terminal-mono { @@ -428,3 +431,45 @@ margin: 0 auto; font-family: var(--font-body, ui-monospace, monospace); } + +/* --- Reliable responsive grids (see manifest.yaml input_css_append: + the host scans rendered block HTML, not theme override templates, so + responsive Tailwind grid utilities in overrides are purge-fragile; + define multi-column layout in CSS instead). minmax(0,1fr) prevents + long content forcing horizontal overflow on mobile. --- */ +.tty-grid, +.tty-grid-2, +.tty-grid-3, +.tty-grid-4 { display: grid; gap: 1.5rem; grid-template-columns: repeat(1, minmax(0, 1fr)); } +@media (min-width: 640px) { + .tty-grid-2, + .tty-grid-3, + .tty-grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } +} +@media (min-width: 1024px) { + .tty-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } + .tty-grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } +} +.tty-grid-stats2, +.tty-grid-stats3, +.tty-grid-stats4 { display: grid; gap: 2rem; grid-template-columns: repeat(2, minmax(0, 1fr)); } +@media (min-width: 768px) { + .tty-grid-stats3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } + .tty-grid-stats4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } +} + +/* --- Two-column form grid (col-span utilities purge-fragile) --- */ +.tty-form-grid { display: grid; gap: 1.25rem; grid-template-columns: repeat(1, minmax(0, 1fr)); } +.tty-form-grid > .tty-col-full { grid-column: 1 / -1; } +@media (min-width: 640px) { .tty-form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } } + +/* --- Contained console shell for full-width landing pages --- */ +.tty-console-shell { width: 100%; max-width: 56rem; margin: 1.5rem auto; padding-inline: 1rem; } + +/* --- Hero heights (purge-fragile utilities pinned in CSS) --- */ +.tty-hero { position: relative; } +.tty-hero-screen { min-height: 100vh; } +.tty-hero-tall { min-height: 75vh; } +.tty-hero-half { min-height: 50vh; } +.tty-hero-auto { min-height: 24rem; } +.tty-hero-media { min-height: 20rem; } diff --git a/blocks/boot_log.ninjatpl b/blocks/boot_log.ninjatpl index f459c4e..6f8b1d9 100644 --- a/blocks/boot_log.ninjatpl +++ b/blocks/boot_log.ninjatpl @@ -1 +1 @@ -{% if lines %}
{% for line in lines %}[ {{ forloop.Counter0 }} ] {{ line }}{% endfor %}{% set cur = cursor|default:"block" %}{% if cur != "none" %}{% if cur == "underscore" %}_{% elif cur == "pipe" %}|{% else %}█{% endif %}{% endif %}
{% endif %} +{% if lines %}
{% for line in lines %}[ {{ forloop.Counter0 }} ] {{ line }}{% endfor %}{% set cur = cursor|default:"block" %}{% if cur != "none" %}{% if cur == "underscore" %}_{% elif cur == "pipe" %}|{% else %}█{% endif %}{% endif %}
{% endif %} diff --git a/blocks/code_console.ninjatpl b/blocks/code_console.ninjatpl index a42e708..99e2b7e 100644 --- a/blocks/code_console.ninjatpl +++ b/blocks/code_console.ninjatpl @@ -1 +1 @@ -{% if command or output %}
{% if command %}
{{ prompt|default:"$" }}{{ command }}
{% endif %}{% if output %}
{{ output }}
{% endif %}
{% endif %} +{% if command or output %}
{% if command %}
{{ prompt|default:"$" }}{{ command }}
{% endif %}{% if output %}
{{ output }}
{% endif %}
{% endif %} diff --git a/manifest.yaml b/manifest.yaml index b0b7ddb..6fcd716 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -458,6 +458,58 @@ css: } @media (max-width: 768px) { .terminal-article-grid { grid-template-columns: 1fr; } } + /* --- Reliable responsive grids ---------------------------- + The host scans rendered block HTML, not theme override + .ninjatpl files, so responsive Tailwind grid utilities used + only in overrides (lg:grid-cols-*, sm:grid-cols-2) are + purge-fragile and can silently collapse to one column on + desktop. Multi-column layout is therefore defined here as + plain CSS, which is compiled into the Tailwind input and can + never be purged. minmax(0,1fr) stops long/unbreakable content + from forcing horizontal overflow on mobile. */ + .tty-grid, + .tty-grid-2, + .tty-grid-3, + .tty-grid-4 { display: grid; gap: 1.5rem; grid-template-columns: repeat(1, minmax(0, 1fr)); } + @media (min-width: 640px) { + .tty-grid-2, + .tty-grid-3, + .tty-grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } + } + @media (min-width: 1024px) { + .tty-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } + .tty-grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } + } + .tty-grid-stats2, + .tty-grid-stats3, + .tty-grid-stats4 { display: grid; gap: 2rem; grid-template-columns: repeat(2, minmax(0, 1fr)); } + @media (min-width: 768px) { + .tty-grid-stats3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } + .tty-grid-stats4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } + } + + /* --- Two-column form grid (col-span utilities in overrides + are purge-fragile; pin the grid in CSS) ------------------- */ + .tty-form-grid { display: grid; gap: 1.25rem; grid-template-columns: repeat(1, minmax(0, 1fr)); } + .tty-form-grid > .tty-col-full { grid-column: 1 / -1; } + @media (min-width: 640px) { .tty-form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } } + + /* --- Contained console shell ------------------------------ + The persona code-console / boot-log blocks ship no max-width + wrapper, so on a full-width landing page they run edge-to-edge + while the neighbouring sections are contained. This shell + centres them and keeps a mobile side gutter. */ + .tty-console-shell { width: 100%; max-width: 56rem; margin: 1.5rem auto; padding-inline: 1rem; } + + /* --- Hero heights (min-h-screen / arbitrary min-h-[..] are + purge-fragile utilities; pin them in CSS) ----------------- */ + .tty-hero { position: relative; } + .tty-hero-screen { min-height: 100vh; } + .tty-hero-tall { min-height: 75vh; } + .tty-hero-half { min-height: 50vh; } + .tty-hero-auto { min-height: 24rem; } + .tty-hero-media { min-height: 20rem; } + /* --- Reduced motion: kill all motion ---------------------- */ @media (prefers-reduced-motion: reduce) { diff --git a/templates/overrides/terminal/contact-card.ninjatpl b/templates/overrides/terminal/contact-card.ninjatpl index 3cc7fa5..16f13dc 100644 --- a/templates/overrides/terminal/contact-card.ninjatpl +++ b/templates/overrides/terminal/contact-card.ninjatpl @@ -1,9 +1,9 @@ {# contact-card terminal override: neon channel panels. All field reads + control flow preserved. #} -
+
{% if heading %}

{{ heading }}

{% endif %} {% if description %}

{{ description }}

{% endif %} -
+
{% for channel in channels %}
{% if channel.icon %}{% endif %} diff --git a/templates/overrides/terminal/contact-form.ninjatpl b/templates/overrides/terminal/contact-form.ninjatpl index de44b05..5a2e0f7 100644 --- a/templates/overrides/terminal/contact-form.ninjatpl +++ b/templates/overrides/terminal/contact-form.ninjatpl @@ -1,6 +1,6 @@ {# contact-form terminal override: terminal console chrome. The {% form %} block, honeypot, field loop, form.submitted/errors reads and _block_id/bid submit wiring are preserved byte-for-byte — only presentational classes change. #} {% with bid=_block_id|default:context.blockId %} -
+
{% if form.submitted %}
@@ -12,9 +12,9 @@ {% if description %}

{{ description }}

{% endif %} {% if form.errors._form %}
{{ form.errors._form }}
{% endif %} {% with formAction="/api/blocks/"|add:bid|add:"/submit" formTarget="#contact-form-"|add:bid %} - {% form action=formAction hx_target=formTarget hx_swap="outerHTML" class="mt-8 grid grid-cols-1 gap-5 sm:grid-cols-2" %} + {% form action=formAction hx_target=formTarget hx_swap="outerHTML" class="mt-8 tty-form-grid" %} {% for field in fields %} -
+
{% if field.label %}{% endif %} {% if field.type == "textarea" %} @@ -29,7 +29,7 @@
{% endfor %} -
+
{% endform %} diff --git a/templates/overrides/terminal/cta.ninjatpl b/templates/overrides/terminal/cta.ninjatpl index db7643b..8f1bbd2 100644 --- a/templates/overrides/terminal/cta.ninjatpl +++ b/templates/overrides/terminal/cta.ninjatpl @@ -1,5 +1,5 @@ {# cta terminal override: scanline overlay, heading, CTAs. All field reads preserved. #} -
+
diff --git a/templates/overrides/terminal/feature-grid.ninjatpl b/templates/overrides/terminal/feature-grid.ninjatpl index 638981e..2a1a60f 100644 --- a/templates/overrides/terminal/feature-grid.ninjatpl +++ b/templates/overrides/terminal/feature-grid.ninjatpl @@ -1,11 +1,11 @@ {# feature_grid terminal override: scanline hero overlay, mono eyebrow, heading, neon HUD cards. All field reads + control flow preserved. #} -
+
{% if eyebrow %}

{{ eyebrow }}

{% endif %} {% if heading %}

{{ heading }}

{% endif %} {% if intro %}

{{ intro }}

{% endif %} -
+
{% for item in items %}
{% if item.icon %}
{% endif %} diff --git a/templates/overrides/terminal/hero.ninjatpl b/templates/overrides/terminal/hero.ninjatpl index 7135fb1..66f81f9 100644 --- a/templates/overrides/terminal/hero.ninjatpl +++ b/templates/overrides/terminal/hero.ninjatpl @@ -26,13 +26,13 @@ {% endmacro %} {% if variant == "split" %}
-
{% if hasImage %}{% img backgroundImage alt=headlineText class="absolute inset-0 h-full w-full object-cover" layout="hero" %}{% else %}
{% endif %}
+
{% if hasImage %}{% img backgroundImage alt=headlineText class="absolute inset-0 h-full w-full object-cover" layout="hero" %}{% else %}
{% endif %}
{{ herocontent("left") }}
{% else %} {% set align = "center" %} {% if variant == "left-aligned" %}{% set align = "left" %}{% endif %} -
+
{% if hasImage %}
{% img backgroundImage alt=headlineText class="h-full w-full object-cover" layout="hero" %}{% if showOverlay %}
{% endif %}
{% else %}
{% endif %}
{{ herocontent(align) }}
diff --git a/templates/overrides/terminal/pricing.ninjatpl b/templates/overrides/terminal/pricing.ninjatpl index 108923b..0b6eb6b 100644 --- a/templates/overrides/terminal/pricing.ninjatpl +++ b/templates/overrides/terminal/pricing.ninjatpl @@ -1,10 +1,10 @@ {# pricing terminal override: scanline overlay, heading, HUD neon tier panels (magenta highlight / cyan standard), CTAs. Field reads + control flow preserved. #} -
+
{% if heading %}

{{ heading }}

{% endif %} {% if intro %}

{{ intro }}

{% endif %} -
+
{% for tier in tiers %}
{% if tier.badge %}{{ tier.badge }}{% endif %} diff --git a/templates/overrides/terminal/stats.ninjatpl b/templates/overrides/terminal/stats.ninjatpl index a7050e1..6591b88 100644 --- a/templates/overrides/terminal/stats.ninjatpl +++ b/templates/overrides/terminal/stats.ninjatpl @@ -1,10 +1,10 @@ {# stats terminal override: scanline overlay, heading, HUD stat tiles, mono labels. data-stats + countUp script/attrs preserved byte-for-byte. #} -
+
{% if heading %}

{{ heading }}

{% endif %} {% if intro %}

{{ intro }}

{% endif %} -
+
{% for item in items %}
{% if item.prefix %}{{ item.prefix }}{% endif %}{{ item.value }}{% if item.suffix %}{{ item.suffix }}{% endif %}