WenyiWU0111 commited on
Commit
1ac8436
·
verified ·
1 Parent(s): 71e7e75

First screen is the playable game

Browse files
Files changed (4) hide show
  1. index.html +16 -16
  2. play.html +10 -2
  3. static/css/v1.css +26 -35
  4. static/js/v1.js +43 -28
index.html CHANGED
@@ -24,12 +24,26 @@
24
  </div>
25
  </nav>
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  <header class="hero" id="top">
28
  <div class="hero-bg" aria-hidden="true">
29
  <video id="hero-video" muted loop playsinline preload="auto" data-src="hero_evo.mp4" data-poster="hero_evo.webp"></video>
30
  </div>
31
- <div class="hero-inner has-play">
32
- <div class="hero-text">
33
  <p class="eyebrow">Agentic verification · Progressive development</p>
34
  <h1>EvoGame</h1>
35
  <p class="hero-sub">Self-Evolving Game Generation through Agentic Verification and Progressive Development</p>
@@ -40,20 +54,6 @@
40
  <a class="btn" href="#" aria-disabled="true">Code <small>soon</small></a>
41
  <a class="btn" href="#" aria-disabled="true">Dataset <small>soon</small></a>
42
  </div>
43
- </div>
44
- <section class="play-card" id="play" aria-labelledby="play-title" hidden>
45
- <div class="play-top">
46
- <span class="play-tag">Playable</span>
47
- <b id="play-title"></b>
48
- <span class="play-who" id="play-who"></span>
49
- </div>
50
- <div class="play-screen" id="play-screen"></div>
51
- <div class="play-ctl">
52
- <div class="seg" id="play-rounds" role="group" aria-label="Which build to play"></div>
53
- <span class="play-keys" id="play-keys"></span>
54
- <button class="btn btn-sm play-fs" id="play-fs" type="button" hidden>⛶ Fullscreen</button>
55
- </div>
56
- </section>
57
  </div>
58
  </header>
59
 
 
24
  </div>
25
  </nav>
26
 
27
+ <section class="stage" id="stage" aria-label="Playable game" hidden>
28
+ <div class="stage-game" id="stage-game"></div>
29
+ <div class="stage-bar">
30
+ <div class="stage-about">
31
+ <b id="stage-title"></b>
32
+ <span id="stage-who"></span>
33
+ </div>
34
+ <div class="stage-mid">
35
+ <div class="seg" id="stage-rounds" role="group" aria-label="Which build to play"></div>
36
+ <span class="stage-keys" id="stage-keys"></span>
37
+ </div>
38
+ <a class="btn btn-primary stage-down" href="#top">Explore EvoGame ↓</a>
39
+ </div>
40
+ </section>
41
+
42
  <header class="hero" id="top">
43
  <div class="hero-bg" aria-hidden="true">
44
  <video id="hero-video" muted loop playsinline preload="auto" data-src="hero_evo.mp4" data-poster="hero_evo.webp"></video>
45
  </div>
46
+ <div class="hero-inner">
 
47
  <p class="eyebrow">Agentic verification · Progressive development</p>
48
  <h1>EvoGame</h1>
49
  <p class="hero-sub">Self-Evolving Game Generation through Agentic Verification and Progressive Development</p>
 
54
  <a class="btn" href="#" aria-disabled="true">Code <small>soon</small></a>
55
  <a class="btn" href="#" aria-disabled="true">Dataset <small>soon</small></a>
56
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  </div>
58
  </header>
59
 
play.html CHANGED
@@ -12,7 +12,10 @@
12
  <style>
13
  html, body { margin: 0; height: 100%; background: #05060a; overflow: hidden; }
14
  #canvas { display: block; width: 100%; height: 100%; outline: none; }
15
- #boot { position: absolute; inset: 0; display: grid; place-items: center; color: #b4bccd; font: 500 13px/1.5 ui-monospace, "JetBrains Mono", Menlo, monospace; text-align: center; padding: 16px; }
 
 
 
16
  #boot[hidden] { display: none; }
17
  #bar { width: min(320px, 70vw); height: 4px; margin: 10px auto 0; border-radius: 2px; background: #1c2130; overflow: hidden; }
18
  #bar i { display: block; height: 100%; width: 0; background: #c6f432; transition: width .15s; }
@@ -20,7 +23,7 @@
20
  </head>
21
  <body>
22
  <canvas id="canvas" tabindex="0">Your browser does not support the canvas element.</canvas>
23
- <div id="boot"><div><div id="msg">Loading engine…</div><div id="bar"><i></i></div></div></div>
24
  <script>
25
  (() => {
26
  const q = new URLSearchParams(location.search);
@@ -29,6 +32,9 @@
29
  const pckSize = +q.get("pck_size") || 0;
30
  const wasmGz = +q.get("wasm_gz") || 0;
31
  const msg = document.getElementById("msg"), bar = document.querySelector("#bar i");
 
 
 
32
  const mb = (n) => (n / 1048576).toFixed(1);
33
  const show = (text, frac) => { msg.textContent = text; if (frac != null) bar.style.width = `${Math.min(100, frac * 100)}%`; };
34
  const fail = (e) => { console.error(e); show(`Could not start the game: ${e && e.message ? e.message : e}`); };
@@ -88,6 +94,8 @@
88
  };
89
  document.head.append(script);
90
  addEventListener("pointerdown", () => document.getElementById("canvas").focus());
 
 
91
  })();
92
  </script>
93
  </body>
 
12
  <style>
13
  html, body { margin: 0; height: 100%; background: #05060a; overflow: hidden; }
14
  #canvas { display: block; width: 100%; height: 100%; outline: none; }
15
+ #boot { position: absolute; inset: 0; display: grid; place-items: center; color: #b4bccd; font: 500 13px/1.5 ui-monospace, "JetBrains Mono", Menlo, monospace; text-align: center; padding: 16px; background: #05060a center / cover no-repeat; }
16
+ #boot::before { content: ""; position: absolute; inset: 0; background: rgba(5, 6, 10, .6); backdrop-filter: blur(3px); }
17
+ #boot > div { position: relative; }
18
+ #boot h1 { margin: 0 0 6px; font: 700 clamp(22px, 4vw, 40px)/1.1 system-ui, sans-serif; color: #eef1f7; }
19
  #boot[hidden] { display: none; }
20
  #bar { width: min(320px, 70vw); height: 4px; margin: 10px auto 0; border-radius: 2px; background: #1c2130; overflow: hidden; }
21
  #bar i { display: block; height: 100%; width: 0; background: #c6f432; transition: width .15s; }
 
23
  </head>
24
  <body>
25
  <canvas id="canvas" tabindex="0">Your browser does not support the canvas element.</canvas>
26
+ <div id="boot"><div><h1 id="boot-title"></h1><div id="msg">Loading engine…</div><div id="bar"><i></i></div></div></div>
27
  <script>
28
  (() => {
29
  const q = new URLSearchParams(location.search);
 
32
  const pckSize = +q.get("pck_size") || 0;
33
  const wasmGz = +q.get("wasm_gz") || 0;
34
  const msg = document.getElementById("msg"), bar = document.querySelector("#bar i");
35
+ document.getElementById("boot-title").textContent = q.get("title") || "";
36
+ if (/^(https?:|\/|\.)/.test(q.get("poster") || "") || /^[\w/.-]+$/.test(q.get("poster") || ""))
37
+ document.getElementById("boot").style.backgroundImage = `url("${encodeURI(q.get("poster"))}")`;
38
  const mb = (n) => (n / 1048576).toFixed(1);
39
  const show = (text, frac) => { msg.textContent = text; if (frac != null) bar.style.width = `${Math.min(100, frac * 100)}%`; };
40
  const fail = (e) => { console.error(e); show(`Could not start the game: ${e && e.message ? e.message : e}`); };
 
94
  };
95
  document.head.append(script);
96
  addEventListener("pointerdown", () => document.getElementById("canvas").focus());
97
+ // the game fills the embedding page's first screen: let the mouse wheel keep scrolling that page
98
+ if (parent !== window) addEventListener("wheel", (e) => { try { parent.scrollBy(0, e.deltaY); } catch { /* cross-origin parent */ } }, { passive: true, capture: true });
99
  })();
100
  </script>
101
  </body>
static/css/v1.css CHANGED
@@ -82,38 +82,29 @@
82
  .modal.simple .m-prompt, .modal.simple .m-h, .modal.simple #m-reqhint, .modal.simple #m-reqs, .modal.simple #m-evo { display: none; }
83
  @media (max-width: 760px) { .modal.simple .modal-info { grid-template-columns: 1fr; } .modal.simple .m-score { grid-column: 1; grid-row: auto; } }
84
 
85
- /* ---------- playable hero ---------- */
86
- .hero-inner.has-play { display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: clamp(28px, 4vw, 56px); align-items: center; max-width: 1320px; width: 100%; text-align: left; }
87
- .has-play .hero-text { min-width: 0; }
88
- .has-play h1 { font-size: clamp(56px, 8vw, 112px); }
89
- .has-play .hero-sub { margin-inline: 0; }
90
- .has-play .hero-cta { justify-content: flex-start; }
91
- .play-card { background: rgba(10, 12, 18, .88); border: 1px solid var(--hover-border); border-radius: 18px; padding: 12px; box-shadow: 0 30px 80px rgba(0,0,0,.55); backdrop-filter: blur(10px); display: grid; gap: 10px; min-width: 0; }
92
- .play-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 2px 4px 0; }
93
- .play-top b { font: 700 17px var(--display); }
94
- .play-tag { font: 700 10px var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--accent-ink); background: var(--accent); padding: 3px 7px; border-radius: 5px; }
95
- .play-who { color: var(--muted); font-size: 13px; display: inline-flex; align-items: center; gap: 6px; }
96
- .play-screen { position: relative; aspect-ratio: 16 / 9; max-width: 100%; border-radius: 10px; overflow: hidden; background: var(--media-bg); }
97
- .play-screen iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }
98
- .play-poster { position: absolute; inset: 0; width: 100%; height: 100%; padding: 0; border: 0; cursor: pointer; background: var(--media-bg); display: grid; place-items: center; color: var(--text); }
99
- .play-poster img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; filter: brightness(.55); transition: filter .2s, transform .4s; }
100
- .play-poster:hover img, .play-poster:focus-visible img { filter: brightness(.7); transform: scale(1.02); }
101
- .play-poster:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
102
- .play-go { position: relative; display: grid; justify-items: center; gap: 10px; }
103
- .play-go i { width: 76px; height: 76px; border-radius: 50%; background: var(--accent); display: grid; place-items: center; box-shadow: 0 0 0 10px rgba(198,244,50,.18); transition: transform .2s; }
104
- .play-go i::before { content: ""; margin-left: 6px; border-left: 22px solid var(--accent-ink); border-top: 14px solid transparent; border-bottom: 14px solid transparent; }
105
- .play-poster:hover .play-go i { transform: scale(1.06); }
106
- .play-go b { font: 700 18px var(--display); text-shadow: 0 2px 12px rgba(0,0,0,.7); }
107
- .play-go span { font: 500 12px var(--mono); color: var(--text-2); text-shadow: 0 1px 8px rgba(0,0,0,.8); }
108
- .play-ctl { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
109
- .play-ctl .seg button { display: inline-flex; gap: 8px; align-items: baseline; }
110
- .play-ctl .seg button b { font: 700 12px var(--mono); color: var(--muted); }
111
- .play-ctl .seg button.on b { color: var(--accent); }
112
- .play-keys { font: 500 12px var(--mono); color: var(--muted); }
113
- .play-fs { margin-left: auto; }
114
- @media (max-width: 1020px) {
115
- .hero-inner.has-play { grid-template-columns: 1fr; text-align: center; max-width: 820px; }
116
- .has-play .hero-sub { margin-inline: auto; }
117
- .has-play .hero-cta { justify-content: center; }
118
- .play-card { text-align: left; }
119
- }
 
82
  .modal.simple .m-prompt, .modal.simple .m-h, .modal.simple #m-reqhint, .modal.simple #m-reqs, .modal.simple #m-evo { display: none; }
83
  @media (max-width: 760px) { .modal.simple .modal-info { grid-template-columns: 1fr; } .modal.simple .m-score { grid-column: 1; grid-row: auto; } }
84
 
85
+ /* ---------- the first screen is a game ---------- */
86
+ .stage { position: relative; height: 100vh; height: 100svh; display: grid; grid-template-rows: minmax(0, 1fr) auto; background: var(--media-bg); }
87
+ .stage-game { position: relative; min-height: 0; overflow: hidden; }
88
+ .stage-game iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }
89
+ .stage-cover { position: absolute; inset: 0; display: grid; place-items: center; text-align: center; padding: 24px; }
90
+ .stage-cover img, .stage-cover video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
91
+ .stage-cover img { filter: brightness(.45) blur(2px); transform: scale(1.03); }
92
+ .stage-cover > div { position: relative; display: grid; gap: 12px; justify-items: center; max-width: 560px; }
93
+ .stage-cover h2 { font-size: clamp(28px, 5vw, 48px); text-wrap: balance; }
94
+ .stage-cover p { margin: 0; color: var(--text-2); }
95
+ .stage-wipe { position: absolute; inset: 0; border: 0; border-radius: 0; aspect-ratio: auto; }
96
+ .stage-hint { position: absolute; left: 50%; top: 16px; transform: translateX(-50%); z-index: 2; pointer-events: none; font: 600 13px var(--mono); color: var(--accent-ink); background: var(--accent); padding: 6px 12px; border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.4); transition: opacity .4s; white-space: nowrap; }
97
+ .stage-hint.gone { opacity: 0; }
98
+ .stage-bar { display: flex; align-items: center; gap: 12px 20px; flex-wrap: wrap; padding: 10px var(--gutter); background: var(--bg); border-top: 1px solid var(--border); }
99
+ .stage-about { display: grid; gap: 1px; min-width: 0; }
100
+ .stage-about b { font: 700 16px var(--display); }
101
+ .stage-about span { color: var(--muted); font-size: 13px; display: inline-flex; align-items: center; gap: 6px; }
102
+ .stage-mid { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-inline: auto; }
103
+ .stage-mid .seg button { display: inline-flex; gap: 8px; align-items: baseline; }
104
+ .stage-mid .seg button b { font: 700 12px var(--mono); color: var(--muted); }
105
+ .stage-mid .seg button.on b { color: var(--accent); }
106
+ .stage-keys { font: 500 12px var(--mono); color: var(--muted); }
107
+ body.on-stage .nav { transform: translateY(-100%); }
108
+ .nav { transition: transform .25s, border-color .2s, background .2s; }
109
+ @media (max-width: 720px) { .stage-mid { margin-inline: 0; } .stage-down { margin-left: auto; } .stage-keys { display: none; } }
110
+ .btn-reset { border: 0; padding: 24px; margin: 0; width: 100%; height: 100%; cursor: pointer; color: var(--text); font: inherit; background: none; }
 
 
 
 
 
 
 
 
 
static/js/v1.js CHANGED
@@ -12,41 +12,39 @@ Evo.boot(({ games, arms }) => {
12
  const hv = $("#hero-video");
13
  Evo.mountHeroVideo(hv, { src: hv.dataset.src, poster: hv.dataset.poster });
14
 
15
- // ---------- playable hero: the real round-0 and round-30 builds, in the browser ----------
16
- (async function playable() {
17
  const cfg = window.EVO_PLAY || await fetch(Evo.data("play.json")).then((r) => (r.ok ? r.json() : null)).catch(() => null);
18
  const game = cfg?.games?.[0];
 
19
  if (!game) return;
20
- const screen = $("#play-screen"), rounds = $("#play-rounds"), fsBtn = $("#play-fs");
21
  const ROUNDS = [["r0", "Round 0"], ["r30", "Round 30"]];
22
  const label = (r) => ROUNDS.find(([k]) => k === r)[1];
23
- let sel = "r30", running = false;
24
- $("#play-title").textContent = game.title;
25
- $("#play-who").replaceChildren(dot(game.model), `${game.model} · Godot 4`);
26
- $("#play-keys").textContent = game.controls;
 
27
 
28
- function idle() {
29
- running = false;
30
- fsBtn.hidden = true;
31
- const mbs = Math.round((cfg.engine.wasm_gz + game.builds[sel].size) / 1048576);
32
- const poster = h("button", { class: "play-poster", type: "button", "aria-label": `Play ${game.title}, ${label(sel)}` },
33
- h("img", { src: Evo.media(`${game.pair}/${sel === "r0" ? "p0" : "final"}.webp`), alt: "" }),
34
- h("span", { class: "play-go" }, h("i"), h("b", {}, `Play ${label(sel)} in your browser`), h("span", {}, `${mbs} MB download · keyboard`)));
35
- poster.addEventListener("click", start);
36
- screen.replaceChildren(poster);
37
- }
38
- function start() {
39
- running = true;
40
- Evo.pauseAll();
41
- $("#hero-video").pause();
42
  const b = game.builds[sel];
43
  const src = "play.html?" + new URLSearchParams({
44
  engine: Evo.media(cfg.engine.base), wasm_gz: cfg.engine.wasm_gz, pck: Evo.media(b.pck), pck_size: b.size,
 
45
  });
46
- const frame = h("iframe", { src, title: `${game.title}, ${label(sel)}`, allow: "autoplay; fullscreen; gamepad" });
 
47
  frame.addEventListener("load", () => frame.contentWindow?.focus());
48
- screen.replaceChildren(frame);
49
- fsBtn.hidden = false;
 
 
 
 
 
 
 
50
  }
51
  rounds.replaceChildren(...ROUNDS.map(([r, t]) => h("button", {
52
  type: "button", class: r === sel ? "on" : "", "data-r": r,
@@ -54,12 +52,29 @@ Evo.boot(({ games, arms }) => {
54
  if (sel === r) return;
55
  sel = r;
56
  rounds.querySelectorAll("button").forEach((x) => x.classList.toggle("on", x.dataset.r === r));
57
- running ? start() : idle();
58
  },
59
  }, t, h("b", {}, s100(game.scores[r])))));
60
- fsBtn.addEventListener("click", () => screen.querySelector("iframe")?.requestFullscreen?.());
61
- idle();
62
- $("#play").hidden = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  })();
64
 
65
  // ---------- headline: one row per generator ----------
 
12
  const hv = $("#hero-video");
13
  Evo.mountHeroVideo(hv, { src: hv.dataset.src, poster: hv.dataset.poster });
14
 
15
+ // ---------- first screen: a real game, already running ----------
16
+ (async function stage() {
17
  const cfg = window.EVO_PLAY || await fetch(Evo.data("play.json")).then((r) => (r.ok ? r.json() : null)).catch(() => null);
18
  const game = cfg?.games?.[0];
19
+ const el = $("#stage");
20
  if (!game) return;
21
+ const box = $("#stage-game"), rounds = $("#stage-rounds");
22
  const ROUNDS = [["r0", "Round 0"], ["r30", "Round 30"]];
23
  const label = (r) => ROUNDS.find(([k]) => k === r)[1];
24
+ const canPlay = matchMedia("(pointer: fine)").matches && !navigator.connection?.saveData;
25
+ let sel = "r30", frame = null;
26
+ $("#stage-title").textContent = canPlay ? `You're playing ${game.title}` : `${game.title}: round 0 → round 30`;
27
+ $("#stage-who").replaceChildren(dot(game.model), `made by ${game.model}, then improved over 30 EvoGame rounds`);
28
+ $("#stage-keys").textContent = canPlay ? game.controls : "";
29
 
30
+ function run() {
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  const b = game.builds[sel];
32
  const src = "play.html?" + new URLSearchParams({
33
  engine: Evo.media(cfg.engine.base), wasm_gz: cfg.engine.wasm_gz, pck: Evo.media(b.pck), pck_size: b.size,
34
+ title: `${game.title} · ${label(sel)}`, poster: Evo.media(`${game.pair}/${sel === "r0" ? "p0" : "final"}.webp`),
35
  });
36
+ frame = h("iframe", { src, title: `${game.title}, ${label(sel)}`, allow: "autoplay; fullscreen; gamepad" });
37
+ const hint = h("div", { class: "stage-hint" }, `Live game · ${game.controls}`);
38
  frame.addEventListener("load", () => frame.contentWindow?.focus());
39
+ box.replaceChildren(frame, hint);
40
+ const dismiss = () => hint.classList.add("gone");
41
+ setTimeout(dismiss, 6000);
42
+ frame.addEventListener("load", () => frame.contentWindow?.addEventListener("keydown", dismiss, { once: true }));
43
+ }
44
+ function watch() { // touch screens and data saver: the same two builds, replayed
45
+ const wipe = Evo.wipeFrame(game.pair, { alt: game.title, eager: true, cls: "wipe stage-wipe", after: "Round 30" });
46
+ box.replaceChildren(wipe);
47
+ Evo.observe(wipe);
48
  }
49
  rounds.replaceChildren(...ROUNDS.map(([r, t]) => h("button", {
50
  type: "button", class: r === sel ? "on" : "", "data-r": r,
 
52
  if (sel === r) return;
53
  sel = r;
54
  rounds.querySelectorAll("button").forEach((x) => x.classList.toggle("on", x.dataset.r === r));
55
+ if (canPlay) run();
56
  },
57
  }, t, h("b", {}, s100(game.scores[r])))));
58
+ if (!canPlay) rounds.hidden = true;
59
+ el.hidden = false;
60
+ canPlay ? run() : watch();
61
+
62
+ // hide the site nav while the game has the screen; free the game once it is far out of view
63
+ new IntersectionObserver(([e]) => {
64
+ document.body.classList.toggle("on-stage", e.intersectionRatio > 0.6);
65
+ if (e.intersectionRatio > 0.2) $("#hero-video").pause();
66
+ }, { threshold: [0, 0.2, 0.6, 1] }).observe(el);
67
+ new IntersectionObserver(([e]) => {
68
+ if (!canPlay) return;
69
+ if (!e.isIntersecting && frame) {
70
+ frame = null;
71
+ const again = h("button", { class: "stage-cover btn-reset", type: "button" },
72
+ h("img", { src: Evo.media(`${game.pair}/${sel === "r0" ? "p0" : "final"}.webp`), alt: "" }),
73
+ h("div", {}, h("h2", {}, `${game.title} is paused`), h("span", { class: "btn btn-primary" }, "Play again")));
74
+ again.addEventListener("click", run);
75
+ box.replaceChildren(again);
76
+ }
77
+ }, { rootMargin: "300px 0px" }).observe(el);
78
  })();
79
 
80
  // ---------- headline: one row per generator ----------