{% 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 @@