prashantAI Claude Opus 4.8 (1M context) commited on
Commit
fcf8063
Β·
1 Parent(s): ffb4f53

Visual polish: animated hero, glowing timeline spine, badge/icon cards

Browse files

Pure CSS/SVG upgrade (no generated images): animated gradient hero,
vertical timeline spine with glowing year-nodes for ripples, gradient
fork badges, hover lift, checkmark strengths, icon-led stat cards and
winners/losers. Makes results feel like a designed timeline, not plain text.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +122 -47
app.py CHANGED
@@ -215,6 +215,19 @@ def _list_items(items) -> str:
215
  return "".join(f"<li>{html.escape(str(i))}</li>" for i in items if str(i).strip())
216
 
217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  def render_timeline_html(timeline: Dict, direction: str) -> str:
219
  """Render a personal counterfactual timeline as structured cards."""
220
  is_up = direction.lower() == "upward"
@@ -224,30 +237,31 @@ def render_timeline_html(timeline: Dict, direction: str) -> str:
224
  return f"""
225
  <div class="rd-stack">
226
  <div class="rd-card rd-fork">
227
- <div class="rd-eyebrow">{arrow} {label}</div>
 
228
  <h2 class="rd-title">{html.escape(timeline.get('title', 'Alternate Timeline'))}</h2>
229
  <p class="rd-lead">{html.escape(timeline.get('divergence', ''))}</p>
230
  </div>
231
  <div class="rd-card">
232
- <div class="rd-eyebrow">Ripples through the years</div>
233
- <ul class="rd-list">{_list_items(timeline.get('ripples', []))}</ul>
234
  </div>
235
  <div class="rd-card">
236
- <div class="rd-eyebrow">Who you might have become</div>
237
  <p>{html.escape(timeline.get('the_you', ''))}</p>
238
  </div>
239
  <div class="rd-card rd-return">
240
  <div class="rd-eyebrow">↩ Returning to your actual life</div>
241
  <p>{html.escape(ret.get('opening', ''))}</p>
242
  <div class="rd-sublabel">Strengths you actually built</div>
243
- <ul class="rd-list">{_list_items(ret.get('strengths', []))}</ul>
244
  <p class="rd-lesson"><strong>Lesson:</strong> {html.escape(ret.get('lesson', ''))}</p>
245
  </div>
246
  <div class="rd-card rd-question">
247
- <div class="rd-eyebrow">A question to sit with</div>
248
  <p class="rd-q">{html.escape(ret.get('reflection_question', ''))}</p>
249
  </div>
250
- <blockquote class="rd-quote">β€œ{html.escape(timeline.get('quote', ''))}”</blockquote>
251
  </div>
252
  """
253
 
@@ -257,29 +271,30 @@ def render_world_html(world: Dict) -> str:
257
  return f"""
258
  <div class="rd-stack">
259
  <div class="rd-card rd-fork">
 
260
  <div class="rd-eyebrow">Year {html.escape(str(world.get('year_divergence', '')))} Β· Point of divergence</div>
261
  <h2 class="rd-title">{html.escape(world.get('title', 'Alternate World'))}</h2>
262
  <p class="rd-lead">{html.escape(world.get('divergence_point', ''))}</p>
263
  </div>
264
  <div class="rd-card">
265
- <div class="rd-eyebrow">Ripples</div>
266
- <ul class="rd-list">{_list_items(world.get('ripples', []))}</ul>
267
  </div>
268
  <div class="rd-card">
269
- <div class="rd-eyebrow">How this world evolved</div>
270
  <p>{html.escape(world.get('world_outcome', ''))}</p>
271
  </div>
272
  <div class="rd-grid">
273
- <div class="rd-card"><div class="rd-sublabel">Economy</div><p>{html.escape(world.get('economy', ''))}</p></div>
274
- <div class="rd-card"><div class="rd-sublabel">Culture</div><p>{html.escape(world.get('culture', ''))}</p></div>
275
- <div class="rd-card"><div class="rd-sublabel">Technology</div><p>{html.escape(world.get('technology', ''))}</p></div>
276
  </div>
277
  <div class="rd-grid-2">
278
- <div class="rd-card rd-winners"><div class="rd-sublabel">Who thrived</div><ul class="rd-list">{_list_items(world.get('winners', []))}</ul></div>
279
- <div class="rd-card rd-losers"><div class="rd-sublabel">Who struggled</div><ul class="rd-list">{_list_items(world.get('losers', []))}</ul></div>
280
  </div>
281
  <div class="rd-card rd-question">
282
- <div class="rd-eyebrow">What it teaches us</div>
283
  <p class="rd-q">{html.escape(world.get('reflection', ''))}</p>
284
  </div>
285
  </div>
@@ -319,10 +334,18 @@ body, .gradio-container {
319
  .hero-title {
320
  font-size: 3.5rem;
321
  font-weight: 800;
322
- background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
 
323
  -webkit-background-clip: text;
 
324
  -webkit-text-fill-color: transparent;
325
  letter-spacing: -1px;
 
 
 
 
 
 
326
  }
327
 
328
  .hero-subtitle {
@@ -424,55 +447,107 @@ body, .gradio-container {
424
  }
425
 
426
  /* Structured result cards (timeline / world) */
427
- .rd-stack { display: flex; flex-direction: column; gap: 1rem; margin-top: 0.5rem; }
 
 
 
 
 
428
 
429
  .rd-card {
430
- background: linear-gradient(180deg, rgba(17, 24, 39, 0.6), rgba(11, 16, 32, 0.35));
 
 
431
  border: 1px solid var(--border);
432
- border-radius: 16px;
433
- padding: 20px 24px;
 
 
 
 
 
 
 
434
  }
435
 
436
- .rd-fork { border-left: 3px solid var(--accent-blue); }
437
- .rd-return { border-left: 3px solid var(--accent-purple); }
 
 
 
 
 
 
 
 
438
  .rd-question {
439
- border: 1px solid var(--accent-purple);
440
- background: rgba(139, 92, 246, 0.08);
 
 
 
 
 
 
 
 
 
 
 
441
  }
442
 
443
  .rd-eyebrow {
444
- font-size: 0.8rem;
445
- font-weight: 700;
446
- letter-spacing: 0.05em;
447
- text-transform: uppercase;
448
- color: var(--accent-blue);
449
- margin-bottom: 0.5rem;
450
  }
451
- .rd-sublabel {
452
- font-size: 0.85rem;
453
- font-weight: 600;
454
- color: var(--text-secondary);
455
- margin: 0.75rem 0 0.35rem;
 
 
 
 
 
 
456
  }
457
- .rd-title { font-size: 1.6rem; font-weight: 800; margin: 0.2rem 0 0.5rem; color: var(--text-primary); }
458
- .rd-lead { color: var(--text-secondary); font-size: 1.05rem; }
459
- .rd-card p { color: var(--text-primary); line-height: 1.6; margin: 0.25rem 0; }
 
 
 
 
 
 
 
 
 
 
 
 
460
  .rd-list { margin: 0.25rem 0 0; padding-left: 1.2rem; }
461
  .rd-list li { color: var(--text-primary); line-height: 1.6; margin-bottom: 0.35rem; }
462
- .rd-lesson { margin-top: 0.75rem; color: var(--text-secondary); }
463
- .rd-q { font-size: 1.1rem; font-style: italic; color: var(--text-primary); }
 
464
  .rd-quote {
465
- border-left: 3px solid var(--accent-blue);
466
- margin: 0.25rem 0 0;
467
- padding: 0.5rem 0 0.5rem 1rem;
468
- font-size: 1.2rem;
469
- font-style: italic;
470
- color: var(--text-primary);
471
  }
 
 
472
  .rd-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }
473
  .rd-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
 
474
  .rd-winners { border-left: 3px solid #34D399; }
475
  .rd-losers { border-left: 3px solid #F87171; }
 
476
 
477
  @media (max-width: 1024px) {
478
  .rd-grid, .rd-grid-2 { grid-template-columns: 1fr; }
 
215
  return "".join(f"<li>{html.escape(str(i))}</li>" for i in items if str(i).strip())
216
 
217
 
218
+ def _timeline_nodes(items) -> str:
219
+ """Render a list of strings as glowing timeline-spine nodes."""
220
+ return "".join(
221
+ f"<div class='rd-node'><p>{html.escape(str(i))}</p></div>"
222
+ for i in items if str(i).strip()
223
+ )
224
+
225
+
226
+ def _check_items(items) -> str:
227
+ """Render a list of strings as checkmark list items."""
228
+ return "".join(f"<li>{html.escape(str(i))}</li>" for i in items if str(i).strip())
229
+
230
+
231
  def render_timeline_html(timeline: Dict, direction: str) -> str:
232
  """Render a personal counterfactual timeline as structured cards."""
233
  is_up = direction.lower() == "upward"
 
237
  return f"""
238
  <div class="rd-stack">
239
  <div class="rd-card rd-fork">
240
+ <div class="rd-badge">{arrow}</div>
241
+ <div class="rd-eyebrow">{label}</div>
242
  <h2 class="rd-title">{html.escape(timeline.get('title', 'Alternate Timeline'))}</h2>
243
  <p class="rd-lead">{html.escape(timeline.get('divergence', ''))}</p>
244
  </div>
245
  <div class="rd-card">
246
+ <div class="rd-eyebrow">⏳ Ripples through the years</div>
247
+ <div class="rd-timeline">{_timeline_nodes(timeline.get('ripples', []))}</div>
248
  </div>
249
  <div class="rd-card">
250
+ <div class="rd-eyebrow">πŸŒ— Who you might have become</div>
251
  <p>{html.escape(timeline.get('the_you', ''))}</p>
252
  </div>
253
  <div class="rd-card rd-return">
254
  <div class="rd-eyebrow">↩ Returning to your actual life</div>
255
  <p>{html.escape(ret.get('opening', ''))}</p>
256
  <div class="rd-sublabel">Strengths you actually built</div>
257
+ <ul class="rd-checks">{_check_items(ret.get('strengths', []))}</ul>
258
  <p class="rd-lesson"><strong>Lesson:</strong> {html.escape(ret.get('lesson', ''))}</p>
259
  </div>
260
  <div class="rd-card rd-question">
261
+ <div class="rd-eyebrow">πŸ’­ A question to sit with</div>
262
  <p class="rd-q">{html.escape(ret.get('reflection_question', ''))}</p>
263
  </div>
264
+ <blockquote class="rd-quote">{html.escape(timeline.get('quote', ''))}</blockquote>
265
  </div>
266
  """
267
 
 
271
  return f"""
272
  <div class="rd-stack">
273
  <div class="rd-card rd-fork">
274
+ <div class="rd-badge">🌍</div>
275
  <div class="rd-eyebrow">Year {html.escape(str(world.get('year_divergence', '')))} Β· Point of divergence</div>
276
  <h2 class="rd-title">{html.escape(world.get('title', 'Alternate World'))}</h2>
277
  <p class="rd-lead">{html.escape(world.get('divergence_point', ''))}</p>
278
  </div>
279
  <div class="rd-card">
280
+ <div class="rd-eyebrow">⏳ Ripples</div>
281
+ <div class="rd-timeline">{_timeline_nodes(world.get('ripples', []))}</div>
282
  </div>
283
  <div class="rd-card">
284
+ <div class="rd-eyebrow">🌐 How this world evolved</div>
285
  <p>{html.escape(world.get('world_outcome', ''))}</p>
286
  </div>
287
  <div class="rd-grid">
288
+ <div class="rd-card rd-stat"><span class="rd-ico">πŸ’°</span><div class="rd-sublabel">Economy</div><p>{html.escape(world.get('economy', ''))}</p></div>
289
+ <div class="rd-card rd-stat"><span class="rd-ico">🎭</span><div class="rd-sublabel">Culture</div><p>{html.escape(world.get('culture', ''))}</p></div>
290
+ <div class="rd-card rd-stat"><span class="rd-ico">βš™οΈ</span><div class="rd-sublabel">Technology</div><p>{html.escape(world.get('technology', ''))}</p></div>
291
  </div>
292
  <div class="rd-grid-2">
293
+ <div class="rd-card rd-winners"><span class="rd-ico">πŸ“ˆ</span><div class="rd-sublabel">Who thrived</div><ul class="rd-list">{_list_items(world.get('winners', []))}</ul></div>
294
+ <div class="rd-card rd-losers"><span class="rd-ico">πŸ“‰</span><div class="rd-sublabel">Who struggled</div><ul class="rd-list">{_list_items(world.get('losers', []))}</ul></div>
295
  </div>
296
  <div class="rd-card rd-question">
297
+ <div class="rd-eyebrow">🧭 What it teaches us</div>
298
  <p class="rd-q">{html.escape(world.get('reflection', ''))}</p>
299
  </div>
300
  </div>
 
334
  .hero-title {
335
  font-size: 3.5rem;
336
  font-weight: 800;
337
+ background: linear-gradient(110deg, var(--accent-blue), var(--accent-purple), #F0ABFC, var(--accent-blue));
338
+ background-size: 250% auto;
339
  -webkit-background-clip: text;
340
+ background-clip: text;
341
  -webkit-text-fill-color: transparent;
342
  letter-spacing: -1px;
343
+ animation: rdHero 7s ease infinite;
344
+ }
345
+ @keyframes rdHero {
346
+ 0% { background-position: 0% center; }
347
+ 50% { background-position: 100% center; }
348
+ 100% { background-position: 0% center; }
349
  }
350
 
351
  .hero-subtitle {
 
447
  }
448
 
449
  /* Structured result cards (timeline / world) */
450
+ @keyframes rdFadeUp {
451
+ from { opacity: 0; transform: translateY(14px); }
452
+ to { opacity: 1; transform: translateY(0); }
453
+ }
454
+
455
+ .rd-stack { display: flex; flex-direction: column; gap: 1.1rem; margin-top: 0.5rem; }
456
 
457
  .rd-card {
458
+ position: relative;
459
+ overflow: hidden;
460
+ background: linear-gradient(180deg, rgba(17, 24, 39, 0.65), rgba(11, 16, 32, 0.4));
461
  border: 1px solid var(--border);
462
+ border-radius: 18px;
463
+ padding: 22px 26px;
464
+ animation: rdFadeUp 0.5s ease both;
465
+ transition: border-color .25s ease, box-shadow .25s ease, transform .25s ease;
466
+ }
467
+ .rd-card:hover {
468
+ border-color: rgba(56, 189, 248, 0.55);
469
+ box-shadow: 0 10px 34px rgba(5, 8, 22, 0.6), 0 0 24px rgba(56, 189, 248, 0.12);
470
+ transform: translateY(-2px);
471
  }
472
 
473
+ .rd-fork {
474
+ border: 1px solid rgba(56, 189, 248, 0.35);
475
+ background:
476
+ radial-gradient(1100px 200px at 0% 0%, rgba(56, 189, 248, 0.13), transparent 60%),
477
+ linear-gradient(180deg, rgba(17, 24, 39, 0.72), rgba(11, 16, 32, 0.45));
478
+ }
479
+ .rd-fork::before, .rd-return::before {
480
+ content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
481
+ background: linear-gradient(180deg, var(--accent-blue), var(--accent-purple));
482
+ }
483
  .rd-question {
484
+ border: 1px solid rgba(139, 92, 246, 0.5);
485
+ background:
486
+ radial-gradient(800px 130px at 100% 0%, rgba(139, 92, 246, 0.18), transparent 70%),
487
+ rgba(139, 92, 246, 0.06);
488
+ }
489
+
490
+ .rd-badge {
491
+ display: inline-flex; align-items: center; justify-content: center;
492
+ width: 46px; height: 46px; border-radius: 50%;
493
+ font-size: 1.4rem; font-weight: 800; color: #fff;
494
+ background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
495
+ box-shadow: 0 0 20px rgba(56, 189, 248, 0.5);
496
+ margin-bottom: 0.7rem;
497
  }
498
 
499
  .rd-eyebrow {
500
+ font-size: 0.78rem; font-weight: 700; letter-spacing: 0.08em;
501
+ text-transform: uppercase; color: var(--accent-blue); margin-bottom: 0.55rem;
 
 
 
 
502
  }
503
+ .rd-sublabel { font-size: 0.85rem; font-weight: 700; color: var(--text-secondary); margin: 0.9rem 0 0.4rem; letter-spacing: .03em; }
504
+ .rd-title { font-size: 1.7rem; font-weight: 800; margin: 0.1rem 0 0.5rem; color: var(--text-primary); letter-spacing: -0.5px; }
505
+ .rd-lead { color: var(--text-secondary); font-size: 1.08rem; line-height: 1.55; }
506
+ .rd-card p { color: var(--text-primary); line-height: 1.65; margin: 0.25rem 0; }
507
+
508
+ /* Timeline spine */
509
+ .rd-timeline { position: relative; margin: 0.3rem 0 0; padding-left: 30px; }
510
+ .rd-timeline::before {
511
+ content: ""; position: absolute; left: 7px; top: 6px; bottom: 6px; width: 2px;
512
+ background: linear-gradient(180deg, var(--accent-blue), var(--accent-purple));
513
+ opacity: 0.55;
514
  }
515
+ .rd-node { position: relative; padding-bottom: 1rem; }
516
+ .rd-node:last-child { padding-bottom: 0; }
517
+ .rd-node::before {
518
+ content: ""; position: absolute; left: -30px; top: 4px;
519
+ width: 14px; height: 14px; border-radius: 50%;
520
+ background: var(--accent-blue); border: 3px solid var(--bg-dark);
521
+ box-shadow: 0 0 12px rgba(56, 189, 248, 0.75);
522
+ }
523
+ .rd-node p { margin: 0; color: var(--text-primary); line-height: 1.6; }
524
+
525
+ /* Strengths with check marks */
526
+ .rd-checks { list-style: none; margin: 0.25rem 0 0; padding: 0; }
527
+ .rd-checks li { position: relative; padding-left: 1.7rem; margin-bottom: 0.45rem; color: var(--text-primary); line-height: 1.55; }
528
+ .rd-checks li::before { content: "βœ“"; position: absolute; left: 0; color: #34D399; font-weight: 800; }
529
+
530
  .rd-list { margin: 0.25rem 0 0; padding-left: 1.2rem; }
531
  .rd-list li { color: var(--text-primary); line-height: 1.6; margin-bottom: 0.35rem; }
532
+ .rd-lesson { margin-top: 0.8rem; color: var(--text-secondary); }
533
+ .rd-q { font-size: 1.15rem; font-style: italic; color: var(--text-primary); line-height: 1.6; }
534
+
535
  .rd-quote {
536
+ position: relative; margin: 0.3rem 0 0; padding: 0.5rem 0 0.5rem 2.6rem;
537
+ font-size: 1.28rem; font-style: italic; color: var(--text-primary); line-height: 1.5;
538
+ }
539
+ .rd-quote::before {
540
+ content: "\201C"; position: absolute; left: 0; top: 0;
541
+ font-size: 3.4rem; line-height: 1; color: var(--accent-purple); font-family: Georgia, serif;
542
  }
543
+
544
+ /* Stat + win/lose grids */
545
  .rd-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }
546
  .rd-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
547
+ .rd-stat { border-top: 3px solid var(--accent-blue); }
548
  .rd-winners { border-left: 3px solid #34D399; }
549
  .rd-losers { border-left: 3px solid #F87171; }
550
+ .rd-ico { display: block; font-size: 1.6rem; margin-bottom: 0.35rem; }
551
 
552
  @media (max-width: 1024px) {
553
  .rd-grid, .rd-grid-2 { grid-template-columns: 1fr; }