/**
 * Shared `<head>` + base `<style>` block for every server-rendered HTML page
 * Neotoma ships (root landing, bundled product docs, OAuth UI, future
 * surfaces).
 *
 * Surfaces import `renderBundledPageShell` and pass an options bag plus their
 * body-level HTML; the shell handles the boilerplate (`<!doctype html>`,
 * meta tags, viewport, robots policy) and provides a tokenised base `<style>`
 * sourced from
 * [src/services/bundled_pages/tokens.ts](./tokens.ts). Surface-specific styles
 * are appended via `extraHead`.
 *
 * Code coupling instead of comment coupling: when two bundled-page surfaces
 * "share a palette," they share this function — the design-system validator
 * enforces that registered surfaces emit `<!doctype html>` only through here.
 */
import { BUNDLED_PAGE_PALETTE, BUNDLED_PAGE_RADII, BUNDLED_PAGE_TYPOGRAPHY, bundledPageCssVariables, } from "./tokens.js";
/**
 * Render the base shared `<style>` block: CSS variables for both light and
 * dark themes, body typography, default link color, and a few utility
 * classes used across surfaces (`.muted`, `.cta`).
 */
export function renderBaseBundledPageStyles(tokensToExpose) {
    const lightVars = bundledPageCssVariables("light", tokensToExpose);
    const darkVars = bundledPageCssVariables("dark", tokensToExpose);
    const TYPO = BUNDLED_PAGE_TYPOGRAPHY;
    const RADII = BUNDLED_PAGE_RADII;
    return `
:root {
  color-scheme: light dark;
${lightVars}
  --radius-sm: ${RADII.sm};
  --radius-md: ${RADII.md};
  --radius-lg: ${RADII.lg};
}
@media (prefers-color-scheme: dark) {
  :root {
${darkVars}
  }
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: hsl(var(--background));
  color: hsl(var(--foreground));
  font-family: ${TYPO.fontFamilySans};
  font-size: ${TYPO.fontSizeBase};
  line-height: ${TYPO.lineHeightBase};
}
code, pre, .font-mono {
  font-family: ${TYPO.fontFamilyMono};
}
a { color: hsl(var(--primary)); text-decoration: underline; text-underline-offset: 2px; }
.muted { color: hsl(var(--muted-foreground)); font-size: ${TYPO.fontSizeSmall}; }
.cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 14px;
  background: hsl(var(--primary));
  color: hsl(var(--primary-foreground));
  border-radius: var(--radius-md);
  text-decoration: none;
  font-weight: 500;
}
.cta:hover { filter: brightness(0.96); }
`.trim();
}
/**
 * Render a complete bundled-page HTML document. Body-only fragments call this
 * once at their top-level entry point.
 */
export function renderBundledPageShell(opts, bodyHtml) {
    const lang = opts.lang ?? "en";
    const robots = opts.robots ?? "index, follow";
    const faviconHref = opts.faviconHref ?? "/favicon.ico";
    const bodyClass = opts.bodyClassName ? ` class="${escapeAttr(opts.bodyClassName)}"` : "";
    const extraHead = opts.extraHead ?? "";
    const extraStyles = opts.extraStyles
        ? `\n<style>${opts.extraStyles}</style>`
        : "";
    const inlineScript = opts.inlineScript
        ? `\n<script>${opts.inlineScript}</script>`
        : "";
    return `<!doctype html>
<html lang="${escapeAttr(lang)}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="${escapeAttr(robots)}">
<link rel="icon" href="${escapeAttr(faviconHref)}">
<title>${escapeText(opts.title)}</title>
<style>${renderBaseBundledPageStyles(opts.tokensToExpose)}</style>${extraStyles}
${extraHead}
</head>
<body${bodyClass}>
${bodyHtml}${inlineScript}
</body>
</html>`;
}
/**
 * CSS for the bundled-page `.banner` markup contract.
 *
 * Surfaces can opt in by appending the return value to their `extraStyles`
 * block. The class names match the React `Banner` primitive
 * (`frontend/src/components/ui/banner.tsx`) so server-rendered and
 * client-rendered surfaces stay visually aligned.
 */
export function renderBundledBannerStyles() {
    const P = BUNDLED_PAGE_PALETTE;
    return `
.banner {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid hsl(var(--border));
  border-radius: var(--radius-md);
  background: hsl(var(--muted));
  color: hsl(var(--foreground));
  font-size: ${BUNDLED_PAGE_TYPOGRAPHY.fontSizeBase};
  line-height: 1.5;
}
.banner__icon {
  flex: 0 0 auto;
  margin-top: 2px;
  font-size: 16px;
  line-height: 1;
}
.banner__body {
  flex: 1 1 auto;
  min-width: 0;
}
.banner__title {
  margin: 0 0 4px;
  font-weight: 600;
  font-size: ${BUNDLED_PAGE_TYPOGRAPHY.fontSizeBase};
}
.banner__content {
  margin: 0;
}
.banner--info {
  border-color: hsl(${P.info_soft_foreground.light.hsl} / 0.25);
  background: hsl(${P.info_soft.light.hsl});
  color: hsl(${P.info_soft_foreground.light.hsl});
}
.banner--success {
  border-color: hsl(${P.success_soft_foreground.light.hsl} / 0.25);
  background: hsl(${P.success_soft.light.hsl});
  color: hsl(${P.success_soft_foreground.light.hsl});
}
.banner--warning {
  border-color: hsl(${P.notice_border.light.hsl});
  background: hsl(${P.notice_background.light.hsl});
  color: hsl(${P.notice_foreground.light.hsl});
}
.banner--destructive {
  border-color: hsl(${P.destructive_soft_foreground.light.hsl} / 0.25);
  background: hsl(${P.destructive_soft.light.hsl});
  color: hsl(${P.destructive_soft_foreground.light.hsl});
}
.banner--neutral {
  border-color: hsl(var(--border));
  background: hsl(var(--muted));
  color: hsl(var(--foreground));
}
@media (prefers-color-scheme: dark) {
  .banner--info {
    border-color: hsl(${P.info_soft_foreground.dark.hsl} / 0.3);
    background: hsl(${P.info_soft.dark.hsl});
    color: hsl(${P.info_soft_foreground.dark.hsl});
  }
  .banner--success {
    border-color: hsl(${P.success_soft_foreground.dark.hsl} / 0.3);
    background: hsl(${P.success_soft.dark.hsl});
    color: hsl(${P.success_soft_foreground.dark.hsl});
  }
  .banner--warning {
    border-color: hsl(${P.notice_border.dark.hsl});
    background: hsl(${P.notice_background.dark.hsl});
    color: hsl(${P.notice_foreground.dark.hsl});
  }
  .banner--destructive {
    border-color: hsl(${P.destructive_soft_foreground.dark.hsl} / 0.3);
    background: hsl(${P.destructive_soft.dark.hsl});
    color: hsl(${P.destructive_soft_foreground.dark.hsl});
  }
}
`.trim();
}
const DEFAULT_BANNER_ICONS = {
    info: "&#8505;",
    success: "&#10003;",
    warning: "&#9888;",
    destructive: "&#9940;",
    neutral: "&#128227;",
};
/**
 * Render a `.banner` block matching the React `Banner` markup contract.
 *
 * The class names track `frontend/src/components/ui/banner.tsx` (`banner`,
 * `banner--<variant>`, `banner__icon`, `banner__body`, `banner__title`,
 * `banner__content`). Surfaces that emit banners must include
 * {@link renderBundledBannerStyles} in their `extraStyles`.
 */
export function renderBundledBanner(opts) {
    const variant = opts.variant ?? "info";
    const icon = opts.icon === null
        ? null
        : opts.icon ?? DEFAULT_BANNER_ICONS[variant];
    const titleHtml = opts.title
        ? `<p class="banner__title">${escapeText(opts.title)}</p>`
        : "";
    const bodyHtml = opts.body
        ? `<div class="banner__content">${opts.body}</div>`
        : "";
    const iconHtml = icon !== null
        ? `<span class="banner__icon" aria-hidden="true">${icon}</span>`
        : "";
    const role = variant === "destructive" ? "alert" : "status";
    return `<div class="banner banner--${variant}" role="${role}">${iconHtml}<div class="banner__body">${titleHtml}${bodyHtml}</div></div>`;
}
/**
 * Re-export the palette so consumers can `import { renderBundledPageShell, P }`
 * in one go. Most surfaces pull both the shell and a couple of token hexes.
 */
export { BUNDLED_PAGE_PALETTE as P } from "./tokens.js";
function escapeAttr(value) {
    return value
        .replace(/&/g, "&amp;")
        .replace(/"/g, "&quot;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;");
}
function escapeText(value) {
    return value
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;");
}
//# sourceMappingURL=html_shell.js.map