This commit is contained in:
Tvishi Agarwal
2026-04-06 23:46:40 +00:00
parent e6a13289ee
commit 978d1c35eb
2 changed files with 10 additions and 29 deletions
+9 -29
View File
@@ -26,36 +26,16 @@ const G10=[{p:"EURUSD",q:"EUR",i:1},{p:"GBPUSD",q:"GBP",i:1},{p:"USDJPY",q:"JPY"
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 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 SYM=[...new Set([...G10,...EMP].map(x=>x.q))].join(",");
const RT=[{c:"US",f:"🇺🇸",cb:5.50,ccy:"USD"},{c:"Eurozone",f:"🇪🇺",cb:4.50,ccy:"EUR"},{c:"UK",f:"🇬🇧",cb:5.25,ccy:"GBP"},{c:"Japan",f:"🇯🇵",cb:0.10,ccy:"JPY"},{c:"Switzerland",f:"🇨🇭",cb:1.75,ccy:"CHF"},{c:"Australia",f:"🇦🇺",cb:4.35,ccy:"AUD"},{c:"New Zealand",f:"🇳🇿",cb:5.50,ccy:"NZD"},{c:"Canada",f:"🇨🇦",cb:5.00,ccy:"CAD"},{c:"Sweden",f:"🇸🇪",cb:4.00,ccy:"SEK"},{c:"Norway",f:"🇳🇴",cb:4.50,ccy:"NOK"},{c:"Brazil",f:"🇧🇷",cb:10.75,ccy:"BRL"},{c:"Mexico",f:"🇲🇽",cb:11.00,ccy:"MXN"},{c:"South Africa",f:"🇿🇦",cb:8.25,ccy:"ZAR"},{c:"India",f:"🇮🇳",cb:6.50,ccy:"INR"},{c:"South Korea",f:"🇰🇷",cb:3.50,ccy:"KRW"},{c:"Turkey",f:"🇹🇷",cb:50.00,ccy:"TRY"},{c:"Poland",f:"🇵🇱",cb:5.75,ccy:"PLN"}]; 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 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 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 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 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 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"]};
// ═══ FULL MACRO DATABASE ═════════════════════════════════════════ const MD={};
const MD={
US:{cpi:"3.2%",cpiP:"3.1%",cpiT:"re-accelerating",core:"3.8%",gdp:"2.5%",gdpT:"resilient",unemp:"3.7%",unempT:"near cycle lows",wages:"4.1% YoY",pmi:"52.6",tb:"-$68.5B",stance:"Hawkish hold",next:"2026-05-01",last:"Hold 5.50%",pricing:"2 cuts in 12m"},
Eurozone:{cpi:"2.6%",cpiP:"2.8%",cpiT:"falling toward target",core:"3.1%",gdp:"0.3%",gdpT:"anaemic",unemp:"6.4%",unempT:"stable",wages:"3.5% YoY",pmi:"49.2",tb:"+€28B",stance:"Easing bias",next:"2026-04-10",last:"Hold 4.50%",pricing:"3 cuts in 12m"},
UK:{cpi:"3.4%",cpiP:"4.0%",cpiT:"falling but sticky services",core:"4.5%",gdp:"0.2%",gdpT:"tepid",unemp:"4.2%",unempT:"rising",wages:"5.6% YoY",pmi:"53.8",tb:"-£4.8B",stance:"Cautious hold",next:"2026-05-09",last:"Hold 5.25%",pricing:"2 cuts in 12m"},
Japan:{cpi:"2.8%",cpiP:"2.5%",cpiT:"rising and broadening",core:"2.5%",gdp:"1.2%",gdpT:"recovering",unemp:"2.5%",unempT:"tight",wages:"2.1% (Shunto: 5.3%)",pmi:"51.4",tb:"-¥580B",stance:"Cautious normalisation",next:"2026-04-26",last:"Hold 0.10%",pricing:"1 hike in 12m"},
Switzerland:{cpi:"1.4%",cpiP:"1.2%",cpiT:"low",core:"1.2%",gdp:"0.6%",gdpT:"modest",unemp:"2.3%",unempT:"stable",wages:"1.8% YoY",pmi:"48.5",tb:"+CHF5.2B",stance:"Easing",next:"2026-06-19",last:"Cut to 1.75%",pricing:"1-2 more cuts"},
Australia:{cpi:"3.6%",cpiP:"3.4%",cpiT:"sticky",core:"3.9%",gdp:"1.5%",gdpT:"slowing",unemp:"3.9%",unempT:"stable",wages:"4.2% YoY",pmi:"50.1",tb:"+A$8.5B",stance:"On hold",next:"2026-05-20",last:"Hold 4.35%",pricing:"2 cuts in 12m"},
"New Zealand":{cpi:"4.7%",cpiP:"4.7%",cpiT:"sticky",core:"4.3%",gdp:"-0.3%",gdpT:"recession",unemp:"4.3%",unempT:"rising",wages:"3.8% YoY",pmi:"47.1",tb:"-NZ$1.2B",stance:"Easing",next:"2026-05-28",last:"Cut to 5.50%",pricing:"3-4 cuts"},
Canada:{cpi:"2.9%",cpiP:"2.8%",cpiT:"near target",core:"3.2%",gdp:"1.0%",gdpT:"slowing",unemp:"5.8%",unempT:"rising",wages:"3.9% YoY",pmi:"49.8",tb:"-C$1.5B",stance:"Easing",next:"2026-04-16",last:"Cut to 5.00%",pricing:"2-3 more cuts"},
Sweden:{cpi:"2.2%",cpiP:"2.5%",cpiT:"at target",core:"2.5%",gdp:"0.1%",gdpT:"stagnant",unemp:"8.2%",unempT:"high",wages:"3.5% YoY",pmi:"48.2",tb:"+SEK12B",stance:"Easing",next:"2026-05-07",last:"Cut to 4.00%",pricing:"2-3 more cuts"},
Norway:{cpi:"3.6%",cpiP:"3.9%",cpiT:"sticky",core:"4.1%",gdp:"0.8%",gdpT:"stable",unemp:"3.7%",unempT:"low",wages:"5.2% YoY",pmi:"51.8",tb:"+NOK80B",stance:"Hawkish hold",next:"2026-05-08",last:"Hold 4.50%",pricing:"1-2 cuts"},
Brazil:{cpi:"4.5%",cpiP:"4.2%",cpiT:"rising",core:"4.0%",gdp:"2.1%",gdpT:"strong",unemp:"7.8%",unempT:"falling",wages:"N/A",pmi:"52.1",tb:"+$7.2B",stance:"Tightening",next:"2026-05-07",last:"Hike to 10.75%",pricing:"Terminal ~12%"},
Mexico:{cpi:"4.2%",cpiP:"4.4%",cpiT:"slowly falling",core:"4.6%",gdp:"2.5%",gdpT:"resilient",unemp:"2.8%",unempT:"tight",wages:"N/A",pmi:"51.5",tb:"-$5.8B",stance:"Cautious hold",next:"2026-05-15",last:"Hold 11.00%",pricing:"2-3 cuts"},
"South Africa":{cpi:"5.3%",cpiP:"5.1%",cpiT:"elevated",core:"4.6%",gdp:"0.7%",gdpT:"weak",unemp:"32.1%",unempT:"structural",wages:"N/A",pmi:"49.5",tb:"-ZAR15B",stance:"Hold",next:"2026-05-29",last:"Hold 8.25%",pricing:"1-2 cuts"},
India:{cpi:"5.1%",cpiP:"5.3%",cpiT:"moderating",core:"4.2%",gdp:"6.5%",gdpT:"strong",unemp:"7.1%",unempT:"stable",wages:"N/A",pmi:"58.3",tb:"-$20B",stance:"Easing bias",next:"2026-04-09",last:"Cut to 6.50%",pricing:"2 more cuts"},
"South Korea":{cpi:"2.1%",cpiP:"2.3%",cpiT:"at target",core:"2.0%",gdp:"2.0%",gdpT:"recovering",unemp:"2.7%",unempT:"stable",wages:"N/A",pmi:"50.8",tb:"+$4.5B",stance:"Easing",next:"2026-05-29",last:"Cut to 3.50%",pricing:"1-2 cuts"},
Turkey:{cpi:"65%",cpiP:"67%",cpiT:"falling from peak",core:"70%",gdp:"3.2%",gdpT:"slowing",unemp:"9.4%",unempT:"stable",wages:"N/A",pmi:"49.1",tb:"-$7.5B",stance:"Hawkish hold",next:"2026-04-17",last:"Hold 50.00%",pricing:"Cuts H2 2026"},
Poland:{cpi:"3.9%",cpiP:"3.7%",cpiT:"rising",core:"4.1%",gdp:"2.8%",gdpT:"solid",unemp:"5.1%",unempT:"stable",wages:"12% YoY",pmi:"50.2",tb:"-PLN5B",stance:"On hold",next:"2026-05-07",last:"Hold 5.75%",pricing:"Cuts unlikely H1"},
};
const DCAL=[{date:"2026-04-08",time:"12:30",ctry:"🇺🇸",ev:"FOMC Minutes",prev:"—",fcast:"—",act:"—",imp:"red"},{date:"2026-04-09",time:"12:30",ctry:"🇺🇸",ev:"US CPI YoY",prev:"3.2%",fcast:"3.1%",act:"—",imp:"red"},{date:"2026-04-10",time:"11:00",ctry:"🇪🇺",ev:"ECB Rate Decision",prev:"4.50%",fcast:"4.25%",act:"—",imp:"red"},{date:"2026-04-10",time:"07:00",ctry:"🇬🇧",ev:"UK GDP MoM",prev:"-0.1%",fcast:"0.2%",act:"—",imp:"red"}];
const CP=["EURUSD","GBPUSD","USDJPY","AUDUSD","USDCAD","NZDUSD","Gold","US10Y"];const CM=[[1,.82,-.31,.65,-.72,.58,.42,-.35],[.82,1,-.22,.55,-.61,.48,.35,-.28],[-.31,-.22,1,-.45,.38,-.42,-.18,.72],[.65,.55,-.45,1,-.58,.85,.52,-.42],[-.72,-.61,.38,-.58,1,-.52,-.38,.32],[.58,.48,-.42,.85,-.52,1,.45,-.38],[.42,.35,-.18,.52,-.38,.45,1,-.55],[-.35,-.28,.72,-.42,.32,-.38,-.55,1]];
/* ═══ UI ATOMS ════════════════════════════════════════════════════ */ /* ═══ UI ATOMS ════════════════════════════════════════════════════ */
const Dot=({on})=><span style={{display:"inline-block",width:6,height:6,borderRadius:"50%",background:on?C.g:C.am,marginRight:5}}/>; const Dot=({on})=><span style={{display:"inline-block",width:6,height:6,borderRadius:"50%",background:on?C.g:C.am,marginRight:5}}/>;
@@ -66,13 +46,13 @@ function RiskBar({d,live}){const vd=(d.VIX?.chg||0)<0,dd=(d.DXY?.chg||0)<0,on=vd
/* ═══ PAGES ═══════════════════════════════════════════════════════ */ /* ═══ 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 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 · Others: CB rate only":"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.toFixed(2)+"%"},{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 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 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)??a.cb,yb=gY(b.c)??b.cb,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})}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 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??5.5;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:"cb",l:"CB Rate",rn:r=>r.cb.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={RT.filter(r=>r.c!=="US").map(r=>({...r,carry:+((yld[r.c]?.y2??r.cb)-uY).toFixed(2)})).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 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 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(){const cC=v=>v>=.7?"rgba(34,197,94,"+v*.7+")":v>=.3?"rgba(34,197,94,"+v*.4+")":v>=-.3?"rgba(148,163,184,0.1)":v>=-.7?"rgba(239,68,68,"+Math.abs(v)*.4+")":"rgba(239,68,68,"+Math.abs(v)*.7+")";return <div><div style={{marginBottom:12}}><span style={{color:C.ac,fontSize:13,fontWeight:700}}>30-Day Correlation Matrix</span></div><div style={{overflowX:"auto"}}><div style={{display:"inline-block"}}><div style={{display:"flex"}}><div style={{width:70}}/>{CP.map(p=><div key={p} style={{width:40,textAlign:"center",fontSize:8,color:C.txM,fontWeight:600,transform:"rotate(-45deg)",height:40,display:"flex",alignItems:"flex-end",justifyContent:"center",paddingBottom:4}}>{p}</div>)}</div>{CP.map((p,i)=><div key={p} style={{display:"flex"}}><div style={{width:70,fontSize:10,color:C.txD,fontWeight:600,textAlign:"right",paddingRight:8,display:"flex",alignItems:"center",justifyContent:"flex-end"}}>{p}</div>{CM[i].map((v,j)=><div key={j} title={v.toFixed(2)} style={{width:40,height:40,display:"flex",alignItems:"center",justifyContent:"center",background:i===j?"rgba(99,102,241,0.3)":cC(v),border:"1px solid "+C.bd+"40",fontSize:9,fontFamily:Mn,fontWeight:600,color:Math.abs(v)>.5?C.tx:C.txM}}>{i===j?"1":v.toFixed(1)}</div>)}</div>)}</div></div></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 ═══════════════════════════════════════════════════ */ /* ═══ DEEP DIVE ═══════════════════════════════════════════════════ */
function Deep({ctry,g10,em,yld,news,nL,risk}){ function Deep({ctry,g10,em,yld,news,nL,risk}){
@@ -345,7 +325,7 @@ function Deep({ctry,g10,em,yld,news,nL,risk}){
} }
/* ═══ BRIEF ═══════════════════════════════════════════════════════ */ /* ═══ 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").map(r=>({ccy:r.ccy,c:+((yld[r.c]?.y2??r.cb)-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 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 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 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 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>}
@@ -359,7 +339,7 @@ function App(){
const [g10D,sG10]=useState([]);const [emD,sEM]=useState([]);const [fxL,sFxL]=useState(false); const [g10D,sG10]=useState([]);const [emD,sEM]=useState([]);const [fxL,sFxL]=useState(false);
const [yld,sYld]=useState({});const [yL,sYL]=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 [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(DCAL);const [cL,sCL]=useState(false);const [news,sNews]=useState([]);const [nL,sNL]=useState(false); 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 [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 svK=useCallback(nk=>{setKeys(nk);Object.entries(nk).forEach(([k,v])=>sk(k,v))},[]);
const load=useCallback(async()=>{ const load=useCallback(async()=>{
+1
View File
@@ -105,6 +105,7 @@ ${JSON.stringify(payload, null, 2)}
text: { text: {
format: { format: {
type: "json_schema", type: "json_schema",
name: "country_deep_dive",
strict: true, strict: true,
schema schema
} }