| import base64 |
| import pathlib |
|
|
| import gradio as gr |
|
|
| from judge import judge |
| from levels import HOME_ID, LEVEL_ORDER, LEVELS |
|
|
| STATIC = pathlib.Path(__file__).parent / "static" |
|
|
| |
| |
| |
| _SCRATCH = base64.b64encode((STATIC / "pencil-scratch.mp3").read_bytes()).decode() |
|
|
| |
| |
| |
| |
| gr.set_static_paths(paths=[STATIC]) |
|
|
| |
| |
| def _file_route(name: str) -> str: |
| return "gradio_api/file=" + str(STATIC / name) |
|
|
|
|
| |
| |
| |
| |
| def _level_client(lid: str) -> dict: |
| cv = LEVELS[lid].client_value() |
| cv["music"] = _file_route(cv["music"]) if cv["music"] else "" |
| return cv |
|
|
|
|
| |
| |
| |
| GAME = { |
| "levels": [_level_client(lid) for lid in LEVEL_ORDER], |
| "order": LEVEL_ORDER, |
| "home": HOME_ID, |
| "scratchAudio": "data:audio/mpeg;base64," + _SCRATCH, |
| "music": _file_route("Cipher2.mp3"), |
| } |
|
|
| HEAD = """ |
| <script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| <link href="https://fonts.googleapis.com/css2?family=Patrick+Hand&family=Caveat:wght@600;700&display=swap" rel="stylesheet"> |
| """ |
|
|
| |
| |
| |
| |
| |
| |
| BLOCKS_CSS = """ |
| footer { display: none !important; } |
| html, body { height: 100%; overflow: hidden; overscroll-behavior: none; } |
| .gradio-container { padding: 0 !important; max-width: 100% !important; background: #faf8f2 !important; } |
| .gradio-container .main, .gradio-container .wrap { background: #faf8f2 !important; } |
| /* Zero Gradio's own wrapper padding so the game sits flush at the top — else a |
| ~26px gap pushes the board down and clips the bottom hint off mobile screens. */ |
| .gradio-container .main, .html-container { padding: 0 !important; } |
| |
| /* Smoothly fade Gradio's paper chrome to dark in glitch mode (matches the eased |
| filter flip on .sq-root) so the centred column's side-bars don't glare beside |
| the inverted board (the board's paper inverts to ~#05070d). */ |
| .gradio-container, .gradio-container .main, .gradio-container .wrap, |
| .gradio-container .contain, html, body { |
| transition: background 0.85s ease-in-out; |
| } |
| html.sq-glitch-page, |
| html.sq-glitch-page body, |
| html.sq-glitch-page .gradio-container, |
| html.sq-glitch-page .gradio-container .main, |
| html.sq-glitch-page .gradio-container .wrap, |
| html.sq-glitch-page .gradio-container .contain { |
| background: #05070d !important; |
| } |
| /* drifting scanlines over the negative, for that decoded-signal hum */ |
| @keyframes sq-scanline { |
| from { background-position: 0 0; } |
| to { background-position: 0 120px; } |
| } |
| /* the victory title's rainbow sweep (the gradient is 200% wide; slide it one |
| full gradient-width for a seamless loop). Lives here, un-scoped, because the |
| component-nested css_template can't host @keyframes. */ |
| @keyframes sq-rainbow { |
| from { background-position: 0% 50%; } |
| to { background-position: 200% 50%; } |
| } |
| """ |
|
|
| with gr.Blocks(css=BLOCKS_CSS, title="Semantique") as demo: |
| game = gr.HTML( |
| value=GAME, |
| html_template=(STATIC / "game.html").read_text(), |
| css_template=(STATIC / "style.css").read_text(), |
| js_on_load=(STATIC / "game.js").read_text(), |
| head=HEAD, |
| server_functions=[judge], |
| container=False, |
| padding=False, |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch() |
|
|