# 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.
       ============================================================ */

    /* Bundled faces. The host does not yet emit @font-face for
       bundled_fonts (LoadedPlugin.BundledFonts is unconsumed), so the theme
       declares its own faces against the served artifact paths
       (/templates/noir/fonts/...). Presets set typography.fontHeading/body/mono
       to template:noir:<Family> so --font-heading/--font-body/--font-mono
       resolve to these families. (gotham@1.2.0 precedent.) */
    @font-face { font-family: 'Source Serif 4'; src: url('/templates/noir/fonts/source-serif-4-400.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; }
    @font-face { font-family: 'Source Serif 4'; src: url('/templates/noir/fonts/source-serif-4-400-italic.woff2') format('woff2'); font-weight: 400; font-style: italic; font-display: swap; }
    @font-face { font-family: 'IBM Plex Mono'; src: url('/templates/noir/fonts/ibm-plex-mono-400.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; }
    @font-face { font-family: 'IBM Plex Mono'; src: url('/templates/noir/fonts/ibm-plex-mono-500.woff2') format('woff2'); font-weight: 500; font-style: normal; font-display: swap; }

    @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; }
      }
    }

    /* Empty-media graceful state — the demo seed ships no images, so any
       media frame that renders an empty <img> 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%; }
