deadshot2003 commited on
Commit
a23250f
Β·
verified Β·
1 Parent(s): ea68e4a

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +236 -311
src/streamlit_app.py CHANGED
@@ -1,5 +1,4 @@
1
  import streamlit as st
2
- import random
3
  import base64
4
  import os
5
  import time
@@ -8,15 +7,13 @@ st.set_page_config(page_title="A Special Invitation 🌸", page_icon="🌸", lay
8
 
9
  # ── session state ──────────────────────────────────────────────────────────────
10
  PASSWORD = "0207"
11
- defaults = {
12
  "authenticated": False,
13
- "no_clicks": 0,
14
- "show_yes_dialog": False,
15
- "showing_no_image": False,
16
- "no_image_index": 0,
17
- "no_pos": (1, 3), # (row, col) in the grid
18
- }
19
- for k, v in defaults.items():
20
  if k not in st.session_state:
21
  st.session_state[k] = v
22
 
@@ -39,136 +36,174 @@ st.markdown("""
39
  @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,700;1,400&family=Lato:wght@300;400;700&display=swap');
40
 
41
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
42
- html, body, .stApp { background: #080508 !important; min-height: 100vh; overflow-x: hidden; }
43
 
44
  header, footer, #MainMenu, .stDeployButton,
45
  [data-testid="stToolbar"], [data-testid="stDecoration"],
46
  [data-testid="stStatusWidget"] { display: none !important; }
47
- .block-container { padding: 0 !important; max-width: 100% !important; }
48
-
49
- #heart-canvas { position:fixed; top:0; left:0; width:100vw; height:100vh; pointer-events:none; z-index:0; }
50
 
51
- /* ── LOGIN ── */
52
- .login-page {
53
- position: relative; z-index: 10;
54
- min-height: 100vh;
55
- display: flex; align-items: center; justify-content: center;
56
- padding: 20px;
57
- }
58
- .login-card {
59
- background: linear-gradient(145deg, rgba(28,8,20,0.97), rgba(12,3,10,0.99));
60
- border: 1px solid rgba(255,105,180,0.3);
61
- border-radius: 28px;
62
- padding: 52px 44px 44px;
63
- max-width: 380px; width: 100%;
64
- text-align: center;
65
- box-shadow: 0 0 80px rgba(255,20,147,0.12);
66
- animation: floatCard 6s ease-in-out infinite;
67
  }
68
- @keyframes floatCard { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-8px)} }
69
- .login-icon { font-size:48px; display:block; margin-bottom:16px; filter:drop-shadow(0 0 12px rgba(255,20,147,0.5)); }
70
- .login-eyebrow { font-family:'Lato',sans-serif; font-size:10px; letter-spacing:5px; color:#ff69b4; opacity:.7; text-transform:uppercase; margin-bottom:10px; }
71
- .login-heading { font-family:'Playfair Display',serif; font-size:26px; color:#fff; margin-bottom:28px; }
72
 
73
- div[data-testid="stTextInput"] input {
74
- background: rgba(255,182,193,0.06) !important;
75
- border: 1px solid rgba(255,105,180,0.3) !important;
76
- border-radius: 50px !important; color: #fff !important;
77
- padding: 14px 22px !important; font-family: 'Lato',sans-serif !important;
78
- letter-spacing: 2px !important; text-align: center !important; font-size: 14px !important;
79
  }
80
- div[data-testid="stTextInput"] input::placeholder { color:rgba(255,182,193,0.35) !important; }
81
- div[data-testid="stTextInput"] input:focus { border-color:rgba(255,20,147,0.6) !important; outline:none !important; }
82
- div[data-testid="stTextInput"] label { display:none !important; }
83
 
84
- /* ── MAIN CARD ── */
85
- .v-wrapper {
86
- position: relative; z-index: 10;
87
- display: flex; flex-direction: column;
88
- align-items: center; justify-content: center;
89
- padding: 40px 20px 10px;
90
- }
91
  .card {
 
92
  background: linear-gradient(155deg, rgba(28,8,20,0.96), rgba(14,3,11,0.98));
93
  border: 1px solid rgba(255,105,180,0.25);
94
- border-radius: 30px;
95
- padding: 52px 48px 40px;
96
- max-width: 500px; width: 100%;
97
  text-align: center;
98
- box-shadow: 0 0 80px rgba(255,20,147,0.12), inset 0 1px 0 rgba(255,182,193,0.08);
 
99
  animation: floatCard 7s ease-in-out infinite;
100
  }
101
- .rose { font-size:52px; display:block; margin-bottom:18px; animation:roseGlow 2.5s ease-in-out infinite; filter:drop-shadow(0 0 14px rgba(255,20,147,0.55)); }
102
- @keyframes roseGlow { 0%,100%{transform:scale(1) rotate(-3deg)} 50%{transform:scale(1.1) rotate(3deg)} }
103
- .eyebrow { font-family:'Lato',sans-serif; font-size:10px; letter-spacing:5px; text-transform:uppercase; color:#ff69b4; opacity:.7; margin-bottom:12px; }
104
- .deco { width:48px; height:1px; background:linear-gradient(90deg,transparent,#ff69b4 50%,transparent); margin:0 auto 20px; }
105
- .question { font-family:'Playfair Display',serif; font-size:28px; font-weight:700; color:#fff; line-height:1.4; margin-bottom:8px; }
 
 
 
 
 
 
 
 
 
106
  .question em { color:#ff69b4; font-style:italic; }
107
- .hearts-deco { font-size:13px; color:#ff69b4; opacity:.35; letter-spacing:10px; margin:14px 0 18px; }
108
- .card-sub { font-family:'Lato',sans-serif; font-size:13px; color:rgba(255,182,193,.45); line-height:1.7; margin-bottom:10px; }
 
109
 
110
- /* ── NO IMAGE OVERLAY ── */
111
- .no-overlay {
112
- position: fixed; top:0; left:0; width:100vw; height:100vh;
113
- background: rgba(0,0,0,0.88); backdrop-filter: blur(14px);
114
- z-index: 2000;
115
- display: flex; flex-direction:column; align-items:center; justify-content:center;
116
- animation: fadeIn 0.35s ease; gap: 18px; padding: 30px;
117
- }
118
- @keyframes fadeIn { from{opacity:0} to{opacity:1} }
119
- .no-overlay img {
120
- max-width: 320px; width: 88%; border-radius: 20px;
121
- border: 1px solid rgba(255,105,180,0.25);
122
- box-shadow: 0 0 60px rgba(255,20,147,0.2), 0 20px 50px rgba(0,0,0,0.5);
123
- animation: popIn 0.4s cubic-bezier(0.34,1.56,0.64,1);
124
- }
125
- @keyframes popIn { from{transform:scale(0.7);opacity:0} to{transform:scale(1);opacity:1} }
126
- .no-overlay-title { font-family:'Playfair Display',serif; font-size:22px; color:#ff69b4; font-style:italic; text-align:center; }
127
- .no-overlay-sub { font-family:'Lato',sans-serif; font-size:13px; color:rgba(255,182,193,0.45); text-align:center; line-height:1.7; }
128
- .no-timer-bar-wrap { width:200px; height:3px; background:rgba(255,105,180,0.15); border-radius:3px; overflow:hidden; }
129
- .no-timer-bar { height:100%; background:linear-gradient(90deg,#ff1493,#ff69b4); border-radius:3px; animation:drainBar 3s linear forwards; }
130
- @keyframes drainBar { from{width:100%} to{width:0%} }
131
-
132
- /* ── YES OVERLAY ── */
133
- .yes-overlay {
134
- position: fixed; top:0; left:0; width:100vw; height:100vh;
135
- background: rgba(0,0,0,0.88); backdrop-filter: blur(14px);
136
- z-index: 2000;
137
- display: flex; flex-direction:column; align-items:center; justify-content:center;
138
- animation: fadeIn 0.35s ease; gap: 18px; padding: 30px;
139
  }
140
- .yes-overlay img { max-width: 320px; width: 88%; border-radius: 20px;
141
- border: 1px solid rgba(255,105,180,0.25);
142
- box-shadow: 0 0 60px rgba(255,20,147,0.25), 0 20px 50px rgba(0,0,0,0.5);
143
- animation: popIn 0.4s cubic-bezier(0.34,1.56,0.64,1); }
144
- .yes-overlay-title { font-family:'Playfair Display',serif; font-size:26px; font-weight:700; color:#fff; text-align:center; }
145
- .yes-overlay-sub { font-family:'Lato',sans-serif; font-size:14px; color:rgba(255,182,193,0.55); text-align:center; line-height:1.8; }
146
 
147
- /* ── ALL BUTTONS base ── */
148
  div[data-testid="stButton"] > button {
 
 
 
149
  border-radius: 50px !important;
150
- font-family: 'Lato',sans-serif !important;
 
 
151
  letter-spacing: 1.3px !important;
152
  text-transform: uppercase !important;
153
- font-weight: 700 !important;
154
- font-size: 11px !important;
155
- padding: 10px 22px !important;
156
- border: none !important;
157
- transition: all 0.3s ease !important;
 
 
 
 
158
  }
159
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  /* ── NO MESSAGE ── */
161
- .no-msg { font-family:'Playfair Display',serif; font-style:italic; font-size:14px;
162
- color:rgba(255,182,193,0.45); text-align:center; margin-top:8px; animation:fadeIn 0.5s ease; }
 
 
 
 
 
 
163
 
164
- /* ── spacer rows ── */
165
- .spacer { height: 48px; }
166
- .spacer-sm { height: 24px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  </style>
168
  """, unsafe_allow_html=True)
169
 
170
- # ── hearts canvas ──────────────────────────────────────────────────────────────
171
- show_confetti = "true" if st.session_state.show_yes_dialog else "false"
172
  st.markdown(f"""
173
  <canvas id="heart-canvas"></canvas>
174
  <script>
@@ -179,16 +214,16 @@ st.markdown(f"""
179
  function resize(){{c.width=window.innerWidth;c.height=window.innerHeight;}}
180
  resize(); window.addEventListener('resize',resize);
181
  const cols=['#ff69b4','#ff1493','#ffb6c1','#ff85a2','#ff4d8f'];
182
- const hearts=[];
183
- function H(){{this.reset();}}
184
  H.prototype.reset=function(){{
185
- this.x=Math.random()*c.width; this.y=c.height+20;
186
- this.s=Math.random()*14+5; this.sp=Math.random()*1.0+0.3;
187
- this.op=Math.random()*0.4+0.07; this.sw=Math.random()*1.6-0.8;
188
- this.sa=Math.random()*Math.PI*2; this.col=cols[Math.floor(Math.random()*cols.length)];
189
  this.rot=Math.random()*Math.PI*2;
190
  }};
191
- for(let i=0;i<40;i++){{const h=new H();h.y=Math.random()*c.height;hearts.push(h);}}
 
192
  function dh(x,y,s,col,op,rot){{
193
  ctx.save();ctx.translate(x,y);ctx.rotate(rot);
194
  ctx.globalAlpha=op;ctx.fillStyle=col;
@@ -215,23 +250,19 @@ st.markdown(f"""
215
  const cctx=cc.getContext('2d');
216
  const pc=['#ff1493','#ff69b4','#ffb6c1','#ffd700','#ff4500','#fff0f5'];
217
  const pieces=[];
218
- for(let i=0;i<150;i++) pieces.push({{
219
  x:Math.random()*cc.width,y:-10-Math.random()*200,
220
- sz:Math.random()*9+3,col:pc[Math.floor(Math.random()*pc.length)],
221
- sp:Math.random()*5+3,sw:Math.random()*3-1.5,
222
- rot:Math.random()*360,rs:Math.random()*6-3,op:1
223
  }});
224
  function ac(){{
225
- cctx.clearRect(0,0,cc.width,cc.height);
226
- let alive=false;
227
  pieces.forEach(p=>{{
228
- if(p.y<cc.height+10){{
229
- alive=true;p.y+=p.sp;p.x+=p.sw;p.rot+=p.rs;
230
- if(p.y>cc.height*.55)p.op-=0.01;
231
  cctx.save();cctx.translate(p.x,p.y);cctx.rotate(p.rot*Math.PI/180);
232
  cctx.globalAlpha=Math.max(0,p.op);cctx.fillStyle=p.col;
233
- cctx.fillRect(-p.sz/2,-p.sz/2,p.sz,p.sz*.55);
234
- cctx.restore();
235
  }}
236
  }});
237
  if(alive)requestAnimationFrame(ac);else cc.remove();
@@ -247,26 +278,20 @@ st.markdown(f"""
247
  # ══════════════════════════════════════════════════════════════════════════════
248
  if not st.session_state.authenticated:
249
  st.markdown("""
250
- <div class="login-page">
251
  <div class="login-card">
252
  <span class="login-icon">πŸ”</span>
253
  <p class="login-eyebrow">Private Access</p>
254
  <p class="login-heading">Enter your secret code</p>
255
  </div>
256
  </div>
257
- <style>.block-container { margin-top: -26vh !important; }</style>
258
  """, unsafe_allow_html=True)
259
- _, mid, _ = st.columns([1, 2, 1])
260
  with mid:
261
- pwd = st.text_input("code", type="password", placeholder="✦ Access Code ✦", label_visibility="collapsed")
262
- st.markdown("""
263
- <style>
264
- div[data-testid="stButton"] > button {
265
- background: linear-gradient(135deg,#ff1493,#ff69b4) !important;
266
- color:#fff !important; box-shadow:0 4px 24px rgba(255,20,147,0.4) !important;
267
- width:100% !important; padding:13px 0 !important;
268
- }
269
- </style>""", unsafe_allow_html=True)
270
  if st.button("✦ Unlock ✦", use_container_width=True):
271
  if pwd == PASSWORD:
272
  st.session_state.authenticated = True
@@ -276,200 +301,100 @@ if not st.session_state.authenticated:
276
  st.stop()
277
 
278
  # ══════════════════════════════════════════════════════════════════════════════
279
- # YES SCREEN
280
  # ══════════════════════════════════════════════════════════════════════════════
281
- if st.session_state.show_yes_dialog:
282
  b64, mime = img_to_b64("yes_image")
283
- img_tag = f'<img src="data:{mime};base64,{b64}"/>' if b64 else '<div style="font-size:80px">🌸</div>'
 
284
  st.markdown(f"""
285
- <div class="yes-overlay">
286
  {img_tag}
287
- <p class="yes-overlay-title">She said YES! πŸŽ‰πŸŒΉ</p>
288
- <p class="yes-overlay-sub">You've made this Valentine's Day absolutely perfect.<br>This means the entire world 🌸</p>
289
  </div>
290
  """, unsafe_allow_html=True)
291
  st.stop()
292
 
293
  # ══════════════════════════════════════════════════════════════════════════════
294
- # NO IMAGE TIMER (3 seconds)
295
  # ══════════════════════════════════════════════════════════════════════════════
296
- if st.session_state.showing_no_image:
297
- no_stems = ["no1", "no2", "no3"]
298
- idx = st.session_state.no_image_index % len(no_stems)
299
- b64, mime = img_to_b64(no_stems[idx])
300
- img_tag = f'<img src="data:{mime};base64,{b64}"/>' if b64 else '<div style="font-size:80px">😒</div>'
301
- titles = ["Why though... πŸ₯Ί", "Really? πŸ’”", "My poor heart...", "Please reconsider πŸ™"]
302
- t = titles[idx % len(titles)]
303
- st.markdown(f"""
304
- <div class="no-overlay">
305
- {img_tag}
306
- <p class="no-overlay-title">{t}</p>
307
- <p class="no-overlay-sub">Going back in 3 seconds... Maybe reconsider? 😏</p>
308
- <div class="no-timer-bar-wrap"><div class="no-timer-bar"></div></div>
309
- </div>
310
- """, unsafe_allow_html=True)
311
- time.sleep(3)
312
- st.session_state.showing_no_image = False
313
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
 
315
  # ══════════════════════════════════════════════════════════════════════════════
316
- # MAIN CARD
317
  # ══════════════════════════════════════════════════════════════════════════════
318
  st.markdown("""
319
- <div class="v-wrapper">
320
- <div class="card">
321
- <span class="rose">🌹</span>
322
- <p class="eyebrow">A Special Request</p>
323
- <div class="deco"></div>
324
- <p class="question">Chondi, will you give me the honour of being your<br><em>platonic Valentine</em><br>this year?</p>
325
- <p class="hearts-deco">β™‘ &nbsp;&nbsp; β™‘ &nbsp;&nbsp; β™‘</p>
326
- <p class="card-sub">No pressure... but also, just a little. 🌸</p>
327
- </div>
328
  </div>
329
  """, unsafe_allow_html=True)
330
 
331
- # ══════════════════════════════════════════════════════════════════════════════
332
- # 2D BUTTON GRID
333
- # Layout: 3 rows Γ— 5 cols. YES always at (row1, col2 center).
334
- # NO moves to one of 8 fixed spots across the grid.
335
- #
336
- # Row 0 [ ] [ ] [ ] [ ] [ ] ← top row (positions 0-3)
337
- # Row 1 [ ] [YES] [ ] [ ] [ ] ← middle (positions 4-5, skip center)
338
- # Row 2 [ ] [ ] [ ] [ ] [ ] ← bottom row (positions 6-7)
339
- #
340
- # All NO positions as (row, col_index_in_that_row):
341
- # top-left(0,0) top-center-left(0,1) top-center-right(0,3) top-right(0,4)
342
- # mid-left(1,0) mid-right(1,4)
343
- # bot-left(2,0) bot-center-left(2,1) bot-center-right(2,3) bot-right(2,4)
344
- # ══════════════════════════════════════════════════════════════════════════════
345
 
 
 
 
 
 
 
 
 
 
346
  no_messages = [
347
  "Are you sure? πŸ₯Ί", "Please reconsider... πŸ’”",
348
  "My heart is shattering...", "I'll wait forever 🌸",
349
  "Pretty please? πŸ™", "This wasn't supposed to happen 😒",
350
  "One more chance? πŸ’—", "Your finger slipped, right? 😭",
351
  ]
352
-
353
- # All valid NO positions: (row, col) β€” row 0=top 1=mid 2=bot, col 0-4
354
- no_positions = [
355
- (0, 0), (0, 1), (0, 3), (0, 4), # top row
356
- (1, 0), (1, 4), # mid left & right
357
- (2, 0), (2, 1), (2, 3), (2, 4), # bottom row
358
- ]
359
-
360
- cur_pos = st.session_state.no_pos
361
- no_row, no_col = cur_pos
362
-
363
- # Button styling
364
- st.markdown("""
365
- <style>
366
- div[data-testid="stButton"] > button {
367
- background: linear-gradient(135deg,#ff1493,#ff69b4) !important;
368
- color: #fff !important;
369
- box-shadow: 0 4px 20px rgba(255,20,147,0.35) !important;
370
- }
371
- div[data-testid="stButton"] > button:hover {
372
- transform: translateY(-2px) scale(1.05) !important;
373
- box-shadow: 0 8px 28px rgba(255,20,147,0.6) !important;
374
- }
375
- </style>
376
- """, unsafe_allow_html=True)
377
-
378
- def empty_cell():
379
- st.markdown("<div style='height:44px'></div>", unsafe_allow_html=True)
380
-
381
- # ── ROW 0 (top) ────────────────────────────────────────────────────────────────
382
- r0 = st.columns([1, 1, 1, 1, 1])
383
- for col_i, col in enumerate(r0):
384
- with col:
385
- if no_row == 0 and no_col == col_i:
386
- st.markdown("""
387
- <style>
388
- div[data-testid="stButton"]:nth-of-type(1) > button {
389
- background: rgba(255,182,193,0.05) !important;
390
- color: rgba(255,182,193,0.5) !important;
391
- border: 1px solid rgba(255,182,193,0.2) !important;
392
- box-shadow: none !important;
393
- }
394
- div[data-testid="stButton"]:nth-of-type(1) > button:hover {
395
- transform: none !important; box-shadow: none !important;
396
- background: rgba(255,182,193,0.09) !important;
397
- color: rgba(255,182,193,0.75) !important;
398
- }
399
- </style>""", unsafe_allow_html=True)
400
- if st.button("No", key="no_btn", use_container_width=True):
401
- st.session_state.no_clicks += 1
402
- st.session_state.no_image_index = st.session_state.no_clicks - 1
403
- st.session_state.showing_no_image = True
404
- new = random.choice([p for p in no_positions if p != cur_pos])
405
- st.session_state.no_pos = new
406
- st.rerun()
407
- else:
408
- empty_cell()
409
-
410
- st.markdown("<div style='height:8px'></div>", unsafe_allow_html=True)
411
-
412
- # ── ROW 1 (middle β€” YES always col 2) ─────────────────────────────────────────
413
- r1 = st.columns([1, 1, 1, 1, 1])
414
- for col_i, col in enumerate(r1):
415
- with col:
416
- if col_i == 2:
417
- if st.button("✦ Yes! ✦", key="yes_btn", use_container_width=True):
418
- st.session_state.show_yes_dialog = True
419
- st.rerun()
420
- elif no_row == 1 and no_col == col_i:
421
- st.markdown("""
422
- <style>
423
- div[data-testid="stButton"]:nth-of-type(2) > button {
424
- background: rgba(255,182,193,0.05) !important;
425
- color: rgba(255,182,193,0.5) !important;
426
- border: 1px solid rgba(255,182,193,0.2) !important;
427
- box-shadow: none !important;
428
- }
429
- div[data-testid="stButton"]:nth-of-type(2) > button:hover {
430
- transform: none !important; box-shadow: none !important;
431
- }
432
- </style>""", unsafe_allow_html=True)
433
- if st.button("No", key="no_btn", use_container_width=True):
434
- st.session_state.no_clicks += 1
435
- st.session_state.no_image_index = st.session_state.no_clicks - 1
436
- st.session_state.showing_no_image = True
437
- new = random.choice([p for p in no_positions if p != cur_pos])
438
- st.session_state.no_pos = new
439
- st.rerun()
440
- else:
441
- empty_cell()
442
-
443
- st.markdown("<div style='height:8px'></div>", unsafe_allow_html=True)
444
-
445
- # ── ROW 2 (bottom) ─────────────────────────────────────────────────────────────
446
- r2 = st.columns([1, 1, 1, 1, 1])
447
- for col_i, col in enumerate(r2):
448
- with col:
449
- if no_row == 2 and no_col == col_i:
450
- st.markdown("""
451
- <style>
452
- div[data-testid="stButton"]:nth-of-type(3) > button {
453
- background: rgba(255,182,193,0.05) !important;
454
- color: rgba(255,182,193,0.5) !important;
455
- border: 1px solid rgba(255,182,193,0.2) !important;
456
- box-shadow: none !important;
457
- }
458
- div[data-testid="stButton"]:nth-of-type(3) > button:hover {
459
- transform: none !important; box-shadow: none !important;
460
- }
461
- </style>""", unsafe_allow_html=True)
462
- if st.button("No", key="no_btn", use_container_width=True):
463
- st.session_state.no_clicks += 1
464
- st.session_state.no_image_index = st.session_state.no_clicks - 1
465
- st.session_state.showing_no_image = True
466
- new = random.choice([p for p in no_positions if p != cur_pos])
467
- st.session_state.no_pos = new
468
- st.rerun()
469
- else:
470
- empty_cell()
471
-
472
- # No message
473
- if st.session_state.no_clicks > 0:
474
- idx = (st.session_state.no_clicks - 1) % len(no_messages)
475
  st.markdown(f"<p class='no-msg'>{no_messages[idx]}</p>", unsafe_allow_html=True)
 
1
  import streamlit as st
 
2
  import base64
3
  import os
4
  import time
 
7
 
8
  # ── session state ──────────────────────────────────────────────────────────────
9
  PASSWORD = "0207"
10
+ for k, v in {
11
  "authenticated": False,
12
+ "show_yes": False,
13
+ "showing_no": False,
14
+ "no_index": 0,
15
+ "no_timer_start": None,
16
+ }.items():
 
 
17
  if k not in st.session_state:
18
  st.session_state[k] = v
19
 
 
36
  @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,700;1,400&family=Lato:wght@300;400;700&display=swap');
37
 
38
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
39
+ html, body, .stApp { background: #080508 !important; min-height: 100vh; }
40
 
41
  header, footer, #MainMenu, .stDeployButton,
42
  [data-testid="stToolbar"], [data-testid="stDecoration"],
43
  [data-testid="stStatusWidget"] { display: none !important; }
 
 
 
44
 
45
+ .block-container {
46
+ padding: 1rem 1rem 1rem !important;
47
+ max-width: 480px !important;
48
+ margin: 0 auto !important;
 
 
 
 
 
 
 
 
 
 
 
 
49
  }
 
 
 
 
50
 
51
+ #heart-canvas {
52
+ position: fixed; top:0; left:0;
53
+ width:100vw; height:100vh;
54
+ pointer-events:none; z-index:0;
 
 
55
  }
 
 
 
56
 
57
+ /* ── CARD ── */
 
 
 
 
 
 
58
  .card {
59
+ position: relative; z-index: 10;
60
  background: linear-gradient(155deg, rgba(28,8,20,0.96), rgba(14,3,11,0.98));
61
  border: 1px solid rgba(255,105,180,0.25);
62
+ border-radius: 24px;
63
+ padding: 36px 24px 28px;
 
64
  text-align: center;
65
+ box-shadow: 0 0 60px rgba(255,20,147,0.1), inset 0 1px 0 rgba(255,182,193,0.07);
66
+ margin-bottom: 20px;
67
  animation: floatCard 7s ease-in-out infinite;
68
  }
69
+ @keyframes floatCard { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-6px)} }
70
+
71
+ .rose { font-size:44px; display:block; margin-bottom:14px;
72
+ animation:roseGlow 2.5s ease-in-out infinite;
73
+ filter:drop-shadow(0 0 12px rgba(255,20,147,0.5)); }
74
+ @keyframes roseGlow { 0%,100%{transform:scale(1) rotate(-3deg)} 50%{transform:scale(1.08) rotate(3deg)} }
75
+
76
+ .eyebrow { font-family:'Lato',sans-serif; font-size:9px; letter-spacing:4px;
77
+ text-transform:uppercase; color:#ff69b4; opacity:.7; margin-bottom:10px; }
78
+ .deco { width:40px; height:1px;
79
+ background:linear-gradient(90deg,transparent,#ff69b4 50%,transparent);
80
+ margin:0 auto 16px; }
81
+ .question { font-family:'Playfair Display',serif; font-size:22px; font-weight:700;
82
+ color:#fff; line-height:1.45; margin-bottom:6px; }
83
  .question em { color:#ff69b4; font-style:italic; }
84
+ .hearts-deco { font-size:11px; color:#ff69b4; opacity:.3; letter-spacing:8px; margin:10px 0 12px; }
85
+ .card-sub { font-family:'Lato',sans-serif; font-size:12px;
86
+ color:rgba(255,182,193,.4); line-height:1.6; }
87
 
88
+ /* ── BUTTONS ── */
89
+ .btn-row {
90
+ position: relative; z-index: 10;
91
+ display: flex;
92
+ justify-content: center;
93
+ align-items: center;
94
+ gap: 14px;
95
+ margin-top: 4px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  }
 
 
 
 
 
 
97
 
 
98
  div[data-testid="stButton"] > button {
99
+ background: linear-gradient(135deg, #ff1493, #ff69b4) !important;
100
+ color: #fff !important;
101
+ border: none !important;
102
  border-radius: 50px !important;
103
+ font-family: 'Lato', sans-serif !important;
104
+ font-size: 12px !important;
105
+ font-weight: 700 !important;
106
  letter-spacing: 1.3px !important;
107
  text-transform: uppercase !important;
108
+ padding: 11px 26px !important;
109
+ box-shadow: 0 4px 18px rgba(255,20,147,0.38) !important;
110
+ transition: all 0.2s ease !important;
111
+ width: auto !important;
112
+ min-width: 100px !important;
113
+ max-width: 160px !important;
114
+ }
115
+ div[data-testid="stButton"] > button:active {
116
+ transform: scale(0.96) !important;
117
  }
118
 
119
+ /* ── OVERLAY (No image + Yes image) ── */
120
+ .overlay {
121
+ position: fixed; top:0; left:0; width:100vw; height:100vh;
122
+ background: rgba(0,0,0,0.88);
123
+ backdrop-filter: blur(12px);
124
+ z-index: 2000;
125
+ display: flex; flex-direction: column;
126
+ align-items: center; justify-content: center;
127
+ padding: 24px; gap: 14px;
128
+ animation: fadeIn 0.3s ease;
129
+ }
130
+ @keyframes fadeIn { from{opacity:0} to{opacity:1} }
131
+
132
+ .overlay img {
133
+ max-width: 280px; width: 82%;
134
+ border-radius: 18px;
135
+ border: 1px solid rgba(255,105,180,0.2);
136
+ box-shadow: 0 0 50px rgba(255,20,147,0.18), 0 16px 40px rgba(0,0,0,0.5);
137
+ animation: popIn 0.35s cubic-bezier(0.34,1.56,0.64,1);
138
+ }
139
+ @keyframes popIn { from{transform:scale(0.75);opacity:0} to{transform:scale(1);opacity:1} }
140
+
141
+ .overlay-title {
142
+ font-family: 'Playfair Display', serif;
143
+ font-size: 22px; color: #ff69b4;
144
+ font-style: italic; text-align: center;
145
+ }
146
+ .overlay-title.white { color: #fff; font-style: normal; font-weight: 700; }
147
+ .overlay-sub {
148
+ font-family: 'Lato', sans-serif;
149
+ font-size: 12px; color: rgba(255,182,193,0.45);
150
+ text-align: center; line-height: 1.65;
151
+ }
152
+ .timer-bar-wrap { width:140px; height:3px; background:rgba(255,105,180,0.15); border-radius:3px; overflow:hidden; }
153
+ .timer-bar { height:100%; background:linear-gradient(90deg,#ff1493,#ff69b4); border-radius:3px; animation:drain 3s linear forwards; }
154
+ @keyframes drain { from{width:100%} to{width:0%} }
155
+
156
  /* ── NO MESSAGE ── */
157
+ .no-msg {
158
+ position: relative; z-index: 10;
159
+ font-family: 'Playfair Display', serif;
160
+ font-style: italic; font-size: 13px;
161
+ color: rgba(255,182,193,0.4);
162
+ text-align: center; margin-top: 10px;
163
+ animation: fadeIn 0.5s ease;
164
+ }
165
 
166
+ /* ── LOGIN ── */
167
+ .login-wrap {
168
+ position: relative; z-index: 10;
169
+ min-height: 100vh;
170
+ display: flex; flex-direction: column;
171
+ align-items: center; justify-content: center;
172
+ padding: 20px;
173
+ }
174
+ .login-card {
175
+ background: linear-gradient(145deg, rgba(28,8,20,0.97), rgba(12,3,10,0.99));
176
+ border: 1px solid rgba(255,105,180,0.3);
177
+ border-radius: 24px;
178
+ padding: 40px 32px 36px;
179
+ max-width: 340px; width: 100%;
180
+ text-align: center;
181
+ box-shadow: 0 0 60px rgba(255,20,147,0.1);
182
+ animation: floatCard 6s ease-in-out infinite;
183
+ }
184
+ .login-icon { font-size:42px; display:block; margin-bottom:12px;
185
+ filter:drop-shadow(0 0 10px rgba(255,20,147,0.5)); }
186
+ .login-eyebrow { font-family:'Lato',sans-serif; font-size:9px; letter-spacing:4px;
187
+ color:#ff69b4; opacity:.7; text-transform:uppercase; margin-bottom:8px; }
188
+ .login-heading { font-family:'Playfair Display',serif; font-size:22px; color:#fff; margin-bottom:24px; }
189
+
190
+ div[data-testid="stTextInput"] input {
191
+ background: rgba(255,182,193,0.06) !important;
192
+ border: 1px solid rgba(255,105,180,0.3) !important;
193
+ border-radius: 50px !important; color: #fff !important;
194
+ padding: 12px 20px !important;
195
+ font-family: 'Lato',sans-serif !important;
196
+ letter-spacing: 2px !important; text-align: center !important;
197
+ font-size: 14px !important;
198
+ }
199
+ div[data-testid="stTextInput"] input::placeholder { color:rgba(255,182,193,0.35) !important; }
200
+ div[data-testid="stTextInput"] input:focus { border-color:rgba(255,20,147,0.6) !important; outline:none !important; }
201
+ div[data-testid="stTextInput"] label { display:none !important; }
202
  </style>
203
  """, unsafe_allow_html=True)
204
 
205
+ # ── floating hearts ────────────────────────────────────────────────────────────
206
+ show_confetti = "true" if st.session_state.show_yes else "false"
207
  st.markdown(f"""
208
  <canvas id="heart-canvas"></canvas>
209
  <script>
 
214
  function resize(){{c.width=window.innerWidth;c.height=window.innerHeight;}}
215
  resize(); window.addEventListener('resize',resize);
216
  const cols=['#ff69b4','#ff1493','#ffb6c1','#ff85a2','#ff4d8f'];
217
+ const H=function(){{this.reset();}};
 
218
  H.prototype.reset=function(){{
219
+ this.x=Math.random()*c.width;this.y=c.height+20;
220
+ this.s=Math.random()*12+4;this.sp=Math.random()*0.9+0.25;
221
+ this.op=Math.random()*0.35+0.06;this.sw=Math.random()*1.4-0.7;
222
+ this.sa=Math.random()*Math.PI*2;this.col=cols[Math.floor(Math.random()*cols.length)];
223
  this.rot=Math.random()*Math.PI*2;
224
  }};
225
+ const hearts=[];
226
+ for(let i=0;i<30;i++){{const h=new H();h.y=Math.random()*c.height;hearts.push(h);}}
227
  function dh(x,y,s,col,op,rot){{
228
  ctx.save();ctx.translate(x,y);ctx.rotate(rot);
229
  ctx.globalAlpha=op;ctx.fillStyle=col;
 
250
  const cctx=cc.getContext('2d');
251
  const pc=['#ff1493','#ff69b4','#ffb6c1','#ffd700','#ff4500','#fff0f5'];
252
  const pieces=[];
253
+ for(let i=0;i<120;i++) pieces.push({{
254
  x:Math.random()*cc.width,y:-10-Math.random()*200,
255
+ sz:Math.random()*8+3,col:pc[Math.floor(Math.random()*pc.length)],
256
+ sp:Math.random()*4+2,sw:Math.random()*2-1,rot:Math.random()*360,rs:Math.random()*4-2,op:1
 
257
  }});
258
  function ac(){{
259
+ cctx.clearRect(0,0,cc.width,cc.height);let alive=false;
 
260
  pieces.forEach(p=>{{
261
+ if(p.y<cc.height+10){{alive=true;p.y+=p.sp;p.x+=p.sw;p.rot+=p.rs;
262
+ if(p.y>cc.height*.5)p.op-=0.012;
 
263
  cctx.save();cctx.translate(p.x,p.y);cctx.rotate(p.rot*Math.PI/180);
264
  cctx.globalAlpha=Math.max(0,p.op);cctx.fillStyle=p.col;
265
+ cctx.fillRect(-p.sz/2,-p.sz/2,p.sz,p.sz*.55);cctx.restore();
 
266
  }}
267
  }});
268
  if(alive)requestAnimationFrame(ac);else cc.remove();
 
278
  # ══════════════════════════════════════════════════════════════════════════════
279
  if not st.session_state.authenticated:
280
  st.markdown("""
281
+ <div class="login-wrap">
282
  <div class="login-card">
283
  <span class="login-icon">πŸ”</span>
284
  <p class="login-eyebrow">Private Access</p>
285
  <p class="login-heading">Enter your secret code</p>
286
  </div>
287
  </div>
288
+ <style>.block-container { margin-top: -20vh !important; }</style>
289
  """, unsafe_allow_html=True)
290
+ _, mid, _ = st.columns([1, 4, 1])
291
  with mid:
292
+ pwd = st.text_input("code", type="password",
293
+ placeholder="✦ Access Code ✦",
294
+ label_visibility="collapsed")
 
 
 
 
 
 
295
  if st.button("✦ Unlock ✦", use_container_width=True):
296
  if pwd == PASSWORD:
297
  st.session_state.authenticated = True
 
301
  st.stop()
302
 
303
  # ══════════════════════════════════════════════════════════════════════════════
304
+ # YES OVERLAY
305
  # ══════════════════════════════════════════════════════════════════════════════
306
+ if st.session_state.show_yes:
307
  b64, mime = img_to_b64("yes_image")
308
+ img_tag = f'<img src="data:{mime};base64,{b64}"/>' if b64 \
309
+ else '<div style="font-size:72px;margin-bottom:4px">🌸</div>'
310
  st.markdown(f"""
311
+ <div class="overlay">
312
  {img_tag}
313
+ <p class="overlay-title white">She said YES! πŸŽ‰πŸŒΉπŸ€“</p>
314
+ <p class="overlay-sub"> YAYYYYYY!!!! <br>You've made this Valentine's Day absolutely perfect. πŸ€“</p>
315
  </div>
316
  """, unsafe_allow_html=True)
317
  st.stop()
318
 
319
  # ══════════════════════════════════════════════════════════════════════════════
320
+ # NO OVERLAY (3 second timer β€” robust)
321
  # ══════════════════════════════════════════════════════════════════════════════
322
+ if st.session_state.showing_no:
323
+ if st.session_state.no_timer_start is None:
324
+ st.session_state.no_timer_start = time.time()
325
+
326
+ elapsed = time.time() - st.session_state.no_timer_start
327
+
328
+ if elapsed >= 3.0:
329
+ st.session_state.showing_no = False
330
+ st.session_state.no_timer_start = None
331
+ st.rerun()
332
+ else:
333
+ no_stems = ["no1", "no2", "no3"]
334
+ idx = st.session_state.no_index % 3
335
+ st.session_state.no_index += 1
336
+ b64, mime = img_to_b64(no_stems[idx])
337
+ img_tag = f'<img src="data:{mime};base64,{b64}"/>' if b64 \
338
+ else '<div style="font-size:72px;margin-bottom:4px">😒</div>'
339
+ titles = ["Why though... πŸ₯Ί", "Really? πŸ’”", "My poor heart...", "Please reconsider πŸ™"]
340
+ t = titles[idx % 4]
341
+ remaining = max(0.1, 3.0 - elapsed)
342
+
343
+ st.markdown(f"""
344
+ <div class="overlay">
345
+ {img_tag}
346
+ <p class="overlay-title">{t}</p>
347
+ <p class="overlay-sub">Going back in {int(remaining)+1}s... Maybe reconsider? 😏</p>
348
+ <div class="timer-bar-wrap">
349
+ <div class="timer-bar" style="animation-duration:{remaining:.2f}s"></div>
350
+ </div>
351
+ </div>
352
+ """, unsafe_allow_html=True)
353
+
354
+ time.sleep(remaining)
355
+ st.session_state.showing_no = False
356
+ st.session_state.no_timer_start = None
357
+ st.rerun()
358
 
359
  # ══════════════════════════════════════════════════════════════════════════════
360
+ # MAIN PAGE
361
  # ══════════════════════════════════════════════════════════════════════════════
362
  st.markdown("""
363
+ <div class="card">
364
+ <span class="rose">🌹</span>
365
+ <p class="eyebrow">A Special Request</p>
366
+ <div class="deco"></div>
367
+ <p class="question">Chondi, will you give me the hounour of being your<br><em>platonic Valentine</em><br>this year?</p>
368
+ <p class="hearts-deco">β™‘ &nbsp; β™‘ &nbsp; β™‘</p>
369
+ <p class="card-sub">No pressure... but also, just a little. 🌸</p>
 
 
370
  </div>
371
  """, unsafe_allow_html=True)
372
 
373
+ # ── Two buttons, same row, centered ───────────────────────────────────────────
374
+ st.markdown("<div class='btn-row'>", unsafe_allow_html=True)
375
+
376
+ _, c1, gap, c2, _ = st.columns([1, 2, 0.3, 2, 1])
377
+
378
+ with c1:
379
+ if st.button("✦ Yes! ✦", key="yes_btn", use_container_width=True):
380
+ st.session_state.show_yes = True
381
+ st.rerun()
 
 
 
 
 
382
 
383
+ with c2:
384
+ if st.button("No", key="no_btn", use_container_width=True):
385
+ st.session_state.showing_no = True
386
+ st.session_state.no_timer_start = None
387
+ st.rerun()
388
+
389
+ st.markdown("</div>", unsafe_allow_html=True)
390
+
391
+ # No message shown below buttons
392
  no_messages = [
393
  "Are you sure? πŸ₯Ί", "Please reconsider... πŸ’”",
394
  "My heart is shattering...", "I'll wait forever 🌸",
395
  "Pretty please? πŸ™", "This wasn't supposed to happen 😒",
396
  "One more chance? πŸ’—", "Your finger slipped, right? 😭",
397
  ]
398
+ if st.session_state.no_index > 0:
399
+ idx = (st.session_state.no_index - 1) % len(no_messages)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  st.markdown(f"<p class='no-msg'>{no_messages[idx]}</p>", unsafe_allow_html=True)