File size: 6,480 Bytes
fecc811
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
"""Gradio HTML blocks for the DreamVoice UI β€” starfield, moon, and storybook."""

from __future__ import annotations

import gradio as gr

_STARFIELD_CSS = """
@import url('https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,700;1,400&family=Pacifico&display=swap');

/* ── reset & canvas ──────────────────────────────────────────── */
#dv-stars-root, #dv-stars-root * { box-sizing: border-box; margin: 0; padding: 0; }
#dv-stars-root {
  position: fixed; inset: 0; z-index: -1; overflow: hidden;
  background: linear-gradient(170deg, #0a0a1a 0%, #1a1a3e 100%);
  pointer-events: none;
}

/* ── stars ───────────────────────────────────────────────────── */
#dv-stars-root .star {
  position: absolute; border-radius: 50%; background: #fff;
  animation: dv-twinkle var(--dur) ease-in-out infinite alternate;
  opacity: 0;
}
@keyframes dv-twinkle { 0% { opacity: .15; } 100% { opacity: 1; } }

/* ── moon ────────────────────────────────────────────────────── */
#dv-moon {
  position: absolute; top: 60px; right: 10%; width: 90px; height: 90px;
  border-radius: 50%; background: radial-gradient(circle at 35% 35%, #fffbe6 0%, #f5c842 60%, #c9a020 100%);
  box-shadow: 0 0 40px 12px rgba(245,200,66,.35), 0 0 80px 24px rgba(245,200,66,.15);
  animation: dv-moon-glow 4s ease-in-out infinite alternate;
}
@keyframes dv-moon-glow {
  0% { box-shadow: 0 0 40px 12px rgba(245,200,66,.35), 0 0 80px 24px rgba(245,200,66,.15); }
  100% { box-shadow: 0 0 55px 18px rgba(245,200,66,.45), 0 0 100px 32px rgba(245,200,66,.2); }
}

/* ── storybook flip container ────────────────────────────────── */
#dv-storybook-wrap {
  perspective: 1200px; display: flex; justify-content: center; align-items: center;
  min-height: 340px; margin: 0 auto 1.5rem; max-width: 680px;
}
#dv-storybook {
  width: 100%; min-height: 340px; position: relative;
  transform-style: preserve-3d;
  transition: transform 0.8s cubic-bezier(.4,0,.2,1);
  transform-origin: center left;
  /* Plays each time Stage 2 becomes visible (display none→block restarts it),
     so the book "opens" on every reveal. */
  animation: dv-book-open .85s cubic-bezier(.2,.7,.2,1) both;
}
@keyframes dv-book-open {
  0%   { transform: rotateY(-72deg); opacity: 0; }
  100% { transform: rotateY(0deg);   opacity: 1; }
}
#dv-storybook.dv-flipped { transform: rotateY(180deg); }

#dv-book-front, #dv-book-back {
  position: absolute; inset: 0; backface-visibility: hidden;
  border-radius: 24px; padding: 2rem 2.2rem;
  font-family: 'Lora', Georgia, serif; font-size: 1.05rem; line-height: 1.7;
  color: #eee; overflow-y: auto;
  background: linear-gradient(135deg, rgba(30,30,70,.85) 0%, rgba(15,15,45,.92) 100%);
  border: 1.5px solid rgba(245,200,66,.25);
  box-shadow: 0 8px 32px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.06);
}
#dv-book-back { transform: rotateY(180deg); }

#dv-storybook h2.dv-story-title {
  font-family: 'Pacifico', cursive; font-size: 1.65rem; color: #f5c842;
  margin-bottom: 1rem; text-shadow: 0 2px 8px rgba(245,200,66,.25);
}
#dv-storybook .dv-story-text { white-space: pre-wrap; }

/* ── accent buttons (shared) ─────────────────────────────────── */
.dv-btn-accent {
  background: #f5c842; color: #1a1a3e; border: none; border-radius: 24px;
  padding: .65rem 1.8rem; font-weight: 700; font-size: 1rem; cursor: pointer;
  font-family: 'Lora', Georgia, serif; transition: transform .15s, box-shadow .15s;
}
.dv-btn-accent:hover { transform: translateY(-1px); box-shadow: 0 4px 14px rgba(245,200,66,.4); }
.dv-btn-accent:active { transform: translateY(0); }

/* ── stage headings & helpers ────────────────────────────────── */
.dv-section-title {
  font-family: 'Pacifico', cursive; font-size: 1.5rem; color: #f5c842;
  text-align: center; margin: .5rem 0 1rem;
}
.dv-note {
  font-family: 'Lora', Georgia, serif; font-size: .88rem;
  color: rgba(255,255,255,.55); text-align: center; margin-top: .3rem;
}
"""

_STARFIELD_JS = """
(function(){
  var root = document.getElementById('dv-stars-root');
  if (!root) return;
  for (var i = 0; i < 120; i++){
    var s = document.createElement('div');
    s.className = 'star';
    var size = 1.2 + Math.random() * 2.5;
    s.style.width  = size + 'px';
    s.style.height = size + 'px';
    s.style.left   = Math.random() * 100 + '%';
    s.style.top    = Math.random() * 100 + '%';
    s.style.setProperty('--dur', (1.5 + Math.random() * 3) + 's');
    s.style.animationDelay = (Math.random() * 4) + 's';
    root.appendChild(s);
  }
})();
"""


def starfield_background() -> gr.HTML:
    """Full-viewport animated starfield with a glowing moon."""
    return gr.HTML(
        value='<div id="dv-stars-root"></div><div id="dv-moon"></div>',
        head=f"<style>{_STARFIELD_CSS}</style>",
        js_on_load=_STARFIELD_JS,
        elem_id="dv-stars-root",
    )


def storybook_markup(story: str = "", title: str = "Tonight's Story") -> str:
    """Return the storybook HTML string with *story* and *title* HTML-escaped.

    Use this both to build the initial component and to update it after a story
    is generated, so the storybook frame/title/escaping are preserved.
    """
    import html as _html

    safe_title = _html.escape(title)
    safe_story = _html.escape(story) if story else "<em>The storybook is waiting…</em>"

    return f"""
<div id="dv-storybook-wrap">
  <div id="dv-storybook">
    <div id="dv-book-front">
      <h2 class="dv-story-title">{safe_title}</h2>
      <div class="dv-story-text">{safe_story}</div>
    </div>
    <div id="dv-book-back">
      <h2 class="dv-story-title">The End</h2>
      <p style="text-align:center;margin-top:2rem;color:#f5c842;font-size:1.2rem;">
        Sweet dreams ✨
      </p>
    </div>
  </div>
</div>
"""


def storybook_html(story: str = "", title: str = "Tonight's Story") -> gr.HTML:
    """Render the story inside the 3D storybook flip container."""
    return gr.HTML(value=storybook_markup(story, title), elem_id="dv-storybook")