AndrΓ© Oliveira commited on
Commit
d75df79
Β·
1 Parent(s): a304a75

split css

Browse files
Files changed (2) hide show
  1. app.py +46 -1048
  2. style.css +1183 -0
app.py CHANGED
@@ -11,6 +11,39 @@ The user picks both layers with image buttons in the UI.
11
  Aesthetic: children's drawing β€” crayon textures, wobbly hand-drawn borders.
12
  """
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  import base64
15
  import glob
16
  import os
@@ -44,9 +77,19 @@ except ImportError:
44
  # Vision module (drawing β†’ "loves" phrase). Falls back to stroke analysis
45
  # internally if MiniCPM-V can't load, so the drawing input always produces
46
  # SOMETHING for the lullaby to be about.
 
 
 
 
 
 
47
  try:
48
  import draw.vision as vision # vision.describe(image) -> {"loves": str, ...}
49
- except ImportError:
 
 
 
 
50
  vision = None
51
 
52
 
@@ -2283,1051 +2326,6 @@ ATTRIBUTION_HTML = """
2283
  """
2284
 
2285
 
2286
- CSS = """
2287
- @import url('https://fonts.googleapis.com/css2?family=Fredoka+One&family=Patrick+Hand&family=Gloria+Hallelujah&display=swap');
2288
-
2289
- :root {
2290
- --paper: #fffef7;
2291
- --paper-edge: #f5ecd9;
2292
- --crayon-blue: #4a90d9;
2293
- --crayon-red: #e15a4e;
2294
- --crayon-yellow:#f5c842;
2295
- --crayon-green: #6fb05e;
2296
- --crayon-purple:#9b6db5;
2297
- --crayon-pink: #f08ab0;
2298
- --crayon-orange:#f0934a;
2299
- --ink: #2d3142;
2300
- /* dark mode surface tokens (overridden below when .dark-mode is active) */
2301
- --surface-card: var(--paper);
2302
- --surface-input: rgba(255,255,255,0.6);
2303
- --surface-dropdown: #ffffff;
2304
- --surface-canvas: #f3f1ec;
2305
- --surface-canvas-inner: #fdfbf3;
2306
- }
2307
-
2308
- /* ── Dark mode ── */
2309
- .dark-mode {
2310
- --paper: #1a1b2e;
2311
- --paper-edge: #16172a;
2312
- --ink: #e8e4d9;
2313
- --crayon-blue: #6aabf0;
2314
- --crayon-yellow:#f5c842;
2315
- --crayon-red: #f07066;
2316
- --crayon-orange:#f5a060;
2317
- --crayon-green: #80cc6e;
2318
- --surface-card: #1e2040;
2319
- --surface-input: rgba(30, 32, 64, 0.85);
2320
- --surface-dropdown: #252848;
2321
- --surface-canvas: #1c1e38;
2322
- --surface-canvas-inner: #23264a;
2323
- }
2324
-
2325
- /* Dark mode toggle button */
2326
- #dark-toggle-btn {
2327
- background: var(--paper) !important;
2328
- border: 2.5px solid var(--ink) !important;
2329
- border-radius: 50px !important;
2330
- padding: 6px 14px !important;
2331
- font-family: 'Fredoka One', cursive !important;
2332
- font-size: 1.05rem !important;
2333
- color: var(--ink) !important;
2334
- cursor: pointer !important;
2335
- box-shadow: 3px 3px 0 rgba(45,49,66,0.15) !important;
2336
- transition: background 0.2s, color 0.2s, border-color 0.2s !important;
2337
- width: auto !important;
2338
- margin: 8px 0 0 0 !important;
2339
- display: inline-flex !important;
2340
- align-items: center !important;
2341
- gap: 6px !important;
2342
- }
2343
- #dark-toggle-btn:hover {
2344
- transform: translate(-1px,-1px) !important;
2345
- box-shadow: 4px 4px 0 rgba(45,49,66,0.2) !important;
2346
- }
2347
-
2348
- .gradio-container {
2349
- background: var(--paper) !important;
2350
- background-image:
2351
- radial-gradient(circle at 20% 30%, rgba(245, 200, 66, 0.06) 0%, transparent 30%),
2352
- radial-gradient(circle at 80% 70%, rgba(74, 144, 217, 0.06) 0%, transparent 30%),
2353
- radial-gradient(circle at 50% 90%, rgba(240, 138, 176, 0.06) 0%, transparent 30%);
2354
- font-family: 'Patrick Hand', sans-serif !important;
2355
- color: var(--ink) !important;
2356
- min-height: 100vh;
2357
- transition: background 0.3s ease, color 0.3s ease;
2358
- }
2359
- .dark-mode .gradio-container {
2360
- background-image:
2361
- radial-gradient(circle at 20% 30%, rgba(106,171,240,0.05) 0%, transparent 30%),
2362
- radial-gradient(circle at 80% 70%, rgba(155,109,181,0.05) 0%, transparent 30%),
2363
- radial-gradient(circle at 50% 90%, rgba(245,200,66,0.04) 0%, transparent 30%) !important;
2364
- }
2365
-
2366
- #title-block {
2367
- text-align: center;
2368
- padding: 40px 24px 16px;
2369
- }
2370
- #title-block::after {
2371
- content: "";
2372
- display: block;
2373
- width: 280px;
2374
- height: 18px;
2375
- margin: 20px auto 0;
2376
- /* Hand-drawn squiggle. Inline SVG as a background image β€” single ink color,
2377
- gentle wave reading as "kid drew a wavy line under the title". */
2378
- background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 280 18' fill='none' stroke='%232d3142' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'><path d='M4 9 C 22 2, 38 16, 56 9 S 90 2, 108 9 S 142 16, 160 9 S 194 2, 212 9 S 246 16, 276 9'/></svg>");
2379
- background-repeat: no-repeat;
2380
- background-size: contain;
2381
- background-position: center;
2382
- opacity: 0.85;
2383
- }
2384
- #title-block h1 {
2385
- font-family: 'Fredoka One', cursive !important;
2386
- font-weight: 400 !important;
2387
- font-size: 4.2rem !important;
2388
- color: var(--ink) !important;
2389
- margin: 0 !important;
2390
- letter-spacing: 0.02em;
2391
- line-height: 1 !important;
2392
- text-shadow: 3px 3px 0px rgba(245, 200, 66, 0.45);
2393
- }
2394
- #title-block .subtitle {
2395
- font-family: 'Patrick Hand', sans-serif !important;
2396
- font-size: 1.4rem !important;
2397
- color: var(--crayon-blue) !important;
2398
- margin-top: 12px;
2399
- font-weight: 400;
2400
- transform: rotate(-1.5deg);
2401
- display: inline-block;
2402
- }
2403
- /* Orientation copy under the title. Sits BELOW the sketched squiggle (the
2404
- #title-block::after pseudo-element) so the squiggle reads as the title's
2405
- closing flourish, and the explainer is its own calmer block underneath.
2406
- Plain warm prose β€” what the app IS and who does WHAT, in two lines. */
2407
- #title-explainer {
2408
- font-family: 'Patrick Hand', sans-serif;
2409
- font-size: 1.15rem;
2410
- color: var(--ink);
2411
- opacity: 0.82;
2412
- margin: 12px auto 24px;
2413
- max-width: 640px;
2414
- line-height: 1.5;
2415
- text-align: center;
2416
- }
2417
- #title-explainer .explainer-roles {
2418
- display: block;
2419
- margin-top: 6px;
2420
- font-size: 1.0rem;
2421
- opacity: 0.85;
2422
- font-style: italic;
2423
- }
2424
-
2425
- /* ---------- Step diagram (1 ➜ 2 ➜ 3) -------------------------------------
2426
- Wayfinding strip just under the title. Three larger pills connected
2427
- by dashed lines, hand-drawn aesthetic to match the rest of the app.
2428
- Each pill contains: number badge (popped above), emoji icon, short
2429
- heading, and a two-line description. Step 1 is styled with a dashed
2430
- border to visually telegraph "optional" β€” backs up the explicit
2431
- "(optional)" text underneath.
2432
-
2433
- The diagram doubles as the main explainer of how Lolaby works, so it
2434
- sits prominently above the cards. */
2435
- #step-diagram {
2436
- display: flex;
2437
- align-items: flex-start; /* top-align so descs of different
2438
- lengths don't shift the headings */
2439
- justify-content: center;
2440
- gap: 6px;
2441
- margin: 8px auto 32px;
2442
- flex-wrap: wrap;
2443
- max-width: 920px;
2444
- }
2445
- #step-diagram .step {
2446
- display: flex;
2447
- flex-direction: column;
2448
- align-items: center;
2449
- gap: 4px;
2450
- padding: 14px 18px 16px;
2451
- background: rgba(255, 255, 255, 0.35);
2452
- border: 2.5px solid var(--ink);
2453
- border-radius: 16px 20px 14px 18px; /* irregular to look hand-drawn */
2454
- width: 240px;
2455
- flex: 0 1 240px;
2456
- box-shadow: 2px 2px 0 rgba(45, 49, 66, 0.12);
2457
- position: relative;
2458
- }
2459
- .dark-mode #step-diagram .step {
2460
- background: rgba(30, 32, 64, 0.75);
2461
- box-shadow: 2px 2px 0 rgba(0,0,0,0.3);
2462
- }
2463
- #step-diagram .step-optional {
2464
- border-style: dashed;
2465
- background: rgba(255, 255, 255, 0.35);
2466
- opacity: 0.92;
2467
- }
2468
- .dark-mode #step-diagram .step-optional {
2469
- background: rgba(30, 32, 64, 0.35);
2470
- }
2471
- #step-diagram .step-num {
2472
- font-family: 'Fredoka One', sans-serif;
2473
- font-size: 1.25rem;
2474
- color: var(--ink);
2475
- background: var(--crayon-yellow);
2476
- width: 30px;
2477
- height: 30px;
2478
- border-radius: 50%;
2479
- border: 2.5px solid var(--ink);
2480
- display: inline-flex;
2481
- align-items: center;
2482
- justify-content: center;
2483
- line-height: 1;
2484
- margin-top: -28px; /* pop the number above the pill */
2485
- margin-bottom: 4px;
2486
- box-shadow: 1px 1px 0 rgba(45, 49, 66, 0.15);
2487
- }
2488
- #step-diagram .step-icon {
2489
- font-size: 1.7rem;
2490
- line-height: 1;
2491
- margin: 2px 0;
2492
- }
2493
- #step-diagram .step-label {
2494
- font-family: 'Fredoka One', sans-serif;
2495
- font-size: 1.05rem;
2496
- color: var(--ink);
2497
- text-align: center;
2498
- line-height: 1.2;
2499
- margin-bottom: 4px;
2500
- }
2501
- #step-diagram .step-sub {
2502
- font-family: 'Patrick Hand', sans-serif;
2503
- font-weight: normal;
2504
- font-size: 0.85rem;
2505
- opacity: 0.7;
2506
- display: block;
2507
- margin-top: 2px;
2508
- }
2509
- #step-diagram .step-desc {
2510
- font-family: 'Patrick Hand', sans-serif;
2511
- font-size: 0.95rem;
2512
- color: var(--ink);
2513
- opacity: 0.82;
2514
- text-align: center;
2515
- line-height: 1.35;
2516
- margin-top: 2px;
2517
- }
2518
- #step-diagram .step-connector {
2519
- flex: 0 0 24px;
2520
- height: 0;
2521
- border-top: 2.5px dashed var(--ink);
2522
- opacity: 0.5;
2523
- /* Connector centred vertically against the pill's vertical mid-point.
2524
- The pills are ~150px tall now, so the connector needs a ~70px top
2525
- offset so it visually meets the middle of the boxes, not the top. */
2526
- margin-top: 70px;
2527
- border-radius: 50%;
2528
- }
2529
- @media (max-width: 880px) {
2530
- /* When the strip can't fit three pills + connectors, stack vertically.
2531
- Hide connectors so the pills stack cleanly. */
2532
- #step-diagram { flex-direction: column; align-items: center; gap: 24px; }
2533
- #step-diagram .step-connector { display: none; }
2534
- #step-diagram .step { width: 280px; flex: 0 0 auto; }
2535
- }
2536
-
2537
- /* ---------- Step badge (small number inside a card tab) ------------------
2538
- Reinforces the diagram by anchoring each card to its step number. Once
2539
- a user scrolls past the diagram they can still see which step they're
2540
- on by looking at the card tab. */
2541
- .step-badge {
2542
- display: inline-flex;
2543
- align-items: center;
2544
- justify-content: center;
2545
- width: 20px;
2546
- height: 20px;
2547
- margin-right: 6px;
2548
- background: #2d3142 !important;
2549
- color: #ffffff !important;
2550
- border-radius: 50%;
2551
- font-family: 'Fredoka One', sans-serif;
2552
- font-size: 0.85rem;
2553
- line-height: 1;
2554
- vertical-align: middle;
2555
- position: relative;
2556
- top: -1px;
2557
- }
2558
- .step-badge-inline {
2559
- background: var(--crayon-yellow);
2560
- color: var(--ink);
2561
- border: 2px solid var(--ink);
2562
- margin-right: 8px;
2563
- }
2564
-
2565
- /* ---------- Step-3 hint above the Sing button ---------------------------
2566
- Sits right above the primary CTA so a non-power user sees a clear
2567
- "this is the next thing to do" line before the button itself. */
2568
- .step-3-hint {
2569
- font-family: 'Patrick Hand', sans-serif;
2570
- font-size: 1.05rem;
2571
- color: var(--ink);
2572
- text-align: center;
2573
- margin: 18px 0 8px;
2574
- opacity: 0.9;
2575
- }
2576
-
2577
- label > span,
2578
- .gradio-container label > span:first-child {
2579
- font-family: 'Fredoka One', cursive !important;
2580
- font-size: 1.2rem !important;
2581
- color: var(--ink) !important;
2582
- font-weight: 400 !important;
2583
- }
2584
-
2585
- input[type="text"],
2586
- input[type="number"],
2587
- textarea,
2588
- select {
2589
- background: rgba(255, 255, 255, 0.6) !important;
2590
- border: 2.5px solid var(--ink) !important;
2591
- border-radius: 14px 18px 12px 16px !important;
2592
- color: var(--ink) !important;
2593
- font-family: 'Patrick Hand', sans-serif !important;
2594
- font-size: 1.15rem !important;
2595
- padding: 10px 14px !important;
2596
- box-shadow: 3px 3px 0 rgba(45, 49, 66, 0.12);
2597
- transition: all 0.18s ease;
2598
- }
2599
- .dark-mode input[type="text"],
2600
- .dark-mode input[type="number"],
2601
- .dark-mode textarea,
2602
- .dark-mode select,
2603
- .dark-mode input {
2604
- background: var(--paper) !important;
2605
- color: var(--ink) !important;
2606
- box-shadow: 3px 3px 0 rgba(0,0,0,0.25) !important;
2607
- }
2608
- input[type="text"]:focus, textarea:focus, select:focus {
2609
- border-color: var(--crayon-blue) !important;
2610
- box-shadow: 4px 4px 0 var(--crayon-yellow);
2611
- outline: none;
2612
- }
2613
-
2614
- /* Dropdown: style only the visible trigger input, leave the options panel
2615
- with Gradio defaults so it stays usable. */
2616
- .gradio-dropdown input {
2617
- background: rgba(255, 255, 255, 0.6) !important;
2618
- border: 2.5px solid var(--ink) !important;
2619
- border-radius: 14px 18px 12px 16px !important;
2620
- color: var(--ink) !important;
2621
- font-family: 'Patrick Hand', sans-serif !important;
2622
- font-size: 1.15rem !important;
2623
- padding: 10px 14px !important;
2624
- box-shadow: 3px 3px 0 rgba(45, 49, 66, 0.12);
2625
- }
2626
- .dark-mode .gradio-dropdown input {
2627
- background: rgba(30, 32, 64, 0.85) !important;
2628
- color: var(--ink) !important;
2629
- }
2630
- /* Make sure the options popup is on top and uses readable defaults */
2631
- .gradio-dropdown ul,
2632
- .gradio-dropdown [role="listbox"],
2633
- ul.options {
2634
- background: #ffffff !important;
2635
- border: 2px solid var(--ink) !important;
2636
- border-radius: 8px !important;
2637
- font-family: 'Patrick Hand', sans-serif !important;
2638
- color: var(--ink) !important;
2639
- z-index: 9999 !important;
2640
- box-shadow: 4px 4px 0 rgba(45, 49, 66, 0.18);
2641
- }
2642
- .gradio-dropdown ul li,
2643
- .gradio-dropdown [role="option"],
2644
- ul.options li {
2645
- padding: 8px 14px !important;
2646
- font-family: 'Patrick Hand', sans-serif !important;
2647
- font-size: 1.05rem !important;
2648
- color: var(--ink) !important;
2649
- background: transparent !important;
2650
- cursor: pointer;
2651
- }
2652
- .gradio-dropdown ul li:hover,
2653
- .gradio-dropdown [role="option"]:hover,
2654
- ul.options li:hover {
2655
- background: var(--crayon-yellow) !important;
2656
- }
2657
- .dark-mode .gradio-dropdown ul,
2658
- .dark-mode .gradio-dropdown [role="listbox"],
2659
- .dark-mode ul.options {
2660
- color: var(--paper) !important;
2661
- }
2662
- .dark-mode .reset-button {
2663
- color: var(--ink) !important;
2664
- }
2665
-
2666
- .dark-mode ul.options li, .dark-mode .gradio-dropdown ul li,
2667
- .dark-mode .gradio-dropdown [role="option"] {
2668
- color: var(--paper) !important;
2669
- }
2670
-
2671
- input[type="range"] { accent-color: var(--crayon-red) !important; }
2672
-
2673
- .form-card {
2674
- background: var(--paper) !important;
2675
- border: 3px solid var(--ink) !important;
2676
- border-radius: 22px 18px 26px 16px !important;
2677
- padding: 28px !important;
2678
- box-shadow: 5px 5px 0 rgba(45, 49, 66, 0.08);
2679
- /* No transform here β€” it creates a stacking context that breaks
2680
- Gradio's fixed-position dropdown popup. */
2681
- }
2682
- .dark-mode .form-card {
2683
- box-shadow: 5px 5px 0 rgba(0,0,0,0.3) !important;
2684
- }
2685
- .dark-mode .form-card div {
2686
- background-color: var(--paper) !important;
2687
- }
2688
- .form-card-right {
2689
- background: var(--paper) !important;
2690
- border: 3px solid var(--ink) !important;
2691
- border-radius: 18px 24px 16px 22px !important;
2692
- padding: 28px !important;
2693
- box-shadow: 5px 5px 0 rgba(245, 200, 66, 0.25);
2694
- }
2695
- .dark-mode .form-card-right {
2696
- box-shadow: 5px 5px 0 rgba(245,200,66,0.12) !important;
2697
- }
2698
- .dark-mode .form-card-right div {
2699
- background-color: var(--paper) !important;
2700
- }
2701
-
2702
- /* ---- Drawing canvas card ----------------------------------------------- */
2703
- /* Matches the form-card aesthetic: paper background, ink border, wobbly
2704
- corners that read as "drawn by hand". Sits above the form as the hero. */
2705
- .draw-card {
2706
- background: var(--paper) !important;
2707
- border: 3px solid var(--ink) !important;
2708
- border-radius: 24px 16px 22px 18px !important;
2709
- padding: 24px 28px 18px !important;
2710
- box-shadow: 5px 5px 0 rgba(74, 144, 217, 0.18);
2711
- margin-bottom: 12px;
2712
- }
2713
- .dark-mode .draw-card {
2714
- box-shadow: 5px 5px 0 rgba(106,171,240,0.12) !important;
2715
- }
2716
- .draw-card .card-heading {
2717
- margin-bottom: 4px;
2718
- }
2719
- .draw-hint {
2720
- font-family: 'Patrick Hand', sans-serif;
2721
- color: var(--ink);
2722
- opacity: 0.7;
2723
- font-size: 1.0rem;
2724
- margin: 0 0 14px 0;
2725
- }
2726
-
2727
- /* ---------- Split draw card: canvas | upload ------------------------------
2728
- The draw card hosts two side-by-side image inputs so trackpad-allergic
2729
- users (i.e. small children) can upload a phone photo of a paper drawing
2730
- instead of trying to draw with a mouse. Soft vertical divider between
2731
- the two halves; on narrow viewports the columns stack and the divider
2732
- becomes a horizontal one. */
2733
- #draw-row {
2734
- gap: 18px;
2735
- align-items: stretch;
2736
- }
2737
- .draw-half {
2738
- padding: 0 6px;
2739
- position: relative;
2740
- z-index: 2; /* sit above background doodles */
2741
- }
2742
- /* Vertical divider between the two halves, drawn on the canvas-side's
2743
- right edge so it sits exactly between the two columns. */
2744
- .draw-half-canvas {
2745
- border-right: 2px dashed rgba(45, 49, 66, 0.18);
2746
- padding-right: 18px;
2747
- }
2748
- .draw-half-upload {
2749
- padding-left: 6px;
2750
- }
2751
- /* Small label above each half so people immediately see "this side OR
2752
- that side." Same font as the hint, slightly bolder, slightly darker. */
2753
- .draw-half-label {
2754
- font-family: 'Patrick Hand', sans-serif;
2755
- font-size: 1.05rem;
2756
- color: var(--ink);
2757
- opacity: 0.85;
2758
- margin: 0 0 8px;
2759
- font-weight: 700;
2760
- letter-spacing: 0.01em;
2761
- }
2762
- /* The little caption under the upload widget, mirroring the canvas's
2763
- `.draw-hint` styling but scoped to the upload half. */
2764
- .draw-half-hint {
2765
- font-family: 'Patrick Hand', sans-serif;
2766
- color: var(--ink);
2767
- opacity: 0.65;
2768
- font-size: 0.95rem;
2769
- margin: 8px 2px 0;
2770
- text-align: center;
2771
- }
2772
- /* Match the upload widget's idle look to the canvas surround so the two
2773
- halves feel like siblings. Gradio's default upload widget has a plain
2774
- white drop-zone β€” we soften that to the same warm taupe used for the
2775
- canvas surround. */
2776
- .gradio-container #draw-upload {
2777
- background-color: transparent !important;
2778
- background-image: none !important;
2779
- }
2780
- .gradio-container #draw-upload .upload-container,
2781
- .gradio-container #draw-upload .image-container {
2782
- border: 2px dashed rgba(45, 49, 66, 0.25) !important;
2783
- border-radius: 14px !important;
2784
- background-color: #fdfbf3 !important;
2785
- }
2786
- .dark-mode .gradio-container #draw-upload .upload-container,
2787
- .dark-mode .gradio-container #draw-upload .image-container {
2788
- border: 2px dashed rgba(232, 228, 217, 0.25) !important;
2789
- background-color: var(--paper) !important;
2790
- }
2791
- /* When viewport gets narrow, stack the two halves vertically and convert
2792
- the vertical divider into a horizontal one so it still reads as a
2793
- "this OR that" separator. */
2794
- @media (max-width: 760px) {
2795
- .draw-half-canvas {
2796
- border-right: none;
2797
- border-bottom: 2px dashed rgba(45, 49, 66, 0.18);
2798
- padding-right: 0;
2799
- padding-bottom: 18px;
2800
- margin-bottom: 6px;
2801
- }
2802
- .draw-half-upload {
2803
- padding-left: 0;
2804
- }
2805
- }
2806
- /* Tab-pill labels that sit ABOVE each card heading, classifying who the
2807
- section is for. Visually paired (same shape) but color-differentiated
2808
- (kid = playful crayon-yellow accent, grown-up = grounded ink) so the
2809
- two roles are recognisable at a glance. */
2810
- .card-tab {
2811
- font-family: 'Patrick Hand', sans-serif;
2812
- font-size: 0.95rem;
2813
- font-weight: 400;
2814
- display: inline-block;
2815
- padding: 4px 14px;
2816
- border-radius: 12px 16px 14px 12px;
2817
- margin: 0 0 10px;
2818
- letter-spacing: 0.02em;
2819
- transform: rotate(-1.2deg);
2820
- border: 2px solid var(--ink);
2821
- }
2822
- .card-tab-kid {
2823
- background: #f4d8a0;
2824
- color: var(--ink);
2825
- }
2826
-
2827
- .dark-mode .card-tab-kid {
2828
- color: var(--paper) !important;
2829
- }
2830
-
2831
- /* Grown-up tab: soft crayon-blue wash β€” paired with yellow (the kid tab) as
2832
- a classic complementary crayon pair, calm enough to read as the more
2833
- sober adult-facing section. Solid border (the dashed border had it
2834
- visually colliding with the Clear-drawing button). */
2835
- .card-tab-grownup {
2836
- background: rgba(74, 144, 217, 0.22) !important; /* crayon-blue @ 22% */
2837
- color: var(--ink);
2838
- }
2839
- /* Subtitle under the form heading β€” explains what's required vs optional in
2840
- one breath, so the user isn't surprised by validation errors. Same
2841
- visual weight as .draw-hint so the two cards feel like siblings. */
2842
- .form-role-hint {
2843
- font-family: 'Patrick Hand', sans-serif;
2844
- color: var(--ink);
2845
- opacity: 0.7;
2846
- font-size: 1.0rem;
2847
- margin: 0 0 14px 0;
2848
- }
2849
- /* The Sketchpad itself: give the inner canvas a creamy "paper" feel and
2850
- the surround a dashed-ink border so it sits inside our card. The actual
2851
- surround color is set lower in the taupe block below. */
2852
- #draw-canvas .image-container, #draw-canvas canvas {
2853
- background: #fdfbf3 !important;
2854
- border-radius: 14px !important;
2855
- }
2856
- #draw-canvas {
2857
- border: 2.5px dashed var(--ink) !important;
2858
- border-radius: 14px !important;
2859
- overflow: hidden;
2860
- }
2861
- #draw-canvas canvas {
2862
- border: none !important;
2863
- }
2864
-
2865
- /* Drawing canvas surround.
2866
- IDLE state: warm muted taupe β€” repainted via CSS-variable overrides
2867
- scoped to #draw-canvas (Gradio uses --block-background-fill and friends
2868
- to color component surrounds; overriding them recolors the idle wrapper
2869
- without fighting Sketchpad's active editor chrome).
2870
- ACTIVE editing state: Gradio's Sketchpad swaps in its own dark editor
2871
- surface using inline styles / different variables, which I'm accepting
2872
- as a constraint β€” fighting it produced more side effects than wins. */
2873
- .gradio-container #draw-canvas {
2874
- --block-background-fill: #f3f1ec !important;
2875
- --background-fill-primary: #f3f1ec !important;
2876
- --background-fill-secondary: #f3f1ec !important;
2877
- --neutral-50: #f3f1ec !important;
2878
- --neutral-100: #f3f1ec !important;
2879
- background-color: #f3f1ec !important;
2880
- background-image: none !important;
2881
- }
2882
- .dark-mode .gradio-container #draw-canvas {
2883
- --block-background-fill: var(--paper) !important;
2884
- --background-fill-primary: var(--paper) !important;
2885
- --background-fill-secondary: var(--paper) !important;
2886
- --neutral-50: var(--paper) !important;
2887
- --neutral-100: var(--paper) !important;
2888
- background-color: var(--paper) !important;
2889
- }
2890
- /* Also paint direct child wrappers (Gradio's Sketchpad nests several divs;
2891
- any "inactive" surround inherits/uses these surfaces). The active editor
2892
- uses a deeper element with its own inline style that we no longer fight. */
2893
- .gradio-container #draw-canvas > div,
2894
- .gradio-container #draw-canvas .block,
2895
- .gradio-container #draw-canvas .wrap,
2896
- .gradio-container #draw-canvas .container {
2897
- background-color: #f3f1ec !important;
2898
- background-image: none !important;
2899
- }
2900
- .dark-mode .gradio-container #draw-canvas > div,
2901
- .dark-mode .gradio-container #draw-canvas .block,
2902
- .dark-mode .gradio-container #draw-canvas .wrap,
2903
- .dark-mode .gradio-container #draw-canvas .container {
2904
- background-color: var(--paper) !important;
2905
- }
2906
- /* The inner <canvas> stays cream-white when idle (it's the "paper") */
2907
- .gradio-container #draw-canvas canvas {
2908
- background-color: #fdfbf3 !important;
2909
- background-image: none !important;
2910
- }
2911
- .dark-mode .gradio-container #draw-canvas canvas {
2912
- background-color: #23264a !important;
2913
- }
2914
-
2915
- /* The "Clear drawing" button β€” secondary visual weight; the primary CTA is
2916
- still "Sing with Lola" further down the page. */
2917
- #reset-drawing-btn {
2918
- margin-top: 10px !important;
2919
- background: transparent !important;
2920
- border: 2px dashed var(--ink) !important;
2921
- color: var(--ink) !important;
2922
- font-family: 'Patrick Hand', sans-serif !important;
2923
- font-size: 1.0rem !important;
2924
- padding: 6px 16px !important;
2925
- border-radius: 12px !important;
2926
- box-shadow: none !important;
2927
- width: auto !important;
2928
- align-self: flex-start;
2929
- }
2930
- #reset-drawing-btn:hover {
2931
- background: rgba(45, 49, 66, 0.04) !important;
2932
- }
2933
-
2934
- /* ---- "What Lola saw" hint ----------------------------------------------
2935
- Sits between the audio player and the lyrics. Reserves a CONSTANT amount
2936
- of vertical space whether or not it has content, so the layout never
2937
- reflows when content arrives (which was previously pushing the lyrics
2938
- down on the first generation and overlapping them mid-transition).
2939
- The visual chrome (background, border, padding) only renders when the
2940
- inner .saw-hint div exists; the outer container keeps its margin always. */
2941
- #saw-hint {
2942
- margin: 4px 0 14px !important; /* ALWAYS reserve this space */
2943
- min-height: 12px; /* small constant gap when empty */
2944
- }
2945
- .saw-hint {
2946
- font-family: 'Patrick Hand', sans-serif;
2947
- color: var(--ink);
2948
- background: rgba(245, 200, 66, 0.10); /* faint crayon-yellow */
2949
- border-left: 4px solid var(--crayon-yellow);
2950
- border-radius: 8px 12px 10px 8px;
2951
- padding: 10px 14px;
2952
- margin: 0; /* margin lives on #saw-hint now */
2953
- font-size: 1.0rem;
2954
- line-height: 1.45;
2955
- }
2956
- .dark-mode .saw-label {
2957
- color: var(--ink) !important;
2958
- }
2959
- .saw-hint .saw-label {
2960
- font-weight: 700;
2961
- margin-right: 4px;
2962
- }
2963
- .saw-hint em {
2964
- font-style: italic;
2965
- color: var(--ink);
2966
- opacity: 0.85;
2967
- }
2968
- .saw-hint .saw-plus {
2969
- margin-left: 6px;
2970
- opacity: 0.75;
2971
- font-size: 0.95rem;
2972
- }
2973
- .saw-hint-empty {
2974
- background: rgba(74, 144, 217, 0.08); /* faint crayon-blue */
2975
- border-left-color: var(--crayon-blue);
2976
- font-style: italic;
2977
- }
2978
-
2979
- /* Instrument grid */
2980
- .instr-grid {
2981
- display: grid !important;
2982
- grid-template-columns: repeat(4, 1fr) !important;
2983
- gap: 12px !important;
2984
- margin: 0 0 14px !important;
2985
- }
2986
- /* Gradio wraps every gr.HTML in a div with default margin; squash the gap
2987
- between the picker title and the instrument grid. */
2988
- .form-card-left .gr-block:has(> .instr-grid),
2989
- .form-card-left .gr-block:has(> #picker-hint) {
2990
- margin: 0 !important;
2991
- padding: 0 !important;
2992
- }
2993
-
2994
- /* Slider β€” Gradio's value bubble sits in a small box that can clip the
2995
- rounded border at the bottom. Give the wrapper extra padding so the
2996
- bubble's full outline shows, and force no overflow clipping on the
2997
- surrounding block. */
2998
- .gr-slider, .gradio-slider, [data-testid="slider"] {
2999
- overflow: visible !important;
3000
- padding-bottom: 14px !important;
3001
- }
3002
- .gr-slider input[type="number"],
3003
- .gradio-slider input[type="number"],
3004
- [data-testid="slider"] input[type="number"] {
3005
- overflow: visible !important;
3006
- }
3007
- /* The bubble container itself */
3008
- .gr-slider .wrap, .gradio-slider .wrap {
3009
- overflow: visible !important;
3010
- }
3011
- .instr-cell {
3012
- background: var(--paper) !important;
3013
- border: 2.5px solid var(--ink) !important;
3014
- border-radius: 16px 20px 14px 18px !important;
3015
- padding: 10px 6px 8px !important;
3016
- cursor: pointer;
3017
- box-shadow: 3px 3px 0 rgba(45, 49, 66, 0.12);
3018
- transition: all 0.2s ease;
3019
- display: flex;
3020
- flex-direction: column;
3021
- align-items: center;
3022
- gap: 4px;
3023
- font-family: 'Patrick Hand', sans-serif;
3024
- font-size: 1rem;
3025
- color: var(--ink);
3026
- }
3027
- .dark-mode .instr-cell {
3028
- box-shadow: 3px 3px 0 rgba(0,0,0,0.3) !important;
3029
- }
3030
- .dark-mode .instr-cell span {
3031
- color: var(--ink) !important;
3032
- }
3033
- .instr-cell:hover {
3034
- transform: translate(-2px, -2px) rotate(-2deg);
3035
- box-shadow: 5px 5px 0 rgba(45, 49, 66, 0.18);
3036
- }
3037
- .instr-cell.selected {
3038
- background: var(--crayon-yellow) !important;
3039
- box-shadow: 3px 3px 0 var(--ink) !important;
3040
- transform: rotate(-1deg);
3041
- }
3042
- .instr-cell img {
3043
- width: 56px;
3044
- height: 56px;
3045
- object-fit: contain;
3046
- pointer-events: none;
3047
- }
3048
- .instr-cell span { pointer-events: none; }
3049
- /* The little "1" / "2" badge in the corner of selected cells β€” tells the
3050
- user which slot they've assigned the instrument to. */
3051
- .instr-cell { position: relative; }
3052
-
3053
- .slot-badge {
3054
- position: absolute;
3055
- top: -8px;
3056
- right: -8px;
3057
- background: #2d3142 !important;
3058
- color: #ffffff !important;
3059
- font-family: 'Fredoka One', cursive;
3060
- font-size: 0.85rem;
3061
- width: 22px;
3062
- height: 22px;
3063
- border-radius: 50%;
3064
- display: flex !important;
3065
- align-items: center;
3066
- justify-content: center;
3067
- box-shadow: 2px 2px 0 rgba(45, 49, 66, 0.25);
3068
- pointer-events: none;
3069
- }
3070
- #picker-hint {
3071
- font-family: 'Patrick Hand', sans-serif;
3072
- font-size: 0.95rem;
3073
- color: var(--crayon-blue);
3074
- margin: -2px 0 10px;
3075
- padding: 4px 8px;
3076
- border-radius: 6px;
3077
- transition: background 0.25s ease, color 0.25s ease;
3078
- min-height: 0;
3079
- }
3080
- #picker-hint:empty {
3081
- margin: 0;
3082
- padding: 0;
3083
- }
3084
- #picker-hint.hint-flash {
3085
- background: var(--crayon-red);
3086
- color: #fff;
3087
- transform: rotate(-0.5deg);
3088
- }
3089
- #picker-hint.hint-error {
3090
- font-weight: 700;
3091
- box-shadow: 2px 2px 0 rgba(45, 49, 66, 0.2);
3092
- }
3093
-
3094
- .section-title {
3095
- font-family: 'Fredoka One', cursive;
3096
- font-size: 1.4rem;
3097
- color: var(--ink);
3098
- margin: 18px 0 2px;
3099
- display: flex;
3100
- align-items: baseline;
3101
- gap: 10px;
3102
- }
3103
- .section-title .which {
3104
- font-family: 'Patrick Hand', sans-serif;
3105
- font-size: 1rem;
3106
- color: var(--crayon-blue);
3107
- font-style: italic;
3108
- }
3109
-
3110
- button#generate-btn, .primary {
3111
- background: var(--crayon-red) !important;
3112
- color: #fffef7 !important;
3113
- border: 3px solid var(--ink) !important;
3114
- border-radius: 26px 18px 28px 22px !important;
3115
- font-family: 'Fredoka One', cursive !important;
3116
- font-size: 1.7rem !important;
3117
- font-weight: 400 !important;
3118
- padding: 18px 36px !important;
3119
- cursor: pointer;
3120
- box-shadow: 5px 5px 0 var(--ink);
3121
- transition: all 0.18s ease;
3122
- letter-spacing: 0.03em;
3123
- width: 100%;
3124
- margin-top: 16px;
3125
- }
3126
- button#generate-btn:hover {
3127
- transform: translate(-2px, -2px) rotate(-1deg);
3128
- box-shadow: 7px 7px 0 var(--ink);
3129
- background: var(--crayon-orange) !important;
3130
- }
3131
- button#generate-btn:active {
3132
- transform: translate(2px, 2px);
3133
- box-shadow: 2px 2px 0 var(--ink);
3134
- }
3135
-
3136
- #output-lyrics textarea {
3137
- background:
3138
- linear-gradient(transparent 28px, rgba(74, 144, 217, 0.18) 28px, rgba(74, 144, 217, 0.18) 29px, transparent 29px) !important;
3139
- background-size: 100% 30px !important;
3140
- background-color: rgba(255, 255, 255, 0.7) !important;
3141
- font-family: 'Gloria Hallelujah', cursive !important;
3142
- font-size: 1.25rem !important;
3143
- line-height: 30px !important;
3144
- color: var(--ink) !important;
3145
- border: 2.5px solid var(--ink) !important;
3146
- border-radius: 16px 20px 14px 18px !important;
3147
- padding: 12px 18px !important;
3148
- }
3149
- .dark-mode #output-lyrics textarea {
3150
- background:
3151
- linear-gradient(transparent 28px, rgba(106, 171, 240, 0.15) 28px, rgba(106, 171, 240, 0.15) 29px, transparent 29px) !important;
3152
- background-size: 100% 30px !important;
3153
- background-color: rgba(30, 32, 64, 0.8) !important;
3154
- }
3155
-
3156
- audio {
3157
- border-radius: 18px !important;
3158
- border: 2.5px solid var(--ink) !important;
3159
- background: var(--paper) !important;
3160
- box-shadow: 3px 3px 0 rgba(45, 49, 66, 0.1);
3161
- }
3162
- .dark-mode audio {
3163
- box-shadow: 3px 3px 0 rgba(0,0,0,0.3) !important;
3164
- }
3165
-
3166
- .attribution {
3167
- text-align: center;
3168
- font-family: 'Patrick Hand', sans-serif;
3169
- font-size: 0.85rem;
3170
- color: rgba(45, 49, 66, 0.55);
3171
- margin: 24px auto 16px;
3172
- padding: 12px;
3173
- }
3174
- .attribution a {
3175
- color: var(--crayon-blue);
3176
- text-decoration: none;
3177
- border-bottom: 1px dashed var(--crayon-blue);
3178
- }
3179
-
3180
- /* Footer container β€” wraps the icon attribution and a professional credit
3181
- block. A subtle divider sits between them. */
3182
- .footer {
3183
- text-align: center;
3184
- padding: 8px 12px 20px;
3185
- }
3186
- .footer .attribution {
3187
- padding: 6px 12px 14px;
3188
- border-bottom: 1px dashed rgba(45, 49, 66, 0.18);
3189
- margin-bottom: 12px;
3190
- }
3191
- /* Powered-by line β€” sits below the icon attribution. Small and quiet:
3192
- names the open-source models behind the app and gives judges one-click
3193
- access to each on the Hub. */
3194
- .footer .powered-by {
3195
- font-family: 'Patrick Hand', sans-serif;
3196
- font-size: 0.92rem;
3197
- color: var(--ink);
3198
- opacity: 0.72;
3199
- letter-spacing: 0.01em;
3200
- padding: 0 0 4px;
3201
- }
3202
- .footer .powered-by a {
3203
- color: var(--ink);
3204
- text-decoration: none;
3205
- border-bottom: 1px dotted rgba(45, 49, 66, 0.45);
3206
- padding: 0 1px;
3207
- transition: color 0.2s ease, border-color 0.2s ease;
3208
- }
3209
- .footer .powered-by a:hover {
3210
- color: var(--crayon-blue);
3211
- border-bottom-color: var(--crayon-blue);
3212
- }
3213
-
3214
- footer { display: none !important; }
3215
- .gr-form { gap: 14px !important; }
3216
-
3217
- /* Hide the state-holder textboxes β€” they're in the DOM only so JS can write to them */
3218
- .pick-state { display: none !important; }
3219
-
3220
- /* Card-level section headings (replaces inline gr.Markdown "###" usage) */
3221
- .card-heading {
3222
- font-family: 'Fredoka One', cursive;
3223
- font-size: 1.5rem;
3224
- color: var(--ink);
3225
- margin: 4px 0 14px;
3226
- letter-spacing: 0.01em;
3227
- }
3228
-
3229
- /* Advanced settings accordion */
3230
- #advanced-accordion {
3231
- margin-top: 22px !important;
3232
- border: 2.5px dashed var(--ink) !important;
3233
- border-radius: 14px 18px 12px 16px !important;
3234
- background: rgba(255, 255, 255, 0.3) !important;
3235
- overflow: visible !important;
3236
- }
3237
- .dark-mode #advanced-accordion {
3238
- background: rgba(30, 32, 64, 0.5) !important;
3239
- }
3240
- #advanced-accordion > button,
3241
- #advanced-accordion .label-wrap,
3242
- #advanced-accordion summary {
3243
- font-family: 'Fredoka One', cursive !important;
3244
- font-size: 1.1rem !important;
3245
- color: var(--ink) !important;
3246
- padding: 10px 14px !important;
3247
- }
3248
-
3249
- /* Inline error banner β€” appears under the form when validation fails */
3250
- #error-banner {
3251
- margin-top: 12px;
3252
- }
3253
- .error-banner-inner {
3254
- background: #fde6e3;
3255
- border: 2.5px solid var(--crayon-red);
3256
- border-radius: 14px 18px 12px 16px;
3257
- color: #8a2a22;
3258
- font-family: 'Patrick Hand', sans-serif;
3259
- font-size: 1.1rem;
3260
- padding: 12px 16px;
3261
- box-shadow: 3px 3px 0 rgba(225, 90, 78, 0.25);
3262
- transform: rotate(-0.4deg);
3263
- }
3264
- .dark-mode .error-banner-inner {
3265
- background: #3a1a1a;
3266
- color: #f0a0a0;
3267
- }
3268
-
3269
- /* ---------- Success banner (between heading and audio in lullaby card) ---
3270
- Sits between "Your lullaby" heading and the audio player. One-liner,
3271
- no emoji β€” quiet confirmation that generation completed. Friendly
3272
- green palette to mirror the error-banner's red, but smaller padding
3273
- and no rotation since it's an inline confirmation, not a celebration. */
3274
- #success-banner {
3275
- margin: 4px 0 10px;
3276
- }
3277
- .success-banner-inner {
3278
- background: #e5f3dc;
3279
- border: 2.5px solid #6fb05e;
3280
- border-radius: 12px 16px 10px 14px;
3281
- color: #2f5a26;
3282
- font-family: 'Patrick Hand', sans-serif;
3283
- font-size: 1.05rem;
3284
- padding: 8px 14px;
3285
- text-align: center;
3286
- box-shadow: 2px 2px 0 rgba(111, 176, 94, 0.2);
3287
- }
3288
- .dark-mode .success-banner-inner {
3289
- background: #1a3020;
3290
- color: #90cc80;
3291
- }
3292
- /* Idle (pre-generation) variant β€” soft yellow "waiting" tone, no green
3293
- to avoid implying success before there's a song. Same shape as the
3294
- success state so the slot doesn't jump around when content changes. */
3295
- .success-banner-idle {
3296
- background: rgba(245, 200, 66, 0.14); /* whisper of crayon-yellow */
3297
- border: 2px dashed rgba(45, 49, 66, 0.40);
3298
- border-radius: 12px 16px 10px 14px;
3299
- color: var(--ink);
3300
- opacity: 0.78;
3301
- font-family: 'Patrick Hand', sans-serif;
3302
- font-size: 1.0rem;
3303
- padding: 8px 14px;
3304
- text-align: center;
3305
- }
3306
-
3307
- /* ---------- "Both inputs" notice (in the draw card) ---------------------
3308
- Soft, low-key, informational tone. Not red/alarming because the user
3309
- didn't do anything wrong β€” we're just being transparent about which
3310
- input wins when both are present. */
3311
- #both-inputs-notice {
3312
- margin: 14px 18px 0;
3313
- }
3314
- .both-inputs-inner {
3315
- background: rgba(245, 200, 66, 0.18); /* whisper of crayon-yellow */
3316
- border: 2px dashed rgba(45, 49, 66, 0.45);
3317
- border-radius: 12px 16px 10px 14px;
3318
- color: var(--ink);
3319
- font-family: 'Patrick Hand', sans-serif;
3320
- font-size: 1.0rem;
3321
- padding: 10px 14px;
3322
- line-height: 1.4;
3323
- }
3324
- .both-inputs-inner .both-inputs-icon {
3325
- margin-right: 4px;
3326
- font-size: 1.1rem;
3327
- }
3328
- """
3329
-
3330
-
3331
  # JS injected once; the buttons call pickInstrument() which writes the chosen
3332
  # value into the hidden Gradio textbox for that group and triggers its change.
3333
  SELECTION_JS = """
@@ -3497,7 +2495,7 @@ function flashComboHint() {
3497
  """
3498
 
3499
 
3500
- with gr.Blocks(css=CSS, title="Lolaby", theme=gr.themes.Citrus(),
3501
  head=f"<script>{SELECTION_JS}</script>") as demo:
3502
  gr.HTML("""
3503
  <div id="title-block">
@@ -3883,7 +2881,7 @@ gr.mount_gradio_app(
3883
  demo,
3884
  path="/",
3885
  theme=gr.themes.Citrus(),
3886
- css=CSS,
3887
  head=f"<script>{SELECTION_JS}</script>",
3888
  )
3889
 
 
11
  Aesthetic: children's drawing β€” crayon textures, wobbly hand-drawn borders.
12
  """
13
 
14
+ # ZeroGPU requirement: `spaces` must be imported BEFORE any CUDA/torch-touching
15
+ # library, and at least one @spaces.GPU-decorated function must be registered at
16
+ # import time, or the Space aborts at startup with:
17
+ # "No @spaces.GPU function detected during startup"
18
+ # Our real GPU function (describe_with_vision) lives in draw/vision.py, which is
19
+ # imported below behind a try/except β€” if that import ever fails, ZeroGPU would
20
+ # see zero decorated functions. So we import spaces first and register a tiny
21
+ # top-level probe here that ALWAYS exists. It's never called; it just guarantees
22
+ # the startup scan succeeds regardless of what happens to the vision import.
23
+ try:
24
+ import spaces
25
+ _HAVE_SPACES = True
26
+ except ImportError:
27
+ # Not on a ZeroGPU Space (e.g. local/laptop or a CPU Space). Provide a no-op
28
+ # decorator so the probe definition below is harmless. Mirrors HF's behavior.
29
+ class _SpacesShim:
30
+ def GPU(self, *args, **kwargs):
31
+ if len(args) == 1 and callable(args[0]) and not kwargs:
32
+ return args[0]
33
+ def deco(fn):
34
+ return fn
35
+ return deco
36
+ spaces = _SpacesShim()
37
+ _HAVE_SPACES = False
38
+
39
+
40
+ @spaces.GPU(duration=10)
41
+ def _zerogpu_startup_probe():
42
+ # Never invoked. Exists only so ZeroGPU's startup scan always finds a
43
+ # @spaces.GPU function, even if the vision module import fails.
44
+ return True
45
+
46
+
47
  import base64
48
  import glob
49
  import os
 
77
  # Vision module (drawing β†’ "loves" phrase). Falls back to stroke analysis
78
  # internally if MiniCPM-V can't load, so the drawing input always produces
79
  # SOMETHING for the lullaby to be about.
80
+ #
81
+ # We catch Exception (not just ImportError) so a heavy transitive failure
82
+ # doesn't crash the whole app, but we PRINT the traceback β€” a silent swallow
83
+ # here previously hid the real reason the vision module wasn't loading on the
84
+ # Space. The top-level _zerogpu_startup_probe above still guarantees ZeroGPU's
85
+ # startup scan passes even if this import fails.
86
  try:
87
  import draw.vision as vision # vision.describe(image) -> {"loves": str, ...}
88
+ except Exception as _vision_import_err:
89
+ import traceback as _tb
90
+ print("WARNING: could not import draw.vision β€” vision disabled, "
91
+ "falling back to stroke analysis. Real error follows:")
92
+ _tb.print_exc()
93
  vision = None
94
 
95
 
 
2326
  """
2327
 
2328
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2329
  # JS injected once; the buttons call pickInstrument() which writes the chosen
2330
  # value into the hidden Gradio textbox for that group and triggers its change.
2331
  SELECTION_JS = """
 
2495
  """
2496
 
2497
 
2498
+ with gr.Blocks(css_paths="style.css", title="Lolaby", theme=gr.themes.Citrus(),
2499
  head=f"<script>{SELECTION_JS}</script>") as demo:
2500
  gr.HTML("""
2501
  <div id="title-block">
 
2881
  demo,
2882
  path="/",
2883
  theme=gr.themes.Citrus(),
2884
+ css_paths="style.css",
2885
  head=f"<script>{SELECTION_JS}</script>",
2886
  )
2887
 
style.css ADDED
@@ -0,0 +1,1183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Fredoka+One&family=Patrick+Hand&family=Gloria+Hallelujah&display=swap');
2
+
3
+ :root {
4
+ --paper: #fffef7;
5
+ --paper-edge: #f5ecd9;
6
+ --crayon-blue: #4a90d9;
7
+ --crayon-red: #e15a4e;
8
+ --crayon-yellow: #f5c842;
9
+ --crayon-green: #6fb05e;
10
+ --crayon-purple: #9b6db5;
11
+ --crayon-pink: #f08ab0;
12
+ --crayon-orange: #f0934a;
13
+ --ink: #2d3142;
14
+ /* dark mode surface tokens (overridden below when .dark-mode is active) */
15
+ --surface-card: var(--paper);
16
+ --surface-input: rgba(255, 255, 255, 0.6);
17
+ --surface-dropdown: #ffffff;
18
+ --surface-canvas: #f3f1ec;
19
+ --surface-canvas-inner: #fdfbf3;
20
+ }
21
+
22
+ /* ── Dark mode ── */
23
+ .dark-mode {
24
+ --paper: #1a1b2e;
25
+ --paper-edge: #16172a;
26
+ --ink: #e8e4d9;
27
+ --crayon-blue: #6aabf0;
28
+ --crayon-yellow: #f5c842;
29
+ --crayon-red: #f07066;
30
+ --crayon-orange: #f5a060;
31
+ --crayon-green: #80cc6e;
32
+ --surface-card: #1e2040;
33
+ --surface-input: rgba(30, 32, 64, 0.85);
34
+ --surface-dropdown: #252848;
35
+ --surface-canvas: #1c1e38;
36
+ --surface-canvas-inner: #23264a;
37
+ }
38
+
39
+ /* Dark mode toggle button */
40
+ #dark-toggle-btn {
41
+ background: var(--paper) !important;
42
+ border: 2.5px solid var(--ink) !important;
43
+ border-radius: 50px !important;
44
+ padding: 6px 14px !important;
45
+ font-family: 'Fredoka One', cursive !important;
46
+ font-size: 1.05rem !important;
47
+ color: var(--ink) !important;
48
+ cursor: pointer !important;
49
+ box-shadow: 3px 3px 0 rgba(45, 49, 66, 0.15) !important;
50
+ transition: background 0.2s, color 0.2s, border-color 0.2s !important;
51
+ width: auto !important;
52
+ margin: 8px 0 0 0 !important;
53
+ display: inline-flex !important;
54
+ align-items: center !important;
55
+ gap: 6px !important;
56
+ }
57
+
58
+ #dark-toggle-btn:hover {
59
+ transform: translate(-1px, -1px) !important;
60
+ box-shadow: 4px 4px 0 rgba(45, 49, 66, 0.2) !important;
61
+ }
62
+
63
+ .gradio-container {
64
+ background: var(--paper) !important;
65
+ background-image:
66
+ radial-gradient(circle at 20% 30%, rgba(245, 200, 66, 0.06) 0%, transparent 30%),
67
+ radial-gradient(circle at 80% 70%, rgba(74, 144, 217, 0.06) 0%, transparent 30%),
68
+ radial-gradient(circle at 50% 90%, rgba(240, 138, 176, 0.06) 0%, transparent 30%);
69
+ font-family: 'Patrick Hand', sans-serif !important;
70
+ color: var(--ink) !important;
71
+ min-height: 100vh;
72
+ transition: background 0.3s ease, color 0.3s ease;
73
+ }
74
+
75
+ .dark-mode .gradio-container {
76
+ background-image:
77
+ radial-gradient(circle at 20% 30%, rgba(106, 171, 240, 0.05) 0%, transparent 30%),
78
+ radial-gradient(circle at 80% 70%, rgba(155, 109, 181, 0.05) 0%, transparent 30%),
79
+ radial-gradient(circle at 50% 90%, rgba(245, 200, 66, 0.04) 0%, transparent 30%) !important;
80
+ }
81
+
82
+ #title-block {
83
+ text-align: center;
84
+ padding: 40px 24px 16px;
85
+ }
86
+
87
+ #title-block::after {
88
+ content: "";
89
+ display: block;
90
+ width: 280px;
91
+ height: 18px;
92
+ margin: 20px auto 0;
93
+ /* Hand-drawn squiggle. Inline SVG as a background image β€” single ink color,
94
+ gentle wave reading as "kid drew a wavy line under the title". */
95
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 280 18' fill='none' stroke='%232d3142' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'><path d='M4 9 C 22 2, 38 16, 56 9 S 90 2, 108 9 S 142 16, 160 9 S 194 2, 212 9 S 246 16, 276 9'/></svg>");
96
+ background-repeat: no-repeat;
97
+ background-size: contain;
98
+ background-position: center;
99
+ opacity: 0.85;
100
+ }
101
+
102
+ #title-block h1 {
103
+ font-family: 'Fredoka One', cursive !important;
104
+ font-weight: 400 !important;
105
+ font-size: 4.2rem !important;
106
+ color: var(--ink) !important;
107
+ margin: 0 !important;
108
+ letter-spacing: 0.02em;
109
+ line-height: 1 !important;
110
+ text-shadow: 3px 3px 0px rgba(245, 200, 66, 0.45);
111
+ }
112
+
113
+ #title-block .subtitle {
114
+ font-family: 'Patrick Hand', sans-serif !important;
115
+ font-size: 1.4rem !important;
116
+ color: var(--crayon-blue) !important;
117
+ margin-top: 12px;
118
+ font-weight: 400;
119
+ transform: rotate(-1.5deg);
120
+ display: inline-block;
121
+ }
122
+
123
+ /* Orientation copy under the title. Sits BELOW the sketched squiggle (the
124
+ #title-block::after pseudo-element) so the squiggle reads as the title's
125
+ closing flourish, and the explainer is its own calmer block underneath.
126
+ Plain warm prose β€” what the app IS and who does WHAT, in two lines. */
127
+ #title-explainer {
128
+ font-family: 'Patrick Hand', sans-serif;
129
+ font-size: 1.15rem;
130
+ color: var(--ink);
131
+ opacity: 0.82;
132
+ margin: 12px auto 24px;
133
+ max-width: 640px;
134
+ line-height: 1.5;
135
+ text-align: center;
136
+ }
137
+
138
+ #title-explainer .explainer-roles {
139
+ display: block;
140
+ margin-top: 6px;
141
+ font-size: 1.0rem;
142
+ opacity: 0.85;
143
+ font-style: italic;
144
+ }
145
+
146
+ /* ---------- Step diagram (1 ➜ 2 ➜ 3) -------------------------------------
147
+ Wayfinding strip just under the title. Three larger pills connected
148
+ by dashed lines, hand-drawn aesthetic to match the rest of the app.
149
+ Each pill contains: number badge (popped above), emoji icon, short
150
+ heading, and a two-line description. Step 1 is styled with a dashed
151
+ border to visually telegraph "optional" β€” backs up the explicit
152
+ "(optional)" text underneath.
153
+
154
+ The diagram doubles as the main explainer of how Lolaby works, so it
155
+ sits prominently above the cards. */
156
+ #step-diagram {
157
+ display: flex;
158
+ align-items: flex-start;
159
+ /* top-align so descs of different
160
+ lengths don't shift the headings */
161
+ justify-content: center;
162
+ gap: 6px;
163
+ margin: 8px auto 32px;
164
+ flex-wrap: wrap;
165
+ max-width: 920px;
166
+ }
167
+
168
+ #step-diagram .step {
169
+ display: flex;
170
+ flex-direction: column;
171
+ align-items: center;
172
+ gap: 4px;
173
+ padding: 14px 18px 16px;
174
+ background: rgba(255, 255, 255, 0.35);
175
+ border: 2.5px solid var(--ink);
176
+ border-radius: 16px 20px 14px 18px;
177
+ /* irregular to look hand-drawn */
178
+ width: 240px;
179
+ flex: 0 1 240px;
180
+ box-shadow: 2px 2px 0 rgba(45, 49, 66, 0.12);
181
+ position: relative;
182
+ }
183
+
184
+ .dark-mode #step-diagram .step {
185
+ background: rgba(30, 32, 64, 0.75);
186
+ box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.3);
187
+ }
188
+
189
+ #step-diagram .step-optional {
190
+ border-style: dashed;
191
+ background: rgba(255, 255, 255, 0.35);
192
+ opacity: 0.92;
193
+ }
194
+
195
+ .dark-mode #step-diagram .step-optional {
196
+ background: rgba(30, 32, 64, 0.35);
197
+ }
198
+
199
+ #step-diagram .step-num {
200
+ font-family: 'Fredoka One', sans-serif;
201
+ font-size: 1.25rem;
202
+ color: var(--ink);
203
+ background: var(--crayon-yellow);
204
+ width: 30px;
205
+ height: 30px;
206
+ border-radius: 50%;
207
+ border: 2.5px solid var(--ink);
208
+ display: inline-flex;
209
+ align-items: center;
210
+ justify-content: center;
211
+ line-height: 1;
212
+ margin-top: -28px;
213
+ /* pop the number above the pill */
214
+ margin-bottom: 4px;
215
+ box-shadow: 1px 1px 0 rgba(45, 49, 66, 0.15);
216
+ }
217
+
218
+ #step-diagram .step-icon {
219
+ font-size: 1.7rem;
220
+ line-height: 1;
221
+ margin: 2px 0;
222
+ }
223
+
224
+ #step-diagram .step-label {
225
+ font-family: 'Fredoka One', sans-serif;
226
+ font-size: 1.05rem;
227
+ color: var(--ink);
228
+ text-align: center;
229
+ line-height: 1.2;
230
+ margin-bottom: 4px;
231
+ }
232
+
233
+ #step-diagram .step-sub {
234
+ font-family: 'Patrick Hand', sans-serif;
235
+ font-weight: normal;
236
+ font-size: 0.85rem;
237
+ opacity: 0.7;
238
+ display: block;
239
+ margin-top: 2px;
240
+ }
241
+
242
+ #step-diagram .step-desc {
243
+ font-family: 'Patrick Hand', sans-serif;
244
+ font-size: 0.95rem;
245
+ color: var(--ink);
246
+ opacity: 0.82;
247
+ text-align: center;
248
+ line-height: 1.35;
249
+ margin-top: 2px;
250
+ }
251
+
252
+ #step-diagram .step-connector {
253
+ flex: 0 0 24px;
254
+ height: 0;
255
+ border-top: 2.5px dashed var(--ink);
256
+ opacity: 0.5;
257
+ /* Connector centred vertically against the pill's vertical mid-point.
258
+ The pills are ~150px tall now, so the connector needs a ~70px top
259
+ offset so it visually meets the middle of the boxes, not the top. */
260
+ margin-top: 70px;
261
+ border-radius: 50%;
262
+ }
263
+
264
+ @media (max-width: 880px) {
265
+
266
+ /* When the strip can't fit three pills + connectors, stack vertically.
267
+ Hide connectors so the pills stack cleanly. */
268
+ #step-diagram {
269
+ flex-direction: column;
270
+ align-items: center;
271
+ gap: 24px;
272
+ }
273
+
274
+ #step-diagram .step-connector {
275
+ display: none;
276
+ }
277
+
278
+ #step-diagram .step {
279
+ width: 280px;
280
+ flex: 0 0 auto;
281
+ }
282
+ }
283
+
284
+ /* ---------- Step badge (small number inside a card tab) ------------------
285
+ Reinforces the diagram by anchoring each card to its step number. Once
286
+ a user scrolls past the diagram they can still see which step they're
287
+ on by looking at the card tab. */
288
+ .step-badge {
289
+ display: inline-flex;
290
+ align-items: center;
291
+ justify-content: center;
292
+ width: 20px;
293
+ height: 20px;
294
+ margin-right: 6px;
295
+ background: #2d3142 !important;
296
+ color: #ffffff !important;
297
+ border-radius: 50%;
298
+ font-family: 'Fredoka One', sans-serif;
299
+ font-size: 0.85rem;
300
+ line-height: 1;
301
+ vertical-align: middle;
302
+ position: relative;
303
+ top: -1px;
304
+ }
305
+
306
+ .step-badge-inline {
307
+ background: var(--crayon-yellow);
308
+ color: var(--ink);
309
+ border: 2px solid var(--ink);
310
+ margin-right: 8px;
311
+ }
312
+
313
+ /* ---------- Step-3 hint above the Sing button ---------------------------
314
+ Sits right above the primary CTA so a non-power user sees a clear
315
+ "this is the next thing to do" line before the button itself. */
316
+ .step-3-hint {
317
+ font-family: 'Patrick Hand', sans-serif;
318
+ font-size: 1.05rem;
319
+ color: var(--ink);
320
+ text-align: center;
321
+ margin: 18px 0 8px;
322
+ opacity: 0.9;
323
+ }
324
+
325
+ label>span,
326
+ .gradio-container label>span:first-child {
327
+ font-family: 'Fredoka One', cursive !important;
328
+ font-size: 1.2rem !important;
329
+ color: var(--ink) !important;
330
+ font-weight: 400 !important;
331
+ }
332
+
333
+ input[type="text"],
334
+ input[type="number"],
335
+ textarea,
336
+ select {
337
+ background: rgba(255, 255, 255, 0.6) !important;
338
+ border: 2.5px solid var(--ink) !important;
339
+ border-radius: 14px 18px 12px 16px !important;
340
+ color: var(--ink) !important;
341
+ font-family: 'Patrick Hand', sans-serif !important;
342
+ font-size: 1.15rem !important;
343
+ padding: 10px 14px !important;
344
+ box-shadow: 3px 3px 0 rgba(45, 49, 66, 0.12);
345
+ transition: all 0.18s ease;
346
+ }
347
+
348
+ .dark-mode input[type="text"],
349
+ .dark-mode input[type="number"],
350
+ .dark-mode textarea,
351
+ .dark-mode select,
352
+ .dark-mode input {
353
+ background: var(--paper) !important;
354
+ color: var(--ink) !important;
355
+ box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.25) !important;
356
+ }
357
+
358
+ input[type="text"]:focus,
359
+ textarea:focus,
360
+ select:focus {
361
+ border-color: var(--crayon-blue) !important;
362
+ box-shadow: 4px 4px 0 var(--crayon-yellow);
363
+ outline: none;
364
+ }
365
+
366
+ /* Dropdown: style only the visible trigger input, leave the options panel
367
+ with Gradio defaults so it stays usable. */
368
+ .gradio-dropdown input {
369
+ background: rgba(255, 255, 255, 0.6) !important;
370
+ border: 2.5px solid var(--ink) !important;
371
+ border-radius: 14px 18px 12px 16px !important;
372
+ color: var(--ink) !important;
373
+ font-family: 'Patrick Hand', sans-serif !important;
374
+ font-size: 1.15rem !important;
375
+ padding: 10px 14px !important;
376
+ box-shadow: 3px 3px 0 rgba(45, 49, 66, 0.12);
377
+ }
378
+
379
+ .dark-mode .gradio-dropdown input {
380
+ background: rgba(30, 32, 64, 0.85) !important;
381
+ color: var(--ink) !important;
382
+ }
383
+
384
+ /* Make sure the options popup is on top and uses readable defaults */
385
+ .gradio-dropdown ul,
386
+ .gradio-dropdown [role="listbox"],
387
+ ul.options {
388
+ background: #ffffff !important;
389
+ border: 2px solid var(--ink) !important;
390
+ border-radius: 8px !important;
391
+ font-family: 'Patrick Hand', sans-serif !important;
392
+ color: var(--ink) !important;
393
+ z-index: 9999 !important;
394
+ box-shadow: 4px 4px 0 rgba(45, 49, 66, 0.18);
395
+ }
396
+
397
+ .gradio-dropdown ul li,
398
+ .gradio-dropdown [role="option"],
399
+ ul.options li {
400
+ padding: 8px 14px !important;
401
+ font-family: 'Patrick Hand', sans-serif !important;
402
+ font-size: 1.05rem !important;
403
+ color: var(--ink) !important;
404
+ background: transparent !important;
405
+ cursor: pointer;
406
+ }
407
+
408
+ .gradio-dropdown ul li:hover,
409
+ .gradio-dropdown [role="option"]:hover,
410
+ ul.options li:hover {
411
+ background: var(--crayon-yellow) !important;
412
+ }
413
+
414
+ .dark-mode .gradio-dropdown ul,
415
+ .dark-mode .gradio-dropdown [role="listbox"],
416
+ .dark-mode ul.options {
417
+ color: var(--paper) !important;
418
+ }
419
+
420
+ .dark-mode .reset-button {
421
+ color: var(--ink) !important;
422
+ }
423
+
424
+ .dark-mode ul.options li,
425
+ .dark-mode .gradio-dropdown ul li,
426
+ .dark-mode .gradio-dropdown [role="option"] {
427
+ color: var(--paper) !important;
428
+ }
429
+
430
+ input[type="range"] {
431
+ accent-color: var(--crayon-red) !important;
432
+ }
433
+
434
+ .form-card {
435
+ background: var(--paper) !important;
436
+ border: 3px solid var(--ink) !important;
437
+ border-radius: 22px 18px 26px 16px !important;
438
+ padding: 28px !important;
439
+ box-shadow: 5px 5px 0 rgba(45, 49, 66, 0.08);
440
+ /* No transform here β€” it creates a stacking context that breaks
441
+ Gradio's fixed-position dropdown popup. */
442
+ }
443
+
444
+ .dark-mode .form-card {
445
+ box-shadow: 5px 5px 0 rgba(0, 0, 0, 0.3) !important;
446
+ }
447
+
448
+ .dark-mode .form-card div {
449
+ background-color: var(--paper) !important;
450
+ }
451
+
452
+ .form-card-right {
453
+ background: var(--paper) !important;
454
+ border: 3px solid var(--ink) !important;
455
+ border-radius: 18px 24px 16px 22px !important;
456
+ padding: 28px !important;
457
+ box-shadow: 5px 5px 0 rgba(245, 200, 66, 0.25);
458
+ }
459
+
460
+ .dark-mode .form-card-right {
461
+ box-shadow: 5px 5px 0 rgba(245, 200, 66, 0.12) !important;
462
+ }
463
+
464
+ .dark-mode .form-card-right div {
465
+ background-color: var(--paper) !important;
466
+ }
467
+
468
+ /* ---- Drawing canvas card ----------------------------------------------- */
469
+ /* Matches the form-card aesthetic: paper background, ink border, wobbly
470
+ corners that read as "drawn by hand". Sits above the form as the hero. */
471
+ .draw-card {
472
+ background: var(--paper) !important;
473
+ border: 3px solid var(--ink) !important;
474
+ border-radius: 24px 16px 22px 18px !important;
475
+ padding: 24px 28px 18px !important;
476
+ box-shadow: 5px 5px 0 rgba(74, 144, 217, 0.18);
477
+ margin-bottom: 12px;
478
+ }
479
+
480
+ .dark-mode .draw-card {
481
+ box-shadow: 5px 5px 0 rgba(106, 171, 240, 0.12) !important;
482
+ }
483
+
484
+ .draw-card .card-heading {
485
+ margin-bottom: 4px;
486
+ }
487
+
488
+ .draw-hint {
489
+ font-family: 'Patrick Hand', sans-serif;
490
+ color: var(--ink);
491
+ opacity: 0.7;
492
+ font-size: 1.0rem;
493
+ margin: 0 0 14px 0;
494
+ }
495
+
496
+ /* ---------- Split draw card: canvas | upload ------------------------------
497
+ The draw card hosts two side-by-side image inputs so trackpad-allergic
498
+ users (i.e. small children) can upload a phone photo of a paper drawing
499
+ instead of trying to draw with a mouse. Soft vertical divider between
500
+ the two halves; on narrow viewports the columns stack and the divider
501
+ becomes a horizontal one. */
502
+ #draw-row {
503
+ gap: 18px;
504
+ align-items: stretch;
505
+ }
506
+
507
+ .draw-half {
508
+ padding: 0 6px;
509
+ position: relative;
510
+ z-index: 2;
511
+ /* sit above background doodles */
512
+ }
513
+
514
+ /* Vertical divider between the two halves, drawn on the canvas-side's
515
+ right edge so it sits exactly between the two columns. */
516
+ .draw-half-canvas {
517
+ border-right: 2px dashed rgba(45, 49, 66, 0.18);
518
+ padding-right: 18px;
519
+ }
520
+
521
+ .draw-half-upload {
522
+ padding-left: 6px;
523
+ }
524
+
525
+ /* Small label above each half so people immediately see "this side OR
526
+ that side." Same font as the hint, slightly bolder, slightly darker. */
527
+ .draw-half-label {
528
+ font-family: 'Patrick Hand', sans-serif;
529
+ font-size: 1.05rem;
530
+ color: var(--ink);
531
+ opacity: 0.85;
532
+ margin: 0 0 8px;
533
+ font-weight: 700;
534
+ letter-spacing: 0.01em;
535
+ }
536
+
537
+ /* The little caption under the upload widget, mirroring the canvas's
538
+ `.draw-hint` styling but scoped to the upload half. */
539
+ .draw-half-hint {
540
+ font-family: 'Patrick Hand', sans-serif;
541
+ color: var(--ink);
542
+ opacity: 0.65;
543
+ font-size: 0.95rem;
544
+ margin: 8px 2px 0;
545
+ text-align: center;
546
+ }
547
+
548
+ /* Match the upload widget's idle look to the canvas surround so the two
549
+ halves feel like siblings. Gradio's default upload widget has a plain
550
+ white drop-zone β€” we soften that to the same warm taupe used for the
551
+ canvas surround. */
552
+ .gradio-container #draw-upload {
553
+ background-color: transparent !important;
554
+ background-image: none !important;
555
+ }
556
+
557
+ .gradio-container #draw-upload .upload-container,
558
+ .gradio-container #draw-upload .image-container {
559
+ border: 2px dashed rgba(45, 49, 66, 0.25) !important;
560
+ border-radius: 14px !important;
561
+ background-color: #fdfbf3 !important;
562
+ }
563
+
564
+ .dark-mode .gradio-container #draw-upload .upload-container,
565
+ .dark-mode .gradio-container #draw-upload .image-container {
566
+ border: 2px dashed rgba(232, 228, 217, 0.25) !important;
567
+ background-color: var(--paper) !important;
568
+ }
569
+
570
+ /* When viewport gets narrow, stack the two halves vertically and convert
571
+ the vertical divider into a horizontal one so it still reads as a
572
+ "this OR that" separator. */
573
+ @media (max-width: 760px) {
574
+ .draw-half-canvas {
575
+ border-right: none;
576
+ border-bottom: 2px dashed rgba(45, 49, 66, 0.18);
577
+ padding-right: 0;
578
+ padding-bottom: 18px;
579
+ margin-bottom: 6px;
580
+ }
581
+
582
+ .draw-half-upload {
583
+ padding-left: 0;
584
+ }
585
+ }
586
+
587
+ /* Tab-pill labels that sit ABOVE each card heading, classifying who the
588
+ section is for. Visually paired (same shape) but color-differentiated
589
+ (kid = playful crayon-yellow accent, grown-up = grounded ink) so the
590
+ two roles are recognisable at a glance. */
591
+ .card-tab {
592
+ font-family: 'Patrick Hand', sans-serif;
593
+ font-size: 0.95rem;
594
+ font-weight: 400;
595
+ display: inline-block;
596
+ padding: 4px 14px;
597
+ border-radius: 12px 16px 14px 12px;
598
+ margin: 0 0 10px;
599
+ letter-spacing: 0.02em;
600
+ transform: rotate(-1.2deg);
601
+ border: 2px solid var(--ink);
602
+ }
603
+
604
+ .card-tab-kid {
605
+ background: #f4d8a0;
606
+ color: var(--ink);
607
+ }
608
+
609
+ .dark-mode .card-tab-kid {
610
+ color: var(--paper) !important;
611
+ }
612
+
613
+ /* Grown-up tab: soft crayon-blue wash β€” paired with yellow (the kid tab) as
614
+ a classic complementary crayon pair, calm enough to read as the more
615
+ sober adult-facing section. Solid border (the dashed border had it
616
+ visually colliding with the Clear-drawing button). */
617
+ .card-tab-grownup {
618
+ background: rgba(74, 144, 217, 0.22) !important;
619
+ /* crayon-blue @ 22% */
620
+ color: var(--ink);
621
+ }
622
+
623
+ /* Subtitle under the form heading β€” explains what's required vs optional in
624
+ one breath, so the user isn't surprised by validation errors. Same
625
+ visual weight as .draw-hint so the two cards feel like siblings. */
626
+ .form-role-hint {
627
+ font-family: 'Patrick Hand', sans-serif;
628
+ color: var(--ink);
629
+ opacity: 0.7;
630
+ font-size: 1.0rem;
631
+ margin: 0 0 14px 0;
632
+ }
633
+
634
+ /* The Sketchpad itself: give the inner canvas a creamy "paper" feel and
635
+ the surround a dashed-ink border so it sits inside our card. The actual
636
+ surround color is set lower in the taupe block below. */
637
+ #draw-canvas .image-container,
638
+ #draw-canvas canvas {
639
+ background: #fdfbf3 !important;
640
+ border-radius: 14px !important;
641
+ }
642
+
643
+ #draw-canvas {
644
+ border: 2.5px dashed var(--ink) !important;
645
+ border-radius: 14px !important;
646
+ overflow: hidden;
647
+ }
648
+
649
+ #draw-canvas canvas {
650
+ border: none !important;
651
+ }
652
+
653
+ /* Drawing canvas surround.
654
+ IDLE state: warm muted taupe β€” repainted via CSS-variable overrides
655
+ scoped to #draw-canvas (Gradio uses --block-background-fill and friends
656
+ to color component surrounds; overriding them recolors the idle wrapper
657
+ without fighting Sketchpad's active editor chrome).
658
+ ACTIVE editing state: Gradio's Sketchpad swaps in its own dark editor
659
+ surface using inline styles / different variables, which I'm accepting
660
+ as a constraint β€” fighting it produced more side effects than wins. */
661
+ .gradio-container #draw-canvas {
662
+ --block-background-fill: #f3f1ec !important;
663
+ --background-fill-primary: #f3f1ec !important;
664
+ --background-fill-secondary: #f3f1ec !important;
665
+ --neutral-50: #f3f1ec !important;
666
+ --neutral-100: #f3f1ec !important;
667
+ background-color: #f3f1ec !important;
668
+ background-image: none !important;
669
+ }
670
+
671
+ .dark-mode .gradio-container #draw-canvas {
672
+ --block-background-fill: var(--paper) !important;
673
+ --background-fill-primary: var(--paper) !important;
674
+ --background-fill-secondary: var(--paper) !important;
675
+ --neutral-50: var(--paper) !important;
676
+ --neutral-100: var(--paper) !important;
677
+ background-color: var(--paper) !important;
678
+ }
679
+
680
+ /* Also paint direct child wrappers (Gradio's Sketchpad nests several divs;
681
+ any "inactive" surround inherits/uses these surfaces). The active editor
682
+ uses a deeper element with its own inline style that we no longer fight. */
683
+ .gradio-container #draw-canvas>div,
684
+ .gradio-container #draw-canvas .block,
685
+ .gradio-container #draw-canvas .wrap,
686
+ .gradio-container #draw-canvas .container {
687
+ background-color: #f3f1ec !important;
688
+ background-image: none !important;
689
+ }
690
+
691
+ .dark-mode .gradio-container #draw-canvas>div,
692
+ .dark-mode .gradio-container #draw-canvas .block,
693
+ .dark-mode .gradio-container #draw-canvas .wrap,
694
+ .dark-mode .gradio-container #draw-canvas .container {
695
+ background-color: var(--paper) !important;
696
+ }
697
+
698
+ /* The inner <canvas> stays cream-white when idle (it's the "paper") */
699
+ .gradio-container #draw-canvas canvas {
700
+ background-color: #fdfbf3 !important;
701
+ background-image: none !important;
702
+ }
703
+
704
+ .dark-mode .gradio-container #draw-canvas canvas {
705
+ background-color: #23264a !important;
706
+ }
707
+
708
+ /* The "Clear drawing" button β€” secondary visual weight; the primary CTA is
709
+ still "Sing with Lola" further down the page. */
710
+ #reset-drawing-btn {
711
+ margin-top: 10px !important;
712
+ background: transparent !important;
713
+ border: 2px dashed var(--ink) !important;
714
+ color: var(--ink) !important;
715
+ font-family: 'Patrick Hand', sans-serif !important;
716
+ font-size: 1.0rem !important;
717
+ padding: 6px 16px !important;
718
+ border-radius: 12px !important;
719
+ box-shadow: none !important;
720
+ width: auto !important;
721
+ align-self: flex-start;
722
+ }
723
+
724
+ #reset-drawing-btn:hover {
725
+ background: rgba(45, 49, 66, 0.04) !important;
726
+ }
727
+
728
+ /* ---- "What Lola saw" hint ----------------------------------------------
729
+ Sits between the audio player and the lyrics. Reserves a CONSTANT amount
730
+ of vertical space whether or not it has content, so the layout never
731
+ reflows when content arrives (which was previously pushing the lyrics
732
+ down on the first generation and overlapping them mid-transition).
733
+ The visual chrome (background, border, padding) only renders when the
734
+ inner .saw-hint div exists; the outer container keeps its margin always. */
735
+ #saw-hint {
736
+ margin: 4px 0 14px !important;
737
+ /* ALWAYS reserve this space */
738
+ min-height: 12px;
739
+ /* small constant gap when empty */
740
+ }
741
+
742
+ .saw-hint {
743
+ font-family: 'Patrick Hand', sans-serif;
744
+ color: var(--ink);
745
+ background: rgba(245, 200, 66, 0.10);
746
+ /* faint crayon-yellow */
747
+ border-left: 4px solid var(--crayon-yellow);
748
+ border-radius: 8px 12px 10px 8px;
749
+ padding: 10px 14px;
750
+ margin: 0;
751
+ /* margin lives on #saw-hint now */
752
+ font-size: 1.0rem;
753
+ line-height: 1.45;
754
+ }
755
+
756
+ .dark-mode .saw-label {
757
+ color: var(--ink) !important;
758
+ }
759
+
760
+ .saw-hint .saw-label {
761
+ font-weight: 700;
762
+ margin-right: 4px;
763
+ }
764
+
765
+ .saw-hint em {
766
+ font-style: italic;
767
+ color: var(--ink);
768
+ opacity: 0.85;
769
+ }
770
+
771
+ .saw-hint .saw-plus {
772
+ margin-left: 6px;
773
+ opacity: 0.75;
774
+ font-size: 0.95rem;
775
+ }
776
+
777
+ .saw-hint-empty {
778
+ background: rgba(74, 144, 217, 0.08);
779
+ /* faint crayon-blue */
780
+ border-left-color: var(--crayon-blue);
781
+ font-style: italic;
782
+ }
783
+
784
+ /* Instrument grid */
785
+ .instr-grid {
786
+ display: grid !important;
787
+ grid-template-columns: repeat(4, 1fr) !important;
788
+ gap: 12px !important;
789
+ margin: 0 0 14px !important;
790
+ }
791
+
792
+ /* Gradio wraps every gr.HTML in a div with default margin; squash the gap
793
+ between the picker title and the instrument grid. */
794
+ .form-card-left .gr-block:has(> .instr-grid),
795
+ .form-card-left .gr-block:has(> #picker-hint) {
796
+ margin: 0 !important;
797
+ padding: 0 !important;
798
+ }
799
+
800
+ /* Slider β€” Gradio's value bubble sits in a small box that can clip the
801
+ rounded border at the bottom. Give the wrapper extra padding so the
802
+ bubble's full outline shows, and force no overflow clipping on the
803
+ surrounding block. */
804
+ .gr-slider,
805
+ .gradio-slider,
806
+ [data-testid="slider"] {
807
+ overflow: visible !important;
808
+ padding-bottom: 14px !important;
809
+ }
810
+
811
+ .gr-slider input[type="number"],
812
+ .gradio-slider input[type="number"],
813
+ [data-testid="slider"] input[type="number"] {
814
+ overflow: visible !important;
815
+ }
816
+
817
+ /* The bubble container itself */
818
+ .gr-slider .wrap,
819
+ .gradio-slider .wrap {
820
+ overflow: visible !important;
821
+ }
822
+
823
+ .instr-cell {
824
+ background: var(--paper) !important;
825
+ border: 2.5px solid var(--ink) !important;
826
+ border-radius: 16px 20px 14px 18px !important;
827
+ padding: 10px 6px 8px !important;
828
+ cursor: pointer;
829
+ box-shadow: 3px 3px 0 rgba(45, 49, 66, 0.12);
830
+ transition: all 0.2s ease;
831
+ display: flex;
832
+ flex-direction: column;
833
+ align-items: center;
834
+ gap: 4px;
835
+ font-family: 'Patrick Hand', sans-serif;
836
+ font-size: 1rem;
837
+ color: var(--ink);
838
+ }
839
+
840
+ .dark-mode .instr-cell {
841
+ box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.3) !important;
842
+ }
843
+
844
+ .dark-mode .instr-cell span {
845
+ color: var(--ink) !important;
846
+ }
847
+
848
+ .instr-cell:hover {
849
+ transform: translate(-2px, -2px) rotate(-2deg);
850
+ box-shadow: 5px 5px 0 rgba(45, 49, 66, 0.18);
851
+ }
852
+
853
+ .instr-cell.selected {
854
+ background: var(--crayon-yellow) !important;
855
+ box-shadow: 3px 3px 0 var(--ink) !important;
856
+ transform: rotate(-1deg);
857
+ }
858
+
859
+ .instr-cell img {
860
+ width: 56px;
861
+ height: 56px;
862
+ object-fit: contain;
863
+ pointer-events: none;
864
+ }
865
+
866
+ .instr-cell span {
867
+ pointer-events: none;
868
+ }
869
+
870
+ /* The little "1" / "2" badge in the corner of selected cells β€” tells the
871
+ user which slot they've assigned the instrument to. */
872
+ .instr-cell {
873
+ position: relative;
874
+ }
875
+
876
+ .slot-badge {
877
+ position: absolute;
878
+ top: -8px;
879
+ right: -8px;
880
+ background: #2d3142 !important;
881
+ color: #ffffff !important;
882
+ font-family: 'Fredoka One', cursive;
883
+ font-size: 0.85rem;
884
+ width: 22px;
885
+ height: 22px;
886
+ border-radius: 50%;
887
+ display: flex !important;
888
+ align-items: center;
889
+ justify-content: center;
890
+ box-shadow: 2px 2px 0 rgba(45, 49, 66, 0.25);
891
+ pointer-events: none;
892
+ }
893
+
894
+ #picker-hint {
895
+ font-family: 'Patrick Hand', sans-serif;
896
+ font-size: 0.95rem;
897
+ color: var(--crayon-blue);
898
+ margin: -2px 0 10px;
899
+ padding: 4px 8px;
900
+ border-radius: 6px;
901
+ transition: background 0.25s ease, color 0.25s ease;
902
+ min-height: 0;
903
+ }
904
+
905
+ #picker-hint:empty {
906
+ margin: 0;
907
+ padding: 0;
908
+ }
909
+
910
+ #picker-hint.hint-flash {
911
+ background: var(--crayon-red);
912
+ color: #fff;
913
+ transform: rotate(-0.5deg);
914
+ }
915
+
916
+ #picker-hint.hint-error {
917
+ font-weight: 700;
918
+ box-shadow: 2px 2px 0 rgba(45, 49, 66, 0.2);
919
+ }
920
+
921
+ .section-title {
922
+ font-family: 'Fredoka One', cursive;
923
+ font-size: 1.4rem;
924
+ color: var(--ink);
925
+ margin: 18px 0 2px;
926
+ display: flex;
927
+ align-items: baseline;
928
+ gap: 10px;
929
+ }
930
+
931
+ .section-title .which {
932
+ font-family: 'Patrick Hand', sans-serif;
933
+ font-size: 1rem;
934
+ color: var(--crayon-blue);
935
+ font-style: italic;
936
+ }
937
+
938
+ button#generate-btn,
939
+ .primary {
940
+ background: var(--crayon-red) !important;
941
+ color: #fffef7 !important;
942
+ border: 3px solid var(--ink) !important;
943
+ border-radius: 26px 18px 28px 22px !important;
944
+ font-family: 'Fredoka One', cursive !important;
945
+ font-size: 1.7rem !important;
946
+ font-weight: 400 !important;
947
+ padding: 18px 36px !important;
948
+ cursor: pointer;
949
+ box-shadow: 5px 5px 0 var(--ink);
950
+ transition: all 0.18s ease;
951
+ letter-spacing: 0.03em;
952
+ width: 100%;
953
+ margin-top: 16px;
954
+ }
955
+
956
+ button#generate-btn:hover {
957
+ transform: translate(-2px, -2px) rotate(-1deg);
958
+ box-shadow: 7px 7px 0 var(--ink);
959
+ background: var(--crayon-orange) !important;
960
+ }
961
+
962
+ button#generate-btn:active {
963
+ transform: translate(2px, 2px);
964
+ box-shadow: 2px 2px 0 var(--ink);
965
+ }
966
+
967
+ #output-lyrics textarea {
968
+ background:
969
+ linear-gradient(transparent 28px, rgba(74, 144, 217, 0.18) 28px, rgba(74, 144, 217, 0.18) 29px, transparent 29px) !important;
970
+ background-size: 100% 30px !important;
971
+ background-color: rgba(255, 255, 255, 0.7) !important;
972
+ font-family: 'Gloria Hallelujah', cursive !important;
973
+ font-size: 1.25rem !important;
974
+ line-height: 30px !important;
975
+ color: var(--ink) !important;
976
+ border: 2.5px solid var(--ink) !important;
977
+ border-radius: 16px 20px 14px 18px !important;
978
+ padding: 12px 18px !important;
979
+ }
980
+
981
+ .dark-mode #output-lyrics textarea {
982
+ background:
983
+ linear-gradient(transparent 28px, rgba(106, 171, 240, 0.15) 28px, rgba(106, 171, 240, 0.15) 29px, transparent 29px) !important;
984
+ background-size: 100% 30px !important;
985
+ background-color: rgba(30, 32, 64, 0.8) !important;
986
+ }
987
+
988
+ audio {
989
+ border-radius: 18px !important;
990
+ border: 2.5px solid var(--ink) !important;
991
+ background: var(--paper) !important;
992
+ box-shadow: 3px 3px 0 rgba(45, 49, 66, 0.1);
993
+ }
994
+
995
+ .dark-mode audio {
996
+ box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.3) !important;
997
+ }
998
+
999
+ .attribution {
1000
+ text-align: center;
1001
+ font-family: 'Patrick Hand', sans-serif;
1002
+ font-size: 0.85rem;
1003
+ color: rgba(45, 49, 66, 0.55);
1004
+ margin: 24px auto 16px;
1005
+ padding: 12px;
1006
+ }
1007
+
1008
+ .attribution a {
1009
+ color: var(--crayon-blue);
1010
+ text-decoration: none;
1011
+ border-bottom: 1px dashed var(--crayon-blue);
1012
+ }
1013
+
1014
+ /* Footer container β€” wraps the icon attribution and a professional credit
1015
+ block. A subtle divider sits between them. */
1016
+ .footer {
1017
+ text-align: center;
1018
+ padding: 8px 12px 20px;
1019
+ }
1020
+
1021
+ .footer .attribution {
1022
+ padding: 6px 12px 14px;
1023
+ border-bottom: 1px dashed rgba(45, 49, 66, 0.18);
1024
+ margin-bottom: 12px;
1025
+ }
1026
+
1027
+ /* Powered-by line β€” sits below the icon attribution. Small and quiet:
1028
+ names the open-source models behind the app and gives judges one-click
1029
+ access to each on the Hub. */
1030
+ .footer .powered-by {
1031
+ font-family: 'Patrick Hand', sans-serif;
1032
+ font-size: 0.92rem;
1033
+ color: var(--ink);
1034
+ opacity: 0.72;
1035
+ letter-spacing: 0.01em;
1036
+ padding: 0 0 4px;
1037
+ }
1038
+
1039
+ .footer .powered-by a {
1040
+ color: var(--ink);
1041
+ text-decoration: none;
1042
+ border-bottom: 1px dotted rgba(45, 49, 66, 0.45);
1043
+ padding: 0 1px;
1044
+ transition: color 0.2s ease, border-color 0.2s ease;
1045
+ }
1046
+
1047
+ .footer .powered-by a:hover {
1048
+ color: var(--crayon-blue);
1049
+ border-bottom-color: var(--crayon-blue);
1050
+ }
1051
+
1052
+ footer {
1053
+ display: none !important;
1054
+ }
1055
+
1056
+ .gr-form {
1057
+ gap: 14px !important;
1058
+ }
1059
+
1060
+ /* Hide the state-holder textboxes β€” they're in the DOM only so JS can write to them */
1061
+ .pick-state {
1062
+ display: none !important;
1063
+ }
1064
+
1065
+ /* Card-level section headings (replaces inline gr.Markdown "###" usage) */
1066
+ .card-heading {
1067
+ font-family: 'Fredoka One', cursive;
1068
+ font-size: 1.5rem;
1069
+ color: var(--ink);
1070
+ margin: 4px 0 14px;
1071
+ letter-spacing: 0.01em;
1072
+ }
1073
+
1074
+ /* Advanced settings accordion */
1075
+ #advanced-accordion {
1076
+ margin-top: 22px !important;
1077
+ border: 2.5px dashed var(--ink) !important;
1078
+ border-radius: 14px 18px 12px 16px !important;
1079
+ background: rgba(255, 255, 255, 0.3) !important;
1080
+ overflow: visible !important;
1081
+ }
1082
+
1083
+ .dark-mode #advanced-accordion {
1084
+ background: rgba(30, 32, 64, 0.5) !important;
1085
+ }
1086
+
1087
+ #advanced-accordion>button,
1088
+ #advanced-accordion .label-wrap,
1089
+ #advanced-accordion summary {
1090
+ font-family: 'Fredoka One', cursive !important;
1091
+ font-size: 1.1rem !important;
1092
+ color: var(--ink) !important;
1093
+ padding: 10px 14px !important;
1094
+ }
1095
+
1096
+ /* Inline error banner β€” appears under the form when validation fails */
1097
+ #error-banner {
1098
+ margin-top: 12px;
1099
+ }
1100
+
1101
+ .error-banner-inner {
1102
+ background: #fde6e3;
1103
+ border: 2.5px solid var(--crayon-red);
1104
+ border-radius: 14px 18px 12px 16px;
1105
+ color: #8a2a22;
1106
+ font-family: 'Patrick Hand', sans-serif;
1107
+ font-size: 1.1rem;
1108
+ padding: 12px 16px;
1109
+ box-shadow: 3px 3px 0 rgba(225, 90, 78, 0.25);
1110
+ transform: rotate(-0.4deg);
1111
+ }
1112
+
1113
+ .dark-mode .error-banner-inner {
1114
+ background: #3a1a1a;
1115
+ color: #f0a0a0;
1116
+ }
1117
+
1118
+ /* ---------- Success banner (between heading and audio in lullaby card) ---
1119
+ Sits between "Your lullaby" heading and the audio player. One-liner,
1120
+ no emoji β€” quiet confirmation that generation completed. Friendly
1121
+ green palette to mirror the error-banner's red, but smaller padding
1122
+ and no rotation since it's an inline confirmation, not a celebration. */
1123
+ #success-banner {
1124
+ margin: 4px 0 10px;
1125
+ }
1126
+
1127
+ .success-banner-inner {
1128
+ background: #e5f3dc;
1129
+ border: 2.5px solid #6fb05e;
1130
+ border-radius: 12px 16px 10px 14px;
1131
+ color: #2f5a26;
1132
+ font-family: 'Patrick Hand', sans-serif;
1133
+ font-size: 1.05rem;
1134
+ padding: 8px 14px;
1135
+ text-align: center;
1136
+ box-shadow: 2px 2px 0 rgba(111, 176, 94, 0.2);
1137
+ }
1138
+
1139
+ .dark-mode .success-banner-inner {
1140
+ background: #1a3020;
1141
+ color: #90cc80;
1142
+ }
1143
+
1144
+ /* Idle (pre-generation) variant β€” soft yellow "waiting" tone, no green
1145
+ to avoid implying success before there's a song. Same shape as the
1146
+ success state so the slot doesn't jump around when content changes. */
1147
+ .success-banner-idle {
1148
+ background: rgba(245, 200, 66, 0.14);
1149
+ /* whisper of crayon-yellow */
1150
+ border: 2px dashed rgba(45, 49, 66, 0.40);
1151
+ border-radius: 12px 16px 10px 14px;
1152
+ color: var(--ink);
1153
+ opacity: 0.78;
1154
+ font-family: 'Patrick Hand', sans-serif;
1155
+ font-size: 1.0rem;
1156
+ padding: 8px 14px;
1157
+ text-align: center;
1158
+ }
1159
+
1160
+ /* ---------- "Both inputs" notice (in the draw card) ---------------------
1161
+ Soft, low-key, informational tone. Not red/alarming because the user
1162
+ didn't do anything wrong β€” we're just being transparent about which
1163
+ input wins when both are present. */
1164
+ #both-inputs-notice {
1165
+ margin: 14px 18px 0;
1166
+ }
1167
+
1168
+ .both-inputs-inner {
1169
+ background: rgba(245, 200, 66, 0.18);
1170
+ /* whisper of crayon-yellow */
1171
+ border: 2px dashed rgba(45, 49, 66, 0.45);
1172
+ border-radius: 12px 16px 10px 14px;
1173
+ color: var(--ink);
1174
+ font-family: 'Patrick Hand', sans-serif;
1175
+ font-size: 1.0rem;
1176
+ padding: 10px 14px;
1177
+ line-height: 1.4;
1178
+ }
1179
+
1180
+ .both-inputs-inner .both-inputs-icon {
1181
+ margin-right: 4px;
1182
+ font-size: 1.1rem;
1183
+ }