fix(noir): reveal race, light-mode legibility, spacing, media, SVG guard
- Reveal: shorten scroll-reveal safety timeout below the capture settle and reveal-all when viewport height is unknown (headless), so main content is no longer stranded mid-fade at ~0.25 opacity in both modes; give the article template the robust reveal script it was missing. Fixes the washed-out light-mode body/quote copy AND the H1-over-first-line overlap (the translateY mid-transition was dropping the heading onto the body). - Headings: add bottom margin per level so H1 no longer butts the first paragraph. - Article: collapse the aside rail (and its divider border) when empty; populate each case article's aside with case-file marginalia. - Light mode: bump muted-foreground contrast across all five presets (~L38->L30) for comfortable AA on secondary text; dark palettes untouched. - Media: wire 6 licensed noir stock shots as seed media — harbour-night hero (with readable light-on-scrim hero text in both modes), per-article lead photos, typewriter on About. - SVG geometry guard: explicit width/height on 26 <use> icon SVGs; replace 15 unsized ::icon:: shortcodes with sized <svg><use> markup. - Declare required_icon_packs (lucide, simple-icons). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
34a9bc61c0
commit
2f7239b76a
@ -7,3 +7,4 @@ description = "Detective and true-crime dossier theme: case-file cards, typewrit
|
||||
kind = "theme"
|
||||
categories = ["templates"]
|
||||
tags = ["noir", "detective", "true-crime", "dossier", "typewriter", "monochrome", "editorial", "dark"]
|
||||
required_icon_packs = ["lucide", "simple-icons"]
|
||||
|
||||
10
presets.json
10
presets.json
@ -17,7 +17,7 @@
|
||||
"secondary": "40 8% 90%",
|
||||
"secondaryForeground": "24 12% 14%",
|
||||
"muted": "40 8% 91%",
|
||||
"mutedForeground": "28 6% 38%",
|
||||
"mutedForeground": "28 9% 30%",
|
||||
"accent": "0 60% 48%",
|
||||
"accentForeground": "40 20% 97%",
|
||||
"destructive": "0 74% 40%",
|
||||
@ -67,7 +67,7 @@
|
||||
"secondary": "20 8% 90%",
|
||||
"secondaryForeground": "0 8% 12%",
|
||||
"muted": "20 8% 92%",
|
||||
"mutedForeground": "12 6% 36%",
|
||||
"mutedForeground": "12 9% 29%",
|
||||
"accent": "8 68% 50%",
|
||||
"accentForeground": "20 12% 98%",
|
||||
"destructive": "358 78% 42%",
|
||||
@ -117,7 +117,7 @@
|
||||
"secondary": "38 16% 88%",
|
||||
"secondaryForeground": "30 18% 14%",
|
||||
"muted": "38 14% 90%",
|
||||
"mutedForeground": "32 12% 38%",
|
||||
"mutedForeground": "32 14% 30%",
|
||||
"accent": "26 70% 48%",
|
||||
"accentForeground": "38 30% 97%",
|
||||
"destructive": "0 70% 44%",
|
||||
@ -167,7 +167,7 @@
|
||||
"secondary": "210 14% 89%",
|
||||
"secondaryForeground": "215 22% 14%",
|
||||
"muted": "210 12% 91%",
|
||||
"mutedForeground": "214 10% 38%",
|
||||
"mutedForeground": "214 13% 30%",
|
||||
"accent": "8 62% 48%",
|
||||
"accentForeground": "210 20% 97%",
|
||||
"destructive": "0 72% 44%",
|
||||
@ -217,7 +217,7 @@
|
||||
"secondary": "0 0% 92%",
|
||||
"secondaryForeground": "0 0% 12%",
|
||||
"muted": "0 0% 93%",
|
||||
"mutedForeground": "0 0% 36%",
|
||||
"mutedForeground": "0 0% 28%",
|
||||
"accent": "0 0% 12%",
|
||||
"accentForeground": "0 0% 98%",
|
||||
"destructive": "0 70% 42%",
|
||||
|
||||
BIN
seed/media/foggy-night.jpg
Normal file
BIN
seed/media/foggy-night.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 266 KiB |
BIN
seed/media/foggy-street.jpg
Normal file
BIN
seed/media/foggy-street.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
BIN
seed/media/harbour-night.jpg
Normal file
BIN
seed/media/harbour-night.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 228 KiB |
BIN
seed/media/streetlamp.jpg
Normal file
BIN
seed/media/streetlamp.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
BIN
seed/media/typewriter-keys.jpg
Normal file
BIN
seed/media/typewriter-keys.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 210 KiB |
BIN
seed/media/typewriter.jpg
Normal file
BIN
seed/media/typewriter.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 165 KiB |
@ -6,6 +6,14 @@
|
||||
"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,6 +40,9 @@
|
||||
"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.",
|
||||
@ -113,8 +124,9 @@
|
||||
"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": "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" } }
|
||||
{ "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" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -134,7 +146,10 @@
|
||||
"parent_slug": "/blog",
|
||||
"blocks": [
|
||||
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "The Harbour Light", "level": 1 } },
|
||||
{ "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>" } }
|
||||
{ "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>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>" } },
|
||||
{ "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>" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -144,7 +159,10 @@
|
||||
"parent_slug": "/blog",
|
||||
"blocks": [
|
||||
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Room 14", "level": 1 } },
|
||||
{ "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>" } }
|
||||
{ "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>" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -154,7 +172,10 @@
|
||||
"parent_slug": "/blog",
|
||||
"blocks": [
|
||||
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "The Last Bus", "level": 1 } },
|
||||
{ "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>" } }
|
||||
{ "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>" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@ -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 md:grid-cols-[1fr_16rem]">
|
||||
<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 %}">
|
||||
<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>
|
||||
<aside class="w-full md:border-l md:border-[hsl(var(--border))] md:pl-8">
|
||||
{% if slots.aside %}<aside class="w-full noir-reveal md:border-l md:border-[hsl(var(--border))] md:pl-8">
|
||||
<div class="md:sticky md:top-6">{{ slots.aside|safe }}</div>
|
||||
</aside>
|
||||
</aside>{% endif %}
|
||||
</div>
|
||||
<footer class="w-full mt-auto noir-rule">
|
||||
<div class="max-w-5xl mx-auto px-4">{{ slots.footer|safe }}</div>
|
||||
@ -25,8 +25,11 @@
|
||||
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.12});
|
||||
els.forEach(function(el){io.observe(el);});
|
||||
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);
|
||||
})();
|
||||
</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(r.top<h*0.92&&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(!h||(r.top<h*0.95&&r.bottom>0)){el.classList.add('noir-in');}else{io.observe(el);}});
|
||||
window.addEventListener('load',showAll);
|
||||
setTimeout(showAll,1600);
|
||||
setTimeout(showAll,450);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@ -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(r.top<h*0.92&&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(!h||(r.top<h*0.95&&r.bottom>0)){el.classList.add('noir-in');}else{io.observe(el);}});
|
||||
window.addEventListener('load',showAll);
|
||||
setTimeout(showAll,1600);
|
||||
setTimeout(showAll,450);
|
||||
})();
|
||||
</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 %}">::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>
|
||||
<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>
|
||||
(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 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 %}
|
||||
{% 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 %}
|
||||
</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" %}::{{ icon }}:sm::{% endif %}{{ label|default:"Learn more" }}{% if icon and iconPosition == "right" %}::{{ icon }}:sm::{% 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" %}<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>
|
||||
{% 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" %}::{{ icon }}:sm::{% endif %}{{ label|default:"Learn more" }}{% if icon and iconPosition == "right" %}::{{ icon }}:sm::{% endif %}</a>
|
||||
<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>
|
||||
{% else %}
|
||||
<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>
|
||||
<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>
|
||||
{% 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 }}::lucide:arrow-right:sm::</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 }}<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>
|
||||
{% 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 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 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 %}
|
||||
<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 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 width="24" height="24" 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 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 width="16" height="16" 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 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 width="16" height="16" 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));">::{{ icon|default:"lucide:asterisk" }}:sm::</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));"><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>
|
||||
{% 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 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 width="16" height="16" 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 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 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>
|
||||
</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 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 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 %}
|
||||
<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 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 width="16" height="16" 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 %}::{{ s.icon }}::{% 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 %}<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 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 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 %}
|
||||
{% 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 %}
|
||||
|
||||
@ -9,31 +9,34 @@
|
||||
{% set sUrl = ctaSecondary.url %}
|
||||
{% set variant = template|default:"centered" %}
|
||||
{% set hasImage = backgroundImage %}
|
||||
{% 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 %}
|
||||
{# When there is a background image the content sits on a dark grayscale
|
||||
photo + dark scrim in BOTH modes, so text must be light regardless of mode.
|
||||
onImg switches foreground/muted-foreground reads 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 %}
|
||||
{% 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">{{ sText }}</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 %}
|
||||
</div>{% endif %}
|
||||
{% if trustLogos %}<div class="mt-10">
|
||||
<p class="noir-label mb-3" style="color:hsl(var(--muted-foreground));">Trusted by</p>
|
||||
<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>
|
||||
<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") }}</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", false) }}</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 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>
|
||||
<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>
|
||||
</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 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 width="16" height="16" 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 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 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>
|
||||
</div>
|
||||
{% endif %}
|
||||
</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">::{{ 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>
|
||||
{% 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>
|
||||
{% 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));">::lucide:chevron-right:sm::</span>
|
||||
<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>
|
||||
<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 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 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 %}
|
||||
</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 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 width="16" height="16" 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 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 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>
|
||||
{% 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 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 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 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 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 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 %}
|
||||
</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 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>
|
||||
<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>
|
||||
</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 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 width="16" height="16" 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 %}::{{ 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 %}
|
||||
{% 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 %}
|
||||
<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">::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>
|
||||
{% 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>
|
||||
(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;}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user