| """Gradio UI assembly for the Alien Obfuscator. |
| |
| This module constructs the Gradio Blocks interface with three tabs (Encrypt, |
| Solve, Challenge) and wires them to the riddle generator, parser, and game |
| engine callbacks. |
| """ |
|
|
| import json |
| from typing import Union |
|
|
| import gradio as gr |
|
|
| from alien_obfuscator.config import ( |
| APP_TITLE, |
| DEFAULT_GAME_DURATION_MINUTES, |
| POINTS_PER_CORRECT, |
| SPEED_BONUS_POINTS, |
| SPEED_BONUS_SECONDS, |
| STREAK_BONUS_POINTS, |
| THEME_LABELS, |
| ) |
| from alien_obfuscator.ui.challenge import ( |
| ChallengeAnswerResult, |
| GameOverResult, |
| generate_challenge_riddle, |
| ) |
| from alien_obfuscator.ui.encrypt import encrypt_message |
| from alien_obfuscator.ui.helpers import on_color_change |
| from alien_obfuscator.ui.solve import check_answer, parse_riddle_card |
| from alien_obfuscator.ui.theme import DISABLED_RADIO_CSS, TIMER_HTML |
|
|
|
|
| def build_ui() -> gr.Blocks: |
| """Construct and return the Gradio Blocks application. |
| |
| Returns |
| ------- |
| gr.Blocks |
| The fully assembled Gradio interface with Fallout themed terminal style. |
| """ |
| with gr.Blocks(title="Alien Obfuscator", head=TIMER_HTML, css=DISABLED_RADIO_CSS) as demo: |
| gr.Markdown(f"# {APP_TITLE}") |
|
|
| with gr.Row(): |
| with gr.Column(scale=3): |
| color_dropdown = gr.Dropdown( |
| choices=["Green", "Blue", "Red", "Light gray", "No Theme"], |
| value="Green", |
| label="[ PARAM: TERMINAL COLOR ]", |
| interactive=True, |
| ) |
| color_dropdown.change( |
| on_color_change, |
| inputs=color_dropdown, |
| outputs=None, |
| js="""(color) => { |
| const root = document.documentElement; |
| const theme = document.getElementById('terminal-theme-css'); |
| if (color === "No Theme") { |
| if (theme) theme.disabled = true; |
| return color; |
| } |
| if (theme) theme.disabled = false; |
| if (color === "Green") { |
| root.style.setProperty('--terminal-color', '#33ff33'); |
| root.style.setProperty('--terminal-color-glow', 'rgba(51, 255, 51, 0.8)'); |
| root.style.setProperty('--terminal-color-glow-low', 'rgba(51, 255, 51, 0.5)'); |
| root.style.setProperty('--terminal-color-glow-container', 'rgba(51, 255, 51, 0.25)'); |
| root.style.setProperty('--terminal-color-glow-container-outer', 'rgba(51, 255, 51, 0.15)'); |
| root.style.setProperty('--terminal-bg-glow', '#0d1f0d'); |
| root.style.setProperty('--terminal-bg', '#030703'); |
| root.style.setProperty('--terminal-placeholder', '#1a5c1a'); |
| root.style.setProperty('--terminal-disabled-bg', '#010301'); |
| root.style.setProperty('--terminal-disabled-color', '#114411'); |
| root.style.setProperty('--terminal-border-dim', '#22aa22'); |
| } else if (color === "Blue") { |
| root.style.setProperty('--terminal-color', '#3399ff'); |
| root.style.setProperty('--terminal-color-glow', 'rgba(51, 153, 255, 0.8)'); |
| root.style.setProperty('--terminal-color-glow-low', 'rgba(51, 153, 255, 0.5)'); |
| root.style.setProperty('--terminal-color-glow-container', 'rgba(51, 153, 255, 0.25)'); |
| root.style.setProperty('--terminal-color-glow-container-outer', 'rgba(51, 153, 255, 0.15)'); |
| root.style.setProperty('--terminal-bg-glow', '#0a1c33'); |
| root.style.setProperty('--terminal-bg', '#02050a'); |
| root.style.setProperty('--terminal-placeholder', '#153e66'); |
| root.style.setProperty('--terminal-disabled-bg', '#000103'); |
| root.style.setProperty('--terminal-disabled-color', '#0f2d4a'); |
| root.style.setProperty('--terminal-border-dim', '#2277aa'); |
| } else if (color === "Red") { |
| root.style.setProperty('--terminal-color', '#ff3333'); |
| root.style.setProperty('--terminal-color-glow', 'rgba(255, 51, 51, 0.8)'); |
| root.style.setProperty('--terminal-color-glow-low', 'rgba(255, 51, 51, 0.5)'); |
| root.style.setProperty('--terminal-color-glow-container', 'rgba(255, 51, 51, 0.25)'); |
| root.style.setProperty('--terminal-color-glow-container-outer', 'rgba(255, 51, 51, 0.15)'); |
| root.style.setProperty('--terminal-bg-glow', '#2b0a0a'); |
| root.style.setProperty('--terminal-bg', '#0a0202'); |
| root.style.setProperty('--terminal-placeholder', '#661515'); |
| root.style.setProperty('--terminal-disabled-bg', '#030000'); |
| root.style.setProperty('--terminal-disabled-color', '#4a0f0f'); |
| root.style.setProperty('--terminal-border-dim', '#aa2222'); |
| } else if (color === "Light gray") { |
| root.style.setProperty('--terminal-color', '#e0e0e0'); |
| root.style.setProperty('--terminal-color-glow', 'rgba(224, 224, 224, 0.8)'); |
| root.style.setProperty('--terminal-color-glow-low', 'rgba(224, 224, 224, 0.5)'); |
| root.style.setProperty('--terminal-color-glow-container', 'rgba(224, 224, 224, 0.25)'); |
| root.style.setProperty('--terminal-color-glow-container-outer', 'rgba(224, 224, 224, 0.15)'); |
| root.style.setProperty('--terminal-bg-glow', '#242424'); |
| root.style.setProperty('--terminal-bg', '#0a0a0a'); |
| root.style.setProperty('--terminal-placeholder', '#5c5c5c'); |
| root.style.setProperty('--terminal-disabled-bg', '#030303'); |
| root.style.setProperty('--terminal-disabled-color', '#444444'); |
| root.style.setProperty('--terminal-border-dim', '#aaaaaa'); |
| } |
| return color; |
| }""", |
| ) |
| with gr.Tabs(): |
| |
| with gr.Tab("Encrypt"): |
| with gr.Row(): |
| with gr.Column(): |
| plaintext_input = gr.Textbox( |
| label="[ INPUT: MESSAGE TO ENCRYPT ]", |
| placeholder="Enter your secret message...", |
| lines=2, |
| max_lines=3, |
| ) |
| theme_dropdown = gr.Dropdown( |
| choices=[(label, key) for key, label in THEME_LABELS.items()], |
| value="greek_myth", |
| label="[ PARAM: THEME SELECT ]", |
| ) |
| encrypt_btn = gr.Button("ENCRYPT") |
|
|
| with gr.Row(visible=False) as encrypt_output_row: |
| with gr.Column(): |
| riddle_card = gr.Textbox( |
| label="Generated Riddle", |
| lines=10, |
| interactive=False, |
| elem_id="riddle-card", |
| ) |
| copy_riddle_btn = gr.Button("Copy Riddle") |
| correct_hint = gr.Textbox( |
| label="[ SECURE DATA: CORRECT ANSWER ]", |
| interactive=False, |
| ) |
| encrypt_error = gr.Textbox( |
| label="[ SYSTEM STATUS ]", |
| interactive=False, |
| visible=False, |
| ) |
| riddle_state = gr.Textbox(visible=False) |
|
|
| def on_encrypt(plain: str, theme: str): |
| card, hint, err, state = encrypt_message(plain, theme) |
| return { |
| encrypt_output_row: gr.update(visible=True), |
| riddle_card: card, |
| correct_hint: hint, |
| encrypt_error: gr.update(value=err, visible=bool(err)), |
| riddle_state: state, |
| } |
|
|
| encrypt_btn.click( |
| on_encrypt, |
| inputs=[plaintext_input, theme_dropdown], |
| outputs=[ |
| encrypt_output_row, |
| riddle_card, |
| correct_hint, |
| encrypt_error, |
| riddle_state, |
| ], |
| ) |
|
|
| copy_riddle_btn.click( |
| fn=None, |
| js="""() => { |
| const ta = document.querySelector('#riddle-card textarea, #riddle-card input'); |
| if (!ta) return; |
| navigator.clipboard.writeText(ta.value).catch(() => { |
| const orig = ta.readOnly; |
| ta.readOnly = false; |
| ta.select(); |
| document.execCommand('copy'); |
| ta.readOnly = orig; |
| ta.blur(); |
| }); |
| }""", |
| ) |
|
|
| |
| with gr.Tab("Solve"): |
| with gr.Row(): |
| with gr.Column(): |
| paste_input = gr.Textbox( |
| label="[ INPUT: PASTE RIDDLE CARD ]", |
| placeholder="Paste the shared riddle here...", |
| lines=10, |
| ) |
| parse_btn = gr.Button("PARSE RIDDLE") |
|
|
| with gr.Row(visible=False) as solve_output_row: |
| with gr.Column(): |
| riddle_display = gr.Textbox( |
| label="[ DECIPHERING: RIDDLE ]", |
| lines=5, |
| interactive=False, |
| ) |
| solve_options = gr.Radio( |
| label="[ SELECT ANSWER ]", |
| choices=[], |
| interactive=True, |
| ) |
| solve_feedback = gr.Textbox( |
| label="[ DECRYPTION RESULT ]", |
| interactive=False, |
| ) |
| solve_reveal = gr.Textbox( |
| label="[ REVEALED ANSWER DATA ]", |
| interactive=False, |
| ) |
| solve_state = gr.Textbox(visible=False) |
| solve_attempts = gr.Textbox( |
| label="[ ATTACK ATTEMPTS ]", |
| interactive=False, |
| visible=False, |
| ) |
| next_btn = gr.Button("> DECRYPT ANOTHER?") |
|
|
| def on_parse(card: str): |
| riddle, opts_json, err, state = parse_riddle_card(card) |
| if err: |
| return { |
| solve_output_row: gr.update(visible=False), |
| solve_feedback: err, |
| } |
| options = json.loads(opts_json) |
| return { |
| solve_output_row: gr.update(visible=True), |
| riddle_display: riddle, |
| solve_options: gr.update( |
| choices=[f"{chr(65 + i)}) {o}" for i, o in enumerate(options)], value=None |
| ), |
| solve_feedback: "", |
| solve_reveal: "", |
| solve_state: state, |
| solve_attempts: "", |
| } |
|
|
| parse_btn.click( |
| on_parse, |
| inputs=paste_input, |
| outputs=[ |
| solve_output_row, |
| riddle_display, |
| solve_options, |
| solve_feedback, |
| solve_reveal, |
| solve_state, |
| solve_attempts, |
| ], |
| ) |
|
|
| def on_answer(selected: Union[str, None], state: str) -> tuple: |
| if not selected: |
| return "", "", state |
| idx = ord(selected.split(")")[0]) - ord("A") |
| fb, rev, new_state = check_answer(idx, state) |
| return fb, rev, new_state |
|
|
| solve_options.change( |
| on_answer, |
| inputs=[solve_options, solve_state], |
| outputs=[solve_feedback, solve_reveal, solve_state], |
| ) |
|
|
| next_btn.click( |
| lambda: { |
| solve_output_row: gr.update(visible=False), |
| paste_input: "", |
| }, |
| outputs=[solve_output_row, paste_input], |
| ) |
|
|
| |
| with gr.Tab("Challenge"): |
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("## CHALLENGE PROTOCOL") |
| gr.Markdown( |
| "Execute decryption sequences. Complete as many nodes as possible before terminal lockout." |
| ) |
| theme_filter = gr.Dropdown( |
| choices=[("All", "All")] |
| + [(label, key) for key, label in THEME_LABELS.items() if key != "surprise"], |
| value="All", |
| label="[ PARAM: THEME FILTER ]", |
| ) |
| start_btn = gr.Button("START GAME") |
|
|
| with gr.Row(visible=False) as game_row: |
| with gr.Column(): |
| timer_display = gr.Textbox( |
| label="[ TIMER: TIME REMAINING ]", |
| value="10:00", |
| interactive=False, |
| elem_id="timer-display", |
| ) |
| score_display = gr.Textbox( |
| label="[ ACCOUNT: SCORE ]", |
| value="0", |
| interactive=False, |
| ) |
| streak_display = gr.Textbox( |
| label="[ STATUS: STREAK ]", |
| value="0", |
| interactive=False, |
| ) |
| challenge_riddle = gr.Textbox( |
| label="[ ACTIVE RIDDLE ]", |
| lines=5, |
| interactive=False, |
| ) |
| challenge_options = gr.Radio( |
| label="[ CHOOSE SOLUTION ]", |
| choices=[], |
| interactive=True, |
| elem_id="challenge-options", |
| ) |
| challenge_feedback = gr.Textbox( |
| label="[ COMMAND FEEDBACK ]", |
| interactive=False, |
| ) |
| challenge_correct = gr.Textbox( |
| label="[ REVEALED SOLUTION ]", |
| interactive=False, |
| visible=False, |
| ) |
| next_challenge_btn = gr.Button("> NEXT RIDDLE") |
| end_game_btn = gr.Button("> END GAME") |
|
|
| with gr.Row(visible=False) as game_over_row: |
| with gr.Column(): |
| final_score = gr.Textbox( |
| label="[ SUMMARY: FINAL SCORE ]", |
| interactive=False, |
| ) |
| new_game_btn = gr.Button("> NEW GAME") |
|
|
| |
| state_bridge = gr.Textbox(visible=False) |
| answer_time_left = gr.Textbox(visible=False) |
|
|
| with gr.Row(visible=False): |
| game_over_trigger = gr.Button( |
| "Game Over", |
| elem_id="game-over-trigger", |
| ) |
|
|
| def on_start_game(theme: str): |
| duration_seconds = DEFAULT_GAME_DURATION_MINUTES * 60 |
| riddle, opts, correct = generate_challenge_riddle(theme) |
| options = json.loads(opts) |
| state = json.dumps( |
| { |
| "score": 0, |
| "streak": 0, |
| "time_left": duration_seconds, |
| "correct_index": int(correct), |
| "theme": theme, |
| "riddle_start_time": duration_seconds, |
| "active": True, |
| } |
| ) |
| return { |
| game_row: gr.update(visible=True), |
| game_over_row: gr.update(visible=False), |
| challenge_riddle: riddle, |
| challenge_options: gr.update( |
| choices=[f"{chr(65 + i)}) {o}" for i, o in enumerate(options)], |
| value=None, |
| interactive=True, |
| ), |
| challenge_feedback: "", |
| challenge_correct: gr.update(visible=False), |
| score_display: "0", |
| streak_display: "0", |
| timer_display: f"{DEFAULT_GAME_DURATION_MINUTES:02d}:00", |
| state_bridge: state, |
| } |
|
|
| def on_game_over(state: str) -> GameOverResult: |
| """Handle game over when the timer expires. |
| |
| This function takes the current JSON-encoded state, marks the game as inactive, |
| sets remaining time to zero, and prepares the UI transition to the game over screen. |
| It also extracts the final score to be displayed. |
| |
| Parameters |
| ---------- |
| state : str |
| JSON-encoded game state. |
| |
| Returns |
| ------- |
| GameOverResult |
| Custom result object containing timer display, game state, final score, and UI visibility |
| updates. |
| """ |
| if not state: |
| return GameOverResult( |
| "00:00", "", gr.update(visible=False), gr.update(visible=True), "0" |
| ) |
| st = json.loads(state) |
| st["active"] = False |
| st["time_left"] = 0 |
| final_score_value = str(st.get("score", 0)) |
| return GameOverResult( |
| "00:00", |
| json.dumps(st), |
| gr.update(visible=False), |
| gr.update(visible=True), |
| gr.update(value=final_score_value), |
| ) |
|
|
| game_over_trigger.click( |
| on_game_over, |
| inputs=[state_bridge], |
| outputs=[ |
| timer_display, |
| state_bridge, |
| game_row, |
| game_over_row, |
| final_score, |
| ], |
| js="""() => { |
| window.stopGameTimer(); |
| return [JSON.stringify(window.readGameState())]; |
| }""", |
| ).then( |
| lambda x: x, |
| inputs=state_bridge, |
| outputs=state_bridge, |
| js="""(state_json) => { |
| try { |
| window.writeGameState(JSON.parse(state_json)); |
| } catch (e) { console.error('writeGameState failed:', e); } |
| return state_json; |
| }""", |
| ) |
|
|
| start_btn.click( |
| on_start_game, |
| inputs=theme_filter, |
| outputs=[ |
| game_row, |
| game_over_row, |
| challenge_riddle, |
| challenge_options, |
| challenge_feedback, |
| challenge_correct, |
| score_display, |
| streak_display, |
| timer_display, |
| state_bridge, |
| ], |
| js=f"""(theme) => {{ |
| window.startGameTimer({DEFAULT_GAME_DURATION_MINUTES * 60}); |
| return theme; |
| }}""", |
| ).then( |
| lambda x: x, |
| inputs=state_bridge, |
| outputs=state_bridge, |
| js="""(state_json) => { |
| try { |
| window.writeGameState(JSON.parse(state_json)); |
| } catch (e) { console.error('writeGameState failed:', e); } |
| return state_json; |
| }""", |
| ) |
|
|
| def on_challenge_answer( |
| selected: Union[str, None], state: str, current_time_left: Union[str, int] |
| ) -> ChallengeAnswerResult: |
| try: |
| current_time_left_int = int(current_time_left) |
| except (ValueError, TypeError): |
| current_time_left_int = 0 |
|
|
| if not selected or not state: |
| return ChallengeAnswerResult("", gr.update(visible=False), state, gr.update(), "", "") |
|
|
| idx = ord(selected.split(")")[0]) - ord("A") |
| st = json.loads(state) |
| correct_idx = st["correct_index"] |
| st["time_left"] = current_time_left_int |
|
|
| if idx == correct_idx: |
| points = POINTS_PER_CORRECT |
| st["streak"] += 1 |
| streak_bonus = st["streak"] * STREAK_BONUS_POINTS |
| speed_bonus = 0 |
| elapsed = ( |
| int(st.get("riddle_start_time", current_time_left_int)) - current_time_left_int |
| ) |
| if elapsed <= SPEED_BONUS_SECONDS: |
| speed_bonus = SPEED_BONUS_POINTS |
| total_points = points + streak_bonus + speed_bonus |
| st["score"] += total_points |
| fb = f"Correct! +{total_points} points ({points} base + {streak_bonus} streak" |
| if speed_bonus: |
| fb += f" + {speed_bonus} speed)" |
| else: |
| fb += ")" |
| return ChallengeAnswerResult( |
| fb, |
| gr.update(value="", visible=False), |
| json.dumps(st), |
| gr.update(interactive=False), |
| str(st["score"]), |
| str(st["streak"]), |
| ) |
| else: |
| st["streak"] = 0 |
| fb = f"Wrong! The answer was {chr(65 + correct_idx)}." |
| reveal = f"Correct: {chr(65 + correct_idx)}" |
| return ChallengeAnswerResult( |
| fb, |
| gr.update(value=reveal, visible=True), |
| json.dumps(st), |
| gr.update(interactive=False), |
| str(st["score"]), |
| str(st["streak"]), |
| ) |
|
|
| challenge_options.select( |
| on_challenge_answer, |
| inputs=[challenge_options, state_bridge, answer_time_left], |
| outputs=[ |
| challenge_feedback, |
| challenge_correct, |
| state_bridge, |
| challenge_options, |
| score_display, |
| streak_display, |
| ], |
| js="""(selected, state, _) => { |
| var remainingSec = window.gameEndTime |
| ? Math.max(0, Math.floor((window.gameEndTime - Date.now()) / 1000)) |
| : 0; |
| return [selected, JSON.stringify(window.readGameState()), remainingSec]; |
| }""", |
| ).then( |
| lambda x: x, |
| inputs=state_bridge, |
| outputs=state_bridge, |
| js="""(state_json) => { |
| try { |
| window.writeGameState(JSON.parse(state_json)); |
| } catch (e) { console.error('writeGameState failed:', e); } |
| var inputs = document.querySelectorAll('#challenge-options input[type="radio"]'); |
| inputs.forEach(function(el) { el.disabled = true; }); |
| return state_json; |
| }""", |
| ) |
|
|
| def on_next_challenge(state: str, current_time_left: Union[str, int]): |
| """Generate the next riddle challenge and update game state. |
| |
| This function parses the current state, generates a new challenge riddle |
| for the current theme, resets/records the starting time for this riddle, |
| and returns the updated options and state. |
| |
| Parameters |
| ---------- |
| state : str |
| JSON-serialized string representing the current game state. |
| current_time_left : Union[str, int] |
| The time remaining in the game. |
| |
| Returns |
| ------- |
| dict |
| Mapping of Gradio components to their updated values. |
| """ |
| try: |
| current_time_left_int = int(current_time_left) |
| except (ValueError, TypeError): |
| current_time_left_int = 0 |
|
|
| if not state: |
| return { |
| challenge_riddle: "ERROR: Game state is missing. Please start a new game.", |
| challenge_options: gr.update(choices=[], value=None, interactive=True), |
| challenge_feedback: "", |
| challenge_correct: gr.update(visible=False), |
| state_bridge: state, |
| } |
| try: |
| st = json.loads(state) |
| except (json.JSONDecodeError, TypeError): |
| return { |
| challenge_riddle: "ERROR: Game state is corrupted. Please start a new game.", |
| challenge_options: gr.update(choices=[], value=None, interactive=True), |
| challenge_feedback: "", |
| challenge_correct: gr.update(visible=False), |
| state_bridge: state, |
| } |
| if not st.get("active", False): |
| return { |
| challenge_riddle: "SESSION EXPIRED: Click START GAME to begin a new session.", |
| challenge_options: gr.update(choices=[], value=None, interactive=True), |
| challenge_feedback: "", |
| challenge_correct: gr.update(visible=False), |
| state_bridge: json.dumps(st), |
| } |
|
|
| st["time_left"] = current_time_left_int |
| try: |
| riddle, opts, correct = generate_challenge_riddle(st["theme"]) |
| options = json.loads(opts) |
| except Exception as exc: |
| return { |
| challenge_riddle: f"Error: {exc}", |
| challenge_options: gr.update(choices=[], value=None, interactive=True), |
| challenge_feedback: "", |
| challenge_correct: gr.update(visible=False), |
| state_bridge: json.dumps(st), |
| } |
| st["correct_index"] = int(correct) |
| st["riddle_start_time"] = current_time_left_int |
| return { |
| challenge_riddle: riddle, |
| challenge_options: gr.update( |
| choices=[f"{chr(65 + i)}) {o}" for i, o in enumerate(options)], |
| value=None, |
| interactive=True, |
| ), |
| challenge_feedback: "", |
| challenge_correct: gr.update(visible=False), |
| state_bridge: json.dumps(st), |
| } |
|
|
| next_challenge_btn.click( |
| on_next_challenge, |
| inputs=[state_bridge, answer_time_left], |
| outputs=[ |
| challenge_riddle, |
| challenge_options, |
| challenge_feedback, |
| challenge_correct, |
| state_bridge, |
| ], |
| js="""(_) => { |
| window.riddleStartTime = Date.now(); |
| var remainingSec = window.gameEndTime |
| ? Math.max(0, Math.floor((window.gameEndTime - Date.now()) / 1000)) |
| : 0; |
| return [JSON.stringify(window.readGameState()), remainingSec]; |
| }""", |
| ).then( |
| lambda x: x, |
| inputs=state_bridge, |
| outputs=state_bridge, |
| js="""(state_json) => { |
| try { |
| window.writeGameState(JSON.parse(state_json)); |
| } catch (e) { console.error('writeGameState failed:', e); } |
| var inputs = document.querySelectorAll('#challenge-options input[type="radio"]'); |
| inputs.forEach(function(el) { el.disabled = false; }); |
| return state_json; |
| }""", |
| ) |
|
|
| end_game_btn.click( |
| on_game_over, |
| inputs=[state_bridge], |
| outputs=[ |
| timer_display, |
| state_bridge, |
| game_row, |
| game_over_row, |
| final_score, |
| ], |
| js="""() => { |
| window.stopGameTimer(); |
| return [JSON.stringify(window.readGameState())]; |
| }""", |
| ).then( |
| lambda x: x, |
| inputs=state_bridge, |
| outputs=state_bridge, |
| js="""(state_json) => { |
| try { |
| window.writeGameState(JSON.parse(state_json)); |
| } catch (e) { console.error('writeGameState failed:', e); } |
| return state_json; |
| }""", |
| ) |
|
|
| new_game_btn.click( |
| lambda: { |
| game_over_row: gr.update(visible=False), |
| theme_filter: "All", |
| }, |
| outputs=[game_over_row, theme_filter], |
| js="""() => { window.stopGameTimer(); }""", |
| ) |
|
|
| |
| with gr.Tab("About"): |
| gr.Markdown( |
| """ |
| ## > SYSTEM SPECIFICATION: ALIEN OBFUSCATOR v1.0 |
| |
| ROBCO INDUSTRIES DEFENSE PROTOCOL (SECURE PORT) |
| |
| ### OPERATION DIRECTIVES |
| 1. **[ ENCRYPT ]** — Input plaintext message and select theme cipher. |
| The cryptographic engine will construct a human-resolvable riddle. |
| 2. **[ SOLVE ]** — Paste target riddle card and process decryption. |
| 3. **[ CHALLENGE ]** — Race against the system timer to solve multiple nodes. |
| |
| ### HARDWARE ARCHITECTURE |
| - User Interface: Gradio Monospace TUI Terminal |
| - Decryption Engine: LLM-powered multi-modal cipher generation |
| - Local Database: Curated public-domain text corpus |
| |
| ### RESOURCE BUDGET |
| - Primary Core: up to 31 Billion parameters |
| - System Allocation: ≤ 32 Billion parameters total |
| """ |
| ) |
|
|
| return demo |
|
|