""" UI layout — CUSTOM Gradio Blocks layout for DoodleBook. Aesthetic: "Construction-paper scrapbook" — a handmade keepsake, not a dashboard. Signature details: - Warm construction-paper grain background (inline SVG fractal noise) - Wobbly hand-drawn borders via an SVG displacement filter (#wobble) - Washi-tape strips, slightly rotated cards, polaroid-pinned doodle - Crayon-textured hand-lettered headings (Gaegu / Caveat) + legible body (Nunito) Off-Brand Badge: zero Gradio defaults. Every styled element uses elem_classes so the CSS actually targets Gradio 5's DOM (the previous build targeted Gradio 3/4 `.gr-*` classes that no longer exist, so almost nothing applied). """ import gradio as gr from config import VOICE_CHOICES, DEFAULT_VOICE THEMES = [ "brave adventure", "making a new friend", "overcoming a fear", "helping someone", "lost and found", "learning something new", ] # --------------------------------------------------------------------------- # Fonts: injected via (reliable) instead of CSS @import (often stripped). # --------------------------------------------------------------------------- HEAD = """ """ # --------------------------------------------------------------------------- # Hidden SVG defs: the hand-drawn "wobble" filter + a crayon underline squiggle. # Applied to pseudo-element borders so text stays crisp while frames go wavy. # --------------------------------------------------------------------------- SVG_DEFS = """ """ CSS = r""" /* ============================================================================ DOODLEBOOK — CONSTRUCTION-PAPER SCRAPBOOK ============================================================================ */ :root { --paper: #f6ecd4; /* warm construction-paper base */ --paper-2: #efe0c2; --ink: #2e2a26; /* soft black, like a felt pen */ --ink-soft: #6b5d4f; --crayon-orange: #ef6a3a; --crayon-teal: #2ba39a; --crayon-sun: #f4c64a; --crayon-berry: #d6517a; --crayon-sky: #4a9fd6; --crayon-leaf: #74b85a; --tape: rgba(244, 198, 74, 0.55); } /* --- Neutralize the Gradio theme so OUR styles win (was: Soft theme bleed) --- */ .gradio-container, .gradio-container *:not(svg):not(path) { --block-background-fill: transparent; --block-border-width: 0px; --block-shadow: none; --panel-background-fill: transparent; --input-background-fill: #fffdf6; --body-text-color: var(--ink); } .gradio-container { max-width: 1180px !important; margin: 0 auto !important; background: transparent !important; font-family: 'Nunito', sans-serif !important; color: var(--ink); } /* Paper-grain backdrop: layered fractal noise + soft color blooms */ body, gradio-app { background-color: var(--paper) !important; background-image: radial-gradient(circle at 12% 18%, rgba(239,106,58,0.10), transparent 38%), radial-gradient(circle at 88% 12%, rgba(43,163,154,0.10), transparent 40%), radial-gradient(circle at 70% 88%, rgba(214,81,122,0.08), transparent 42%), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E") !important; background-attachment: fixed !important; } /* Reusable hand-drawn frame: a wobbly inked border that doesn't disturb content */ .framed { position: relative; } .framed::after { content: ""; position: absolute; inset: 7px; border: 2.5px solid var(--ink); border-radius: 16px; filter: url(#wobble); pointer-events: none; opacity: 0.85; } /* Washi-tape strip helper */ .taped::before { content: ""; position: absolute; top: -12px; left: 50%; width: 110px; height: 26px; transform: translateX(-50%) rotate(-3deg); background: repeating-linear-gradient(45deg, rgba(255,255,255,.35) 0 6px, transparent 6px 12px), var(--tape); box-shadow: 0 2px 5px rgba(0,0,0,.12); z-index: 3; } /* ============================== HEADER ============================== */ .app-header { text-align: center; padding: 34px 16px 10px; position: relative; } .app-title { font-family: 'Gaegu', cursive !important; font-weight: 700 !important; font-size: clamp(44px, 7vw, 78px) !important; line-height: 0.95 !important; color: var(--ink) !important; margin: 0 !important; letter-spacing: 1px; text-shadow: 2px 2px 0 var(--crayon-sun), 4px 4px 0 rgba(239,106,58,.35); } .app-title .doodle-o { color: var(--crayon-orange); display: inline-block; transform: rotate(-6deg); } .app-subtitle { font-family: 'Caveat', cursive !important; font-size: clamp(20px, 3vw, 28px) !important; color: var(--ink-soft) !important; margin-top: 6px !important; } /* hand-drawn crayon underline under the title */ .title-squiggle { display:block; margin: 6px auto 0; width: min(360px, 70%); height: 16px; } .title-squiggle path { stroke: var(--crayon-teal); stroke-width: 5; fill: none; stroke-linecap: round; filter: url(#wobble-strong); } /* ============================== CARDS ============================== */ .input-card, .output-card { position: relative; background: #fffdf6 !important; border-radius: 18px; padding: 30px 26px 26px; margin: 8px; box-shadow: 0 10px 26px rgba(46,42,38,.12), 0 2px 0 rgba(0,0,0,.04); } .input-card { transform: rotate(-0.7deg); } .output-card { transform: rotate(0.5deg); } .input-card:hover, .output-card:hover { transform: rotate(0deg); transition: transform .35s ease; } /* section labels above fields */ .card-eyebrow { font-family: 'Gaegu', cursive; font-weight: 700; font-size: 22px; color: var(--crayon-orange); margin: 0 0 10px; transform: rotate(-1.5deg); } /* ============================== FIELDS ============================== */ .field label span, .doodle-input label span, .tiny-toggle label span { font-family: 'Gaegu', cursive !important; font-weight: 700 !important; font-size: 19px !important; color: var(--ink) !important; } .field textarea, .field input[type="text"], .field input:not([type]), .field .wrap .secondary-wrap input, .field [data-testid="textbox"], .field input[role="listbox"], .field .secondary-wrap, .field [class*="dropdown"] input { font-family: 'Nunito', sans-serif !important; font-size: 17px !important; color: var(--ink) !important; background: #fffdf6 !important; border: 2.5px solid var(--ink) !important; border-radius: 12px !important; padding: 11px 14px !important; box-shadow: 2px 3px 0 rgba(46,42,38,.10) !important; } .field textarea:focus, .field input:focus { border-color: var(--crayon-orange) !important; box-shadow: 2px 3px 0 rgba(239,106,58,.30) !important; outline: none !important; } /* doodle image as a pinned polaroid */ .doodle-input { position: relative; } .doodle-input .image-container, .doodle-input [data-testid="image"] { background: #ffffff !important; border: none !important; border-radius: 4px !important; padding: 12px 12px 34px !important; /* polaroid bottom lip */ box-shadow: 0 8px 18px rgba(46,42,38,.18) !important; transform: rotate(-2deg); } .doodle-input img { border-radius: 2px !important; } .doodle-input .upload-container, .doodle-input [data-testid="image"] .wrap { border: 3px dashed var(--crayon-sky) !important; border-radius: 8px !important; background: #f3f9ff !important; color: var(--ink-soft) !important; } /* ============================== BUTTONS ============================== */ /* elem_classes land on the