polats commited on
Commit
2026366
·
verified ·
1 Parent(s): 76d7f3c

Deploy Karate Wiener (kimodo kata maker)

Browse files
.gitattributes CHANGED
@@ -99,3 +99,4 @@ stance_library_verify.png filter=lfs diff=lfs merge=lfs -text
99
  assets/default_scene.webp filter=lfs diff=lfs merge=lfs -text
100
  assets/citizen_skin_color.png filter=lfs diff=lfs merge=lfs -text
101
  comic_sheet_v0.png filter=lfs diff=lfs merge=lfs -text
 
 
99
  assets/default_scene.webp filter=lfs diff=lfs merge=lfs -text
100
  assets/citizen_skin_color.png filter=lfs diff=lfs merge=lfs -text
101
  comic_sheet_v0.png filter=lfs diff=lfs merge=lfs -text
102
+ comic_koma_sample.png filter=lfs diff=lfs merge=lfs -text
app.py CHANGED
@@ -3591,12 +3591,17 @@ def improve_dojo_prompt(payload_json: str | dict | None) -> str:
3591
  )
3592
  user = (f"Turn these ideas into a dojo scene prompt: {words}" if words
3593
  else "Invent a creative, unexpected dojo scene prompt.")
 
 
 
 
 
3594
  if not TINY_AYA_SPACE:
3595
  return json.dumps({"ok": False, "error": "tiny-aya space not configured"})
3596
  try:
3597
  from gradio_client import Client
3598
  client = Client(TINY_AYA_SPACE, token=_hf_token())
3599
- out = client.predict(system, user, 220, 0.9, api_name="/generate")
3600
  text = (out or "").strip()
3601
  # Prefer the first substantial line (drops any stray preamble the model adds).
3602
  best = ""
 
3591
  )
3592
  user = (f"Turn these ideas into a dojo scene prompt: {words}" if words
3593
  else "Invent a creative, unexpected dojo scene prompt.")
3594
+ # A fresh nonce + high temperature make every suggestion / reroll different even
3595
+ # for identical input. The model is told not to echo it.
3596
+ import random
3597
+ nonce = random.randint(1, 1_000_000)
3598
+ user += f"\n\nGive a fresh, different idea than before (random key {nonce}; never mention this key)."
3599
  if not TINY_AYA_SPACE:
3600
  return json.dumps({"ok": False, "error": "tiny-aya space not configured"})
3601
  try:
3602
  from gradio_client import Client
3603
  client = Client(TINY_AYA_SPACE, token=_hf_token())
3604
+ out = client.predict(system, user, 220, 1.05, api_name="/generate")
3605
  text = (out or "").strip()
3606
  # Prefer the first substantial line (drops any stray preamble the model adds).
3607
  best = ""
comic_compose.py CHANGED
@@ -48,20 +48,20 @@ def render_panel(Pf, shot, W=PANEL_W, H=PANEL_H, speed_lines=False):
48
  center = (Pf.min(axis=0) + Pf.max(axis=0)) / 2.0
49
  for a, b in ground_grid(center):
50
  (p2, z) = project(np.array([a, b]), cam, W, H)
51
- alpha = float(np.clip(0.30 - 0.035 * z.mean(), 0.05, 0.30))
52
- ax.plot(p2[:, 0], p2[:, 1], color=GRID_COL, lw=1.0, alpha=alpha, zorder=1)
53
 
54
  # speed lines (gag/action): radial strokes from the panel edge toward the figure
55
  if speed_lines:
56
  j2, _ = project(Pf, cam, W, H)
57
  cx, cy = j2.mean(axis=0)
58
  rng = np.random.default_rng(7) # fixed seed: deterministic art
59
- for ang in np.linspace(0, 2 * np.pi, 26, endpoint=False):
60
- r0 = max(W, H) * 0.75
61
- r1 = r0 * rng.uniform(0.55, 0.72)
62
  x0, y0 = cx + r0 * np.cos(ang), cy + r0 * np.sin(ang)
63
  x1, y1 = cx + r1 * np.cos(ang), cy + r1 * np.sin(ang)
64
- ax.plot([x0, x1], [y0, y1], color=INK, lw=1.4, alpha=0.22, zorder=2)
65
 
66
  j2, jz = project(Pf, cam, W, H)
67
  # px-per-meter near the head, for sizing the head circle and end caps
@@ -171,8 +171,11 @@ def _sticker_slot(draw, panel_box, expression):
171
  s = 108
172
  bx, by = x0 + 12, y1 - 12 - s
173
  draw.rounded_rectangle([bx, by, bx + s, by + s], radius=12, outline="#bba76f", width=2)
174
- f = _font(40)
175
- draw.text((bx + s / 2, by + s / 2 - 12), "🌭", font=f, anchor="mm")
 
 
 
176
  draw.text((bx + s / 2, by + s - 16), expression, font=_font(15), fill="#bba76f", anchor="mm")
177
 
178
 
 
48
  center = (Pf.min(axis=0) + Pf.max(axis=0)) / 2.0
49
  for a, b in ground_grid(center):
50
  (p2, z) = project(np.array([a, b]), cam, W, H)
51
+ alpha = float(np.clip(0.55 - 0.05 * z.mean(), 0.12, 0.55))
52
+ ax.plot(p2[:, 0], p2[:, 1], color=GRID_COL, lw=1.2, alpha=alpha, zorder=1)
53
 
54
  # speed lines (gag/action): radial strokes from the panel edge toward the figure
55
  if speed_lines:
56
  j2, _ = project(Pf, cam, W, H)
57
  cx, cy = j2.mean(axis=0)
58
  rng = np.random.default_rng(7) # fixed seed: deterministic art
59
+ for ang in np.linspace(0, 2 * np.pi, 30, endpoint=False):
60
+ r0 = max(W, H) * 0.80
61
+ r1 = r0 * rng.uniform(0.50, 0.66)
62
  x0, y0 = cx + r0 * np.cos(ang), cy + r0 * np.sin(ang)
63
  x1, y1 = cx + r1 * np.cos(ang), cy + r1 * np.sin(ang)
64
+ ax.plot([x0, x1], [y0, y1], color=INK, lw=2.0, alpha=0.40, zorder=2)
65
 
66
  j2, jz = project(Pf, cam, W, H)
67
  # px-per-meter near the head, for sizing the head circle and end caps
 
171
  s = 108
172
  bx, by = x0 + 12, y1 - 12 - s
173
  draw.rounded_rectangle([bx, by, bx + s, by + s], radius=12, outline="#bba76f", width=2)
174
+ try:
175
+ emoji = ImageFont.truetype("C:/Windows/Fonts/seguiemj.ttf", 44)
176
+ draw.text((bx + s / 2, by + s / 2 - 12), "🌭", font=emoji, anchor="mm", embedded_color=True)
177
+ except OSError:
178
+ draw.text((bx + s / 2, by + s / 2 - 12), "KW", font=_font(34, bold=True), fill="#bba76f", anchor="mm")
179
  draw.text((bx + s / 2, by + s - 16), expression, font=_font(15), fill="#bba76f", anchor="mm")
180
 
181
 
comic_koma_sample.png ADDED

Git LFS Details

  • SHA256: 688be648d6c021ea8e7d45f41f8007470dcf89ba3962b2035f970aec33eec26e
  • Pointer size: 131 Bytes
  • Size of remote file: 365 kB
comic_sheet_v0.png CHANGED

Git LFS Details

  • SHA256: f060ef75bef0590346160dfeb9f1aa942ce36d486f08c6b00f027aeef39a9791
  • Pointer size: 132 Bytes
  • Size of remote file: 1.04 MB

Git LFS Details

  • SHA256: dbcd2a64232569c07d3f6e2767f7d6cdbceff0f73b4b5561dec417ce7f55b565
  • Pointer size: 132 Bytes
  • Size of remote file: 1.18 MB
comic_shots.py CHANGED
@@ -15,10 +15,10 @@ RIGHTJ = {2, 5, 8, 11, 14, 17, 19, 21}
15
  SHOTS = {
16
  # aim_up biases the look-at above the bbox center so the figure sits LOW in
17
  # the frame — the upper strip belongs to the speech bubble.
18
- "establishing": dict(az=38.0, el=10.0, margin=1.16, roll=0.0, fov=40.0, aim_up=0.22),
19
- "action": dict(az=55.0, el=4.0, margin=1.02, roll=-4.0, fov=46.0, aim_up=0.16),
20
  "gag": dict(az=22.0, el=16.0, margin=0.98, roll=13.0, fov=50.0, aim_up=0.10),
21
- "hero": dict(az=32.0, el=-16.0, margin=1.04, roll=0.0, fov=38.0, aim_up=0.14),
22
  }
23
 
24
 
@@ -43,7 +43,9 @@ def make_camera(shot_name, Pf):
43
  eye = target + direction * dist
44
  # Don't let the camera dip below the floor (hero shots on grounded poses).
45
  eye[1] = max(eye[1], 0.12)
46
- return dict(eye=eye, target=target, fov=fov, roll=np.radians(s["roll"]))
 
 
47
 
48
 
49
  def project(points, cam, W, H):
 
15
  SHOTS = {
16
  # aim_up biases the look-at above the bbox center so the figure sits LOW in
17
  # the frame — the upper strip belongs to the speech bubble.
18
+ "establishing": dict(az=38.0, el=10.0, margin=1.06, roll=0.0, fov=40.0, aim_up=0.18),
19
+ "action": dict(az=55.0, el=4.0, margin=1.08, roll=-4.0, fov=46.0, aim_up=0.18),
20
  "gag": dict(az=22.0, el=16.0, margin=0.98, roll=13.0, fov=50.0, aim_up=0.10),
21
+ "hero": dict(az=32.0, el=-16.0, margin=1.08, roll=0.0, fov=38.0, aim_up=0.16),
22
  }
23
 
24
 
 
43
  eye = target + direction * dist
44
  # Don't let the camera dip below the floor (hero shots on grounded poses).
45
  eye[1] = max(eye[1], 0.12)
46
+ aim = target.copy()
47
+ aim[1] += s.get("aim_up", 0.0) * radius
48
+ return dict(eye=eye, target=aim, fov=fov, roll=np.radians(s["roll"]))
49
 
50
 
51
  def project(points, cam, W, H):
tabs/dojo.drawer.html CHANGED
@@ -1,9 +1,8 @@
1
  <aside id="kimodo-dojo-drawer" class="kimodo-drawer" aria-label="Dojo drawer">
2
- <h2>Dojo</h2>
3
 
4
  <!-- ============ LIBRARY VIEW: list of dojos (Mine / Community) ============ -->
5
  <div id="kimodo-dojo-library" class="kimodo-dojo-view">
6
- <div class="kimodo-cz-seg kimodo-cz-seg--sub" id="kimodo-dojo-scope" aria-label="Dojo scope">
7
  <button type="button" class="kimodo-cz-segbtn active" data-scope="mine">Mine</button>
8
  <button type="button" class="kimodo-cz-segbtn" data-scope="community">Community</button>
9
  </div>
@@ -14,14 +13,34 @@
14
 
15
  <!-- ============ CREATE VIEW: prompt + suggest + generate + progress ======= -->
16
  <div id="kimodo-dojo-create" class="kimodo-dojo-view" hidden>
17
- <button id="kimodo-dojo-back" class="kimodo-dojo-back" type="button" title="Back to dojos">‹ Dojos</button>
18
- <div class="kimodo-drawer-sub">Describe a room scene for the viewer.</div>
19
 
20
- <label class="kimodo-dojo-label" for="kimodo-dojo-prompt">Scene prompt</label>
 
 
 
21
  <textarea id="kimodo-dojo-prompt" class="kimodo-dojo-prompt" rows="5"
22
- spellcheck="true" placeholder="A few words, or leave blank and tap Suggest…">A traditional martial arts dojo room, polished wooden floor, training mats, wooden wall panels, warm lantern light, full room visible, high detail, no people, no text.</textarea>
23
  <button id="kimodo-dojo-improve" class="kimodo-dojo-improve" type="button"
24
- title="Improve the prompt with tiny-aya (or suggest one if empty)"> Improve with tiny-aya</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  <details class="kimodo-dojo-adv">
27
  <summary>Advanced settings</summary>
@@ -50,24 +69,5 @@
50
  </div>
51
  </div>
52
  </details>
53
-
54
- <div class="kimodo-dojo-actions">
55
- <button id="kimodo-dojo-generate" class="kimodo-dojo-primary" type="button">Generate Scene</button>
56
- <button id="kimodo-dojo-clear" type="button">Clear</button>
57
- </div>
58
-
59
- <div id="kimodo-dojo-progress" class="kimodo-dojo-progress" aria-label="Dojo generation progress">
60
- <div class="kimodo-dojo-step" data-step="flux-iso"><span></span>Flux isometric</div>
61
- <div class="kimodo-dojo-step" data-step="flux-pano"><span></span>Flux 360</div>
62
- <div class="kimodo-dojo-step" data-step="splat"><span></span>TripoSplat</div>
63
- <div class="kimodo-dojo-step" data-step="viewer"><span></span>Viewer load</div>
64
- </div>
65
- <div id="kimodo-dojo-time" class="kimodo-dojo-time" aria-live="polite"></div>
66
- <div id="kimodo-dojo-detail" class="kimodo-dojo-detail" aria-live="polite"></div>
67
- <div id="kimodo-dojo-preview-wrap" class="kimodo-dojo-preview-wrap">
68
- <figure><figcaption>Isometric</figcaption><img id="kimodo-dojo-preview" alt="Flux isometric result" /></figure>
69
- <figure><figcaption>360</figcaption><img id="kimodo-dojo-preview-pano" alt="Flux 360 result" /></figure>
70
- </div>
71
- <div id="kimodo-dojo-status" class="kimodo-drawer-sub kimodo-dojo-status">Ready.</div>
72
  </div>
73
  </aside>
 
1
  <aside id="kimodo-dojo-drawer" class="kimodo-drawer" aria-label="Dojo drawer">
 
2
 
3
  <!-- ============ LIBRARY VIEW: list of dojos (Mine / Community) ============ -->
4
  <div id="kimodo-dojo-library" class="kimodo-dojo-view">
5
+ <div class="kimodo-cz-seg kimodo-cz-seg--sub kimodo-dojo-seg" id="kimodo-dojo-scope" aria-label="Dojo scope">
6
  <button type="button" class="kimodo-cz-segbtn active" data-scope="mine">Mine</button>
7
  <button type="button" class="kimodo-cz-segbtn" data-scope="community">Community</button>
8
  </div>
 
13
 
14
  <!-- ============ CREATE VIEW: prompt + suggest + generate + progress ======= -->
15
  <div id="kimodo-dojo-create" class="kimodo-dojo-view" hidden>
16
+ <button id="kimodo-dojo-back" class="kimodo-dojo-back" type="button" title="Back to dojos">‹</button>
 
17
 
18
+ <div class="kimodo-dojo-promptrow">
19
+ <label class="kimodo-dojo-label" for="kimodo-dojo-prompt">Describe the dojo</label>
20
+ <button id="kimodo-dojo-erase" class="kimodo-dojo-erase" type="button" title="Clear the prompt" aria-label="Clear the prompt">⌫</button>
21
+ </div>
22
  <textarea id="kimodo-dojo-prompt" class="kimodo-dojo-prompt" rows="5"
23
+ spellcheck="true" placeholder="a random dojo"></textarea>
24
  <button id="kimodo-dojo-improve" class="kimodo-dojo-improve" type="button"
25
+ title="Let tiny-aya write or reroll the prompt">let tiny-aya decide 🎲</button>
26
+
27
+ <div class="kimodo-dojo-actions">
28
+ <button id="kimodo-dojo-generate" class="kimodo-dojo-primary" type="button">Generate Scene</button>
29
+ </div>
30
+
31
+ <div id="kimodo-dojo-progress" class="kimodo-dojo-progress" aria-label="Dojo generation progress">
32
+ <div class="kimodo-dojo-step" data-step="flux-iso"><span></span>Flux isometric</div>
33
+ <div class="kimodo-dojo-step" data-step="flux-pano"><span></span>Flux 360</div>
34
+ <div class="kimodo-dojo-step" data-step="splat"><span></span>TripoSplat</div>
35
+ <div class="kimodo-dojo-step" data-step="viewer"><span></span>Viewer load</div>
36
+ </div>
37
+ <div id="kimodo-dojo-time" class="kimodo-dojo-time" aria-live="polite"></div>
38
+ <div id="kimodo-dojo-detail" class="kimodo-dojo-detail" aria-live="polite"></div>
39
+ <div id="kimodo-dojo-preview-wrap" class="kimodo-dojo-preview-wrap">
40
+ <figure><figcaption>Isometric</figcaption><img id="kimodo-dojo-preview" alt="Flux isometric result" /></figure>
41
+ <figure><figcaption>360</figcaption><img id="kimodo-dojo-preview-pano" alt="Flux 360 result" /></figure>
42
+ </div>
43
+ <div id="kimodo-dojo-status" class="kimodo-drawer-sub kimodo-dojo-status">Ready.</div>
44
 
45
  <details class="kimodo-dojo-adv">
46
  <summary>Advanced settings</summary>
 
69
  </div>
70
  </div>
71
  </details>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  </div>
73
  </aside>
tabs/dojo.tab.css CHANGED
@@ -229,17 +229,25 @@
229
 
230
  /* ============ Library / create restructure ============ */
231
  .kimodo-dojo-view[hidden] { display: none; }
 
 
 
 
 
 
 
232
  .kimodo-dojo-addbtn {
 
233
  width: 100%;
234
- padding: 9px;
235
  margin-bottom: 10px;
236
- font-size: 12.5px;
237
  font-weight: 600;
238
  cursor: pointer;
239
- border-radius: 8px;
240
  background: #4a3320 !important;
241
  color: #ffe0ad !important;
242
- border: 1px solid #b97736 !important;
243
  box-shadow: none !important;
244
  }
245
  .kimodo-dojo-addbtn:hover { background: #5a3f27 !important; }
@@ -315,30 +323,53 @@
315
  text-align: center;
316
  color: #87909b;
317
  }
 
318
  .kimodo-dojo-back {
319
- padding: 4px 2px;
320
- margin-bottom: 4px;
321
- font-size: 13px;
322
- cursor: pointer;
323
  color: #ffd28a !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  background: none !important;
325
  border: 0 !important;
326
  box-shadow: none !important;
327
  }
 
 
328
  .kimodo-dojo-improve {
329
  width: 100%;
330
  margin: 7px 0 4px;
331
- padding: 7px;
332
- font-size: 11.5px;
333
  font-weight: 600;
334
  cursor: pointer;
335
  border-radius: 7px;
336
- background: #2a2440 !important;
337
- color: #cbb6ff !important;
338
- border: 1px solid #4a3a6a !important;
339
  box-shadow: none !important;
340
  }
341
- .kimodo-dojo-improve:hover:not(:disabled) { background: #342c52 !important; }
342
  .kimodo-dojo-improve:disabled { opacity: .55; cursor: default; }
343
  .kimodo-dojo-adv {
344
  margin: 8px 0;
 
229
 
230
  /* ============ Library / create restructure ============ */
231
  .kimodo-dojo-view[hidden] { display: none; }
232
+
233
+ /* Mine|Community pill — dojo (amber) palette, NOT the purple My Karate one. */
234
+ .kimodo-dojo-seg { background: #201a16; border-color: #4a3320; }
235
+ .kimodo-dojo-seg .kimodo-cz-segbtn { color: #b9905a !important; }
236
+ .kimodo-dojo-seg .kimodo-cz-segbtn.active { background: #4a3320 !important; color: #ffe0ad !important; }
237
+
238
+ /* "+ Add dojo" — same dashed-outline style as "+ Add a stance", dojo colors. */
239
  .kimodo-dojo-addbtn {
240
+ display: block;
241
  width: 100%;
242
+ padding: 11px;
243
  margin-bottom: 10px;
244
+ font-size: 13px;
245
  font-weight: 600;
246
  cursor: pointer;
247
+ border-radius: 9px;
248
  background: #4a3320 !important;
249
  color: #ffe0ad !important;
250
+ border: 1px dashed #b97736 !important;
251
  box-shadow: none !important;
252
  }
253
  .kimodo-dojo-addbtn:hover { background: #5a3f27 !important; }
 
323
  text-align: center;
324
  color: #87909b;
325
  }
326
+ /* Back to the dojo list — arrow only, like My Karate's ‹ back. */
327
  .kimodo-dojo-back {
328
+ background: none !important;
329
+ border: 0 !important;
330
+ box-shadow: none !important;
 
331
  color: #ffd28a !important;
332
+ font-size: 26px;
333
+ line-height: 1;
334
+ cursor: pointer;
335
+ padding: 0 6px 6px 2px;
336
+ }
337
+ .kimodo-dojo-back:hover { color: #ffe0ad !important; }
338
+
339
+ /* "Describe the dojo" heading row with a right-anchored erase icon. */
340
+ .kimodo-dojo-promptrow {
341
+ display: flex;
342
+ align-items: center;
343
+ justify-content: space-between;
344
+ margin: 4px 0 5px;
345
+ }
346
+ .kimodo-dojo-promptrow .kimodo-dojo-label { margin: 0; }
347
+ .kimodo-dojo-erase {
348
+ padding: 2px 6px;
349
+ font-size: 14px;
350
+ line-height: 1;
351
+ cursor: pointer;
352
+ color: #b9905a !important;
353
  background: none !important;
354
  border: 0 !important;
355
  box-shadow: none !important;
356
  }
357
+ .kimodo-dojo-erase:hover { color: #ffd28a !important; }
358
+
359
  .kimodo-dojo-improve {
360
  width: 100%;
361
  margin: 7px 0 4px;
362
+ padding: 8px;
363
+ font-size: 12px;
364
  font-weight: 600;
365
  cursor: pointer;
366
  border-radius: 7px;
367
+ background: #33271a !important;
368
+ color: #ffd28a !important;
369
+ border: 1px solid #7a5424 !important;
370
  box-shadow: none !important;
371
  }
372
+ .kimodo-dojo-improve:hover:not(:disabled) { background: #3f3020 !important; }
373
  .kimodo-dojo-improve:disabled { opacity: .55; cursor: default; }
374
  .kimodo-dojo-adv {
375
  margin: 8px 0;
tabs/dojo.tab.js CHANGED
@@ -3,6 +3,8 @@
3
  let dojoMode = 'splat';
4
  let lastScene = null;
5
  let dojoScope = 'mine';
 
 
6
 
7
  // Community starters — tapping one opens the create view prefilled, so generating
8
  // produces a real flux + splat scene (which then lands under "Mine").
@@ -25,6 +27,7 @@
25
  grid: document.getElementById('kimodo-dojo-grid'),
26
  emptyMsg: document.getElementById('kimodo-dojo-empty'),
27
  improve: document.getElementById('kimodo-dojo-improve'),
 
28
  prompt: document.getElementById('kimodo-dojo-prompt'),
29
  mode: document.getElementById('kimodo-dojo-mode'),
30
  roomSize: document.getElementById('kimodo-dojo-room-size'),
@@ -294,6 +297,7 @@
294
  showView('create');
295
  resetProgress();
296
  const els = dojoEls();
 
297
  if (seed) {
298
  if (els.prompt) { els.prompt.value = seed.prompt || ''; els.prompt.dispatchEvent(new Event('input', { bubbles: true })); }
299
  dojoMode = seed.mode || 'splat';
@@ -301,8 +305,10 @@
301
  applySettings(seed.settings);
302
  setStatus('Loaded "' + (seed.name || 'starter') + '". Tweak and Generate.');
303
  } else {
304
- setStatus('Ready.');
 
305
  }
 
306
  }
307
 
308
  function mineCard(entry) {
@@ -363,14 +369,25 @@
363
  }
364
 
365
  // ---------- tiny-aya prompt suggestion ----------
 
 
 
 
 
 
 
 
 
 
366
  function improvePrompt() {
367
  const els = dojoEls();
368
  const payloadInput = hiddenInput('dojo-suggest-payload');
369
  const trigger = document.querySelector('#dojo-suggest-btn button, #dojo-suggest-btn');
370
  if (!payloadInput || !trigger) { setStatus('Suggest backend missing.'); return; }
371
  const words = (els.prompt && els.prompt.value || '').trim();
372
- if (els.improve) { els.improve.disabled = true; els.improve.textContent = '✦ Thinking…'; }
373
- setStatus(words ? 'Improving your prompt with tiny-aya…' : 'Suggesting a prompt with tiny-aya…');
 
374
  writeHidden('dojo-suggest-result', '');
375
  writeHidden('dojo-suggest-payload', JSON.stringify({ words: words }));
376
  const started = Date.now();
@@ -382,16 +399,19 @@
382
  window.setTimeout(() => trigger.click(), 40);
383
 
384
  function finishImprove(val) {
385
- if (els.improve) { els.improve.disabled = busy; els.improve.textContent = '✦ Improve with tiny-aya'; }
 
386
  try {
387
  const r = JSON.parse(val);
388
  if (r.ok && r.prompt) {
389
  if (els.prompt) { els.prompt.value = r.prompt; els.prompt.dispatchEvent(new Event('input', { bubbles: true })); }
390
- setStatus('Prompt suggested ✦');
 
391
  } else {
392
- setStatus('Suggest failed: ' + (r.error || 'unknown'));
393
  }
394
- } catch (e) { setStatus('Suggest failed.'); }
 
395
  }
396
  }
397
 
@@ -399,7 +419,7 @@
399
  async function runGenerate() {
400
  if (busy) return;
401
  const cfg = currentConfig();
402
- if (!cfg.prompt) { setStatus('Add a scene prompt first (or tap Improve).'); return; }
403
  const payloadInput = hiddenInput('dojo-payload');
404
  const trigger = document.querySelector('#dojo-btn button, #dojo-btn');
405
  if (!payloadInput || !trigger) { setStatus('Dojo backend controls are missing.'); return; }
@@ -479,7 +499,13 @@
479
  });
480
  if (els.add) els.add.addEventListener('click', () => openCreate(null));
481
  if (els.back) els.back.addEventListener('click', () => showView('library'));
482
- if (els.improve) els.improve.addEventListener('click', () => { if (!busy) improvePrompt(); });
 
 
 
 
 
 
483
 
484
  if (els.mode) els.mode.addEventListener('click', (e) => {
485
  const button = e.target.closest('.kimodo-cz-segbtn[data-mode]');
@@ -498,12 +524,6 @@
498
  });
499
 
500
  if (els.generate) els.generate.addEventListener('click', runGenerate);
501
- if (els.clear) els.clear.addEventListener('click', () => {
502
- const frame = previewFrame();
503
- if (frame && frame.contentWindow) frame.contentWindow.postMessage({ kind: 'dojo-clear' }, '*');
504
- resetProgress();
505
- setStatus('Scene cleared.');
506
- });
507
  }
508
 
509
  window.addEventListener('kimodo-drawer-open', (e) => {
 
3
  let dojoMode = 'splat';
4
  let lastScene = null;
5
  let dojoScope = 'mine';
6
+ let improvedByAya = false; // current prompt came from tiny-aya → button reads "reroll"
7
+ let improving = false; // a tiny-aya suggestion call is in flight
8
 
9
  // Community starters — tapping one opens the create view prefilled, so generating
10
  // produces a real flux + splat scene (which then lands under "Mine").
 
27
  grid: document.getElementById('kimodo-dojo-grid'),
28
  emptyMsg: document.getElementById('kimodo-dojo-empty'),
29
  improve: document.getElementById('kimodo-dojo-improve'),
30
+ erase: document.getElementById('kimodo-dojo-erase'),
31
  prompt: document.getElementById('kimodo-dojo-prompt'),
32
  mode: document.getElementById('kimodo-dojo-mode'),
33
  roomSize: document.getElementById('kimodo-dojo-room-size'),
 
297
  showView('create');
298
  resetProgress();
299
  const els = dojoEls();
300
+ improvedByAya = false;
301
  if (seed) {
302
  if (els.prompt) { els.prompt.value = seed.prompt || ''; els.prompt.dispatchEvent(new Event('input', { bubbles: true })); }
303
  dojoMode = seed.mode || 'splat';
 
305
  applySettings(seed.settings);
306
  setStatus('Loaded "' + (seed.name || 'starter') + '". Tweak and Generate.');
307
  } else {
308
+ if (els.prompt) { els.prompt.value = ''; } // blank by default — placeholder "a random dojo"
309
+ setStatus('Describe a dojo, or let tiny-aya decide 🎲');
310
  }
311
+ updateImproveBtn();
312
  }
313
 
314
  function mineCard(entry) {
 
369
  }
370
 
371
  // ---------- tiny-aya prompt suggestion ----------
372
+ // Improve-button label tracks state: blank prompt → "let tiny-aya decide",
373
+ // tiny-aya already wrote one → "reroll", otherwise → "improve".
374
+ function updateImproveBtn() {
375
+ const els = dojoEls();
376
+ if (!els.improve || improving) return;
377
+ const has = !!(els.prompt && els.prompt.value.trim());
378
+ els.improve.textContent = improvedByAya ? 'reroll 🎲'
379
+ : (has ? '✦ Improve with tiny-aya' : 'let tiny-aya decide 🎲');
380
+ }
381
+
382
  function improvePrompt() {
383
  const els = dojoEls();
384
  const payloadInput = hiddenInput('dojo-suggest-payload');
385
  const trigger = document.querySelector('#dojo-suggest-btn button, #dojo-suggest-btn');
386
  if (!payloadInput || !trigger) { setStatus('Suggest backend missing.'); return; }
387
  const words = (els.prompt && els.prompt.value || '').trim();
388
+ improving = true;
389
+ if (els.improve) { els.improve.disabled = true; els.improve.textContent = '🎲 thinking…'; }
390
+ setStatus(words ? 'Rerolling your prompt with tiny-aya…' : 'Letting tiny-aya invent a dojo…');
391
  writeHidden('dojo-suggest-result', '');
392
  writeHidden('dojo-suggest-payload', JSON.stringify({ words: words }));
393
  const started = Date.now();
 
399
  window.setTimeout(() => trigger.click(), 40);
400
 
401
  function finishImprove(val) {
402
+ improving = false;
403
+ if (els.improve) els.improve.disabled = busy;
404
  try {
405
  const r = JSON.parse(val);
406
  if (r.ok && r.prompt) {
407
  if (els.prompt) { els.prompt.value = r.prompt; els.prompt.dispatchEvent(new Event('input', { bubbles: true })); }
408
+ improvedByAya = true; // set AFTER the input event (which resets it) so the button reads "reroll"
409
+ setStatus('Prompt ready ✦ — tweak it or tap Generate.');
410
  } else {
411
+ setStatus('tiny-aya failed: ' + (r.error || 'unknown'));
412
  }
413
+ } catch (e) { setStatus('tiny-aya failed.'); }
414
+ updateImproveBtn();
415
  }
416
  }
417
 
 
419
  async function runGenerate() {
420
  if (busy) return;
421
  const cfg = currentConfig();
422
+ if (!cfg.prompt) { setStatus('Describe a dojo first, or tap “let tiny-aya decide 🎲”.'); return; }
423
  const payloadInput = hiddenInput('dojo-payload');
424
  const trigger = document.querySelector('#dojo-btn button, #dojo-btn');
425
  if (!payloadInput || !trigger) { setStatus('Dojo backend controls are missing.'); return; }
 
499
  });
500
  if (els.add) els.add.addEventListener('click', () => openCreate(null));
501
  if (els.back) els.back.addEventListener('click', () => showView('library'));
502
+ if (els.improve) els.improve.addEventListener('click', () => { if (!busy && !improving) improvePrompt(); });
503
+ if (els.erase) els.erase.addEventListener('click', () => {
504
+ if (els.prompt) { els.prompt.value = ''; els.prompt.dispatchEvent(new Event('input', { bubbles: true })); els.prompt.focus(); }
505
+ setStatus('Prompt cleared — let tiny-aya decide 🎲');
506
+ });
507
+ // Manual edits drop the "reroll" state (a fresh prompt → "improve"/"decide").
508
+ if (els.prompt) els.prompt.addEventListener('input', () => { if (improving) return; improvedByAya = false; updateImproveBtn(); });
509
 
510
  if (els.mode) els.mode.addEventListener('click', (e) => {
511
  const button = e.target.closest('.kimodo-cz-segbtn[data-mode]');
 
524
  });
525
 
526
  if (els.generate) els.generate.addEventListener('click', runGenerate);
 
 
 
 
 
 
527
  }
528
 
529
  window.addEventListener('kimodo-drawer-open', (e) => {