mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
feat(landing-page): add HTML Anything page and responsive header (#2452)
This commit is contained in:
@@ -41,6 +41,35 @@
|
||||
);
|
||||
}
|
||||
|
||||
// Hamburger menu toggle. Active only at narrow viewports (CSS hides
|
||||
// the toggle button at ≥1100px). Click toggles `.is-open` on the
|
||||
// header; outside-click, Escape, and clicking any link inside the
|
||||
// menu close it again. Keeps `aria-expanded` in sync.
|
||||
const toggle = document.querySelector('[data-nav-toggle]');
|
||||
const primaryNav = document.querySelector('[data-nav-primary]');
|
||||
if (toggle && primaryNav && nav) {
|
||||
const setOpen = (open) => {
|
||||
nav.classList.toggle('is-open', open);
|
||||
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
};
|
||||
toggle.addEventListener('click', (ev) => {
|
||||
ev.stopPropagation();
|
||||
setOpen(!nav.classList.contains('is-open'));
|
||||
});
|
||||
// Close when clicking any actual nav link (but not the Product
|
||||
// parent trigger — its href='/' is a real page link, so we let it
|
||||
// navigate which closes the menu naturally).
|
||||
primaryNav.querySelectorAll('a').forEach((link) => {
|
||||
link.addEventListener('click', () => setOpen(false));
|
||||
});
|
||||
document.addEventListener('click', (ev) => {
|
||||
if (!nav.contains(ev.target)) setOpen(false);
|
||||
});
|
||||
document.addEventListener('keydown', (ev) => {
|
||||
if (ev.key === 'Escape') setOpen(false);
|
||||
});
|
||||
}
|
||||
|
||||
const starSlots = document.querySelectorAll('[data-github-stars]');
|
||||
if (starSlots.length > 0) {
|
||||
fetch(REPO_API, {
|
||||
|
||||
@@ -20,7 +20,15 @@ const ext = {
|
||||
|
||||
export interface HeaderProps {
|
||||
/** Nav highlight target. `'home'` is the default for `/`. */
|
||||
active?: 'home' | 'skills' | 'systems' | 'templates' | 'craft' | 'tutorials' | 'blog';
|
||||
active?:
|
||||
| 'home'
|
||||
| 'product'
|
||||
| 'html-anything'
|
||||
| 'skills'
|
||||
| 'systems'
|
||||
| 'templates'
|
||||
| 'craft'
|
||||
| 'blog';
|
||||
/**
|
||||
* Live counts from the Markdown catalogs. Required so we can never
|
||||
* silently render stale fallback numbers when a caller forgets to
|
||||
@@ -62,8 +70,80 @@ export function Header({
|
||||
<b>Studio Nº 01</b>Berlin / Open / Earth
|
||||
</span>
|
||||
</a>
|
||||
<nav>
|
||||
{/*
|
||||
Mobile / tablet hamburger. Hidden by CSS at ≥1100px (the desktop
|
||||
breakpoint where the full nav fits). At narrower widths it toggles
|
||||
`.is-open` on the parent <header> via a small handler in
|
||||
`header-enhancer.astro` — when open, the `<nav>` element below
|
||||
drops down underneath the header bar as a vertical list.
|
||||
*/}
|
||||
<button
|
||||
type='button'
|
||||
className='nav-toggle'
|
||||
aria-label='Toggle navigation menu'
|
||||
aria-controls='primary-nav'
|
||||
aria-expanded='false'
|
||||
data-nav-toggle
|
||||
>
|
||||
<span className='nav-toggle-icon' aria-hidden='true' />
|
||||
</button>
|
||||
<nav id='primary-nav' data-nav-primary>
|
||||
<ul className='nav-links'>
|
||||
<li className='has-dropdown'>
|
||||
{/*
|
||||
Product menu — top-level group exposing the Open Design family.
|
||||
CSS-only dropdown via :hover / :focus-within (no JS), so this
|
||||
still renders correctly under static export with no React
|
||||
runtime on the client. The trigger is a focusable <a> rather
|
||||
than a button so it remains a keyboard tab stop, with
|
||||
aria-haspopup signaling the submenu to assistive tech.
|
||||
*/}
|
||||
<a
|
||||
href='/'
|
||||
className={
|
||||
active === 'product' ||
|
||||
active === 'home' ||
|
||||
active === 'html-anything'
|
||||
? 'is-active'
|
||||
: undefined
|
||||
}
|
||||
aria-haspopup='true'
|
||||
aria-expanded='false'
|
||||
>
|
||||
Product
|
||||
<span className='dropdown-caret' aria-hidden='true'>▾</span>
|
||||
</a>
|
||||
<ul className='nav-dropdown' role='menu'>
|
||||
<li role='none'>
|
||||
<a
|
||||
role='menuitem'
|
||||
href='/'
|
||||
className={
|
||||
active === 'home' || active === 'product'
|
||||
? 'is-active'
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<span className='dropdown-name'>Open Design</span>
|
||||
<span className='dropdown-blurb'>
|
||||
The agentic design surface — skills, systems, templates.
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li role='none'>
|
||||
<a
|
||||
role='menuitem'
|
||||
href='/html-anything/'
|
||||
className={linkClass('html-anything')}
|
||||
>
|
||||
<span className='dropdown-name'>HTML Anything</span>
|
||||
<span className='dropdown-blurb'>
|
||||
Markdown / data → ship-ready HTML, by your local agent.
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href='/skills/' className={linkClass('skills')}>
|
||||
Skills<span className='num'>{counts.skills}</span>
|
||||
@@ -84,11 +164,6 @@ export function Header({
|
||||
Craft<span className='num'>{counts.craft}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='/tutorials/' className={linkClass('tutorials')}>
|
||||
Tutorials
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='/blog/' className={linkClass('blog')}>
|
||||
Blog
|
||||
|
||||
@@ -283,6 +283,140 @@ body::before {
|
||||
right: -16px;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/*
|
||||
* Product dropdown — CSS-only, no JS. The submenu is hidden by default and
|
||||
* revealed on `:hover` of the parent `li.has-dropdown`, or on `:focus-within`
|
||||
* for keyboard navigation. This keeps the static export self-contained.
|
||||
*/
|
||||
.nav-links li.has-dropdown {
|
||||
position: relative;
|
||||
}
|
||||
.nav-links .dropdown-caret {
|
||||
display: inline-block;
|
||||
margin-left: 4px;
|
||||
font-size: 10px;
|
||||
color: var(--ink-faint);
|
||||
transition: transform 0.18s ease;
|
||||
}
|
||||
.nav-links li.has-dropdown:hover .dropdown-caret,
|
||||
.nav-links li.has-dropdown:focus-within .dropdown-caret {
|
||||
transform: translateY(1px);
|
||||
color: var(--coral);
|
||||
}
|
||||
.nav-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: -16px;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
list-style: none;
|
||||
min-width: 320px;
|
||||
background: var(--paper);
|
||||
border: 1px solid rgba(26, 26, 26, 0.12);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 12px 36px rgba(26, 26, 26, 0.08);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-4px);
|
||||
transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s;
|
||||
z-index: 50;
|
||||
}
|
||||
.nav-links li.has-dropdown:hover .nav-dropdown,
|
||||
.nav-links li.has-dropdown:focus-within .nav-dropdown {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
/* invisible bridge so cursor can travel from trigger to menu without
|
||||
* crossing a gap that closes the menu mid-traverse */
|
||||
.nav-dropdown::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 10px;
|
||||
}
|
||||
.nav-dropdown a {
|
||||
display: block;
|
||||
padding: 10px 12px;
|
||||
border-radius: 6px;
|
||||
color: var(--ink);
|
||||
text-decoration: none;
|
||||
transition: background 0.12s ease;
|
||||
}
|
||||
.nav-dropdown a:hover {
|
||||
background: rgba(255, 122, 89, 0.08);
|
||||
color: var(--ink);
|
||||
}
|
||||
.nav-dropdown a.is-active {
|
||||
background: rgba(26, 26, 26, 0.04);
|
||||
}
|
||||
.nav-dropdown .dropdown-name {
|
||||
display: block;
|
||||
font-family: var(--sans);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.nav-dropdown .dropdown-blurb {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: var(--ink-faint);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hamburger toggle. Hidden at desktop widths (≥1080px). At narrower
|
||||
* viewports it replaces the inline nav-links and toggles a panel
|
||||
* underneath the header bar via a small handler in `header-enhancer`.
|
||||
*/
|
||||
.nav-toggle {
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
margin-left: auto;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: 1px solid rgba(26, 26, 26, 0.18);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
color: var(--ink);
|
||||
transition: background 0.18s ease, border-color 0.18s ease;
|
||||
}
|
||||
.nav-toggle:hover {
|
||||
background: rgba(26, 26, 26, 0.04);
|
||||
border-color: rgba(26, 26, 26, 0.32);
|
||||
}
|
||||
.nav-toggle-icon,
|
||||
.nav-toggle-icon::before,
|
||||
.nav-toggle-icon::after {
|
||||
display: block;
|
||||
width: 18px;
|
||||
height: 2px;
|
||||
background: currentColor;
|
||||
border-radius: 2px;
|
||||
transition: transform 0.18s ease, opacity 0.18s ease;
|
||||
}
|
||||
.nav-toggle-icon {
|
||||
position: relative;
|
||||
}
|
||||
.nav-toggle-icon::before,
|
||||
.nav-toggle-icon::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
.nav-toggle-icon::before { top: -6px; }
|
||||
.nav-toggle-icon::after { top: 6px; }
|
||||
/* When open, morph into an "X" */
|
||||
.nav.is-open .nav-toggle-icon { background: transparent; }
|
||||
.nav.is-open .nav-toggle-icon::before { top: 0; transform: rotate(45deg); }
|
||||
.nav.is-open .nav-toggle-icon::after { top: 0; transform: rotate(-45deg); }
|
||||
|
||||
.nav-side {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -1936,7 +2070,7 @@ footer {
|
||||
@media (max-width: 1200px) {
|
||||
.topbar-inner .mid { display: none; }
|
||||
}
|
||||
/* nav: between 1080 and 1180 the brand tail + 5 nav links + 2 CTAs + dot
|
||||
/* nav: between 1080 and 1180 the brand tail + nav links + 2 CTAs + dot
|
||||
* crowd the row. Drop the brand sub-meta first, then tighten link spacing,
|
||||
* so the Star CTA never has to compress. */
|
||||
@media (max-width: 1180px) {
|
||||
@@ -1944,6 +2078,86 @@ footer {
|
||||
.brand-meta { display: none; }
|
||||
.nav-links { gap: 28px; }
|
||||
}
|
||||
|
||||
/* nav: at ≤1080px there's no clean way to fit Product · Skills · Systems ·
|
||||
* Templates · Craft · Blog · Contact + Download + Star + dot in one row,
|
||||
* so swap to a hamburger panel. Star CTA stays visible in the bar (it's
|
||||
* the social-proof CTA); Download moves into the panel. The Product
|
||||
* submenu flattens from a hover-popup to an always-expanded inline list,
|
||||
* since hover doesn't translate to touch.
|
||||
*/
|
||||
@media (max-width: 1080px) {
|
||||
.nav-toggle { display: inline-flex; }
|
||||
.brand { white-space: nowrap; }
|
||||
/* Hide Download from the bar (Star stays). */
|
||||
.nav-side .nav-cta.ghost { display: none; }
|
||||
/* Collapse the nav <ul> into a panel that drops below the header bar.
|
||||
* The header is `position: sticky`, so absolute-positioning the panel
|
||||
* relative to the header element keeps it pinned correctly. */
|
||||
.nav { position: sticky; }
|
||||
.nav > .nav-inner > nav[data-nav-primary] {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-6px);
|
||||
transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s;
|
||||
background: var(--paper);
|
||||
border-top: 1px solid rgba(26, 26, 26, 0.08);
|
||||
box-shadow: 0 12px 32px rgba(26, 26, 26, 0.06);
|
||||
}
|
||||
.nav.is-open > .nav-inner > nav[data-nav-primary] {
|
||||
pointer-events: auto;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
.nav-links {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0;
|
||||
padding: 12px 24px 20px 24px;
|
||||
}
|
||||
.nav-links li {
|
||||
border-bottom: 1px solid rgba(26, 26, 26, 0.06);
|
||||
}
|
||||
.nav-links li:last-child { border-bottom: none; }
|
||||
.nav-links a {
|
||||
display: block;
|
||||
padding: 14px 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
.nav-links a .num {
|
||||
position: static;
|
||||
margin-left: 6px;
|
||||
font-size: 11px;
|
||||
}
|
||||
/* Product dropdown flattens — always show OD + HA as nested static items. */
|
||||
.nav-links li.has-dropdown { position: static; }
|
||||
.nav-links .dropdown-caret { display: none; }
|
||||
.nav-dropdown {
|
||||
position: static;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: none;
|
||||
margin: 0 0 4px 0;
|
||||
padding: 0 0 4px 16px;
|
||||
border: none;
|
||||
border-left: 2px solid rgba(26, 26, 26, 0.08);
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
min-width: 0;
|
||||
}
|
||||
.nav-dropdown::before { display: none; }
|
||||
.nav-dropdown a { padding: 8px 8px; border-radius: 4px; }
|
||||
.nav-dropdown .dropdown-blurb { display: none; }
|
||||
}
|
||||
@media (max-width: 1080px) {
|
||||
.container { padding: 0 32px; }
|
||||
.hero h1 { font-size: clamp(36px, 4.6vw, 54px); }
|
||||
@@ -2024,9 +2238,12 @@ footer {
|
||||
.foot-grid { grid-template-columns: 1fr 1fr; gap: 32px; }
|
||||
.foot-bottom { flex-direction: column; align-items: flex-start; gap: 12px; }
|
||||
.foot-bottom .right { flex-wrap: wrap; gap: 12px 20px; }
|
||||
/* nav */
|
||||
/* nav — at ≤880px tighten padding; nav-links stay reachable through
|
||||
* the hamburger panel introduced at ≤1080px. Brand meta and Download
|
||||
* stay hidden; Star CTA still pings in the bar. */
|
||||
.nav { padding: 16px 0; }
|
||||
.nav-links, .brand-meta, .nav-cta { display: none; }
|
||||
.brand-meta { display: none; }
|
||||
.nav-side .nav-cta.ghost { display: none; }
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.topbar-inner { gap: 14px; }
|
||||
|
||||
@@ -0,0 +1,780 @@
|
||||
---
|
||||
/*
|
||||
* /html-anything/ — sister-project landing page for nexu-io/html-anything.
|
||||
*
|
||||
* One-off marketing surface (not a catalog channel). HA is a separate
|
||||
* repo, so content is hardcoded from its README rather than driven by
|
||||
* a content collection. Stats (stars, license) are fetched live from
|
||||
* the GitHub API at build time, falling back to a hardcoded snapshot
|
||||
* dated in the page itself when the API is unreachable.
|
||||
*
|
||||
* Active nav slot is `home` because we deliberately do not promote
|
||||
* this into the top nav — it's a destination for SEO + direct links,
|
||||
* not a primary channel like /skills/ or /tutorials/.
|
||||
*/
|
||||
import Layout from '../../_components/sub-page-layout.astro';
|
||||
|
||||
const HA_REPO = 'nexu-io/html-anything';
|
||||
const HA_URL = `https://github.com/${HA_REPO}`;
|
||||
const HA_REPO_API = `https://api.github.com/repos/${HA_REPO}`;
|
||||
|
||||
/*
|
||||
* Imagery sourced directly from the HA repo via raw.githubusercontent.com.
|
||||
* This is a temporary CDN — for production polish a follow-up should mirror
|
||||
* these PNGs into the same Cloudflare R2 bucket (`open-design-static`) the
|
||||
* rest of the marketing site already uses, then route them through Image
|
||||
* Resizing for `format=auto` + width-aware variants.
|
||||
*/
|
||||
const HA_RAW = `https://raw.githubusercontent.com/${HA_REPO}/main`;
|
||||
const banner = `${HA_RAW}/docs/assets/banner.png`;
|
||||
const shots = {
|
||||
entry: `${HA_RAW}/docs/screenshots/01-entry-view.png`,
|
||||
picker: `${HA_RAW}/docs/screenshots/02-template-picker.png`,
|
||||
stream: `${HA_RAW}/docs/screenshots/03-streaming.png`,
|
||||
exportUI: `${HA_RAW}/docs/screenshots/04-export.png`,
|
||||
deck: `${HA_RAW}/docs/screenshots/05-deck-mode.png`,
|
||||
hyper: `${HA_RAW}/docs/screenshots/06-hyperframes.png`,
|
||||
};
|
||||
const skillShots = {
|
||||
guizang: `${HA_RAW}/docs/screenshots/skills/deck-guizang-editorial.png`,
|
||||
swiss: `${HA_RAW}/docs/screenshots/skills/deck-swiss-international.png`,
|
||||
parchment: `${HA_RAW}/docs/screenshots/skills/doc-kami-parchment.png`,
|
||||
poster: `${HA_RAW}/docs/screenshots/skills/magazine-poster.png`,
|
||||
};
|
||||
|
||||
// Hardcoded snapshot — last manually verified date. Used as fallback
|
||||
// and as the dateline shown in the footer of the at-a-glance card.
|
||||
const SNAPSHOT_DATE = '2026-05-19';
|
||||
const FALLBACK_STARS = '4.1K';
|
||||
const FALLBACK_FORKS = '435';
|
||||
const FALLBACK_LICENSE = 'Apache-2.0';
|
||||
|
||||
function formatCount(count: unknown): string | null {
|
||||
if (typeof count !== 'number' || !Number.isFinite(count) || count <= 0) return null;
|
||||
if (count < 1000) return String(count);
|
||||
return `${(count / 1000).toFixed(1).replace(/\.0$/, '')}K`;
|
||||
}
|
||||
|
||||
let starsLabel = FALLBACK_STARS;
|
||||
let forksLabel = FALLBACK_FORKS;
|
||||
let licenseLabel = FALLBACK_LICENSE;
|
||||
|
||||
try {
|
||||
const response = await fetch(HA_REPO_API, {
|
||||
headers: { Accept: 'application/vnd.github+json' },
|
||||
});
|
||||
if (response.ok) {
|
||||
const repo = (await response.json()) as {
|
||||
stargazers_count?: number;
|
||||
forks_count?: number;
|
||||
license?: { spdx_id?: string };
|
||||
};
|
||||
starsLabel = formatCount(repo.stargazers_count) ?? starsLabel;
|
||||
forksLabel = formatCount(repo.forks_count) ?? forksLabel;
|
||||
licenseLabel = repo.license?.spdx_id ?? licenseLabel;
|
||||
}
|
||||
} catch {
|
||||
// Network unavailable at build time — keep fallbacks.
|
||||
}
|
||||
|
||||
const title = 'HTML Anything — your local AI agent writes the HTML, you ship it | Open Design';
|
||||
const description =
|
||||
'HTML Anything is the agentic HTML editor from the Open Design family. Reuse the coding-agent CLI you already authenticated — Claude Code, Codex, Cursor, Gemini, Copilot, OpenCode, Qwen, Aider — to turn Markdown, CSV, or JSON into ship-ready HTML for WeChat, X, Zhihu, and Xiaohongshu. Open-source, Apache-2.0.';
|
||||
|
||||
const surfaces = [
|
||||
{ name: 'Magazine article', blurb: 'Long-form editorial layout with drop caps and pull quotes.' },
|
||||
{ name: 'Keynote deck', blurb: 'Slide deck with editorial covers and Swiss-grid variants.' },
|
||||
{ name: 'Résumé', blurb: 'One-page CV in print-ready typographic hierarchy.' },
|
||||
{ name: 'Poster', blurb: 'Newsprint poster — single big idea, eye-catching grid.' },
|
||||
{ name: 'Xiaohongshu card', blurb: 'Vertical 3:4 card sized for the Little Red Book feed.' },
|
||||
{ name: 'Tweet card', blurb: '2× PNG export for X / Weibo with native ratios.' },
|
||||
{ name: 'Web prototype', blurb: 'Functional landing-page or product-page sketch.' },
|
||||
{ name: 'Data report', blurb: 'Tabular data → annotated dashboard in one shot.' },
|
||||
{ name: 'Hyperframes video', blurb: 'Storyboard frames you can hand off to Remotion.' },
|
||||
];
|
||||
|
||||
const agents = [
|
||||
'Claude Code',
|
||||
'Cursor Agent',
|
||||
'OpenAI Codex',
|
||||
'Gemini CLI',
|
||||
'GitHub Copilot CLI',
|
||||
'OpenCode',
|
||||
'Qwen Coder',
|
||||
'Aider',
|
||||
];
|
||||
|
||||
const exportTargets = [
|
||||
{ channel: 'WeChat MP', how: 'CSS inlined via juice — paste straight into the editor.' },
|
||||
{ channel: 'X / Weibo', how: '2× PNG via modern-screenshot, copied through ClipboardItem.' },
|
||||
{ channel: 'Xiaohongshu', how: 'Vertical card export sized for the feed.' },
|
||||
{ channel: 'Zhihu', how: 'LaTeX equations rendered as image placeholders.' },
|
||||
{ channel: 'Standalone HTML', how: 'Single-file download. Self-contained, no asset paths.' },
|
||||
{ channel: 'High-DPI PNG', how: '2× rendering for crisp screenshots on any platform.' },
|
||||
];
|
||||
|
||||
const ideas = [
|
||||
{
|
||||
headline: 'Markdown is the draft. HTML is what humans read.',
|
||||
body: 'You ship the rendered surface, not the source. HTML Anything closes the gap between your raw notes and the polished page a reader actually sees.',
|
||||
},
|
||||
{
|
||||
headline: 'Your agent already knows you.',
|
||||
body: 'No new account, no API key, no extra subscription. The CLI in your terminal is the rendering engine — HTML Anything just runs your skill.',
|
||||
},
|
||||
{
|
||||
headline: 'Skills are folders, not magic.',
|
||||
body: '75 templates, each a SKILL.md folder you can fork, edit, and reuse. The picker is a directory listing in disguise.',
|
||||
},
|
||||
];
|
||||
|
||||
const roadmap = [
|
||||
{ tag: 'Stable', items: ['Agent detection', 'Skill registry & picker', 'SSE streaming render', 'Sandboxed iframe preview', 'Format auto-detect (Markdown · CSV · TSV · JSON · SQL · Excel)'] },
|
||||
{ tag: 'In progress', items: ['Multi-template compare view', 'Hyperframes → .mp4 Remotion handoff'] },
|
||||
{ tag: 'Planned', items: ['Browser extension', 'History + version diff', 'Skill marketplace'] },
|
||||
];
|
||||
|
||||
const jsonLd: Array<Record<string, unknown>> = [
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
name: 'HTML Anything',
|
||||
alternateName: 'The agentic HTML editor',
|
||||
applicationCategory: 'DeveloperApplication',
|
||||
operatingSystem: 'macOS, Linux, Windows',
|
||||
description,
|
||||
url: HA_URL,
|
||||
license: 'https://www.apache.org/licenses/LICENSE-2.0',
|
||||
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
|
||||
author: {
|
||||
'@type': 'Organization',
|
||||
name: 'Open Design',
|
||||
url: 'https://open-design.ai/',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: [
|
||||
{ '@type': 'ListItem', position: 1, name: 'Open Design', item: 'https://open-design.ai/' },
|
||||
{ '@type': 'ListItem', position: 2, name: 'HTML Anything', item: 'https://open-design.ai/html-anything/' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: [
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'Which coding agents does HTML Anything support?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'Eight CLIs are auto-detected: Claude Code, Cursor Agent, OpenAI Codex, Gemini CLI, GitHub Copilot CLI, OpenCode, Qwen Coder, and Aider. HTML Anything reuses whichever sessions you have already authenticated — no extra API key required.',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'Where can I export from HTML Anything?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'Built-in export targets include WeChat MP (CSS inlined via juice), X / Weibo / Xiaohongshu (2× PNG via modern-screenshot), Zhihu (with image placeholders for equations), and standalone .html or high-DPI .png download.',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'Is HTML Anything related to Open Design?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'Yes. HTML Anything is a sister project from the same team behind Open Design. Open Design is the broader agentic design surface (skills · systems · templates); HTML Anything focuses specifically on Markdown / data → ship-ready HTML.',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<Layout title={title} description={description} active="product" ogImage={banner} jsonLd={jsonLd}>
|
||||
<header class="catalog-head">
|
||||
<span class="label">Sister project</span>
|
||||
<h1 class="display">
|
||||
<em>HTML Anything</em> — your local AI agent writes the HTML, you ship it<span class="dot">.</span>
|
||||
</h1>
|
||||
<p class="lead">
|
||||
The agentic HTML editor from the Open Design family. Reuse the
|
||||
coding-agent CLI you've already authenticated to turn Markdown,
|
||||
CSV, or JSON into ship-ready HTML for WeChat, X, Zhihu, and
|
||||
Xiaohongshu — no new keys, no copy-paste tax.
|
||||
</p>
|
||||
<p class="ha-cta">
|
||||
<a class="ha-btn ha-btn-primary" href={HA_URL} rel="noopener">
|
||||
View on GitHub →
|
||||
</a>
|
||||
<a class="ha-btn" href={`${HA_URL}#quickstart`} rel="noopener">
|
||||
Quickstart
|
||||
</a>
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<figure class="ha-hero-figure">
|
||||
<img
|
||||
src={banner}
|
||||
alt="HTML Anything — six surface modes preview banner"
|
||||
width="2400"
|
||||
height="1260"
|
||||
loading="eager"
|
||||
fetchpriority="high"
|
||||
decoding="async"
|
||||
/>
|
||||
</figure>
|
||||
|
||||
<section class="ha-glance" aria-label="At a glance">
|
||||
<ul>
|
||||
<li><span class="num">{starsLabel}</span><span class="lbl">GitHub stars</span></li>
|
||||
<li><span class="num">8</span><span class="lbl">Coding-agent CLIs</span></li>
|
||||
<li><span class="num">75</span><span class="lbl">Skill templates</span></li>
|
||||
<li><span class="num">9</span><span class="lbl">Surface modes</span></li>
|
||||
<li><span class="num">{licenseLabel}</span><span class="lbl">License</span></li>
|
||||
</ul>
|
||||
<p class="ha-glance-foot">
|
||||
Stats verified <time datetime={SNAPSHOT_DATE}>{SNAPSHOT_DATE}</time> · live where the API is reachable.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="ha-section" aria-labelledby="ha-why">
|
||||
<h2 id="ha-why" class="ha-h2">Why this exists</h2>
|
||||
<div class="ha-ideas">
|
||||
{ideas.map((idea) => (
|
||||
<article class="ha-idea">
|
||||
<h3>{idea.headline}</h3>
|
||||
<p>{idea.body}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="ha-section" aria-labelledby="ha-flow">
|
||||
<h2 id="ha-flow" class="ha-h2">How it works</h2>
|
||||
<ol class="ha-flow">
|
||||
<li>
|
||||
<span class="step-num">01</span>
|
||||
<div class="step-body">
|
||||
<h3>Drop in your input</h3>
|
||||
<p>Markdown, CSV, TSV, JSON, SQL, Excel, or plain text. Tabular data is parsed in-browser via papaparse and xlsx — nothing leaves your laptop.</p>
|
||||
<figure class="step-shot">
|
||||
<img src={shots.entry} alt="HTML Anything entry view — paste or drop an input" loading="lazy" decoding="async" />
|
||||
</figure>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<span class="step-num">02</span>
|
||||
<div class="step-body">
|
||||
<h3>Pick a skill, agent renders it</h3>
|
||||
<p>Choose from 75 SKILL.md templates. The local CLI you already authenticated streams its stdout straight into a sandboxed iframe via SSE — you watch the layout assemble in real time.</p>
|
||||
<div class="step-shot-pair">
|
||||
<figure>
|
||||
<img src={shots.picker} alt="Template picker showing 75 skills" loading="lazy" decoding="async" />
|
||||
<figcaption>Template picker — 75 skills, organized by surface mode.</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src={shots.stream} alt="Live SSE streaming of HTML output" loading="lazy" decoding="async" />
|
||||
<figcaption>SSE streaming render — agent stdout becomes the layout.</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<span class="step-num">03</span>
|
||||
<div class="step-body">
|
||||
<h3>Export to your channel</h3>
|
||||
<p>One click for WeChat MP, X / Weibo / Xiaohongshu, Zhihu, standalone .html, or 2× PNG. Output is presentation-ready, not a starter kit.</p>
|
||||
<figure class="step-shot">
|
||||
<img src={shots.exportUI} alt="Export menu with WeChat / X / Xiaohongshu / Zhihu / HTML / PNG targets" loading="lazy" decoding="async" />
|
||||
</figure>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section class="ha-section" aria-labelledby="ha-showcase">
|
||||
<h2 id="ha-showcase" class="ha-h2">Showcase</h2>
|
||||
<p class="ha-section-lead">
|
||||
A glimpse of what HTML Anything ships out of the box. Each tile is a
|
||||
real skill in the registry — fork the folder, swap the content, hit
|
||||
export.
|
||||
</p>
|
||||
<ul class="ha-showcase-grid">
|
||||
<li>
|
||||
<figure>
|
||||
<img src={skillShots.guizang} alt="Guizang editorial deck — magazine spread sample" loading="lazy" decoding="async" />
|
||||
<figcaption>
|
||||
<span class="ha-showcase-name">deck-guizang-editorial</span>
|
||||
<span class="ha-showcase-tag">Magazine deck · 16:9</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
</li>
|
||||
<li>
|
||||
<figure>
|
||||
<img src={skillShots.swiss} alt="Swiss international deck — grid-based layout" loading="lazy" decoding="async" />
|
||||
<figcaption>
|
||||
<span class="ha-showcase-name">deck-swiss-international</span>
|
||||
<span class="ha-showcase-tag">Swiss grid deck</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
</li>
|
||||
<li>
|
||||
<figure>
|
||||
<img src={skillShots.parchment} alt="Kami parchment document — warm cream long-form layout" loading="lazy" decoding="async" />
|
||||
<figcaption>
|
||||
<span class="ha-showcase-name">doc-kami-parchment</span>
|
||||
<span class="ha-showcase-tag">Long-form document</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
</li>
|
||||
<li>
|
||||
<figure>
|
||||
<img src={skillShots.poster} alt="Magazine poster — newsprint layout" loading="lazy" decoding="async" />
|
||||
<figcaption>
|
||||
<span class="ha-showcase-name">magazine-poster</span>
|
||||
<span class="ha-showcase-tag">Newsprint poster</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="ha-section" aria-labelledby="ha-modes">
|
||||
<h2 id="ha-modes" class="ha-h2">Two surfaces beyond the page</h2>
|
||||
<p class="ha-section-lead">
|
||||
HTML Anything also covers presentation-style decks and storyboard
|
||||
frames you can hand off to a video pipeline.
|
||||
</p>
|
||||
<div class="ha-modes-grid">
|
||||
<figure>
|
||||
<img src={shots.deck} alt="Deck mode — slide-by-slide presentation view" loading="lazy" decoding="async" />
|
||||
<figcaption>
|
||||
<strong>Keynote deck mode</strong> — full-bleed slide layouts with
|
||||
Atelier Zero typography and Swiss-grid variants.
|
||||
</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src={shots.hyper} alt="Hyperframes — storyboard frames for video" loading="lazy" decoding="async" />
|
||||
<figcaption>
|
||||
<strong>Hyperframes</strong> — storyboard frames sized and labelled
|
||||
so the next stop can be a Remotion render.
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="ha-section" aria-labelledby="ha-surfaces">
|
||||
<h2 id="ha-surfaces" class="ha-h2">Nine surface modes</h2>
|
||||
<p class="ha-section-lead">
|
||||
Each surface is a curated layout family — pick the one that
|
||||
matches where the output is going to live.
|
||||
</p>
|
||||
<ul class="ha-surface-grid">
|
||||
{surfaces.map((s) => (
|
||||
<li class="ha-surface">
|
||||
<span class="ha-surface-name">{s.name}</span>
|
||||
<span class="ha-surface-blurb">{s.blurb}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="ha-section" aria-labelledby="ha-agents">
|
||||
<h2 id="ha-agents" class="ha-h2">Eight coding agents auto-detected</h2>
|
||||
<p class="ha-section-lead">
|
||||
HTML Anything inherits whichever CLI sessions you already have on
|
||||
your machine. No extra account, no extra API key.
|
||||
</p>
|
||||
<ul class="ha-agents">
|
||||
{agents.map((a) => (
|
||||
<li>{a}</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="ha-section" aria-labelledby="ha-export">
|
||||
<h2 id="ha-export" class="ha-h2">Export targets</h2>
|
||||
<dl class="ha-export">
|
||||
{exportTargets.map((t) => (
|
||||
<div class="ha-export-row">
|
||||
<dt>{t.channel}</dt>
|
||||
<dd>{t.how}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section class="ha-section" aria-labelledby="ha-quickstart">
|
||||
<h2 id="ha-quickstart" class="ha-h2">Quickstart</h2>
|
||||
<pre class="ha-code"><code>{`git clone https://github.com/nexu-io/html-anything
|
||||
cd html-anything
|
||||
pnpm install
|
||||
pnpm -F @html-anything/next dev
|
||||
# → http://localhost:3000`}</code></pre>
|
||||
<p class="ha-section-lead">
|
||||
The agent runs locally on your laptop. You can also deploy the
|
||||
Next.js shell to Vercel — only the rendering loop stays local.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="ha-section" aria-labelledby="ha-roadmap">
|
||||
<h2 id="ha-roadmap" class="ha-h2">Roadmap</h2>
|
||||
<div class="ha-roadmap">
|
||||
{roadmap.map((bucket) => (
|
||||
<article>
|
||||
<h3 class="ha-roadmap-tag">{bucket.tag}</h3>
|
||||
<ul>{bucket.items.map((it) => <li>{it}</li>)}</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="ha-tieback" aria-labelledby="ha-tieback">
|
||||
<h2 id="ha-tieback" class="ha-h2">From the Open Design family</h2>
|
||||
<p>
|
||||
<strong>Open Design</strong> is the broader agentic design surface — skills,
|
||||
systems, templates, the whole studio. <strong>HTML Anything</strong> is the
|
||||
narrow tool: Markdown or data in, ship-ready HTML out.
|
||||
</p>
|
||||
<p>
|
||||
<a class="ha-btn" href="/">Visit Open Design →</a>
|
||||
<a class="ha-btn" href="/skills/" rel="noopener">Browse Skills</a>
|
||||
<a class="ha-btn" href={HA_URL} rel="noopener">HTML Anything on GitHub</a>
|
||||
</p>
|
||||
</section>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
/* Page-local styles. Tokens come from globals.css; layout/typography
|
||||
* follows the Atelier Zero conventions used in sub-pages.css. */
|
||||
|
||||
.ha-cta {
|
||||
margin-top: 1.25rem;
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.ha-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.55rem 1rem;
|
||||
border: 1px solid var(--ink, #1a1a1a);
|
||||
border-radius: 999px;
|
||||
font-size: 0.95rem;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: background 120ms ease, color 120ms ease;
|
||||
}
|
||||
.ha-btn:hover { background: var(--ink, #1a1a1a); color: var(--paper, #efe7d2); }
|
||||
.ha-btn-primary { background: var(--ink, #1a1a1a); color: var(--paper, #efe7d2); }
|
||||
.ha-btn-primary:hover { background: transparent; color: inherit; }
|
||||
|
||||
.ha-hero-figure {
|
||||
margin: 2rem 0 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
.ha-hero-figure img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.ha-glance {
|
||||
margin: 3rem 0;
|
||||
border-top: 1px solid currentColor;
|
||||
border-bottom: 1px solid currentColor;
|
||||
padding: 1.25rem 0;
|
||||
}
|
||||
.ha-glance ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 1.5rem 2rem;
|
||||
}
|
||||
.ha-glance li {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.ha-glance .num {
|
||||
font-family: var(--font-display, 'GT Sectra', Georgia, serif);
|
||||
font-size: 2.25rem;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.ha-glance .lbl {
|
||||
font-size: 0.85rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.ha-glance-foot {
|
||||
margin-top: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.ha-section { margin: 4rem 0; }
|
||||
.ha-h2 {
|
||||
font-family: var(--font-display, 'GT Sectra', Georgia, serif);
|
||||
font-size: 2rem;
|
||||
line-height: 1.15;
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
.ha-section-lead {
|
||||
max-width: 60ch;
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.55;
|
||||
opacity: 0.85;
|
||||
margin: 0 0 2rem 0;
|
||||
}
|
||||
|
||||
.ha-ideas {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
.ha-idea h3 {
|
||||
font-family: var(--font-display, 'GT Sectra', Georgia, serif);
|
||||
font-size: 1.25rem;
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
.ha-idea p { margin: 0; line-height: 1.55; opacity: 0.85; }
|
||||
|
||||
.ha-flow {
|
||||
list-style: none;
|
||||
counter-reset: ha-step;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
.ha-flow li {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 0.5rem 1.5rem;
|
||||
padding: 1.5rem 0;
|
||||
border-top: 1px solid currentColor;
|
||||
}
|
||||
.ha-flow li:last-child { border-bottom: 1px solid currentColor; }
|
||||
.step-num {
|
||||
font-family: var(--font-display, 'GT Sectra', Georgia, serif);
|
||||
font-size: 1.5rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.step-body { display: grid; gap: 1rem; }
|
||||
.ha-flow h3 {
|
||||
font-family: var(--font-display, 'GT Sectra', Georgia, serif);
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.ha-flow p { margin: 0; line-height: 1.55; opacity: 0.85; max-width: 64ch; }
|
||||
.step-shot, .step-shot-pair figure {
|
||||
margin: 0.5rem 0 0 0;
|
||||
}
|
||||
.step-shot img, .step-shot-pair img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.step-shot-pair {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
.step-shot-pair figcaption {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.7;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.ha-showcase-grid {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
.ha-showcase-grid figure {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
.ha-showcase-grid img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 4 / 3;
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
.ha-showcase-name {
|
||||
display: block;
|
||||
font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.ha-showcase-tag {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.65;
|
||||
margin-top: 0.15rem;
|
||||
}
|
||||
|
||||
.ha-modes-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
.ha-modes-grid figure {
|
||||
margin: 0;
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.ha-modes-grid img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.ha-modes-grid figcaption {
|
||||
line-height: 1.5;
|
||||
opacity: 0.85;
|
||||
}
|
||||
.ha-modes-grid figcaption strong {
|
||||
font-family: var(--font-display, 'GT Sectra', Georgia, serif);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ha-surface-grid {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
.ha-surface {
|
||||
border: 1px solid currentColor;
|
||||
padding: 1rem 1.1rem;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
.ha-surface-name {
|
||||
font-family: var(--font-display, 'GT Sectra', Georgia, serif);
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
.ha-surface-blurb {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.45;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.ha-agents {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.ha-agents li {
|
||||
border: 1px solid currentColor;
|
||||
padding: 0.4rem 0.85rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.ha-export {
|
||||
margin: 0;
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.ha-export-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(180px, 1fr) 3fr;
|
||||
gap: 1rem 2rem;
|
||||
padding: 0.75rem 0;
|
||||
border-bottom: 1px dashed currentColor;
|
||||
}
|
||||
.ha-export-row dt {
|
||||
font-family: var(--font-display, 'GT Sectra', Georgia, serif);
|
||||
font-size: 1.05rem;
|
||||
margin: 0;
|
||||
}
|
||||
.ha-export-row dd { margin: 0; opacity: 0.85; line-height: 1.5; }
|
||||
|
||||
.ha-code {
|
||||
background: rgba(0,0,0,0.04);
|
||||
border: 1px solid currentColor;
|
||||
padding: 1rem 1.25rem;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
.ha-code code {
|
||||
font-family: ui-monospace, 'SF Mono', Menlo, Monaco, Consolas, monospace;
|
||||
}
|
||||
|
||||
.ha-roadmap {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
.ha-roadmap-tag {
|
||||
font-family: var(--font-display, 'GT Sectra', Georgia, serif);
|
||||
font-size: 1rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
margin: 0 0 0.75rem 0;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.ha-roadmap ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
gap: 0.4rem;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.ha-tieback {
|
||||
margin: 5rem 0 3rem 0;
|
||||
padding: 2rem 0;
|
||||
border-top: 1px solid currentColor;
|
||||
border-bottom: 1px solid currentColor;
|
||||
}
|
||||
.ha-tieback p {
|
||||
max-width: 60ch;
|
||||
line-height: 1.6;
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
.ha-tieback p:last-child {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user