import { hslToHex } from "./design.js"; import type { Tokens } from "./types.js"; export function buildCss(tokens: Tokens): string { return `/* Generated by the one-page-website skill. Design tokens live in :root. */ :root { color-scheme: light dark; --font-display: ${tokens.fontDisplay}; --font-sans: ${tokens.fontSans}; --font-mono: ${tokens.fontMono}; --heading-weight: ${tokens.headingWeight}; --heading-tracking: ${tokens.headingTracking}; --measure: ${tokens.measure}; --radius: ${tokens.radius}; --radius-lg: ${tokens.radiusLg}; --accent: ${tokens.accent}; --accent-contrast: ${tokens.accentContrast}; --accent-soft: ${hslToHex(tokens.hue, 0.7, 0.94)}; --accent-strong: ${hslToHex(tokens.hue, 0.72, 0.34)}; --bg: #FFFFFF; --bg-alt: ${hslToHex(tokens.hue, 0.32, 0.975)}; --surface: #FFFFFF; --border: ${hslToHex(tokens.hue, 0.18, 0.88)}; --text: ${hslToHex(tokens.hue, 0.26, 0.12)}; --muted: ${hslToHex(tokens.hue, 0.12, 0.42)}; --shadow: 0 1px 2px rgba(15, 20, 30, 0.06), 0 12px 32px -18px rgba(15, 20, 30, 0.28); --space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem; --space-4: 1rem; --space-6: 1.5rem; --space-8: 2rem; --space-12: 3rem; --space-16: 4rem; --space-24: 6rem; --container: 1140px; } @media (prefers-color-scheme: dark) { :root { --accent: ${hslToHex(tokens.hue, 0.72, 0.62)}; --accent-contrast: #0B0E14; --accent-soft: ${hslToHex(tokens.hue, 0.4, 0.2)}; --accent-strong: ${hslToHex(tokens.hue, 0.7, 0.72)}; --bg: ${hslToHex(tokens.hue, 0.16, 0.07)}; --bg-alt: ${hslToHex(tokens.hue, 0.15, 0.1)}; --surface: ${hslToHex(tokens.hue, 0.14, 0.12)}; --border: ${hslToHex(tokens.hue, 0.12, 0.24)}; --text: #EEF1F6; --muted: ${hslToHex(tokens.hue, 0.1, 0.68)}; --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 18px 40px -24px rgba(0, 0, 0, 0.8); } } *, *::before, *::after { box-sizing: border-box; } html { -webkit-text-size-adjust: 100%; } @media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } } body { margin: 0; background: var(--bg); color: var(--text); font-family: var(--font-sans); font-size: clamp(1rem, 0.96rem + 0.2vw, 1.0625rem); line-height: 1.65; -webkit-font-smoothing: antialiased; } img, svg { max-width: 100%; height: auto; } h1, h2, h3 { font-family: var(--font-display); font-weight: var(--heading-weight); letter-spacing: var(--heading-tracking); line-height: 1.15; margin: 0 0 var(--space-4); text-wrap: balance; } h1 { font-size: clamp(2.25rem, 1.6rem + 3.1vw, 3.75rem); } h2 { font-size: clamp(1.75rem, 1.4rem + 1.6vw, 2.5rem); } h3 { font-size: clamp(1.125rem, 1.05rem + 0.35vw, 1.3rem); } p { margin: 0 0 var(--space-4); } a { color: var(--accent-strong); } a:hover { color: var(--accent); } :where(a, button, summary, input, [tabindex]):focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; border-radius: var(--radius); } .skip-link { position: absolute; left: var(--space-4); top: -100%; z-index: 100; padding: var(--space-3) var(--space-4); background: var(--accent); color: var(--accent-contrast); border-radius: var(--radius); font-weight: 600; text-decoration: none; } .skip-link:focus { top: var(--space-4); } .container { width: 100%; max-width: var(--container); margin-inline: auto; padding-inline: var(--space-6); } .section { padding-block: var(--space-16); } .section--alt { background: var(--bg-alt); } .section__head { max-width: var(--measure); margin-bottom: var(--space-12); } .section__eyebrow { display: inline-block; margin-bottom: var(--space-3); font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted); } .lede { font-size: 1.125rem; color: var(--muted); max-width: var(--measure); } /* ---- header ---- */ .site-header { position: sticky; top: 0; z-index: 50; background: color-mix(in srgb, var(--bg) 88%, transparent); border-bottom: 1px solid transparent; backdrop-filter: saturate(180%) blur(12px); transition: border-color 150ms ease; } .site-header[data-scrolled="true"] { border-bottom-color: var(--border); } .site-header__inner { display: flex; align-items: center; gap: var(--space-6); min-height: 4rem; } .brand { display: inline-flex; align-items: center; gap: var(--space-3); font-weight: 700; font-family: var(--font-display); letter-spacing: var(--heading-tracking); color: var(--text); text-decoration: none; margin-right: auto; } .brand__mark { display: grid; place-items: center; width: 1.75rem; height: 1.75rem; border-radius: var(--radius); background: var(--accent); color: var(--accent-contrast); font-size: 0.9rem; font-weight: 800; } .nav-toggle { display: none; align-items: center; gap: var(--space-2); padding: var(--space-2) var(--space-3); border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); color: var(--text); font: inherit; font-size: 0.875rem; cursor: pointer; } .nav-toggle__bars { display: block; width: 1rem; height: 2px; background: currentColor; box-shadow: 0 5px currentColor, 0 -5px currentColor; } .site-nav__list { display: flex; align-items: center; gap: var(--space-6); margin: 0; padding: 0; list-style: none; } .site-nav__list a { color: var(--muted); text-decoration: none; font-size: 0.9375rem; font-weight: 500; } .site-nav__list a:hover { color: var(--text); } .site-nav__list a[aria-current="true"] { color: var(--text); } /* ---- buttons ---- */ .btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); padding: 0.75rem 1.25rem; border-radius: var(--radius); border: 1px solid transparent; font: inherit; font-weight: 600; line-height: 1; text-decoration: none; cursor: pointer; transition: transform 120ms ease, background-color 120ms ease; } .btn:active { transform: translateY(1px); } .btn--primary { background: var(--accent); color: var(--accent-contrast); } .btn--primary:hover { background: var(--accent-strong); color: var(--accent-contrast); } .btn--ghost { background: transparent; color: var(--text); border-color: var(--border); } .btn--ghost:hover { background: var(--bg-alt); color: var(--text); } .btn--sm { padding: 0.5rem 0.875rem; font-size: 0.875rem; } /* ---- hero ---- */ .hero { padding-block: var(--space-24) var(--space-16); } .hero__grid { display: grid; gap: var(--space-12); align-items: center; } .hero__badge { display: inline-flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-6); padding: 0.3rem 0.75rem; border-radius: 999px; background: var(--accent-soft); color: var(--accent-strong); font-size: 0.8125rem; font-weight: 600; } .hero__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-8); } .hero__note { margin-top: var(--space-4); font-size: 0.875rem; color: var(--muted); } .hero__panel { border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); box-shadow: var(--shadow); padding: var(--space-6); } .hero__panel-bar { display: flex; gap: var(--space-2); margin-bottom: var(--space-6); } .hero__panel-bar span { width: 0.625rem; height: 0.625rem; border-radius: 999px; background: var(--border); } .hero__panel-row { display: flex; align-items: center; gap: var(--space-4); padding-block: var(--space-3); border-top: 1px solid var(--border); } .hero__panel-row:first-of-type { border-top: 0; } .hero__panel-key { font-family: var(--font-mono); font-size: 0.8125rem; color: var(--muted); } .hero__panel-val { margin-left: auto; font-weight: 700; } /* ---- features ---- */ .grid { display: grid; gap: var(--space-6); } .grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); } .card { padding: var(--space-6); border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); } .card__icon { display: grid; place-items: center; width: 2.5rem; height: 2.5rem; margin-bottom: var(--space-4); border-radius: var(--radius); background: var(--accent-soft); color: var(--accent-strong); } .card p:last-child { margin-bottom: 0; } /* ---- proof ---- */ .stats { display: grid; gap: var(--space-6); grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); margin-bottom: var(--space-12); } .stat__value { font-family: var(--font-display); font-size: clamp(2rem, 1.5rem + 2vw, 3rem); font-weight: var(--heading-weight); letter-spacing: var(--heading-tracking); line-height: 1; } .stat__label { margin-top: var(--space-2); color: var(--muted); font-size: 0.9375rem; } .quote { margin: 0; padding: var(--space-8); border-left: 3px solid var(--accent); border-radius: 0 var(--radius-lg) var(--radius-lg) 0; background: var(--surface); } .quote p { font-size: 1.125rem; max-width: var(--measure); } .quote footer { color: var(--muted); font-size: 0.9375rem; } /* ---- pricing ---- */ .tier { display: flex; flex-direction: column; } .tier--featured { border-color: var(--accent); box-shadow: var(--shadow); } .tier__badge { display: inline-block; margin-bottom: var(--space-3); padding: 0.15rem 0.6rem; border-radius: 999px; background: var(--accent); color: var(--accent-contrast); font-size: 0.75rem; font-weight: 700; } .tier__price { display: flex; align-items: baseline; gap: var(--space-2); margin-block: var(--space-4); } .tier__amount { font-family: var(--font-display); font-size: 2.25rem; font-weight: var(--heading-weight); letter-spacing: var(--heading-tracking); } .tier__cadence { color: var(--muted); font-size: 0.875rem; } .tier__list { list-style: none; margin: 0 0 var(--space-6); padding: 0; display: grid; gap: var(--space-2); } .tier__list li { position: relative; padding-left: 1.5rem; font-size: 0.9375rem; } .tier__list li::before { content: ""; position: absolute; left: 0; top: 0.55em; width: 0.5rem; height: 0.5rem; border-radius: 999px; background: var(--accent); } .tier .btn { margin-top: auto; } /* ---- faq ---- */ .faq { display: grid; gap: var(--space-3); max-width: 52rem; } .faq__item { border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); } .faq__item summary { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); padding: var(--space-4) var(--space-6); font-weight: 600; cursor: pointer; list-style: none; } .faq__item summary::-webkit-details-marker { display: none; } .faq__item summary::after { content: "+"; font-family: var(--font-mono); font-size: 1.25rem; color: var(--muted); } .faq__item[open] summary::after { content: "\\2212"; } .faq__answer { padding: 0 var(--space-6) var(--space-6); color: var(--muted); max-width: var(--measure); } /* ---- cta ---- */ .cta-band { padding: var(--space-16) var(--space-8); border-radius: var(--radius-lg); background: var(--accent); color: var(--accent-contrast); text-align: center; } .cta-band h2 { color: inherit; } .cta-band p { color: inherit; opacity: 0.9; max-width: var(--measure); margin-inline: auto; } .cta-band .btn--primary { background: var(--accent-contrast); color: var(--accent); } .cta-band .btn--primary:hover { background: var(--accent-contrast); opacity: 0.9; color: var(--accent); } /* ---- rich text (from --copy) ---- */ .rich-text { max-width: var(--measure); } .rich-text > :last-child { margin-bottom: 0; } .rich-text ul, .rich-text ol { padding-left: 1.25rem; margin: 0 0 var(--space-4); } .rich-text li { margin-bottom: var(--space-2); } .rich-text code { font-family: var(--font-mono); font-size: 0.9em; background: var(--bg-alt); padding: 0.1em 0.35em; border-radius: 4px; } .rich-text pre { overflow-x: auto; padding: var(--space-4); background: var(--bg-alt); border-radius: var(--radius); } .rich-text blockquote { margin: 0 0 var(--space-4); padding-left: var(--space-4); border-left: 3px solid var(--border); color: var(--muted); } .rich-text table { width: 100%; border-collapse: collapse; } .rich-text th, .rich-text td { text-align: left; padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--border); } /* ---- footer ---- */ .site-footer { padding-block: var(--space-12); border-top: 1px solid var(--border); color: var(--muted); font-size: 0.9375rem; } .site-footer__inner { display: flex; flex-wrap: wrap; gap: var(--space-4); align-items: center; justify-content: space-between; } .site-footer ul { display: flex; flex-wrap: wrap; gap: var(--space-6); margin: 0; padding: 0; list-style: none; } .site-footer a { color: var(--muted); text-decoration: none; } .site-footer a:hover { color: var(--text); } /* ---- responsive ---- */ @media (min-width: 60rem) { .hero__grid { grid-template-columns: 1.05fr 0.95fr; } .section { padding-block: var(--space-24); } } @media (max-width: 52rem) { .site-header__inner { flex-wrap: wrap; } .site-nav { flex-basis: 100%; } .site-nav__list { flex-direction: column; align-items: flex-start; gap: var(--space-2); padding-bottom: var(--space-4); } /* Without JavaScript the menu stays open; the toggle only exists for the JS path. */ html.js .nav-toggle { display: inline-flex; } html.js .site-nav { display: none; } html.js .site-nav[data-open="true"] { display: block; } } @media print { .site-header, .nav-toggle, .skip-link { display: none; } .section { padding-block: var(--space-8); } } `; } /* ------------------------------------------------------------------ */ /* script */ /* ------------------------------------------------------------------ */ export function buildJs(): string { return `/* Generated by the one-page-website skill. Progressive enhancement only: every behaviour below is an upgrade on markup that already works with JavaScript disabled. */ (function () { "use strict"; var doc = document; var reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); /* ---- mobile navigation ---- */ var toggle = doc.querySelector(".nav-toggle"); var nav = doc.getElementById("site-nav"); function closeNav() { if (!toggle || !nav) return; toggle.setAttribute("aria-expanded", "false"); nav.setAttribute("data-open", "false"); } if (toggle && nav) { toggle.hidden = false; closeNav(); toggle.addEventListener("click", function () { var open = toggle.getAttribute("aria-expanded") === "true"; toggle.setAttribute("aria-expanded", open ? "false" : "true"); nav.setAttribute("data-open", open ? "false" : "true"); }); doc.addEventListener("keydown", function (event) { if (event.key === "Escape") closeNav(); }); } /* ---- smooth scroll + focus management ---- */ doc.addEventListener("click", function (event) { var link = event.target && event.target.closest ? event.target.closest('a[href^="#"]') : null; if (!link) return; var id = link.getAttribute("href").slice(1); if (!id) return; var target = doc.getElementById(id); if (!target) return; event.preventDefault(); closeNav(); target.scrollIntoView({ behavior: reduceMotion.matches ? "auto" : "smooth", block: "start" }); // Move focus so keyboard and screen reader users land where sighted users do. if (!target.hasAttribute("tabindex")) target.setAttribute("tabindex", "-1"); target.focus({ preventScroll: true }); if (history.replaceState) history.replaceState(null, "", "#" + id); }); /* ---- FAQ accordion: keep one open at a time ---- */ var faqItems = Array.prototype.slice.call(doc.querySelectorAll(".faq__item")); faqItems.forEach(function (item) { item.addEventListener("toggle", function () { if (!item.open) return; faqItems.forEach(function (other) { if (other !== item) other.open = false; }); }); }); /* ---- header shadow on scroll ---- */ var header = doc.querySelector(".site-header"); if (header) { var onScroll = function () { header.setAttribute("data-scrolled", window.scrollY > 8 ? "true" : "false"); }; onScroll(); window.addEventListener("scroll", onScroll, { passive: true }); } /* ---- scrollspy on the nav ---- */ var navLinks = Array.prototype.slice.call(doc.querySelectorAll('#site-nav a[href^="#"]')); if (navLinks.length && "IntersectionObserver" in window) { var sections = navLinks .map(function (link) { return doc.getElementById(link.getAttribute("href").slice(1)); }) .filter(Boolean); var observer = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (!entry.isIntersecting) return; navLinks.forEach(function (link) { var active = link.getAttribute("href") === "#" + entry.target.id; if (active) link.setAttribute("aria-current", "true"); else link.removeAttribute("aria-current"); }); }); }, { rootMargin: "-45% 0px -50% 0px", threshold: 0 }); sections.forEach(function (section) { observer.observe(section); }); } })(); `; } /* ------------------------------------------------------------------ */ /* html */ /* ------------------------------------------------------------------ */