'use strict'; /** * openvibe-shared/showcase — the sections a product's home page is built from (plan T11, D92), rendered on the * server as plain HTML (complete without JavaScript, readable by crawlers) and styled from the network's theme * tokens, so every product's home reads as one family: * * const sc = require('openvibe-shared/showcase'); * `` (cached; carries the ring icons' CSS too) or * ``, then sc.hero({ eyebrow, title, accent, lede, actions, note, aside }) (an eyebrow that is * only the site's name is left out: the navbar shows it; { icon, text } gives it an icon) * + sc.features({ id, title, lede, items: [{ icon, title, text, href }] }) * + sc.steps({ title, lede, items: [{ title, text, href }] }) * + sc.code({ title, lede, samples: [{ label, lang, code }] }) * + sc.demo({ title, lede, iframe: { src, title, height } }) (or html: trusted markup) * + sc.compare({ title, lede, columns: ['OpenVibe', 'Elsewhere'], rows: [{ label, values: [true, 'paid add-on'] }] }) * + sc.pricing({ title, lede, tiers: [{ name, price, per, items, action, highlight }], note }) * + sc.limits({ title, lede, columns, rows: [{ label, values }], source }) pricing that equals the API's limits * + sc.stories({ title, items: [{ who, text, outcome }] }) * + sc.stats({ title, items: [{ value, label, href, icon }] }) a strip of real numbers (three across on a phone) * + sc.cards({ title, lede, items: [{ href, title, text, image, badge, meta, live }], layout: 'grid'|'rail', * ratio: '16/9'|'4/3'|'1/1', more: { label, href } }) rich content: videos, streams, posts * + sc.spotlight({ title, lede, items: [{ title, text, image, video, alt, href, actions }] }) features, shown * + sc.faq({ title, items: [[question, answer]] }) an accordion, with FAQPage data (faqJsonLd) * + sc.network({ service, title, lede, items }) "Part of OpenVibe": related products * + sc.cta({ title, text, actions }) * sc.jsonLd(obj) is a `; const faqJsonLd = (items) => jsonLd({ '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: (items || []).filter((q) => Array.isArray(q) && q[0] && q[1]).map(([q, a]) => ({ '@type': 'Question', name: String(q), acceptedAnswer: { '@type': 'Answer', text: String(a) } })) }); /** * Where each home page leads next on the network: the other places in the same menu group, in the group's own order * (OpenVibe.Network frame/sites.js GROUPS, the owner's regrouping of 2026-11-10), so "More on OpenVibe" shows the * same neighbourhood the launcher and the footer show. Up to four. [to, what, href]; `to` is the name and icon the * card shows (Email is `email`, OpenRestream `openre`, Studio `studio`, Locker `locker`). */ const GROUPS = { watch: [ ['live', 'Go live, watch streams', 'https://openvibe.live/'], ['video', 'Clips and past streams', 'https://openvibe.video/'], ['pics', 'Share pictures', 'https://openvibe.pics/'], ['download', 'Your files', 'https://openvibe.download/'], ['openre', 'Stream everywhere', 'https://openre.stream/'], ['cam', 'Your camera, in private', 'https://openvibe.cam/'], ['tours', 'Cities, routes, stays', 'https://openvibe.tours/'], ], talk: [ ['community', 'Posts and pastes', 'https://openvibe.community/'], ['chat', 'Chat with anyone', 'https://openvibe.chat/'], ['tips', 'Support streamers', 'https://openvibe.tips/'], ['vip', 'Back your creators', 'https://openvibe.vip/'], ['email', 'Masks and inboxes', 'https://openvibe.email/'], ['help', 'Help nearby', 'https://openvibe.help/'], ['network', 'Your account', 'https://openvibe.network/'], ], play: [ ['games', 'Play in your browser', 'https://openvibe.games/'], ['quest', 'Earn badges', 'https://openvibe.quest/'], ['surf', 'Surf maps and the web', 'https://openvibe.surf/'], ['fun', 'Something to do', 'https://openvibe.fun/'], ['meme', 'Memes with receipts', 'https://openvibe.meme/'], ['casino', 'Odds shown, chips free', 'https://openvibe.casino/'], ['inventory', 'Your items', 'https://openvibe.my/inventory'], ], read: [ ['news', 'Sourced, not spun', 'https://openvibe.news/'], ['today', 'Your schedule', 'https://openvibe.today/'], ['report', 'Your numbers, honest', 'https://openvibe.report/'], ['search', 'Search everything', 'https://search.openvibe.network/'], ['wiki', 'Facts with sources', 'https://openvibe.wiki/'], ['blog', 'Write long-form', 'https://openvibe.blog/'], ['vision', 'Ask a picture', 'https://openvibe.vision/'], ], shop: [ ['deals', 'Deals people share', 'https://openvibe.deals/'], ['coupons', 'Codes that work', 'https://openvibe.coupons/'], ['shoes', 'Every shoe, every price', 'https://open.shoes/'], ['reviews', 'Honest reviews', 'https://openvibe.reviews/'], ['trade', 'Watch the markets', 'https://openvibe.trade/'], ['rent', 'Places to rent', 'https://openvibe.rent/'], ['work', 'Find a job', 'https://openvibe.work/'], ], tools: [ ['tools', 'Every online tool', 'https://openvibe.tools/'], ['design', 'Try themes live', 'https://openvibe.design/'], ['domains', 'Names and prices', 'https://openvibe.domains/'], ['host', 'Compare hosting', 'https://openvibe.host/'], ['studio', 'Edit photos in your browser', 'https://openvibe.studio/'], ['locker', 'Files only you can open', 'https://openvibe.locker/'], ['food', 'Food near you', 'https://openvibe.food/'], ], make: [ ['space', 'Host a site or app', 'https://openvibe.space/'], ['codes', 'Code with any agent', 'https://openvibe.codes/'], ['bot', 'Drive a robot', 'https://openvibe.bot/'], ['actor', 'The agent engine', 'https://openvibe.actor/'], ['computer', 'Your computer, your way', 'https://openvibe.computer/'], ['connect', 'Share a port with Link', 'https://connect.openvibe.link/'], ], build: [ ['services', 'APIs, SDK and keys', 'https://openvibe.services/'], ['exchange', 'AI models, one account', 'https://openvibe.exchange/'], ['run', 'Run jobs anywhere', 'https://openvibe.run/'], ['zone', 'S3 storage', 'https://openvibe.zone/'], ['events', 'Event streams', 'https://openvibe.events/'], ['watch', 'Know when it changes', 'https://openvibe.watch/'], ['git', 'Host and browse code', 'https://git.openvibe.codes/'], ], }; // The service names home pages pass that are not the card's own name (Video is media-hub, Media is its API, // Restream's service is openre, Email's mail, Studio's image, Locker's lock, Homes is Rent's section, Bench Report's). const RELATED_ALIAS = { media: 'video', 'media-hub': 'video', video: 'video', stream: 'openre', openre: 'openre', restream: 'openre', mail: 'email', email: 'email', image: 'studio', studio: 'studio', lock: 'locker', locker: 'locker', homes: 'rent', bench: 'report', }; const RELATED_ALL = Object.entries(GROUPS).flatMap(([group, members]) => members.map(([to, what, href]) => ({ group, to, what, href }))); const RELATED_BY_ID = new Map(RELATED_ALL.map((x) => [x.to, x])); function relatedFor(key) { const id = RELATED_ALIAS[key] || key; const here = RELATED_BY_ID.get(id); if (!here) return null; return RELATED_ALL.filter((x) => x.group === here.group && x.to !== id).slice(0, 4).map((x) => [x.to, x.what, x.href]); } const RELATED = {}; for (const x of RELATED_ALL) RELATED[x.to] = relatedFor(x.to); for (const [alias, id] of Object.entries(RELATED_ALIAS)) if (RELATED_BY_ID.has(id)) RELATED[alias] = relatedFor(id); // A product without its own list shows one place from each of the first four groups, in the groups' order. const DEFAULT_RELATED = ['live', 'community', 'games', 'news'].map((id) => { const x = RELATED_BY_ID.get(id); return [x.to, x.what, x.href]; }); /** * "Part of OpenVibe": up to four related products for this one (RELATED, or the caller's own items in the same * [to, what, href] or { to, what, href } shape), each a ring icon, its name and why to go there. */ function network({ id: sid, service, title = 'More on OpenVibe', lede = 'One account across every OpenVibe site.', items, icon: secIcon } = {}) { const list = (items || RELATED[String(service || '').toLowerCase()] || DEFAULT_RELATED) .map((x) => (Array.isArray(x) ? { to: x[0], what: x[1], href: x[2] } : x)) .filter((x) => x && x.what && safeHref(x.href) && x.to !== service).slice(0, 4); if (!list.length) return ''; const h = `sc-${slug(title)}`; return `
${head(title, lede, h, secIcon || 'spark')}

Every OpenVibe site

`; } /** * apps({ id, title, lede, groups: [{ name, items }], items, more: { href, label } }) — a set of apps as a launcher, the * way a phone's home screen shows them: each item { icon ('ov:live' or 'fa-…'), name, text, href }. Phones see icon * tiles three or four to a row with the name under the icon (the one-line text is for screen readers and wider * screens); from 600 px each tile is a row: icon, name, the text in one line. groups put a small heading over each * set (the network's neighbourhoods); items without groups is one grid. A set of two to four, or of eight, twelve…, * gets a column count that fills its rows (four tiles: four across on a phone, two by two from 600 px, four from * 960 px), so no tile sits alone on a row. Empty → ''. */ function apps({ id: sid, title = '', lede = '', groups = null, items = null, more = null, icon: secIcon } = {}) { const sets = (groups || [{ name: '', items: items || [] }]).map((g) => ({ name: g.name || '', items: (g.items || []).filter((x) => x && x.name && safeHref(x.href)) })).filter((g) => g.items.length); if (!sets.length) return ''; const h = `sc-${slug(title || 'apps')}`; const glyph = (x) => { const k = String(x.icon || 'ov:spark'); return k.startsWith('ov:') ? appIcon(k.slice(3), 56) : `${icon(k, 22)}`; }; const tile = (x) => `
  • ${glyph(x)}${esc(x.name)}${x.text ? `${esc(x.text)}` : ''}
  • `; // Six or nine are three a row (4.46.1): four columns left one tile alone on the last row. const cols = (n) => (n >= 2 && n <= 4 ? n : n % 4 === 0 ? 4 : n % 3 === 0 ? 3 : 0); const body = sets.map((g) => `${g.name ? `

    ${esc(g.name)}

    ` : ''}`).join(''); const end = more && safeHref(more.href) ? `

    ${esc(more.label || 'See all')}

    ` : ''; return `
    ${head(title, lede, h, secIcon || 'grid')}${body}${end}
    `; } /** The closing call to action. */ function cta({ title, text, actions: acts } = {}) { if (!title) return ''; return `

    ${esc(title)}

    ${text ? `

    ${esc(text)}

    ` : ''}${actions(acts)}
    `; } // ── Rooms (plan 2026-10-28 §3.3): a room's front door, in the order every room shares ── let roomsLib = null, kitLib = null; const fmtNum = (v) => (typeof v === 'number' ? v.toLocaleString('en-US') : String(v)); /** The room's motif when it brings none: its icon in an aura of its accent, two slow orbits and their dots. */ function roomMotif(r) { kitLib = kitLib || require('./kit'); return ``; } /** * room({ room, hero, proof, rails, scenes, explore, dev, faq, network, html }) — a room's front door, in the order every * room shares: hero → live proof → rails (Right now, For you…) → feature scenes → explore → for developers → questions → * the rest of OpenVibe. Wrapped in .ov-room with the room's accent (rooms.js); what has nothing to show is left out. * room: 'surf' (rooms.js id or host; an unknown one keeps the theme's accent) * hero: { title (≤ 8 words), lede, search: { action, name, placeholder, hints: [typed in turn], label }, * start: { href, label } (when there is no box), chips: [{ label, href }] (≤ 5), motif (trusted HTML) } * proof: [{ value, label, live: true } | { value, label, asOf: 'Oct 28' }] — real numbers only; 0 or none is left out * rails: [{ id, title, more, items: [kit.card(...)], size }] — kit.rail * scenes: [{ title, text, html (trusted: the room's own component in a demo state), action: { href, label } }] * explore: { title, items: [{ href, title, text, icon }] } — sc.features · dev: sc.code's arguments, with links * faq: { title, items: [[q, a]] } · network: sc.network's arguments · html: trusted markup after the scenes * Needs kit.css, room.css (cards, rails, the room's accent) and showcase.css; motion.js makes the numbers tick and the box type. */ function room({ room: key, hero: h = {}, proof, rails, scenes, explore, dev, faq: q, network: net, html } = {}) { roomsLib = roomsLib || require('./rooms'); kitLib = kitLib || require('./kit'); const r = roomsLib.get(key); const chips = (h.chips || []).filter((c) => c && c.label && safeHref(c.href)).slice(0, 5); const box = h.search && h.search.action ? `` : ''; const start = !box && h.start && safeHref(h.start.href) ? `
    ${esc(h.start.label || 'Start')}
    ` : ''; const nums = (proof || []).filter((p) => p && p.label && p.value != null && p.value !== '' && p.value !== 0); const proofHTML = nums.length ? `` : ''; const heroHTML = h.title ? `
    ` + `
    ${h.pageHead ? kitLib.pageHead({ ...h.pageHead, title: h.title, lede: h.lede }).replace('

    ', '

    ') : `

    ${esc(h.title)}

    ${h.lede ? `

    ${esc(h.lede)}

    ` : ''}`}${box}${start}` + `${chips.length ? `` : ''}${proofHTML}
    ` + `
    ${h.motif != null ? h.motif : roomMotif(r)}
    ` : ''; const railsHTML = (rails || []).map((x) => kitLib.rail(x)).join(''); const sceneList = (scenes || []).filter((x) => x && x.title && x.html != null); const scenesHTML = sceneList.length ? `
    ${sceneList.map((x, i) => `
    ` + `

    ${esc(x.title)}

    ${x.text ? `

    ${esc(x.text)}

    ` : ''}${x.action && safeHref(x.action.href) ? `${esc(x.action.label || 'Try it')}${kitLib.icon('chevron')}` : ''}
    ` + `
    ${x.html}
    `).join('')}
    ` : ''; const devLinks = dev && has(dev.links) ? `` : ''; const devHTML = dev ? code(dev).replace(/<\/section>$/, `${devLinks}`) : ''; return `
    ${heroHTML}${railsHTML}${scenesHTML}${html != null ? html : ''}` + `${explore ? features(explore) : ''}${devHTML}${q ? faq(q) : ''}${net ? network(net) : ''}
    `; } // More on OpenVibe's live strip: up to four things happening across the network, filled in the browser from Network's // Frame (the same data the menu shows: who is live, the sites' trending cards). Text only; https links only; nothing // shows when Network does not answer. A page that renders network() includes this once: ``. const NETWORK_JS = "(function(){try{var box=document.querySelector('[data-ov-now]');if(!box||!window.fetch)return;var here=location.hostname;" + "function ok(u){try{var x=new URL(u);return x.protocol==='https:'&&x.hostname!==here?x.href:null}catch(e){return null}}" + "function row(tag,name,title,href,live){var a=document.createElement('a');a.href=href;var s=document.createElement('span');s.textContent=tag;if(live)s.className='live';a.appendChild(s);var t=document.createElement('em');t.style.fontStyle='normal';t.textContent=title;a.appendChild(t);box.appendChild(a)}" + "Promise.all([fetch('https://openvibe.network/api/frame/pulse',{credentials:'omit'}).then(function(r){return r.ok?r.json():null}).catch(function(){return null}),fetch('https://openvibe.network/api/frame?v=2',{credentials:'omit',headers:{accept:'application/json'}}).then(function(r){return r.ok?r.json():null}).catch(function(){return null})]).then(function(v){var n=0,p=v[0],f=v[1];" + "var h=document.createElement('b');h.textContent='Happening on OpenVibe';box.appendChild(h);" + "if(p&&Number(p.live_now)>0){var names=(p.live||[]).slice(0,3).map(function(c){return c.name}).filter(Boolean).join(', ');row('Live',null,(p.live_now==1?'1 stream live now':p.live_now+' streams live now')+(names?': '+names:''),'https://openvibe.live/',true);n++}" + "(f&&f.cards||[]).forEach(function(c){if(n>=4||!c||!c.items||!c.items.length)return;var it=c.items[0],u=ok(it.url);if(!u||!it.title)return;row(String(c.title||c.site||'').slice(0,24),null,String(it.title).slice(0,90),u,false);n++});" + "if(n)box.hidden=false})}catch(e){}})();"; const CSS = ` ${PARTS_CSS} /* The hero's visual (4.26.0): the aura (rings, dots, the mark) or an orbit of the site's places. */ .sc-vis{position:relative;width:min(100%,380px);aspect-ratio:1;margin:0 auto;container-type:inline-size} .sc-aura-glow{position:absolute;inset:28%;border-radius:50%;background:radial-gradient(closest-side,color-mix(in srgb,var(--accent,#3b82f6) 42%,transparent),transparent);filter:blur(8px);animation:sc-breathe 6s ease-in-out infinite} .sc-ring{position:absolute;border-radius:50%;border:1px dashed color-mix(in srgb,var(--accent-light,#60a5fa) 34%,transparent);} .sc-ring.r1{inset:24%}.sc-ring.r2{inset:12%;animation-duration:105s;animation-direction:reverse;opacity:.8}.sc-ring.r3{inset:1%;animation-duration:150s;opacity:.55} .sc-ring i{position:absolute;top:-5px;left:calc(50% - 5px);width:10px;height:10px;border-radius:50%;background:var(--accent,#3b82f6);box-shadow:0 0 14px var(--accent,#3b82f6)} .sc-ring i+i{top:auto;bottom:-4px;left:calc(30% - 4px);width:8px;height:8px;background:var(--accent-light,#60a5fa)} .sc-vis-mark{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);z-index:2} .sc-orb-ring{position:absolute;inset:0;border-radius:50%} .sc-orb-ring::before{content:"";position:absolute;border-radius:50%;border:1px dashed color-mix(in srgb,var(--accent-light,#60a5fa) 30%,transparent)} .sc-orb-ring.o1{--R:30cqw;z-index:2}.sc-orb-ring.o1::before{inset:20%} .sc-orb-ring.o2{--R:44cqw;animation-duration:140s;animation-direction:reverse}.sc-orb-ring.o2::before{inset:6%;opacity:.55} .sc-orb-node{position:absolute;left:50%;top:50%;width:40px;height:40px;margin:-20px;transform:rotate(calc(360deg / var(--n) * var(--i))) translateY(calc(-1 * var(--R))) rotate(calc(-360deg / var(--n) * var(--i)));color:var(--text-primary,#e6edf7);text-decoration:none} .sc-orb-node.is-big{width:54px;height:54px;margin:-27px} .sc-orb-face{display:flex;flex-direction:column;align-items:center;gap:4px;width:100%;height:100%} .o2 .sc-orb-face{animation-duration:140s;animation-direction:normal} .sc-orb-face>.ov-icon,.sc-orb-face>svg,.sc-orb-face>i{display:grid;place-items:center;flex:none;width:100%;height:100%;border-radius:50%;background:color-mix(in srgb,var(--bg-secondary,#101828) 88%,transparent);box-shadow:0 0 0 1px color-mix(in srgb,var(--accent-light,#60a5fa) 30%,transparent),0 8px 22px rgba(0,0,0,.35);transition:transform .3s cubic-bezier(.2,1.4,.3,1)} .sc-orb-name{font-size:11.5px;font-weight:700;color:var(--text-secondary,#96a7c2);white-space:nowrap;padding:1px 7px;border-radius:999px;background:color-mix(in srgb,var(--bg-primary,#0a0f1c) 78%,transparent)} a.sc-orb-node:hover .sc-orb-face>*:first-child,a.sc-orb-node:focus-visible .sc-orb-face>*:first-child{transform:scale(1.15)} .sc-orb-core{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);z-index:3;display:flex;flex-direction:column;align-items:center;gap:2px;color:var(--text-primary,#e6edf7);text-decoration:none;text-align:center} .sc-orb-core .sc-vis-mark{position:static;transform:none} .sc-orb-core b{margin-top:6px;font-size:14px;font-weight:800}.sc-orb-core small{font-size:12px;color:var(--text-muted,#7386a3)} @keyframes sc-breathe{0%,100%{opacity:.7;transform:scale(.95)}50%{opacity:1;transform:scale(1.05)}} @media (max-width:860px){ .sc-hero.sc-has-vis{display:block} .sc-hero.sc-has-vis .sc-hero-vis{position:absolute;z-index:-1;top:-26px;right:0;width:180px;opacity:.36;pointer-events:none} .sc-hero.sc-has-vis .sc-aura .sc-vis-mark{display:none!important} .sc-hero.sc-has-orbit{display:flex;flex-direction:column} .sc-hero.sc-has-orbit .sc-hero-vis{order:1;position:static;width:auto;opacity:1;pointer-events:auto;margin:22px 0 0} .sc-hero.sc-has-orbit .sc-vis{width:min(82vw,300px)} .sc-hero.sc-has-orbit .sc-orb-core small{max-width:8em;font-size:10px;line-height:1.2} .sc-hero.sc-has-orbit .sc-orb-core .sc-vis-mark{width:50px!important;height:50px!important} .sc-orb-node{width:32px;height:32px;margin:-16px}.sc-orb-node.is-big{width:44px;height:44px;margin:-22px}.sc-orb-name{font-size:10.5px} } .sc-hero,.sc-sec,.sc-cta{max-width:1080px;margin:0 auto;padding:0 24px;font-family:var(--ov-font,'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif)} .sc-hero{padding-top:56px;padding-bottom:8px} .sc-hero.sc-split{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);gap:32px;align-items:center} .sc-eyebrow{display:inline-block;margin:0 0 14px;padding:4px 12px;border-radius:999px;border:1px solid var(--border-light,var(--border,#2c3d5c));background:var(--bg-secondary,#101828);color:var(--accent-light,var(--accent,#60a5fa));font-size:12px;font-weight:700;letter-spacing:.4px} .sc-hero h1{font-size:clamp(32px,4.6vw,52px);line-height:1.08;letter-spacing:-1.2px;margin:0;font-weight:800} .sc-accent{background:linear-gradient(120deg,var(--accent-light,#60a5fa),var(--accent,#3b82f6),var(--secondary,#22d3ee));-webkit-background-clip:text;background-clip:text;color:transparent} .sc-hero-lede{margin:16px 0 0;max-width:640px;font-size:17px;line-height:1.6;color:var(--text-secondary,#96a7c2)} .sc-hero-aside{min-width:0} .sc-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:24px} .sc-btn{display:inline-flex;align-items:center;gap:8px;padding:11px 20px;border-radius:12px;font-weight:700;font-size:15px;text-decoration:none;color:var(--text-primary,#e6edf7);border:1px solid var(--border-light,var(--border,#2c3d5c));background:transparent;transition:border-color .15s,background .15s,transform .15s} .sc-btn:hover,.sc-btn:focus-visible{border-color:var(--accent,#3b82f6);background:var(--bg-hover,#1c2a44);outline:0} .sc-btn.sc-primary{border-color:transparent;color:var(--on-accent,#fff);background:linear-gradient(135deg,var(--accent,#3b82f6),var(--accent-dark,#1d4ed8))} .sc-btn.sc-primary:hover{transform:translateY(-1px);filter:brightness(1.06)} .sc-note{margin:14px 0 0;font-size:13.5px;color:var(--text-muted,#7386a3)} .sc-note a{color:var(--accent-light,#60a5fa)} .sc-sec{margin-top:48px;padding-top:32px;border-top:1px solid color-mix(in srgb,var(--border,#263244) 70%,transparent);container-type:inline-size} .sc-hero+.sc-sec,.sc-sec:first-child{border-top:0;padding-top:0} .sc-sec>h2,.sc-cta>h2{display:flex;align-items:center;gap:12px;font-size:clamp(22px,2.6vw,30px);letter-spacing:-.02em;margin:0 0 6px} .sc-h-ic{flex:none;display:grid;place-items:center;width:36px;height:36px;border-radius:11px;background:color-mix(in srgb,var(--accent,#3b82f6) 14%,transparent);color:var(--accent-light,#60a5fa)} .sc-h-ic svg{display:block} .sc-sec>h2:has(.sc-h-ic)+.sc-lede{margin-left:48px} @media (max-width:520px){.sc-sec>h2+.sc-lede{margin-left:0}} .sc-lede{color:var(--text-secondary,#96a7c2);margin:0 0 18px;max-width:760px;line-height:1.55} .sc-grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fill,minmax(250px,1fr))} @container (min-width:520px) and (max-width:1035px){.sc-grid:has(>:nth-child(4):last-child){grid-template-columns:repeat(2,minmax(0,1fr))}} @media (min-width:900px){.sc-grid:has(>:nth-child(5):last-child),.sc-grid:has(>:nth-child(6):last-child),.sc-grid:has(>:nth-child(9):last-child){grid-template-columns:repeat(3,minmax(0,1fr))}} .sc-card{display:flex;gap:14px;align-items:flex-start;padding:16px;border-radius:16px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828);color:inherit;text-decoration:none;transition:border-color .15s,transform .15s} a.sc-card:hover,a.sc-card:focus-visible{border-color:var(--accent,#3b82f6);transform:translateY(-2px);outline:0} .sc-ic{width:40px;height:40px;flex:none;display:grid;place-items:center;border-radius:12px;font-size:17px;color:var(--accent-light,#60a5fa);background:color-mix(in srgb,var(--accent,#3b82f6) 14%,transparent)} .sc-ic.sc-ic-ov{background:none;border-radius:50%} .sc-ovi--glyph{border-radius:50%;color:var(--accent,#3b82f6);background:color-mix(in srgb,var(--accent,#3b82f6) 13%,transparent);box-shadow:inset 0 0 0 1.5px color-mix(in srgb,var(--accent,#3b82f6) 42%,transparent)}.sc-ovi--glyph svg{width:54%!important;height:54%!important} .sc-card b{display:block;font-size:15.5px}.sc-card small{display:block;margin-top:3px;color:var(--text-secondary,#96a7c2);font-size:13.5px;line-height:1.45} .sc-steps{list-style:none;margin:0;padding:0;counter-reset:sc;display:grid;gap:14px 24px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr))} @container (min-width:584px) and (max-width:1191px){.sc-steps:has(>:nth-child(4):last-child){grid-template-columns:repeat(2,minmax(0,1fr))}} .sc-steps li{counter-increment:sc;display:grid;grid-template-columns:34px 1fr;column-gap:12px;align-items:baseline;align-content:start} .sc-steps li::before{content:counter(sc);grid-row:span 2;width:30px;height:30px;border-radius:50%;display:grid;place-items:center;font-weight:800;font-size:13px;color:var(--accent-light,#60a5fa);border:1px solid var(--border-light,#2c3d5c);background:var(--bg-secondary,#101828)} .sc-steps b{font-size:15.5px}.sc-steps b a{color:inherit;text-decoration:underline;text-decoration-color:var(--accent,#3b82f6);text-underline-offset:3px} .sc-steps span{color:var(--text-secondary,#96a7c2);font-size:14px;line-height:1.5} .sc-code{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(320px,1fr))} .sc-code figure{margin:0;border-radius:14px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-primary,#0a0f1c);overflow:hidden;min-width:0} .sc-code figcaption{padding:8px 14px;font-size:12px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;color:var(--text-muted,#7386a3);border-bottom:1px solid var(--border,rgba(255,255,255,.08))} .sc-code pre{margin:0;padding:14px;overflow:auto;font-size:13px;line-height:1.55} .sc-demo{border-radius:16px;border:1px solid var(--border,rgba(255,255,255,.08));overflow:hidden;background:var(--bg-secondary,#101828)} .sc-demo iframe{display:block;width:100%;border:0;background:var(--bg-primary,#0a0f1c)} .sc-table-wrap{overflow-x:auto;border-radius:14px;border:1px solid var(--border,rgba(255,255,255,.08))} .sc-table{width:100%;border-collapse:collapse;font-size:14px} .sc-table th,.sc-table td{padding:11px 14px;text-align:left;border-bottom:1px solid var(--border,rgba(255,255,255,.08))} .sc-table thead th{font-size:12px;letter-spacing:.5px;text-transform:uppercase;color:var(--text-muted,#7386a3);background:var(--bg-secondary,#101828)} .sc-table tbody th{font-weight:600;width:44%}.sc-table tr:last-child>*{border-bottom:0} .sc-table .sc-us{background:color-mix(in srgb,var(--accent,#3b82f6) 7%,transparent)} .sc-yes{color:var(--success-text,var(--success,#22c55e));font-weight:800}.sc-no{color:var(--text-muted,#7386a3)} .sc-tiers{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))} .sc-tier{display:flex;flex-direction:column;padding:18px;border-radius:16px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)} .sc-tier.sc-hl{border-color:var(--accent,#3b82f6);box-shadow:0 0 0 1px var(--accent,#3b82f6) inset} .sc-tier-name{font-size:13px;letter-spacing:.6px;text-transform:uppercase;color:var(--text-muted,#7386a3)} .sc-price{margin:6px 0 10px;font-size:28px;font-weight:800}.sc-price small{font-size:14px;font-weight:600;color:var(--text-secondary,#96a7c2)} .sc-tier ul{margin:0;padding-left:18px;color:var(--text-secondary,#96a7c2);font-size:14px;line-height:1.6} .sc-tier-act{margin-top:auto;padding-top:14px} .sc-stories{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(260px,1fr))} .sc-story{margin:0;padding:18px;border-radius:16px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)} .sc-story blockquote{margin:0;font-size:15px;line-height:1.55}.sc-story figcaption{margin-top:10px;font-weight:700;font-size:13.5px} .sc-story figcaption span{display:block;font-weight:500;color:var(--text-secondary,#96a7c2)} .sc-next-list{list-style:none;margin:0;padding:0;display:grid;gap:10px} .sc-next-item{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px} .sc-next-main{flex:1 1 320px;display:flex;align-items:center;gap:14px;padding:14px 16px;border-radius:14px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828);color:inherit;text-decoration:none;transition:border-color .15s,transform .15s} .sc-next-main:hover,.sc-next-main:focus-visible{border-color:var(--accent,#3b82f6);transform:translateX(2px);outline:0} .sc-next-body{flex:1;min-width:0}.sc-next-body b{display:block;font-size:15.5px} .sc-next-body small{display:block;margin-top:2px;color:var(--text-secondary,#96a7c2);font-size:13.5px;line-height:1.45} .sc-next-on{display:block;margin-top:4px;font-size:12px;font-weight:700;letter-spacing:.3px;color:var(--accent-light,var(--accent,#60a5fa))} .sc-next-go{font-size:20px;color:var(--text-muted,#7386a3);transition:transform .15s}.sc-next-main:hover .sc-next-go{transform:translateX(3px);color:var(--accent-light,#60a5fa)} .sc-next-embed{font-size:13px;font-weight:600;color:var(--accent-light,#60a5fa)} .sc-cta{margin-top:64px;margin-bottom:24px;text-align:center} .sc-cta>div,.sc-cta .sc-actions{justify-content:center}.sc-cta p{color:var(--text-secondary,#96a7c2);margin:6px auto 0;max-width:620px} .sc-head-row{display:flex;align-items:flex-end;justify-content:space-between;gap:12px;flex-wrap:wrap}.sc-head-row>div{min-width:0}.sc-head-row h2{margin:0 0 6px;font-size:clamp(22px,2.6vw,30px);letter-spacing:-.02em} .sc-more{font-weight:700;font-size:14px;color:var(--accent-light,var(--accent,#60a5fa));text-decoration:none;white-space:nowrap;margin-bottom:18px}.sc-more:hover{text-decoration:underline} .sc-stats{display:grid;gap:10px;margin:0;grid-template-columns:repeat(auto-fit,minmax(150px,1fr))} .sc-stat{display:flex;flex-direction:column-reverse;gap:2px;padding:16px 18px;border-radius:16px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828);color:inherit;text-decoration:none} .sc-stat dd{margin:0;font-size:clamp(24px,3vw,32px);font-weight:800;letter-spacing:-.02em;font-variant-numeric:tabular-nums}.sc-stat dt{font-size:13.5px;color:var(--text-secondary,#96a7c2)} a.sc-stat:hover{border-color:var(--accent,#3b82f6)} .sc-cards{list-style:none;margin:0;padding:0;display:grid;gap:14px} .sc-cards.sc-wrap{grid-template-columns:repeat(auto-fill,minmax(230px,1fr))} .sc-cards.sc-rail{grid-auto-flow:column;grid-auto-columns:minmax(240px,300px);overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:8px;scrollbar-width:thin} .sc-cards.sc-rail>li{scroll-snap-align:start} .sc-tile{display:flex;flex-direction:column;height:100%;border-radius:16px;overflow:hidden;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828);color:inherit;text-decoration:none;transition:border-color .15s,transform .2s,box-shadow .2s} .sc-tile:hover,.sc-tile:focus-visible{border-color:var(--accent,#3b82f6);transform:translateY(-3px);box-shadow:0 12px 30px rgba(0,0,0,.28);outline:0} .sc-thumb{position:relative;display:block;aspect-ratio:var(--sc-ar,16/9);background:linear-gradient(135deg,color-mix(in srgb,var(--accent,#3b82f6) 22%,var(--bg-tertiary,#172238)),var(--bg-tertiary,#172238))} .sc-thumb img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;transition:transform .35s ease}.sc-tile:hover .sc-thumb img{transform:scale(1.04)} .sc-badge{position:absolute;left:10px;top:10px;padding:3px 8px;border-radius:6px;font-size:11px;font-weight:800;letter-spacing:.4px;color:#fff;background:rgba(0,0,0,.66)} .sc-badge.sc-live{background:color-mix(in srgb,var(--live-red,#ef4444) 80%,#000)}.sc-badge.sc-live::before{content:'';display:inline-block;width:6px;height:6px;margin-right:5px;border-radius:50%;background:#fff;vertical-align:1px;animation:scPulse 1.4s ease-in-out infinite} .sc-tile-body{display:block;padding:12px 14px 14px}.sc-tile-body b{display:block;font-size:15px;line-height:1.35;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical} .sc-tile-body small{display:block;margin-top:4px;font-size:12.5px;color:var(--text-muted,#7386a3)}.sc-tile-text{display:block;margin-top:6px;font-size:13.5px;line-height:1.45;color:var(--text-secondary,#96a7c2)} .sc-spots{display:grid;gap:28px} .sc-spot{display:grid;grid-template-columns:minmax(0,1.1fr) minmax(0,1fr);gap:28px;align-items:center} .sc-spot:nth-child(even) .sc-spot-media{order:2} .sc-spot-media{border-radius:18px;overflow:hidden;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828);box-shadow:0 18px 50px color-mix(in srgb,var(--accent,#3b82f6) 16%,transparent)} .sc-spot-media img,.sc-spot-media video{display:block;width:100%;height:auto} .sc-spot h3{margin:0 0 8px;font-size:clamp(20px,2.2vw,26px);letter-spacing:-.01em}.sc-spot h3 a{color:inherit;text-decoration:none}.sc-spot h3 a:hover{color:var(--accent-light,#60a5fa)} .sc-spot p{margin:0;color:var(--text-secondary,#96a7c2);line-height:1.6;font-size:15.5px}.sc-spot-act{margin-top:16px} .sc-faq{display:grid;gap:8px;max-width:820px} .sc-faq details,.sc-sec>details:not([class]){border-radius:14px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)} .sc-faq summary,.sc-sec>details:not([class])>summary{cursor:pointer;list-style:none;padding:14px 44px 14px 16px;font-weight:700;position:relative} .sc-faq summary::-webkit-details-marker,.sc-sec>details:not([class])>summary::-webkit-details-marker{display:none} .sc-faq summary::after,.sc-sec>details:not([class])>summary::after{content:'';position:absolute;right:18px;top:50%;width:8px;height:8px;margin-top:-6px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .2s;opacity:.7} .sc-faq details[open] summary::after,.sc-sec>details[open]:not([class])>summary::after{transform:rotate(-135deg);margin-top:-2px} .sc-sec>details:not([class])+details:not([class]){margin-top:8px} .sc-faq p,.sc-sec>details:not([class])>p{margin:0;padding:0 16px 16px;color:var(--text-secondary,#96a7c2);line-height:1.6} .sc-fold{border-radius:16px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)} .sc-fold>summary{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:56px;padding:0 48px 0 18px;cursor:pointer;list-style:none;position:relative} .sc-fold>summary::-webkit-details-marker{display:none} .sc-fold>summary::after{content:'';position:absolute;right:22px;top:50%;width:8px;height:8px;margin-top:-6px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .2s;opacity:.7} .sc-fold[open]>summary::after{transform:rotate(-135deg);margin-top:-2px} .sc-fold>summary:focus-visible{outline:2px solid var(--accent,#3b82f6);outline-offset:-2px;border-radius:16px} .sc-fold>summary h2{margin:0;font-size:clamp(18px,2.2vw,22px)} .sc-fold-hint{color:var(--text-muted,#7386a3);font-size:14px;font-weight:600;text-align:right} .sc-fold-body{padding:0 18px 18px} .sc-fold-body .sc-sec{max-width:none;margin:0;padding:0} .sc-fold-body .sc-sec+.sc-sec{margin-top:22px} /* A list that is one sideways row on a phone (scroll snap, the next card peeking) and whatever the page made it on wide screens. Every item stays in the HTML. --sc-row-w sets a card's width (82% by default; 46% for small tiles). */ @media (max-width:599px){ .sc-row.sc-row{position:relative;display:flex;flex-wrap:nowrap;overflow-x:auto;scroll-snap-type:x mandatory;scroll-padding-inline:16px;scrollbar-width:none;gap:10px;margin-left:-16px;margin-right:-16px;padding:0 16px 4px;grid-template-columns:none} .sc-row.sc-row::-webkit-scrollbar{display:none} .sc-row.sc-row>*{flex:0 0 var(--sc-row-w,82%);min-width:0;scroll-snap-align:start} .sc-fold-hint{display:none} } /* apps(): a launcher. Phones: icon tiles, name under the icon; from 600 px: rows with a one-line tagline. */ .sc-apps-group{margin:18px 0 8px;font-size:13px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--text-secondary,#96a7c2)} .sc-apps-sec .sc-apps-group:first-of-type{margin-top:6px} .sc-apps{list-style:none;margin:0;padding:0;display:grid;gap:6px;grid-template-columns:repeat(auto-fill,minmax(88px,1fr))} .sc-apps a{display:flex;flex-direction:column;align-items:center;gap:6px;min-height:44px;padding:10px 4px;border-radius:14px;color:inherit;text-decoration:none;text-align:center;transition:background .15s} .sc-apps a:hover,.sc-apps a:focus-visible{background:color-mix(in srgb,var(--accent,#3b82f6) 10%,transparent);outline:0} .sc-apps a:focus-visible{box-shadow:0 0 0 2px var(--accent,#3b82f6)} .sc-app-t{min-width:0;max-width:100%} .sc-apps b{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:700} .sc-apps small{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap} .sc-apps-more{margin:14px 0 0;font-weight:700} .sc-apps-more a{display:inline-flex;align-items:center;min-height:44px;color:var(--accent-light,#60a5fa);text-decoration:none} .sc-apps-more a:hover{text-decoration:underline} @media (max-width:599px){.sc-apps--c2{grid-template-columns:repeat(2,minmax(0,1fr))}.sc-apps--c3{grid-template-columns:repeat(3,minmax(0,1fr))}.sc-apps--c4{grid-template-columns:repeat(4,minmax(0,1fr))}} @media (min-width:600px){ .sc-apps{gap:8px;grid-template-columns:repeat(auto-fill,minmax(230px,1fr))} .sc-apps a{flex-direction:row;align-items:center;gap:12px;padding:10px 12px;text-align:left;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)} .sc-apps .ov-appicon{--ova-size:44px!important} .sc-apps a:hover,.sc-apps a:focus-visible{border-color:var(--accent,#3b82f6);background:var(--bg-secondary,#101828)} .sc-apps b{font-size:15px} .sc-apps small{position:static;width:auto;height:auto;clip:auto;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-secondary,#96a7c2);font-size:13px} .sc-apps--c2,.sc-apps--c4{grid-template-columns:repeat(2,minmax(0,1fr))} .sc-apps--c3{grid-template-columns:repeat(3,minmax(0,1fr))} } @media (min-width:960px){.sc-apps--c4{grid-template-columns:repeat(4,minmax(0,1fr))}} .sc-net-now{display:grid;gap:8px;margin:14px 0 0;padding:12px;border-radius:14px;background:color-mix(in srgb,var(--accent,#3b82f6) 6%,var(--bg-secondary,#101828));border:1px solid color-mix(in srgb,var(--accent,#3b82f6) 22%,var(--border,#263244))} .sc-net-now[hidden]{display:none} .sc-net-now b{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--text-muted,#8a96a8)} .sc-net-now a{display:flex;gap:10px;align-items:baseline;min-height:36px;color:inherit;text-decoration:none;font-size:14px;line-height:1.4} .sc-net-now a span{flex:none;min-width:64px;font-weight:700;color:var(--accent-light,#60a5fa)} .sc-net-now a .live{color:var(--live,#ef4444)} .sc-net-all{margin:12px 0 0;font-weight:700}.sc-net-all a{color:var(--accent-light,#60a5fa);text-decoration:none} .sc-net{list-style:none;margin:0;padding:0;display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))} .sc-net a{display:flex;gap:12px;align-items:center;padding:14px;border-radius:14px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828);color:inherit;text-decoration:none;transition:border-color .15s,transform .15s} .sc-net a:hover,.sc-net a:focus-visible{border-color:var(--accent,#3b82f6);transform:translateY(-2px);outline:0} .sc-net b{display:block;font-size:15px}.sc-net small{display:block;color:var(--text-secondary,#96a7c2);font-size:13px;line-height:1.4} .sc-hero{position:relative;isolation:isolate} /* The hero's aura is drawn past its right edge (right:-54px below), so a narrow page that uses it was wider than the screen and a phone scrolled sideways. Clip the viewport's horizontal axis in the kit itself: nothing that fits is cut, and the page's own horizontal scrollers (tables, rails) keep working (2026-10-29). */ html{overflow-x:clip} .sc-hero::before{content:'';position:absolute;z-index:-1;inset:-40px 0 auto;height:340px;pointer-events:none;background:radial-gradient(ellipse at 30% 20%,color-mix(in srgb,var(--accent,#3b82f6) 22%,transparent),transparent 62%),radial-gradient(ellipse at 80% 0%,color-mix(in srgb,var(--secondary,#22d3ee) 14%,transparent),transparent 58%);filter:blur(10px);opacity:.9} @keyframes scPulse{50%{opacity:.35}} @supports (animation-timeline:view()){.sc-sec{animation:scRise linear both;animation-timeline:view();animation-range:entry 0% cover 18%}html[data-ov-motion=off] .sc-sec{animation:none}} @keyframes scRise{from{transform:translateY(calc(18px * var(--ov-move,1)))}to{transform:none}} .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0} @media (max-width:860px){.sc-hero.sc-split{grid-template-columns:1fr}.sc-hero{padding-top:36px}.sc-spot{grid-template-columns:1fr;gap:14px}.sc-spot:nth-child(even) .sc-spot-media{order:0}} @media (max-width:520px){.sc-actions .sc-btn{flex:1 1 100%;justify-content:center}.sc-code{grid-template-columns:1fr}} .sc-stat dd{display:flex;align-items:center;gap:8px}.sc-stat-ic{display:inline-flex;color:var(--accent-light,var(--accent,#60a5fa))} @media (max-width:640px){ .sc-hero,.sc-sec,.sc-cta{padding-left:16px;padding-right:16px} .sc-hero{padding-top:14px}.sc-hero::before{inset:-60px 0 auto;height:280px} .sc-eyebrow{margin-bottom:10px;font-size:11.5px} .sc-hero h1{font-size:clamp(27px,7.6vw,34px);line-height:1.1;letter-spacing:-.8px} .sc-hero-lede{margin-top:10px;font-size:15.5px;line-height:1.5} .sc-actions{display:grid;grid-template-columns:repeat(auto-fit,minmax(0,1fr));gap:8px;margin-top:16px} .sc-actions .sc-btn{justify-content:center;min-height:46px;padding:10px 12px;font-size:14.5px;text-align:center} .sc-note{margin-top:10px;font-size:12px;line-height:1.45} .sc-stats-sec{margin-top:14px} .sc-stats{grid-template-columns:repeat(3,minmax(0,1fr));gap:6px} .sc-stat{padding:10px 10px 9px;border-radius:13px;gap:1px;justify-content:flex-end} .sc-stat dd{font-size:19px;gap:5px;letter-spacing:-.01em}.sc-stat-ic svg,.sc-stat-ic .ov-icon{width:15px;height:15px} .sc-stat dt{font-size:11.5px;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden} } `; module.exports = { NETWORK_JS, icon, room, hero, features, steps, code, demo, fold, compare, pricing, limits, stories, stats, cards, spotlight, faq, network, apps, nextSteps, parts, pick, PARTS_CSS, cta, jsonLd, faqJsonLd, RELATED, DEFAULT_RELATED, CSS, STYLESHEET: 'showcase.css', esc };