1111# Global variable to track phrases.txt modification time.
1212last_phrases_mtime = os .path .getmtime ("phrases.txt" )
1313
14+ FREE_SPACE_TEXT = "FREE MEAT"
15+ FREE_SPACE_TEXT_COLOR = "#FF7f33"
16+
1417# --- New: Color constants and font ---
1518TILE_CLICKED_BG_COLOR = "#3b82f6" # Blue background for clicked tiles
1619TILE_CLICKED_TEXT_COLOR = "white"
1720TILE_UNCLICKED_BG_COLOR = "#facc15" # Yellow background for unclicked tiles
1821TILE_UNCLICKED_TEXT_COLOR = "black"
19- FREE_MEAT_TEXT_COLOR = "#FF7f33" # Color for the FREE MEAT tile
22+
2023
2124HOME_BG_COLOR = "#100079" # Background for home page
2225STREAM_BG_COLOR = "#00FF00" # Background for stream page
2326HEADER_TEXT_COLOR = "#0CB2B3" # Color for header text
2427
25- FONT_FAMILY = "'Super Carnival', sans-serif"
26-
27- # New constants for line-height adjustments
28- LINE_HEIGHT_SHORT = "1.5em"
29- LINE_HEIGHT_DEFAULT = "1em"
28+ HEADER_FONT_FAMILY = "'Super Carnival', sans-serif"
29+ BOARD_TILE_FONT = "Inter" # Set the desired Google Font for board tiles
30+ BOARD_TILE_FONT_WEIGHT = "600" # Default weight for board tiles; adjust as needed.
31+ BOARD_TILE_FONT_STYLE = "normal" # Default font style for board tiles; for example, "normal" or "italic"
3032
3133def get_line_style_for_lines (line_count : int , default_text_color : str ) -> str :
3234 """
@@ -39,10 +41,10 @@ def get_line_style_for_lines(line_count: int, default_text_color: str) -> str:
3941 elif line_count == 2 :
4042 lh = "1.2em" # Slightly reduced spacing for two lines.
4143 elif line_count == 3 :
42- lh = "0.75em " # Even tighter spacing for three lines.
44+ lh = "0.9em " # Even tighter spacing for three lines.
4345 else :
4446 lh = "0.7em" # For four or more lines.
45- return f"font-family: { FONT_FAMILY } ; padding: 0; margin: 0; color: { default_text_color } ; line-height: { lh } ;"
47+ return f"font-family: ' { BOARD_TILE_FONT } ', sans-serif; font-weight: { BOARD_TILE_FONT_WEIGHT } ; font-style: { BOARD_TILE_FONT_STYLE } ; padding: 0; margin: 0; color: { default_text_color } ; line-height: { lh } ;"
4648
4749# Read phrases from a text file and convert them to uppercase.
4850with open ("phrases.txt" , "r" ) as f :
@@ -54,7 +56,7 @@ def get_line_style_for_lines(line_count: int, default_text_color: str) -> str:
5456
5557# Shuffle and create the 5x5 board:
5658shuffled_phrases = random .sample (phrases , 24 ) # Random but fixed order per day
57- shuffled_phrases .insert (12 , "FREE MEAT" ) # Center slot
59+ shuffled_phrases .insert (12 , FREE_SPACE_TEXT ) # Center slot
5860board = [shuffled_phrases [i :i + 5 ] for i in range (0 , 25 , 5 )]
5961
6062# Track clicked tiles and store chip references
@@ -166,23 +168,32 @@ def create_bingo_board():
166168 for row_idx , row in enumerate (board ):
167169 for col_idx , phrase in enumerate (row ):
168170 # Create a clickable card for this cell with reduced padding and centered content. Added 'relative' class for icon overlay.
169- card = ui .card ().classes ("relative p-2 bg-yellow-500 rounded-lg w-full h-full flex items-center justify-center" ).style ("cursor: pointer;" )
171+ card = ui .card ().classes ("relative p-2 rounded-lg w-full h-full flex items-center justify-center" ).style ("cursor: pointer;" )
172+ labels_list = [] # initialize list for storing label metadata
170173 with card :
171174 with ui .column ().classes ("flex flex-col items-center justify-center gap-0 w-full" ):
172175 # Set text color: free meat gets #FF7f33, others black
173- default_text_color = FREE_MEAT_TEXT_COLOR if phrase .upper () == "FREE MEAT" else TILE_UNCLICKED_TEXT_COLOR
176+ default_text_color = FREE_SPACE_TEXT_COLOR if phrase .upper () == FREE_SPACE_TEXT else TILE_UNCLICKED_TEXT_COLOR
174177 lines = split_phrase_into_lines (phrase )
175178 line_count = len (lines )
176179 for line in lines :
177180 with ui .row ().classes ("w-full" ):
178181 if len (line ) <= 3 :
179- ui . label ( line ). classes ( "fit-text-small text-center select-none" ). style ( get_line_style_for_lines ( line_count , default_text_color ))
182+ base_class = "fit-text-small text-center select-none"
180183 else :
181- ui .label (line ).classes ("fit-text text-center select-none" ).style (get_line_style_for_lines (line_count , default_text_color ))
184+ base_class = "fit-text text-center select-none"
185+ # Create the label with initial inline style using get_line_style_for_lines().
186+ lbl = ui .label (line ).classes (base_class ).style (get_line_style_for_lines (line_count , default_text_color ))
187+ # Instead of just storing the label, store its metadata.
188+ labels_list .append ({
189+ "ref" : lbl ,
190+ "base_classes" : base_class ,
191+ "base_style" : get_line_style_for_lines (line_count , default_text_color )
192+ })
182193
183194 tile_buttons [(row_idx , col_idx )] = card
184195
185- if phrase .upper () == "FREE MEAT" :
196+ if phrase .upper () == FREE_SPACE_TEXT :
186197 clicked_tiles .add ((row_idx , col_idx ))
187198 card .style ("color: #FF7f33; border: none;" )
188199 else :
@@ -200,6 +211,7 @@ def toggle_tile(row, col):
200211 else :
201212 logging .debug (f"Tile at { key } clicked" )
202213 clicked_tiles .add (key )
214+
203215 check_winner ()
204216 sync_board_state ()
205217
@@ -246,24 +258,37 @@ def admin_checkbox_change(e, key):
246258 clicked_tiles .discard (key )
247259 sync_board_state ()
248260
261+ def create_board_view (background_color : str , is_global : bool ):
262+ """
263+ Creates a board page view based on the background color and a flag.
264+ If is_global is True, the board uses global variables (home page)
265+ otherwise it uses a local board (stream page).
266+ """
267+ setup_head (background_color )
268+ # Create the board container.
269+ container = ui .element ("div" ).classes ("flex justify-center items-center w-full" )
270+ if is_global :
271+ global home_board_container , tile_buttons
272+ home_board_container = container
273+ tile_buttons = {} # Start with an empty dictionary.
274+ build_board (home_board_container , tile_buttons , toggle_tile )
275+ # Add timers for synchronizing the global board.
276+ ui .timer (0.1 , sync_board_state )
277+ ui .timer (1 , check_phrases_file_change )
278+ else :
279+ local_tile_buttons = build_board (container , {}, toggle_tile )
280+ ui .timer (0.1 , lambda : update_tile_styles (local_tile_buttons ))
281+ # Display the seed beneath the board.
282+ with ui .element ("div" ).classes ("w-full mt-4" ):
283+ ui .label (f"Seed: { today_seed } " ).classes ("text-md text-gray-300 text-center" )
284+
249285@ui .page ("/" )
250286def home_page ():
251- # Set up NiceGUI page and head elements
252- setup_head (HOME_BG_COLOR )
253-
254- global home_board_container , tile_buttons
255- home_board_container = ui .element ("div" ).classes ("flex justify-center items-center w-full" )
256- tile_buttons = {} # Start with an empty dictionary.
257- build_board (home_board_container , tile_buttons , toggle_tile )
287+ create_board_view (HOME_BG_COLOR , True )
258288
259- # Add a timer that calls sync_board_state every 0.1 second to push state updates to all clients
260- ui .timer (0.1 , sync_board_state )
261-
262- # Add a timer to check if phrases.txt has changed
263- ui .timer (1 , check_phrases_file_change )
264-
265- with ui .element ("div" ).classes ("w-full mt-4" ):
266- ui .label (f"Seed: { today_seed } " ).classes ("text-md text-gray-300 text-center" )
289+ @ui .page ("/stream" )
290+ def stream_page ():
291+ create_board_view (STREAM_BG_COLOR , False )
267292
268293@ui .page ("/admin" )
269294def admin_page ():
@@ -303,27 +328,12 @@ def on_checkbox_change(e, key=key):
303328 build_admin_panel ()
304329 ui .timer (0.1 , sync_admin_checkboxes )
305330
306- @ui .page ("/stream" )
307- def stream_page ():
308- # Set up NiceGUI page and head elements
309- setup_head (STREAM_BG_COLOR )
310-
311-
312-
313- # Build the board using the common function (use a local dictionary here)
314- local_tile_buttons = build_board (ui .element ("div" ).classes ("flex justify-center items-center w-full" ), {}, toggle_tile )
315-
316- # Timer to update ONLY the stream view's board (using its local_tile_buttons)
317- ui .timer (0.1 , lambda : update_tile_styles (local_tile_buttons ))
318-
319- with ui .element ("div" ).classes ("w-full mt-4" ):
320- ui .label (f"Seed: { today_seed } " ).classes ("text-md text-gray-300 text-center" )
321-
322331def setup_head (background_color : str ):
323332 """
324333 Set up common head elements: fonts, fitty JS, and background color.
325334 """
326335 ui .add_head_html (f'<link href="https://fonts.cdnfonts.com/css/super-carnival" rel="stylesheet">' )
336+ ui .add_head_html (f'<link href="https://fonts.googleapis.com/css2?family={ BOARD_TILE_FONT .replace (" " , "+" )} &display=swap" rel="stylesheet">' )
327337 ui .add_head_html ('<script src="https://cdn.jsdelivr.net/npm/fitty@2.3.6/dist/fitty.min.js"></script>' )
328338 ui .add_head_html (f'<style>body {{ background-color: { background_color } ; }}</style>' )
329339 ui .add_head_html ("""<script>
@@ -341,7 +351,7 @@ def setup_head(background_color: str):
341351
342352 # Use full width with padding so the header spans edge-to-edge
343353 with ui .element ("div" ).classes ("w-full" ):
344- ui .label ("COMMIT !BINGO" ).classes ("fit-header text-center" ).style (f"font-family: { FONT_FAMILY } ; color: { HEADER_TEXT_COLOR } ;" )
354+ ui .label ("COMMIT !BINGO" ).classes ("fit-header text-center" ).style (f"font-family: { HEADER_FONT_FAMILY } ; color: { HEADER_TEXT_COLOR } ;" )
345355
346356def build_board (parent , tile_buttons_dict : dict , on_tile_click ):
347357 """
@@ -355,40 +365,77 @@ def build_board(parent, tile_buttons_dict: dict, on_tile_click):
355365 for row_idx , row in enumerate (board ):
356366 for col_idx , phrase in enumerate (row ):
357367 card = ui .card ().classes (
358- "relative p-2 bg-yellow-500 rounded-lg w-full h-full flex items-center justify-center"
368+ "relative p-2 rounded-lg w-full h-full flex items-center justify-center"
359369 ).style ("cursor: pointer;" )
370+ labels_list = [] # initialize list for storing label metadata
360371 with card :
361372 with ui .column ().classes ("flex flex-col items-center justify-center gap-0 w-full" ):
362- default_text_color = FREE_MEAT_TEXT_COLOR if phrase .upper () == "FREE MEAT" else TILE_UNCLICKED_TEXT_COLOR
373+ # Set text color: FREE MEAT uses FREE_MEAT_TEXT_COLOR, others use TILE_UNCLICKED_TEXT_COLOR
374+ default_text_color = FREE_SPACE_TEXT_COLOR if phrase .upper () == FREE_SPACE_TEXT else TILE_UNCLICKED_TEXT_COLOR
363375 lines = split_phrase_into_lines (phrase )
364376 line_count = len (lines )
365377 for line in lines :
366378 with ui .row ().classes ("w-full" ):
367379 if len (line ) <= 3 :
368- ui . label ( line ). classes ( "fit-text-small text-center select-none" ). style ( get_line_style_for_lines ( line_count , default_text_color ))
380+ base_class = "fit-text-small text-center select-none"
369381 else :
370- ui .label (line ).classes ("fit-text text-center select-none" ).style (get_line_style_for_lines (line_count , default_text_color ))
371- tile_buttons_dict [(row_idx , col_idx )] = card
372- if phrase .upper () == "FREE MEAT" :
382+ base_class = "fit-text text-center select-none"
383+ # Create the label with initial inline style using get_line_style_for_lines().
384+ lbl = ui .label (line ).classes (base_class ).style (get_line_style_for_lines (line_count , default_text_color ))
385+ # Instead of just storing the label, store its metadata.
386+ labels_list .append ({
387+ "ref" : lbl ,
388+ "base_classes" : base_class ,
389+ "base_style" : get_line_style_for_lines (line_count , default_text_color )
390+ })
391+ # Store both the card and its labels in the global tile_buttons dict.
392+ tile_buttons_dict [(row_idx , col_idx )] = {"card" : card , "labels" : labels_list }
393+ if phrase .upper () == FREE_SPACE_TEXT :
373394 clicked_tiles .add ((row_idx , col_idx ))
374- card .style (f"color: { FREE_MEAT_TEXT_COLOR } ; border: none;" )
395+ card .style (f"color: { FREE_SPACE_TEXT_COLOR } ; border: none;" )
375396 else :
376397 card .on ("click" , lambda e , r = row_idx , c = col_idx : on_tile_click (r , c ))
377398 return tile_buttons_dict
378399
379400def update_tile_styles (tile_buttons_dict : dict ):
380401 """
381- Update styles for each tile in the given dictionary based on the global clicked_tiles.
402+ Update styles for each tile and its text labels based on the global clicked_tiles.
382403 """
383- for (r , c ), card in tile_buttons_dict .items ():
384- if board [r ][c ].upper () == "FREE MEAT" :
404+ for (r , c ), tile in tile_buttons_dict .items ():
405+ # tile is a dict with keys "card" and "labels"
406+ phrase = board [r ][c ]
407+ if phrase .upper () == FREE_SPACE_TEXT :
385408 continue
409+
386410 if (r , c ) in clicked_tiles :
387- new_style = f"background-color: { TILE_CLICKED_BG_COLOR } ; color: { TILE_CLICKED_TEXT_COLOR } ; border: none;"
411+ new_card_style = f"background-color: { TILE_CLICKED_BG_COLOR } ; color: { TILE_CLICKED_TEXT_COLOR } ; border: none;"
412+ new_label_color = TILE_CLICKED_TEXT_COLOR
388413 else :
389- new_style = f"background-color: { TILE_UNCLICKED_BG_COLOR } ; color: { TILE_UNCLICKED_TEXT_COLOR } ; border: none;"
390- card .style (new_style )
391- card .update ()
414+ new_card_style = f"background-color: { TILE_UNCLICKED_BG_COLOR } ; color: { TILE_UNCLICKED_TEXT_COLOR } ; border: none;"
415+ new_label_color = TILE_UNCLICKED_TEXT_COLOR
416+
417+ # Update the card style.
418+ tile ["card" ].style (new_card_style )
419+ tile ["card" ].update ()
420+
421+ # Recalculate the line count for the current phrase.
422+ lines = split_phrase_into_lines (phrase )
423+ line_count = len (lines )
424+ # Recalculate label style based on the new color.
425+ new_label_style = get_line_style_for_lines (line_count , new_label_color )
426+
427+ # Update all label elements for this tile.
428+ for label_info in tile ["labels" ]:
429+ lbl = label_info ["ref" ]
430+ # Reapply the stored base classes.
431+ lbl .classes (label_info ["base_classes" ])
432+ # Update inline style (which may now use a new color due to tile click state).
433+ lbl .style (new_label_style )
434+ lbl .update ()
435+ ui .run_javascript (
436+ "fitty('.fit-text', { multiLine: true, minSize: 10, maxSize: 1000 });"
437+ "fitty('.fit-text-small', { multiLine: true, minSize: 10, maxSize: 72 });"
438+ )
392439
393440def check_phrases_file_change ():
394441 """
@@ -416,6 +463,10 @@ def check_phrases_file_change():
416463 tile_buttons .clear () # Clear global dictionary.
417464 build_board (home_board_container , tile_buttons , toggle_tile )
418465 home_board_container .update () # Force update so new styles are applied immediately.
466+ ui .run_javascript (
467+ "fitty('.fit-text', { multiLine: true, minSize: 10, maxSize: 1000 });"
468+ "fitty('.fit-text-small', { multiLine: true, minSize: 10, maxSize: 72 });"
469+ )
419470
420471# Run the NiceGUI app
421472ui .run (port = 8080 , title = "Commit Bingo" , dark = False )
0 commit comments