Spaces:
Running on Zero
Running on Zero
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>SYSTMS · ACTION</title> | |
| <link rel="icon" type="image/png" href="/assets/favicon.png" /> | |
| <link rel="apple-touch-icon" href="/assets/favicon.png" /> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Archivo:wght@900&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="/styles.css" /> | |
| <link rel="stylesheet" href="/sections.css" /> | |
| <link rel="stylesheet" href="/action.css" /> | |
| <style> | |
| .display { font-family: 'Archivo', 'Helvetica Neue', 'Arial Black', sans-serif; font-weight: 900; font-stretch: condensed; } | |
| .mono { font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; } | |
| /* Nav logo override — force size because base .logo rule is winning otherwise */ | |
| .nav .brand .logo{border:none ;width:auto ;height:28px ;padding:0 ;background:none ;display:flex ;align-items:center ;font-size:0 ;overflow:hidden } | |
| .nav .brand .logo img{height:28px ;max-height:28px ;width:auto ;display:block } | |
| /* Hero H1 — tighten clamp so "// ACTION" doesn't triple-wrap */ | |
| .v-hero.v-action h1{font-size:clamp(64px,9vw,120px) ;letter-spacing:-0.035em } | |
| /* Hero stage shortened so the full-width marquee below it fits in the same viewport */ | |
| .v-hero.v-action .stage{min-height:calc(100vh - 320px) } | |
| /* Left column: eyebrow + h1 stay pinned to top, lead + CTA centered in the space below */ | |
| .v-hero.v-action .left{justify-content:flex-start } | |
| .v-hero.v-action .left > div:last-child{margin-top:auto;margin-bottom:auto} | |
| /* Wrap the hero image in a 5:4 box so the figure-crop corners pin to its actual edges */ | |
| .v-hero.v-action .figure-img-wrap{position:relative;width:100%;max-height:86%;aspect-ratio:1024/816} | |
| .v-hero.v-action .figure-img-wrap .figure-img{position:absolute;inset:0;width:100%;height:100%;max-width:none;max-height:none;object-fit:cover;filter:drop-shadow(0 60px 60px rgba(0,0,0,0.8))} | |
| .v-hero.v-action .figure-img-wrap .figure-crop{position:absolute;z-index:3} | |
| .v-hero.v-action .figure-img-wrap .figure-crop.tl{top:0;left:0} | |
| .v-hero.v-action .figure-img-wrap .figure-crop.tr{top:0;right:0;left:auto} | |
| .v-hero.v-action .figure-img-wrap .figure-crop.bl{bottom:0;left:0;top:auto} | |
| .v-hero.v-action .figure-img-wrap .figure-crop.br{bottom:0;right:0;top:auto;left:auto} | |
| /* Full-width marquee strip — spans the entire page width, sits below the hero */ | |
| .sample-marquee{position:relative;padding:24px 28px 32px;border-top:1px solid var(--line);background:#0A0A0A;z-index:10} | |
| .sample-marquee .sample-head{display:flex;justify-content:space-between;align-items:center;max-width:1600px;margin:0 auto 16px;font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:0.26em;color:var(--muted);text-transform:uppercase} | |
| .sample-marquee .sample-head .count{color:var(--text)} | |
| .sample-marquee .marquee-track-wrap{overflow:hidden;-webkit-mask-image:linear-gradient(90deg,transparent,#000 4%,#000 96%,transparent);mask-image:linear-gradient(90deg,transparent,#000 4%,#000 96%,transparent)} | |
| .sample-marquee .track{display:flex;gap:14px;width:max-content;animation:marquee 55s linear infinite} | |
| .sample-marquee .sample-card{flex:0 0 200px;aspect-ratio:9/16 ;margin:0} | |
| .sample-marquee .sample-card img{object-fit:cover ;filter:none } | |
| .sample-marquee .sample-card .s-meta{font-size:8px ;padding:10px 10px 8px } | |
| .sample-marquee .sample-card .s-tag{font-size:8px ;padding:3px 6px } | |
| .sample-marquee .sample-card{transition:transform .25s ease, border-color .25s ease} | |
| .sample-marquee .sample-card:hover{transform:translateY(-4px);border-color:var(--accent)} | |
| /* Sample lightbox — full-screen viewer on click, backdrop-click or Esc to close */ | |
| .sample-lightbox{padding:40px} | |
| .sample-lightbox .lightbox-inner{width:min(1100px,94vw);max-height:90vh;display:flex;flex-direction:column;background:#0b0b0b;border:1px solid var(--line-2);border-radius:14px;overflow:hidden;box-shadow:0 40px 100px rgba(0,0,0,0.7)} | |
| .sample-lightbox .lightbox-head{display:flex;justify-content:space-between;align-items:center;padding:14px 22px;border-bottom:1px solid var(--line);font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:0.22em;color:var(--muted);text-transform:uppercase} | |
| .sample-lightbox .lightbox-head .lightbox-tag{color:var(--text)} | |
| .sample-lightbox .lightbox-head .close{cursor:pointer;transition:color .18s} | |
| .sample-lightbox .lightbox-head .close:hover{color:var(--accent)} | |
| .sample-lightbox .lightbox-imgwrap{flex:1;min-height:0;display:flex;align-items:center;justify-content:center;background:#000;overflow:hidden} | |
| .sample-lightbox .lightbox-imgwrap img{max-width:100%;max-height:100%;object-fit:contain;display:block} | |
| .sample-lightbox .lightbox-foot{display:flex;justify-content:space-between;align-items:center;padding:14px 22px;border-top:1px solid var(--line);font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:0.22em;color:var(--muted);text-transform:uppercase} | |
| </style> | |
| </head> | |
| <body> | |
| <nav class="nav"> | |
| <div class="brand"> | |
| <div class="logo"><img src="/assets/logo.png" alt="SYSTMS" /></div> | |
| <div class="name">SYSTMS</div> | |
| <div class="sep"></div> | |
| <div class="sub">ACTION</div> | |
| </div> | |
| <div class="right"> | |
| <div class="pill">v1.0 · ACT/BF16</div> | |
| <div class="pill red">● ONLINE</div> | |
| <a id="auth-pill" class="pill" href="/login" style="text-decoration:none;cursor:pointer">SIGN IN ↗</a> | |
| <a class="pill" href="https://www.systms.ai" target="_blank" rel="noopener" style="text-decoration:none;cursor:pointer">SYSTMS.AI ↗</a> | |
| </div> | |
| </nav> | |
| <script> | |
| // Update the auth pill to reflect logged-in state. | |
| fetch('/me', { credentials: 'include' }) | |
| .then(r => r.json()) | |
| .then(data => { | |
| const el = document.getElementById('auth-pill'); | |
| if (!el) return; | |
| if (data.logged_in) { | |
| el.textContent = '● ' + (data.name || 'SIGNED IN').toUpperCase(); | |
| el.removeAttribute('href'); | |
| el.style.cursor = 'default'; | |
| } | |
| }) | |
| .catch(() => {}); | |
| </script> | |
| <div id="root"></div> | |
| <footer class="footer"> | |
| <span>BUILT BY SYSTMS · 2026</span> | |
| <span>HF-SPACES / SYSTMS-ACTION</span> | |
| <span>CC-BY-NC-SA 4.0</span> | |
| </footer> | |
| <!-- React --> | |
| <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script> | |
| <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script> | |
| <script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script> | |
| <script type="module"> | |
| try { | |
| const { Client, handle_file } = await import("https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js"); | |
| window.__gradio = { Client, handle_file }; | |
| console.log("[action] gradio client loaded"); | |
| } catch (e) { | |
| console.error("[action] failed to load @gradio/client:", e); | |
| window.__gradio = { Client: null, handle_file: null, __error: e }; | |
| } | |
| </script> | |
| <script type="text/babel" src="/action-variants.jsx"></script> | |
| <script type="text/babel" src="/action-sections.jsx"></script> | |
| <script type="text/babel" src="/flow.jsx"></script> | |
| <script type="text/babel"> | |
| function App() { | |
| const [file, setFile] = React.useState(null); | |
| const [phase, setPhase] = React.useState('idle'); | |
| const [resultUrl, setResultUrl] = React.useState(null); | |
| const [beforeUrl, setBeforeUrl] = React.useState(null); | |
| // null = unknown (still fetching /me), true/false once we know | |
| const [loggedIn, setLoggedIn] = React.useState(null); | |
| // Anon users get ANON_GEN_LIMIT free gens before being gated to /login. | |
| // Count persisted in localStorage so it survives reloads. | |
| const ANON_GEN_LIMIT = 1; | |
| const [anonGenCount, setAnonGenCount] = React.useState(() => { | |
| try { return parseInt(localStorage.getItem('action_anon_count') || '0', 10) || 0; } catch { return 0; } | |
| }); | |
| React.useEffect(() => { | |
| fetch('/me', { credentials: 'include' }) | |
| .then(r => r.json()) | |
| .then(d => setLoggedIn(!!d.logged_in)) | |
| .catch(() => setLoggedIn(false)); | |
| }, []); | |
| // On mount, check for ?session=<id> in the URL — if a result is already | |
| // stored on the server (user tabbed away during a previous gen and came | |
| // back), jump straight to the result modal. | |
| React.useEffect(() => { | |
| const params = new URLSearchParams(window.location.search); | |
| const sid = params.get('session'); | |
| if (!sid) return; | |
| fetch(`/result/${encodeURIComponent(sid)}?type=output`, { method: 'HEAD' }) | |
| .then(r => { | |
| if (r.ok) { | |
| setBeforeUrl(`/result/${encodeURIComponent(sid)}?type=input`); | |
| setResultUrl(`/result/${encodeURIComponent(sid)}?type=output`); | |
| setPhase('result'); | |
| } else { | |
| clearSessionFromUrl(); | |
| } | |
| }) | |
| .catch(() => clearSessionFromUrl()); | |
| }, []); | |
| const clearSessionFromUrl = () => { | |
| const p = new URLSearchParams(window.location.search); | |
| if (!p.has('session')) return; | |
| p.delete('session'); | |
| const qs = p.toString(); | |
| window.history.replaceState(null, '', window.location.pathname + (qs ? '?' + qs : '')); | |
| }; | |
| const onFile = (f) => { | |
| if (!f) return; | |
| setFile(f); | |
| setBeforeUrl(URL.createObjectURL(f)); | |
| setPhase('processing'); | |
| }; | |
| const onDone = (url) => { | |
| setResultUrl(url); | |
| setPhase('result'); | |
| if (loggedIn === false) { | |
| const next = anonGenCount + 1; | |
| try { localStorage.setItem('action_anon_count', String(next)); } catch {} | |
| setAnonGenCount(next); | |
| } | |
| }; | |
| const onError = () => { | |
| setPhase('idle'); | |
| setFile(null); | |
| clearSessionFromUrl(); | |
| }; | |
| const onClose = () => { | |
| setPhase('idle'); | |
| clearSessionFromUrl(); | |
| }; | |
| const heroImage = '/assets/hero.png'; | |
| return ( | |
| <> | |
| <HeroVariant onFile={onFile} heroImage={heroImage} loggedIn={loggedIn} anonGenCount={anonGenCount} anonGenLimit={ANON_GEN_LIMIT} /> | |
| <SampleStrip /> | |
| <ProcessingModal open={phase === 'processing'} file={file} onDone={onDone} onError={onError} onClose={onClose} /> | |
| <ResultModal open={phase === 'result'} beforeUrl={beforeUrl} afterUrl={resultUrl} onClose={onClose} /> | |
| </> | |
| ); | |
| } | |
| // Render as soon as the JSX globals are defined. __gradio may still be loading — | |
| // that's fine, ProcessingModal only needs it on inference, not render. | |
| const waitForReady = () => { | |
| if (window.HeroVariant && window.ProcessingModal && window.ResultModal && window.FIGURES) { | |
| console.log("[action] rendering"); | |
| ReactDOM.createRoot(document.getElementById('root')).render(<App />); | |
| } else { | |
| setTimeout(waitForReady, 30); | |
| } | |
| }; | |
| waitForReady(); | |
| </script> | |
| </body> | |
| </html> | |