kavyabhand commited on
Commit
e270790
Β·
verified Β·
1 Parent(s): a7ed409

Deploy Aether Garden application

Browse files
Files changed (3) hide show
  1. app.py +64 -26
  2. ui/relationship_graph.py +14 -10
  3. ui/styles.css +268 -0
app.py CHANGED
@@ -356,7 +356,7 @@ def filter_entities(location_filter, type_filter, status_filter, search):
356
  status=status_filter,
357
  search=search or None,
358
  )
359
- return render_entity_grid(entities)
360
 
361
 
362
  def filter_book(day, entry_type, search):
@@ -637,6 +637,13 @@ def build_app() -> gr.Blocks:
637
  with gr.Blocks(**blocks_kwargs) as demo:
638
  session_id = gr.State(value=str(uuid.uuid4()))
639
 
 
 
 
 
 
 
 
640
  opening_gate = gr.HTML(value=opening_gate_html(), elem_id="realm-opening-host")
641
  presence_bar = gr.HTML(value="", elem_id="presence-bar")
642
  header = gr.HTML(value=get_header_html(), elem_classes=["realm-header-wrap"])
@@ -706,7 +713,6 @@ def build_app() -> gr.Blocks:
706
  with gr.Column(elem_classes=["tome-offpage-store"]):
707
  world_vignette = gr.HTML(value=render_world_vignette())
708
  realm_diorama = gr.HTML(value="")
709
- map_pick = gr.Textbox(visible=False, elem_id="map_location_pick")
710
  with gr.Accordion("How this world works", open=False):
711
  gr.HTML(
712
  '<div class="how-it-works">'
@@ -747,7 +753,6 @@ def build_app() -> gr.Blocks:
747
  container=False,
748
  padding=False,
749
  )
750
- explore_place_pick = gr.Textbox(visible=False, elem_id="explore_place_pick")
751
  wander_btn = gr.Button("✦ Wander at random", variant="secondary", size="sm")
752
 
753
  with gr.Column(scale=1, elem_classes=["tome-page-right", "tome-printed-page", "tome-explore-stage"]):
@@ -761,7 +766,6 @@ def build_app() -> gr.Blocks:
761
  padding=False,
762
  elem_id="explore-diorama-host",
763
  )
764
- portal_pick = gr.Textbox(visible=False, elem_id="portal_location_pick")
765
 
766
  with gr.Column(elem_classes=["tome-offpage-store"]):
767
  places_gallery = gr.Gallery(
@@ -794,7 +798,6 @@ def build_app() -> gr.Blocks:
794
  with gr.Row(elem_classes=["tome-spread-row"]):
795
  with gr.Column(scale=1, elem_classes=["tome-page-left", "tome-printed-page", "tome-book-index"]):
796
  day_tabs = gr.HTML(value=render_day_tabs(_init_book_day))
797
- book_day_pick = gr.Textbox(visible=False, elem_id="book_day_pick")
798
  _day_choices = list(range(max(1, _init_book_day - 14), _init_book_day + 1))
799
  book_day_select = gr.Dropdown(
800
  choices=[(f"Day {d}", d) for d in reversed(_day_choices)],
@@ -849,8 +852,6 @@ def build_app() -> gr.Blocks:
849
  rel_graph = gr.HTML(value=render_relationship_graph())
850
 
851
  with gr.Tab("Souls of the Garden", id="entities"):
852
- entity_pick = gr.Textbox(visible=False, elem_id="entity_pick")
853
-
854
  with gr.Column(elem_classes=["tome-spread-shell"]):
855
  with gr.Row(elem_classes=["tome-spread-row"]):
856
  with gr.Column(scale=1, elem_classes=["tome-page-left", "tome-printed-page", "tome-souls-index"]):
@@ -972,6 +973,16 @@ def build_app() -> gr.Blocks:
972
  _realm_map_pick,
973
  inputs=[map_pick],
974
  outputs=[world_map, location_panel],
 
 
 
 
 
 
 
 
 
 
975
  )
976
 
977
  explore_place_pick.change(
@@ -982,6 +993,16 @@ def build_app() -> gr.Blocks:
982
  lambda pick: int(pick) if pick and str(pick).isdigit() else None,
983
  inputs=[explore_place_pick],
984
  outputs=[explore_loc],
 
 
 
 
 
 
 
 
 
 
985
  )
986
 
987
  portal_pick.change(
@@ -1107,7 +1128,7 @@ def build_app() -> gr.Blocks:
1107
  f.change(
1108
  filter_entities,
1109
  [loc_filter, type_filter, status_filter, entity_search],
1110
- [entity_grid],
1111
  )
1112
 
1113
  entity_pick.change(select_entity_by_id, [entity_pick], [entity_detail])
@@ -1635,29 +1656,38 @@ def build_app() -> gr.Blocks:
1635
  wireTomeNav();
1636
  wirePageExpand();
1637
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1638
  const bind = () => {
1639
  document.querySelectorAll('#world-map [data-location-id]').forEach((node) => {
1640
  if (node.dataset.mapBound) return;
1641
  node.dataset.mapBound = '1';
1642
  node.style.cursor = 'pointer';
1643
- node.addEventListener('click', () => {
 
 
1644
  const id = node.getAttribute('data-location-id');
1645
- const root = document.querySelector('#map_location_pick');
1646
- const input = root && (root.querySelector('textarea') || root.querySelector('input'));
1647
- if (input && id) {
1648
- input.value = id;
1649
- input.dispatchEvent(new Event('input', { bubbles: true }));
1650
- }
1651
  });
1652
  });
1653
  };
1654
  const fireHiddenInput = (id, value) => {
1655
- const root = document.querySelector(id);
1656
- const input = root && (root.querySelector('textarea') || root.querySelector('input'));
1657
- if (input) {
1658
- input.value = value;
1659
- input.dispatchEvent(new Event('input', { bubbles: true }));
1660
- }
1661
  };
1662
 
1663
  const wireClickable = (el, handler) => {
@@ -1680,7 +1710,11 @@ def build_app() -> gr.Blocks:
1680
  document.querySelectorAll('.place-ribbon-item').forEach((btn) => {
1681
  wireClickable(btn, () => {
1682
  const id = btn.getAttribute('data-location-id');
1683
- if (id) fireHiddenInput('#explore_place_pick', id);
 
 
 
 
1684
  });
1685
  });
1686
  document.querySelectorAll('.day-tab').forEach((btn) => {
@@ -1690,12 +1724,16 @@ def build_app() -> gr.Blocks:
1690
  });
1691
  });
1692
  document.querySelectorAll('.entity-grid-card').forEach((card) => {
1693
- if (card.dataset.entityBound) return;
1694
- card.dataset.entityBound = '1';
1695
  card.style.cursor = 'pointer';
 
 
1696
  wireClickable(card, () => {
1697
- const id = card.getAttribute('data-entity-id');
1698
- if (id) fireHiddenInput('#entity_pick', id);
 
 
1699
  });
1700
  });
1701
  document.querySelectorAll('.location-modal-close').forEach((btn) => {
 
356
  status=status_filter,
357
  search=search or None,
358
  )
359
+ return render_entity_grid(entities), render_entity_hologram(entities[0] if entities else None)
360
 
361
 
362
  def filter_book(day, entry_type, search):
 
637
  with gr.Blocks(**blocks_kwargs) as demo:
638
  session_id = gr.State(value=str(uuid.uuid4()))
639
 
640
+ # Hidden pickers at root β€” Gradio won't wire events inside display:none offpage stores
641
+ map_pick = gr.Textbox(visible=False, elem_id="map_location_pick")
642
+ explore_place_pick = gr.Textbox(visible=False, elem_id="explore_place_pick")
643
+ book_day_pick = gr.Textbox(visible=False, elem_id="book_day_pick")
644
+ entity_pick = gr.Textbox(visible=False, elem_id="entity_pick")
645
+ portal_pick = gr.Textbox(visible=False, elem_id="portal_location_pick")
646
+
647
  opening_gate = gr.HTML(value=opening_gate_html(), elem_id="realm-opening-host")
648
  presence_bar = gr.HTML(value="", elem_id="presence-bar")
649
  header = gr.HTML(value=get_header_html(), elem_classes=["realm-header-wrap"])
 
713
  with gr.Column(elem_classes=["tome-offpage-store"]):
714
  world_vignette = gr.HTML(value=render_world_vignette())
715
  realm_diorama = gr.HTML(value="")
 
716
  with gr.Accordion("How this world works", open=False):
717
  gr.HTML(
718
  '<div class="how-it-works">'
 
753
  container=False,
754
  padding=False,
755
  )
 
756
  wander_btn = gr.Button("✦ Wander at random", variant="secondary", size="sm")
757
 
758
  with gr.Column(scale=1, elem_classes=["tome-page-right", "tome-printed-page", "tome-explore-stage"]):
 
766
  padding=False,
767
  elem_id="explore-diorama-host",
768
  )
 
769
 
770
  with gr.Column(elem_classes=["tome-offpage-store"]):
771
  places_gallery = gr.Gallery(
 
798
  with gr.Row(elem_classes=["tome-spread-row"]):
799
  with gr.Column(scale=1, elem_classes=["tome-page-left", "tome-printed-page", "tome-book-index"]):
800
  day_tabs = gr.HTML(value=render_day_tabs(_init_book_day))
 
801
  _day_choices = list(range(max(1, _init_book_day - 14), _init_book_day + 1))
802
  book_day_select = gr.Dropdown(
803
  choices=[(f"Day {d}", d) for d in reversed(_day_choices)],
 
852
  rel_graph = gr.HTML(value=render_relationship_graph())
853
 
854
  with gr.Tab("Souls of the Garden", id="entities"):
 
 
855
  with gr.Column(elem_classes=["tome-spread-shell"]):
856
  with gr.Row(elem_classes=["tome-spread-row"]):
857
  with gr.Column(scale=1, elem_classes=["tome-page-left", "tome-printed-page", "tome-souls-index"]):
 
973
  _realm_map_pick,
974
  inputs=[map_pick],
975
  outputs=[world_map, location_panel],
976
+ ).then(
977
+ None, None, None,
978
+ js="""
979
+ () => {
980
+ setTimeout(() => {
981
+ const modal = document.querySelector('.location-modal-overlay');
982
+ if (modal) modal.classList.add('is-open');
983
+ }, 120);
984
+ }
985
+ """,
986
  )
987
 
988
  explore_place_pick.change(
 
993
  lambda pick: int(pick) if pick and str(pick).isdigit() else None,
994
  inputs=[explore_place_pick],
995
  outputs=[explore_loc],
996
+ ).then(
997
+ None, None, None,
998
+ js="""
999
+ () => {
1000
+ setTimeout(() => {
1001
+ const frame = document.querySelector('.tome-explore-stage .diorama-frame');
1002
+ if (frame) frame.focus();
1003
+ }, 200);
1004
+ }
1005
+ """,
1006
  )
1007
 
1008
  portal_pick.change(
 
1128
  f.change(
1129
  filter_entities,
1130
  [loc_filter, type_filter, status_filter, entity_search],
1131
+ [entity_grid, entity_detail],
1132
  )
1133
 
1134
  entity_pick.change(select_entity_by_id, [entity_pick], [entity_detail])
 
1656
  wireTomeNav();
1657
  wirePageExpand();
1658
 
1659
+ const triggerGradioInput = (elemId, value) => {
1660
+ const root = document.getElementById(elemId) || document.querySelector('#' + elemId);
1661
+ if (!root) return false;
1662
+ const input = root.querySelector('textarea')
1663
+ || root.querySelector('input[type="text"]')
1664
+ || root.querySelector('input:not([type="hidden"])');
1665
+ if (!input) return false;
1666
+ const proto = input.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
1667
+ const setter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
1668
+ if (setter) setter.call(input, String(value));
1669
+ else input.value = String(value);
1670
+ input.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
1671
+ input.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
1672
+ return true;
1673
+ };
1674
+
1675
  const bind = () => {
1676
  document.querySelectorAll('#world-map [data-location-id]').forEach((node) => {
1677
  if (node.dataset.mapBound) return;
1678
  node.dataset.mapBound = '1';
1679
  node.style.cursor = 'pointer';
1680
+ node.addEventListener('click', (e) => {
1681
+ e.preventDefault();
1682
+ e.stopPropagation();
1683
  const id = node.getAttribute('data-location-id');
1684
+ if (id) triggerGradioInput('map_location_pick', id);
 
 
 
 
 
1685
  });
1686
  });
1687
  };
1688
  const fireHiddenInput = (id, value) => {
1689
+ const elemId = (id || '').replace(/^#/, '');
1690
+ triggerGradioInput(elemId, value);
 
 
 
 
1691
  };
1692
 
1693
  const wireClickable = (el, handler) => {
 
1710
  document.querySelectorAll('.place-ribbon-item').forEach((btn) => {
1711
  wireClickable(btn, () => {
1712
  const id = btn.getAttribute('data-location-id');
1713
+ if (!id) return;
1714
+ document.querySelectorAll('.place-ribbon-item').forEach((el) => {
1715
+ el.classList.toggle('is-selected', el.getAttribute('data-location-id') === id);
1716
+ });
1717
+ fireHiddenInput('explore_place_pick', id);
1718
  });
1719
  });
1720
  document.querySelectorAll('.day-tab').forEach((btn) => {
 
1724
  });
1725
  });
1726
  document.querySelectorAll('.entity-grid-card').forEach((card) => {
1727
+ const id = card.getAttribute('data-entity-id');
1728
+ if (!id) return;
1729
  card.style.cursor = 'pointer';
1730
+ if (card.dataset.entityBound === id) return;
1731
+ card.dataset.entityBound = id;
1732
  wireClickable(card, () => {
1733
+ document.querySelectorAll('.entity-grid-card').forEach((el) => {
1734
+ el.classList.toggle('is-selected', el.getAttribute('data-entity-id') === id);
1735
+ });
1736
+ fireHiddenInput('entity_pick', id);
1737
  });
1738
  });
1739
  document.querySelectorAll('.location-modal-close').forEach((btn) => {
ui/relationship_graph.py CHANGED
@@ -264,22 +264,26 @@ def render_bonds_sidebar(rel_type: str = "all") -> str:
264
  f'<span class="rel-legend-item"><i style="background:{c}"></i>{k}</span>'
265
  for k, c in REL_COLORS.items()
266
  )
267
- rel_list = []
268
- for rel in relationships[:10]:
269
  t = TYPE_SHORT.get(rel["relationship_type"], rel["relationship_type"])
270
- desc = (rel.get("description") or "A bond still forming…")[:72]
271
- rel_list.append(
272
- f'<li><b>{rel["entity_a_name"]}</b> ↔ <b>{rel["entity_b_name"]}</b> '
273
- f'Β· {t} β€” <em>{desc}</em></li>'
274
- )
 
 
 
 
275
 
276
  total = len(get_all_relationships())
277
  return f"""
278
  <div class="bonds-sidebar">
279
  <p class="tome-printed-kicker">The Web of Bonds</p>
280
- <p class="bonds-sidebar-desc">{len(relationships)} shown Β· {total} threads total</p>
281
- <div class="rel-legend">{legend}</div>
282
- <ul class="rel-list">{"".join(rel_list)}</ul>
283
  </div>
284
  """
285
 
 
264
  f'<span class="rel-legend-item"><i style="background:{c}"></i>{k}</span>'
265
  for k, c in REL_COLORS.items()
266
  )
267
+ cards = []
268
+ for rel in relationships[:8]:
269
  t = TYPE_SHORT.get(rel["relationship_type"], rel["relationship_type"])
270
+ color = REL_COLORS.get(rel["relationship_type"], "#605848")
271
+ desc = (rel.get("description") or "A bond still forming…")[:56]
272
+ cards.append(f"""
273
+ <div class="bond-card" style="--bond-color:{color}">
274
+ <span class="bond-card-type">{t}</span>
275
+ <p class="bond-card-names">{rel["entity_a_name"]} ↔ {rel["entity_b_name"]}</p>
276
+ <p class="bond-card-desc">{desc}</p>
277
+ </div>
278
+ """)
279
 
280
  total = len(get_all_relationships())
281
  return f"""
282
  <div class="bonds-sidebar">
283
  <p class="tome-printed-kicker">The Web of Bonds</p>
284
+ <p class="bonds-sidebar-desc">{len(relationships)} of {total} threads</p>
285
+ <div class="rel-legend bonds-legend-compact">{legend}</div>
286
+ <div class="bond-cards">{"".join(cards)}</div>
287
  </div>
288
  """
289
 
ui/styles.css CHANGED
@@ -4622,3 +4622,271 @@ body,
4622
  .realm-opening.is-back-visible .realm-prologue-card {
4623
  display: none !important;
4624
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4622
  .realm-opening.is-back-visible .realm-prologue-card {
4623
  display: none !important;
4624
  }
4625
+
4626
+ /* ═══════════════════════════════════════════════════════════════════════════
4627
+ FIX PACK β€” centering, book edges, interactions, readability
4628
+ ═══════════════════════════════════════════════════════════════════════════ */
4629
+
4630
+ #realm-opening-host,
4631
+ #realm-opening-host .realm-opening {
4632
+ position: fixed !important;
4633
+ inset: 0 !important;
4634
+ width: 100vw !important;
4635
+ max-width: 100vw !important;
4636
+ margin: 0 !important;
4637
+ left: 0 !important;
4638
+ right: 0 !important;
4639
+ display: grid !important;
4640
+ place-items: center !important;
4641
+ }
4642
+
4643
+ .realm-opening-inner {
4644
+ width: 100% !important;
4645
+ max-width: 100% !important;
4646
+ display: flex !important;
4647
+ align-items: center !important;
4648
+ justify-content: center !important;
4649
+ margin: 0 auto !important;
4650
+ }
4651
+
4652
+ .realm-book-stage {
4653
+ width: 100%;
4654
+ display: flex !important;
4655
+ align-items: center !important;
4656
+ justify-content: center !important;
4657
+ margin: 0 auto !important;
4658
+ }
4659
+
4660
+ .realm-book-3d {
4661
+ margin: 0 auto !important;
4662
+ left: auto !important;
4663
+ right: auto !important;
4664
+ }
4665
+
4666
+ .realm-book-front .realm-book-spine {
4667
+ left: -10px !important;
4668
+ transform: none !important;
4669
+ top: 6% !important;
4670
+ bottom: 6% !important;
4671
+ width: 14px !important;
4672
+ border-radius: 4px 0 0 4px !important;
4673
+ }
4674
+
4675
+ .realm-cover-front,
4676
+ .realm-cover-back {
4677
+ border: 3px solid rgba(92, 58, 28, 0.75) !important;
4678
+ outline: 1px solid rgba(201, 161, 59, 0.35) !important;
4679
+ outline-offset: 3px;
4680
+ box-shadow:
4681
+ inset 0 2px 0 rgba(255, 240, 198, 0.12),
4682
+ inset 0 -3px 8px rgba(0, 0, 0, 0.35),
4683
+ 0 22px 60px rgba(0, 0, 0, 0.55) !important;
4684
+ }
4685
+
4686
+ /* Book spread β€” leather edge frame (top + sides) */
4687
+ .realm-tabs [role="tabpanel"] {
4688
+ box-shadow:
4689
+ inset 0 14px 20px -6px rgba(48, 28, 14, 0.18),
4690
+ inset 14px 0 18px -8px rgba(72, 44, 18, 0.12),
4691
+ inset -14px 0 18px -8px rgba(72, 44, 18, 0.12),
4692
+ inset 0 -8px 16px rgba(168, 134, 83, 0.1),
4693
+ 0 18px 38px rgba(76, 50, 22, 0.26) !important;
4694
+ }
4695
+
4696
+ /* Page I right β€” distribute content vertically */
4697
+ .tome-page-centered {
4698
+ display: flex !important;
4699
+ flex-direction: column !important;
4700
+ justify-content: space-between !important;
4701
+ gap: 0.4rem !important;
4702
+ min-height: 0 !important;
4703
+ }
4704
+
4705
+ .tome-page-centered .realm-stats-strip,
4706
+ .tome-page-centered .current-event,
4707
+ .tome-page-centered .realm-pulse,
4708
+ .tome-page-centered .activity-feed {
4709
+ flex-shrink: 0;
4710
+ margin-bottom: 0.35rem !important;
4711
+ }
4712
+
4713
+ .tome-page-centered .realm-summon-block,
4714
+ .tome-page-centered .realm-summon-input,
4715
+ .tome-page-centered .seal-summon-btn {
4716
+ flex-shrink: 0;
4717
+ }
4718
+
4719
+ .tome-page-left .realm-map,
4720
+ .tome-page-left #world-map {
4721
+ flex: 1 1 auto;
4722
+ min-height: 200px;
4723
+ max-height: 42%;
4724
+ }
4725
+
4726
+ /* Explore left β€” padding */
4727
+ .tome-explore-index {
4728
+ padding: 0.65rem 0.85rem 0.75rem 0.55rem !important;
4729
+ gap: 0.5rem !important;
4730
+ }
4731
+
4732
+ .tome-explore-index .place-ribbon-item {
4733
+ padding: 0.5rem 0.55rem !important;
4734
+ margin-bottom: 0.15rem;
4735
+ }
4736
+
4737
+ .tome-explore-index .wander-btn,
4738
+ .tome-explore-index button {
4739
+ margin-top: 0.35rem !important;
4740
+ }
4741
+
4742
+ .tome-explore-stage {
4743
+ padding: 0.55rem 0.55rem 0.65rem 0.75rem !important;
4744
+ }
4745
+
4746
+ .tome-explore-stage .diorama-header {
4747
+ display: block !important;
4748
+ padding: 0.55rem 0.75rem !important;
4749
+ }
4750
+
4751
+ .tome-explore-stage .diorama-wrap {
4752
+ min-height: 300px !important;
4753
+ flex: 1 1 auto !important;
4754
+ }
4755
+
4756
+ .tome-explore-stage .diorama-frame {
4757
+ min-height: 280px !important;
4758
+ height: min(46vh, 400px) !important;
4759
+ }
4760
+
4761
+ /* Souls β€” parchment hologram (readable) */
4762
+ .tome-souls-stage .hologram-stage {
4763
+ background: linear-gradient(168deg, #faf3df 0%, #ebe0c4 55%, #dccca8 100%) !important;
4764
+ border: 1px solid rgba(138, 106, 58, 0.45) !important;
4765
+ box-shadow:
4766
+ inset 0 0 24px rgba(88, 56, 24, 0.08),
4767
+ 0 12px 28px rgba(76, 50, 22, 0.18) !important;
4768
+ transform: none !important;
4769
+ color: #3d2814 !important;
4770
+ }
4771
+
4772
+ .tome-souls-stage .hologram-beam {
4773
+ display: none;
4774
+ }
4775
+
4776
+ .tome-souls-stage .hologram-meta {
4777
+ color: #8a6424 !important;
4778
+ }
4779
+
4780
+ .tome-souls-stage .hologram-name {
4781
+ color: #3d2814 !important;
4782
+ font-family: 'Cinzel Decorative', 'Cinzel', serif !important;
4783
+ }
4784
+
4785
+ .tome-souls-stage .hologram-appearance,
4786
+ .tome-souls-stage .hologram-memory,
4787
+ .tome-souls-stage .hologram-goals {
4788
+ color: #4a3420 !important;
4789
+ font-family: 'IM Fell English', serif !important;
4790
+ line-height: 1.5 !important;
4791
+ }
4792
+
4793
+ .tome-souls-stage .hologram-greeting {
4794
+ color: #5c4228 !important;
4795
+ border-left-color: rgba(138, 106, 58, 0.55) !important;
4796
+ }
4797
+
4798
+ .tome-souls-stage .hologram-banner {
4799
+ height: 72px;
4800
+ }
4801
+
4802
+ .tome-souls-index .entity-grid-card.is-selected {
4803
+ outline: 2px solid rgba(138, 106, 58, 0.75);
4804
+ outline-offset: 2px;
4805
+ box-shadow: 0 6px 16px rgba(76, 50, 22, 0.2) !important;
4806
+ }
4807
+
4808
+ .tome-souls-index {
4809
+ padding: 0.55rem 0.75rem 0.65rem 0.55rem !important;
4810
+ }
4811
+
4812
+ .tome-souls-index .entity-grid {
4813
+ max-height: 360px !important;
4814
+ }
4815
+
4816
+ /* Bonds β€” compact cards */
4817
+ .bond-cards {
4818
+ display: flex;
4819
+ flex-direction: column;
4820
+ gap: 0.4rem;
4821
+ overflow-y: auto;
4822
+ max-height: 380px;
4823
+ padding-right: 0.2rem;
4824
+ }
4825
+
4826
+ .bond-card {
4827
+ padding: 0.45rem 0.55rem;
4828
+ border-radius: 8px;
4829
+ border-left: 3px solid var(--bond-color, #8a6a3a);
4830
+ background: rgba(255, 249, 232, 0.72);
4831
+ border: 1px solid rgba(138, 106, 58, 0.22);
4832
+ border-left-width: 3px;
4833
+ border-left-color: var(--bond-color, #8a6a3a);
4834
+ }
4835
+
4836
+ .bond-card-type {
4837
+ font-family: 'Cinzel', serif;
4838
+ font-size: 0.62rem;
4839
+ letter-spacing: 0.1em;
4840
+ text-transform: uppercase;
4841
+ color: var(--bond-color, #8a6a3a);
4842
+ }
4843
+
4844
+ .bond-card-names {
4845
+ margin: 0.15rem 0;
4846
+ font-family: 'Cinzel', serif;
4847
+ font-size: 0.78rem;
4848
+ color: #3d2814;
4849
+ line-height: 1.25;
4850
+ }
4851
+
4852
+ .bond-card-desc {
4853
+ margin: 0;
4854
+ font-family: 'IM Fell English', serif;
4855
+ font-style: italic;
4856
+ font-size: 0.76rem;
4857
+ color: #5c4228;
4858
+ line-height: 1.35;
4859
+ }
4860
+
4861
+ .bonds-legend-compact {
4862
+ margin-bottom: 0.35rem;
4863
+ }
4864
+
4865
+ .bonds-legend-compact .rel-legend-item {
4866
+ font-size: 0.68rem;
4867
+ }
4868
+
4869
+ .bonds-sidebar .rel-list {
4870
+ display: none;
4871
+ }
4872
+
4873
+ /* Book day view β€” fill right page */
4874
+ .tome-book-index + .column .book-day-view,
4875
+ .tome-page-right .book-day-view {
4876
+ min-height: 200px;
4877
+ }
4878
+
4879
+ .tome-page-right .book-entries {
4880
+ overflow-y: auto !important;
4881
+ max-height: 420px !important;
4882
+ }
4883
+
4884
+ .tome-book-index {
4885
+ padding: 0.55rem 0.75rem !important;
4886
+ gap: 0.45rem !important;
4887
+ }
4888
+
4889
+ /* Location modal always on top */
4890
+ .location-modal-overlay.is-open {
4891
+ z-index: 9900 !important;
4892
+ }