Skip to content

Commit 68b09d1

Browse files
standardize design
Signed-off-by: Jonathan Irvin <djfoxyslpr@gmail.com>
1 parent dcddbb3 commit 68b09d1

1 file changed

Lines changed: 162 additions & 97 deletions

File tree

main.py

Lines changed: 162 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,39 @@
77
# Set up logging
88
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
99

10+
# --- New: Color constants and font ---
11+
TILE_CLICKED_BG_COLOR = "#3b82f6" # Blue background for clicked tiles
12+
TILE_CLICKED_TEXT_COLOR = "white"
13+
TILE_UNCLICKED_BG_COLOR = "#facc15" # Yellow background for unclicked tiles
14+
TILE_UNCLICKED_TEXT_COLOR = "black"
15+
FREE_MEAT_TEXT_COLOR = "#FF7f33" # Color for the FREE MEAT tile
16+
17+
HOME_BG_COLOR = "#100079" # Background for home page
18+
STREAM_BG_COLOR = "#00FF00" # Background for stream page
19+
HEADER_TEXT_COLOR = "#0CB2B3" # Color for header text
20+
21+
FONT_FAMILY = "'Super Carnival', sans-serif"
22+
23+
# New constants for line-height adjustments
24+
LINE_HEIGHT_SHORT = "1.5em"
25+
LINE_HEIGHT_DEFAULT = "1em"
26+
27+
def get_line_style_for_lines(line_count: int, default_text_color: str) -> str:
28+
"""
29+
Return a complete style string with an adjusted line-height based on the number of lines
30+
that resulted from splitting the phrase.
31+
Fewer lines (i.e. unsplit phrases) get a higher line-height, while more lines get a lower one.
32+
"""
33+
if line_count == 1:
34+
lh = "1.5em" # More spacing for a single line.
35+
elif line_count == 2:
36+
lh = "1.2em" # Slightly reduced spacing for two lines.
37+
elif line_count == 3:
38+
lh = "0.9em" # Even tighter spacing for three lines.
39+
else:
40+
lh = "0.9em" # For four or more lines.
41+
return f"font-family: {FONT_FAMILY}; padding: 0; margin: 0; color: {default_text_color}; line-height: {lh};"
42+
1043
# Read phrases from a text file and convert them to uppercase.
1144
with open("phrases.txt", "r") as f:
1245
phrases = [line.strip().upper() for line in f if line.strip()]
@@ -37,6 +70,8 @@ def split_phrase_into_lines(phrase: str) -> list:
3770
return [words[0]]
3871
elif len(words) == 2:
3972
return [words[0], words[1]]
73+
elif len(words) == 3:
74+
return [words[0], words[1], words[2]]
4075
else:
4176
mid = round(len(words) / 2)
4277
return [" ".join(words[:mid]), " ".join(words[mid:])]
@@ -52,21 +87,22 @@ def create_bingo_board():
5287
for row_idx, row in enumerate(board):
5388
for col_idx, phrase in enumerate(row):
5489
# Create a clickable card for this cell with reduced padding and centered content. Added 'relative' class for icon overlay.
55-
card = ui.card().classes("relative p-2 bg-yellow-500 hover:bg-yellow-400 rounded-lg w-full h-full flex items-center justify-center").style("cursor: pointer;")
90+
card = ui.card().classes("relative p-2 bg-yellow-500 rounded-lg w-full h-full flex items-center justify-center").style("cursor: pointer;")
5691
with card:
5792
with ui.column().classes("flex flex-col items-center justify-center gap-0 w-full"):
5893
# Set text color: free meat gets #FF7f33, others black
59-
default_text_color = "#FF7f33" if phrase.upper() == "FREE MEAT" else "black"
60-
for line in split_phrase_into_lines(phrase):
61-
ui.label(line).classes("fit-text text-center select-none").style(f"font-family: 'Super Carnival', sans-serif; padding: 0; margin: 0; color: {default_text_color};")
62-
# After the column, add a hidden check icon overlay
63-
icon = ui.icon("check").classes("absolute inset-0 m-auto text-3xl text-white").style("display: none;")
94+
default_text_color = FREE_MEAT_TEXT_COLOR if phrase.upper() == "FREE MEAT" else TILE_UNCLICKED_TEXT_COLOR
95+
lines = split_phrase_into_lines(phrase)
96+
line_count = len(lines)
97+
for line in lines:
98+
with ui.row().classes("w-full"):
99+
ui.label(line).classes("fit-text text-center select-none").style(get_line_style_for_lines(line_count, default_text_color))
100+
64101
tile_buttons[(row_idx, col_idx)] = card
65-
tile_icons[(row_idx, col_idx)] = icon
102+
66103
if phrase.upper() == "FREE MEAT":
67104
clicked_tiles.add((row_idx, col_idx))
68-
card.style("color: #FF7f33; background: #facc15; border: none;")
69-
icon.style("display: block;")
105+
card.style("color: #FF7f33; border: none;")
70106
else:
71107
card.on("click", lambda e, r=row_idx, c=col_idx: toggle_tile(r, c))
72108

@@ -95,37 +131,19 @@ def check_winner():
95131
ui.notify("BINGO!", color="green", duration=5)
96132

97133
def sync_board_state():
98-
# Sync the styles of each tile according to the global clicked_tiles
99-
for r in range(5):
100-
for c in range(5):
101-
key = (r, c)
102-
# Skip updating the FREE MEAT cell
103-
if board[r][c].upper() == "FREE MEAT":
104-
continue
105-
if key in clicked_tiles:
106-
tile_buttons[key].style("background: #22c55e; color: white; border: none;")
107-
tile_icons[key].style("display: block;")
108-
else:
109-
tile_buttons[key].style("background: #facc15; border: none; color: black;")
110-
tile_icons[key].style("display: none;")
111-
tile_buttons[key].update()
112-
tile_icons[key].update()
113-
# --- New: update admin panel checkboxes when board state syncs ---
134+
update_tile_styles(tile_buttons)
114135
sync_admin_checkboxes()
115136
update_admin_visibility()
116137

117138
def sync_admin_checkboxes():
118139
"""
119-
Sync the values in both copies of each admin checkbox with the global clicked_tiles.
140+
Sync the value in each admin panel checkbox with the global clicked_tiles.
120141
"""
121142
for key, chks in admin_checkboxes.items():
122143
new_value = key in clicked_tiles
123-
if chks["left"].value != new_value:
124-
chks["left"].value = new_value
125-
chks["left"].update()
126-
if chks["right"].value != new_value:
127-
chks["right"].value = new_value
128-
chks["right"].update()
144+
if "single" in chks and chks["single"].value != new_value:
145+
chks["single"].value = new_value
146+
chks["single"].update()
129147

130148
def update_admin_visibility():
131149
"""
@@ -134,11 +152,9 @@ def update_admin_visibility():
134152
- right copy is visible only when checked (value True)
135153
"""
136154
for key, chks in admin_checkboxes.items():
137-
val = chks["left"].value # both copies hold the same value
138-
chks["left"].set_visibility(not val) # show left box only when unchecked
139-
chks["right"].set_visibility(val) # show right box only when checked
140-
chks["left"].update()
141-
chks["right"].update()
155+
val = chks["single"].value # both copies hold the same value
156+
chks["single"].set_visibility(not val) # show left box only when unchecked
157+
chks["single"].update()
142158

143159
def admin_checkbox_change(e, key):
144160
# When a checkbox in the admin page is toggled, update the global clicked_tiles
@@ -148,33 +164,19 @@ def admin_checkbox_change(e, key):
148164
clicked_tiles.discard(key)
149165
sync_board_state()
150166

151-
# Set up NiceGUI page and head elements
152-
ui.page("/")
153-
ui.add_head_html('<link href="https://fonts.cdnfonts.com/css/super-carnival" rel="stylesheet">')
154-
ui.add_head_html('<script src="https://cdn.jsdelivr.net/npm/fitty@2.3.6/dist/fitty.min.js"></script>')
155-
ui.add_head_html('<style>body { background-color: #100079; }</style>')
156-
157-
with ui.element("div").classes("w-full max-w-3xl mx-auto"):
158-
ui.label("COMMIT BINGO!").classes("fit-header text-center").style("font-family: 'Super Carnival', sans-serif; color: #0CB2B3;")
159-
160-
create_bingo_board()
167+
@ui.page("/")
168+
def home_page():
169+
# Set up NiceGUI page and head elements
170+
setup_head(HOME_BG_COLOR)
161171

162-
# Add a timer that calls sync_board_state every 1 second to push state updates to all clients
163-
ui.timer(1, sync_board_state)
172+
global tile_buttons
173+
tile_buttons = build_board(ui.element("div").classes("flex justify-center items-center w-full"), tile_buttons, toggle_tile)
164174

165-
with ui.element("div").classes("w-full mt-4"):
166-
ui.label(f"Seed: {today_seed}").classes("text-md text-gray-300 text-center")
175+
# Add a timer that calls sync_board_state every 1 second to push state updates to all clients
176+
ui.timer(0.1, sync_board_state)
167177

168-
ui.add_head_html("""<script>
169-
document.addEventListener('DOMContentLoaded', () => {
170-
fitty('.fit-text', { multiLine: true, maxSize: 100 });
171-
fitty('.fit-header', { multiLine: true, maxSize: 200 });
172-
});
173-
window.addEventListener('resize', () => {
174-
fitty('.fit-text', { multiLine: true, maxSize: 100 });
175-
fitty('.fit-header', { multiLine: true, maxSize: 200 });
176-
});
177-
</script>""")
178+
with ui.element("div").classes("w-full mt-4"):
179+
ui.label(f"Seed: {today_seed}").classes("text-md text-gray-300 text-center")
178180

179181
@ui.page("/admin")
180182
def admin_page():
@@ -183,55 +185,118 @@ def reset_board():
183185
# Re-add FREE MEAT at the center (position (2,2))
184186
clicked_tiles.add((2, 2))
185187
sync_board_state()
186-
build_admin_panel()
188+
build_admin_panel() # rebuild panel to reflect state changes
189+
187190
with ui.row().classes("zd max-w-xl mx-auto p-4") as container:
188191
ui.label("Admin Panel").classes("text-h4 text-center")
189192
ui.button("Reset Board", on_click=reset_board)
193+
190194
def build_admin_panel():
191195
panel.clear() # clear previous panel content
192196
with panel:
193-
with ui.row():
194-
with ui.column():
195-
ui.label("Uncalled").classes("text-h5 text-center")
196-
# Create left (uncalled) checkboxes inside this column.
197-
for r in range(5):
198-
for c in range(5):
199-
key = (r, c)
200-
phrase = board[r][c]
201-
def on_admin_checkbox_change(e, key=key):
202-
if e.value:
203-
clicked_tiles.add(key)
204-
else:
205-
clicked_tiles.discard(key)
206-
sync_board_state()
207-
update_admin_visibility()
208-
build_admin_panel() # re-render admin panel after change
209-
left_chk = ui.checkbox(phrase, value=(key in clicked_tiles), on_change=on_admin_checkbox_change)
210-
admin_checkboxes.setdefault(key, {})["left"] = left_chk
211-
with ui.column():
212-
ui.label("Called").classes("text-h5 text-center")
213-
# Create right (called) checkboxes inside this column.
214-
for r in range(5):
215-
for c in range(5):
216-
key = (r, c)
217-
phrase = board[r][c]
218-
def on_admin_checkbox_change(e, key=key):
197+
with ui.column():
198+
# Single column design: list each tile with a toggle checkbox.
199+
for r in range(5):
200+
for c in range(5):
201+
key = (r, c)
202+
phrase = board[r][c]
203+
with ui.row().classes("items-center"):
204+
ui.label(f"{phrase} ({r},{c})").classes("w-3/4")
205+
def on_checkbox_change(e, key=key):
219206
if e.value:
220207
clicked_tiles.add(key)
221208
else:
222209
clicked_tiles.discard(key)
223210
sync_board_state()
224-
update_admin_visibility()
225-
build_admin_panel() # re-render admin panel after change
226-
right_chk = ui.checkbox(phrase, value=(key in clicked_tiles), on_change=on_admin_checkbox_change)
227-
admin_checkboxes.setdefault(key, {})["right"] = right_chk
211+
cb = ui.checkbox("", value=(key in clicked_tiles), on_change=on_checkbox_change)
212+
# Save a single reference to this admin checkbox
213+
admin_checkboxes[key] = {"single": cb}
228214

229-
panel = ui.row() # container for the checkboxes row
215+
panel = ui.column() # container for the admin panel
216+
build_admin_panel()
217+
ui.timer(0.1, sync_admin_checkboxes)
230218

231-
219+
@ui.page("/stream")
220+
def stream_page():
221+
# Set up NiceGUI page and head elements
222+
setup_head(STREAM_BG_COLOR)
232223

233-
234-
build_admin_panel()
235-
ui.timer(1, update_admin_visibility)
224+
225+
226+
# Build the board using the common function (use a local dictionary here)
227+
local_tile_buttons = build_board(ui.element("div").classes("flex justify-center items-center w-full"), {}, toggle_tile)
228+
229+
# Timer to update ONLY the stream view's board (using its local_tile_buttons)
230+
ui.timer(0.1, lambda: update_tile_styles(local_tile_buttons))
231+
232+
with ui.element("div").classes("w-full mt-4"):
233+
ui.label(f"Seed: {today_seed}").classes("text-md text-gray-300 text-center")
234+
235+
def setup_head(background_color: str):
236+
"""
237+
Set up common head elements: fonts, fitty JS, and background color.
238+
"""
239+
ui.add_head_html(f'<link href="https://fonts.cdnfonts.com/css/super-carnival" rel="stylesheet">')
240+
ui.add_head_html('<script src="https://cdn.jsdelivr.net/npm/fitty@2.3.6/dist/fitty.min.js"></script>')
241+
ui.add_head_html(f'<style>body {{ background-color: {background_color}; }}</style>')
242+
ui.add_head_html("""<script>
243+
document.addEventListener('DOMContentLoaded', () => {
244+
fitty('.fit-text', { multiLine: true, minSize: 10, maxSize: 1000 });
245+
fitty('.fit-header', { multiLine: true, minSize: 10, maxSize: 2000 });
246+
});
247+
window.addEventListener('resize', () => {
248+
fitty('.fit-text', { multiLine: true, minSize: 10, maxSize: 1000 });
249+
fitty('.fit-header', { multiLine: true, minSize: 10, maxSize: 2000 });
250+
});
251+
</script>""")
252+
253+
# Use full width with padding so the header spans edge-to-edge
254+
with ui.element("div").classes("w-full"):
255+
ui.label("COMMIT !BINGO").classes("fit-header text-center").style(f"font-family: {FONT_FAMILY}; color: {HEADER_TEXT_COLOR};")
256+
257+
def build_board(parent, tile_buttons_dict: dict, on_tile_click):
258+
"""
259+
Build the common Bingo board in the given parent element.
260+
The resulting tile UI elements are added to tile_buttons_dict.
261+
"""
262+
with parent:
263+
# Use full width and add padding so the board touches the edges with a gap
264+
with ui.element("div").classes("w-full aspect-square p-4"):
265+
with ui.grid(columns=5).classes("gap-2 h-full grid-rows-5"):
266+
for row_idx, row in enumerate(board):
267+
for col_idx, phrase in enumerate(row):
268+
card = ui.card().classes(
269+
"relative p-2 bg-yellow-500 rounded-lg w-full h-full flex items-center justify-center"
270+
).style("cursor: pointer;")
271+
with card:
272+
with ui.column().classes("flex flex-col items-center justify-center gap-0 w-full"):
273+
default_text_color = FREE_MEAT_TEXT_COLOR if phrase.upper() == "FREE MEAT" else TILE_UNCLICKED_TEXT_COLOR
274+
lines = split_phrase_into_lines(phrase)
275+
line_count = len(lines)
276+
for line in lines:
277+
with ui.row().classes("w-full"):
278+
ui.label(line).classes("fit-text text-center select-none").style(get_line_style_for_lines(line_count, default_text_color))
279+
tile_buttons_dict[(row_idx, col_idx)] = card
280+
if phrase.upper() == "FREE MEAT":
281+
clicked_tiles.add((row_idx, col_idx))
282+
card.style(f"color: {FREE_MEAT_TEXT_COLOR}; border: none;")
283+
else:
284+
card.on("click", lambda e, r=row_idx, c=col_idx: on_tile_click(r, c))
285+
return tile_buttons_dict
286+
287+
def update_tile_styles(tile_buttons_dict: dict):
288+
"""
289+
Update styles for each tile in the given dictionary based on the global clicked_tiles.
290+
"""
291+
for (r, c), card in tile_buttons_dict.items():
292+
if board[r][c].upper() == "FREE MEAT":
293+
continue
294+
if (r, c) in clicked_tiles:
295+
new_style = f"background-color: {TILE_CLICKED_BG_COLOR}; color: {TILE_CLICKED_TEXT_COLOR}; border: none;"
296+
else:
297+
new_style = f"background-color: {TILE_UNCLICKED_BG_COLOR}; color: {TILE_UNCLICKED_TEXT_COLOR}; border: none;"
298+
card.style(new_style)
299+
card.update()
236300

301+
# Run the NiceGUI app
237302
ui.run(port=8080, title="Commit Bingo", dark=False)

0 commit comments

Comments
 (0)