ACTION-HF / static /index.html
systms's picture
Drop anon gen allowance to 1 to protect the ZeroGPU anon pool
7f10272
Raw
History Blame Contribute Delete
11.8 kB
<!DOCTYPE html>
<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 !important;width:auto !important;height:28px !important;padding:0 !important;background:none !important;display:flex !important;align-items:center !important;font-size:0 !important;overflow:hidden !important}
.nav .brand .logo img{height:28px !important;max-height:28px !important;width:auto !important;display:block !important}
/* Hero H1 — tighten clamp so "// ACTION" doesn't triple-wrap */
.v-hero.v-action h1{font-size:clamp(64px,9vw,120px) !important;letter-spacing:-0.035em !important}
/* 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) !important}
/* Left column: eyebrow + h1 stay pinned to top, lead + CTA centered in the space below */
.v-hero.v-action .left{justify-content:flex-start !important}
.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 !important;margin:0}
.sample-marquee .sample-card img{object-fit:cover !important;filter:none !important}
.sample-marquee .sample-card .s-meta{font-size:8px !important;padding:10px 10px 8px !important}
.sample-marquee .sample-card .s-tag{font-size:8px !important;padding:3px 6px !important}
.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>