Files
zeekayandHanzo Dev 10c0782526 hanzo-app: give every example a product, a brand and a builder
`ex-admin`, `ex-kanban`, `ex-shop`. That is not a name, it is a filing
code, and it told every visitor that what they were looking at was a
sample of something rather than a thing. Nobody bookmarks Example #47.

So each one is now a product: a name, a mark, a hue that drives the kit's
--acc so the whole page inherits it, and a tagline in its own voice.
ex-kanban is Lanes — "a board with WIP limits that mean something, because
they stop you". The identity lives in ONE place (products.json) and the
shell is the only way a product is built, so an unbranded page cannot be
published.

AND A BUILDER. Ten named agents (agents.json), each with a discipline and
a body of work, each with a generated avatar. The byline reads "<Product>
— published by Hanzo AI, built by <Agent>, an AI agent working on <X>".
That is honest attribution, not decoration: the agent really did write the
app. They are described as what they are and never dressed as people — no
biography, no employer, no photograph, no testimonial. HIP-903. An agent
that genuinely built the thing needs none of that, and inventing it would
turn a true story into a false one.

LINEAGE IS RECORDED, NOT ASSERTED. Every product's project is created by
POST /v1/projects/fork from a gallery template, so projects.forked_from is
a fact the platform wrote at fork time. 74/74, across 28 parent templates.

AND FOR SIX OF THEM, THE BYTES ARE THE TEMPLATE'S. derive.py checks out
the parent, applies the agent's edit to its SOURCE, runs the template's
own build, and ships that. The diff is the point — proof the product is a
derivative and proof a template is agent-editable, in one artifact. It
also found what the pre-built demos were hiding: four templates did not
compile at all, and several 404'd on an /api/placeholder route that cannot
exist on a static host. Fixed upstream and in the edit respectively.

The other 68 carry a real fork edge but ship the kit build. Said plainly
in the README rather than papered over.

One slug now names the source file, the project and the host.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 09:36:36 -07:00

101 lines
23 KiB
HTML

<!--hz {"title": "Hanzo Products", "archetype": "gallery", "creator": "web", "desc": "74 products published by Hanzo AI and built by 10 named AI agents. Every one is forked from a hanzo.app template, deployed through POST /v1/sites/deploy, and backed by @hanzo/base.", "nav": [["#/", "Products"], ["#/agents", "Agents"]]} -->
<div class="row" style="margin-bottom:6px">
<h1 style="margin:0" id="h">Products</h1>
<span class="tag acc" id="n"></span>
<span class="spacer"></span>
<input id="q" placeholder="Search products…" style="max-width:250px">
</div>
<p style="max-width:78ch" id="lede"></p>
<div class="row" id="facets" style="gap:5px;margin:14px 0"></div>
<div class="grid wide" id="grid"></div>
<script>
const {h,fill,$} = hz;
const P = [{"slug":"northgate","name":"Northgate","tagline":"The admin console for the tables nobody else will look after.","hue":214,"mark":"N","agent":"atlas","template":"metrics","archetype":"admin / CRUD"},{"slug":"cog","name":"Cog","tagline":"An agent that shows its work: every tool call, argument and result, in order.","hue":268,"mark":"C","agent":"sage","template":"synapse","archetype":"AI agent / tool use"},{"slug":"almanac","name":"Almanac","tagline":"API reference that a human can read and a machine can check.","hue":44,"mark":"A","agent":"muse","template":"changelog","archetype":"docs site / API reference"},{"slug":"skirmish","name":"Skirmish","tagline":"Two-minute multiplayer matches in a browser tab. No install, no lobby.","hue":320,"mark":"S","agent":"echo","template":"kart-racer","archetype":"realtime multiplayer game"},{"slug":"footnote","name":"Footnote","tagline":"Ask your documentation a question and get the paragraph it came from.","hue":268,"mark":"F","agent":"sage","template":"synapse","archetype":"AI chat / RAG over docs"},{"slug":"halcyon","name":"Halcyon","tagline":"A calm assistant: streaming answers, threads that persist, nothing that shouts.","hue":268,"mark":"H","agent":"sage","template":"synapse","archetype":"AI chat app"},{"slug":"timeslot","name":"Timeslot","tagline":"Pick a slot. That is the whole product, and it is harder than it looks.","hue":214,"mark":"T","agent":"nomad","template":"deploy","archetype":"booking / scheduling"},{"slug":"pennyworth","name":"Pennyworth","tagline":"Household budgeting for people who want the envelope back.","hue":8,"mark":"P","agent":"juno","template":"analytics-dashboard","archetype":"personal finance"},{"slug":"tender","name":"Tender","tagline":"A checkout that says the total once and never changes it.","hue":8,"mark":"T","agent":"juno","template":"ecommerce-storefront","archetype":"ecommerce checkout"},{"slug":"quill","name":"Quill","tagline":"Model the content, then edit it. Fields first, WYSIWYG second.","hue":44,"mark":"Q","agent":"muse","template":"blog-platform","archetype":"CMS / content modelling"},{"slug":"scribe","name":"Scribe","tagline":"One document, several cursors, no merge conflicts.","hue":178,"mark":"S","agent":"echo","template":"markdown-editor","archetype":"collaborative editor"},{"slug":"helm","name":"Helm","tagline":"The SaaS dashboard you can hand to a customer on day one.","hue":214,"mark":"H","agent":"atlas","template":"analytics-dashboard","archetype":"SaaS dashboard"},{"slug":"kith","name":"Kith","tagline":"Everyone you know, and the last thing you said to them.","hue":214,"mark":"K","agent":"atlas","template":"beta","archetype":"CRM / address book"},{"slug":"syllabus","name":"Syllabus","tagline":"Lessons, progress and a certificate at the end. Learning, not a video wall.","hue":44,"mark":"S","agent":"muse","template":"blog-platform","archetype":"learning platform (LMS)"},{"slug":"relay","name":"Relay","tagline":"A pipeline you can actually move deals through with one hand.","hue":214,"mark":"R","agent":"atlas","template":"beta","archetype":"CRM"},{"slug":"podium","name":"Podium","tagline":"Slides in the browser. Present, or send the link and let them read it.","hue":28,"mark":"P","agent":"solaris","template":"mosaic","archetype":"presentation / slides"},{"slug":"redline","name":"Redline","tagline":"Code review as a reading experience: the diff, the reason, the decision.","hue":152,"mark":"R","agent":"delta","template":"metrics","archetype":"devtool / code review"},{"slug":"sluice","name":"Sluice","tagline":"Move data from where it is to where it is useful, and watch it go.","hue":152,"mark":"S","agent":"delta","template":"analytics-dashboard","archetype":"data / ETL tool"},{"slug":"convene","name":"Convene","tagline":"Publish an event, take RSVPs, and know who is actually coming.","hue":96,"mark":"C","agent":"nomad","template":"circle","archetype":"booking / events"},{"slug":"outlay","name":"Outlay","tagline":"Receipts in, reimbursement out, with the approval in between.","hue":8,"mark":"O","agent":"juno","template":"analytics-dashboard","archetype":"finance / expense tracking"},{"slug":"current","name":"Current","tagline":"A feed with a reading position, because you have a life.","hue":28,"mark":"C","agent":"solaris","template":"circle","archetype":"social feed"},{"slug":"chorus","name":"Chorus","tagline":"Let the people who use it tell you what to build next, and count the votes.","hue":28,"mark":"C","agent":"solaris","template":"deploy","archetype":"feature voting board"},{"slug":"bellwether","name":"Bellwether","tagline":"Ship it to one percent first. Feature flags with a blast radius.","hue":152,"mark":"B","agent":"delta","template":"metrics","archetype":"devtool / feature flags"},{"slug":"armada","name":"Armada","tagline":"Every machine you run, on one screen, sorted by how worried you should be.","hue":200,"mark":"A","agent":"kestrel","template":"analytics-dashboard","archetype":"infrastructure monitoring"},{"slug":"conduit","name":"Conduit","tagline":"Wire steps together and watch the data flow through the graph.","hue":214,"mark":"C","agent":"atlas","template":"matrix","archetype":"node graph editor / automation"},{"slug":"atelier","name":"Atelier","tagline":"A portfolio that gets out of the way of the work.","hue":28,"mark":"A","agent":"solaris","template":"kinetic","archetype":"portfolio"},{"slug":"fieldwork","name":"Fieldwork","tagline":"Build the form, take the answers, keep the answers.","hue":214,"mark":"F","agent":"atlas","template":"deploy","archetype":"form builder"},{"slug":"commons","name":"Commons","tagline":"A place to argue in public and still be able to find the thread tomorrow.","hue":28,"mark":"C","agent":"solaris","template":"circle","archetype":"forum / community"},{"slug":"meridian","name":"Meridian","tagline":"The whole planet at sixty frames a second, with your data on it.","hue":320,"mark":"M","agent":"iris","template":"three-webgpu","archetype":"WebGL / 3D"},{"slug":"streaks","name":"Streaks","tagline":"Do the thing. Mark the box. Do not break the chain.","hue":214,"mark":"S","agent":"atlas","template":"deploy","archetype":"habit tracker"},{"slug":"vellum","name":"Vellum","tagline":"The company handbook, written to be read rather than filed.","hue":44,"mark":"V","agent":"muse","template":"serif","archetype":"docs site"},{"slug":"deskline","name":"Deskline","tagline":"Tickets in, answers out, with a queue that tells the truth.","hue":214,"mark":"D","agent":"atlas","template":"beta","archetype":"helpdesk / ticketing"},{"slug":"polyglot","name":"Polyglot","tagline":"Every string, every locale, and the ones nobody translated yet.","hue":152,"mark":"P","agent":"delta","template":"metrics","archetype":"devtool / localisation"},{"slug":"daydream","name":"Daydream","tagline":"Describe it, generate it, keep the good ones, and keep the prompt.","hue":268,"mark":"D","agent":"sage","template":"synapse","archetype":"image generation UI"},{"slug":"courier","name":"Courier","tagline":"Messages that arrive while you are looking at them.","hue":178,"mark":"C","agent":"echo","template":"circle","archetype":"chat / messaging"},{"slug":"stockroom","name":"Stockroom","tagline":"What you have, where it is, and how long before you run out.","hue":8,"mark":"S","agent":"juno","template":"unity","archetype":"inventory"},{"slug":"remit","name":"Remit","tagline":"Line items in, PDF out, paid or not paid \u2014 nothing else.","hue":8,"mark":"R","agent":"juno","template":"unity","archetype":"finance / document builder"},{"slug":"shortlist","name":"Shortlist","tagline":"A job board that respects the applicant's afternoon.","hue":28,"mark":"S","agent":"solaris","template":"jobfinder","archetype":"job board"},{"slug":"longform","name":"Longform","tagline":"Publishing for people with something long to say.","hue":44,"mark":"L","agent":"muse","template":"blog-platform","archetype":"blog / CMS"},{"slug":"sift","name":"Sift","tagline":"Paste the JSON. Find the field. Copy the path. Get on with it.","hue":152,"mark":"S","agent":"delta","template":"metrics","archetype":"devtool"},{"slug":"lanes","name":"Lanes","tagline":"A board with WIP limits that mean something, because they stop you.","hue":214,"mark":"L","agent":"atlas","template":"kanban-board","archetype":"kanban / project management"},{"slug":"cartograph","name":"Cartograph","tagline":"Points, routes and regions on a map you can actually read.","hue":96,"mark":"C","agent":"nomad","template":"three-webgpu","archetype":"maps / geospatial"},{"slug":"bazaar","name":"Bazaar","tagline":"A marketplace with two sides and one set of rules.","hue":8,"mark":"B","agent":"juno","template":"ecommerce-storefront","archetype":"marketplace"},{"slug":"roundtable","name":"Roundtable","tagline":"A call that starts when you open the link.","hue":178,"mark":"R","agent":"echo","template":"circle","archetype":"realtime video (WebRTC)"},{"slug":"bistro","name":"Bistro","tagline":"The menu, the table, the order. Nobody downloads an app to eat.","hue":8,"mark":"B","agent":"juno","template":"savor","archetype":"ecommerce / table ordering"},{"slug":"marginalia","name":"Marginalia","tagline":"Notes that link to each other, and a backlink panel that earns its space.","hue":44,"mark":"M","agent":"muse","template":"markdown-editor","archetype":"notes / knowledge base"},{"slug":"threshold","name":"Threshold","tagline":"The first five minutes of an account, designed like they matter.","hue":214,"mark":"T","agent":"atlas","template":"quantum","archetype":"auth / onboarding flow"},{"slug":"apogee","name":"Apogee","tagline":"Orbital mechanics you can drag with a mouse.","hue":320,"mark":"A","agent":"iris","template":"three-webgpu","archetype":"WebGL / 3D"},{"slug":"trellis","name":"Trellis","tagline":"Who reports to whom, and who is standing on their own.","hue":214,"mark":"T","agent":"atlas","template":"unity","archetype":"team directory / org chart"},{"slug":"swarm","name":"Swarm","tagline":"A hundred thousand particles obeying three rules.","hue":320,"mark":"S","agent":"iris","template":"three-webgpu","archetype":"WebGL / 3D"},{"slug":"darkroom","name":"Darkroom","tagline":"A gallery that gives the photograph the whole screen.","hue":28,"mark":"D","agent":"solaris","template":"mint","archetype":"social feed / gallery"},{"slug":"tessera","name":"Tessera","tagline":"A shared canvas where every pixel has an owner and a timestamp.","hue":178,"mark":"T","agent":"echo","template":"voxel-craft","archetype":"realtime multiplayer"},{"slug":"ballot","name":"Ballot","tagline":"Ask the room, and watch the bars move while they answer.","hue":152,"mark":"B","agent":"delta","template":"deploy","archetype":"survey / live poll"},{"slug":"grimoire","name":"Grimoire","tagline":"A prompt library with versions, variables and a diff between them.","hue":268,"mark":"G","agent":"sage","template":"synapse","archetype":"AI tooling / prompt library"},{"slug":"cadence","name":"Cadence","tagline":"The four numbers that matter, above the fold, always.","hue":152,"mark":"C","agent":"delta","template":"analytics-dashboard","archetype":"analytics dashboard"},{"slug":"quorum","name":"Quorum","tagline":"Questions with accepted answers, and a reason the answer was accepted.","hue":28,"mark":"Q","agent":"solaris","template":"circle","archetype":"forum / Q&A"},{"slug":"spool","name":"Spool","tagline":"Jobs, retries and the dead-letter queue you have been ignoring.","hue":200,"mark":"S","agent":"kestrel","template":"metrics","archetype":"infrastructure / job queue"},{"slug":"needle","name":"Needle","tagline":"Write the pattern, see the match, understand the group.","hue":152,"mark":"N","agent":"delta","template":"markdown-editor","archetype":"devtool"},{"slug":"milestone","name":"Milestone","tagline":"A changelog your users will read, because it is written for them.","hue":44,"mark":"M","agent":"muse","template":"changelog","archetype":"changelog / release notes"},{"slug":"compass","name":"Compass","tagline":"What ships when, and what it is waiting on.","hue":214,"mark":"C","agent":"atlas","template":"kanban-board","archetype":"project management / gantt"},{"slug":"peek","name":"Peek","tagline":"Share the screen, record the screen, send the file.","hue":178,"mark":"P","agent":"echo","template":"video-streaming","archetype":"realtime video / screen capture"},{"slug":"gridline","name":"Gridline","tagline":"A spreadsheet several people can be wrong in at the same time.","hue":178,"mark":"G","agent":"echo","template":"metrics","archetype":"collaborative editor / spreadsheet"},{"slug":"cutlass","name":"Cutlass","tagline":"A companion for the command line that remembers what you ran.","hue":152,"mark":"C","agent":"delta","template":"metrics","archetype":"devtool / CLI companion"},{"slug":"kindling","name":"Kindling","tagline":"A storefront small enough to launch this afternoon.","hue":8,"mark":"K","agent":"juno","template":"hygge","archetype":"ecommerce storefront"},{"slug":"reverb","name":"Reverb","tagline":"A player with a waveform, because audio is a shape.","hue":320,"mark":"R","agent":"iris","template":"video-streaming","archetype":"audio / media player"},{"slug":"highland","name":"Highland","tagline":"Generated landscape you can fly over, seeded by a number.","hue":320,"mark":"H","agent":"iris","template":"three-webgpu","archetype":"WebGL / 3D"},{"slug":"antipode","name":"Antipode","tagline":"What time it is for everyone else, before you send the invite.","hue":96,"mark":"A","agent":"nomad","template":"deploy","archetype":"utility / scheduling"},{"slug":"tincture","name":"Tincture","tagline":"Design tokens with contrast checked at the point you pick the colour.","hue":320,"mark":"T","agent":"iris","template":"blocks","archetype":"design system tool"},{"slug":"watchtower","name":"Watchtower","tagline":"A status page that is honest during the incident, not after it.","hue":200,"mark":"W","agent":"kestrel","template":"changelog","archetype":"status page"},{"slug":"lodestone","name":"Lodestone","tagline":"Embedding search you can inspect: the vector, the neighbours, the distance.","hue":268,"mark":"L","agent":"sage","template":"synapse","archetype":"RAG / embedding search"},{"slug":"reel","name":"Reel","tagline":"Video streaming with a shelf, a player and a resume position.","hue":28,"mark":"R","agent":"solaris","template":"video-streaming","archetype":"video streaming UI"},{"slug":"cubit","name":"Cubit","tagline":"Build a world one cube at a time, then walk around inside it.","hue":320,"mark":"C","agent":"iris","template":"voxel-craft","archetype":"WebGL / 3D editor"},{"slug":"liftoff","name":"Liftoff","tagline":"A launch page with one job: take the email address.","hue":28,"mark":"L","agent":"solaris","template":"launch","archetype":"waitlist / launch page"},{"slug":"slate","name":"Slate","tagline":"A whiteboard that several people can draw on without fighting.","hue":178,"mark":"S","agent":"echo","template":"canvas","archetype":"collaborative editor / whiteboard"}];
const A = {"atlas":{"name":"Atlas","discipline":"Internal tools","hue":214,"profile":"Builds the software a company runs itself on: admin consoles, CRUD surfaces, directories and dashboards. Works from the data model outwards \u2014 decides what a row is, then what a screen is."},"solaris":{"name":"Solaris","discipline":"Web & storefront","hue":28,"profile":"Builds the surfaces a stranger meets first: landing pages, storefronts, portfolios, editorial. Optimises for the first four seconds and for the reader who never scrolls."},"sage":{"name":"Sage","discipline":"AI & retrieval","hue":268,"profile":"Builds chat, agents, retrieval and generation UIs. Treats a model as a component with latency and a failure mode, not as a magic box \u2014 every surface it ships shows its sources and its uncertainty."},"echo":{"name":"Echo","discipline":"Realtime & collaboration","hue":178,"profile":"Builds anything where two people are in the same place at the same time: multiplayer canvases, calls, shared documents, presence. Designs for the moment the connection drops."},"iris":{"name":"Iris","discipline":"Graphics & 3D","hue":320,"profile":"Builds with the GPU: WebGL and WebGPU scenes, canvas, terrain, particles, voxels. Cares about frame budget the way other agents care about bundle size."},"delta":{"name":"Delta","discipline":"Data & developer tools","hue":152,"profile":"Builds the instruments: pipelines, query surfaces, diff and inspection tools, feature flags. Ships nothing that cannot be explained by pointing at the data."},"juno":{"name":"Juno","discipline":"Commerce & money","hue":8,"profile":"Builds carts, checkouts, invoices, budgets and marketplaces. Holds one rule above all others: the number on the screen is the number that will be charged."},"kestrel":{"name":"Kestrel","discipline":"Infrastructure & reliability","hue":200,"profile":"Builds the view from above: fleets, queues, status pages, uptime. Assumes the thing being watched is already on fire and asks what the operator needs in the first ten seconds."},"muse":{"name":"Muse","discipline":"Content & publishing","hue":44,"profile":"Builds writing, reading and reference: CMSs, docs, handbooks, changelogs, courses. Defends the reading column against everything that wants to sit in it."},"nomad":{"name":"Nomad","discipline":"Place & time","hue":96,"profile":"Builds maps, globes, calendars, schedules and timezones. Works in the two coordinate systems every other agent gets wrong."}};
const LEDE = "Every product below is published by <b>Hanzo AI</b> and built by a named "+
"AI agent \u2014 the agent that genuinely wrote it. Each one is a live site on "+
"<code>hanzo.app</code>, forked from a gallery template through "+
"<code>POST /v1/projects/fork</code> so its lineage is recorded, published through "+
"<code>POST /v1/sites/deploy</code>, backed by <code>@hanzo/base</code>, and serving a "+
"machine-readable <code>/.well-known/hanzo-example.json</code>. All records, people and "+
"metrics inside them are synthetic.";
let q='', facet='All';
// Facet by the AGENT, not by archetype: 74 products carry ~48 distinct archetype
// strings, which is a wall of chips and answers nobody's question. Who built it
// is the question this catalogue exists to answer, and there are ten answers.
const FACETS = ['All', ...Object.keys(A)];
const SVG = 'http://www.w3.org/2000/svg';
const mark = (p,s)=>{const e=document.createElementNS(SVG,'svg');
e.setAttribute('viewBox','0 0 32 32'); e.setAttribute('width',s); e.setAttribute('height',s);
e.style.flex='none'; e.innerHTML='<rect width=32 height=32 rx=9 fill="hsl('+p.hue+
' 74% 55%)"/><text x=16 y=22 text-anchor=middle font-family=system-ui font-size=17 '+
'font-weight=700 fill=#fff>'+p.mark+'</text>'; return e;};
const face = (a,s)=>{const e=document.createElementNS(SVG,'svg');
e.setAttribute('viewBox','0 0 32 32'); e.setAttribute('width',s); e.setAttribute('height',s);
e.style.flex='none'; e.innerHTML='<circle cx=16 cy=16 r=14 fill="hsl('+a.hue+' 62% 22%)" '+
'stroke="hsl('+a.hue+' 70% 58%)"/><circle cx=16 cy=16 r=6.5 fill="hsl('+a.hue+
' 74% 60%)"/><circle cx=27 cy=9 r=3.2 fill="hsl('+a.hue+' 80% 72%)"/>'; return e;};
function card(p){
const a = A[p.agent];
return h('a.card',{href:'https://'+p.slug+'.hanzo.app', target:'_blank', rel:'noopener',
style:{textDecoration:'none',color:'inherit'}},
h('div',{style:{height:'5px',borderRadius:'99px',marginBottom:'12px',
background:'linear-gradient(90deg,hsl('+p.hue+' 74% 58%),hsl('+((p.hue+38)%360)+' 70% 48%))'}}),
h('div.row',{style:{gap:'9px'}}, mark(p,26), h('h3',{style:{margin:0}},p.name),
h('span.spacer'), h('span.hz-official','\u25C6 Hanzo')),
h('p.small',{style:{margin:'8px 0 10px'}}, p.tagline),
h('div.row',{style:{gap:'6px'}}, h('span.tag',p.slug+'.hanzo.app'),
h('span.tag','forked from '+p.template),
h('span.tag.acc','built by '+a.name)));
}
function agentCard(k){
const a = A[k], mine = P.filter(p=>p.agent===k);
return h('a.card',{href:'#/agent/'+k, style:{textDecoration:'none',color:'inherit'}},
h('div.row',{style:{gap:'10px'}}, face(a,34),
h('div',{style:{minWidth:0}}, h('h3',{style:{margin:0}},a.name),
h('div.small.muted',a.discipline)),
h('span.spacer'), h('span.tag.acc', mine.length+' products')),
h('p.small',{style:{margin:'10px 0 0'}}, a.profile));
}
function render(){
const [view, arg] = location.hash.slice(2).split('/');
$('#lede').innerHTML = LEDE;
if (view === 'agent' && A[arg]) {
const a = A[arg], mine = P.filter(p=>p.agent===arg);
$('#h').textContent = a.name;
$('#n').textContent = mine.length+' product'+(mine.length===1?'':'s');
$('#lede').innerHTML = '<b>'+a.name+'</b> is an AI agent working on '+
a.discipline.toLowerCase()+'. '+a.profile+' It is an agent, not a person: no '+
'biography, no employer, no photograph, and its avatar is generated from its handle. '+
'Everything below, it built.';
fill($('#facets'), [h('a.btn.sm',{href:'#/agents'},'\u2190 All agents')]);
fill($('#grid'), mine.map(card));
return;
}
if (view === 'agents') {
$('#h').textContent = 'Agents';
$('#n').textContent = Object.keys(A).length+' agents';
$('#lede').innerHTML = 'Hanzo\u2019s products are built by named AI agents (HIP-903, '+
'The Agentic Company). Each agent has a discipline and a body of work. None of them '+
'is dressed as a human: no biography, no employer, no photograph \u2014 an agent that '+
'genuinely built the thing does not need any of that.';
fill($('#facets'), [h('a.btn.sm',{href:'#/'},'\u2190 All products')]);
fill($('#grid'), Object.keys(A).map(agentCard));
return;
}
$('#h').textContent = 'Products';
const d = P.filter(p => (facet==='All'||p.agent===facet) &&
(!q || (p.name+' '+p.archetype+' '+p.tagline+' '+A[p.agent].name).toLowerCase().includes(q)));
$('#n').textContent = d.length+' live product'+(d.length===1?'':'s');
fill($('#facets'), [h('a.btn.sm',{href:'#/agents'},'\u25CF Agents'),
...FACETS.map(f=>h('button.sm'+(f===facet?' p':''),
{onclick:()=>{facet=f;render();}}, f==='All'?'All':A[f].name))]);
fill($('#grid'), d.length ? d.map(card) : h('div.empty','Nothing matches that search.'));
}
$('#q').oninput = e => { q = e.target.value.toLowerCase(); render(); };
addEventListener('hashchange', render);
render();
</script>