Refactor streak modal to overlay native React component on transparent celebration canvas iframe
This commit is contained in:
@@ -153,6 +153,20 @@
|
|||||||
.stage.play .count{animation:none; opacity:1; transform:none}
|
.stage.play .count{animation:none; opacity:1; transform:none}
|
||||||
.stage.play.tier3 .scene,.stage.play.tier3 .flash{animation:none}
|
.stage.play.tier3 .scene,.stage.play.tier3 .flash{animation:none}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
body.sad {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
body::after {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
#stage, .hint {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<canvas id="fw"></canvas>
|
<canvas id="fw"></canvas>
|
||||||
@@ -169,13 +183,16 @@
|
|||||||
<div class="caption" id="caption">GROWTH SCORE</div>
|
<div class="caption" id="caption">GROWTH SCORE</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
<div class="panel" style="background: transparent; box-shadow: none;">
|
<label for="num">Number</label>
|
||||||
<button class="replay" id="enter-workspace" style="width: 100%; height: 56px; font-size: 1.125rem; background: #00F0FF; color: black; box-shadow: 0 0 20px -3px #00F0FF; text-transform: none; border-radius: 12px; margin-top: 16px;">Enter Workspace</button>
|
<input id="num" type="text" inputmode="numeric" value="2,026" aria-label="Target number">
|
||||||
|
<input id="cap" class="cap" type="text" value="GROWTH SCORE" aria-label="Caption" maxlength="16">
|
||||||
|
<button class="replay" id="replay">Replay</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="hint" style="position:absolute;bottom:6px;left:50%;transform:translateX(-50%);z-index:11">
|
||||||
|
under 5 = sad · higher = bigger celebration
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const stage=document.getElementById('stage');
|
const stage=document.getElementById('stage');
|
||||||
@@ -336,9 +353,8 @@
|
|||||||
|
|
||||||
function play(){
|
function play(){
|
||||||
clearTimeout(countTimer); clearTimeout(moodTimer); cancelAnimationFrame(countRAF); stopFw();
|
clearTimeout(countTimer); clearTimeout(moodTimer); cancelAnimationFrame(countRAF); stopFw();
|
||||||
|
|
||||||
const target=parseTarget();
|
const target=parseTarget();
|
||||||
const sad = target === 0;
|
const sad = target<5;
|
||||||
const t = sad ? 0 : Math.min(1, Math.log10(Math.max(target,5)/5)/3); // 5→0 … 5000+→1
|
const t = sad ? 0 : Math.min(1, Math.log10(Math.max(target,5)/5)/3); // 5→0 … 5000+→1
|
||||||
|
|
||||||
// set mood + intensity
|
// set mood + intensity
|
||||||
@@ -351,8 +367,7 @@
|
|||||||
stage.style.setProperty('--halo-w',(160+t*70).toFixed(0)+'%');
|
stage.style.setProperty('--halo-w',(160+t*70).toFixed(0)+'%');
|
||||||
}
|
}
|
||||||
|
|
||||||
capEl.textContent= sad ? "START YOUR STREAK!" : "DAY STREAK ACTIVE";
|
capEl.textContent=(capInput.value||'').trim();
|
||||||
|
|
||||||
numEl.textContent='0';
|
numEl.textContent='0';
|
||||||
void stage.offsetWidth; stage.classList.add('play');
|
void stage.offsetWidth; stage.classList.add('play');
|
||||||
|
|
||||||
@@ -363,11 +378,8 @@
|
|||||||
countTimer=setTimeout(()=>countUp(target), 2750);
|
countTimer=setTimeout(()=>countUp(target), 2750);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
replay.addEventListener('click', play);
|
||||||
const enterBtn = document.getElementById('enter-workspace');
|
numInput.addEventListener('keydown', e=>{ if(e.key==='Enter') play(); });
|
||||||
enterBtn.addEventListener('click', () => {
|
capInput.addEventListener('keydown', e=>{ if(e.key==='Enter') play(); });
|
||||||
window.parent.postMessage('close-streak-modal', '*');
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('load', ()=>setTimeout(play,180));
|
window.addEventListener('load', ()=>setTimeout(play,180));
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
47
scratch/port_streak_clean.py
Normal file
47
scratch/port_streak_clean.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
def port_html():
|
||||||
|
source_path = r"C:\Users\naman\OneDrive\Desktop\pragativeer-v2\pragativeer-celebration_1.html"
|
||||||
|
dest_path = r"C:\Users\naman\OneDrive\Desktop\pragativeer-v2\public\streak-animation.html"
|
||||||
|
|
||||||
|
with open(source_path, 'r', encoding='utf-8') as f:
|
||||||
|
html = f.read()
|
||||||
|
|
||||||
|
# 1. Inject CSS to make background transparent and hide stage layout
|
||||||
|
hide_css = """
|
||||||
|
body {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
body.sad {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
body::after {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
#stage, .hint {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Insert hide_css inside <style>
|
||||||
|
style_tag = "</style>"
|
||||||
|
html = html.replace(style_tag, hide_css + "\n</style>", 1)
|
||||||
|
|
||||||
|
# 2. Replace parseTarget to read from URL search params
|
||||||
|
parse_target_replacement = """
|
||||||
|
const parseTarget=()=>{
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const s = params.get('streak');
|
||||||
|
const v=parseFloat(s);
|
||||||
|
return isFinite(v)?v:0;
|
||||||
|
};
|
||||||
|
"""
|
||||||
|
html = re.sub(r"const parseTarget=\(\)=>\{.*?\};", parse_target_replacement, html, flags=re.DOTALL)
|
||||||
|
|
||||||
|
with open(dest_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(html)
|
||||||
|
print("Ported successfully to public/streak-animation.html")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
port_html()
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Flame, Trophy, Sparkles } from "lucide-react";
|
||||||
import { useActiveUser } from "@/hooks/useActiveUser";
|
import { useActiveUser } from "@/hooks/useActiveUser";
|
||||||
|
|
||||||
export function StreakOnboardingModal() {
|
export function StreakOnboardingModal() {
|
||||||
@@ -17,31 +19,99 @@ export function StreakOnboardingModal() {
|
|||||||
}
|
}
|
||||||
}, [user, loading]);
|
}, [user, loading]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleMessage = (event: MessageEvent) => {
|
|
||||||
if (event.data === 'close-streak-modal') {
|
|
||||||
if (user) {
|
|
||||||
sessionStorage.setItem(`hasSeenStreakPopup_${user.id}`, "true");
|
|
||||||
}
|
|
||||||
setIsOpen(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("message", handleMessage);
|
|
||||||
return () => window.removeEventListener("message", handleMessage);
|
|
||||||
}, [user]);
|
|
||||||
|
|
||||||
if (!isOpen || !user) return null;
|
if (!isOpen || !user) return null;
|
||||||
|
|
||||||
const streak = user.streak_count;
|
const streak = user.streak_count;
|
||||||
|
const targetExam = user.target_exam?.replace("_", " ") || "JEE/NEET";
|
||||||
|
|
||||||
|
// Streak-based wishes and titles
|
||||||
|
let wishTitle = "Welcome to PragatiVeer! 🌟";
|
||||||
|
let wishMessage = `Prepare yourself to conquer ${targetExam}. Your daily practice starts here!`;
|
||||||
|
let badgeBorderColor = "border-[#00F0FF]/30";
|
||||||
|
let badgeShadow = "shadow-[0_0_25px_rgba(0,240,255,0.25)]";
|
||||||
|
|
||||||
|
if (streak === 0) {
|
||||||
|
wishTitle = "Start Your Streak Today! 🚀";
|
||||||
|
wishMessage = "Today is Day 1 of your journey. Establish your streak now and build the consistency needed for IIT/AIIMS!";
|
||||||
|
badgeBorderColor = "border-blue-500/30";
|
||||||
|
badgeShadow = "shadow-[0_0_20px_rgba(59,130,246,0.2)]";
|
||||||
|
} else if (streak < 4) {
|
||||||
|
wishTitle = "Keep the Fire Burning! 🔥";
|
||||||
|
wishMessage = `Great job! You have maintained a ${streak}-day study streak. Keep practicing daily to build solid muscle memory!`;
|
||||||
|
badgeBorderColor = "border-orange-500/30";
|
||||||
|
badgeShadow = "shadow-[0_0_20px_rgba(249,115,22,0.25)]";
|
||||||
|
} else {
|
||||||
|
wishTitle = "Unstoppable Dedication! ⚡";
|
||||||
|
wishMessage = `Incredible! A ${streak}-day streak shows true championship discipline. You are outperforming the competition. Let's make today count!`;
|
||||||
|
badgeBorderColor = "border-[#D1FF4D]/30";
|
||||||
|
badgeShadow = "shadow-[0_0_25px_rgba(209,255,77,0.25)]";
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEnter = () => {
|
||||||
|
sessionStorage.setItem(`hasSeenStreakPopup_${user.id}`, "true");
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-[100] bg-black">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/90 backdrop-blur-lg animate-in fade-in duration-300">
|
||||||
|
|
||||||
|
{/* Celebration Animation Canvas Overlay */}
|
||||||
<iframe
|
<iframe
|
||||||
src={`/streak-animation.html?streak=${streak}`}
|
src={`/streak-animation.html?streak=${streak}`}
|
||||||
className="w-full h-full border-none"
|
className="absolute inset-0 w-full h-full border-none pointer-events-none z-10"
|
||||||
title="Streak Celebration"
|
title="Streak Celebration"
|
||||||
|
allowTransparency
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Adaptive Modal Content Card */}
|
||||||
|
<div className="bg-background/95 border border-border/80 shadow-[0_0_60px_-10px_rgba(0,240,255,0.15)] rounded-3xl w-full max-w-lg p-8 text-center space-y-6 relative overflow-hidden animate-in zoom-in-95 slide-in-from-bottom-4 duration-300 z-20 pointer-events-auto mx-4">
|
||||||
|
|
||||||
|
{/* Subtle background glow */}
|
||||||
|
<div className="absolute -right-20 -top-20 size-48 bg-[#00F0FF]/5 rounded-full blur-3xl pointer-events-none" />
|
||||||
|
<div className="absolute -left-20 -bottom-20 size-48 bg-primary/5 rounded-full blur-3xl pointer-events-none" />
|
||||||
|
|
||||||
|
{/* Pulsing Badge with PragatiVeer Logo */}
|
||||||
|
<div className="relative size-28 mx-auto flex items-center justify-center">
|
||||||
|
{/* Glowing pulse rings */}
|
||||||
|
<div className="absolute inset-2 rounded-full bg-[#00F0FF]/10 animate-ping" style={{ animationDuration: '3s' }} />
|
||||||
|
|
||||||
|
<img
|
||||||
|
src="/PragatiVeer_Logo/SVG_Logos/Square_dark_background.svg"
|
||||||
|
alt="PragatiVeer"
|
||||||
|
className={`size-24 rounded-full border-2 ${badgeBorderColor} ${badgeShadow} animate-pulse relative z-10 object-cover`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Text Greetings */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
{streak > 0 ? (
|
||||||
|
<div className="inline-flex items-center gap-1.5 px-3 py-1 bg-orange-500/10 border border-orange-500/20 text-orange-500 text-xs font-extrabold rounded-full tracking-wider uppercase">
|
||||||
|
<Flame size={12} className="fill-current animate-bounce" /> {streak} Day Streak Active
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="inline-flex items-center gap-1.5 px-3 py-1 bg-blue-500/10 border border-blue-500/20 text-blue-400 text-xs font-extrabold rounded-full tracking-wider uppercase">
|
||||||
|
<Sparkles size={12} className="animate-spin" style={{ animationDuration: '4s' }} /> Day 1 Awaits
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<h3 className="text-2xl md:text-3xl font-black tracking-tight bg-gradient-to-r from-white via-white to-gray-400 bg-clip-text text-transparent">
|
||||||
|
{wishTitle}
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-400 text-sm md:text-base leading-relaxed max-w-sm mx-auto pt-1">
|
||||||
|
{wishMessage}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action Button */}
|
||||||
|
<div className="pt-2">
|
||||||
|
<Button
|
||||||
|
onClick={handleEnter}
|
||||||
|
className="w-full h-14 rounded-xl text-lg font-black bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 shadow-[0_0_20px_-3px_#00F0FF] hover:shadow-[0_0_30px_#00F0FF] hover:-translate-y-1 transition-all duration-300"
|
||||||
|
>
|
||||||
|
Enter Workspace
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user