From 0b3013ce81aee3172ec7163c6994b2dd5922187a Mon Sep 17 00:00:00 2001 From: Alex Dunmow Date: Sun, 5 Jul 2026 17:40:19 +0800 Subject: [PATCH] fix(theme): drive responsive layout from theme-owned CSS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Host Tailwind JIT does not scan theme templates, so responsive grid/hero utilities (sm:/md:/lg:grid-cols-*, min-h-screen, arbitrary values) are dropped and multi-column layouts collapse to one column. Ship the load-bearing responsive layout as compiled theme CSS and point the override templates at it. Layout only — colours stay on semantic tokens. Co-Authored-By: Claude Opus 4.8 --- manifest.yaml | 49 +++++++++++++++++++ templates/noir/default.ninjatpl | 7 ++- templates/noir/landing.ninjatpl | 9 ++-- templates/overrides/noir/blog-hero.ninjatpl | 2 +- .../overrides/noir/contact-card.ninjatpl | 2 +- templates/overrides/noir/cta.ninjatpl | 2 +- templates/overrides/noir/event-list.ninjatpl | 2 +- .../overrides/noir/feature-grid.ninjatpl | 6 +-- .../overrides/noir/featured-posts.ninjatpl | 2 +- templates/overrides/noir/gallery.ninjatpl | 2 +- templates/overrides/noir/hero.ninjatpl | 8 +-- .../overrides/noir/hours-location.ninjatpl | 2 +- templates/overrides/noir/menu-list.ninjatpl | 2 +- templates/overrides/noir/pricing.ninjatpl | 2 +- .../overrides/noir/related-posts.ninjatpl | 2 +- .../overrides/noir/rich-section.ninjatpl | 2 +- templates/overrides/noir/stats.ninjatpl | 2 +- templates/overrides/noir/team.ninjatpl | 2 +- templates/overrides/noir/testimonial.ninjatpl | 2 +- templates/overrides/noir/timeline.ninjatpl | 2 +- 20 files changed, 82 insertions(+), 27 deletions(-) diff --git a/manifest.yaml b/manifest.yaml index 495d02e..e8f6f6b 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -338,3 +338,52 @@ css: .noir-redact { transition: none; } } } + + /* ============================================================ + Explicit responsive layout (authored CSS — NOT purged). + The host Tailwind JIT does not reliably scan a theme's override + templates, so sm:/md:/lg: grid variants declared there can be + dropped from the build (a grid then never expands on desktop + and never collapses on mobile). These media-query rules own the + column counts deterministically and always collapse to a single + column on phones. + ============================================================ */ + .noir-cols { display: grid; grid-template-columns: 1fr; gap: 1.5rem; } + @media (min-width: 640px) { + .noir-cols-2, .noir-cols-3, .noir-cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } + } + @media (min-width: 1024px) { + .noir-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } + .noir-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } + } + /* Grids that stay two-up on phones (galleries, stat rows, footer links). */ + .noir-cols-2up { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.5rem; } + @media (min-width: 768px) { + .noir-cols-2up-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } + .noir-cols-2up-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } + } + /* Media/text split — one column on phones, two from the tablet width. */ + .noir-split { display: grid; grid-template-columns: 1fr; gap: 2.5rem; } + @media (min-width: 768px) { .noir-split { grid-template-columns: repeat(2, minmax(0, 1fr)); } } + /* Gap modifiers (used alongside any grid class above). */ + .noir-gap-sm { gap: 0.75rem; } + .noir-gap-lg { gap: 2rem; } + + /* Hero vertical rhythm — replaces the unreliable min-h-[75vh] arbitrary + class so the masthead has bounded, predictable breathing room instead + of collapsing to nothing or ballooning to 75vh of empty space. */ + .noir-hero-pad { padding-top: 3.5rem; padding-bottom: 3.5rem; } + @media (min-width: 768px) { .noir-hero-pad { padding-top: 5rem; padding-bottom: 5rem; } } + @media (min-width: 1024px) { .noir-hero-pad { padding-top: 6.5rem; padding-bottom: 6.5rem; } } + + /* Empty-media graceful state — the demo seed ships no images, so any + media frame that renders an empty must not balloon into a grey + box or show broken-image chrome. Images never exceed their column. */ + .noir-file img, .bn-card img, .bn-gallery img, figure img { max-width: 100%; height: auto; } + img[src=""], img:not([src]) { display: none; } + + /* Overflow safety — long typed/mono labels wrap rather than push the + page wider than a phone viewport (uppercase + wide tracking is the risk). */ + .noir-label, .noir-mono, .noir-heading { overflow-wrap: break-word; } + /* Never let a block force a horizontal scrollbar on small screens. */ + [data-block-override], [data-block] { max-width: 100%; } diff --git a/templates/noir/default.ninjatpl b/templates/noir/default.ninjatpl index 29aceb0..db28cea 100644 --- a/templates/noir/default.ninjatpl +++ b/templates/noir/default.ninjatpl @@ -22,8 +22,11 @@ var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches; var els=document.querySelectorAll('.noir-reveal'); if(reduce||!('IntersectionObserver' in window)){els.forEach(function(el){el.classList.add('noir-in');});return;} - var io=new IntersectionObserver(function(entries){entries.forEach(function(e){if(e.isIntersecting){e.target.classList.add('noir-in');io.unobserve(e.target);}});},{threshold:0.12}); - els.forEach(function(el){io.observe(el);}); + var io=new IntersectionObserver(function(entries){entries.forEach(function(e){if(e.isIntersecting){e.target.classList.add('noir-in');io.unobserve(e.target);}});},{threshold:0,rootMargin:'0px 0px -8% 0px'}); + function showAll(){els.forEach(function(el){el.classList.add('noir-in');});} + els.forEach(function(el){var r=el.getBoundingClientRect();var h=window.innerHeight||document.documentElement.clientHeight||0;if(r.top0){el.classList.add('noir-in');}else{io.observe(el);}}); + window.addEventListener('load',showAll); + setTimeout(showAll,1600); })(); diff --git a/templates/noir/landing.ninjatpl b/templates/noir/landing.ninjatpl index cdaee02..c19feb7 100644 --- a/templates/noir/landing.ninjatpl +++ b/templates/noir/landing.ninjatpl @@ -5,7 +5,7 @@ {{ admin_banner_html|safe }}
{{ slots.hero|safe }}
- {% if slots.main %}
{{ slots.main|safe }}
{% endif %} + {% if slots.main %}
{{ slots.main|safe }}
{% endif %}
{{ slots.cta|safe }}