// React loaded via CDN const { useState, useEffect, useCallback, useRef, createContext, useContext } = React; /* ── Font loader ─────────────────────────────────────────────────────── */ function FontLoader() { useEffect(() => { const id = "iq-fonts"; if (document.getElementById(id)) return; const l = document.createElement("link"); l.id = id; l.rel = "stylesheet"; l.href = "https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400&family=DM+Sans:wght@300;400;500;600&display=swap"; document.head.appendChild(l); }, []); return null; } /* ── CSS ─────────────────────────────────────────────────────────────── */ const CSS = ` *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } body { background: #07090f; transition: background 0.35s; } body.light { background: #f5f0e8; } select option { background: #161c2a; color: #f0ebe0; } body.light select option { background: #fff; color: #0a0804; } body.light { color: #0a0804; font-weight: 500; } body.light h1, body.light h2, body.light h3, body.light strong, body.light b { font-weight: 800; color: #000; } input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; } input::placeholder { color: rgba(240,235,224,0.22); } body.light input::placeholder { color: rgba(30,20,10,0.3); } textarea::placeholder { color: rgba(240,235,224,0.22); } body.light textarea::placeholder { color: rgba(30,20,10,0.3); } ::-webkit-scrollbar { width: 4px; } ::-webkit-scrollbar-thumb { background: rgba(200,169,110,0.35); border-radius: 4px; } * { transition: background-color 0.25s, border-color 0.25s, color 0.15s; } @keyframes fadeUp { from{opacity:0;transform:translateY(18px)} to{opacity:1;transform:translateY(0)} } @keyframes floatIn { from{opacity:0;transform:translateY(32px) scale(0.97)} to{opacity:1;transform:translateY(0) scale(1)} } @keyframes spin { to{transform:rotate(360deg)} } @keyframes shimTxt { 0%{background-position:-200% center} 100%{background-position:200% center} } @keyframes meshA { 0%,100%{transform:translate(0,0) scale(1)} 40%{transform:translate(40px,-28px) scale(1.07)} 70%{transform:translate(-18px,36px) scale(0.95)} } @keyframes meshB { 0%,100%{transform:translate(0,0)} 50%{transform:translate(-55px,28px) rotate(11deg)} } @keyframes glow { 0%,100%{box-shadow:0 4px 20px rgba(200,169,110,0.22)} 50%{box-shadow:0 4px 38px rgba(200,169,110,0.42)} } @keyframes imgPop { from{opacity:0;transform:scale(0.94)} to{opacity:1;transform:scale(1)} } .fi { animation: floatIn 0.85s cubic-bezier(0.22,1,0.36,1) both; } .fu { animation: fadeUp 0.6s cubic-bezier(0.22,1,0.36,1) both; } .sht { background: linear-gradient(90deg,#c8a96e 0%,#e2c98a 40%,#f0ebe0 50%,#e2c98a 60%,#c8a96e 100%); background-size: 200% auto; -webkit-background-clip:text; -webkit-text-fill-color:transparent; background-clip:text; animation: shimTxt 4s linear infinite; } .ch { transition:transform 0.15s,filter 0.15s; cursor:pointer; } .ch:hover { transform:scale(1.04); filter:brightness(1.16); } .bg { transition:filter 0.18s,transform 0.18s; cursor:pointer; } .bg:hover { filter:brightness(1.1); transform:translateY(-1px); } .bo { transition:border-color 0.18s,color 0.18s; cursor:pointer; } .bo:hover { border-color:#c8a96e !important; color:#c8a96e !important; } .tb { transition:color 0.2s,border-bottom-color 0.2s; cursor:pointer; } .tb:hover { color:#e2c98a !important; } .ie:focus { border-color:#c8a96e !important; box-shadow:0 0 0 3px rgba(200,169,110,0.13) !important; outline:none; } .img-thumb { animation: imgPop 0.3s ease both; } .upload-zone:hover { border-color: rgba(200,169,110,0.5) !important; background: rgba(200,169,110,0.06) !important; } `; /* ── Theme palettes ─────────────────────────────────────────────────── */ const DARK_THEME = { bg:"#07090f", card:"#111520", card2:"#161c2a", gold:"#c8a96e", goldLight:"#e2c98a", cream:"#f0ebe0", muted:"rgba(240,235,224,0.32)", green:"#2a9d74", red:"#b84040", border:"rgba(200,169,110,0.15)", headerBg:"rgba(9,11,17,0.97)", tabBg:"rgba(10,12,18,0.93)", inputBg:"rgba(240,235,224,0.04)", inputBorder:"rgba(200,169,110,0.16)", overlayBg:"rgba(7,9,15,0.9)", statBg:"#161c2a", blobA:"rgba(155,35,53,0.17)", blobB:"rgba(200,169,110,0.09)", gridLine:"rgba(200,169,110,0.027)", cellEmpty:"rgba(240,235,224,0.02)", cellBorderDefault:"rgba(200,169,110,0.07)", encBg:"rgba(200,169,110,0.06)", encBorder:"rgba(200,169,110,0.14)", footerBg:"rgba(7,9,15,0.55)", }; const LIGHT_THEME = { bg:"#f5f0e8", card:"#ffffff", card2:"#f0ebe0", gold:"#9b6e2a", goldLight:"#c8902a", cream:"#0a0804", muted:"rgba(10,8,4,0.5)", green:"#1a7a55", red:"#a03030", border:"rgba(155,110,42,0.22)", headerBg:"rgba(255,252,245,0.97)", tabBg:"rgba(245,240,232,0.98)", inputBg:"rgba(26,18,8,0.04)", inputBorder:"rgba(155,110,42,0.22)", overlayBg:"rgba(30,20,10,0.7)", statBg:"#efe9da", blobA:"rgba(155,35,53,0.08)", blobB:"rgba(200,169,110,0.12)", gridLine:"rgba(155,110,42,0.06)", cellEmpty:"rgba(26,18,8,0.02)", cellBorderDefault:"rgba(155,110,42,0.1)", encBg:"rgba(155,110,42,0.06)", encBorder:"rgba(155,110,42,0.18)", footerBg:"rgba(245,240,232,0.9)", }; function mkStyles(T){ const IS={ width:"100%", padding:"11px 14px", background:T.inputBg, border:"1px solid "+T.inputBorder, borderRadius:9, color:T.cream, fontFamily:"'DM Sans',sans-serif", fontSize:"0.9rem", outline:"none", transition:"border-color 0.2s,box-shadow 0.2s" }; const LS={ display:"block", fontFamily:"'DM Sans',sans-serif", fontSize:"0.62rem", color:T.muted, letterSpacing:2, textTransform:"uppercase", marginBottom:7 }; return{IS,LS}; } /* ── Theme Context — every component calls useT() for current theme ─── */ const ThemeCtx = createContext(DARK_THEME); function useT(){ return useContext(ThemeCtx); } function useStyles(){ const T=useT(); return {...mkStyles(T), T}; } /* ── Data ────────────────────────────────────────────────────────────── */ const CURRENCIES = [ {code:"USD",symbol:"$",flag:"🇺🇸"},{code:"EUR",symbol:"€",flag:"🇪🇺"}, {code:"GBP",symbol:"£",flag:"🇬🇧"},{code:"JPY",symbol:"¥",flag:"🇯🇵"}, {code:"CAD",symbol:"CA$",flag:"🇨🇦"},{code:"AUD",symbol:"A$",flag:"🇦🇺"}, {code:"BTC",symbol:"₿",flag:"🪙"},{code:"ETH",symbol:"Ξ",flag:"🔷"}, ]; const MONTHS = ["January","February","March","April","May","June","July","August","September","October","November","December"]; const WDAYS = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]; const ATYPES = ["Stocks","Forex","Crypto","Options","Futures","Commodities","ETFs","Other"]; const DTYPES = ["days","weeks","months","years"]; /* ── Personalized encouragements ─────────────────────────────────────── */ const ENCOURAGEMENTS = [ (n) => `Good morning, ${n}! Every trade is a step closer to your freedom. 🔥`, (n) => `${n}, discipline today builds the lifestyle you want tomorrow. 💪`, (n) => `You're on the path, ${n}. Stay consistent — compounding rewards patience. 📈`, (n) => `${n}, the market rewards those who show up prepared. Let's get it! 🎯`, (n) => `Keep stacking those wins, ${n}. Your goal is getting closer every day. 🚀`, (n) => `${n}, one focused session can change your week. Go make it count. ⚡`, (n) => `Legends are built one trade at a time, ${n}. Today's your day. 👑`, (n) => `${n}, trust the process. Your strategy + consistency = freedom. 💎`, ]; function getDailyEncouragement(firstName) { const day = new Date().getDay(); return ENCOURAGEMENTS[day % ENCOURAGEMENTS.length](firstName || "Trader"); } /* ── Utils ───────────────────────────────────────────────────────────── */ function fmtDate(y,m,d){ return y+"-"+String(m+1).padStart(2,"0")+"-"+String(d).padStart(2,"0"); } function todayStr(){ const n=new Date(); return fmtDate(n.getFullYear(),n.getMonth(),n.getDate()); } function isWknd(y,m,d){ const w=new Date(y,m,d).getDay(); return w===0||w===6; } function firstName(name){ return name ? name.trim().split(" ")[0] : "Trader"; } function buildDays(start,durType,durVal,inclWknd){ const days=[],d=new Date(start); const total=durType==="weeks"?durVal*7:durType==="months"?durVal*30:durType==="years"?durVal*365:durVal; let n=0; while(n grows principal // cashOut = dailyProfit - reinvested -> liquid cash kept let principal=dep, totalCash=0; const sc=[]; for(const date of days){ const dailyProfit = principal*(pct/100); const reinvested = dailyProfit*(rv/100); const cashOut = dailyProfit - reinvested; principal += reinvested; totalCash += cashOut; sc.push({ date, dailyProfit, principal, cashOut, totalCash, totalWealth: principal + totalCash, }); } return{ sc, goalAmount: principal + totalCash }; } function money(sym,n){ if(n===undefined||n===null) return sym+"0.00"; return(n<0?"-":"")+sym+Math.abs(n).toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2}); } function getSym(code){ return(CURRENCIES.find(c=>c.code===code)||{symbol:"$"}).symbol; } /* ── Storage ─────────────────────────────────────────────────────────── */ async function dbGet(k){ try{const r=await window.storage.get(k);return r?JSON.parse(r.value):null;}catch(_){return null;} } async function dbSet(k,v){ try{await window.storage.set(k,JSON.stringify(v));}catch(_){} } /* ── Notifications ───────────────────────────────────────────────────── */ function askNotif(){ if(typeof Notification!=="undefined"&&Notification.permission==="default") Notification.requestPermission(); } function pushNotif(title,body){ if(typeof Notification!=="undefined"&&Notification.permission==="granted") try{new Notification(title,{body});}catch(_){} } /* ── Logo ────────────────────────────────────────────────────────────── */ function Logo({sz=48,showText=true}){ const T=useT(); return(
IQ {showText&&(
I QUIT Goal Tracker
)}
); } /* ── Login / Sign-up ─────────────────────────────────────────────────── */ /* ── Login / Sign-up ─────────────────────────────────────────────────── */ function LoginPage({onLogin}){ const T=useT(); const {IS,LS}=mkStyles(T); const [mode,setMode]=useState("login"); const [firstName_,setFirstName]=useState(""); const [lastName,setLastName]=useState(""); const [email,setEmail]=useState(""); const [pass,setPass]=useState(""); const [err,setErr]=useState(""); const [loading,setLoading]=useState(false); const [isMobile,setIsMobile]=useState(()=>window.innerWidth<700); useEffect(()=>{ const fn=()=>setIsMobile(window.innerWidth<700); window.addEventListener("resize",fn); return()=>window.removeEventListener("resize",fn); },[]); const submit=async()=>{ setErr(""); setLoading(true); await new Promise(r=>setTimeout(r,700)); if(mode==="signup"){ if(!firstName_.trim()){setErr("Please enter your first name.");setLoading(false);return;} if(!email.trim()){setErr("Please enter your email address.");setLoading(false);return;} const fullName=(firstName_.trim()+" "+lastName.trim()).trim(); await dbSet("iq-user",{name:fullName,firstName:firstName_.trim(),email}); onLogin({name:fullName,firstName:firstName_.trim(),email}); }else{ const saved=await dbGet("iq-user"); if(saved){ onLogin(saved); } else{ const fn=email.split("@")[0]||"Trader"; onLogin({name:fn,firstName:fn,email}); } } setLoading(false); }; const FormFields=( <> {mode==="signup"&&( <>
setFirstName(e.target.value)}/>
setLastName(e.target.value)}/>
setEmail(e.target.value)}/>
)} {mode==="login"&&(
setEmail(e.target.value)}/>
)}
setPass(e.target.value)} onKeyDown={e=>{if(e.key==="Enter")submit();}}/>
{err&&(
{err}
)}
{mode==="login"?"No account? ":"Already have one? "} {setMode(m=>m==="login"?"signup":"login");setErr("");}}> {mode==="login"?"Sign up free":"Sign in"}
Your data \u00b7 Your goals \u00b7 Your freedom
); /* ════ MOBILE PORTRAIT ════ */ if(isMobile){ return(
{/* Hero top */}

{"\"Quit trading for a "} paycheck {" \u2014 and let trading become your paycheck.\""}

{[["$2.4M","Avg Goal"],["84%","Win Rate"],["12K+","Days"]].map(([v,l])=>(
{v}
{l}
))}
{/* Form card */}

{mode==="login"?"Welcome back.":"Start your journey."}

{mode==="login"?"Sign in to your personal dashboard.":"Create your free account below."}

{FormFields}
); } /* ════ DESKTOP ════ */ return(

{"\"The moment you decide to "} quit trading for a paycheck {" is the moment trading becomes your paycheck.\""}

{[["$2.4M","Avg. Goal Set"],["84%","Win Rate"],["12K+","Days Logged"]].map(([v,l])=>(
{v}
{l}
))}
\u00a9 2025 I Quit Goal Tracker \u00b7 Built for Freedom

{mode==="login"?"Welcome back.":"Start your journey."}

{mode==="login"?"Sign in to your personal dashboard.":"Create your free account below."}

{FormFields}
); } /* ── Overlay Modal ───────────────────────────────────────────────────── */ function Modal({onClose,children}){ const T=useT(); useEffect(()=>{ const fn=e=>{if(e.key==="Escape")onClose();}; window.addEventListener("keydown",fn); return()=>window.removeEventListener("keydown",fn); },[onClose]); return(
{if(e.target===e.currentTarget)onClose();}} style={{position:"fixed",top:0,right:0,bottom:0,left:0,background:T.overlayBg,backdropFilter:"blur(8px)",zIndex:300,display:"flex",alignItems:"center",justifyContent:"center",padding:16}}>
{children}
); } /* ── Trade Modal ─────────────────────────────────────────────────────── */ function TradeModal({dateStr,existing,currency,onSave,onClose}){ const T=useT(); const {IS,LS}=mkStyles(T); const sym=getSym(currency); const fileRef=useRef(null); const cameraRef=useRef(null); const [form,setForm]=useState(()=>({ asset:"", assetType:"Stocks", direction:"Long", entry:"", exit:"", size:"", pnl:"", notes:"", win:null, setupImage: null, ...(existing||{}) })); const set=k=>e=>setForm(f=>({...f,[k]:e.target.value})); // Dynamic labels based on asset type const isOptions = form.assetType==="Options"; const isFutures = form.assetType==="Futures"; const dir1Label = isOptions ? "📞 Call" : "▲ Long"; const dir2Label = isOptions ? "🅿 Put" : "▼ Short"; const dir1Val = isOptions ? "Call" : "Long"; const dir2Val = isOptions ? "Put" : "Short"; const entryLabel = isFutures ? "Entry Ticks" : "Entry Price"; const exitLabel = isFutures ? "Exit Ticks" : "Exit Price"; const entryPh = isFutures ? "e.g. 4520" : "0.00"; const exitPh = isFutures ? "e.g. 4535" : "0.00"; // When asset type changes, reset direction to default for that type const handleAssetType=e=>{ const t=e.target.value; setForm(f=>({...f,assetType:t,direction:t==="Options"?"Call":"Long"})); }; const autoCalc=()=>{ const p=parseFloat(form.entry),q=parseFloat(form.exit),s=parseFloat(form.size); if(!isNaN(p)&&!isNaN(q)&&!isNaN(s)){ let raw=(q-p)*s; if(form.direction==="Short"||form.direction==="Put") raw=-raw; setForm(f=>({...f,pnl:raw.toFixed(2),win:raw>=0})); } }; // Image upload handler const handleImageFile=file=>{ if(!file) return; const reader=new FileReader(); reader.onload=e=>setForm(f=>({...f,setupImage:e.target.result})); reader.readAsDataURL(file); }; const save=()=>{ const n=parseFloat(form.pnl); onSave({...form,pnl:n,win:n>=0}); }; const pnlNum=parseFloat(form.pnl); const pnlColor=pnlNum>=0?T.green:T.red; return( {/* Header */}
Trade Journal
{dateStr}
{/* Row 1: Asset + Type */}
{/* Row 2: Direction (dynamic labels) + Size */}
{[dir1Val,dir2Val].map((val,idx)=>{ const lbl=idx===0?dir1Label:dir2Label; const isActive=form.direction===val; const activeCol=isOptions?(idx===0?"#2a9d74":"#b84040"):(idx===0?T.green:T.red); return( ); })}
{/* Row 3: Entry + Exit (dynamic labels for Futures) */}
{/* P&L */}
setForm(f=>({...f,pnl:e.target.value,win:parseFloat(e.target.value)>=0}))} placeholder="0.00"/>
{/* Notes */}