fix(brutalist): remediate layout/whitespace + mobile responsiveness
The host public CSS is a render-driven JIT (`@import "tailwindcss" source(none)`); a runtime-installed codeless theme lives in data/plugins, which is in NO @source glob, so its .ninjatpl utility classes only compile if a built-in emits the exact string or the class is safelisted. Non- safelisted families were silently dropping on a fresh provision, causing: feature-grid rendering full-width instead of 3 columns (lg:grid-cols-3 absent), card/section content crammed against borders (p-* shorthand absent), and touching grid items (gap-6 absent). - manifest.yaml: add a "layout guarantee layer" of raw CSS scoped to .brutalist-page (compiled unconditionally via input_css_append) defining grid-cols/col-span/columns + all sm/md/lg responsive variants, p-* shorthand, gap-*, space-y-*, aspect-*, object-*, w/h-full. Deterministic columns on desktop AND 1-col collapse on mobile via real media queries. - manifest.yaml: cut excessive .bru-section padding (6rem->4rem desktop, 4rem->2.75rem mobile) to remove the oversized dead bands between sections. - project_ledger: was a bare <section> with no wrap -> rendered full-bleed (rows touched the viewport edges). Wrap in bru-section + bru-wrap; add an optional heading field (parity with changelog/spec_sheet). - navbar: move mobile drawer + overlay geometry (fixed/top/right/h-full/ w-80/max-w-[85vw]/inset-0/z) into the inline <style> so the non-safelisted positioning classes can't leave the drawer unpositioned; display stays on safelisted flex/md:hidden. Neutral rgba scrim. - image: guard empty src so absent media collapses instead of an empty bordered box (empty-media graceful state). Persona (concrete, chunky borders, hard offset shadows, candy accents) unchanged. No .reveal/scroll-hide pattern in this theme. Version not bumped. make build, ninja plugin verify, and check-safety all exit 0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
2adea9ef22
commit
50f58205f0
@ -1,3 +1,6 @@
|
||||
<section data-block="brutalist:project_ledger">
|
||||
<section class="bru-section bg-background text-foreground" data-block="brutalist:project_ledger">
|
||||
<div class="bru-wrap">
|
||||
{% if heading %}<h2 class="bru-kicker mb-8" style="font-size:clamp(1.5rem,4vw,2.75rem)">{{ heading }}</h2>{% endif %}
|
||||
{% if not rows %}<p class="brutalist-mono" style="padding: 1rem 0; color: hsl(var(--muted-foreground));">No projects listed.</p>{% else %}<ol style="list-style: none; padding: 0; margin: 0;">{% for row in rows %}<li class="brutalist-ledger-row"><span class="ledger-no">{{ row.no }}</span><span class="ledger-year">{{ row.year }}</span><span class="ledger-client">{% if row.link %}<a href="{{ row.link }}" style="color: inherit; text-decoration: none;">{{ row.client }}</a>{% else %}{{ row.client }}{% endif %}</span><span class="ledger-role">{{ row.role }}</span><span class="ledger-arrow">{% if row.link %}<a href="{{ row.link }}" aria-label="View project" style="color: inherit; text-decoration: none;">→</a>{% endif %}</span></li>{% endfor %}</ol>{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -4,6 +4,12 @@
|
||||
"description": "Ordered project list on a 12-col grid: number, year, client, role, link",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"heading": {
|
||||
"type": "string",
|
||||
"title": "Heading",
|
||||
"description": "Optional section heading shown above the ledger",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"rows": {
|
||||
"type": "array",
|
||||
"title": "Rows",
|
||||
|
||||
121
manifest.yaml
121
manifest.yaml
@ -475,8 +475,8 @@ css:
|
||||
via hsl(var(--token)); all type via var(--font-*).
|
||||
============================================================ */
|
||||
|
||||
.bru-section { padding: 4rem 0; }
|
||||
@media (min-width: 768px) { .bru-section { padding: 6rem 0; } }
|
||||
.bru-section { padding: 2.75rem 0; }
|
||||
@media (min-width: 768px) { .bru-section { padding: 4rem 0; } }
|
||||
.bru-wrap { width: 100%; max-width: 80rem; margin: 0 auto; padding: 0 1.5rem; }
|
||||
.bru-wrap-mid { width: 100%; max-width: 64rem; margin: 0 auto; padding: 0 1.5rem; }
|
||||
.bru-wrap-narrow { width: 100%; max-width: 44rem; margin: 0 auto; padding: 0 1.5rem; }
|
||||
@ -647,3 +647,120 @@ css:
|
||||
.bru-spec-row dt { text-transform: uppercase; color: hsl(var(--muted-foreground)); }
|
||||
.bru-spec-row dd { margin: 0; text-align: right; color: hsl(var(--foreground)); }
|
||||
|
||||
/* ============================================================
|
||||
LAYOUT GUARANTEE LAYER — the host's public CSS is a render-driven
|
||||
JIT built with `@import "tailwindcss" source(none)`. A codeless
|
||||
theme installs to data/plugins, which is NOT in any @source glob,
|
||||
so its .ninjatpl utility classes only reach the compiled CSS if a
|
||||
built-in template emits the exact string or the class is in the
|
||||
host safelist. These families are NOT safelisted and were silently
|
||||
dropping on a fresh provision (collapsed grids, zero padding,
|
||||
touching items): grid-cols-{1,2,4,5}, every responsive grid
|
||||
variant, col-span-*, the p-* shorthand, gap-{3..12}/gap-x/gap-y,
|
||||
space-y-*, columns-*, aspect-*, object-*, w/h-full. Define them
|
||||
here (input_css_append is compiled UNCONDITIONALLY) so every block
|
||||
lays out deterministically on desktop AND collapses on mobile.
|
||||
Scoped to .brutalist-page; structural only — no colours. Values
|
||||
mirror Tailwind's defaults so any host-compiled copy is identical.
|
||||
============================================================ */
|
||||
|
||||
.brutalist-page .grid { display: grid; }
|
||||
.brutalist-page .grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
||||
.brutalist-page .grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.brutalist-page .grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
.brutalist-page .grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||
.brutalist-page .grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
||||
.brutalist-page .col-span-2 { grid-column: span 2 / span 2; }
|
||||
.brutalist-page .col-span-3 { grid-column: span 3 / span 3; }
|
||||
.brutalist-page .columns-2 { columns: 2; }
|
||||
|
||||
/* Padding shorthand (px-*/py-* are safelisted; p-* is not). */
|
||||
.brutalist-page .p-0 { padding: 0; }
|
||||
.brutalist-page .p-1 { padding: 0.25rem; }
|
||||
.brutalist-page .p-2 { padding: 0.5rem; }
|
||||
.brutalist-page .p-3 { padding: 0.75rem; }
|
||||
.brutalist-page .p-4 { padding: 1rem; }
|
||||
.brutalist-page .p-5 { padding: 1.25rem; }
|
||||
.brutalist-page .p-6 { padding: 1.5rem; }
|
||||
.brutalist-page .p-8 { padding: 2rem; }
|
||||
.brutalist-page .p-16 { padding: 4rem; }
|
||||
|
||||
/* Gaps (only gap-1/1.5/2 are safelisted). */
|
||||
.brutalist-page .gap-0 { gap: 0; }
|
||||
.brutalist-page .gap-3 { gap: 0.75rem; }
|
||||
.brutalist-page .gap-4 { gap: 1rem; }
|
||||
.brutalist-page .gap-5 { gap: 1.25rem; }
|
||||
.brutalist-page .gap-6 { gap: 1.5rem; }
|
||||
.brutalist-page .gap-8 { gap: 2rem; }
|
||||
.brutalist-page .gap-10 { gap: 2.5rem; }
|
||||
.brutalist-page .gap-12 { gap: 3rem; }
|
||||
.brutalist-page .gap-x-2 { column-gap: 0.5rem; }
|
||||
.brutalist-page .gap-x-3 { column-gap: 0.75rem; }
|
||||
.brutalist-page .gap-x-4 { column-gap: 1rem; }
|
||||
.brutalist-page .gap-x-10 { column-gap: 2.5rem; }
|
||||
.brutalist-page .gap-y-1 { row-gap: 0.25rem; }
|
||||
.brutalist-page .gap-y-2 { row-gap: 0.5rem; }
|
||||
.brutalist-page .gap-y-6 { row-gap: 1.5rem; }
|
||||
|
||||
/* Vertical rhythm (space-y-*). */
|
||||
.brutalist-page .space-y-0 > * + * { margin-top: 0; }
|
||||
.brutalist-page .space-y-1 > * + * { margin-top: 0.25rem; }
|
||||
.brutalist-page .space-y-2 > * + * { margin-top: 0.5rem; }
|
||||
.brutalist-page .space-y-3 > * + * { margin-top: 0.75rem; }
|
||||
.brutalist-page .space-y-4 > * + * { margin-top: 1rem; }
|
||||
.brutalist-page .space-y-5 > * + * { margin-top: 1.25rem; }
|
||||
.brutalist-page .space-y-6 > * + * { margin-top: 1.5rem; }
|
||||
.brutalist-page .space-y-8 > * + * { margin-top: 2rem; }
|
||||
.brutalist-page .space-y-10 > * + * { margin-top: 2.5rem; }
|
||||
.brutalist-page .space-y-12 > * + * { margin-top: 3rem; }
|
||||
|
||||
/* Sizing used for media fill + logos. */
|
||||
.brutalist-page .w-full { width: 100%; }
|
||||
.brutalist-page .h-full { height: 100%; }
|
||||
.brutalist-page .w-auto { width: auto; }
|
||||
.brutalist-page .h-auto { height: auto; }
|
||||
|
||||
/* Media boxes: a fixed aspect ratio so ABSENT/empty media can never
|
||||
balloon into a tall empty box — it stays a compact bordered tile. */
|
||||
.brutalist-page .aspect-video { aspect-ratio: 16 / 9; }
|
||||
.brutalist-page .aspect-square { aspect-ratio: 1 / 1; }
|
||||
.brutalist-page .aspect-\[3\/4\] { aspect-ratio: 3 / 4; }
|
||||
.brutalist-page .aspect-\[4\/3\] { aspect-ratio: 4 / 3; }
|
||||
.brutalist-page .aspect-\[21\/9\] { aspect-ratio: 21 / 9; }
|
||||
.brutalist-page .object-cover { object-fit: cover; }
|
||||
.brutalist-page .object-contain { object-fit: contain; }
|
||||
.brutalist-page .object-fill { object-fit: fill; }
|
||||
.brutalist-page img { max-width: 100%; }
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.brutalist-page .sm\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.brutalist-page .sm\:col-span-1 { grid-column: span 1 / span 1; }
|
||||
.brutalist-page .sm\:col-span-2 { grid-column: span 2 / span 2; }
|
||||
.brutalist-page .sm\:gap-6 { gap: 1.5rem; }
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.brutalist-page .md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.brutalist-page .md\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
.brutalist-page .md\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||
.brutalist-page .md\:col-span-3 { grid-column: span 3 / span 3; }
|
||||
.brutalist-page .md\:columns-2 { columns: 2; }
|
||||
.brutalist-page .md\:columns-3 { columns: 3; }
|
||||
.brutalist-page .md\:columns-4 { columns: 4; }
|
||||
.brutalist-page .md\:gap-16 { gap: 4rem; }
|
||||
.brutalist-page .md\:p-6 { padding: 1.5rem; }
|
||||
.brutalist-page .md\:p-8 { padding: 2rem; }
|
||||
.brutalist-page .md\:p-14 { padding: 3.5rem; }
|
||||
.brutalist-page .md\:px-10 { padding-left: 2.5rem; padding-right: 2.5rem; }
|
||||
.brutalist-page .md\:py-14 { padding-top: 3.5rem; padding-bottom: 3.5rem; }
|
||||
.brutalist-page .md\:py-16 { padding-top: 4rem; padding-bottom: 4rem; }
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.brutalist-page .lg\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.brutalist-page .lg\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
.brutalist-page .lg\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||
.brutalist-page .lg\:grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
||||
.brutalist-page .lg\:col-span-1 { grid-column: span 1 / span 1; }
|
||||
.brutalist-page .lg\:col-span-2 { grid-column: span 2 / span 2; }
|
||||
.brutalist-page .lg\:col-span-3 { grid-column: span 3 / span 3; }
|
||||
}
|
||||
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
{# brutalist image override — hard-bordered figure, mono caption. Fields: src, alt, size, align, aspectRatio, objectFit, caption. Rounding is killed page-wide. #}
|
||||
{% with size=size|default:"full" align=align|default:"center" aspectRatio=aspectRatio|default:"auto" objectFit=objectFit|default:"cover" %}<figure class="flex-1 {% if class %}{{ class }} {% endif %}{% if align == "left" %}flex justify-start{% elif align == "right" %}flex justify-end{% else %}flex justify-center{% endif %}"><div class="bru-slab bru-shadow relative overflow-hidden {% if size == "xs" %}max-w-[200px] w-full{% elif size == "sm" %}max-w-[400px] w-full{% elif size == "md" %}max-w-[600px] w-full{% elif size == "lg" %}max-w-[800px] w-full{% else %}w-full{% endif %}{% if aspectRatio == "square" %} aspect-square{% elif aspectRatio == "video" %} aspect-video{% elif aspectRatio == "portrait" %} aspect-[3/4]{% elif aspectRatio == "wide" %} aspect-[21/9]{% endif %}">{% if objectFit == "contain" %}{% img src alt=alt class="w-full h-full object-contain" layout=size %}{% elif objectFit == "fill" %}{% img src alt=alt class="w-full h-full object-fill" layout=size %}{% else %}{% img src alt=alt class="w-full h-full object-cover" layout=size %}{% endif %}</div>{% if caption %}<figcaption class="bru-meta mt-3">{{ caption }}</figcaption>{% endif %}</figure>{% endwith %}
|
||||
{% with size=size|default:"full" align=align|default:"center" aspectRatio=aspectRatio|default:"auto" objectFit=objectFit|default:"cover" %}{% if src %}<figure class="flex-1 {% if class %}{{ class }} {% endif %}{% if align == "left" %}flex justify-start{% elif align == "right" %}flex justify-end{% else %}flex justify-center{% endif %}"><div class="bru-slab bru-shadow relative overflow-hidden {% if size == "xs" %}max-w-[200px] w-full{% elif size == "sm" %}max-w-[400px] w-full{% elif size == "md" %}max-w-[600px] w-full{% elif size == "lg" %}max-w-[800px] w-full{% else %}w-full{% endif %}{% if aspectRatio == "square" %} aspect-square{% elif aspectRatio == "video" %} aspect-video{% elif aspectRatio == "portrait" %} aspect-[3/4]{% elif aspectRatio == "wide" %} aspect-[21/9]{% endif %}">{% if objectFit == "contain" %}{% img src alt=alt class="w-full h-full object-contain" layout=size %}{% elif objectFit == "fill" %}{% img src alt=alt class="w-full h-full object-fill" layout=size %}{% else %}{% img src alt=alt class="w-full h-full object-cover" layout=size %}{% endif %}</div>{% if caption %}<figcaption class="bru-meta mt-3">{{ caption }}</figcaption>{% endif %}</figure>{% endif %}{% endwith %}
|
||||
|
||||
@ -23,8 +23,13 @@
|
||||
</nav>
|
||||
<style>
|
||||
.bn-navbar-toggle{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;}
|
||||
.bn-navbar-drawer{transform:translateX(100%);transition:transform .3s ease;}
|
||||
.bn-navbar-overlay{opacity:0;pointer-events:none;transition:opacity .3s ease;}
|
||||
/* Drawer + overlay geometry lives here, not on Tailwind classes: fixed/top-0/
|
||||
right-0/h-full/w-80/max-w-[85vw]/inset-0/z-* are not in the host safelist and
|
||||
a runtime-installed theme's classes aren't JIT-scanned, so relying on them
|
||||
would leave the mobile drawer unpositioned. display stays on the safelisted
|
||||
flex/flex-col/md:hidden classes so the drawer is desktop-hidden. */
|
||||
.bn-navbar-drawer{position:fixed;top:0;right:0;height:100%;width:20rem;max-width:85vw;z-index:50;transform:translateX(100%);transition:transform .3s ease;}
|
||||
.bn-navbar-overlay{position:fixed;inset:0;z-index:40;background:rgba(0,0,0,0.55);opacity:0;pointer-events:none;transition:opacity .3s ease;}
|
||||
.bn-navbar-toggle:checked ~ .bn-navbar-drawer{transform:translateX(0);}
|
||||
.bn-navbar-toggle:checked ~ .bn-navbar-overlay{opacity:1;pointer-events:auto;}
|
||||
.bn-navbar-drawer details > summary::-webkit-details-marker{display:none;}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user