Spaces:
Sleeping
Sleeping
Commit Β·
fcf8063
1
Parent(s): ffb4f53
Visual polish: animated hero, glowing timeline spine, badge/icon cards
Browse filesPure 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>
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-
|
|
|
|
| 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 |
-
<
|
| 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-
|
| 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">
|
| 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 |
-
<
|
| 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(
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 428 |
|
| 429 |
.rd-card {
|
| 430 |
-
|
|
|
|
|
|
|
| 431 |
border: 1px solid var(--border);
|
| 432 |
-
border-radius:
|
| 433 |
-
padding:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 434 |
}
|
| 435 |
|
| 436 |
-
.rd-fork {
|
| 437 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 438 |
.rd-question {
|
| 439 |
-
border: 1px solid
|
| 440 |
-
background:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 441 |
}
|
| 442 |
|
| 443 |
.rd-eyebrow {
|
| 444 |
-
font-size: 0.
|
| 445 |
-
|
| 446 |
-
letter-spacing: 0.05em;
|
| 447 |
-
text-transform: uppercase;
|
| 448 |
-
color: var(--accent-blue);
|
| 449 |
-
margin-bottom: 0.5rem;
|
| 450 |
}
|
| 451 |
-
.rd-sublabel {
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 456 |
}
|
| 457 |
-
.rd-
|
| 458 |
-
.rd-
|
| 459 |
-
.rd-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 463 |
-
.rd-q { font-size: 1.
|
|
|
|
| 464 |
.rd-quote {
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
color: var(--
|
| 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; }
|