themes-magazine-bold/manifest.yaml
Alex Dunmow 921fa33f6b fix(magazine-bold): remediate layout/whitespace + mobile responsiveness
Layout/whitespace:
- cover_story: fix hero deck ragged one-word-per-line wrap (44px text-deck in
  a narrow 3-col rail) via a right-sized inline standfirst; keep proven grid
  spans; trim doubled vertical padding.
- card: story titles were rendered by .mb-caps at 0.72rem tracked mono-caps,
  reading as cramped tiny strips; restore a real Archivo Black headline size.
- newsletter-signup: replace the twin stray dash artifact (two mb-rule bars)
  with a single fluoro accent bar; tighten padding.
- landing: constrain the story river to a cohesive editorial column (mb-river)
  instead of full 80rem (killed the dead space beside text-only cards); trim
  stacked section padding across lead/river/drop_calendar/lookbook/newsletter.

Empty-media graceful states (demo seed ships no images):
- hero (split): stop rendering the min-h-[320px] bg-muted half-panel when no
  backgroundImage; text takes full width instead.
- video-embed: gate the whole aspect frame on url|media (was a full empty box
  with a dead play button).
- image: guard the sizing box on src so an absent image collapses.
- logo-strip / hero trustLogos: defensive {% if logo.image %} guards.

Mobile responsiveness (JIT does not reliably scan theme .ninjatpl, so
layout-critical responsive rules are literal manifest CSS, not theme utility
classes):
- text-folio: break-word + lower clamp floor (54px) to prevent horizontal
  overflow of giant headlines at ~375px.
- lookbook grid + river column driven by guaranteed .mb-grid-resp/.mb-river.
- stats: drop divide-x (spurious divider on row-wrap on mobile).

Scroll-reveal hardening (.mb-reveal was opacity:0 with the observer script only
in cover_story, so lookbook on /about stayed permanently hidden; .no-js
fallback was dead): default visible, JS opts in to hide-then-reveal, safety-net
full reveal on timeout + window load, reduced-motion/no-JS/no-observer all show.

No version bump. Build + verify + check-safety all exit 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:27:53 +08:00

482 lines
19 KiB
YAML

# Magazine Bold theme — codeless .bnp manifest (Theme Fleet Wave A, WO-TF-021).
# Synthesized into manifest.pb by `ninja plugin build` (no plugin.wasm).
#
# Persona: a streetwear label / fashion-drop publication. Print-cover energy
# translated to drops and lookbooks — massive condensed uppercase headlines
# (Archivo Black), duotone imagery, grid-breaking overlap, mono datelines and
# fluoro accent slabs. Newsprint-plus-voltage by day, ink-black-plus-fluoro by
# night. Colour ALWAYS via hsl(var(--token)); font families ALWAYS via
# var(--font-heading|body|mono, <fallback>). No literal colours, no hardcoded
# families. Motion tier MID: JS effects (marquee, scroll reveal, duotone hover),
# no canvas, prefers-reduced-motion honored with static fallbacks.
theme_presets: presets.json
bundled_fonts: fonts.json
master_pages: master_pages.json
system_templates:
- key: magazine-bold
title: Magazine Bold
description: "A streetwear label's cover-story chrome: massive condensed uppercase headlines, duotone imagery, grid-breaking overlap and fluoro accent slabs. Built for fashion drops, lookbooks and culture publications."
page_templates:
- system: magazine-bold
key: default
title: Default
description: "Standard page: masthead, single reading column, colophon footer."
slots: [header, main, footer]
- system: magazine-bold
key: full-width
title: Full Bleed
description: "Edge-to-edge for lookbooks, cover images and photo essays."
slots: [header, main, footer]
- system: magazine-bold
key: landing
title: Cover / Drop
description: "Cover-story stack: masthead, lead drop, river of stories, colophon."
slots: [masthead, lead, river, footer]
- system: magazine-bold
key: article
title: Feature
description: "Long-form feature: byline, drop-cap opener, reading column with a marginalia rail."
slots: [header, byline, main, marginalia, footer]
- system: magazine-bold
key: blog-index
title: Archive
description: "Drop archive: masthead, featured drop, and a river of stories."
slots: [header, featured, main, footer]
- system: magazine-bold
key: contact
title: Studio
description: "Contact layout with an optional aside for stockists, hours and location."
slots: [header, main, aside, footer]
- system: magazine-bold
key: auth
title: Members
description: "Centred members shell with wordmark and a fluoro rule."
slots: [main, footer]
template_overrides:
# Chrome (4)
- { template: magazine-bold, block: navbar }
- { template: magazine-bold, block: footer }
- { template: magazine-bold, block: utility-bar }
- { template: magazine-bold, block: announcement-bar }
# Marketing (12)
- { template: magazine-bold, block: hero }
- { template: magazine-bold, block: feature-grid }
- { template: magazine-bold, block: rich-section }
- { template: magazine-bold, block: stats }
- { template: magazine-bold, block: testimonial }
- { template: magazine-bold, block: logo-strip }
- { template: magazine-bold, block: pricing }
- { template: magazine-bold, block: team }
- { template: magazine-bold, block: faq }
- { template: magazine-bold, block: timeline }
- { template: magazine-bold, block: cta }
- { template: magazine-bold, block: countdown }
# Primitives (11)
- { template: magazine-bold, block: heading }
- { template: magazine-bold, block: text }
- { template: magazine-bold, block: rich-text }
- { template: magazine-bold, block: image }
- { template: magazine-bold, block: quote }
- { template: magazine-bold, block: divider }
- { template: magazine-bold, block: button }
- { template: magazine-bold, block: card }
- { template: magazine-bold, block: gallery }
- { template: magazine-bold, block: video-embed }
- { template: magazine-bold, block: audio-embed }
# Commerce / local (6)
- { template: magazine-bold, block: menu-list }
- { template: magazine-bold, block: event-list }
- { template: magazine-bold, block: contact-form }
- { template: magazine-bold, block: contact-card }
- { template: magazine-bold, block: hours-location }
- { template: magazine-bold, block: social-links }
# Blog family (12)
- { template: magazine-bold, block: blog-hero }
- { template: magazine-bold, block: featured-posts }
- { template: magazine-bold, block: related-posts }
- { template: magazine-bold, block: popular-posts }
- { template: magazine-bold, block: category-list }
- { template: magazine-bold, block: post-hero }
- { template: magazine-bold, block: post-metadata }
- { template: magazine-bold, block: author-bio }
- { template: magazine-bold, block: author-bio-hero }
- { template: magazine-bold, block: newsletter-signup }
- { template: magazine-bold, block: breadcrumbs }
- { template: magazine-bold, block: sidebar-nav }
# Auth surface (3)
- { template: magazine-bold, block: auth-form }
- { template: magazine-bold, block: password-reset-form }
- { template: magazine-bold, block: auth-status }
# System (1)
- { template: magazine-bold, block: page-suggestions }
email_wrappers:
- magazine-bold
css:
input_css_append: |
/* === Magazine Bold theme — utility layer ===
*
* Streetwear / fashion-drop identity: massive condensed uppercase headlines,
* heavy 2px rules, hard square edges, mono datelines and fluoro accent slabs.
* Colour is ALWAYS consumed via hsl(var(--token)); font families ALWAYS via
* var(--font-heading|body|mono, <fallback>). No literal colours, no hardcoded
* families.
*/
@layer utilities {
:root {
--mb-measure: 68ch;
--mb-rule: 2px solid hsl(var(--foreground));
}
/* --- Font slots (consume CMS-managed font variables) --- */
.font-display { font-family: var(--font-heading, "Archivo Black", "Arial Black", sans-serif); }
.font-sans { font-family: var(--font-body, "Inter", system-ui, sans-serif); }
.font-mono { font-family: var(--font-mono, "IBM Plex Mono", ui-monospace, monospace); }
/* --- Page widths / reading measures --- */
.mb-column {
max-width: var(--mb-measure);
margin-left: auto; margin-right: auto;
padding-left: 1rem; padding-right: 1rem;
}
.mb-wide {
max-width: 80rem;
margin-left: auto; margin-right: auto;
padding-left: 1rem; padding-right: 1rem;
}
.mb-article-grid {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
max-width: 84rem;
margin-left: auto; margin-right: auto;
padding-left: 1rem; padding-right: 1rem;
}
@media (min-width: 1024px) {
.mb-article-grid { grid-template-columns: minmax(0, 68ch) 18rem; }
}
/* --- Prose reading column --- */
.mb-prose {
max-width: var(--mb-measure);
margin-left: auto; margin-right: auto;
font-family: var(--font-body, "Inter", system-ui, sans-serif);
color: hsl(var(--foreground));
line-height: 1.6;
}
.mb-prose p { margin: 0 0 1.15em 0; }
.mb-prose h1, .mb-prose h2, .mb-prose h3, .mb-prose h4 {
font-family: var(--font-heading, "Archivo Black", "Arial Black", sans-serif);
text-transform: uppercase;
letter-spacing: -0.01em;
line-height: 1.0;
color: hsl(var(--foreground));
}
.mb-prose a {
color: hsl(var(--accent));
text-decoration: underline;
text-decoration-thickness: 2px;
text-underline-offset: 2px;
}
.mb-prose blockquote {
font-weight: 500;
border-left: 4px solid hsl(var(--accent));
padding-left: 1rem; margin-left: 0;
color: hsl(var(--foreground));
}
/* --- Mono / tracked labels (kickers, eyebrows, meta) --- */
.mb-caps {
font-family: var(--font-mono, "IBM Plex Mono", ui-monospace, monospace);
text-transform: uppercase;
letter-spacing: 0.14em;
font-weight: 500;
font-size: 0.72rem;
line-height: 1.35;
}
/* --- Display heading (Archivo Black, massive condensed caps) --- */
.mb-heading {
font-family: var(--font-heading, "Archivo Black", "Arial Black", sans-serif);
text-transform: uppercase;
letter-spacing: -0.02em;
line-height: 0.95;
color: hsl(var(--foreground));
}
.mb-underline { position: relative; padding-bottom: 0.28em; }
.mb-underline::after {
content: "";
position: absolute; left: 0; right: 0; bottom: 0;
height: 4px;
background: hsl(var(--accent));
}
/* --- Heavy rules (bars, not hairlines) --- */
hr.mb-rule, .mb-rule, .mb-hairline {
border: 0;
border-top: var(--mb-rule);
margin: 0;
}
.mb-hairline-top { border-top: var(--mb-rule); }
.mb-hairline-bottom { border-bottom: var(--mb-rule); }
/* --- Body text override --- */
.mb-body {
font-family: var(--font-body, "Inter", system-ui, sans-serif);
color: hsl(var(--foreground));
line-height: 1.6;
}
/* --- Inline accent link --- */
.mb-link {
color: hsl(var(--accent));
text-decoration: underline;
text-decoration-thickness: 2px;
text-underline-offset: 3px;
transition: color 120ms ease, text-decoration-color 120ms ease;
}
.mb-link:hover, .mb-link:focus-visible { color: hsl(var(--foreground)); }
/* --- Button — fluoro / ink slab, square, uppercase mono --- */
.mb-button, .mb-btn {
display: inline-block;
padding: 0.7rem 1.4rem;
font-family: var(--font-mono, "IBM Plex Mono", ui-monospace, monospace);
font-size: 0.72rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.14em;
background-color: hsl(var(--foreground));
color: hsl(var(--background));
border: 2px solid hsl(var(--foreground));
text-decoration: none;
cursor: pointer;
transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}
.mb-button:hover, .mb-button:focus, .mb-btn:hover, .mb-btn:focus {
background-color: hsl(var(--accent));
color: hsl(var(--accent-foreground));
border-color: hsl(var(--accent));
}
.mb-button:focus-visible, .mb-btn:focus-visible {
outline: 2px solid hsl(var(--ring));
outline-offset: 2px;
}
/* --- Panel / card — heavy 2px frame, hard edges, no shadow --- */
.mb-panel, .mb-card {
border: 2px solid hsl(var(--foreground));
background-color: hsl(var(--card));
transition: border-color 120ms ease, transform 120ms ease;
}
.mb-panel:hover, .mb-card:hover { border-color: hsl(var(--accent)); }
/* --- Form field — heavy border, square, accent focus --- */
.mb-input {
width: 100%;
border: 2px solid hsl(var(--input));
background-color: hsl(var(--background));
color: hsl(var(--foreground));
padding: 0.6rem 0.8rem;
font-family: var(--font-body, "Inter", system-ui, sans-serif);
font-size: 0.95rem;
border-radius: 0;
}
.mb-input::placeholder { color: hsl(var(--muted-foreground)); }
.mb-input:focus {
outline: none;
border-color: hsl(var(--accent));
box-shadow: 0 0 0 2px hsl(var(--accent));
}
/* --- Figure / caption --- */
.mb-figure { margin: 1.5rem 0; }
.mb-figure img { display: block; width: 100%; height: auto; }
.mb-figure figcaption {
margin-top: 0.6rem;
font-family: var(--font-mono, "IBM Plex Mono", ui-monospace, monospace);
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: hsl(var(--muted-foreground));
}
.mb-caption {
font-family: var(--font-mono, "IBM Plex Mono", ui-monospace, monospace);
font-size: 0.72rem;
line-height: 1.4;
letter-spacing: 0.04em;
color: hsl(var(--muted-foreground));
}
/* --- Drop cap — oversized Archivo Black accent first letter --- */
.mb-dropcap p:first-of-type::first-letter {
font-family: var(--font-heading, "Archivo Black", "Arial Black", sans-serif);
color: hsl(var(--accent));
float: left;
font-size: 4.6em;
line-height: 0.78;
padding-right: 0.1em;
padding-top: 0.02em;
margin-bottom: -0.08em;
}
/* --- Marginalia rail (mono side notes, MID motion reveal) --- */
.mb-marginalia {
font-family: var(--font-mono, "IBM Plex Mono", ui-monospace, monospace);
font-size: 0.75rem;
line-height: 1.5;
color: hsl(var(--muted-foreground));
}
.mb-marginalia aside {
margin: 0 0 1.5rem 0;
padding-left: 0.75rem;
border-left: var(--mb-rule);
opacity: 0;
animation: mb-fade-in 500ms ease forwards;
}
@keyframes mb-fade-in { to { opacity: 1; } }
@media (max-width: 768px) {
.mb-marginalia {
margin-top: 1.5rem;
border-top: var(--mb-rule);
padding-top: 1rem;
}
}
/* --- Masthead wordmark --- */
.mb-masthead-wordmark {
font-family: var(--font-heading, "Archivo Black", "Arial Black", sans-serif);
text-transform: uppercase;
letter-spacing: -0.03em;
color: hsl(var(--foreground));
}
/* --- Display size utilities (folio / deck / kicker) --- */
.text-folio {
font-family: var(--font-heading, "Archivo Black", "Arial Black", sans-serif);
font-size: clamp(54px, 15vw, 220px);
line-height: 0.82;
letter-spacing: -0.04em;
text-transform: uppercase;
overflow-wrap: break-word;
word-break: break-word;
}
.text-deck {
font-family: var(--font-body, "Inter", system-ui, sans-serif);
font-size: clamp(24px, 3vw, 44px);
line-height: 1.12;
letter-spacing: -0.01em;
font-weight: 500;
}
.text-kicker {
font-family: var(--font-mono, "IBM Plex Mono", ui-monospace, monospace);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.18em;
font-weight: 600;
}
/* --- Asymmetric editorial grids (persona blocks) --- */
.mb-grid-12 { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 1.5rem; }
.mb-photo-grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 1.25rem; }
.mb-frame-half { grid-column: span 6 / span 6; }
.mb-frame-full { grid-column: 1 / -1; }
.mb-frame-tall { grid-column: span 5 / span 5; grid-row: span 2; }
@media (max-width: 768px) {
.mb-photo-grid { grid-template-columns: 1fr; }
.mb-frame-half, .mb-frame-full, .mb-frame-tall { grid-column: 1 / -1; grid-row: auto; }
}
/* --- Guaranteed responsive helpers — host Tailwind JIT does NOT reliably scan
* theme .ninjatpl files, so any layout-critical responsive rule lives here as
* literal CSS (always emitted) instead of interpolated/utility classes that
* could be dropped (grids that then never collapse on mobile). --- */
.mb-river {
max-width: 64rem;
margin-left: auto; margin-right: auto;
padding-left: 1rem; padding-right: 1rem;
}
.mb-grid-resp { display: grid; grid-template-columns: 1fr; gap: 1.5rem; }
@media (min-width: 640px) {
.mb-grid-resp.mb-cols-2, .mb-grid-resp.mb-cols-3, .mb-grid-resp.mb-cols-4 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (min-width: 1024px) {
.mb-grid-resp.mb-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.mb-grid-resp.mb-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
/* --- Color-block accent panel --- */
.colorblock-accent { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); }
/* --- Full-bleed image --- */
.mb-fullbleed { width: 100vw; max-width: 100vw; margin-left: 50%; transform: translateX(-50%); }
/* --- Duotone photo treatment (signature — persona blocks) --- */
.mb-duotone { position: relative; }
.mb-duotone img { filter: grayscale(1) contrast(1.15); display: block; width: 100%; height: 100%; object-fit: cover; }
.mb-duotone::after {
content: ""; position: absolute; inset: 0;
background: hsl(var(--accent));
mix-blend-mode: multiply;
opacity: 0.55;
transition: opacity 200ms ease;
pointer-events: none;
}
.mb-duotone:hover::after { opacity: 0.2; }
@media (prefers-reduced-motion: reduce) {
.mb-duotone:hover::after { opacity: 0.55; }
}
/* --- Scroll reveal (MID motion) — ROBUST: visible by default (no-JS / no-observer
* / reduced-motion / un-scrolled full-page capture all show content). JS opts
* IN to the hidden-then-reveal animation by adding .mb-armed, and a safety net
* force-reveals everything. Never leaves content permanently hidden. --- */
.mb-reveal { opacity: 1; transform: none; }
.mb-reveal.mb-armed { opacity: 0; transform: translateY(16px); transition: opacity 500ms ease, transform 500ms ease; }
.mb-reveal.mb-armed.mb-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
.mb-reveal, .mb-reveal.mb-armed { opacity: 1; transform: none; transition: none; }
}
/* --- Marquee ticker (MID motion; paused on hover; static fallback) --- */
.mb-marquee { overflow: hidden; white-space: nowrap; }
.mb-marquee-track {
display: inline-block;
will-change: transform;
animation: mb-marquee 22s linear infinite;
}
.mb-marquee:hover .mb-marquee-track { animation-play-state: paused; }
@keyframes mb-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) {
.mb-marquee-track { animation: none; }
}
/* --- Reading-progress bar (fluoro; scroll-timeline; reduced-motion guard) --- */
.mb-progress {
position: sticky; top: 0;
height: 4px;
background: hsl(var(--accent));
transform-origin: 0 50%;
animation: mb-progress linear both;
animation-timeline: scroll(root block);
z-index: 50;
}
@keyframes mb-progress { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@media (prefers-reduced-motion: reduce) {
.mb-progress { animation: none; transform: scaleX(0); }
}
/* --- Per-instance pull-quote accent override (persona quote) --- */
[data-mb-accent="pink"] { --mb-accent-h: 330; --mb-accent-s: 95%; --mb-accent-l: 58%; }
[data-mb-accent="blue"] { --mb-accent-h: 220; --mb-accent-s: 100%; --mb-accent-l: 56%; }
[data-mb-accent="lime"] { --mb-accent-h: 78; --mb-accent-s: 90%; --mb-accent-l: 50%; }
[data-mb-accent] .mb-quote-bar { background-color: hsl(var(--mb-accent-h) var(--mb-accent-s) var(--mb-accent-l)); }
}