PageSourceSearch

https://feminine-rising.netlify.app/mentorship

html feminine-rising.netlify.app collected 2026-10-03 10:43:46 UTC 319,020 bytes, 8,360 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3<head>
4<meta charset="UTF-8">
5<meta name="viewport" content="width=device-width, initial-scale=1.0">
6<title>Private Mentorship — Feminine Rising</title>
7<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png">
8<link rel="icon" type="image/png" sizes="192x192" href="/favicon-192.png">
9<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
10<link rel="preconnect" href="https://fonts.googleapis.com">
11<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;1,400&family=Lora:ital,wght@0,400;0,600;1,400&family=Marcellus&family=Great+Vibes&display=swap" rel="stylesheet">
12<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2/dist/umd/supabase.min.js"></script>
vendor: 1 bytes, line 12
12
13<script src="/lame.min.js"></script>
13
14<style>
15/* ============================================
16   DESIGN TOKENS
17   ============================================ */
18:root {
19  --gold: #C5A55A;
20  --gold-light: #D4BA7A;
21  --gold-pale: #F0E6CC;
22  --plum: #8B2252;
23  --plum-deep: #6B1A42;
24  --berry: #940E4B;
25  --cream: #FBF8F3;
26  --cream-warm: #F7F0E5;
27  --charcoal: #2E2E2E;
28  --sidebar-width: 240px;
29}
30
31* { margin: 0; padding: 0; box-sizing: border-box; }
32
33body {
34  font-family: 'Lora', serif;
35  background: var(--cream);
36  color: var(--charcoal);
37  min-height: 100vh;
38}
39
40/* ============================================
41   PASSWORD SCREEN
42   ============================================ */
43#password-screen {
44  min-height: 100vh;
45  display: flex;
46  flex-direction: column;
47  align-items: center;
48  justify-content: center;
49  background: var(--cream);
50  position: relative;
51}
52
53#password-screen::before {
54  content: '';
55  position: absolute;
56  top: 0; left: 0; right: 0;
57  height: 200px;
58  background: var(--plum);
59}
60
61.password-card {
62  position: relative;
63  background: var(--cream);
64  border: 1px solid var(--gold-light);
65  border-radius: 12px;
66  padding: 60px 50px 50px;
67  max-width: 460px;
68  width: 90%;
69  text-align: center;
70  box-shadow: 0 8px 40px rgba(139, 34, 82, 0.08);
71  margin-top: -40px;
72}
73
74.password-logo { width: 60px; height: 60px; margin-bottom: 20px; }
75
76.password-title {
77  font-family: 'Marcellus', serif;
78  font-size: 13px;
79  letter-spacing: 4px;
80  color: var(--gold);
81  text-transform: uppercase;
82  margin-bottom: 8px;
83}
84
85.password-heading {
86  font-family: 'Cormorant Garamond', serif;
87  font-size: 30px;
88  color: var(--plum-deep);
89  margin-bottom: 6px;
90  font-weight: 400;
91}
92
93.password-subtitle {
94  font-family: 'Cormorant Garamond', serif;
95  font-style: italic;
96  font-size: 16px;
97  color: var(--berry);
98  margin-bottom: 30px;
99}
100
101.password-divider {
102  display: flex;
103  align-items: center;
104  gap: 12px;
105  margin-bottom: 30px;
106}
107
108.password-divider::before,
109.password-divider::after {
110  content: '';
111  flex: 1;
112  height: 1px;
113  background: var(--gold-light);
114}
115
116.password-divider span { color: var(--gold); font-size: 14px; }
117
118.password-prompt {
119  font-family: 'Cormorant Garamond', serif;
120  font-style: italic;
121  font-size: 15px;
122  color: var(--plum);
123  margin-bottom: 20px;
124}
125
126.password-input {
127  width: 100%;
128  padding: 14px 20px;
129  border: 1px solid var(--gold-light);
130  border-radius: 8px;
131  background: white;
132  font-family: 'Lora', serif;
133  font-size: 14px;
134  color: var(--charcoal);
135  text-align: center;
136  letter-spacing: 2px;
137  outline: none;
138  transition: border-color 0.3s;
139  margin-bottom: 16px;
140}
141
142.password-input:focus {
143  border-color: var(--gold);
144  box-shadow: 0 0 0 3px rgba(197, 165, 90, 0.15);
145}
146
147.password-input::placeholder { color: #bbb; letter-spacing: 1px; }
148
149.password-btn {
150  width: 100%;
151  padding: 14px;
152  background: var(--plum);
153  color: var(--gold);
154  border: none;
155  border-radius: 8px;
156  font-family: 'Marcellus', serif;
157  font-size: 13px;
158  letter-spacing: 3px;
159  text-transform: uppercase;
160  cursor: pointer;
161  transition: all 0.3s;
162}
163
164.password-btn:hover {
165  background: var(--plum-deep);
166  box-shadow: 0 4px 16px rgba(107, 26, 66, 0.3);
167}
168
169.password-error {
170  color: var(--berry);
171  font-size: 13px;
172  font-style: italic;
173  margin-top: 12px;
174  opacity: 0;
175  transition: opacity 0.3s;
176}
177
178.password-error.show { opacity: 1; }
179
180.back-link {
181  position: relative;
182  margin-top: 20px;
183  font-size: 12px;
184  color: var(--gold);
185  text-decoration: none;
186  font-family: 'Marcellus', serif;
187  letter-spacing: 2px;
188  text-transform: uppercase;
189}
190
191.back-link:hover { color: var(--plum); }
192
193.back-to-portal {
194  position: fixed;
195  top: 16px;
196  left: 20px;
197  display: inline-flex;
198  align-items: center;
199  gap: 6px;
200  padding: 8px 18px;
201  background: rgba(107, 26, 66, 0.7);
202  border: 1px solid rgba(197, 165, 90, 0.4);
203  border-radius: 6px;
204  color: var(--gold-light);
205  font-family: 'Marcellus', serif;
206  font-size: 10px;
207  letter-spacing: 2px;
208  text-transform: uppercase;
209  text-decoration: none;
210  transition: all 0.3s;
211  z-index: 300;
212}
213
214.back-to-portal:hover {
215  background: rgba(107, 26, 66, 0.95);
216  border-color: var(--gold);
217  color: var(--gold);
218}
219
220/* ============================================
221   MAGIC LINK AUTH SCREEN
222   ============================================ */
223#auth-screen {
224  display: none;
225  min-height: 100vh;
226  flex-direction: column;
227  align-items: center;
228  justify-content: center;
229  background: var(--cream);
230  position: relative;
231}
232
233#auth-screen::before {
234  content: '';
235  position: absolute;
236  top: 0; left: 0; right: 0;
237  height: 200px;
238  background: var(--plum);
239}
240
241.auth-card {
242  position: relative;
243  background: var(--cream);
244  border: 1px solid var(--gold-light);
245  border-radius: 12px;
246  padding: 50px 50px 44px;
247  max-width: 460px;
248  width: 90%;
249  text-align: center;
250  box-shadow: 0 8px 40px rgba(139, 34, 82, 0.08);
251  margin-top: -40px;
252}
253
254.auth-icon {
255  font-size: 32px;
256  color: var(--gold);
257  margin-bottom: 16px;
258}
259
260.auth-title {
261  font-family: 'Cormorant Garamond', serif;
262  font-size: 26px;
263  color: var(--plum-deep);
264  font-weight: 400;
265  margin-bottom: 8px;
266}
267
268.auth-text {
269  font-family: 'Cormorant Garamond', serif;
270  font-style: italic;
271  font-size: 15px;
272  color: var(--plum);
273  margin-bottom: 24px;
274  line-height: 1.6;
275}
276
277.auth-input {
278  width: 100%;
279  padding: 14px 20px;
280  border: 1px solid var(--gold-light);
281  border-radius: 8px;
282  background: white;
283  font-family: 'Lora', serif;
284  font-size: 14px;
285  color: var(--charcoal);
286  text-align: center;
287  outline: none;
288  transition: border-color 0.3s;
289  margin-bottom: 16px;
290}
291
292.auth-input:focus {
293  border-color: var(--gold);
294  box-shadow: 0 0 0 3px rgba(197, 165, 90, 0.15);
295}
296
297.auth-btn {
298  width: 100%;
299  padding: 14px;
300  background: var(--berry);
301  color: var(--gold);
302  border: 2px solid var(--gold);
303  border-radius: 8px;
304  font-family: 'Marcellus', serif;
305  font-size: 12px;
306  letter-spacing: 3px;
307  text-transform: uppercase;
308  cursor: pointer;
309  transition: all 0.3s;
310}
311
312.auth-btn:hover {
313  background: var(--plum-deep);
314  border-color: var(--gold-light);
315  box-shadow: 0 4px 16px rgba(148, 14, 75, 0.3);
316}
317
318.auth-btn:disabled {
319  opacity: 0.5;
320  cursor: not-allowed;
321}
322
323.auth-message {
324  font-family: 'Cormorant Garamond', serif;
325  font-style: italic;
326  font-size: 14px;
327  color: var(--berry);
328  margin-top: 16px;
329  opacity: 0;
330  transition: opacity 0.3s;
331  line-height: 1.5;
332}
333
334.auth-message.show { opacity: 1; }
335
336.auth-message.success { color: var(--plum); }
337
338/* ============================================
339   NAME ONBOARDING
340   ============================================ */
341#name-screen {
342  display: none;
343  min-height: 100vh;
344  flex-direction: column;
345  align-items: center;
346  justify-content: center;
347  background: var(--cream);
348  position: relative;
349}
350
351#name-screen::before {
352  content: '';
353  position: absolute;
354  top: 0; left: 0; right: 0;
355  height: 200px;
356  background: var(--plum);
357}
358
359/* ============================================
360   HUB LAYOUT
361   ============================================ */
362#hub {
363  display: none;
364  min-height: 100vh;
365}
366
367.hub-layout {
368  display: flex;
369  min-height: 100vh;
370}
371
372/* Sidebar */
373.sidebar {
374  width: var(--sidebar-width);
375  background: white;
376  border-right: 1px solid rgba(197, 165, 90, 0.2);
377  display: flex;
378  flex-direction: column;
379  position: fixed;
380  top: 0;
381  left: 0;
382  bottom: 0;
383  z-index: 100;
384  transition: transform 0.3s ease;
385}
386
387.sidebar-header {
388  padding: 28px 24px 20px;
389  border-bottom: 1px solid rgba(197, 165, 90, 0.15);
390  text-align: center;
391}
392
393.sidebar-logo {
394  width: 40px;
395  height: 40px;
396  margin-bottom: 10px;
397}
398
399.sidebar-brand {
400  font-family: 'Marcellus', serif;
401  font-size: 9px;
402  letter-spacing: 4px;
403  color: var(--gold);
404  text-transform: uppercase;
405  margin-bottom: 4px;
406}
407
408.sidebar-title {
409  font-family: 'Cormorant Garamond', serif;
410  font-size: 18px;
411  color: var(--plum-deep);
412  font-weight: 400;
413}
414
415.sidebar-nav {
416  flex: 1;
417  padding: 20px 0;
418  overflow-y: auto;
419}
420
421.sidebar-link {
422  display: flex;
423  align-items: center;
424  gap: 12px;
425  padding: 14px 24px;
426  font-family: 'Marcellus', serif;
427  font-size: 10px;
428  letter-spacing: 2.5px;
429  color: var(--charcoal);
430  text-transform: uppercase;
431  text-decoration: none;
432  cursor: pointer;
433  transition: all 0.3s;
434  border-left: 3px solid transparent;
435  opacity: 0.6;
436}
437
438.sidebar-link:hover {
439  opacity: 1;
440  background: var(--cream);
441  color: var(--plum);
442}
443
444.sidebar-link.active {
445  opacity: 1;
446  color: var(--berry);
447  border-left-color: var(--berry);
448  background: rgba(148, 14, 75, 0.04);
449}
450
451.sidebar-link-icon {
452  font-size: 16px;
453  width: 20px;
454  text-align: center;
455  flex-shrink: 0;
456}
457
458.sidebar-footer {
459  padding: 20px 24px;
460  border-top: 1px solid rgba(197, 165, 90, 0.15);
461}
462
463.sidebar-user {
464  font-family: 'Cormorant Garamond', serif;
465  font-style: italic;
466  font-size: 15px;
467  color: var(--plum);
468  margin-bottom: 8px;
469  display: flex;
470  align-items: center;
471  gap: 8px;
472}
473
474.sidebar-presence-dot {
475  display: inline-block;
476  width: 8px;
477  height: 8px;
478  border-radius: 50%;
479  background: #4caf50;
480  box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.6);
481  animation: presencePulse 2s ease-in-out infinite;
482}
483
484@keyframes presencePulse {
485  0%, 100% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.6); }
486  50% { box-shadow: 0 0 0 6px rgba(76, 175, 80, 0); }
487}
488
489.sidebar-presence-toggle {
490  display: block;
491  background: none;
492  border: 1px solid var(--gold);
493  border-radius: 14px;
494  font-family: 'Marcellus', serif;
495  font-size: 10.5px;
496  letter-spacing: 1.2px;
497  color: var(--berry);
498  font-weight: 600;
499  text-transform: uppercase;
500  cursor: pointer;
501  padding: 7px 14px;
502  margin-bottom: 10px;
503  transition: all 0.3s;
504}
505.sidebar-presence-toggle:hover {
506  border-color: var(--berry);
507  color: var(--berry);
508}
509.sidebar-presence-toggle.is-here {
510  background: var(--berry);
511  color: white;
512  border-color: var(--berry);
513}
514
515/* "Sumir is in the room" banner on home view */
516.presence-banner {
517  display: inline-flex;
518  align-items: center;
519  gap: 10px;
520  padding: 10px 22px;
521  background: rgba(76, 175, 80, 0.1);
522  border: 1px solid rgba(76, 175, 80, 0.4);
523  border-radius: 24px;
524  margin-bottom: 24px;
525  animation: presenceBannerFade 1s ease;
526}
527.presence-banner-dot {
528  width: 9px;
529  height: 9px;
530  border-radius: 50%;
531  background: #4caf50;
532  box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.6);
533  animation: presencePulse 2s ease-in-out infinite;
534}
535.presence-banner-text {
536  font-family: 'Cormorant Garamond', serif;
537  font-style: italic;
538  font-size: 15px;
539  color: var(--plum-deep);
540}
541@keyframes presenceBannerFade {
542  from { opacity: 0; transform: translateY(-6px); }
543  to { opacity: 1; transform: translateY(0); }
544}
545
546.sidebar-signout {
547  font-family: 'Marcellus', serif;
548  font-size: 9px;
549  letter-spacing: 2px;
550  color: var(--gold);
551  text-transform: uppercase;
552  cursor: pointer;
553  text-decoration: none;
554  transition: color 0.3s;
555  background: none;
556  border: none;
557  padding: 0;
558}
559
560.sidebar-signout:hover { color: var(--berry); }
561
562.sidebar-back {
563  display: block;
564  margin-top: 12px;
565  font-family: 'Marcellus', serif;
566  font-size: 9px;
567  letter-spacing: 2px;
568  color: var(--gold);
569  text-transform: uppercase;
570  text-decoration: none;
571  transition: color 0.3s;
572}
573
574.sidebar-back:hover { color: var(--plum); }
575
576/* Mobile hamburger */
577.hamburger {
578  display: none;
579  position: fixed;
580  top: 16px;
581  left: 16px;
582  z-index: 200;
583  width: 40px;
584  height: 40px;
585  background: var(--plum);
586  border: 1px solid var(--gold);
587  border-radius: 8px;
588  cursor: pointer;
589  align-items: center;
590  justify-content: center;
591  flex-direction: column;
592  gap: 5px;
593}
594
595.hamburger span {
596  display: block;
597  width: 18px;
598  height: 1.5px;
599  background: var(--gold);
600  transition: all 0.3s;
601}
602
603.sidebar-overlay {
604  display: none;
605  position: fixed;
606  inset: 0;
607  background: rgba(0,0,0,0.3);
608  z-index: 90;
609}
610
611/* Content area */
612.content-area {
613  flex: 1;
614  margin-left: var(--sidebar-width);
615  min-height: 100vh;
616}
617
618.content-header {
619  background: var(--plum);
620  padding: 32px 40px 28px;
621  position: relative;
622}
623
624.content-header::after {
625  content: '';
626  position: absolute;
627  bottom: -3px;
628  left: 5%; right: 5%;
629  height: 3px;
630  background: var(--gold);
631}
632
633.content-header-eyebrow {
634  font-family: 'Marcellus', serif;
635  font-size: 10px;
636  letter-spacing: 5px;
637  color: var(--gold);
638  text-transform: uppercase;
639  margin-bottom: 6px;
640}
641
642.content-header-title {
643  font-family: 'Cormorant Garamond', serif;
644  font-size: 30px;
645  color: white;
646  font-weight: 400;
647}
648
649.content-body {
650  max-width: 720px;
651  margin: 0 auto;
652  padding: 40px 30px 60px;
653}
654
655/* ============================================
656   VIEW CONTAINERS
657   ============================================ */
658.view { display: none; }
659.view.active { display: block; }
660
661/* ============================================
662   COMMUNITY FEED
663   ============================================ */
664/* Post composer (teacher only) */
665.composer {
666  background: white;
667  border-radius: 14px;
668  border: 1px solid rgba(197, 165, 90, 0.25);
669  margin-bottom: 28px;
670  overflow: hidden;
671}
672
673.composer-trigger {
674  display: flex;
675  align-items: center;
676  gap: 12px;
677  padding: 16px 22px;
678  cursor: pointer;
679  transition: background 0.2s;
680}
681.composer-trigger:hover { background: rgba(197, 165, 90, 0.06); }
682.composer-trigger-avatar {
683  width: 36px;
684  height: 36px;
685  border-radius: 50%;
686  background: var(--plum);
687  color: var(--gold);
688  display: flex;
689  align-items: center;
690  justify-content: center;
691  font-family: 'Marcellus', serif;
692  font-size: 14px;
693  font-weight: 600;
694  flex-shrink: 0;
695}
696.composer-trigger-text {
697  font-family: 'Lora', serif;
698  font-size: 14px;
699  color: #aaa;
700  font-style: italic;
701}
702
703.composer-expanded {
704  display: none;
705  padding: 0 22px 20px;
706  border-top: 1px solid rgba(197, 165, 90, 0.15);
707}
708.composer-expanded.open { display: block; }
709
710.composer-label {
711  font-family: 'Marcellus', serif;
712  font-size: 9px;
713  letter-spacing: 3px;
714  color: var(--gold);
715  text-transform: uppercase;
716  margin-bottom: 14px;
717  padding-top: 18px;
718}
719
720.composer-title-input {
721  width: 100%;
722  padding: 10px 14px;
723  border: 1px solid rgba(197, 165, 90, 0.3);
724  border-radius: 6px;
725  background: var(--cream);
726  font-family: 'Cormorant Garamond', serif;
727  font-size: 20px;
728  color: var(--plum-deep);
729  outline: none;
730  margin-bottom: 10px;
731  transition: border-color 0.3s;
732}
733
734.composer-title-input:focus { border-color: var(--gold); }
735.composer-title-input::placeholder { color: #bbb; }
736
737.composer-textarea {
738  width: 100%;
739  min-height: 120px;
740  padding: 14px;
741  border: 1px solid rgba(197, 165, 90, 0.3);
742  border-radius: 6px;
743  background: var(--cream);
744  font-family: 'Lora', serif;
745  font-size: 13px;
746  line-height: 1.75;
747  color: var(--charcoal);
748  outline: none;
749  resize: vertical;
750  transition: border-color 0.3s;
751}
752
753.composer-textarea:focus { border-color: var(--gold); }
754.composer-textarea::placeholder { color: #bbb; }
755
756.composer-footer {
757  display: flex;
758  justify-content: space-between;
759  align-items: center;
760  margin-top: 14px;
761}
762
763.composer-type {
764  font-family: 'Marcellus', serif;
765  font-size: 10px;
766  letter-spacing: 1px;
767  color: var(--charcoal);
768  padding: 6px 12px;
769  border: 1px solid rgba(197, 165, 90, 0.3);
770  border-radius: 6px;
771  background: white;
772  outline: none;
773  cursor: pointer;
774}
775
776.composer-submit {
777  padding: 10px 28px;
778  background: var(--plum);
779  color: var(--gold);
780  border: none;
781  border-radius: 8px;
782  font-family: 'Marcellus', serif;
783  font-size: 10px;
784  letter-spacing: 2px;
785  text-transform: uppercase;
786  cursor: pointer;
787  transition: all 0.3s;
788}
789
790.composer-submit:hover {
791  background: var(--plum-deep);
792  box-shadow: 0 3px 12px rgba(107, 26, 66, 0.3);
793}
794
795.composer-submit:disabled { opacity: 0.4; cursor: not-allowed; }
796
797/* Voice recorder */
798#vr-mic-btn.recording {
799  background: var(--gold);
800  color: var(--berry);
801  animation: vrPulse 1.4s ease-in-out infinite;
802}
803@keyframes vrPulse {
804  0%, 100% { box-shadow: 0 0 0 0 rgba(197, 165, 90, 0.6); transform: scale(1); }
805  50% { box-shadow: 0 0 0 16px rgba(197, 165, 90, 0); transform: scale(1.04); }
806}
807#vr-mic-btn:hover {
808  transform: scale(1.05);
809}
810
811/* Inline voice-note player in feed */
812.voice-note-player {
813  display: flex;
814  align-items: center;
815  gap: 14px;
816  padding: 16px 18px;
817  background: linear-gradient(135deg, var(--cream-warm) 0%, rgba(197, 165, 90, 0.08) 100%);
818  border: 1px solid rgba(197, 165, 90, 0.3);
819  border-left: 4px solid var(--berry);
820  border-radius: 12px;
821  margin: 14px 0;
822}
823.voice-note-icon {
824  width: 40px; height: 40px;
825  border-radius: 50%;
826  background: var(--berry);
827  color: var(--gold-light);
828  display: flex; align-items: center; justify-content: center;
829  font-size: 20px;
830  flex-shrink: 0;
831}
832.voice-note-label {
833  font-family: 'Marcellus', serif;
834  font-size: 9px;
835  letter-spacing: 2px;
836  color: var(--berry);
837  text-transform: uppercase;
838  margin-bottom: 2px;
839}
840.vn-play-btn {
841  width: 42px; height: 42px;
842  border-radius: 50%;
843  background: var(--berry);
844  color: var(--gold-light);
845  border: none;
846  cursor: pointer;
847  display: flex; align-items: center; justify-content: center;
848  font-size: 18px;
849  flex-shrink: 0;
850  transition: transform 0.15s, background 0.15s;
851}
852.vn-play-btn:hover { transform: scale(1.08); background: var(--plum-deep); }
853.vn-controls { flex: 1; display: flex; flex-direction: column; gap: 4px; }
854.vn-progress-wrap {
855  width: 100%;
856  height: 20px;
857  background: transparent;
858  cursor: pointer;
859  position: relative;
860  display: flex;
861  align-items: center;
862}
863.vn-progress-track {
864  width: 100%;
865  height: 5px;
866  background: rgba(197,165,90,0.2);
867  border-radius: 3px;
868  position: relative;
869}
870.vn-progress-bar {
871  height: 100%;
872  background: var(--berry);
873  border-radius: 3px;
874  width: 0%;
875  position: relative;
876}
877.vn-progress-bar::after {
878  content: '';
879  position: absolute;
880  right: -6px;
881  top: 50%;
882  transform: translateY(-50%);
883  width: 12px;
884  height: 12px;
885  background: var(--berry);
886  border-radius: 50%;
887  box-shadow: 0 1px 4px rgba(0,0,0,0.2);
888}
889.vn-time-row {
890  display: flex;
891  justify-content: space-between;
892  align-items: center;
893}
894.vn-elapsed, .vn-remaining {
895  font-family: 'Marcellus', serif;
896  font-size: 10px;
897  letter-spacing: 1px;
898  color: var(--plum);
899  opacity: 0.7;
900}
901
902/* Feed posts */
903.feed-empty {
904  text-align: center;
905  padding: 60px 20px;
906}
907
908.feed-empty-icon {
909  font-size: 36px;
910  color: var(--gold-light);
911  margin-bottom: 16px;
912}
913
914.feed-empty-text {
915  font-family: 'Cormorant Garamond', serif;
916  font-style: italic;
917  font-size: 18px;
918  color: var(--plum);
919  opacity: 0.6;
920}
921
922.post-card {
923  background: white;
924  border-radius: 14px;
925  border: 1px solid rgba(197, 165, 90, 0.2);
926  border-left: 4px solid var(--berry);
927  padding: 32px 28px 24px;
928  margin-bottom: 28px;
929  animation: fadeInUp 0.6s ease;
930}
931
932@keyframes fadeInUp {
933  from { opacity: 0; transform: translateY(12px); }
934  to { opacity: 1; transform: translateY(0); }
935}
936
937.post-type-label {
938  font-family: 'Marcellus', serif;
939  font-size: 11px;
940  letter-spacing: 2px;
941  color: var(--berry);
942  text-transform: uppercase;
943  margin-bottom: 10px;
944  font-weight: 600;
945  opacity: 0.95;
946}
947
948.post-title {
949  font-family: 'Cormorant Garamond', serif;
950  font-size: 24px;
951  color: var(--plum-deep);
952  font-weight: 400;
953  margin-bottom: 14px;
954  line-height: 1.3;
955}
956
957.post-body {
958  font-family: 'Lora', serif;
959  font-size: 13px;
960  line-height: 1.85;
961  color: var(--charcoal);
962  opacity: 0.88;
963  margin-bottom: 20px;
964  white-space: pre-wrap;
965}
966
967.post-meta {
968  display: flex;
969  justify-content: space-between;
970  align-items: center;
971  padding-top: 16px;
972  border-top: 1px solid rgba(197, 165, 90, 0.12);
973}
974
975.post-author {
976  display: flex;
977  align-items: center;
978  gap: 8px;
979}
980
981.post-author-name {
982  font-family: 'Marcellus', serif;
983  font-size: 11px;
984  letter-spacing: 1px;
985  color: var(--berry);
986}
987
988.post-date {
989  font-family: 'Marcellus', serif;
990  font-size: 11px;
991  letter-spacing: 1px;
992  color: var(--berry);
993  opacity: 0.85;
994}
995
996.heart-btn {
997  display: flex;
998  align-items: center;
999  gap: 6px;
1000  background: none;
1001  border: none;
1002  cursor: pointer;
1003  padding: 6px 10px;
1004  border-radius: 20px;
1005  transition: all 0.3s;
1006}
1007
1008.heart-btn:hover {
1009  background: rgba(197, 165, 90, 0.1);
1010}
1011
1012.heart-icon {
1013  font-size: 19px;
1014  color: var(--berry);
1015  opacity: 0.55;
1016  transition: all 0.3s;
1017}
1018
1019.heart-btn:hover .heart-icon {
1020  opacity: 1;
1021}
1022
1023.heart-btn.hearted .heart-icon {
1024  color: var(--berry);
1025  opacity: 1;
1026}
1027
1028.heart-count {
1029  font-family: 'Marcellus', serif;
1030  font-size: 12px;
1031  color: var(--berry);
1032  font-weight: 600;
1033  min-width: 12px;
1034}
1035
1036@keyframes heartPulse {
1037  0% { transform: scale(1); }
1038  50% { transform: scale(1.3); }
1039  100% { transform: scale(1); }
1040}
1041
1042.heart-btn.pulse .heart-icon {
1043  animation: heartPulse 0.3s ease;
1044}
1045
1046/* Sister-to-sister emoji reactions on posts/comments */
1047.sister-reactions-row {
1048  display: flex;
1049  flex-wrap: wrap;
1050  gap: 6px;
1051  align-items: center;
1052  margin: 12px 0 0;
1053}
1054.sister-reaction-chip {
1055  display: inline-flex;
1056  align-items: center;
1057  gap: 4px;
1058  padding: 3px 9px;
1059  background: rgba(197, 165, 90, 0.1);
1060  border: 1px solid rgba(197, 165, 90, 0.25);
1061  border-radius: 14px;
1062  font-size: 13px;
1063  cursor: pointer;
1064  transition: all 0.2s;
1065}
1066.sister-reaction-chip:hover {
1067  background: rgba(197, 165, 90, 0.2);
1068}
1069.sister-reaction-chip.mine {
1070  background: rgba(148, 14, 75, 0.12);
1071  border-color: rgba(148, 14, 75, 0.4);
1072}
1073.sister-reaction-count {
1074  font-family: 'Marcellus', serif;
1075  font-size: 9px;
1076  color: var(--berry);
1077  letter-spacing: 1px;
1078}
1079.sister-reaction-picker {
1080  display: inline-flex;
1081  gap: 2px;
1082  padding: 2px 4px;
1083  background: white;
1084  border: 1px solid rgba(197, 165, 90, 0.3);
1085  border-radius: 14px;
1086  opacity: 0.6;
1087  transition: opacity 0.2s;
1088}
1089.sister-reaction-picker:hover {
1090  opacity: 1;
1091}
1092.sister-reaction-add {
1093  background: none;
1094  border: none;
1095  padding: 3px 6px;
1096  font-size: 13px;
1097  cursor: pointer;
1098  border-radius: 10px;
1099  transition: all 0.2s;
1100}
1101.sister-reaction-add:hover {
1102  background: rgba(197, 165, 90, 0.15);
1103  transform: scale(1.2);
1104}
1105
1106/* Post delete */
1107.post-delete {
1108  background: none;
1109  border: none;
1110  font-size: 14px;
1111  color: var(--gold-light);
1112  cursor: pointer;
1113  opacity: 0.4;
1114  transition: all 0.3s;
1115  padding: 4px 8px;
1116}
1117
1118.post-delete:hover {
1119  opacity: 1;
1120  color: var(--berry);
1121}
1122
1123/* Reflections */
1124.reflections-section {
1125  margin-top: 16px;
1126  padding-top: 16px;
1127  border-top: 1px solid rgba(197, 165, 90, 0.1);
1128}
1129
1130.reflections-toggle {
1131  font-family: 'Marcellus', serif;
1132  font-size: 11px;
1133  letter-spacing: 1.5px;
1134  color: var(--berry);
1135  font-weight: 600;
1136  text-transform: uppercase;
1137  cursor: pointer;
1138  background: none;
1139  border: none;
1140  padding: 6px 0;
1141  transition: color 0.3s;
1142  opacity: 0.85;
1143}
1144.reflections-toggle:hover { opacity: 1; }
1145
1146/* "Show more comments" button for long threads */
1147.show-more-comments {
1148  display: block;
1149  width: 100%;
1150  background: var(--cream-warm);
1151  border: 1px dashed rgba(197, 165, 90, 0.4);
1152  color: var(--plum);
1153  font-family: 'Marcellus', serif;
1154  font-size: 10px;
1155  letter-spacing: 1.5px;
1156  text-transform: uppercase;
1157  padding: 10px 16px;
1158  margin-bottom: 12px;
1159  border-radius: 8px;
1160  cursor: pointer;
1161  transition: all 0.3s;
1162}
1163.show-more-comments:hover {
1164  background: var(--cream);
1165  border-color: var(--gold);
1166  color: var(--berry);
1167}
1168
1169.reply-btn {
1170  font-family: 'Marcellus', serif;
1171  font-size: 11px;
1172  letter-spacing: 1.5px;
1173  color: var(--plum-deep);
1174  font-weight: 600;
1175  text-transform: uppercase;
1176  cursor: pointer;
1177  background: none;
1178  border: none;
1179  padding: 6px 0;
1180  margin-left: 16px;
1181  transition: color 0.3s;
1182  opacity: 0.85;
1183}
1184.reply-btn:hover { color: var(--berry); opacity: 1; }
1185
1186.reflections-bar {
1187  display: flex;
1188  align-items: center;
1189}
1190
1191.reflections-list {
1192  margin-top: 14px;
1193}
1194
1195.reflection-card {
1196  background: var(--cream-warm);
1197  border-radius: 10px;
1198  border: 1px solid rgba(197, 165, 90, 0.12);
1199  padding: 18px 20px 14px;
1200  margin-bottom: 10px;
1201  animation: fadeInUp 0.4s ease;
1202}
1203
1204.reflection-body {
1205  font-family: 'Lora', serif;
1206  font-size: 13px;
1207  line-height: 1.75;
1208  color: var(--charcoal);
1209  opacity: 0.85;
1210  margin-bottom: 10px;
1211  white-space: pre-wrap;
1212}
1213
1214.reflection-meta {
1215  display: flex;
1216  justify-content: space-between;
1217  align-items: center;
1218}
1219
1220.reflection-author {
1221  font-family: 'Cormorant Garamond', serif;
1222  font-style: italic;
1223  font-size: 14px;
1224  color: var(--plum);
1225}
1226
1227.reflection-date {
1228  font-family: 'Marcellus', serif;
1229  font-size: 8px;
1230  letter-spacing: 1px;
1231  color: var(--charcoal);
1232  opacity: 0.35;
1233}
1234
1235.reflection-composer {
1236  margin-top: 14px;
1237}
1238
1239.reflection-textarea {
1240  width: 100%;
1241  min-height: 70px;
1242  padding: 12px;
1243  border: 1px solid rgba(197, 165, 90, 0.25);
1244  border-radius: 8px;
1245  background: white;
1246  font-family: 'Lora', serif;
1247  font-size: 12px;
1248  line-height: 1.65;
1249  color: var(--charcoal);
1250  outline: none;
1251  resize: vertical;
1252  transition: border-color 0.3s;
1253}
1254
1255.reflection-textarea:focus { border-color: var(--gold); }
1256.reflection-textarea::placeholder { color: #bbb; }
1257
1258.reflection-submit {
1259  margin-top: 8px;
1260  padding: 8px 20px;
1261  background: var(--cream-warm);
1262  color: var(--plum);
1263  border: 1px solid var(--gold-light);
1264  border-radius: 6px;
1265  font-family: 'Marcellus', serif;
1266  font-size: 9px;
1267  letter-spacing: 2px;
1268  text-transform: uppercase;
1269  cursor: pointer;
1270  transition: all 0.3s;
1271}
1272
1273.reflection-submit:hover {
1274  background: var(--gold);
1275  color: white;
1276  border-color: var(--gold);
1277}
1278
1279/* Diamond divider */
1280.feed-divider {
1281  display: flex;
1282  align-items: center;
1283  justify-content: center;
1284  gap: 14px;
1285  padding: 4px 0 24px;
1286  max-width: 200px;
1287  margin: 0 auto;
1288}
1289
1290.feed-divider::before,
1291.feed-divider::after {
1292  content: '';
1293  flex: 1;
1294  height: 1px;
1295  background: var(--gold-light);
1296}
1297
1298.feed-divider span { color: var(--gold); font-size: 8px; }
1299
1300/* ============================================
1301   ABOUT VIEW
1302   ============================================ */
1303.pillar-card {
1304  background: white;
1305  border-radius: 14px;
1306  border: 1px solid rgba(197, 165, 90, 0.2);
1307  padding: 36px 32px 32px;
1308  margin-bottom: 20px;
1309}
1310
1311.pillar-number {
1312  font-family: 'Marcellus', serif;
1313  font-size: 9px;
1314  letter-spacing: 3px;
1315  color: var(--gold);
1316  text-transform: uppercase;
1317  margin-bottom: 8px;
1318}
1319
1320.pillar-title {
1321  font-family: 'Cormorant Garamond', serif;
1322  font-size: 22px;
1323  color: var(--plum-deep);
1324  font-weight: 400;
1325  margin-bottom: 14px;
1326}
1327
1328.pillar-body {
1329  font-family: 'Lora', serif;
1330  font-size: 13px;
1331  line-height: 1.75;
1332  color: var(--charcoal);
1333  opacity: 0.85;
1334  margin-bottom: 14px;
1335}
1336
1337.pillar-movement {
1338  font-family: 'Cormorant Garamond', serif;
1339  font-style: italic;
1340  font-size: 14px;
1341  color: var(--berry);
1342  padding-top: 10px;
1343  border-top: 1px solid rgba(197, 165, 90, 0.15);
1344}
1345
1346.about-welcome {
1347  font-family: 'Cormorant Garamond', serif;
1348  font-style: italic;
1349  font-size: 18px;
1350  line-height: 1.7;
1351  color: var(--plum);
1352  text-align: center;
1353  margin-bottom: 32px;
1354}
1355
1356.about-divider {
1357  display: flex;
1358  align-items: center;
1359  justify-content: center;
1360  gap: 14px;
1361  padding: 10px 0 30px;
1362  max-width: 300px;
1363  margin: 0 auto;
1364}
1365
1366.about-divider::before,
1367.about-divider::after {
1368  content: '';
1369  flex: 1;
1370  height: 1px;
1371  background: var(--gold-light);
1372}
1373
1374.about-divider span { color: var(--gold); font-size: 10px; }
1375
1376/* Structure card */
1377.structure-card {
1378  background: white;
1379  border-radius: 14px;
1380  border: 1px solid rgba(197, 165, 90, 0.2);
1381  padding: 40px 32px;
1382  text-align: center;
1383  margin-bottom: 20px;
1384}
1385
1386.structure-heading {
1387  font-family: 'Marcellus', serif;
1388  font-size: 10px;
1389  letter-spacing: 4px;
1390  color: var(--gold);
1391  text-transform: uppercase;
1392  margin-bottom: 6px;
1393}
1394
1395.structure-title {
1396  font-family: 'Cormorant Garamond', serif;
1397  font-size: 24px;
1398  color: var(--plum-deep);
1399  font-weight: 400;
1400  margin-bottom: 20px;
1401}
1402
1403.structure-item {
1404  display: flex;
1405  gap: 14px;
1406  margin-bottom: 20px;
1407  align-items: flex-start;
1408  text-align: left;
1409  max-width: 520px;
1410  margin-left: auto;
1411  margin-right: auto;
1412}
1413
1414.structure-icon {
1415  color: var(--gold);
1416  font-size: 14px;
1417  margin-top: 2px;
1418  flex-shrink: 0;
1419}
1420
1421.structure-item-title {
1422  font-family: 'Cormorant Garamond', serif;
1423  font-size: 17px;
1424  color: var(--plum-deep);
1425  margin-bottom: 4px;
1426}
1427
1428.structure-item-text {
1429  font-family: 'Lora', serif;
1430  font-size: 12px;
1431  line-height: 1.65;
1432  color: var(--charcoal);
1433  opacity: 0.8;
1434}
1435
1436/* Leadership card */
1437.leadership-card {
1438  background: var(--plum-deep);
1439  border-radius: 14px;
1440  padding: 44px 36px;
1441  position: relative;
1442  overflow: hidden;
1443  margin-bottom: 20px;
1444}
1445
1446.leadership-card::before {
1447  content: '';
1448  position: absolute;
1449  top: 0; left: 0; right: 0;
1450  height: 3px;
1451  background: var(--gold);
1452}
1453
1454.leadership-eyebrow {
1455  font-family: 'Marcellus', serif;
1456  font-size: 9px;
1457  letter-spacing: 4px;
1458  color: var(--gold);
1459  text-transform: uppercase;
1460  margin-bottom: 10px;
1461  text-align: center;
1462}
1463
1464.leadership-title {
1465  font-family: 'Cormorant Garamond', serif;
1466  font-size: 24px;
1467  color: white;
1468  font-weight: 400;
1469  text-align: center;
1470  margin-bottom: 20px;
1471}
1472
1473.leadership-body {
1474  font-family: 'Lora', serif;
1475  font-size: 13px;
1476  line-height: 1.8;
1477  color: rgba(255,255,255,0.85);
1478  margin-bottom: 16px;
1479}
1480
1481.leadership-emphasis {
1482  font-family: 'Cormorant Garamond', serif;
1483  font-style: italic;
1484  font-size: 15px;
1485  line-height: 1.7;
1486  color: var(--gold-light);
1487  text-align: center;
1488  padding-top: 16px;
1489  border-top: 1px solid rgba(197, 165, 90, 0.25);
1490}
1491
1492/* ============================================
1493   JOURNAL VIEW
1494   ============================================ */
1495/* Thoughtful Inquiries card */
1496.inquiry-banner {
1497  background: white;
1498  border-radius: 14px;
1499  border: 1px solid rgba(197, 165, 90, 0.25);
1500  margin-bottom: 24px;
1501  overflow: hidden;
1502  box-shadow: 0 2px 16px rgba(139, 34, 82, 0.04);
1503}
1504.inquiry-banner-header {
1505  display: flex;
1506  align-items: center;
1507  justify-content: space-between;
1508  padding: 20px 24px;
1509  cursor: pointer;
1510  transition: background 0.2s;
1511  border-left: 4px solid var(--berry);
1512}
1513.inquiry-banner-header:hover { background: var(--cream-warm); }
1514.inquiry-banner-title {
1515  font-family: 'Cormorant Garamond', serif;
1516  font-size: 20px;
1517  color: var(--berry);
1518  font-weight: 600;
1519}
1520.inquiry-banner-subtitle {
1521  font-family: 'Lora', serif;
1522  font-size: 11px;
1523  color: var(--charcoal);
1524  opacity: 0.6;
1525  margin-top: 2px;
1526}
1527.inquiry-banner-arrow {
1528  font-size: 16px;
1529  color: var(--gold);
1530  transition: transform 0.3s;
1531}
1532.inquiry-banner.open .inquiry-banner-arrow { transform: rotate(180deg); }
1533.inquiry-banner-body {
1534  display: none;
1535  padding: 0 28px 28px;
1536  border-top: 1px solid rgba(197, 165, 90, 0.12);
1537}
1538.inquiry-banner.open .inquiry-banner-body { display: block; }
1539.inquiry-intro {
1540  font-family: 'Cormorant Garamond', serif;
1541  font-style: italic;
1542  font-size: 16px;
1543  line-height: 1.7;
1544  color: var(--plum);
1545  text-align: center;
1546  padding: 24px 10px 20px;
1547}
1548.inquiry-section {
1549  margin-bottom: 24px;
1550}
1551.inquiry-section-num {
1552  font-family: 'Marcellus', serif;
1553  font-size: 9px;
1554  letter-spacing: 3px;
1555  text-transform: uppercase;
1556  color: var(--gold);
1557  margin-bottom: 8px;
1558}
1559.inquiry-section-q {
1560  font-family: 'Cormorant Garamond', serif;
1561  font-size: 19px;
1562  line-height: 1.5;
1563  color: var(--berry);
1564  font-weight: 600;
1565  margin-bottom: 14px;
1566}
1567.inquiry-cat-title {
1568  font-family: 'Marcellus', serif;
1569  font-size: 10px;
1570  letter-spacing: 2px;
1571  text-transform: uppercase;
1572  color: var(--berry);
1573  font-weight: 700;
1574  margin: 16px 0 8px;
1575}
1576.inquiry-input {
1577  width: 100%;
1578  border: none;
1579  border-bottom: 1px solid rgba(197, 165, 90, 0.3);
1580  background: transparent;
1581  font-family: 'Lora', serif;
1582  font-size: 13px;
1583  color: var(--charcoal);
1584  padding: 8px 4px;
1585  margin-bottom: 6px;
1586  outline: none;
1587  transition: border-color 0.2s;
1588}
1589.inquiry-input:focus { border-bottom-color: var(--berry); }
1590.inquiry-input::placeholder { color: var(--gold-light); font-style: italic; }
1591.inquiry-line-label {
1592  font-family: 'Marcellus', serif;
1593  font-size: 10px;
1594  color: var(--gold);
1595  margin-right: 8px;
1596  flex-shrink: 0;
1597}
1598.inquiry-line-row {
1599  display: flex;
1600  align-items: flex-end;
1601  gap: 4px;
1602}
1603.inquiry-save-row {
1604  display: flex;
1605  align-items: center;
1606  justify-content: space-between;
1607  margin-top: 20px;
1608  padding-top: 16px;
1609  border-top: 1px solid rgba(197, 165, 90, 0.12);
1610}
1611.inquiry-save-btn {
1612  font-family: 'Marcellus', serif;
1613  font-size: 10px;
1614  letter-spacing: 2px;
1615  text-transform: uppercase;
1616  color: var(--gold);
1617  background: var(--berry);
1618  border: 1.5px solid var(--gold);
1619  border-radius: 8px;
1620  padding: 10px 24px;
1621  cursor: pointer;
1622  transition: all 0.3s;
1623}
1624.inquiry-save-btn:hover { background: var(--plum-deep); }
1625.inquiry-saved-msg {
1626  font-family: 'Lora', serif;
1627  font-size: 11px;
1628  font-style: italic;
1629  color: var(--gold);
1630  opacity: 0;
1631  transition: opacity 0.3s;
1632}
1633.inquiry-saved-msg.show { opacity: 1; }
1634.inquiry-divider {
1635  display: flex;
1636  align-items: center;
1637  justify-content: center;
1638  gap: 10px;
1639  padding: 8px 0 16px;
1640  max-width: 200px;
1641  margin: 0 auto;
1642}
1643.inquiry-divider::before, .inquiry-divider::after {
1644  content: '';
1645  flex: 1;
1646  height: 1px;
1647  background: var(--gold-light);
1648}
1649.inquiry-divider span { color: var(--gold); font-size: 8px; }
1650
1651.journal-prompt-card {
1652  background: white;
1653  border-radius: 14px;
1654  border: 1px solid rgba(197, 165, 90, 0.2);
1655  border-left: 4px solid var(--gold);
1656  padding: 28px 28px 24px;
1657  margin-bottom: 20px;
1658}
1659
1660.journal-prompt-label {
1661  font-family: 'Marcellus', serif;
1662  font-size: 9px;
1663  letter-spacing: 3px;
1664  color: var(--gold);
1665  text-transform: uppercase;
1666  margin-bottom: 10px;
1667}
1668
1669.journal-prompt-text {
1670  font-family: 'Cormorant Garamond', serif;
1671  font-style: italic;
1672  font-size: 18px;
1673  color: var(--plum);
1674  line-height: 1.6;
1675}
1676
1677.journal-entry-card {
1678  background: var(--cream-warm);
1679  border-radius: 10px;
1680  border: 1px solid rgba(197, 165, 90, 0.12);
1681  padding: 22px 24px;
1682  margin-bottom: 14px;
1683  cursor: pointer;
1684  transition: border-color 0.3s, box-shadow 0.3s;
1685}
1686.journal-entry-card:hover {
1687  border-color: rgba(197, 165, 90, 0.35);
1688  box-shadow: 0 2px 12px rgba(197, 165, 90, 0.08);
1689}
1690.journal-entry-card.expanded {
1691  cursor: default;
1692  border-color: var(--gold-light);
1693}
1694
1695.journal-entry-date {
1696  font-family: 'Marcellus', serif;
1697  font-size: 9px;
1698  letter-spacing: 2px;
1699  color: var(--berry);
1700  text-transform: uppercase;
1701  margin-bottom: 8px;
1702}
1703
1704.journal-entry-body {
1705  font-family: 'Lora', serif;
1706  font-size: 13px;
1707  line-height: 1.75;
1708  color: var(--charcoal);
1709  opacity: 0.85;
1710  white-space: pre-wrap;
1711}
1712.journal-entry-body.truncated {
1713  display: -webkit-box;
1714  -webkit-line-clamp: 2;
1715  -webkit-box-orient: vertical;
1716  overflow: hidden;
1717}
1718.journal-entry-actions {
1719  display: none;
1720  margin-top: 14px;
1721  padding-top: 12px;
1722  border-top: 1px solid rgba(197, 165, 90, 0.15);
1723  gap: 8px;
1724}
1725.journal-entry-card.expanded .journal-entry-actions {
1726  display: flex;
1727}
1728.journal-entry-card.expanded .journal-entry-body.truncated {
1729  display: block;
1730  -webkit-line-clamp: unset;
1731  overflow: visible;
1732}
1733.journal-entry-action-btn {
1734  padding: 5px 14px;
1735  border-radius: 16px;
1736  font-family: 'Marcellus', serif;
1737  font-size: 9px;
1738  letter-spacing: 1.5px;
1739  text-transform: uppercase;
1740  cursor: pointer;
1741  border: 1px solid;
1742  transition: all 0.3s;
1743}
1744.journal-entry-action-btn.edit-btn {
1745  background: none;
1746  border-color: var(--gold);
1747  color: var(--gold);
1748}
1749.journal-entry-action-btn.edit-btn:hover {
1750  background: var(--gold);
1751  color: white;
1752}
1753.journal-entry-action-btn.delete-btn {
1754  background: none;
1755  border-color: rgba(148, 14, 75, 0.3);
1756  color: var(--berry);
1757}
1758.journal-entry-action-btn.delete-btn:hover {
1759  background: var(--berry);
1760  color: white;
1761}
1762.journal-entry-action-btn.vis-btn {
1763  background: none;
1764  border-color: rgba(148, 14, 75, 0.2);
1765  color: var(--plum);
1766}
1767.journal-entry-action-btn.vis-btn:hover {
1768  background: var(--plum);
1769  color: white;
1770}
1771.journal-entry-collapse-hint {
1772  display: none;
1773  margin-top: 10px;
1774  font-family: 'Marcellus', serif;
1775  font-size: 8px;
1776  letter-spacing: 1.5px;
1777  text-transform: uppercase;
1778  color: var(--gold);
1779  text-align: right;
1780}
1781.journal-entry-card.expanded .journal-entry-collapse-hint {
1782  display: block;
1783}
1784
1785/* Journal thread (reactions + replies) */
1786.journal-thread {
1787  display: none;
1788  margin-top: 14px;
1789  padding-top: 14px;
1790  border-top: 1px solid rgba(197, 165, 90, 0.18);
1791}
1792.journal-entry-card.expanded .journal-thread {
1793  display: block;
1794}
1795
1796.j-reactions-row {
1797  display: flex;
1798  flex-wrap: wrap;
1799  gap: 6px;
1800  align-items: center;
1801  margin-bottom: 12px;
1802}
1803.j-reaction-chip {
1804  display: inline-flex;
1805  align-items: center;
1806  gap: 4px;
1807  padding: 3px 9px;
1808  background: rgba(197, 165, 90, 0.1);
1809  border: 1px solid rgba(197, 165, 90, 0.25);
1810  border-radius: 14px;
1811  font-size: 13px;
1812  cursor: pointer;
1813  transition: all 0.2s;
1814}
1815.j-reaction-chip:hover {
1816  background: rgba(197, 165, 90, 0.2);
1817}
1818.j-reaction-chip.mine {
1819  background: rgba(148, 14, 75, 0.12);
1820  border-color: rgba(148, 14, 75, 0.4);
1821}
1822.j-reaction-count {
1823  font-family: 'Marcellus', serif;
1824  font-size: 9px;
1825  color: var(--berry);
1826  letter-spacing: 1px;
1827}
1828.j-emoji-picker {
1829  display: inline-flex;
1830  gap: 2px;
1831  padding: 2px 4px;
1832  background: white;
1833  border: 1px solid rgba(197, 165, 90, 0.3);
1834  border-radius: 14px;
1835}
1836.j-emoji-btn {
1837  background: none;
1838  border: none;
1839  padding: 3px 6px;
1840  font-size: 14px;
1841  cursor: pointer;
1842  border-radius: 10px;
1843  transition: all 0.2s;
1844}
1845.j-emoji-btn:hover {
1846  background: rgba(197, 165, 90, 0.15);
1847  transform: scale(1.2);
1848}
1849
1850.j-replies-list {
1851  margin: 12px 0;
1852}
1853.j-reply {
1854  background: white;
1855  border-radius: 8px;
1856  padding: 12px 14px;
1857  margin-bottom: 8px;
1858  border-left: 2px solid var(--gold);
1859}
1860.j-reply-author {
1861  font-family: 'Marcellus', serif;
1862  font-size: 9px;
1863  letter-spacing: 1.5px;
1864  color: var(--berry);
1865  text-transform: uppercase;
1866  margin-bottom: 4px;
1867}
1868.j-reply-body {
1869  font-family: 'Lora', serif;
1870  font-size: 12.5px;
1871  line-height: 1.65;
1872  color: var(--charcoal);
1873  opacity: 0.9;
1874  white-space: pre-wrap;
1875}
1876.j-reply-date {
1877  font-family: 'Marcellus', serif;
1878  font-size: 8px;
1879  letter-spacing: 1px;
1880  color: var(--gold);
1881  margin-top: 6px;
1882  opacity: 0.7;
1883}
1884.j-reply-delete {
1885  background: none;
1886  border: none;
1887  color: var(--berry);
1888  opacity: 0.4;
1889  font-size: 12px;
1890  cursor: pointer;
1891  float: right;
1892  padding: 0 4px;
1893}
1894.j-reply-delete:hover {
1895  opacity: 1;
1896}
1897
1898.j-reply-form {
1899  display: flex;
1900  gap: 8px;
1901  margin-top: 10px;
1902}
1903.j-reply-input {
1904  flex: 1;
1905  padding: 8px 12px;
1906  border: 1px solid rgba(197, 165, 90, 0.3);
1907  border-radius: 18px;
1908  background: white;
1909  font-family: 'Lora', serif;
1910  font-size: 12.5px;
1911  color: var(--charcoal);
1912  outline: none;
1913  transition: border-color 0.3s;
1914}
1915.j-reply-input:focus { border-color: var(--gold); }
1916.j-reply-send {
1917  padding: 6px 16px;
1918  background: var(--plum);
1919  color: var(--gold);
1920  border: none;
1921  border-radius: 18px;
1922  font-family: 'Marcellus', serif;
1923  font-size: 9px;
1924  letter-spacing: 1.5px;
1925  text-transform: uppercase;
1926  cursor: pointer;
1927  transition: all 0.3s;
1928}
1929.j-reply-send:hover { background: var(--plum-deep); }
1930
1931.journal-compose {
1932  background: white;
1933  border-radius: 14px;
1934  border: 1px solid rgba(197, 165, 90, 0.25);
1935  padding: 24px;
1936  margin-bottom: 24px;
1937}
1938
1939.journal-compose-label {
1940  font-family: 'Marcellus', serif;
1941  font-size: 9px;
1942  letter-spacing: 3px;
1943  color: var(--gold);
1944  text-transform: uppercase;
1945  margin-bottom: 12px;
1946}
1947
1948.journal-textarea {
1949  width: 100%;
1950  min-height: 140px;
1951  padding: 14px;
1952  border: 1px solid rgba(197, 165, 90, 0.3);
1953  border-radius: 6px;
1954  background: var(--cream);
1955  font-family: 'Lora', serif;
1956  font-size: 13px;
1957  line-height: 1.75;
1958  color: var(--charcoal);
1959  outline: none;
1960  resize: vertical;
1961  transition: border-color 0.3s;
1962}
1963
1964.journal-textarea:focus { border-color: var(--gold); }
1965
1966.journal-visibility-toggle {
1967  display: flex;
1968  gap: 4px;
1969}
1970.journal-vis-btn {
1971  padding: 5px 12px;
1972  background: none;
1973  border: 1px solid rgba(197, 165, 90, 0.25);
1974  border-radius: 16px;
1975  font-family: 'Marcellus', serif;
1976  font-size: 9px;
1977  letter-spacing: 1px;
1978  text-transform: uppercase;
1979  color: var(--plum-light);
1980  cursor: pointer;
1981  transition: all 0.3s;
1982}
1983.journal-vis-btn:hover { border-color: var(--gold); }
1984.journal-vis-btn.active {
1985  background: var(--gold);
1986  border-color: var(--gold);
1987  color: white;
1988}
1989.journal-entry-visibility {
1990  font-family: 'Marcellus', serif;
1991  font-size: 8px;
1992  letter-spacing: 1.5px;
1993  text-transform: uppercase;
1994  padding: 2px 8px;
1995  border-radius: 10px;
1996  display: inline-block;
1997  margin-left: 8px;
1998}
1999.journal-entry-visibility.private {
2000  background: rgba(197, 165, 90, 0.12);
2001  color: var(--gold);
2002}
2003.journal-entry-visibility.public {
2004  background: rgba(148, 14, 75, 0.1);
2005  color: var(--berry);
2006}
2007
2008.journal-save {
2009  margin-top: 10px;
2010  padding: 10px 24px;
2011  background: var(--plum);
2012  color: var(--gold);
2013  border: none;
2014  border-radius: 8px;
2015  font-family: 'Marcellus', serif;
2016  font-size: 10px;
2017  letter-spacing: 2px;
2018  text-transform: uppercase;
2019  cursor: pointer;
2020  transition: all 0.3s;
2021}
2022
2023.journal-save:hover {
2024  background: var(--plum-deep);
2025}
2026
2027.journal-save-draft {
2028  background: white;
2029  color: var(--plum);
2030  border: 1px solid var(--gold);
2031}
2032.journal-save-draft:hover {
2033  background: var(--cream-warm);
2034  color: var(--berry);
2035}
2036
2037/* Draft badge on entry cards */
2038.journal-draft-badge {
2039  display: inline-block;
2040  font-family: 'Marcellus', serif;
2041  font-size: 8px;
2042  letter-spacing: 1.5px;
2043  text-transform: uppercase;
2044  padding: 2px 8px;
2045  border-radius: 10px;
2046  background: rgba(197, 165, 90, 0.15);
2047  border: 1px dashed var(--gold);
2048  color: var(--plum);
2049  margin-left: 8px;
2050}
2051
2052/* Bookmark / favorite button (used in library) */
2053.bookmark-btn {
2054  background: none;
2055  border: none;
2056  cursor: pointer;
2057  font-size: 22px;
2058  color: var(--gold-light);
2059  padding: 4px 8px;
2060  transition: all 0.2s;
2061}
2062.bookmark-btn:hover {
2063  color: var(--berry);
2064  transform: scale(1.15);
2065}
2066.bookmark-btn.bookmarked {
2067  color: var(--berry);
2068}
2069
2070/* ============================================
2071   MEDIA UPLOAD & EDIT
2072   ============================================ */
2073.composer-media-row {
2074  display: flex;
2075  align-items: center;
2076  gap: 8px;
2077  margin-top: 8px;
2078}
2079.media-upload-btn {
2080  display: inline-flex;
2081  align-items: center;
2082  gap: 5px;
2083  padding: 6px 14px;
2084  background: none;
2085  border: 1px solid rgba(197, 165, 90, 0.3);
2086  border-radius: 6px;
2087  font-family: 'Marcellus', serif;
2088  font-size: 9px;
2089  letter-spacing: 1.5px;
2090  text-transform: uppercase;
2091  color: var(--plum-light);
2092  cursor: pointer;
2093  transition: all 0.3s;
2094}
2095.media-upload-btn:hover { border-color: var(--gold); color: var(--plum); }
2096.media-upload-btn .upload-icon { font-size: 14px; }
2097.media-preview {
2098  margin-top: 10px;
2099  position: relative;
2100  display: inline-block;
2101}
2102.media-preview img, .media-preview video {
2103  max-width: 100%;
2104  max-height: 200px;
2105  border-radius: 8px;
2106  border: 1px solid rgba(197, 165, 90, 0.2);
2107}
2108.media-preview-remove {
2109  position: absolute;
2110  top: 4px;
2111  right: 4px;
2112  background: rgba(0,0,0,0.6);
2113  color: white;
2114  border: none;
2115  border-radius: 50%;
2116  width: 24px;
2117  height: 24px;
2118  font-size: 14px;
2119  cursor: pointer;
2120  display: flex;
2121  align-items: center;
2122  justify-content: center;
2123}
2124.post-media {
2125  margin: 12px 0;
2126  border-radius: 10px;
2127  overflow: hidden;
2128  max-width: 440px;
2129  display: flex;
2130  justify-content: center;
2131  background: #000;
2132}
2133.post-media img {
2134  max-width: 100%;
2135  max-height: 420px;
2136  width: auto;
2137  border-radius: 10px;
2138  cursor: pointer;
2139  object-fit: contain;
2140}
2141.post-media video {
2142  max-width: 100%;
2143  max-height: 420px;
2144  width: auto;
2145  border-radius: 10px;
2146  object-fit: contain;
2147}
2148@media (max-width: 600px) {
2149  .post-media { max-width: 100%; }
2150  .post-media img,
2151  .post-media video { max-height: 320px; }
2152}
2153.post-edit-btn {
2154  background: none;
2155  border: none;
2156  color: var(--plum-light);
2157  font-family: 'Marcellus', serif;
2158  font-size: 9px;
2159  letter-spacing: 1px;
2160  text-transform: uppercase;
2161  cursor: pointer;
2162  opacity: 0.7;
2163  transition: opacity 0.2s;
2164  padding: 2px 6px;
2165}
2166.post-edit-btn:hover { opacity: 1; color: var(--plum); }
2167.post-hide-btn {
2168  background: none;
2169  border: none;
2170  color: var(--plum-light);
2171  font-family: 'Marcellus', serif;
2172  font-size: 9px;
2173  letter-spacing: 1px;
2174  text-transform: uppercase;
2175  cursor: pointer;
2176  opacity: 0.7;
2177  transition: opacity 0.2s;
2178  padding: 2px 6px;
2179}
2180.post-hide-btn:hover { opacity: 1; color: var(--plum); }
2181
2182.edit-overlay {
2183  position: fixed;
2184  top: 0; left: 0; right: 0; bottom: 0;
2185  background: rgba(0,0,0,0.5);
2186  display: flex;
2187  align-items: center;
2188  justify-content: center;
2189  z-index: 1000;
2190}
2191
2192/* Toast notifications */
2193.fr-toast-container {
2194  position: fixed;
2195  bottom: 24px;
2196  left: 50%;
2197  transform: translateX(-50%);
2198  z-index: 2000;
2199  display: flex;
2200  flex-direction: column;
2201  gap: 10px;
2202  pointer-events: none;
2203  width: calc(100% - 40px);
2204  max-width: 420px;
2205  align-items: center;
2206}
2207.fr-toast {
2208  background: var(--plum);
2209  color: var(--gold);
2210  padding: 14px 22px;
2211  border-radius: 10px;
2212  font-family: 'Lora', serif;
2213  font-size: 13px;
2214  line-height: 1.5;
2215  box-shadow: 0 6px 24px rgba(107, 26, 66, 0.35);
2216  border: 1px solid var(--gold-light);
2217  opacity: 0;
2218  transform: translateY(10px);
2219  transition: opacity 0.3s, transform 0.3s;
2220  pointer-events: auto;
2221  text-align: center;
2222  max-width: 100%;
2223}
2224.fr-toast.show { opacity: 1; transform: translateY(0); }
2225.fr-toast.error { background: var(--berry); }
2226.fr-toast.success { background: var(--plum); }
2227
2228/* Loading state for buttons */
2229button.is-loading {
2230  opacity: 0.7;
2231  cursor: wait;
2232  position: relative;
2233}
2234button.is-loading::after {
2235  content: '';
2236  display: inline-block;
2237  width: 8px;
2238  height: 8px;
2239  margin-left: 6px;
2240  border-radius: 50%;
2241  background: currentColor;
2242  animation: frPulse 0.9s ease-in-out infinite;
2243  vertical-align: middle;
2244}
2245@keyframes frPulse {
2246  0%, 100% { opacity: 0.3; transform: scale(0.8); }
2247  50% { opacity: 1; transform: scale(1.2); }
2248}
2249.edit-modal {
2250  background: var(--cream);
2251  border-radius: 14px;
2252  border: 2px solid var(--gold-pale);
2253  padding: 28px;
2254  width: 90%;
2255  max-width: 500px;
2256  box-shadow: 0 20px 60px rgba(0,0,0,0.2);
2257}
2258.edit-modal-title {
2259  font-family: 'Marcellus', serif;
2260  font-size: 9px;
2261  letter-spacing: 3px;
2262  text-transform: uppercase;
2263  color: var(--gold);
2264  margin-bottom: 14px;
2265}
2266.edit-modal textarea {
2267  width: 100%;
2268  min-height: 120px;
2269  padding: 12px;
2270  border: 1px solid rgba(197, 165, 90, 0.3);
2271  border-radius: 6px;
2272  background: white;
2273  font-family: 'Lora', serif;
2274  font-size: 13px;
2275  line-height: 1.75;
2276  color: var(--charcoal);
2277  outline: none;
2278  resize: vertical;
2279}
2280.edit-modal textarea:focus { border-color: var(--gold); }
2281.edit-modal-actions {
2282  display: flex;
2283  justify-content: flex-end;
2284  gap: 10px;
2285  margin-top: 14px;
2286}
2287.edit-modal-cancel {
2288  padding: 8px 18px;
2289  background: none;
2290  border: 1px solid rgba(197, 165, 90, 0.3);
2291  border-radius: 6px;
2292  font-family: 'Marcellus', serif;
2293  font-size: 9px;
2294  letter-spacing: 1.5px;
2295  text-transform: uppercase;
2296  color: var(--plum-light);
2297  cursor: pointer;
2298}
2299.edit-modal-save {
2300  padding: 8px 18px;
2301  background: var(--plum);
2302  border: none;
2303  border-radius: 6px;
2304  font-family: 'Marcellus', serif;
2305  font-size: 9px;
2306  letter-spacing: 1.5px;
2307  text-transform: uppercase;
2308  color: var(--gold);
2309  cursor: pointer;
2310}
2311.edit-modal-save:hover { background: var(--plum-deep); }
2312
2313.comment-media-btn {
2314  background: none;
2315  border: none;
2316  font-size: 16px;
2317  cursor: pointer;
2318  padding: 4px;
2319  opacity: 0.5;
2320  transition: opacity 0.2s;
2321}
2322.comment-media-btn:hover { opacity: 1; }
2323
2324/* Emoji Picker */
2325.emoji-picker {
2326  position: absolute;
2327  bottom: 36px;
2328  left: 0;
2329  background: white;
2330  border: 1px solid rgba(197, 165, 90, 0.3);
2331  border-radius: 12px;
2332  box-shadow: 0 8px 30px rgba(0,0,0,0.12);
2333  padding: 12px;
2334  width: 280px;
2335  max-height: 260px;
2336  overflow-y: auto;
2337  z-index: 100;
2338}
2339.emoji-picker-grid {
2340  display: grid;
2341  grid-template-columns: repeat(8, 1fr);
2342  gap: 2px;
2343}
2344.emoji-btn {
2345  background: none;
2346  border: none;
2347  font-size: 20px;
2348  cursor: pointer;
2349  padding: 4px;
2350  border-radius: 6px;
2351  transition: background 0.15s;
2352}
2353.emoji-btn:hover {
2354  background: rgba(197, 165, 90, 0.15);
2355}
2356.emoji-category-label {
2357  font-family: 'Marcellus', serif;
2358  font-size: 8px;
2359  letter-spacing: 2px;
2360  text-transform: uppercase;
2361  color: var(--gold);
2362  margin: 8px 0 4px;
2363}
2364.emoji-category-label:first-child { margin-top: 0; }
2365
2366/* ============================================
2367   FEED TOOLBAR (Filters + Search)
2368   ============================================ */
2369.feed-toolbar {
2370  display: flex;
2371  align-items: center;
2372  justify-content: space-between;
2373  gap: 12px;
2374  margin-bottom: 20px;
2375  flex-wrap: wrap;
2376}
2377
2378.feed-filters {
2379  display: flex;
2380  gap: 4px;
2381}
2382
2383.feed-filter-btn {
2384  padding: 7px 16px;
2385  background: none;
2386  border: 1px solid var(--gold);
2387  border-radius: 20px;
2388  font-family: 'Marcellus', serif;
2389  font-size: 11px;
2390  letter-spacing: 1.2px;
2391  text-transform: uppercase;
2392  color: var(--plum-deep);
2393  font-weight: 600;
2394  cursor: pointer;
2395  transition: all 0.3s;
2396}
2397.feed-filter-btn:hover {
2398  border-color: var(--gold);
2399  color: var(--berry);
2400  background: rgba(197, 165, 90, 0.08);
2401}
2402.feed-filter-btn.active {
2403  background: var(--gold);
2404  border-color: var(--gold);
2405  color: white;
2406}
2407
2408.feed-search {
2409  position: relative;
2410  min-width: 200px;
2411}
2412.feed-search-input {
2413  width: 100%;
2414  padding: 7px 14px 7px 32px;
2415  border: 1px solid rgba(197, 165, 90, 0.25);
2416  border-radius: 20px;
2417  background: white;
2418  font-family: 'Lora', serif;
2419  font-size: 12px;
2420  color: var(--charcoal);
2421  outline: none;
2422  transition: border-color 0.3s;
2423}
2424.feed-search-input:focus {
2425  border-color: var(--gold);
2426}
2427.feed-search-input::placeholder {
2428  color: #C0B8A8;
2429  font-style: italic;
2430}
2431.feed-search-icon {
2432  position: absolute;
2433  left: 11px;
2434  top: 50%;
2435  transform: translateY(-50%);
2436  font-size: 13px;
2437  color: var(--gold-light);
2438  pointer-events: none;
2439}
2440.feed-search-clear {
2441  position: absolute;
2442  right: 8px;
2443  top: 50%;
2444  transform: translateY(-50%);
2445  background: none;
2446  border: none;
2447  font-size: 14px;
2448  color: var(--plum-light);
2449  cursor: pointer;
2450  display: none;
2451  padding: 2px 4px;
2452}
2453.feed-search-clear.show { display: block; }
2454
2455@media (max-width: 600px) {
2456  .feed-toolbar { flex-direction: column; align-items: stretch; }
2457  .feed-search { min-width: unset; }
2458}
2459
2460/* ============================================
2461   REPLAYS / PLACEHOLDER VIEW
2462   ============================================ */
2463.placeholder-view {
2464  text-align: center;
2465  padding: 80px 30px;
2466}
2467
2468.placeholder-icon {
2469  font-size: 48px;
2470  color: var(--gold-light);
2471  margin-bottom: 20px;
2472}
2473
2474.placeholder-title {
2475  font-family: 'Cormorant Garamond', serif;
2476  font-size: 26px;
2477  color: var(--plum-deep);
2478  font-weight: 400;
2479  margin-bottom: 10px;
2480}
2481
2482.placeholder-text {
2483  font-family: 'Cormorant Garamond', serif;
2484  font-style: italic;
2485  font-size: 16px;
2486  color: var(--plum);
2487  opacity: 0.6;
2488}
2489
2490/* ============================================
2491   REPLAYS
2492   ============================================ */
2493.replay-upload {
2494  background: white;
2495  border-radius: 14px;
2496  border: 1px solid rgba(197, 165, 90, 0.25);
2497  padding: 24px;
2498  margin-bottom: 24px;
2499}
2500.replay-upload-label {
2501  font-family: 'Marcellus', serif;
2502  font-size: 9px;
2503  letter-spacing: 3px;
2504  color: var(--gold);
2505  text-transform: uppercase;
2506  margin-bottom: 12px;
2507}
2508.replay-card {
2509  background: var(--cream-warm);
2510  border-radius: 12px;
2511  border: 1px solid rgba(197, 165, 90, 0.12);
2512  padding: 24px;
2513  margin-bottom: 18px;
2514}
2515.replay-card-title {
2516  font-family: 'Cormorant Garamond', serif;
2517  font-size: 22px;
2518  color: var(--plum-deep);
2519  font-weight: 400;
2520  margin-bottom: 6px;
2521}
2522.replay-card-desc {
2523  font-family: 'Lora', serif;
2524  font-size: 13px;
2525  line-height: 1.6;
2526  color: var(--charcoal);
2527  opacity: 0.8;
2528  margin-bottom: 14px;
2529}
2530.replay-card-date {
2531  font-family: 'Marcellus', serif;
2532  font-size: 9px;
2533  letter-spacing: 2px;
2534  color: var(--berry);
2535  text-transform: uppercase;
2536  margin-bottom: 12px;
2537}
2538.replay-card video {
2539  max-width: 440px;
2540  width: 100%;
2541  max-height: 420px;
2542  border-radius: 10px;
2543  margin-top: 10px;
2544  display: block;
2545  background: #000;
2546  object-fit: contain;
2547}
2548@media (max-width: 600px) {
2549  .replay-card video {
2550    max-width: 100%;
2551    max-height: 320px;
2552  }
2553}
2554.replay-card-actions {
2555  display: flex;
2556  gap: 8px;
2557  margin-top: 12px;
2558  justify-content: flex-end;
2559}
2560.replay-delete-btn {
2561  padding: 4px 12px;
2562  border-radius: 14px;
2563  font-family: 'Marcellus', serif;
2564  font-size: 8px;
2565  letter-spacing: 1.5px;
2566  text-transform: uppercase;
2567  cursor: pointer;
2568  background: none;
2569  border: 1px solid rgba(148, 14, 75, 0.3);
2570  color: var(--berry);
2571  transition: all 0.3s;
2572}
2573.replay-delete-btn:hover {
2574  background: var(--berry);
2575  color: white;
2576}
2577
2578/* ============================================
2579   TOOLS & GUIDES
2580   ============================================ */
2581.tools-grid {
2582  display: flex;
2583  flex-direction: column;
2584  gap: 16px;
2585}
2586.tool-card {
2587  background: white;
2588  border-radius: 14px;
2589  border: 1px solid rgba(197, 165, 90, 0.2);
2590  overflow: hidden;
2591  cursor: pointer;
2592  transition: border-color 0.3s, box-shadow 0.3s;
2593}
2594.tool-card:hover {
2595  border-color: var(--gold-light);
2596  box-shadow: 0 2px 16px rgba(197, 165, 90, 0.1);
2597}
2598.tool-card-header {
2599  display: flex;
2600  align-items: center;
2601  justify-content: space-between;
2602  padding: 20px 24px;
2603}
2604.tool-card-header-left {
2605  display: flex;
2606  align-items: center;
2607  gap: 14px;
2608}
2609.tool-card-icon {
2610  font-size: 24px;
2611  color: var(--gold);
2612}
2613.tool-card-title {
2614  font-family: 'Cormorant Garamond', serif;
2615  font-size: 20px;
2616  color: var(--plum-deep);
2617  font-weight: 400;
2618}
2619.tool-card-subtitle {
2620  font-family: 'Lora', serif;
2621  font-size: 11px;
2622  color: var(--charcoal);
2623  opacity: 0.5;
2624  margin-top: 2px;
2625}
2626.tool-card-arrow {
2627  font-size: 16px;
2628  color: var(--gold-light);
2629  transition: transform 0.3s;
2630}
2631.tool-card.open .tool-card-arrow {
2632  transform: rotate(90deg);
2633}
2634.tool-card-content {
2635  display: none;
2636  padding: 0 24px 24px;
2637  border-top: 1px solid rgba(197, 165, 90, 0.1);
2638}
2639.tool-card.open .tool-card-content {
2640  display: block;
2641}
2642.guide-section {
2643  margin-top: 18px;
2644}
2645.guide-section-title {
2646  font-family: 'Marcellus', serif;
2647  font-size: 10px;
2648  letter-spacing: 2px;
2649  text-transform: uppercase;
2650  color: var(--berry);
2651  margin-bottom: 10px;
2652}
2653.guide-step {
2654  display: flex;
2655  gap: 12px;
2656  margin-bottom: 14px;
2657}
2658.guide-step-num {
2659  flex-shrink: 0;
2660  width: 26px;
2661  height: 26px;
2662  border-radius: 50%;
2663  background: var(--gold);
2664  color: white;
2665  font-family: 'Marcellus', serif;
2666  font-size: 12px;
2667  display: flex;
2668  align-items: center;
2669  justify-content: center;
2670  margin-top: 2px;
2671}
2672.guide-step-text {
2673  font-family: 'Lora', serif;
2674  font-size: 13px;
2675  line-height: 1.65;
2676  color: var(--charcoal);
2677}
2678.guide-step-text strong {
2679  color: var(--plum-deep);
2680}
2681.guide-note {
2682  margin-top: 14px;
2683  padding: 14px 16px;
2684  background: var(--cream-warm);
2685  border-radius: 8px;
2686  border-left: 3px solid var(--gold);
2687  font-family: 'Lora', serif;
2688  font-size: 12px;
2689  line-height: 1.6;
2690  color: var(--charcoal);
2691  opacity: 0.85;
2692}
2693.guide-note strong {
2694  color: var(--plum-deep);
2695}
2696.guide-divider {
2697  height: 1px;
2698  background: rgba(197, 165, 90, 0.15);
2699  margin: 20px 0;
2700}
2701
2702/* ============================================
2703   LOADING
2704   ============================================ */
2705.loading {
2706  display: flex;
2707  align-items: center;
2708  justify-content: center;
2709  padding: 60px 20px;
2710  gap: 8px;
2711}
2712
2713.loading-dot {
2714  width: 8px;
2715  height: 8px;
2716  background: var(--gold-light);
2717  border-radius: 50%;
2718  animation: loadingPulse 1.2s infinite;
2719}
2720
2721.loading-dot:nth-child(2) { animation-delay: 0.2s; }
2722.loading-dot:nth-child(3) { animation-delay: 0.4s; }
2723
2724@keyframes loadingPulse {
2725  0%, 100% { opacity: 0.3; transform: scale(0.8); }
2726  50% { opacity: 1; transform: scale(1); }
2727}
2728
2729/* ============================================
2730   RESPONSIVE
2731   ============================================ */
2732@media (max-width: 768px) {
2733  .sidebar {
2734    transform: translateX(-100%);
2735  }
2736
2737  .sidebar.open {
2738    transform: translateX(0);
2739  }
2740
2741  .hamburger {
2742    display: flex;
2743  }
2744
2745  .sidebar-overlay.show {
2746    display: block;
2747  }
2748
2749  .content-area {
2750    margin-left: 0;
2751  }
2752
2753  .content-header {
2754    padding-left: 70px;
2755  }
2756
2757  .password-card,
2758  .auth-card { padding: 40px 30px 36px; }
2759
2760  .content-header-title { font-size: 24px; }
2761
2762  /* More legible sidebar nav on mobile */
2763  .sidebar-link {
2764    font-size: 11px;
2765    letter-spacing: 1.5px;
2766    padding: 14px 22px;
2767  }
2768
2769  /* Prevent iOS Safari from auto-zooming when focusing form fields.
2770     iOS only zooms when the input font-size is < 16px. */
2771  input[type="text"],
2772  input[type="email"],
2773  input[type="password"],
2774  input[type="search"],
2775  input[type="number"],
2776  input[type="tel"],
2777  input[type="url"],
2778  textarea,
2779  select {
2780    font-size: 16px !important;
2781  }
2782}
2783
2784@media (max-width: 480px) {
2785  .post-card { padding: 24px 20px 18px; }
2786  .composer { padding: 20px; }
2787  .post-meta { flex-direction: column; gap: 10px; align-items: flex-start; }
2788}
2789
2790/* Print Journal Button */
2791.journal-print-btn {
2792  display: inline-flex;
2793  align-items: center;
2794  gap: 6px;
2795  padding: 8px 20px;
2796  background: none;
2797  border: 1.5px solid var(--gold);
2798  border-radius: 8px;
2799  color: var(--gold);
2800  font-family: 'Marcellus', serif;
2801  font-size: 9px;
2802  letter-spacing: 2px;
2803  text-transform: uppercase;
2804  cursor: pointer;
2805  transition: all 0.3s;
2806  margin-bottom: 20px;
2807}
2808.journal-print-btn:hover {
2809  background: var(--gold);
2810  color: white;
2811}
2812.journal-print-btn .print-icon {
2813  font-size: 14px;
2814}
2815
2816/* ============================================
2817   PRINT STYLES — BRANDED JOURNAL
2818   ============================================ */
2819@media print {
2820  /* Hide everything except journal content */
2821  body * { visibility: hidden; }
2822  #journal-print-area, #journal-print-area * { visibility: visible; }
2823  #journal-print-area {
2824    position: absolute;
2825    top: 0;
2826    left: 0;
2827    width: 100%;
2828    padding: 0;
2829    margin: 0;
2830  }
2831
2832  .journal-print-header {
2833    text-align: center;
2834    padding: 40px 0 30px;
2835    border-bottom: 2px solid #C5A55A;
2836    margin-bottom: 30px;
2837  }
2838  .journal-print-logo {
2839    width: 70px;
2840    height: auto;
2841    margin-bottom: 12px;
2842  }
2843  .journal-print-brand {
2844    font-family: 'Marcellus', serif;
2845    font-size: 9px;
2846    letter-spacing: 4px;
2847    text-transform: uppercase;
2848    color: #940E4B;
2849    margin-bottom: 4px;
2850  }
2851  .journal-print-title {
2852    font-family: 'Cormorant Garamond', serif;
2853    font-size: 28px;
2854    font-weight: 400;
2855    color: #3A3A3A;
2856    margin: 4px 0 0;
2857  }
2858  .journal-print-name {
2859    font-family: 'Cormorant Garamond', serif;
2860    font-size: 16px;
2861    font-style: italic;
2862    color: #C5A55A;
2863    margin-top: 8px;
2864  }
2865  .journal-print-entry {
2866    padding: 22px 0;
2867    border-bottom: 1px solid rgba(197, 165, 90, 0.3);
2868    page-break-inside: avoid;
2869  }
2870  .journal-print-entry:last-child {
2871    border-bottom: none;
2872  }
2873  .journal-print-date {
2874    font-family: 'Marcellus', serif;
2875    font-size: 8px;
2876    letter-spacing: 2.5px;
2877    text-transform: uppercase;
2878    color: #C5A55A;
2879    margin-bottom: 8px;
2880  }
2881  .journal-print-body {
2882    font-family: 'Lora', serif;
2883    font-size: 12px;
2884    line-height: 1.85;
2885    color: #3A3A3A;
2886    white-space: pre-wrap;
2887  }
2888  .journal-print-footer {
2889    text-align: center;
2890    margin-top: 40px;
2891    padding-top: 20px;
2892    border-top: 2px solid #C5A55A;
2893  }
2894  .journal-print-footer-text {
2895    font-family: 'Cormorant Garamond', serif;
2896    font-size: 11px;
2897    font-style: italic;
2898    color: #C5A55A;
2899    letter-spacing: 1px;
2900  }
2901  .journal-print-diamond {
2902    color: #C5A55A;
2903    font-size: 14px;
2904    margin-bottom: 8px;
2905  }
2906}
2907
2908/* ============================================
2909   HOME / WELCOME VIEW
2910   ============================================ */
2911.home-view {
2912  display: flex;
2913  flex-direction: column;
2914  align-items: center;
2915  justify-content: center;
2916  min-height: calc(100vh - 120px);
2917  padding: 40px 30px;
2918  text-align: center;
2919}
2920.home-logo {
2921  width: 110px;
2922  height: 110px;
2923  margin-bottom: 28px;
2924  opacity: 0.9;
2925  animation: homeLogoFade 1.5s ease-in-out;
2926}
2927
2928/* ============================================
2929   ONBOARDING POPUP — first-login nudge
2930   ============================================ */
2931.onboarding-overlay {
2932  position: fixed;
2933  inset: 0;
2934  z-index: 300;
2935  pointer-events: none;
2936  opacity: 0;
2937  animation: onboardFadeIn 1.5s ease 6s forwards;
2938}
2939
2940@keyframes onboardFadeIn {
2941  to { opacity: 1; }
2942}
2943
2944.onboarding-popup {
2945  position: fixed;
2946  top: 50%;
2947  left: calc(var(--sidebar-width) + 60px);
2948  transform: translateY(-50%);
2949  background: white;
2950  border: 1px solid rgba(197, 165, 90, 0.3);
2951  border-radius: 16px;
2952  padding: 32px 36px;
2953  box-shadow: 0 8px 40px rgba(139, 34, 82, 0.12);
2954  z-index: 301;
2955  max-width: 340px;
2956  text-align: center;
2957  pointer-events: auto;
2958}
2959
2960.onboarding-popup-title {
2961  font-family: 'Cormorant Garamond', serif;
2962  font-size: 22px;
2963  color: var(--berry);
2964  font-weight: 600;
2965  margin-bottom: 10px;
2966  line-height: 1.3;
2967}
2968
2969.onboarding-popup-text {
2970  font-family: 'Lora', serif;
2971  font-size: 13px;
2972  line-height: 1.7;
2973  color: var(--charcoal);
2974  opacity: 0.85;
2975}
2976
2977/* Animated arrow pointing left toward the sidebar */
2978.onboarding-arrow {
2979  position: fixed;
2980  top: 50%;
2981  left: calc(var(--sidebar-width) + 24px);
2982  transform: translateY(-50%);
2983  z-index: 302;
2984  pointer-events: none;
2985  animation: arrowBounce 1.2s ease-in-out infinite;
2986}
2987
2988.onboarding-arrow svg {
2989  width: 28px;
2990  height: 28px;
2991  fill: none;
2992  stroke: var(--gold);
2993  stroke-width: 2.5;
2994  stroke-linecap: round;
2995  stroke-linejoin: round;
2996  filter: drop-shadow(0 0 6px rgba(197, 165, 90, 0.5));
2997}
2998
2999@keyframes arrowBounce {
3000  0%, 100% { transform: translateY(-50%) translateX(0); }
3001  50% { transform: translateY(-50%) translateX(-12px); }
3002}
3003
3004/* Fade out */
3005.onboarding-overlay.fade-out {
3006  transition: opacity 1.2s ease;
3007  opacity: 0 !important;
3008}
3009
3010@media (max-width: 768px) {
3011  .onboarding-popup {
3012    left: 50%;
3013    transform: translate(-50%, -50%);
3014    top: 40%;
3015    max-width: 280px;
3016    padding: 24px 28px;
3017  }
3018  .onboarding-arrow {
3019    display: none;
3020  }
3021}
3022
3023/* Gold sparkles that pour outward when entering the hub
3024   Built as inline SVGs for soft glow + twinkle */
3025.welcome-star {
3026  position: fixed;
3027  pointer-events: none;
3028  will-change: transform, opacity;
3029  z-index: 50; /* in front of tiles by default */
3030  user-select: none;
3031  display: flex;
3032  align-items: center;
3033  justify-content: center;
3034}
3035/* "Behind" sparkles drift below the tiles for parallax depth */
3036.welcome-star.behind {
3037  z-index: 1;
3038  filter: blur(0.6px); /* slightly soft, like distant points of light */
3039}
3040.welcome-star.behind svg {
3041  filter: drop-shadow(0 0 6px rgba(197, 165, 90, 0.7))
3042          drop-shadow(0 0 14px rgba(212, 186, 122, 0.45));
3043  opacity: 0.85;
3044}
3045.welcome-star svg {
3046  width: 100%;
3047  height: 100%;
3048  filter: drop-shadow(0 0 4px rgba(197, 165, 90, 0.8))
3049          drop-shadow(0 0 10px rgba(212, 186, 122, 0.5));
3050  animation: twinkle 1.8s ease-in-out infinite;
3051}
3052@keyframes twinkle {
3053  0%, 100% { opacity: 1; transform: scale(1); }
3054  50% { opacity: 0.55; transform: scale(0.78); }
3055}
3056.home-greeting {
3057  font-family: 'Marcellus', serif;
3058  font-size: 12px;
3059  letter-spacing: 5px;
3060  text-transform: uppercase;
3061  color: var(--gold);
3062  margin-bottom: 10px;
3063  animation: homeTextFade 1.8s ease-in-out;
3064}
3065.home-name {
3066  font-family: 'Great Vibes', cursive;
3067  font-size: 52px;
3068  color: #A83060;
3069  margin-bottom: 28px;
3070  animation: homeTextFade 2.2s ease-in-out;
3071}
3072.home-divider {
3073  display: flex;
3074  align-items: center;
3075  gap: 16px;
3076  margin-bottom: 28px;
3077  animation: homeTextFade 2.5s ease-in-out;
3078}
3079.home-divider-line {
3080  width: 60px;
3081  height: 1px;
3082  background: var(--gold-light);
3083}
3084.home-divider-diamond {
3085  color: var(--gold);
3086  font-size: 10px;
3087}
3088.home-message {
3089  font-family: 'Cormorant Garamond', serif;
3090  font-style: italic;
3091  font-size: 20px;
3092  line-height: 1.7;
3093  color: var(--plum);
3094  max-width: 520px;
3095  margin-bottom: 40px;
3096  animation: homeTextFade 2.8s ease-in-out;
3097}
3098.home-rooms {
3099  display: grid;
3100  grid-template-columns: repeat(3, 1fr);
3101  gap: 14px;
3102  max-width: 540px;
3103  width: 100%;
3104  animation: homeTextFade 3.2s ease-in-out;
3105}
3106@media (max-width: 500px) {
3107  .home-rooms {
3108    grid-template-columns: repeat(2, 1fr);
3109  }
3110}
3111.home-room-card {
3112  background: white;
3113  border: 1px solid rgba(197, 165, 90, 0.18);
3114  border-radius: 12px;
3115  padding: 22px 16px;
3116  cursor: pointer;
3117  transition: all 0.3s;
3118  text-align: center;
3119  /* Sit above the "behind" sparkles but below the "front" ones for parallax depth */
3120  position: relative;
3121  z-index: 10;
3122}
3123.home-room-card:hover {
3124  border-color: var(--gold);
3125  box-shadow: 0 4px 20px rgba(197, 165, 90, 0.12);
3126  transform: translateY(-2px);
3127}
3128.home-room-icon {
3129  font-size: 22px;
3130  color: var(--gold);
3131  margin-bottom: 8px;
3132}
3133.home-room-title {
3134  font-family: 'Marcellus', serif;
3135  font-size: 11.5px;
3136  letter-spacing: 1.5px;
3137  text-transform: uppercase;
3138  color: var(--berry);
3139  font-weight: 600;
3140  margin-bottom: 6px;
3141}
3142.home-room-desc {
3143  font-family: 'Lora', serif;
3144  font-size: 12px;
3145  color: var(--plum);
3146  opacity: 0.65;
3147  line-height: 1.45;
3148  font-style: italic;
3149}
3150@keyframes homeLogoFade {
3151  0% { opacity: 0; transform: scale(0.9); }
3152  100% { opacity: 0.9; transform: scale(1); }
3153}
3154@keyframes homeTextFade {
3155  0%, 30% { opacity: 0; transform: translateY(8px); }
3156  100% { opacity: 1; transform: translateY(0); }
3157}
3158@media (max-width: 600px) {
3159  #members-stats { grid-template-columns: 1fr !important; }
3160  #library-analytics-stats { grid-template-columns: 1fr !important; }
3161}
3162</style>
3163</head>
3164<body>
3165
3166<!-- ============================================
3167     PASSWORD SCREEN
3168     ============================================ -->
3169<div id="password-screen">
3170  <a class='back-to-portal' href='/'>&larr; Back to Main Portal</a>
3171  <div class="password-card">
3172    <img src="feminine-rising-logo.png" alt="Feminine Rising" class="password-logo">
3173    <div class="password-title">
3173Feminine Rising</div>
3174    <h1 class="password-heading">Private Mentorship</h1>
3175    <div class="password-subtitle">Exclusive Client Resources</div>
3176
3177    <div class="password-divider"><span>&#9830;&#xFE0E;</span></div>
3178
3179    <p class="password-prompt">Please enter your mentorship access code</p>
3180    <input type="text" id="password-input" class="password-input" placeholder="Access Code" autofocus>
3181    <button onclick="checkPassword()" class="password-btn">Enter</button>
3182    <p id="password-error" class="password-error">The access code you entered is not recognized. Please try again.</p>
3183  </div>
3184  <a class='back-link' href='/'>&larr; Back to Main Portal</a>
3185</div>
3186
3187<!-- ============================================
3188     AUTH SCREEN (Email + Password)
3189     ============================================ -->
3190<div id="auth-screen">
3191  <a class='back-to-portal' href='/'>&larr; Back to Main Portal</a>
3192  <div class="auth-card">
3193    <div class="auth-icon">&#10022;</div>
3194    <h2 class="auth-title">Welcome, Sister</h2>
3195    <p class="auth-text" id="auth-subtitle">
3196      Create your account to enter the mentorship hub.
3197    </p>
3198    <input type="email" id="auth-email" class="auth-input" placeholder="[email protected]" autocomplete="email" autocapitalize="none" autocorrect="off" spellcheck="false">
3199    <div style="position:relative;margin-top:10px;">
3200      <input type="password" id="auth-password" class="auth-input" placeholder="Create a password" autocomplete="current-password" autocapitalize="none" autocorrect="off" spellcheck="false" style="padding-right:46px;">
3201      <button type="button" onclick="toggleAuthPasswordVisibility()" aria-label="Show or hide password" style="position:absolute;right:12px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;color:var(--gold);padding:4px 8px;line-height:1;">
3202        <span id="auth-eye-icon">&#128065;</span>
3203      </button>
3204    </div>
3205    <button id="auth-send-btn" onclick="authSubmit()" class="auth-btn">Create My Account</button>
3206    <p id="auth-message" class="auth-message"></p>
3207    <div style="margin-top:18px;">
3208      <button id="auth-toggle" onclick="toggleAuthMode()" style="background:none;border:none;color:var(--plum-light);font-family:'Cormorant Garamond',serif;font-size:14px;font-style:italic;cursor:pointer;letter-spacing:0.3px;opacity:0.85;transition:opacity 0.2s;" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='0.85'">Already have an account? Sign in</button>
3209    </div>
3210    <div id="forgot-row" style="margin-top:10px;display:none;">
3211      <button onclick="sendPasswordReset()" style="background:none;border:none;color:var(--gold);font-family:'Marcellus',serif;font-size:10px;letter-spacing:2px;cursor:pointer;text-transform:uppercase;opacity:0.8;transition:opacity 0.2s;" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='0.8'">Forgot password?</button>
3212    </div>
3213  </div>
3214</div>
3215
3216<!-- ============================================
3217     RESET PASSWORD SCREEN (after clicking email link)
3218     ============================================ -->
3219<div id="reset-screen" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;align-items:center;justify-content:center;background:var(--cream);z-index:50;">
3220  <div class="auth-card">
3221    <div class="auth-icon">&#10022;</div>
3222    <h2 class="auth-title">Set a New Password</h2>
3223    <p class="auth-text">Choose a new password for your account.</p>
3224    <div style="position:relative;">
3225      <input type="password" id="reset-password" class="auth-input" placeholder="New password (8+ characters)" autocomplete="new-password" autocapitalize="none" autocorrect="off" spellcheck="false" style="padding-right:46px;">
3226      <button type="button" onclick="toggleResetPasswordVisibility()" aria-label="Show or hide password" style="position:absolute;right:12px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;color:var(--gold);padding:4px 8px;line-height:1;">
3227        <span id="reset-eye-icon">&#128065;</span>
3228      </button>
3229    </div>
3230    <div style="position:relative;margin-top:10px;">
3231      <input type="password" id="reset-password-confirm" class="auth-input" placeholder="Confirm new password" autocomplete="new-password" autocapitalize="none" autocorrect="off" spellcheck="false" style="padding-right:46px;">
3232      <button type="button" onclick="toggleResetPasswordVisibility()" aria-label="Show or hide password" style="position:absolute;right:12px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;color:var(--gold);padding:4px 8px;line-height:1;">
3233        <span id="reset-eye-icon-2">&#128065;</span>
3234      </button>
3235    </div>
3236    <button id="reset-submit-btn" onclick="updatePasswordSubmit()" class="auth-btn" style="margin-top:14px;">Update Password</button>
3237    <p id="reset-message" class="auth-message"></p>
3238  </div>
3239</div>
3240
3241<!-- ============================================
3242     NAME ONBOARDING
3243     ============================================ -->
3244<div id="name-screen">
3245  <div class="auth-card">
3246    <div class="auth-icon">&#10022;</div>
3247    <h2 class="auth-title">One Last Thing</h2>
3248    <p class="auth-text">
3249      What name shall we call you in circle?
3250    </p>
3251    <input type="text" id="name-input" class="auth-input" placeholder="Your name" style="text-align:center;">
3252    <button onclick="saveName()" class="auth-btn">Enter the Circle</button>
3253  </div>
3254</div>
3255
3256<!-- ============================================
3257     THE HUB
3258     ============================================ -->
3259<div id="hub">
3260  <div class="hub-layout">
3261
3262    <!-- Mobile hamburger -->
3263    <button class="hamburger" onclick="toggleSidebar()">
3264      <span></span><span></span><span></span>
3265    </button>
3266    <div class="sidebar-overlay" onclick="toggleSidebar()"></div>
3267
3268    <!-- Sidebar -->
3269    <nav class="sidebar">
3270      <div class="sidebar-header">
3271        <img src="feminine-rising-logo.png" alt="Feminine Rising" class="sidebar-logo">
3272        <div class="sidebar-brand">
3272Feminine Rising</div>
3273        <div class="sidebar-title">Private Mentorship</div>
3274      </div>
3275
3276      <div class="sidebar-nav">
3277        <a class="sidebar-link active" data-view="home" onclick="switchView('home')">
3278          <span class="sidebar-link-icon">&#10047;</span> Home
3279        </a>
3280        <a class="sidebar-link" data-view="about" onclick="switchView('about')">
3281          <span class="sidebar-link-icon" style="line-height:1;display:inline-flex;align-items:center;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5c-1.5-1.5-4-2.5-7-2.5v15c3 0 5.5 1 7 2.5"/><path d="M12 5c1.5-1.5 4-2.5 7-2.5v15c-3 0-5.5 1-7 2.5"/><path d="M12 5v15"/></svg></span> About
3282        </a>
3283        <a class="sidebar-link" data-view="feed" onclick="switchView('feed')">
3284          <span class="sidebar-link-icon" style="line-height:1;display:inline-flex;align-items:center;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12c-2-2.5-4-4-6-4c-2.2 0-4 1.8-4 4s1.8 4 4 4c2 0 4-1.5 6-4z"/><path d="M12 12c2 2.5 4 4 6 4c2.2 0 4-1.8 4-4s-1.8-4-4-4c-2 0-4 1.5-6 4z"/></svg></span> Community
3285        </a>
3286        <a class="sidebar-link" data-view="teachings" onclick="switchView('teachings')">
3287          <span class="sidebar-link-icon">&#10022;</span> Teachings
3288        </a>
3289        <a class="sidebar-link" data-view="replays" onclick="switchView('replays')">
3290          <span class="sidebar-link-icon">&#9654;</span> Replays
3291        </a>
3292        <a class="sidebar-link" data-view="journal" onclick="switchView('journal')">
3293          <span class="sidebar-link-icon">&#9998;</span> Journal
3294        </a>
3295        <a class="sidebar-link" data-view="harvest" onclick="switchView('harvest')">
3296          <span class="sidebar-link-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2c-3 4-6 7-6 12a6 6 0 0 0 12 0c0-5-3-8-6-12z"/><path d="M12 14v6"/><path d="M9 18c1.5-1 4.5-1 6 0"/></svg></span> Monthly Harvest
3297        </a>
3298        <a class="sidebar-link" data-view="schedule" onclick="switchView('schedule')">
3299          <span class="sidebar-link-icon">&#9783;</span> Schedule
3300        </a>
3301        <a class="sidebar-link" data-view="tools" onclick="switchView('tools')">
3302          <span class="sidebar-link-icon">&#9881;</span> Tools
3303        </a>
3304        <a class="sidebar-link" data-view="members" onclick="switchView('members')" id="sidebar-members-link" style="display:none;">
3305          <span class="sidebar-link-icon">&#9733;</span> Members
3306        </a>
3307        <a class='sidebar-link' href='/crm' id='sidebar-crm-link' style='display:none;'>
3308          <span class="sidebar-link-icon">&#9673;</span> CRM
3309        </a>
3310      </div>
3311
3312      <div class="sidebar-footer">
3313        <div class="sidebar-user" id="sidebar-user-name">
3314          <span id="sidebar-user-name-text">Sister</span>
3315          <span id="sidebar-presence-dot" class="sidebar-presence-dot" style="display:none;" title="Sumir is here"></span>
3316        </div>
3317        <!-- Teacher-only presence toggle -->
3318        <button id="sidebar-presence-toggle" onclick="togglePresence()" class="sidebar-presence-toggle" style="display:none;">
3319          <span id="sidebar-presence-label">&#9825; Mark as Here</span>
3320        </button>
3321        <button class="sidebar-signout" onclick="signOut()">Sign Out</button>
3322        <a class='sidebar-back' href='/'>&larr; Back to Main Portal</a>
3323      </div>
3324    </nav>
3325
3326    <!-- Content area -->
3327    <main class="content-area">
3328
3329      <!-- Dynamic header -->
3330      <header class="content-header">
3331        <div class="content-header-eyebrow" id="header-eyebrow">Private Mentorship</div>
3332        <h1 class="content-header-title" id="header-title">Community</h1>
3333      </header>
3334
3335      <!-- ===== HOME VIEW ===== -->
3336      <div class="view" id="view-home">
3337        <div class="home-view">
3338          <!-- "Sumir is in the room" banner -->
3339          <div id="presence-banner" class="presence-banner" style="display:none;">
3340            <span class="presence-banner-dot"></span>
3341            <span class="presence-banner-text">Sumir is in the room</span>
3342          </div>
3343          <img src="feminine-rising-logo.png" alt="Feminine Rising" class="home-logo" id="home-logo">
3344          <div class="home-greeting" id="home-greeting">Welcome</div>
3345          <div class="home-name" id="home-user-name">Sister</div>
3346          <div class="home-divider">
3347            <div class="home-divider-line"></div>
3348            <div class="home-divider-diamond">&#10022;</div>
3349            <div class="home-divider-line"></div>
3350          </div>
3351          <div class="home-rooms">
3352            <div class="home-room-card" onclick="switchView('feed')">
3353              <div class="home-room-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12c-2-2.5-4-4-6-4c-2.2 0-4 1.8-4 4s1.8 4 4 4c2 0 4-1.5 6-4z"/><path d="M12 12c2 2.5 4 4 6 4c2.2 0 4-1.8 4-4s-1.8-4-4-4c-2 0-4 1.5-6 4z"/></svg></div>
3354              <div class="home-room-title">Community</div>
3355              <div class="home-room-desc">Our shared space</div>
3356            </div>
3357            <div class="home-room-card" onclick="switchView('teachings')">
3358              <div class="home-room-icon">&#10022;</div>
3359              <div class="home-room-title">Teachings</div>
3360              <div class="home-room-desc">Fertile Ground</div>
3361            </div>
3362            <div class="home-room-card" onclick="switchView('replays')">
3363              <div class="home-room-icon">&#9654;</div>
3364              <div class="home-room-title">Replays</div>
3365              <div class="home-room-desc">Session recordings</div>
3366            </div>
3367            <div class="home-room-card" onclick="switchView('harvest')">
3368              <div class="home-room-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2c-3 4-6 7-6 12a6 6 0 0 0 12 0c0-5-3-8-6-12z"/><path d="M12 14v6"/><path d="M9 18c1.5-1 4.5-1 6 0"/></svg></div>
3369              <div class="home-room-title">Monthly Harvest</div>
3370              <div class="home-room-desc">Reflect &amp; gather</div>
3371            </div>
3372            <div class="home-room-card" onclick="switchView('journal')">
3373              <div class="home-room-icon">&#9998;</div>
3374              <div class="home-room-title">Journal</div>
3375              <div class="home-room-desc">Your sacred pages</div>
3376            </div>
3377            <div class="home-room-card" onclick="switchView('schedule')">
3378              <div class="home-room-icon">&#9783;</div>
3379              <div class="home-room-title">Schedule</div>
3380              <div class="home-room-desc">Upcoming sessions</div>
3381            </div>
3382          </div>
3383        </div>
3384      </div>
3385
3386      <!-- ===== COMMUNITY FEED VIEW ===== -->
3387      <div class="view active" id="view-feed">
3388        <div class="content-body">
3389
3390          <!-- Composer (teacher only) -->
3391          <div class="composer" id="post-composer" style="display:none;">
3392            <div class="composer-trigger" onclick="toggleComposer()">
3393              <div class="composer-trigger-avatar" id="composer-avatar">S</div>
3394              <span class="composer-trigger-text">Share something with the community...</span>
3395            </div>
3396            <div class="composer-expanded" id="composer-expanded">
3397              <div style="display:flex;justify-content:space-between;align-items:center;">
3398                <div class="composer-label" style="padding-top:18px;">Share With Us</div>
3399                <button onclick="closeComposer()" style="background:none;border:none;font-size:20px;color:var(--plum-light);cursor:pointer;padding:12px 4px 0;opacity:0.5;transition:opacity 0.2s;" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='0.5'">&times;</button>
3400              </div>
3401              <input type="text" class="composer-title-input" id="compose-title" placeholder="Title (optional)">
3402              <textarea class="composer-textarea" id="compose-body" placeholder="What would you like to share..."></textarea>
3403              <div id="compose-media-preview" class="media-preview" style="display:none;"></div>
3404              <input type="file" id="compose-media-input" accept="image/*,video/*" style="display:none;" onchange="handleMediaSelect(this)">
3405              <div class="composer-footer">
3406                <div style="display:flex;align-items:center;gap:8px;">
3407                  <select class="composer-type" id="compose-type">
3408                    <option value="teaching" class="teacher-only-option">Teaching</option>
3409                    <option value="announcement" class="teacher-only-option">Announcement</option>
3410                    <option value="reflection">Reflection</option>
3411                    <option value="prompt">Prompt</option>
3412                  </select>
3413                  <button class="media-upload-btn" onclick="document.getElementById('compose-media-input').click()">
3414                    <span class="upload-icon">&#128247;</span> Photo / Video
3415                  </button>
3416                  <button class="media-upload-btn" onclick="openVoiceRecorder()" title="Record a voice note (5 min max)">
3417                    <span class="upload-icon">&#127908;</span> Voice Note
3418                  </button>
3419                  <div style="position:relative;display:inline-block;">
3420                    <button class="media-upload-btn" onclick="toggleEmojiPicker('compose-body')">
3421                      <span class="upload-icon">&#128522;</span> Emoji
3422                    </button>
3423                    <div class="emoji-picker" id="emoji-picker-compose-body" style="display:none;"></div>
3424                  </div>
3425                </div>
3426                <button class="composer-submit" id="compose-submit" onclick="createPost()">Share</button>
3427              </div>
3428              <!-- Voice note preview (shown after recording) -->
3429              <div id="voice-note-preview" style="display:none;margin-top:10px;padding:14px 16px;background:var(--cream-warm);border:1px solid var(--gold);border-radius:10px;">
3430                <div style="display:flex;align-items:center;gap:10px;">
3431                  <span style="font-size:20px;">&#127908;</span>
3432                  <audio id="voice-note-audio" controls style="flex:1;height:36px;"></audio>
3433                  <button onclick="clearVoiceNote()" title="Remove" style="background:none;border:none;color:var(--berry);font-size:18px;cursor:pointer;padding:4px 8px;">&times;</button>
3434                </div>
3435                <div id="voice-note-duration" style="font-family:'Marcellus',serif;font-size:10px;letter-spacing:1.5px;color:var(--gold);margin-top:6px;"></div>
3436              </div>
3437            </div>
3438          </div>
3439
3440          <!-- Voice Recorder Modal -->
3441          <div id="voice-recorder-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.55);z-index:2000;align-items:center;justify-content:center;padding:20px;">
3442            <div style="background:white;border-radius:18px;padding:40px 36px;max-width:420px;width:100%;text-align:center;box-shadow:0 12px 40px rgba(0,0,0,0.2);">
3443              <div style="font-family:'Marcellus',serif;font-size:11px;letter-spacing:3px;color:var(--gold);text-transform:uppercase;margin-bottom:8px;">Voice Note</div>
3444              <h3 style="font-family:'Cormorant Garamond',serif;font-size:24px;color:var(--plum-deep);font-weight:400;margin-bottom:4px;" id="vr-title">Speak from the heart</h3>
3445              <p id="vr-subtitle" style="font-family:'Cormorant Garamond',serif;font-style:italic;font-size:14px;color:var(--plum);margin-bottom:24px;">Tap the microphone to begin &mdash; up to 5 minutes</p>
3446
3447              <button id="vr-mic-btn" onclick="toggleRecording()" aria-label="Start or stop recording" style="width:110px;height:110px;border-radius:50%;background:var(--berry);color:var(--gold-light);border:3px solid var(--gold);font-size:44px;cursor:pointer;transition:all 0.3s;display:inline-flex;align-items:center;justify-content:center;">
3448                <span id="vr-mic-icon">&#127908;</span>
3449              </button>
3450
3451              <div id="vr-timer" style="font-family:'Marcellus',serif;font-size:18px;letter-spacing:2px;color:var(--berry);margin-top:22px;">0:00</div>
3452              <div id="vr-hint" style="font-family:'Cormorant Garamond',serif;font-style:italic;font-size:13px;color:var(--plum);margin-top:8px;opacity:0.8;">5 minutes maximum</div>
3453
3454              <!-- Preview section (hidden during recording) -->
3455              <div id="vr-preview" style="display:none;margin-top:22px;">
3456                <audio id="vr-playback" controls style="width:100%;height:40px;"></audio>
3457              </div>
3458
3459              <div style="margin-top:28px;display:flex;gap:10px;justify-content:center;flex-wrap:wrap;">
3460                <button id="vr-cancel" onclick="closeVoiceRecorder()" style="padding:10px 22px;background:white;color:var(--plum);border:1px solid var(--gold);border-radius:22px;font-family:'Marcellus',serif;font-size:10px;letter-spacing:2px;text-transform:uppercase;cursor:pointer;">Cancel</button>
3461                <button id="vr-rerecord" onclick="resetVoiceRecorder()" style="display:none;padding:10px 22px;background:white;color:var(--plum);border:1px solid var(--gold);border-radius:22px;font-family:'Marcellus',serif;font-size:10px;letter-spacing:2px;text-transform:uppercase;cursor:pointer;">Re-record</button>
3462                <button id="vr-use" onclick="attachVoiceNote()" style="display:none;padding:10px 22px;background:var(--berry);color:white;border:none;border-radius:22px;font-family:'Marcellus',serif;font-size:10px;letter-spacing:2px;text-transform:uppercase;cursor:pointer;">Use This Recording</button>
3463              </div>
3464            </div>
3465          </div>
3466
3467          <!-- Filter & Search Toolbar -->
3468          <div class="feed-toolbar">
3469            <div class="feed-filters">
3470              <button class="feed-filter-btn active" data-filter="all" onclick="filterFeed('all')">All</button>
3471              <button class="feed-filter-btn" data-filter="announcement" onclick="filterFeed('announcement')">Announcements</button>
3472              <button class="feed-filter-btn" data-filter="teaching" onclick="filterFeed('teaching')">Teachings</button>
3473              <button class="feed-filter-btn" data-filter="prompt" onclick="filterFeed('prompt')">Prompts</button>
3474              <button class="feed-filter-btn" data-filter="reflection" onclick="filterFeed('reflection')">Reflections</button>
3475              <button class="feed-filter-btn" data-filter="hidden" onclick="filterFeed('hidden')" id="feed-hidden-btn" style="display:none;border-style:dashed;">Hidden</button>
3476            </div>
3477            <div class="feed-search">
3478              <span class="feed-search-icon">&#9906;</span>
3479              <input type="text" class="feed-search-input" id="feed-search-input" placeholder="Search posts..." oninput="searchFeed(this.value)">
3480              <button class="feed-search-clear" id="feed-search-clear" onclick="clearSearch()">&times;</button>
3481            </div>
3482          </div>
3483
3484          <!-- One-time legacy audio converter (teacher only, auto-hidden) -->
3485          <div id="legacy-audio-banner" style="display:none;margin:0 0 18px;padding:16px 18px;background:linear-gradient(135deg,var(--cream-warm) 0%,rgba(197,165,90,0.12) 100%);border:1px solid rgba(197,165,90,0.35);border-left:4px solid var(--berry);border-radius:12px;">
3486            <div style="font-family:'Marcellus',serif;font-size:10px;letter-spacing:2px;text-transform:uppercase;color:var(--berry);margin-bottom:6px;">Voice Note Upgrade</div>
3487            <div id="legacy-audio-text" style="font-family:'Lora',serif;font-size:13.5px;color:var(--charcoal);line-height:1.6;margin-bottom:12px;">Some older voice notes use a format that doesn&rsquo;t play on iPhones. Convert them to MP3 so every sister can listen on any device.</div>
3488            <button id="legacy-audio-btn" onclick="convertLegacyVoiceNotes()" style="font-family:'Marcellus',serif;font-size:11px;letter-spacing:2px;text-transform:uppercase;color:white;background:var(--berry);border:none;padding:10px 22px;border-radius:8px;cursor:pointer;">&#10022; Convert Legacy Voice Notes</button>
3489          </div>
3490
3491          <!-- Feed -->
3492          <div id="feed-container">
3493            <div class="loading">
3494              <div class="loading-dot"></div>
3495              <div class="loading-dot"></div>
3496              <div class="loading-dot"></div>
3497            </div>
3498          </div>
3499
3500        </div>
3501      </div>
3502
3503      <!-- ===== ABOUT VIEW ===== -->
3504      <div class="view" id="view-about">
3505        <div class="content-body">
3506          <p class="about-welcome">
3507            <em>May we release the endless pursuit and return to what is already luminous and available to the welcoming heart.</em>
3508          </p>
3509
3510          <div class="about-divider"><span>&#9830;&#xFE0E;</span></div>
3511
3512          <div class="pillar-card">
3513            <div class="pillar-number">Pillar One</div>
3514            <div class="pillar-title">Embodied Devotion &amp; Presence</div>
3515            <p class="pillar-body">Slowing down as a primary orientation. Cultivating presence through the body rather than the mind. Softening control and over-efforting. Practicing surrender without collapse. Returning to what is actually happening now &mdash; and trusting that it is enough. Devotion, beauty, and sensuality as ways of perceiving and inhabiting life.</p>
3516            <div class="pillar-movement">Core movement: From thinking &mdash; into sensing, feeling, and allowing.</div>
3517          </div>
3518
3519          <div class="pillar-card">
3520            <div class="pillar-number">Pillar Two</div>
3521            <div class="pillar-title">The Sovereign Body &mdash; Let Us Nourish &amp; Support Her</div>
3522            <p class="pillar-body">The body is the foundation of everything we build here. Before we can lead, sense, or transmit &mdash; we must inhabit. We will approach the body from every dimension: emotional awareness, energetic attunement, spiritual practice, <em>and</em> the practical, tangible steps that most programs overlook &mdash; bloodwork, nutrition, sleep architecture, menstrual cycle awareness, hormonal stages, movement as practice, and the daily rhythms that keep us rooted, vital, and truly alive.</p>
3523            <div class="pillar-movement">Core foundation: The body as home, instrument, and teacher &mdash; honored in full.</div>
3524          </div>
3525
3526          <div class="pillar-card">
3527            <div class="pillar-number">Pillar Three</div>
3528            <div class="pillar-title">Internal Leadership &amp; Innate Integrity</div>
3529            <p class="pillar-body">The central thread that runs through everything. Feeling, knowing, and acting in alignment. Trusting perception and inner authority. Speaking clearly, directly, and in the moment. Leading with grace, devotion, and precision. Confidence in choosing direction. Stability in leadership while being witnessed.</p>
3530            <div class="pillar-movement">This is the thread that weaves through it all. We will honor it always.</div>
3531          </div>
3532
3533          <div class="pillar-card">
3534            <div class="pillar-number">Pillar Four</div>
3535            <div class="pillar-title">Facilitation Embodiment &amp; Attunement</div>
3536            <p class="pillar-body">Sensing subtle energy in self and others. Reading what is present beneath words. Tracking shifts, openings, and contractions in real time &mdash; and developing the precision to name what is actually happening. Knowing when to intervene and when to allow. Facilitating without over-controlling or disappearing.</p>
3537            <div class="pillar-movement">Core practice: Perceive it. Trust it. Name it. Lead from there.</div>
3538          </div>
3539
3540          <div class="pillar-card">
3541            <div class="pillar-number">Pillar Five</div>
3542            <div class="pillar-title">Power, Expression &amp; Sacred Energy</div>
3543            <p class="pillar-body">Connecting with sensual and sexual life force as creative energy &mdash; and learning to wield it with integrity. Expanding the range of expression: playfulness, sensuality, raw primal energy, direct transmission. Opening the vocal channel. Feeling safe being seen in full power.</p>
3544            <div class="pillar-movement">Core invitation: Claim the full spectrum of who you are.</div>
3545          </div>
3546
3547          <div class="pillar-card">
3548            <div class="pillar-number">Pillar Six</div>
3549            <div class="pillar-title">Consciousness Expansion &amp; The Unknown</div>
3550            <p class="pillar-body">Accessing what is not yet known through embodied awareness. Expanding beyond current identity and limitation. Developing trust in subtle perception, inner knowing, and non-linear intelligence. Staying present when clarity is not immediate &mdash; not rushing to resolve, fix, or define.</p>
3551            <div class="pillar-movement">Core capacity: Deep inner listening. Trust in emergence.</div>
3552          </div>
3553
3554          <div class="about-divider"><span>&#9830;&#xFE0E;</span></div>
3555
3556          <div class="structure-card">
3557            <div class="structure-heading" style="color:var(--berry);font-size:12px;font-weight:600;letter-spacing:5px;">June 2026 &ndash; January 2027</div>
3558            <div class="structure-title">The Container</div>
3559            <p style="margin-bottom:20px;font-family:'Cormorant Garamond',serif;font-style:italic;font-size:16px;line-height:1.7;text-align:center;color:var(--plum-deep);font-weight:600;">An 8-month journey<br>Held with intentional spaciousness &mdash; honoring the principle that slow is the new way.</p>
3560
3561            <div class="about-divider"><span>&#9830;&#xFE0E;</span></div>
3562
3563            <div class="structure-item">
3564              <div class="structure-icon">&#9830;&#xFE0E;</div>
3565              <div>
3566                <div class="structure-item-title">Group Immersions</div>
3567                <p class="structure-item-text" style="margin-bottom:2px;"><strong>3 Calls per Month &middot; 3 Hours Each</strong></p>
3568                <p class="structure-item-text" style="margin-bottom:2px;">Teaching Call &times; 1</p>
3569                <p class="structure-item-text" style="margin-bottom:8px;">Practice Calls &times; 2</p>
3570                <p class="structure-item-text">Where your personal evolution and professional self deepen side by side&mdash;informing and shaping one another within a powerful and creative field of other women doing the same.</p>
3571              </div>
3572            </div>
3573
3574            <div class="about-divider"><span>&#9830;&#xFE0E;</span></div>
3575
3576            <div class="structure-item">
3577              <div class="structure-icon">&#9830;&#xFE0E;</div>
3578              <div>
3579                <div class="structure-item-title">Private Mentorship</div>
3580                <p class="structure-item-text" style="margin-bottom:2px;"><strong>8 Private Sessions &times; 1 Hour Each</strong></p>
3581                <p class="structure-item-text" style="margin-bottom:8px;">Begins in July through the end of February.</p>
3582                <p class="structure-item-text" style="margin-bottom:8px;">You may use these sessions at any cadence that suits you within the 8-month container. This is your dedicated space to go deeper, receive guidance, and be met in direct one-on-one support with Sumir.</p>
3583                <p class="structure-item-text" style="font-size:11px;opacity:0.75;font-style:italic;">Please note that if you miss your session without previously rescheduling or are more than 10 minutes late without notifying Sumir at least 1 hour prior, the session is considered forfeited. Sumir has a full schedule as well, and her time is sacred, as is yours.</p>
3584              </div>
3585            </div>
3586
3587            <div class="about-divider"><span>&#9830;&#xFE0E;</span></div>
3588
3589            <div class="structure-item">
3590              <div class="structure-icon">&#9830;&#xFE0E;</div>
3591              <div>
3592                <div class="structure-item-title">Monthly Reflection &amp; Harvest</div>
3593                <p class="structure-item-text">At the end of each month, we will reflect, share, and harvest the abundance of what has been lived, given, and received. Upleveling our experience&mdash;and one another&mdash;through our shared experiences, personal transmissions, and wisdom.</p>              </div>
3594            </div>
3595
3596            <div class="about-divider"><span>&#9830;&#xFE0E;</span></div>
3597
3598            <div class="structure-item">
3599              <div class="structure-icon">&#9830;&#xFE0E;</div>
3600              <div>
3601                <div class="structure-item-title">Ceremonial Opening</div>
3602                <p class="structure-item-text">We begin together June 17, 2026 &mdash;establishing the field through presence, connection, and shared intention.</p>
3603              </div>
3604            </div>
3605
3606            <div class="about-divider"><span>&#9830;&#xFE0E;</span></div>
3607
3608            <div class="structure-item">
3609              <div class="structure-icon">&#9830;&#xFE0E;</div>
3610              <div>
3611                <div class="structure-item-title">6-Day Women&rsquo;s Immersion (Netherlands)</div>
3612                <p class="structure-item-text" style="margin-bottom:8px;">A fully immersive, in-person experience at New Eden. Your participation, accommodation and meals are all included. Your included event is September 15&ndash;21, 2026.</p>
3613                <p class="structure-item-text" style="font-size:11px;opacity:0.75;font-style:italic;">If you&rsquo;re unable to make this event, a future event will not be provided. In the unlikely event of a pandemic or unexpected worldwide emergency, the event will be postponed for the entire group and rescheduled as soon as Sumir&rsquo;s schedule and the world situation allows.</p>
3614              </div>
3615            </div>
3616
3617            <div class="about-divider"><span>&#9830;&#xFE0E;</span></div>
3618
3619            <div class="structure-item">
3620              <div class="structure-icon">&#9830;&#xFE0E;</div>
3621              <div>
3622                <div class="structure-item-title">Embodied Leadership</div>
3623                <p class="structure-item-text">As a woman deepens in her own evolution, it becomes the very spark of her leadership.</p>
3624                <p class="structure-item-text" style="margin-top:8px;">It is not what she says, the training she has had or the modality she uses&mdash;it is the embodiment of her being that draws others to her. Her presence becomes the transmission.</p>
3625              </div>
3626            </div>
3627          </div>
3628
3629          <div class="leadership-card">
3630            <div class="leadership-eyebrow">Leadership Embodied</div>
3631            <div class="leadership-title">Step Into the Seat</div>
3632            <p class="leadership-body">Step into a facilitator / teaching seat within Sumir&rsquo;s live event, guiding a group of 15+ women, real participants with real expectations and desires. Embody your leadership, receive direct feedback from your peers and coaching/support from Sumir as you lead in real time.</p>
3633          </div>
3634        </div>
3635      </div>
3636
3637      <!-- ===== TEACHINGS VIEW ===== -->
3638      <div class="view" id="view-teachings">
3639        <div class="content-body">
3640          <!-- Filter & Search Toolbar -->
3641          <div class="feed-toolbar" id="teachings-toolbar" style="display:none;">
3642            <div class="feed-filters">
3643              <button class="feed-filter-btn active" data-tfilter="all" onclick="filterTeachings('all')">All</button>
3644              <button class="feed-filter-btn" data-tfilter="teaching" onclick="filterTeachings('teaching')">Teachings</button>
3645              <button class="feed-filter-btn" data-tfilter="announcement" onclick="filterTeachings('announcement')">Announcements</button>
3646            </div>
3647            <div class="feed-search">
3648              <span class="feed-search-icon">&#9906;</span>
3649              <input type="text" class="feed-search-input" id="teachings-search-input" placeholder="Search teachings..." oninput="searchTeachings(this.value)">
3650              <button class="feed-search-clear" id="teachings-search-clear" onclick="clearTeachingsSearch()">&times;</button>
3651            </div>
3652          </div>
3653
3654          <div id="teachings-container">
3655            <div class="loading">
3656              <div class="loading-dot"></div>
3657              <div class="loading-dot"></div>
3658              <div class="loading-dot"></div>
3659            </div>
3660          </div>
3661        </div>
3662      </div>
3663
3664      <!-- ===== REPLAYS VIEW ===== -->
3665      <div class="view" id="view-replays">
3666        <div class="content-body">
3667          <!-- Teacher Upload -->
3668          <div class="replay-upload" id="replay-upload" style="display:none;">
3669            <div class="replay-upload-label">Upload a Session Replay</div>
3670            <input type="text" class="journal-textarea" id="replay-title" placeholder="Session title (e.g. Group Call — June 12)" style="min-height:auto;padding:12px 14px;margin-bottom:10px;">
3671            <textarea class="journal-textarea" id="replay-desc" placeholder="Brief description (optional)" style="min-height:60px;margin-bottom:10px;"></textarea>
3672            <div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
3673              <label style="cursor:pointer;padding:8px 16px;border:1px solid var(--gold-light);border-radius:8px;font-family:'Marcellus',serif;font-size:9px;letter-spacing:2px;text-transform:uppercase;color:var(--plum);">
3674                &#127909; Choose Video
3675                <input type="file" accept="video/*" id="replay-video-input" onchange="handleReplayVideoSelect(this)" style="display:none;">
3676              </label>
3677              <span id="replay-video-name" style="font-size:12px;color:var(--charcoal);opacity:0.6;"></span>
3678              <button class="journal-save" id="replay-upload-btn" onclick="uploadReplay()" style="margin-top:0;margin-left:auto;">Upload Replay</button>
3679            </div>
3680          </div>
3681
3682          <div id="replays-container">
3683            <div class="placeholder-view" id="replays-empty">
3684              <div class="placeholder-icon">&#9654;</div>
3685              <div class="placeholder-title">Replays</div>
3686              <p class="placeholder-text">Session replays will appear here as they become available.</p>
3687            </div>
3688          </div>
3689        </div>
3690      </div>
3691
3692      <!-- ===== JOURNAL VIEW ===== -->
3693      <div class="view" id="view-journal">
3694        <div class="content-body">
3695
3696          <!-- Thoughtful Inquiries -->
3697          <div class="inquiry-banner" id="inquiry-banner">
3698            <div class="inquiry-banner-header" onclick="document.getElementById('inquiry-banner').classList.toggle('open')">
3699              <div>
3700                <div class="inquiry-banner-title">Thoughtful Inquiries</div>
3701                <div class="inquiry-banner-subtitle">Preparing the Field for Our Opening Ceremony &mdash; June 17, 2026</div>
3702              </div>
3703              <span class="inquiry-banner-arrow">&#9660;</span>
3704            </div>
3705            <div class="inquiry-banner-body">
3706              <p class="inquiry-intro">Before we gather on June 17th, you are invited to sit with the following inquiries. Perhaps you also have a few inquiries for yourself.</p>
3707
3708              <!-- Inquiry 1 -->
3709              <div class="inquiry-section">
3710                <div class="inquiry-section-num">Inquiry One</div>
3711                <div class="inquiry-section-q">What is it that you are hoping to experience inside this Private Mentorship field?</div>
3712                <div class="inquiry-line-row"><span class="inquiry-line-label">1.</span><input class="inquiry-input" data-field="q1_1" placeholder=""></div>
3713                <div class="inquiry-line-row"><span class="inquiry-line-label">2.</span><input class="inquiry-input" data-field="q1_2" placeholder=""></div>
3714                <div class="inquiry-line-row"><span class="inquiry-line-label">3.</span><input class="inquiry-input" data-field="q1_3" placeholder=""></div>
3715              </div>
3716
3717              <div class="inquiry-divider"><span>&#9830;&#xFE0E;</span></div>
3718
3719              <!-- Inquiry 2 -->
3720              <div class="inquiry-section">
3721                <div class="inquiry-section-num">Inquiry Two</div>
3722                <div class="inquiry-section-q">What are you prepared to release and transform &mdash;
3722 to support creating and bringing forth that experience?</div>
3723                <div class="inquiry-line-row"><span class="inquiry-line-label">1.</span><input class="inquiry-input" data-field="q2_1" placeholder=""></div>
3724                <div class="inquiry-line-row"><span class="inquiry-line-label">2.</span><input class="inquiry-input" data-field="q2_2" placeholder=""></div>
3725                <div class="inquiry-line-row"><span class="inquiry-line-label">3.</span><input class="inquiry-input" data-field="q2_3" placeholder=""></div>
3726              </div>
3727
3728              <div class="inquiry-divider"><span>&#9830;&#xFE0E;</span></div>
3729
3730              <!-- Inquiry 3 -->
3731              <div class="inquiry-section">
3732                <div class="inquiry-section-num">Inquiry Three</div>
3733                <div class="inquiry-section-q">What are you prepared and looking forward to offering to this space &mdash; to co-create the most electrifying, magnetic, healing field for all?</div>
3734                <div class="inquiry-line-row"><span class="inquiry-line-label">1.</span><input class="inquiry-input" data-field="q3_1" placeholder=""></div>
3735                <div class="inquiry-line-row"><span class="inquiry-line-label">2.</span><input class="inquiry-input" data-field="q3_2" placeholder=""></div>
3736                <div class="inquiry-line-row"><span class="inquiry-line-label">3.</span><input class="inquiry-input" data-field="q3_3" placeholder=""></div>
3737              </div>
3738
3739              <div class="inquiry-divider"><span>&#9830;&#xFE0E;</span></div>
3740
3741              <!-- Inquiry 4 -->
3742              <div class="inquiry-section">
3743                <div class="inquiry-section-num">Inquiry Four</div>
3744                <div class="inquiry-section-q">What are you emotionally, energetically, and practically hoping to experience during this Private Mentorship?</div>
3745
3746                <div class="inquiry-cat-title">Emotional</div>
3747                <div class="inquiry-line-row"><span class="inquiry-line-label">1.</span><input class="inquiry-input" data-field="q4_emotional_1" placeholder=""></div>
3748                <div class="inquiry-line-row"><span class="inquiry-line-label">2.</span><input class="inquiry-input" data-field="q4_emotional_2" placeholder=""></div>
3749                <div class="inquiry-line-row"><span class="inquiry-line-label">3.</span><input class="inquiry-input" data-field="q4_emotional_3" placeholder=""></div>
3750
3751                <div class="inquiry-cat-title">Energetic</div>
3752                <div class="inquiry-line-row"><span class="inquiry-line-label">1.</span><input class="inquiry-input" data-field="q4_energetic_1" placeholder=""></div>
3753                <div class="inquiry-line-row"><span class="inquiry-line-label">2.</span><input class="inquiry-input" data-field="q4_energetic_2" placeholder=""></div>
3754                <div class="inquiry-line-row"><span class="inquiry-line-label">3.</span><input class="inquiry-input" data-field="q4_energetic_3" placeholder=""></div>
3755
3756                <div class="inquiry-cat-title">Spiritual</div>
3757                <div class="inquiry-line-row"><span class="inquiry-line-label">1.</span><input class="inquiry-input" data-field="q4_spiritual_1" placeholder=""></div>
3758                <div class="inquiry-line-row"><span class="inquiry-line-label">2.</span><input class="inquiry-input" data-field="q4_spiritual_2" placeholder=""></div>
3759                <div class="inquiry-line-row"><span class="inquiry-line-label">3.</span><input class="inquiry-input" data-field="q4_spiritual_3" placeholder=""></div>
3760
3761                <div class="inquiry-cat-title">Practical</div>
3762                <div class="inquiry-line-row"><span class="inquiry-line-label">1.</span><input class="inquiry-input" data-field="q4_practical_1" placeholder=""></div>
3763                <div class="inquiry-line-row"><span class="inquiry-line-label">2.</span><input class="inquiry-input" data-field="q4_practical_2" placeholder=""></div>
3764                <div class="inquiry-line-row"><span class="inquiry-line-label">3.</span><input class="inquiry-input" data-field="q4_practical_3" placeholder=""></div>
3765
3766                <div class="inquiry-cat-title">Physical</div>
3767                <div class="inquiry-line-row"><span class="inquiry-line-label">1.</span><input class="inquiry-input" data-field="q4_physical_1" placeholder=""></div>
3768                <div class="inquiry-line-row"><span class="inquiry-line-label">2.</span><input class="inquiry-input" data-field="q4_physical_2" placeholder=""></div>
3769                <div class="inquiry-line-row"><span class="inquiry-line-label">3.</span><input class="inquiry-input" data-field="q4_physical_3" placeholder=""></div>
3770
3771                <div class="inquiry-cat-title">Facilitator Experience</div>
3772                <div class="inquiry-line-row"><span class="inquiry-line-label">1.</span><input class="inquiry-input" data-field="q4_facilitator_1" placeholder=""></div>
3773                <div class="inquiry-line-row"><span class="inquiry-line-label">2.</span><input class="inquiry-input" data-field="q4_facilitator_2" placeholder=""></div>
3774                <div class="inquiry-line-row"><span class="inquiry-line-label">3.</span><input class="inquiry-input" data-field="q4_facilitator_3" placeholder=""></div>
3775              </div>
3776
3777              <div class="inquiry-divider"><span>&#9830;&#xFE0E;</span></div>
3778
3779              <!-- Inquiry 5 -->
3780              <div class="inquiry-section">
3781                <div class="inquiry-section-num">Inquiry Five</div>
3782                <div class="inquiry-section-q">What are you most excited about?</div>
3783                <div class="inquiry-line-row"><span class="inquiry-line-label">1.</span><input class="inquiry-input" data-field="q5_excited_1" placeholder=""></div>
3784                <div class="inquiry-line-row"><span class="inquiry-line-label">2.</span><input class="inquiry-input" data-field="q5_excited_2" placeholder=""></div>
3785                <div class="inquiry-line-row"><span class="inquiry-line-label">3.</span><input class="inquiry-input" data-field="q5_excited_3" placeholder=""></div>
3786
3787                <div class="inquiry-section-q" style="margin-top:20px;">What are you most fearful or nervous about?</div>
3788                <div class="inquiry-line-row"><span class="inquiry-line-label">1.</span><input class="inquiry-input" data-field="q5_fearful_1" placeholder=""></div>
3789                <div class="inquiry-line-row"><span class="inquiry-line-label">2.</span><input class="inquiry-input" data-field="q5_fearful_2" placeholder=""></div>
3790                <div class="inquiry-line-row"><span class="inquiry-line-label">3.</span><input class="inquiry-input" data-field="q5_fearful_3" placeholder=""></div>
3791              </div>
3792
3793              <!-- Save -->
3794              <div class="inquiry-save-row">
3795                <span class="inquiry-saved-msg" id="inquiry-saved-msg">Saved as Journal Entry #1</span>
3796                <button class="inquiry-save-btn" onclick="saveInquiryResponses()">Save My Responses</button>
3797              </div>
3798            </div>
3799          </div>
3800
3801          <div id="journal-prompts"></div>
3802
3803          <div class="journal-compose">
3804            <div class="journal-compose-label">Write in Your Journal</div>
3805            <textarea class="journal-textarea" id="journal-body" placeholder="What is alive in you right now..."></textarea>
3806            <div style="display:flex;align-items:center;justify-content:space-between;margin-top:10px;flex-wrap:wrap;gap:10px;">
3807              <div class="journal-visibility-toggle">
3808                <button class="journal-vis-btn active" id="vis-private" onclick="setJournalVisibility('private')">&#128274; Private</button>
3809                <button class="journal-vis-btn" id="vis-public" onclick="setJournalVisibility('public')">&#128065; Visible to Sumir</button>
3810              </div>
3811              <div style="display:flex;gap:8px;">
3812                <button class="journal-save journal-save-draft" onclick="saveJournalEntry(true)" title="Save without publishing">Save as Draft</button>
3813                <button class="journal-save" onclick="saveJournalEntry(false)">Save Entry</button>
3814              </div>
3815            </div>
3816            <div id="editing-draft-banner" style="display:none;margin-top:10px;padding:8px 12px;background:rgba(197,165,90,0.1);border:1px dashed var(--gold);border-radius:6px;font-family:'Lora',serif;font-size:12px;color:var(--plum);">
3817              <span>&#9998; Editing draft &mdash; <button onclick="cancelDraftEdit()" style="background:none;border:none;color:var(--berry);text-decoration:underline;cursor:pointer;font-family:inherit;font-size:inherit;padding:0;">cancel</button></span>
3818            </div>
3819          </div>
3820
3821          <div id="journal-toolbar" style="display:none;align-items:center;gap:12px;margin-bottom:14px;flex-wrap:wrap;">
3822            <div style="position:relative;flex:1;
3822min-width:200px;">
3823              <input type="text" id="journal-search-input" placeholder="Search your journal..." oninput="filterJournalEntries(this.value)" style="width:100%;padding:10px 36px 10px 14px;border:1px solid rgba(197,165,90,0.3);border-radius:18px;background:white;font-family:'Lora',serif;font-size:13px;color:var(--charcoal);outline:none;">
3824              <button id="journal-search-clear" onclick="clearJournalSearch()" style="position:absolute;right:10px;top:50%;transform:translateY(-50%);background:none;border:none;color:var(--gold);font-size:16px;cursor:pointer;display:none;line-height:1;padding:4px 6px;">&times;</button>
3825            </div>
3826            <div id="journal-print-trigger" style="display:flex;gap:8px;">
3827              <button class="journal-print-btn" onclick="printJournal()" title="Print or Save as PDF">
3828                <span class="print-icon">&#9998;</span> Print / Save as PDF
3829              </button>
3830            </div>
3831          </div>
3832
3833          <div id="journal-entries"></div>
3834        </div>
3835      </div>
3836
3837      <!-- ===== SCHEDULE VIEW ===== -->
3838      <div class="view" id="view-schedule">
3839        <div class="content-body">
3840          <div style="max-width:600px;margin:0 auto;">
3841
3842            <div style="background:white;border:1px solid rgba(197,165,90,0.25);border-radius:14px;padding:32px 28px;text-align:center;margin-bottom:30px;box-shadow:0 2px 12px rgba(197,165,90,0.08);">
3843              <p style="margin-bottom:14px;"><a href="https://us06web.zoom.us/j/7772877728" target="_blank" style="font-family:'Marcellus',serif;font-size:11px;letter-spacing:2px;text-transform:uppercase;color:white;background:var(--plum);text-decoration:none;border:none;padding:12px 30px;border-radius:8px;display:inline-block;transition:all 0.3s;">&#10022; Join Zoom Call</a></p>
3844              <p style="font-family:'Lora',serif;font-size:11px;color:var(--charcoal);opacity:0.75;word-break:break-all;user-select:all;cursor:text;">https://us06web.zoom.us/j/7772877728</p>
3845            </div>
3846
3847            <div class="about-divider"><span>&#9830;&#xFE0E;</span></div>
3848
3849            <!-- June -->
3850            <div style="margin-bottom:24px;">
3851              <div style="font-family:'Marcellus',serif;font-size:10px;letter-spacing:3px;text-transform:uppercase;color:var(--berry);margin-bottom:10px;">June</div>
3852              <div style="display:flex;flex-direction:column;gap:8px;">
3853                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3854                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">June 17 &middot; 4pm CET</span>
3855                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Opening Ceremony</span>
3856                </div>
3857                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3858                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">June 28 &middot; 4pm CET</span>
3859                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Teaching Call</span>
3860                </div>
3861              </div>
3862            </div>
3863
3864            <!-- July -->
3865            <div style="margin-bottom:24px;">
3866              <div style="font-family:'Marcellus',serif;font-size:10px;letter-spacing:3px;text-transform:uppercase;color:var(--berry);margin-bottom:10px;">July</div>
3867              <div style="display:flex;flex-direction:column;gap:8px;">
3868                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3869                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">July 5 &middot; 4pm CET</span>
3870                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Teaching Call</span>
3871                </div>
3872                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3873                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">July 19 &middot; 4pm CET</span>
3874                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Practice Call</span>
3875                </div>
3876                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3877                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">July 22 &middot; 6pm CET</span>
3878                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Practice Call</span>
3879                </div>
3880              </div>
3881            </div>
3882
3883            <!-- August -->
3884            <div style="margin-bottom:24px;">
3885              <div style="font-family:'Marcellus',serif;font-size:10px;letter-spacing:3px;text-transform:uppercase;color:var(--berry);margin-bottom:10px;">August</div>
3886              <div style="display:flex;flex-direction:column;gap:8px;">
3887                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3888                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">August 5 &middot; 3pm CET</span>
3889                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Teaching Call</span>
3890                </div>
3891                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3892                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">August 12 &middot; 3pm CET</span>
3893                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Practice Call</span>
3894                </div>
3895                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3896                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">August 19 &middot; 6pm CET</span>
3897                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Practice Call</span>
3898                </div>
3899              </div>
3900            </div>
3901
3902            <!-- September -->
3903            <div style="margin-bottom:24px;">
3904              <div style="font-family:'Marcellus',serif;font-size:10px;letter-spacing:3px;text-transform:uppercase;color:var(--berry);margin-bottom:10px;">September</div>
3905              <div style="display:flex;flex-direction:column;gap:8px;">
3906                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3907                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">September 6 &middot; 4pm CET</span>
3908                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Teaching &amp; Event Tee Up</span>
3909                </div>
3910                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3911                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">September 15 &ndash; 21</span>
3912                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Live Event</span>
3913                </div>
3914                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3915                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">September 27 &middot; 4pm CET</span>
3916                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Event Follow Up</span>
3917                </div>
3918              </div>
3919            </div>
3920
3921            <!-- October -->
3922            <div style="margin-bottom:24px;">
3923              <div style="font-family:'Marcellus',serif;font-size:10px;letter-spacing:3px;text-transform:uppercase;color:var(--berry);margin-bottom:10px;">October</div>
3924              <div style="display:flex;flex-direction:column;gap:8px;">
3925                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3926                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">TBD</span>
3927                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Teaching Call</span>
3928                </div>
3929                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3930                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">TBD</span>
3931                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Practice Call 1</span>
3932                </div>
3933                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3934                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">TBD</span>
3935                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Practice Call 2</span>
3936                </div>
3937              </div>
3938            </div>
3939
3940            <!-- November -->
3941            <div style="margin-bottom:24px;">
3942              <div style="font-family:'Marcellus',serif;font-size:10px;letter-spacing:3px;text-transform:uppercase;color:var(--berry);margin-bottom:10px;">November</div>
3943              <div style="display:flex;flex-direction:column;gap:8px;">
3944                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3945                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">TBD</span>
3946                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Teaching Call</span>
3947                </div>
3948                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3949                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">TBD</span>
3950                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Practice Call 1</span>
3951                </div>
3952                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3953                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">TBD</span>
3954                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Practice Call 2</span>
3955                </div>
3956              </div>
3957            </div>
3958
3959            <div class="about-divider"><span>&#9830;&#xFE0E;</span></div>
3960
3961            <!-- Integration Period — December -->
3962            <div style="background:var(--cream-warm);border-radius:14px;border:1px solid rgba(197,165,90,0.15);padding:28px 24px;text-align:center;margin-bottom:24px;">
3963              <div style="font-family:'Marcellus',serif;font-size:10px;letter-spacing:3px;text-transform:uppercase;color:var(--gold);margin-bottom:6px;">Integration Period</div>
3964              <div style="font-family:'Cormorant Garamond',serif;font-size:22px;color:var(--plum-deep);margin-bottom:8px;">December</div>
3965              <p style="font-family:'Cormorant Garamond',serif;font-style:italic;font-size:15px;color:var(--plum);opacity:0.8;">A dedicated month for rest, integration, and embodiment.<br>No group calls.</p>
3966            </div>
3967
3968            <div class="about-divider"><span>&#9830;&#xFE0E;</span></div>
3969
3970            <!-- January -->
3971            <div style="margin-bottom:24px;">
3972              <div style="font-family:'Marcellus',serif;font-size:10px;letter-spacing:3px;text-transform:uppercase;color:var(--berry);margin-bottom:10px;">January</div>
3973              <div style="display:flex;flex-direction:column;gap:8px;">
3974                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3975                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">TBD</span>
3976                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Teaching Call</span>
3977                </div>
3978                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3979                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">TBD</span>
3980                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Practice Call 1</span>
3981                </div>
3982                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3983                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">TBD</span>
3984                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Practice Call 2</span>
3985                </div>
3986              </div>
3987            </div>
3988
3989            <!-- February -->
3990            <div style="margin-bottom:24px;">
3991              <div style="font-family:'Marcellus',serif;font-size:10px;letter-spacing:3px;text-transform:uppercase;color:var(--berry);margin-bottom:10px;">February</div>
3992              <div style="display:flex;flex-direction:column;gap:8px;">
3993                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3994                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">TBD</span>
3995                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Teaching Call</span>
3996                </div>
3997                <div style="background:white;border:1px solid rgba(197,165,90,0.15);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
3998                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">TBD</span>
3999                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--gold);">Practice Call 1</span>
4000                </div>
4001                <div style="background:white;border:1px solid rgba(148,14,75,0.12);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
4002                  <span style="font-family:'Cormorant Garamond',serif;font-size:17px;color:var(--plum-deep);">TBD</span>
4003                  <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--berry);">Practice Call 2 / Final Call &#10022;</span>
4004                </div>
4005              </div>
4006            </div>
4007
4008          </div>
4009        </div>
4010      </div>
4011
4012      <!-- ===== TOOLS VIEW ===== -->
4013      <div class="view" id="view-tools">
4014        <div class="content-body">
4015          <p class="about-welcome" style="margin-bottom:24px;">
4016            <em>Simple guides to help you make the most of this space.</em>
4017          </p>
4018
4019          <div class="tools-grid">
4020
4021            <!-- Tutorial: Thoughtful Inquiries -->
4022            <div class="tool-card" onclick="this.classList.toggle('open')">
4023              <div class="tool-card-header">
4024                <div class="tool-card-header-left">
4025                  <div class="tool-card-icon">&#10047;</div>
4026                  <div>
4027                    <div class="tool-card-title">Completing Your Thoughtful Inquiries</div>
4028                    <div class="tool-card-subtitle">Your first journal entry &amp; preparation for the Opening Ceremony</div>
4029                  </div>
4030                </div>
4031                <div class="tool-card-arrow">&#9654;</div>
4032              </div>
4033              <div class="tool-card-content">
4034
4035                <div class="guide-note" style="border-left-color:var(--berry);">
4036                  <strong>What are the Thoughtful Inquiries?</strong> These are five reflective inquiries designed to help you begin entering the field we will co-create together. Your responses become your very first journal entry &mdash; and they&rsquo;re always available for you to revisit and update as you evolve through this experience.
4037                </div>
4038
4039                <div class="guide-section">
4040                  <div class="guide-section-title">Step by Step</div>
4041                  <div class="guide-steps">
4042                    <div class="guide-step">
4043                      <div class="guide-step-num">1</div>
4044                      <div class="guide-step-text">Tap <strong>Journal</strong> in the sidebar.</div>
4045                    </div>
4046                    <div class="guide-step">
4047                      <div class="guide-step-num">2</div>
4048                      <div class="guide-step-text">At the very top of the page, you&rsquo;ll see the <strong>Thoughtful Inquiries</strong> card. Tap it to expand.</div>
4049                    </div>
4050                    <div class="guide-step">
4051                      <div class="guide-step-num">3</div>
4052                      <div class="guide-step-text">Read each inquiry slowly. There are five in total, each with space for your responses.</div>
4053                    </div>
4054                    <div class="guide-step">
4055                      <div class="guide-step-num">4</div>
4056                      <div class="guide-step-text">Fill in your responses on the lines provided. You don&rsquo;t need to complete them all at once &mdash; you can return and update them anytime.</div>
4057                    </div>
4058                    <div class="guide-step">
4059                      <div class="guide-step-num">5</div>
4060                      <div class="guide-step-text">When you&rsquo;re ready, tap <strong>Save My Responses</strong> at the bottom. Your answers are saved as Journal Entry #1.</div>
4061                    </div>
4062                    <div class="guide-step">
4063                      <div class="guide-step-num">6</div>
4064                      <div class="guide-step-text">Come prepared to share your responses at our <strong>Opening Ceremony on June 17th</strong>.</div>
4065                    </div>
4066                  </div>
4067                </div>
4068
4069                <div class="guide-note">
4070                  <strong>A few things to know:</strong>
4071                  <ul style="padding-left:20px;line-height:1.8;margin-top:8px;margin-bottom:0;">
4072                    <li>Your Thoughtful Inquiry responses will be <strong>automatically visible to Sumir</strong>, so she can prepare and hold the field with and for you.</li>
4073                    <li>All other Journal Entries moving forward will default to <strong>private</strong> (your eyes only) unless you specifically choose to share them with Sumir via the option provided on each journal entry.</li>
4074                    <li>You can update your inquiry responses any time up to <strong>June 17th</strong>, at which point we will seal them and let them be as they are &mdash; visible but unchangeable &mdash; until the end of our experience together.</li>
4075                  </ul>
4076                </div>
4077
4078              </div>
4079            </div>
4080
4081            <!-- Tutorial: Golden Path Teachings Access -->
4082            <div class="tool-card" onclick="this.classList.toggle('open')">
4083              <div class="tool-card-header">
4084                <div class="tool-card-header-left">
4085                  <div class="tool-card-icon">&#10024;</div>
4086                  <div>
4087                    <div class="tool-card-title">Golden Path Teachings Access</div>
4088                    <div class="tool-card-subtitle">How to access the full Golden Path teaching library</div>
4089                  </div>
4090                </div>
4091                <div class="tool-card-arrow">&#9654;</div>
4092              </div>
4093              <div class="tool-card-content">
4094
4095                <div class="guide-note" style="border-left-color:var(--berry);">
4096                  <strong>What is the Golden Path Library?</strong> As a Private Mentorship member, you have full access to the Golden Path teaching collection &mdash; a library of teachings, practices, and sacred resources. This is a separate portal that uses the same access code as your Private Mentorship.
4097                </div>
4098
4099                <div class="guide-section">
4100                  <div class="guide-section-title">Step by Step</div>
4101                  <div class="guide-steps">
4102                    <div class="guide-step">
4103                      <div class="guide-step-num">1</div>
4104                      <div class="guide-step-text">Go to <strong>femininerising.co</strong> in your browser.</div>
4105                    </div>
4106                    <div class="guide-step">
4107                      <div class="guide-step-num">2</div>
4108                      <div class="guide-step-text">On the Portal page, find and click the <strong>Golden Path Library</strong> tile.</div>
4109                    </div>
4110                    <div class="guide-step">
4111                      <div class="guide-step-num">3</div>
4112                      <div class="guide-step-text">When prompted, enter the access code: <strong style="color:var(--berry);letter-spacing:1px;">embodied2026</strong></div>
4113                    </div>
4114                    <div class="guide-step">
4115                      <div class="guide-step-num">4</div>
4116                      <div class="guide-step-text">You&rsquo;re in. Browse the full library of teachings, bookmark your favorites, and return anytime.</div>
4117                    </div>
4118                  </div>
4119                </div>
4120
4121                <div class="guide-note">
4122                  <strong>Good to know:</strong>
4123                  <ul style="padding-left:20px;line-height:1.8;margin-top:8px;margin-bottom:0;">
4124                    <li>This is the same access code you use for your Private Mentorship portal.</li>
4125                    <li>The Golden Path Library is available to you throughout the duration of your mentorship.</li>
4126                    <li>If you have any trouble accessing the library, contact <strong>[email protected]</strong>.</li>
4127                  </ul>
4128                </div>
4129
4130              </div>
4131            </div>
4132
4133            <!-- Tutorial 1: Video Posts -->
4134            <div class="tool-card" onclick="this.classList.toggle('open')">
4135              <div class="tool-card-header">
4136                <div class="tool-card-header-left">
4137                  <div class="tool-card-icon">&#127909;</div>
4138                  <div>
4139                    <div class="tool-card-title">How to Share a Video Post</div>
4140                    <div class="tool-card-subtitle">Record &amp; share video in the Community</div>
4141                  </div>
4142                </div>
4143                <div class="tool-card-arrow">&#9654;</div>
4144              </div>
4145              <div class="tool-card-content">
4146
4147                <div class="guide-note">
4148                  <strong>Good news:</strong> The steps are the same whether you&rsquo;re on iPhone or Android. Wherever you see your camera roll or file picker, you can also record a new video on the spot.
4149                </div>
4150
4151                <div class="guide-section">
4152                  <div class="guide-section-title">From Your Phone</div>
4153
4154                  <div class="guide-step">
4155                    <div class="guide-step-num">1</div>
4156                    <div class="guide-step-text">Tap <strong>Community</strong> in the sidebar.</div>
4157                  </div>
4158
4159                  <div class="guide-step">
4160                    <div class="guide-step-num">2</div>
4161                    <div class="guide-step-text">Tap the <strong>&ldquo;Share something with the community...&rdquo;</strong> bar at the top. The composer will expand.</div>
4162                  </div>
4163
4164                  <div class="guide-step">
4165                    <div class="guide-step-num">3</div>
4166                    <div class="guide-step-text">Write your message in the text area. Add a title if you&rsquo;d like (optional).</div>
4167                  </div>
4168
4169                  <div class="guide-step">
4170                    <div class="guide-step-num">4</div>
4171                    <div class="guide-step-text">Tap the <strong>&#127909; Photo / Video</strong> button at the bottom.</div>
4172                  </div>
4173
4174                  <div class="guide-step">
4175                    <div class="guide-step-num">5</div>
4176                    <div class="guide-step-text">Your phone will ask you to <strong>choose a file</strong>. You can either:<br>
4177                      &bull; Select an existing video from your camera roll<br>
4178                      &bull; Choose <strong>&ldquo;Camera&rdquo;</strong> to record a new video right there</div>
4179                  </div>
4180
4181                  <div class="guide-step">
4182                    <div class="guide-step-num">6</div>
4183                    <div class="guide-step-text">Once selected, you&rsquo;ll see a <strong>preview</strong> of your video in the composer. You can tap the <strong>&times;</strong> to remove it if you change your mind.</div>
4184                  </div>
4185
4186                  <div class="guide-step">
4187                    <div class="guide-step-num">7</div>
4188                    <div class="guide-step-text">When you&rsquo;re ready, tap <strong>Share</strong>. The video will upload and your post will appear in the feed.</div>
4189                  </div>
4190                </div>
4191
4192                <div class="guide-divider"></div>
4193
4194                <div class="guide-section">
4195                  <div class="guide-section-title">From Your Computer</div>
4196
4197                  <div class="guide-step">
4198                    <div class="guide-step-num">1</div>
4199                    <div class="guide-step-text">Click <strong>Community</strong> in the sidebar.</div>
4200                  </div>
4201
4202                  <div class="guide-step">
4203                    <div class="guide-step-num">2</div>
4204                    <div class="guide-step-text">Click the <strong>&ldquo;Share something with the community...&rdquo;</strong> bar. The composer will expand.</div>
4205                  </div>
4206
4207                  <div class="guide-step">
4208                    <div class="guide-step-num">3</div>
4209                    <div class="guide-step-text">Write your message and click the <strong>&#127909; Photo / Video</strong> button.</div>
4210                  </div>
4211
4212                  <div class="guide-step">
4213                    <div class="guide-step-num">4</div>
4214                    <div class="guide-step-text">A file picker will open. Navigate to where your video is saved and select it.</div>
4215                  </div>
4216
4217                  <div class="guide-step">
4218                    <div class="guide-step-num">5</div>
4219                    <div class="guide-step-text">You&rsquo;ll see a preview. When you&rsquo;re ready, click <strong>Share</strong>.</div>
4220                  </div>
4221                </div>
4222
4223                <div class="guide-divider"></div>
4224
4225                <div class="guide-note">
4226                  <strong>&#128274; Privacy note:</strong> All videos shared in this space are protected. They cannot be downloaded or saved by other members. This is a safe space to be seen.
4227                </div>
4228
4229                <div class="guide-note" style="border-left-color:var(--berry);">
4230                  <strong>Tip:</strong> Shorter videos (under 2 minutes) upload fastest. If your video is long, give it a moment &mdash; you&rsquo;ll see &ldquo;Sharing...&rdquo; while it uploads.
4231                </div>
4232
4233              </div>
4234            </div>
4235
4236            <!-- Tutorial: Voice Notes -->
4237            <div class="tool-card" onclick="this.classList.toggle('open')">
4238              <div class="tool-card-header">
4239                <div class="tool-card-header-left">
4240                  <div class="tool-card-icon">&#127908;</div>
4241                  <div>
4242                    <div class="tool-card-title">How to Post a Voice Note</div>
4243                    <div class="tool-card-subtitle">Share a spoken reflection in the Community (up to 5 minutes)</div>
4244                  </div>
4245                </div>
4246                <div class="tool-card-arrow">&#9654;</div>
4247              </div>
4248              <div class="tool-card-content">
4249
4250                <div class="guide-note">
4251                  Voice notes are a beautiful way to share when you&rsquo;d rather speak than type. Your phone or computer will ask permission to use your microphone the first time &mdash; just tap <strong>Allow</strong>.
4252                </div>
4253
4254                <div class="guide-section">
4255                  <div class="guide-section-title">Step by Step</div>
4256
4257                  <div class="guide-step">
4258                    <div class="guide-step-num">1</div>
4259                    <div class="guide-step-text">Tap <strong>Community</strong> in the sidebar.</div>
4260                  </div>
4261
4262                  <div class="guide-step">
4263                    <div class="guide-step-num">2</div>
4264                    <div class="guide-step-text">Tap the <strong>&ldquo;Share something with the community&hellip;&rdquo;</strong> bar at the top. The composer will expand.</div>
4265                  </div>
4266
4267                  <div class="guide-step">
4268                    <div class="guide-step-num">3</div>
4269                    <div class="guide-step-text">Tap the <strong>&#127908; Voice Note</strong> button at the bottom of the composer. A recording window will open.</div>
4270                  </div>
4271
4272                  <div class="guide-step">
4273                    <div class="guide-step-num">4</div>
4274                    <div class="guide-step-text">The first time you do this, your browser will ask to use your microphone. Tap <strong>Allow</strong>.</div>
4275                  </div>
4276
4277                  <div class="guide-step">
4278                    <div class="guide-step-num">5</div>
4279                    <div class="guide-step-text">Tap the big <strong>berry microphone button</strong> to start recording. You&rsquo;ll see a timer counting up.</div>
4280                  </div>
4281
4282                  <div class="guide-step">
4283                    <div class="guide-step-num">6</div>
4284                    <div class="guide-step-text">When you&rsquo;re finished, tap the button again to stop. (It auto-stops at 5 minutes.)</div>
4285                  </div>
4286
4287                  <div class="guide-step">
4288                    <div class="guide-step-num">7</div>
4289                    <div class="guide-step-text">Listen back using the little audio player. Don&rsquo;t love it? Tap <strong>Re-record</strong> to try again.</div>
4290                  </div>
4291
4292                  <div class="guide-step">
4293                    <div class="guide-step-num">8</div>
4294                    <div class="guide-step-text">When you&rsquo;re happy, tap <strong>Use This Recording</strong>. It will attach to your post.</div>
4295                  </div>
4296
4297                  <div class="guide-step">
4298                    <div class="guide-step-num">9</div>
4299                    <div class="guide-step-text">(Optional) Add a <strong>title</strong> or a short note if you&rsquo;d like. You can also post the voice note alone.</div>
4300                  </div>
4301
4302                  <div class="guide-step">
4303                    <div class="guide-step-num">10</div>
4304                    <div class="guide-step-text">Tap <strong>Share</strong>. Your voice note will appear in the Community feed with a little &#127908; label, ready for your sisters to listen.</div>
4305                  </div>
4306                </div>
4307
4308                <div class="guide-note" style="border-left-color:var(--berry);">
4309                  <strong>Tip:</strong> Record somewhere quiet. Take a breath before you start. There&rsquo;s no right way &mdash; just your real voice, shared with love.
4310                </div>
4311
4312                <div class="guide-note">
4313                  <strong>A Few Gentle Notes</strong>
4314                  <ul style="padding-left:20px;line-height:1.8;margin-top:8px;margin-bottom:0;">
4315                    <li><strong>5-minute maximum</strong> &mdash; so we honor each other&rsquo;s time and attention.</li>
4316                    <li><strong>You can delete your own voice notes</strong> any time by tapping the <strong>&times;</strong> on your post.</li>
4317                    <li><strong>Reactions &amp; comments</strong> work the same as any other post &mdash; send hearts, emojis, or written replies.</li>
4318                    <li><strong>Privacy:</strong> Voice notes only appear in our private Community feed. They are not shared publicly.</li>
4319                  </ul>
4320                </div>
4321
4322              </div>
4323            </div>
4324
4325            <!-- Tutorial 2: Journal -->
4326            <div class="tool-card" onclick="this.classList.toggle('open')">
4327              <div class="tool-card-header">
4328                <div class="tool-card-header-left">
4329                  <div class="tool-card-icon">&#9998;</div>
4330                  <div>
4331                    <div class="tool-card-title">How to Use Your Journal</div>
4332                    <div class="tool-card-subtitle">Write, reflect, and print your entries</div>
4333                  </div>
4334                </div>
4335                <div class="tool-card-arrow">&#9654;</div>
4336              </div>
4337              <div class="tool-card-content">
4338
4339                <div class="guide-section">
4340                  <div class="guide-section-title">Writing an Entry</div>
4341
4342                  <div class="guide-step">
4343                    <div class="guide-step-num">1</div>
4344                    <div class="guide-step-text">Tap <strong>Journal</strong> in the sidebar.</div>
4345                  </div>
4346
4347                  <div class="guide-step">
4348                    <div class="guide-step-num">2</div>
4349                    <div class="guide-step-text">You&rsquo;ll see a text area that says <strong>&ldquo;What is alive in you right now...&rdquo;</strong> &mdash; write whatever is moving through you.</div>
4350                  </div>
4351
4352                  <div class="guide-step">
4353                    <div class="guide-step-num">3</div>
4354                    <div class="guide-step-text">Before saving, choose your <strong>visibility</strong>:</div>
4355                  </div>
4356                </div>
4357
4358                <div class="guide-note">
4359                  <strong>&#128274; Private</strong> &mdash; Only you can see this entry. It is yours alone. No one else &mdash; not even Sumir &mdash; can read it.
4360                </div>
4361
4362                <div class="guide-note" style="border-left-color:var(--berry);">
4363                  <strong>&#128065; Visible to Sumir</strong> &mdash; This entry will also be visible to Sumir in a dedicated &ldquo;Shared With You&rdquo; section of her journal view. Use this when you want Sumir to witness, read, or hold something with you.
4364                </div>
4365
4366                <div class="guide-section">
4367                  <div class="guide-step">
4368                    <div class="guide-step-num">4</div>
4369                    <div class="guide-step-text">Tap <strong>Save Entry</strong>. Your entry will appear below with the date and a badge showing whether it&rsquo;s private or shared.</div>
4370                  </div>
4371                </div>
4372
4373                <div class="guide-divider"></div>
4374
4375                <div class="guide-section">
4376                  <div class="guide-section-title">Managing Your Entries</div>
4377
4378                  <div class="guide-step">
4379                    <div class="guide-step-num">&#10022;</div>
4380                    <div class="guide-step-text"><strong>Tap any entry</strong> to expand it and see the full text along with action buttons.</div>
4381                  </div>
4382
4383                  <div class="guide-step">
4384                    <div class="guide-step-num">&#9998;</div>
4385                    <div class="guide-step-text"><strong>Edit</strong> &mdash; Update the text of an existing entry.</div>
4386                  </div>
4387
4388                  <div class="guide-step">
4389                    <div class="guide-step-num">&#9830;&#xFE0E;</div>
4390                    <div class="guide-step-text"><strong>Share with Sumir / Make Private</strong> &mdash; Toggle the visibility of any entry at any time. Changed your mind? Just tap to switch.</div>
4391                  </div>
4392
4393                  <div class="guide-step">
4394                    <div class="guide-step-num">&times;</div>
4395                    <div class="guide-step-text"><strong>Delete</strong> &mdash; Permanently remove an entry you no longer want.</div>
4396                  </div>
4397                </div>
4398
4399                <div class="guide-divider"></div>
4400
4401                <div class="guide-section">
4402                  <div class="guide-section-title">Printing Your Journal</div>
4403
4404                  <div class="guide-step">
4405                    <div class="guide-step-num">1</div>
4406                    <div class="guide-step-text">Tap the <strong>&#9998; Print My Journal</strong> button that appears above your entries.</div>
4407                  </div>
4408
4409                  <div class="guide-step">
4410                    <div class="guide-step-num">2</div>
4411                    <div class="guide-step-text">A beautifully formatted version of all your entries will be prepared, and your browser&rsquo;s print dialog will open.</div>
4412                  </div>
4413
4414                  <div class="guide-step">
4415                    <div class="guide-step-num">3</div>
4416                    <div class="guide-step-text">You can print it or save it as a PDF &mdash; a beautiful keepsake of your journey.</div>
4417                  </div>
4418                </div>
4419
4420                <div class="guide-note">
4421                  <strong>&#128156; Remember:</strong> Your journal is a sacred space. Write freely, honestly, and without judgment. Your words are yours.
4422                </div>
4423
4424              </div>
4425            </div>
4426
4427          </div>
4428        </div>
4429      </div>
4430
4431      <!-- ===== MONTHLY HARVEST VIEW ===== -->
4432      <div class="view" id="view-harvest">
4433        <div class="content-body">
4434
4435          <!-- Teacher: Create new harvest (hidden for students) -->
4436          <div id="harvest-composer" style="display:none;margin-bottom:30px;">
4437            <div style="background:white;border-radius:14px;border:1px solid rgba(197,165,90,0.25);padding:24px;">
4438              <div style="font-family:'Marcellus',serif;font-size:10px;letter-spacing:2.5px;color:var(--gold);text-transform:uppercase;margin-bottom:14px;">Create Monthly Harvest</div>
4439              <select id="harvest-month-select" style="width:100%;padding:10px 14px;border:1px solid rgba(197,165,90,0.3);border-radius:8px;font-family:'Lora',serif;font-size:13px;margin-bottom:10px;background:var(--cream);color:var(--charcoal);">
4440                <option value="">Select Month...</option>
4441                <option value="June 2026">June 2026</option>
4442                <option value="July 2026">July 2026</option>
4443                <option value="August 2026">August 2026</option>
4444                <option value="September 2026">September 2026</option>
4445                <option value="October 2026">October 2026</option>
4446                <option value="November 2026">November 2026</option>
4447                <option value="December 2026">December 2026</option>
4448                <option value="January 2027">January 2027</option>
4449              </select>
4450              <input type="text" id="harvest-title-input" placeholder="Harvest title (optional)" style="width:100%;padding:10px 14px;border:1px solid rgba(197,165,90,0.3);border-radius:8px;font-family:'Lora',serif;font-size:13px;margin-bottom:10px;background:var(--cream);">
4451              <textarea id="harvest-prompt-input" placeholder="Write the inquiry or prompt for this month..." style="width:100%;padding:14px;border:1px solid rgba(197,165,90,0.3);border-radius:8px;font-family:'Lora',serif;font-size:13px;min-height:120px;resize:vertical;background:var(--cream);"></textarea>
4452              <div style="display:flex;justify-content:flex-end;margin-top:12px;">
4453                <button onclick="createHarvest()" style="background:var(--berry);color:white;border:none;padding:10px 24px;border-radius:8px;font-family:'Marcellus',serif;font-size:10px;letter-spacing:2px;text-transform:uppercase;cursor:pointer;">Publish Harvest</button>
4454              </div>
4455            </div>
4456          </div>
4457
4458          <!-- Harvest months container -->
4459          <div id="harvest-months-container">
4460            <div style="text-align:center;padding:40px 20px;color:var(--charcoal);opacity:0.5;">Loading harvests...</div>
4461          </div>
4462
4463        </div>
4464      </div>
4465
4466      <!-- ===== MEMBERS VIEW ===== -->
4467      <div class="view" id="view-members">
4468        <div class="content-body">
4469          <div style="margin-bottom:30px;">
4470            <div style="font-family:'Cormorant Garamond',serif;font-size:22px;color:var(--plum-deep);margin-bottom:4px;">Your Community</div>
4471            <div style="font-size:12px;color:var(--charcoal);opacity:0.6;">Members who have joined the Private Mentorship</div>
4472          </div>
4473
4474          <div id="members-stats" style="display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-bottom:30px;">
4475            <div style="background:white;border-radius:12px;border:1px solid rgba(197,165,90,0.2);padding:20px;text-align:center;">
4476              <div style="font-family:'Cormorant Garamond',serif;font-size:28px;color:var(--plum-deep);" id="stat-total">&mdash;</div>
4477              <div style="font-family:'Marcellus',serif;font-size:9px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">Total Members</div>
4478            </div>
4479            <div style="background:white;border-radius:12px;border:1px solid rgba(197,165,90,0.2);padding:20px;text-align:center;">
4480              <div style="font-family:'Cormorant Garamond',serif;font-size:28px;color:var(--plum-deep);" id="stat-active-7">&mdash;</div>
4481              <div style="font-family:'Marcellus',serif;font-size:9px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">Active (7 days)</div>
4482            </div>
4483            <div style="background:white;border-radius:12px;border:1px solid rgba(197,165,90,0.2);padding:20px;text-align:center;">
4484              <div style="font-family:'Cormorant Garamond',serif;font-size:28px;color:var(--plum-deep);" id="stat-active-30">&mdash;</div>
4485              <div style="font-family:'Marcellus',serif;font-size:9px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">Active (30 days)</div>
4486            </div>
4487          </div>
4488
4489          <div style="background:white;border-radius:14px;border:1px solid rgba(197,165,90,0.2);overflow:hidden;">
4490            <table style="width:100%;border-collapse:collapse;font-size:13px;">
4491              <thead>
4492                <tr style="background:var(--cream-warm);border-bottom:1px solid rgba(197,165,90,0.2);">
4493                  <th style="text-align:left;padding:14px 18px;font-family:'Marcellus',serif;font-size:10px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">Name</th>
4494                  <th style="text-align:left;padding:14px 18px;font-family:'Marcellus',serif;font-size:10px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">Joined</th>
4495                  <th style="text-align:left;padding:14px 18px;font-family:'Marcellus',serif;font-size:10px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">Last Active</th>
4496                  <th style="text-align:left;padding:14px 18px;font-family:'Marcellus',serif;font-size:10px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">Role</th>
4497                </tr>
4498              </thead>
4499              <tbody id="members-table-body">
4500                <tr><td colspan="4" style="padding:30px;text-align:center;color:var(--charcoal);opacity:0.5;">Loading members...</td></tr>
4501              </tbody>
4502            </table>
4503          </div>
4504
4505          <!-- Library Members Section -->
4506          <div style="margin-top:40px;">
4507            <div style="font-family:'Cormorant Garamond',serif;font-size:22px;color:var(--plum-deep);margin-bottom:4px;">Library Members</div>
4508            <div style="font-size:12px;color:var(--charcoal);opacity:0.6;margin-bottom:20px;">Everyone who has signed in to the Golden Path Library</div>
4509
4510            <div id="library-members-stats" style="display:grid;grid-template-columns:repeat(2,1fr);gap:14px;margin-bottom:20px;">
4511              <div style="background:white;border-radius:12px;border:1px solid rgba(197,165,90,0.2);padding:20px;text-align:center;">
4512                <div style="font-family:'Cormorant Garamond',serif;font-size:28px;color:var(--plum-deep);" id="stat-lib-members">&mdash;</div>
4513                <div style="font-family:'Marcellus',serif;font-size:9px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">Unique Members</div>
4514              </div>
4515              <div style="background:white;border-radius:12px;border:1px solid rgba(197,165,90,0.2);padding:20px;text-align:center;">
4516                <div style="font-family:'Cormorant Garamond',serif;font-size:28px;color:var(--plum-deep);" id="stat-lib-signins">&mdash;</div>
4517                <div style="font-family:'Marcellus',serif;font-size:9px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">Total Sign-Ins</div>
4518              </div>
4519            </div>
4520
4521            <div style="background:white;border-radius:14px;border:1px solid rgba(197,165,90,0.2);overflow:hidden;margin-bottom:30px;">
4522              <table style="width:100%;border-collapse:collapse;font-size:13px;">
4523                <thead>
4524                  <tr style="background:var(--cream-warm);border-bottom:1px solid rgba(197,165,90,0.2);">
4525                    <th style="text-align:left;padding:14px 18px;font-family:'Marcellus',serif;font-size:10px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">Name / Email</th>
4526                    <th style="text-align:center;padding:14px 18px;font-family:'Marcellus',serif;font-size:10px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">Sign-Ins</th>
4527                    <th style="text-align:center;padding:14px 18px;font-family:'Marcellus',serif;font-size:10px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">Favorites</th>
4528                    <th style="text-align:left;padding:14px 18px;font-family:'Marcellus',serif;font-size:10px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">Last Visit</th>
4529                  </tr>
4530                </thead>
4531                <tbody id="library-members-body">
4532                  <tr><td colspan="4" style="padding:30px;text-align:center;color:var(--charcoal);opacity:0.5;">Loading library members...</td></tr>
4533                </tbody>
4534              </table>
4535            </div>
4536          </div>
4537
4538          <!-- Library Analytics Section -->
4539          <div style="margin-top:40px;">
4540            <div style="font-family:'Cormorant Garamond',serif;font-size:22px;color:var(--plum-deep);margin-bottom:4px;">Library Analytics</div>
4541            <div style="font-size:12px;color:var(--charcoal);opacity:0.6;margin-bottom:20px;">Teaching page views &amp; PDF downloads in the Golden Path Library</div>
4542
4543            <div id="library-analytics-stats" style="display:grid;grid-template-columns:repeat(2,1fr);gap:14px;margin-bottom:20px;">
4544              <div style="background:white;border-radius:12px;border:1px solid rgba(197,165,90,0.2);padding:20px;text-align:center;">
4545                <div style="font-family:'Cormorant Garamond',serif;font-size:28px;color:var(--plum-deep);" id="stat-lib-views">&mdash;</div>
4546                <div style="font-family:'Marcellus',serif;font-size:9px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">Total Page Views</div>
4547              </div>
4548              <div style="background:white;border-radius:12px;border:1px solid rgba(197,165,90,0.2);padding:20px;text-align:center;">
4549                <div style="font-family:'Cormorant Garamond',serif;font-size:28px;color:var(--plum-deep);" id="stat-lib-prints">&mdash;</div>
4550                <div style="font-family:'Marcellus',serif;font-size:9px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">PDF Downloads</div>
4551              </div>
4552            </div>
4553
4554            <div style="background:white;border-radius:14px;border:1px solid rgba(197,165,90,0.2);overflow:hidden;">
4555              <table style="width:100%;border-collapse:collapse;font-size:13px;">
4556                <thead>
4557                  <tr style="background:var(--cream-warm);border-bottom:1px solid rgba(197,165,90,0.2);">
4558                    <th style="text-align:left;padding:14px 18px;font-family:'Marcellus',serif;font-size:10px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">Teaching</th>
4559                    <th style="text-align:center;padding:14px 18px;font-family:'Marcellus',serif;font-size:10px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">Views</th>
4560                    <th style="text-align:center;padding:14px 18px;font-family:'Marcellus',serif;font-size:10px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;">PDF Downloads</th>
4561                  </tr>
4562                </thead>
4563                <tbody id="library-analytics-body">
4564                  <tr><td colspan="3" style="padding:30px;text-align:center;color:var(--charcoal);opacity:0.5;">Loading analytics...</td></tr>
4565                </tbody>
4566              </table>
4567            </div>
4568          </div>
4569
4570        </div>
4571      </div>
4572
4573    </main>
4574
4575  <!-- Hidden print area -->
4576  <div id="journal-print-area" style="display:none;">
4577    <div class="journal-print-header">
4578      <img src="feminine-rising-logo.png" class="journal-print-logo" alt="Feminine Rising">
4579      <div class="journal-print-brand">
4579Feminine Rising</div>
4580      <div class="journal-print-title">My Wisdom Journal</div>
4581      <div class="journal-print-name" id="journal-print-name"></div>
4582    </div>
4583    <div id="journal-print-entries"></div>
4584    <div class="journal-print-footer">
4585      <div class="journal-print-diamond">&#10022;</div>
4586      <div class="journal-print-footer-text">Your words are sacred. Your wisdom is your own.</div>
4587    </div>
4588  </div>
4589  </div>
4590</div>
4591
4592<!-- ============================================
4593     JAVASCRIPT
4594     ============================================ -->
4595<script>
4596// ============================================================
4597// SUPABASE CONFIG
4598// Replace these with your Supabase project credentials
4599// ============================================================
4600const SUPABASE_URL = 'https://ttvjonqfljiinmkjbqvj.supabase.co';
4601const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InR0dmpvbnFmbGppaW5ta2picXZqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzYyNTkyNDQsImV4cCI6MjA5MTgzNTI0NH0.mQ9NUsw4cFMf2uRxPWoLl2ig04O0lHbqjx7N3hfG3iU';
4602
4603// Store the createClient factory before our variable shadows the global
4604const _supabaseFactory = window.supabase ? window.supabase.createClient : null;
4605let sbClient = null;
4606let currentUser = null;
4607let currentProfile = null;
4608
4609// Initialize Supabase (only if configured)
4610function initSupabase() {
4611  if (SUPABASE_URL === 'YOUR_SUPABASE_URL') return false;
4612  try {
4613    if (!_supabaseFactory) {
4614      console.error('Supabase library not loaded');
4615      return false;
4616    }
4617    sbClient = _supabaseFactory(SUPABASE_URL, SUPABASE_ANON_KEY);
4618    return true;
4619  } catch (e) {
4620    console.error('Supabase init error:', e);
4621    return false;
4622  }
4623}
4624
4625// ============================================================
4626// PASSWORD GATE
4627// ============================================================
4628const ACCESS_CODE = 'embodied2026';
4629
4630function checkPassword() {
4631  const input = document.getElementById('password-input').value;
4632  const error = document.getElementById('password-error');
4633
4634  if (input.toLowerCase() === ACCESS_CODE.toLowerCase()) {
4635    localStorage.setItem('mentorship_authenticated', 'true');
4636    proceedAfterPassword();
4637  } else {
4638    error.classList.add('show');
4639    document.getElementById('password-input').value = '';
4640    document.getElementById('password-input').focus();
4641  }
4642}
4643
4644document.getElementById('password-input').addEventListener('keypress', function(e) {
4645  if (e.key === 'Enter') checkPassword();
4646});
4647
4648function proceedAfterPassword() {
4649  document.getElementById('password-screen').style.display = 'none';
4650
4651  if (!initSupabase()) {
4652    // Supabase not configured — show hub directly in demo mode
4653    showHub('Demo User', 'teacher');
4654    return;
4655  }
4656
4657  // Check for existing session
4658  // Detect if this page load is a password recovery flow.
4659  // Supabase sends users back with #type=recovery (or ?type=recovery) in the URL.
4660  const hash = window.location.hash || '';
4661  const search = window.location.search || '';
4662  const isRecovery = hash.includes('type=recovery') || search.includes('type=recovery');
4663
4664  sbClient.auth.getSession().then(({ data: { session } }) => {
4665    if (isRecovery) {
4666      // Show the "set new password" screen instead of signing the user in
4667      showResetScreen();
4668    } else if (session) {
4669      handleSignedIn(session.user);
4670    } else {
4671      showAuthScreen();
4672    }
4673  });
4674
4675  // Listen for auth state changes (magic link callback / recovery)
4676  sbClient.auth.onAuthStateChange((event, session) => {
4677    if (event === 'PASSWORD_RECOVERY') {
4678      showResetScreen();
4679    } else if (event === 'SIGNED_IN' && session) {
4680      // If we're in a recovery flow, don't auto-redirect into the hub
4681      if (document.getElementById('reset-screen').style.display === 'flex') return;
4682      handleSignedIn(session.user);
4683    }
4684  });
4685}
4686
4687function toggleResetPasswordVisibility() {
4688  const inputs = [document.getElementById('reset-password'), document.getElementById('reset-password-confirm')];
4689  const icons = [document.getElementById('reset-eye-icon'), document.getElementById('reset-eye-icon-2')];
4690  const shown = inputs[0].type === 'text';
4691  inputs.forEach(i => { if (i) i.type = shown ? 'password' : 'text'; });
4692  // 👁 = shown/visible, 🙈 = hidden
4693  icons.forEach(ic => { if (ic) ic.innerHTML = shown ? '&#128065;' : '&#128584;'; });
4694}
4695
4696function toggleAuthPasswordVisibility() {
4697  const input = document.getElementById('auth-password');
4698  const icon = document.getElementById('auth-eye-icon');
4699  if (!input) return;
4700  const shown = input.type === 'text';
4701  input.type = shown ? 'password' : 'text';
4702  if (icon) icon.innerHTML = shown ? '&#128065;' : '&#128584;';
4703}
4704
4705function showResetScreen() {
4706  // Hide other screens
4707  const auth = document.getElementById('auth-screen');
4708  if (auth) auth.style.display = 'none';
4709  const name = document.getElementById('name-screen');
4710  if (name) name.style.display = 'none';
4711  const hub = document.getElementById('hub-screen');
4712  if (hub) hub.style.display = 'none';
4713  const reset = document.getElementById('reset-screen');
4714  if (reset) reset.style.display = 'flex';
4715}
4716
4717async function updatePasswordSubmit() {
4718  const pw = document.getElementById('reset-password').value;
4719  const pw2 = document.getElementById('reset-password-confirm').value;
4720  const message = document.getElementById('reset-message');
4721  const btn = document.getElementById('reset-submit-btn');
4722
4723  if (!pw || pw.length < 8) {
4724    message.textContent = 'Password must be at least 8 characters.';
4725    message.className = 'auth-message show';
4726    return;
4727  }
4728  if (pw !== pw2) {
4729    message.textContent = 'Passwords do not match. Please re-type.';
4730    message.className = 'auth-message show';
4731    return;
4732  }
4733
4734  setButtonLoading(btn, true, 'Updating...');
4735
4736  try {
4737    const { error } = await sbClient.auth.updateUser({ password: pw });
4738    if (error) {
4739      message.textContent = error.message;
4740      message.className = 'auth-message show';
4741      setButtonLoading(btn, false);
4742      return;
4743    }
4744    message.textContent = 'Password updated! Taking you to the hub...';
4745    message.className = 'auth-message show success';
4746    // Get current user and proceed
4747    const { data: { user } } = await sbClient.auth.getUser();
4748    setTimeout(() => {
4749      document.getElementById('reset-screen').style.display = 'none';
4750      // Clear the recovery URL fragment
4751      if (window.history.replaceState) {
4752        window.history.replaceState(null, '', window.location.pathname);
4753      }
4754      if (user) {
4755        welcomeRitualPending = true;
4756        primeAudioContext();
4757        handleSignedIn(user);
4758      }
4759    }, 900);
4760  } catch (e) {
4761    message.textContent = 'Could not update password. Please try again.';
4762    message.className = 'auth-message show';
4763    setButtonLoading(btn, false);
4764  }
4765}
4766
4767// ============================================================
4768// AUTH (Email + Password)
4769// ============================================================
4770let authMode = 'signup'; // 'signup' or 'signin'
4771
4772function showAuthScreen() {
4773  document.getElementById('auth-screen').style.display = 'flex';
4774}
4775
4776function toggleAuthMode() {
4777  const btn = document.getElementById('auth-send-btn');
4778  const toggle = document.getElementById('auth-toggle');
4779  const subtitle = document.getElementById('auth-subtitle');
4780  const passInput = document.getElementById('auth-password');
4781
4782  if (authMode === 'signup') {
4783    authMode = 'signin';
4784    btn.textContent = 'Sign In';
4785    toggle.textContent = 'New here? Create an account';
4786    subtitle.textContent = 'Sign in to enter the mentorship hub.';
4787    passInput.placeholder = 'Your password';
4788    document.getElementById('forgot-row').style.display = 'block';
4789  } else {
4790    authMode = 'signup';
4791    btn.textContent = 'Create My Account';
4792    toggle.textContent = 'Already have an account? Sign in';
4793    subtitle.textContent = 'Create your account to enter the mentorship hub.';
4794    passInput.placeholder = 'Create a password';
4795    document.getElementById('forgot-row').style.display = 'none';
4796  }
4797
4798  // Clear messages
4799  document.getElementById('auth-message').className = 'auth-message';
4800}
4801
4802async function sendPasswordReset() {
4803  const email = document.getElementById('auth-email').value.trim();
4804  const message = document.getElementById('auth-message');
4805
4806  if (!email) {
4807    message.textContent = 'Please enter your email above first, then tap Forgot password.';
4808    message.className = 'auth-message show';
4809    return;
4810  }
4811
4812  message.textContent = 'Sending reset link...';
4813  message.className = 'auth-message show';
4814
4815  try {
4816    const { error } = await sbClient.auth.resetPasswordForEmail(email, {
4817      redirectTo: window.location.origin + '/mentorship.html'
4818    });
4819    if (error) {
4820      message.textContent = error.message;
4821      message.className = 'auth-message show';
4822    } else {
4823      message.textContent = 'Check your email for a password reset link.';
4824      message.className = 'auth-message show success';
4825    }
4826  } catch (e) {
4827    message.textContent = 'Could not send reset link. Please try again or email [email protected].';
4828    message.className = 'auth-message show';
4829  }
4830}
4831
4832async function authSubmit() {
4833  const email = document.getElementById('auth-email').value.trim();
4834  const password = document.getElementById('auth-password').value;
4835  const message = document.getElementById('auth-message');
4836  const btn = document.getElementById('auth-send-btn');
4837
4838  if (!email) {
4839    message.textContent = 'Please enter your email.';
4840    message.className = 'auth-message show';
4841    return;
4842  }
4843
4844  if (!password || password.length < 6) {
4845    message.textContent = 'Password must be at least 6 characters.';
4846    message.className = 'auth-message show';
4847    return;
4848  }
4849
4850  btn.disabled = true;
4851  const originalText = btn.textContent;
4852  btn.textContent = 'One moment...';
4853
4854  let result;
4855
4856  if (authMode === 'signup') {
4857    result = await sbClient.auth.signUp({ email, password });
4858    if (result.error) {
4859      // If user already exists, try signing in instead
4860      if (result.error.message.includes('already registered') || result.error.message.includes('already been registered')) {
4861        result = await sbClient.auth.signInWithPassword({ email, password });
4862      }
4863    }
4864  } else {
4865    result = await sbClient.auth.signInWithPassword({ email, password });
4866  }
4867
4868  if (result.error) {
4869    message.textContent = result.error.message;
4870    message.className = 'auth-message show';
4871    btn.disabled = false;
4872    btn.textContent = originalText;
4873  } else if (result.data?.user) {
4874    // Fresh sign-in: trigger the welcome ritual when the hub opens.
4875    welcomeRitualPending = true;
4876    // Pre-warm an AudioContext during this synchronous click event so
4877    // browsers will allow the chime to play later (asynchronous code
4878    // loses the user-gesture context otherwise).
4879    primeAudioContext();
4880    handleSignedIn(result.data.user);
4881  }
4882}
4883
4884// Holds the pre-warmed AudioContext for the welcome chime
4885let _welcomeAudioCtx = null;
4886function primeAudioContext() {
4887  try {
4888    if (_welcomeAudioCtx) return;
4889    _welcomeAudioCtx = new (window.AudioContext || window.webkitAudioContext)();
4890    if (_welcomeAudioCtx.state === 'suspended') {
4891      _welcomeAudioCtx.resume().catch(() => {});
4892    }
4893  } catch (e) {
4894    _welcomeAudioCtx = null;
4895  }
4896}
4897
4898document.getElementById('auth-email').addEventListener('keypress', function(e) {
4899  if (e.key === 'Enter') document.getElementById('auth-password').focus();
4900});
4901document.getElementById('auth-password').addEventListener('keypress', function(e) {
4902  if (e.key === 'Enter') authSubmit();
4903});
4904
4905// ============================================================
4906// EMOJI PICKER
4907// ============================================================
4908const emojiData = {
4909  'Favorites': ['❤️','🙏','✨','💛','🌹','💫','🦋','🔥','💜','🌸','🌿','💖','🌺','☀️','🕊️','💎'],
4910  'Feelings': ['😊','🥰','😌','😍','🤗','😢','😂','💪','🙌','👏','🤍','💕','🫶','😘','🥹','✊'],
4911  'Nature': ['🌻','🌷','🌙','⭐','🌊','🍃','🌈','🪷','🌾','🪻','🌱','🍂','💐','🪴','🌞','🏔️'],
4912  'Symbols': ['♥️','✦','◆','∞','☽','♡','⚡','🎯','📿','🕯️','🪶','🎶','📖','🗝️','🧿','🪬']
4913};
4914
4915let activeEmojiTarget = null;
4916
4917function toggleEmojiPicker(targetId) {
4918  const picker = document.getElementById('emoji-picker-' + targetId);
4919
4920  // Close any other open pickers
4921  document.querySelectorAll('.emoji-picker').forEach(p => {
4922    if (p !== picker) p.style.display = 'none';
4923  });
4924
4925  if (picker.style.display === 'none') {
4926    // Build picker content if empty
4927    if (!picker.innerHTML) {
4928      let html = '';
4929      for (const [category, emojis] of Object.entries(emojiData)) {
4930        html += `<div class="emoji-category-label">${category}</div><div class="emoji-picker-grid">`;
4931        emojis.forEach(e => {
4932          html += `<button class="emoji-btn" onclick="insertEmoji('${e}', '${targetId}')">${e}</button>`;
4933        });
4934        html += '</div>';
4935      }
4936      picker.innerHTML = html;
4937    }
4938    picker.style.display = 'block';
4939    activeEmojiTarget = targetId;
4940  } else {
4941    picker.style.display = 'none';
4942    activeEmojiTarget = null;
4943  }
4944}
4945
4946function insertEmoji(emoji, targetId) {
4947  const textarea = document.getElementById(targetId);
4948  if (!textarea) return;
4949
4950  const start = textarea.selectionStart;
4951  const end = textarea.selectionEnd;
4952  const text = textarea.value;
4953  textarea.value = text.substring(0, start) + emoji + text.substring(end);
4954  textarea.selectionStart = textarea.selectionEnd = start + emoji.length;
4955  textarea.focus();
4956}
4957
4958// Close emoji picker when clicking outside
4959document.addEventListener('click', (e) => {
4960  if (!e.target.closest('.emoji-picker') && !e.target.closest('.media-upload-btn') && !e.target.closest('.comment-emoji-btn')) {
4961    document.querySelectorAll('.emoji-picker').forEach(p => p.style.display = 'none');
4962  }
4963});
4964
4965function previewHub() {
4966  document.getElementById('auth-screen').style.display = 'none';
4967  showHub('Sister', 'teacher');
4968}
4969
4970// ============================================================
4971// USER SESSION
4972// ============================================================
4973async function handleSignedIn(user) {
4974  currentUser = user;
4975  document.getElementById('auth-screen').style.display = 'none';
4976
4977  // Fetch profile
4978  let { data: profile } = await sbClient
4979    .from('profiles')
4980    .select('*')
4981    .eq('id', user.id)
4982    .single();
4983
4984  // If the trigger didn't fire, create the profile now
4985  if (!profile) {
4986    const { data: created } = await sbClient
4987      .from('profiles')
4988      .upsert({ id: user.id, display_name: 'Sister', role: 'student' })
4989      .select()
4990      .single();
4991    profile = created;
4992  }
4993
4994  if (profile) {
4995    currentProfile = profile;
4996    sbClient.from('profiles').update({ last_login: new Date().toISOString() }).eq('id', user.id);
4997    if (profile.display_name === 'Sister') {
4998      showNameScreen();
4999    } else {
5000      showHub(profile.display_name, profile.role);
5001    }
5002  } else {
5003    currentProfile = null;
5004    showNameScreen();
5005  }
5006}
5007
5008function showNameScreen() {
5009  document.getElementById('name-screen').style.display = 'flex';
5010}
5011
5012async function saveName() {
5013  const name = document.getElementById('name-input').value.trim();
5014  if (!name) return;
5015
5016  const btn = document.querySelector('#name-screen .auth-btn');
5017  setButtonLoading(btn, true, 'One moment...');
5018
5019  if (sbClient && currentUser) {
5020    const isNew = !currentProfile || !currentProfile.id;
5021    // Upsert handles both create and update in one call
5022    const { data, error } = await sbClient
5023      .from('profiles')
5024      .upsert({ id: currentUser.id, display_name: name, role: currentProfile?.role || 'student' })
5025      .select()
5026      .single();
5027
5028    if (error) {
5029      console.error('Profile save error:', error);
5030      showToast('Could not save your name — please try again.', 'error');
5031      setButtonLoading(btn, false);
5032      return;
5033    }
5034
5035    currentProfile = data || { id: currentUser.id, display_name: name, role: 'student' };
5036
5037    if (isNew) {
5038      try {
5039        const formData = new URLSearchParams();
5040        formData.append('form-name', 'new-member-signup');
5041        formData.append('member-name', name);
5042        formData.append('member-email', currentUser.email || '');
5043        formData.append('signup-date', new Date().toLocaleString());
5044        fetch('/', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: formData.toString() }).catch(() => {});
5045      } catch(e) {}
5046    }
5047  }
5048
5049  document.getElementById('name-screen').style.display = 'none';
5050  showHub(name, currentProfile ? currentProfile.role : 'student');
5051}
5052
5053document.getElementById('name-input').addEventListener('keypress', function(e) {
5054  if (e.key === 'Enter') saveName();
5055});
5056
5057async function signOut() {
5058  if (sbClient) {
5059    await sbClient.auth.signOut();
5060  }
5061  localStorage.removeItem('mentorship_authenticated');
5062  currentUser = null;
5063  currentProfile = null;
5064  window.location.reload();
5065}
5066
5067// ============================================================
5068// HUB
5069// ============================================================
5070// ============================================================
5071// PRESENCE — "Sumir is in the room"
5072// ============================================================
5073async function togglePresence() {
5074  if (!sbClient || !currentUser || !currentProfile || currentProfile.role !== 'teacher') return;
5075
5076  const newState = !currentProfile.is_present;
5077  const { error } = await sbClient
5078    .from('profiles')
5079    .update({ is_present: newState })
5080    .eq('id', currentUser.id);
5081  if (error) {
5082    showToast('Could not update presence: ' + error.message, 'error');
5083    return;
5084  }
5085  currentProfile.is_present = newState;
5086  updatePresenceUI(newState, true);
5087  showToast(newState ? 'You are now visible as "in the room"' : 'You are now away', 'success');
5088}
5089
5090function updatePresenceUI(teacherIsPresent, viewerIsTeacher) {
5091  // Banner on home view
5092  const banner = document.getElementById('presence-banner');
5093  if (banner) banner.style.display = teacherIsPresent ? 'inline-flex' : 'none';
5094
5095  // Sidebar dot — shown next to teacher's name when she's present (visible to everyone if presence is on)
5096  const dot = document.getElementById('sidebar-presence-dot');
5097  if (dot && viewerIsTeacher) {
5098    dot.style.display = teacherIsPresent ? 'inline-block' : 'none';
5099  }
5100
5101  // Toggle button label (teacher only)
5102  const toggleBtn = document.getElementById('sidebar-presence-toggle');
5103  const toggleLabel = document.getElementById('sidebar-presence-label');
5104  if (toggleBtn && toggleLabel && viewerIsTeacher) {
5105    if (teacherIsPresent) {
5106      toggleBtn.classList.add('is-here');
5107      toggleLabel.innerHTML = '&#10084; In the Room';
5108    } else {
5109      toggleBtn.classList.remove('is-here');
5110      toggleLabel.innerHTML = '&#9825; Mark as Here';
5111    }
5112  }
5113}
5114
5115async function refreshPresence() {
5116  if (!sbClient) return;
5117  try {
5118    // Look up the teacher's current presence
5119    const { data: teacher } = await sbClient
5120      .from('profiles')
5121      .select('is_present')
5122      .eq('role', 'teacher')
5123      .limit(1)
5124      .single();
5125    const teacherIsPresent = !!(teacher && teacher.is_present);
5126    const viewerIsTeacher = currentProfile && currentProfile.role === 'teacher';
5127    updatePresenceUI(teacherIsPresent, true /* always show banner regardless of viewer */);
5128    // For dot beside teacher's name, only show if viewer IS the teacher
5129    const dot = document.getElementById('sidebar-presence-dot');
5130    if (dot) dot.style.display = (viewerIsTeacher && teacherIsPresent) ? 'inline-block' : 'none';
5131  } catch (e) {
5132    // No teacher row found yet — silently ignore
5133  }
5134}
5135
5136// ============================================================
5137// WELCOME RITUAL — gold star sparkles + soft singing-bowl chime
5138// Plays once per session right when the hub opens.
5139// ============================================================
5140function playWelcomeChime(existingCtx) {
5141  try {
5142    const ctx = existingCtx || new (window.AudioContext || window.webkitAudioContext)();
5143    // If suspended, try to resume (works inside user-gesture chains)
5144    if (ctx.state === 'suspended') {
5145      ctx.resume().catch(() => {});
5146    }
5147    // If still suspended after attempting resume, give up gracefully
5148    if (ctx.state === 'suspended' && !existingCtx) {
5149      // We don't close it — it may resume after the user clicks the sound fallback
5150      return false;
5151    }
5152    const now = ctx.currentTime;
5153
5154    // Master gain — very gentle
5155    const master = ctx.createGain();
5156    master.gain.setValueAtTime(0.00001, now);
5157    master.gain.linearRampToValueAtTime(0.025, now + 2.0);
5158    master.gain.exponentialRampToValueAtTime(0.0001, now + 22);
5159    master.connect(ctx.destination);
5160
5161    // Warm low-pass filter
5162    const filter = ctx.createBiquadFilter();
5163    filter.type = 'lowpass';
5164    filter.frequency.setValueAtTime(1800, now);
5165    filter.frequency.exponentialRampToValueAtTime(450, now + 22);
5166    filter.Q.value = 0.35;
5167    filter.connect(master);
5168
5169    // Sweet bowl partials (G3 fundamental + octave + fifth + soft inharmonic)
5170    const fundamental = 196;
5171    const partials = [
5172      { ratio: 1.0,    vol: 1.00, settle: 0.997 },
5173      { ratio: 2.0,    vol: 0.32, settle: 0.998 },
5174      { ratio: 1.5,    vol: 0.22, settle: 0.997 },
5175      { ratio: 2.76,   vol: 0.16, settle: 0.996 },
5176      { ratio: 1.005,  vol: 0.35, settle: 0.998 }
5177    ];
5178    partials.forEach((p, i) => {
5179      const osc = ctx.createOscillator();
5180      const g = ctx.createGain();
5181      osc.type = 'sine';
5182      osc.frequency.value = fundamental * p.ratio;
5183      osc.frequency.linearRampToValueAtTime(fundamental * p.ratio * p.settle, now + 16);
5184      g.gain.setValueAtTime(0.00001, now);
5185      g.gain.linearRampToValueAtTime(p.vol, now + 1.0 + i * 0.15);
5186      const decayTime = 22 - i * 1.2;
5187      g.gain.exponentialRampToValueAtTime(0.00001, now + decayTime);
5188      osc.connect(g);
5189      g.connect(filter);
5190      osc.start(now);
5191      osc.stop(now + decayTime + 0.5);
5192    });
5193    return true;
5194  } catch (e) {
5195    return false;
5196  }
5197}
5198
5199// Soft, glowing 4-pointed sparkle (built as inline SVG for proper glow + control).
5200// Two crossed elongated diamonds = classic "twinkle" shape used in animation.
5201function makeSparkleSvg(hue, lightness) {
5202  // Returns the SVG string. The gradient gives a luminous center, fading to soft gold.
5203  const id = 'g' + Math.floor(Math.random() * 1000000);
5204  return `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
5205    <defs>
5206      <radialGradient id="${id}" cx="50%" cy="50%" r="50%">
5207        <stop offset="0%" stop-color="hsl(${hue}, 100%, 92%)" stop-opacity="1" />
5208        <stop offset="40%" stop-color="hsl(${hue}, 80%, ${lightness}%)" stop-opacity="0.95" />
5209        <stop offset="100%" stop-color="hsl(${hue}, 70%, ${lightness - 10}%)" stop-opacity="0" />
5210      </radialGradient>
5211    </defs>
5212    <!-- vertical diamond -->
5213    <path d="M12 0 L13.4 10.6 L12 24 L10.6 10.6 Z" fill="url(#${id})" />
5214    <!-- horizontal diamond -->
5215    <path d="M0 12 L10.6 10.6 L24 12 L10.6 13.4 Z" fill="url(#${id})" />
5216    <!-- soft center -->
5217    <circle cx="12" cy="12" r="2.2" fill="hsl(${hue}, 100%, 95%)" opacity="0.85" />
5218  </svg>`;
5219}
5220
5221function spawnWelcomeStars() {
5222  const logo = document.getElementById('home-logo');
5223  if (!logo) return;
5224
5225  const rect = logo.getBoundingClientRect();
5226  const centerX = rect.left + rect.width / 2;
5227  const centerY = rect.top + rect.height / 2;
5228
5229  const COUNT = 95;
5230  for (let i = 0; i < COUNT; i++) {
5231    const star = document.createElement('div');
5232    // ~40% of stars go BEHIND the tiles for depth/parallax effect
5233    const isBehind = Math.random() < 0.40;
5234    star.className = isBehind ? 'welcome-star behind' : 'welcome-star';
5235
5236    // Varied sizes — behind stars tend to be smaller (depth perception)
5237    const size = isBehind
5238      ? 6 + Math.random() * 12   // behind: 6–18px (smaller, distant)
5239      : 10 + Math.random() * 16;  // front: 10–26px (larger, closer)
5240    star.style.width = size + 'px';
5241    star.style.height = size + 'px';
5242
5243    // Color variation in the gold range (HSL)
5244    const hue = 36 + Math.random() * 10; // gold-yellow range
5245    const lightness = 55 + Math.random() * 20; // 55–75
5246    star.innerHTML = makeSparkleSvg(hue, lightness);
5247
5248    // Stagger the twinkle animation so they don't pulse in sync
5249    const twinkleDelay = -Math.random() * 1.8; // negative delay = phase offset
5250    const twinkleSvg = star.querySelector('svg');
5251    if (twinkleSvg) twinkleSvg.style.animationDelay = twinkleDelay + 's';
5252
5253    // Position at the logo's center
5254    star.style.left = (centerX - size / 2) + 'px';
5255    star.style.top = (centerY - size / 2) + 'px';
5256    star.style.opacity = '0';
5257
5258    document.body.appendChild(star);
5259
5260    // Drift outward — bias upward like fairy dust
5261    const angle = (Math.random() - 0.5) * Math.PI * 1.7 - Math.PI / 2;
5262    const dist = 200 + Math.random() * 400;
5263    const dx = Math.cos(angle) * dist;
5264    const dy = Math.sin(angle) * dist;
5265
5266    // Longer overall drift + much more spread-out staggering for ethereal release
5267    const duration = 16000 + Math.random() * 9000;        // 16–25 seconds
5268    const delay = 200 + Math.random() * 7500;              // 0.2–7.7s — releases over a longer window
5269
5270    // Random rotation for sparkle variety
5271    const rot = (Math.random() - 0.5) * 360;
5272
5273    star.animate(
5274      [
5275        { transform: `translate(0, 0) scale(0.3) rotate(0deg)`, opacity: 0 },
5276        { transform: `translate(${dx * 0.10}px, ${dy * 0.10}px) scale(0.85) rotate(${rot * 0.15}deg)`, opacity: 0.85, offset: 0.10 },
5277        { transform: `translate(${dx * 0.25}px, ${dy * 0.25}px) scale(1) rotate(${rot * 0.3}deg)`, opacity: 0.95, offset: 0.22 },
5278        { transform: `translate(${dx * 0.55}px, ${dy * 0.55}px) scale(1.1) rotate(${rot * 0.55}deg)`, opacity: 0.85, offset: 0.55 },
5279        { transform: `translate(${dx * 0.78}px, ${dy * 0.78}px) scale(0.95) rotate(${rot * 0.78}deg)`, opacity: 0.55, offset: 0.78 },
5280        { transform: `translate(${dx}px, ${dy}px) scale(0.6) rotate(${rot}deg)`, opacity: 0 }
5281      ],
5282      {
5283        duration,
5284        delay,
5285        easing: 'cubic-bezier(0.18, 0.55, 0.40, 1)', // gentler easing
5286        fill: 'forwards'
5287      }
5288    );
5289
5290    setTimeout(() => star.remove(), duration + delay + 200);
5291  }
5292}
5293
5294// Module-level flag: set to true when a fresh sign-in happens.
5295// This way "page refresh with existing session" doesn't trigger the ritual,
5296// but every time someone enters their email/password, it does.
5297let welcomeRitualPending = false;
5298
5299function playWelcomeRitual() {
5300  if (!welcomeRitualPending) return;
5301  welcomeRitualPending = false;
5302
5303  // Quick start — sparkles begin almost immediately
5304  setTimeout(() => spawnWelcomeStars(), 80);
5305
5306  // Try to play the chime using the pre-warmed AudioContext (created during the
5307  // user's "Sign In" click). Only show the fallback if the browser STILL blocks audio.
5308  setTimeout(() => {
5309    const ok = playWelcomeChime(_welcomeAudioCtx);
5310    if (!ok) showSoundFallback();
5311  }, 80);
5312
5313  // Show onboarding popup (first login only)
5314  showOnboardingPopup();
5315}
5316
5317// ============================================================
5318// ONBOARDING POPUP — gentle nudge toward the sidebar
5319// Shows once per user (tracked in localStorage), auto-dismisses
5320// after 10 seconds or when user clicks a sidebar link.
5321// ============================================================
5322function showOnboardingPopup() {
5323  const storageKey = 'fr_onboarding_seen';
5324  if (localStorage.getItem(storageKey)) return;
5325
5326  // Create overlay container
5327  const overlay = document.createElement('div');
5328  overlay.className = 'onboarding-overlay';
5329  overlay.innerHTML = `
5330    <div class="onboarding-popup">
5331      <div class="onboarding-popup-title">Explore Your Private Mentorship Portal</div>
5332      <div class="onboarding-popup-text">Take a look at all of the options on the left-hand side &mdash; each one has been lovingly prepared for you.</div>
5333    </div>
5334  `;
5335
5336  // Create animated arrow
5337  const arrow = document.createElement('div');
5338  arrow.className = 'onboarding-arrow';
5339  arrow.innerHTML = `<svg viewBox="0 0 24 24"><path d="M19 12H5M5 12l6-6M5 12l6 6"/></svg>`;
5340
5341  document.body.appendChild(overlay);
5342  document.body.appendChild(arrow);
5343
5344  // Fade in the arrow in sync with the overlay
5345  arrow.style.opacity = '0';
5346  arrow.style.animation = 'none';
5347  setTimeout(() => {
5348    arrow.style.transition = 'opacity 0.8s ease';
5349    arrow.style.opacity = '1';
5350    arrow.style.animation = 'arrowBounce 1.2s ease-in-out infinite';
5351  }, 6000);
5352
5353  function dismiss() {
5354    localStorage.setItem(storageKey, 'true');
5355    // Kill the animation so the transition can take over
5356    overlay.style.animation = 'none';
5357    overlay.style.opacity = '1';
5358    // Force reflow, then fade out
5359    void overlay.offsetWidth;
5360    overlay.style.transition = 'opacity 1.2s ease';
5361    overlay.style.opacity = '0';
5362    arrow.style.transition = 'opacity 1.2s ease';
5363    arrow.style.opacity = '0';
5364    setTimeout(() => {
5365      overlay.remove();
5366      arrow.remove();
5367    }, 1400);
5368    // Clean up listener
5369    document.querySelectorAll('.sidebar-link').forEach(link => {
5370      link.removeEventListener('click', dismiss);
5371    });
5372    clearTimeout(autoTimer);
5373  }
5374
5375  // Auto-dismiss: visible by 7.5s, stays 7 more seconds = 14.5s total
5376  const autoTimer = setTimeout(dismiss, 14500);
5377
5378  // Dismiss on sidebar click
5379  document.querySelectorAll('.sidebar-link').forEach(link => {
5380    link.addEventListener('click', dismiss);
5381  });
5382}
5383
5384// Centered, more visible "tap for sound" pill shown only if browser blocked the chime
5385function showSoundFallback() {
5386  if (document.getElementById('welcome-sound-toggle')) return;
5387  const btn = document.createElement('button');
5388  btn.id = 'welcome-sound-toggle';
5389  btn.setAttribute('aria-label', 'Tap for soft chime');
5390  btn.innerHTML = '<span style="font-size:18px;">&#128266;</span> &nbsp;Tap for soft chime';
5391  btn.style.cssText = 'position:fixed;top:24px;left:50%;transform:translateX(-50%);padding:11px 22px;border-radius:24px;background:rgba(255,255,255,0.95);border:1px solid var(--gold);color:var(--berry);font-family:Marcellus,serif;font-size:11px;letter-spacing:1.5px;text-transform:uppercase;cursor:pointer;z-index:200;display:flex;align-items:center;justify-content:center;transition:all 0.3s;box-shadow:0 4px 20px rgba(148,14,75,0.2);animation:soundPillPulse 2s ease infinite;';
5392  btn.onclick = function() {
5393    playWelcomeChime();
5394    btn.style.transition = 'all 0.4s ease';
5395    btn.style.opacity = '0';
5396    btn.style.transform = 'translateX(-50%) translateY(-10px)';
5397    setTimeout(() => btn.remove(), 400);
5398  };
5399  document.body.appendChild(btn);
5400  // Inject a pulse keyframe if not present
5401  if (!document.getElementById('sound-pill-keyframes')) {
5402    const style = document.createElement('style');
5403    style.id = 'sound-pill-keyframes';
5404    style.textContent = '@keyframes soundPillPulse { 0%,100% { box-shadow: 0 4px 20px rgba(148,14,75,0.2); } 50% { box-shadow: 0 4px 28px rgba(148,14,75,0.4); } }';
5405    document.head.appendChild(style);
5406  }
5407  // Auto-fade after 18 seconds (longer than before so she has time to notice)
5408  setTimeout(() => {
5409    if (!btn.parentNode) return;
5410    btn.style.opacity = '0';
5411    setTimeout(() => btn.remove(), 1200);
5412  }, 18000);
5413}
5414
5415function showHub(displayName, role) {
5416  document.getElementById('password-screen').style.display = 'none';
5417  document.getElementById('auth-screen').style.display = 'none';
5418  document.getElementById('name-screen').style.display = 'none';
5419  document.getElementById('hub').style.display = 'block';
5420
5421  document.getElementById('sidebar-user-name-text').textContent = displayName;
5422
5423  // Set greeting: "Welcome" on first visit, "Welcome back" on return visits
5424  // Use a flag to avoid double-setting when onAuthStateChange re-triggers showHub
5425  const greetingKey = 'fr_has_visited';
5426  const greetingEl = document.getElementById('home-greeting');
5427  if (greetingEl && !window._greetingSet) {
5428    window._greetingSet = true;
5429    if (localStorage.getItem(greetingKey)) {
5430      greetingEl.textContent = 'Welcome back';
5431    } else {
5432      greetingEl.textContent = 'Welcome';
5433    }
5434    // Mark as visited AFTER setting the greeting, with a delay to avoid race conditions
5435    setTimeout(() => localStorage.setItem(greetingKey, 'true'), 3000);
5436  }
5437
5438  // Set composer avatar
5439  const avatarEl = document.getElementById('composer-avatar');
5440  if (avatarEl) avatarEl.textContent = displayName.charAt(0).toUpperCase();
5441
5442  // Show composer for all authenticated users
5443  document.getElementById('post-composer').style.display = 'block';
5444
5445  // Hide teacher-only post types (Teaching, Announcement) for students, default to Reflection
5446  if (role !== 'teacher') {
5447    document.querySelectorAll('.teacher-only-option').forEach(opt => opt.style.display = 'none');
5448    document.getElementById('compose-type').value = 'reflection';
5449  }
5450
5451  // Show members link, CRM link, and harvest composer for teachers
5452  const membersLink = document.getElementById('sidebar-members-link');
5453  if (membersLink && role === 'teacher') membersLink.style.display = '';
5454  const crmLink = document.getElementById('sidebar-crm-link');
5455  if (crmLink && role === 'teacher') crmLink.style.display = '';
5456
5457  // Show presence toggle (teacher only) and check current presence state for everyone
5458  if (role === 'teacher') {
5459    const presenceToggle = document.getElementById('sidebar-presence-toggle');
5460    if (presenceToggle) presenceToggle.style.display = 'block';
5461  }
5462  refreshPresence();
5463  // Refresh presence every 60 seconds for everyone
5464  if (window._presenceTimer) clearInterval(window._presenceTimer);
5465  window._presenceTimer = setInterval(refreshPresence, 60000);
5466
5467  const harvestComposer = document.getElementById('harvest-composer');
5468  if (harvestComposer && role === 'teacher') harvestComposer.style.display = 'block';
5469
5470  // Check for hash-based routing
5471  const hash = window.location.hash.replace('#', '');
5472  if (hash && ['home', 'feed', 'about', 'teachings', 'replays', 'journal', 'harvest', 'sc
5472hedule', 'tools', 'members'].includes(hash)) {
5473    switchView(hash);
5474  } else {
5475    switchView('home');
5476  }
5477
5478  // Welcome ritual: gold star sparkles + soft chime when entering the hub
5479  // (only fires once per session, only on the home view)
5480  if (!hash || hash === 'home') {
5481    playWelcomeRitual();
5482  }
5483
5484  // Load feed
5485  loadFeed();
5486  setupRealtime();
5487}
5488
5489// ============================================================
5490// VIEW SWITCHING
5491// ============================================================
5492const VIEW_TITLES = {
5493  feed: ['Private Mentorship', 'Community'],
5494  about: ['Private Mentorship', 'About'],
5495  teachings: ['Private Mentorship', 'Teachings & Transmissions'],
5496  replays: ['Private Mentorship', 'Replays'],
5497  home: ['Private Mentorship', ''],
5498  journal: ['Private Mentorship', 'Your Wisdom Journal'],
5499  schedule: ['Private Mentorship', 'Schedule'],
5500  tools: ['Private Mentorship', 'Tools & Guides'],
5501  harvest: ['Private Mentorship', 'Monthly Harvest'],
5502  members: ['Private Mentorship', 'Members & Analytics']
5503};
5504
5505function switchView(viewName) {
5506  // Update sidebar
5507  document.querySelectorAll('.sidebar-link').forEach(link => {
5508    link.classList.toggle('active', link.dataset.view === viewName);
5509  });
5510
5511  // Update views
5512  document.querySelectorAll('.view').forEach(view => {
5513    view.classList.remove('active');
5514  });
5515  document.getElementById('view-' + viewName).classList.add('active');
5516
5517  // Update header
5518  const [eyebrow, title] = VIEW_TITLES[viewName] || ['Private Mentorship', viewName];
5519  document.getElementById('header-eyebrow').textContent = eyebrow;
5520  document.getElementById('header-title').textContent = title;
5521
5522  // Hide header on home view
5523  const headerEl = document.querySelector('.content-header');
5524  if (headerEl) headerEl.style.display = viewName === 'home' ? 'none' : 'block';
5525
5526  // Update home greeting name
5527  if (viewName === 'home') {
5528    const name = document.getElementById('sidebar-user-name-text').textContent || 'Sister';
5529    document.getElementById('home-user-name').textContent = name;
5530  }
5531
5532  // Update URL hash
5533  window.location.hash = viewName;
5534
5535  // Scroll to top
5536  window.scrollTo(0, 0);
5537
5538  // Close mobile sidebar
5539  document.querySelector('.sidebar').classList.remove('open');
5540  document.querySelector('.sidebar-overlay').classList.remove('show');
5541
5542  // Load view-specific data
5543  if (viewName === 'teachings') loadTeachings();
5544  if (viewName === 'journal') { loadJournal(); loadInquiryResponses(); }
5545  if (viewName === 'replays') loadReplays();
5546  if (viewName === 'harvest') loadHarvests();
5547  if (viewName === 'members') loadMembers();
5548}
5549
5550function toggleSidebar() {
5551  document.querySelector('.sidebar').classList.toggle('open');
5552  document.querySelector('.sidebar-overlay').classList.toggle('show');
5553}
5554
5555// ============================================================
5556// COMMUNITY FEED
5557// ============================================================
5558async function loadFeed() {
5559  const container = document.getElementById('feed-container');
5560
5561  if (!sbClient) {
5562    container.innerHTML = renderDemoFeed();
5563    return;
5564  }
5565
5566  const { data: posts, error } = await sbClient
5567    .from('posts')
5568    .select(`
5569      *,
5570      author:profiles!posts_author_id_fkey(display_name, role),
5571      reflections(
5572        *,
5573        author:profiles!reflections_author_id_fkey(display_name, role),
5574        hearts(user_id),
5575        reflection_reactions(id, user_id, emoji)
5576      ),
5577      hearts(user_id),
5578      post_reactions(id, user_id, emoji)
5579    `)
5580    .order('created_at', { ascending: false });
5581
5582  if (error) {
5583    container.innerHTML = '<p style="text-align:center;color:var(--berry);">Unable to load feed.</p>';
5584    return;
5585  }
5586
5587  if (!posts || posts.length === 0) {
5588    container.innerHTML = `
5589      <div class="feed-empty">
5590        <div class="feed-empty-icon">&#10022;</div>
5591        <p class="feed-empty-text">The circle is quiet for now.</p>
5592      </div>`;
5593    return;
5594  }
5595
5596  container.innerHTML = posts.map(post => renderPost(post)).join('');
5597
5598  // Show Hidden filter button for teachers
5599  const hiddenBtn = document.getElementById('feed-hidden-btn');
5600  if (hiddenBtn && currentProfile && currentProfile.role === 'teacher') {
5601    const hiddenCount = posts.filter(p => p.hidden_from_feed).length;
5602    if (hiddenCount > 0) {
5603      hiddenBtn.style.display = '';
5604      hiddenBtn.textContent = 'Hidden (' + hiddenCount + ')';
5605    } else {
5606      hiddenBtn.style.display = 'none';
5607    }
5608  }
5609
5610  // Apply filters to hide hidden posts by default
5611  applyFeedFilters();
5612
5613  // Preload voice note audio as blobs so play works instantly
5614  preloadVoiceNotes();
5615
5616  // Teacher-only: offer to convert any legacy WebM notes to MP3
5617  checkLegacyVoiceNotes();
5618}
5619
5620function renderPost(post) {
5621  const authorName = post.author?.display_name || 'Sister';
5622  const isTeacher = post.author?.role === 'teacher';
5623  const date = new Date(post.created_at).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
5624  const heartCount = post.hearts ? post.hearts.length : 0;
5625  const isHearted = post.hearts && currentUser ? post.hearts.some(h => h.user_id === currentUser.id) : false;
5626  const isOwner = currentUser && post.author_id === currentUser.id;
5627  const isCurrentTeacher = currentProfile && currentProfile.role === 'teacher';
5628  const typeLabel = post.post_type === 'prompt' ? 'Journal Prompt'
5629    : post.post_type === 'announcement' ? '✦ Announcement'
5630    : post.post_type === 'reflection' ? 'Reflection'
5631    : post.post_type === 'voice_note' ? '🎤 Voice Note'
5632    : 'Teaching';
5633
5634  const reflections = post.reflections || [];
5635  reflections.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
5636
5637  // Media HTML — voice notes get a special inline player
5638  let mediaHtml = '';
5639  if (post.media_url) {
5640    if (post.media_type === 'audio' || post.post_type === 'voice_note') {
5641      const vnId = 'vn-' + post.id;
5642      mediaHtml = `<div class="voice-note-player" id="${vnId}" data-url="${post.media_url}">
5643        <button class="vn-play-btn" onclick="vnToggle('${vnId}')" aria-label="Play voice note">&#9654;&#xFE0E;</button>
5644        <div class="vn-controls">
5645          <div class="voice-note-label">Voice Note from ${escapeHtml(authorName)}</div>
5646          <div class="vn-progress-wrap" onclick="vnSeek(event,'${vnId}')">
5647            <div class="vn-progress-track">
5648              <div class="vn-progress-bar"></div>
5649            </div>
5650          </div>
5651          <div class="vn-time-row">
5652            <span class="vn-elapsed">0:00</span>
5653            <span class="vn-remaining">0:00</span>
5654          </div>
5655        </div>
5656      </div>`;
5657    } else if (post.media_type === 'video') {
5658      mediaHtml = `<div class="post-media"><video controls controlslist="nodownload nofullscreen noremoteplayback" disablepictureinpicture oncontextmenu="return false;" preload="metadata" src="${post.media_url}#t=0.001"></video></div>`;
5659    } else {
5660      mediaHtml = `<div class="post-media"><img src="${post.media_url}" alt="Post media" oncontextmenu="return false;" draggable="false"></div>`;
5661    }
5662  }
5663
5664  // Build reactions strip + picker
5665  const postReactionsHtml = renderReactionsRow(post.post_reactions || [], 'post', post.id);
5666
5667  // Don't show the placeholder body for voice-note-only posts
5668  const showBody = post.body && post.body !== '(voice note)';
5669
5670  let html = `
5671    <div class="post-card" data-post-id="${post.id}" data-post-type="${post.post_type}" data-hidden="${!!post.hidden_from_feed}">
5672      <div class="post-type-label">${typeLabel}</div>
5673      ${post.title ? `<div class="post-title">${escapeHtml(post.title)}</div>` : ''}
5674      ${showBody ? `<div class="post-body">${escapeHtml(post.body)}</div>` : ''}
5675      ${mediaHtml}
5676      ${postReactionsHtml}
5677      <div class="post-meta">
5678        <div class="post-author">
5679          <span class="post-author-name">${escapeHtml(authorName)}</span>
5680          <span class="post-date">${date}</span>
5681        </div>
5682        <div style="display:flex;align-items:center;gap:8px;">
5683          ${isOwner ? `<button class="post-edit-btn" onclick="editPost('${post.id}', this)">Edit</button>` : ''}
5684          ${isCurrentTeacher ? `<button class="post-hide-btn" onclick="toggleHideFromFeed('${post.id}', ${!!post.hidden_from_feed})">${post.hidden_from_feed ? '&#9670; Show in Feed' : '&#9671; Hide from Feed'}</button>` : ''}
5685          <button class="heart-btn ${isHearted ? 'hearted' : ''}" onclick="toggleHeart('${post.id}', 'post')" aria-label="${isHearted ? 'Remove heart' : 'Send heart'}" aria-pressed="${isHearted}">
5686            <span class="heart-icon" aria-hidden="true">${isHearted ? '&#9829;' : '&#9825;'}</span>
5687            <span class="heart-count" aria-label="${heartCount} hearts">${heartCount || ''}</span>
5688          </button>
5689          ${(isOwner || isCurrentTeacher) ? `<button class="post-delete" onclick="deletePost('${post.id}')" title="Delete">&times;</button>` : ''}
5690        </div>
5691      </div>
5692
5693      <!-- Comments -->
5694      <div class="reflections-section">
5695        <div class="reflections-bar">
5696          <button class="reflections-toggle" onclick="toggleReflections('${post.id}')">
5697            ${reflections.length} ${reflections.length === 1 ? 'comment' : 'comments'} &#9662;
5698          </button>
5699          <button class="reply-btn" onclick="openReply('${post.id}')">Reply</button>
5700        </div>
5701        <div class="reflections-list" id="reflections-${post.id}" style="display:none;">
5702          ${(() => {
5703            const COMMENT_LIMIT = 10;
5704            if (reflections.length <= COMMENT_LIMIT) {
5705              return reflections.map(r => renderReflection(r)).join('');
5706            }
5707            const hiddenCount = reflections.length - COMMENT_LIMIT;
5708            const visible = reflections.slice(-COMMENT_LIMIT);
5709            const hidden = reflections.slice(0, hiddenCount);
5710            return `
5711              <button class="show-more-comments" onclick="showAllComments('${post.id}')" id="show-more-${post.id}">
5712                &#9662; Show ${hiddenCount} earlier comment${hiddenCount === 1 ? '' : 's'}
5713              </button>
5714              <div id="hidden-comments-${post.id}" style="display:none;">${hidden.map(r => renderReflection(r)).join('')}</div>
5715              ${visible.map(r => renderReflection(r)).join('')}`;
5716          })()}
5717          <div class="reflection-composer">
5718            <textarea class="reflection-textarea" id="reflection-input-${post.id}" placeholder="Write a comment..."></textarea>
5719            <div id="comment-media-preview-${post.id}" class="media-preview" style="display:none;"></div>
5720            <input type="file" id="comment-media-input-${post.id}" accept="image/*,video/*" style="display:none;" onchange="handleCommentMediaSelect(this, '${post.id}')">
5721            <div style="display:flex;align-items:center;justify-content:space-between;margin-top:6px;">
5722              <div style="display:flex;align-items:center;gap:4px;">
5723                <button class="comment-media-btn" onclick="document.getElementById('comment-media-input-${post.id}').click()" title="Add photo or video">&#128247;</button>
5724                <div style="position:relative;display:inline-block;">
5725                  <button class="comment-media-btn comment-emoji-btn" onclick="toggleEmojiPicker('reflection-input-${post.id}')" title="Add emoji">&#128522;</button>
5726                  <div class="emoji-picker" id="emoji-picker-reflection-input-${post.id}" style="display:none;"></div>
5727                </div>
5728              </div>
5729              <button class="reflection-submit" onclick="submitReflection('${post.id}')">Share</button>
5730            </div>
5731          </div>
5732        </div>
5733      </div>
5734    </div>
5735    <div class="feed-divider"><span>&#9830;&#xFE0E;</span></div>`;
5736
5737  return html;
5738}
5739
5740async function toggleHideFromFeed(postId, currentlyHidden) {
5741  if (!sbClient) return;
5742  const { error } = await sbClient
5743    .from('posts')
5744    .update({ hidden_from_feed: !currentlyHidden })
5745    .eq('id', postId);
5746  if (error) {
5747    console.error('Error toggling hide from feed:', error);
5748    return;
5749  }
5750  const activeView = window.location.hash.replace('#', '') || 'home';
5751  if (activeView === 'teachings') {
5752    loadTeachings();
5753  } else {
5754    loadFeed();
5755  }
5756}
5757
5758function renderReflection(r) {
5759  const name = r.author?.display_name || 'Sister';
5760  const date = new Date(r.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
5761  const isOwner = currentUser && r.author_id === currentUser.id;
5762  const isCurrentTeacher = currentProfile && currentProfile.role === 'teacher';
5763
5764  // Hearts on comments
5765  const rHearts = r.hearts || [];
5766  const rHeartCount = rHearts.length;
5767  const rIsHearted = rHearts.some(h => h.user_id === currentUser?.id);
5768
5769  let rMediaHtml = '';
5770  if (r.media_url) {
5771    if (r.media_type === 'video') {
5772      rMediaHtml = `<div class="post-media" style="margin:8px 0;"><video controls controlslist="nodownload nofullscreen noremoteplayback" disablepictureinpicture oncontextmenu="return false;" preload="metadata" src="${r.media_url}#t=0.001" style="max-height:200px;"></video></div>`;
5773    } else {
5774      rMediaHtml = `<div class="post-media" style="margin:8px 0;"><img src="${r.media_url}" alt="" style="max-height:200px;" oncontextmenu="return false;" draggable="false"></div>`;
5775    }
5776  }
5777
5778  // Emoji reactions on this reflection
5779  const reflectionReactionsHtml = renderReactionsRow(r.reflection_reactions || [], 'reflection', r.id);
5780
5781  return `
5782    <div class="reflection-card" data-reflection-id="${r.id}">
5783      <div class="reflection-body">${escapeHtml(r.body)}</div>
5784      ${rMediaHtml}
5785      ${reflectionReactionsHtml}
5786      <div class="reflection-meta">
5787        <span class="reflection-author">${escapeHtml(name)}</span>
5788        <span style="display:flex;align-items:center;gap:8px;">
5789          <button class="heart-btn ${rIsHearted ? 'hearted' : ''}" onclick="toggleHeart('${r.id}', 'reflection')" style="font-size:12px;padding:2px 4px;" aria-label="${rIsHearted ? 'Remove heart' : 'Send heart'}" aria-pressed="${rIsHearted}">
5790            <span class="heart-icon" aria-hidden="true">${rIsHearted ? '&#9829;' : '&#9825;'}</span>
5791            <span class="heart-count" aria-label="${rHeartCount} hearts">${rHeartCount > 0 ? rHeartCount : ''}</span>
5792          </button>
5793          <button class="post-edit-btn" onclick="replyToComment('${r.post_id}', '${escapeHtml(name)}')" style="font-size:8px;">Reply</button>
5794          ${isOwner ? `<button class="post-edit-btn" onclick="editComment('${r.id}', '${r.post_id}', this)" style="font-size:8px;">Edit</button>` : ''}
5795          <span class="reflection-date">${date}</span>
5796          ${(isOwner || isCurrentTeacher) ? `<button class="post-delete" onclick="deleteReflection('${r.id}', '${r.post_id}')" title="Delete" style="font-size:12px;">&times;</button>` : ''}
5797        </span>
5798      </div>
5799    </div>`;
5800}
5801
5802// Shared reactions renderer for posts and reflections
5803const SISTER_EMOJIS = ['❤️', '✨', '🙏', '💛', '🤗'];
5804
5805function renderReactionsRow(reactions, type, targetId) {
5806  // type is 'post' or 'reflection' — used to call the right toggle function
5807  const fnName = type === 'post' ? 'togglePostReaction' : 'toggleReflectionReaction';
5808
5809  // Group by emoji
5810  const grouped = {};
5811  reactions.forEach(r => {
5812    if (!grouped[r.emoji]) grouped[r.emoji] = [];
5813    grouped[r.emoji].push(r);
5814  });
5815
5816  const chips = Object.keys(grouped).map(emoji => {
5817    const list = grouped[emoji];
5818    const mine = currentUser && list.some(r => r.user_id === currentUser.id);
5819    return `<button class="sister-reaction-chip ${mine ? 'mine' : ''}" onclick="${fnName}('${targetId}','${emoji}')" aria-label="${list.length} ${emoji} reaction${list.length === 1 ? '' : 's'}${mine ? ', tap to remove yours' : ', tap to add yours'}" aria-pressed="${mine}"><span aria-hidden="true">${emoji}</span><span class="sister-reaction-count" aria-hidden="true">${list.length}</span></button>`;
5820  }).join('');
5821
5822  const picker = `<span class="sister-reaction-picker" onclick="event.stopPropagation();">${SISTER_EMOJIS.map(em => `<button class="sister-reaction-add" onclick="${fnName}('${targetId}','${em}')" aria-label="React with ${em}"><span aria-hidden="true">${em}</span></button>`).join('')}</span>`;
5823
5824  return `<div class="sister-reactions-row">${chips}${picker}</div>`;
5825}
5826
5827async function togglePostReaction(postId, emoji) {
5828  if (!sbClient || !currentUser) return;
5829  const { data: existing } = await sbClient
5830    .from('post_reactions')
5831    .select('id')
5832    .eq('post_id', postId)
5833    .eq('user_id', currentUser.id)
5834    .eq('emoji', emoji);
5835  if (existing && existing.length > 0) {
5836    await sbClient.from('post_reactions').delete().eq('id', existing[0].id);
5837  } else {
5838    await sbClient.from('post_reactions').insert({ post_id: postId, user_id: currentUser.id, emoji });
5839  }
5840  await loadFeed();
5841}
5842
5843async function toggleReflectionReaction(reflectionId, emoji) {
5844  if (!sbClient || !currentUser) return;
5845  const { data: existing } = await sbClient
5846    .from('reflection_reactions')
5847    .select('id')
5848    .eq('reflection_id', reflectionId)
5849    .eq('user_id', currentUser.id)
5850    .eq('emoji', emoji);
5851  if (existing && existing.length > 0) {
5852    await sbClient.from('reflection_reactions').delete().eq('id', existing[0].id);
5853  } else {
5854    await sbClient.from('reflection_reactions').insert({ reflection_id: reflectionId, user_id: currentUser.id, emoji });
5855  }
5856  await loadFeed();
5857}
5858
5859function toggleReflections(postId) {
5860  const el = document.getElementById('reflections-' + postId);
5861  el.style.display = el.style.display === 'none' ? 'block' : 'none';
5862}
5863
5864function showAllComments(postId) {
5865  const hidden = document.getElementById('hidden-comments-' + postId);
5866  const btn = document.getElementById('show-more-' + postId);
5867  if (hidden) hidden.style.display = 'block';
5868  if (btn) btn.style.display = 'none';
5869}
5870
5871function toggleComposer() {
5872  const expanded = document.getElementById('composer-expanded');
5873  expanded.classList.toggle('open');
5874  if (expanded.classList.contains('open')) {
5875    document.getElementById('compose-body').focus();
5876  }
5877}
5878
5879function closeComposer() {
5880  document.getElementById('composer-expanded').classList.remove('open');
5881  document.getElementById('compose-title').value = '';
5882  document.getElementById('compose-body').value = '';
5883  clearMediaPreview();
5884}
5885
5886function openReply(postId) {
5887  // Open the comments section and focus the reply input
5888  const el = document.getElementById('reflections-' + postId);
5889  el.style.display = 'block';
5890  const input = document.getElementById('reflection-input-' + postId);
5891  if (input) input.focus();
5892}
5893
5894function replyToComment(postId, authorName) {
5895  // Open comments, pre-fill with @name
5896  const el = document.getElementById('reflections-' + postId);
5897  el.style.display = 'block';
5898  const input = document.getElementById('reflection-input-' + postId);
5899  if (input) {
5900    input.value = '@' + authorName + ' ';
5901    input.focus();
5902  }
5903}
5904
5905// Demo feed when Supabase isn't configured
5906function renderDemoFeed() {
5907  return `
5908    <div class="post-card">
5909      <div class="post-type-label">Teaching</div>
5910      <div class="post-title">
5910Welcome to Your Private Circle</div>
5911      <div class="post-body">This is your sacred community space. Once Supabase is connected, you'll be able to post teachings, and your students will share comments right here in real-time.
5912
5913Each teaching you share will appear as an elegant card. Students respond beneath with their own comments. A gold heart lets them quietly say: I see you. I'm with you.
5914
5915This is not social media. This is sitting in circle.</div>
5916      <div class="post-meta">
5917        <div class="post-author">
5918          <span class="post-author-name">Sumir</span>
5919          <span class="post-date">April 15, 2026</span>
5920        </div>
5921        <div>
5922          <button class="heart-btn hearted">
5923            <span class="heart-icon">&#9829;</span>
5924            <span class="heart-count">3</span>
5925          </button>
5926        </div>
5927      </div>
5928      <div class="reflections-section">
5929        <button class="reflections-toggle" onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'">
5930          2 comments &#9662;
5931        </button>
5932        <div class="reflections-list" style="display:none;">
5933          <div class="reflection-card">
5934            <div class="reflection-body">This space already feels different. Like a room that was waiting for us. Thank you for creating this.</div>
5935            <div class="reflection-meta">
5936              <span class="reflection-author">Sarah</span>
5937              <span class="reflection-date">Apr 15</span>
5938            </div>
5939          </div>
5940          <div class="reflection-card">
5941            <div class="reflection-body">I felt something shift just reading this. Looking forward to what unfolds here.</div>
5942            <div class="reflection-meta">
5943              <span class="reflection-author">Maria</span>
5944              <span class="reflection-date">Apr 15</span>
5945            </div>
5946          </div>
5947        </div>
5948      </div>
5949    </div>
5950    <div class="feed-divider"><span>&#9830;&#xFE0E;</span></div>`;
5951}
5952
5953// ============================================================
5954// FEED FILTER & SEARCH
5955// ============================================================
5956let currentFilter = 'all';
5957let currentSearch = '';
5958
5959function filterFeed(type) {
5960  currentFilter = type;
5961  // Update active button
5962  document.querySelectorAll('.feed-filter-btn').forEach(btn => {
5963    btn.classList.toggle('active', btn.dataset.filter === type);
5964  });
5965  applyFeedFilters();
5966}
5967
5968function searchFeed(query) {
5969  currentSearch = query.toLowerCase().trim();
5970  const clearBtn = document.getElementById('feed-search-clear');
5971  clearBtn.classList.toggle('show', currentSearch.length > 0);
5972  applyFeedFilters();
5973}
5974
5975function clearSearch() {
5976  document.getElementById('feed-search-input').value = '';
5977  currentSearch = '';
5978  document.getElementById('feed-search-clear').classList.remove('show');
5979  applyFeedFilters();
5980}
5981
5982function applyFeedFilters() {
5983  const posts = document.querySelectorAll('#feed-container .post-card');
5984  const dividers = document.querySelectorAll('#feed-container .feed-divider');
5985  let visibleCount = 0;
5986
5987  posts.forEach((post, i) => {
5988    const postType = post.dataset.postType || '';
5989    const title = post.querySelector('.post-title');
5990    const body = post.querySelector('.post-body');
5991    const author = post.querySelector('.post-author-name');
5992
5993    // Check filter
5994    const isHidden = post.dataset.hidden === 'true';
5995    let matchesFilter = false;
5996    if (currentFilter === 'hidden') {
5997      matchesFilter = isHidden;
5998    } else if (currentFilter === 'all') {
5999      matchesFilter = !isHidden;
6000    } else {
6001      matchesFilter = !isHidden && postType === currentFilter;
6002    }
6003
6004    // Check search
6005    let matchesSearch = true;
6006    if (currentSearch) {
6007      const searchText = [
6008        title ? title.textContent : '',
6009        body ? body.textContent : '',
6010        author ? author.textContent : ''
6011      ].join(' ').toLowerCase();
6012
6013      // Also search reflections within the post
6014      const reflections = post.querySelectorAll('.reflection-body');
6015      reflections.forEach(r => { searchText + ' ' + r.textContent.toLowerCase(); });
6016
6017      matchesSearch = searchText.includes(currentSearch);
6018    }
6019
6020    const visible = matchesFilter && matchesSearch;
6021    post.style.display = visible ? '' : 'none';
6022    if (dividers[i]) dividers[i].style.display = visible ? '' : 'none';
6023    if (visible) visibleCount++;
6024  });
6025
6026  // Show/hide empty state
6027  const emptyEls = document.querySelectorAll('#feed-container .feed-empty');
6028  if (visibleCount === 0 && posts.length > 0) {
6029    // All posts filtered out — show a message
6030    let noResults = document.getElementById('feed-no-results');
6031    if (!noResults) {
6032      noResults = document.createElement('div');
6033      noResults.id = 'feed-no-results';
6034      noResults.className = 'feed-empty';
6035      noResults.innerHTML = '<div class="feed-empty-icon">&#10022;</div><p class="feed-empty-text" style="font-style:italic;">No posts match your search.</p>';
6036      document.getElementById('feed-container').appendChild(noResults);
6037    }
6038    noResults.style.display = '';
6039  } else {
6040    const noResults = document.getElementById('feed-no-results');
6041    if (noResults) noResults.style.display = 'none';
6042  }
6043}
6044
6045// ============================================================
6046// MEDIA UPLOAD
6047// ============================================================
6048let pendingMediaFile = null;
6049let pendingCommentMedia = {}; // keyed by postId
6050let pendingVoiceNoteFile = null; // holds the recorded voice note Blob until posted
6051
6052// ============================================================
6053// VOICE NOTE RECORDER (5 min max)
6054// ============================================================
6055const VOICE_NOTE_MAX_SECONDS = 300; // 5 minutes
6056let vrMediaRecorder = null;
6057let vrChunks = [];
6058let vrStartTime = 0;
6059let vrTimerInterval = null;
6060let vrStream = null;
6061// MP3 recorder state (Web Audio + lamejs — plays on every browser incl. iOS)
6062let vrAudioCtx = null;
6063let vrSourceNode = null;
6064let vrProcessor = null;
6065let vrMp3Encoder = null;
6066let vrMp3Data = [];
6067let vrRecording = false;
6068let vrRecordedBlob = null;
6069
6070function openVoiceRecorder() {
6071  // Reset state
6072  vrChunks = [];
6073  vrRecordedBlob = null;
6074  document.getElementById('vr-mic-icon').innerHTML = '&#127908;';
6075  document.getElementById('vr-mic-btn').classList.remove('recording');
6076  document.getElementById('vr-title').textContent = 'Speak from the heart';
6077  document.getElementById('vr-subtitle').textContent = 'Tap the microphone to begin — up to 5 minutes';
6078  document.getElementById('vr-timer').textContent = '0:00';
6079  document.getElementById('vr-hint').textContent = '5 minutes maximum';
6080  document.getElementById('vr-preview').style.display = 'none';
6081  document.getElementById('vr-rerecord').style.display = 'none';
6082  document.getElementById('vr-use').style.display = 'none';
6083  document.getElementById('voice-recorder-modal').style.display = 'flex';
6084}
6085
6086function vrTeardownGraph() {
6087  // Disconnect the Web Audio graph and release the microphone
6088  try { if (vrProcessor) { vrProcessor.disconnect(); vrProcessor.onaudioprocess = null; } } catch (e) {}
6089  try { if (vrSourceNode) vrSourceNode.disconnect(); } catch (e) {}
6090  vrProcessor = null;
6091  vrSourceNode = null;
6092  if (vrStream) {
6093    vrStream.getTracks().forEach(t => t.stop());
6094    vrStream = null;
6095  }
6096  vrRecording = false;
6097}
6098
6099function closeVoiceRecorder() {
6100  // Stop any active recording / release mic
6101  if (vrRecording) { try { stopMp3Recording(); } catch (e) {} }
6102  vrTeardownGraph();
6103  if (vrTimerInterval) { clearInterval(vrTimerInterval); vrTimerInterval = null; }
6104  document.getElementById('voice-recorder-modal').style.display = 'none';
6105}
6106
6107function resetVoiceRecorder() {
6108  vrChunks = [];
6109  vrRecordedBlob = null;
6110  openVoiceRecorder();
6111}
6112
6113async function toggleRecording() {
6114  if (vrRecording) {
6115    stopMp3Recording();
6116    return;
6117  }
6118
6119  // Encoder must be available (lame.min.js loaded)
6120  if (typeof lamejs === 'undefined' || !lamejs.Mp3Encoder) {
6121    showToast('Recorder is still loading — please try again in a moment.', 'error');
6122    return;
6123  }
6124
6125  // Create/resume the AudioContext synchronously inside this click so iOS
6126  // Safari allows microphone capture (it loses the user gesture after await).
6127  try {
6128    if (!vrAudioCtx) {
6129      vrAudioCtx = new (window.AudioContext || window.webkitAudioContext)();
6130    }
6131    if (vrAudioCtx.state === 'suspended') { await vrAudioCtx.resume(); }
6132  } catch (e) {
6133    showToast('Audio is not supported on this browser.', 'error');
6134    return;
6135  }
6136
6137  // Request the microphone
6138  try {
6139    vrStream = await navigator.mediaDevices.getUserMedia({
6140      audio: { echoCancellation: true, noiseSuppression: true, channelCount: 1 }
6141    });
6142  } catch (e) {
6143    showToast('Microphone access denied. Please allow microphone permission and try again.', 'error');
6144    return;
6145  }
6146
6147  const sampleRate = vrAudioCtx.sampleRate; // typically 44100 or 48000
6148  vrMp3Encoder = new lamejs.Mp3Encoder(1, sampleRate, 96); // mono, 96kbps — small + clear for voice
6149  vrMp3Data = [];
6150
6151  vrSourceNode = vrAudioCtx.createMediaStreamSource(vrStream);
6152  // ScriptProcessorNode has the widest support (incl. older iOS) for raw PCM capture
6153  vrProcessor = vrAudioCtx.createScriptProcessor(4096, 1, 1);
6154
6155  vrProcessor.onaudioprocess = (ev) => {
6156    if (!vrRecording) return;
6157    const input = ev.inputBuffer.getChannelData(0); // Float32 [-1,1]
6158    const samples = new Int16Array(input.length);
6159    for (let i = 0; i < input.length; i++) {
6160      let s = Math.max(-1, Math.min(1, input[i]));
6161      samples[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
6162    }
6163    const mp3buf = vrMp3Encoder.encodeBuffer(samples);
6164    if (mp3buf.length > 0) vrMp3Data.push(new Int8Array(mp3buf));
6165  };
6166
6167  vrSourceNode.connect(vrProcessor);
6168  // Connect to destination through a muted gain so the graph runs without echo
6169  const silentGain = vrAudioCtx.createGain();
6170  silentGain.gain.value = 0;
6171  vrProcessor.connect(silentGain);
6172  silentGain.connect(vrAudioCtx.destination);
6173
6174  vrRecording = true;
6175  vrStartTime = Date.now();
6176  document.getElementById('vr-mic-btn').classList.add('recording');
6177  document.getElementById('vr-mic-icon').innerHTML = '&#9632;'; // stop square
6178  document.getElementById('vr-title').textContent = 'Recording…';
6179  document.getElementById('vr-subtitle').textContent = 'Tap again to stop';
6180
6181  vrTimerInterval = setInterval(() => {
6182    const elapsed = Math.floor((Date.now() - vrStartTime) / 1000);
6183    document.getElementById('vr-timer').textContent = formatDuration(elapsed);
6184    if (elapsed >= VOICE_NOTE_MAX_SECONDS) {
6185      stopMp3Recording();
6186      showToast('Voice note reached the 5-minute maximum.', 'success');
6187    }
6188  }, 300);
6189}
6190
6191function stopMp3Recording() {
6192  if (!vrRecording) return;
6193  vrRecording = false;
6194  if (vrTimerInterval) { clearInterval(vrTimerInterval); vrTimerInterval = null; }
6195
6196  // Flush any remaining MP3 data
6197  try {
6198    const end = vrMp3Encoder.flush();
6199    if (end.length > 0) vrMp3Data.push(new Int8Array(end));
6200  } catch (e) {}
6201
6202  // Stop the graph + mic
6203  vrTeardownGraph();
6204
6205  // Assemble the final MP3 blob
6206  vrRecordedBlob = new Blob(vrMp3Data, { type: 'audio/mpeg' });
6207  vrMp3Data = [];
6208  vrMp3Encoder = null;
6209
6210  const url = URL.createObjectURL(vrRecordedBlob);
6211  const playback = document.getElementById('vr-playback');
6212  playback.src = url;
6213  document.getElementById('vr-preview').style.display = 'block';
6214  document.getElementById('vr-mic-btn').classList.remove('recording');
6215  document.getElementById('vr-mic-icon').innerHTML = '&#127908;';
6216  document.getElementById('vr-title').textContent = 'Listen back';
6217  document.getElementById('vr-subtitle').textContent = 'Use this recording, or re-record.';
6218  document.getElementById('vr-rerecord').style.display = 'inline-block';
6219  document.getElementById('vr-use').style.display = 'inline-block';
6220}
6221
6222function formatDuration(seconds) {
6223  const m = Math.floor(seconds / 60);
6224  const s = seconds % 60;
6225  return m + ':' + (s < 10 ? '0' : '') + s;
6226}
6227
6228function attachVoiceNote() {
6229  if (!vrRecordedBlob) return;
6230
6231  // Turn the Blob into a File so uploadMedia can handle it (always MP3 now)
6232  const filename = `voice-note-${Date.now()}.mp3`;
6233  pendingVoiceNoteFile = new File([vrRecordedBlob], filename, { type: 'audio/mpeg' });
6234
6235  // Show the preview row in the composer
6236  const previewEl = document.getElementById('voice-note-preview');
6237  const audioEl = document.getElementById('voice-note-audio');
6238  audioEl.src = URL.createObjectURL(vrRecordedBlob);
6239  const durationEl = document.getElementById('voice-note-duration');
6240
6241  audioEl.onloadedmetadata = () => {
6242    const dur = isFinite(audioEl.duration) ? Math.round(audioEl.duration) : 0;
6243    durationEl.textContent = 'Voice note attached &middot; ' + formatDuration(dur);
6244    // Force re-render
6245    durationEl.innerHTML = durationEl.textContent;
6246  };
6247  previewEl.style.display = 'block';
6248
6249  closeVoiceRecorder();
6250  showToast('Voice note attached. Add a title or text if you\'d like, then tap Share.', 'success');
6251}
6252
6253function clearVoiceNote() {
6254  pendingVoiceNoteFile = null;
6255  document.getElementById('voice-note-preview').style.display = 'none';
6256  document.getElementById('voice-note-audio').src = '';
6257  document.getElementById('voice-note-duration').textContent = '';
6258}
6259
6260async function uploadMedia(file) {
6261  const ext = file.name.split('.').pop();
6262  const path = `${currentUser.id}/${Date.now()}.${ext}`;
6263
6264  const { data, error } = await sbClient.storage
6265    .from('Media')
6266    .upload(path, file, {
6267      cacheControl: '3600',
6268      upsert: false
6269    });
6270
6271  if (error) {
6272    console.error('Upload error:', error);
6273    showToast('Media upload failed: ' + error.message, 'error');
6274    return null;
6275  }
6276
6277  const { data: urlData } = sbClient.storage
6278    .from('Media')
6279    .getPublicUrl(path);
6280
6281  console.log('Upload success. URL:', urlData.publicUrl);
6282
6283  return {
6284    url: urlData.publicUrl,
6285    type: file.type.startsWith('audio')
6286      ? 'audio'
6287      : (file.type.startsWith('video') ? 'video' : 'image')
6288  };
6289}
6290
6291function handleMediaSelect(input) {
6292  const file = input.files[0];
6293  if (!file) return;
6294
6295  pendingMediaFile = file;
6296  const preview = document.getElementById('compose-media-preview');
6297  preview.style.display = 'block';
6298
6299  if (file.type.startsWith('video')) {
6300    const url = URL.createObjectURL(file);
6301    preview.innerHTML = `<video src="${url}" style="max-height:200px;border-radius:8px;" controls></video><button class="media-preview-remove" onclick="clearMediaPreview()">&times;</button>`;
6302  } else {
6303    const reader = new FileReader();
6304    reader.onload = (e) => {
6305      preview.innerHTML = `<img src="${e.target.result}"><button class="media-preview-remove" onclick="clearMediaPreview()">&times;</button>`;
6306    };
6307    reader.readAsDataURL(file);
6308  }
6309}
6310
6311function clearMediaPreview() {
6312  pendingMediaFile = null;
6313  const preview = document.getElementById('compose-media-preview');
6314  preview.style.display = 'none';
6315  preview.innerHTML = '';
6316  document.getElementById('compose-media-input').value = '';
6317}
6318
6319function handleCommentMediaSelect(input, postId) {
6320  const file = input.files[0];
6321  if (!file) return;
6322
6323  pendingCommentMedia[postId] = file;
6324  const preview = document.getElementById('comment-media-preview-' + postId);
6325  preview.style.display = 'block';
6326
6327  if (file.type.startsWith('video')) {
6328    const url = URL.createObjectURL(file);
6329    preview.innerHTML = `<video src="${url}" style="max-height:150px;border-radius:8px;" controls></video><button class="media-preview-remove" onclick="clearCommentMedia('${postId}')">&times;</button>`;
6330  } else {
6331    const reader = new FileReader();
6332    reader.onload = (e) => {
6333      preview.innerHTML = `<img src="${e.target.result}" style="max-height:150px;"><button class="media-preview-remove" onclick="clearCommentMedia('${postId}')">&times;</button>`;
6334    };
6335    reader.readAsDataURL(file);
6336  }
6337}
6338
6339function clearCommentMedia(postId) {
6340  delete pendingCommentMedia[postId];
6341  const preview = document.getElementById('comment-media-preview-' + postId);
6342  preview.style.display = 'none';
6343  preview.innerHTML = '';
6344  document.getElementById('comment-media-input-' + postId).value = '';
6345}
6346
6347// ============================================================
6348// FEED ACTIONS
6349// ============================================================
6350async function createPost() {
6351  if (!sbClient || !currentUser) return;
6352
6353  const title = document.getElementById('compose-title').value.trim();
6354  const body = document.getElementById('compose-body').value.trim();
6355  const type = document.getElementById('compose-type').value;
6356
6357  // Voice notes can post without typed body text
6358  const hasVoiceNote = !!pendingVoiceNoteFile;
6359  if (!body && !hasVoiceNote) return;
6360
6361  const btn = document.getElementById('compose-submit');
6362  btn.disabled = true;
6363  btn.textContent = 'Sharing...';
6364
6365  try {
6366    let mediaUrl = null;
6367    let mediaType = null;
6368
6369    // Voice note takes priority (it's a different kind of media)
6370    if (hasVoiceNote) {
6371      const result = await uploadMedia(pendingVoiceNoteFile);
6372      if (result) {
6373        mediaUrl = result.url;
6374        mediaType = 'audio';
6375      } else {
6376        btn.disabled = false;
6377        btn.textContent = 'Share';
6378        return;
6379      }
6380    } else if (pendingMediaFile) {
6381      const result = await uploadMedia(pendingMediaFile);
6382      if (result) {
6383        mediaUrl = result.url;
6384        mediaType = result.type;
6385      } else {
6386        btn.disabled = false;
6387        btn.textContent = 'Share';
6388        return;
6389      }
6390    }
6391
6392    const postData = {
6393      author_id: currentUser.id,
6394      title: title || null,
6395      // If no typed body but voice note present, supply a placeholder so the NOT NULL column is happy
6396      body: body || (hasVoiceNote ? '(voice note)' : ''),
6397      // Mark as voice_note so the feed renders it specially
6398      post_type: hasVoiceNote ? 'voice_note' : type
6399    };
6400    if (mediaUrl) {
6401      postData.media_url = mediaUrl;
6402      postData.media_type = mediaType;
6403    }
6404
6405    const { error } = await sbClient.from('posts').insert(postData);
6406
6407    if (error) {
6408      console.error('Post error:', error);
6409      showToast('Could not share: ' + error.message, 'error');
6410    } else {
6411      document.getElementById('compose-title').value = '';
6412      document.getElementById('compose-body').value = '';
6413      clearMediaPreview();
6414      clearVoiceNote();
6415      closeComposer();
6416      await loadFeed();
6417    }
6418  } catch (e) {
6419    console.error('Post exception:', e);
6420    showToast('Something went wrong: ' + e.message, 'error');
6421  }
6422
6423  btn.disabled = false;
6424  document.getElementById('compose-submit').textContent = 'Share';
6425}
6426
6427async function deletePost(postId) {
6428  if (!sbClient) return;
6429  showConfirmModal('Remove this post?', async () => {
6430    await sbClient.from('posts').delete().eq('id', postId);
6431    await loadFeed();
6432  });
6433}
6434
6435async function submitReflection(postId) {
6436  if (!sbClient || !currentUser) return;
6437
6438  const input = document.getElementById('reflection-input-' + postId);
6439  const body = input.value.trim();
6440  if (!body) return;
6441
6442  // Find the Share button next to this input
6443  const btn = input.parentElement?.querySelector('.reflection-submit') || input.closest('.reflection-input-row')?.querySelector('.reflection-submit');
6444  setButtonLoading(btn, true, 'Sharing...');
6445
6446  try {
6447    let mediaUrl = null;
6448    let mediaType = null;
6449
6450    if (pendingCommentMedia[postId]) {
6451      const result = await uploadMedia(pendingCommentMedia[postId]);
6452      if (result) {
6453        mediaUrl = result.url;
6454        mediaType = result.type;
6455      }
6456      clearCommentMedia(postId);
6457    }
6458
6459    const commentData = {
6460      post_id: postId,
6461      author_id: currentUser.id,
6462      body
6463    };
6464    if (mediaUrl) {
6465      commentData.media_url = mediaUrl;
6466      commentData.media_type = mediaType;
6467    }
6468
6469    const { error } = await sbClient.from('reflections').insert(commentData);
6470    if (error) throw error;
6471
6472    input.value = '';
6473    await loadFeed();
6474    setTimeout(() => {
6475      const el = document.getElementById('reflections-' + postId);
6476      if (el) el.style.display = 'block';
6477    }, 100);
6478  } catch (e) {
6479    showToast('Could not share comment: ' + (e.message || e), 'error');
6480  } finally {
6481    setButtonLoading(btn, false);
6482  }
6483}
6484
6485// ============================================================
6486// EDIT POSTS & COMMENTS
6487// ============================================================
6488// Branded toast notification (replaces alert)
6489function showToast(message, type) {
6490  let container = document.getElementById('fr-toast-container');
6491  if (!container) {
6492    container = document.createElement('div');
6493    container.id = 'fr-toast-container';
6494    container.className = 'fr-toast-container';
6495    document.body.appendChild(container);
6496  }
6497  const toast = document.createElement('div');
6498  toast.className = 'fr-toast ' + (type || '');
6499  toast.textContent = message;
6500  container.appendChild(toast);
6501  // Animate in
6502  requestAnimationFrame(() => toast.classList.add('show'));
6503  // Auto-dismiss
6504  setTimeout(() => {
6505    toast.classList.remove('show');
6506    setTimeout(() => toast.remove(), 350);
6507  }, 4200);
6508}
6509
6510// Toggle a button into a loading state and back
6511function setButtonLoading(btn, loading, loadingText) {
6512  if (!btn) return;
6513  if (loading) {
6514    btn.disabled = true;
6515    if (!btn.dataset.originalText) btn.dataset.originalText = btn.textContent;
6516    btn.textContent = loadingText || 'One moment...';
6517    btn.classList.add('is-loading');
6518  } else {
6519    btn.disabled = false;
6520    if (btn.dataset.originalText) {
6521      btn.textContent = btn.dataset.originalText;
6522      delete btn.dataset.originalText;
6523    }
6524    btn.classList.remove('is-loading');
6525  }
6526}
6527
6528function showConfirmModal(message, onConfirm) {
6529  const overlay = document.createElement('div');
6530  overlay.className = 'edit-overlay';
6531  overlay.innerHTML = `
6532    <div class="edit-modal" style="text-align:center;max-width:360px;">
6533      <div style="font-size:28px;color:var(--gold);margin-bottom:14px;">&#10022;</div>
6534      <p style="font-family:'Cormorant Garamond',serif;font-size:18px;color:var(--charcoal);margin-bottom:24px;">${message}</p>
6535      <div class="edit-modal-actions" style="justify-content:center;">
6536        <button class="edit-modal-cancel" onclick="this.closest('.edit-overlay').remove()">Cancel</button>
6537        <button class="edit-modal-save" id="confirm-yes-btn" style="background:var(--berry);">Remove</button>
6538      </div>
6539    </div>`;
6540  document.body.appendChild(overlay);
6541  overlay.querySelector('#confirm-yes-btn').addEventListener('click', async () => {
6542    overlay.remove();
6543    await onConfirm();
6544  });
6545}
6546
6547function editPost(postId, btn) {
6548  const card = btn.closest('.post-card');
6549  const bodyEl = card.querySelector('.post-body');
6550  const currentText = bodyEl.textContent;
6551
6552  const overlay = document.createElement('div');
6553  overlay.className = 'edit-overlay';
6554  overlay.innerHTML = `
6555    <div class="edit-modal">
6556      <div class="edit-modal-title">Edit Post</div>
6557      <textarea id="edit-textarea">${escapeHtml(currentText)}</textarea>
6558      <div class="edit-modal-actions">
6559        <button class="edit-modal-cancel" onclick="this.closest('.edit-overlay').remove()">Cancel</button>
6560        <button class="edit-modal-save" onclick="savePostEdit('${postId}', this)">Save</button>
6561      </div>
6562    </div>`;
6563  document.body.appendChild(overlay);
6564  overlay.querySelector('textarea').focus();
6565}
6566
6567async function savePostEdit(postId, btn) {
6568  const overlay = btn.closest('.edit-overlay');
6569  const newBody = overlay.querySelector('textarea').value.trim();
6570  if (!newBody) return;
6571
6572  btn.textContent = 'Saving...';
6573  await sbClient.from('posts').update({ body: newBody }).eq('id', postId);
6574  overlay.remove();
6575  await loadFeed();
6576}
6577
6578function editComment(reflectionId, postId, btn) {
6579  const card = btn.closest('.reflection-card');
6580  const bodyEl = card.querySelector('.reflection-body');
6581  const currentText = bodyEl.textContent;
6582
6583  const overlay = document.createElement('div');
6584  overlay.className = 'edit-overlay';
6585  overlay.innerHTML = `
6586    <div class="edit-modal">
6587      <div class="edit-modal-title">Edit Comment</div>
6588      <textarea id="edit-textarea">${escapeHtml(currentText)}</textarea>
6589      <div class="edit-modal-actions">
6590        <button class="edit-modal-cancel" onclick="this.closest('.edit-overlay').remove()">Cancel</button>
6591        <button class="edit-modal-save" onclick="saveCommentEdit('${reflectionId}', '${postId}', this)">Save</button>
6592      </div>
6593    </div>`;
6594  document.body.appendChild(overlay);
6595  overlay.querySelector('textarea').focus();
6596}
6597
6598async function saveCommentEdit(reflectionId, postId, btn) {
6599  const overlay = btn.closest('.edit-overlay');
6600  const newBody = overlay.querySelector('textarea').value.trim();
6601  if (!newBody) return;
6602
6603  btn.textContent = 'Saving...';
6604  await sbClient.from('reflections').update({ body: newBody }).eq('id', reflectionId);
6605  overlay.remove();
6606  await loadFeed();
6607  setTimeout(() => {
6608    const el = document.getElementById('reflections-' + postId);
6609    if (el) el.style.display = 'block';
6610  }, 100);
6611}
6612
6613async function deleteReflection(reflectionId, postId) {
6614  if (!sbClient) return;
6615  showConfirmModal('Remove this comment?', async () => {
6616    await sbClient.from('reflections').delete().eq('id', reflectionId);
6617    await loadFeed();
6618    setTimeout(() => {
6619      const el = document.getElementById('reflections-' + postId);
6620      if (el) el.style.display = 'block';
6621    }, 100);
6622  });
6623}
6624
6625async function toggleHeart(targetId, type) {
6626  if (!sbClient || !currentUser) return;
6627
6628  const btn = event.currentTarget;
6629  const isHearted = btn.classList.contains('hearted');
6630
6631  // Optimistic UI
6632  btn.classList.toggle('hearted');
6633  btn.classList.add('pulse');
6634  setTimeout(() => btn.classList.remove('pulse'), 300);
6635
6636  const icon = btn.querySelector('.heart-icon');
6637  const count = btn.querySelector('.heart-count');
6638  const currentCount = parseInt(count.textContent) || 0;
6639
6640  if (isHearted) {
6641    icon.innerHTML = '&#9825;';
6642    count.textContent = currentCount - 1 > 0 ? currentCount - 1 : '';
6643    await sbClient.from('hearts').delete().match({
6644      user_id: currentUser.id,
6645      [type === 'post' ? 'post_id' : 'reflection_id']: targetId
6646    });
6647  } else {
6648    icon.innerHTML = '&#9829;';
6649    count.textContent = currentCount + 1;
6650    await sbClient.from('hearts').insert({
6651      user_id: currentUser.id,
6652      [type === 'post' ? 'post_id' : 'reflection_id']: targetId
6653    });
6654  }
6655}
6656
6657// ============================================================
6658// TEACHINGS VIEW (filtered feed)
6659// ============================================================
6660async function loadTeachings() {
6661  const container = document.getElementById('teachings-container');
6662  const toolbar = document.getElementById('teachings-toolbar');
6663
6664  if (!sbClient) {
6665    toolbar.style.display = 'none';
6666    container.innerHTML = '<div class="placeholder-view"><div class="placeholder-icon">&#10022;</div><div class="placeholder-title">Teachings & Transmissions</div><p class="placeholder-text" style="margin-top:6px;font-style:italic;color:var(--plum);">Yours and Mine.</p></div>';
6667    return;
6668  }
6669
6670  const { data: posts } = await sbClient
6671    .from('posts')
6672    .select(`*, author:profiles!posts_author_id_fkey(display_name, role), hearts(user_id), post_reactions(id, user_id, emoji)`)
6673    .in('post_type', ['teaching', 'announcement'])
6674    .order('created_at', { ascending: false });
6675
6676  if (!posts || posts.length === 0) {
6677    toolbar.style.display = 'none';
6678    container.innerHTML = '<div class="placeholder-view"><div class="placeholder-icon">&#10022;</div><div class="placeholder-title">Teachings & Transmissions</div><p class="placeholder-text" style="margin-top:6px;font-style:italic;color:var(--plum);">Yours and Mine.</p></div>';
6679    return;
6680  }
6681
6682  toolbar.style.display = 'flex';
6683  container.innerHTML = posts.map(post => {
6684    const date = new Date(post.created_at).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
6685    const typeLabel = post.post_type === 'prompt' ? 'Journal Prompt' : 'Teaching';
6686    return `
6687      <div class="teachings-card post-card" data-type="${post.post_type}" style="cursor:pointer;" onclick="switchView('feed')">
6688        <div class="post-type-label">${typeLabel}</div>
6689        ${post.title ? `<div class="post-title">${escapeHtml(post.title)}</div>` : ''}
6690        <div class="post-body" style="max-height:100px;overflow:hidden;">${escapeHtml(post.body)}</div>
6691        <div class="post-meta">
6692          <div class="post-author">
6693            <span class="post-author-name">${escapeHtml(post.author?.display_name || 'Sumir')}</span>
6694            <span class="post-date">${date}</span>
6695          </div>
6696        </div>
6697      </div>`;
6698  }).join('');
6699}
6700
6701// Teachings filter & search
6702let teachingsFilter = 'all';
6703let teachingsSearch = '';
6704
6705function filterTeachings(type) {
6706  teachingsFilter = type;
6707  document.querySelectorAll('[data-tfilter]').forEach(btn => {
6708    btn.classList.toggle('active', btn.dataset.tfilter === type);
6709  });
6710  applyTeachingsFilters();
6711}
6712
6713function searchTeachings(query) {
6714  teachingsSearch = query.toLowerCase().trim();
6715  document.getElementById('teachings-search-clear').classList.toggle('show', teachingsSearch.length > 0);
6716  applyTeachingsFilters();
6717}
6718
6719function clearTeachingsSearch() {
6720  document.getElementById('teachings-search-input').value = '';
6721  teachingsSearch = '';
6722  document.getElementById('teachings-search-clear').classList.remove('show');
6723  applyTeachingsFilters();
6724}
6725
6726function applyTeachingsFilters() {
6727  const cards = document.querySelectorAll('#teachings-container .teachings-card');
6728  let visibleCount = 0;
6729
6730  cards.forEach(card => {
6731    const type = card.dataset.type;
6732    const title = card.querySelector('.post-title');
6733    const body = card.querySelector('.post-body');
6734
6735    const matchesFilter = teachingsFilter === 'all' || type === teachingsFilter;
6736
6737    let matchesSearch = true;
6738    if (teachingsSearch) {
6739      const text = [(title ? title.textContent : ''), (body ? body.textContent : '')].join(' ').toLowerCase();
6740      matchesSearch = text.includes(teachingsSearch);
6741    }
6742
6743    const visible = matchesFilter && matchesSearch;
6744    card.style.display = visible ? '' : 'none';
6745    if (visible) visibleCount++;
6746  });
6747
6748  // No results message
6749  let noResults = document.getElementById('teachings-no-results');
6750  if (visibleCount === 0 && cards.length > 0) {
6751    if (!noResults) {
6752      noResults = document.createElement('div');
6753      noResults.id = 'teachings-no-results';
6754      noResults.className = 'feed-empty';
6755      noResults.innerHTML = '<div class="feed-empty-icon">&#10022;</div><p class="feed-empty-text" style="font-style:italic;">No teachings match your search.</p>';
6756      document.getElementById('teachings-container').appendChild(noResults);
6757    }
6758    noResults.style.display = '';
6759  } else if (noResults) {
6760    noResults.style.display = 'none';
6761  }
6762}
6763
6764// ============================================================
6765// JOURNAL
6766// ============================================================
6767async function loadJournal() {
6768  if (!sbClient || !currentUser) return;
6769
6770  // Load prompts (posts with type 'prompt')
6771  const { data: prompts } = await sbClient
6772    .from('posts')
6773    .select('*')
6774    .eq('post_type', 'prompt')
6775    .order('created_at', { ascending: false })
6776    .limit(3);
6777
6778  const promptsContainer = document.getElementById('journal-prompts');
6779  // Journal prompts are hidden from mentees until Sumir is ready to use them
6780  const showPrompts = currentProfile && currentProfile.role === 'teacher';
6781  if (showPrompts && prompts && prompts.length > 0) {
6782    promptsContainer.innerHTML = prompts.map(p => `
6783      <div class="journal-prompt-card">
6784        <div class="journal-prompt-label">Journal Prompt</div>
6785        <div class="journal-prompt-text">${escapeHtml(p.body)}</div>
6786      </div>`).join('');
6787  } else {
6788    promptsContainer.innerHTML = '';
6789  }
6790
6791  // Load user's own journal entries (with reactions + replies for shared ones)
6792  const { data: ownEntries } = await sbClient
6793    .from('journal_entries')
6794    .select('*, journal_reactions(id, user_id, emoji), journal_replies(id, user_id, body, created_at, author:profiles!journal_replies_user_id_fkey(display_name))')
6795    .eq('user_id', currentUser.id)
6796    .order('created_at', { ascending: false });
6797
6798  // If teacher, also load public entries from students
6799  let studentEntries = [];
6800  if (currentProfile && currentProfile.role === 'teacher') {
6801    const { data: pubEntries } = await sbClient
6802      .from('journal_entries')
6803      .select('*, author:profiles!journal_entries_user_id_fkey(display_name), journal_reactions(id, user_id, emoji), journal_replies(id, user_id, body, created_at, author:profiles!journal_replies_user_id_fkey(display_name))')
6804      .eq('visibility', 'public')
6805      .neq('user_id', currentUser.id)
6806      .order('created_at', { ascending: false });
6807    studentEntries = pubEntries || [];
6808  }
6809
6810  const entriesContainer = document.getElementById('journal-entries');
6811  const printTrigger = document.getElementById('journal-print-trigger');
6812  const toolbar = document.getElementById('journal-toolbar');
6813  const allEntries = ownEntries || [];
6814
6815  if (allEntries.length > 0 || studentEntries.length > 0) {
6816    if (toolbar) toolbar.style.display = 'flex';
6817    if (printTrigger) printTrigger.style.display = 'flex';
6818    let html = '';
6819
6820    // Own entries (filter out inquiry responses — they show in the inquiry banner)
6821    const visibleEntries = allEntries.filter(e => !e.body.startsWith('::thoughtful-inquiries::'));
6822    if (visibleEntries.length > 0) {
6823      html += visibleEntries.map(e => {
6824        const date = new Date(e.created_at).toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' });
6825        const vis = e.visibility || 'private';
6826        const isDraft = !!e.is_draft;
6827        const visLabel = isDraft ? '&#9998; Draft' : (vis === 'public' ? '&#128065; Shared with Sumir' : '&#128274; Private');
6828        const visClass = isDraft ? 'private' : (vis === 'public' ? 'public' : 'private');
6829        const toggleVisLabel = vis === 'public' ? 'Make Private' : 'Share with Sumir';
6830        const toggleVisValue = vis === 'public' ? 'private' : 'public';
6831        const threadHtml = (!isDraft && vis === 'public') ? renderJournalThread(e) 
6831: '';
6832        const draftBadge = isDraft ? '<span class="journal-draft-badge">Draft</span>' : '';
6833        const publishBtn = isDraft ? `<button class="journal-entry-action-btn vis-btn" onclick="event.stopPropagation();publishDraft('${e.id}')" style="border-color:var(--berry);color:var(--berry);">&#10022; Publish</button>` : `<button class="journal-entry-action-btn vis-btn" onclick="event.stopPropagation();toggleJournalVisibility('${e.id}','${toggleVisValue}')">◆ ${toggleVisLabel}</button>`;
6834        return `
6835          <div class="journal-entry-card" data-entry-body="${escapeHtml(e.body).toLowerCase()}" data-entry-date="${date.toLowerCase()}" data-is-draft="${isDraft}" onclick="toggleJournalEntry(this, event)">
6836            <div style="display:flex;align-items:center;flex-wrap:wrap;">
6837              <div class="journal-entry-date">${date}</div>
6838              <span class="journal-entry-visibility ${visClass}">${visLabel}</span>
6839              ${draftBadge}
6840            </div>
6841            <div class="journal-entry-body truncated">${escapeHtml(e.body)}</div>
6842            <div class="journal-entry-actions">
6843              <button class="journal-entry-action-btn edit-btn" onclick="event.stopPropagation();editJournalEntry('${e.id}','${vis}')">✎ Edit</button>
6844              ${publishBtn}
6845              <button class="journal-entry-action-btn delete-btn" onclick="event.stopPropagation();deleteJournalEntry('${e.id}')">✕ Delete</button>
6846            </div>
6847            ${threadHtml}
6848            <div class="journal-entry-collapse-hint">tap to close</div>
6849          </div>`;
6850      }).join('');
6851    }
6852
6853    // Student entries (teacher view)
6854    if (studentEntries.length > 0) {
6855      // Separate inquiry responses from regular entries
6856      const inquiryEntries = studentEntries.filter(e => e.body.startsWith('::thoughtful-inquiries::'));
6857      const regularStudentEntries = studentEntries.filter(e => !e.body.startsWith('::thoughtful-inquiries::'));
6858
6859      // Render inquiry responses first
6860      if (inquiryEntries.length > 0) {
6861        html += '<div style="margin-top:30px;padding-top:20px;border-top:2px solid var(--gold-pale);"><div class="journal-compose-label" style="margin-bottom:16px;color:var(--berry);">Thoughtful Inquiry Responses</div>';
6862        html += inquiryEntries.map(e => {
6863          const date = new Date(e.created_at).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
6864          const authorName = e.author?.display_name || 'Sister';
6865          const threadHtml = renderJournalThread(e);
6866          let formattedBody = '';
6867          try {
6868            const responses = JSON.parse(e.body.replace('::thoughtful-inquiries::', ''));
6869            const labels = {
6870              q1: 'What are you hoping to experience?',
6871              q2: 'What are you prepared to release and transform?',
6872              q3: 'What are you prepared to offer to this space?',
6873              q4_emotional: 'Emotional', q4_energetic: 'Energetic', q4_spiritual: 'Spiritual',
6874              q4_practical: 'Practical', q4_physical: 'Physical', q4_facilitator: 'Facilitator Experience',
6875              q5_excited: 'Most excited about', q5_fearful: 'Most fearful/nervous about'
6876            };
6877            // Group responses
6878            const groups = {};
6879            Object.entries(responses).forEach(([key, val]) => {
6880              const prefix = key.replace(/_\d+$/, '');
6881              if (!groups[prefix]) groups[prefix] = [];
6882              groups[prefix].push(val);
6883            });
6884            Object.entries(groups).forEach(([prefix, vals]) => {
6885              const label = labels[prefix] || prefix;
6886              formattedBody += '<div style="margin-bottom:12px;"><span style="font-family:Marcellus,serif;font-size:10px;letter-spacing:1.5px;text-transform:uppercase;color:var(--berry);">' + escapeHtml(label) + '</span>';
6887              vals.forEach(v => { formattedBody += '<div style="padding-left:12px;font-size:13px;line-height:1.7;opacity:0.88;">&#9830;&#xFE0E; ' + escapeHtml(v) + '</div>'; });
6888              formattedBody += '</div>';
6889            });
6890          } catch (err) { formattedBody = escapeHtml(e.body); }
6891          return `
6892          <div class="journal-entry-card" style="border-left:3px solid var(--berry);" onclick="toggleJournalEntry(this, event)">
6893            <div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;">
6894              <div class="journal-entry-date">${escapeHtml(authorName)} &mdash; ${date}</div>
6895              <span style="font-family:'Marcellus',serif;font-size:8px;letter-spacing:1.5px;padding:2px 7px;border-radius:8px;background:rgba(148,14,75,0.1);color:var(--berry);">INQUIRY</span>
6896            </div>
6897            <div class="journal-entry-body truncated">${formattedBody}</div>
6898            ${threadHtml}
6899            <div class="journal-entry-collapse-hint">tap to close</div>
6900          </div>`;
6901        }).join('');
6902        html += '</div>';
6903      }
6904
6905      // Render regular shared entries
6906      if (regularStudentEntries.length > 0) {
6907        html += '<div style="margin-top:30px;padding-top:20px;border-top:2px solid var(--gold-pale);"><div class="journal-compose-label" style="margin-bottom:16px;color:var(--berry);">Shared With You</div>';
6908        html += regularStudentEntries.map(e => {
6909          const date = new Date(e.created_at).toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' });
6910          const authorName = e.author?.display_name || 'Sister';
6911          const threadHtml = renderJournalThread(e);
6912          return `
6913          <div class="journal-entry-card" style="border-left:3px solid var(--berry);" onclick="toggleJournalEntry(this, event)">
6914            <div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;">
6915              <div class="journal-entry-date">${date}</div>
6916              <span style="font-family:'Marcellus',serif;font-size:9px;letter-spacing:1px;color:var(--berry);">${escapeHtml(authorName)}</span>
6917            </div>
6918            <div class="journal-entry-body truncated">${escapeHtml(e.body)}</div>
6919            ${threadHtml}
6920            <div class="journal-entry-collapse-hint">tap to close</div>
6921          </div>`;
6922        }).join('');
6923        html += '</div>';
6924      }
6925    }
6926
6927    entriesContainer.innerHTML = html;
6928  } else {
6929    if (toolbar) toolbar.style.display = 'none';
6930    if (printTrigger) printTrigger.style.display = 'none';
6931    entriesContainer.innerHTML = '';
6932  }
6933}
6934
6935// Search across journal entries (own entries only, by date and body text)
6936function filterJournalEntries(query) {
6937  const q = (query || '').trim().toLowerCase();
6938  const clearBtn = document.getElementById('journal-search-clear');
6939  if (clearBtn) clearBtn.style.display = q ? 'block' : 'none';
6940
6941  const cards = document.querySelectorAll('#journal-entries .journal-entry-card');
6942  let visible = 0;
6943  cards.forEach(card => {
6944    if (!q) {
6945      card.style.display = '';
6946      visible++;
6947      return;
6948    }
6949    const body = card.dataset.entryBody || '';
6950    const date = card.dataset.entryDate || '';
6951    const match = body.indexOf(q) !== -1 || date.indexOf(q) !== -1;
6952    card.style.display = match ? '' : 'none';
6953    if (match) visible++;
6954  });
6955
6956  // Show "no results" state
6957  let noResults = document.getElementById('journal-no-results');
6958  if (q && visible === 0) {
6959    if (!noResults) {
6960      noResults = document.createElement('div');
6961      noResults.id = 'journal-no-results';
6962      noResults.style.cssText = 'text-align:center;padding:40px 20px;color:var(--plum);font-family:Cormorant Garamond,serif;font-style:italic;font-size:18px;';
6963      noResults.innerHTML = '<div style="font-size:24px;color:var(--gold);margin-bottom:8px;">&#10022;</div>No entries match your search.';
6964      document.getElementById('journal-entries').appendChild(noResults);
6965    }
6966    noResults.style.display = '';
6967  } else if (noResults) {
6968    noResults.style.display = 'none';
6969  }
6970}
6971
6972function clearJournalSearch() {
6973  const input = document.getElementById('journal-search-input');
6974  if (input) input.value = '';
6975  filterJournalEntries('');
6976}
6977
6978let journalVisibility = 'private';
6979
6980function setJournalVisibility(vis) {
6981  journalVisibility = vis;
6982  document.getElementById('vis-private').classList.toggle('active', vis === 'private');
6983  document.getElementById('vis-public').classList.toggle('active', vis === 'public');
6984}
6985
6986// Track which draft is currently being edited (if any)
6987let editingDraftId = null;
6988
6989async function saveJournalEntry(asDraft) {
6990  if (!sbClient || !currentUser) return;
6991
6992  const body = document.getElementById('journal-body').value.trim();
6993  if (!body) return;
6994
6995  const btn = asDraft
6996    ? document.querySelector('.journal-save-draft')
6997    : document.querySelector('.journal-save:not(.journal-save-draft)');
6998  setButtonLoading(btn, true, asDraft ? 'Saving draft...' : 'Saving...');
6999
7000  try {
7001    let error;
7002    if (editingDraftId) {
7003      // Updating an existing draft
7004      const updateData = {
7005        body,
7006        visibility: journalVisibility,
7007        is_draft: !!asDraft
7008      };
7009      ({ error } = await sbClient.from('journal_entries').update(updateData).eq('id', editingDraftId));
7010    } else {
7011      ({ error } = await sbClient.from('journal_entries').insert({
7012        user_id: currentUser.id,
7013        body,
7014        visibility: journalVisibility,
7015        is_draft: !!asDraft
7016      }));
7017    }
7018    if (error) throw error;
7019
7020    document.getElementById('journal-body').value = '';
7021    journalVisibility = 'private';
7022    setJournalVisibility('private');
7023    editingDraftId = null;
7024    document.getElementById('editing-draft-banner').style.display = 'none';
7025    await loadJournal();
7026    showToast(asDraft ? 'Draft saved' : 'Entry saved', 'success');
7027  } catch (e) {
7028    showToast('Could not save: ' + (e.message || e), 'error');
7029  } finally {
7030    setButtonLoading(btn, false);
7031  }
7032}
7033
7034// ============================================================
7035// THOUGHTFUL INQUIRIES — Save & Load
7036// ============================================================
7037const INQUIRY_JOURNAL_TAG = '::thoughtful-inquiries::';
7038
7039async function loadInquiryResponses() {
7040  if (!sbClient || !currentUser) return;
7041
7042  // Look for an existing inquiry journal entry
7043  const { data: entries } = await sbClient
7044    .from('journal_entries')
7045    .select('id, body')
7046    .eq('user_id', currentUser.id)
7047    .like('body', INQUIRY_JOURNAL_TAG + '%')
7048    .limit(1);
7049
7050  if (entries && entries.length > 0) {
7051    try {
7052      const jsonStr = entries[0].body.replace(INQUIRY_JOURNAL_TAG, '');
7053      const responses = JSON.parse(jsonStr);
7054      // Populate inputs
7055      document.querySelectorAll('.inquiry-input').forEach(input => {
7056        const field = input.dataset.field;
7057        if (field && responses[field]) {
7058          input.value = responses[field];
7059        }
7060      });
7061      // Store the entry ID for updates
7062      document.getElementById('inquiry-banner').dataset.entryId = entries[0].id;
7063    } catch (e) { /* ignore parse errors */ }
7064  }
7065}
7066
7067async function saveInquiryResponses() {
7068  if (!sbClient || !currentUser) return;
7069
7070  const btn = document.querySelector('.inquiry-save-btn');
7071  const msg = document.getElementById('inquiry-saved-msg');
7072  btn.textContent = 'SAVING...';
7073  btn.disabled = true;
7074
7075  // Gather all responses
7076  const responses = {};
7077  document.querySelectorAll('.inquiry-input').forEach(input => {
7078    const field = input.dataset.field;
7079    if (field && input.value.trim()) {
7080      responses[field] = input.value.trim();
7081    }
7082  });
7083
7084  const body = INQUIRY_JOURNAL_TAG + JSON.stringify(responses);
7085  const banner = document.getElementById('inquiry-banner');
7086  const existingId = banner.dataset.entryId;
7087
7088  try {
7089    if (existingId) {
7090      // Update existing entry — always keep inquiry visible to Sumir
7091      const { error } = await sbClient.from('journal_entries')
7092        .update({ body, visibility: 'public' })
7093        .eq('id', existingId);
7094      if (error) throw error;
7095    } else {
7096      // Create new entry — inquiry responses are always visible to Sumir
7097      const { data, error } = await sbClient.from('journal_entries')
7098        .insert({ user_id: currentUser.id, body, visibility: 'public', is_draft: false })
7099        .select('id')
7100        .single();
7101      if (error) throw error;
7102      if (data) banner.dataset.entryId = data.id;
7103    }
7104
7105    msg.classList.add('show');
7106    setTimeout(() => msg.classList.remove('show'), 3000);
7107    showToast('Inquiry responses saved as Journal Entry #1', 'success');
7108  } catch (e) {
7109    showToast('Could not save: ' + (e.message || e), 'error');
7110  } finally {
7111    btn.textContent = 'SAVE MY RESPONSES';
7112    btn.disabled = false;
7113  }
7114}
7115
7116async function publishDraft(entryId) {
7117  if (!sbClient || !currentUser) return;
7118  showConfirmModal('Publish this draft as a journal entry?', async () => {
7119    const { error } = await sbClient.from('journal_entries').update({ is_draft: false }).eq('id', entryId);
7120    if (error) {
7121      showToast('Could not publish: ' + error.message, 'error');
7122      return;
7123    }
7124    showToast('Draft published', 'success');
7125    await loadJournal();
7126  });
7127}
7128
7129function cancelDraftEdit() {
7130  editingDraftId = null;
7131  document.getElementById('journal-body').value = '';
7132  document.getElementById('editing-draft-banner').style.display = 'none';
7133  setJournalVisibility('private');
7134}
7135
7136function toggleJournalEntry(card, evt) {
7137  // Don't collapse when clicking inside the thread (reactions, reply input, etc.)
7138  if (evt && evt.target.closest('.journal-thread')) return;
7139  if (card.classList.contains('expanded')) {
7140    card.classList.remove('expanded');
7141  } else {
7142    // Collapse any other expanded entries
7143    document.querySelectorAll('.journal-entry-card.expanded').forEach(c => c.classList.remove('expanded'));
7144    card.classList.add('expanded');
7145  }
7146}
7147
7148// ============================================================
7149// JOURNAL REACTIONS + REPLIES (private 1-on-1 thread with Sumir)
7150// ============================================================
7151const JOURNAL_EMOJIS = ['❤️', '✨', '🙏', '💛', '🤗'];
7152
7153function renderJournalThread(entry) {
7154  const reactions = entry.journal_reactions || [];
7155  const replies = (entry.journal_replies || []).slice().sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
7156
7157  // Group reactions by emoji
7158  const grouped = {};
7159  reactions.forEach(r => {
7160    if (!grouped[r.emoji]) grouped[r.emoji] = [];
7161    grouped[r.emoji].push(r);
7162  });
7163
7164  let chipsHtml = Object.keys(grouped).map(emoji => {
7165    const list = grouped[emoji];
7166    const mine = currentUser && list.some(r => r.user_id === currentUser.id);
7167    return `<button class="j-reaction-chip ${mine ? 'mine' : ''}" onclick="event.stopPropagation();toggleJournalReaction('${entry.id}','${emoji}')" aria-label="${list.length} ${emoji} reaction${list.length === 1 ? '' : 's'}${mine ? ', tap to remove yours' : ', tap to add yours'}" aria-pressed="${mine}"><span aria-hidden="true">${emoji}</span><span class="j-reaction-count" aria-hidden="true">${list.length}</span></button>`;
7168  }).join('');
7169
7170  const pickerHtml = `
7171    <span class="j-emoji-picker" onclick="event.stopPropagation();">
7172      ${JOURNAL_EMOJIS.map(em => `<button class="j-emoji-btn" onclick="event.stopPropagation();toggleJournalReaction('${entry.id}','${em}')" aria-label="React with ${em}"><span aria-hidden="true">${em}</span></button>`).join('')}
7173    </span>`;
7174
7175  const repliesHtml = replies.map(r => {
7176    const isMine = currentUser && r.user_id === currentUser.id;
7177    const author = r.author?.display_name || 'Sister';
7178    const dateStr = new Date(r.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) + ' · ' + new Date(r.created_at).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
7179    return `
7180      <div class="j-reply">
7181        ${isMine ? `<button class="j-reply-delete" onclick="event.stopPropagation();deleteJournalReply('${r.id}')">✕</button>` : ''}
7182        <div class="j-reply-author">${escapeHtml(author)}</div>
7183        <div class="j-reply-body">${escapeHtml(r.body)}</div>
7184        <div class="j-reply-date">${dateStr}</div>
7185      </div>`;
7186  }).join('');
7187
7188  return `
7189    <div class="journal-thread" onclick="event.stopPropagation();">
7190      <div class="j-reactions-row">
7191        ${chipsHtml}
7192        ${pickerHtml}
7193      </div>
7194      <div class="j-replies-list">${repliesHtml}</div>
7195      <div class="j-reply-form">
7196        <input type="text" class="j-reply-input" id="j-reply-input-${entry.id}" placeholder="Write a reply…" onclick="event.stopPropagation();" onkeydown="if(event.key==='Enter'){event.preventDefault();sendJournalReply('${entry.id}');}">
7197        <button class="j-reply-send" onclick="event.stopPropagation();sendJournalReply('${entry.id}')">Send</button>
7198      </div>
7199    </div>`;
7200}
7201
7202async function toggleJournalReaction(entryId, emoji) {
7203  if (!sbClient || !currentUser) return;
7204  // Check if this user already reacted with this emoji
7205  const { data: existing } = await sbClient
7206    .from('journal_reactions')
7207    .select('id')
7208    .eq('journal_entry_id', entryId)
7209    .eq('user_id', currentUser.id)
7210    .eq('emoji', emoji);
7211
7212  if (existing && existing.length > 0) {
7213    await sbClient.from('journal_reactions').delete().eq('id', existing[0].id);
7214  } else {
7215    await sbClient.from('journal_reactions').insert({
7216      journal_entry_id: entryId,
7217      user_id: currentUser.id,
7218      emoji
7219    });
7220  }
7221  await loadJournal();
7222  // Re-expand the entry that was open
7223  const card = document.querySelector(`[onclick*="${entryId}"]`)?.closest('.journal-entry-card');
7224  if (card) card.classList.add('expanded');
7225}
7226
7227async function sendJournalReply(entryId) {
7228  if (!sbClient || !currentUser) return;
7229  const input = document.getElementById('j-reply-input-' + entryId);
7230  if (!input) return;
7231  const body = input.value.trim();
7232  if (!body) return;
7233
7234  const btn = input.parentElement?.querySelector('.j-reply-send');
7235  setButtonLoading(btn, true, '...');
7236
7237  try {
7238    const { error } = await sbClient.from('journal_replies').insert({
7239      journal_entry_id: entryId,
7240      user_id: currentUser.id,
7241      body
7242    });
7243    if (error) throw error;
7244    input.value = '';
7245    showToast('Reply sent', 'success');
7246    await loadJournal();
7247    // Re-expand the entry that was open
7248    const card = document.querySelector(`[onclick*="${entryId}"]`)?.closest('.journal-entry-card');
7249    if (card) card.classList.add('expanded');
7250  } catch (e) {
7251    showToast('Could not send reply: ' + (e.message || e), 'error');
7252  } finally {
7253    setButtonLoading(btn, false);
7254  }
7255}
7256
7257async function deleteJournalReply(replyId) {
7258  if (!sbClient || !currentUser) return;
7259  showConfirmModal('Delete this reply?', async () => {
7260    await sbClient.from('journal_replies').delete().eq('id', replyId);
7261    await loadJournal();
7262  });
7263}
7264
7265function editJournalEntry(entryId, currentVis) {
7266  if (!sbClient || !currentUser) return;
7267  // Fetch the entry to get current body
7268  sbClient.from('journal_entries').select('*').eq('id', entryId).single().then(({ data: entry }) => {
7269    if (!entry) return;
7270
7271    // If it's a draft, load it back into the main composer for continued editing
7272    if (entry.is_draft) {
7273      editingDraftId = entry.id;
7274      document.getElementById('journal-body').value = entry.body;
7275      journalVisibility = entry.visibility || 'private';
7276      setJournalVisibility(journalVisibility);
7277      document.getElementById('editing-draft-banner').style.display = 'block';
7278      document.getElementById('journal-body').focus();
7279      // Scroll the composer into view
7280      document.querySelector('.journal-compose').scrollIntoView({ behavior: 'smooth', block: 'center' });
7281      return;
7282    }
7283
7284    // For published entries, use the inline edit modal
7285    const overlay = document.createElement('div');
7286    overlay.className = 'edit-overlay';
7287    overlay.innerHTML = `
7288      <div class="edit-modal">
7289        <div class="edit-modal-title">Edit Journal Entry</div>
7290        <textarea class="edit-modal-textarea" id="edit-journal-textarea" style="min-height:160px;">${escapeHtml(entry.body)}</textarea>
7291        <div class="edit-modal-actions">
7292          <button class="edit-modal-cancel" onclick="this.closest('.edit-overlay').remove()">Cancel</button>
7293          <button class="edit-modal-save" onclick="saveJournalEdit('${entryId}')">Save</button>
7294        </div>
7295      </div>`;
7296    document.body.appendChild(overlay);
7297    setTimeout(() => document.getElementById('edit-journal-textarea').focus(), 100);
7298  });
7299}
7300
7301async function saveJournalEdit(entryId) {
7302  const textarea = document.getElementById('edit-journal-textarea');
7303  const newBody = textarea.value.trim();
7304  if (!newBody) return;
7305  await sbClient.from('journal_entries').update({ body: newBody }).eq('id', entryId);
7306  document.querySelector('.edit-overlay').remove();
7307  await loadJournal();
7308}
7309
7310function deleteJournalEntry(entryId) {
7311  showConfirmModal('Are you sure you want to delete this journal entry?', async () => {
7312    await sbClient.from('journal_entries').delete().eq('id', entryId);
7313    await loadJournal();
7314  });
7315}
7316
7317async function toggleJournalVisibility(entryId, newVisibility) {
7318  await sbClient.from('journal_entries').update({ visibility: newVisibility }).eq('id', entryId);
7319  await loadJournal();
7320}
7321
7322function printJournal() {
7323  const entries = document.querySelectorAll('#journal-entries .journal-entry-card');
7324  if (entries.length === 0) return;
7325
7326  const displayName = document.getElementById('sidebar-user-name-text').textContent || 'Sister';
7327  document.getElementById('journal-print-name').textContent = displayName;
7328
7329  // Build print entries
7330  const printContainer = document.getElementById('journal-print-entries');
7331  printContainer.innerHTML = '';
7332  entries.forEach(entry => {
7333    const date = entry.querySelector('.journal-entry-date').textContent;
7334    const body = entry.querySelector('.journal-entry-body').textContent;
7335    printContainer.innerHTML += `
7336      <div class="journal-print-entry">
7337        <div class="journal-print-date">${date}</div>
7338        <div class="journal-print-body">${escapeHtml(body)}</div>
7339      </div>`;
7340  });
7341
7342  // Show print area, print, then hide
7343  const printArea = document.getElementById('journal-print-area');
7344  printArea.style.display = 'block';
7345  window.print();
7346  printArea.style.display = 'none';
7347}
7348
7349// ============================================================
7350// ============================================================
7351// REPLAYS
7352// ============================================================
7353let pendingReplayFile = null;
7354
7355function handleReplayVideoSelect(input) {
7356  const file = input.files[0];
7357  if (!file) return;
7358  pendingReplayFile = file;
7359  document.getElementById('replay-video-name').textContent = file.name;
7360}
7361
7362async function uploadReplay() {
7363  if (!sbClient || !currentUser) return;
7364  const title = document.getElementById('replay-title').value.trim();
7365  if (!title) { showToast('Please enter a session title.', 'error'); return; }
7366  if (!pendingReplayFile) { showToast('Please choose a video file.', 'error'); return; }
7367
7368  const btn = document.getElementById('replay-upload-btn');
7369  btn.disabled = true;
7370  btn.textContent = 'Uploading...';
7371
7372  try {
7373    const result = await uploadMedia(pendingReplayFile);
7374    if (!result) {
7375      btn.disabled = false;
7376      btn.textContent = 'Upload Replay';
7377      return;
7378    }
7379
7380    const desc = document.getElementById('replay-desc').value.trim();
7381    const { error } = await sbClient.from('replays').insert({
7382      title,
7383      description: desc || null,
7384      video_url: result.url,
7385      uploaded_by: currentUser.id
7386    });
7387
7388    if (error) {
7389      console.error('Replay insert error:', error);
7390      showToast('Could not save replay: ' + error.message, 'error');
7391    } else {
7392      document.getElementById('replay-title').value = '';
7393      document.getElementById('replay-desc').value = '';
7394      document.getElementById('replay-video-name').textContent = '';
7395      document.getElementById('replay-video-input').value = '';
7396      pendingReplayFile = null;
7397      await loadReplays();
7398      // Auto-announce in community
7399      await sbClient.from('posts').insert({
7400        author_id: currentUser.id,
7401        title: '&#9654; Your Replay is Up!',
7402        body: title + ' is now available in the Replays section.',
7403        post_type: 'announcement'
7404      });
7405    }
7406  } catch (e) {
7407    console.error('Replay exception:', e);
7408    showToast('Something went wrong: ' + e.message, 'error');
7409  }
7410
7411  btn.disabled = false;
7412  btn.textContent = 'Upload Replay';
7413}
7414
7415async function loadReplays() {
7416  if (!sbClient) return;
7417
7418  // Show upload form for teachers
7419  if (currentProfile && currentProfile.role === 'teacher') {
7420    document.getElementById('replay-upload').style.display = 'block';
7421  }
7422
7423  const { data: replays, error } = await sbClient
7424    .from('replays')
7425    .select('*')
7426    .order('created_at', { ascending: false });
7427
7428  const container = document.getElementById('replays-container');
7429  const emptyState = document.getElementById('replays-empty');
7430
7431  if (!replays || replays.length === 0) {
7432    container.innerHTML = '';
7433    container.appendChild(emptyState);
7434    emptyState.style.display = 'block';
7435    return;
7436  }
7437
7438  const isTeacher = currentProfile && currentProfile.role === 'teacher';
7439
7440  container.innerHTML = replays.map(r => {
7441    const date = new Date(r.created_at).toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' });
7442    return `
7443      <div class="replay-card">
7444        <div class="replay-card-date">${date}</div>
7445        <div class="replay-card-title">${escapeHtml(r.title)}</div>
7446        ${r.description ? `<div class="replay-card-desc">${escapeHtml(r.description)}</div>` : ''}
7447        <video controls controlslist="nodownload nofullscreen noremoteplayback" disablepictureinpicture oncontextmenu="return false;" preload="metadata" src="${r.video_url}#t=0.001"></video>
7448        ${isTeacher ? `<div class="replay-card-actions"><button class="replay-delete-btn" onclick="deleteReplay('${r.id}')">&#10005; Remove</button></div>` : ''}
7449      </div>`;
7450  }).join('');
7451}
7452
7453async function deleteReplay(replayId) {
7454  showConfirmModal('Are you sure you want to remove this replay?', async () => {
7455    await sbClient.from('replays').delete().eq('id', replayId);
7456    await loadReplays();
7457  });
7458}
7459
7460// REALTIME
7461// ============================================================
7462function setupRealtime() {
7463  if (!sbClient) return;
7464
7465  sbClient.channel('mentorship-feed')
7466    .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'posts' }, () => loadFeed())
7467    .on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'posts' }, () => loadFeed())
7468    .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'reflections' }, () => loadFeed())
7469    .on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'reflections' }, () => loadFeed())
7470    .on('postgres_changes', { event: '*', schema: 'public', table: 'hearts' }, () => {
7471      // Don't reload entire feed for hearts — handled optimistically
7472    })
7473    .subscribe();
7474}
7475
7476// ============================================================
7477// UTILITIES
7478// ============================================================
7479function escapeHtml(text) {
7480  if (!text) return '';
7481  const div = document.createElement('div');
7482  div.textContent = text;
7483  return div.innerHTML;
7484}
7485
7486// ============================================================
7487// INIT
7488// ============================================================
7489// First, try to restore an existing Supabase session (skip password + auth)
7490(async function init() {
7491  // CAPTURE URL state IMMEDIATELY — before the Supabase SDK clears it.
7492  const urlHash = window.location.hash || '';
7493  const urlSearch = window.location.search || '';
7494  const isRecoveryUrl =
7495    urlHash.indexOf('type=recovery') !== -1 ||
7496    urlSearch.indexOf('type=recovery') !== -1 ||
7497    (urlSearch.indexOf('code=') !== -1 && urlSearch.indexOf('error') === -1); // PKCE flow
7498
7499  // If this is a password-recovery flow, bypass the gate and force the reset screen.
7500  if (isRecoveryUrl) {
7501    localStorage.setItem('mentorship_authenticated', 'true'); // bypass the gate for this user
7502    document.getElementById('password-screen').style.display = 'none';
7503
7504    if (initSupabase()) {
7505      // Listen for PASSWORD_RECOVERY (legacy/hash flow) and SIGNED_IN (PKCE flow during recovery)
7506      sbClient.auth.onAuthStateChange((event, session) => {
7507        if (event === 'PASSWORD_RECOVERY') {
7508          showResetScreen();
7509        } else if (event === 'SIGNED_IN' && session) {
7510          // We KNOW this is a recovery flow because the URL said so.
7511          showResetScreen();
7512        }
7513      });
7514
7515      // For the PKCE flow, manually exchange the code if Supabase didn't auto-process it.
7516      if (urlSearch.indexOf('code=') !== -1) {
7517        try {
7518          const params = new URLSearchParams(urlSearch);
7519          const code = params.get('code');
7520          if (code) await sbClient.auth.exchangeCodeForSession(code);
7521        } catch (e) { console.warn('Code exchange:', e); }
7522      }
7523
7524      // Safety net: force the reset screen to appear even if no auth event fires.
7525      setTimeout(() => {
7526        const reset = document.getElementById('reset-screen');
7527        if (reset && reset.style.display !== 'flex') showResetScreen();
7528      }, 600);
7529      return;
7530    }
7531  }
7532
7533  if (initSupabase()) {
7534    const { data: { session } } = await sbClient.auth.getSession();
7535    if (session) {
7536      // Already logged in — skip password and auth screens entirely
7537      localStorage.setItem('mentorship_authenticated', 'true');
7538      document.getElementById('password-screen').style.display = 'none';
7539      handleSignedIn(session.user);
7540
7541      // Listen for auth changes
7542      sbClient.auth.onAuthStateChange((event, sess) => {
7543        if (event === 'PASSWORD_RECOVERY') {
7544          showResetScreen();
7545        } else if (event === 'SIGNED_IN' && sess) {
7546          handleSignedIn(sess.user);
7547        }
7548      });
7549      return;
7550    }
7551
7552    // No session yet — still wire up listeners in case PASSWORD_RECOVERY fires shortly
7553    sbClient.auth.onAuthStateChange((event, sess) => {
7554      if (event === 'PASSWORD_RECOVERY') {
7555        document.getElementById('password-screen').style.display = 'none';
7556        showResetScreen();
7557      } else if (event === 'SIGNED_IN' && sess) {
7558        handleSignedIn(sess.user);
7559      }
7560    });
7561  }
7562
7563  // No active session — check password gate
7564  if (localStorage.getItem('mentorship_authenticated') === 'true') {
7565    proceedAfterPassword();
7566  }
7567})();
7568
7569// Handle hash routing
7570window.addEventListener('hashchange', () => {
7571  const hash = window.location.hash.replace('#', '');
7572  if (hash && document.getElementById('hub').style.display === 'block') {
7573    switchView(hash);
7574  }
7575});
7576
7577// ============================================================
7578// MONTHLY HARVEST
7579// ============================================================
7580const HARVEST_MONTHS = [
7581  'June 2026', 'July 2026', 'August 2026', 'September 2026',
7582  'October 2026', 'November 2026', 'December 2026',
7583  'January 2027'
7584];
7585
7586async function loadHarvests() {
7587  if (!sbClient) return;
7588
7589  const { data: harvests } = await sbClient
7590    .from('monthly_harvests')
7591    .select('*, responses:harvest_responses(*, hearts, hearted_by)')
7592    .order('created_at', { ascending: false });
7593
7594  const container = document.getElementById('harvest-months-container');
7595  const isTeacher = currentProfile && currentProfile.role === 'teacher';
7596
7597  if (!harvests || harvests.length === 0) {
7598    container.innerHTML = `
7599      <div style="text-align:center;padding:50px 20px;">
7600        <div style="font-size:40px;margin-bottom:16px;opacity:0.3;">&#10022;</div>
7601        <div style="font-family:'Cormorant Garamond',serif;font-size:20px;color:var(--plum-deep);margin-bottom:8px;">No harvests yet</div>
7602        <div style="font-size:13px;color:var(--charcoal);opacity:0.5;">${isTeacher ? 'Create the first monthly harvest above.' : 'Your teacher will share the first harvest prompt soon.'}</div>
7603      </div>`;
7604    return;
7605  }
7606
7607  // Group by month
7608  const byMonth = {};
7609  harvests.forEach(h => {
7610    if (!byMonth[h.month_label]) byMonth[h.month_label] = [];
7611    byMonth[h.month_label].push(h);
7612  });
7613
7614  // Sort months by HARVEST_MONTHS order
7615  const sortedMonths = Object.keys(byMonth).sort((a, b) => {
7616    return HARVEST_MONTHS.indexOf(b) - HARVEST_MONTHS.indexOf(a);
7617  });
7618
7619  container.innerHTML = sortedMonths.map(month => {
7620    const monthHarvests = byMonth[month];
7621    return `
7622      <div class="harvest-month-card" style="margin-bottom:20px;">
7623        <div onclick="this.parentElement.classList.toggle('open')" style="display:flex;align-items:center;justify-content:space-between;padding:20px 24px;background:white;border-radius:14px;border:1px solid rgba(197,165,90,0.25);cursor:pointer;transition:all 0.3s;">
7624          <div>
7625            <div style="font-family:'Marcellus',serif;font-size:10px;letter-spacing:3px;color:var(--gold);text-transform:uppercase;margin-bottom:4px;">${month}</div>
7626            <div style="font-family:'Cormorant Garamond',serif;font-size:18px;color:var(--plum-deep);">${monthHarvests.length} ${monthHarvests.length === 1 ? 'harvest' : 'harvests'}</div>
7627          </div>
7628          <div style="color:var(--gold);font-size:18px;transition:transform 0.3s;" class="harvest-chevron">&#9662;</div>
7629        </div>
7630        <div class="harvest-month-content" style="display:none;padding:0 8px;">
7631          ${monthHarvests.map(h => renderHarvestCard(h, isTeacher)).join('')}
7632        </div>
7633      </div>`;
7634  }).join('');
7635
7636  // Auto-open the most recent month
7637  const firstCard = container.querySelector('.harvest-month-card');
7638  if (firstCard) firstCard.classList.add('open');
7639
7640  // Apply open state
7641  container.querySelectorAll('.harvest-month-card').forEach(card => {
7642    if (card.classList.contains('open')) {
7643      card.querySelector('.harvest-month-content').style.display = 'block';
7644      card.querySelector('.harvest-chevron').style.transform = 'rotate(180deg)';
7645    }
7646  });
7647
7648  // Observe toggle
7649  const observer = new MutationObserver(mutations => {
7650    mutations.forEach(m => {
7651      if (m.attributeName === 'class') {
7652        const card = m.target;
7653        const content = card.querySelector('.harvest-month-content');
7654        const chevron = card.querySelector('.harvest-chevron');
7655        if (card.classList.contains('open')) {
7656          content.style.display = 'block';
7657          chevron.style.transform = 'rotate(180deg)';
7658        } else {
7659          content.style.display = 'none';
7660          chevron.style.transform = 'rotate(0deg)';
7661        }
7662      }
7663    });
7664  });
7665  container.querySelectorAll('.harvest-month-card').forEach(card => {
7666    observer.observe(card, { attributes: true, attributeFilter: ['class'] });
7667  });
7668}
7669
7670function renderHarvestCard(harvest, isTeacher) {
7671  const myResponse = harvest.responses ? harvest.responses.find(r => r.user_id === currentUser.id) : null;
7672  const responses = harvest.responses || [];
7673  const responseCount = responses.length;
7674
7675  const deleteBtn = isTeacher ? `<button onclick="deleteHarvest('${harvest.id}')" style="background:none;border:none;color:var(--charcoal);opacity:0.3;cursor:pointer;font-size:11px;padding:4px 8px;transition:opacity 0.2s;" onmouseover="this.style.opacity='0.7'" onmouseout="this.style.opacity='0.3'">&#10005; Remove</button>` : '';
7676
7677  // All responses visible to everyone with hearts
7678  const allResponses = responseCount > 0 ? `
7679    <div style="margin-top:16px;padding-top:16px;border-top:1px solid rgba(197,165,90,0.15);">
7680      <div style="font-family:'Marcellus',serif;font-size:9px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;margin-bottom:10px;">${responseCount} ${responseCount === 1 ? 'Harvest' : 'Harvests'} Shared</div>
7681      ${responses.map(r => {
7682        const heartCount = r.hearts || 0;
7683        const iHearted = r.hearted_by && r.hearted_by.includes(currentUser.id);
7684        const heartStyle = iHearted ? 'color:#C04070;opacity:1;' : 'color:var(--charcoal);opacity:0.35;';
7685        const isOwn = r.user_id === currentUser.id;
7686        return `
7687        <div style="background:${isOwn ? 'var(--cream-warm)' : 'var(--cream)'};border-radius:10px;padding:14px 16px;margin-bottom:8px;${isOwn ? 'border-left:3px solid var(--gold);' : ''}">
7688          <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;">
7689            <div style="font-size:11px;font-weight:600;color:var(--plum-deep);">${escapeHtml(r.display_name || 'Member')}${isOwn ? ' <span style="font-weight:400;opacity:0.5;">(you)</span>' : ''}</div>
7690            <div style="font-size:10px;color:var(--charcoal);opacity:0.4;">${new Date(r.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}</div>
7691          </div>
7692          <div style="font-size:13px;line-height:1.7;white-space:pre-wrap;">${escapeHtml(r.body)}</div>
7693          <div style="margin-top:8px;">
7694            <button onclick="toggleHarvestHeart('${r.id}')" style="background:none;border:none;cursor:pointer;font-size:14px;padding:2px 6px;transition:all 0.2s;${heartStyle}" aria-label="${iHearted ? 'Remove heart' : 'Send heart'}" aria-pressed="${iHearted}">
7695              <span aria-hidden="true">${iHearted ? '&#10084;' : '&#9825;'}${heartCount > 0 ? ' <span style="font-size:11px;">' + heartCount + '</span>' : ''}</span>
7696            </button>
7697          </div>
7698        </div>`;
7699      }).join('')}
7700    </div>` : '';
7701
7702  // Name dropdown for response form
7703  const currentName = currentProfile ? currentProfile.display_name : '';
7704  const nameDropdown = `
7705    <select id="harvest-name-${harvest.id}" style="width:100%;padding:10px 14px;border:1px solid rgba(197,165,90,0.3);border-radius:8px;font-family:'Lora',serif;font-size:13px;margin-bottom:10px;background:var(--cream);color:var(--charcoal);">
7706      <option value="">Select your name...</option>
7707      <option value="Sumir" ${currentName === 'Sumir' ? 'selected' : ''}>Sumir</option>
7708      <option value="Beth" ${currentName === 'Beth' ? 'selected' : ''}>Beth</option>
7709      <option value="Mindy" ${currentName === 'Mindy' ? 'selected' : ''}>Mindy</option>
7710    </select>`;
7711
7712  return `
7713    <div style="background:white;border-radius:14px;border:1px solid rgba(197,165,90,0.15);padding:24px;margin-top:12px;">
7714      <div style="display:flex;justify-content:space-between;align-items:flex-start;">
7715        <div>
7716          ${harvest.title ? `<div style="font-family:'Cormorant Garamond',serif;font-size:18px;color:var(--plum-deep);margin-bottom:8px;">${escapeHtml(harvest.title)}</div>` : ''}
7717        </div>
7718        ${deleteBtn}
7719      </div>
7720      <div style="font-size:14px;line-height:1.8;color:var(--charcoal);white-space:pre-wrap;">${escapeHtml(harvest.prompt_text)}</div>
7721
7722      <!-- Response form or my response -->
7723      <div style="margin-top:20px;padding-top:16px;border-top:1px solid rgba(197,165,90,0.15);">
7724        ${myResponse ? `
7725          <div style="font-family:'Marcellus',serif;font-size:9px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;margin-bottom:8px;">&#10003; Your harvest has been shared</div>
7726        ` : `
7727          <div style="font-family:'Marcellus',serif;font-size:9px;letter-spacing:2px;color:var(--gold);text-transform:uppercase;margin-bottom:8px;">Share Your Harvest</div>
7728          ${nameDropdown}
7729          <textarea id="harvest-response-${harvest.id}" placeholder="Take a moment to reflect... What are you harvesting this month?" style="width:100%;padding:14px;border:1px solid rgba(197,165,90,0.3);border-radius:10px;font-family:'Lora',serif;font-size:13px;min-height:100px;resize:vertical;background:var(--cream);line-height:1.7;"></textarea>
7730          <div style="display:flex;justify-content:flex-end;margin-top:10px;">
7731            <button onclick="submitHarvestResponse('${harvest.id}')" style="background:var(--berry);color:white;border:none;padding:9px 20px;border-radius:8px;font-family:'Marcellus',serif;font-size:9px;letter-spacing:2px;text-transform:uppercase;cursor:pointer;">Share Harvest</button>
7732          </div>
7733        `}
7734      </div>
7735
7736      ${allResponses}
7737    </div>`;
7738}
7739
7740async function createHarvest() {
7741  if (!sbClient || !currentUser) return;
7742  const month = document.getElementById('harvest-month-select').value;
7743  const title = document.getElementById('harvest-title-input').value.trim();
7744  const prompt = document.getElementById('harvest-prompt-input').value.trim();
7745
7746  if (!month) { showToast('Please select a month.', 'error'); return; }
7747  if (!prompt) { showToast('Please write a prompt or inquiry.', 'error'); return; }
7748
7749  const { error } = await sbClient.from('monthly_harvests').insert({
7750    month_label: month,
7751    title: title || null,
7752    prompt_text: prompt,
7753    author_id: currentUser.id
7754  });
7755
7756  if (error) {
7757    console.error('Harvest create error:', error);
7758    showToast('Could not create harvest: ' + error.message, 'error');
7759    return;
7760  }
7761
7762  document.getElementById('harvest-month-select').value = '';
7763  document.getElementById('harvest-title-input').value = '';
7764  document.getElementById('harvest-prompt-input').value = '';
7765  loadHarvests();
7766}
7767
7768async function submitHarvestResponse(harvestId) {
7769  if (!sbClient || !currentUser) return;
7770  const nameSelect = document.getElementById('harvest-name-' + harvestId);
7771  const textarea = document.getElementById('harvest-response-' + harvestId);
7772  const selectedName = nameSelect.value;
7773  const body = textarea.value.trim();
7774
7775  if (!selectedName) { showToast('Please select your name.', 'error'); return; }
7776  if (!body) { showToast('Please write your reflection before sharing.', 'error'); return; }
7777
7778  const btn = textarea.parentElement?.querySelector('button') || document.querySelector(`button[onclick*="submitHarvestResponse('${harvestId}')"]`);
7779  setButtonLoading(btn, true, 'Sharing...');
7780
7781  try {
7782    const { error } = await sbClient.from('harvest_responses').insert({
7783      harvest_id: harvestId,
7784      user_id: currentUser.id,
7785      display_name: selectedName,
7786      body: body,
7787      hearts: 0,
7788      hearted_by: []
7789    });
7790    if (error) throw error;
7791    showToast('Your reflection has been shared', 'success');
7792    loadHarvests();
7793  } catch (e) {
7794    console.error('Harvest response error:', e);
7795    showToast('Could not save your response: ' + (e.message || e), 'error');
7796    setButtonLoading(btn, false);
7797  }
7798}
7799
7800async function toggleHarvestHeart(responseId) {
7801  if (!sbClient || !currentUser) return;
7802
7803  // Get current response
7804  const { data: response } = await sbClient
7805    .from('harvest_responses')
7806    .select('hearts, hearted_by')
7807    .eq('id', responseId)
7808    .single();
7809
7810  if (!response) return;
7811
7812  let heartedBy = response.hearted_by || [];
7813  let hearts = response.hearts || 0;
7814  const alreadyHearted = heartedBy.includes(currentUser.id);
7815
7816  if (alreadyHearted) {
7817    heartedBy = heartedBy.filter(id => id !== currentUser.id);
7818    hearts = Math.max(0, hearts - 1);
7819  } else {
7820    heartedBy.push(currentUser.id);
7821    hearts += 1;
7822  }
7823
7824  await sbClient
7825    .from('harvest_responses')
7826    .update({ hearts, hearted_by: heartedBy })
7827    .eq('id', responseId);
7828
7829  loadHarvests();
7830}
7831
7832async function deleteHarvest(harvestId) {
7833  showConfirmModal('Remove this harvest and all its responses?', async () => {
7834    await sbClient.from('harvest_responses').delete().eq('harvest_id', harvestId);
7835    await sbClient.from('monthly_harvests').delete().eq('id', harvestId);
7836    loadHarvests();
7837  });
7838}
7839
7840// ============================================================
7841// MEMBERS & ANALYTICS
7842// ============================================================
7843async function loadMembers() {
7844  if (!sbClient || !currentProfile || currentProfile.role !== 'teacher') return;
7845
7846  // Load all profiles, EXCLUDING any teacher accounts (so admin activity doesn't skew metrics)
7847  const { data: allProfiles } = await sbClient
7848    .from('profiles')
7849    .select('*')
7850    .order('created_at', { ascending: false });
7851
7852  if (!allProfiles) return;
7853
7854  // Filter out teacher accounts
7855  const members = allProfiles.filter(m => m.role !== 'teacher');
7856
7857  // Stats
7858  const now = new Date();
7859  const sevenDaysAgo = new Date(now - 7 * 24 * 60 * 60 * 1000);
7860  const thirtyDaysAgo = new Date(now - 30 * 24 * 60 * 60 * 1000);
7861
7862  const total = members.length;
7863  const active7 = members.filter(m => m.last_login && new Date(m.last_login) > sevenDaysAgo).length;
7864  const active30 = members.filter(m => m.last_login && new Date(m.last_login) > thirtyDaysAgo).length;
7865
7866  document.getElementById('stat-total').textContent = total;
7867  document.getElementById('stat-active-7').textContent = active7;
7868  document.getElementById('stat-active-30').textContent = active30;
7869
7870  // Members table
7871  const tbody = document.getElementById('members-table-body');
7872  if (members.length === 0) {
7873    tbody.innerHTML = '<tr><td colspan="4" style="padding:30px;text-align:center;color:var(--charcoal);opacity:0.5;">No members yet</td></tr>';
7874    return;
7875  }
7876
7877  tbody.innerHTML = members.map(m => {
7878    const joinDate = m.created_at ? new Date(m.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : '\u2014';
7879    const lastActive = m.last_login ? timeAgo(new Date(m.last_login)) : 'Never';
7880    const roleBadge = m.role === 'teacher'
7881      ? '<span style="background:var(--gold);color:white;padding:2px 8px;border-radius:10px;font-size:10px;font-family:Marcellus,serif;letter-spacing:1px;text-transform:uppercase;">Teacher</span>'
7882      : '<span style="background:var(--cream-warm);color:var(--charcoal);padding:2px 8px;border-radius:10px;font-size:10px;font-family:Marcellus,serif;letter-spacing:1px;text-transform:uppercase;">Member</span>';
7883    return `<tr style="border-bottom:1px solid rgba(197,165,90,0.1);">
7884      <td style="padding:12px 18px;font-weight:600;">${escapeHtml(m.display_name || 'Unnamed')}</td>
7885      <td style="padding:12px 18px;opacity:0.7;">${joinDate}</td>
7886      <td style="padding:12px 18px;opacity:0.7;">${lastActive}</td>
7887      <td style="padding:12px 18px;">${roleBadge}</td>
7888    </tr>`;
7889  }).join('');
7890
7891  // Load library analytics + members
7892  loadLibraryAnalytics();
7893  loadLibraryMembers();
7894}
7895
7896// ============================================================
7897// LIBRARY MEMBERS — who's signing in to the Golden Path Library
7898// ============================================================
7899async function loadLibraryMembers() {
7900  if (!sbClient || !currentProfile || currentProfile.role !== 'teacher') return;
7901
7902  // Load all library sessions
7903  const { data: sessions } = await sbClient
7904    .from('library_sessions')
7905    .select('user_id, user_email, created_at')
7906    .order('created_at', { ascending: false });
7907
7908  // Load all library bookmarks (to count favorites per user)
7909  const { data: bookmarks } = await sbClient
7910    .from('library_bookmarks')
7911    .select('user_id, teaching_key');
7912
7913  // Load all profiles (to map user_id → display_name)
7914  const { data: profiles } = await sbClient
7915    .from('profiles')
7916    .select('id, display_name, role');
7917
7918  // Build a set of teacher user IDs to EXCLUDE from metrics
7919  const teacherIds = new Set();
7920  (profiles || []).forEach(p => { if (p.role === 'teacher') teacherIds.add(p.id); });
7921
7922  // Filter out any sessions from teachers (so admin activity doesn't skew metrics)
7923  const filteredSessions = (sessions || []).filter(s => !teacherIds.has(s.user_id));
7924
7925  if (!filteredSessions || filteredSessions.length === 0) {
7926    document.getElementById('stat-lib-members').textContent = '0';
7927    document.getElementById('stat-lib-signins').textContent = '0';
7928    document.getElementById('library-members-body').innerHTML = '<tr><td colspan="4" style="padding:30px;text-align:center;color:var(--charcoal);opacity:0.5;">No library sign-ins yet</td></tr>';
7929    return;
7930  }
7931
7932  // Build profile lookup
7933  const profileMap = {};
7934  (profiles || []).forEach(p => { profileMap[p.id] = p; });
7935
7936  // Count bookmarks per user (also excluding teacher)
7937  const bookmarkCounts = {};
7938  (bookmarks || []).forEach(b => {
7939    if (teacherIds.has(b.user_id)) return; // skip teacher's bookmarks
7940    bookmarkCounts[b.user_id] = (bookmarkCounts[b.user_id] || 0) + 1;
7941  });
7942
7943  // Group sessions by user
7944  const userMap = {};
7945  filteredSessions.forEach(s => {
7946    if (!s.user_id) return;
7947    if (!userMap[s.user_id]) {
7948      userMap[s.user_id] = {
7949        user_id: s.user_id,
7950        email: s.user_email || '',
7951        signins: 0,
7952        last_visit: s.created_at  // first one (newest) is last_visit
7953      };
7954    }
7955    userMap[s.user_id].signins++;
7956    // Keep latest timestamp
7957    if (new Date(s.created_at) > new Date(userMap[s.user_id].last_visit)) {
7958      userMap[s.user_id].last_visit = s.created_at;
7959    }
7960  });
7961
7962  const members = Object.values(userMap).sort((a, b) => new Date(b.last_visit) - new Date(a.last_visit));
7963
7964  // Update stat tiles (uses filtered sessions, so teacher activity is excluded)
7965  document.getElementById('stat-lib-members').textContent = members.length;
7966  document.getElementById('stat-lib-signins').textContent = filteredSessions.length;
7967
7968  // Render table rows
7969  document.getElementById('library-members-body').innerHTML = members.map(m => {
7970    const profile = profileMap[m.user_id];
7971    const displayName = profile?.display_name || '';
7972    const role = profile?.role;
7973    const isMentorship = role === 'student' || role === 'teacher';
7974    const lastVisit = timeAgo(new Date(m.last_visit));
7975    const favCount = bookmarkCounts[m.user_id] || 0;
7976    const tag = isMentorship
7977      ? '<span style="display:inline-block;font-family:Marcellus,serif;font-size:8px;letter-spacing:1.5px;padding:2px 7px;border-radius:8px;background:rgba(148,14,75,0.1);color:var(--berry);margin-left:8px;">MENTORSHIP</span>'
7978      : '<span style="display:inline-block;font-family:Marcellus,serif;font-size:8px;letter-spacing:1.5px;padding:2px 7px;border-radius:8px;background:rgba(197,165,90,0.15);color:var(--gold);margin-left:8px;">LIBRARY ONLY</span>';
7979
7980    const nameOrEmail = displayName
7981      ? `<span style="font-weight:600;">${escapeHtml(displayName)}</span><br><span style="font-size:11px;opacity:0.7;">${escapeHtml(m.email)}</span>`
7982      : `<span style="font-weight:600;">${escapeHtml(m.email)}</span>`;
7983
7984    return `<tr style="border-bottom:1px solid rgba(197,165,90,0.1);">
7985      <td style="padding:12px 18px;">${nameOrEmail}${tag}</td>
7986      <td style="padding:12px 18px;text-align:center;font-family:'Marcellus',serif;color:var(--berry);">${m.signins}</td>
7987      <td style="padding:12px 18px;text-align:center;color:var(--berry);">${favCount > 0 ? '&#9829; ' + favCount : '&mdash;'}</td>
7988      <td style="padding:12px 18px;opacity:0.7;font-size:12px;">${lastVisit}</td>
7989    </tr>`;
7990  }).join('');
7991}
7992
7993function timeAgo(date) {
7994  const seconds = Math.floor((new Date() - date) / 1000);
7995  if (seconds < 60) return 'Just now';
7996  const minutes = Math.floor(seconds / 60);
7997  if (minutes < 60) return minutes + 'm ago';
7998  const hours = Math.floor(minutes / 60);
7999  if (hours < 24) return hours + 'h ago';
8000  const days = Math.floor(hours / 24);
8001  if (days === 1) return 'Yesterday';
8002  if (days < 7) return days + ' days ago';
8003  if (days < 30) return Math.floor(days / 7) + 'w ago';
8004  return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
8005}
8006
8007const TEACHING_NAMES = {
8008  'teaching-01': 'Insights into Survival, Trauma & Regulation',
8009  'teaching-02': 'Stories & Beliefs',
8010  'teaching-03': 'Consciousness Is Primary',
8011  'teaching-04': '80/20: Returning to Center',
8012  'teaching-05': 'Accepting What Is',
8013  'teaching-06': 'Automatic, Conscious, Simple',
8014  'teaching-07': 'Opening Our Sight',
8015  'teaching-09': 'Rewriting the Code',
8016  'teaching-10': 'The S Practice'
8017};
8018
8019async function loadLibraryAnalytics() {
8020  try {
8021    const { data: analytics } = await sbClient
8022      .from('library_analytics')
8023      .select('*');
8024
8025    if (!analytics || analytics.length === 0) {
8026      document.getElementById('stat-lib-views').textContent = '0';
8027      document.getElementById('stat-lib-prints').textContent = '0';
8028      document.getElementById('library-analytics-body').innerHTML = '<tr><td colspan="3" style="padding:30px;text-align:center;color:var(--charcoal);opacity:0.5;">No analytics data yet</td></tr>';
8029      return;
8030    }
8031
8032    const views = analytics.filter(a => a.event_type === 'view');
8033    const prints = analytics.filter(a => a.event_type === 'print');
8034
8035    document.getElementById('stat-lib-views').textContent = views.length;
8036    document.getElementById('stat-lib-prints').textContent = prints.length;
8037
8038    // Group by teaching
8039    const teachingStats = {};
8040    analytics.forEach(a => {
8041      if (!teachingStats[a.teaching_id]) teachingStats[a.teaching_id] = { views: 0, prints: 0 };
8042      if (a.event_type === 'view') teachingStats[a.teaching_id].views++;
8043      if (a.event_type === 'print') teachingStats[a.teaching_id].prints++;
8044    });
8045
8046    const sorted = Object.entries(teachingStats).sort((a, b) => b[1].views - a[1].views);
8047
8048    document.getElementById('library-analytics-body').innerHTML = sorted.map(([id, stats]) => {
8049      const name = TEACHING_NAMES[id] || id;
8050      return `<tr style="border-bottom:1px solid rgba(197,165,90,0.1);">
8051        <td style="padding:12px 18px;">${escapeHtml(name)}</td>
8052        <td style="padding:12px 18px;text-align:center;font-weight:600;">${stats.views}</td>
8053        <td style="padding:12px 18px;text-align:center;font-weight:600;">${stats.prints}</td>
8054      </tr>`;
8055    }).join('');
8056  } catch(e) {
8057    console.error('Library analytics error:', e);
8058  }
8059}
8060
8061// Voice note player — blob + HTMLAudioElement with duration fix for broken WebM
8062var vnCache = {};
8063
8064function vnFormatTime(s) {
8065  if (!s || !isFinite(s)) return '0:00';
8066  var m = Math.floor(s / 60);
8067  var sec = Math.floor(s % 60);
8068  return m + ':' + (sec < 10 ? '0' : '') + sec;
8069}
8070
8071function preloadVoiceNotes() {
8072  document.querySelectorAll('.voice-note-player[data-url]').forEach(function(wrap) {
8073    var id = wrap.id;
8074    var url = wrap.dataset.url;
8075    if (!url || vnCache[id]) return;
8076    vnCache[id] = { loading: true };
8077
8078    fetch(url)
8079      .then(function(r) { return r.blob(); })
8080      .then(function(blob) {
8081        // Older notes are WebM (broken duration, no iOS playback); new notes are MP3.
8082        var isWebm = /webm/i.test(blob.type) || /\.webm(\?|$)/i.test(url);
8083        var blobUrl = URL.createObjectURL(blob);
8084        var audio = new Audio();
8085        audio.preload = 'auto';
8086        audio.src = blobUrl;
8087
8088        var durationResolved = false;
8089
8090        function finishSetup(dur) {
8091          if (durationResolved) return;
8092          durationResolved = true;
8093          vnCache[id] = {
8094            audio: audio,
8095            blobUrl: blobUrl,
8096            duration: dur,
8097            isWebm: isWebm,
8098            ready: true
8099          };
8100          audio.currentTime = 0;
8101          var elEl = wrap.querySelector('.vn-elapsed');
8102          var remEl = wrap.querySelector('.vn-remaining');
8103          if (elEl) elEl.textContent = '0:00';
8104          if (remEl) remEl.textContent = vnFormatTime(dur); // full length shown before play
8105
8106          // Wire up events
8107          audio.addEventListener('ended', function() {
8108            var btn = wrap.querySelector('.vn-play-btn');
8109            btn.innerHTML = '&#9654;&#xFE0E;';
8110            wrap.querySelector('.vn-progress-bar').style.width = '0%';
8111            wrap.querySelector('.vn-elapsed').textContent = '0:00';
8112            wrap.querySelector('.vn-remaining').textContent = vnFormatTime(dur);
8113            if (vnCache[id].rafId) cancelAnimationFrame(vnCache[id].rafId);
8114          });
8115        }
8116
8117        audio.addEventListener('loadedmetadata', function() {
8118          if (audio.duration && isFinite(audio.duration) && audio.duration > 0 && audio.duration < 86400) {
8119            finishSetup(audio.duration);
8120          } else if (isWebm) {
8121            // Broken WebM duration — seek to end to force the browser to find it
8122            audio.currentTime = 1e7;
8123          }
8124        });
8125
8126        audio.addEventListener('timeupdate', function onSeekEnd() {
8127          if (durationResolved) return;
8128          if (audio.duration && isFinite(audio.duration) && audio.duration > 0 && audio.duration < 86400) {
8129            audio.removeEventListener('timeupdate', onSeekEnd);
8130            finishSetup(audio.duration);
8131          }
8132        });
8133
8134        // Fallback timeout — if duration still not resolved after 5s, use what we have
8135        setTimeout(function() {
8136          if (!durationResolved) {
8137            var dur = (audio.duration && isFinite(audio.duration)) ? audio.duration : 300;
8138            console.warn('[VoiceNote ' + id + '] Duration fallback: ' + dur);
8139            finishSetup(dur);
8140          }
8141        }, 5000);
8142      })
8143      .catch(function(err) {
8144        console.error('Voice note preload failed:', id, err);
8145        vnCache[id] = { failed: true };
8146      });
8147  });
8148}
8149
8150function vnUpdateProgress(id) {
8151  var c = vnCache[id];
8152  if (!c || !c.audio) return;
8153  var audio = c.audio;
8154  if (audio.paused && !audio.ended) return;
8155  var wrap = document.getElementById(id);
8156  if (!wrap) return;
8157  var pct = c.duration > 0 ? (audio.currentTime / c.duration * 100) : 0;
8158  if (pct > 100) pct = 100;
8159  wrap.querySelector('.vn-progress-bar').style.width = pct + '%';
8160  wrap.querySelector('.vn-elapsed').textContent = vnFormatTime(audio.currentTime);
8161  wrap.querySelector('.vn-remaining').textContent = '-' + vnFormatTime(Math.max(0, c.duration - audio.currentTime));
8162  if (!audio.paused && !audio.ended) {
8163    c.rafId = requestAnimationFrame(function() { vnUpdateProgress(id); });
8164  }
8165}
8166
8167function vnToggle(id) {
8168  var wrap = document.getElementById(id);
8169  if (!wrap) return;
8170  var btn = wrap.querySelector('.vn-play-btn');
8171  var c = vnCache[id];
8172  if (!c || !c.ready) { btn.innerHTML = '&#8987;'; return; }
8173
8174  var audio = c.audio;
8175
8176  // If playing, pause
8177  if (!audio.paused) {
8178    audio.pause();
8179    btn.innerHTML = '&#9654;&#xFE0E;';
8180    if (c.rafId) cancelAnimationFrame(c.rafId);
8181    return;
8182  }
8183
8184  // Pause all others
8185  Object.keys(vnCache).forEach(function(k) {
8186    if (k !== id && vnCache[k].audio && !vnCache[k].audio.paused) {
8187      vnCache[k].audio.pause();
8188      var otherWrap = document.getElementById(k);
8189      if (otherWrap) {
8190        otherWrap.querySelector('.vn-play-btn').innerHTML = '&#9654;&#xFE0E;';
8191        if (vnCache[k].rafId) cancelAnimationFrame(vnCache[k].rafId);
8192      }
8193    }
8194  });
8195
8196  // WebM files need a tiny nudge past zero to start; MP3 plays cleanly from 0
8197  if (c.isWebm && audio.currentTime < 0.01) audio.currentTime = 0.01;
8198  audio.play().then(function() {
8199    btn.innerHTML = '&#9646;&#9646;';
8200    vnUpdateProgress(id);
8201  }).catch(function(err) {
8202    console.error('Play failed:', err);
8203    btn.innerHTML = '&#9654;&#xFE0E;';
8204  });
8205}
8206
8207function vnSeek(event, id) {
8208  var c = vnCache[id];
8209  if (!c || !c.ready) return;
8210  var track = document.getElementById(id).querySelector('.vn-progress-track');
8211  var rect = track.getBoundingClientRect();
8212  var pct = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width));
8213  var seekTo = pct * c.duration;
8214  var wasPlaying = !c.audio.paused;
8215
8216  c.audio.currentTime = seekTo;
8217
8218  var wrap = document.getElementById(id);
8219  wrap.querySelector('.vn-progress-bar').style.width = (pct * 100) + '%';
8220  wrap.querySelector('.vn-elapsed').textContent = vnFormatTime(seekTo);
8221  wrap.querySelector('.vn-remaining').textContent = '-' + vnFormatTime(Math.max(0, c.duration - seekTo));
8222
8223  if (wasPlaying) vnUpdateProgress(id);
8224}
8225
8226// ============================================================
8227// ONE-TIME LEGACY AUDIO CONVERTER (WebM -> MP3)
8228// Teacher-only. Decodes old WebM notes in-browser (native decode) and
8229// re-encodes to MP3 with lamejs so they play on every device incl. iPhone.
8230// ============================================================
8231async function findLegacyVoiceNotes() {
8232  if (!sbClient) return [];
8233  const { data, error } = await sbClient
8234    .from('posts')
8235    .select('id, media_url, media_type, post_type')
8236    .or('media_type.eq.audio,post_type.eq.voice_note');
8237  if (error) { console.error('Legacy scan failed:', error); return []; }
8238  return (data || []).filter(p => p.media_url && /\.webm(\?|$)/i.test(p.media_url));
8239}
8240
8241async function checkLegacyVoiceNotes() {
8242  // Only teachers can run the migration (and only they can update others' posts)
8243  if (!currentProfile || currentProfile.role !== 'teacher') return;
8244  const banner = document.getElementById('legacy-audio-banner');
8245  if (!banner) return;
8246  const legacy = await findLegacyVoiceNotes();
8247  if (legacy.length > 0) {
8248    document.getElementById('legacy-audio-text').innerHTML =
8249      legacy.length + ' older voice note' + (legacy.length === 1 ? '' : 's') +
8250      ' use a format that doesn&rsquo;t play on iPhones. Convert to MP3 so every sister can listen on any device. Your original posts stay in place &mdash; only the audio format is upgraded.';
8251    banner.style.display = 'block';
8252  } else {
8253    banner.style.display = 'none';
8254  }
8255}
8256
8257async function convertOneWebmToMp3(url) {
8258  // Fetch the WebM bytes and decode natively (Chrome decodes Opus/WebM)
8259  const resp = await fetch(url.split('#')[0]);
8260  const buf = await resp.arrayBuffer();
8261  const ctx = new (window.AudioContext || window.webkitAudioContext)();
8262  const audioBuffer = await ctx.decodeAudioData(buf);
8263
8264  // Downmix to mono Float32
8265  const len = audioBuffer.length;
8266  const mono = new Float32Array(len);
8267  const chs = audioBuffer.numberOfChannels;
8268  for (let c = 0; c < chs; c++) {
8269    const d = audioBuffer.getChannelData(c);
8270    for (let i = 0; i < len; i++) mono[i] += d[i] / chs;
8271  }
8272  const sampleRate = Math.round(audioBuffer.sampleRate);
8273  try { ctx.close(); } catch (e) {}
8274
8275  // Encode to MP3 (mono, 96kbps)
8276  const encoder = new lamejs.Mp3Encoder(1, sampleRate, 96);
8277  const block = 1152;
8278  const mp3Data = [];
8279  const samples = new Int16Array(len);
8280  for (let i = 0; i < len; i++) {
8281    const s = Math.max(-1, Math.min(1, mono[i]));
8282    samples[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
8283  }
8284  for (let i = 0; i < samples.length; i += block) {
8285    const chunk = samples.subarray(i, i + block);
8286    const enc = encoder.encodeBuffer(chunk);
8287    if (enc.length > 0) mp3Data.push(new Int8Array(enc));
8288  }
8289  const end = encoder.flush();
8290  if (end.length > 0) mp3Data.push(new Int8Array(end));
8291  return new Blob(mp3Data, { type: 'audio/mpeg' });
8292}
8293
8294async function convertLegacyVoiceNotes() {
8295  const btn = document.getElementById('legacy-audio-btn');
8296  if (!btn || btn.disabled) return;
8297  if (!currentProfile || currentProfile.role !== 'teacher') {
8298    showToast('Only a teacher can run this conversion.', 'error');
8299    return;
8300  }
8301  btn.disabled = true;
8302  const original = btn.innerHTML;
8303
8304  const legacy = await findLegacyVoiceNotes();
8305  if (legacy.length === 0) {
8306    document.getElementById('legacy-audio-banner').style.display = 'none';
8307    return;
8308  }
8309
8310  let done = 0, failed = 0;
8311  for (let i = 0; i < legacy.length; i++) {
8312    const post = legacy[i];
8313    btn.innerHTML = 'Converting ' + (i + 1) + ' of ' + legacy.length + '…';
8314    try {
8315      const mp3Blob = await convertOneWebmToMp3(post.media_url);
8316      const path = currentUser.id + '/converted-' + post.id + '-' + Date.now() + '.mp3';
8317      const { error: upErr } = await sbClient.storage
8318        .from('Media')
8319        .upload(path, mp3Blob, { cacheControl: '3600', upsert: true, contentType: 'audio/mpeg' });
8320      if (upErr) throw upErr;
8321
8322      const { data: urlData } = sbClient.storage.from('Media').getPublicUrl(path);
8323
8324      const { error: updErr } = await sbClient
8325        .from('posts')
8326        .update({ media_url: urlData.publicUrl, media_type: 'audio' })
8327        .eq('id', post.id);
8328      if (updErr) throw updErr;
8329
8330      done++;
8331    } catch (e) {
8332      console.error('Convert failed for post ' + post.id, e);
8333      failed++;
8334    }
8335  }
8336
8337  btn.innerHTML = original;
8338  btn.disabled = false;
8339
8340  if (failed === 0) {
8341    showToast('Converted ' + done + ' voice note' + (done === 1 ? '' : 's') + ' to MP3. They now play on every device.', 'success');
8342    document.getElementById('legacy-audio-banner').style.display = 'none';
8343    // Clear cached players and reload the feed so the new MP3s render
8344    vnCache = {};
8345    if (typeof loadFeed === 'function') loadFeed();
8346  } else {
8347    showToast('Converted ' + done + ', but ' + failed + ' failed. Check the console and try again.', 'error');
8348  }
8349}
8350</script>
8350
8351
8352<form method='POST' name='new-member-signup' style='display:none;'>
8353  <input type="hidden" name="form-name" value="new-member-signup">
8354  <input type="hidden" name="member-name" id="notify-member-name">
8355  <input type="hidden" name="member-email" id="notify-member-email">
8356  <input type="hidden" name="signup-date" id="notify-signup-date">
8357</form>
8358
8359</body>
8360</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.