kavyabhand commited on
Commit
07e6e32
·
verified ·
1 Parent(s): 95c1233

Deploy Aether Garden application

Browse files
Files changed (3) hide show
  1. app.py +117 -58
  2. ui/book_display.py +10 -8
  3. ui/styles.css +333 -3
app.py CHANGED
@@ -333,7 +333,12 @@ def filter_entities(location_filter, type_filter, status_filter, search):
333
 
334
 
335
  def filter_book(entry_type, search):
336
- return render_book_of_ages(entry_type=entry_type, search=search)
 
 
 
 
 
337
 
338
 
339
  def chat_with_soul(soul_id: str, player_message: str) -> str:
@@ -557,50 +562,52 @@ def build_app() -> gr.Blocks:
557
 
558
  with gr.Tabs(elem_id="tome-spreads", elem_classes=["realm-tabs"]) as tabs:
559
  with gr.Tab("The Realm", id="realm"):
560
- world_vignette = gr.HTML(value=render_world_vignette())
561
- realm_pulse = gr.HTML(value=render_realm_pulse())
562
-
563
- with gr.Accordion("How this world works", open=False):
564
- gr.HTML(
565
- '<div class="how-it-works">'
566
- '<p><b>1 · You summon.</b> Describe anything below and an AI gives it a name, '
567
- 'a look, goals, fears, and a home among the eight places.</p>'
568
- '<p><b>2 · The world lives on its own.</b> Every hour, even when no one is here, '
569
- 'the souls wander, meet, form bonds, and the land throws up strange events.</p>'
570
- '<p><b>3 · Nothing is forgotten.</b> Every arrival, meeting, and event is written '
571
- 'into the Book of Ages forever. Open the <b>Explore</b> tab to step inside a place '
572
- 'and meet who lives there.</p>'
573
- '</div>'
574
- )
575
-
576
- with gr.Row(elem_classes=["tome-spread-row"]):
577
- with gr.Column(scale=3, elem_classes=["tome-page-left"]):
578
- world_map = gr.HTML(
579
- value=render_world_map(),
580
- elem_id="world-map",
 
 
 
 
 
 
 
 
 
 
581
  )
582
- location_panel = gr.HTML(
583
- value=render_location_panel(None),
 
 
584
  )
585
- realm_diorama = gr.HTML(value="")
586
- map_pick = gr.Textbox(visible=False, elem_id="map_location_pick")
587
-
588
- with gr.Column(scale=2, elem_classes=["sidebar-panel", "tome-page-right"]):
589
- current_event = gr.HTML(value=render_current_event())
590
- activity_feed = gr.HTML(value=render_activity_feed())
591
- with gr.Accordion("⚡ Advance the World Now", open=False):
592
- gr.HTML('<p style="color:var(--realm-text-tertiary);font-size:0.82rem;margin-bottom:0.6rem;">Manually trigger one simulation tick — the world will generate a new event, run soul encounters, and update memories in real time.</p>')
593
- live_tick_btn = gr.Button("Run simulation tick →", variant="secondary", size="sm")
594
- tick_result = gr.HTML()
595
 
596
  location_select = gr.Dropdown(
597
  choices=[(l["name"], l["id"]) for l in get_all_locations()],
598
  label="Explore Location",
599
- visible=True,
600
  )
601
 
602
- featured_chars = gr.HTML(value=render_featured_characters())
603
-
604
  with gr.Tab("Explore the Realm", id="places"):
605
  _all_locs = get_all_locations()
606
  _default_loc_id = _all_locs[0]["id"] if _all_locs else None
@@ -645,26 +652,35 @@ def build_app() -> gr.Blocks:
645
  soul_card = gr.HTML()
646
 
647
  with gr.Tab("Book of Ages", id="book"):
648
- with gr.Row():
649
- book_filter = gr.Dropdown(
650
- choices=[
651
- ("All", "all"),
652
- ("Arrivals", "arrival"),
653
- ("Interactions", "interaction"),
654
- ("Events", "world_event"),
655
- ("Milestones", "milestone"),
656
- ("Dreams", "dream_fragment"),
657
- ],
658
- value="all",
659
- label="Filter",
660
- scale=1,
661
- )
662
- book_search = gr.Textbox(
663
- placeholder="Search by name...",
664
- label="Search",
665
- scale=2,
666
- )
667
- book_display = gr.HTML(value=render_book_of_ages())
 
 
 
 
 
 
 
 
 
668
 
669
  with gr.Tab("Bonds and Alliances", id="bonds"):
670
  gr.HTML(
@@ -676,6 +692,7 @@ def build_app() -> gr.Blocks:
676
  rel_graph = gr.HTML(value=render_relationship_graph())
677
 
678
  with gr.Tab("Souls of the Garden", id="entities"):
 
679
  with gr.Row():
680
  loc_filter = gr.Dropdown(
681
  choices=["all"] + [l["name"] for l in get_all_locations()],
@@ -962,11 +979,13 @@ def build_app() -> gr.Blocks:
962
  panels.forEach((panel, i) => {
963
  const active = i === panelIndex;
964
  panel.style.setProperty('display', active ? 'flex' : 'none', 'important');
 
965
  panel.style.setProperty('height', active ? '565px' : '0', 'important');
966
  panel.style.setProperty('max-height', active ? '565px' : '0', 'important');
967
- panel.style.setProperty('overflow', active ? 'auto' : 'hidden', 'important');
968
  panel.setAttribute('aria-hidden', active ? 'false' : 'true');
969
  });
 
970
  const pad = document.getElementById('aether-walk-pad');
971
  if (pad && panelIndex !== 1) {
972
  pad.classList.remove('visible', 'explore-only');
@@ -1149,6 +1168,44 @@ def build_app() -> gr.Blocks:
1149
  setActivePanel(sections[currentSpread].panel);
1150
  };
1151
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1152
  const wireOpening = () => {
1153
  const gate = document.getElementById('realm-opening');
1154
  if (!gate || gate.dataset.bound) return;
@@ -1258,6 +1315,7 @@ def build_app() -> gr.Blocks:
1258
 
1259
  wireOpening();
1260
  wireTomeNav();
 
1261
 
1262
  const bind = () => {
1263
  document.querySelectorAll('#world-map [data-location-id]').forEach((node) => {
@@ -1446,6 +1504,7 @@ def build_app() -> gr.Blocks:
1446
  wireDioramaFrames();
1447
  wireWalkPad();
1448
  updateWalkPadVisibility();
 
1449
  }).observe(document.body, { childList: true, subtree: true });
1450
  setInterval(updateWalkPadVisibility, 800);
1451
 
 
333
 
334
 
335
  def filter_book(entry_type, search):
336
+ return render_book_of_ages(
337
+ entry_type=entry_type,
338
+ search=search,
339
+ limit=8,
340
+ include_header=False,
341
+ )
342
 
343
 
344
  def chat_with_soul(soul_id: str, player_message: str) -> str:
 
562
 
563
  with gr.Tabs(elem_id="tome-spreads", elem_classes=["realm-tabs"]) as tabs:
564
  with gr.Tab("The Realm", id="realm"):
565
+ with gr.Column(elem_classes=["tome-spread-shell"]):
566
+ with gr.Row(elem_classes=["tome-spread-row"]):
567
+ with gr.Column(scale=1, elem_classes=["tome-page-left", "tome-printed-page"]):
568
+ world_map = gr.HTML(
569
+ value=render_world_map(),
570
+ elem_id="world-map",
571
+ )
572
+ location_panel = gr.HTML(
573
+ value=render_location_panel(None),
574
+ )
575
+
576
+ with gr.Column(scale=1, elem_classes=["tome-page-right", "tome-printed-page"]):
577
+ current_event = gr.HTML(value=render_current_event())
578
+ activity_feed = gr.HTML(value=render_activity_feed(limit=3))
579
+
580
+ with gr.Column(elem_classes=["tome-offpage-store"]):
581
+ world_vignette = gr.HTML(value=render_world_vignette())
582
+ realm_pulse = gr.HTML(value=render_realm_pulse())
583
+ realm_diorama = gr.HTML(value="")
584
+ map_pick = gr.Textbox(visible=False, elem_id="map_location_pick")
585
+ with gr.Accordion("How this world works", open=False):
586
+ gr.HTML(
587
+ '<div class="how-it-works">'
588
+ '<p><b>1 · You summon.</b> Describe anything below and an AI gives it a name, '
589
+ 'a look, goals, fears, and a home among the eight places.</p>'
590
+ '<p><b>2 · The world lives on its own.</b> Every hour, even when no one is here, '
591
+ 'the souls wander, meet, form bonds, and the land throws up strange events.</p>'
592
+ '<p><b>3 · Nothing is forgotten.</b> Every arrival, meeting, and event is written '
593
+ 'into the Book of Ages forever. Turn the page to <b>Explore</b> or open the '
594
+ '<b>Book of Ages</b> for the full chronicle.</p>'
595
+ '</div>'
596
  )
597
+ with gr.Accordion("⚡ Advance the World Now", open=False):
598
+ gr.HTML(
599
+ '<p class="tome-muted">Manually trigger one simulation tick — the world will '
600
+ 'generate a new event, run soul encounters, and update memories in real time.</p>'
601
  )
602
+ live_tick_btn = gr.Button("Run simulation tick →", variant="secondary", size="sm")
603
+ tick_result = gr.HTML()
 
 
 
 
 
 
 
 
604
 
605
  location_select = gr.Dropdown(
606
  choices=[(l["name"], l["id"]) for l in get_all_locations()],
607
  label="Explore Location",
608
+ visible=False,
609
  )
610
 
 
 
611
  with gr.Tab("Explore the Realm", id="places"):
612
  _all_locs = get_all_locations()
613
  _default_loc_id = _all_locs[0]["id"] if _all_locs else None
 
652
  soul_card = gr.HTML()
653
 
654
  with gr.Tab("Book of Ages", id="book"):
655
+ with gr.Column(elem_classes=["tome-spread-shell"]):
656
+ with gr.Row(elem_classes=["tome-spread-row"]):
657
+ with gr.Column(scale=1, elem_classes=["tome-page-left", "tome-printed-page", "tome-book-index"]):
658
+ gr.HTML(
659
+ '<div class="tome-printed-heading">'
660
+ '<h2 class="book-title">Book of Ages</h2>'
661
+ '<p class="book-subtitle">Every creation, encounter, and event — remembered forever</p>'
662
+ '</div>'
663
+ )
664
+ book_filter = gr.Dropdown(
665
+ choices=[
666
+ ("All", "all"),
667
+ ("Arrivals", "arrival"),
668
+ ("Interactions", "interaction"),
669
+ ("Events", "world_event"),
670
+ ("Milestones", "milestone"),
671
+ ("Dreams", "dream_fragment"),
672
+ ],
673
+ value="all",
674
+ label="Filter",
675
+ )
676
+ book_search = gr.Textbox(
677
+ placeholder="Search by name...",
678
+ label="Search",
679
+ )
680
+ with gr.Column(scale=1, elem_classes=["tome-page-right", "tome-printed-page"]):
681
+ book_display = gr.HTML(
682
+ value=render_book_of_ages(limit=8, include_header=False)
683
+ )
684
 
685
  with gr.Tab("Bonds and Alliances", id="bonds"):
686
  gr.HTML(
 
692
  rel_graph = gr.HTML(value=render_relationship_graph())
693
 
694
  with gr.Tab("Souls of the Garden", id="entities"):
695
+ featured_chars = gr.HTML(value=render_featured_characters())
696
  with gr.Row():
697
  loc_filter = gr.Dropdown(
698
  choices=["all"] + [l["name"] for l in get_all_locations()],
 
979
  panels.forEach((panel, i) => {
980
  const active = i === panelIndex;
981
  panel.style.setProperty('display', active ? 'flex' : 'none', 'important');
982
+ panel.style.setProperty('flex-direction', 'column', 'important');
983
  panel.style.setProperty('height', active ? '565px' : '0', 'important');
984
  panel.style.setProperty('max-height', active ? '565px' : '0', 'important');
985
+ panel.style.setProperty('overflow', 'hidden', 'important');
986
  panel.setAttribute('aria-hidden', active ? 'false' : 'true');
987
  });
988
+ wirePageExpand();
989
  const pad = document.getElementById('aether-walk-pad');
990
  if (pad && panelIndex !== 1) {
991
  pad.classList.remove('visible', 'explore-only');
 
1168
  setActivePanel(sections[currentSpread].panel);
1169
  };
1170
 
1171
+ const wirePageExpand = () => {
1172
+ document.querySelectorAll('.tome-page-left, .tome-page-right').forEach((page) => {
1173
+ if (page.querySelector(':scope > .tome-expand-btn')) return;
1174
+ const side = page.classList.contains('tome-page-left') ? 'left' : 'right';
1175
+ const btn = document.createElement('button');
1176
+ btn.type = 'button';
1177
+ btn.className = 'tome-expand-btn';
1178
+ btn.dataset.expand = side;
1179
+ btn.setAttribute('aria-label', `Expand ${side} page`);
1180
+ btn.title = `Expand ${side} page`;
1181
+ btn.textContent = '⛶';
1182
+ btn.addEventListener('click', (e) => {
1183
+ e.preventDefault();
1184
+ e.stopPropagation();
1185
+ const root = document.querySelector('.gradio-container');
1186
+ if (!root) return;
1187
+ const cls = `tome-expanded-${side}`;
1188
+ const on = root.classList.toggle(cls);
1189
+ root.classList.remove(side === 'left' ? 'tome-expanded-right' : 'tome-expanded-left');
1190
+ if (!on) root.classList.remove(cls);
1191
+ });
1192
+ page.insertBefore(btn, page.firstChild);
1193
+ });
1194
+
1195
+ if (!document.getElementById('tome-expand-backdrop')) {
1196
+ const backdrop = document.createElement('button');
1197
+ backdrop.id = 'tome-expand-backdrop';
1198
+ backdrop.type = 'button';
1199
+ backdrop.className = 'tome-expand-backdrop';
1200
+ backdrop.setAttribute('aria-label', 'Close expanded page');
1201
+ backdrop.addEventListener('click', () => {
1202
+ const root = document.querySelector('.gradio-container');
1203
+ root?.classList.remove('tome-expanded-left', 'tome-expanded-right');
1204
+ });
1205
+ document.body.appendChild(backdrop);
1206
+ }
1207
+ };
1208
+
1209
  const wireOpening = () => {
1210
  const gate = document.getElementById('realm-opening');
1211
  if (!gate || gate.dataset.bound) return;
 
1315
 
1316
  wireOpening();
1317
  wireTomeNav();
1318
+ wirePageExpand();
1319
 
1320
  const bind = () => {
1321
  document.querySelectorAll('#world-map [data-location-id]').forEach((node) => {
 
1504
  wireDioramaFrames();
1505
  wireWalkPad();
1506
  updateWalkPadVisibility();
1507
+ wirePageExpand();
1508
  }).observe(document.body, { childList: true, subtree: true });
1509
  setInterval(updateWalkPadVisibility, 800);
1510
 
ui/book_display.py CHANGED
@@ -25,6 +25,7 @@ def render_book_of_ages(
25
  entry_type: str = "all",
26
  search: str = "",
27
  limit: int = 40,
 
28
  ) -> str:
29
  entries = get_entries(limit=limit, entry_type=entry_type if entry_type != "all" else None, search=search or None)
30
 
@@ -44,14 +45,15 @@ def render_book_of_ages(
44
  if entry_type == "dream_fragment":
45
  subtitle = "Dreams of the Valley — fragments that surfaced while giants slept"
46
 
47
- parts = [
48
- '<div class="book-container">',
49
- '<div class="book-header">',
50
- '<h2 class="book-title">Book of Ages</h2>',
51
- f'<p class="book-subtitle">{subtitle}</p>',
52
- "</div>",
53
- '<div class="book-entries">',
54
- ]
 
55
 
56
  for entry in entries:
57
  if entry["world_day"] != current_day:
 
25
  entry_type: str = "all",
26
  search: str = "",
27
  limit: int = 40,
28
+ include_header: bool = True,
29
  ) -> str:
30
  entries = get_entries(limit=limit, entry_type=entry_type if entry_type != "all" else None, search=search or None)
31
 
 
45
  if entry_type == "dream_fragment":
46
  subtitle = "Dreams of the Valley — fragments that surfaced while giants slept"
47
 
48
+ parts = ['<div class="book-container">']
49
+ if include_header:
50
+ parts.extend([
51
+ '<div class="book-header">',
52
+ '<h2 class="book-title">Book of Ages</h2>',
53
+ f'<p class="book-subtitle">{subtitle}</p>',
54
+ "</div>",
55
+ ])
56
+ parts.append('<div class="book-entries">')
57
 
58
  for entry in entries:
59
  if entry["world_day"] != current_day:
ui/styles.css CHANGED
@@ -2937,8 +2937,10 @@ body,
2937
  height: 565px !important;
2938
  max-height: 565px !important;
2939
  min-height: 0 !important;
2940
- overflow: auto !important;
2941
- padding: 0.4rem 1rem 1rem !important;
 
 
2942
  }
2943
 
2944
  .realm-tabs [role="tabpanel"] > .column,
@@ -3138,7 +3140,7 @@ body,
3138
  .realm-tabs [role="tabpanel"] {
3139
  height: 565px !important;
3140
  max-height: 565px !important;
3141
- overflow: auto !important;
3142
  }
3143
 
3144
  .realm-opening-inner {
@@ -3639,3 +3641,331 @@ body,
3639
  border-radius: 10px;
3640
  overflow: hidden;
3641
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2937
  height: 565px !important;
2938
  max-height: 565px !important;
2939
  min-height: 0 !important;
2940
+ overflow: hidden !important;
2941
+ padding: 0.5rem 0.85rem 0.75rem !important;
2942
+ display: flex !important;
2943
+ flex-direction: column !important;
2944
  }
2945
 
2946
  .realm-tabs [role="tabpanel"] > .column,
 
3140
  .realm-tabs [role="tabpanel"] {
3141
  height: 565px !important;
3142
  max-height: 565px !important;
3143
+ overflow: hidden !important;
3144
  }
3145
 
3146
  .realm-opening-inner {
 
3641
  border-radius: 10px;
3642
  overflow: hidden;
3643
  }
3644
+
3645
+ /* -------------------------------------------------------------------------- */
3646
+ /* Living book spread — no scroll, printed pages, expand option */
3647
+ /* -------------------------------------------------------------------------- */
3648
+ .tome-offpage-store {
3649
+ display: none !important;
3650
+ height: 0 !important;
3651
+ max-height: 0 !important;
3652
+ overflow: hidden !important;
3653
+ visibility: hidden !important;
3654
+ pointer-events: none !important;
3655
+ margin: 0 !important;
3656
+ padding: 0 !important;
3657
+ }
3658
+
3659
+ .tome-spread-shell {
3660
+ flex: 1 1 auto;
3661
+ min-height: 0;
3662
+ display: flex;
3663
+ flex-direction: column;
3664
+ overflow: hidden;
3665
+ }
3666
+
3667
+ .tome-spread-row {
3668
+ flex: 1 1 auto;
3669
+ min-height: 0 !important;
3670
+ max-height: 100% !important;
3671
+ align-items: stretch !important;
3672
+ overflow: hidden !important;
3673
+ gap: 0 !important;
3674
+ margin: 0 !important;
3675
+ }
3676
+
3677
+ .tome-spread-row > .column,
3678
+ .tome-spread-row > .gr-column {
3679
+ min-height: 0 !important;
3680
+ max-height: 100% !important;
3681
+ overflow: hidden !important;
3682
+ }
3683
+
3684
+ .tome-printed-page {
3685
+ position: relative;
3686
+ display: flex !important;
3687
+ flex-direction: column !important;
3688
+ min-height: 0 !important;
3689
+ max-height: 100% !important;
3690
+ overflow: hidden !important;
3691
+ padding: 0.85rem 1.15rem 0.75rem !important;
3692
+ background:
3693
+ radial-gradient(ellipse 110px 85px at 94% 90%, rgba(72, 44, 18, 0.14) 0%, transparent 64%),
3694
+ radial-gradient(ellipse 95px 75px at 6% 12%, rgba(58, 34, 14, 0.1) 0%, transparent 58%),
3695
+ radial-gradient(ellipse 80px 60px at 88% 8%, rgba(54, 32, 12, 0.08) 0%, transparent 55%),
3696
+ radial-gradient(circle at 5% 38%, rgba(88, 62, 32, 0.09) 0 2px, transparent 3px),
3697
+ radial-gradient(circle at 94% 62%, rgba(78, 54, 28, 0.08) 0 2px, transparent 3px),
3698
+ linear-gradient(168deg, #ebe0c4 0%, #e0d0aa 45%, #d4c094 100%);
3699
+ box-shadow: inset 0 0 28px rgba(88, 56, 24, 0.08);
3700
+ isolation: isolate;
3701
+ }
3702
+
3703
+ .tome-printed-page::before {
3704
+ content: "";
3705
+ position: absolute;
3706
+ inset: 0;
3707
+ pointer-events: none;
3708
+ z-index: 0;
3709
+ opacity: 0.4;
3710
+ background:
3711
+ repeating-linear-gradient(
3712
+ 0deg,
3713
+ transparent,
3714
+ transparent 2px,
3715
+ rgba(62, 40, 18, 0.02) 2px,
3716
+ rgba(62, 40, 18, 0.02) 3px
3717
+ ),
3718
+ radial-gradient(ellipse 100% 90% at 50% 50%, transparent 58%, rgba(48, 30, 14, 0.08) 100%);
3719
+ mix-blend-mode: multiply;
3720
+ }
3721
+
3722
+ .tome-printed-page > * {
3723
+ position: relative;
3724
+ z-index: 1;
3725
+ min-height: 0;
3726
+ }
3727
+
3728
+ .tome-page-left {
3729
+ padding-right: 2.4rem !important;
3730
+ padding-left: 0.5rem !important;
3731
+ border-right: 1px solid rgba(98, 62, 28, 0.12);
3732
+ }
3733
+
3734
+ .tome-page-right {
3735
+ padding-left: 2.4rem !important;
3736
+ padding-right: 0.5rem !important;
3737
+ }
3738
+
3739
+ .tome-expand-btn {
3740
+ position: absolute;
3741
+ top: 0.35rem;
3742
+ right: 0.4rem;
3743
+ z-index: 6;
3744
+ width: 1.65rem;
3745
+ height: 1.65rem;
3746
+ border: 1px solid rgba(98, 62, 28, 0.35);
3747
+ border-radius: 6px;
3748
+ background: rgba(248, 239, 216, 0.72);
3749
+ color: #5c4228;
3750
+ font-size: 0.72rem;
3751
+ line-height: 1;
3752
+ cursor: pointer;
3753
+ opacity: 0.55;
3754
+ transition: opacity 0.2s ease, background 0.2s ease;
3755
+ }
3756
+
3757
+ .tome-page-right .tome-expand-btn {
3758
+ right: auto;
3759
+ left: 0.4rem;
3760
+ }
3761
+
3762
+ .tome-printed-page:hover .tome-expand-btn {
3763
+ opacity: 1;
3764
+ }
3765
+
3766
+ .tome-expand-btn:hover {
3767
+ background: rgba(240, 214, 150, 0.9);
3768
+ }
3769
+
3770
+ .tome-expand-backdrop {
3771
+ display: none;
3772
+ position: fixed;
3773
+ inset: 0;
3774
+ z-index: 2190;
3775
+ border: 0;
3776
+ background: rgba(8, 5, 3, 0.62);
3777
+ cursor: pointer;
3778
+ }
3779
+
3780
+ .gradio-container.tome-expanded-left .tome-expand-backdrop,
3781
+ .gradio-container.tome-expanded-right .tome-expand-backdrop {
3782
+ display: block;
3783
+ }
3784
+
3785
+ .gradio-container.tome-expanded-left .tome-page-left,
3786
+ .gradio-container.tome-expanded-right .tome-page-right {
3787
+ position: fixed !important;
3788
+ z-index: 2200 !important;
3789
+ top: 50% !important;
3790
+ left: 50% !important;
3791
+ transform: translate(-50%, -50%) !important;
3792
+ width: min(920px, 92vw) !important;
3793
+ max-width: 92vw !important;
3794
+ height: min(82vh, 680px) !important;
3795
+ max-height: 82vh !important;
3796
+ overflow: auto !important;
3797
+ padding: 1.5rem 1.75rem !important;
3798
+ border: 1px solid rgba(98, 62, 28, 0.45) !important;
3799
+ box-shadow: 0 28px 80px rgba(8, 4, 2, 0.55) !important;
3800
+ }
3801
+
3802
+ .gradio-container.tome-expanded-left .tome-page-right,
3803
+ .gradio-container.tome-expanded-right .tome-page-left {
3804
+ opacity: 0.12;
3805
+ pointer-events: none;
3806
+ }
3807
+
3808
+ /* Printed ink — content sits on paper, not in UI cards */
3809
+ .tome-printed-page .current-event,
3810
+ .tome-printed-page .activity-feed,
3811
+ .tome-printed-page .location-panel,
3812
+ .tome-printed-page .book-container,
3813
+ .tome-printed-page .book-entries {
3814
+ background: transparent !important;
3815
+ box-shadow: none !important;
3816
+ border: none !important;
3817
+ border-radius: 0 !important;
3818
+ }
3819
+
3820
+ .tome-printed-page .current-event {
3821
+ padding: 0 0 0.65rem !important;
3822
+ margin: 0 0 0.5rem !important;
3823
+ border-bottom: 1px solid rgba(98, 62, 28, 0.22) !important;
3824
+ }
3825
+
3826
+ .tome-printed-page .event-label,
3827
+ .tome-printed-page .event-title,
3828
+ .tome-printed-page .event-text,
3829
+ .tome-printed-page .event-locations {
3830
+ color: #3d2a16 !important;
3831
+ text-shadow: 0 1px 0 rgba(255, 236, 196, 0.2);
3832
+ }
3833
+
3834
+ .tome-printed-page .event-label {
3835
+ color: #7a5a28 !important;
3836
+ font-family: 'IM Fell English', serif !important;
3837
+ }
3838
+
3839
+ .tome-printed-page .activity-feed {
3840
+ padding: 0 !important;
3841
+ margin: 0 !important;
3842
+ overflow: hidden !important;
3843
+ }
3844
+
3845
+ .tome-printed-page .feed-item {
3846
+ padding: 0.4rem 0 !important;
3847
+ border-bottom: 1px solid rgba(98, 62, 28, 0.16) !important;
3848
+ background: transparent !important;
3849
+ }
3850
+
3851
+ .tome-printed-page .feed-content {
3852
+ color: #3d2a16 !important;
3853
+ font-family: 'IM Fell English', serif !important;
3854
+ font-size: 0.88rem !important;
3855
+ line-height: 1.45 !important;
3856
+ display: -webkit-box;
3857
+ -webkit-line-clamp: 2;
3858
+ -webkit-box-orient: vertical;
3859
+ overflow: hidden;
3860
+ }
3861
+
3862
+ .tome-printed-page .feed-day {
3863
+ color: #8a6424 !important;
3864
+ font-family: 'Cinzel', serif !important;
3865
+ }
3866
+
3867
+ .tome-printed-page #world-map,
3868
+ .tome-printed-page .realm-map {
3869
+ flex: 1 1 auto;
3870
+ min-height: 0;
3871
+ max-height: 360px;
3872
+ width: 100% !important;
3873
+ border: 1px solid rgba(98, 62, 28, 0.28) !important;
3874
+ border-radius: 2px !important;
3875
+ box-shadow: inset 0 0 18px rgba(58, 36, 14, 0.12) !important;
3876
+ background: rgba(12, 18, 14, 0.92) !important;
3877
+ }
3878
+
3879
+ .tome-printed-page .location-panel {
3880
+ padding: 0.45rem 0 0 !important;
3881
+ margin: 0 !important;
3882
+ font-family: 'IM Fell English', serif !important;
3883
+ font-style: italic;
3884
+ font-size: 0.82rem !important;
3885
+ color: #5c4228 !important;
3886
+ }
3887
+
3888
+ .tome-printed-page .location-panel * {
3889
+ color: #5c4228 !important;
3890
+ }
3891
+
3892
+ .tome-book-index .tome-printed-heading {
3893
+ margin-bottom: 0.75rem;
3894
+ padding-bottom: 0.65rem;
3895
+ border-bottom: 1px solid rgba(98, 62, 28, 0.22);
3896
+ }
3897
+
3898
+ .tome-book-index .book-title {
3899
+ margin: 0 0 0.35rem;
3900
+ font-size: 1.35rem !important;
3901
+ color: #7a5a28 !important;
3902
+ }
3903
+
3904
+ .tome-book-index .book-subtitle {
3905
+ margin: 0;
3906
+ font-family: 'IM Fell English', serif;
3907
+ font-style: italic;
3908
+ font-size: 0.88rem;
3909
+ color: #5c4228;
3910
+ }
3911
+
3912
+ .tome-printed-page .book-entries {
3913
+ column-count: 1 !important;
3914
+ column-gap: 0 !important;
3915
+ overflow: hidden !important;
3916
+ max-height: 100%;
3917
+ }
3918
+
3919
+ .tome-printed-page .book-entry {
3920
+ break-inside: avoid;
3921
+ margin-bottom: 0.55rem !important;
3922
+ padding-bottom: 0.45rem !important;
3923
+ border-bottom: 1px solid rgba(98, 62, 28, 0.14) !important;
3924
+ background: transparent !important;
3925
+ }
3926
+
3927
+ .tome-printed-page .entry-content {
3928
+ font-family: 'IM Fell English', serif !important;
3929
+ font-size: 0.86rem !important;
3930
+ line-height: 1.45 !important;
3931
+ display: -webkit-box;
3932
+ -webkit-line-clamp: 3;
3933
+ -webkit-box-orient: vertical;
3934
+ overflow: hidden;
3935
+ }
3936
+
3937
+ .tome-printed-page .book-day-header {
3938
+ font-family: 'Cinzel', serif !important;
3939
+ color: #8a6424 !important;
3940
+ margin: 0.35rem 0 !important;
3941
+ }
3942
+
3943
+ /* Aged spread binding — worn parchment across both pages */
3944
+ .realm-tabs [role="tabpanel"] {
3945
+ background:
3946
+ radial-gradient(ellipse 130px 100px at 3% 92%, rgba(72, 44, 18, 0.1) 0%, transparent 62%),
3947
+ radial-gradient(ellipse 120px 95px at 97% 8%, rgba(58, 34, 14, 0.09) 0%, transparent 60%),
3948
+ linear-gradient(90deg,
3949
+ #e8d8b4 0%,
3950
+ #e2d0a6 44.5%,
3951
+ #c9a87a 48.8%,
3952
+ #8e6840 49.8%,
3953
+ #6b4a2c 50%,
3954
+ #8e6840 50.2%,
3955
+ #c9a87a 51.2%,
3956
+ #e2d0a6 55.5%,
3957
+ #ebe2c6 100%) !important;
3958
+ }
3959
+
3960
+ .realm-tabs [role="tabpanel"]::-webkit-scrollbar {
3961
+ display: none !important;
3962
+ width: 0 !important;
3963
+ height: 0 !important;
3964
+ }
3965
+
3966
+ .tome-muted {
3967
+ font-family: 'IM Fell English', serif;
3968
+ font-style: italic;
3969
+ font-size: 0.82rem;
3970
+ color: #5c4228;
3971
+ }