Surn commited on
Commit
8bd908f
·
1 Parent(s): 23ac082

v0.2.36 Add user-specific features and UI enhancements

Browse files

Updated the version to 0.2.36. Introduced `user_id` and `subscription_level` attributes in `GameResult` and `GameState` to support user-specific functionality. Expanded `game_mode` options to include "easy."

Enhanced the UI with improved styling and layout adjustments:
- Styled the guess input box and added placeholder text.
- Increased font sizes in various components for better readability.
- Adjusted dialog and footer layouts.

Added conditional rendering for subscription-based features, such as difficulty and timer display. Updated CSS selectors and styles for better maintainability.

README.md CHANGED
@@ -19,21 +19,17 @@ tags:
19
 
20
  > **This project is used by [huggingface.co](https://huggingface.co/spaces/Surn/BattleWords) as a demonstration of interactive word games in Python.**
21
 
22
- **Current Version:** 0.2.35
23
- **Last Updated:** 2026-01-29
24
 
25
  BattleWords is a vocabulary learning game inspired by classic Battleship mechanics. The objective is to discover hidden words on a grid, earning points for strategic guessing before all letters are revealed.
26
 
27
  ## Recent Changes
28
- - version 0.2.35
29
- - New spinner implementation for loading states
30
- - Updated graphics and improved visual polish
31
- - Favicon added for browser tab branding
32
- - Leaderboard navigation moved to footer menu (not sidebar)
33
- - Game over dialog integrates leaderboard submission and displays qualification results
34
- - Leaderboard page routing uses query parameters and custom navigation links
35
- - Footer navigation links to Leaderboard, Play, and Settings pages
36
- - Minimal documentation and UI updates for these changes
37
 
38
 
39
  ## Features
@@ -213,7 +209,13 @@ CRYPTO_PK= # Reserved for future signing
213
  - Personal high scores sidebar with filtering
214
  - Player statistics tracking (games played, averages, bests)
215
 
216
- - version 0.2.34
 
 
 
 
 
 
217
  - New spinner implementation for loading states
218
  - Updated graphics and improved visual polish
219
  - Favicon added for browser tab branding
 
19
 
20
  > **This project is used by [huggingface.co](https://huggingface.co/spaces/Surn/BattleWords) as a demonstration of interactive word games in Python.**
21
 
22
+ **Current Version:** 0.2.36
23
+ **Last Updated:** 2026-02-01
24
 
25
  BattleWords is a vocabulary learning game inspired by classic Battleship mechanics. The objective is to discover hidden words on a grid, earning points for strategic guessing before all letters are revealed.
26
 
27
  ## Recent Changes
28
+ - version 0.2.36
29
+ - Added `user_id` and `subscription_level` to game state/results for user-specific features
30
+ - Expanded `game_mode` options (includes `easy`)
31
+ - UI polish: improved guess input styling (placeholder, typography) and layout tweaks (dialog/footer)
32
+ - Conditional rendering for subscription-based features (difficulty and timer display)
 
 
 
 
33
 
34
 
35
  ## Features
 
209
  - Personal high scores sidebar with filtering
210
  - Player statistics tracking (games played, averages, bests)
211
 
212
+ - version 0.2.36
213
+ - Added `user_id` and `subscription_level` to game state/results for user-specific features
214
+ - Expanded `game_mode` options (includes `easy`)
215
+ - UI polish: improved guess input styling (placeholder, typography) and layout tweaks (dialog/footer)
216
+ - Conditional rendering for subscription-based features (difficulty and timer display)
217
+
218
+ - version 0.2.35
219
  - New spinner implementation for loading states
220
  - Updated graphics and improved visual polish
221
  - Favicon added for browser tab branding
battlewords/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "0.2.35"
2
  __all__ = ["models", "generator", "logic", "ui", "game_storage", "modules"]
 
1
+ __version__ = "0.2.36"
2
  __all__ = ["models", "generator", "logic", "ui", "game_storage", "modules"]
battlewords/local_storage.py CHANGED
@@ -65,6 +65,9 @@ class GameResult:
65
  words_found: List[str]
66
  completed_at: str
67
  player_name: Optional[str] = None
 
 
 
68
 
69
  def to_dict(self) -> Dict[str, Any]:
70
  return asdict(self)
 
65
  words_found: List[str]
66
  completed_at: str
67
  player_name: Optional[str] = None
68
+ user_id: Optional[str] = None
69
+ subscription_level: Optional[int] = 0
70
+
71
 
72
  def to_dict(self) -> Dict[str, Any]:
73
  return asdict(self)
battlewords/models.py CHANGED
@@ -96,7 +96,9 @@ class GameState:
96
  score: int
97
  last_action: str
98
  can_guess: bool
99
- game_mode: Literal["classic", "too easy"] = "classic"
100
  points_by_word: Dict[str, int] = field(default_factory=dict)
101
  start_time: Optional[datetime] = None
102
- end_time: Optional[datetime] = None
 
 
 
96
  score: int
97
  last_action: str
98
  can_guess: bool
99
+ game_mode: Literal["classic", "easy", "too easy"] = "classic"
100
  points_by_word: Dict[str, int] = field(default_factory=dict)
101
  start_time: Optional[datetime] = None
102
+ end_time: Optional[datetime] = None
103
+ user_id: Optional[str] = None
104
+ subscription_level : Optional[int] = 0
battlewords/settings_page.py CHANGED
@@ -26,6 +26,9 @@ _PERSISTED_SETTING_KEYS: tuple[str, ...] = (
26
  "effects_volume",
27
  "enable_sound_effects",
28
  "music_track_path",
 
 
 
29
  )
30
 
31
 
 
26
  "effects_volume",
27
  "enable_sound_effects",
28
  "music_track_path",
29
+ "user_id",
30
+ "subscription_level",
31
+ "player_name",
32
  )
33
 
34
 
battlewords/ui.py CHANGED
@@ -769,6 +769,37 @@ def _render_guess_form(state: GameState):
769
  st.markdown(
770
  """
771
  <style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
772
  /* Grey-out the guess textbox when disabled */
773
  .st-key-guess_input input:disabled {
774
  background-color: #ffffff66 !important;
@@ -781,7 +812,7 @@ def _render_guess_form(state: GameState):
781
  border-color: #ffffff !important;
782
  }
783
  .bw-incorrect-guesses {
784
- font-size: 0.7rem;
785
  color: #ff9999;
786
  margin-top: -10px;
787
  font-style: italic;
@@ -833,6 +864,9 @@ def _render_guess_form(state: GameState):
833
  }
834
 
835
  .stForm { padding-bottom: 30px; }
 
 
 
836
 
837
  @media (max-width: 640px) {
838
  .st-emotion-cache-1xwdq91, .st-emotion-cache-1r70o5, {
@@ -850,7 +884,7 @@ def _render_guess_form(state: GameState):
850
  }
851
  .st-emotion-cache-bw9c3d, .st-emotion-cache-1w9nhvb, .st-emotion-cache-bah2wz, .st-emotion-cache-tuze3w, .st-emotion-cache-nwgyir, .st-emotion-cache-1pzr114 {
852
  min-width: unset;
853
- }
854
  }
855
  </style>
856
  """,
@@ -858,7 +892,7 @@ def _render_guess_form(state: GameState):
858
  )
859
 
860
  with st.form("guess_form", width="stretch", clear_on_submit=True):
861
- col1, col2, col3 = st.columns([0.4, 0.25, 0.35], vertical_alignment="bottom")
862
  with col1:
863
  guess_text = st.text_input(
864
  "Your Guess",
@@ -867,7 +901,8 @@ def _render_guess_form(state: GameState):
867
  width=200,
868
  key="guess_input",
869
  help=tooltip_text, # Use Streamlit's built-in help parameter for tooltip
870
- disabled=not state.can_guess
 
871
  )
872
  with col2:
873
  submitted = st.form_submit_button("OK", disabled=not state.can_guess, width=100, key="guess_submit")
@@ -967,7 +1002,7 @@ def _render_score_panel(state: GameState):
967
  .bold-text {{ font-weight: 700; }}
968
  .blue-background {{ background:#1d64c8dd; opacity:0.9; color:#fff; }}
969
  .shiny-border {{ position: relative; padding: 10px; background: #333; color: white; border-radius: 1.25rem; overflow: hidden; }}
970
- .bw-score-panel-container {{ height: 100%; overflow: hidden; text-align:center;}}
971
  .bw-score-panel-container table tbody tr h3 {{display: flex;flex-direction: row;justify-content: space-evenly;flex-wrap: wrap;}}
972
 
973
  table {{ width: 100%; margin: 0 auto; border-collapse: separate; border-spacing: 0; }}
@@ -1074,9 +1109,12 @@ def _game_over_content(state: GameState) -> None:
1074
  # Render difficulty line only if we have a value
1075
  difficulty_html = (
1076
  f'<tr><td colspan=\"3\"><h5 class=\"m-2\">Word list difficulty: {difficulty_value:.2f}</h5></td></tr>'
1077
- if difficulty_value is not None else ""
1078
  )
1079
 
 
 
 
1080
  # Build table body HTML for dialog content
1081
  word_rows = []
1082
  for w in state.puzzle.words:
@@ -1087,14 +1125,14 @@ def _game_over_content(state: GameState) -> None:
1087
  )
1088
 
1089
  table_html = (
1090
- "<table class=\"shiny-border\" style=\"border-radius:0.75rem; overflow:hidden; width:100%; margin:0 auto; border-collapse:separate; border-spacing: 0;\">"
1091
  "<thead><tr>"
1092
  "<th scope=\"col\">Word</th>"
1093
  "<th scope=\"col\">Letters</th>"
1094
  "<th scope=\"col\">Extra</th>"
1095
  "</tr></thead>"
1096
  f"<tbody>{''.join(word_rows)}"
1097
- f"<tr><td colspan=\"3\"><h5 class=\"m-2\">Total: {state.score} <span style='font-size:1.25rem; color:#1d64c8;'>&nbsp;⏱ {timer_str}</span></h5></td></tr>"
1098
  f"{difficulty_html}"
1099
  "</tbody>"
1100
  "</table>"
@@ -1104,12 +1142,14 @@ def _game_over_content(state: GameState) -> None:
1104
  st.markdown(
1105
  """
1106
  <style>
 
1107
  .bw-dialog-container {
1108
  border-radius: 1rem;
1109
  box-shadow: 0 0 32px #1d64c8;
1110
  background: linear-gradient(-45deg, #1d64c8, #ffffff, #1d64c8, #666666);
1111
  color: #fff;
1112
- padding: 16px;
 
1113
  }
1114
  .bw-dialog-header { display:flex; justify-content: space-between; align-items:center; }
1115
  .bw-dialog-title, .st-emotion-cache-11elpad p { margin: 0; font-weight: bold;font-size: 1.5rem;text-align: center;flex: auto;filter:drop-shadow(1px 1px 2px #003);}
@@ -1123,6 +1163,8 @@ def _game_over_content(state: GameState) -> None:
1123
  .st-key-new_game_btn_dialog button, .st-key-close_game_over button {
1124
  height: 50px !important;
1125
  }
 
 
1126
  .st-key-new_game_btn_dialog:hover, .st-key-close_game_over:hover{
1127
  /*background: linear-gradient(-45deg, #1d64c8, #ffffff, #1d64c8, #666666);*/
1128
  background: #1d64c8 !important;
 
769
  st.markdown(
770
  """
771
  <style>
772
+ .st-key-guess_input .stTextInput label .st-emotion-cache-lyi571, .st-key-guess_input .stTextInput label .st-emotion-cache-on6lu2 {
773
+ font-size: 1.2em;
774
+ }
775
+ .st-key-guess_input .stTextInput input {
776
+ font-size: 1.1em;
777
+ }
778
+ /* Placeholder styling for guess textbox */
779
+ .st-key-guess_input .stTextInput input::placeholder {
780
+ font-size: 0.75em;
781
+ font-style: italic;
782
+ opacity: 0.9;
783
+ color: #a1a1aa;
784
+ }
785
+ .st-key-guess_input .stTextInput input::-webkit-input-placeholder {
786
+ font-size: 0.75em;
787
+ font-style: italic;
788
+ opacity: 0.9;
789
+ color: #a1a1aa;
790
+ }
791
+ .st-key-guess_input .stTextInput input:-ms-input-placeholder {
792
+ font-size: 0.75em;
793
+ font-style: italic;
794
+ opacity: 0.9;
795
+ color: #a1a1aa;
796
+ }
797
+ .st-key-guess_input .stTextInput input::-ms-input-placeholder {
798
+ font-size: 0.75em;
799
+ font-style: italic;
800
+ opacity: 0.9;
801
+ color: #a1a1aa;
802
+ }
803
  /* Grey-out the guess textbox when disabled */
804
  .st-key-guess_input input:disabled {
805
  background-color: #ffffff66 !important;
 
812
  border-color: #ffffff !important;
813
  }
814
  .bw-incorrect-guesses {
815
+ font-size: 0.8rem;
816
  color: #ff9999;
817
  margin-top: -10px;
818
  font-style: italic;
 
864
  }
865
 
866
  .stForm { padding-bottom: 30px; }
867
+
868
+ .stColumn .st-emotion-cache-6023h2 {margin-top:0px;}
869
+ /*.stColumn .st-emotion-cache-1w9nhvb { display:none;} hide OK BUTTON col spacing under form */
870
 
871
  @media (max-width: 640px) {
872
  .st-emotion-cache-1xwdq91, .st-emotion-cache-1r70o5, {
 
884
  }
885
  .st-emotion-cache-bw9c3d, .st-emotion-cache-1w9nhvb, .st-emotion-cache-bah2wz, .st-emotion-cache-tuze3w, .st-emotion-cache-nwgyir, .st-emotion-cache-1pzr114 {
886
  min-width: unset;
887
+ }
888
  }
889
  </style>
890
  """,
 
892
  )
893
 
894
  with st.form("guess_form", width="stretch", clear_on_submit=True):
895
+ col1, col2, col3 = st.columns([0.525, 0.025, 0.5], vertical_alignment="bottom")
896
  with col1:
897
  guess_text = st.text_input(
898
  "Your Guess",
 
901
  width=200,
902
  key="guess_input",
903
  help=tooltip_text, # Use Streamlit's built-in help parameter for tooltip
904
+ disabled=not state.can_guess,
905
+ placeholder="Enter guess",
906
  )
907
  with col2:
908
  submitted = st.form_submit_button("OK", disabled=not state.can_guess, width=100, key="guess_submit")
 
1002
  .bold-text {{ font-weight: 700; }}
1003
  .blue-background {{ background:#1d64c8dd; opacity:0.9; color:#fff; }}
1004
  .shiny-border {{ position: relative; padding: 10px; background: #333; color: white; border-radius: 1.25rem; overflow: hidden; }}
1005
+ .bw-score-panel-container {{ height: 100%; overflow: hidden; text-align:center; font-size: 1.1em;}}
1006
  .bw-score-panel-container table tbody tr h3 {{display: flex;flex-direction: row;justify-content: space-evenly;flex-wrap: wrap;}}
1007
 
1008
  table {{ width: 100%; margin: 0 auto; border-collapse: separate; border-spacing: 0; }}
 
1109
  # Render difficulty line only if we have a value
1110
  difficulty_html = (
1111
  f'<tr><td colspan=\"3\"><h5 class=\"m-2\">Word list difficulty: {difficulty_value:.2f}</h5></td></tr>'
1112
+ if difficulty_value is not None and st.session_state.get("subscription_level", 0) > 0 else ""
1113
  )
1114
 
1115
+ timer_html = (f'<span style="font-size:1.25rem; color:#1d64c8;">&nbsp;⏱ {timer_str}</span>'
1116
+ if st.session_state.get("subscription_level", 0) > 0 else "")
1117
+
1118
  # Build table body HTML for dialog content
1119
  word_rows = []
1120
  for w in state.puzzle.words:
 
1125
  )
1126
 
1127
  table_html = (
1128
+ "<table class=\"shiny-border word-table\" style=\"border-radius:0.75rem; overflow:hidden; width:100%; margin:0 auto; border-collapse:separate; border-spacing: 0;\">"
1129
  "<thead><tr>"
1130
  "<th scope=\"col\">Word</th>"
1131
  "<th scope=\"col\">Letters</th>"
1132
  "<th scope=\"col\">Extra</th>"
1133
  "</tr></thead>"
1134
  f"<tbody>{''.join(word_rows)}"
1135
+ f"<tr><td colspan=\"3\"><h5 class=\"m-2\">Total: {state.score} {timer_html}</h5></td></tr>"
1136
  f"{difficulty_html}"
1137
  "</tbody>"
1138
  "</table>"
 
1142
  st.markdown(
1143
  """
1144
  <style>
1145
+ .stDialog div.st-e1 { padding: 1.5rem 1.5rem 0.1rem !important;}
1146
  .bw-dialog-container {
1147
  border-radius: 1rem;
1148
  box-shadow: 0 0 32px #1d64c8;
1149
  background: linear-gradient(-45deg, #1d64c8, #ffffff, #1d64c8, #666666);
1150
  color: #fff;
1151
+ padding: 0px;
1152
+ font-size: 1.1em;
1153
  }
1154
  .bw-dialog-header { display:flex; justify-content: space-between; align-items:center; }
1155
  .bw-dialog-title, .st-emotion-cache-11elpad p { margin: 0; font-weight: bold;font-size: 1.5rem;text-align: center;flex: auto;filter:drop-shadow(1px 1px 2px #003);}
 
1163
  .st-key-new_game_btn_dialog button, .st-key-close_game_over button {
1164
  height: 50px !important;
1165
  }
1166
+ .word-table h5,.st-emotion-cache-16vm6ba h5 {font-size: 1.5rem !important; padding: 4px;}
1167
+ .st-key-close_game_over button p {font-size:1.4em;}
1168
  .st-key-new_game_btn_dialog:hover, .st-key-close_game_over:hover{
1169
  /*background: linear-gradient(-45deg, #1d64c8, #ffffff, #1d64c8, #666666);*/
1170
  background: #1d64c8 !important;
battlewords/ui_helpers.py CHANGED
@@ -410,11 +410,11 @@ def inject_styles() -> None:
410
  .stHeading {
411
  margin-bottom: -1.2rem !important;
412
  margin-top: -1.5rem !important;
413
- # font-size: 1.75rem !important; /* Title */
414
- line-height: 1.1 !important;
415
  }
416
  #subtitle {
417
- font-size: 16px !important; /* Subheader */
418
  margin-top: .1rem !important;
419
  }
420
  /* Base grid cell visuals */
@@ -463,9 +463,9 @@ def inject_styles() -> None:
463
  /* div[data-testid="stButton"] button { max-width: 100%; aspect-ratio: 1 / 1; border-radius: 0; background: #1d64c8; color: #ffffff; font-weight: 700; padding: 0.5rem 0.75rem; min-height: 2.5rem; min-width: 2.5rem;} */
464
  .st-key-new_game_btn, .st-key-sort_wordlist_btn, .st-key-filter_wordlist_btn, .st-key-settings_save_apply_btn, .st-key-settings_discard_btn { margin: 0 auto; aspect-ratio: unset; z-index:9999;}
465
  .st-key-new_game_btn > div[data-testid="stButton"] button, .st-key-sort_wordlist_btn > div[data-testid="stButton"] button, .st-key-filter_wordlist_btn > div[data-testid="stButton"] button, .st-key-settings_save_apply_btn > div[data-testid="stButton"] button, .st-key-settings_discard_btn > div[data-testid="stButton"] button { aspect-ratio: unset; text-align:center; height: auto; width: 160px; border-radius:8px;}
466
- .stVerticalBlock .st-emotion-cache-tn0cau.e1wguzas3 div:first-child { display: none; }
467
- .stVerticalBlock .st-emotion-cache-tn0cau.e1wguzas3 div:nth-child(2) { display: none; }
468
- .stVerticalBlock .st-emotion-cache-tn0cau.e1wguzas3 div:nth-child(3) { display: none; }
469
  .st-emotion-cache-18kf3ut.e1wguzas41 { display: none; }
470
 
471
  .bw-fadein-prep {visibility: hidden !important;}
@@ -491,7 +491,7 @@ def inject_styles() -> None:
491
  .st-key-new_game_btn > div[data-testid="stButton"] button, .st-key-sort_wordlist_btn > div[data-testid="stButton"] button { min-height: calc(100% + 20px) !important;}
492
  .st-emotion-cache-ckafi0 { gap:0.1rem !important; min-height: calc(100% + 20px) !important; aspect-ratio: 1 / 1; width: auto !important; flex: 1 1 auto !important; min-width: 100% !important; max-width: 100% !important;}
493
  /*.st-emotion-cache-1n6tfoc { aspect-ratio: 1 / 1; min-height: calc(100% + 20px) !important;}*/
494
- .st-emotion-cache-1n6tfoc::before { min-height: calc(100% + 20px) !important; }
495
 
496
  }
497
  div[data-testid="stElementToolbarButtonContainer"], button[data-testid="stBaseButton-elementToolbar"], button[data-testid="stBaseButton-elementToolbar"]:hover {
@@ -632,7 +632,7 @@ def render_footer(current_page: str = "play") -> None:
632
  color: #d7faff;
633
  text-decoration: none;
634
  font-weight: 600;
635
- font-size: 0.85rem;
636
  padding: 0.4rem 0.8rem;
637
  border-radius: 0.5rem;
638
  background: rgba(29, 100, 200, 0.3);
@@ -650,7 +650,7 @@ def render_footer(current_page: str = "play") -> None:
650
  border-color: rgba(32, 212, 108, 0.5);
651
  }}
652
  .stMainBlockContainer {{
653
- padding-bottom: 70px !important;
654
  }}
655
  </style>
656
  <div class="bw-footer">
 
410
  .stHeading {
411
  margin-bottom: -1.2rem !important;
412
  margin-top: -1.5rem !important;
413
+ # font-size: 1.8rem !important; /* Title */
414
+ line-height: 1.15 !important;
415
  }
416
  #subtitle {
417
+ font-size: 18px !important; /* Subheader */
418
  margin-top: .1rem !important;
419
  }
420
  /* Base grid cell visuals */
 
463
  /* div[data-testid="stButton"] button { max-width: 100%; aspect-ratio: 1 / 1; border-radius: 0; background: #1d64c8; color: #ffffff; font-weight: 700; padding: 0.5rem 0.75rem; min-height: 2.5rem; min-width: 2.5rem;} */
464
  .st-key-new_game_btn, .st-key-sort_wordlist_btn, .st-key-filter_wordlist_btn, .st-key-settings_save_apply_btn, .st-key-settings_discard_btn { margin: 0 auto; aspect-ratio: unset; z-index:9999;}
465
  .st-key-new_game_btn > div[data-testid="stButton"] button, .st-key-sort_wordlist_btn > div[data-testid="stButton"] button, .st-key-filter_wordlist_btn > div[data-testid="stButton"] button, .st-key-settings_save_apply_btn > div[data-testid="stButton"] button, .st-key-settings_discard_btn > div[data-testid="stButton"] button { aspect-ratio: unset; text-align:center; height: auto; width: 160px; border-radius:8px;}
466
+ .stVerticalBlock .st-emotion-cache-tn0cau.e1wguzas3 div.element-container:first-child { display: none; }
467
+ .stVerticalBlock.st-emotion-cache-tn0cau.e1wguzas3 div.element-container:nth-child(2), .stVerticalBlock.st-emotion-cache-tn0cau.ek2vi383 div.element-container:nth-child(2) { display: none; }
468
+ .stVerticalBlock.st-emotion-cache-tn0cau.e1wguzas3 div.element-container:nth-child(3), .stVerticalBlock.st-emotion-cache-tn0cau.ek2vi383 div.element-container:nth-child(3) { display: none; }
469
  .st-emotion-cache-18kf3ut.e1wguzas41 { display: none; }
470
 
471
  .bw-fadein-prep {visibility: hidden !important;}
 
491
  .st-key-new_game_btn > div[data-testid="stButton"] button, .st-key-sort_wordlist_btn > div[data-testid="stButton"] button { min-height: calc(100% + 20px) !important;}
492
  .st-emotion-cache-ckafi0 { gap:0.1rem !important; min-height: calc(100% + 20px) !important; aspect-ratio: 1 / 1; width: auto !important; flex: 1 1 auto !important; min-width: 100% !important; max-width: 100% !important;}
493
  /*.st-emotion-cache-1n6tfoc { aspect-ratio: 1 / 1; min-height: calc(100% + 20px) !important;}*/
494
+ .st-emotion-cache-1n6tfoc::before { min-height: calc(80% + 20px) !important; }
495
 
496
  }
497
  div[data-testid="stElementToolbarButtonContainer"], button[data-testid="stBaseButton-elementToolbar"], button[data-testid="stBaseButton-elementToolbar"]:hover {
 
632
  color: #d7faff;
633
  text-decoration: none;
634
  font-weight: 600;
635
+ font-size: 0.9rem;
636
  padding: 0.4rem 0.8rem;
637
  border-radius: 0.5rem;
638
  background: rgba(29, 100, 200, 0.3);
 
650
  border-color: rgba(32, 212, 108, 0.5);
651
  }}
652
  .stMainBlockContainer {{
653
+ padding-bottom: 60px !important;
654
  }}
655
  </style>
656
  <div class="bw-footer">
claude.md CHANGED
@@ -3,8 +3,8 @@
3
  ## Project Overview
4
  BattleWords is a vocabulary learning game inspired by Battleship mechanics, built with Streamlit and Python 3.12. Players reveal cells on a 12x12 grid to discover hidden words and earn points for strategic guessing.
5
 
6
- **Current Version:** 0.2.35 (Stable - Spinner, Graphics, Favicon, UI Navigation)
7
- **Last Updated:** 2026-01-29
8
  **Next Version:** 0.3.0 (In Development - Wrdler improvements)
9
  **Repository:** https://github.com/Oncorporation/BattleWords.git
10
  **Live Demo:** https://huggingface.co/spaces/Surn/BattleWords
@@ -16,10 +16,17 @@ The detailed porting checklist lives in `specs/upgrade.mdx`.
16
  ## Recent Changes
17
  See `README.md` for the canonical release notes: [Recent Changes](README.md#recent-changes)
18
 
 
 
 
 
 
 
19
  - v0.2.35
20
  - New spinner implementation for loading states
21
  - Updated graphics and improved visual polish
22
  - Favicon added for browser tab branding
 
23
  - Leaderboard navigation moved to footer menu (not sidebar)
24
  - Game over dialog integrates leaderboard submission and displays qualification results
25
  - Leaderboard page routing uses query parameters and custom navigation links
 
3
  ## Project Overview
4
  BattleWords is a vocabulary learning game inspired by Battleship mechanics, built with Streamlit and Python 3.12. Players reveal cells on a 12x12 grid to discover hidden words and earn points for strategic guessing.
5
 
6
+ **Current Version:** 0.2.36 (Stable - User Features, UI Enhancements)
7
+ **Last Updated:** 2026-02-01
8
  **Next Version:** 0.3.0 (In Development - Wrdler improvements)
9
  **Repository:** https://github.com/Oncorporation/BattleWords.git
10
  **Live Demo:** https://huggingface.co/spaces/Surn/BattleWords
 
16
  ## Recent Changes
17
  See `README.md` for the canonical release notes: [Recent Changes](README.md#recent-changes)
18
 
19
+ - v0.2.36
20
+ - Added `user_id` and `subscription_level` to game state/results for user-specific features
21
+ - Expanded `game_mode` options (includes `easy`)
22
+ - UI polish: improved guess input styling (placeholder, typography) and layout tweaks (dialog/footer)
23
+ - Conditional rendering for subscription-based features (difficulty and timer display)
24
+
25
  - v0.2.35
26
  - New spinner implementation for loading states
27
  - Updated graphics and improved visual polish
28
  - Favicon added for browser tab branding
29
+ - Favicon added for browser tab branding
30
  - Leaderboard navigation moved to footer menu (not sidebar)
31
  - Game over dialog integrates leaderboard submission and displays qualification results
32
  - Leaderboard page routing uses query parameters and custom navigation links
pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
  [project]
2
  name = "battlewords"
3
- version = "0.2.35"
4
  description = "BattleWords vocabulary game with game sharing via shortened game_id URL referencing server-side JSON settings"
5
  readme = "README.md"
6
  requires-python = ">=3.12,<3.13"
 
1
  [project]
2
  name = "battlewords"
3
+ version = "0.2.36"
4
  description = "BattleWords vocabulary game with game sharing via shortened game_id URL referencing server-side JSON settings"
5
  readme = "README.md"
6
  requires-python = ">=3.12,<3.13"
specs/requirements.mdx CHANGED
@@ -1,18 +1,15 @@
1
  # Battlewords: Implementation Requirements
2
 
3
- **Current Version:** 0.2.35
4
- **Last Updated:** 2026-01-29
5
 
6
- ## Recent Changes (v0.2.35)
7
  See `README.md` for the canonical release notes: [Recent Changes](README.md#recent-changes)
8
 
9
- - New spinner implementation for loading states
10
- - Updated graphics and improved visual polish
11
- - Favicon added for browser tab branding
12
- - Leaderboard navigation moved to footer menu
13
- - Game over dialog now integrates leaderboard submission and displays qualification results
14
- - Leaderboard page routing uses query parameters and custom navigation links
15
- - Footer navigation links to Leaderboard, Play, and Settings pages
16
 
17
  This document breaks down the tasks to build Battlewords using the game rules described in `specs.md`. It is organized in phases: a minimal Proof of Concept (POC), a Beta Version (0.5.0), and a Full Version (1.0.0).
18
 
 
1
  # Battlewords: Implementation Requirements
2
 
3
+ **Current Version:** 0.2.36
4
+ **Last Updated:** 2026-02-01
5
 
6
+ ## Recent Changes (v0.2.36)
7
  See `README.md` for the canonical release notes: [Recent Changes](README.md#recent-changes)
8
 
9
+ - Added `user_id` and `subscription_level` to game state/results for user-specific features
10
+ - Expanded `game_mode` options (includes `easy`)
11
+ - UI polish: improved guess input styling (placeholder, typography) and layout tweaks (dialog/footer)
12
+ - Conditional rendering for subscription-based features (difficulty and timer display)
 
 
 
13
 
14
  This document breaks down the tasks to build Battlewords using the game rules described in `specs.md`. It is organized in phases: a minimal Proof of Concept (POC), a Beta Version (0.5.0), and a Full Version (1.0.0).
15
 
specs/specs.mdx CHANGED
@@ -1,7 +1,7 @@
1
  # Battlewords Game Requirements (specs.md)
2
 
3
- **Current Version:** 0.2.35
4
- **Last Updated:** 2026-01-29
5
 
6
  ## Overview
7
  Battlewords is inspired by the classic Battleship game, but uses words instead of ships. The objective is to discover hidden words on a grid, earning points for strategic guessing before all letters are revealed.
@@ -9,16 +9,13 @@ Battlewords is inspired by the classic Battleship game, but uses words instead o
9
  ## Upgrade Checklist
10
  The detailed Wrdler  BattleWords porting checklist is tracked in `specs/upgrade.mdx`.
11
 
12
- ## Recent Changes (v0.2.35)
13
  See `README.md` for the canonical release notes: [Recent Changes](README.md#recent-changes)
14
 
15
- - New spinner implementation for loading states
16
- - Updated graphics and improved visual polish
17
- - Favicon added for browser tab branding
18
- - Leaderboard navigation moved to footer menu
19
- - Game over dialog now integrates leaderboard submission and displays qualification results
20
- - Leaderboard page routing uses query parameters and custom navigation links
21
- - Footer navigation links to Leaderboard, Play, and Settings pages
22
 
23
  ---
24
 
 
1
  # Battlewords Game Requirements (specs.md)
2
 
3
+ **Current Version:** 0.2.36
4
+ **Last Updated:** 2026-02-01
5
 
6
  ## Overview
7
  Battlewords is inspired by the classic Battleship game, but uses words instead of ships. The objective is to discover hidden words on a grid, earning points for strategic guessing before all letters are revealed.
 
9
  ## Upgrade Checklist
10
  The detailed Wrdler  BattleWords porting checklist is tracked in `specs/upgrade.mdx`.
11
 
12
+ ## Recent Changes (v0.2.36)
13
  See `README.md` for the canonical release notes: [Recent Changes](README.md#recent-changes)
14
 
15
+ - Added `user_id` and `subscription_level` to game state/results for user-specific features
16
+ - Expanded `game_mode` options (includes `easy`)
17
+ - UI polish: improved guess input styling (placeholder, typography) and layout tweaks (dialog/footer)
18
+ - Conditional rendering for subscription-based features (difficulty and timer display)
 
 
 
19
 
20
  ---
21