Re-persona noir from photography to the locked detective/true-crime
direction: case-file panels, typewriter type, redaction bars, evidence
board, film grain. High-key gallery (light) / darkroom black + amber
safelight (dark).
- 49 mandatory builtin overrides (chrome, marketing, primitives,
commerce, blog family, auth, page-suggestions), re-skinned from the
frozen builtin defaults with every field/provider/tag/JS preserved;
legacy button/card field reads reconciled to the new contract.
- 7 page templates (default, full-width, landing, article, blog-index,
contact, auth) + themed email wrapper.
- 4 persona blocks (case_file, evidence_board, redacted_quote,
case_dossier); old photography blocks deleted (now overrides).
- 5 dual-mode presets (19 tokens, light+dark tuned).
- Bundled OFL fonts (Source Serif 4, IBM Plex Mono) + licenses;
RECOMMENDED_FONTS/ICONS updated; required_icon_packs=lucide.
- Master pages + seed demo ("The Cold File": home/about/blog x3/contact
with case_tips data table + row_inserted email-admins workflow/login).
- noir CSS vocabulary via manifest css.input_css_append; MID motion
(scroll-reveal + gallery lightbox, reduced-motion honored, no canvas).
Verified: make build exit 0; check-safety 32 checks 19 ok 13 skip -> OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
341 lines
12 KiB
YAML
341 lines
12 KiB
YAML
# Noir theme — codeless .bnp manifest (WO-TF-022, Wave A).
|
|
# Detective / true-crime dossier. Synthesized into manifest.pb by
|
|
# `ninja plugin build` (no plugin.wasm).
|
|
theme_presets: presets.json
|
|
bundled_fonts: fonts.json
|
|
master_pages: master_pages.json
|
|
|
|
system_templates:
|
|
- key: noir
|
|
title: Noir
|
|
description: "Detective and true-crime dossier theme: case-file cards, typewriter type, redaction bars, evidence board and film grain. High-key gallery by day, darkroom black by night."
|
|
|
|
page_templates:
|
|
- system: noir
|
|
key: default
|
|
title: Default
|
|
description: "Case-file page with a typed masthead and a dissolved footer rule."
|
|
slots: [header, main, footer]
|
|
- system: noir
|
|
key: full-width
|
|
title: Full Width
|
|
description: "Edge-to-edge dossier layout for evidence boards and lightbox grids."
|
|
slots: [header, main, footer]
|
|
- system: noir
|
|
key: landing
|
|
title: Landing
|
|
description: "Spotlit case hero over film grain, a caption rule and a closing CTA."
|
|
slots: [hero, main, cta, footer]
|
|
- system: noir
|
|
key: article
|
|
title: Case File
|
|
description: "Long-form case write-up with a sticky evidence rail and typed prose."
|
|
slots: [header, main, aside, footer]
|
|
- system: noir
|
|
key: blog-index
|
|
title: Case Index
|
|
description: "Blog index framed as a case log, with featured and post-grid slots."
|
|
slots: [header, featured, main, footer]
|
|
- system: noir
|
|
key: contact
|
|
title: Tip Line
|
|
description: "Contact layout with an optional aside for hours and location."
|
|
slots: [header, main, aside, footer]
|
|
- system: noir
|
|
key: auth
|
|
title: Access
|
|
description: "Centred access shell with a typed wordmark over a darkroom ground."
|
|
slots: [main, footer]
|
|
|
|
template_overrides:
|
|
# Chrome (4)
|
|
- { template: noir, block: navbar }
|
|
- { template: noir, block: footer }
|
|
- { template: noir, block: utility-bar }
|
|
- { template: noir, block: announcement-bar }
|
|
# Marketing (12)
|
|
- { template: noir, block: hero }
|
|
- { template: noir, block: feature-grid }
|
|
- { template: noir, block: rich-section }
|
|
- { template: noir, block: stats }
|
|
- { template: noir, block: testimonial }
|
|
- { template: noir, block: logo-strip }
|
|
- { template: noir, block: pricing }
|
|
- { template: noir, block: team }
|
|
- { template: noir, block: faq }
|
|
- { template: noir, block: timeline }
|
|
- { template: noir, block: cta }
|
|
- { template: noir, block: countdown }
|
|
# Primitives (11)
|
|
- { template: noir, block: heading }
|
|
- { template: noir, block: text }
|
|
- { template: noir, block: rich-text }
|
|
- { template: noir, block: image }
|
|
- { template: noir, block: quote }
|
|
- { template: noir, block: divider }
|
|
- { template: noir, block: button }
|
|
- { template: noir, block: card }
|
|
- { template: noir, block: gallery }
|
|
- { template: noir, block: video-embed }
|
|
- { template: noir, block: audio-embed }
|
|
# Commerce / local (6)
|
|
- { template: noir, block: menu-list }
|
|
- { template: noir, block: event-list }
|
|
- { template: noir, block: contact-form }
|
|
- { template: noir, block: contact-card }
|
|
- { template: noir, block: hours-location }
|
|
- { template: noir, block: social-links }
|
|
# Blog family (12)
|
|
- { template: noir, block: blog-hero }
|
|
- { template: noir, block: featured-posts }
|
|
- { template: noir, block: related-posts }
|
|
- { template: noir, block: popular-posts }
|
|
- { template: noir, block: category-list }
|
|
- { template: noir, block: post-hero }
|
|
- { template: noir, block: post-metadata }
|
|
- { template: noir, block: author-bio }
|
|
- { template: noir, block: author-bio-hero }
|
|
- { template: noir, block: newsletter-signup }
|
|
- { template: noir, block: breadcrumbs }
|
|
- { template: noir, block: sidebar-nav }
|
|
# Auth surface (3)
|
|
- { template: noir, block: auth-form }
|
|
- { template: noir, block: password-reset-form }
|
|
- { template: noir, block: auth-status }
|
|
# System (1)
|
|
- { template: noir, block: page-suggestions }
|
|
|
|
email_wrappers:
|
|
- noir
|
|
|
|
css:
|
|
input_css_append: |
|
|
/* ============================================================
|
|
Noir Theme — Detective / true-crime dossier utility CSS.
|
|
Injected via CSSManifest.InputCSSAppend.
|
|
|
|
Colour is ALWAYS consumed via hsl(var(--token)) with kebab-case
|
|
tokens (the CMS emits --muted-foreground, --card-foreground, ...
|
|
from the camelCase preset keys). Font families ALWAYS flow through
|
|
var(--font-heading|body|mono, <fallback>) — never a hardcoded face.
|
|
============================================================ */
|
|
|
|
@layer utilities {
|
|
/* --- Typography slots -------------------------------------- */
|
|
/* Fallback stacks name the ideal typewriter faces first
|
|
(Special Elite / Courier Prime, admin-assignable via Google
|
|
Fonts) then the shipped OFL defaults (IBM Plex Mono, Source
|
|
Serif 4). */
|
|
.noir-heading {
|
|
font-family: var(--font-heading, "Special Elite", "IBM Plex Mono", "Courier New", monospace);
|
|
letter-spacing: 0.01em;
|
|
}
|
|
.noir-body {
|
|
font-family: var(--font-body, "Source Serif 4", Georgia, "Times New Roman", serif);
|
|
}
|
|
.noir-mono {
|
|
font-family: var(--font-mono, "Courier Prime", "IBM Plex Mono", "Courier New", monospace);
|
|
}
|
|
|
|
/* .noir-label — typed evidence label / eyebrow. */
|
|
.noir-label {
|
|
font-family: var(--font-mono, "Courier Prime", "IBM Plex Mono", "Courier New", monospace);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.22em;
|
|
font-size: 0.7rem;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
/* --- Hairline rules ---------------------------------------- */
|
|
.noir-rule { border: none; border-top: 1px solid hsl(var(--border)); margin: 0; }
|
|
.noir-rule-b { border-bottom: 1px solid hsl(var(--border)); }
|
|
.noir-hairline { border: 1px solid hsl(var(--border)); }
|
|
|
|
/* --- .noir-file — manila case-file card -------------------- */
|
|
/* Square-cornered paper panel with a hairline border and a top
|
|
file-tab drawn as a border accent. */
|
|
.noir-file {
|
|
position: relative;
|
|
background-color: hsl(var(--card));
|
|
color: hsl(var(--card-foreground));
|
|
border: 1px solid hsl(var(--border));
|
|
border-top: 2px solid hsl(var(--primary));
|
|
}
|
|
|
|
/* .noir-tab — folder-tab caption bar sitting above a file. */
|
|
.noir-tab {
|
|
display: inline-block;
|
|
background-color: hsl(var(--primary));
|
|
color: hsl(var(--primary-foreground));
|
|
font-family: var(--font-mono, "Courier Prime", "IBM Plex Mono", "Courier New", monospace);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.18em;
|
|
font-size: 0.65rem;
|
|
padding: 0.2rem 0.7rem;
|
|
}
|
|
|
|
/* --- .noir-stamp — rotated case-status stamp --------------- */
|
|
.noir-stamp {
|
|
display: inline-block;
|
|
border: 2px solid currentColor;
|
|
padding: 0.2rem 0.6rem;
|
|
font-family: var(--font-mono, "Courier Prime", "IBM Plex Mono", "Courier New", monospace);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.2em;
|
|
font-size: 0.7rem;
|
|
transform: rotate(-7deg);
|
|
opacity: 0.9;
|
|
}
|
|
|
|
/* --- .noir-redact — redaction bar over text ---------------- */
|
|
/* Solid ink bar; reveals on hover/focus. Reduced-motion safe:
|
|
the reveal is a colour swap with no transform. */
|
|
.noir-redact {
|
|
background-color: hsl(var(--foreground));
|
|
color: transparent;
|
|
border-radius: 1px;
|
|
padding: 0 0.15em;
|
|
transition: background-color 0.18s ease, color 0.18s ease;
|
|
cursor: help;
|
|
}
|
|
.noir-redact:hover,
|
|
.noir-redact:focus-visible {
|
|
background-color: transparent;
|
|
color: hsl(var(--foreground));
|
|
outline: none;
|
|
}
|
|
|
|
/* --- .noir-pin — evidence-board push pin ------------------- */
|
|
.noir-pin { position: relative; }
|
|
.noir-pin::before {
|
|
content: "";
|
|
position: absolute;
|
|
top: -7px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 9999px;
|
|
background: hsl(var(--primary));
|
|
box-shadow: 0 1px 2px hsl(var(--foreground) / 0.5);
|
|
z-index: 2;
|
|
}
|
|
|
|
/* --- .noir-tape — masking-tape corner strip ---------------- */
|
|
.noir-tape { position: relative; }
|
|
.noir-tape::before {
|
|
content: "";
|
|
position: absolute;
|
|
top: -0.5rem;
|
|
left: 50%;
|
|
transform: translateX(-50%) rotate(-3deg);
|
|
width: 4.5rem;
|
|
height: 1.1rem;
|
|
background: hsl(var(--muted-foreground) / 0.28);
|
|
border-left: 1px dashed hsl(var(--muted-foreground) / 0.4);
|
|
border-right: 1px dashed hsl(var(--muted-foreground) / 0.4);
|
|
z-index: 2;
|
|
}
|
|
|
|
/* --- .noir-grain — 3.5% film-grain overlay ----------------- */
|
|
.noir-grain { position: relative; isolation: isolate; }
|
|
.noir-grain::after {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: 1;
|
|
pointer-events: none;
|
|
opacity: 0.035;
|
|
background-image:
|
|
repeating-radial-gradient(circle at 19% 27%, hsl(var(--foreground)) 0 1px, transparent 1px 3px),
|
|
repeating-radial-gradient(circle at 71% 63%, hsl(var(--foreground)) 0 1px, transparent 1px 3px);
|
|
mix-blend-mode: overlay;
|
|
}
|
|
|
|
/* --- .noir-spotlight — darkroom vignette ------------------- */
|
|
.noir-spotlight { position: relative; }
|
|
.noir-spotlight::before {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: 1;
|
|
pointer-events: none;
|
|
background: radial-gradient(120% 90% at 50% 35%, transparent 40%, hsl(var(--background) / 0.85) 100%);
|
|
}
|
|
|
|
/* --- .noir-btn — square outline button --------------------- */
|
|
.noir-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
background-color: transparent;
|
|
color: hsl(var(--foreground));
|
|
border: 1px solid hsl(var(--foreground));
|
|
padding: 0.7rem 1.5rem;
|
|
font-family: var(--font-mono, "Courier Prime", "IBM Plex Mono", "Courier New", monospace);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.18em;
|
|
font-size: 0.72rem;
|
|
text-decoration: none;
|
|
transition: background-color 0.18s ease, color 0.18s ease, border-color 0.18s ease;
|
|
cursor: pointer;
|
|
}
|
|
.noir-btn:hover,
|
|
.noir-btn:focus-visible {
|
|
background-color: hsl(var(--primary));
|
|
color: hsl(var(--primary-foreground));
|
|
border-color: hsl(var(--primary));
|
|
}
|
|
.noir-btn:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; }
|
|
|
|
/* .noir-btn-solid — filled evidence-accent variant. */
|
|
.noir-btn-solid {
|
|
background-color: hsl(var(--primary));
|
|
color: hsl(var(--primary-foreground));
|
|
border-color: hsl(var(--primary));
|
|
}
|
|
.noir-btn-solid:hover,
|
|
.noir-btn-solid:focus-visible {
|
|
background-color: transparent;
|
|
color: hsl(var(--foreground));
|
|
border-color: hsl(var(--foreground));
|
|
}
|
|
|
|
/* --- .noir-link — evidence-accent underline link ----------- */
|
|
.noir-link {
|
|
color: hsl(var(--primary));
|
|
text-decoration: none;
|
|
border-bottom: 1px solid hsl(var(--primary));
|
|
transition: color 0.18s ease, border-color 0.18s ease;
|
|
}
|
|
.noir-link:hover,
|
|
.noir-link:focus-visible {
|
|
color: hsl(var(--accent));
|
|
border-bottom-color: hsl(var(--accent));
|
|
}
|
|
|
|
/* --- Scroll reveal (MID motion, progressive enhancement) --- */
|
|
/* No-JS / no class: content is fully visible. The page template
|
|
adds `.noir-js` to <html> only when JS runs. */
|
|
.noir-js .noir-reveal {
|
|
opacity: 0;
|
|
transform: translateY(14px);
|
|
transition: opacity 0.6s ease, transform 0.6s ease;
|
|
will-change: opacity, transform;
|
|
}
|
|
.noir-js .noir-reveal.noir-in {
|
|
opacity: 1;
|
|
transform: none;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.noir-js .noir-reveal {
|
|
opacity: 1;
|
|
transform: none;
|
|
transition: none;
|
|
}
|
|
.noir-redact { transition: none; }
|
|
}
|
|
}
|