Compare commits
No commits in common. "d2ae2529d40f27e1f451d6dc2e2eabc61a7c2266" and "34a9bc61c06076c461d8d98eb2e96157ab9cebfd" have entirely different histories.
d2ae2529d4
...
34a9bc61c0
@ -120,17 +120,6 @@ css:
|
||||
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
|
||||
|
||||
@ -16,10 +16,8 @@
|
||||
"page_templates": ["landing", "full-width"],
|
||||
"blocks": [
|
||||
{ "block_key": "navbar", "title": "Case-File Masthead", "content": { "menuName": "main", "logoType": "text", "logoText": { "text": "The Cold File" }, "variant": "transparent-over-hero" }, "slot": "hero", "sort_order": 0 },
|
||||
{ "block_key": "slot", "title": "Hero Content", "content": { "slotName": "hero", "placeholder": "Hero" }, "slot": "hero", "sort_order": 1 },
|
||||
{ "block_key": "slot", "title": "Main Content", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
|
||||
{ "block_key": "divider", "title": "CTA Rule", "content": { "variant": "line", "weight": "thick", "width": "full" }, "slot": "cta", "sort_order": 0 },
|
||||
{ "block_key": "slot", "title": "CTA Content", "content": { "slotName": "cta", "placeholder": "Closing call to action" }, "slot": "cta", "sort_order": 1 },
|
||||
{ "block_key": "footer", "title": "Site Footer", "content": { "layout": "columns", "companyName": "The Cold File", "tagline": "Cases that were never closed." }, "slot": "footer", "sort_order": 0 }
|
||||
]
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
name = "noir"
|
||||
display_name = "Noir"
|
||||
scope = "@themes"
|
||||
version = "0.4.0"
|
||||
version = "0.3.0"
|
||||
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."
|
||||
kind = "theme"
|
||||
categories = ["templates"]
|
||||
|
||||
45
presets.json
@ -5,13 +5,6 @@
|
||||
"description": "The default. High-key gallery by day, ink-black type, evidence red. Darkroom black by night lit by an amber safelight.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"typography": {
|
||||
"fontHeading": "template:noir:IBM Plex Mono",
|
||||
"fontBody": "template:noir:Source Serif 4",
|
||||
"fontMono": "template:noir:IBM Plex Mono",
|
||||
"fontSizeBase": "17",
|
||||
"lineHeightBase": "1.7"
|
||||
},
|
||||
"lightColors": {
|
||||
"background": "40 14% 96%",
|
||||
"foreground": "24 12% 10%",
|
||||
@ -24,7 +17,7 @@
|
||||
"secondary": "40 8% 90%",
|
||||
"secondaryForeground": "24 12% 14%",
|
||||
"muted": "40 8% 91%",
|
||||
"mutedForeground": "28 9% 30%",
|
||||
"mutedForeground": "28 6% 38%",
|
||||
"accent": "0 60% 48%",
|
||||
"accentForeground": "40 20% 97%",
|
||||
"destructive": "0 74% 40%",
|
||||
@ -62,13 +55,6 @@
|
||||
"description": "Red-forward in both modes. The colour of the string on the board and the line under the name.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"typography": {
|
||||
"fontHeading": "template:noir:IBM Plex Mono",
|
||||
"fontBody": "template:noir:Source Serif 4",
|
||||
"fontMono": "template:noir:IBM Plex Mono",
|
||||
"fontSizeBase": "17",
|
||||
"lineHeightBase": "1.7"
|
||||
},
|
||||
"lightColors": {
|
||||
"background": "20 10% 97%",
|
||||
"foreground": "0 8% 8%",
|
||||
@ -81,7 +67,7 @@
|
||||
"secondary": "20 8% 90%",
|
||||
"secondaryForeground": "0 8% 12%",
|
||||
"muted": "20 8% 92%",
|
||||
"mutedForeground": "12 9% 29%",
|
||||
"mutedForeground": "12 6% 36%",
|
||||
"accent": "8 68% 50%",
|
||||
"accentForeground": "20 12% 98%",
|
||||
"destructive": "358 78% 42%",
|
||||
@ -119,13 +105,6 @@
|
||||
"description": "The darkroom under amber safelight. Warm bone paper by day, deep umber and amber glow by night.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"typography": {
|
||||
"fontHeading": "template:noir:IBM Plex Mono",
|
||||
"fontBody": "template:noir:Source Serif 4",
|
||||
"fontMono": "template:noir:IBM Plex Mono",
|
||||
"fontSizeBase": "17",
|
||||
"lineHeightBase": "1.7"
|
||||
},
|
||||
"lightColors": {
|
||||
"background": "38 30% 95%",
|
||||
"foreground": "30 18% 12%",
|
||||
@ -138,7 +117,7 @@
|
||||
"secondary": "38 16% 88%",
|
||||
"secondaryForeground": "30 18% 14%",
|
||||
"muted": "38 14% 90%",
|
||||
"mutedForeground": "32 14% 30%",
|
||||
"mutedForeground": "32 12% 38%",
|
||||
"accent": "26 70% 48%",
|
||||
"accentForeground": "38 30% 97%",
|
||||
"destructive": "0 70% 44%",
|
||||
@ -176,13 +155,6 @@
|
||||
"description": "Steel and cold light. Grey-blue neutral by day, slate-black room lit by a single steel lamp by night.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"typography": {
|
||||
"fontHeading": "template:noir:IBM Plex Mono",
|
||||
"fontBody": "template:noir:Source Serif 4",
|
||||
"fontMono": "template:noir:IBM Plex Mono",
|
||||
"fontSizeBase": "17",
|
||||
"lineHeightBase": "1.7"
|
||||
},
|
||||
"lightColors": {
|
||||
"background": "210 16% 95%",
|
||||
"foreground": "215 22% 12%",
|
||||
@ -195,7 +167,7 @@
|
||||
"secondary": "210 14% 89%",
|
||||
"secondaryForeground": "215 22% 14%",
|
||||
"muted": "210 12% 91%",
|
||||
"mutedForeground": "214 13% 30%",
|
||||
"mutedForeground": "214 10% 38%",
|
||||
"accent": "8 62% 48%",
|
||||
"accentForeground": "210 20% 97%",
|
||||
"destructive": "0 72% 44%",
|
||||
@ -233,13 +205,6 @@
|
||||
"description": "Pure document. Stark black-on-white by day, near-monochrome by night, with the faintest red where the bar lifts.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"typography": {
|
||||
"fontHeading": "template:noir:IBM Plex Mono",
|
||||
"fontBody": "template:noir:Source Serif 4",
|
||||
"fontMono": "template:noir:IBM Plex Mono",
|
||||
"fontSizeBase": "17",
|
||||
"lineHeightBase": "1.7"
|
||||
},
|
||||
"lightColors": {
|
||||
"background": "0 0% 98%",
|
||||
"foreground": "0 0% 8%",
|
||||
@ -252,7 +217,7 @@
|
||||
"secondary": "0 0% 92%",
|
||||
"secondaryForeground": "0 0% 12%",
|
||||
"muted": "0 0% 93%",
|
||||
"mutedForeground": "0 0% 28%",
|
||||
"mutedForeground": "0 0% 36%",
|
||||
"accent": "0 0% 12%",
|
||||
"accentForeground": "0 0% 98%",
|
||||
"destructive": "0 70% 42%",
|
||||
|
||||
BIN
preview.png
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 1001 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 604 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 606 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 266 KiB |
|
Before Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 228 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 210 KiB |
|
Before Width: | Height: | Size: 165 KiB |
@ -6,14 +6,6 @@
|
||||
"tagline": "Cases that were never closed."
|
||||
}
|
||||
},
|
||||
"media": [
|
||||
{ "id": "7a1c0001-0000-4000-8000-000000000001", "file": "media/harbour-night.jpg", "alt": "A working harbour at night under sodium light", "folder": "noir" },
|
||||
{ "id": "7a1c0001-0000-4000-8000-000000000002", "file": "media/foggy-night.jpg", "alt": "A deserted street lost in night fog", "folder": "noir" },
|
||||
{ "id": "7a1c0001-0000-4000-8000-000000000003", "file": "media/typewriter.jpg", "alt": "An old typewriter on a desk", "folder": "noir" },
|
||||
{ "id": "7a1c0001-0000-4000-8000-000000000004", "file": "media/streetlamp.jpg", "alt": "A single streetlamp in the dark", "folder": "noir" },
|
||||
{ "id": "7a1c0001-0000-4000-8000-000000000005", "file": "media/foggy-street.jpg", "alt": "A wet street under fog and lamplight", "folder": "noir" },
|
||||
{ "id": "7a1c0001-0000-4000-8000-000000000006", "file": "media/typewriter-keys.jpg", "alt": "Close detail of typewriter keys", "folder": "noir" }
|
||||
],
|
||||
"data_tables": [
|
||||
{
|
||||
"key": "case_tips",
|
||||
@ -32,18 +24,14 @@
|
||||
"slug": "/",
|
||||
"title": "The Cold File",
|
||||
"template_key": "landing",
|
||||
"reconcile_blocks": true,
|
||||
"blocks": [
|
||||
{
|
||||
"block_key": "hero",
|
||||
"title": "Hero",
|
||||
"slot": "hero",
|
||||
"slot": "main",
|
||||
"sort_order": 1,
|
||||
"content": {
|
||||
"template": "centered",
|
||||
"backgroundImage": "7a1c0001-0000-4000-8000-000000000001",
|
||||
"overlayColor": "dark",
|
||||
"overlayOpacity": 55,
|
||||
"eyebrow": "A true-crime file",
|
||||
"headline": "The Cold File",
|
||||
"subheadline": "Cases that were never closed.",
|
||||
@ -104,8 +92,8 @@
|
||||
{
|
||||
"block_key": "cta",
|
||||
"title": "Closing CTA",
|
||||
"slot": "cta",
|
||||
"sort_order": 1,
|
||||
"slot": "main",
|
||||
"sort_order": 5,
|
||||
"content": {
|
||||
"background": "band",
|
||||
"heading": "Know something we don't?",
|
||||
@ -125,9 +113,8 @@
|
||||
"blocks": [
|
||||
{ "block_key": "heading", "title": "Heading", "slot": "main", "sort_order": 1, "content": { "text": "About the File", "level": 1 } },
|
||||
{ "block_key": "rich-text", "title": "Story", "slot": "main", "sort_order": 2, "content": { "html": "<p>The Cold File is a true-crime publication. We take one unsolved case at a time and set it out plainly, from the record up.</p><p>We do not name the guilty. We do not sell a theory. We show what is known, mark what is not, and leave the reader to weigh it. When new facts arrive, the file is updated in the open.</p>" } },
|
||||
{ "block_key": "image", "title": "The desk", "slot": "main", "sort_order": 3, "content": { "src": "7a1c0001-0000-4000-8000-000000000003", "alt": "The desk where the files are read", "caption": "The desk. Every file starts here.", "aspectRatio": "video", "size": "full", "objectFit": "cover" } },
|
||||
{ "block_key": "divider", "title": "Divider", "slot": "main", "sort_order": 4, "content": { "variant": "line", "weight": "thick", "width": "narrow" } },
|
||||
{ "block_key": "quote", "title": "Quote", "slot": "main", "sort_order": 5, "content": { "style": "pull", "align": "center", "quote": "A cold case is not a closed one. It is a file waiting for one more fact.", "attribution": "The Cold File" } }
|
||||
{ "block_key": "divider", "title": "Divider", "slot": "main", "sort_order": 3, "content": { "variant": "line", "weight": "thick", "width": "narrow" } },
|
||||
{ "block_key": "quote", "title": "Quote", "slot": "main", "sort_order": 4, "content": { "style": "pull", "align": "center", "quote": "A cold case is not a closed one. It is a file waiting for one more fact.", "attribution": "The Cold File" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -145,13 +132,9 @@
|
||||
"title": "The Harbour Light",
|
||||
"template_key": "article",
|
||||
"parent_slug": "/blog",
|
||||
"reconcile_blocks": true,
|
||||
"blocks": [
|
||||
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "The Harbour Light", "level": 1 } },
|
||||
{ "block_key": "image", "title": "The pier", "slot": "main", "sort_order": 2, "content": { "src": "7a1c0001-0000-4000-8000-000000000002", "alt": "The East Dock lost in night fog", "caption": "Pier 9, East Dock — the same hour we walked it.", "aspectRatio": "video", "size": "full", "objectFit": "cover" } },
|
||||
{ "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 3, "content": { "html": "<p>The watchman made his last radio check at four minutes past two. He was due at the gate by half past. He never signed out.</p><p>The lamp on Pier 9 was still lit at dawn. The tide had turned twice. No boat was reported missing. No body was found. The file was opened, worked for a season, and set aside.</p><p>Start with what the record holds. A duty roster with his name in the two-to-six column. A radio log with four entries in his hand, the last at 02:04, marked routine. A gate ledger that shows every man off the dock by three — every man but one.</p><p>The season it was worked, the file gathered the usual paper. Two dozen interviews. A diver's report for the water off the pier head, twice. A note from the gate man, who swore the light was burning when he cycled past at ten to three and swore just as hard that nobody stood under it.</p><p>Then the file stops. Not closed — stopped. The last page is a requisition for more diving time that nobody signed.</p><p>We pulled the harbour master's log and the tide tables for that week. The two do not agree on when the water turned. One of them was written up after the fact, and we think we know which.</p><p>We walked the pier at the same hour. The lamp throws light about forty feet. The gate is ninety. For six of those steps a man is in the dark. We are still checking what stands in them.</p><p>There is one more thing the record holds, and we set it down here exactly as it appears. The radio log's final line, in a different hand, timed 02:31: <em>checked pier, all quiet</em>. Nobody at the harbour that season would say whose hand it was.</p><p>The file is open. If you worked the East Dock in March 1998, or knew someone who did, the tip line is at the end of this page.</p>" } },
|
||||
{ "block_key": "heading", "title": "Rail heading", "slot": "aside", "sort_order": 1, "content": { "text": "Case File", "level": 3 } },
|
||||
{ "block_key": "text", "title": "Case metadata", "slot": "aside", "sort_order": 2, "content": { "text": "<dl class=\"not-prose space-y-4\"><div><dt class=\"noir-label text-muted-foreground\">Case No.</dt><dd class=\"noir-mono text-sm text-foreground\">CF-0451</dd></div><div><dt class=\"noir-label text-muted-foreground\">Filed</dt><dd class=\"noir-mono text-sm text-foreground\">March 1998</dd></div><div><dt class=\"noir-label text-muted-foreground\">Location</dt><dd class=\"noir-mono text-sm text-foreground\">Pier 9, East Dock</dd></div><div><dt class=\"noir-label text-muted-foreground\">Last contact</dt><dd class=\"noir-mono text-sm text-foreground\">02:04, radio check</dd></div><div><dt class=\"noir-label text-muted-foreground\">Status</dt><dd class=\"noir-mono text-sm text-foreground\">Open, unsolved</dd></div></dl>" } }
|
||||
{ "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "<p>The watchman made his last radio check at four minutes past two. He was due at the gate by half past. He never signed out.</p><p>The lamp on Pier 9 was still lit at dawn. The tide had turned twice. No boat was reported missing. No body was found. The file was opened, worked for a season, and set aside.</p><p>We walked the pier at the same hour. The record and the place do not agree on one point. We are still checking which one is wrong.</p>" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -161,10 +144,7 @@
|
||||
"parent_slug": "/blog",
|
||||
"blocks": [
|
||||
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Room 14", "level": 1 } },
|
||||
{ "block_key": "image", "title": "The hall", "slot": "main", "sort_order": 2, "content": { "src": "7a1c0001-0000-4000-8000-000000000004", "alt": "A single lamp at the quiet end of the hall", "caption": "The quiet end of the hall. One lamp, kept lit.", "aspectRatio": "video", "size": "full", "objectFit": "cover" } },
|
||||
{ "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 3, "content": { "html": "<p>The guest signed the register in a hand that did not match the name. He paid a week in advance and asked for the quiet end of the hall.</p><p>On the fourth night the room went dark and stayed that way. The suitcase was still there. The bed was made. The window was latched from the inside.</p><p>The file lists three people who saw him arrive and none who saw him leave. The night porter swears the corridor was watched the whole shift. The register says otherwise, in the same hand that signed the wrong name.</p><p>We are trying to reconcile that. So far the only thing everyone agrees on is the smell of a struck match, and no one who admits to lighting one.</p>" } },
|
||||
{ "block_key": "heading", "title": "Rail heading", "slot": "aside", "sort_order": 1, "content": { "text": "Case File", "level": 3 } },
|
||||
{ "block_key": "text", "title": "Case metadata", "slot": "aside", "sort_order": 2, "content": { "text": "<dl class=\"not-prose space-y-4\"><div><dt class=\"noir-label text-muted-foreground\">Case No.</dt><dd class=\"noir-mono text-sm text-foreground\">CF-0212</dd></div><div><dt class=\"noir-label text-muted-foreground\">Filed</dt><dd class=\"noir-mono text-sm text-foreground\">November 2003</dd></div><div><dt class=\"noir-label text-muted-foreground\">Location</dt><dd class=\"noir-mono text-sm text-foreground\">The Old Exchange</dd></div><div><dt class=\"noir-label text-muted-foreground\">Last seen</dt><dd class=\"noir-mono text-sm text-foreground\">Night four, 21:10</dd></div><div><dt class=\"noir-label text-muted-foreground\">Status</dt><dd class=\"noir-mono text-sm text-foreground\">Open, unsolved</dd></div></dl>" } }
|
||||
{ "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "<p>The guest signed the register in a hand that did not match the name. He paid a week in advance and asked for the quiet end of the hall.</p><p>On the fourth night the room went dark and stayed that way. The suitcase was still there. The bed was made. The window was latched from the inside.</p><p>The file lists three people who saw him arrive and none who saw him leave. We are trying to reconcile that.</p>" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -174,10 +154,7 @@
|
||||
"parent_slug": "/blog",
|
||||
"blocks": [
|
||||
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "The Last Bus", "level": 1 } },
|
||||
{ "block_key": "image", "title": "The route", "slot": "main", "sort_order": 2, "content": { "src": "7a1c0001-0000-4000-8000-000000000005", "alt": "A wet street under lamplight on the last route", "caption": "The last route, run again at the same hour.", "aspectRatio": "video", "size": "full", "objectFit": "cover" } },
|
||||
{ "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 3, "content": { "html": "<p>The driver logged the final stop on schedule. Twelve minutes later the bus rolled into the depot. It was empty and the doors were locked from inside.</p><p>The fare box held the right count for a full run. The seats were cold. The last passenger on the ticket record never reached home.</p><p>We drove the route ourselves at the same hour and clocked every stop. Between the second-last and the depot there is a stretch with no lights and no camera. It takes about twelve minutes.</p><p>We have the route, the timing, and the log. What we do not have is what happened in those twelve minutes, or why the doors were locked from the inside.</p>" } },
|
||||
{ "block_key": "heading", "title": "Rail heading", "slot": "aside", "sort_order": 1, "content": { "text": "Case File", "level": 3 } },
|
||||
{ "block_key": "text", "title": "Case metadata", "slot": "aside", "sort_order": 2, "content": { "text": "<dl class=\"not-prose space-y-4\"><div><dt class=\"noir-label text-muted-foreground\">Case No.</dt><dd class=\"noir-mono text-sm text-foreground\">CF-0388</dd></div><div><dt class=\"noir-label text-muted-foreground\">Filed</dt><dd class=\"noir-mono text-sm text-foreground\">July 2009</dd></div><div><dt class=\"noir-label text-muted-foreground\">Route</dt><dd class=\"noir-mono text-sm text-foreground\">Night 9, outbound</dd></div><div><dt class=\"noir-label text-muted-foreground\">Depot</dt><dd class=\"noir-mono text-sm text-foreground\">00:12, locked</dd></div><div><dt class=\"noir-label text-muted-foreground\">Status</dt><dd class=\"noir-mono text-sm text-foreground\">Open, unsolved</dd></div></dl>" } }
|
||||
{ "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "<p>The driver logged the final stop on schedule. Twelve minutes later the bus rolled into the depot. It was empty and the doors were locked from inside.</p><p>The fare box held the right count for a full run. The seats were cold. The last passenger on the ticket record never reached home.</p><p>We have the route, the timing, and the log. What we do not have is the twelve minutes.</p>" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@ -6,13 +6,13 @@
|
||||
<header class="w-full">
|
||||
<div class="max-w-3xl mx-auto px-4">{{ slots.header|safe }}</div>
|
||||
</header>
|
||||
<div class="flex-grow grid gap-12 max-w-5xl mx-auto w-full px-4 py-12 {% if slots.aside %}md:grid-cols-[1fr_16rem]{% endif %}">
|
||||
<div class="flex-grow grid gap-12 max-w-5xl mx-auto w-full px-4 py-12 md:grid-cols-[1fr_16rem]">
|
||||
<main class="min-w-0 noir-reveal">
|
||||
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center"><p class="noir-label" style="color:hsl(var(--muted-foreground));">No content blocks assigned to this page.</p></div>{% endif %}
|
||||
</main>
|
||||
{% if slots.aside %}<aside class="w-full noir-reveal md:border-l md:border-[hsl(var(--border))] md:pl-8">
|
||||
<aside class="w-full md:border-l md:border-[hsl(var(--border))] md:pl-8">
|
||||
<div class="md:sticky md:top-6">{{ slots.aside|safe }}</div>
|
||||
</aside>{% endif %}
|
||||
</aside>
|
||||
</div>
|
||||
<footer class="w-full mt-auto noir-rule">
|
||||
<div class="max-w-5xl mx-auto px-4">{{ slots.footer|safe }}</div>
|
||||
@ -25,11 +25,8 @@
|
||||
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,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(!h||(r.top<h*0.95&&r.bottom>0)){el.classList.add('noir-in');}else{io.observe(el);}});
|
||||
window.addEventListener('load',showAll);
|
||||
setTimeout(showAll,450);
|
||||
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);});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@ -24,9 +24,9 @@
|
||||
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,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(!h||(r.top<h*0.95&&r.bottom>0)){el.classList.add('noir-in');}else{io.observe(el);}});
|
||||
els.forEach(function(el){var r=el.getBoundingClientRect();var h=window.innerHeight||document.documentElement.clientHeight||0;if(r.top<h*0.92&&r.bottom>0){el.classList.add('noir-in');}else{io.observe(el);}});
|
||||
window.addEventListener('load',showAll);
|
||||
setTimeout(showAll,450);
|
||||
setTimeout(showAll,1600);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
{{ head_html|safe }}
|
||||
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col noir-body">
|
||||
{{ admin_banner_html|safe }}
|
||||
<section class="w-full relative noir-grain">{{ slots.hero|safe }}</section>
|
||||
<section class="w-full relative noir-grain noir-spotlight">{{ slots.hero|safe }}</section>
|
||||
<main class="flex-grow">
|
||||
{% if slots.main %}<div class="max-w-3xl mx-auto w-full px-4 py-10 noir-reveal">{{ slots.main|safe }}</div>{% endif %}
|
||||
</main>
|
||||
@ -21,9 +21,9 @@
|
||||
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,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(!h||(r.top<h*0.95&&r.bottom>0)){el.classList.add('noir-in');}else{io.observe(el);}});
|
||||
els.forEach(function(el){var r=el.getBoundingClientRect();var h=window.innerHeight||document.documentElement.clientHeight||0;if(r.top<h*0.92&&r.bottom>0){el.classList.add('noir-in');}else{io.observe(el);}});
|
||||
window.addEventListener('load',showAll);
|
||||
setTimeout(showAll,450);
|
||||
setTimeout(showAll,1600);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{# noir override — audio-embed; preserves url, media, artwork, title, artist, download, data-bn-audio, data-audio-url, data-audio-media, {% img %} call, and the facade IIFE verbatim. Only the player card restyled (square evidence panel). #}
|
||||
<figure data-block-override="noir:audio-embed" class="bn-audio noir-file my-8 overflow-hidden{% if class %} {{ class }}{% endif %}" data-bn-audio data-audio-url="{{ url|default:"" }}" data-audio-media="{% if media %}{{ media|mediaURL }}{% endif %}"><div class="flex items-center gap-4 p-4">{% if artwork %}<div class="noir-hairline h-16 w-16 shrink-0 overflow-hidden bg-muted grayscale">{% img artwork alt=title|default:"" class="h-full w-full object-cover" %}</div>{% endif %}<div class="min-w-0 flex-1">{% if title %}<p class="noir-heading truncate text-sm" style="color:hsl(var(--foreground));">{{ title }}</p>{% endif %}{% if artist %}<p class="noir-body truncate text-sm" style="color:hsl(var(--muted-foreground));">{{ artist }}</p>{% endif %}<div class="mt-2 flex items-center gap-3" data-bn-audio-slot><button type="button" data-bn-audio-play class="noir-btn noir-btn-solid text-sm" aria-label="Play{% if title %}: {{ title }}{% endif %}"><svg width="16" height="16" aria-hidden="true"><use href="/icons/lucide.svg#play"></use></svg> Play</button>{% if download %}<a href="{% if media %}{{ media|mediaURL }}{% else %}{{ url }}{% endif %}" download class="noir-link inline-flex items-center gap-1 text-sm"><svg width="16" height="16" aria-hidden="true"><use href="/icons/lucide.svg#download"></use></svg> Download</a>{% endif %}</div></div></div></figure><script>
|
||||
<figure data-block-override="noir:audio-embed" class="bn-audio noir-file my-8 overflow-hidden{% if class %} {{ class }}{% endif %}" data-bn-audio data-audio-url="{{ url|default:"" }}" data-audio-media="{% if media %}{{ media|mediaURL }}{% endif %}"><div class="flex items-center gap-4 p-4">{% if artwork %}<div class="noir-hairline h-16 w-16 shrink-0 overflow-hidden bg-muted grayscale">{% img artwork alt=title|default:"" class="h-full w-full object-cover" %}</div>{% endif %}<div class="min-w-0 flex-1">{% if title %}<p class="noir-heading truncate text-sm" style="color:hsl(var(--foreground));">{{ title }}</p>{% endif %}{% if artist %}<p class="noir-body truncate text-sm" style="color:hsl(var(--muted-foreground));">{{ artist }}</p>{% endif %}<div class="mt-2 flex items-center gap-3" data-bn-audio-slot><button type="button" data-bn-audio-play class="noir-btn noir-btn-solid text-sm" aria-label="Play{% if title %}: {{ title }}{% endif %}">::lucide:play:sm:: Play</button>{% if download %}<a href="{% if media %}{{ media|mediaURL }}{% else %}{{ url }}{% endif %}" download class="noir-link inline-flex items-center gap-1 text-sm">::lucide:download:sm:: Download</a>{% endif %}</div></div></div></figure><script>
|
||||
(function(){
|
||||
if(window.__bnAudioFacade)return;window.__bnAudioFacade=true;
|
||||
document.addEventListener('click',function(e){
|
||||
|
||||
@ -11,10 +11,10 @@
|
||||
{% if author.location %}<span>{{ author.location }}</span>{% endif %}
|
||||
</div>
|
||||
{% if showSocial %}<div class="mt-6 flex items-center justify-center gap-4">
|
||||
{% if author.website_url %}<a href="{{ author.website_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="Website"><svg width="20" height="20" class="h-5 w-5" aria-hidden="true"><use href="/icons/lucide.svg#globe"></use></svg></a>{% endif %}
|
||||
{% if author.twitter_handle %}<a href="https://twitter.com/{{ author.twitter_handle|cut:"@" }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="Twitter"><svg width="20" height="20" class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#x"></use></svg></a>{% endif %}
|
||||
{% if author.linkedin_url %}<a href="{{ author.linkedin_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="LinkedIn"><svg width="20" height="20" class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#linkedin"></use></svg></a>{% endif %}
|
||||
{% if author.github_url %}<a href="{{ author.github_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="GitHub"><svg width="20" height="20" class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#github"></use></svg></a>{% endif %}
|
||||
{% if author.website_url %}<a href="{{ author.website_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="Website"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/lucide.svg#globe"></use></svg></a>{% endif %}
|
||||
{% if author.twitter_handle %}<a href="https://twitter.com/{{ author.twitter_handle|cut:"@" }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="Twitter"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#x"></use></svg></a>{% endif %}
|
||||
{% if author.linkedin_url %}<a href="{{ author.linkedin_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="LinkedIn"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#linkedin"></use></svg></a>{% endif %}
|
||||
{% if author.github_url %}<a href="{{ author.github_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="GitHub"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#github"></use></svg></a>{% endif %}
|
||||
</div>{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -2,11 +2,11 @@
|
||||
{% with align=align|default:"left" %}
|
||||
<div data-block-override="noir:button" class="bn-button flex {% if align == "center" %}justify-center{% elif align == "right" %}justify-end{% else %}justify-start{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||
{% if style == "ghost" or style == "link" %}
|
||||
<a href="{{ link|default:'#' }}"{% if newTab %} target="_blank" rel="noopener"{% endif %} class="noir-link inline-flex items-center gap-2 text-sm">{% if icon and iconPosition != "right" %}<svg width="16" height="16" aria-hidden="true"><use href="/icons/{{ icon|split:":"|first }}.svg#{{ icon|split:":"|last }}"></use></svg>{% endif %}{{ label|default:"Learn more" }}{% if icon and iconPosition == "right" %}<svg width="16" height="16" aria-hidden="true"><use href="/icons/{{ icon|split:":"|first }}.svg#{{ icon|split:":"|last }}"></use></svg>{% endif %}</a>
|
||||
<a href="{{ link|default:'#' }}"{% if newTab %} target="_blank" rel="noopener"{% endif %} class="noir-link inline-flex items-center gap-2 text-sm">{% if icon and iconPosition != "right" %}::{{ icon }}:sm::{% endif %}{{ label|default:"Learn more" }}{% if icon and iconPosition == "right" %}::{{ icon }}:sm::{% endif %}</a>
|
||||
{% elif style == "secondary" or style == "outline" %}
|
||||
<a href="{{ link|default:'#' }}"{% if newTab %} target="_blank" rel="noopener"{% endif %} class="noir-btn">{% if icon and iconPosition != "right" %}<svg width="16" height="16" aria-hidden="true"><use href="/icons/{{ icon|split:":"|first }}.svg#{{ icon|split:":"|last }}"></use></svg>{% endif %}{{ label|default:"Learn more" }}{% if icon and iconPosition == "right" %}<svg width="16" height="16" aria-hidden="true"><use href="/icons/{{ icon|split:":"|first }}.svg#{{ icon|split:":"|last }}"></use></svg>{% endif %}</a>
|
||||
<a href="{{ link|default:'#' }}"{% if newTab %} target="_blank" rel="noopener"{% endif %} class="noir-btn">{% if icon and iconPosition != "right" %}::{{ icon }}:sm::{% endif %}{{ label|default:"Learn more" }}{% if icon and iconPosition == "right" %}::{{ icon }}:sm::{% endif %}</a>
|
||||
{% else %}
|
||||
<a href="{{ link|default:'#' }}"{% if newTab %} target="_blank" rel="noopener"{% endif %} class="noir-btn noir-btn-solid">{% if icon and iconPosition != "right" %}<svg width="16" height="16" aria-hidden="true"><use href="/icons/{{ icon|split:":"|first }}.svg#{{ icon|split:":"|last }}"></use></svg>{% endif %}{{ label|default:"Learn more" }}{% if icon and iconPosition == "right" %}<svg width="16" height="16" aria-hidden="true"><use href="/icons/{{ icon|split:":"|first }}.svg#{{ icon|split:":"|last }}"></use></svg>{% endif %}</a>
|
||||
<a href="{{ link|default:'#' }}"{% if newTab %} target="_blank" rel="noopener"{% endif %} class="noir-btn noir-btn-solid">{% if icon and iconPosition != "right" %}::{{ icon }}:sm::{% endif %}{{ label|default:"Learn more" }}{% if icon and iconPosition == "right" %}::{{ icon }}:sm::{% endif %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endwith %}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{# card override — manila case-file panel. Fields mirror builtin: layout, link, media, title, text, linkLabel. Media via {% img %}. #}
|
||||
{% with layout=layout|default:"vertical" %}
|
||||
<article data-block-override="noir:card" class="bn-card noir-file overflow-hidden transition-colors {% if layout == "horizontal" %}flex flex-col sm:flex-row{% endif %}{% if class %} {{ class }}{% endif %}">{% if link %}<a href="{{ link }}" class="contents">{% endif %}{% if media %}<div class="overflow-hidden bg-muted grayscale {% if layout == "horizontal" %}sm:w-2/5 sm:shrink-0{% endif %}">{% if layout == "horizontal" %}{% img media alt=title|default:"" class="h-full w-full object-cover" %}{% else %}{% img media alt=title|default:"" class="aspect-video h-full w-full object-cover" %}{% endif %}</div>{% endif %}<div class="flex flex-col gap-2 p-5 md:p-6 {% if layout == "horizontal" %}justify-center{% endif %}">{% if title %}<h3 class="noir-heading text-lg" style="color:hsl(var(--foreground));">{{ title }}</h3>{% endif %}{% if text %}<div class="noir-body text-sm leading-relaxed" style="color:hsl(var(--muted-foreground));">{{ text|safe }}</div>{% endif %}{% if linkLabel and link %}<span class="mt-2 inline-flex items-center gap-1 noir-label" style="color:hsl(var(--primary));">{{ linkLabel }}<svg width="16" height="16" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></span>{% endif %}</div>{% if link %}</a>{% endif %}</article>
|
||||
<article data-block-override="noir:card" class="bn-card noir-file overflow-hidden transition-colors {% if layout == "horizontal" %}flex flex-col sm:flex-row{% endif %}{% if class %} {{ class }}{% endif %}">{% if link %}<a href="{{ link }}" class="contents">{% endif %}{% if media %}<div class="overflow-hidden bg-muted grayscale {% if layout == "horizontal" %}sm:w-2/5 sm:shrink-0{% endif %}">{% if layout == "horizontal" %}{% img media alt=title|default:"" class="h-full w-full object-cover" %}{% else %}{% img media alt=title|default:"" class="aspect-video h-full w-full object-cover" %}{% endif %}</div>{% endif %}<div class="flex flex-col gap-2 p-5 md:p-6 {% if layout == "horizontal" %}justify-center{% endif %}">{% if title %}<h3 class="noir-heading text-lg" style="color:hsl(var(--foreground));">{{ title }}</h3>{% endif %}{% if text %}<div class="noir-body text-sm leading-relaxed" style="color:hsl(var(--muted-foreground));">{{ text|safe }}</div>{% endif %}{% if linkLabel and link %}<span class="mt-2 inline-flex items-center gap-1 noir-label" style="color:hsl(var(--primary));">{{ linkLabel }}::lucide:arrow-right:sm::</span>{% endif %}</div>{% if link %}</a>{% endif %}</article>
|
||||
{% endwith %}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<div class="mt-10 grid grid-cols-1 gap-4 {% if columns == 3 %}sm:grid-cols-2 lg:grid-cols-3{% elif columns == 1 %}{% else %}sm:grid-cols-2{% endif %}">
|
||||
{% for channel in channels %}
|
||||
<div class="noir-file flex items-start gap-4 p-5">
|
||||
{% if channel.icon %}<span class="inline-flex h-10 w-10 shrink-0 items-center justify-center border border-border bg-primary/10 text-primary"><svg width="20" height="20" class="h-5 w-5" aria-hidden="true"><use href="/icons/{{ channel.icon|split:":"|first }}.svg#{{ channel.icon|split:":"|last }}"></use></svg></span>{% endif %}
|
||||
{% if channel.icon %}<span class="inline-flex h-10 w-10 shrink-0 items-center justify-center border border-border bg-primary/10 text-primary"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/{{ channel.icon|split:":"|first }}.svg#{{ channel.icon|split:":"|last }}"></use></svg></span>{% endif %}
|
||||
<div class="min-w-0">
|
||||
{% if channel.label %}<div class="noir-label" style="color:hsl(var(--muted-foreground));">{{ channel.label }}</div>{% endif %}
|
||||
{% if channel.value %}<div class="noir-heading mt-0.5 break-words text-base" style="color:hsl(var(--card-foreground));">{% if channel.link %}<a href="{{ channel.link }}" class="transition-colors hover:text-primary">{{ channel.value }}</a>{% else %}{{ channel.value }}{% endif %}</div>{% endif %}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<div class="mx-auto max-w-2xl px-4">
|
||||
{% if form.submitted %}
|
||||
<div class="noir-file p-10 text-center">
|
||||
<div class="mx-auto mb-4 inline-flex h-12 w-12 items-center justify-center rounded-full" style="box-shadow:inset 0 0 0 1px hsl(var(--primary));color:hsl(var(--primary));"><svg width="24" height="24" class="h-6 w-6" aria-hidden="true"><use href="/icons/lucide.svg#check"></use></svg></div>
|
||||
<div class="mx-auto mb-4 inline-flex h-12 w-12 items-center justify-center rounded-full" style="box-shadow:inset 0 0 0 1px hsl(var(--primary));color:hsl(var(--primary));"><svg class="h-6 w-6" aria-hidden="true"><use href="/icons/lucide.svg#check"></use></svg></div>
|
||||
<p class="noir-body text-lg" style="color:hsl(var(--card-foreground));">{{ form.message|default:successMessage|default:"Thanks — your message has been sent." }}</p>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
</div>
|
||||
<div class="mt-8 hidden" data-countdown-expired>
|
||||
<p class="noir-heading text-xl" style="color:hsl(var(--foreground));">{{ expiredMessage|default:"This has started." }}</p>
|
||||
{% if expiredUrl and expiredLabel %}<a href="{{ expiredUrl }}" class="noir-btn noir-btn-solid mt-4">{{ expiredLabel }}<svg width="16" height="16" class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||
{% if expiredUrl and expiredLabel %}<a href="{{ expiredUrl }}" class="noir-btn noir-btn-solid mt-4">{{ expiredLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<noscript><p class="mt-6 text-muted-foreground">Counting down to <time datetime="{{ target }}">{{ target }}</time>.</p></noscript>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
{% if text %}<p class="noir-body mt-3 text-lg {% if background == "band" %}text-primary-foreground/80{% else %}text-muted-foreground{% endif %}">{{ text }}</p>{% endif %}
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center justify-center gap-3 {% if background != "split" %}mt-8{% else %}shrink-0{% endif %}">
|
||||
{% if primaryUrl and primaryLabel %}<a href="{{ primaryUrl }}" class="noir-btn noir-btn-solid">{{ primaryLabel }}<svg width="16" height="16" class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||
{% if primaryUrl and primaryLabel %}<a href="{{ primaryUrl }}" class="noir-btn noir-btn-solid">{{ primaryLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||
{% if secondaryUrl and secondaryLabel %}<a href="{{ secondaryUrl }}" class="noir-link inline-flex items-center">{{ secondaryLabel }}</a>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{# noir override — divider; preserves variant (line/ornament/spacer-rule), weight, width, icon. Ornament icon via ::{{ icon }}:sm::. #}
|
||||
{% with variant=variant|default:"line" weight=weight|default:"thin" width=width|default:"full" %}
|
||||
{% if variant == "ornament" %}
|
||||
<div data-block-override="noir:divider" class="bn-divider mx-auto my-8 flex items-center gap-4 {% if width == "narrow" %}max-w-[200px]{% elif width == "wide" %}max-w-3xl{% else %}w-full{% endif %}{% if class %} {{ class }}{% endif %}" role="separator"><span class="noir-rule flex-1"></span><span class="shrink-0" style="color:hsl(var(--primary));"><svg width="16" height="16" aria-hidden="true"><use href="/icons/{{ icon|default:"lucide:asterisk"|split:":"|first }}.svg#{{ icon|default:"lucide:asterisk"|split:":"|last }}"></use></svg></span><span class="noir-rule flex-1"></span></div>
|
||||
<div data-block-override="noir:divider" class="bn-divider mx-auto my-8 flex items-center gap-4 {% if width == "narrow" %}max-w-[200px]{% elif width == "wide" %}max-w-3xl{% else %}w-full{% endif %}{% if class %} {{ class }}{% endif %}" role="separator"><span class="noir-rule flex-1"></span><span class="shrink-0" style="color:hsl(var(--primary));">::{{ icon|default:"lucide:asterisk" }}:sm::</span><span class="noir-rule flex-1"></span></div>
|
||||
{% elif variant == "spacer-rule" %}
|
||||
<div data-block-override="noir:divider" class="bn-divider mx-auto py-10 flex justify-center {% if width == "narrow" %}max-w-[200px]{% elif width == "wide" %}max-w-3xl{% else %}w-full{% endif %}{% if class %} {{ class }}{% endif %}" role="separator"><hr class="mx-auto w-16 border-0 border-t {% if weight == "thick" %}border-t-2{% elif weight == "medium" %}border-t-2{% endif %} border-border"></div>
|
||||
{% else %}
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
<h3 class="noir-heading text-lg" style="color:hsl(var(--card-foreground));">{{ event.title }}</h3>
|
||||
{% if event.venue %}<p class="noir-label" style="color:hsl(var(--muted-foreground));">{{ event.venue }}</p>{% endif %}
|
||||
{% if event.description %}<p class="noir-body text-sm" style="color:hsl(var(--muted-foreground));">{{ event.description }}</p>{% endif %}
|
||||
{% if event.link %}<a href="{{ event.link }}" class="noir-link mt-auto inline-flex items-center gap-1 pt-2 text-sm">{{ event.linkLabel|default:"Details" }}<svg width="16" height="16" class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||
{% if event.link %}<a href="{{ event.link }}" class="noir-link mt-auto inline-flex items-center gap-1 pt-2 text-sm">{{ event.linkLabel|default:"Details" }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
{% endif %}{% endfor %}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
<details class="group py-2"{% if variant == "open-list" %} open{% endif %}>
|
||||
<summary class="noir-heading flex cursor-pointer list-none items-center justify-between gap-4 py-3 text-lg marker:content-none [&::-webkit-details-marker]:hidden" style="color:hsl(var(--foreground));">
|
||||
<span>{{ item.question }}</span>
|
||||
<svg width="20" height="20" class="h-5 w-5 shrink-0 text-primary transition-transform duration-200 group-open:rotate-180" aria-hidden="true"><use href="/icons/lucide.svg#chevron-down"></use></svg>
|
||||
<svg class="h-5 w-5 shrink-0 text-primary transition-transform duration-200 group-open:rotate-180" aria-hidden="true"><use href="/icons/lucide.svg#chevron-down"></use></svg>
|
||||
</summary>
|
||||
<div class="noir-body pb-4 [&_a]:text-primary [&_a]:underline" style="color:hsl(var(--muted-foreground));">{{ item.answer|safe }}</div>
|
||||
</details>
|
||||
|
||||
@ -7,10 +7,10 @@
|
||||
<div class="mt-10 grid grid-cols-1 gap-6 sm:grid-cols-2 {% if columns == 2 %}lg:grid-cols-2{% elif columns == 4 %}lg:grid-cols-4{% else %}lg:grid-cols-3{% endif %}">
|
||||
{% for item in items %}
|
||||
<div class="{% if layout == "card" %}noir-file p-6 text-card-foreground transition-colors{% elif layout == "centered" %}text-center{% endif %}">
|
||||
{% if item.icon %}<div class="mb-3 text-primary{% if layout == "centered" %} flex justify-center{% endif %}"><svg width="28" height="28" class="h-7 w-7" aria-hidden="true"><use href="/icons/{{ item.icon|split:":"|first }}.svg#{{ item.icon|split:":"|last }}"></use></svg></div>{% endif %}
|
||||
{% if item.icon %}<div class="mb-3 text-primary{% if layout == "centered" %} flex justify-center{% endif %}"><svg class="h-7 w-7" aria-hidden="true"><use href="/icons/{{ item.icon|split:":"|first }}.svg#{{ item.icon|split:":"|last }}"></use></svg></div>{% endif %}
|
||||
<h3 class="noir-heading text-lg" style="color:hsl(var(--foreground));">{% if item.linkUrl %}<a href="{{ item.linkUrl }}" class="transition-colors hover:text-primary">{{ item.title }}</a>{% else %}{{ item.title }}{% endif %}</h3>
|
||||
{% if item.text %}<p class="noir-body mt-2" style="color:hsl(var(--muted-foreground));">{{ item.text }}</p>{% endif %}
|
||||
{% if item.linkUrl and item.linkLabel %}<a href="{{ item.linkUrl }}" class="noir-link mt-3 inline-flex items-center gap-1 text-sm">{{ item.linkLabel }}<svg width="16" height="16" class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||
{% if item.linkUrl and item.linkLabel %}<a href="{{ item.linkUrl }}" class="noir-link mt-3 inline-flex items-center gap-1 text-sm">{{ item.linkLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
{% set fl = layout %}
|
||||
{% if not fl %}{% if template == "centered" %}{% set fl = "centered" %}{% else %}{% set fl = "columns" %}{% endif %}{% endif %}
|
||||
{% set fbg = backgroundColor|default:"bg-muted/50" %}
|
||||
{% macro sociallinks(cls) %}{% if social_items %}<div class="flex items-center gap-4 {{ cls }}">{% for s in social_items %}<a href="{{ s.url }}" target="_blank" rel="noopener" title="{{ s.label }}" class="text-muted-foreground hover:text-foreground transition-colors"><span class="sr-only">{{ s.label }}</span>{% if s.icon %}<svg width="20" height="20" aria-hidden="true"><use href="/icons/{{ s.icon|split:":"|first }}.svg#{{ s.icon|split:":"|last }}"></use></svg>{% else %}{{ s.label }}{% endif %}</a>{% endfor %}</div>{% endif %}{% endmacro %}
|
||||
{% macro sociallinks(cls) %}{% if social_items %}<div class="flex items-center gap-4 {{ cls }}">{% for s in social_items %}<a href="{{ s.url }}" target="_blank" rel="noopener" title="{{ s.label }}" class="text-muted-foreground hover:text-foreground transition-colors"><span class="sr-only">{{ s.label }}</span>{% if s.icon %}::{{ s.icon }}::{% else %}{{ s.label }}{% endif %}</a>{% endfor %}</div>{% endif %}{% endmacro %}
|
||||
{% macro newsletter() %}{% if showNewsletter %}<div class="max-w-sm"><h3 class="noir-label" style="color:hsl(var(--primary));">{{ newsletterTitle|default:"Stay in the loop" }}</h3>{% if newsletterDescription %}<p class="noir-body mt-1 text-sm" style="color:hsl(var(--muted-foreground));">{{ newsletterDescription }}</p>{% endif %}<div class="mt-3">{% signup_form list=newsletterListSlug|default:"main" source="footer" submit=newsletterButtonText|default:"Subscribe" %}</div></div>{% endif %}{% endmacro %}
|
||||
{% macro legalbar() %}<div class="mt-10 pt-6 border-t border-border flex flex-col sm:flex-row items-center justify-between gap-3 text-sm text-muted-foreground"><p class="noir-mono">{% if copyright %}© {{ copyright }}{% else %}© {{ companyName }}{% endif %}</p>{% if legal_items %}<nav class="flex flex-wrap items-center gap-x-4 gap-y-1">{% for l in legal_items %}<a href="{{ l.url|default:'#' }}"{% if l.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="noir-label hover:text-foreground transition-colors">{{ l.label }}</a>{% endfor %}</nav>{% endif %}</div>{% endmacro %}
|
||||
<footer data-block-override="noir:footer" class="w-full border-t border-border {{ fbg }} {{ class }}">
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{# heading override — typed case-file header. Fields mirror builtin: text, level, textClass. #}
|
||||
{% if level == 1 %}<h1 data-block-override="noir:heading" class="noir-heading text-4xl md:text-5xl mb-5 flex-1 {{ textClass }}" style="color:hsl(var(--foreground));">{{ text }}</h1>
|
||||
{% elif level == 3 %}<h3 data-block-override="noir:heading" class="noir-heading text-2xl md:text-3xl mb-3 flex-1 {{ textClass }}" style="color:hsl(var(--foreground));">{{ text }}</h3>
|
||||
{% elif level == 4 %}<h4 data-block-override="noir:heading" class="noir-heading text-xl md:text-2xl mb-2 flex-1 {{ textClass }}" style="color:hsl(var(--foreground));">{{ text }}</h4>
|
||||
{% elif level == 5 %}<h5 data-block-override="noir:heading" class="noir-heading text-lg md:text-xl mb-2 flex-1 {{ textClass }}" style="color:hsl(var(--foreground));">{{ text }}</h5>
|
||||
{% elif level == 6 %}<h6 data-block-override="noir:heading" class="noir-heading text-base md:text-lg mb-2 flex-1 {{ textClass }}" style="color:hsl(var(--foreground));">{{ text }}</h6>
|
||||
{% else %}<h2 data-block-override="noir:heading" class="noir-heading text-3xl md:text-4xl mb-4 flex-1 {{ textClass }}" style="color:hsl(var(--foreground));">{{ text }}</h2>{% endif %}
|
||||
{% if level == 1 %}<h1 data-block-override="noir:heading" class="noir-heading text-4xl md:text-5xl flex-1 {{ textClass }}" style="color:hsl(var(--foreground));">{{ text }}</h1>
|
||||
{% elif level == 3 %}<h3 data-block-override="noir:heading" class="noir-heading text-2xl md:text-3xl flex-1 {{ textClass }}" style="color:hsl(var(--foreground));">{{ text }}</h3>
|
||||
{% elif level == 4 %}<h4 data-block-override="noir:heading" class="noir-heading text-xl md:text-2xl flex-1 {{ textClass }}" style="color:hsl(var(--foreground));">{{ text }}</h4>
|
||||
{% elif level == 5 %}<h5 data-block-override="noir:heading" class="noir-heading text-lg md:text-xl flex-1 {{ textClass }}" style="color:hsl(var(--foreground));">{{ text }}</h5>
|
||||
{% elif level == 6 %}<h6 data-block-override="noir:heading" class="noir-heading text-base md:text-lg flex-1 {{ textClass }}" style="color:hsl(var(--foreground));">{{ text }}</h6>
|
||||
{% else %}<h2 data-block-override="noir:heading" class="noir-heading text-3xl md:text-4xl flex-1 {{ textClass }}" style="color:hsl(var(--foreground));">{{ text }}</h2>{% endif %}
|
||||
|
||||
@ -9,32 +9,31 @@
|
||||
{% set sUrl = ctaSecondary.url %}
|
||||
{% set variant = template|default:"centered" %}
|
||||
{% set hasImage = backgroundImage %}
|
||||
{# With a background image the content sits on a dark grayscale photo + dark scrim in BOTH modes, so onImg switches text to light utility classes. #}
|
||||
{% set showOverlay = hasImage %}
|
||||
{% macro herocontent(align, onImg) %}
|
||||
{% if eyebrowText %}<div class="mb-5 flex items-center gap-4 {% if align == "center" %}justify-center{% endif %}"><span class="noir-rule w-10"></span><span class="noir-label{% if onImg %} text-primary{% endif %}"{% if not onImg %} style="color:hsl(var(--primary));"{% endif %}>{{ eyebrowText }}</span><span class="noir-rule w-10"></span></div>{% endif %}
|
||||
{% if headlineText %}<h1 class="noir-heading text-4xl sm:text-5xl lg:text-6xl leading-tight{% if onImg %} text-white{% endif %}"{% if not onImg %} style="color:hsl(var(--foreground));"{% endif %}>{{ headlineText }}</h1>{% endif %}
|
||||
{% if subText %}<h2 class="noir-body mt-5 text-xl sm:text-2xl max-w-2xl{% if align == "center" %} mx-auto{% endif %}{% if onImg %} text-white/85{% endif %}"{% if not onImg %} style="color:hsl(var(--muted-foreground));"{% endif %}>{{ subText }}</h2>{% endif %}
|
||||
{% if descText %}<p class="noir-body mt-4 max-w-xl leading-relaxed{% if align == "center" %} mx-auto{% endif %}{% if onImg %} text-white/80{% endif %}"{% if not onImg %} style="color:hsl(var(--muted-foreground));"{% endif %}>{{ descText }}</p>{% endif %}
|
||||
{% set showOverlay = hasImage and overlayColor != "none" and overlayColor != "" %}
|
||||
{% macro herocontent(align) %}
|
||||
{% if eyebrowText %}<div class="mb-5 flex items-center gap-4 {% if align == "center" %}justify-center{% endif %}"><span class="noir-rule w-10"></span><span class="noir-label" style="color:hsl(var(--primary));">{{ eyebrowText }}</span><span class="noir-rule w-10"></span></div>{% endif %}
|
||||
{% if headlineText %}<h1 class="noir-heading text-4xl sm:text-5xl lg:text-6xl leading-tight" style="color:hsl(var(--foreground));">{{ headlineText }}</h1>{% endif %}
|
||||
{% if subText %}<h2 class="noir-body mt-5 text-xl sm:text-2xl max-w-2xl{% if align == "center" %} mx-auto{% endif %}" style="color:hsl(var(--muted-foreground));">{{ subText }}</h2>{% endif %}
|
||||
{% if descText %}<p class="noir-body mt-4 max-w-xl leading-relaxed{% if align == "center" %} mx-auto{% endif %}" style="color:hsl(var(--muted-foreground));">{{ descText }}</p>{% endif %}
|
||||
{% if pText or sText %}<div class="mt-8 flex flex-col sm:flex-row gap-4{% if align == "center" %} justify-center{% endif %}">
|
||||
{% if pText and pUrl %}<a href="{{ pUrl }}" class="noir-btn noir-btn-solid inline-flex items-center justify-center gap-2">{{ pText }}</a>{% endif %}
|
||||
{% if sText and sUrl %}<a href="{{ sUrl }}" class="noir-link inline-flex items-center justify-center{% if onImg %} text-white border-white/70 hover:text-white/80{% endif %}">{{ sText }}</a>{% endif %}
|
||||
{% if sText and sUrl %}<a href="{{ sUrl }}" class="noir-link inline-flex items-center justify-center">{{ sText }}</a>{% endif %}
|
||||
</div>{% endif %}
|
||||
{% if trustLogos %}<div class="mt-10">
|
||||
<p class="noir-label mb-3{% if onImg %} text-white/70{% endif %}"{% if not onImg %} style="color:hsl(var(--muted-foreground));"{% endif %}>Trusted by</p>
|
||||
<p class="noir-label mb-3" style="color:hsl(var(--muted-foreground));">Trusted by</p>
|
||||
<div class="flex flex-wrap items-center gap-6{% if align == "center" %} justify-center{% endif %}">{% for logo in trustLogos %}{% if logo.url %}<a href="{{ logo.url }}" target="_blank" rel="noopener noreferrer" class="opacity-60 transition-opacity hover:opacity-100">{% img logo.image alt=logo.alt class="h-8 w-auto" %}</a>{% else %}{% img logo.image alt=logo.alt class="h-8 w-auto opacity-60" %}{% endif %}{% endfor %}</div>
|
||||
</div>{% endif %}
|
||||
{% endmacro %}
|
||||
{% if variant == "split" %}
|
||||
<section data-block-override="noir:hero" class="hero {% if hasImage %}grid grid-cols-1 lg:grid-cols-2{% endif %} overflow-hidden bg-background text-foreground noir-grain{% if class %} {{ class }}{% endif %}">
|
||||
{% if hasImage %}<div class="relative min-h-80 bg-muted grayscale">{% img backgroundImage alt=headlineText class="absolute inset-0 h-full w-full object-cover" layout="hero" %}</div>{% endif %}
|
||||
<div class="flex flex-col justify-center py-16 md:py-20 lg:py-24 px-6 lg:px-12"><div class="max-w-lg text-left">{{ herocontent("left", false) }}</div></div>
|
||||
<div class="flex flex-col justify-center py-16 md:py-20 lg:py-24 px-6 lg:px-12"><div class="max-w-lg text-left">{{ herocontent("left") }}</div></div>
|
||||
</section>
|
||||
{% else %}
|
||||
{% set align = "center" %}
|
||||
{% if variant == "left-aligned" %}{% set align = "left" %}{% endif %}
|
||||
<section data-block-override="noir:hero" class="hero relative flex flex-col overflow-hidden bg-background text-foreground noir-grain {% if minHeight == "screen" %}min-h-screen justify-center{% else %}py-16 md:py-20 lg:py-24{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||
{% if hasImage %}<div class="absolute inset-0 z-0 grayscale">{% img backgroundImage alt=headlineText class="h-full w-full object-cover" layout="hero" %}{% if showOverlay %}<div class="absolute inset-0 {% if overlayColor == "light" %}bg-background/60{% elif overlayColor == "primary" %}bg-primary/50{% elif overlayOpacity <= 30 %}bg-black/30{% elif overlayOpacity <= 50 %}bg-black/50{% elif overlayOpacity <= 70 %}bg-black/70{% else %}bg-black/80{% endif %}"></div>{% endif %}</div>{% endif %}
|
||||
<div class="relative z-10 mx-auto w-full max-w-4xl px-4 sm:px-6 lg:px-8 {% if variant == "left-aligned" %}text-left{% else %}text-center{% endif %}">{{ herocontent(align, hasImage) }}</div>
|
||||
{% if hasImage %}<div class="absolute inset-0 z-0 noir-spotlight grayscale">{% img backgroundImage alt=headlineText class="h-full w-full object-cover" layout="hero" %}{% if showOverlay %}<div class="absolute inset-0 {% if overlayColor == "light" %}bg-background/60{% elif overlayColor == "primary" %}bg-primary/50{% elif overlayOpacity <= 30 %}bg-black/30{% elif overlayOpacity <= 50 %}bg-black/50{% elif overlayOpacity <= 70 %}bg-black/70{% else %}bg-black/80{% endif %}"></div>{% endif %}</div>{% endif %}
|
||||
<div class="relative z-10 mx-auto w-full max-w-4xl px-4 sm:px-6 lg:px-8 {% if variant == "left-aligned" %}text-left{% else %}text-center{% endif %}">{{ herocontent(align) }}</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
@ -19,11 +19,11 @@
|
||||
{% if specialHoursNote %}<p class="noir-body mt-4 border-t border-border pt-3 text-sm" style="color:hsl(var(--muted-foreground));">{{ specialHoursNote }}</p>{% endif %}
|
||||
{% if address %}<div class="mt-6"><div class="noir-label" style="color:hsl(var(--primary));">Address</div><address class="noir-body mt-1 whitespace-pre-line not-italic" style="color:hsl(var(--card-foreground));">{{ address }}</address></div>{% endif %}
|
||||
{% if phone %}<div class="mt-4"><div class="noir-label" style="color:hsl(var(--primary));">Phone</div><a href="tel:{{ phone }}" class="noir-link mt-1 inline-block">{{ phone }}</a></div>{% endif %}
|
||||
{% if directionsUrl %}<a href="{{ directionsUrl }}" target="_blank" rel="noopener noreferrer" class="noir-btn noir-btn-solid mt-6 inline-flex items-center gap-2"><svg width="16" height="16" class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#navigation"></use></svg>Get directions</a>{% endif %}
|
||||
{% if directionsUrl %}<a href="{{ directionsUrl }}" target="_blank" rel="noopener noreferrer" class="noir-btn noir-btn-solid mt-6 inline-flex items-center gap-2"><svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#navigation"></use></svg>Get directions</a>{% endif %}
|
||||
</div>
|
||||
{% if mapEmbedUrl %}
|
||||
<div class="aspect-square w-full overflow-hidden noir-file md:aspect-auto">
|
||||
<button type="button" data-map-src="{{ mapEmbedUrl }}" onclick="var f=document.createElement('iframe');f.src=this.getAttribute('data-map-src');f.setAttribute('class','h-full w-full border-0');f.setAttribute('loading','lazy');f.setAttribute('referrerpolicy','no-referrer-when-downgrade');f.setAttribute('title','Map');this.parentNode.replaceChild(f,this);" class="noir-label flex h-full min-h-[16rem] w-full items-center justify-center gap-2 transition-colors hover:bg-accent hover:text-accent-foreground" style="color:hsl(var(--primary));"><svg width="20" height="20" class="h-5 w-5" aria-hidden="true"><use href="/icons/lucide.svg#map-pin"></use></svg>{{ mapButtonLabel|default:"Load map" }}</button>
|
||||
<button type="button" data-map-src="{{ mapEmbedUrl }}" onclick="var f=document.createElement('iframe');f.src=this.getAttribute('data-map-src');f.setAttribute('class','h-full w-full border-0');f.setAttribute('loading','lazy');f.setAttribute('referrerpolicy','no-referrer-when-downgrade');f.setAttribute('title','Map');this.parentNode.replaceChild(f,this);" class="noir-label flex h-full min-h-[16rem] w-full items-center justify-center gap-2 transition-colors hover:bg-accent hover:text-accent-foreground" style="color:hsl(var(--primary));"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/lucide.svg#map-pin"></use></svg>{{ mapButtonLabel|default:"Load map" }}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
{# image override — square evidence frame with a typed mono caption. Fields mirror builtin: src, alt, caption, size, align, aspectRatio, objectFit. Media resolved via {% img %} (the media filter is not registered in the block render path). #}
|
||||
{% with size=size|default:"full" align=align|default:"center" objectFit=objectFit|default:"cover" %}
|
||||
<figure data-block-override="noir:image" class="my-8 flex-1 flex flex-col {% if align == "left" %}items-start{% elif align == "right" %}items-end{% else %}items-center{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||
<figure data-block-override="noir:image" class="my-8 flex-1 {% if align == "left" %}flex justify-start{% elif align == "right" %}flex justify-end{% else %}flex justify-center{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||
<div class="noir-file p-2 {% 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 %}">
|
||||
<div class="relative overflow-hidden bg-muted grayscale {% if aspectRatio == "square" %}aspect-square{% elif aspectRatio == "video" %}aspect-video{% elif aspectRatio == "portrait" %}aspect-[3/4]{% elif aspectRatio == "wide" %}aspect-[21/9]{% endif %}">
|
||||
<div class="relative overflow-hidden bg-muted {% 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 %}{% elif objectFit == "none" %}{% img src alt=alt class="w-full h-full object-none" layout=size %}{% else %}{% img src alt=alt class="w-full h-full object-cover" layout=size %}{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
{% macro navlink(item) %}<a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}{% if item.title %} title="{{ item.title }}"{% endif %} class="noir-label px-3 py-2 text-foreground/80 hover:text-primary transition-colors {{ item.css_class }}">{{ item.label }}</a>{% endmacro %}
|
||||
{% macro dropdown(item) %}<div class="relative group">
|
||||
<button type="button" class="noir-label px-3 py-2 text-foreground/80 hover:text-primary transition-colors inline-flex items-center gap-1">{{ item.label }}<svg class="h-4 w-4 transition-transform group-hover:rotate-180" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg></button>
|
||||
{% if use_mega %}<div class="absolute left-1/2 -translate-x-1/2 mt-1 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50"><div class="noir-file p-6 grid gap-8" style="grid-template-columns:repeat({{ item.children|length|default:1 }},minmax(10rem,1fr));min-width:min(90vw,42rem)">{% for child in item.children %}<div class="space-y-1">{% if child.item_type == "header" %}<div class="noir-label px-2 py-1 border-b border-border mb-2" style="color:hsl(var(--primary));">{{ child.label }}</div>{% else %}<a href="{{ child.url|default:'#' }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="block px-2 py-2 hover:bg-accent transition-colors {{ child.css_class }}">{% if child.icon %}<span class="mr-1.5 text-primary"><svg width="20" height="20" aria-hidden="true"><use href="/icons/{{ child.icon|split:":"|first }}.svg#{{ child.icon|split:":"|last }}"></use></svg></span>{% endif %}<span class="noir-heading text-sm text-foreground">{{ child.label }}</span>{% if child.description %}<span class="noir-body block text-xs text-muted-foreground mt-0.5">{{ child.description }}</span>{% endif %}</a>{% endif %}</div>{% endfor %}</div></div>
|
||||
{% if use_mega %}<div class="absolute left-1/2 -translate-x-1/2 mt-1 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50"><div class="noir-file p-6 grid gap-8" style="grid-template-columns:repeat({{ item.children|length|default:1 }},minmax(10rem,1fr));min-width:min(90vw,42rem)">{% for child in item.children %}<div class="space-y-1">{% if child.item_type == "header" %}<div class="noir-label px-2 py-1 border-b border-border mb-2" style="color:hsl(var(--primary));">{{ child.label }}</div>{% else %}<a href="{{ child.url|default:'#' }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="block px-2 py-2 hover:bg-accent transition-colors {{ child.css_class }}">{% if child.icon %}<span class="mr-1.5 text-primary">::{{ child.icon }}::</span>{% endif %}<span class="noir-heading text-sm text-foreground">{{ child.label }}</span>{% if child.description %}<span class="noir-body block text-xs text-muted-foreground mt-0.5">{{ child.description }}</span>{% endif %}</a>{% endif %}</div>{% endfor %}</div></div>
|
||||
{% else %}<div class="absolute left-0 mt-1 w-56 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50"><div class="noir-file py-2">{% for child in item.children %}{% if child.item_type == "header" %}<div class="noir-label px-4 py-2" style="color:hsl(var(--primary));">{{ child.label }}</div>{% else %}<a href="{{ child.url|default:'#' }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="noir-body block px-4 py-2 text-sm text-foreground hover:bg-accent transition-colors {{ child.css_class }}">{{ child.label }}</a>{% endif %}{% endfor %}</div></div>{% endif %}
|
||||
</div>{% endmacro %}
|
||||
{% macro brand(size) %}<a href="/" class="flex-shrink-0 flex items-center">{% if logoType == "text" and logoText.text %}<span class="noir-heading text-xl {{ logoText.color }}" style="color:hsl(var(--foreground));">{{ logoText.text }}</span>{% elif logo %}<img src="{{ logo }}" alt="{{ alt|default:companyName }}" class="{{ size }} w-auto"{% if logoMaxWidth or logoMaxHeight %} style="{% if logoMaxWidth %}max-width:{{ logoMaxWidth }};{% endif %}{% if logoMaxHeight %}max-height:{{ logoMaxHeight }};{% endif %}"{% endif %}/>{% elif companyName %}<span class="noir-heading text-xl" style="color:hsl(var(--foreground));">{{ companyName }}</span>{% endif %}</a>{% endmacro %}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<ul class="mt-6 space-y-3 max-w-lg mx-auto">
|
||||
{% for suggestion in suggestions %}
|
||||
<li class="flex items-start gap-3">
|
||||
<span class="mt-0.5 shrink-0" style="color:hsl(var(--primary));"><svg width="16" height="16" aria-hidden="true"><use href="/icons/lucide.svg#chevron-right"></use></svg></span>
|
||||
<span class="mt-0.5 shrink-0" style="color:hsl(var(--primary));">::lucide:chevron-right:sm::</span>
|
||||
<div>
|
||||
<a href="{{ suggestion.slug }}" class="noir-link font-medium">{{ suggestion.title }}</a>
|
||||
{% if showDescription and suggestion.excerpt %}<p class="noir-body mt-1 text-sm" style="color:hsl(var(--muted-foreground));">{{ suggestion.excerpt }}</p>{% endif %}
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
</p>
|
||||
{% if tier.description %}<p class="noir-body mt-3 text-center text-sm" style="color:hsl(var(--muted-foreground));">{{ tier.description }}</p>{% endif %}
|
||||
{% if tier.features %}<ul class="noir-body mt-6 flex-1 space-y-3 text-sm">
|
||||
{% for feature in tier.features %}<li class="flex items-start gap-2"><svg width="16" height="16" class="mt-0.5 h-4 w-4 shrink-0 text-primary" aria-hidden="true"><use href="/icons/lucide.svg#check"></use></svg><span>{{ feature }}</span></li>{% endfor %}
|
||||
{% for feature in tier.features %}<li class="flex items-start gap-2"><svg class="mt-0.5 h-4 w-4 shrink-0 text-primary" aria-hidden="true"><use href="/icons/lucide.svg#check"></use></svg><span>{{ feature }}</span></li>{% endfor %}
|
||||
</ul>{% endif %}
|
||||
{% if tier.ctaUrl and tier.ctaLabel %}{% if tier.highlighted %}<a href="{{ tier.ctaUrl }}" class="noir-btn noir-btn-solid mt-8 w-full">{{ tier.ctaLabel }}</a>{% else %}<a href="{{ tier.ctaUrl }}" class="noir-btn mt-8 w-full">{{ tier.ctaLabel }}</a>{% endif %}{% endif %}
|
||||
</div>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
{% if eyebrow %}<div class="mb-3 flex items-center gap-4"><span class="noir-rule w-12"></span><p class="noir-label" style="color:hsl(var(--primary));">{{ eyebrow }}</p></div>{% endif %}
|
||||
{% if heading %}<h2 class="noir-heading mt-2 text-3xl md:text-4xl" style="color:hsl(var(--foreground));">{{ heading }}</h2>{% endif %}
|
||||
{% if body %}<div class="noir-body mt-4 space-y-4 text-lg text-muted-foreground [&_a]:text-primary [&_a]:underline [&_strong]:text-foreground">{{ body|safe }}</div>{% endif %}
|
||||
{% if ctaUrl and ctaLabel %}<a href="{{ ctaUrl }}" class="noir-btn noir-btn-solid mt-6 inline-flex items-center gap-2">{{ ctaLabel }}<svg width="16" height="16" class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||
{% if ctaUrl and ctaLabel %}<a href="{{ ctaUrl }}" class="noir-btn noir-btn-solid mt-6 inline-flex items-center gap-2">{{ ctaLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
|
||||
</div>
|
||||
<div class="{% if mediaPosition == "left" %}md:order-1{% else %}md:order-2{% endif %}">
|
||||
{% if media %}<div class="noir-file p-2 overflow-hidden"><div class="overflow-hidden bg-muted grayscale">{% img media alt=mediaAlt class="w-full h-auto object-cover" %}</div></div>{% endif %}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
{% if title %}<h3 class="noir-label {% if compact %}px-2 mb-4{% else %}px-3 mb-5{% endif %}" style="color:hsl(var(--muted-foreground));">{{ title }}</h3>{% endif %}
|
||||
<ul class="{% if compact %}space-y-0.5{% else %}space-y-1{% endif %}">
|
||||
{% for item in menu.items %}
|
||||
<li><a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}{% if item.title %} title="{{ item.title }}"{% endif %} data-bn-sidebar-link class="noir-label flex items-center rounded-none text-muted-foreground transition-colors hover:text-primary hover:bg-accent {% if compact %}px-2 py-1.5{% else %}px-3 py-2{% endif %} {{ item.css_class }}">{% if item.icon %}<svg width="16" height="16" class="mr-2 h-4 w-4" style="color:hsl(var(--primary));" aria-hidden="true"><use href="/icons/{{ item.icon|split:":"|first }}.svg#{{ item.icon|split:":"|last }}"></use></svg>{% endif %}{{ item.label }}</a></li>
|
||||
<li><a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}{% if item.title %} title="{{ item.title }}"{% endif %} data-bn-sidebar-link class="noir-label flex items-center rounded-none text-muted-foreground transition-colors hover:text-primary hover:bg-accent {% if compact %}px-2 py-1.5{% else %}px-3 py-2{% endif %} {{ item.css_class }}">{% if item.icon %}<svg class="mr-2 h-4 w-4" style="color:hsl(var(--primary));" aria-hidden="true"><use href="/icons/{{ item.icon|split:":"|first }}.svg#{{ item.icon|split:":"|last }}"></use></svg>{% endif %}{{ item.label }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
{% for link in links %}{% if link.url %}
|
||||
<li>
|
||||
<a href="{{ link.url }}" target="_blank" rel="noopener noreferrer" aria-label="{{ link.label|default:link.network }}" class="noir-file inline-flex items-center gap-2 text-foreground transition-colors hover:bg-primary/10 hover:text-primary {% if style == "stack" %}w-full px-4 py-2.5{% else %}p-2.5{% endif %}">
|
||||
{% if link.network %}<svg width="20" height="20" class="h-5 w-5 text-primary" aria-hidden="true"><use href="/icons/{{ link.network|split:":"|first }}.svg#{{ link.network|split:":"|last }}"></use></svg>{% endif %}
|
||||
{% if link.network %}<svg class="h-5 w-5 text-primary" aria-hidden="true"><use href="/icons/{{ link.network|split:":"|first }}.svg#{{ link.network|split:":"|last }}"></use></svg>{% endif %}
|
||||
{% if style == "stack" and link.label %}<span class="noir-label">{{ link.label }}</span>{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
{% if member.role %}<p class="noir-label" style="color:hsl(var(--primary));">{{ member.role }}</p>{% endif %}
|
||||
{% if member.bio %}<p class="noir-body mt-2 text-sm" style="color:hsl(var(--muted-foreground));">{{ member.bio }}</p>{% endif %}
|
||||
{% if member.socials %}<div class="mt-3 flex gap-3 {% if layout != "list" %}justify-center{% endif %}">
|
||||
{% for social in member.socials %}{% if social.url %}<a href="{{ social.url }}" class="text-muted-foreground transition-colors hover:text-primary" aria-label="Social link"><svg width="20" height="20" class="h-5 w-5" aria-hidden="true"><use href="/icons/{{ social.icon|split:":"|first }}.svg#{{ social.icon|split:":"|last }}"></use></svg></a>{% endif %}{% endfor %}
|
||||
{% for social in member.socials %}{% if social.url %}<a href="{{ social.url }}" class="text-muted-foreground transition-colors hover:text-primary" aria-label="Social link"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/{{ social.icon|split:":"|first }}.svg#{{ social.icon|split:":"|last }}"></use></svg></a>{% endif %}{% endfor %}
|
||||
</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -6,11 +6,11 @@
|
||||
{% for q in quotes %}
|
||||
<figure class="noir-file flex flex-col p-6 text-card-foreground{% if layout == "carousel" %} min-w-[18rem] max-w-sm shrink-0 snap-start{% endif %}">
|
||||
{% if q.rating %}<div class="mb-3 flex gap-0.5" aria-label="Rated {{ q.rating }} out of 5">
|
||||
<svg width="16" height="16" class="h-4 w-4 {% if q.rating >= 1 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||
<svg width="16" height="16" class="h-4 w-4 {% if q.rating >= 2 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||
<svg width="16" height="16" class="h-4 w-4 {% if q.rating >= 3 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||
<svg width="16" height="16" class="h-4 w-4 {% if q.rating >= 4 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||
<svg width="16" height="16" class="h-4 w-4 {% if q.rating >= 5 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||
<svg class="h-4 w-4 {% if q.rating >= 1 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||
<svg class="h-4 w-4 {% if q.rating >= 2 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||
<svg class="h-4 w-4 {% if q.rating >= 3 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||
<svg class="h-4 w-4 {% if q.rating >= 4 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||
<svg class="h-4 w-4 {% if q.rating >= 5 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
|
||||
</div>{% endif %}
|
||||
<blockquote class="noir-body flex-1 text-lg leading-relaxed" style="color:hsl(var(--card-foreground));">{% if q.quote %}“{{ q.quote }}”{% endif %}</blockquote>
|
||||
<div class="my-4 flex items-center"><span class="noir-rule w-16"></span></div>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
{% for step in steps %}
|
||||
<li class="{% if orientation == "horizontal" %}text-center{% else %}relative pl-14{% endif %}">
|
||||
<span class="noir-mono flex h-8 w-8 items-center justify-center rounded-full bg-primary text-sm font-semibold text-primary-foreground {% if orientation == "horizontal" %}mx-auto{% else %}absolute left-0 top-0 ring-4 ring-background{% endif %}">
|
||||
{% if marker == "numbered" %}{{ forloop.counter }}{% elif marker == "dot" %}<span class="h-2 w-2 rounded-full bg-primary-foreground"></span>{% else %}<svg width="16" height="16" class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#flag"></use></svg>{% endif %}
|
||||
{% if marker == "numbered" %}{{ forloop.counter }}{% elif marker == "dot" %}<span class="h-2 w-2 rounded-full bg-primary-foreground"></span>{% else %}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#flag"></use></svg>{% endif %}
|
||||
</span>
|
||||
<div class="{% if orientation == "horizontal" %}mt-4{% else %}pt-1{% endif %}">
|
||||
{% if step.label %}<p class="noir-label" style="color:hsl(var(--primary));">{{ step.label }}</p>{% endif %}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{# noir override — utility-bar; preserves variant, showOnMobile, leftItems/rightItems {icon,label,link}, ubitem macro + ::pack:name:: icon shorthand. #}
|
||||
{% set ubcls = "bg-muted text-muted-foreground border-b border-border" %}
|
||||
{% if variant == "primary" %}{% set ubcls = "bg-primary text-primary-foreground" %}{% elif variant == "dark" %}{% set ubcls = "bg-foreground text-background" %}{% endif %}
|
||||
{% macro ubitem(it) %}{% if it.link %}<a href="{{ it.link }}" class="noir-label inline-flex items-center gap-1.5 hover:text-foreground transition-colors">{% if it.icon %}<svg width="20" height="20" aria-hidden="true"><use href="/icons/{{ it.icon|split:":"|first }}.svg#{{ it.icon|split:":"|last }}"></use></svg>{% endif %}{% if it.label %}<span>{{ it.label }}</span>{% endif %}</a>{% else %}<span class="noir-label inline-flex items-center gap-1.5">{% if it.icon %}<svg width="20" height="20" aria-hidden="true"><use href="/icons/{{ it.icon|split:":"|first }}.svg#{{ it.icon|split:":"|last }}"></use></svg>{% endif %}{% if it.label %}<span>{{ it.label }}</span>{% endif %}</span>{% endif %}{% endmacro %}
|
||||
{% macro ubitem(it) %}{% if it.link %}<a href="{{ it.link }}" class="noir-label inline-flex items-center gap-1.5 hover:text-foreground transition-colors">{% if it.icon %}::{{ it.icon }}::{% endif %}{% if it.label %}<span>{{ it.label }}</span>{% endif %}</a>{% else %}<span class="noir-label inline-flex items-center gap-1.5">{% if it.icon %}::{{ it.icon }}::{% endif %}{% if it.label %}<span>{{ it.label }}</span>{% endif %}</span>{% endif %}{% endmacro %}
|
||||
<div data-block-override="noir:utility-bar" class="w-full text-[0.7rem] {% if not showOnMobile %}hidden sm:block{% endif %} {{ ubcls }} {{ class }}">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-9 flex items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-5 flex-wrap">{% for it in leftItems %}{{ ubitem(it) }}{% endfor %}</div>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{# noir override — video-embed; preserves aspect, url, media, poster, title, caption, data-bn-video, data-video-url, data-video-media, {% img %} calls, and the facade IIFE verbatim. Only frame/button restyled (square evidence frame, grayscale poster). #}
|
||||
{% with aspect=aspect|default:"16:9" %}<figure data-block-override="noir:video-embed" class="bn-video my-8{% if class %} {{ class }}{% endif %}"><div class="noir-file p-2"><div class="bn-video-frame relative w-full overflow-hidden bg-muted grayscale {% if aspect == "4:3" %}aspect-[4/3]{% elif aspect == "1:1" %}aspect-square{% elif aspect == "21:9" %}aspect-[21/9]{% else %}aspect-video{% endif %}" data-bn-video data-video-url="{{ url|default:"" }}" data-video-media="{% if media %}{{ media|mediaURL }}{% endif %}"><button type="button" data-bn-video-play class="group absolute inset-0 flex h-full w-full items-center justify-center focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring" aria-label="Play video{% if title %}: {{ title }}{% endif %}">{% if poster %}{% img poster alt=title|default:"" class="absolute inset-0 h-full w-full object-cover" %}{% endif %}<span class="absolute inset-0 bg-foreground/10 transition-colors group-hover:bg-foreground/20" aria-hidden="true"></span><span class="relative inline-flex h-16 w-16 items-center justify-center rounded-full bg-primary text-primary-foreground transition-transform group-hover:scale-105 motion-reduce:transform-none"><svg width="32" height="32" aria-hidden="true"><use href="/icons/lucide.svg#play"></use></svg></span></button></div></div>{% if caption %}<figcaption class="noir-mono mt-3 text-center text-sm" style="color:hsl(var(--muted-foreground));">{{ caption }}</figcaption>{% endif %}</figure><script>
|
||||
{% with aspect=aspect|default:"16:9" %}<figure data-block-override="noir:video-embed" class="bn-video my-8{% if class %} {{ class }}{% endif %}"><div class="noir-file p-2"><div class="bn-video-frame relative w-full overflow-hidden bg-muted grayscale {% if aspect == "4:3" %}aspect-[4/3]{% elif aspect == "1:1" %}aspect-square{% elif aspect == "21:9" %}aspect-[21/9]{% else %}aspect-video{% endif %}" data-bn-video data-video-url="{{ url|default:"" }}" data-video-media="{% if media %}{{ media|mediaURL }}{% endif %}"><button type="button" data-bn-video-play class="group absolute inset-0 flex h-full w-full items-center justify-center focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring" aria-label="Play video{% if title %}: {{ title }}{% endif %}">{% if poster %}{% img poster alt=title|default:"" class="absolute inset-0 h-full w-full object-cover" %}{% endif %}<span class="absolute inset-0 bg-foreground/10 transition-colors group-hover:bg-foreground/20" aria-hidden="true"></span><span class="relative inline-flex h-16 w-16 items-center justify-center rounded-full bg-primary text-primary-foreground transition-transform group-hover:scale-105 motion-reduce:transform-none">::lucide:play:xl::</span></button></div></div>{% if caption %}<figcaption class="noir-mono mt-3 text-center text-sm" style="color:hsl(var(--muted-foreground));">{{ caption }}</figcaption>{% endif %}</figure><script>
|
||||
(function(){
|
||||
if(window.__bnVideoFacade)return;window.__bnVideoFacade=true;
|
||||
function yt(u){var m=u.match(/(?:youtube\.com\/(?:watch\?v=|embed\/|shorts\/)|youtu\.be\/)([\w-]{6,})/);return m?m[1]:null;}
|
||||
|
||||