diff --git a/README.md b/README.md index 541df63..14f1ae7 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,14 @@ contract is `docs/FUSION.md` in the HAL 8 repository. - Chat, code, web search, images, video and voice are always on. - Python code blocks run in the visitor's browser. +The launch film lives in `fusion/launch/`. It includes: + +- `fusion-ai-launch.mp4`, the 32-second, 16:9 cut shown on the HAL 8 page (the "Meet Fusion AI" card) and in the widget's "Watch the launch film" lightbox; +- `poster.jpg`, its poster frame; +- `fusion-ai-launch.vtt`, its subtitles. + +The film was rendered in Blender from the Apex Universe scene. The Fusion pane in it is the real widget, playing answers Fusion AI actually gave. The voice is ElevenLabs "Brian", and the score is original. + The widget is `fusion/fusion.js` and `fusion/fusion.css`. After editing either, run `python3 tools/fusion_inject.py`: it inlines the CSS and re-stamps the module version inside `index.html`, and re-running it diff --git a/fusion/fusion.css b/fusion/fusion.css index 81a3708..3eb25f5 100644 --- a/fusion/fusion.css +++ b/fusion/fusion.css @@ -76,3 +76,18 @@ .f8-fab{right:18px;bottom:18px} .f8-open .f8-fab{display:none} } +.f8-watch{margin-top:14px;display:flex;align-items:center;gap:9px;width:100%;padding:10px 13px;border:1px solid rgba(255,42,26,.35);border-radius:10px;background:linear-gradient(90deg,rgba(255,42,26,.10),rgba(255,42,26,0));color:var(--ink);font-size:13.5px;text-align:left} +.f8-watch i{width:9px;height:9px;border-radius:50%;background:#FF2A1A;box-shadow:0 0 12px #FF2A1A;flex:none} +.f8-watch span{margin-left:auto;font-family:var(--f-mono);font-size:10.5px;color:var(--ink-3)} +.f8-watch:hover{border-color:rgba(255,42,26,.7)} +.f8-film{position:fixed;inset:0;z-index:300;background:rgba(0,0,0,.86);display:flex;align-items:center;justify-content:center;padding:24px} +.f8-film-frame{position:relative;width:min(1200px,100%);aspect-ratio:16/9;border-radius:12px;overflow:hidden;box-shadow:0 30px 90px rgba(0,0,0,.7),0 0 0 1px rgba(255,255,255,.08)} +.f8-film video{width:100%;height:100%;display:block;background:#000} +.f8-film-x{position:absolute;top:10px;right:10px;width:38px;height:38px;border-radius:50%;border:0;background:rgba(0,0,0,.55);color:#fff;font-size:24px;line-height:1;cursor:pointer} +.f8-launch{display:grid;grid-template-columns:minmax(0,1.55fr) minmax(0,1fr);gap:28px;align-items:center;margin:30px 0 8px;padding:18px;border:1px solid var(--rule);border-radius:16px;background:linear-gradient(135deg,rgba(255,42,26,.06),rgba(255,255,255,.015) 45%)} +.f8-launch-media{border-radius:12px;overflow:hidden;background:#000;aspect-ratio:16/9;box-shadow:0 18px 50px rgba(0,0,0,.5)} +.f8-launch-media video{width:100%;height:100%;display:block} +.f8-launch-kicker{font-family:var(--f-mono);font-size:10.5px;letter-spacing:.22em;color:#FF5A47;margin-bottom:10px} +.f8-launch h3{font-family:var(--f-display);font-size:30px;line-height:1.1;margin:0 0 12px;color:var(--ink)} +.f8-launch p{color:var(--ink-2);font-size:15px;line-height:1.55;margin:0 0 18px} +@media (max-width:900px){.f8-launch{grid-template-columns:1fr}} diff --git a/fusion/fusion.js b/fusion/fusion.js index b21b4fc..1172252 100644 --- a/fusion/fusion.js +++ b/fusion/fusion.js @@ -8,6 +8,8 @@ // pictures and clips it makes are deleted after a day. const EYE = 'universe/logos/hal8-eye.png'; +const LAUNCH = new URL('launch/', import.meta.url); +const FILM_V = '1'; const STORE = 'f8.chat.v1'; const VISITOR = 'f8.visitor'; const PYODIDE = 'https://cdn.jsdelivr.net/pyodide/v0.26.4/full/'; @@ -147,7 +149,8 @@ export function mount(root, opts = {}) { if (!list.length) { const s = SUGGEST[state.mode]; body.innerHTML = `

${state.mode === 'incognito' ? "You're incognito. Nothing you say here is kept." : "Hi, I'm Fusion AI. Ask, code, create or just talk."}

-
${s.map(([k, t]) => ``).join('')}
`; +
${s.map(([k, t]) => ``).join('')}
+ ${state.mode === 'standard' ? '' : ''}`; return; } body.innerHTML = list.map((m, i) => bubble(m, i)).join(''); @@ -286,6 +289,27 @@ export function mount(root, opts = {}) { else if (act === 'speak') speak(list[+b.dataset.i]?.content || ''); else if (act === 'copy-code') navigator.clipboard?.writeText(b.closest('.f8-code').querySelector('pre').textContent); else if (act === 'run') runPython(b); + else if (act === 'watch') watchFilm(); }); - return { open: () => setOpen(true), destroy() { state.ctrl?.abort(); stopVoice(); root.innerHTML = ''; } }; + function watchFilm() { + const box = document.createElement('div'); + box.className = 'f8-film'; + box.setAttribute('role', 'dialog'); + box.setAttribute('aria-label', 'Fusion AI launch film'); + box.innerHTML = `
+
`; + const close = () => { box.querySelector('video')?.pause(); box.remove(); document.removeEventListener('keydown', onKey); }; + const onKey = e => { if (e.key === 'Escape') close(); }; + box.addEventListener('click', e => { if (e.target === box || e.target.closest('.f8-film-x')) close(); }); + document.addEventListener('keydown', onKey); + document.body.appendChild(box); + } + const onExternalOpen = () => { setOpen(true); }; + window.addEventListener('fusion:open', onExternalOpen); + return { + open: () => setOpen(true), + destroy() { state.ctrl?.abort(); stopVoice(); window.removeEventListener('fusion:open', onExternalOpen); root.innerHTML = ''; }, + }; } diff --git a/fusion/launch/fusion-ai-launch.mp4 b/fusion/launch/fusion-ai-launch.mp4 new file mode 100644 index 0000000..ebb5ad4 Binary files /dev/null and b/fusion/launch/fusion-ai-launch.mp4 differ diff --git a/fusion/launch/fusion-ai-launch.vtt b/fusion/launch/fusion-ai-launch.vtt new file mode 100644 index 0000000..5642d0a --- /dev/null +++ b/fusion/launch/fusion-ai-launch.vtt @@ -0,0 +1,45 @@ +WEBVTT + +1 +00:00:01.600 --> 00:00:03.700 +Across three chains… + +2 +00:00:06.100 --> 00:00:08.800 +…one intelligence was always watching. + +3 +00:00:12.200 --> 00:00:13.600 +Ask it anything. + +4 +00:00:14.300 --> 00:00:15.500 +Write code. + +5 +00:00:16.350 --> 00:00:17.900 +Create worlds. + +6 +00:00:19.600 --> 00:00:21.100 +Or just… talk. + +7 +00:00:22.100 --> 00:00:23.900 +And when it’s just you… + +8 +00:00:24.000 --> 00:00:25.300 +…nothing is kept. + +9 +00:00:26.450 --> 00:00:27.700 +Fusion AI. + +10 +00:00:27.950 --> 00:00:29.100 +Powered by HAL 8. + +11 +00:00:29.450 --> 00:00:31.500 +Now live. diff --git a/fusion/launch/poster.jpg b/fusion/launch/poster.jpg new file mode 100644 index 0000000..fd05b0f Binary files /dev/null and b/fusion/launch/poster.jpg differ diff --git a/index.html b/index.html index 70dcc29..30eb50f 100644 --- a/index.html +++ b/index.html @@ -403,7 +403,7 @@ document.addEventListener('DOMContentLoaded', async function() { \ No newline at end of file diff --git a/tools/fusion_inject.py b/tools/fusion_inject.py index 8f2ece7..9bfa700 100644 --- a/tools/fusion_inject.py +++ b/tools/fusion_inject.py @@ -24,6 +24,8 @@ subprocess.run([sys.executable, str(tool), "decode", str(SITE / "index.html"), s page = tmp.read_text("utf-8") VER = hashlib.sha1((SITE / "fusion" / "fusion.js").read_bytes()).hexdigest()[:10] +FILM = SITE / "fusion" / "launch" / "fusion-ai-launch.mp4" +FILM_V = hashlib.sha1(FILM.read_bytes()).hexdigest()[:10] if FILM.exists() else "0" COMPONENT = f"""/*F8-BEGIN*/ // ============================================================================ // FUSION AI — powered by HAL 8 (fusion/fusion.js; the key lives on HAL 8, never here) @@ -39,6 +41,21 @@ function FusionAI() {{ }}, []); return MG("div", {{ ref }}); }} +// the launch film, on the HAL 8 page (fusion/launch/; "Talk to Fusion AI" opens the widget) +function FusionLaunchCard() {{ + const film = "fusion/launch/fusion-ai-launch"; + return MG("div", {{ className: "f8-launch" }}, + MG("div", {{ className: "f8-launch-media" }}, + MG("video", {{ src: film + ".mp4?v={FILM_V}", poster: "fusion/launch/poster.jpg?v={FILM_V}", controls: true, playsInline: true, preload: "none" }}, + MG("track", {{ kind: "captions", src: film + ".vtt?v={FILM_V}", srcLang: "en", label: "English" }}))), + MG("div", {{ className: "f8-launch-copy" }}, + MG("div", {{ className: "f8-launch-kicker" }}, "NEW · FUSION AI"), + MG("h3", null, "Meet Fusion AI. ", MG("span", {{ className: "serif-i" }}, "Powered by HAL 8.")), + MG("p", null, "The assistant on this site. Ask anything, write and run code, make images and short video, or just talk. Standard keeps your chat in this browser; Incognito keeps nothing."), + MG("div", {{ className: "h8-cta" }}, + MG("button", {{ className: "mgx-btn pri", onClick: () => window.dispatchEvent(new CustomEvent("fusion:open")) }}, "Talk to Fusion AI"), + MG("a", {{ className: "mgx-btn", href: film + ".mp4", download: "fusion-ai-launch.mp4" }}, "Download the film")))); +}} /*F8-END*/ """ @@ -54,6 +71,11 @@ else: assert old.count("function ") == 1 and "SYSTEM_PROMPT" in old and "Powered by Claude" in old, "unexpected old block" page = page[:i] + COMPONENT + page[j:] assert "Powered by Claude" not in page and page.count("function FusionAI()") == 1 +# the card sits in the HAL 8 section, right above "Inside the harness" +HOOK = 'MG("div", { className: "mg-subhead" }, "Inside the harness"),' +if "MG(FusionLaunchCard, null)" not in page: + assert page.count(HOOK) == 1, "HAL 8 section hook not found" + page = page.replace(HOOK, "MG(FusionLaunchCard, null),\n " + HOOK) css = (SITE / "fusion" / "fusion.css").read_text("utf-8") page = re.sub(r"\n", "", page, flags=re.S)