first commit
This commit is contained in:
985
templates/blog/src/layouts/Base.astro
Normal file
985
templates/blog/src/layouts/Base.astro
Normal file
@@ -0,0 +1,985 @@
|
||||
---
|
||||
import { getMenu, getEmDashCollection } from "emdash";
|
||||
import {
|
||||
WidgetArea,
|
||||
EmDashHead,
|
||||
EmDashBodyStart,
|
||||
EmDashBodyEnd,
|
||||
} from "emdash/ui";
|
||||
import { createPublicPageContext } from "emdash/page";
|
||||
import LiveSearch from "emdash/ui/search";
|
||||
import "../styles/theme.css";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description?: string | null;
|
||||
image?: string | null;
|
||||
canonical?: string | null;
|
||||
robots?: string | null;
|
||||
type?: "website" | "article";
|
||||
publishedTime?: string | null;
|
||||
modifiedTime?: string | null;
|
||||
author?: string | null;
|
||||
/** Pass content reference for plugin page contributions on content pages */
|
||||
content?: { collection: string; id: string; slug?: string | null };
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
image,
|
||||
canonical,
|
||||
robots,
|
||||
type = "website",
|
||||
publishedTime,
|
||||
modifiedTime,
|
||||
author,
|
||||
content,
|
||||
} = Astro.props;
|
||||
const siteTitle = "My Blog";
|
||||
// If title already includes site title (from getSeoMeta), use as-is
|
||||
const fullTitle = title.includes(siteTitle) ? title : `${title} — ${siteTitle}`;
|
||||
|
||||
// Fetch primary menu defined in seed
|
||||
const menu = await getMenu("primary");
|
||||
|
||||
// Fetch pages for footer
|
||||
const { entries: pages } = await getEmDashCollection("pages");
|
||||
|
||||
// Build public page context for plugin contributions
|
||||
// SEO data is passed here and rendered securely by EmDashHead
|
||||
const pageCtx = createPublicPageContext({
|
||||
Astro,
|
||||
kind: content ? "content" : "custom",
|
||||
pageType: type,
|
||||
title: fullTitle,
|
||||
description,
|
||||
canonical,
|
||||
image,
|
||||
content,
|
||||
seo: { ogImage: image, robots },
|
||||
articleMeta: { publishedTime, modifiedTime, author },
|
||||
siteName: siteTitle,
|
||||
});
|
||||
|
||||
// Check if user is logged in (for showing admin link)
|
||||
const isLoggedIn = !!Astro.locals.user;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,400;14..32,500;14..32,600;14..32,700&family=JetBrains+Mono:wght@400;500&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>{fullTitle}</title>
|
||||
<EmDashHead page={pageCtx} />
|
||||
<script is:inline>
|
||||
// Apply theme immediately to prevent flash
|
||||
(function () {
|
||||
var c = document.cookie;
|
||||
var i = c.indexOf("theme=");
|
||||
var theme = i >= 0 ? c.slice(i + 6).split(";")[0] : null;
|
||||
if (theme === "dark" || theme === "light") {
|
||||
document.documentElement.classList.add(theme);
|
||||
} else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
document.documentElement.classList.add("dark");
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<EmDashBodyStart page={pageCtx} />
|
||||
<header class="site-header">
|
||||
<nav class="nav">
|
||||
<a href="/" class="site-title">{siteTitle}</a>
|
||||
<div class="nav-right">
|
||||
<LiveSearch
|
||||
placeholder="Search..."
|
||||
class="site-search"
|
||||
inputClass="site-search-input"
|
||||
resultsClass="site-search-results"
|
||||
resultClass="site-search-result"
|
||||
collections={["posts", "pages"]}
|
||||
/>
|
||||
<div class="nav-links">
|
||||
{
|
||||
menu?.items.map((item) => (
|
||||
<a href={item.url} target={item.target}>
|
||||
{item.label}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
{
|
||||
isLoggedIn && (
|
||||
<a href="/_emdash/admin" class="nav-admin">
|
||||
Admin
|
||||
</a>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="footer-inner">
|
||||
<div class="footer-grid">
|
||||
<div class="footer-brand">
|
||||
<a href="/" class="footer-logo">{siteTitle}</a>
|
||||
<p class="footer-tagline">Thoughts, stories, and ideas.</p>
|
||||
</div>
|
||||
<div class="footer-nav">
|
||||
<h4 class="footer-heading">Navigate</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/posts">All Posts</a></li>
|
||||
{
|
||||
pages.slice(0, 3).map((page) => (
|
||||
<li>
|
||||
<a href={`/pages/${page.data.slug || page.id}`}>
|
||||
{page.data.title}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-nav">
|
||||
<h4 class="footer-heading">Connect</h4>
|
||||
<ul class="footer-links">
|
||||
{
|
||||
menu?.items.map((item) => (
|
||||
<li>
|
||||
<a
|
||||
href={item.url}
|
||||
target={item.target}
|
||||
rel={
|
||||
item.target === "_blank"
|
||||
? "noopener noreferrer"
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
<li><a href="/rss.xml">RSS Feed</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-widgets-section">
|
||||
<WidgetArea name="footer" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p class="footer-copyright">
|
||||
Powered by <a href="https://emdashcms.com">EmDash</a>
|
||||
</p>
|
||||
<div class="theme-switcher">
|
||||
<button
|
||||
type="button"
|
||||
class="theme-btn"
|
||||
data-theme="light"
|
||||
aria-label="Light mode"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
><circle cx="12" cy="12" r="5"></circle><line
|
||||
x1="12"
|
||||
y1="1"
|
||||
x2="12"
|
||||
y2="3"></line><line x1="12" y1="21" x2="12" y2="23"
|
||||
></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"
|
||||
></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"
|
||||
></line><line x1="1" y1="12" x2="3" y2="12"></line><line
|
||||
x1="21"
|
||||
y1="12"
|
||||
x2="23"
|
||||
y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"
|
||||
></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"
|
||||
></line></svg
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="theme-btn"
|
||||
data-theme="dark"
|
||||
aria-label="Dark mode"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"
|
||||
></path></svg
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="theme-btn"
|
||||
data-theme="system"
|
||||
aria-label="System theme"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
><rect x="2" y="3" width="20" height="14" rx="2" ry="2"
|
||||
></rect><line x1="8" y1="21" x2="16" y2="21"></line><line
|
||||
x1="12"
|
||||
y1="17"
|
||||
x2="12"
|
||||
y2="21"></line></svg
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// Theme switcher
|
||||
const THEME_REGEX = /theme=([^;]+)/;
|
||||
const themeBtns =
|
||||
document.querySelectorAll<HTMLButtonElement>(".theme-btn");
|
||||
const root = document.documentElement;
|
||||
|
||||
function setCookie(
|
||||
name: string,
|
||||
value: string,
|
||||
maxAge: number = 31536000
|
||||
) {
|
||||
const secure = location.protocol === "https:" ? "; Secure" : "";
|
||||
if (value === "") {
|
||||
document.cookie = `${name}=; path=/; max-age=0; SameSite=Lax${secure}`;
|
||||
} else {
|
||||
document.cookie = `${name}=${value}; path=/; max-age=${maxAge}; SameSite=Lax${secure}`;
|
||||
}
|
||||
}
|
||||
|
||||
function setTheme(theme: string) {
|
||||
if (theme === "system") {
|
||||
setCookie("theme", "");
|
||||
root.classList.remove("light", "dark");
|
||||
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
root.classList.add("dark");
|
||||
}
|
||||
} else {
|
||||
setCookie("theme", theme);
|
||||
root.classList.remove("light", "dark");
|
||||
root.classList.add(theme);
|
||||
}
|
||||
updateActiveBtn(theme);
|
||||
}
|
||||
|
||||
function updateActiveBtn(theme: string) {
|
||||
themeBtns.forEach((btn) => {
|
||||
btn.classList.toggle("active", btn.dataset.theme === theme);
|
||||
});
|
||||
}
|
||||
|
||||
function getStoredTheme(): string {
|
||||
const match = document.cookie.match(THEME_REGEX);
|
||||
return match ? match[1] : "system";
|
||||
}
|
||||
|
||||
// Initialize
|
||||
const storedTheme = getStoredTheme();
|
||||
setTheme(storedTheme);
|
||||
|
||||
themeBtns.forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
setTheme(btn.dataset.theme || "system");
|
||||
});
|
||||
});
|
||||
|
||||
// Listen for system preference changes
|
||||
window
|
||||
.matchMedia("(prefers-color-scheme: dark)")
|
||||
.addEventListener("change", (e) => {
|
||||
if (getStoredTheme() === "system") {
|
||||
root.classList.toggle("dark", e.matches);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style is:global>
|
||||
*:where(:not([class*="emdash"]):not([class*="ec-"])),
|
||||
*:where(:not([class*="emdash"]):not([class*="ec-"]))::before,
|
||||
*:where(:not([class*="emdash"]):not([class*="ec-"]))::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
ul,
|
||||
ol,
|
||||
figure,
|
||||
blockquote,
|
||||
dl,
|
||||
dd {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:root {
|
||||
/* Colors - Light mode (default) */
|
||||
--color-bg: #ffffff;
|
||||
--color-bg-subtle: #fafafa;
|
||||
--color-text: #1a1a1a;
|
||||
--color-text-secondary: #525252;
|
||||
--color-muted: #8b8b8b;
|
||||
--color-border: #e5e5e5;
|
||||
--color-border-subtle: #f0f0f0;
|
||||
--color-surface: #f7f7f7;
|
||||
--color-accent: #0066cc;
|
||||
--color-accent-hover: #0052a3;
|
||||
--color-on-accent: white;
|
||||
--color-accent-ring: color-mix(
|
||||
in srgb,
|
||||
var(--color-accent) 25%,
|
||||
transparent
|
||||
);
|
||||
|
||||
/* EmDash search theming */
|
||||
--emdash-search-bg: var(--color-bg);
|
||||
--emdash-search-text: var(--color-text);
|
||||
--emdash-search-muted: var(--color-muted);
|
||||
--emdash-search-border: var(--color-border);
|
||||
--emdash-search-hover: var(--color-surface);
|
||||
--emdash-search-highlight: var(--color-text);
|
||||
|
||||
/* Typography */
|
||||
--font-sans:
|
||||
"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
sans-serif;
|
||||
--font-mono:
|
||||
"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
|
||||
/* Type scale - more refined */
|
||||
--font-size-xs: 0.8125rem;
|
||||
--font-size-sm: 0.875rem;
|
||||
--font-size-base: 1rem;
|
||||
--font-size-lg: 1.125rem;
|
||||
--font-size-xl: 1.25rem;
|
||||
--font-size-2xl: 1.5rem;
|
||||
--font-size-3xl: 2rem;
|
||||
--font-size-4xl: 2.5rem;
|
||||
--font-size-5xl: 3.5rem;
|
||||
|
||||
/* Line heights */
|
||||
--leading-tight: 1.15;
|
||||
--leading-snug: 1.3;
|
||||
--leading-normal: 1.5;
|
||||
--leading-relaxed: 1.7;
|
||||
|
||||
/* Spacing - more generous */
|
||||
--spacing-1: 0.25rem;
|
||||
--spacing-2: 0.5rem;
|
||||
--spacing-3: 0.75rem;
|
||||
--spacing-4: 1rem;
|
||||
--spacing-5: 1.25rem;
|
||||
--spacing-6: 1.5rem;
|
||||
--spacing-8: 2rem;
|
||||
--spacing-10: 2.5rem;
|
||||
--spacing-12: 3rem;
|
||||
--spacing-16: 4rem;
|
||||
--spacing-20: 5rem;
|
||||
--spacing-24: 6rem;
|
||||
|
||||
/* Legacy spacing aliases */
|
||||
--spacing-xs: var(--spacing-1);
|
||||
--spacing-sm: var(--spacing-2);
|
||||
--spacing-md: var(--spacing-4);
|
||||
--spacing-lg: var(--spacing-6);
|
||||
--spacing-xl: var(--spacing-8);
|
||||
--spacing-2xl: var(--spacing-12);
|
||||
--spacing-3xl: var(--spacing-16);
|
||||
|
||||
/* Layout - wider for three-column */
|
||||
--content-width: 680px;
|
||||
--wide-width: 1200px;
|
||||
--max-width: var(--content-width);
|
||||
--gutter-width: 200px;
|
||||
--radius: 4px;
|
||||
--radius-lg: 8px;
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 120ms ease;
|
||||
--transition-base: 180ms ease;
|
||||
|
||||
/* Nav */
|
||||
--nav-height: 64px;
|
||||
|
||||
/* Search */
|
||||
--search-input-width: 180px;
|
||||
|
||||
/* Article layout */
|
||||
--meta-col-width: 180px;
|
||||
|
||||
/* Avatar sizes */
|
||||
--avatar-size-xs: 18px;
|
||||
--avatar-size-sm: 20px;
|
||||
--avatar-size-md: 24px;
|
||||
--avatar-size-lg: 32px;
|
||||
|
||||
/* Letter spacing */
|
||||
--tracking-tight: -0.03em;
|
||||
--tracking-snug: -0.02em;
|
||||
--tracking-wide: 0.06em;
|
||||
--tracking-wider: 0.08em;
|
||||
|
||||
/* Tag pill */
|
||||
--tag-padding-y: 2px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-dropdown: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||
--shadow-btn-active: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* Dark mode via system preference (when no explicit class) */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not(.light) {
|
||||
--color-bg: #0d0d0d;
|
||||
--color-bg-subtle: #141414;
|
||||
--color-text: #ededed;
|
||||
--color-text-secondary: #a0a0a0;
|
||||
--color-muted: #6b6b6b;
|
||||
--color-border: #2a2a2a;
|
||||
--color-border-subtle: #1f1f1f;
|
||||
--color-surface: #181818;
|
||||
--color-accent: #4d9fff;
|
||||
--color-accent-hover: #6eb0ff;
|
||||
}
|
||||
}
|
||||
|
||||
/* Explicit dark mode */
|
||||
:root.dark {
|
||||
--color-bg: #0d0d0d;
|
||||
--color-bg-subtle: #141414;
|
||||
--color-text: #ededed;
|
||||
--color-text-secondary: #a0a0a0;
|
||||
--color-muted: #6b6b6b;
|
||||
--color-border: #2a2a2a;
|
||||
--color-border-subtle: #1f1f1f;
|
||||
--color-surface: #181818;
|
||||
--color-accent: #4d9fff;
|
||||
--color-accent-hover: #6eb0ff;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--font-size-base);
|
||||
line-height: var(--leading-relaxed);
|
||||
color: var(--color-text);
|
||||
background: var(--color-bg);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
a:where(:not([class*="emdash"]):not([class*="ec-"])) {
|
||||
color: var(--color-accent);
|
||||
text-decoration: none;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
a:where(:not([class*="emdash"]):not([class*="ec-"])):hover {
|
||||
color: var(--color-accent-hover);
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: var(--font-sans);
|
||||
line-height: var(--leading-tight);
|
||||
font-weight: 600;
|
||||
letter-spacing: var(--tracking-snug);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 700;
|
||||
letter-spacing: var(--tracking-tight);
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--color-accent);
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.site-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: color-mix(in srgb, var(--color-bg) 65%, transparent);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
max-width: var(--wide-width);
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-4) var(--spacing-6);
|
||||
height: var(--nav-height);
|
||||
}
|
||||
|
||||
.site-title {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
text-decoration: none;
|
||||
letter-spacing: var(--tracking-snug);
|
||||
}
|
||||
|
||||
.site-title:hover {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.nav-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-6);
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: var(--spacing-5);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
text-decoration: none;
|
||||
color: var(--color-text);
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.nav-admin {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-muted);
|
||||
text-decoration: none;
|
||||
opacity: 0.5;
|
||||
transition: opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.nav-admin:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Search styling */
|
||||
.site-search {
|
||||
position: relative;
|
||||
width: var(--search-input-width);
|
||||
--emdash-search-border-focus: var(--color-accent);
|
||||
}
|
||||
|
||||
:global(.site-search-input) {
|
||||
width: var(--search-input-width);
|
||||
padding: var(--spacing-2) var(--spacing-3);
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--font-size-sm);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
transition:
|
||||
border-color var(--transition-fast),
|
||||
box-shadow var(--transition-fast);
|
||||
}
|
||||
|
||||
:global(.site-search-input)::placeholder {
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
:global(.site-search-input):focus,
|
||||
:global(.site-search-input):focus-visible {
|
||||
outline: none;
|
||||
border-color: var(--color-accent) !important;
|
||||
box-shadow: 0 0 0 3px var(--color-accent-ring);
|
||||
}
|
||||
|
||||
:global(.site-search-results) {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-top: var(--spacing-2);
|
||||
background: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-dropdown);
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
:global(.site-search-results .emdash-live-search-loading),
|
||||
:global(.site-search-results .emdash-live-search-no-results) {
|
||||
padding: var(--spacing-4);
|
||||
text-align: center;
|
||||
color: var(--color-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
:global(.site-search-result) {
|
||||
display: block;
|
||||
padding: var(--spacing-3) var(--spacing-4);
|
||||
text-decoration: none;
|
||||
color: var(--color-text);
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
:global(.site-search-result):last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
:global(.site-search-result):hover,
|
||||
:global(.site-search-result):focus,
|
||||
:global(.site-search-result.focused) {
|
||||
background: var(--color-surface);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
:global(.site-search-result .emdash-live-search-result-title) {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
:global(.site-search-result .emdash-live-search-result-collection) {
|
||||
display: block;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-wide);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
:global(.site-search-result .emdash-live-search-result-snippet) {
|
||||
display: block;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-muted);
|
||||
margin-top: var(--spacing-1);
|
||||
line-height: var(--leading-snug);
|
||||
}
|
||||
|
||||
:global(.site-search-result .emdash-live-search-result-snippet mark) {
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
main {
|
||||
min-height: calc(100vh - var(--nav-height) - 300px);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.site-footer {
|
||||
background: var(--color-bg-subtle);
|
||||
border-top: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
|
||||
.footer-inner {
|
||||
max-width: var(--wide-width);
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-16) var(--spacing-6) var(--spacing-8);
|
||||
}
|
||||
|
||||
.footer-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr 1fr 1fr;
|
||||
gap: var(--spacing-12);
|
||||
margin-bottom: var(--spacing-12);
|
||||
}
|
||||
|
||||
.footer-brand {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.footer-logo {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
text-decoration: none;
|
||||
letter-spacing: var(--tracking-snug);
|
||||
}
|
||||
|
||||
.footer-tagline {
|
||||
margin-top: var(--spacing-3);
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-muted);
|
||||
line-height: var(--leading-relaxed);
|
||||
}
|
||||
|
||||
.footer-nav {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.footer-heading {
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-wider);
|
||||
color: var(--color-muted);
|
||||
margin-bottom: var(--spacing-4);
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.footer-links li {
|
||||
margin-bottom: var(--spacing-2);
|
||||
}
|
||||
|
||||
.footer-links a {
|
||||
color: var(--color-text-secondary);
|
||||
text-decoration: none;
|
||||
font-size: var(--font-size-sm);
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.footer-links a:hover {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.footer-widgets-section :global(.widget-area) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.footer-widgets-section :global(.widget) {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.footer-widgets-section :global(.widget__title) {
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-wider);
|
||||
color: var(--color-muted);
|
||||
margin-bottom: var(--spacing-4);
|
||||
}
|
||||
|
||||
.footer-widgets-section :global(.widget__content) {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: var(--leading-relaxed);
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: var(--spacing-6);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.footer-copyright {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.footer-copyright a {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Theme switcher */
|
||||
.theme-switcher {
|
||||
display: flex;
|
||||
gap: var(--spacing-1);
|
||||
padding: var(--spacing-1);
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.theme-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 28px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-muted);
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.theme-btn:hover {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.theme-btn.active {
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
box-shadow: var(--shadow-btn-active);
|
||||
}
|
||||
|
||||
.theme-btn svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.footer-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
.footer-brand {
|
||||
grid-column: span 2;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.site-header {
|
||||
position: relative;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.nav {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: auto;
|
||||
gap: var(--spacing-2);
|
||||
padding: var(--spacing-3) var(--spacing-4);
|
||||
}
|
||||
|
||||
.site-title {
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
.nav-right {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.site-search {
|
||||
order: 0;
|
||||
max-width: 140px;
|
||||
}
|
||||
|
||||
:global(.site-search-input) {
|
||||
width: 140px !important;
|
||||
padding: var(--spacing-1) var(--spacing-2) !important;
|
||||
font-size: var(--font-size-sm) !important;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
order: 1;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
column-gap: var(--spacing-3);
|
||||
row-gap: var(--spacing-1);
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.nav-admin {
|
||||
order: 2;
|
||||
position: absolute;
|
||||
right: var(--spacing-4);
|
||||
top: var(--spacing-3);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.footer-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.footer-brand {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-controls {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// ⌘K / Ctrl+K to focus search
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
||||
e.preventDefault();
|
||||
const searchInput = document.querySelector(
|
||||
".site-search-input"
|
||||
) as HTMLInputElement;
|
||||
if (searchInput) {
|
||||
searchInput.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<EmDashBodyEnd page={pageCtx} />
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user