Files
FXandRatesDashboard/index.html
T
Tvishi Agarwal 3e4a3ed03f test1
2026-04-09 20:16:51 +00:00

385 lines
41 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/>
<title>FX RADAR TEST v4</title>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.23.9/babel.min.js"></script>
<style>*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}html,body,#root{height:100%}body{background:#0a0e17;font-family:'IBM Plex Sans',system-ui,sans-serif;color:#e2e8f0;font-size:13px}::-webkit-scrollbar{width:5px;height:5px}::-webkit-scrollbar-track{background:#0a0e17}::-webkit-scrollbar-thumb{background:#334155;border-radius:3px}button{font-family:inherit;cursor:pointer}table{border-spacing:0}@keyframes spin{to{transform:rotate(360deg)}}.sp{width:12px;height:12px;border:2px solid #334155;border-top-color:#6366f1;border-radius:50%;animation:spin .8s linear infinite;display:inline-block;vertical-align:middle}@media(max-width:900px){.g2{grid-template-columns:1fr!important}.g3{grid-template-columns:1fr!important}.hmob{display:none!important}}</style>
</head><body><div id="root"></div>
<script type="text/babel">
const {useState,useEffect,useRef,useCallback}=React;
const C={bg:"#0a0e17",bgC:"#111827",bd:"#1e293b",tx:"#e2e8f0",txD:"#94a3b8",txM:"#64748b",g:"#22c55e",r:"#ef4444",b:"#3b82f6",am:"#f59e0b",ac:"#6366f1",acD:"#4338ca",tl:"#14b8a6"};
const cc=v=>v>0?C.g:v<0?C.r:C.txM;const fc=(v,s="%")=>v!=null?(v>0?"+":"")+Number(v).toFixed(2)+s:"—";const fb=v=>v!=null?(v>0?"+":"")+Number(v).toFixed(1)+"bp":"—";const pc=(a,b)=>b?+((a/b-1)*100).toFixed(2):null;const Mn="'JetBrains Mono',monospace";
const _d=s=>{try{return atob(s)}catch{return""}};const _K={finnhub:_d("ZDc5ZGg1cHIwMXFxcG1oZ2QwYjBkNzlkaDVwcjAxcXFwbWhnZDBiZw==")};
const gk=n=>{try{return localStorage.getItem("fxr_"+n)||""}catch{return""}};const sk=(n,v)=>{try{localStorage.setItem("fxr_"+n,v)}catch{}};
const jf=async u=>{const r=await fetch(u);if(!r.ok)throw new Error(r.status);return r.json()};
async function fFr(d,sym){try{return await jf("https://api.frankfurter.dev/v1/"+d+"?base=USD&symbols="+sym)}catch{return null}}
async function fFD(){try{const r=await fetch("/.netlify/functions/fred-yields");if(!r.ok)throw new Error("fred-yields failed");return await r.json()}catch{return null}}
async function fECB(){try{const r=await fetch("/.netlify/functions/ecb-yields");if(!r.ok)throw new Error("ecb-yields failed");return await r.json()}catch(err){console.error("ECB yields fetch failed",err);return null}}
async function fFh(ep,p,k){if(!k)return null;try{const q=new URLSearchParams({...p,token:k}).toString();return await jf("https://finnhub.io/api/v1/"+ep+"?"+q)}catch{return null}}
async function fData(){const r=await fetch("/data.json?_="+Date.now());if(!r.ok)throw new Error("Failed to load data.json");return await r.json()}
const ago=n=>{const d=new Date();d.setDate(d.getDate()-n);return d.toISOString().slice(0,10)};const fwd=n=>{const d=new Date();d.setDate(d.getDate()+n);return d.toISOString().slice(0,10)};const jan2=()=>new Date().getFullYear()+"-01-02";
const G10=[{p:"EURUSD",q:"EUR",i:1},{p:"GBPUSD",q:"GBP",i:1},{p:"USDJPY",q:"JPY",i:0},{p:"USDCHF",q:"CHF",i:0},{p:"AUDUSD",q:"AUD",i:1},{p:"NZDUSD",q:"NZD",i:1},{p:"USDCAD",q:"CAD",i:0},{p:"USDSEK",q:"SEK",i:0},{p:"USDNOK",q:"NOK",i:0}];
const EMP=[{p:"USDCNY",q:"CNY"},{p:"USDMXN",q:"MXN"},{p:"USDBRL",q:"BRL"},{p:"USDZAR",q:"ZAR"},{p:"USDINR",q:"INR"},{p:"USDKRW",q:"KRW"},{p:"USDTRY",q:"TRY"},{p:"USDPLN",q:"PLN"},{p:"USDHUF",q:"HUF"},{p:"USDCZK",q:"CZK"},{p:"USDSGD",q:"SGD"},{p:"USDIDR",q:"IDR"},{p:"USDTHB",q:"THB"}].map(x=>({...x,i:0}));
const SYM=[...new Set([...G10,...EMP].map(x=>x.q))].join(",");
const RT=[{c:"US",f:"🇺🇸",cb:null,ccy:"USD"},{c:"Eurozone",f:"🇪🇺",cb:null,ccy:"EUR"},{c:"UK",f:"🇬🇧",cb:null,ccy:"GBP"},{c:"Japan",f:"🇯🇵",cb:null,ccy:"JPY"},{c:"Switzerland",f:"🇨🇭",cb:null,ccy:"CHF"},{c:"Australia",f:"🇦🇺",cb:null,ccy:"AUD"},{c:"New Zealand",f:"🇳🇿",cb:null,ccy:"NZD"},{c:"Canada",f:"🇨🇦",cb:null,ccy:"CAD"},{c:"Sweden",f:"🇸🇪",cb:null,ccy:"SEK"},{c:"Norway",f:"🇳🇴",cb:null,ccy:"NOK"},{c:"Brazil",f:"🇧🇷",cb:null,ccy:"BRL"},{c:"Mexico",f:"🇲🇽",cb:null,ccy:"MXN"},{c:"South Africa",f:"🇿🇦",cb:null,ccy:"ZAR"},{c:"India",f:"🇮🇳",cb:null,ccy:"INR"},{c:"South Korea",f:"🇰🇷",cb:null,ccy:"KRW"},{c:"Turkey",f:"🇹🇷",cb:null,ccy:"TRY"},{c:"Poland",f:"🇵🇱",cb:null,ccy:"PLN"}];
const cOf={};RT.forEach(r=>cOf[r.c]=r.ccy);
const tvF={US:"TVC:DXY",Eurozone:"FX:EURUSD",UK:"FX:GBPUSD",Japan:"FX:USDJPY",Switzerland:"FX:USDCHF",Australia:"FX:AUDUSD","New Zealand":"FX:NZDUSD",Canada:"FX:USDCAD",Sweden:"FX:USDSEK",Norway:"FX:USDNOK",Brazil:"FX:USDBRL",Mexico:"FX:USDMXN","South Africa":"FX:USDZAR",India:"FX:USDINR","South Korea":"FX:USDKRW",Turkey:"FX:USDTRY",Poland:"FX:USDPLN"};
const tvY={US:"TVC:US10Y",Eurozone:"TVC:DE10Y",UK:"TVC:GB10Y",Japan:"TVC:JP10Y",Canada:"TVC:CA10Y",Australia:"TVC:AU10Y"};
const pOf={US:"DXY",Eurozone:"EURUSD",UK:"GBPUSD",Japan:"USDJPY",Switzerland:"USDCHF",Australia:"AUDUSD","New Zealand":"NZDUSD",Canada:"USDCAD",Sweden:"USDSEK",Norway:"USDNOK",Brazil:"USDBRL",Mexico:"USDMXN","South Africa":"USDZAR",India:"USDINR","South Korea":"USDKRW",Turkey:"USDTRY",Poland:"USDPLN"};
const nKw={US:["fed","fomc","treasury","dollar","powell","nonfarm"],Eurozone:["ecb","lagarde","eurozone","bund"],UK:["boe","bailey","gilt","sterling","uk gdp","uk cpi"],Japan:["boj","ueda","yen","jgb","japan"],Switzerland:["snb","swiss","franc"],Australia:["rba","aussie","australia"],Canada:["boc","canada","loonie"],"New Zealand":["rbnz","kiwi","new zealand"],Sweden:["riksbank","sweden"],Norway:["norges","norway"],Brazil:["bcb","brazil","real"],Mexico:["banxico","mexico","peso"],"South Africa":["sarb","south africa","rand"],India:["rbi","india","rupee"],"South Korea":["bok","korea","won"],Turkey:["cbrt","turkey","lira"],Poland:["nbp","poland","zloty"]};
const MD={};
/* ═══ UI ATOMS ════════════════════════════════════════════════════ */
const Dot=({on})=><span style={{display:"inline-block",width:6,height:6,borderRadius:"50%",background:on?C.g:C.am,marginRight:5}}/>;
function Tabs({tabs,act,set}){return <div style={{display:"flex",gap:2,borderBottom:"1px solid "+C.bd,marginBottom:12,overflowX:"auto"}}>{tabs.map(t=><button key={t} onClick={()=>set(t)} style={{padding:"8px 16px",fontSize:12,fontWeight:600,background:act===t?C.ac+"20":"transparent",color:act===t?C.ac:C.txD,border:"none",borderBottom:act===t?"2px solid "+C.ac:"2px solid transparent",whiteSpace:"nowrap"}}>{t}</button>)}</div>}
function Tbl({cols,rows,sm}){return <div style={{overflowX:"auto"}}><table style={{width:"100%",borderCollapse:"collapse",fontSize:sm?11:12}}><thead><tr>{cols.map(c=><th key={c.k} style={{padding:"6px 10px",textAlign:c.a||"right",color:C.txM,fontWeight:600,fontSize:10,letterSpacing:1,textTransform:"uppercase",borderBottom:"1px solid "+C.bd,whiteSpace:"nowrap",background:C.bgC}}>{c.l}</th>)}</tr></thead><tbody>{rows.map((r,i)=><tr key={i} style={{background:i%2?C.bg+"60":"transparent"}}>{cols.map(c=>{const v=c.rn?c.rn(r):r[c.k];return <td key={c.k} style={{padding:"5px 10px",textAlign:c.a||"right",fontFamily:c.nm?"inherit":Mn,fontSize:sm?11:12,color:c.cl?c.cl(r):C.tx,whiteSpace:"nowrap",borderBottom:"1px solid "+C.bd+"20"}}>{v!=null?v:"—"}</td>})}</tr>)}</tbody></table></div>}
function TV({sym,h=280}){const [shouldLoad,setShouldLoad]=React.useState(false);React.useEffect(()=>{setShouldLoad(false)},[sym]);if(!sym)return <div style={{height:h,borderRadius:6,border:"1px solid "+C.bd,display:"flex",alignItems:"center",justifyContent:"center",color:C.txM,fontSize:11}}>No chart</div>;if(!shouldLoad)return <div style={{height:h,borderRadius:6,border:"1px solid "+C.bd,background:C.bg,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:10,color:C.txM,fontSize:11}}><div style={{fontSize:12,color:C.txD}}>Chart ready to load</div><div style={{fontFamily:Mn,color:C.tx}}>{sym}</div><button onClick={()=>setShouldLoad(true)} style={{padding:"8px 14px",borderRadius:6,border:"1px solid "+C.ac+"50",background:C.ac+"18",color:C.ac,fontSize:12,fontWeight:700}}>Load Chart</button></div>;return <iframe loading="lazy" title={sym} src={"https://s.tradingview.com/widgetembed/?symbol="+encodeURIComponent(sym)+"&interval=D&range=3M&theme=dark&style=1&locale=en&hide_legend=1&hide_volume=1&hide_side_toolbar=1&allow_symbol_change=0&withdateranges=1&backgroundColor=%23111827&gridColor=%231e293b"} style={{width:"100%",height:h,borderRadius:6,border:"1px solid "+C.bd,background:C.bg}} frameBorder="0"/>}
function RiskBar({d,live}){const vd=(d.VIX?.chg||0)<0,dd=(d.DXY?.chg||0)<0,on=vd&&dd,off=!vd&&!dd,bc=on?C.g:off?C.r:C.am;return <div style={{background:"linear-gradient(90deg,"+bc+"18 0%,"+C.bg+" 50%,"+bc+"18 100%)",borderBottom:"1px solid "+bc+"40",padding:"5px 16px",display:"flex",alignItems:"center",gap:16,overflowX:"auto",whiteSpace:"nowrap",fontSize:12}}><span style={{color:bc,fontWeight:700,fontSize:10,letterSpacing:2,padding:"2px 8px",border:"1px solid "+bc+"60",borderRadius:3}}>{on?"RISK ON":off?"RISK OFF":"MIXED"}</span><Dot on={live}/>{Object.entries(d).map(([k,v])=><span key={k} style={{display:"inline-flex",gap:4,alignItems:"center"}}><span style={{color:C.txM,fontWeight:600,fontSize:11}}>{k}</span><span style={{color:C.tx,fontFamily:Mn,fontWeight:600,fontSize:12}}>{v.level!=null?Number(v.level).toLocaleString(undefined,{maximumFractionDigits:2}):"—"}</span>{v.chg!=null&&<span style={{color:cc(v.chg),fontFamily:Mn,fontSize:10}}>{k.includes("10Y")?fb(v.chg):fc(v.chg,k==="VIX"?"":"%")}</span>}</span>)}</div>}
/* ═══ PAGES ═══════════════════════════════════════════════════════ */
function FxTbl({data,live}){return <div><div style={{display:"flex",alignItems:"center",gap:8,marginBottom:8}}><Dot on={live}/><span style={{color:C.txM,fontSize:10}}>{live?"Live via Frankfurter":"Loading..."}</span></div><Tbl cols={[{k:"pair",l:"Pair",a:"left",nm:1,cl:()=>C.b},{k:"spot",l:"Spot",rn:r=>r.spot!=null?Number(r.spot).toFixed(r.spot>100?2:4):"—"},{k:"d1",l:"1D %",rn:r=>fc(r.d1),cl:r=>cc(r.d1)},{k:"w1",l:"1W %",rn:r=>fc(r.w1),cl:r=>cc(r.w1)},{k:"m1",l:"1M %",rn:r=>fc(r.m1),cl:r=>cc(r.m1)},{k:"ytd",l:"YTD %",rn:r=>fc(r.ytd),cl:r=>cc(r.ytd)}]} rows={data}/></div>}
function YldTbl({yld,live}){return <div><div style={{display:"flex",alignItems:"center",gap:8,marginBottom:8}}><Dot on={live}/><span style={{color:C.txM,fontSize:10}}>{live?"US via FRED · EU via ECB":"No yield data"}</span></div><Tbl cols={[{k:"c",l:"Country",a:"left",nm:1,rn:r=>r.f+" "+r.c},{k:"cb",l:"CB Rate",rn:r=>r.cb!=null?r.cb.toFixed(2)+"%":"—",cl:r=>r.cb!=null?C.tx:C.txM},{k:"y2",l:"2Y",rn:r=>r.y2!=null?r.y2.toFixed(2):"—",cl:r=>r.y2?C.tx:C.txM},{k:"y10",l:"10Y",rn:r=>r.y10!=null?r.y10.toFixed(2):"—",cl:r=>r.y10?C.tx:C.txM},{k:"s",l:"2s10s",rn:r=>r.y2&&r.y10?fb((r.y10-r.y2)*100):"—",cl:r=>r.y2&&r.y10?cc(r.y10-r.y2):C.txM}]} rows={RT.map(r=>({...r,...(yld[r.c]||{})}))} sm/></div>}
function Cmdt({d}){const items=[{k:"gold",l:"Gold XAU",s:"TVC:GOLD"},{k:"wti",l:"WTI Crude",s:"TVC:USOIL"},{k:"brent",l:"Brent",s:"TVC:UKOIL"}];return <div><div className="g3" style={{display:"grid",gridTemplateColumns:"repeat(3,1fr)",gap:12,marginBottom:16}}>{items.map(it=>{const v=d[it.k];return <div key={it.k} style={{background:C.bg,border:"1px solid "+C.bd,borderRadius:6,padding:14}}><div style={{color:C.txM,fontSize:10,fontWeight:600,textTransform:"uppercase",letterSpacing:1,marginBottom:4}}>{it.l}</div><div style={{color:C.tx,fontSize:24,fontWeight:700,fontFamily:Mn}}>{v?.level!=null?"$"+Number(v.level).toLocaleString(undefined,{maximumFractionDigits:2}):"—"}</div>{v?.chg!=null&&<div style={{color:cc(v.chg),fontSize:12,fontFamily:Mn,marginTop:2}}>{fc(v.chg)}</div>}</div>})}</div><div className="g3" style={{display:"grid",gridTemplateColumns:"repeat(3,1fr)",gap:12}}>{items.map(it=><div key={it.k}><TV sym={it.s} h={220}/></div>)}</div></div>}
function XCry({yld}){const g=RT.filter(r=>["US","Eurozone","UK","Japan","Switzerland","Australia","New Zealand","Canada","Sweden","Norway"].includes(r.c));const gY=c=>yld[c]?.y2??null;const pairs=[];for(let i=0;i<g.length;i++)for(let j=i+1;j<g.length;j++){const a=g[i],b=g[j],ya=gY(a.c),yb=gY(b.c);if(ya==null||yb==null)continue;const d=Math.abs(ya-yb),lc=ya>yb?a:b,sc=ya>yb?b:a;pairs.push({lc:lc.ccy,sc:sc.ccy,lf:lc.f,sf:sc.f,ly:Math.max(ya,yb),sy:Math.min(ya,yb),carry:d})}if(!pairs.length)return <div style={{padding:20,textAlign:"center",color:C.txM,fontSize:12}}>Carry data requires live yield feeds (FRED / ECB)</div>;return <Tbl cols={[{k:"rk",l:"#",a:"center",cl:r=>r.rk<=5?C.g:C.txD},{k:"t",l:"Trade",a:"left",nm:1,rn:r=>r.lf+" Long "+r.lc+" / "+r.sf+" Short "+r.sc},{k:"ly",l:"Long Yld",rn:r=>r.ly.toFixed(2)+"%"},{k:"sy",l:"Short Yld",rn:r=>r.sy.toFixed(2)+"%"},{k:"carry",l:"Carry",rn:r=>"+"+r.carry.toFixed(2)+"%",cl:r=>r.carry>3?C.g:r.carry>1?C.tl:C.txD},{k:"bp",l:"bps",rn:r=>"+"+(r.carry*100).toFixed(0),cl:r=>r.carry>3?C.g:r.carry>1?C.tl:C.txD}]} rows={pairs.sort((a,b)=>b.carry-a.carry).map((r,i)=>({...r,rk:i+1}))} sm/>}
function UCry({yld}){const uY=yld.US?.y2;if(uY==null)return <div style={{padding:20,textAlign:"center",color:C.txM,fontSize:12}}>Carry data requires live US yield feed (FRED)</div>;const rows=RT.filter(r=>r.c!=="US").map(r=>{const ry=yld[r.c]?.y2;return {...r,carry:ry!=null?+((ry-uY).toFixed(2)):null}}).filter(r=>r.carry!=null).sort((a,b)=>b.carry-a.carry).map((r,i)=>({...r,rk:i+1}));if(!rows.length)return <div style={{padding:20,textAlign:"center",color:C.txM,fontSize:12}}>No foreign yield data available yet</div>;return <Tbl cols={[{k:"rk",l:"#",a:"center",cl:r=>r.rk<=3?C.g:r.rk>=13?C.r:C.txD},{k:"c",l:"Currency",a:"left",nm:1,rn:r=>r.f+" "+r.ccy},{k:"y",l:"2Y Yield",rn:r=>(yld[r.c]?.y2!=null?yld[r.c].y2.toFixed(2)+"%":"—")},{k:"carry",l:"vs USD",rn:r=>fc(r.carry),cl:r=>cc(r.carry)},{k:"dir",l:"Direction",a:"left",nm:1,rn:r=>r.carry>0.3?"Long "+r.ccy:"Short "+r.ccy,cl:r=>r.carry>0.3?C.g:C.r}]} rows={rows} sm/>}
function CarryP({yld}){const [t,sT]=useState("Cross G10");return <div><Tabs tabs={["Cross G10","vs USD"]} act={t} set={sT}/>{t==="Cross G10"?<XCry yld={yld}/>:<UCry yld={yld}/>}</div>}
function Markets({g10,em,fxL,yld,yL}){const [t,sT]=useState("G10 FX");return <div><Tabs tabs={["G10 FX","EM FX","Rates","Carry"]} act={t} set={sT}/>{t==="G10 FX"&&<FxTbl data={g10} live={fxL}/>}{t==="EM FX"&&<FxTbl data={em} live={fxL}/>}{t==="Rates"&&<YldTbl yld={yld} live={yL}/>}{t==="Carry"&&<CarryP yld={yld}/>}</div>}
function CorrMx(){return <div><div style={{marginBottom:12}}><span style={{color:C.ac,fontSize:13,fontWeight:700}}>30-Day Correlation Matrix</span></div><div style={{padding:40,textAlign:"center",color:C.txM,fontSize:12,border:"1px dashed "+C.bd,borderRadius:6}}>—<br/><span style={{fontSize:11,marginTop:8,display:"inline-block"}}>Correlation data not available — requires a live feed</span></div></div>}
/* ═══ DEEP DIVE ═══════════════════════════════════════════════════ */
function Deep({ctry,g10,em,yld,news,nL,risk}){
const ccy=cOf[ctry],fp=[...g10,...em].find(x=>x.pair===pOf[ctry]),y=yld[ctry]||{},tpl=RT.find(x=>x.c===ctry),m=MD[ctry]||{};
const kws=nKw[ctry]||[ccy.toLowerCase()];const cN=news.filter(n=>kws.some(kw=>(n.headline||"").toLowerCase().includes(kw)));const dN=cN.length>=2?cN:news;
const ce=Object.entries(y).filter(([k,v])=>v!=null&&k.startsWith("y")).map(([k,v])=>[k.replace("y","").toUpperCase(),v]);
const [ai,setAi]=useState(null);
const [aiLd,setAiLd]=useState(false);
const [aiErr,setAiErr]=useState("");
const gen = async () => {
setAiLd(true);
setAiErr("");
try {
const body = {
country: ctry,
currency: ccy,
fx: fp ? {
pair: fp.pair,
spot: fp.spot,
d1: fp.d1,
w1: fp.w1,
m1: fp.m1,
ytd: fp.ytd
} : null,
yields: y,
centralBank: tpl ? {
country: tpl.c,
policyRate: tpl.cb
} : null,
macro: m,
riskBar: risk,
headlines: dN.slice(0, 8).map(n => ({
headline: n.headline,
source: n.source,
datetime: n.datetime
}))
};
const res = await fetch("/.netlify/functions/deep-dive-summary", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(body)
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.error || "Failed to generate AI summary");
}
setAi(data);
} catch (err) {
setAiErr(err.message || "AI request failed");
} finally {
setAiLd(false);
}
};
return <div>
<div style={{background:C.bgC,border:"1px solid "+C.bd,borderRadius:8,padding:20,marginBottom:16}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:12}}>
<div style={{color:C.tx,fontSize:15,fontWeight:700}}>🧠 AI MACRO SUMMARY {ctry} ({ccy})</div>
<button
onClick={gen}
disabled={aiLd}
style={{
padding:"8px 18px",
background:"linear-gradient(135deg,"+C.tl+",#38bdf8)",
color:"#fff",
border:"none",
borderRadius:6,
fontSize:12,
fontWeight:700
}}
>
{aiLd ? "Generating..." : ai ? "↻ Regenerate View" : "Generate View"}
</button>
</div>
{aiErr && (
<div style={{padding:12,background:"#2a1111",border:"1px solid #7f1d1d",borderRadius:6,color:"#fca5a5",marginBottom:12}}>
{aiErr}
</div>
)}
{!ai && !aiLd && !aiErr && (
<div style={{padding:20,background:C.bg,borderRadius:6,border:"1px dashed "+C.bd,textAlign:"center",color:C.txM,fontSize:12}}>
Click "Generate View" for a live AI deep dive
</div>
)}
{ai && (
<>
<div style={{marginBottom:14}}>
<div style={{color:C.tl,fontSize:11,fontWeight:700,textTransform:"uppercase",letterSpacing:1,marginBottom:6}}>
Trading Narrative
</div>
<div style={{padding:14,background:C.bg,borderRadius:6,border:"1px solid "+C.tl+"25",color:C.tx,fontSize:13,lineHeight:1.75}}>
{ai.narrative}
</div>
</div>
<div style={{color:C.tl,fontSize:11,fontWeight:700,textTransform:"uppercase",letterSpacing:1,marginBottom:8}}>
Macroeconomic Data & Trends
</div>
<div style={{display:"grid",gridTemplateColumns:"1fr",gap:10,marginBottom:14}}>
{ai.macro_sections.map((sec, i) => (
<div key={i} style={{background:C.bg,padding:14,borderRadius:6,border:"1px solid "+C.bd}}>
<div style={{color:C.b,fontSize:11,fontWeight:700,textTransform:"uppercase",letterSpacing:1,marginBottom:8}}>
{sec.title}
</div>
<div style={{display:"grid",gap:6}}>
{sec.bullets.map((b, j) => (
<div key={j} style={{color:C.txD,fontSize:12,lineHeight:1.6}}>
{b}
</div>
))}
</div>
</div>
))}
</div>
<div className="g2" style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:12}}>
<div style={{background:C.bg,padding:14,borderRadius:6,border:"1px solid "+C.bd}}>
<div style={{color:C.g,fontSize:11,fontWeight:700,textTransform:"uppercase",letterSpacing:1,marginBottom:8}}>
FX View
</div>
<div style={{color:C.tx,fontWeight:700,marginBottom:6}}>{ai.fx_view.direction}</div>
<div style={{color:C.txD,fontSize:12,lineHeight:1.6,marginBottom:8}}>{ai.fx_view.rationale}</div>
{ai.fx_view.levels_to_watch.map((x, i) => (
<div key={i} style={{color:C.am,fontSize:11,marginBottom:4}}> {x}</div>
))}
</div>
<div style={{background:C.bg,padding:14,borderRadius:6,border:"1px solid "+C.bd}}>
<div style={{color:C.r,fontSize:11,fontWeight:700,textTransform:"uppercase",letterSpacing:1,marginBottom:8}}>
Rates View
</div>
<div style={{color:C.tx,fontWeight:700,marginBottom:6}}>{ai.rates_view.bias}</div>
<div style={{color:C.txD,fontSize:12,lineHeight:1.6}}>{ai.rates_view.rationale}</div>
</div>
</div>
<div className="g2" style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:12,marginTop:12}}>
<div style={{background:C.bg,padding:14,borderRadius:6,border:"1px solid "+C.bd}}>
<div style={{color:C.am,fontSize:11,fontWeight:700,textTransform:"uppercase",letterSpacing:1,marginBottom:8}}>
Key Risks
</div>
{ai.key_risks.map((x, i) => (
<div key={i} style={{color:C.txD,fontSize:12,lineHeight:1.6,marginBottom:4}}> {x}</div>
))}
</div>
<div style={{background:C.bg,padding:14,borderRadius:6,border:"1px solid "+C.bd}}>
<div style={{color:C.tl,fontSize:11,fontWeight:700,textTransform:"uppercase",letterSpacing:1,marginBottom:8}}>
Headline Takeaways
</div>
{ai.headline_takeaways.map((x, i) => (
<div key={i} style={{color:C.txD,fontSize:12,lineHeight:1.6,marginBottom:4}}> {x}</div>
))}
</div>
</div>
</>
)}
</div>
<div className="g2" style={{display:"grid",gridTemplateColumns:"1.1fr 0.9fr",gap:16}}>
<div style={{background:C.bgC,border:"1px solid "+C.bd,borderRadius:8,padding:16}}>
<div style={{display:"flex",justifyContent:"space-between",alignItems:"flex-start",marginBottom:8}}>
<div>
<div style={{color:C.txM,fontSize:10,fontWeight:700,textTransform:"uppercase",letterSpacing:1,marginBottom:4}}>
FX {pOf[ctry]}
</div>
<div style={{color:C.tx,fontSize:34,fontWeight:800,fontFamily:Mn}}>
{fp?.spot!=null?Number(fp.spot).toFixed(fp.spot>100?2:4):"—"}
</div>
<div style={{display:"flex",gap:8,flexWrap:"wrap",marginTop:6}}>
{[["1D",fp?.d1],["1W",fp?.w1],["1M",fp?.m1],["YTD",fp?.ytd]].map(([t,v])=>(
<span key={t} style={{fontSize:11,fontFamily:Mn,color:v!=null?cc(v):C.txM}}>
{t}: {v!=null?fc(v):"—"}
</span>
))}
</div>
</div>
</div>
<TV sym={tvF[ctry]} h={290}/>
</div>
<div style={{background:C.bgC,border:"1px solid "+C.bd,borderRadius:8,padding:16}}>
<div style={{color:C.tx,fontSize:14,fontWeight:700,marginBottom:10}}>Yield Curve</div>
<div style={{display:"grid",gap:6,marginBottom:12}}>
{Object.entries(y)
.filter(([k,v]) => k.startsWith("y") && v != null)
.map(([k,v]) => (
<div key={k} style={{display:"flex",justifyContent:"space-between",padding:"4px 0",borderBottom:"1px solid "+C.bd+"30"}}>
<span style={{color:C.txD,fontSize:11}}>{k.replace("y","").toUpperCase()}</span>
<span style={{color:C.tx,fontFamily:Mn,fontSize:12}}>{v.toFixed(2)}%</span>
</div>
))}
</div>
{tvY[ctry]
? <TV sym={tvY[ctry]} h={170}/>
: <div style={{height:170,borderRadius:6,border:"1px solid "+C.bd,display:"flex",alignItems:"center",justifyContent:"center",color:C.txM,fontSize:11}}>
Yield chart available for US, Eurozone, UK, Japan, Canada, Australia
</div>
}
</div>
</div>
<div className="g3" style={{display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gap:16,marginTop:16}}>
<div style={{background:C.bgC,border:"1px solid "+C.bd,borderRadius:8,padding:16}}>
<div style={{color:C.tx,fontSize:13,fontWeight:700,marginBottom:10}}>Central Bank</div>
<div style={{display:"grid",gap:8}}>
<div style={{display:"flex",justifyContent:"space-between"}}><span style={{color:C.txM,fontSize:11}}>Policy Rate</span><span style={{color:C.tx,fontWeight:700,fontFamily:Mn}}>{tpl?.cb!=null ? tpl.cb.toFixed(2)+"%" : "—"}</span></div>
<div style={{display:"flex",justifyContent:"space-between"}}><span style={{color:C.txM,fontSize:11}}>Stance</span><span style={{color:C.am,fontSize:11,fontWeight:700}}>{m.stance||"—"}</span></div>
<div style={{display:"flex",justifyContent:"space-between"}}><span style={{color:C.txM,fontSize:11}}>Last Decision</span><span style={{color:C.tx,fontSize:11}}>{m.last||"—"}</span></div>
<div style={{display:"flex",justifyContent:"space-between"}}><span style={{color:C.txM,fontSize:11}}>Next Meeting</span><span style={{color:C.g,fontSize:11}}>{m.next||"—"}</span></div>
<div style={{display:"flex",justifyContent:"space-between"}}><span style={{color:C.txM,fontSize:11}}>Pricing</span><span style={{color:C.tx,fontSize:11}}>{m.pricing||"—"}</span></div>
</div>
</div>
<div style={{background:C.bgC,border:"1px solid "+C.bd,borderRadius:8,padding:16}}>
<div style={{color:C.tx,fontSize:13,fontWeight:700,marginBottom:10}}>Macro Snapshot</div>
<div className="g3" style={{display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gap:8}}>
<div style={{background:C.bg,padding:10,borderRadius:6}}>
<div style={{color:C.txM,fontSize:10}}>CPI YoY</div>
<div style={{color:C.tx,fontSize:22,fontWeight:700,fontFamily:Mn}}>{m.cpi||"—"}</div>
<div style={{color:C.am,fontSize:10}}>Prior {m.cpiP||"—"}</div>
</div>
<div style={{background:C.bg,padding:10,borderRadius:6}}>
<div style={{color:C.txM,fontSize:10}}>GDP</div>
<div style={{color:C.tx,fontSize:22,fontWeight:700,fontFamily:Mn}}>{m.gdp||"—"}</div>
<div style={{color:C.am,fontSize:10}}>{m.gdpT||"—"}</div>
</div>
<div style={{background:C.bg,padding:10,borderRadius:6}}>
<div style={{color:C.txM,fontSize:10}}>Unemployment</div>
<div style={{color:C.tx,fontSize:22,fontWeight:700,fontFamily:Mn}}>{m.unemp||"—"}</div>
<div style={{color:C.am,fontSize:10}}>{m.unempT||"—"}</div>
</div>
</div>
</div>
<div style={{background:C.bgC,border:"1px solid "+C.bd,borderRadius:8,padding:16}}>
<div style={{display:"flex",alignItems:"center",gap:8,marginBottom:10}}>
<span style={{color:C.tx,fontSize:13,fontWeight:700}}>News</span>
<Dot on={nL}/>
</div>
<div style={{display:"grid",gap:8}}>
{dN.slice(0,4).map((n,i)=>(
<div key={i} style={{padding:"8px 10px",background:C.bg,borderRadius:6}}>
<a href={n.url||"#"} target="_blank" rel="noopener" style={{color:C.tx,fontSize:11,fontWeight:500,lineHeight:1.4,textDecoration:"none",display:"block"}}>
{n.headline}
</a>
<div style={{color:C.txM,fontSize:9,marginTop:3}}>
{n.source} · {n.datetime?new Date(n.datetime*1000).toLocaleString("en-GB",{day:"numeric",month:"short",hour:"2-digit",minute:"2-digit"}):"—"}
</div>
</div>
))}
</div>
</div>
</div>
</div>;
}
/* ═══ BRIEF ═══════════════════════════════════════════════════════ */
function genBr(g10,em,risk,yld,cal,cmd){const L=[];const vix=risk.VIX,dxy=risk.DXY;const vD=(vix?.chg||0)<0,dD=(dxy?.chg||0)<0;if(vD&&dD)L.push("Risk on — VIX down "+Math.abs(vix?.chg||0).toFixed(1)+" at "+(vix?.level?.toFixed(1)||"—")+", USD softer at "+(dxy?.level?.toFixed(1)||"—")+" ("+fc(dxy?.chg)+"). Supportive for carry.");else if(!vD&&!dD)L.push("Defensive tone — VIX up at "+(vix?.level?.toFixed(1)||"—")+", USD bid at "+(dxy?.level?.toFixed(1)||"—")+" ("+fc(dxy?.chg)+"). JPY/CHF finding support.");else L.push("Mixed — VIX "+(vix?.level?.toFixed(1)||"—")+" ("+fc(vix?.chg,"")+"), DXY "+(dxy?.level?.toFixed(1)||"—")+" ("+fc(dxy?.chg)+"). Positioning ahead of events.");const mv=[...g10,...em].filter(x=>x.d1!=null).sort((a,b)=>Math.abs(b.d1)-Math.abs(a.d1)).slice(0,4);if(mv.length)L.push("Movers: "+mv.map(x=>x.pair+" "+(x.d1>0?"▲":"▼")+Math.abs(x.d1).toFixed(2)+"%").join(", ")+".");if(cmd.gold?.level)L.push("Gold $"+cmd.gold.level.toFixed(0)+(cmd.gold.chg!=null?" ("+fc(cmd.gold.chg)+")":"")+"."+(cmd.wti?.level?" WTI $"+cmd.wti.level.toFixed(2)+(cmd.wti.chg!=null?" ("+fc(cmd.wti.chg)+")":"")+".":" "));const u2=yld.US?.y2;if(u2!=null){const tc=RT.filter(r=>r.c!=="US"&&yld[r.c]?.y2!=null).map(r=>({ccy:r.ccy,c:+((yld[r.c].y2)-u2).toFixed(2)})).filter(x=>x.c>1).sort((a,b)=>b.c-a.c).slice(0,3);L.push("US 2Y "+u2.toFixed(2)+"%."+(tc.length?" Top carry: "+tc.map(x=>x.ccy+" +"+x.c+"%").join(", ")+".":" "))}const hi=cal.filter(e=>e.imp==="red").slice(0,4);if(hi.length)L.push("Watch: "+hi.map(e=>e.ctry+" "+e.ev+" ("+e.date+")").join(", ")+".");return L.join(" ")}
function Brief({g10,em,yld,risk,cal,cmd}){const [br,sBr]=useState(null);const [ts,sTs]=useState(null);const [ld,sLd]=useState(false);const gen=()=>{sLd(true);setTimeout(()=>{sBr(genBr(g10,em,risk,yld,cal,cmd));sTs(new Date().toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit"}));sLd(false)},600)};return <div style={{background:C.bgC,border:"1px solid "+C.bd,borderRadius:8,padding:20}}><div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:16,flexWrap:"wrap",gap:8}}><div style={{display:"flex",alignItems:"center",gap:10}}><span style={{fontSize:20}}>🧠</span><div><div style={{color:C.tx,fontSize:16,fontWeight:700}}>AI Morning Brief</div><div style={{color:C.txM,fontSize:11}}>Live FX, yields, carry, commodities & calendar</div></div></div><div style={{display:"flex",alignItems:"center",gap:8}}>{ts&&<span style={{color:C.txM,fontSize:10}}>Generated {ts}</span>}<button onClick={gen} disabled={ld} style={{padding:"10px 24px",background:ld?"transparent":"linear-gradient(135deg,"+C.ac+","+C.acD+")",color:ld?C.ac:"#fff",border:ld?"1px solid "+C.ac+"40":"none",borderRadius:6,fontSize:12,fontWeight:700}}>{ld?<><span className="sp" style={{marginRight:4}}/>Analysing</>:br?"↻ Regen":"⚡ Generate Brief"}</button></div></div>{br?<div style={{padding:16,background:C.bg,borderRadius:8,border:"1px solid "+C.ac+"25"}}><div style={{color:C.tx,fontSize:13,fontStyle:"italic",lineHeight:1.7}}>{br}</div></div>:<div style={{padding:24,background:C.bg,borderRadius:8,border:"1px dashed "+C.bd,textAlign:"center",color:C.txM,fontSize:12}}>Click "Generate Brief"</div>}</div>}
function Cal({evts,live}){const [fl,sF]=useState("All");const gF=["🇺🇸","🇪🇺","🇬🇧","🇯🇵","🇨🇭","🇦🇺","🇳🇿","🇨🇦","🇸🇪","🇳🇴"];return <div><div style={{display:"flex",gap:6,marginBottom:10,alignItems:"center"}}><Dot on={live}/>{["All","G10","EM","CB"].map(f=><button key={f} onClick={()=>sF(f)} style={{padding:"5px 14px",fontSize:11,fontWeight:600,borderRadius:4,background:fl===f?C.ac+"25":C.bg,color:fl===f?C.ac:C.txD,border:"1px solid "+(fl===f?C.ac+"50":C.bd)}}>{f}</button>)}</div><Tbl cols={[{k:"date",l:"Date",a:"left"},{k:"time",l:"Time",a:"left"},{k:"ctry",l:"",a:"center",nm:1},{k:"ev",l:"Event",a:"left",nm:1,cl:r=>r.imp==="red"?C.r:C.am},{k:"prev",l:"Prev"},{k:"fcast",l:"Fcst"},{k:"act",l:"Act",cl:r=>(r.act&&r.act!=="—")?C.g:C.txM}]} rows={evts.filter(e=>{if(fl==="G10")return gF.includes(e.ctry);if(fl==="EM")return!gF.includes(e.ctry);if(fl==="CB")return(e.ev||"").toLowerCase().match(/rate|fomc|boj|ecb|rba|rbnz|boc/);return true})}/></div>}
function Sett({open,close,keys,save}){const [lk,sL]=useState(keys);useEffect(()=>sL(keys),[keys,open]);if(!open)return null;return <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.7)",zIndex:1000,display:"flex",alignItems:"center",justifyContent:"center"}} onClick={close}><div style={{background:C.bgC,border:"1px solid "+C.bd,borderRadius:10,padding:24,width:520,maxWidth:"92vw"}} onClick={e=>e.stopPropagation()}><div style={{display:"flex",justifyContent:"space-between",marginBottom:16}}><span style={{color:C.tx,fontSize:16,fontWeight:700}}> API Keys</span><button onClick={close} style={{background:"none",border:"none",color:C.txM,fontSize:20}}>✕</button></div>{[{k:"finnhub",l:"Finnhub — VIX, commodities, news, calendar",u:"https://finnhub.io/register"}].map(a=><div key={a.k} style={{marginBottom:14,padding:12,background:C.bg,borderRadius:6,border:"1px solid "+C.bd}}><div style={{display:"flex",justifyContent:"space-between",marginBottom:4}}><span style={{color:C.tx,fontSize:12,fontWeight:600}}>{a.l}</span><a href={a.u} target="_blank" rel="noopener" style={{color:C.ac,fontSize:10,textDecoration:"none"}}>Get key →</a></div><input type="password" value={lk[a.k]||""} onChange={e=>sL({...lk,[a.k]:e.target.value})} style={{width:"100%",padding:"8px 12px",background:C.bgC,border:"1px solid "+C.bd,borderRadius:4,color:C.tx,fontSize:12,fontFamily:Mn,boxSizing:"border-box"}}/></div>)}<button onClick={()=>{save(lk);close()}} style={{width:"100%",padding:"12px",background:"linear-gradient(135deg,"+C.ac+","+C.acD+")",color:"#fff",border:"none",borderRadius:6,fontSize:13,fontWeight:700}}>Save</button></div></div>}
function Clock(){const [clk,setClk]=useState(new Date());useEffect(()=>{const t=setInterval(()=>setClk(new Date()),1000);return()=>clearInterval(t)},[]);const lt=clk.toLocaleString("en-GB",{timeZone:"Europe/London",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:false});const nt=clk.toLocaleString("en-US",{timeZone:"America/New_York",hour:"2-digit",minute:"2-digit",hour12:false});const tt=clk.toLocaleString("en-US",{timeZone:"Asia/Tokyo",hour:"2-digit",minute:"2-digit",hour12:false});return <span className="hmob" style={{fontSize:10,color:C.txM}}>LON <span style={{color:C.tx,fontFamily:Mn}}>{lt}</span> NY <span style={{color:C.tx,fontFamily:Mn}}>{nt}</span> TKY <span style={{color:C.tx,fontFamily:Mn}}>{tt}</span></span>}
/* ═══ APP ══════════════════════════════════════════════════════════ */
function App(){
const [sec,setSec]=useState("brief");const [ctry,setCtry]=useState("US");const [sO,setSO]=useState(false);
const [keys,setKeys]=useState({finnhub:gk("finnhub")||_K.finnhub});
const [g10D,sG10]=useState([]);const [emD,sEM]=useState([]);const [fxL,sFxL]=useState(false);
const [yld,sYld]=useState({});const [yL,sYL]=useState(false);
const [risk,sRisk]=useState({DXY:{level:null,chg:null},VIX:{level:null,chg:null},Gold:{level:null,chg:null},WTI:{level:null,chg:null},Brent:{level:null,chg:null},"US 10Y":{level:null,chg:null}});
const [rL,sRL]=useState(false);const [cal,sCal]=useState([]);const [cL,sCL]=useState(false);const [news,sNews]=useState([]);const [nL,sNL]=useState(false);
const [cmd,sCmd]=useState({gold:{},wti:{},brent:{}});const [ld,sLd]=useState(false);const [lr,sLR]=useState(null);
const svK=useCallback(nk=>{setKeys(nk);Object.entries(nk).forEach(([k,v])=>sk(k,v))},[]);
const load=useCallback(async()=>{
sLd(true);
try{
const data=await fData();
const mapFx=defs=>defs.map(d=>{const x=data.fx?.[d.p];return{pair:d.p,spot:x?.price??null,d1:x?.d1_pct??null,w1:x?.w1_pct??null,m1:x?.m1_pct??null,ytd:null}});
sG10(mapFx(G10));sEM(mapFx(EMP));sFxL(true);
const mappedYields={};
if(data.yields){Object.entries(data.yields).forEach(([k,v])=>{if(k!=="US_yf")mappedYields[k]=v})}
sYld(mappedYields);sYL(true);
const dxy=data.fx?.DXY;const vix=data.volatility?.["VIX (S&P 500 Vol)"];const move=data.volatility?.["MOVE (Bond Vol)"];const usY=mappedYields.US||{};
sRisk({DXY:{level:dxy?.price??null,chg:dxy?.d1_pct??null},VIX:{level:vix?.price??null,chg:vix?.change_pct??null},Gold:{level:data.commodities?.gold?.price??null,chg:data.commodities?.gold?.d1_pct??null},WTI:{level:data.commodities?.wti?.price??null,chg:data.commodities?.wti?.d1_pct??null},Brent:{level:data.commodities?.brent?.price??null,chg:data.commodities?.brent?.d1_pct??null},"US 10Y":{level:usY.y10??null,chg:null},MOVE:{level:move?.price??null,chg:move?.change_pct??null}});
sRL(true);
sCmd({gold:{level:data.commodities?.gold?.price??null,chg:data.commodities?.gold?.d1_pct??null},wti:{level:data.commodities?.wti?.price??null,chg:data.commodities?.wti?.d1_pct??null},brent:{level:data.commodities?.brent?.price??null,chg:data.commodities?.brent?.d1_pct??null}});
const parsedNews=(data.news||[]).map(n=>{const dt=typeof n.datetime==="number"?n.datetime:Math.floor(Date.parse(n.datetime)/1000)||0;return{...n,datetime:dt}});sNews(parsedNews);sNL(true);
const parsedCal=(data.calendar||[]).map(e=>({date:e.date||"",time:e.time||"",ctry:e.ctry||"",ev:e.ev||"",prev:e.prev??"—",fcast:e.fcast??"—",act:e.act??"—",imp:e.imp||"orange"}));sCal(parsedCal);sCL(true);
sLR(new Date());
}catch(err){console.error("Failed to load dashboard data",err)}finally{sLd(false)}
},[]);
useEffect(()=>{load()},[load]);
return <div style={{minHeight:"100vh",background:C.bg}}>
<RiskBar d={risk} live={rL||fxL}/>
<div style={{position:"sticky",top:0,zIndex:100,background:C.bgC,borderBottom:"1px solid "+C.bd,padding:"0 16px",display:"flex",alignItems:"center",justifyContent:"space-between",flexWrap:"wrap"}}>
<div style={{display:"flex",alignItems:"center",gap:6,overflowX:"auto"}}><span style={{fontWeight:800,fontSize:14,color:C.ac,padding:"12px 0"}}>FX</span><span style={{fontWeight:800,fontSize:14,color:C.tx}}>RADAR</span><div style={{width:1,height:20,background:C.bd,margin:"0 8px"}}/>{[{id:"brief",l:"🧠 Brief"},{id:"markets",l:"📊 Markets"},{id:"commod",l:"🛢 Commodities"},{id:"corr",l:"🔗 Correlations"},{id:"deep",l:"🌍 Deep Dive"},{id:"cal",l:"📅 Calendar"}].map(s=><button key={s.id} onClick={()=>setSec(s.id)} style={{padding:"12px 10px",fontSize:12,fontWeight:600,background:"transparent",border:"none",color:sec===s.id?C.tx:C.txM,borderBottom:sec===s.id?"2px solid "+C.ac:"2px solid transparent",whiteSpace:"nowrap"}}>{s.l}</button>)}</div>
<div style={{display:"flex",alignItems:"center",gap:8,padding:"8px 0"}}><button onClick={load} disabled={ld} style={{background:C.tl+"18",border:"1px solid "+C.tl+"40",borderRadius:4,color:C.tl,padding:"4px 12px",fontSize:11,fontWeight:600}}>{ld?"...":"↻ Refresh"}</button>{lr&&<span style={{color:C.txM,fontSize:9}}>⏱{lr.toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",second:"2-digit"})}</span>}<Clock/><button onClick={()=>setSO(true)} style={{background:C.g+"18",border:"1px solid "+C.g+"50",borderRadius:4,color:C.g,padding:"4px 8px",fontSize:11,fontWeight:600}}>✓</button></div>
</div>
<div style={{padding:"16px 20px",maxWidth:1600,margin:"0 auto"}}>
{sec==="brief"&&<Brief g10={g10D} em={emD} yld={yld} risk={risk} cal={cal} cmd={cmd}/>}
{sec==="markets"&&<div style={{background:C.bgC,border:"1px solid "+C.bd,borderRadius:8,padding:16}}><Markets g10={g10D} em={emD} fxL={fxL} yld={yld} yL={yL}/></div>}
{sec==="commod"&&<div style={{background:C.bgC,border:"1px solid "+C.bd,borderRadius:8,padding:16}}><div style={{color:C.tx,fontSize:15,fontWeight:700,marginBottom:12}}>Commodities</div><Cmdt d={cmd}/></div>}
{sec==="corr"&&<div style={{background:C.bgC,border:"1px solid "+C.bd,borderRadius:8,padding:20}}><CorrMx/></div>}
{sec==="deep"&&<div><div style={{display:"flex",flexWrap:"wrap",gap:4,marginBottom:16}}>{RT.map(c=><button key={c.c} onClick={()=>setCtry(c.c)} style={{padding:"6px 12px",fontSize:11,fontWeight:600,borderRadius:4,background:ctry===c.c?C.ac+"25":C.bgC,color:ctry===c.c?C.ac:C.txD,border:"1px solid "+(ctry===c.c?C.ac+"50":C.bd)}}>{c.f} {c.c}</button>)}</div><Deep ctry={ctry} g10={g10D} em={emD} yld={yld} news={news} nL={nL} risk={risk}/></div>}
{sec==="cal"&&<div style={{background:C.bgC,border:"1px solid "+C.bd,borderRadius:8,padding:16}}><div style={{marginBottom:12}}><span style={{color:C.tx,fontSize:15,fontWeight:700}}>Economic Calendar</span></div><Cal evts={cal} live={cL}/></div>}
</div>
<div style={{textAlign:"center",padding:"12px 0",borderTop:"1px solid "+C.bd+"20"}}><span style={{color:C.txM,fontSize:10}}>FX RADAR v4 · <Dot on={fxL}/>{fxL?"Live":"..."}</span></div>
<Sett open={sO} close={()=>setSO(false)} keys={keys} save={svK}/>
</div>;
}
ReactDOM.createRoot(document.getElementById("root")).render(<App/>);
</script></body></html>