1import{c as Ki,r as d,j as e,bK as Nc,dG as Tr,bo as Ia,W as Hr,B as qr,z as St,a9 as Sc,G as Cr,aH as Da,bb as ao,$ as Gn,bv as $i,a0 as Tc,a8 as Fa,F as Cc,Q as Wn,ba as Hi,bp as Mc,s as Vn,A as Ec,dH as Ac,dI as Rc,a1 as zc,a2 as Pc,bz as Lc,v as Ga}from"./index-CD_KZbct.js";import{A as Oc,G as qi}from"./grip-vertical-xr00kbOt.js";import{P as Bc}from"./pin-DSiNECM2.js";/** 2 * @license lucide-react v0.453.0 - ISC 3 * 4 * This source code is licensed under the ISC license. 5 * See the LICENSE file in the root directory of this source tree. 6 */const no=Ki("ReceiptText",[["path",{d:"M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z",key:"q3az6g"}],["path",{d:"M14 8H8",key:"1l3xfs"}],["path",{d:"M16 12H8",key:"1fr5h0"}],["path",{d:"M13 16H8",key:"wsln4y"}]]);/** 7 * @license lucide-react v0.453.0 - ISC 8 * 9 * This source code is licensed under the ISC license. 10 * See the LICENSE file in the root directory of this source tree. 11 */const $a=Ki("SlidersVertical",[["line",{x1:"4",x2:"4",y1:"21",y2:"14",key:"1p332r"}],["line",{x1:"4",x2:"4",y1:"10",y2:"3",key:"gb41h5"}],["line",{x1:"12",x2:"12",y1:"21",y2:"12",key:"hf2csr"}],["line",{x1:"12",x2:"12",y1:"8",y2:"3",key:"1kfi7u"}],["line",{x1:"20",x2:"20",y1:"21",y2:"16",key:"1lhrwl"}],["line",{x1:"20",x2:"20",y1:"12",y2:"3",key:"16vvfq"}],["line",{x1:"2",x2:"6",y1:"14",y2:"14",key:"1uebub"}],["line",{x1:"10",x2:"14",y1:"8",y2:"8",key:"1yglbp"}],["line",{x1:"18",x2:"22",y1:"16",y2:"16",key:"1jxqpz"}]]),rt=50,ho=c=>isFinite(c)?c:0,i=c=>(c<0?"â$":"$")+Math.round(Math.abs(ho(c))).toLocaleString(),xe=c=>{const w=Math.abs(c);return w>=1e6?(c<0?"â$":"$")+(w/1e6).toFixed(1)+"M":(c<0?"â$":"$")+Math.round(w/1e3)+"k"},Un=c=>{const w=Math.abs(ho(c)),x=c<0?"â$":"$",v=Math.round(w/1e3);return v===0?"$0":v>=1e3?x+(w/1e6).toFixed(2)+"M":x+v+"k"},so=c=>{const w=Math.abs(ho(c));return w>=1e4||w<1e3?Un(c):(c<0?"â$":"$")+(w/1e3).toFixed(w%1e3?1:0)+"k"},R=c=>{const w=c*100;return Math.abs(w%1)<1e-9?w.toFixed(0)+"%":Math.abs(w*10%1)<1e-9?w.toFixed(1)+"%":Math.abs(w*100%1)<1e-9?w.toFixed(2)+"%":w.toFixed(2).replace(/0+$/,"").replace(/\.$/,"")+"%"},Gi=(c,w)=>{const v=w-c<=15?5:10,y=new Set([c,w]);for(let A=Math.ceil(c/v)*v;A<w;A+=v)y.add(A);return[...y].sort((A,q)=>A-q)},Ic=(c,w)=>{const x=c.map(v=>({...v})).sort((v,y)=>v.y-y.y);for(const v of x)v.y=Math.max(v.y,v.h/2-4);for(let v=1;v<x.length;v++){const y=(x[v-1].h+x[v].h)/2+6;x[v].y-x[v-1].y<y&&(x[v].y=x[v-1].y+y)}for(let v=x.length-1;v>=0;v--)if(x[v].y=Math.min(x[v].y,w-x[v].h/2+10),v>0){const y=(x[v-1].h+x[v].h)/2+6;x[v].y-x[v-1].y<y&&(x[v-1].y=x[v].y-y)}return v=>x.find(y=>y.k===v).y},Wi=["Synthetic loan","Second mortgage","HELOC","Margin","Pledged-asset line / SBLOC","Other"],na=["Second mortgage","HELOC"],qa=["Pledged-asset line / SBLOC"],Dc=c=>!!c&&qa.includes(c.type)&&!!c.noPay&&!!c.deductible,Fc=(c,w)=>Dc(c)?w==="noPay"?{...c,deductible:!1}:w==="deductible"?{...c,noPay:!1}:{...c,deductible:!1}:c,Yi=(c,w=320)=>{const[x,v]=d.useState(c),y=d.useRef({raf:0,disp:c});y.current.disp=x;const A=c.join("|");return d.useEffect(()=>{const q=y.current.disp;if(q.length!==c.length||typeof window<"u"&&window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches){v(c);return}if(q.every((dr,Ue)=>Math.abs(dr-c[Ue])<1e-6))return;const le=q.slice(),Z=performance.now(),ue=dr=>{const Ue=Math.min(1,(dr-Z)/w),Va=Ue<.5?4*Ue*Ue*Ue:1-Math.pow(-2*Ue+2,3)/2;v(Ue>=1?c:le.map((la,Xn)=>la+(c[Xn]-la)*Va)),Ue<1&&(y.current.raf=requestAnimationFrame(ue))};return cancelAnimationFrame(y.current.raf),y.current.raf=requestAnimationFrame(ue),()=>cancelAnimationFrame(y.current.raf)},[A,w]),x.length===c.length?x:c},oo=(c,w,x=6)=>{const v=c.map(A=>({...A}));for(let A=1;A<v.length;A++){const q=(v[A-1].h+v[A].h)/2+x;v[A].y-v[A-1].y<q&&(v[A].y=v[A-1].y+q)}for(let A=v.length-1;A>=0;A--){const q=w-v[A].h/2;if(v[A].y>q&&(v[A].y=q),A<v.length-1){const le=(v[A].h+v[A+1].h)/2+x;v[A+1].y-v[A].y<le&&(v[A].y=v[A+1].y-le)}}for(let A=0;A<v.length;A++){const q=v[A].h/2;if(v[A].y<q&&(v[A].y=q),A>0){const le=(v[A-1].h+v[A].h)/2+x;v[A].y-v[A-1].y<le&&(v[A].y=v[A-1].y+le)}}const y={};return v.forEach(A=>{y[A.k]=A.y}),y},Wa=[{r:10,thr:[0,0],fedLTCG:0,niit:!1},{r:12,thr:[12400,24800],fedLTCG:0,niit:!1},{r:22,thr:[50400,100800],fedLTCG:15,niit:!1},{r:24,thr:[105700,211400],fedLTCG:15,niit:!1},{r:32,thr:[201775,403550],fedLTCG:15,niit:!0},{r:35,thr:[256225,512450],fedLTCG:20,niit:!0},{r:37,thr:[640600,768700],fedLTCG:20,niit:!0}],$c="https://www.irs.gov/pub/irs-drop/rp-25-32.pdf",Hc=40400,qc=1e4,Gc="https://uscode.house.gov/view.xhtml?req=granuleid:USC-prelim-title26-section164&num=0&edition=prelim#:~:text=calendar%20year%202026%2C%20%2440%2C400",Ui=c=>c===0?"$0":c%1e3?"$"+(c/1e3).toFixed(1)+"k":xe(c),co={r:28,thr:null,fedLTCG:20,niit:!0},Gr=[...Wa.slice(0,4),co,...Wa.slice(4)],_n={fed:{label:"Federal",ord:()=>0},ca:{label:"California",ord:c=>c>=37?13.3:c>=35?10.3:c>=24?9.3:c>=22?8:c>=12?4:1},ny:{label:"New York",ord:c=>c>=37?10.9:c>=35||c>=24?6.85:c>=22?6:c>=12?5.5:4},nyc:{label:"NYC",ord:c=>(c>=37?10.9:c>=35||c>=24?6.85:c>=22?6:c>=12?5.5:4)+3.876}},sa=(c,w)=>{const x=_n[w].ord(c.thr===null?37:c.r),v=c.r+x,y=c.fedLTCG+(c.niit?3.8:0)+x;return{ord:Math.round(v*10)/10,ltcg:Math.round(y*10)/10}},Vi=(c,w,x)=>{const v=_n[x]||_n.fed;for(const y of Wa){const A=v.ord(y.r),q=Math.round((y.r+A)*10)/10,le=Math.round((y.fedLTCG+(y.niit?3.8:0)+A)*10)/10;if(Math.abs(c-q)<.05&&Math.abs(w-le)<.05)return y.r}return null},ia="https://constructioncoverage.com/research/average-property-tax-by-state-county-city",Ji="https://www.boe.ca.gov/",Ye={AR:"https://www.dfa.arkansas.gov/office/arkansas-assessment-coordination-division/real-property/property-tax-relief/",DC:"https://otr.cfo.dc.gov/page/real-property-tax-rates",FL:"https://floridarevenue.com/property/Documents/2025_County_Profiles.pdf",MI:"https://treas-secure.state.mi.us/ptestimator",NJ:"https://www.nj.gov/treasury/taxation/pdf/lpt/gtr/2025taxrates.pdf",NM:"https://www.nmlegis.gov/Entity/LFC/Documents/Revenue_Reports/Property%20Taxes%20in%20NM%20brief_FINAL.pdf",NY:"https://www.tax.ny.gov/research/property/reports/fvtaxrates/overall_county_13.htm",SC:"https://dor.sc.gov/sites/dor/files/Documents/Policy%20Manuals/SCTIED-2025-Chapter%205.pdf",
11SF:"https://sftreasurer.org/property/secured-property-taxes",WA:"https://dor.wa.gov/about/statistics-reports/interactive-data/average-levy-rates-county-interactive-data-graphic",TX:"https://comptroller.texas.gov/taxes/property-tax/rates/"},po=[["Alabama",.372],["Alaska",.9],["Arizona",.426,ia,5],["Arkansas",1,Ye.AR,5],["California",null,Ji,2],["Colorado",.519],["Connecticut",1.355],["Delaware",.513],["District of Columbia",.78,Ye.DC,10],["Florida",1.602,Ye.FL,3],["Georgia",.767],["Hawaii",.308],["Idaho",.433],["Illinois",1.793],["Indiana",.756],["Iowa",1.249],["Kansas",1.201],["Kentucky",.723],["Louisiana",.557],["Maine",.903],["Maryland",.902],["Massachusetts",.948],["Michigan",1.5,Ye.MI,3],["Minnesota",.987],["Mississippi",.541],["Missouri",.849],["Montana",.592],["Nebraska",1.379],["Nevada",.504],["New Hampshire",1.35],["New Jersey",1.61,Ye.NJ],["New Mexico",1.1,Ye.NM,3],["New York",1.227,Ye.NY],["North Carolina",.624],["North Dakota",.941],["Ohio",1.276],["Oklahoma",.78],["Oregon",.789,ia,3],["Pennsylvania",1.137],["Rhode Island",.997],["South Carolina",.55,Ye.SC,3],["South Dakota",.998],["Tennessee",.462],["Texas",1.65,Ye.TX,10],["Utah",.452],["Vermont",1.399],["Virginia",.754],["Washington",.896,Ye.WA],["West Virginia",.483],["Wisconsin",1.188],["Wyoming",.582]],oa={California:{src:Ji,rows:[["Alameda",1.29],["Alpine",1.02],["Amador",1.06],["Butte",1.11],["Calaveras",1.08],["Colusa",1.05],["Contra Costa",1.19],["Del Norte",1.05],["El Dorado",1.1],["Fresno",1.18],["Glenn",1.06],["Humboldt",1.09],["Imperial",1.19],["Inyo",1.04],["Kern",1.22],["Kings",1.13],["Lake",1.1],["Lassen",1.09],["Los Angeles",1.22],["Madera",1.09],["Marin",1.13],["Mariposa",1.02],["Mendocino",1.08],["Merced",1.12],["Modoc",1.06],["Mono",1.03],["Monterey",1.11],["Napa",1.1],["Nevada",1.06],["Orange",1.08],["Placer",1.1],["Plumas",1.05],["Riverside",1.25],["Sacramento",1.14],["San Benito",1.13],["San Bernardino",1.18],["San Diego",1.12],["San Francisco",1.18268,Ye.SF],["San Joaquin",1.16],["San Luis Obispo",1.08],["San Mateo",1.13],["Santa Barbara",1.06],["Santa Clara",1.21],["Santa Cruz",1.12],["Shasta",1.09],["Sierra",1.05],["Siskiyou",1.07],["Solano",1.14],["Sonoma",1.12],["Stanislaus",1.12],["Sutter",1.12],["Tehama",1.06],["Trinity",1.03],["Tulare",1.13],["Tuolumne",1.05],["Ventura",1.09],["Yolo",1.15],["Yuba",1.14]]},Texas:{src:Ye.TX,rows:[["Harris (Houston)",1.9],["Dallas",2],["Tarrant (Fort Worth)",2],["Bexar (San Antonio)",2.05],["Travis (Austin)",1.9],["Collin (Plano)",1.45],["Denton",1.75],["Fort Bend",1.95],["Williamson",1.75],["Montgomery",1.55],["Hidalgo",1.6],["El Paso",2.1],["Galveston",1.8],["Bell",1.8],["Nueces (Corpus Christi)",1.9]]},Florida:{src:Ye.FL,rows:[["Miami-Dade",1.757],["Broward",1.965],["Palm Beach",1.721],["Hillsborough (Tampa)",1.835],["Orange (Orlando)",1.68],["Pinellas",1.831],["Duval (Jacksonville)",1.78],["Lee",1.382],["Polk",1.531],["Brevard",1.345],["Pasco",1.753],["Seminole",1.541],["Sarasota",1.199],["St. Johns",1.383],["Alachua (Gainesville)",2.06],["Escambia (Pensacola)",1.362],["Collier (Naples)",.953],["Monroe (Keys)",.805],["Walton",.876]]},"New York":{src:Ye.NY,rows:[["New York City (Class 1 home)",.85],["Westchester",1.6],["Nassau",1.9],["Suffolk",1.9],["Rockland",2.2],["Putnam",2.03],["Orange",2.15],["Dutchess",1.8],["Erie (Buffalo)",1.76],["Monroe (Rochester)",2.16],["Onondaga (Syracuse)",2.1],["Albany",1.9],["Broome",2.06],["Saratoga",1.5]]},"New Jersey":{src:Ye.NJ,rows:[["Bergen",1.653],["Essex",1.782],["Hudson",1.377],["Passaic",2.004],["Union",1.715],["Morris",1.569],["Middlesex",1.739],["Monmouth",1.317],["Ocean",1.253],["Somerset",1.766],["Mercer",1.87],["Burlington",1.89],["Camden",2.254],["Gloucester",2.092],["Atlantic",1.508],["Cumberland",1.947],["Salem",2.557],["Sussex",1.956],["Warren",1.993],["Hunterdon",1.868],["Cape May",.619]]},Illinois:{src:ia,rows:[["Cook (Chicago)",1.9],["DuPage",1.95],["Lake",2.02],["Will",2.05],["Kane",2.15],["McHenry",2.03],["Kendall",2.14],["DeKalb",2.09],["Winnebago (Rockford)",2.1],["Peoria",2.17],["Kankakee",2.02],["Madison",1.8],["St. Clair",1.9],["Champaign",1.95],["Sangamon (Springfield)",1.8]]},Washington:{src:Ye.WA,rows:[["King (Seattle)",.894],["Pierce (Tacoma)",1.014],["Snohomish",.832],["Spokane",.985],["Clark (Vancouver)",.954],["Thurston (Olympia)",.989],["Kitsap",.862],["Yakima",.907],["Whatcom",.805],["Skagit",.87],["Benton",.879],["Walla Walla",1.024],["Island",.728],["Jefferson",.822],["San Juan",.592]]},Massachusetts:{src:ia,rows:[["Middlesex",1],["Norfolk",1.05],["Suffolk (Boston)",.65],["Essex",.99],["Worcester",1.22],["Plymouth",1.1],["Bristol",1.1],["Hampden (Springfield)",1.34],["Hampshire",1.36],["Franklin",1.37],["Berkshire",1.3],["Barnstable (Cape Cod)",.62],["Dukes (Martha's Vineyard)",.4],["Nantucket",.3]]},Connecticut:{src:ia,rows:[["Fairfield",1.55],["Hartford",2.4],["New Haven",2.38],["Middlesex",2.01
11],["Tolland",1.95],["New London",1.75],["Litchfield",1.7],["Windham",1.85]]}},io=(c,w)=>{const x=po.find(([y])=>y===c)||[],v=oa[c];if(v&&w){const y=v.rows.find(([A])=>A===w);if(y)return{rate:y[1],src:y[2]||v.src,cap:x[3]??null}}return{rate:x[1]??null,src:x[2]||ia,cap:x[3]??null}},Wc=c=>{const w=oa[c];if(!w)return null;const x=w.rows.map(([,v])=>v);return[Math.min(...x),Math.max(...x)]},Yc=(c,w)=>{const x=Wc(c);return x?`${c} · ${R(x[0]/100).replace("%","")}â${R(x[1]/100)}`:w!=null?`${c} · ${R(w/100)}`:c},ke=(c,w,x,v)=>e.jsx("button",{type:"button",className:"bvr-lgsw"+(c?" on":""),role:"switch","aria-checked":c,"aria-label":x,title:v,onClick:w,children:e.jsx("span",{className:"knob"})}),nt=c=>{const w=c.target;requestAnimationFrame(()=>{try{w.select()}catch{}})},Ha=({size:c=14})=>e.jsxs("svg",{width:c,height:c,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:[e.jsx("rect",{x:"3",y:"3",width:"18",height:"18",rx:"2"}),e.jsx("path",{d:"M3 9h18M3 15h18M9 3v18M15 3v18"})]}),Ya=c=>{if(c.key!=="Enter")return;c.preventDefault();const w=c.currentTarget;w.blur(),requestAnimationFrame(()=>{try{w.focus(),w.select()}catch{}})},Ua=(c,w,x)=>w>0?Math.ceil((c+1e-9)/x)*x:Math.floor((c-1e-9)/x)*x,Uc=(c,w)=>Math.max(0,Math.round(Ua(c,w,.5)*10)/10),Vc=(c,w)=>Math.max(0,Math.round(Ua(c,w,.25)*100)/100),_i=(c,w)=>Math.max(0,Ua(c,w,1)),Zi=(c,w)=>{const x=c>=2e6?1e5:c>=75e4?5e4:25e3;return Math.max(0,Ua(c,w,x))},_c=(c,w)=>Math.max(1,Math.round(c)+w),Xc=(c,w)=>Math.max(0,Ua(c,w,100)),lo={base:{label:"Baseline (most counties)",btn:"Baseline",sub:"most counties",v:[832750,1066250,1288800,1601750]},nyc:{label:"NYC metro",btn:"NYC metro",sub:"New YorkâNewarkâJersey City metro",v:[1209750,1548975,1872225,2326875]},high:{label:"High-cost ceiling (SF · LA · DC · most of AK/HI)",btn:"High-cost",sub:"SF · LA · DC · most of AK/HI",v:[1249125,1599375,1933200,2402625]}},Kc="https://www.fhfa.gov/news/news-release/fhfa-announces-conforming-loan-limit-values-for-2026";function Yn({rate:c,inValue:w}){const x=d.useRef(null);return d.useEffect(()=>{const v=x.current;!v||document.activeElement===v||(v.value=R(c.v/100))},[c.v]),e.jsx("input",{ref:x,className:"bvr-editpct"+(w?" inval":""),defaultValue:R(c.v/100),onFocus:nt,onClick:v=>v.stopPropagation(),onMouseDown:v=>v.stopPropagation(),onKeyDown:v=>{v.stopPropagation(),Ya(v)},onBlur:v=>{const y=parseFloat(v.target.value.replace("%",""));isNaN(y)||c.set(y),v.target.value=R((isNaN(y)?c.v:y)/100)}})}function Le({v:c,set:w,w:x=52,step:v}){const y=d.useRef(null),A=v||Uc;return d.useEffect(()=>{const q=y.current;!q||document.activeElement===q||(q.value=R(c/100))},[c]),e.jsx("input",{ref:y,className:"bvr-in num",style:{width:x},defaultValue:R(c/100),onFocus:nt,onKeyDown:q=>{if(q.key==="ArrowUp"||q.key==="ArrowDown"){q.preventDefault();const le=q.key==="ArrowUp"?1:-1,Z=parseFloat(q.target.value.replace("%","")),ue=A(isNaN(Z)?c:Z,le);w(ue),q.target.value=R(ue/100),requestAnimationFrame(()=>{try{q.target.select()}catch{}});return}Ya(q)},onBlur:q=>{const le=parseFloat(q.target.value.replace("%",""));isNaN(le)||w(le),q.target.value=R((isNaN(le)?c:le)/100)}})}function Tt({v:c,set:w,w:x=96,inputRef:v,step:y}){const A=d.useRef(null),q=y||Zi,le=Z=>{A.current=Z,typeof v=="function"?v(Z):v&&"current"in v&&(v.current=Z)};return d.useEffect(()=>{const Z=A.current;!Z||document.activeElement===Z||(Z.value="$"+Math.round(c).toLocaleString())},[c]),e.jsx("input",{ref:le,className:"bvr-in num",style:{width:x},defaultValue:"$"+Math.round(c).toLocaleString(),onFocus:nt,onKeyDown:Z=>{if(Z.key==="ArrowUp"||Z.key==="ArrowDown"){Z.preventDefault();const ue=Z.key==="ArrowUp"?1:-1,dr=parseFloat(Z.target.value.replace(/[^0-9.]/g,"")),Ue=q(isNaN(dr)?c:dr,ue);w(Ue),Z.target.value="$"+Math.round(Ue).toLocaleString(),requestAnimationFrame(()=>{try{Z.target.select()}catch{}});return}Ya(Z)},onBlur:Z=>{const ue=parseFloat(Z.target.value.replace(/[^0-9.]/g,""));isNaN(ue)||w(ue),Z.target.value="$"+Math.round(isNaN(ue)?c:ue).toLocaleString()}})}function Se({label:c,children:w,help:x}){return e.jsxs("div",{className:"bvr-fld",children:[e.jsx("label",{className:x?"has-help":"",onClick:x||void 0,children:c}),w]})}const at=({open:c,children:w})=>e.jsx("div",{className:"bvr-collapse collapse-motion"+(c?" open":""),"aria-hidden":!c,children:e.jsx("div",{className:"bvr-collapse-i",children:w})});
11function Jc(){const[c,w]=d.useState(5),[x,v]=d.useState(1),[y,A]=d.useState(25),q=d.useRef({touched:!1,armed:!1}),le=d.useRef(0),Z=d.useRef(null),[ue,dr]=d.useState(600),Ue=d.useRef(null),Va=d.useRef(null),la=d.useRef(null),[Xn,tl]=d.useState(!1),[Wr,rl]=d.useState(!0),[_a,bo]=d.useState(!1),[al,nl]=d.useState(!1),uo=d.useRef(null);d.useEffect(()=>{const t=uo.current;if(!t||typeof IntersectionObserver>"u")return;const r=new IntersectionObserver(([a])=>{nl(!a.isIntersecting&&a.boundingClientRect.top<64)},{rootMargin:"-64px 0px 0px 0px",threshold:0});return r.observe(t),()=>r.disconnect()},[]);const Xa=(t,r,a,n)=>e.jsx("button",{className:"bvr-vtab","aria-label":"Expand assumptions â "+r,onClick:()=>{bo(!1),ge(s=>({...s,[a]:!0}))},children:e.jsxs("span",{className:"bvr-vtab-i",children:[e.jsxs("span",{className:"bvr-vtab-h",children:[t,r]}),e.jsx("span",{className:"bvr-vtab-rows",children:n.map(([s,o])=>e.jsxs("span",{className:"bvr-vtab-row",children:[e.jsx("span",{className:"bvr-vtab-l",children:s}),e.jsx("b",{className:"num",children:o})]},s))})]})}),[Kn,Ka]=d.useState(!1),Jn=d.useRef(null),sl=()=>{clearTimeout(Jn.current),Ka(!0)},ol=()=>{clearTimeout(Jn.current),Jn.current=setTimeout(()=>Ka(!1),140)},il=()=>{const t=Ue.current;t&&t.scrollIntoView({behavior:"smooth",block:"start"})},Zn=d.useRef({}),[Ja,st]=d.useState(null),Ct=t=>r=>{r?Zn.current[t]=r:delete Zn.current[t]},Mr=t=>r=>{r.key==="Enter"&&(r.preventDefault(),r.currentTarget.blur(),st(t))};d.useEffect(()=>{if(!Ja)return;const t=Ja.startsWith("pop-"),r=setTimeout(()=>{const a=Zn.current[Ja];if(a){if(!t){const n=a.getBoundingClientRect(),s=window.innerHeight||document.documentElement.clientHeight;n.top>=0&&n.bottom<=s||a.scrollIntoView({block:"center",behavior:"smooth"})}typeof a.focus=="function"&&a.focus({preventScroll:!0}),typeof a.select=="function"&&a.select()}st(null)},t?130:90);return()=>clearTimeout(r)},[Ja]);const ll=x<=1?0:x;d.useEffect(()=>{w(t=>Math.max(x<=1?0:x,Math.min(y,t)))},[x,y]);const[G,vo]=d.useState(25e5),[re,mo]=d.useState(5e5),[ca,Qn]=d.useState(1e4),[Yr,es]=d.useState(2),[cl,Za]=d.useState(!1),qe=ca+G*(Yr/100),[Ve,Qa]=d.useState(4),[ht,da]=d.useState(8250),[Mt,en]=d.useState(4),[xo,Er]=d.useState(!1),[Et,pr]=d.useState(null),[hr,go]=d.useState(70),[X,fo]=d.useState(!1),[At,yo]=d.useState(2),[Oe,tn]=d.use
11State(!1),[pa,wo]=d.useState("high"),[Ur,ko]=d.useState(4500),[Ge,rn]=d.useState(50),[Rt,jo]=d.useState(70),[Ar,No]=d.useState(25),[Vr,ts]=d.useState(5),[Ht,an]=d.useState(1.18268),[bt,nn]=d.useState(2),[_e,sn]=d.useState(!0),[Kt,on]=d.useState(1),[Jt,ln]=d.useState(.5),[ut,rs]=d.useState("pct"),[br,as]=d.useState(4e3),[ur,cn]=d.useState(.2),[vr,So]=d.useState(0),[ha,To]=d.useState(4),[_r,Co]=d.useState(600),[qt,Mo]=d.useState(6),[ne,Zt]=d.useState(45.3),[be,Rr]=d.useState(34.1),[ot,dn]=d.useState(5e5),[Xe,Eo]=d.useState("ca"),[Te,dl]=d.useState("California"),[pn,Ao]=d.useState("San Francisco"),[ba,Ro]=d.useState("MFJ"),[Q,Xr]=d.useState(!0),[Gt,ns]=d.useState("usd"),[Qt,ss]=d.useState(1e4),[mr,os]=d.useState(100),[et,is]=d.useState(!1),[hn,zo]=d.useState(!1),pl=t=>{Ro(t),dn(t==="MFJ"?5e5:25e4)},[Ke,Po]=d.useState(70),[er,ls]=d.useState(4),[Wt,cs]=d.useState(7),zr=Et==null?re:Math.max(0,Math.min(re,Et)),vt=Math.max(0,re-zr),Lo=(()=>{const t=be/100*(hr/100);return t>=1?0:t/(1-t)})(),Je=vt*Lo,[te,Oo]=d.useState([{id:"m1",kind:"mortgage",name:"Mortgage",amount:1e6,rate:6.5,term:30,deductible:!0,open:!0},{id:"s1",kind:"secured",type:"Synthetic loan",amount:1e6,rate:5,term:4,deductible:!0,autoRoll:!0,noPay:!0,open:!1},{id:"s2",kind:"secured",type:"Pledged-asset line / SBLOC",amount:0,rate:6,term:30,deductible:!1,noPay:!1,open:!1},{id:"s3",kind:"secured",type:"HELOC",amount:0,rate:7.5,term:10,deductible:!1,noPay:!1,open:!1}]),[W,Pr]=d.useState(null),[ds,Bo]=d.useState(null),[ps,hs]=d.useState({left:-9999,top:-9999,ready:!1}),Lr=d.useRef(null),[bn,bs]=d.useState(null),ua=d.useRef(null),us=d.useRef(!1),[vs,ms]=d.useState(!0),[va,xs]=d.useState(!1);d.useEffect(()=>{xs(!1),_o(!1)},[W==null?void 0:W.kind,W==null?void 0:W.key]);const[gs,Io]=d.useState(!1),[Do,fs]=d.useState(!1),[Or,ys]=d.useState(!1),Fo=()=>{const t=!(Do||Or);fs(t),ys(t)},[it,hl]=d.useState(!0),[bl,ul]=d.useState(!0),$o=d.useRef(!1);d.useEffect(()=>{if($o.current)return;$o.current=!0;const t=setTimeout(()=>ul(!1),5e3);return()=>{clearTimeout(t)}},[]);const[un,vn]=d.useState(null),[vl,Ho]=d.useState(!1),tr=d.useRef(null),ml=()=>{tr.current&&(clearTimeout(tr.current),tr.current=null),Ho(!0),Io(!0)},xl=()=>{tr.current&&clearTimeout(tr.current),Io(!1),tr.current=setTimeout(()=>{Ho(!1),tr.current=null},320)};d.useEffect(()=>()=>{tr.current&&clearTimeout(tr.current)},[]);const[ce,ge]=d.useState({scenario:!1,ownership:!1,financing:!1,tax:!1,mix:!0,wealth:!0,river:!1}),Br=d.useRef(null),mn=d.useRef(null),xn=d.useRef(null),F=(t,r,a)=>{r.stopPropagation();const n=r.currentTarget;if(W&&Br.current===n){Pr(null),Br.current=null;return}Br.current=n;const s=(a||n).getBoundingClientRect();hs({left:-9999,top:-9999,ready:!1}),bs(null),ms(!0),Pr({...t,aTop:s.top,aBottom:s.bottom,aLeft:s.left,aRight:s.right})},gl=t=>{const r=Lr.current;if(!r)return;const a=r.getBoundingClientRect();ua.current={dx:t.clientX-a.left,dy:t.clientY-a.top,moved:!1};const n=o=>{const b=Lr.current,p=ua.current;if(!b||!p)return;p.moved=!0;const u=b.offsetWidth||316,f=b.offsetHeight||200,S=Math.max(8,Math.min(o.clientX-p.dx,window.innerWidth-u-8)),k=Math.max(8,Math.min(o.clientY-p.dy,window.innerHeight-f-8));bs({x:S,y:k})},s=()=>{window.removeEventListener("pointermove",n),window.removeEventListener("pointerup",s),ua.current&&ua.current.moved&&(us.current=!0,setTimeout(()=>{us.current=!1},0)),ua.current=null};window.addEventListener("pointermove",n),window.addEventListener("pointerup",s)},qo=(t,r)=>{if(!r)return;Br.current=r;const a=r.getBoundingClientRect();hs({left:-9999,top:-9999,ready:!1}),bs(null),ms(!0),Pr({...t,aTop:a.top,aBottom:a.bottom,aLeft:a.left,aRight:a.right})},ws=t=>{Pr(null),Br.current=null,t==="price"?(ge(r=>({...r,scenario:!0})),setTimeout(()=>qo({kind:"help",key:"funding",focus:"pop-price"},mn.current),60)):t==="down"?(ge(r=>({...r,financing:!0})),setTimeout(()=>{st("down"),qo({kind:"help",key:"downfund"},xn.current)},60)):(ge(r=>({...r,financing:!0})),De(t,{open:!0}),setTimeout(()=>st("loan-"+t),60))},De=(t,r)=>Oo(a=>a.map(n=>{if(n.id!==t)return n;const s={...n,...r},o=r.noPay!==void 0&&r.noPay?"noPay":r.deductible!==void 0&&r.deductible?"deductible":void 0;return Fc(s,o)})),gn=(t,r,a)=>{const n=!t[r],s=r==="noPay"?"deductible":"noPay",o=n&&!!t[s]&&qa.includes(t.type);
11De(t.id,o?{[r]:n,[s]:!1}:{[r]:n}),o&&(W&&W.kind==="help"&&W.key==="sbloc"?(a.stopPropagation(),Pr(b=>({...b,conflict:r,cn:(b.cn||0)+1}))):F({kind:"help",key:"sbloc",loanId:t.id,conflict:r,cn:1},a))},Go=d.useRef(!0);d.useEffect(()=>{if(Go.current){Go.current=!1;return}ut==="pct"&&cn(X?.25:.2)},[X]);const[K,fn]=d.useState("s1"),[yn,Wo]=d.useState(null),[Ze,fl]=d.useState(0),ks=d.useRef(null),zt=["price","down","m1","s1","s2","s3"],Yo=zt.filter(t=>t!=="price"),xr=t=>t==="price"?"Purchase price":t==="down"?"Down payment":(te.find(r=>r.id===t)||{}).type||(te.find(r=>r.id===t)||{}).name||t,yl={"Purchase price":"Price","Down payment":"Down pmt",Mortgage:"Mortgage","Synthetic loan":"Synthetic","Second mortgage":"2nd Mortgage",HELOC:"HELOC",Margin:"Margin","Pledged-asset line / SBLOC":"SBLOC",Other:"Other"},wl=t=>{const r=xr(t);return yl[r]||r.split(" /")[0]},mt=t=>t==="price"?G:t==="down"?re:(te.find(r=>r.id===t)||{}).amount||0,Ir=(t,r)=>{t==="price"?vo(r):t==="down"?mo(r):De(t,{amount:r})},Uo=d.useRef(null),js=t=>{const r=re,a=Et==null||Math.abs(Et-r)<1;Be("down",Math.max(0,t)),Et!=null&&Et>t&&!a&&pr(t),clearTimeout(Uo.current),a&&Math.abs(t-r)>=1&&(Uo.current=setTimeout(()=>{pr(Math.max(0,t))},470))},[Kr,Vo]=d.useState(null),Ns=d.useRef(null),wn=t=>{Wo(t),ks.current&&clearTimeout(ks.current),ks.current=setTimeout(()=>Wo(null),1300),Vo(t),Ns.current&&clearTimeout(Ns.current),Ns.current=setTimeout(()=>Vo(null),5e3)},ma=(t,r)=>{const a=()=>Yo.reduce((s,o)=>s+t[o],0);r==="price"?t.price=a():t[r]=Math.max(0,t.price-(a()-t[r]));const n=t.price-a();fl(Math.abs(n)<1?0:n)},[Fe,xa]=d.useState(null),[ga,Jr]=d.useState(null),[Ss,kn]=d.useState(null),Dr=(()=>{if(!Ss)return null;const t=(te.find(r=>r.id==="m1")||{}).amount||0;return Math.abs(t-Ss.v)<1?Ss:null})(),[jn,kl]=d.useState("delta"),[Nn,_o]=d.useState(!1),Be=(t,r)=>{const a=Math.max(0,r);if(Math.abs(mt(t)-a)<.5)return;const n=K;if(n===t){const o=mt(t);Ir(t,a),xa({k:t,prev:o});return}const s={};zt.forEach(o=>s[o]=mt(o)),s[t]=a,ma(s,n),Ir(t,s[t]),n!==t&&Ir(n,s[n]),wn(n)},Ts=t=>{const r=Fe,a={};if(zt.forEach(s=>a[s]=mt(s)),ma(a,t),Ir(t,a[t]),fn(t),wn(t),r&&r.mode==="claim"){r.after&&r.after(),xa(null),Jr(null);return}const n=Fe&&Fe.k;n&&(ge(s=>({...s,[n==="price"?"scenario":"financing"]:!0})),st(n==="price"?"price":n==="down"?"down":"loan-"+n)),xa(null)},Cs=()=>{Fe&&Fe.mode!=="claim"&&Ir(Fe.k,Fe.prev),xa(null),Jr(null)},[xt,fa]=d.useState(!1),[Ms,Es]=d.useState(null),[As,Rs]=d.useState(!1),zs=d.useRef(!1),Ps=d.useRef(null),gt=(t,r)=>{try{window.dispatchEvent(new CustomEvent("bvr-track",{detail:{action:t,value:r===void 0?"":String(r)}}))}catch{}},gr=d.useRef(!1);d.useEffect(()=>{gr.current&>("sticky_sliders",xt?"open":"close")},[xt]),d.useEffect(()=>{gr.current&>("emoji_swap",it?"summary":"detailed")},[it]),d.useEffect(()=>{gr.current&>("home_price",G)},[G]),d.useEffect(()=>{gr.current&>("debt_change",te.length)},[te]),d.useEffect(()=>{gr.current&>("multifamily",X?"on":"off")},[X]),d.useEffect(()=>{gr.current&>("spine_expand",Or?"open":"close")},[Or]),d.useEffect(()=>{gr.current&>("tax_rate",ne)},[ne]),d.useEffect(()=>{gr.current=!0},[]);const Ls=()=>Es(t=>{if(!t)return t;const r=Ps.current,a=r?r.offsetWidth:250,n=r?r.offsetHeight:200;let s=10;if(typeof document<"u"){const S=getComputedStyle(document.documentElement).getPropertyValue("--sticky-top")||(r&&r.parentElement?getComputedStyle(r).getPropertyValue("--sticky-top"):""),k=parseInt(S,10);isFinite(k)&&(s=k)}const o=s+8,b=window.innerWidth-a-8,p=window.innerHeight-n-8,u=Math.max(8,Math.min(t.x,Math.max(8,b))),f=Math.max(o,Math.min(t.y,Math.max(o,p)));return u===t.x&&f===t.y?t:{x:u,y:f}});d.useEffect(()=>{if(!xt)return;const t=requestAnimationFrame(Ls);window.addEventListener("resize",Ls);let r;return zs.current||(zs.current=!0,r=setTimeout(()=>Rs(!0),260)),()=>{cancelAnimationFrame(t),clearTimeout(r),window.removeEventListener("resize",Ls)}},[xt]),d.useEffect(()=>{if(!As)return;const t=setTimeout(()=>Rs(!1),1400);return()=>clearTimeout(t)},[As]);const jl=t=>{const r=Math.pow(10,Math.floor(Math.log10(Math.max(1,t)))),a=t/r;return(a<=1?1:a<=2.5?2.5:a<=5?5:10)*r},Nl=(t,r)=>{const a=Math.max(t,r),n=jl(a/40),s=o=>Math.round(o/n)*n;return{base:a,step:n,min:Math.max(0,s(a*.5)),max:s(a*1.5)}},Sn=[{id:"price",label:"Purchase price",kind:"id$",floor:25e4},{id:"down",label:"Down payment",kind:"id$",floor:1e5,zeroMin:!0},{id:"m1",label:"Mortgage",kind:"id$",floor:2e5,zeroMin:!0},{id:"s1",label:"Synthetic loan",kind:"id$",floor:2e5,zeroMin:!0},{id:"rentMo",label:"Rent / mo",kind:"$",floor:2e3},{id:"appr",label:"Appreciation %",kind:"pct",min:0,max:10,step:.25},{id:"tax",label:"Tax bracket",kind:"bracket"}],Xo=t=>t==="rentMo"?ht:t==="appr"?Ve:zt.includes(t)?mt(t):0,Os=(t,r)=>{t==="rentMo"?da(r):t==="appr"?Qa(r):Be(t,r)},Tn=t=>{const r=Sn.find(n=>n.id===t);if(!r)return{id:t};if(r.kind==="bracket"||r.kind==="pct")return{id:t};const a=Nl(Xo(t),r.floor);if(r.zeroMin&&(a.min=0),t==="m1"){const n=Math.ceil((G+qe)/a.step)*a.step;a.max<n&&(a.max=n)}return{id:t,...a}}
11,[ya,wa]=d.useState(null);d.useEffect(()=>{xt&&ya===null&&wa([Tn("tax"),Tn("m1")])},[xt]);const Ko=(t,r)=>wa(a=>a.map((n,s)=>s===t?Tn(r):n)),Sl=(t,r)=>{if(zt.includes(r)&&K===r){xa({k:r,prev:mt(r),mode:"claim",after:()=>Ko(t,r)});return}Ko(t,r)},Bs=d.useRef(null),Tl=t=>{const a=t.currentTarget.closest(".bvr-slpanel").getBoundingClientRect();Bs.current={dx:t.clientX-a.left,dy:t.clientY-a.top};const n=o=>{const b=Ps.current,p=b?b.offsetWidth:250,u=b?b.offsetHeight:200,f=Math.max(8,Math.min(o.clientX-Bs.current.dx,window.innerWidth-p-8)),S=Math.max(8,Math.min(o.clientY-Bs.current.dy,window.innerHeight-u-8));Es({x:f,y:S})},s=()=>{window.removeEventListener("pointermove",n),window.removeEventListener("pointerup",s)};window.addEventListener("pointermove",n),window.addEventListener("pointerup",s)},Cl=(t,r)=>{De(t,{type:r,kind:"secured",deductible:r==="Synthetic loan",noPay:r==="Synthetic loan"}),Ts(t),Jr(null)},Jo=()=>{const t={};zt.forEach(b=>t[b]=mt(b));const r=Yo.reduce((b,p)=>b+t[p],0),a=t.price-r,n=zt.filter(b=>b!==Fe.k).map(b=>{const p=b==="price",u=p?r:t[b]+a,f=u>=0,S=!p&&t[b]===0&&u>0;return{f:b,target:u,ok:f,isAdd:S,label:xr(b)}}).filter(b=>b.ok||b.f==="price"||t[b.f]>0).filter((b=>p=>p.isAdd?b.v?!1:b.v=!0:!0)({v:!1})).sort((b,p)=>(b.isAdd?2:b.f==="price"?1:0)-(p.isAdd?2:p.f==="price"?1:0)),o=((Fe.k==="price"?["m1"]:["down","m1"]).map(b=>n.find(p=>p.f===b&&p.ok)).find(Boolean)||n.find(b=>b.ok)||{}).f;return{cur:t,gap:a,opts:n,rec:o}};d.useEffect(()=>{if(!Fe)return;const t=r=>{if(r.key==="Escape")r.stopPropagation(),Cs();else if(r.key==="Enter"&&!ga){r.stopPropagation(),r.preventDefault();const{opts:a,rec:n}=Jo(),s=a.find(o=>o.f===n&&o.ok);s&&(s.isAdd?Jr(s.f):Ts(s.f))}};return window.addEventListener("keydown",t,!0),()=>window.removeEventListener("keydown",t,!0)},[Fe,ga]);const Cn=t=>{fn(t);const r={};zt.forEach(a=>r[a]=mt(a)),ma(r,t),Ir(t,r[t]),wn(t)},Zr=({k:t})=>K===t?e.jsx("span",{className:"bvr-autopill",title:"Auto-solved to keep funding balanced",children:"auto"}):e.jsx("button",{className:"bvr-lockbtn",onClick:r=>{r.stopPropagation(),Cn(t)},title:"Locked â click to auto-solve this field instead","aria-label":"Auto-solve "+xr(t),children:e.jsx(Da,{size:10})}),Zo=Vi(ne,be,Xe)===37,fr=Zo&&!hn,Is=Gt==="usd"?Qt===0:mr===0,I=d.useMemo(()=>{const t=re+qe,r=Ve/100,a=_e?Math.min(bt/100,r):r,n=ne/100,s=be/100,o=Ke/100,b=er/100,p=Wt/100,u=te.filter(m=>m.kind==="mortgage"||na.includes(m.type)),f=te.filter(m=>m.kind==="secured"&&!na.includes(m.type)),S=.6*s+.4*n,k=f.reduce((m,M)=>m+M.amount,0),j=75e4,T=u.map(m=>{const M=m.amount>0&&m.rate>0&&m.term>0,Re=m.rate/100/12;return{l:m,ok:M,m:Re,payM:M?m.amount*Re/(1-Math.pow(1+Re,-(m.term*12))):0,bal:m.amount}}),B=f.map(m=>({l:m,r:m.rate/100,bal:m.amount})),P=[...B].reverse(),de=m=>{let M=m;for(let Re=T.length-1;Re>=0&&M>.005;Re--){const ve=Math.min(T[Re].bal,M);T[Re].bal-=ve,M-=ve}return m-M},ae=m=>G*Math.pow(1+a,m-1),je=m=>G*Math.pow(1+r,m-1),fe=m=>ae(m)*(Ht/100),Me=m=>je(m)*(Kt/100),pe=m=>je(m)*(Jt/100),Ce=m=>ut==="pct"?je(m)*(ur/100):br*Math.pow(1.03,m-1),Ee=m=>vr*12*Math.pow(1+ha/100,m-1),D=m=>ht*12*Math.pow(1+Mt/100,m-1),Ie=m=>_r*Math.pow(1.03,m-1),ye=X?1-Ge/100:1,se=m=>{const M=fe(m)*ye;return Gt==="usd"?Math.min(M,Math.max(0,Qt)):M*(Math.max(0,Math.min(100,mr))/100)},wt=m=>se(m)*n,kt=m=>et||fr?0:wt(m),ct=X?Ge/100:0,Lt=X?Math.max(0,At-1):0,Ot=X?G*(Rt/100)*ct/27.5:0,tt=m=>X?Ur*12*Lt*Math.pow(1+Mt/100,m-1):0,Ut=m=>tt(m)*(1-Vr/100),C=m=>D(m)+Ie(m),_=[null],we=[0];let U=0,ie=0;for(let m=1;m<=rt;m++){const M=T.reduce((H,Ne)=>H+Ne.bal,0);let Re=0,ve=0,Bt=0;T.forEach(H=>{let Ne=0,Ba=0;if(H.ok)for(let lr=0;lr<12&&H.bal>0;lr++){const cr=H.bal*H.m,Fi=Math.min(H.payM,H.bal+cr);Ne+=cr,Ba+=Fi,H.bal=Math.max(0,H.bal-(Fi-cr))}Re+=Ne,ve+=Ba,H.l.deductible&&(Bt+=Ne)});const Sr=M*ye,It=Math.min(1,j/Math.max(1,Sr)),jt=Bt*ye*It,Nt=B.map(H=>H.bal);let Dt=0,pt=0,or=0,l=0,g=0,Y=0;B.forEach(H=>{const Ne=H.bal*H.r;Dt+=Ne,H.l.noPay||(pt+=Ne),H.l.type==="Synthetic loan"?(or+=Ne,H.l.deductible?l+=Ne:g+=Ne):H.l.deductible&&(Y+=Ne)});const V=Dt-pt;ie+=g;const $=X?ct*(fe(m)+Me(m)+pe(m)+Ce(m)+Ee(m)+Re):0,O=Ut(m)-$-Ot,me=X&&O>0?O*n:0,J=Ut(m)-me,ze=X&&O<0?-O:0,ee=Oe?ze*n:0;we.push(we[m-1]+(Oe?0:ze));const $e=jt*n,He=l*S,We=Y*n,Qe=$e+He+We,Ft=kt(m),Xt=ve+pt+fe(m)+Me(m)+pe(m)+Ce(m)+Ee(m)-Qe-Ft-J-ee,ir=Xt-C(m);B.forEach(H=>{H.l.noPay&&(H.bal+=H.bal*H.r)});let Pe=Q?Math.max(0,-ir):0;const Oa=Pe;let oe=0;const $t={};if(Pe>.005){const H=Math.max(0,T.reduce((lr,cr)=>lr+cr.bal,0)-j),Ne=de(Math.min(Pe,H));oe+=Ne,Pe-=Ne,P.forEach(lr=>
11{if(Pe<=.005)return;const cr=Math.min(lr.bal,Pe);lr.bal-=cr,Pe-=cr,$t[lr.l.id]=($t[lr.l.id]||0)+cr});const Ba=de(Pe);oe+=Ba,Pe-=Ba}const aa=Math.max(0,Pe),qn=Oa-aa;U+=qn,_.push({mortInt:Re,mortPI:ve,principal:Math.max(0,ve-Re),mortBalStart:M,mortBalAfter:T.reduce((H,Ne)=>H+Ne.bal,0),dedMortInt:jt,taxBenMort:$e,facInt:Dt,facIntCash:pt,facAccr:V,synLoss:or,dedSyn:l,deferSyn:g,deferSynCum:ie,dedOther:Y,taxBenSyn:He,taxBenFac:We,taxBen:Qe,ptBen:Ft,allocExp:$,rentalTaxable:O,rentalTax:me,netRental:J,passiveLoss:ze,depBen:ee,buyOut:Xt,rentOut:C(m),contr:ir,overflow:aa,prepaid:qn,prepaidCum:U,prepayMort:oe,facBalEnd:B.reduce((H,Ne)=>H+Ne.bal,0),loanBals:[...T.map(H=>({id:H.l.id,name:H.l.name||H.l.type,bal:H.bal})),...B.map(H=>({id:H.l.id,name:H.l.type,bal:H.bal}))],facLoans:B.map((H,Ne)=>({id:H.l.id,type:H.l.type,noPay:!!H.l.noPay,rate:H.l.rate,balStart:Nt[Ne],int:Nt[Ne]*H.r,balEnd:H.bal,prepay:$t[H.l.id]||0}))})}const dt=[null],Vt=(vt+Je)*(hr/100);let nr=t*o+Je,he=t*o+Je-Vt,sr=t*(1-o);const jr=[{eqBal:nr,eqBasis:he,cashNet:sr}];let Nr=0,$n=0,_t=0;const $r=[0],Hn=[{invEq:0,invEqBasis:0,invCash:0}];for(let m=1;m<=rt;m++){const M=_[m],Re=nr,ve=sr;nr*=1+p,sr*=1+b*(1-n);const Bt=Q?Math.max(0,M.contr):0;nr+=Bt*o,he+=Bt*o,sr+=Bt*(1-o);const Sr=Nr,It=_t;Nr*=1+p,_t*=1+b*(1-n),M.overflow>0&&(Nr+=M.overflow*o,$n+=M.overflow*o,_t+=M.overflow*(1-o)),$r.push(Nr+_t),Hn.push({invEq:Nr,invEqBasis:$n,invCash:_t}),jr.push({eqBal:nr,eqBasis:he,cashNet:sr});const jt=Re*p,Nt=ve*b*(1-n),Dt=Sr*p+It*b*(1-n);dt.push({y:m,mortPI:M.mortPI,facInt:M.facInt,facIntCash:M.facIntCash,facAccr:M.facAccr,propTax:fe(m),maint:Me(m),capex:pe(m),ins:Ce(m),hoa:Ee(m),taxBen:M.taxBen,buyOut:M.buyOut,rent:D(m),rins:Ie(m),rentOut:M.rentOut,principal:M.principal,assessed:ae(m),market:je(m),mortInt:M.mortInt,mortBalStart:M.mortBalStart,dedIntBase:M.dedMortInt+M.dedSyn+M.dedOther,dedIntMort:M.dedMortInt,dedIntSyn:M.dedSyn,dedIntFac:M.dedOther,taxBenMort:M.taxBenMort,taxBenSyn:M.taxBenSyn,taxBenFac:M.taxBenFac,ptBen:M.ptBen,ptBenPre:wt(m),dedPT:se(m),newPropTax:je(m)*(Ht/100),grossRent:tt(m),vacLoss:tt(m)-Ut(m),collected:Ut(m),allocExp:M.allocExp,deprec:Ot,rentalTaxable:M.rentalTaxable,rentalTax:M.rentalTax,netRental:M.netRental,passiveLoss:M.passiveLoss,depBen:M.depBen,suspCum:we[m],eqGrowth:jt,cashGrowth:Nt,contr:M.contr,portGrowth:jt+Nt,ownGrowth:Dt,ownerSurplus:$r[m],facLoans:M.facLoans,prepaid:M.prepaid,prepaidCum:M.prepaidCum,deferSynCum:M.deferSynCum,overflow:M.overflow,prepayMort:M.prepayMort})}const Ra=m=>{const M=jr[m],Re=M.cashNet+M.eqBal,ve=Math.max(0,M.eqBal-M.eqBasis),Bt=s*ve,Sr=Re-Bt,It=G*Math.pow(1+r,m),jt=It*(qt/100),Nt=m===0?k:_[m].facBalEnd,Dt=m===0?G-re:_[m].mortBalAfter+Nt,pt=m===0?[...u.map(oe=>({id:oe.id,name:oe.name||oe.type,bal:oe.amount})),...f.map(oe=>({id:oe.id,name:oe.type,bal:oe.amount}))]:_[m].loanBals,or=$r[m]||0,l=Hn[m]||{invEq:0,invEqBasis:0,invCash:0},g=m===0?0:_[m].prepaidCum,Y=Math.max(0,l.invEq-l.invEqBasis),V=s*Y,$=It-Dt+or,O=It-jt-(G+qe);let me,J,ze=0,ee=0,$e=0,He=0,We=0;if(X){const oe=Ge/100;We=Ot*m,$e=We*(Ar/100);const $t=Math.max(0,O)*(1-oe),aa=Math.max(0,O)*oe;ze=s*Math.max(0,$t-ot),ee=s*aa,He=we[m]*n,J=Math.max(0,$t-ot)+aa,me=ze+ee}else J=Math.max(0,O-ot),me=s*J;const Qe=Je,Ft=m===0?0:_[m].deferSynCum,Xt=Ft*S,ir=me+V,Pe=Math.min(Xt,ir),Oa=$-jt-me-$e+He-V+Pe;return{rentPre:Re,rentAT:Sr,eqGain:ve,exitLTCG:Bt,eqBal:M.eqBal,eqBasis:M.eqBasis,buyPre:$,buyAT:Oa,mktE:It,sc:jt,payoff:Dt,facPayoff:Nt,facPrin:k,homeGain:O,cgTaxable:J,cgTax:me,Tup:Qe,ownerSurp:or,basis:G+qe,prepaidCum:g,ownerInv:l.invEq+l.invCash,ownInvLTCG:V,loanPayoffs:pt,ownerCG:ze,rentalCG:ee,recapTax:$e,suspRelease:He,cumDeprec:We,deferLoss:Ft,deferShelter:Pe}},za=[];let Pa=-1,La=-1;for(let m=1;m<=rt;m++){const M=Ra(m);za.push({yr:m,buyAT:M.buyAT,buyPre:M.buyPre,rentAT:M.rentAT,rentPre:M.rentPre}),Pa<0&&M.buyAT>=M.rentAT&&(Pa=m),La<0&&M.buyPre>=M.rentPre&&(La=m)}return{series:za,crossover:Pa,preLiqCross:La,years:dt,exitAt:Ra,Dn:t,synBenRate:S,hasSynDed:_[1].dedSyn>.5,hasSynDefer:_[1].deferSyn>.5}},[G,re,qe,Ve,ht,Mt,Je,vt,hr,X,At,Ur,Ge,Rt,Ar,Vr,Oe,Ht,bt,_e,Kt,Jt,br,ut,ur,vr,ha,_r,qt,ne,be,ot,Ke,er,Wt,te,Gt,Qt,mr,et,fr,Q]),N=I.exitAt(c),L=Math.max(1,c),ft=t=>2025+Math.max(1,t),h=I.years[L],Yt=I.Dn,Qo=t=>Yt>0?(t/Yt).toFixed(1)+"Ã today":"",Ds=re+te.reduce((t,r)=>t+r.amount,0),Fs=Ze!==0,rr=(()=>
11{if(!Fs)return null;const t=-Ze,r=te.filter(a=>a.id!==K&&(t<=0||a.amount>=t-.5)).sort((a,n)=>n.amount-a.amount)[0];return r?r.id:K!=="down"&&(t<=0||re>=t-.5)?"down":K==="price"?null:"price"})(),Ml=rr==null?0:rr==="price"?Ds:Math.max(0,G-(Ds-mt(rr))),El=()=>{const t=rr;if(!t)return;const r={};zt.forEach(a=>r[a]=mt(a)),ma(r,t),Ir(t,r[t]),fn(t),wn(t)},$s=rr&&e.jsxs("button",{type:"button",className:"bvr-fundfix",onClick:El,title:"Auto-adjust: set "+xr(rr)+" to "+i(Ml)+" to rebalance, and keep it auto-solved going forward",children:[e.jsx(Oc,{size:10})," auto-adjust"]}),[Al,Mn]=d.useState(!1),[Rl,Hs]=d.useState(!1),[En,zl]=d.useState(""),[ka,Pl]=d.useState(!1),[An,Ll]=d.useState(!1),[ei,Ol]=d.useState(!1),[Bl,Il]=d.useState(()=>{try{return parseInt(window.localStorage.getItem("bvr-xlsx-count-v1")||"0",10)||0}catch{return 0}}),[Dl,qs]=d.useState(!1),[Rn,Fl]=d.useState(""),[ti,zn]=d.useState(!1),[$l,Gs]=d.useState(!1),[Ws,Hl]=d.useState(()=>{try{return!!window.localStorage.getItem("bvr-xlsx-declined-v1")}catch{return!1}}),[ja,Pn]=d.useState(null),Ys=d.useRef([]),ql=t=>{const r=["var(--accent)","var(--buy)","var(--rent)","var(--credit)","var(--prin)"],a=Array.from({length:46},(n,s)=>{const o=s/46*Math.PI*2+Math.random()*.5,b=95+Math.random()*175;return{id:s,x:Math.round(Math.cos(o)*b),y:Math.round(Math.sin(o)*b+30),r:Math.round(Math.random()*720-360),s:Math.round((6+Math.random()*7)*10)/10,d:Math.round(Math.random()*90),dur:Math.round(900+Math.random()*550),color:r[s%r.length],round:Math.random()<.42}});Ys.current.forEach(clearTimeout),Pn({name:t&&t.name||"buyvsrent-model.xlsx",sheets:t&&t.sheets||6,bits:a}),Ys.current=[setTimeout(()=>Pn(n=>n&&{...n,closing:!0}),3200),setTimeout(()=>Pn(null),3640)]},Gl=()=>{Ys.current.forEach(clearTimeout),Pn(null)},Wl=()=>{const t={},r={};return[...new Set([1,5,L,30,38,rt])].filter(n=>n>=1&&n<=rt).sort((n,s)=>n-s).forEach(n=>{const s=I.years[n];s&&(t[n]={...s,facLoans:(s.facLoans||[]).map(o=>({...o}))}),r[n]=I.exitAt(n)}),r[0]=I.exitAt(0),JSON.parse(JSON.stringify({version:1,date:new Date().toISOString().slice(0,10),horizon:L,calYr0:2025,inputs:{price:G,downDollar:re,closingD:ca,closingPct:Yr,closingEff:qe,appr:Ve,sellPct:qt,rentMo:ht,rentInfl:Mt,rins:_r,cashRet:er,eqRet:Wt,mixEq:Ke,investSavings:Q,propTaxRate:Ht,assessCap:bt,assessCapOn:_e,maintR:Kt,capexR:Jt,insurance:br,insurancePct:ur,insuranceMode:ut,hoa:vr,hoaInfl:ha,ord:ne,ltcg:be,excl:ot,filingStatus:ba,taxRegion:Xe,ptDedMode:Gt,ptDedCap:Qt,ptDedPct:mr,amtOn:et,saltAgiBlocked:fr,multiFam:X,units:At,unitRentMo:Ur,invShare:Ge,bldgRatio:Rt,recapRate:Ar,vacancy:Vr,hasPassiveIncome:Oe,embGain:hr,cashPortion:Et,cashCash:zr,gainsFunded:vt,upTaxRate:Lo,upTax:Je,dn:I.Dn,synBenRate:I.synBenRate},loans:te.map(n=>({id:n.id,kind:n.kind,type:n.type||n.name,amount:n.amount,rate:n.rate,term:n.term,deductible:!!n.deductible,noPay:!!n.noPay,autoRoll:!!n.autoRoll})),balancer:K,eng:{crossover:I.crossover,preLiqCross:I.preLiqCross,years:t,exit:r}}))},Yl=!0,ri=4,ai="bvr-xlsx-count-v1",Ul="bvr-xlsx-declined-v1",ni="bvr-xlsx-subscribed-v1",si="bvr-xlsx-unlocked-v1",Vl="[email protected]",oi=async t=>{try{const r=await fetch("/api/bvr-unlock",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({password:t})});if(!r.ok)return{ok:!1};const a=await r.json();return a&&a.configured===!1?{ok:!0,off:!0}:{ok:!!(a&&a.ok),label:a&&a.label}}catch{return{ok:!0,off:!0}}},ii=t=>/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(String(t||"").trim()),_l=t=>{try{return!!window.localStorage.getItem(t)}catch{return!1}},li=t=>{try{window.localStorage.setItem(t,"1")}catch{}},Us=()=>{try{return parseInt(window.localStorage.getItem(ai)||"0",10)||0}catch{return 0}},Xl=()=>{try{window.localStorage.setItem(ai,String(Us()+1))}catch{}},Ln=()=>{gt("export_xlsx",L);const t=window.bvrXlsx.download(Wl());ql(t),Xl(),Il(Us())},Vs=()=>{Hs(!1)},Kl=()=>{li(Ul),Hl(!0),Hs(!1);try{Ln()}catch{}},ci=()=>{if(!(!ii(En)||!ka)){try{window.dispatchEvent(new CustomEvent("bvr-subscribe",{detail:{email:En.trim(),consent:!0,review:An}}))}catch{}li(ni);try{Ln()}catch{}Ol(!0)}},Jl=()=>{const t="Password request",r=`I'd like to request a password. 12 13`+window.location.href;try{window.location.href="mailto:"+Vl+"?subject="+encodeURIComponent(t)+"&body="+encodeURIComponent(r)}catch{}gt("export_xlsx","password_request")},di=async()=>
13{if((await oi(Rn)).ok){try{window.localStorage.setItem(si,Rn)}catch{}Gs(!0),qs(!1),zn(!1);try{Ln()}catch{}return}zn(!0)},pi=Bl>=ri&&!$l,hi=pi?"Password required":"Download .xlsx model",_s=async()=>{try{if(!window.bvrXlsx){gt("export_xlsx","promo"),Mn(!0);return}if(Us()>=ri){let t="";try{t=window.localStorage.getItem(si)||""}catch{}const r=await oi(t);if(!r.ok){Gs(!1),gt("export_xlsx","locked"),zn(!1),qs(!0);return}r.off||Gs(!0)}if(_l(ni)&&!Yl){Ln();return}gt("export_xlsx","gate"),Hs(!0)}catch{}};te.filter(t=>t.kind==="secured"&&!na.includes(t.type)&&t.amount>0);const bi=e.jsxs(e.Fragment,{children:["OBBBA raised the SALT cap to $40,400 for 2026 ($40,000 in 2025). It rises 1% a year through 2029, then drops back to $10,000 in 2030 (half of each for married filing separately). The cap phases down by 30% of modified AGI above $505,000 in 2026 (a threshold that also rises 1% a year), but never below $10,000, so from about $606,000 of MAGI high earners are back at the $10k cap (",e.jsx("a",{className:"bvr-popnotelink",href:Gc,target:"_blank",rel:"noopener noreferrer",onClick:t=>t.stopPropagation(),children:"26 U.S.C. §164(b)(7)"}),"). AMT taxpayers get no SALT benefit at all ","â"," the AMT switch under the tax-benefit line models that. State income tax shares the same cap: if income taxes alone exceed it, the marginal property-tax deduction is effectively $0 (set % deductible to 0 for that case)."]}
13),Zl=()=>({custom:e.jsxs("div",{className:"bvr-saltamt",onClick:t=>t.stopPropagation(),children:[e.jsxs("div",{className:"bvr-poptog",children:[e.jsx("span",{children:"AMT applies"}),ke(et,()=>is(t=>!t),"AMT applies â SALT deduction disallowed",et?"ON: SALT (incl. property tax) is disallowed under AMT â tax benefit is $0":"OFF: no AMT â property tax deductible up to the cap above")]}),et&&e.jsx("div",{className:"bvr-poptoghint",style:{color:"var(--neg)"},children:"SALT deduction disallowed â benefit is $0"})]})}),ui=t=>[...et?[{l:"Deductible (SALT)",v:i(h.dedPT),k:"faint strike"},{l:"Tax benefit â disallowed by AMT",v:i(0),k:t}]:fr?[{l:"Deductible (SALT)",v:i(h.dedPT),k:"faint strike"},{l:"Tax benefit â AGI too high",v:i(0),k:t}]:[{l:"Deductible (SALT)",v:i(h.dedPT)},{l:`Tax benefit (à ${R(ne/100)})`,v:i(h.ptBen),k:t}],Zl()],Fr=io(Te,pn),Ql=Fr.rate!=null&&Math.abs(Ht-Fr.rate)<1e-9,Qr=Te==="California"?"Assessed cap (Prop 13)":"Assessed cap",vi=()=>[Te==="California"?"California's Prop 13 (1978) caps annual growth of a property's ASSESSED value at 2%/yr while you continue to own it. Property tax rides this slow-growing assessed value â not the market value â so your annual bill drifts up much more slowly than the home appreciates.":`An assessed cap limits how fast a property's ASSESSED value may grow while you continue to own it â here ${bt}%/yr. Property tax rides that capped value, not the market value, so the annual bill drifts up more slowly than the home appreciates. California's Prop 13 (1978) is the best-known instance; ${Fr.cap!=null?`${Te} runs its own version, which is why the picker switched this cap ON at ${Fr.cap}%.`:`${Te} reassesses to market instead, which is why the picker switched this cap OFF.`}`,`The base assessment RESETS to the sale price at every change of hands. Buy today at ${i(G)}, your assessed base starts at ${i(G)} and grows at ${R(Math.min(bt,Ve)/100)}. Delay five years and buy at ${i(G*Math.pow(1+Ve/100,5))} instead, your base starts THERE â higher property tax forever. This "reset-on-sale" is the opportunity cost of waiting that the model's ${Te==="California"?"Prop 13":"assessed-cap"} line reveals.`,"Similar-in-spirit caps exist elsewhere: Michigan (5% or CPI), Oregon (3%), Florida's Save Our Homes (3% or CPI on primary), Texas (10%). Non-cap states â Illinois, Georgia, most of the country â have property tax that tracks market appreciation directly. Toggle the switch OFF to model those.","The effective growth rate is min(cap, appreciation): during flat or declining years the assessed value simply matches the market rather than trying to grow past it. Setting the cap % to your appreciation rate produces the same numbers as turning the cap off entirely."],Xs=({k:t,label:r,notes:a})=>e.jsxs("div",{className:"bvr-tipwrap","data-tip":t,children:[e.jsxs("button",{className:"bvr-tiprow"+(ds===t?" on":""),"aria-expanded":ds===t,onClick:n=>{n.stopPropagation(),Bo(s=>s===t?null:t)},children:[e.jsx(Hr,{size:12,className:"bvr-tiprowi"}),e.jsx("span",{className:"bvr-tiprowl",children:r}),e.jsx(St,{size:11,className:"bvr-tiprowc"})]}),ds===t&&e.jsx("div",{className:"bvr-poptip",onClick:n=>n.stopPropagation(),children:a.map((n,s)=>e.jsx("div",{className:"bvr-poptipn",children:n},s))})]}),ec=()=>{const t=r=>{r!=null&&an(r)};return e.jsxs("div",{className:"bvr-ptlkp",onClick:r=>r.stopPropagation(),children:[e.jsxs("div",{className:"bvr-ptlkprow",children:[e.jsx("select",{className:"bvr-sel",value:Te,"aria-label":"State",onChange:r=>{const a=r.target.value;dl(a);const n=a==="California"?oa.California.rows.some(([o])=>o===pn)?pn:"San Francisco":"";Ao(n);const s=io(a,n);sn(s.cap!=null),s.cap!=null&&nn(s.cap),t(s.rate)},children:po.map(([r,a])=>e.jsx("option",{value:r,children:Yc(r,a)},r))}),oa[Te]&&e.jsxs("select",{className:"bvr-sel",value:pn,"aria-label":"County",onChange:r=>{const a=r.target.value;Ao(a),t(io(Te,a).rate)},children:[Te!=="California"&&e.jsx("option",{value:"",children:`Statewide typical · ${R((po.find(([r])=>r===Te)||[])[1]/100)}`}),oa[Te].rows.slice().sort((r,a)=>r[0].localeCompare(a[0])).map(([r,a])=>e.jsx("option",{value:r,children:`${r} · ${R(a/100)}`},r))]})]}),e.jsx(Xs,{k:"lookup",label:"Where this rate comes from",notes:[(Te==="California"?"2026 est. total levy on assessed value (1% Prop 13 base + local add-ons) · SF is the official FY 2025â26 rate · assessed cap switched ON to match":Fr.cap!=null?`2026 est. new-buyer levy on market value at purchase · this state caps assessed growth after purchase (reset at sale), so the assessed cap was switched ON at ${Fr.cap}% to match`:"2026 est. new-buyer rate · this state reassesses to market, so a new buyer pays about the effective rate and the assessed cap was switched OFF to match")+(oa[Te]&&Te!=="California"?" · pick a county to refine":"")+" · or type a rate directly below","Lookup rates are 2026 planning estimates on a NEW-BUYER basis â the year-one tax on a purchase at today's market price. States that cap assessed growth (incl. California) carry the full levy a buyer faces, not the tenure-discounted average; the source link beside the rate opens where each figure comes from. California county figures are typical countywide levies (TRA rates, parcel taxes, and Mello-Roos vary within a county);
13 verify against an actual tax bill."]})]})},tc=()=>e.jsxs("div",{className:"bvr-capblock",children:[e.jsxs("div",{className:"bvr-poptog",onClick:t=>t.stopPropagation(),children:[e.jsx("span",{children:Qr}),e.jsxs("span",{className:"bvr-assesswrap",children:[ke(_e,()=>sn(t=>!t),Qr+" "+(_e?"enabled":"disabled"),_e?"Cap enabled â click to disable":"Cap disabled â click to enable"),e.jsx(Le,{v:bt,set:nn,w:42})]})]}),e.jsx(Xs,{k:"cap",label:Te==="California"?"How Prop 13 works here":"How the assessed cap works",notes:[_e?`Buying locks the assessed base at purchase price, growing ~${bt}%/yr. Waiting to buy resets the base to the higher market value â so this advantage is the yearly property tax the rent-then-buy path forgoes by delaying.`:"Assessed cap OFF: the base is reassessed to market every year, so buying now and buying later face the same tax on the same value â there is no assessed-cap advantage to model. That matches states that reassess to market; switch the cap ON to model a limit on assessed growth.",...vi()]})]}),mi=()=>{const t=et||fr;return e.jsxs("div",{className:"bvr-saltctl",onClick:r=>r.stopPropagation(),children:[e.jsx("div",{className:"bvr-salttitle",children:"SALT deduction"}),Zo?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"bvr-poptoghint bvr-saltagi",children:"Your AGI appears too high for SALT"}),e.jsxs("div",{className:"bvr-poptog",children:[e.jsx("span",{children:"override â allow SALT anyway"}),ke(hn,()=>zo(r=>!r),"Override AGI block â allow SALT",hn?"ON: SALT allowed despite the top-bracket AGI":"OFF: SALT disallowed at the top bracket")]})]}):e.jsx("div",{className:"bvr-poptoghint",children:et?"SALT deduction disallowed under AMT â benefit is $0":"property tax deductible through the SALT cap"}),e.jsx(Xs,{k:"salt",label:"How the SALT cap applies",notes:[bi]}),e.jsxs("div",{className:"bvr-saltbody"+(t?" amtdim":""),"aria-disabled":t||void 0,children:[e.jsxs("div",{className:"bvr-saltrow",children:[e.jsxs("span",{className:"bvr-seg",children:[e.jsx("button",{className:Gt==="usd"?"on":"",onClick:()=>ns("usd"),tabIndex:t?-1:void 0,children:"$ max"}),e.jsx("button",{className:Gt==="pct"?"on":"",onClick:()=>ns("pct"),tabIndex:t?-1:void 0,children:"% deductible"})]}),Gt==="usd"?e.jsx(Tt,{v:Qt,set:ss,w:86}):e.jsx(Le,{v:mr,set:os})]}),Gt==="pct"?e.jsx("input",{type:"range",className:"bvr-pprange",min:0,max:100,step:5,value:Math.max(0,Math.min(100,mr)),onChange:r=>os(+r.target.value),tabIndex:t?-1:void 0}):e.jsx("div",{className:"bvr-saltchips",children:[0,qc,Hc].map(r=>e.jsx("button",{className:"bvr-loanchip"+(Qt===r?" on":""),onClick:()=>ss(r),tabIndex:t?-1:void 0,children:Ui(r)},r))})]})]})},xi=t=>{const r=Math.min(Math.max(0,t.homeGain),ot);return[{l:`Sale net of ${qt}% selling costs`,v:i(t.mktE-t.sc),k:"det"},{l:"Cost basis · price + closing",v:i(-t.basis),k:"det"},{l:t.homeGain<0?"Capital loss":"Capital gain",v:i(t.homeGain),k:"detsub"},{l:`§121 exclusion · up to ${i(ot)}`,v:i(-r),k:"det"},{l:"Taxable gain",v:i(t.cgTaxable),k:"detsub"},{l:"LTCG rate",v:R(be/100),k:"det"}]},gi=t=>[{l:"Equity sleeve value",v:i(t.eqBal),k:"det"},{l:"Equity cost basis",v:i(-t.eqBasis),k:"det"},{l:"Unrealized gain",v:i(t.eqGain),k:"detsub"},{l:"LTCG rate",v:R(be/100),k:"det"}],rc=()=>{const t=[{l:"Sale price",v:i(N.mktE)},{l:"Loan payoff",v:i(-N.payoff),k:"neg"}];N.ownerInv>0&&t.push({l:"Surplus â invested",note:"after debts",v:i(N.ownerInv),k:"credit"}),t.push({l:"Pre-liquidation net worth",v:i(N.buyPre),k:"sub"}),t.push({l:`Prep/Liquidation costs @${qt}%`,v:i(-N.sc),k:"neg"}),X?(t.push({l:"Owner-share CG (§121 applied)",v:i(-N.ownerCG),k:N.ownerCG>0?"neg":"credit"}),t.push({l:`Rental-share CG (${R(Ge/100)})`,v:i(-N.rentalCG),k:"neg"}),t.push({l:"Depreciation recapture",v:i(-N.recapTax),k:"neg"}),N.suspRelease>0&&t.push({l:"Suspended losses release (§469)",v:i(N.suspRelease),k:"credit"})):(t.push(...xi(N)),N.cgTaxable>0?t.push({l:"Capital gains tax",v:i(-N.cgTax),k:"neg"}):t.push({l:"Capital gains tax",v:"$0",k:"credit"})),t.push({l:"After-tax net worth",v:i(N.buyAT),k:"tot"});const r=X?[`§121 shelters only the owner-occupied ${R(1-Ge/100)} of the gain; the rental ${R(Ge/100)}
13 is fully taxable at LTCG.`,`Recapture: ${i(N.cumDeprec)} of depreciation taken à ${R(Ar/100)}. Suspended §469 losses release in full at disposition (à ordinary rate).`]:[N.cgTaxable>0?`Capital-gains tax = ${R(be/100)} à (gain ${i(N.homeGain)} â ${i(ot)} §121 exclusion).`:`Gain of ${i(N.homeGain)} sits within the ${i(ot)} §121 exclusion, so no capital-gains tax.`];return Math.abs(N.facPayoff-N.facPrin)>.5&&r.push(`Loan payoff includes the synthetic loan at ${i(N.facPayoff)} â original ${i(N.facPrin)} plus interest capitalized over ${c} years (no cash payments along the way)${N.prepaidCum>.5?", less surplus prepayments":""}.`),N.Tup>0&&r.push("Stock sold to fund the down payment realized its gain at year 0. The renter's counterfactual keeps that stock instead â a higher pre-liq start whose deferred gain is taxed in the renter's exit figure."),(N.prepaidCum>.5||N.ownerSurp>0)&&r.push(`In years when renting costs more, the owner's surplus prepays debt in the payoff order (see Financing â Payoff ordering)${N.prepaidCum>.5?` â ${i(N.prepaidCum)} of principal retired so far, already reflected in the smaller loan payoff above`:""}${N.ownerInv>.5?`; ${i(N.ownerInv)} overflowed into investments once the debts were gone`:""}.`),{title:`After-tax net worth · sell at year ${c}`,rows:t,notes:r}},ac=()=>{const t=[{l:"Portfolio value",v:i(N.rentPre),k:"sub"},...gi(N),{l:"LT cap gain taxes at sale",v:i(-N.exitLTCG),k:"neg"},{l:"After-tax net worth",v:i(N.rentAT),k:"tot"}],r=[`Cash sleeve already taxed yearly. Equity LTCG = ${R(be/100)} à gain ${i(N.eqGain)}.`],a="Investment portfolio retained â not liquidated for down payment."+(Q?" With contributions of annual renterâs surplus.":"");return{title:`After-tax net worth · liquidate at year ${c}`,subtitle:a,rows:t,notes:r}},Ks=()=>({custom:e.jsxs("div",{className:"bvr-poptog",onClick:t=>t.stopPropagation(),children:[e.jsx("span",{children:"invest the savings?"}),ke(Q,()=>Xr(t=>!t),"Invest the savings "+(Q?"on":"off"))]})}),nc=(t,r)=>{switch(t){case"mortPI":return{title:`Mortgage P+I · year ${L}`,rows:[{l:"Interest",v:i(h.mortInt)},{l:"Principal (builds equity)",v:i(h.principal),k:"credit"},{l:"Total payment",v:i(h.mortPI),k:"tot"},{hr:!0},{head:"Interest â after-tax rate"},{custom:fi({interest:h.mortInt,benefit:h.taxBenMort,principal:h.mortBalStart,benefitDesc:`Deductible mortgage interest à ${R(ne/100)} ordinary marginal rate. Acquisition-debt interest is deductible only on the first $750k of balance (§163(h)), so the benefit is below the full rate once the loan exceeds that.`})}],notes:["Fixed annual payment on the amortizing mortgage(s). The principal portion converts to home equity, not cost."]};case"facInt":{const a=te.find(u=>u.kind==="secured"&&u.type==="Synthetic loan"&&u.amount>0),n=h.facLoans||[],s=n.reduce((u,f)=>u+f.balStart,0),o=n.reduce((u,f)=>u+f.balEnd,0),b=Math.max(0,s+h.facAccr-o),p=h.facAccr>.5;return{title:`Synthetic loan implied interest · year ${L}`,rows:[...n.map(u=>({l:u.type+(u.noPay?" (accrues)":""),v:i(u.int)})),{l:"Implied interest",v:i(h.facInt),k:"sub"},...a?[{custom:e.jsxs("div",{children:[e.jsxs("div",{className:"bvr-poptog",children:[e.jsx("span",{children:"No monthly payments"}),ke(!!a.noPay,()=>De(a.id,{noPay:!a.noPay}),"No monthly payments "+(a.noPay?"yes":"no"))]}),e.jsx("div",{className:"bvr-poptoghint",style:a.noPay?{color:"var(--credit)"}:void 0,children:a.noPay?"interest capitalizes into the balance":"interest paid in cash each year"}),e.jsxs("div",{className:"bvr-poptog",style:{marginTop:8},children:[e.jsx("span",{children:"Assume tax deductible?"}),ke(!!a.deductible,()=>De(a.id,{deductible:!a.deductible}),"Assume tax deductible "+(a.deductible?"yes":"no"))]}),e.jsx("div",{className:"bvr-poptoghint",style:a.deductible?{color:"var(--credit)"}:void 0,children:a.deductible?"offset taxable capital gains (40/60) each year":"accrued capital losses saved, used against capital gains at liquidation"})]})}]:[],...p||b>.5?[{l:"Paid in cash",v:i(h.facIntCash)},{l:"Capitalized into balance",v:i(h.facAccr),k:"credit"},{hr:!0},{l:`Balance · start of yr ${L}`,v:i(s)},...b>.5?[{l:"Prepaid from surplus",v:i(-b),k:"credit"}]:[],{l:`Balance · end of yr ${L}`,v:i(o),k:"tot"}]:[{l:"Paid in cash",v:i(h.facIntCash),k:"tot"}],{hr:!0},{head:"Implied interest â after-tax rate"},{custom:fi({interest:h.facInt,benefit:h.taxBenSyn+h.taxBenFac,principal:s,benefitDesc:h.taxBenSyn+h.taxBenFac>.5?`§1256 blended rate: 0.6 à ${R(be/100)} LT + 0.4 à ${R(ne/100)} ord = ${R(I.synBenRate)} on the deductible loss (not the full ordinary rate â assumes gains to absorb it).`:"Deductibility OFF: the §1256 loss defers to liquidation, so there's no current-year rate reduction â the gross and after-tax rates match this year."})}],notes:["Implied interest cost on the box-spread's short leg â the yield-to-maturity built into the spread's price. No cash coupon; the cost is recognized annually as §1256 mark-to-market loss (60% LT / 40% ST) whether or not any cash is paid.",p?"No payments: each year's implied interest is added to the amount owed, so the balance compounds at the loan's rate and next year's accrual is larger. Rolling at expiry just continues the accrual. The grown balance is repaid at sale/unwind â meanwhile the §1256 tax benefit lands every year. When owning is cheaper than renting, the owner's surplus prepays this balance (payoff order), shrinking future accrual and the exit payoff.":"Interest modeled as paid in cash each year; the original principal is repaid at unwind/expiry.",a&&!a.deductible?`Deductibility OFF: the annual §1256 loss isn't taken currently. Instead it ACCRUES (${i(h.deferSynCum||0)} through yr ${L}) and is carried to liquidation, where the accumulated loss offsets the buyer's capital gains at the
1360/40 rate â a deferred, not forfeited, benefit.`:a?"Deductibility ON: the §1256 loss offsets taxable capital gains (40/60) each year as it arises.":null]}}case"propTax":return{title:`Property tax · year ${L}`,rows:[{custom:ec()},{l:"Tax rate",src:Ql?Fr.src:null,editVal:{v:Ht,set:an}},{custom:tc()},{hr:!0},..._e?[{l:Te==="California"?"Assessed base (Prop 13)":"Assessed base (capped)",v:i(h.assessed)},{l:"Your property tax",v:i(h.propTax),k:"sub"},{l:`Market value in year ${L}`,v:i(h.market)},{l:"Tax if bought new now",v:i(h.newPropTax)},{l:Te==="California"?"Prop 13 advantage":"Assessed-cap advantage",v:i(h.newPropTax-h.propTax),k:"tot"}]:[{l:`Market value in year ${L}`,v:i(h.market)},{l:"Assessed base · tracks market",v:i(h.assessed)},{l:"Your property tax",v:i(h.propTax),k:"tot"}],{hr:!0},{custom:mi()},...ui("credit")],notes:[]};case"saltDed":return{title:"Property-tax deductibility",subtitle:"SALT cap on the owner-occupied share",rows:[{custom:mi()},{l:`Property tax · year ${L}`,v:i(h.propTax)},...ui("totcredit")],notes:[bi,X?"In a multifamily, only the owner-occupied share of property tax runs through SALT â the rental share is already deducted on Schedule E.":null].filter(Boolean)};case"maintcapex":return{title:`Maintenance & capex · year ${L}`,rows:[{l:"Home value",v:i(h.market)},{l:"Maintenance",rate:{v:Kt,set:on},v:i(h.maint)},{l:"Capex reserve",rate:{v:Jt,set:ln},v:i(h.capex)},{l:"Total",v:i(h.maint+h.capex),k:"tot"}],notes:["Both scale with the homeâs market value. Tap a rate to change it."]};case"maint":return{title:`Maintenance · year ${L}`,rows:[{l:"Market value",v:i(h.market)},{l:"Rate",rate:{v:Kt,set:on},v:i(h.maint),k:"tot"}],notes:["Scales with the homeâs replacement/market value."]};case"capex":return{title:`Capex reserve · year ${L}`,rows:[{l:"Market value",v:i(h.market)},{l:"Rate",rate:{v:Jt,set:ln},v:i(h.capex),k:"tot"}],notes:["Reserve for roof, systems, and major components."]};case"ins":return{title:`Insurance · year ${L}`,rows:[{l:"Homeownerâs (buy)",v:i(h.ins)},{l:"Renterâs (rent)",v:i(h.rins)}],notes:["Both inflated 3%/yr from the base premium."]};case"hoa":return{title:`HOA · year ${L}`,rows:[{l:"HOA",v:i(h.hoa),k:"tot"}],notes:["Monthly dues à 12, inflated 3%/yr."]};case"balSheet":{const a=c===0,n=I.exitAt(L-1),s=I.exitAt(a?0:L),o=s.loanPayoffs||[],b=n.loanPayoffs||[],p=Object.fromEntries(b.map(k=>[k.id,k.bal])),u="Memo item â informational, NOT added into net worth. Unrealized gains are already inside the asset values; §469 suspensions and depreciation only affect the exit tax math.",f=(k,j,T,B={})=>{const P=T-j,de=T===j;return e.jsxs("div",{className:"bvr-bsrow"+(B.k?" "+B.k:""),title:de?i(T):`start ${i(j)} â end ${i(T)}`,children:[e.jsxs("span",{className:"bvr-bsl",children:[k,B.note&&e.jsx("em",{className:"bvr-tag",title:B.noteTitle,children:B.note})]}),e.jsx("span",{className:"bvr-bsv num"+(T<0?" neg":""),children:de?e.jsx("span",{className:"bvr-bsnowrap",children:i(T)}):jn==="range"?e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"bvr-bsnowrap",children:i(j)}),e.jsx("span",{className:"bvr-bsarr","aria-hidden":"true",children:"â"}),e.jsx("span",{className:"bvr-bsnowrap",children:i(T)})]}):e.jsxs(e.Fragment,{children:[e.jsxs("em",{className:"bvr-bsd "+(P>=-.5?"up":"dn"),children:[P>=-.5?"+":"â",i(Math.abs(P)).replace("$-","$")]}),e.jsx("span",{className:"bvr-bsnowrap",children:i(T)})]})})]},B.rk||k)},S=o.filter(k=>k.bal>.5||(p[k.id]||0)>.5).map(k=>f(k.name,-(p[k.id]||0),-k.bal,{rk:"bsloan-"+k.id}));return{title:a?"Balance sheet · at purchase":`Balance sheet · year ${L}`,rows:[{custom:e.jsxs("div",{className:"bvr-bs",children:[e.jsxs("div",{className:"bvr-bshead",children:[e.jsx("span",{className:"bvr-bssec",children:"BUYER"}),!a&&e.jsx("button",{className:"bvr-bsmode",onClick:()=>kl(k=>k==="delta"?"range":"delta"),children:jn==="delta"?"show start â end":"show Î vs start"})]}),f("Home value",n.mktE,s.mktE),S,s.prepaidCum>.5&&f("Debt prepaid from surplus",n.prepaidCum,s.prepaidCum,{k:"memo",note:"memo",rk:"prepaid-cum",noteTitle:"Cumulative principal retired by the ownerâs surplus (payoff order) â already inside the falling loan balances above, so it is NOT added again."}),(s.ownerSurp>.5||n.ownerSurp>.5)&&f("Surplus investments",n.ownerSurp,s.ownerSurp,{note:"after debts",rk:"surp-inv",noteTitle:"Owner surplus left over AFTER every loan balance was prepaid to $0 â invested at your reinvestment mix, taxed like the renterâs portfolio at exit."}),(s.ownerSurp>.5||s.prepaidCum>.5||h.contr<-.5)&&e.jsxs("div",{className:"bvr-poptog bvr-bstog",onClick:k=>k.stopPropagation(),children:[e.jsx("span",{children:"invest the savings?"}),ke(Q,()=>Xr(k=>!k),"Invest the savings "+(Q?"on":"off"))]}),e.jsx("div",{className:"bvr-bshr"}),f("Net worth · pre-liq",n.buyPre,s.buyPre,{k:"tot",rk:"nw-buy"}),f("Unrealized gain (if sold)",n.homeGain,s.homeGain,{k:"memo",note:"memo",rk:"ug-buy",noteTitle:u}),X&&f("Suspended losses (§469)",L>1?I.years[L-1].suspCum:0,a?0:I.years[L].suspCum,{k:"memo",note:"memo",noteTitle:u}),X&&f("Cumulative depreciation",n.cumDeprec,s.cumDeprec,{k:"memo",note:"memo",noteTitle:u}),e.jsx("div",{className:"bvr-bshead",style:{marginTop:10},children:e.jsx("span",{className:"bvr-bssec",children:"RENTER"})}),f("Portfolio",n.rentPre,s.rentPre,{rk:"portfolio"}),e.jsx("div",{className:"bvr-bshr"}),f("Net worth · pre-liq",n.rentPre,s.rentPre,{k:"tot",rk:"nw-rent"}),f("Unrealized gains (deferred)",n.eqGain,s.eqGain,{k:"memo",note:"memo",rk:"ug-rent",n
13oteTitle:u}),e.jsxs("button",{className:"bvr-bsnotes-t",onClick:()=>_o(k=>!k),"aria-expanded":Nn,children:["About these numbers ",Nn?"â¾":"â¸"]}),Nn&&e.jsxs("div",{className:"bvr-bsnotes",children:[e.jsx("p",{children:"Start of year = end of the prior year (year 1 starts from the real at-purchase state: buyer at down-payment equity, renter holding the unsold stock). Levels are PRE-liquidation \\u2014 selling costs and gains taxes land only on an actual sale."}),e.jsxs("p",{children:["Buyer rows SUM: home ","â"," loan balances + surplus investments = net worth. Loan balances are REAL: in years when owning costs less than renting, the owner's surplus prepays them in the payoff order (",i(s.prepaidCum)," of principal retired so far), which also shrinks future interest and its tax deduction",s.ownerSurp>.5?`; once every balance hit $0, the surplus overflowed into investments (${i(s.ownerSurp)})`:"","."]}),e.jsx("p",{children:"MEMO rows are informational \\u2014 not added into net worth: unrealized gains are already inside the asset values; \\u00a7469 suspensions and depreciation affect only the exit tax math."})]})]})}],notes:[]}}case"rent":return{title:`Rent · year ${L}`,rows:[{l:"Base annual rent",v:i(ht*12)},{l:"Rent inflation",editVal:{v:Mt,set:en}},{l:`Rent inflated to year ${L}`,v:i(h.rent),k:"sub"},{l:"Renterâs insurance",v:i(h.rins)},{l:"Total rent outflow",v:i(h.rentOut),k:"tot"}],notes:[`Rent grows ${Mt}%/yr; renterâs insurance is a base ${i(_r*12)}/yr and inflates with rent.`]};case"rental":{const a=h.rentalTaxable,n=[{l:"Scheduled rent",v:i(h.grossRent)},{l:"Vacancy & mgmt",rate:{v:Vr,set:ts},v:i(-h.vacLoss),k:"neg"},{l:"Collected rent",v:i(h.collected),k:"sub"},{l:"Allocated expenses",rate:{v:Ge,set:rn},v:i(-h.allocExp),k:"neg"},{l:"Depreciation",v:i(-h.deprec),k:"neg"},{l:"Taxable rental income",v:i(a),k:"sub"},{hr:!0},...a<=0?[{custom:e.jsxs("div",{className:"bvr-poptog",children:[e.jsx("span",{children:"REPs or other passive income?"}),ke(Oe,()=>tn(s=>!s),"REPs or other passive income "+(Oe?"yes":"no"))]})}]:[],a>0?{l:"Rental tax (ordinary)",v:i(-h.rentalTax),k:"neg"}:Oe?{l:"Loss offsets passive income (§469)",v:i(-a),k:"credit"}:{l:"Loss suspended (§469)",v:i(-a),k:"credit"},{l:"Net rental cash after tax",v:i(h.netRental),k:"tot"}];return{title:`Rental unit · year ${L}`,rows:n,notes:[`Allocated expenses = ${R(Ge/100)} of property tax, maintenance & capex, insurance, HOA, and mortgage interest â tap the pill to refine the investment share.`,`Depreciation = price à ${R(Rt/100)} improvements à ${R(Ge/100)} ÷ 27.5 yrs. Losses are passive and suspended at your income (§469); ${i(h.suspCum)} accumulated releases at sale.`]}}case"dep":{const a=h.passiveLoss||0,n=a*(ne/100);return{title:`Depreciation deduction · year ${L}`,rows:[{l:"Depreciation",v:i(h.deprec)},{l:"Passive loss this year",v:i(a),k:"sub"},{custom:e.jsxs("div",{children:[e.jsxs("div",{className:"bvr-poptog",children:[e.jsx("span",{children:"REPs or other passive income?"}),ke(Oe,()=>tn(s=>!s),"REPs or other passive income "+(Oe?"yes":"no"))]}),e.jsx("div",{className:"bvr-poptoghint",style:Oe?{color:"var(--credit)"}:void 0,children:Oe?"100% of passive losses used annually":"suspended · §469"})]})},{hr:!0},Oe?{l:`Annual benefit (${R(ne/100)})`,v:i(h.depBen),k:"credit"}:{l:"Suspended · would be worth",v:i(n),k:"sub"}],notes:[`The rental's paper loss â driven mostly by depreciation (${i(h.deprec)}/yr) â is PASSIVE under §469. Its value depends on whether you qualify as a Real Estate Professional (REP) or have other passive income to absorb it.`,Oe?`Switch ON: the loss offsets your other passive income (other rentals, passive K-1 interests) each year â or, for REPs, any income at all. Benefit = loss à ${R(ne/100)} ordinary rate, credited against the buy path's annual cash flow. Nothing accumulates for release at sale â the loss is used as it arises.`:`Switch OFF (default): no REP status or other passive income, so the loss SUSPENDS â no current-year benefit. Suspended losses accumulate (${i(h.suspCum)} through year ${L}) and release in full at disposition, credited at your ordinary rate in the sale-year math. The washed-out segment shows the benefit you'd capture annually if you had passive income to absorb it.`,"High earners rarely qualify for the active-participation $25k allowance (phases out above $150k AGI) or REP status â the model assumes neither when OFF."]}}case"taxBen":return{title:`Tax benefit · year ${L}`,rows:I.hasSynDed?[{l:"Mortgage interest (deductible)",v:i(h.dedIntMort+h.dedIntFac)},{l:"Ordinary rate",editVal:{v:ne,set:Zt}}
13,{l:"Mortgage tax benefit",v:i(h.taxBenMort+h.taxBenFac),k:"subcredit"},{hr:!0},{l:"Synthetic loan interest",v:i(h.dedIntSyn)},{l:"§1256 rate · 60/40",v:R(I.synBenRate)},{l:"Synth. loan tax benefit",v:i(h.taxBenSyn),k:"subcredit"},{hr:!0},{l:"Total tax benefit",v:i(h.taxBen),k:"totcredit"}]:[{l:"Deductible interest",v:i(h.dedIntBase)},{l:"Ordinary rate",editVal:{v:ne,set:Zt}},{l:"Tax benefit",v:i(h.taxBen),k:"totcredit"}],notes:[I.hasSynDed?`A box spread's financing cost is realized as §1256 loss on the short box â 60% long-term / 40% short-term â so its deduction is worth 0.6 à ${R(be/100)} + 0.4 à ${R(ne/100)} = ${R(I.synBenRate)}, not the full ordinary rate (assumes gains to absorb the loss â open for CPA review).`:null,X?`Only the owner-occupied ${R(1-Ge/100)} of mortgage interest is deducted personally, and the $750k acquisition-debt cap applies to that owner-occupied share of the balance; the rental ${R(Ge/100)} is deducted against rental income on Schedule E (no §163(h) cap) â no double counting.`:"Mortgage interest deductible on up to $750k of acquisition debt, plus deductible securities-backed loan interest, at your ordinary rate."].filter(Boolean)};case"liq":{const a=c===0,n=I.exitAt(a?0:L),s=n.cgTax+n.recapTax+n.ownInvLTCG-n.suspRelease-n.deferShelter;return{title:a?"Liquidation · at purchase":`Liquidation · end of yr ${L}`,rows:[{head:"Buy â sell the home"},{l:"Market value",v:i(n.mktE)},{l:"Loan payoffs",v:i(-n.payoff),k:"neg"},...n.ownerSurp>.5?[{l:"Surplus investments",v:i(n.ownerSurp)}]:[],{l:"Net worth · pre-liq",v:i(n.buyPre),k:"sub"},{l:`Selling costs (${qt}% of value)`,v:i(-n.sc),k:"neg"},...X?[{l:"Gain if sold · net of costs & basis",v:i(n.homeGain)},{l:`Taxable gain · after ${i(ot)} §121`,v:i(n.cgTaxable)}]:xi(n),{l:`Capital-gains tax (${R(be/100)})`,v:i(-n.cgTax),k:"neg"},...X?[{l:`Depreciation recapture (${Ar}%)`,v:i(-n.recapTax),k:"neg"}]:[],...n.suspRelease>.5?[{l:"§469 suspended losses release",v:i(n.suspRelease),k:"credit"}]:[],...n.ownInvLTCG>.5?[{l:"LTCG on surplus investments",v:i(-n.ownInvLTCG),k:"neg"}]:[],...n.deferShelter>.5?[{l:"Deferred §1256 loss shelter",v:i(n.deferShelter),k:"credit"}]:[],{l:"Net worth · post-liq",v:i(n.buyAT),k:"tot"},{hr:!0},{head:"Rent â sell the portfolio"},{l:"Portfolio value · pre-liq",v:i(n.rentPre),k:"sub"},...gi(n),{l:`LTCG tax (${R(be/100)})`,v:i(-n.exitLTCG),k:"neg"},{l:"Net worth · post-liq",v:i(n.rentAT),k:"tot"}],notes:["Every figure is âif you sold at this pointâ â nothing is owed until an actual sale. The pre-liq rows are paper wealth; this popover is the bridge from paper to cash.",`The buyerâs exit taxes net to ${i(s)}: capital gains${X?" + depreciation recapture":""}${n.ownInvLTCG>.5?" + LTCG on invested surplus":""}${n.suspRelease>.5?" â released §469 losses":""}${n.deferShelter>.5?" â the deferred §1256 loss shelter":""} â matching the spineâs Taxes-due row exactly.`]}}case"buyOut":return{title:`Housing outflow · year ${L}`,rows:[{l:"Mortgage P+I",v:i(h.mortPI)},...h.facInt>0?[{l:"Synth. loan impl. interest",v:i(h.facInt)}]:[],...h.facAccr>.5?[{l:"Implied financing accrues",v:i(-h.facAccr),k:"credit"}]:[],{l:"Property tax",v:i(h.propTax)},{l:"Maintenance & capex",v:i(h.maint+h.capex)},{l:"Insurance",v:i(h.ins)},...h.hoa>0?[{l:"HOA",v:i(h.hoa)}]:[],...I.hasSynDed?[{l:"Mortgage tax benefit",v:i(-(h.taxBenMort+h.taxBenFac)),k:"credit"},{l:"Synth. loan tax benefit (§1256)",v:i(-h.taxBenSyn),k:"credit"}]:[{l:"Tax benefit â interest",v:i(-h.taxBen),k:"credit"}],...h.ptBen>0?[{l:"Tax benefit â prop tax (SALT)",v:i(-h.ptBen),k:"credit"}]:[],...h.depBen>0?[{l:"Tax benefit â depreciation (§469)",v:i(-h.depBen),k:"credit"}]:[],...X?[{l:"Rental income (net)",v:i(-h.netRental),k:"credit"}]:[],{l:"Gross outflow",v:i(h.mortPI+h.facInt+h.propTax+h.maint+h.capex+h.ins+h.hoa),k:"sub"},{l:h.facAccr>.5?"Tax benefits and capitalized interest":"Tax benefits",v:i(-(h.facAccr+h.taxBen+h.ptBen+h.depBen+h.netRental)),k:"credit"},{l:"Buyerâs net after-tax outflow",v:i(h.buyOut),k:"tot"},{hr:!0},{l:"Rent",v:i(h.rent)},{l:"Renters insurance",v:i(h.rins)},{l:"Rent outflow",v:i(h.rentOut),k:"tot"}],notes:[h.contr>=0?"Buying costs more this year; the renter invests the surplus into the portfolio at your mix.":`Renting costs more this year; the owner's ${i(-h.contr)} surplus prepays debt in the payoff order â actually paying balances down â then overflows into investing once all debt is retired.`]};case"surplus":{const a=h.contr<0,n=h.contr>0,s=Math.abs(h.contr),o=a?"Homeowner":n?"Renter":"No",b=s<.5?"â":Q?a?"prepays debt, then invests":"invested at your mix":"consumed (not saved)";let p=null;for(let f=2;f<=rt;f++){const S=I.years[f-1].contr,k=I.years[f].contr;if(S!==0&&S>0!=k>0){p=f;break}}const u=r==="prin";return{title:`Wealth building · year ${L}`,rows:[{hero:{who:o,amt:s<.5?"â":i(s),caption:s<.5?"the two paths cost the same this year":(a?"buying is cheaper â ":"renting is cheaper â ")+b,dir:u?"mute":s<.5?"flat":a?"buy":"rent"}},{head:"Buy â equity built this year"},{l:"Mortgage principal â equity",v:i(h.principal),k:"credit",hl:u},...a?[{l:Q?"Net surplus â debt paydown, then invest":"Net surplus (consumed, not saved)",v:i(s),k:Q?"credit":"",hl:!u,hlTone:"credit"}]:[],...a?[Ks()]:[],{hr:!0},{head:"Rent â portfolio built this year"},{l:n?Q?"Net surplus â portfolio":"Net surplus (consumed, not saved)":"Net surplus â portfolio",v:n?i(s):"$0",k:n&&Q?"credit":"",hl:n&&!u,hlTone:"rent"},...n?[Ks()]:[],...s<.5?[Ks()]:[]],notes:["Principal inside the mortgage payment is cash out the door that converts to home equity â a forced saving, not a cost. The net surplus is the extra cash the cheaper path frees up each year.",s<.5?"The two paths cost about the same this year, so neither side has a surplus to deploy.":n?Q?`Renting is cheaper by ${i(s)}; the renter invests that surplus at your reinvestment mix (${R(Ke/100)} equities / ${R(1-Ke/100)} cash).`:`Renting is cheaper by ${i(s)} â but âinvest the savings?â is OFF, so the surplus is lifestyle spend, not added to the portfolio.`:Q?`Buying is cheaper by ${i(s)}; the owner's surplus prepays debt in the payoff order on top of scheduled principal â then invests once all debt is retired.`:`Buying is cheaper by ${i(s)} â but âinvest the savings?â is OFF, so the surplus is lifestyle spend rather than prepaying debt.`,...p?[`Rent inflates at ${Mt}
13%/yr against a mostly fixed mortgage payment â the surplus shrinks and flips to the ownerâs side around year ${p}, which is why this popover changes sides as you scrub.`]:[]]}}case"appr":return{title:`Appreciation & growth · year ${L}`,rows:[{l:"Market value (yr tâ1)",v:i(h.market)},{l:"Appreciation rate",editVal:{v:Ve,set:Qa}},{l:"Buy â home appreciation",v:i(h.market*Ve/100),k:"subcredit"},{hr:!0},{l:`Equity sleeve (${R(Wt/100)})`,v:i(h.eqGrowth)},{l:"Cash sleeve (after-tax)",v:i(h.cashGrowth)},{l:"Rent â portfolio growth",v:i(h.portGrowth),k:"tot"}],notes:[`Leverage: the home appreciates on its full ${i(h.market)} value from a fractional down payment, while the portfolio grows only on invested capital â the core reason the Buy line's slope is steeper.`]};default:return{title:"",rows:[],notes:[]}}},sc=()=>{const t=h.market*Ve/100,r=t+h.principal+(h.contr<0?-h.contr:0),a=h.portGrowth+(h.contr>0?h.contr:0),n=r-a,s=qe+N.sc+N.cgTax,o=N.exitLTCG,b=s-o,p=[{head:`Annual wealth build · yr ${L}`},{l:"Buy builds this year",v:i(r),k:"credit"},{l:"Rent builds this year",v:i(a)},{l:"Buy's annual edge",v:i(n),k:"tot"},{head:`Liquidation costs · yr ${L}`},...qe>0?[{l:"Buy â closing costs (sunk)",v:i(-qe),k:"neg"}]:[],{l:"Buy â selling costs",v:i(-N.sc),k:"neg"},...N.cgTax>0?[{l:"Buy â capital-gains tax",v:i(-N.cgTax),k:"neg"}]:[],{l:"Total buy exit costs",v:i(-s),k:"sub"},{l:"Rent â LTCG on portfolio",v:i(-o),k:"neg"},{l:"Renting's fixed head start",v:i(b),k:"tot"}],u=[`Key driver: leverage. The home appreciates ${R(Ve/100)} on its full ${i(h.market)} value (${i(t)}) from a fractional down payment; the renter's capital compounds on a smaller base. That steeper slope is what overtakes renting's fixed head start.`,n>=0?`By year ${L} buying already builds ${i(n)} more wealth per year, so the gap closesâbreak-even at year ${I.crossover>0?I.crossover:">30"}.`:`At year ${L} renting still builds ${i(-n)} more per year; buying's slope advantage hasn't caught up yet.`];return{title:"What drives buy vs rent",rows:p,notes:u}},ea=({k:t=null,value:r,set:a,min:n=0,max:s,steps:o})=>{const b=!!t&&K===t,p=r>=o.at?o.coarse:o.fine,u=p>=1e3?"$"+Math.round(p/1e3)+"k steps":"$"+p+" steps";return e.jsxs("div",{className:"bvr-ppslider",onClick:f=>f.stopPropagation(),onMouseDown:f=>f.stopPropagation(),children:[e.jsx("input",{type:"range",className:"bvr-pprange",min:n,max:s,step:p,disabled:b,"aria-disabled":b,value:Math.min(s,Math.max(n,r)),onChange:f=>{const S=+f.target.value;a(s-S<p&&S>0?s:S)}}),e.jsxs("div",{className:"bvr-pprangelab",children:[e.jsx("span",{children:n===0?"$0":xe(n)}),e.jsx("span",{className:"num",children:b?"auto-solved · claim via the auto pill":u}),e.jsx("span",{children:xe(s)})]})]})},oc=t=>{switch(t){case"bldg":return{title:"Building vs land split",rows:[{l:"Purchase price",v:i(G)},{l:`Building (${R(Rt/100)})`,v:i(G*Rt/100),k:"credit"},{l:`Land (${R(1-Rt/100)})`,v:i(G*(1-Rt/100))},{hr:!0},{l:"Annual depreciation",v:i(G*Rt/100*(Ge/100)/27.5),k:"tot"}],notes:["Only the building (improvements) can be depreciated â land cannot. The split usually comes from the county assessorâs allocation or an appraisal. Depreciation = building à investment share ÷ 27.5 years (residential straight-line)."]};case"funding":return{title:"Purchase funding · auto-solve",rows:[{custom:e.jsxs("div",{className:"bvr-ppq",children:[e.jsxs("div",{className:"bvr-ppqrow",children:[e.jsx("span",{className:"bvr-ppqlab",children:"Purchase price"}),e.jsx("span",{onClick:r=>r.stopPropagation(),children:e.jsx("input",{ref:Ct("pop-price"),className:"bvr-in num",style:{width:120},defaultValue:"$"+Math.round(G).toLocaleString(),onFocus:nt,onKeyDown:Mr("pop-price"),onBlur:r=>{const a=parseFloat(r.target.value.replace(/[^0-9.]/g,""));isNaN(a)||Be("price",a),r.target.value="$"+Math.round(isNaN(a)?G:a).toLocaleString()}},"pp"+G)})]}),ea({k:"price",value:G,set:r=>Be("price",r),min:25e4,max:1e7,steps:{fine:25e3,coarse:5e4,at:15e5}})]})},{custom:e.jsxs("div",{children:[e.jsxs("div",{className:"cctog"+(qe===0?" ccwarn":""),onClick:()=>Za(r=>!r),children:["+",i(qe)," up front purchasing costs ",e.jsx(Gn,{size:10})]}),e.jsx("div",{className:"bvr-f2expand ccexpand collapse-motion"+(cl?" open":""),children:e.jsxs("div",{className:"bvr-f2expandinner",children:[e.jsxs("div",{className:"ccrow",children:[e.jsx("span",{className:"cclab",children:"Additional up front costs to purchase:"}),e.jsxs("button",{className:"ccx ccxmorph",onClick:()=>{Qn(0),es(0),Za(!1)},"aria-label":"Remove these up-front costs (set to $0)",children:[e.jsx(Cr,{size:10,
13className:"ccxicon"}),e.jsx("span",{className:"ccxtext",children:"remove these"})]})]}),e.jsxs("div",{className:"ccrow",children:[e.jsx("span",{className:"cclab",children:"Fixed $"}),e.jsx("span",{className:"ccr",children:e.jsx("input",{ref:Ct("pop-ccd"),className:"ccin num",defaultValue:"$"+Math.round(ca).toLocaleString(),onClick:r=>r.stopPropagation(),onMouseDown:r=>r.stopPropagation(),onFocus:nt,onKeyDown:r=>{r.stopPropagation(),Mr("pop-ccd")(r)},onBlur:r=>{const a=parseFloat(r.target.value.replace(/[^0-9.]/g,""));isNaN(a)||Qn(a)}},"ccd"+ca)})]}),e.jsxs("div",{className:"ccrow",children:[e.jsx("span",{className:"cclab",children:"+ % of price"}),e.jsxs("span",{className:"ccr",children:[e.jsx("input",{ref:Ct("pop-ccp"),className:"ccin ccinp num",defaultValue:Yr+"%",onClick:r=>r.stopPropagation(),onMouseDown:r=>r.stopPropagation(),onFocus:nt,onKeyDown:r=>{r.stopPropagation(),Mr("pop-ccp")(r)},onBlur:r=>{const a=parseFloat(r.target.value.replace("%",""));isNaN(a)||es(a)}},"ccp"+Yr),e.jsx("span",{className:"cchint num",children:i(G*(Yr/100))})]})]}),e.jsxs("div",{className:"ccrow",children:[e.jsx("span",{className:"cclab",children:"All-in"}),e.jsx("b",{className:"num",children:i(G+qe)})]})]})})]})},{custom:e.jsxs("div",{className:"bvr-poplink",onClick:()=>ws("down"),children:[e.jsxs("span",{children:["Down payment",K==="down"&&e.jsx("em",{className:"bvr-autotag",children:"auto"})]}),e.jsxs("span",{className:"bvr-poplinkv",children:[e.jsx("b",{className:"num",children:i(re)}),e.jsx(Fa,{size:12})]})]})},...te.map(r=>({custom:e.jsxs("div",{className:"bvr-poplink",onClick:()=>ws(r.id),children:[e.jsxs("span",{children:[r.kind==="secured"?K===r.id&&r.type==="Synthetic loan"?"Synth. loan":r.type:r.name,K===r.id&&e.jsx("em",{className:"bvr-autotag",children:"auto"})]}),e.jsxs("span",{className:"bvr-poplinkv",children:[e.jsx("b",{className:"num",children:i(r.amount)}),e.jsx(Fa,{size:12})]})]})})),{l:"Total funding",v:i(re+te.reduce((r,a)=>r+a.amount,0)),k:Ze===0?"tot":"sub"},...Ze!==0?[{l:Ze>0?"Under-funded":"Over-funded",v:i(Math.abs(Ze)),k:"neg"}]:[]],notes:["Set the purchase price by typing or dragging the slider ($25k steps below $1.5M, $50k above). Price must equal down payment plus all loans â exactly one field (tagged AUTO) is solved automatically to keep the identity balanced.","Tap Down payment or any loan to jump to that field in the Financing section â it opens focused with its own details.","Up-front purchasing costs are extra cash today â a fixed $ PLUS a % of price, both applied â and add to your cost basis at sale. Use the remove control to set them to $0 if your price already reflects them (thatâs the only action that changes the stored values; expanding or collapsing the editor never does)."]};case"payoffOrder":{const r=te.filter(s=>s.kind==="mortgage"||na.includes(s.type)).reduce((s,o)=>s+(o.amount||0),0),a=te.filter(s=>s.kind==="secured"&&!na.includes(s.type)).slice().reverse();let n=1;return{title:"Surplus payoff ordering",rows:[{l:`${n++}. Mortgage debt above $750k cap`,note:"non-deductible slice",v:r>75e4?i(r-75e4):"â",k:r>75e4?"":"sub"},...a.map((s,o)=>({l:`${n++}. `+s.type.split(" /")[0],note:o===0?"bottom of list":null,v:i(s.amount||0)})),{l:`${n++}. Mortgage remainder`,note:"deductible slice",v:i(Math.min(75e4,r))},{l:`${n++}. Invest like the renter`,note:"after all debt",v:"",k:"sub"}],notes:["When âinvest the gap surplus?â is ON and the buy path is cheaper, the owner's surplus prepays debt in this order â real principal reductions, so the balances actually fall, later interest shrinks, and so does its tax deduction. The $750k slice goes first because its interest was never deductible. Within each mortgage tranche, the junior (last-listed) loan retires first. Once every balance is retired, further surplus is invested at your reinvestment mix, taxed like the renter's portfolio. Custom re-ordering is on the roadmap."]}}case"downfund":return{title:"Down payment · funding",rows:[{custom:e.jsxs("div",{className:"bvr-poplink",onClick:()=>ws("price"),children:[e.jsxs("span",{children:["Purchase price",K==="price"&&e.jsx("em",{className:"bvr-autotag",children:"auto"})]}),e.jsxs("span",{className:"bvr-poplinkv",children:[e.jsx("b",{className:"num",children:i(G)}),e.jsx(Fa,{size:12})]})]})},{custom:e.jsxs("div",{className:"bvr-poplink",onClick:r=>r.stopPropagation(),children:[e.jsxs("span",{children:["Down payment",K==="down"&&e.jsx("em",{className:"bvr-autotag",children:"auto"})]}),e.jsx("span",{className:"bvr-poplinkv",onClick:r=>r.stopPropagation(),children:e.jsx("input",{ref:Ct("pop-down"),className:"bvr-in num",style:{width:128,textAlign:"right"}
13,defaultValue:"$"+Math.round(re).toLocaleString(),onFocus:nt,onKeyDown:r=>{r.stopPropagation(),Mr("pop-down")(r)},onBlur:r=>{const a=parseFloat(r.target.value.replace(/[^0-9.]/g,""));isNaN(a)||js(Math.max(0,a)),r.target.value="$"+Math.round(isNaN(a)?re:Math.max(0,a)).toLocaleString()}},"popdown"+Math.round(re))})]})},{custom:ea({k:"down",value:re,set:r=>js(r),min:0,max:Math.round(G),steps:{fine:25e3,coarse:5e4,at:15e5}})},...xo?[{custom:e.jsxs("div",{className:"bvr-fund2",children:[e.jsxs("div",{className:"ccrow bvr-f2head",children:[e.jsx("span",{className:"cclab",children:"Funding this down payment"}),e.jsx("button",{className:"ccx",onClick:()=>{Er(!1)},title:"Collapse",children:e.jsx(Cr,{size:10})})]}),e.jsxs("div",{className:"bvr-f2row",children:[e.jsx("span",{children:"Tax-free cash"}),e.jsx("span",{onClick:r=>r.stopPropagation(),children:e.jsx("input",{ref:Ct("pop-cash"),className:"bvr-in num",style:{width:108},defaultValue:"$"+Math.round(zr).toLocaleString(),onFocus:nt,onKeyDown:r=>{r.stopPropagation(),Mr("pop-cash")(r)},onBlur:r=>{const a=parseFloat(r.target.value.replace(/[^0-9.]/g,""));pr(isNaN(a)?0:Math.max(0,Math.min(re,a))),r.target.value="$"+Math.round(isNaN(a)?zr:Math.max(0,Math.min(re,a))).toLocaleString()}},"cash"+zr)})]}),e.jsx("div",{className:"bvr-f2slider",onClick:r=>r.stopPropagation(),onMouseDown:r=>r.stopPropagation(),children:e.jsx("input",{type:"range",className:"bvr-pprange",min:0,max:Math.round(re),step:5e3,value:Math.round(zr),onChange:r=>{const a=+r.target.value;pr(Math.round(re)-a<5e3&&a>0?re:a)}})}),e.jsx("div",{className:"bvr-f2expand collapse-motion"+(vt>0?" open":""),children:e.jsxs("div",{className:"bvr-f2expandinner",children:[e.jsxs("div",{className:"bvr-f2row sub",children:[e.jsx("span",{children:"Raised by selling stock"}),e.jsx("b",{className:"num",children:i(vt)})]}),e.jsxs("div",{className:"bvr-f2row",children:[e.jsx("span",{className:"has-help",onClick:r=>F({kind:"help",key:"embGain"},r),children:"Embedded gain"}),e.jsx("span",{onClick:r=>r.stopPropagation(),children:e.jsx(Le,{v:hr,set:go,w:52})})]}),e.jsxs("div",{className:"bvr-f2row",children:[e.jsx("span",{children:"Sell (gross of tax)"}),e.jsx("b",{className:"num",children:i(vt+Je)})]}),e.jsxs("div",{className:"bvr-f2row",children:[e.jsxs("span",{className:"has-help",onClick:r=>F({kind:"help",key:"embGain"},r),children:["+ Capital gains tax (",R(be/100)," LTCG)"]}),e.jsx("b",{className:"num neg",children:i(Je)})]}),e.jsxs("div",{className:"bvr-f2y0",children:[e.jsxs("div",{className:"bvr-f2row sub",children:[e.jsx("span",{children:"Year 0 · buyer starts with"}),e.jsx("b",{className:"num",children:i(I.exitAt(0).buyPre)})]}),e.jsxs("div",{className:"bvr-f2row",children:[e.jsx("span",{children:"Year 0 · renter keeps (pre-liq)"}),e.jsx("b",{className:"num",children:i(I.exitAt(0).rentPre)})]}),e.jsxs("div",{className:"bvr-f2row",children:[e.jsx("span",{children:"⦠if renter sold today"}),e.jsx("b",{className:"num",children:i(I.exitAt(0).rentAT)})]})]})]})})]})}]:[{custom:vt>0?e.jsxs("div",{className:"cctog bvr-fundsum",onClick:()=>{Er(!0)},children:[e.jsxs("span",{className:"bvr-fundsumlab",children:["Tax-free + asset sale ",e.jsx(Gn,{size:10})]}),e.jsxs("span",{className:"bvr-fundsumval num",children:[xe(zr)," + ",xe(vt+Je)," ",e.jsxs("span",{className:"neg",children:["â ",xe(Je)," tax"]})]})]}):e.jsxs("div",{className:"cctog",onClick:()=>{Er(!0),Et==null&&pr(re)},children:[e.jsx("b",{className:"num",children:"100%"})," Tax-free funds ",e.jsx(Gn,{size:10})]})}],...Ze!==0?[{l:Ze>0?"Under-funded":"Over-funded",v:i(Math.abs(Ze)),k:"neg"}]:[]],notes:["Tax-free funds: by default the whole down payment is cash with no tax cost. Open it to split the down payment into tax-free cash plus a portion raised by selling appreciated stock. Only that sold portion is grossed up â to net it after LTCG on the embedded gain you must sell more. Buying realizes that gain at YEAR 0; the renter's counterfactual keeps the stock and defers it, which is why the renter's pre-liq start sits higher and only converges to the buyer's footing if sold.","The down payment is one leg of the funding identity â tap Purchase price above to manage the whole stack."]};case"invShare":return{title:"Investment share",rows:[],notes:["The fraction of the property used as rental â by square footage or unit count (e.g. you occupy one unit of a duplex â 50%). It allocates expenses and mortgage interest to Schedule E, set
13s the depreciable share, and splits the sale gain between the §121-sheltered owner portion and the fully taxable rental portion."]};case"recap":return{title:"Depreciation recapture rate",rows:[],notes:["Depreciation you deduct lowers basis; at sale that amount is taxed as unrecaptured §1250 gain â up to 25% federal. California taxes it as ordinary income, so a blended CA rate can run ~34%+. Edit to match your situation."]};case"vacancy":return{title:"Vacancy & management",rows:[],notes:["Haircut on scheduled rent for vacant months, turnover, and property-management fees. 5â10% is a common planning range; self-managed with a long-term tenant can run lower."]};case"embGain":return{title:"Embedded gain",rows:[{l:"Raised by selling stock (net)",v:i(vt)},{l:"Sell (gross of tax)",v:i(vt+Je)},{l:`Embedded gain (${R(hr/100)})`,v:i((vt+Je)*(hr/100))},{l:`LTCG on that gain (${R(be/100)})`,v:i(Je),k:"neg"}],notes:["The unrealized-gain fraction of the stock youâd sell: (value â basis) ÷ value. Only the portion of the down payment funded by selling stock triggers LTCG, and itâs grossed up â you must sell enough that the net after tax covers that portion. Buying realizes this tax at year 0; the rent path keeps the stock and defers the gain to its own exit."]};case"assessCap":return{title:Qr,subtitle:_e?"enabled â assessed value grows slower than market":"disabled â assessed value tracks market appreciation",rows:[],notes:vi()};case"excl":return{title:"§121 exclusion",subtitle:"capital-gains exclusion on a primary residence",rows:[],notes:["Gain on your primary residence excluded from tax at sale: $500k married filing jointly, $250k single, if youâve owned and lived there 2 of the last 5 years. In a multifamily, it excludes only the owner-occupied share of the gain â the rented portionâs gain (and depreciation recapture) stays taxable."]};case"mix":{const r=Ke/100*(Wt/100)+(1-Ke/100)*(er/100),a=Ke/100*(Wt/100)+(1-Ke/100)*(er/100)*(1-ne/100);return{title:"Reinvestment mix",rows:[{custom:e.jsxs("div",{style:{padding:"2px 0 4px"},children:[e.jsxs("div",{className:"bvr-mixlab",style:{marginBottom:4},children:[100-Ke,"% cash · ",Ke,"% equities"]}),e.jsx("input",{type:"range",min:"0",max:"100",step:"5",value:Ke,onChange:n=>Po(+n.target.value),className:"bvr-mix","aria-label":"Reinvestment mix â percent equities"}),e.jsxs("div",{className:"bvr-mixrow",style:{marginBottom:0},children:[e.jsx("span",{children:"cash/bond/other"}),e.jsx("span",{children:"equities"})]}),e.jsxs("div",{className:"bvr-mixreturns",children:[e.jsxs("div",{className:"bvr-mixret",children:[e.jsx(Yn,{rate:{v:er,set:ls},inValue:!0}),e.jsx("span",{children:"return"})]}),e.jsxs("div",{className:"bvr-mixret right",children:[e.jsx(Yn,{rate:{v:Wt,set:cs},inValue:!0}),e.jsx("span",{children:"return"})]})]})]})},{l:"Blended pre-tax",v:R(r)},{l:"Blended after-tax",v:R(a),k:"tot"}],notes:[`Blends your ${R(Wt/100)} equity and ${R(er/100)} cash/bond returns. Applies to the renter's portfolio and the surplus either side invests. Bonds are taxed yearly at ordinary; equity compounds tax-deferred with LTCG at exit.${Ke>0?" Note: the blended after-tax figure is PRE-liquidation â with equities above 0% it still includes unrealized capital gains; the LTCG bill settles only at sale (see Net worth · post-liq in the details table).":""}`]}}case"domain":{const r=n=>Math.max(1,Math.min(y-1,n)),a=n=>Math.max(x+1,Math.min(rt,n));return{title:"Chart & horizon range",rows:[{custom:e.jsxs("div",{className:"dmn",children:[e.jsxs("div",{className:"dmnrow",children:[e.jsx("span",{children:"Start year"}),e.jsxs("span",{className:"dmnstep",children:[e.jsx("button",{onClick:()=>v(r(x-1)),children:"â"}),e.jsx("b",{className:"num",children:x}),e.jsx("button",{onClick:()=>v(r(x+1)),children:"+"})]})]}),e.jsxs("div",{className:"dmnrow",children:[e.jsx("span",{children:"End year"}),e.jsxs("span",{className:"dmnstep",children:[e.jsx("button",{onClick:()=>A(a(y-5)),children:"â"}),e.jsx("b",{className:"num",children:y}),e.jsx("button",{onClick:()=>A(a(y+5)),children:"+"})]})]}),e.jsx("div",{className:"dmnpre",children:[[1,15],[1,25],[1,40],[5,30]].map(([n,s])=>e.jsxs("button",{className:x===n&&y===s?"on":"",onClick:()=>{v(n),A(s)},children:[n,"â",s]},n+"-"+s))})]})}],notes:[`Sets the year range for the slider and the chart below (1â${rt}). Tip: on the slider, drag to the right edge, pull back, then hit the edge again to add 10 years.`]}}case"loanamt":{const r=lo[pa]||lo.high,a=r.v,n=X?Math.min(4,Math.max(1,At)):1;return{title:"Mortgage amount or preset",rows:[{custom:e.jsxs("div",{className:"dmn",children:[e.jsxs("div",{className:"dmnrow",children:[e.jsx("span",{children:"Region"}),e.jsx("span",{className:"bvr-seg cllseg",children:Object.entries(lo).map(([s,o])=>e.jsx("button",{className:pa===s?"on":"",onClick:()=>
13wo(s),children:o.btn},s))})]}),e.jsx("div",{className:"dmnsub",children:r.sub}),e.jsxs("div",{className:"dmnnote num",children:["2026 conforming limit, ",n,"-unit: ",e.jsx("b",{children:i(a[n-1])}),e.jsx("a",{className:"bvr-poplsrc bvr-poplsrc-after",href:Kc,target:"_blank",rel:"noopener noreferrer",onClick:s=>s.stopPropagation(),"aria-label":"Open FHFA's 2026 conforming loan limit release","data-testid":"link-bvr-cll-source",children:"source"})]}),e.jsxs("div",{className:"dmnpre",children:[e.jsx("button",{className:Dr&&Dr.kind==="cll"?"on":"",onClick:()=>{const s=a[n-1];kn({kind:"cll",v:s,label:"Conforming · "+(pa==="base"?"baseline":pa==="nyc"?"NYC metro":"high-cost")}),Be("m1",s)},children:"Use conforming limit"}),e.jsx("button",{className:Dr&&Dr.kind==="ltv"?"on":"",onClick:()=>{const s=Math.round(G*.8);kn({kind:"ltv",v:s,label:"80% LTV"}),Be("m1",s)},children:"80% LTV"})]})]})},{custom:e.jsxs("div",{className:"bvr-poplink",style:{cursor:"default"},onClick:s=>s.stopPropagation(),children:[e.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:7},children:["Mortgage ",e.jsx(Zr,{k:"m1"})]}),e.jsx(Tt,{v:(te.find(s=>s.id==="m1")||{}).amount||0,set:s=>{kn(null),Be("m1",s)},w:120})]})},{custom:(()=>{const s=(te.find(b=>b.id==="m1")||{}).amount||0,o=Math.max(1e6,Math.ceil(G/5e4)*5e4);return ea({k:"m1",value:s,set:b=>{kn(null),Be("m1",b)},min:0,max:o,steps:{fine:25e3,coarse:5e4,at:75e4}})})()},{l:"Loan-to-value",v:R(((te.find(s=>s.id==="m1")||{}).amount||0)/Math.max(1,G))}],notes:[`Conforming loans (bought by Fannie/Freddie) cap at these FHFA limits; above them is a jumbo. Multi-unit owner-occupied properties get higher limits â the ${n}-unit figure reflects your ${X?At+" units":"single unit"}.`,"Amounts above the limit still work in the model â theyâre simply jumbo financing."]}}case"presets":{const r=Wa,a=_n,n=Xe;a[n];const s=u=>"$"+(u[0]>=1e3?Math.round(u[0]/1e3)+"K":u[0])+"â/â$"+(u[1]>=1e3?Math.round(u[1]/1e3)+"K":u[1]),o=u=>Math.round(u*10)%10===0?String(Math.round(u)):u.toFixed(1),b=(u,f)=>{const S=a[f].ord(u.thr===null?37:u.r),k=u.r+S,j=u.fedLTCG+(u.niit?3.8:0)+S;return{ord:Math.round(k*10)/10,ltcg:Math.round(j*10)/10}},p=u=>b(u,n);return{title:"Tax rate presets",subtitle:"pick a federal bracket · state is estimated",rows:[{custom:e.jsx("div",{className:"bvr-regionseg",children:Object.keys(a).map(u=>e.jsx("button",{className:"bvr-regionbtn"+(n===u?" on":""),onClick:()=>{const f=[...r,co].find(S=>{const k=b(S,n);return Math.abs(ne-k.ord)<.05&&Math.abs(be-k.ltcg)<.05});if(Eo(u),f){const S=b(f,u);Zt(S.ord),Rr(S.ltcg)}},children:a[u].label},u))})},{custom:e.jsxs("div",{className:"bvr-fedhead",children:[e.jsx("span",{className:"bvr-fedr bvr-fedheadbracket",children:"Bracket"}),e.jsxs("span",{className:"bvr-fedthr",children:["Single","â/â","married"]}),e.jsxs("span",{className:"bvr-fedres",children:["Ordinary","â/â","LT cap gains"]})]})},{custom:e.jsx("div",{className:"bvr-fedlist",children:Gr.map(u=>{const f=p(u),S=Math.abs(ne-f.ord)<.05&&Math.abs(be-f.ltcg)<.05,k=u.thr===null;return e.jsxs("button",{className:"bvr-fedrow"+(k?" bvr-fedrow-amt":"")+(S?" on":""),onClick:()=>{Zt(f.ord),Rr(f.ltcg),is(k)},children:[e.jsx("span",{className:"bvr-fedmark",children:S?e.jsx(Wn,{size:12}):null}),e.jsxs("span",{className:"bvr-fedr num",children:[u.r,"%"]}),k?e.jsx("span",{className:"bvr-fedthr bvr-fedthr-amt",children:"top AMT tax rate"}):e.jsx("span",{className:"bvr-fedthr num",children:s(u.thr)}),e.jsxs("span",{className:"bvr-fedres num",children:[o(f.ord),e.jsx("span",{className:"bvr-fedslash",children:"â/â"}),o(f.ltcg),"%"]})]},u.r+(k?"amt":""))})})},{custom:e.jsx("div",{className:"bvr-rateout",children:[["Ordinary marginal","pop-ordout",ne,Zt],["LT cap gains","pop-ltcgout",be,Rr]].map(([u,f,S,k])=>e.jsxs("div",{className:"bvr-rateoutrow",children:[e.jsx("span",{className:"bvr-rateoutlab",children:u}),e.jsx("span",{onClick:j=>j.stopPropagation(),children:e.jsx("input",{ref:Ct(f),className:"bvr-in num",style:{width:66},defaultValue:R(S/100),onFocus:nt,onKeyDown:j=>{j.stopPropagation(),Mr(f)(j)},onBlur:j=>{const T=parseFloat(j.target.value.replace("%",""));isNaN(T)||k(T),j.target.value=R((isNaN(T)?S:T)/100)}},f+S)})]},f))})}],notes:[e.jsxs(e.Fragment,{children:["Federal brackets are 2026 (",e.jsx("a",{className:"bvr-popnotelink",href:$c,target:"_blank",rel:"noopener noreferrer",onClick:u=>u.stopPropagation(),"data-testid":"link-bvr-fed-brackets-source",children:"IRS Rev. Proc. 2025-32"}),"); thresholds shown single","â/â","married. Pick your top bracket and region and the state marginal + long-term cap-gains rate are estimated; the right column shows the resulting ordinary","â/â","LTCG pair."]}),"State figures use the latest published (2025) rates â 2026 state brackets arenât out yet. LTCG = the federal 0â/â15â/â20% band for that income, plus 3.8%
13NIIT where it applies, plus state (CA and NY tax gains as ordinary income; NYC adds local). Estimates only â fine-tune the fields after."]}}case"ord":case"ltcg":{const r=(a,n,s,o)=>{const b=p=>Math.min(o,Math.max(0,p));return e.jsxs("div",{className:"bvr-taxrate",children:[e.jsxs("div",{className:"bvr-taxrate-h",children:[e.jsx("span",{className:"bvr-taxrate-l",children:a}),e.jsx("input",{className:"bvr-editpct inval num",defaultValue:R(n/100),onFocus:nt,onClick:p=>p.stopPropagation(),onMouseDown:p=>p.stopPropagation(),onKeyDown:p=>{p.stopPropagation(),p.key==="Enter"&&p.target.blur()},onBlur:p=>{const u=parseFloat(p.target.value.replace(/[^0-9.]/g,"")),f=isNaN(u)?n:b(u);s(f),p.target.value=R(f/100)}},a+n)]}),e.jsx("input",{type:"range",className:"bvr-mix",min:0,max:o,step:.1,value:b(n),onChange:p=>s(+p.target.value),"aria-label":a+" rate"}),e.jsxs("div",{className:"bvr-mixrow",children:[e.jsx("span",{children:"0%"}),e.jsxs("span",{children:[o,"%"]})]})]},a)};return{title:"Tax rates",rows:[{custom:e.jsxs("div",{className:"bvr-taxrates",children:[r("Ordinary marginal",ne,Zt,60),r("LT cap gains",be,Rr,45)]})}],notes:["Ordinary marginal = combined federal + state marginal rate on ordinary income. Applies to the cash sleeveâs yearly interest, the value of deductible mortgage interest, positive rental income, and released §469 losses. Top-bracket CA â 37% + 13.3% = 50.3%.","LT cap gains = 20% federal + 3.8% NIIT + state (CA taxes gains as ordinary, 13.3% top) â 37.1%. Applies to home gain above §121, the equity sleeve at liquidation, and stock sold to fund the down payment."]}}case"sellPct":return{title:"Liquidation costs (sale)",rows:[{l:`At year ${c} (${R(qt/100)})`,v:i(N.sc)}],notes:["Home fix-up, staging, agent commissions, transfer taxes, and closing fees on sale â 5â6% is typical. A fixed friction the buy path pays whenever you exit."]};case"capexA":return{title:"Capital expenditures",rows:[],notes:["Reserve for big-ticket replacements â roof, HVAC, repiping â beyond routine maintenance. Modeled as a % of market value set aside yearly."]};case"synth":{const r=te.find(s=>s.kind==="secured"&&s.type==="Synthetic loan"),a="Structurally a short SPX box spread â economically similar to margin borrowing against your investment portfolio, but with rates near Treasury yields and no periodic interest payments. The implied interest recognizes annually as a capital loss (60% LT / 40% ST under IRC §1256), so it lowers AGI in a way SBLOC and standard margin can't.",n=["Structurally: a short box spread on SPX index options. You sell a combination of European-style calls and puts that guarantees a fixed net payment at expiry (out to â¼5 years). The premium you receive upfront is the loan proceeds; the net payment you owe at expiry equals principal + implied interest. There are NO periodic interest payments â just the single settlement at expiry. Traded on Cboe; the position is guaranteed by the Options Clearing Corp. (AA-rated SIFMU).","Economically similar to margin borrowing against your investment portfolio, but with three material differences: (1) rates track Treasury yields plus â20â50 bps of spread â typically 100â200 bps cheaper than SBLOC or standard margin, (2) no periodic interest payments to service, and (3) the tax treatment of the implied interest is fundamentally different â see below.","Under IRC §1256, SPX box spreads are marked-to-market at year-end. The implied interest is recognized annually as a CAPITAL LOSS (60% long-term, 40% short-term), NOT as an interest expense. Two implications matter for a high-tax-bracket buyer: (a) the loss offsets capital gains dollar-for-dollar with any excess spilling into the $3k/yr ordinary offset, and (b) reducing net capital gains lowers AGI, which can preserve AGI-sensitive benefits â full SALT deduction, IRMAA thresholds, NIIT exposure, PPACA subsidies, education credits. Unlike mortgage interest (capped at the first $750k of acquisition-debt principal under §163(h)), the synthetic-loan deduction is UNCAPPED.","Requires sufficient portfolio collateral in the brokerage account holding the box. Reg T margin typically permits borrowing â50% of the portfolio; Portfolio Margin (available at most major brokers for accounts ⥠$125k) can push that to â85% for well-diversified holdings.","Two material risks. MARGIN-CALL risk if the portfolio drops sharply â at a 50% Reg T borrow, a portfolio decline of â28.5% triggers the call; a 30% borrow tolerates â60% before calling (covers every major crash since the Depression). Mitigations: borrow less than the maximum, or keep a HELOC as a
13standby liquidity backstop. REFINANCING risk if you exit before expiry â the box must be unwound at prevailing rates, so a jump in rates makes early exit costly. Mitigation: ladder expirations (e.g., 20% each of 1â5 yr boxes) so a slice rolls off every year.","The Auto-roll toggle above rolls the box at expiry into a fresh box of the same tenor at the current rate, so the debt persists across the horizon. Turn it off if you plan to retire the balance with portfolio proceeds at expiry instead."];return{titleContent:e.jsxs("div",{className:"bvr-popbig",children:[e.jsxs("b",{className:"num",children:[i(r.amount),e.jsxs("span",{className:"bvr-popbigsub",children:["at ",R(r.rate/100)," for ",r.term," yr",r.term===1?"":"s"]})]}),e.jsx("em",{children:"synthetic loan"})]}),subtitle:"long-dated short SPX box spread",rows:[{custom:e.jsxs("div",{className:"bvr-poplink",style:{cursor:"default"},onClick:s=>s.stopPropagation(),children:[e.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:7},children:["Amount ",e.jsx(Zr,{k:r.id})]}),e.jsx(Tt,{v:r.amount,set:s=>Be(r.id,s),w:120})]})},{custom:ea({k:r.id,value:r.amount,set:s=>Be(r.id,s),min:0,max:Math.max(1e6,Math.ceil(G/5e4)*5e4),steps:{fine:25e3,coarse:5e4,at:1e6}})},{custom:e.jsxs("div",{children:[e.jsxs("div",{className:"bvr-poptog",children:[e.jsx("span",{children:"Assume tax deductible?"}),ke(r.deductible,()=>De(r.id,{deductible:!r.deductible}),"Assume tax deductible "+(r.deductible?"yes":"no"))]}),e.jsx("div",{className:"bvr-poptoghint",style:r.deductible?{color:"var(--credit)"}:void 0,children:r.deductible?"offset taxable capital gains (40/60) each year":"accrued capital losses saved, used against capital gains at liquidation"})]})},{custom:e.jsxs("div",{children:[e.jsxs("div",{className:"bvr-poptog",children:[e.jsx("span",{children:"No monthly payments"}),ke(!!r.noPay,()=>De(r.id,{noPay:!r.noPay}),"No monthly payments "+(r.noPay?"yes":"no"))]}),e.jsx("div",{className:"bvr-poptoghint",style:r.noPay?{color:"var(--credit)"}:void 0,children:r.noPay?"interest capitalizes into the balance":"interest paid in cash each year"})]})},{custom:e.jsxs("div",{className:"bvr-poptog",children:[e.jsx("span",{children:"Auto-roll at expiry"}),ke(!!r.autoRoll,()=>De(r.id,{autoRoll:!r.autoRoll}),"Auto-roll "+(r.autoRoll?"yes":"no"))]})}],notes:va?n:[a],footer:e.jsx("button",{className:"bvr-popmore",onClick:()=>xs(s=>!s),children:va?"Show less":"More"})}}case"rentamt":return{title:"Rent / month",rows:[{custom:e.jsxs("div",{className:"bvr-poplink",style:{cursor:"default"},onClick:r=>r.stopPropagation(),children:[e.jsx("span",{children:"Rent / mo"}),e.jsx(Tt,{v:ht,set:da,w:120,step:Xc})]})},{custom:ea({k:null,value:ht,set:da,min:1e3,max:25e3,steps:{fine:250,coarse:500,at:1e4}})},{l:"Rent inflation",editVal:{v:Mt,set:en}},{hr:!0},{l:`Year ${c} rent / mo`,v:i(ht*Math.pow(1+Mt/100,c-1)),k:"tot"}],notes:["Market rent for a home comparable to the one being bought â the anchor of the rent path, growing at rent inflation each year. If the purchase is multifamily, the rent the OWNER collects from the other units is set separately in Ownership."]};case"sbloc":{const r=te.find(o=>o.id===(W&&W.loanId))||te.find(o=>o.kind==="secured"&&qa.includes(o.type));if(!r)return{title:"Pledged-asset line / SBLOC",rows:[],notes:[]};const a=W&&W.conflict,n="A revolving line secured by the securities in your portfolio â not by the home â usually at a floating rate with no set amortization. Two consequences drive the switches above: interest is deductible only in a year you actually PAY it, and because the collateral is stock rather than the residence, even paid interest isnât automatically deductible â that turns on what the borrowed money is traced to.",s=["Structurally: a revolving line of credit secured by a pledge of the marketable securities in your brokerage account. Rates typically float over a short-term index (SOFR plus a spread that narrows as the balance grows). There is no amortization schedule â you can pay interest currently, pay the line down at will, or let the interest capitalize. The lender sets a collateral-maintenance requirement and can demand a paydown or sell the pledged securities if the portfolio falls below it.","WHY THE TWO SWITCHES ABOVE ARE MUTUALLY EXCLUSIVE: a cash-method taxpayer deducts interest in the year it is actually PAID. Interest that accrues and capitalizes into the loan balance has not been paid â and settling interest by drawing more from the SAME lender is generally not treated as payment either. So âNo monthly paymentsâ (capitalize) and âTax deductible?â (credit the benefit every year) cannot both hold for this facility. Pay the interest in cash and it is deductible that year, subject to the limits below; let it capitalize and the deduction waits until the interest is actually paid â typically when the balance is settled.","CONTRAST WITH THE SYNTHETIC LOAN: a short SPX box spread is an IRC §1256 contract, marked to market at year-end. Its implied interest is recognized annually as a capital loss whether or not any cash changes hands â no payment is required to get the annual tax effect. That is a genuinely different mechanism, which is why the synthetic loan can be both non-paying and tax-effective while this line cannot. The exclusion applies to the pledged-asset line only.","EVEN WHEN PAID, DEDUCTIBILITY IS NOT AUTOMATIC. An SBLOC is secured by securities, not by the residence, so its interest is NOT qualified residence interest under §163(h) â the acquisition-debt rules governing the mortgage above do not reach it. Deductibility instead turns on TRACING (Treas. Reg. §1.163-8T): interest traced to investment property is investment interest, deductible only against net investment income under §163(d) with the excess carried forward; interest traced to buying a personal residence is personal interest and is not deductible at all. With the switch ON this model credits the benefit at your ordinary rate and does not model the §163(d) limitation â open for CPA review.","RISK: collateral maintenance. A sharp portfolio drawdown can trigger a demand for repayment or a forced sale of the pledged securities â realizing gains at the worst possible moment. Borrowing well below the maximum advance rate is the usual mitigation."];return{titleContent:e.jsxs("div",{className:"bvr-popbig",children:[e.jsxs("b",{className:"num",children:[i(r.amount),e.jsx("span",{className:"bvr-popbigsub",children:"at "+R(r.rate/100)+" · "+(r.noPay?"accruing":"paid in cash")})]}),e.jsx("em",{children:"pledged-asset line"})]}),subtitle:"securities-based line of credit",rows:[...a?[{custom:e.jsxs("div",{className:"bvr-popconf",role:"status",children:[e.jsx(Hr,{size:12,"aria-hidden":"true"}),e.jsx("span",{children:a==="noPay"?e.jsxs(e.Fragment,{children:[e.jsx("b",{children:"Tax deductible? switched off."})," Interest that capitalizes into the balance hasnât been paid â and a cash-method borrower deducts interest only in the year itâs actually paid."]}):a==="type"?e.jsxs(e.Fragment,{children:[e.jsx("b",{children:"Tax deductible? switched off."})," This facility was carrying both flags. A pledged-asset line canât capitalize its interest and deduct it in the same year, so the deduction dropped â switch it back on and the interest is modeled as paid in cash."]}):e.jsxs(e.Fragment,{children:[e.jsx("b",{children:"No monthly payments switched off."})," To deduct the interest you have to actually pay it â it canât still be capitalizing into the balance."]})})]},"cf"+(W&&W.cn||0))}]:[],{custom:e.jsxs("div",{className:"bvr-poplink",style:{cursor:"default"},onClick:o=>o.stopPropagation(),children:[e.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:7},children:["Amount ",e.jsx(Zr,{k:r.id})]}),e.jsx(Tt,{v:r.amount,set:o=>Be(r.id,o),w:120})]})},{custom:ea({k:r.id,value:r.amount,set:o=>Be(r.id,o),min:0,max:Math.max(1e6,Math.ceil(G/5e4)*5e4),steps:{fine:25e3,coarse:5e4,at:1e6}})},{custom:e.jsxs("div",{children:[e.jsxs("div",{className:"bvr-poptog",children:[e.jsx("span",{children:"No monthly payments"}),ke(!!r.noPay,o=>gn(r,"noPay",o),"No monthly payments "+(r.noPay?"yes":"no"))]}),e.jsx("div",{className:"bvr-poptoghint",style:r.noPay?{color:"var(--credit)"}:void 0,children:r.noPay?"interest capitalizes into the balance â no deduction while unpaid":"interest paid in cash each year"})]})},{custom:e.jsxs("div",{children:[e.jsxs("div",{className:"bvr-poptog",children:[e.jsx("span",{children:"Tax deductible?"}),ke(!!r.deductible,o=>gn(r,"deductible",o),"Tax deductible "+(r.deductible?"yes":"no"))]}),e.jsx("div",{className:"bvr-poptoghint",style:r.deductible?{color:"var(--credit)"}:void 0,children:r.deductible?"benefit at your ordinary rate â assumes the interest is paid and properly traced":"no annual deduction modeled"})]})}],notes:va?s:[n],footer:e.jsx("button",{className:"bvr-popmore",onClick:()=>xs(o=>!o),children:va?"Show less":"More"})}}default:return{title:"",rows:[],notes:[]}}}
13,fi=({interest:t,benefit:r,principal:a,benefitDesc:n})=>{if(!(a>.5)||!(t>.005))return null;const s=t/a,o=(t-r)/a,b=p=>(p*100).toFixed(2)+"%";return e.jsxs("div",{className:"bvr-ratexpl",children:[e.jsxs("div",{className:"bvr-ratexpl-pill",children:[e.jsx("span",{className:"bvr-ratexpl-lab",children:"Effective cost of this debt"}),e.jsxs("em",{className:"bvr-ratepill",children:[e.jsxs("b",{style:{color:"var(--buyGross)"},children:[(s*100).toFixed(1),"%"]}),e.jsx("i",{"aria-hidden":"true",children:"â"}),e.jsxs("b",{style:{color:"var(--buy)"},children:[(o*100).toFixed(1),"%"]})]})]}),e.jsxs("div",{className:"bvr-ratexpl-row",children:[e.jsx("span",{children:"Gross rate"}),e.jsxs("span",{children:[i(t)," ÷ ",i(a)," = ",e.jsx("b",{style:{color:"var(--buyGross)"},children:b(s)})]})]}),e.jsxs("div",{className:"bvr-ratexpl-row",children:[e.jsx("span",{children:"Less tax benefit"}),e.jsxs("span",{style:{color:"var(--credit)"},children:["â",i(r)]})]}),e.jsx("div",{className:"bvr-ratexpl-note",children:n}),e.jsxs("div",{className:"bvr-ratexpl-row tot",children:[e.jsx("span",{children:"After-tax rate"}),e.jsxs("span",{children:["(",i(t)," ","â"," ",i(r),") ÷ ",i(a)," = ",e.jsx("b",{style:{color:"var(--buy)"},children:b(o)})]})]}),e.jsxs("div",{className:"bvr-ratexpl-note",children:["Rate is on the loan's outstanding principal. Capitalized (accrued) interest is ",e.jsx("b",{children:"not"})," netted out here \\u2014 only the tax benefit lowers the rate."]})]})},lt=W?W.kind==="buy"?rc():W.kind==="rent"?ac():W.kind==="driver"?sc():W.kind==="help"?oc(W.key):nc(W.key,W.focus):null;d.useEffect(()=>{if(!W)return;const t=a=>{us.current||a.target.isConnected&&(Lr.current&&Lr.current.contains(a.target)||(Pr(null),Br.current=null))},r=()=>{Pr(null),Br.current=null};return window.addEventListener("resize",r),document.addEventListener("click",t),()=>{window.removeEventListener("resize",r),document.removeEventListener("click",t)}},[W]),d.useLayoutEffect(()=>{if(!W||!Lr.current)return;const t=Lr.current,r=t.offsetWidth,a=t.offsetHeight,n=window.innerWidth,s=window.innerHeight,o=10,b=8,p=10,u=24,f=()=>{let B=W.aTop-4;return B+a>s-o&&(B=s-a-o),B<o&&(B=o),B},S=n-W.aRight-p-o,k=W.aLeft-p-o;let j,T;S>=r?(j=W.aRight+p,T=f()):k>=r?(j=W.aLeft-p-r,T=f()):(j=W.aLeft-4,j+r>n-o&&(j=n-r-o),j<o&&(j=o),s-W.aBottom>=a+b+o?T=W.aBottom+b:W.aTop>=a+u+o?T=W.aTop-a-u:T=Math.max(o,s-a-o)),T+a>s-o&&(T=Math.max(o,s-a-o)),T<o&&(T=o),hs({left:j,top:T,ready:!0}),W.focus&&st(W.focus)},[W,va,Nn]),d.useLayoutEffect(()=>{const t=Z.current;if(!t)return;const r=()=>dr(t.clientWidth||600);r();const a=new ResizeObserver(r);return a.observe(t),()=>a.disconnect()},[]),d.useLayoutEffect(()=>{const t=Va.current;if(!t||typeof document>"u")return;const r=t.closest(".bvr-root");if(!r)return;const a=()=>r.style.setProperty("--titleblock-h",t.offsetHeight+"px");a();const n=new ResizeObserver(a);return n.observe(t),()=>n.disconnect()},[]),d.useLayoutEffect(()=>{const t=la.current;if(!t)return;const r=()=>tl((t.clientWidth||999)<580);r();const a=new ResizeObserver(r);return a.observe(t),()=>a.disconnect()},[]);const On=d.useMemo(()=>{const t=I.exitAt(0),r={yr:0,buyAT:t.buyAT,buyPre:t.buyPre,rentAT:t.rentAT,rentPre:t.rentPre},a=I.series.filter(o=>o.yr>=x&&o.yr<=y),n=[r,...a],s=n.flatMap(o=>[o.buyAT,o.rentAT,o.buyPre,o.rentPre]).concat([0,I.Dn]);return{s:n,mx:Math.max(...s),mn:Math.min(...s)}},[I,x,y]),[yi,wi]=Yi([On.mn,On.mx]),E=d.useMemo(()=>{const b=On.s,p=yi,u=wi,f=u-p||1,S=Math.max(1,y-0),k=B=>52+(B-0)/S*652,j=B=>14+(1-(B-p)/f)*180;return{W:720,H:220,pL:52,pR:16,pT:14,pB:26,X:k,Y:j,mn:p,mx:u,sp:f,path:B=>b.map((P,de)=>(de?"L":"M")+k(P.yr).toFixed(1)+","+j(P[B]).toFixed(1)).join(" "),s:b}},[On,yi,wi,y]),ic=t=>{const a=t.currentTarget.getBoundingClientRect(),s=((t.clientX-a.left)/a.width*E.W-E.pL)/(E.W-E.pL-E.pR);return Math.max(x<=1?0:x,Math.min(y,Math.round(s*E.s[E.s.length-1].yr)))},yt=d.useMemo(()=>{const t=[null];for(let a=1;a<=rt;a++){const n=I.years[a];t.push({y:a,principal:n.principal,intG:n.mortInt+n.facInt,intM:n.mortInt,intS:n.facInt,facAccr:n.facAccr,facIntCash:n.facIntCash,pt:n.propTax,mci:n.maint+n.capex+n.ins+n.hoa,taxBen:n.taxBen,netRental:n.netRental,ptBen:n.ptBen,ptBenPre:n.ptBenPre,depBen:n.depBen,passiveLoss:n.passiveLoss,intNet:n.mortInt+n.facIntCash-n.taxBen-n.netRental-n.depBen,buyOut:n.buyOut,rentOut:n.rentOut,contr:n.contr,buyOutGross:n.buyOut+n.facAccr,extra:Math.max(0,-n.contr),keptB:n.principal+Math.max(0,-n.contr),keptR:Math.max(0,n.contr),burnR:n.rentOut,deployed:Math.max(n.buyOut,n.rentOut),prepayMort:n.prepayMort||0,secPre:(n.facLoans||[]).map(s=>({id:s.id,type:s.type,v:s.prepay||0})),overflow:n.overflow||0})}let r=null;for(let a=2;a<=rt;a++){const n=t[a-1].contr,s=t[a].contr;if(n!==0&&n>0!=s>0){r=a-1+Math.abs(n)/Math.max(1e-9,Math.abs(n)+Math.abs(s));break}}return{rows:t,flip:r}},[I,Q]),Bn=te.some(t=>t.kind==="secured"&&t.type==="Synthetic loan"&&t.amount>0&&t.noPay),lc=te.some(t=>t.kind==="secured"&&t.type==="Synthetic loan"&&t.amount>0),Js=R((Kt+Jt+(ut==="pct"?ur:br/Math.max(1,G)*100))/100),In=t=>typeof window<"u"&&window.matchMedia?window.matchMedia(t).matches:!1,ki=()=>typeof window<"u"?Math.min(window.innerWidth,521):521,[ld,cc]=d.useState(()=>In("(min-width: 900px)")),[Pt,dc]=d.useState(()=>In("(min-width: 521px)")),[pc,hc]=d.useState(ki);d.useEffect(()=>{if(typeof window>"u")return;const t=()=>{cc(In("(min-width: 900px)")),dc(In("(min-width: 521px)")),hc(ki())};return t(),window.addEventListener("resize",t),()=>window.removeEventListener("resize",t)},[]);const bc=d.useMemo(()=>{const t=yt.rows.slice(1).filter(r=>r.y>=x&&r.y<=y);return Math.max(1,...t.map(r=>Math.max(r.deployed,r.buyOutGross)))},[yt,x,y]),[ji]=Yi([bc]),z=d.useMemo(()=>{const t=it,r=720,a=t?290:250,n=52,s=16,o=14,b=26,p=yt.rows.slice(1).filter(C=>C.y>=x&&C.y<=y),u=p.map(C=>{let _=C.intNet,we=Math.max(0,C.pt-C.ptBen),U=C.mci;if(_<0){const ie=Math.max(0,C.buyOut-C.principal)/Math.max(1,we+U);_=0,we*=ie,U*=ie}return{...C,iN:_,p:we,m:U}}),f=ji,S=Math.max(1,y-x),k=C=>n+(C-x)/S*(r-n-s),j=C=>o+(1-Math.max(0,C)/f)*(a-o-b),T=(C,_)=>C.toFixed(1)+" "+_.toFixed(1),B=C=>u.map((_,we)=>(we?"L":"M")+T(k(_.y),j(C(_)))).join(" "),P=C=>u.slice().reverse().map(_=>"L"+T(k(_.y),j(C(_)))).join(" "),de=C=>C.p,ae=C=>C.p+C.m,je=C=>ae(C)+C.iN,fe=C=>C.buyOut,Me=B(de)+` L${T(k(u[u.length-1].y),j(0))} L${T(k(u[0].y),j(0))} Z`,pe=B(ae)+" "+P(de)+" Z",Ce=B(je)+" "+P(ae)+" Z",Ee=B(fe)+" "+P(je)+" Z",D=u[u.length-1],Ie=D?(()=>{const C=[0,de(D),ae(D),je(D),fe(D)].map(j);return[{t:"Property tax",y0:C[0],y1:C[1],off:0}
13,{t:"Maintenance & capex",y0:C[1],y1:C[2],off:.1},{t:"Net interest paid"+(Bn&&!t?"*":""),y0:C[2],y1:C[3],off:.2},{t:"Principal paydown",y0:C[3],y1:C[4],off:.3,onDark:!0}]})():[],ye=C=>p.map(_=>k(_.y).toFixed(1)+","+j(C(_)).toFixed(1)).join(" "),se=yt.flip,wt=(C,_)=>{const we=Math.floor(_),U=Math.min(rt,we+1),ie=_-we;return C(yt.rows[we])*(1-ie)+C(yt.rows[U])*ie},kt=C=>{if(C.length<2)return null;const _=C.map((U,ie)=>(ie?"L":"M")+T(U.x,Math.min(U.yb,U.yr))).join(" "),we=C.slice().reverse().map(U=>"L"+T(U.x,Math.max(U.yb,U.yr))).join(" ");return _+" "+we+" Z"},ct=C=>({x:k(C),yb:j(wt(_=>_.buyOut,C)),yr:j(wt(_=>_.rentOut,C))}),Lt=p.map(C=>({x:k(C.y),yb:j(C.buyOut),yr:j(C.rentOut)}));let Ot=[];if(se!=null&&se>x&&se<y){const C=ct(se),_=Lt.filter((U,ie)=>p[ie].y<=se).concat([C]),we=[C].concat(Lt.filter((U,ie)=>p[ie].y>=se));Ot=[{path:kt(_),who:p[0].contr>0?"rent":"buy"},{path:kt(we),who:p[0].contr>0?"buy":"rent"}]}else Ot=[{path:kt(Lt),who:p[0]&&p[0].contr>0?"rent":"buy"}];const tt=C=>Math.max(0,C.rentOut-C.buyOut);let Ut=[];if(Q&&p.length>1){const C=(p[0].secPre||[]).map(U=>({id:U.id,type:U.type})).reverse(),_=[{id:"__mort",label:"mortgage paydown",color:"var(--prin)",get:U=>U.prepayMort||0},...C.map(U=>({id:U.id,label:(U.type==="Synthetic loan"?"synth. loan":U.type)+" paydown",color:U.type==="Synthetic loan"?"var(--accent)":"var(--muted)",get:ie=>{const dt=(ie.secPre||[]).find(Ae=>Ae.id===U.id);return dt?dt.v:0}})),{id:"__inv",label:"invested",color:null,get:U=>U.overflow||0}].filter(U=>p.some(ie=>U.get(ie)>.5));let we=()=>0;_.forEach(U=>{const ie=we,dt=he=>Math.min(ie(he)+U.get(he),tt(he));let Ae=-1,Vt=0;p.forEach((he,sr)=>{const jr=j(he.buyOut+Math.min(ie(he),tt(he)))-j(he.buyOut+dt(he));jr>Vt&&(Vt=jr,Ae=sr)});const nr=U.color&&Ae>=0&&Vt>=15?{x:Math.max(n+40,Math.min(r-s-40,k(p[Ae].y))),y:(j(p[Ae].buyOut+dt(p[Ae]))+j(p[Ae].buyOut+Math.min(ie(p[Ae]),tt(p[Ae]))))/2+Math.min(.25*Vt,Vt/2-8)}:null;Ut.push({id:U.id,label:U.label,color:U.color,lab:nr,labText:U.label.charAt(0).toUpperCase()+U.label.slice(1),path:B(he=>he.buyOut+dt(he))+" "+P(he=>he.buyOut+Math.min(ie(he),tt(he)))+" Z"}),we=dt})}return{W:r,H:a,pL:n,pR:s,pT:o,pB:b,X:k,Y:j,vmax:f,rs:p,pPrin:Ee,pInt:Ce,pPt:Me,pMci:pe,pts:ye,wedges:Ot,flip:se,bandLabels:Ie,bandsPay:Ut,hasAccrual:p.some(C=>C.buyOutGross-C.buyOut>1),flipPt:se!=null&&se>x&&se<y?ct(se):null}},[yt,x,y,Bn,Q,it,ji]),ta=d.useRef(null),[yr,uc]=d.useState(0),Ni=t=>({onPointerEnter:r=>{r.pointerType==="mouse"&&(ta.current=c)},onPointerMove:r=>{(r.pointerType==="mouse"||r.buttons)&&w(t(r))},onPointerDown:r=>{const a=t(r);w(a),r.pointerType==="mouse"&&(ta.current=a),uc(n=>n+1)},onPointerUp:r=>{r.pointerType==="mouse"&&(ta.current=t(r))},onPointerLeave:r=>{r.pointerType==="mouse"&&ta.current!=null&&(w(ta.current),ta.current=null)}}),Si=d.useRef(null),[ra,vc]=d.useState(null),Ti=d.useRef(null),[wr,Zs]=d.useState(null);d.useLayoutEffect(()=>{const t=()=>{const a=Ti.current;if(!a){Zs(null);return}const n=a.getBoundingClientRect();if(n.width<10){Zs(null);return}const s=[],o=a.querySelector(".bvr-herobars"),b=o?o.getBoundingClientRect():null,p=12,u=b?b.left-n.left-p:0,f=b?b.right-n.left+p:n.width;a.querySelectorAll("[data-hlead]").forEach(j=>{const T=j.dataset.hlead,B=j.dataset.hside;let P=a.querySelector(`[data-htgt="${T}"]`);if(!P&&T==="bpre"&&(P=a.querySelector('[data-htgt="bat"]')),!P&&T==="rpre"&&(P=a.querySelector('[data-htgt="rat"]')),!P)return;const ae=j.querySelector(".bvr-hltxt").getBoundingClientRect(),je=P.getBoundingClientRect(),fe=ae.top+ae.height/2-n.top,Me=je.top-n.top,pe=(B==="L"?ae.right+8:ae.left-8)-n.left,Ce=B==="L"?u:f,Ee=(B==="L"?je.left:je.right)-n.left,D=T[0]==="b"?"var(--buy)":"var(--rent)";s.push({id:T,side:B,xLab:pe,yLab:fe,rail:Ce,xBar:Ee,yBar:Me,tone:D})});const S=a.querySelector('[data-hlead="btoday"]');let k=null;if(S&&b){const j=S.querySelector(".bvr-hltxt").getBoundingClientRect(),B=a.querySelector('[data-htgt="btoday"]').getBoundingClientRect().top-n.top;k={xLab:j.right+8-n.left,yLab:j.top+j.height/2-n.top,railL:u,railR:f,yT:B}}Zs({w:n.width,h:n.height,lines:s.filter(j=>j.id!=="btoday"),todayRule:k})};t();const r=requestAnimationFrame(t);return window.addEventListener("resize",t),()=>{cancelAnimationFrame(r),window.removeEventListener("resize",t)}},[c,I,Yt,ce.wealth]),d.useLayoutEffect(()=>{const t=Si.current;if(!t)return;const r=b=>vc(p=>{const u=p?JSON.stringify(p):"",f=b?JSON.stringify(b):"";return u===f?p:b}),a=()=>
13{if(window.innerWidth<=520){r(null);return}const b=t.querySelector(".bvr-yv2grid");if(!b){r(null);return}const p=b.getBoundingClientRect(),u=[...t.querySelectorAll(".bvr-lgrow[data-k]")],f={};t.querySelectorAll(".bvr-yv2seg[data-k]").forEach(k=>{f[k.getAttribute("data-k")]=k});const S=[];u.forEach(k=>{const j=k.getAttribute("data-k"),T=k.getAttribute("data-side"),B=f[j];if(!B)return;const P=k.getBoundingClientRect(),de=B.getBoundingClientRect();if(de.height<1||P.height<1)return;const ae=Math.round(P.top+P.height/2-p.top),je=Math.round(de.top+de.height/2-p.top),fe=Math.round(T==="buy"?P.right-p.left:P.left-p.left),Me=Math.round(T==="buy"?de.left-p.left:de.right-p.left);S.push({k:j,side:T,x1:fe,y1:ae,x2:Me,y2:je,credit:k.getAttribute("data-credit")==="1",tone:k.getAttribute("data-tone")})}),r({w:Math.round(p.width),h:Math.round(p.height),lines:S})};let n=0;const s=()=>{cancelAnimationFrame(n),n=requestAnimationFrame(a)};s();const o=new ResizeObserver(s);return o.observe(t),t.querySelectorAll(".bvr-lgrow, .bvr-lgswitch, .bvr-yv2seg").forEach(b=>o.observe(b)),window.addEventListener("resize",s),()=>{cancelAnimationFrame(n),o.disconnect(),window.removeEventListener("resize",s)}});const mc=t=>{const r=t.currentTarget.getBoundingClientRect(),n=((t.clientX-r.left)/r.width*z.W-z.pL)/(z.W-z.pL-z.pR);return Math.max(x,Math.min(y,Math.round(x+n*(y-x))))},Ci=(t,r,a)=>{if(!(a>.5)||!(t>.005))return null;const n=t/a,s=(t-r)/a;return e.jsxs("em",{className:"bvr-ratepill",title:`Effective cost on ${i(a)} of principal â gross ${(n*100).toFixed(1)}%, and ${(s*100).toFixed(1)}% after this debt's tax benefit (capitalized interest is NOT netted out).`,children:[e.jsxs("b",{style:{color:"var(--buyGross)"},children:[(n*100).toFixed(1),"%"]}),e.jsx("i",{"aria-hidden":"true",children:"â"}),e.jsxs("b",{style:{color:"var(--buy)"},children:[(s*100).toFixed(1),"%"]})]})},xc=(h.facLoans||[]).reduce((t,r)=>t+r.balStart,0),Mi=[{g:c===0?"Annual housing cost · yr 1: "+ft(1):"Annual housing cost"},{key:"mortPI",l:"Mortgage principal",b:h.principal,r:null},{key:"mortPI",l:"Mortgage interest",b:h.mortInt,r:null,pill:Ci(h.mortInt,h.taxBenMort,h.mortBalStart)},...h.facInt>0?[{key:"facInt",l:"Synth. loan âinterestâ",b:h.facInt,r:null,note:h.facAccr>.5?"accrues":null,pill:Ci(h.facInt,h.taxBenSyn+h.taxBenFac,xc)}]:[],{key:"propTax",l:"Property tax",b:h.propTax,r:null,note:_e&&bt/100<Ve/100?Te==="California"?"Prop 13 base":"capped base":null},{key:"maintcapex",l:"Maintenance & capex",b:h.maint+h.capex,r:null},{key:"ins",l:"Insurance",b:h.ins,r:h.rins},...h.hoa>0?[{key:"hoa",l:"HOA",b:h.hoa,r:null}]:[],{key:"rent",l:"Rent",b:null,r:h.rent},{key:"buyOut",l:"Gross housing outflow",b:h.principal+h.mortInt+h.facInt+h.propTax+h.maint+h.capex+h.ins+h.hoa,r:h.rentOut,tot:!0,mini:!0},...h.facAccr>.5?[{key:"facInt",l:"Implied financing accrues",b:-h.facAccr,r:null,credit:!0,note:"no payment"}]:[],...I.hasSynDed?[{key:"taxBen",l:"Mortgage tax benefit",b:-(h.taxBenMort+h.taxBenFac),r:null,credit:!0},{key:"taxBen",l:"Synth. loan tax benefit",b:-h.taxBenSyn,r:null,credit:!0,note:"§1256"}]:[{key:"taxBen",l:"Tax benefit",b:-h.taxBen,r:null,credit:!0}],...h.propTax>.5||h.ptBen>0||h.ptBenPre>0?[{key:"propTax",l:"Prop-tax benefit",b:-h.ptBen,r:null,credit:!0,note:et?"disallowed · AMT":fr?"disallowed · AGI":Is&&h.ptBen<=.5?"$0 cap":"SALT"}]:[],...X?[{key:"dep",l:"Depreciation benefit",b:-h.depBen,r:null,credit:!0,note:Oe?"§469":"suspended · §469"}]:[],...X?[{key:"rental",l:"Rental income",b:-h.netRental,r:null,credit:!0,note:"unit"}]:[],{key:"buyOut",l:"Net housing outflow",b:h.buyOut,r:h.rentOut,tot:!0,note:c===0?"yr 1":null},{g:c===0?"Wealth building · yr 1":"Wealth building"},...(()=>{const t=Q&&h.contr>.5?h.contr:0,r=[...h.prepayMort>.5?[{n:"mortgage",v:h.prepayMort}]:[],...(h.facLoans||[]).filter(a=>a.prepay>.5).map(a=>({n:a.type,v:a.prepay}))];return[{key:"surplus",focus:"prin",l:"Principal paydown · mortgage",note:"scheduled",b:h.principal,r:null,credit:!0},...r.map(a=>({key:"surplus",focus:"surplus",l:"Principal paydown · "+a.n,note:"surplus prepay",b:a.v,r:null,credit:!0})),{key:"appr",l:"Home appreciation",b:h.market*(Ve/100),r:null,credit:!0},{key:"surplus",focus:"surplus",l:"Surplus invested",b:h.overflow,r:t,credit:!0,note:!Q&&Math.abs(h.contr)>.5?"consumed · not invested":h.contr<-.5&&h.overflow<=.5?"all surplus prepaid debt":null},{key:"appr",l:"Portfolio growth",b:h.ownGrowth,r:h.portGrowth,credit:!0}]})(),...(()=>{const t=c===0,r=I.exitAt(L-1),a=I.exitAt(t?0:L),n=(p,u)=>({end:u,start:p}),s=p=>Math.abs(p)<.5?0:p,o=p=>p.cgTax+p.recapTax+p.ownInvLTCG-p.suspRelease-p.deferShelter;return[{g:t?"Balance sheet · at purchase":"Balance sheet · "+(jn==="range"?"start â end":"end")+" of yr "+L},{key:"balSheet",l:"Assets",bSE:n(r.mktE,a.mktE),rSE:n(r.rentPre,a.rentPre)},{key:"balSheet",l:"Liabilities",bSE:n(-r.payoff,-a.payoff),rSE:null,r:null,neg:!0},...r.ownerSurp>.5||a.ownerSurp>.5?[{key:"balSheet",l:"Surplus investments",note:"after debts",bSE:n(r.ownerSurp,a.ownerSurp),rSE:null,r:null}]:[],{key:"balSheet",l:"Unrealized gain",note:"if sold",bSE:n(r.homeGain,a.homeGain),rSE:n(r.eqGain,a.eqGain)},...X?[{key:"balSheet",l:"Suspended losses",note:"§469",bSE:n(I.years[Math.max(1,L-1)].suspCum*(L>1?1:0),t?0:I.years[L].suspCum),rSE:null,r:null},{key:"balSheet",l:"Cumulative depreciation",bSE:n(r.cumDeprec,a.cumDeprec),rSE:null,r:null}]:[],{key:"balSheet",l:"Net worth · pre-liq",bSE:n(r.buyPre,a.buyPre),rSE:n(r.rentPre,a.rentPre),tot:!0},{key:"liq",l:"Selling costs",note:qt+"%",bSE:n(s(-r.sc),s(-a.sc)),rSE:null,r:null},{key:"liq",l:"Taxes due on sale",note:"if sold",bSE:n(s(-o(r)),s(-o(a))),rSE:n(s(-r.exitLTCG),s(-a.exitLTCG))},{key:"liq",l:"Net worth · post-liq",bSE:n(r.buyAT,a.buyAT),rSE:n(r.rentAT,a.rentAT),tot:!0}]})()],Ei=t=>t===null?e.jsx("span",{className:"bvr-dash",children:"â"}):t===0?e.jsx("span",{className:"bvr-dash",children:"$0"}):i(t),Ai=({end:t,start:r})=>{const a=t-r;return t===r?e.jsx("span",{className:"bvr-secell",children:e.jsx("span",{className:"bvr-senum"+(t<0?" neg":""),children:i(t)})}):e.jsx("span",{className:"bvr-secell",children:jn==="range"?e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"bvr-senum",children:i(r)}),e.jsx("span",{className:"bvr-searr","aria-hidden":"true",children:"â"}),e.jsx("span",{className:"bvr-senum"+(t<0?" neg":""),children:i(t)})]}):e.jsxs(e.Fragment,{children:[e.jsxs("em",{className:"bvr-bsd "+(a>=-.5?"up":"dn"),children:[a>
13=-.5?"+":"â",i(Math.abs(a)).replace("$-","$")]}),e.jsx("span",{className:"bvr-senum"+(t<0?" neg":""),children:i(t)})]})})},Na=N.buyAT>=N.rentAT,Ri=(t,r,a)=>t.map((n,s)=>n.g?e.jsx("span",{className:"bvr-sg",children:n.g},s):e.jsxs(Hi.Fragment,{children:[e.jsx("span",{className:"num bvr-sb clickable"+(n.credit?" credit":"")+(n.tot?" tot":"")+(n.sub?" sub":"")+(n.bSE?" se":"")+(a?" tone-buy":""),onClick:o=>{F({kind:"line",key:n.key,focus:n.focus},o),r&&r(n,o)},children:n.bSE!=null?Ai(n.bSE):Ei(n.b)}),e.jsxs("span",{className:"bvr-slab clickable"+(n.tot?" tot":"")+(n.sub?" sub":""),onClick:o=>{F({kind:"line",key:n.key,focus:n.focus},o),r&&r(n,o)},children:[n.l,n.note&&e.jsx("em",{className:"bvr-tag",children:n.note}),n.pill]}),e.jsx("span",{className:"num bvr-sr clickable"+(n.credit?" credit":"")+(n.tot?" tot":"")+(n.sub?" sub":"")+(n.rSE?" se":"")+(a?" tone-rent":""),onClick:o=>{F({kind:"line",key:n.key,focus:n.focus},o),r&&r(n,o)},children:n.rSE!=null?Ai(n.rSE):Ei(n.r)})]},s)),zi=Math.abs(N.buyAT-N.rentAT),Sa=N.buyPre>=N.rentPre,gc=Math.abs(N.buyPre-N.rentPre),Pi=t=>e.jsx("div",{className:"bvr-hz bvr-hz-below",children:(()=>{const r=ll,a=Math.max(1,y-r),n=(c-r)/a,s=yt.flip,o=s!=null&&s>x+.5&&s<=y,b=o?(s-r)/a:0,p=I.preLiqCross,u=p>0&&p>x+.5&&p<=y,f=u?(p-r)/a:0,S=[25,30,40,50],k=D=>{cancelAnimationFrame(le.current);const Ie=y,ye=performance.now(),se=450,wt=kt=>{const ct=Math.min(1,(kt-ye)/se),Lt=1-Math.pow(1-ct,3);A(Math.round(Ie+(D-Ie)*Lt)),ct<1&&(le.current=requestAnimationFrame(wt))};le.current=requestAnimationFrame(wt)},j=D=>{if(w(D),D>=y)if(q.current.armed){const Ie=S.find(ye=>ye>y);Ie&&k(Math.min(rt,Ie)),q.current={touched:!1,armed:!1}}else q.current.touched=!0;else D<=y-3&&q.current.touched&&(q.current={touched:!1,armed:!0})},T=D=>{const Ie=D.currentTarget.getBoundingClientRect(),ye=(D.clientX-Ie.left)/Ie.width;j(Math.max(r,Math.min(y,Math.round(r+ye*a))))},B={ArrowRight:1,ArrowUp:1,ArrowLeft:-1,ArrowDown:-1,PageUp:5,PageDown:-5},P=D=>{if(D.metaKey||D.ctrlKey||D.altKey)return;if(D.key==="Enter"||D.key===" "){D.preventDefault(),D.stopPropagation(),F({kind:"help",key:"domain"},D);return}const Ie=B[D.key],ye=typeof Ie=="number"?c+Ie:D.key==="Home"?r:D.key==="End"?y:null;ye!=null&&(D.preventDefault(),D.stopPropagation(),j(Math.max(r,Math.min(y,ye))))},de=30,ae=96,je=Math.max(de,Math.min(n*ue,ue-de)),fe=je-de,Me=ue-je-de,Ce=c<10&&Me>=ae?"right":fe>=ae?"left":"hide",Ee=Ce==="right"?je+de+Me/2:fe/2;return e.jsxs("div",{className:"hzbar hzbarB",ref:Z,role:"slider",tabIndex:0,"aria-label":"Holding period","aria-orientation":"horizontal","aria-valuemin":r,"aria-valuemax":y,"aria-valuenow":c,"aria-valuetext":c===0?"at purchase":"year "+ft(c)+" (horizon year "+c+" of "+y+")",onKeyDown:P,onPointerDown:D=>{D.currentTarget.setPointerCapture(D.pointerId),T(D)},onPointerMove:D=>{D.buttons&&T(D)},onDoubleClick:D=>F({kind:"help",key:"domain"},D),title:"Double-tap (or press Enter) to set the year range",children:[e.jsx("div",{className:"hzfillB",style:{width:n*100+"%"}}),o&&e.jsx("div",{className:"hzbe cash",style:{left:b*100+"%"},title:"Cash break-even "+ft(Math.round(s))}),o&&Math.abs(b-n)<=.02&&b>.04&&b<.96&&e.jsx("span",{className:"hzbeC num above",style:{left:b*100+"%"},children:"Cash BE yr "+Math.round(s)}),u&&e.jsx("div",{className:"hzbe wealth",style:{left:f*100+"%"},title:"Wealth break-even "+ft(p)}),u&&Math.abs(f-n)<=.02&&f>.04&&f<.96&&e.jsx("span",{className:"hzbeC num below",style:{left:f*100+"%"},children:"Wealth BE yr "+p}),Ce!=="hide"&&e.jsx("span",{className:"hzcap"+(Ce==="right"?" onright":""),style:{left:Ee+"px"},children:"Holding period"},Ce),e.jsx("span",{className:"hzthumb compact num",style:{left:`clamp(${c===0?44:30}px, ${n*100}%, calc(100% - 30px))`},children:c===0?"at purchase":String(ft(c))}),y>=rt&&e.jsxs("span",{className:"hzmaxtag",children:["max ",rt]})]})})()},t),Ta=5,Dn=d.useRef(null),[Qs,kr]=d.useState(!1),[Ca,Li]=d.useState({scn:!0,fin:!0,own:!0,tax:!0}),Oi=[{id:"scn",label:"Price and market rates",fields:["price","appr","rentMo","rentInfl","cashRet","eqRet","mixEq","closingD","closingPct","multiFam","units","unitRentMo","invShare","bldgRatio","recapRate","vacancy","cllRegion","hasPassiveIncome"]}
13,{id:"fin",label:"Financing",fields:["downDollar","loans","balancer","cashExpanded","cashPortion","embGain"]},{id:"own",label:"Cost of ownership",fields:["propTaxRate","assessCap","assessCapOn","maintR","capexR","insurance","insurancePct","insuranceMode","hoa","hoaInfl","rins","sellPct"]},{id:"tax",label:"Tax assumptions",fields:["ord","ltcg","filingStatus","taxRegion","ptDedMode","ptDedCap","ptDedPct","amtOn","saltAgiOverride","investSavings","excl"]}],fc=()=>{const t=Dn.current;if(!t){kr(!1);return}const r=ar();Oi.forEach(n=>{Ca[n.id]&&n.fields.forEach(s=>{r[s]=t[s]})}),r.loans=r.loans.map(n=>({...n}));const a={price:r.price,down:r.downDollar};r.loans.forEach(n=>{a[n.id]=n.amount}),ma(a,r.balancer),r.price=a.price,r.downDollar=a.down,r.loans=r.loans.map(n=>({...n,amount:a[n.id]!=null?a[n.id]:n.amount})),Ma(n=>[...n,ar()].slice(-Ta)),Ea([]),Fn(r),kr(!1)},eo=d.useRef(null);d.useEffect(()=>{Dn.current||(Dn.current=ar()),eo.current||(eo.current={E:c,hMin:x,hMax:y,sec:ce})},[]);const yc=()=>{const t=Dn.current,r=eo.current;if(!t){kr(!1);return}Ma(a=>[...a,ar()].slice(-Ta)),Ea([]),Fn({...t,loans:t.loans.map(a=>({...a}))}),r&&(w(r.E),v(r.hMin),A(r.hMax),ge({...r.sec})),Za(!1),Er(!1),fa(!1),Es(null),wa(null),Rs(!1),zs.current=!1,kr(!1)};d.useEffect(()=>{if(!Qs)return;const t=r=>{r.key==="Escape"&&(r.stopPropagation(),kr(!1))};return window.addEventListener("keydown",t,!0),()=>window.removeEventListener("keydown",t,!0)},[Qs]);const[wc,Ma]=d.useState([]),[Bi,Ea]=d.useState([]),Aa=d.useRef(null),to=d.useRef(!1),ro=d.useRef(null),ar=()=>({price:G,downDollar:re,appr:Ve,rentMo:ht,rentInfl:Mt,closingD:ca,closingPct:Yr,cashExpanded:xo,cashPortion:Et,embGain:hr,multiFam:X,units:At,unitRentMo:Ur,invShare:Ge,bldgRatio:Rt,recapRate:Ar,vacancy:Vr,cllRegion:pa,hasPassiveIncome:Oe,propTaxRate:Ht,assessCap:bt,assessCapOn:_e,maintR:Kt,capexR:Jt,insurance:br,insurancePct:ur,insuranceMode:ut,hoa:vr,hoaInfl:ha,rins:_r,sellPct:qt,ord:ne,ltcg:be,filingStatus:ba,taxRegion:Xe,ptDedMode:Gt,ptDedCap:Qt,ptDedPct:mr,amtOn:et,saltAgiOverride:hn,investSavings:Q,excl:ot,cashRet:er,eqRet:Wt,mixEq:Ke,loans:te,balancer:K}),Fn=t=>{typeof document<"u"&&document.activeElement instanceof HTMLElement&&document.activeElement.blur(),to.current=!0,vo(t.price),mo(t.downDollar),Qa(t.appr),da(t.rentMo),en(t.rentInfl),Qn(t.closingD),es(t.closingPct),Er(t.cashExpanded),pr(t.cashPortion),go(t.embGain),fo(t.multiFam),yo(t.units),ko(t.unitRentMo),rn(t.invShare),jo(t.bldgRatio),No(t.recapRate),ts(t.vacancy),wo(t.cllRegion),tn(t.hasPassiveIncome),an(t.propTaxRate),nn(t.assessCap),sn(t.assessCapOn),on(t.maintR),ln(t.capexR),as(t.insurance),cn(t.insurancePct),rs(t.insuranceMode),So(t.hoa),To(t.hoaInfl),Co(t.rins),Mo(t.sellPct),Zt(t.ord),Rr(t.ltcg),Ro(t.filingStatus),Eo(t.taxRegion),ns(t.ptDedMode),ss(t.ptDedCap),os(t.ptDedPct),is(!!t.amtOn),zo(!!t.saltAgiOverride),Xr(t.investSavings),dn(t.excl),ls(t.cashRet),cs(t.eqRet),Po(t.mixEq),Oo(t.loans),fn(t.balancer),Aa.current=t},Ii=()=>{Ma(t=>{if(t.length===0)return t;const r=t[t.length-1],a=ar();return Ea(n=>[a,...n].slice(0,Ta)),Fn(r),t.slice(0,-1)})},Di=()=>{Ea(t=>{if(t.length===0)return t;const r=t[0],a=ar();return Ma(n=>[...n,a].slice(-Ta)),Fn(r),t.slice(1)})},kc=()=>{Bi.length>0?Di():Ii()},jc=JSON.stringify(ar());return d.useEffect(()=>{if(Aa.current===null){Aa.current=ar();return}if(to.current){to.current=!1;return}return clearTimeout(ro.current),ro.current=setTimeout(()=>{const t=Aa.current;Ma(r=>[...r,t].slice(-Ta)),Ea([]),Aa.current=ar()},400),()=>clearTimeout(ro.current)},[jc]),d.useEffect(()=>{const t=a=>{if(!a)return!1;const n=a.tagName;if(n==="TEXTAREA")return!0;if(n==="INPUT"){const s=(a.type||"text").toLowerCase();return s==="text"||s==="number"||s==="search"||s==="email"||s==="url"||s==="tel"||s==="password"}return a.isContentEditable===!0},r=a=>{if((a.key==="ArrowLeft"||a.key==="ArrowRight")&&!a.metaKey&&!a.ctrlKey&&!a.altKey&&!a.shiftKey){const f=a.target,S=f&&f.tagName;if(!(t(f)||S==="SELECT"||S==="INPUT"||f&&f.isContentEditable)){a.preventDefault(),w(j=>Math.max(x<=1?0:x,Math.min(y,j+(a.key==="ArrowRight"?1:-1))));return}}if(!(a.metaKey||a.ctrlKey))return;const s=a.key.toLowerCase(),o=a.code==="Slash",b=s==="z"&&a.shiftKey||o&&a.shiftKey,p=s==="y",u=s==="z"&&!a.shift
13Key;!b&&!p&&!u||u&&t(a.target)||(a.preventDefault(),a.stopPropagation(),b?kc():p?Di():Ii())};return window.addEventListener("keydown",r),()=>window.removeEventListener("keydown",r)},[wc,Bi,x,y]),e.jsxs("div",{className:"bvr-root",children:[e.jsx("style",{dangerouslySetInnerHTML:{__html:Zc}}),e.jsxs("div",{className:"bvr-body"+(_a?" railfolded":"")+(Fs?" funderror":""),children:[e.jsxs("div",{className:"bvr-titlefabs",children:[e.jsx("button",{className:"bvr-head-cog"+(al?" faded":""),onClick:il,"aria-label":"Jump to assumptions",children:e.jsx(Nc,{size:16})}),e.jsx("button",{className:"bvr-mobilefab bvr-slfab"+(xt?" on":""),onClick:()=>fa(t=>!t),"aria-label":"Toggle sticky sliders panel",children:e.jsx($a,{size:16,className:"bvr-slrot"})})]}),e.jsxs("div",{className:"bvr-cards-wrap",children:[Fs&&e.jsx("div",{className:"bvr-fundoverlay",role:"alert",children:e.jsxs("div",{className:"bvr-fundoverlay-card",children:[e.jsx("div",{className:"bvr-fundoverlay-h",children:"Inputs are incomplete"}),e.jsx("div",{className:"bvr-fundoverlay-sub",children:"Financing must equal the purchase price before the projection is valid."}),e.jsxs("div",{className:"bvr-fundoverlay-rows",children:[e.jsx("div",{className:"bvr-fundoverlay-sec",children:"Purchase"}),e.jsxs("div",{className:"bvr-fundoverlay-row",children:[e.jsx("span",{children:"Purchase price"}),rr==="price"&&$s,e.jsx("b",{className:"num",children:i(G)})]}),qe>0&&e.jsxs("div",{className:"bvr-fundoverlay-row sub",children:[e.jsx("span",{children:"+ up-front costs (cash at close)"}),e.jsx("b",{className:"num",children:i(qe)})]}),e.jsx("div",{className:"bvr-fundoverlay-sec",children:"Financing"}),e.jsxs("div",{className:"bvr-fundoverlay-row",children:[e.jsxs("span",{children:["Down payment",K==="down"&&e.jsx("em",{className:"bvr-autotag",children:"auto"})]}),rr==="down"&&$s,e.jsx("b",{className:"num",children:i(re)})]}),te.filter(t=>t.amount>.5||K===t.id).map(t=>e.jsxs("div",{className:"bvr-fundoverlay-row",children:[e.jsxs("span",{children:[t.kind==="secured"?t.type:t.name,K===t.id&&e.jsx("em",{className:"bvr-autotag",children:"auto"})]}),rr===t.id&&$s,e.jsx("b",{className:"num",children:i(t.amount)})]},t.id)),e.jsxs("div",{className:"bvr-fundoverlay-row bvr-fundoverlay-tot",children:[e.jsx("span",{children:"Total financing"}),e.jsx("b",{className:"num",children:i(Ds)})]}),e.jsxs("div",{className:"bvr-fundoverlay-row bvr-fundoverlay-gap",children:[e.jsx("span",{children:Ze<0?"Over-funded by":"Short by"}),e.jsx("b",{className:"num",children:i(Math.abs(Ze))})]})]}),e.jsx("div",{className:"bvr-fundoverlay-hint",children:Ze<0?"Lower the mortgage, or raise the down payment or price, to rebalance.":"Raise a loan or the down payment, or lower the price, to rebalance."})]})}),e.jsxs("div",{ref:Va,className:"bvr-titleblock",children:[e.jsxs("div",{className:"bvr-titleblockl",children:[e.jsx("div",{className:"bvr-eyebrow",children:"Wealth building"}),e.jsx("h1",{className:"bvr-h1",children:"Buy vs. Rent?"})]}),e.jsx("button",{className:"bvr-stickytog-title"+(xt?" on":"")+(Wr?"":" floatwhenunpinned"),onClick:()=>fa(t=>!t),"aria-pressed":xt,title:"Sticky sliders","aria-label":"Toggle sticky sliders",children:e.jsx($a,{size:15,className:"bvr-slrot"})})]}),e.jsxs("div",{className:"bvr-panel bvr-vpanel",ref:la,children:[e.jsxs("div",{className:"bvr-panelh",children:[e.jsx("span",{className:"bvr-ptitle",children:e.jsx("span",{className:"bvr-ptag",children:"Yearly cash requirements"})}),e.jsx("span",{className:"bvr-yv2netyr yv2netyr-mob num",children:"yr "+L+", "+ft(L)})]}),(()=>{const t=yt.rows[L],r=(t.intS||0)>.5,a=X?r?360:320:r?300:260,n=X?Oe?t.depBen||0:(t.passiveLoss||0)*(ne/100):0,s=et||fr,o=s?t.ptBenPre||0:t.ptBen,b=l=>{const g=l.dispV!=null?l.dispV:l.v;return Math.abs(g)<.5?"$0":(l.credit?"â":"")+xe(g)},p=t.keptB+t.intG+t.pt+t.mci,u=t.taxBen+o+t.netRental+n+(t.facAccr||0),f=a/Math.max(1,p+u),S=l=>l*f,k=S(p),j=[Q?{k:"extra",v:t.extra||0,pop:"surplus",popFocus:"surplus",label:"Homebuyer net surplus",short:"Homebuyer net surplus",sub:"pay down debt/invest the savings",subSwitch:(t.extra||0)>.5,kept:!0,tone:"credit",tuckRight:!0}:{k:"extra",v:t.extra||0,pop:"surplus",popFocus:"surplus",label:"Homebuyer net surplus",short:"Homebuyer net surplus",sub:"pay down debt/invest the savings",subSwitch:(t.extra||0)>.5,consumed:!0,tone:"muted",tuckRight:!0},{k:"prin",v:t.principal,pop:"surplus",popFocus:"prin",label:"Principal paydown",abbr:"Principal",short:"Principal",sub:"mortgage payment",kept:!0,tone:"buy"},{k:"int",v:t.intM,pop:"mortPI",label:"Interest",short:"Interest",sub:"on the mortgage",lite:!0},{k:"sint",v:t.intS||0,pop:"facInt",label:"Synth. loan interest",short:"Synth. loan",sub:t.facAccr>.5?"accrues · no payments":"implied · box spread",lite:!0}
13,{k:"mci",v:t.mci,pop:"maintcapex",label:"Maintenance & capex",abbr:"Maint. & capex",short:"Maint.",sub:X?`${Js} set-aside, insurance & HOA`:`${Js} set-aside & insurance`},{k:"pt",v:t.pt,pop:"propTax",label:"Property tax",short:"Property tax",sub:_e&&bt<Ve?Te==="California"?"Prop 13 base":"capped base":"on market value"}],T=[{k:"acc",v:t.facAccr||0,pop:"facInt",label:"Implied financing accrues",short:"Synth. loan",sub:"synth. loan â no payment",credit:!0,hatchTone:"neg"},{k:"tb",v:t.taxBen,pop:"taxBen",label:"Interest deduction",short:"Interest ded.",sub:I.hasSynDed?"mortgage + synth. loan":"qual. mortgage only",credit:!0},{k:"nr",v:t.netRental||0,pop:"rental",label:"Rental income",short:"Rental",sub:"net of tax",credit:!0},s?{k:"ptb",v:o,dispV:0,pop:"saltDed",label:"SALT deduction",short:"SALT ded.",sub:et?"lost to AMT":"AGI too high",credit:!0,suspended:!0,forceShow:(t.pt||0)>.5&&o<=.5}:{k:"ptb",v:t.ptBen,pop:"saltDed",label:"SALT deduction",short:"SALT ded.",sub:Is?"cap set to $0":"property tax impact",credit:!0,suspended:Is&&t.ptBen<=.5?!0:void 0,forceShow:(t.pt||0)>.5&&t.ptBen<=.5},Oe?{k:"dep",v:n,pop:"dep",label:"Depreciation deduction",short:"Deprec. ded.",sub:"vs REP / passive income",credit:!0}:{k:"dep",v:n,pop:"dep",label:"Depreciation deduction",short:"Deprec. ded.",sub:"suspended · §469",credit:!0,suspended:!0}],B=[Q?{k:"kept",v:t.keptR||0,pop:"surplus",popFocus:"surplus",label:"Renterâs surplus",short:"Surplus",sub:(t.keptR||0)>.5?"invest the savings":"invested at your mix",subSwitch:(t.keptR||0)>.5,kept:!0,tone:"credit"}:{k:"kept",v:t.keptR||0,pop:"surplus",popFocus:"surplus",label:"Renterâs surplus",short:"Surplus",sub:(t.keptR||0)>.5?"invest the savings":"consumed, not saved",subSwitch:(t.keptR||0)>.5,consumed:!0},{k:"burn",v:t.burnR,pop:"rent",label:`${xe(h.rent/12)}/mo`,short:"Rent",sub:""}],P=(l,g)=>l.consumed||l.suspended?"repeating-linear-gradient(45deg, color-mix(in srgb, var(--faint) 40%, transparent) 0 1.5px, transparent 1.5px 5px), color-mix(in srgb, var(--faint) 8%, var(--card))":l.kept?`repeating-linear-gradient(45deg, color-mix(in srgb, var(--${l.tone||g}) 55%, transparent) 0 1.5px, transparent 1.5px 5px), color-mix(in srgb, var(--${l.tone||g}) 12%, var(--card))`:l.credit?`repeating-linear-gradient(45deg, color-mix(in srgb, var(--${l.hatchTone||"credit"}) 55%, transparent) 0 1.5px, transparent 1.5px 5px), color-mix(in srgb, var(--${l.hatchTone||"credit"}) 12%, var(--card))`:l.lite?`color-mix(in srgb, var(--${g}) 92%, white)`:`var(--${g})`,de=(l,g)=>l.consumed||l.suspended?{background:"repeating-linear-gradient(45deg, color-mix(in srgb, var(--faint) 22%, transparent) 0 1px, transparent 1px 6px), color-mix(in srgb, var(--faint) 5%, var(--card))",boxShadow:"inset 0 0 0 0.75px color-mix(in srgb, var(--faint) 42%, transparent)"}:l.kept?{background:`repeating-linear-gradient(45deg, color-mix(in srgb, var(--${l.tone||g}) 30%, transparent) 0 1px, transparent 1px 6px), color-mix(in srgb, var(--${l.tone||g}) 8%, var(--card))`,boxShadow:`inset 0 0 0 0.5px color-mix(in srgb, var(--${l.tone||g}) 40%, transparent)`}:l.credit?{background:`repeating-linear-gradient(45deg, color-mix(in srgb, var(--${l.hatchTone||"credit"}) 30%, transparent) 0 1px, transparent 1px 6px), color-mix(in srgb, var(--${l.hatchTone||"credit"}) 8%, var(--card))`,boxShadow:`inset 0 0 0 0.5px color-mix(in srgb, var(--${l.hatchTone||"credit"}) 40%, transparent)`}:l.lite?{background:`color-mix(in srgb, var(--${g}) 92%, white)`}:{background:`var(--${g})`},ae=38,je=52,fe=5,Me=T.some(l=>l.v>.5),pe=!Pt&&Me,Ce=Me,Ee=Math.max(0,Math.min(S(t.buyOut),k)),D=Ce?S(t.extra||0):0,Ie=t.principal+t.intG+t.pt+t.mci,ye=Pt?69:Math.max(66,Math.min(110,Math.floor((pc-96)/3))),se={};{let l=0;j.concat(T).forEach(g=>{const Y=S(g.v);se[g.k]=l+Y/2,l+=Y})}Ce&&(se.extra=k-Ee-D/2);const wt=k-S(t.keptR+t.burnR),kt={};{let l=0;B.forEach(g=>{const Y=S(g.v);kt[g.k]=wt+l+Y/2,l+=Y})}const ct=Pt&&t.taxBen>.5&&o>.5,Lt=ct&&n>.5,Ot=Pt&&(t.facAccr||0)>.5,tt=Pt&&(t.extra||0)>.5,Ut=tt&&wt>=ae+fe+2,C=tt?j.filter(l=>l.k!=="extra"):j,_=T.filter(l=>!(Ot&&l.k==="acc")&&!(ct&&l.k==="ptb")&&!(Lt&&l.k==="dep")),we=[...C,..._],U=oo(C.concat(_).filter(l=>l.v>
13.5||l.forceShow).map(l=>({k:l.k,y:se[l.k],h:ae})),a,fe),ie=[],dt=tt?Ut?Math.max(ae/2,Math.min(se.extra??0,wt-ae/2-fe)):Math.max(ae/2,Math.min(se.extra??0,a-ae/2)):null;tt&&ie.push({k:"extra",y:dt,h:ae}),ie.push({k:"kept",y:kt.kept,h:je}),t.burnR>.5&&ie.push({k:"burn",y:kt.burn,h:je});const Ae=[];Ot&&Ae.push({k:"acc",y:se.acc,h:ae}),ct&&Ae.push({k:"ptb",y:se.ptb,h:ae}),Lt&&Ae.push({k:"dep",y:se.dep,h:ae});const Vt=k+ae/2+fe;Ae.forEach(l=>{l.y<Vt&&(l.y=Vt)});const nr=Math.max(a,(Ae.length?Math.max(...Ae.map(l=>l.y)):0)+ae/2+fe),he={...oo(ie,a,fe),...oo(Ae,nr,fe)},sr=Math.max(a,...Ae.map(l=>(he[l.k]??l.y)+ae/2+fe)),jr=16,Nr=30,$n=10,_t={};{let l=k;T.forEach(g=>{_t[g.k]=l,l+=S(g.v)})}const $r={};{let l=k+6;T.forEach(g=>{const Y=S(g.v);if(Y<=.5||pe&&Y>=34)return;pe&&(l=Math.max(l,(_t[g.k]??l)+2));const V=Y>=22?jr:Nr;$r[g.k]=l,l+=V+$n})}const Hn=l=>{var $;const Y=S((($=T.find(O=>O.k===l))==null?void 0:$.v)||0)>=22?jr:Nr;if(pe){const O=_t[l];return O==null?0:O+Y}const V=$r[l];return V==null?0:V+Y},Ra=T.reduce((l,g)=>Math.max(l,Hn(g.k)),0),za=k+T.reduce((l,g)=>l+S(g.v),0),Pa=it?za:pe?Math.max(za,Ra):Ra,La=54,m=6,M=t.keptR>.5||(t.extra||0)>.5,Re=[(Ce?D:0)-m,k-Ee-D-m,k-S(t.keptR+t.burnR)-(M?La:0)],ve=pe?Math.max(0,Math.min(...Re)):0,Bt=Pt?a:Math.max(a,Pa+8)-ve,Sr=(l,g)=>{const Y=l.findIndex(O=>O.v>.5),V=g&&Ce?l.findIndex(O=>O.v>.5&&O.k!=="extra"):-1;let $=-1;for(let O=l.length-1;O>=0;O--)if(l[O].v>.5){$=O;break}return l.map((O,me)=>{const J=S(O.v),ze=me===Y||me===V,ee=me===$,$e=!Pt&&ze&&J>.5&&J<22,He=!Pt&&ee&&!ze&&(g?!O.credit:!0)&&J>.5&&J<32,We=$e||He,Qe=(Pt||pe)&&g&&O.credit&&J>=34,Ft=g&&O.credit&&J>.5&&!(pe&&Qe),Xt=g&&O.credit&&J>=22,ir=xe(O.v),Pe=qn=>((O.short||"").length*.56+ir.length*.62)*qn+13<=ye,Oa=pe?!1:We;let oe,$t=!1;return Oa?oe="":Qe?oe="two":Xt?oe="valonly":Ft?oe="":J>=34?oe="two":pe?J>=20&&Pe(10.5)?oe="one":J>=22?(oe="two",$t=!0):J>=11&&Pe(9)?(oe="one",$t=!0):J>=9?(oe="valonly",$t=!0):oe="":oe=J>=20?"one":J>=14?"valonly":"",{h:J,isFirst:ze,isLast:ee,isEnd:ze||ee,wantEndCallout:pe?We&&oe==="":We,wantSideCallout:Ft,creditValInline:Xt,inline:oe,tiny:$t}})},It=Sr(j.concat(T),!0),jt=Sr(B,!1),Nt={};j.concat(T).forEach((l,g)=>{Nt[l.k]=It[g].inline!==""}),B.forEach((l,g)=>{Nt[l.k]=jt[g].inline!==""});const Dt=(l,g)=>l.suspended||l.consumed?{color:"var(--muted)"}:l.kept?{color:`var(--${l.tone||g})`}:l.credit?{color:`var(--${l.hatchTone||"credit"})`}:null,pt=(l,g,Y)=>{const V=g.subSwitch?"div":"button";return e.jsxs(V,{"data-k":g.k,"data-side":l,"data-credit":g.credit?"1":"0","data-tuck":g.credit||g.tuckRight?"1":"0","data-tone":g.suspended?"muted":g.tone||l,className:"bvr-lgrow"+(g.v<=.5&&!g.forceShow?" collapsed":"")+(un===g.k?" hovlit":""),style:{top:Y},role:g.subSwitch?"button":void 0,tabIndex:g.subSwitch?0:void 0,onKeyDown:g.subSwitch?$=>{($.key==="Enter"||$.key===" ")&&($.preventDefault(),F({kind:"line",key:g.pop,focus:g.popFocus},$))}:void 0,onClick:$=>F({kind:"line",key:g.pop,focus:g.popFocus},$),children:[e.jsx("span",{className:"bvr-lgchip",style:{background:P(g,l)}}),e.jsxs("span",{className:"bvr-lgtxt",children:[e.jsx("em",{style:Dt(g,l),children:Xn&&g.abbr?g.abbr:g.label}),e.jsx("small",{className:g.subSwitch?"has-sw":void 0,children:g.subSwitch?e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"t",children:g.sub}),ke(Q,$=>{$.stopPropagation(),Xr(O=>!O)},"Invest the savings "+(Q?"on":"off"))]}):g.sub})]}),!Nt[g.k]&&e.jsx("b",{className:"num",style:Dt(g,l),children:b(g)})]},g.k)},or=(l,g)=>{const Y=l.kept?"kept":l.consumed?"consumed":l.credit?"credit"+(l.hatchTone==="neg"?" creditneg":""):"burn",{h:V,isFirst:$,isEnd:O,wantEndCallout:me,wantSideCallout:J,creditValInline:ze,inline:ee,tiny:$e}=It[g],He=$e?" t2":"",We=b(l);return e.jsxs("div",{"data-k":l.k,"data-tone":l.tone||"buy",className:"bvr-yv2seg clickable "+Y+(l.suspended?" suspended":""),style:{height:V,...de(l,"buy")},onMouseEnter:()=>vn(l.k),onMouseLeave:()=>vn(Qe=>Qe===l.k?null:Qe),onClick:Qe=>F({kind:"line",key:l.pop,focus:l.popFocus},Qe),children:[ee==="two"&&e.jsxs("div",{className:"bvr-yv2sinner"+(V<64?" tight":"")+He,children:[e.jsx("span",{className:"bvr-yv2slbl",children:l.short}),e.jsx("b",{className:"num",children:We})]}),ee==="one"&&e.jsxs("div",{className:"bvr-yv2sinner one-line"+He,children:[e.jsx("span",{className:"bvr-yv2slbl",children:l.short}),e.jsx("b",{className:"num",children:We})]}),ee==="valonly"&&e.jsx("div",{className:"bvr-yv2sinner"+He,children:e.jsx("b",{className:"num",children:We})}),O&&!l.credit&&e.jsxs("div",{className:"bvr-yv2cout "+($?"above":"below")+" "+Y+(me?" visible":""),children:[e.jsx("em",{children:l.short}),e.jsx("b",{className:"num",children:We})]}),J&&(()=>{const Qe=$r[l.k],Ft=_t[l.k],Xt=Qe!=null&&Ft!=null?Qe-Ft:8,ir=Xt+8;return e.jsxs(e.Fragment,{children:[e.jsx("svg",{className:"bvr-yv2sideconn",width:pe?12:30,height:Math.max(V,1),style:{position:"absolute",left:"100%",top:0,overflow:"visible",pointerEvents:"none"},"aria-hidden":"true",children:e.jsx("path",{d:(()=>{const Pe=pe?12:30;return`M 0 ${V/2} C ${Pe/2} ${V/2}, ${Pe/2} ${ir}, ${Pe} ${ir}`})(),fill:"none",stroke:l.hatchTone==="neg"?"var(--neg)":"var(--credit)",strokeWidth:"1",strokeOpacity:"0.55"})}),e.jsxs("div",{className:"bvr-yv2sidecout "+Y+(l.suspended?" suspended":"")+" visible "+(ze?"labelonly":"twoline"),style:{to
13p:Xt+"px"},children:[e.jsx("em",{children:pe&&l.short||l.label}),!ze&&e.jsx("b",{className:"num",children:We})]})]})})()]},l.k)};return e.jsx("div",{className:"bvr-yv2",ref:Si,children:e.jsxs("div",{className:"bvr-yv2grid"+(it?" yv2simple":"")+(it&&Me?" yv2chips":"")+(pe?" yv2tri":""),style:pe?{"--yv2cw":ye+"px"}:void 0,children:[ra&&e.jsx("svg",{className:"bvr-yv2lines",width:ra.w,height:ra.h,viewBox:`0 0 ${ra.w} ${ra.h}`,children:ra.lines.map(l=>{const g=l.tone==="muted"?"var(--line2)":l.credit||l.tone==="credit"?"var(--credit)":l.tone==="rent"?"var(--rent)":l.tone==="buy"?"var(--buy)":"var(--line2)",Y=(l.x1+l.x2)/2;return e.jsx("path",{"data-k":l.k,className:un===l.k?"hovlit":void 0,d:`M ${l.x1} ${l.y1} C ${Y} ${l.y1}, ${Y} ${l.y2}, ${l.x2} ${l.y2}`,fill:"none",stroke:g,strokeWidth:"1",strokeOpacity:"0.35"},l.side+l.k)})}),e.jsxs("div",{className:"bvr-lghead",style:{color:"var(--buy)",...Me?{width:ye*2+8}:null},children:[e.jsx(Tr,{size:13}),"Buy"]}),e.jsxs("div",{className:"bvr-lghead right",style:{color:"var(--rent)"},children:[e.jsx(Ia,{size:13}),"Rent"]}),(()=>{const l=T.some(Y=>Y.v>.5),g=(Y,V,$,O)=>e.jsxs("button",{className:"bvr-yv2tot "+Y,onClick:me=>F({kind:"line",key:O},me),children:[e.jsx("b",{className:"num",children:xe(V)}),e.jsx("em",{children:$})]},Y);return e.jsxs("div",{className:"bvr-yv2tots",children:[e.jsx("span",{className:"bvr-yv2tot yv2horizon","aria-label":`Year ${L}`,children:e.jsx("b",{className:"num",children:"yr "+L+", "+ft(L)+":"})}),l?e.jsxs("span",{className:"bvr-yv2totpair",children:[e.jsxs("span",{className:"bvr-yv2mhead",style:{color:"var(--buy)"},children:[e.jsx(Tr,{size:11}),"Buy"]}),g("buy gross",Ie,"gross","buyOut"),e.jsx("span",{className:"bvr-yv2totarrow","aria-hidden":"true",children:"â"}),g("buy net",t.buyOut,"net after tax","buyOut")]}):e.jsxs("span",{className:"bvr-yv2totpair solo",children:[e.jsxs("span",{className:"bvr-yv2mhead",style:{color:"var(--buy)"},children:[e.jsx(Tr,{size:11}),"Buy"]}),g("buy net solo",t.buyOut,"net after tax","buyOut")]}),e.jsxs("span",{className:"bvr-yv2totr",children:[e.jsxs("span",{className:"bvr-yv2mhead",style:{color:"var(--rent)"},children:[e.jsx(Ia,{size:11}),"Rent"]}),g("rent",h.rentOut,"all-in","rent")]})]})})(),e.jsx("div",{className:"bvr-lg",style:{height:a},children:we.map(l=>pt("buy",l,U[l.k]??se[l.k]??0))}),e.jsxs("div",{className:"bvr-yv2bars",style:{height:Bt,"--yv2-base":k-ve+"px"}
13,children:[e.jsx("div",{className:"bvr-yv2zero",style:{top:k},children:e.jsx("span",{className:"num",children:"$0"})}),Me&&e.jsx(e.Fragment,{children:(()=>{const l=ye*3+24,g=ee=>{const $e=[0,ye+8,ye*2+24][ee];return Pt?$e:"calc(50% + "+($e-l/2)+"px)"},Y=Pt?0:pe?26:44,V=it&&un?j.concat(T).find(ee=>ee.k===un&&ee.v>.5):null,$=!!V&&(T.includes(V)||Ce&&V.k==="extra"),O=!!V&&!$&&j.includes(V),me=ee=>ee.suspended?" tone-susp":ee.kept?" tone-kept":ee.consumed?" tone-consumed":ee.credit?ee.hatchTone==="neg"?" tone-creditneg":" tone-credit":" onbar",J=ee=>se[ee.k]+Y-ve,ze=ee=>e.jsxs(e.Fragment,{children:[e.jsx("b",{className:"num",children:b(ee)}),e.jsx("em",{children:ee.short})]});return e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"bvr-yv2topval"+(O?" hov"+me(V):" onbar"),"data-chip":"gross","data-hov":O?V.k:void 0,style:{left:g(0),top:O?J(V):(D+k)/2+Y-ve},children:O?ze(V):e.jsxs(e.Fragment,{children:[e.jsx("b",{className:"num",children:xe(Ie)}),e.jsx("em",{children:"Total"})]})}),e.jsx("div",{className:"bvr-yv2topval"+($?" hov"+me(V):""),"data-chip":"net","data-hov":$?V.k:void 0,style:{left:g(1),top:$?J(V):k-Ee/2+Y-ve},children:$?ze(V):e.jsxs(e.Fragment,{children:[e.jsx("b",{className:"num",children:xe(t.buyOut)}),e.jsx("em",{children:"Net after-tax"})]})}),e.jsxs("div",{className:"bvr-yv2topval onbar","data-chip":"rent",style:{left:g(2),top:k-S(t.burnR)/2+Y-ve},children:[e.jsx("b",{className:"num",children:xe(h.rentOut)}),e.jsx("em",{children:"Rent"})]})]})})()}),e.jsx("div",{className:"bvr-yv2col",style:Ce?{marginTop:D-ve}:ve?{marginTop:-ve}:void 0,children:(Me?j:j.concat(T)).map((l,g)=>Ce&&l.k==="extra"?null:or(l,g))}),Me&&(()=>{const l=($,O,me,J)=>({left:$,top:O,width:Math.hypot(me-$,J-O),transform:"rotate("+Math.atan2(J-O,me-$)+"rad)"}),g=Ee-k+2*D,Y=T.reduce(($,O)=>$+(O.suspended?0:O.v),0),V=Ee+D;return e.jsxs("div",{className:"bvr-yv2col bvr-yv2crcol",style:{marginTop:k-Ee-D-ve},children:[e.jsx("div",{className:"bvr-yv2drop",style:l(-8,g,ye,D)}),e.jsx("div",{className:"bvr-yv2drop",style:l(-(ye+8),V,0,V+S(Y))}),Ce&&(()=>{const $=j.find(O=>O.k==="extra");return $?or($,0):null})(),e.jsxs("div",{className:"bvr-yv2netbox",role:"button",tabIndex:0,onClick:$=>F({kind:"line",key:"buyOut"},$),onKeyDown:$=>{($.key==="Enter"||$.key===" ")&&($.preventDefault(),F({kind:"line",key:"buyOut"},$))},style:{height:Ee},children:[Ee>=16&&e.jsx("b",{className:"num bvr-yv2nbv",children:xe(t.buyOut)}),Ee>=(pe?100:96)?e.jsx("span",{children:"Net after-tax cash required to buy"}):Ee>=(pe?48:44)?e.jsx("span",{"data-abbr":"1",children:"Net-AT-cash required"}):null]}),T.map(($,O)=>or($,j.length+O))]})})(),e.jsxs("div",{className:"bvr-yv2col",style:{marginTop:k-S(t.keptR+t.burnR)-ve,position:"relative"},children:[(t.keptR>.5||(t.extra||0)>.5)&&e.jsxs("div",{className:"bvr-mob-lgswitch"+(Q?"":" off"),onClick:l=>F({kind:"help",key:"mix"},l),role:"button",tabIndex:0,onKeyDown:l=>{(l.key==="Enter"||l.key===" ")&&(l.preventDefault(),F({kind:"help",key:"mix"},l))},children:[e.jsx("span",{className:"bvr-lgswtxt",children:"invest the savings?"}),ke(Q,l=>{l.stopPropagation(),Xr(g=>!g)},"Invest the savings "+(Q?"on":"off"))]}),B.map((l,g)=>{const Y=l.kept?"kept":l.consumed?"consumed":"burn",{h:V,isFirst:$,isEnd:O,wantEndCallout:me,inline:J,tiny:ze}=jt[g],ee=ze?" t2":"",$e=xe(l.v);return e.jsxs("div",{"data-k":l.k,"data-tone":l.tone||"rent",className:"bvr-yv2seg clickable "+Y,style:{height:V,...de(l,"rent")},onMouseEnter:()=>vn(l.k),onMouseLeave:()=>vn(He=>He===l.k?null:He),onClick:He=>F({kind:"line",key:l.pop,focus:l.popFocus},He),children:[J==="two"&&e.jsxs("div",{className:"bvr-yv2sinner"+(V<64?" tight":"")+ee,children:[e.jsx("span",{className:"bvr-yv2slbl",children:l.short}),e.jsx("b",{className:"num",children:$e})]}),J==="one"&&e.jsxs("div",{className:"bvr-yv2sinner one-line"+ee,children:[e.jsx("span",{className:"bvr-yv2slbl",children:l.short}),e.jsx("b",{className:"num",children:$e})]}),J==="valonly"&&e.jsx("div",{className:"bvr-yv2sinner"+ee,children:e.jsx("b",{className:"num",children:$e})}),O&&e.jsxs("div",{className:"bvr-yv2cout "+($?"above":"below")+" "+Y+(me?" visible":""),children:[e.jsx("em",{children:l.label}),e.jsx("b",{className:"num",children:$e})]})]},l.k)})]})]}),e.jsxs("div",{className:"bvr-lg right",style:{height:sr},children:[B.map(l=>pt("rent",l,he[l.k]??kt[l.k]??0)),ct&&(()=>{const l=T.find(g=>g.k==="ptb");return!l||l.v<=.5?null:pt("rent",l,he.ptb??se.ptb??0)})(),Lt&&(()=>{const l=T.find(g=>g.k==="dep");return!l||l.v<=.5?null:pt("rent",l,he.dep??se.dep??0)})(),Ot&&(()=>{const l=T.find(g=>g.k==="acc");return!l||l.v<=.5?null:pt("rent",l,he.acc??se.acc??0)})(),tt&&(()=>{const l=j.find(g=>g.k==="extra");return!l||l.v<=.5?null:pt("rent",{...l,tuckRight:Ut},he.extra??dt??0)})()]})]})})})(),e.jsxs("div",{className:"bvr-vfoot",children:[e.jsxs("button",{type:"button",className:"bvr-viewseg"+(bl?" intro":""),"aria-pressed":it,"aria-label":"Swap summary and detail view",onClick:()=>hl(t=>!t),children:[e.jsx("span",{className:"bvr-viewseg-emo","aria-hidden":"true",children:it?"ð":"ð¤"}),e.jsx("span",{className:"bvr-viewseg-wd","aria-hidden":"true",children:it?"
13Summary view":"Detailed view"})]}),e.jsxs("div",{className:"bvr-chartinfo",onMouseEnter:sl,onMouseLeave:ol,children:[e.jsx("button",{className:"bvr-chartinfo-btn"+(Kn?" open":""),onClick:()=>Ka(t=>!t),"aria-label":"Chart legend","aria-expanded":Kn,children:e.jsx(Hr,{size:11})}),Kn&&e.jsxs("div",{className:"bvr-chartinfo-tip",role:"dialog",children:[e.jsx("button",{className:"bvr-chartinfo-x",onClick:()=>Ka(!1),"aria-label":"Close",children:"Ã"}),e.jsxs("div",{className:"bvr-citrow",children:[e.jsx("i",{className:"bvr-citsw solid"}),e.jsxs("span",{children:[e.jsx("b",{children:"Solid fill"})," = hard costs, reserves",lc?" & accruals":""]})]}),e.jsxs("div",{className:"bvr-citrow",children:[e.jsx("i",{className:"bvr-citsw hatch"}),e.jsxs("span",{children:[e.jsx("b",{children:"Hatched"})," = kept savings, tax benefits or capitalized interest"]})]})]})]})]})]}),Pi("under-annual"),e.jsxs("div",{className:"bvr-panel bvr-vpanel bvr-riverp"+(it?" bvr-rivsimple":""),children:[e.jsxs("button",{type:"button",className:"bvr-panelh bvr-riverh","aria-expanded":ce.river,onClick:()=>ge(t=>({...t,river:!t.river})),children:[e.jsx("span",{className:"bvr-ptitle",children:e.jsx("span",{className:"bvr-ptag",children:"Yearly cash requirements"})}),e.jsxs("span",{className:"bvr-riverh-right",children:[ce.river&&(z.hasAccrual?e.jsxs("span",{className:"bvr-legend bvr-riverleg",children:[e.jsxs("span",{className:"bvr-legrow",children:[e.jsx("em",{className:"bvr-leglab",children:"Buy:"}),e.jsx("i",{className:"dash"}),"gross",e.jsx("i",{style:{background:"var(--buy)"}}),"net after-tax",Bn&&!it&&"*"]}),e.jsxs("span",{className:"bvr-legrow",children:[e.jsx("i",{style:{background:"var(--rent)"}}),"Rent"]})]}):e.jsxs("span",{className:"bvr-legend",children:[e.jsx("i",{style:{background:"var(--buy)"}}),"Buy",e.jsx("i",{style:{background:"var(--rent)"}}),"Rent"]})),ce.river?e.jsx(qr,{size:16}):e.jsx(St,{size:16})]})]}),e.jsx(at,{open:ce.river,children:e.jsxs("div",{className:"bvr-riverbody",children:[e.jsxs("svg",{viewBox:`0 0 ${z.W} ${z.H}`,width:"100%",style:{cursor:"crosshair",display:"block",touchAction:"none"},...Ni(mc),children:[e.jsxs("defs",{children:[e.jsx("pattern",{id:"bvrWRdot",width:"7",height:"7",patternUnits:"userSpaceOnUse",children:e.jsx("circle",{cx:"3.5",cy:"3.5",r:"1.15",fill:"color-mix(in srgb, var(--rent) 55%, transparent)"})}),e.jsx("pattern",{id:"bvrWBdot",width:"7",height:"7",patternUnits:"userSpaceOnUse",children:e.jsx("circle",{cx:"3.5",cy:"3.5",r:"1.15",fill:"color-mix(in srgb, var(--buy) 55%, transparent)"})}),e.jsx("pattern",{id:"bvrWR",width:"6",height:"6",patternUnits:"userSpaceOnUse",patternTransform:"rotate(45)",children:e.jsx("line",{x1:"0",y1:"0",x2:"0",y2:"6",stroke:"var(--rent)",strokeWidth:"1.1",opacity:"0.55"})}),e.jsx("pattern",{id:"bvrWB",width:"6",height:"6",patternUnits:"userSpaceOnUse",patternTransform:"rotate(45)",children:e.jsx("line",{x1:"0",y1:"0",x2:"0",y2:"6",stroke:"var(--buy)",strokeWidth:"1.1",opacity:"0.55"})})]}),(()=>{const t=z.rs[0];if(!t)return null;let r=[{v:t.buyOut,c:"var(--buy)"},{v:t.rentOut,c:"var(--rent)"}].map(a=>({...a,gy:z.Y(a.v),ly:z.Y(a.v)}));if(r.sort((a,n)=>a.ly-n.ly),r.length===2&&r[1].ly-r[0].ly<13){const a=(r[0].ly+r[1].ly)/2;r[0].ly=a-6.5,r[1].ly=a+6.5}return e.jsxs("g",{children:[[1,2,3].map(a=>{const n=z.vmax*a/3,s=z.Y(n);return r.some(o=>Math.abs(o.ly-s)<15)?null:e.jsxs("g",{children:[e.jsx("line",{x1:z.pL,y1:s,x2:z.W-z.pR,y2:s,stroke:"var(--line)",strokeWidth:"0.75"}),e.jsx("text",{x:z.pL-6,y:s+3,textAnchor:"end",fontSize:"11",fill:"var(--faint)",children:xe(n)})]},a)}),r.map((a,n)=>e.jsxs("g",{children:[e.jsx("line",{x1:z.pL,y1:a.gy,x2:z.W-z.pR,y2:a.gy,stroke:a.c,strokeWidth:"0.75",strokeOpacity:"0.25",strokeDasharray:"2 3"}),e.jsx("text",{className:"num",x:z.pL-6,y:a.ly+3.5,textAnchor:"end",fontSize:"11",fontWeight:"600",fill:a.c,children:xe(a.v)})]},"v"+n))]})})(),e.jsx("line",{x1:z.pL,y1:z.Y(0),x2:z.W-z.pR,y2:z.Y(0),stroke:"var(--line)",strokeWidth:"0.75"}),e.jsx("text",{x:z.pL-6,y:z.Y(0)+3,textAnchor:"end",fontSize:"11",fill:"var(--faint)",children:xe(0)}),Gi(x,y).map(t=>e.jsx("text",{x:z.X(t),y:z.H-8,textAnchor:"middle",fontSize:"11",fill:"var(--faint)",children:t},t)),e.jsxs("g",{className:"bvr-rivbands",children:[e.jsx("path",{className:"bvr-rvband",d:z.pPrin,fill:"var(--prin)",opacity:"0.9",children:e.jsx("title",{children:"Principal paydown â scheduled mortgage amortization"})}),e.jsx("path",{className:"bvr-rvband",d:z.pInt,fill:"var(--buy)",opacity:"0.30",children:e.jsx("title",{children:"Net interest paid (after tax benefit)"})}),e.jsx("path",{className:"bvr-rvband",d:z.pMci,fill:"var(--buy)",opacity:"0.18",children:e.jsx("title",{children:"Maintenance, capex & insurance"})}),e.jsx("path",{className:"bvr-rvband",d:z.pPt,fill:"var(--buy)",opacity:"0.09",children:e.jsx("title",{children:"Property tax "+(yt.rows.some(t=>t&&t.ptBen>.5)?"(net of SALT benefit)":"(no SALT tax benefit)")})}),z.wedges.map((t,r)=>t.path&&!(t.who==="buy"&&z.bandsPay.length>0)?e.jsx("path",{className:"bvr-rvband",d:t.path,fill:Q?t.who==="rent"?"url(#bvrWR)":"url(#bvrWB)":t.who==="rent"?"url(#bvrWRdot)":"url(#bvrWBdot)",children:e.jsx("title",{children:t.who==="rent"?Q?"gap â renter invests it":"gap â renter's savings, consumed (invest switch OFF)":Q?"gap â owner surplus":"gap â owner's savings, consumed (invest switch OFF)"})},r):null),z.bandsPay.map(t=>e.jsx("path",{className:"bvr-rvband",d:t.path,fill:t.color||"url(#bvrWB)",opacity:t.color?.5:1,children:e.jsx("title",{children:"surplus â "+t.label})},t.id))]}),z.hasAccrual&&e.jsx("polyline",{className:"bvr-rivln gross",points:z.pts(t=>t.buyOutGross),fill:"none",stroke:"var(--buyGross)",strokeWidth:"1.8",strokeDasharray:"5 4",opacity:"1"}),e.jsx("polyline",{className:"bvr-rivln buy",points:z.pts(t=>t.buyOut),fill:"none",stroke:"var(--buy)",strokeWidth:"2.75",strokeLinejoin:"round"}),e.jsx("polyline",{className:"bvr-rivln rent",points:z.pts(t=>t.rentOut),fill:"none",stroke:"var(--rent)",strokeWidth:"2.75",strokeLinejoin:"round"}),e.jsx("line",{className:"bvr-ymark-line"+(yr?" set":""),x1:z.X(L),y1:z.pT,x2:z.X(L),y2:z.H-z.pB},"mkl"+yr),(()=>{const t=Math.max(z.pL+40,Math.min(z.W-z.pR-40,z.X(L)));return e.jsxs(e.Fragment,{children:[e.jsxs("text",{className:"bvr-ymark-lbl"+(yr?" set":""),x:t,y:z.pT+15,textAnchor:"middle",children:["yr ",L]},"mk"+yr),e.jsx("text",{className:"bvr-ymark-val num",x:t,y:z.pT+32,textAnchor:"middle",fill:"var(--buy)",children:xe(I.years[L].buyOut)})]})})(),z.flipPt&&(()=>{const t=Math.round(z.flip*10)/10,r=Number.isInteger(t)?String(t):t.toFixed(1),a=Math.abs(z.X(c)-z.flipPt.x)<=40;return e.jsxs("g",{children:[e.jsx("circle",{cx:z.flipPt.x,cy:z.flipPt.yb,r:"5",fill:"var(--card)",stroke:"var(--ink)",strokeWidth:"2"}),e.jsxs("text",{x:z.flipPt.x,y:a?z.flipPt.yb+20:z.flipPt.yb-11,textAnchor:"middle",fontSize:"11",fontWeight:"600",fill:"var(--ink)",children:["yr ",r]})]})})(),e.jsxs("g",{className:"bvr-rivlabs",children:[z.bandsPay.map(t=>t.lab?e.jsx("text",{x:t.lab.x,y:t.lab.y+3.5,textAnchor:"middle",fontSize:"11",fontWeight:"600",fill:`color-mix(in srgb, ${t.color} 78%, black)`,pointerEvents:"none",children:t.labText},t.id+"-lab"):null),z.rs.length>1&&z.bandLabels.map(t=>{const r=t.y0-t.y1;if(r<14)return null;const a=(t.y0+t.y1)/2+(t.off||0)*r;return e.jsx("text",{x:z.X(z.rs[z.rs.length-1].y)-8,y:a+3.5,textAnchor:"end",fontSize:"11",fontWeight:"600",fill:t.onDark?"#fff":"color-mix(in srgb, var(--buy) 80%, black)",children:t.t},t.t)})]})]}),e.jsxs("div",{className:"bvr-rivleg",children:[Q?e.jsxs("span",{children:[e.jsx("i",{className:"chip",style:{background:"repeating-linear-gradient(135deg, color-mix(in srgb, var(--rent) 55%, transparent) 0 1.5px, transparent 1.5px 5px)"}}),e.jsx("i",{className:"chip",style:{background:"repeating-linear-gradient(135deg, color-mix(in srgb, var(--buy) 55%, transparent) 0 1.5px, transparent 1.5px 5px)"}}),"gap ","â"," invested"]}):e.jsxs("span",{children:[e.jsx("i",{className:"chip",style:{background:"radial-gradient(color-mix(in srgb, var(--rent) 55%, transparent) 1.1px, transparent 1.2px)",backgroundSize:"6px 6px"}}),e.jsx("i",{className:"chip",style:{background:"radial-gradient(color-mix(in srgb, var(--buy) 55%, transparent) 1.1px, transparent 1.2px)",backgroundSize:"6px 6px"}}),"gap ","â"," consumed"]}),e.jsxs("span",{className:"bvr-lgswwrap",children:[e.jsx("span",{className:"bvr-lgswtxt",children:"invest the gap surplus?"}),ke(Q,()=>Xr(t=>!t),"Invest the gap surplus","ON: the cheaper side deploys its surplus â renter invests; owner prepays debt in payoff order, then invests. OFF: both surpluses are lifestyle spend (consumed).")]})]}),Bn&&e.jsx("div",{className:"bvr-rivfoot",children:"* net of synth. loan interest, which accrues to the loan balance â no cash payment"}),e.jsxs("div",{className:"bvr-hint",children:["Hover to preview a year · click to set it",yt.flip?` · surplus flips sides at year ${Math.round(yt.flip*10)/10}`:""]})]})})]}),e.jsxs("div",{className:"bvr-panel bvr-wealthp",children:[e.jsxs("button",{className:"bvr-wealthh",onClick:()=>ge(t=>({...t,wealth:!t.wealth})),"aria-expanded":ce.wealth,children:[e.jsx("span",{className:"bvr-ptitle",children:e.jsx("span",{className:"bvr-ptag",children:"Wealth building"})}),e.jsxs("span",{className:"bvr-wealthh-right",children:[ce.wealth&&e.jsxs("span",{className:"bvr-legend",children:[e.jsx("em",{children:"dashed = pre-liquidation ·"}),e.jsx("i",{style:{background:"var(--buy)"}}),"Buy",e.jsx("i",{style:{background:"var(--rent)"}}),"Rent"]}),ce.wealth?e.jsx(qr,{size:16}):e.jsx(St,{size:16})]})]}),e.jsx(at,{open:ce.wealth,children:e.jsxs("div",{className:"bvr-wealthbody bvr-trajbody",children:[e.jsxs("svg",{viewBox:`0 0 ${E.W} ${E.H}`,width:"100%",style:{cursor:"crosshair",display:"block",touchAction:"none"},...Ni(ic),children:[e.jsx("defs",{children:e.jsx("clipPath",{id:"bvrPlotClip",children:e.jsx("rect",{x:E.pL,y:E.pT,width:E.W
13-E.pL-E.pR,height:E.H-E.pT-E.pB})})}),[0,1,2,3].map(t=>{const r=E.mn+E.sp*t/3,a=E.Y(r);return e.jsxs("g",{children:[e.jsx("line",{x1:E.pL,y1:a,x2:E.W-E.pR,y2:a,stroke:"var(--line)",strokeWidth:"0.5"}),e.jsx("text",{x:E.pL-6,y:a+3,textAnchor:"end",fontSize:"11",fill:"var(--faint)",children:xe(r)})]},t)}),Gi(0,y).map(t=>e.jsx("text",{x:E.X(t),y:E.H-8,textAnchor:"middle",fontSize:"11",fill:"var(--faint)",children:t},t)),e.jsx("line",{x1:E.pL,y1:E.Y(Yt),x2:E.W-E.pR,y2:E.Y(Yt),stroke:"var(--faint)",strokeWidth:"0.75",strokeDasharray:"2 3"}),e.jsx("text",{className:"num",x:E.pL-6,y:E.Y(Yt)+3.5,textAnchor:"end",fontSize:"11",fontWeight:"600",fill:"var(--faint)",children:xe(Yt)}),e.jsx("line",{className:"bvr-ymark-line"+(yr?" set":""),x1:E.X(c),y1:E.pT,x2:E.X(c),y2:E.H-E.pB},"mkl"+yr),(()=>{const t=Math.max(E.pL+26,Math.min(E.W-E.pR-26,E.X(c)));return e.jsx("text",{className:"bvr-ymark-lbl"+(yr?" set":""),x:t,y:E.pT+15,textAnchor:"middle",children:c===0?"at purchase":ft(c)},"mk"+yr)})(),e.jsx("path",{d:E.path("buyPre"),fill:"none",stroke:"var(--buy)",strokeWidth:"1.6",strokeDasharray:"3 3",opacity:"0.7"}),e.jsx("path",{d:E.path("rentPre"),fill:"none",stroke:"var(--rent)",strokeWidth:"1.6",strokeDasharray:"3 3",opacity:"0.7"}),e.jsx("path",{d:E.path("rentAT"),fill:"none",stroke:"var(--rent)",strokeWidth:"2.75",strokeLinejoin:"round"}),e.jsx("path",{d:E.path("buyAT"),fill:"none",stroke:"var(--buy)",strokeWidth:"2.75",strokeLinejoin:"round"}),(()=>{const t=I.preLiqCross>0,r=I.crossover>0;if(!t&&!r)return null;const a=(n,s,o,b,p)=>e.jsxs("g",{className:"bvr-bedot",children:[e.jsxs("g",{className:"bvr-beghost",children:[e.jsx("line",{x1:E.X(s.yr),y1:E.pT+20,x2:E.X(s.yr),y2:E.H-E.pB,stroke:"var(--faint)",strokeWidth:"1",strokeDasharray:"2 3"}),e.jsxs("text",{className:"bvr-belbl big",x:E.X(s.yr),y:E.pT+6,textAnchor:"middle",children:["yr ",s.yr]}),e.jsx("text",{className:"bvr-belbl sub",x:E.X(s.yr),y:E.pT+17,textAnchor:"middle",children:b})]}),e.jsxs("text",{className:"bvr-belbl",x:E.X(s.yr),y:E.Y(o)-9,textAnchor:"middle",children:["yr ",s.yr]}),e.jsx("circle",{cx:E.X(s.yr),cy:E.Y(o),r:"4",fill:"var(--card)",stroke:"var(--buy)",strokeWidth:p?"2":"1.5",strokeDasharray:p?void 0:"2.5 2"}),e.jsx("circle",{cx:E.X(s.yr),cy:E.Y(o),r:"11",fill:"transparent"})]},n);return e.jsxs("g",{children:[t&&a("pre",I.series[I.preLiqCross-1],I.series[I.preLiqCross-1].buyPre,"pre-liq BE",!1),r&&a("at",I.series[I.crossover-1],I.series[I.crossover-1].buyAT,"after-tax BE",!0)]})})(),e.jsx("g",{clipPath:"url(#bvrPlotClip)",children:(()=>{const r=E.Y(0),a=E.X(c),n=(s,o,b,p)=>{const u=E.Y(o),f=[e.jsx("rect",{x:s,y:Math.min(u,r),width:24,height:Math.abs(r-u),fill:p,rx:"1.5"},s+"a")];if(b>o){const S=E.Y(b);f.push(e.jsx("rect",{x:s,y:S,width:24,height:u-S,fill:p,opacity:"0.55",rx:"1.5"},s+"p"))}return f};return[...n(a-24-3,N.buyAT,N.buyPre,"var(--buy)"),...n(a+3,N.rentAT,N.rentPre,"var(--rent)")]})()}),e.jsx("circle",{cx:E.X(c),cy:E.Y(N.buyAT),r:"3.5",fill:"var(--buy)"}),e.jsx("circle",{cx:E.X(c),cy:E.Y(N.rentAT),r:"3.5",fill:"var(--rent)"})]}),e.jsx("div",{className:"bvr-hint",children:"Hover to preview a year · click to set it"})]})}),e.jsxs("div",{className:"bvr-wealthbody bvr-herobody",children:[e.jsx("div",{className:"bvr-wealthdiv"}),(()=>{const a=Math.max(N.buyPre,N.rentPre,Yt,1),n=P=>(1-Math.max(0,P)/a)*188,s=P=>Ic(P,188),o=n(Yt),b=n(N.buyAT),p=n(N.buyPre),u=n(N.rentAT),f=n(N.rentPre),S=s([{k:"pre",y:p,h:32},{k:"at",y:b,h:54},{k:"today",y:o,h:30}]),k=s([{k:"pre",y:f,h:32},{k:"at",y:u,h:54}]),j=P=>S(P)+29,T=P=>k(P)+29,B=()=>e.jsx("i",{className:"bvr-hlead elbow"});return e.jsxs("div",{className:"bvr-herogrid",ref:Ti,children:[e.jsxs("div",{className:"bvr-hlabs",style:{height:217},children:[e.jsxs("div",{className:"bvr-hlab L pre","data-hlead":"bpre","data-hside":"L",style:{top:j("pre")},children:[e.jsxs("span",{className:"bvr-hltxt",children:[e.jsx("em",{children:"Pre-liquidation"}),e.jsx("b",{className:"num pre tone-buy",children:i(N.buyPre)})]}),B()]}),e.jsxs("div",{className:"bvr-hlab L clickable","data-hlead":"bat","data-hside":"L",style:{top:j("at")},onClick:P=>F({kind:"buy"},P),children:[e.jsxs("span",{className:"bvr-hltxt",children:[e.jsxs("em",{children:["After-tax ",e.jsx(Hr,{size:11})]}),e.jsx("b",{className:"num at tone-buy",children:i(N.buyAT)}),e.jsx("small",{className:"num",children:Qo(N.buyAT)})]}),B()]}),e.jsxs("div",{className:"bvr-hlab L today","data-hlead":"btoday","data-hside":"L",style:{to
13p:j("today")},children:[e.jsxs("span",{className:"bvr-hltxt",children:[e.jsx("em",{children:"Initial cash required"}),e.jsx("b",{className:"num tdy",children:i(Yt)})]}),B()]})]}),e.jsxs("div",{className:"bvr-heroc",children:[e.jsxs("div",{className:"bvr-herohead",children:[e.jsxs("span",{style:{color:"var(--buy)"},children:[e.jsx(Tr,{size:12}),"Buy"]}),e.jsxs("span",{style:{color:"var(--rent)"},children:[e.jsx(Ia,{size:12}),"Rent"]})]}),e.jsxs("div",{className:"bvr-herobars",style:{height:188},children:[e.jsx("div",{className:"bvr-htodayline","data-htgt":"btoday",style:{top:o}}),e.jsxs("div",{className:"bvr-hbarw",children:[e.jsx("div",{className:"bvr-hbar","data-htgt":"bat",style:{top:b,bottom:0,background:"var(--buy)",borderRadius:N.buyPre>N.buyAT?"0":"3px 3px 0 0"}}),N.buyPre>N.buyAT&&e.jsx("div",{className:"bvr-hbar ghost","data-htgt":"bpre",style:{top:p,height:b-p,background:"var(--buy)"}})]}),e.jsxs("div",{className:"bvr-hbarw",children:[e.jsx("div",{className:"bvr-hbar","data-htgt":"rat",style:{top:u,bottom:0,background:"var(--rent)",borderRadius:N.rentPre>N.rentAT?"0":"3px 3px 0 0"}}),N.rentPre>N.rentAT&&e.jsx("div",{className:"bvr-hbar ghost","data-htgt":"rpre",style:{top:f,height:u-f,background:"var(--rent)"}})]})]})]}),wr&&e.jsxs("svg",{className:"bvr-hleadslay",width:wr.w,height:wr.h,viewBox:`0 0 ${wr.w} ${wr.h}`,children:[wr.todayRule&&(()=>{const P=wr.todayRule;return e.jsx("polyline",{points:`${P.xLab},${P.yLab} ${P.railL},${P.yLab} ${P.railL},${P.yT} ${P.railR},${P.yT}`,fill:"none",stroke:"var(--faint)",strokeWidth:"1",strokeDasharray:"4 3",strokeLinejoin:"round",strokeLinecap:"round"})})(),wr.lines.map(P=>e.jsxs("g",{children:[e.jsx("polyline",{points:`${P.xLab},${P.yLab} ${P.rail},${P.yLab} ${P.rail},${P.yBar} ${P.xBar},${P.yBar}`,fill:"none",stroke:"var(--line2)",strokeWidth:"1",strokeLinejoin:"round",strokeLinecap:"round"}),e.jsx("circle",{cx:P.xBar,cy:P.yBar,r:"2.75",fill:P.tone})]},P.id))]}),e.jsxs("div",{className:"bvr-hlabs",style:{height:217},children:[e.jsxs("div",{className:"bvr-hlab R pre","data-hlead":"rpre","data-hside":"R",style:{to
13p:T("pre")},children:[B(),e.jsxs("span",{className:"bvr-hltxt",children:[e.jsx("em",{children:"Pre-liquidation"}),e.jsx("b",{className:"num pre tone-rent",children:i(N.rentPre)})]})]}),e.jsxs("div",{className:"bvr-hlab R clickable","data-hlead":"rat","data-hside":"R",style:{top:T("at")},onClick:P=>F({kind:"rent"},P),children:[B(),e.jsxs("span",{className:"bvr-hltxt",children:[e.jsxs("em",{children:["After-tax ",e.jsx(Hr,{size:11})]}),e.jsx("b",{className:"num at tone-rent",children:i(N.rentAT)}),e.jsx("small",{className:"num",children:Qo(N.rentAT)})]})]})]})]})})(),e.jsxs("div",{className:"bvr-heronote",children:["Terminal wealth ",c===0?"at purchase":"at year "+c," · solid = after-tax · light = pre-liquidation"]})]})]}),Pi("under-wealth"),(()=>{const t=Sa!==Na,r=Do||t;return e.jsxs("div",{className:"bvr-verdict bvr-verdict-mid",children:[e.jsxs("div",{className:"bvr-vlead",style:{color:Sa?"var(--buy)":"var(--rent)"},children:[Sa?"Buying":"Renting"," wins: +",Un(gc)," more wealth"]}),e.jsx("div",{className:"bvr-vsub",children:c===0?"pre-liquidation, at purchase":"pre-liquidation, at yr "+c+": "+ft(c)}),!t&&e.jsxs("button",{type:"button",className:"bvr-vmore","aria-expanded":r,"aria-controls":"bvr-verdict-more",onClick:Fo,children:[r?"Hide details":"See the full breakdown",e.jsx(St,{size:12})]}),e.jsx(at,{open:r,children:e.jsxs("div",{id:"bvr-verdict-more",children:[e.jsx("div",{className:"bvr-vlead-sub",style:Sa===Na?void 0:{color:Na?"var(--buy)":"var(--rent)"},children:Sa===Na?e.jsxs(e.Fragment,{children:["and +",Un(zi)," after-tax reinvestment/spending power"]}):e.jsxs(e.Fragment,{children:[Na?"Buying":"Renting"," wins: +",Un(zi)," after-tax reinvestment/spending power"]})}),e.jsxs("div",{className:"bvr-vsub",children:["and ",I.crossover>0?`buying overtakes renting at year ${I.crossover}`:"renting stays ahead through 30 years",e.jsx("button",{className:"bvr-why",onClick:a=>F({kind:"driver"},a),children:"Why?"})]})]})})]})})()]}),e.jsxs("div",{className:"bvr-railstack isopen"+(Wr?" pinned":""),ref:Ue,children:[e.jsxs("div",{className:"bvr-railtools",children:[e.jsxs("button",{className:"bvr-railpin"+(Wr?" on":""),onClick:()=>rl(t=>!t),"aria-pressed":Wr,title:Wr?"Unpin â assumptions scroll with the page again":"Keep assumptions in view while scrolling the charts. If they're taller than the window, scroll inside the pinned column to reach the rest.",children:[e.jsx(Bc,{size:11}),Wr?"Pinned":"Pin"]}),e.jsxs("button",{className:"bvr-railpin bvr-stickytog"+(xt?" on":""),onClick:()=>fa(t=>!t),"aria-pressed":xt,title:"Sticky sliders â a small draggable panel of live what-if sliders",children:[e.jsx($a,{size:11,className:"bvr-slrot"}),"Sticky sliders"]}),e.jsx("button",{className:"bvr-railpin bvr-railfold",onClick:()=>bo(t=>!t),"aria-expanded":!_a,title:_a?"Expand the assumptions column":"Collapse assumptions to a slim strip","aria-label":_a?"Expand the assumptions column":"Collapse the assumptions column",children:e.jsx(Sc,{size:13})})]}),e.jsxs("div",{className:"bvr-railpanels",children:[e.jsx("div",{className:"bvr-railsechead",ref:uo,children:"Settings, inputs & assumptions"}),e.jsxs("aside",{className:"bvr-rail bvr-rail-scn",style:{"--ri":0},children:[e.jsxs("div",{className:"bvr-railwide",children:[e.jsxs("button",{className:"bvr-railh bvr-railh-btn",onClick:()=>ge(t=>({...t,scenario:!t.scenario})),"aria-expanded":ce.scenario,children:[e.jsxs("span",{className:"bvr-railh-lab",children:[e.jsx($a,{size:13}),"Price and market rates"]}),ce.scenario?e.jsx(qr,{size:13}):e.jsx(St,{size:13})]}),e.jsx(at,{open:!ce.scenario,children:e.jsxs("div",{className:"bvr-finsum",children:[X&&e.jsx("div",{className:"bvr-finsumrow clickable",onClick:()=>{ge(t=>({...t,scenario:!0}))},children:e.jsxs("span",{className:"bvr-finsumlab",children:["Multifamily · ",At," units"]})}),e.jsxs("div",{className:"bvr-finsumrow clickable",onClick:()=>{ge(t=>({...t,scenario:!0})),st("price")},children:[e.jsx("span",{className:"bvr-finsumlab",children:"Purchase price"}),e.jsxs("span",{className:"bvr-finsumval"+(Kr==="price"?" bvr-autoglow":""),children:[K!=="price"&&G>0&&e.jsx("button",{className:"bvr-fsx",title:"Set to $0 (auto-solve absorbs it)",onClick:t=>{t.stopPropagation(),Be("price",0)},children:e.jsx(Cr,{size:9})}),K==="price"&&e.jsx("span",{className:"bvr-autopill sm",onClick:t=>t.stopPropagation(),children:"auto"}),e.jsx("b",{className:"num",children:i(G)}),K!=="price"&&e.jsxs("button",{className:"bvr-finsumlockbtn bvr-lockmorph",title:"Locked â tap to auto-solve this field instead",onClick:t=>{t.stopPropagation(),Cn("price")},"aria-label":"Auto-solve purchase price",children:[e.jsx(Da,{size:9,className:"lm-closed"}),e.jsx(ao,{size:9,className:"lm-open"})]})]})]}),X&&e.jsxs("div",{className:"bvr-finsumrow clickable",onClick:()=>{ge(t=>({...t,scenario:!0,ownership:ce.ownership})),st("unitrent")},children:[e.jsx("span",{className:"bvr-finsumlab",children:"Rental income / mo"}),e.jsx("b",{className:"num",children:i(Ur*Math.max(0,At-1))})]}),e.jsxs("div",{className:"bvr-finsumrow clickable",onClick:()=>{ge(t=>({...t,scenario:!0})),st("rentmo")},children:[e.jsx("span",{className:"bvr-finsumlab",children:"Rent / mo"}),e.jsx("b",{className:"num",children:i(ht)})]})]})}),e.jsx(at,{open:ce.scenario,children:e.jsxs("div",{className:"bvr-railb",children:[e.jsxs("div",{className:"bvr-fld bvr-fldstack",ref:mn,children:[e.jsxs("div",{className:"bvr-fldrow",children:[e.jsx("label",{className:"has-help",onClick:t=>F({kind:"help",key:"funding",focus:"pop-price"},t,mn.current),children:"Purchase price"}),e.jsx(Zr,{k:"price"})]}),e.jsx("span",{className:(yn==="price"?"bvr-flash ":"")+(Kr==="price"?"bvr-autoglow ":"")+"bvr-block",children:e.jsx(Tt,{v:G,set:t=>Be("price",t),w:"100%",inputRef:Ct("price")})}),e.jsxs("div",{className:"cctog ccpanel"+(qe===0?" ccwarn":""),onClick:t=>{Za(!0),F({kind:"help",key:"funding"},t,mn.current)},children:["+",i(qe)," up front purchasing costs ",e.jsx(Gn,{size:10})]})]}),e.jsx(Se,{label:"Appreciation",children:e.jsx(Le,{v:Ve,set:Qa})}),e.jsx(Se,{label:"Rent / mo",help:t=>F({kind:"help",key:"rentamt"},t),children:e.jsx(Tt,{v:ht,set:da,inputRef:Ct("rentmo")})}),e.jsx(Se,{label:"Rent inflation",children:e.jsx(Le,{v:Mt,set:en})}),e.jsx(Se,{label:"Cash/bond return",help:t=>F({kind:"help",key:"mix"},t),children:e.jsx(Le,{v:er,set:ls})}),e.jsx(Se,{label:"Equity return",help:t=>F({kind:"help",key:"mix"},t),children:e.jsx(Le,{v:Wt,set:cs})}),e.jsxs("div",{className:"bvr-fundstat",style:{margin:"2px 0 0",fontSize:"11px"},onClick:t=>F({kind:"help",key:"mix"}
13,t),children:["Reinvestment mix · ",Ke,"% equities"]}),e.jsxs("div",{className:"bvr-fund",children:[e.jsxs("div",{className:"bvr-ded",style:{marginTop:0,paddingTop:0,borderTop:"none"},children:[e.jsx("span",{children:"Owner-occupied multifamily"}),ke(X,()=>fo(t=>!t),"Owner-occupied multifamily "+(X?"yes":"no"))]}),X&&e.jsxs("div",{style:{marginTop:8},children:[e.jsxs("div",{className:"bvr-fld",children:[e.jsx("label",{children:"Total units"}),e.jsx("span",{className:"unitseg",children:[2,3,4].map(t=>e.jsx("button",{className:At===t?"on":"",onClick:()=>{yo(t),rn(Math.round((t-1)/t*1e3)/10)},children:t},t))})]}),e.jsx(Se,{label:"Rent / unit / month",children:e.jsx(Tt,{v:Ur,set:ko,w:80,inputRef:Ct("unitrent")})}),e.jsx(Se,{label:"Vacancy & mgmt",help:t=>F({kind:"help",key:"vacancy"},t),children:e.jsx(Le,{v:Vr,set:ts})}),e.jsx(Se,{label:"Investment share",help:t=>F({kind:"help",key:"invShare"},t),children:e.jsx(Le,{v:Ge,set:rn})}),e.jsx(Se,{label:"Building vs land",help:t=>F({kind:"help",key:"bldg"},t),children:e.jsx(Le,{v:Rt,set:jo})}),e.jsx(Se,{label:"Recapture rate",help:t=>F({kind:"help",key:"recap"},t),children:e.jsx(Le,{v:Ar,set:No})}),e.jsxs("div",{className:"bvr-ded",children:[e.jsx("span",{className:"has-help",onClick:t=>F({kind:"line",key:"dep"},t),children:"REPs or other passive income"}),ke(Oe,()=>tn(t=>!t),"REPs or other passive income "+(Oe?"yes":"no"))]}),e.jsxs("div",{className:"bvr-note",children:["You occupy one unit and rent the other ",Math.max(0,At-1)," of ",At,". Rental income, allocated expenses, depreciation, §469 suspension, and exit recapture are all modeled â see the Rental income line's popover."]})]})]})]})})]}),Xa(e.jsx($a,{size:12}),"Price and market rates","scenario",[["Purchase",i(G)],["Rent / mo",i(ht)]])]}),e.jsxs("aside",{className:"bvr-rail bvr-rail-fin",style:{"--ri":1},children:[e.jsxs("div",{className:"bvr-railwide",children:[e.jsxs("button",{className:"bvr-railh bvr-railh-btn",onClick:()=>ge(t=>({...t,financing:!t.financing})),"aria-expanded":ce.financing,children:[e.jsxs("span",{className:"bvr-railh-lab",children:[e.jsx($i,{size:13}),"Financing"]}),ce.financing?e.jsx(qr,{size:13}):e.jsx(St,{size:13})]}),e.jsx(at,{open:!ce.financing,children:e.jsxs("div",{className:"bvr-finsum",children:[e.jsxs("div",{className:"bvr-finsumrow clickable",onClick:t=>{ge(r=>({...r,financing:!0})),Er(!0),Et==null&&pr(re),F({kind:"help",key:"downfund",focus:"pop-down"},t)},children:[e.jsx("span",{className:"bvr-finsumlab",children:"Down payment"}),e.jsxs("span",{className:"bvr-finsumval"+(Kr==="down"?" bvr-autoglow":""),children:[K!=="down"&&re>0&&e.jsx("button",{className:"bvr-fsx",title:"Set to $0 (auto-solve absorbs it)",onClick:t=>{t.stopPropagation(),Be("down",0)},children:e.jsx(Cr,{size:9})}),K==="down"&&e.jsx("span",{className:"bvr-autopill sm",onClick:t=>t.stopPropagation(),children:"auto"}),e.jsx("b",{className:"num",children:i(re)}),K!=="down"&&e.jsxs("button",{className:"bvr-finsumlockbtn bvr-lockmorph",title:"Locked â tap to auto-solve this field instead",onClick:t=>{t.stopPropagation(),Cn("down")},"aria-label":"Auto-solve down payment",children:[e.jsx(Da,{size:9,className:"lm-closed"}),e.jsx(ao,{size:9,className:"lm-open"})]})]})]}),te.filter(t=>t.amount>0||K===t.id).map(t=>e.jsxs("div",{className:"bvr-finsumrow clickable",onClick:()=>{ge(r=>({...r,financing:!0})),De(t.id,{open:!0}),st("loan-"+t.id)},children:[e.jsx("span",{className:"bvr-finsumlab",children:t.kind==="secured"?K===t.id&&t.type==="Synthetic loan"?"Synth. loan":t.type.split(" /")[0]:t.name}),e.jsxs("span",{className:"bvr-finsumval"+(Kr===t.id?" bvr-autoglow":""),children:[K!==t.id&&t.amount>0&&e.jsx("button",{className:"bvr-fsx",title:"Set to $0 (auto-solve absorbs it)",onClick:r=>{r.stopPropagation(),Be(t.id,0)},children:e.jsx(Cr,{size:9})}),K===t.id&&e.jsx("span",{className:"bvr-autopill sm",onClick:r=>{r.stopPropagation()},children:"auto"}),e.jsx("b",{className:"num",children:i(t.amount)}),K!==t.id&&e.jsxs("button",{className:"bvr-finsumlockbtn bvr-lockmorph",title:"Locked â click to auto-solve this field instead",onClick:r=>
13{r.stopPropagation(),Cn(t.id)},"aria-label":"Auto-solve this loan",children:[e.jsx(Da,{size:9,className:"lm-closed"}),e.jsx(ao,{size:9,className:"lm-open"})]})]})]},t.id))]})}),e.jsx(at,{open:ce.financing,children:e.jsxs("div",{className:"bvr-railb",children:[Ze!==0&&e.jsxs("div",{className:"bvr-fundstat warn",onClick:t=>F({kind:"help",key:"funding"},t),children:[Ze>0?"Under":"Over","-funded by ",i(Math.abs(Ze))," â tap a lock to fix"]}),e.jsxs("div",{className:"bvr-fld bvr-fldstack bvr-downfld",ref:xn,children:[e.jsxs("div",{className:"bvr-fldrow",children:[e.jsx("label",{className:"has-help",onClick:t=>{Er(!0),Et==null&&pr(re),F({kind:"help",key:"downfund",focus:"pop-down"},t,xn.current)},children:"Down payment"}),e.jsx(Zr,{k:"down"})]}),e.jsx("span",{className:(yn==="down"?"bvr-flash ":"")+(Kr==="down"?"bvr-autoglow ":"")+"bvr-block",children:e.jsx(Tt,{v:re,set:t=>js(t),w:"100%",inputRef:Ct("down")})}),Je>0&&e.jsxs("div",{className:"bvr-cgsub has-help",onClick:t=>F({kind:"help",key:"downfund"},t,xn.current),children:["plus capital gains tax of ",e.jsx("b",{className:"num",children:i(Je)})]})]}),te.filter(t=>t.open).map(t=>{const r=t.kind==="secured"?t.type:t.name,a=t.kind==="secured"&&na.includes(t.type),n=t.kind==="secured"&&t.type==="Synthetic loan",s=t.kind==="secured"&&qa.includes(t.type);return e.jsxs("div",{className:"bvr-loan",children:[e.jsxs("button",{className:"bvr-loanh",onClick:()=>De(t.id,{open:!1}),children:[e.jsx("span",{className:"bvr-loanm",children:r}),e.jsx(qr,{size:13})]}),e.jsxs("div",{className:"bvr-loanb",children:[t.kind==="secured"&&e.jsxs("div",{className:"bvr-synselwrap",children:[e.jsx("select",{className:"bvr-sel",value:t.type,onChange:o=>{const b=o.target.value,p=qa.includes(b)&&!!t.noPay&&!!t.deductible;De(t.id,{type:b}),p&&F({kind:"help",key:"sbloc",loanId:t.id,conflict:"type",cn:1},o)},children:Wi.map(o=>e.jsx("option",{children:o},o))}),n&&e.jsx("button",{className:"bvr-li bvr-synli",onClick:o=>F({kind:"help",key:"synth"},o),"aria-label":"What is a Synthetic loan?",title:"What is a Synthetic loan?",children:e.jsx(Hr,{size:13})}),s&&e.jsx("button",{className:"bvr-li bvr-synli",onClick:o=>F({kind:"help",key:"sbloc",loanId:t.id},o),"aria-label":"What is a pledged-asset line / SBLOC?",title:"What is a pledged-asset line / SBLOC?",children:e.jsx(Hr,{size:13})})]}),e.jsxs("div",{className:"bvr-mini",children:[e.jsxs("div",{children:[e.jsxs("span",{className:t.id==="m1"?"bvr-loan-amthdr":"bvr-ml",style:{display:"flex",alignItems:"center",justifyContent:"space-between"},children:[t.id==="m1"?e.jsx("span",{className:"bvr-loan-amtlab",style:Dr?{color:"var(--accent)",textDecorationColor:"var(--accent)"}:void 0,onClick:o=>F({kind:"help",key:"loanamt"},o),title:"Mortgage amount presets \\u2014 conforming limits & LTV",children:Dr?Dr.label:"Use preset"}
13):"Amount",e.jsx(Zr,{k:t.id})]}),e.jsxs("span",{className:"bvr-amtwrap "+(yn===t.id?"bvr-flash ":"")+(Kr===t.id?"bvr-autoglow":""),style:{display:"block",position:"relative"},children:[e.jsx("input",{ref:Ct("loan-"+t.id),className:"bvr-in num"+(t.amount>0?" bvr-in-clearable":""),style:{width:"100%"},defaultValue:"$"+t.amount.toLocaleString(),onFocus:o=>{nt(o),t.id==="m1"&&(!W||W.key!=="loanamt")&&F({kind:"help",key:"loanamt"},o,o.currentTarget)},onKeyDown:o=>{if(o.key==="ArrowUp"||o.key==="ArrowDown"){o.preventDefault();const b=parseFloat(o.target.value.replace(/[^0-9.]/g,"")),p=Zi(isNaN(b)?t.amount:b,o.key==="ArrowUp"?1:-1);Be(t.id,p),st("loan-"+t.id);return}Mr("loan-"+t.id)(o)},onBlur:o=>{const b=parseFloat(o.target.value.replace(/[^0-9.]/g,""));Be(t.id,isNaN(b)?0:b)}},t.id+t.amount),t.amount>0&&e.jsx("button",{className:"bvr-amtx",tabIndex:-1,title:"Clear to $0","aria-label":"Clear amount to zero",onMouseDown:o=>o.preventDefault(),onClick:o=>{o.stopPropagation();const b=K===t.id;Be(t.id,0),b||st("loan-"+t.id)},children:e.jsx(Cr,{size:11})})]})]}),e.jsxs("div",{className:"bvr-minirow",children:[e.jsxs("div",{children:[e.jsx("span",{className:"bvr-ml",children:"Rate"}),e.jsx("input",{className:"bvr-in num",defaultValue:t.rate+"%",onFocus:nt,onKeyDown:o=>{if(o.key==="ArrowUp"||o.key==="ArrowDown"){o.preventDefault();const b=parseFloat(o.target.value),p=Vc(isNaN(b)?t.rate:b,o.key==="ArrowUp"?1:-1);De(t.id,{rate:p}),o.target.value=p+"%",requestAnimationFrame(()=>{try{o.target.select()}catch{}});return}Ya(o)},onBlur:o=>{const b=parseFloat(o.target.value);De(t.id,{rate:isNaN(b)?0:b}),o.target.value=(isNaN(b)?0:b)+"%"}})]}),(t.kind==="mortgage"||a||n)&&e.jsxs("div",{children:[e.jsx("span",{className:"bvr-ml",children:"Term"}),e.jsx("input",{className:"bvr-in num",defaultValue:t.term+"y",onFocus:nt,onKeyDown:o=>{if(o.key==="ArrowUp"||o.key==="ArrowDown"){o.preventDefault();const b=parseInt(o.target.value),p=_c(isNaN(b)?t.term:b,o.key==="ArrowUp"?1:-1);De(t.id,{term:p}),o.target.value=p+"y",requestAnimationFrame(()=>{try{o.target.select()}catch{}});return}Ya(o)},onBlur:o=>{const b=parseInt(o.target.value);De(t.id,{term:isNaN(b)?0:b}),o.target.value=(isNaN(b)?0:b)+"y"}})]})]})]}),t.kind==="secured"&&!a&&e.jsxs("div",{className:"bvr-ded",children:[e.jsx("span",{children:"No monthly payments"}),ke(!!t.noPay,o=>gn(t,"noPay",o),"No monthly payments "+(t.noPay?"yes":"no"))]}),n&&e.jsxs("div",{className:"bvr-ded",children:[e.jsx("span",{children:"Auto-roll at exp"}),ke(!!t.autoRoll,()=>De(t.id,{autoRoll:!t.autoRoll}),"Auto-roll "+(t.autoRoll?"yes":"no"))]}),e.jsxs("div",{className:"bvr-ded",children:[e.jsx("span",{children:t.id==="m1"?"Auto-calc. deduction":t.type==="Synthetic loan"?"Assume tax deductible?":"Tax deductible?"}),ke(t.deductible,o=>gn(t,"deductible",o),(t.id==="m1"?"Auto-calc. deduction ":"Tax deductible ")+(t.deductible?"yes":"no"))]}),t.type==="Synthetic loan"&&e.jsx("div",{className:"bvr-poptoghint",style:t.deductible?{color:"var(--credit)",marginTop:-2}:{marginTop:-2},children:t.deductible?"offset taxable capital gains (40/60) each year":"accrued capital losses saved, used against capital gains at liquidation"}),s&&t.deductible&&e.jsx("div",{className:"bvr-poptoghint",style:{marginTop:-2},children:"assumes the interest is paid in cash â capitalized interest isnât deductible while unpaid"})]})]},t.id)}),te.some(t=>!t.open)&&e.jsx("div",{className:"bvr-chiprow",children:te.filter(t=>!t.open).map(t=>e.jsxs("button",{className:"bvr-loanchip"+(yn===t.id?" bvr-flash":""),onClick:()=>{De(t.id,{open:!0}),st("loan-"+t.id)},children:[t.kind==="secured"?t.type==="Second mortgage"?"2nd Mortgage":t.type.split(" /")[0]:t.name,K===t.id?e.jsx("em",{className:"bvr-autotag",children:"auto"}):t.amount>0&&e.jsx(Da,{size:9,className:"bvr-chiplock","aria-hidden":"true"}),t.amount>0&&e.jsx("b",{className:"num",children:xe(t.amount)}),e.jsx(St,{size:11})]},t.id))}),e.jsx("div",{className:"bvr-railnote",children:"Mortgages, 2nd mortgages, and HELOCs amortize; other facilities are interest-only with principal repaid at exit â unless the owner's surplus prepays them early (Payoff ordering below)."}),e.jsxs("button",{className:"bvr-payoffbtn has-help",onClick:t=>F({kind:"help",key:"payoffOrder"},t),children:["Payoff ordering ",e.jsx("em",{children:"default"})]})]})})]}),(()=>{const t=te.filter(a=>a.amount>0),r=t.length===1?[t[0].kind==="secured"?t[0].type.split(" /")[0]:t[0].name,i(t[0].amount)]:["Loans · "+t.length,i(t.reduce((a,n)=>
13a+n.amount,0))];return Xa(e.jsx($i,{size:12}),"Financing","financing",[["Down payment",i(re)],r])})()]}),e.jsxs("aside",{className:"bvr-rail bvr-rail-own",style:{"--ri":2},children:[e.jsxs("div",{className:"bvr-railwide",children:[e.jsxs("button",{className:"bvr-railh bvr-railh-btn",onClick:()=>ge(t=>({...t,ownership:!t.ownership})),"aria-expanded":ce.ownership,children:[e.jsxs("span",{className:"bvr-railh-lab",children:[e.jsx(Tr,{size:13}),"Cost of ownership"]}),ce.ownership?e.jsx(qr,{size:13}):e.jsx(St,{size:13})]}),e.jsx(at,{open:!ce.ownership,children:e.jsxs("div",{className:"bvr-finsum",children:[e.jsxs("div",{className:"bvr-finsumrow clickable",onClick:()=>ge(t=>({...t,ownership:!0})),children:[e.jsx("span",{className:"bvr-finsumlab",children:"Maint., capex & insurance"}),e.jsx("b",{className:"num",children:Js})]}),e.jsxs("div",{className:"bvr-finsumrow clickable",onClick:()=>ge(t=>({...t,ownership:!0})),children:[e.jsx("span",{className:"bvr-finsumlab",children:"Property tax"}),e.jsx("b",{className:"num",children:R(Ht/100)})]}),vr>0&&e.jsxs("div",{className:"bvr-finsumrow clickable",onClick:()=>ge(t=>({...t,ownership:!0})),children:[e.jsx("span",{className:"bvr-finsumlab",children:"HOA / mo"}),e.jsx("b",{className:"num",children:i(vr)})]})]})}),e.jsx(at,{open:ce.ownership,children:e.jsxs("div",{className:"bvr-railb",children:[e.jsx(Se,{label:"Property tax rate",help:t=>F({kind:"line",key:"propTax"},t),children:e.jsx(Le,{v:Ht,set:an})}),e.jsx(Se,{label:Qr,help:t=>F({kind:"line",key:"propTax"},t),children:e.jsxs("div",{className:"bvr-assesswrap",children:[ke(_e,()=>sn(t=>!t),Qr+" "+(_e?"enabled":"disabled"),_e?"Cap enabled â click to disable":"Cap disabled â click to enable"),e.jsx(Le,{v:bt,set:nn,w:42})]})}),e.jsx(Se,{label:"Maintenance",children:e.jsx(Le,{v:Kt,set:on})}),e.jsx(Se,{label:"Capital expenditures",help:t=>F({kind:"help",key:"capexA"},t),children:e.jsx(Le,{v:Jt,set:ln})}),e.jsxs("div",{className:"bvr-fld",children:[e.jsx("label",{children:"Insurance / yr"}),e.jsxs("div",{className:"bvr-insurewrap",children:[e.jsx("button",{className:"bvr-insmodetog"+(ut==="pct"?"":" dol"),title:ut==="pct"?"Percent of market value â click for fixed $/yr":"Fixed $/yr with 3%/yr escalation â click for % of market value",onClick:()=>{if(ut==="pct"){const t=Math.round(G*ur/100/100)*100;as(Math.max(100,t)),rs("dol")}else{const t=Math.round(br/G*100*100)/100;cn(Math.max(.05,t)),rs("pct")}},children:ut==="pct"?"%":"$"}),ut==="pct"?e.jsx(Le,{v:ur,set:cn}):e.jsx(Tt,{v:br,set:as,w:80})]})]}),e.jsx(Se,{label:"HOA / month",children:e.jsx(Tt,{v:vr,set:So,w:80})}),vr>0&&e.jsx(Se,{label:"HOA increase / yr",children:e.jsx(Le,{v:ha,set:To})}),e.jsx(Se,{label:"Renters insurance",children:e.jsx(Tt,{v:_r,set:Co,w:80})}),e.jsx(Se,{label:"Liquidation costs (sale)",help:t=>F({kind:"help",key:"sellPct"},t),children:e.jsx(Le,{v:qt,set:Mo})}),e.jsxs("div",{className:"bvr-note",children:[Qr.startsWith("Assessed cap (")?"Prop 13":"Assessed cap",": the assessed base resets to purchase price, then grows at the capped rate â a lower base than a renter's market-tracking housing cost."]})]})})]}),Xa(e.jsx(Tr,{size:12}),"Cost of ownership","ownership",[["Maint. & ins.",R((Kt+Jt+(ut==="pct"?ur:br/Math.max(1,G)*100))/100)],["Property tax",R(Ht/100)]])]}),e.jsx("div",{className:"bvr-rail-col bvr-rail-col-fin",children:e.jsxs("aside",{className:"bvr-rail bvr-rail-tax",style:{"--ri":3},children:[e.jsxs("div",{className:"bvr-railwide",children:[(()=>{const t=Vi(ne,be,Xe),r=t===37?"TOP":t===35||t===32?"HIGH":null,n=[{MFJ:"Married",MFS:"MFS",HoH:"HoH",Single:"Single"}[ba]||ba,Xe!=="fed"?Xe.toUpperCase():null,r].filter(Boolean);return e.jsxs("button",{className:"bvr-railh bvr-railh-btn",onClick:()=>ge(s=>({...s,tax:!s.tax})),"aria-expanded":ce.tax,children:[e.jsxs("span",{className:"bvr-railh-lab",children:[e.jsx(no,{size:13}),"Tax"]}),e.jsx("span",{className:"bvr-railh-metagrp",children:n.join(" · ")}),ce.tax?e.jsx(qr,{size:13}):e.jsx(St,{size:13})]})})(),e.jsx(at,{open:!ce.tax,children:e.jsxs("div",{className:"bvr-finsum",children:[e.jsxs("div",{className:"bvr-finsumrow clickable",onClick:()=>ge(t=>({...t,tax:!0})),children:[e.jsx("span",{className:"bvr-finsumlab",children:"Ordinary marginal"}),e.jsx("b",{className:"num",children:R(ne/100)})]}),e.jsxs("div",{className:"bvr-finsumrow clickable",onClick:()=>ge(t=>({...t,tax:!0})),children:[e.jsx("span",{className:"bvr-finsumlab",children:"LT cap gains"}),e.jsx("b",{className:"num",children:R(be/100)})]})]})}),e.jsx(at,{open:ce.tax,children:e.jsxs("div",{className:"bvr-railb",children:[e.jsx("div",{className:"bvr-seg filingseg",children:[["Single","Single"],["MFJ","Married"],["MFS","Sep."],["HoH","HoH"]].map(([t,r])=>e.jsx("button",{className:ba===t?"on":"",onClick:()=>pl(t),children:r},t))}),(()=>{const t={fed:"Fed",ca:"CA",ny:"NY",nyc:"NYC"}[Xe]||Xe,r=[...Wa,co].find(s=>{const o=sa(s,Xe);return Math.abs(ne-o.ord)<.05&&Math.abs(be-o.ltcg)<.05}),a=r?r.thr===null?"AMT":r.r+"%":null,n=a?`Tax Presets: ${a} + ${t}`:`Tax Presets · ${t}`;return e.jsxs("button",{className:"bvr-presetbtn",onClick:s=>F({kind:"help",key:"presets"},s),children:[e.jsx(no,{size:12}),n]})})(),e.jsx(Se,{label:"Ordinary marginal",help:t=>F({kind:"help",key:"ord"},t),children:e.jsx(Le,{v:ne,set:Zt,step:_i})}),e.jsx(Se,{label:"LT cap gains",help:t=>F({kind:"help",key:"ltcg"},t),children:e.jsx(Le,{v:be,set:Rr,step:_i})}),e.jsx(Se,{label:"Prop-tax deduction",help:t=>F({kind:"line",key:"saltDed"},t),children:e.jsx("button",{className:"bvr-saltpill num",onClick:t=>{t.stopPropagation(),F({kind:"line",key:"saltDed"},t)}
13,children:et?"$0 · AMT":fr?"$0 · AGI":Gt==="usd"?Qt>0?Ui(Qt)+" cap":"$0":R(Math.max(0,Math.min(100,mr))/100)})}),e.jsx(Se,{label:"Cap gains exclusion",help:t=>F({kind:"help",key:"excl"},t),children:e.jsxs("span",{className:"bvr-seg exclseg",children:[e.jsx("button",{className:ot===25e4?"on":"",onClick:()=>dn(25e4),children:"$250K"}),e.jsx("button",{className:ot===5e5?"on":"",onClick:()=>dn(5e5),children:"$500K"})]})}),e.jsx("div",{className:"bvr-note",children:"Ordinary applies to the cash sleeve and deductible interest. LTCG applies to home gain above the §121 exclusion and the equity sleeve at liquidation."})]})})]}),Xa(e.jsx(no,{size:12}),"Tax","tax",[["Ordinary",R(ne/100)],["LT gains",R(be/100)]])]})}),e.jsxs("div",{className:"bvr-railfoot",children:[e.jsxs("button",{className:"bvr-resetlink",onClick:()=>{Li({scn:!0,fin:!0,own:!0,tax:!0}),kr(!0)},children:[e.jsx(Tc,{size:11})," Reset assumptions","â¦"]}),e.jsxs("button",{className:"bvr-xlsxrail",onClick:_s,title:"Download this scenario as a formula-linked Excel model",children:[e.jsx(Ha,{size:13})," ",hi]})]})]})]}),e.jsxs("section",{className:"bvr-center",children:[e.jsxs("div",{className:"bvr-panel bvr-spinepanel",children:[e.jsxs("button",{type:"button",className:"bvr-panelh bvr-spinehb","aria-expanded":Or,"aria-controls":"bvr-spine-body",onClick:Fo,children:[e.jsx("span",{className:"bvr-ptitle",children:e.jsx("span",{className:"bvr-ptag",children:"Details"})}),e.jsxs("span",{className:"bvr-spinechev",children:[Or?"collapse":"expand",e.jsx(St,{size:13})]})]}),e.jsx(at,{open:!Or,children:e.jsxs("div",{className:"bvr-spine",children:[e.jsxs("span",{className:"bvr-sh bvr-shb",children:[e.jsx(Tr,{size:12}),"Buy"]}),e.jsx("span",{className:"bvr-shy num",children:c===0?"at purchase":"yr "+L+": "+ft(L)}),e.jsxs("span",{className:"bvr-sh bvr-shr",children:[e.jsx(Ia,{size:12}),"Rent"]}),Ri(Mi.filter(t=>t.tot),()=>{fs(!0),ys(!0)},!0),e.jsx("div",{className:"bvr-spineteaser",children:e.jsxs("button",{type:"button",className:"bvr-spinecta",onClick:()=>
13{fs(!0),ys(!0)},children:["Show the full breakdown ",e.jsx(St,{size:12})]})})]})}),e.jsx(at,{open:Or,children:e.jsxs("div",{className:"bvr-spine",id:"bvr-spine-body",children:[e.jsxs("span",{className:"bvr-sh bvr-shb",children:[e.jsx(Tr,{size:12}),"Buy"]}),e.jsx("span",{className:"bvr-shy num",children:c===0?"at purchase":"yr "+L+": "+ft(L)}),e.jsxs("span",{className:"bvr-sh bvr-shr",children:[e.jsx(Ia,{size:12}),"Rent"]}),Ri(Mi),e.jsxs("button",{type:"button",className:"bvr-spinexlsx",onClick:_s,title:"Download this scenario as a formula-linked Excel model",children:[e.jsx(Ha,{size:13})," ",hi]})]})})]}),e.jsxs("button",{className:"bvr-xlsxcard",onClick:_s,children:[e.jsx("span",{className:"bvr-xlsxcard-i",children:e.jsx(Ha,{size:20})}),e.jsxs("span",{className:"bvr-xlsxcard-t",children:[e.jsx("span",{className:"bvr-xlsxcard-h",children:pi?"Password required":"Download the Excel model"}),e.jsx("span",{className:"bvr-xlsxcard-s",children:"Formula-linked · every assumption stays editable"})]}),e.jsx(Fa,{size:18,className:"bvr-xlsxcard-go"})]}),e.jsxs("div",{className:"bvr-foot"+(gs?" bvr-foot-expanded":""),children:["One engine · two after-tax balance sheets liquidated at year ",c,". Pre-liquidation includes mortgage interest and annual taxes but excludes selling costs and gains realized at sale/liquidation â those apply only on an actual sale. ",Te==="California"?"Prop 13":"The assessed cap"," caps the assessed base growth at ",bt,"%/yr. We encourage CPA review of",e.jsxs("span",{className:"bvr-footmore-brief"+(vl?" brief-hidden":""),children:["⦠",e.jsx("button",{type:"button",className:"bvr-footmore","aria-expanded":gs,"aria-controls":"bvr-foot-more",onClick:ml,children:"more"})]}),e.jsxs("span",{id:"bvr-foot-more",className:"bvr-footmore-full",children:[" your assumptions used for: SALT interaction, §121 timing, and per-sleeve deferral precision. A candid note: precise multi-decade after-tax modeling is ultimately impossible. Tax law is rewritten every few years â rates, brackets, caps, credits, and exclusions all move, and fixed-dollar thresholds (the $750k acquisition-debt cap, SALT caps, the §121 exclusion) are not inflation-indexed, so they erode in real terms; inflation, interest-rate, home-price, rent, and investment-return paths are unknowable decades out; insurance, assessments, and state and local rules reprice; and your own income, filing status, and timing will change. Treat the results as a directional comparison of two strategies under one consistent set of assumptions â not a prediction of any specific year's outcome. ",e.jsx("button",{type:"button",className:"bvr-footmore","aria-expanded":gs,"aria-controls":"bvr-foot-more",onClick:xl,children:"less"})]})]}),e.jsxs("div",{className:"bvr-disclosure",children:[e.jsx("div",{className:"bvr-disct",children:"Important disclosures"}),e.jsx("p",{children:"This tool is provided for educational and illustrative purposes only. It is general in nature, does not take your individual circumstances or objectives into account, and does not constitute investment, tax, legal, or accounting advice â nor a recommendation to buy, sell, or finance any home, security, loan, or other asset."}),e.jsx("p",{children:"All results are hypothetical projections driven entirely by the assumptions entered (or the defaults shown). They are estimates, not guarantees: actual results will differ, and modest changes in assumptions â home-price appreciation, rent inflation, investment returns, interest rates, or tax rates â can materially change the comparison. Hypothetical results do not represent actual client accounts, actual trading, or costs of real transactions."}),e.jsx("p",{children:"Tax treatment is intentionally simplified and relies on 2026 planning estimates that may change, including federal and state brackets, SALT caps, AMT treatment, depreciation and recapture, §121, §469, §1256, and property-tax tables. Not every limitation is modeled (for example, §163(d) investment-interest limits and capital-loss limitations). Consult a qualified tax professional (CPA) and your own advisers about your specific situation before acting on any output shown here."})]}),e.jsx("div",{className:"bvr-copyright",children:"© 2026 Impact Labs Inc. All rights reserved."})]})]}),xt&&ya&&e.jsxs("div",{ref:Ps,className:"bvr-slpanel"+(As?" bvr-slwiggle":""),style:Ms?{left:Ms.x,top:Ms.y,right:"auto",bottom:"auto"}:void 0,children:[e.jsxs("div",{className:"bvr-slhead",onPointerDown:Tl,children:[e.jsxs("span",{className:"bvr-slheadt",children:[e.jsx(qi,{size:13,className:"bvr-slgrip"}
13)," Sticky sliders"]}),e.jsx("div",{className:"bvr-slheadr",children:e.jsx("button",{className:"bvr-slclose",onPointerDown:t=>t.stopPropagation(),onClick:()=>fa(!1),"aria-label":"Close sticky sliders",children:e.jsx(Cr,{size:11})})})]}),ya.map((t,r)=>{const a=Sn.find(p=>p.id===t.id)||{},n=t.id==="tax"?null:Xo(t.id);let s=0,o=null,b=null;return t.id==="tax"&&(s=Gr.findIndex(p=>{const u=sa(p,Xe);return Math.abs(ne-u.ord)<.05&&Math.abs(be-u.ltcg)<.05}),s<0&&(s=Gr.reduce((p,u,f)=>Math.abs(sa(u,Xe).ord-ne)<Math.abs(sa(Gr[p],Xe).ord-ne)?f:p,0)),o=Gr[s],b=sa(o,Xe)),e.jsxs("div",{className:"bvr-slrow",children:[e.jsxs("div",{className:"bvr-slmeta",children:[e.jsx("select",{className:"bvr-slsel",value:t.id,onChange:p=>Sl(r,p.target.value),"aria-label":"Slider input",children:Sn.map(p=>e.jsx("option",{value:p.id,children:p.label+(zt.includes(p.id)&&K===p.id?"â[auto]":"")},p.id))}),t.id==="tax"?e.jsxs("span",{className:"bvr-slval bvr-slvaltax num",children:[e.jsx("b",{children:o.thr===null?"AMT 28%":o.r+"%"}),": ",b.ord,"/",b.ltcg,"%"]}):e.jsxs("span",{className:"bvr-slval num",children:[e.jsx("input",{className:"bvr-slvin num",defaultValue:a.kind==="pct"?n+"%":so(n),onFocus:p=>{a.kind!=="pct"&&(p.target.value=Math.round(n)),nt(p)},onPointerDown:p=>p.stopPropagation(),onKeyDown:p=>{p.stopPropagation(),p.key==="Enter"&&p.target.blur()},onBlur:p=>{let u;if(a.kind==="pct")u=parseFloat(p.target.value.replace(/[^0-9.]/g,""));else{const S=/^([0-9.]+)\s*([km])?$/.exec(p.target.value.replace(/[$,\sâ-]/g,"").toLowerCase());u=S?parseFloat(S[1])*(S[2]==="m"?1e6:S[2]==="k"?1e3:1):NaN}isNaN(u)||Os(t.id,Math.max(0,u));const f=isNaN(u)?n:Math.max(0,u);p.target.value=a.kind==="pct"?f+"%":so(f)}},t.id+"-"+Math.round(n)),zt.includes(t.id)&&K===t.id&&e.jsx("em",{className:"bvr-autotag",children:"auto"})]}),ya.length>1&&e.jsx("button",{className:"bvr-slrm",onClick:()=>wa(p=>p.filter((u,f)=>f!==r)),"aria-label":"Remove slider",children:e.jsx(Cr,{size:9})})]}),t.id==="tax"?e.jsx("input",{type:"range",className:"bvr-slrange",min:0,max:Gr.length-1,step:1,value:s,onChange:p=>{const u=Gr[+p.target.value],f=sa(u,Xe);Zt(f.ord),Rr(f.ltcg)}}):a.kind==="pct"?e.jsx("input",{type:"range",className:"bvr-slrange",min:a.min,max:a.max,step:a.step,value:n,onChange:p=>Os(t.id,+p.target.value)}):e.jsx("input",{type:"range",className:"bvr-slrange",min:t.min,max:t.max,step:t.step,value:Math.max(t.min,Math.min(t.max,n)),onChange:p=>Os(t.id,+p.target.value)}),e.jsx("div",{className:"bvr-slticks num",children:t.id==="tax"?e.jsxs(e.Fragment,{children:[e.jsx("span",{children:"10%"}),e.jsx("span",{children:"37%"})]}):a.kind==="pct"?e.jsxs(e.Fragment,{children:[e.jsxs("span",{children:[a.min,"%"]}),e.jsxs("span",{children:[a.max,"%"]})]}):e.jsxs(e.Fragment,{children:[e.jsx("span",{children:xe(t.min)}),e.jsx("span",{children:xe(t.max)})]})})]},r)}),e.jsxs("div",{className:"bvr-slfoot",children:[ya.length<3&&e.jsxs("button",{className:"bvr-sladd",onClick:()=>wa(t=>{const r=t.map(n=>n.id),a=Sn.find(n=>!r.includes(n.id));return[...t,Tn(a?a.id:"price")]}),children:[e.jsx(Cc,{size:11})," Add"]}),e.jsxs("span",{className:"bvr-autopill bvr-slfootpill",title:"Auto-solved to keep funding balanced â "+xr(K)+" "+i(mt(K)),children:[e.jsx("span",{className:"bvr-slfootpre",children:"Auto:"}),e.jsx("span",{className:"bvr-slfootname",children:wl(K)}),e.jsx("span",{className:"bvr-slfootdot","aria-hidden":"true",children:"·"}),e.jsx("span",{className:"bvr-slfootv num",children:so(mt(K))})]})]})]}),ja&&e.jsx("div",{className:"bvr-xlcel"+(ja.closing?" closing":""),onClick:Gl,role:"status","aria-label":"Excel model downloaded",children:e.jsxs("div",{className:"bvr-xlcel-card",onClick:t=>t.stopPropagation(),children:[e.jsx("div",{className:"bvr-xlcel-burst","aria-hidden":"true",children:ja.bits.map(t=>e.jsx("span",{className:"bvr-xlcel-bit"+(t.round?" round":""),style:{"--x":t.x+"px","--y":t.y+"px","--r":t.r+"deg","--sz":t.s+"px","--d":t.d+"ms","--dur":t.dur+"ms",background:t.color}},t.id))}),e.jsxs("div",{className:"bvr-xlcel-seal","aria-hidden":"true",children:[e.jsx("span",{className:"bvr-xlcel-halo"}),e.jsxs("svg",{viewBox:"0 0 56 56",className:"bvr-xlcel-svg",children:[e.jsx("circle",{className:"bvr-xlcel-ring",cx:"28",cy:"28",r:"25"}),e.jsx("path",{className:"bvr-xlcel-tick",d:"M16 29 l8 8 l16 -17"})]})]}),e.jsx("div",{className:"bvr-xlcel-h",children:"Excel model downloaded"}),e.jsxs("div",{className:"bvr-xlcel-sub",children:[ja.sheets," live sheets · every assumption formula-linked · parity-checked against this app"]}),e.jsxs("div",{className:"bvr-xlcel-file",children:[e.jsx(Ha,{size:13})," ",ja.name]})]})}),Al&&e.jsx("div",{className:"bvr-rswrap",onClick:()=>Mn(!1),children:e.jsxs("div",{className:"bvr-rs bvr-xlsxpromo",onClick:t=>t.stopPropagation(),role:"dialog","aria-label":"Get the Excel model",children:[e.jsx("div",{className:"bvr-xlsxpromo-ico",children:e.jsx(Ha,{size:22})}),e.jsx("div",{className:"bvr-rst",children:"Get the Excel model"}),e.jsxs("div",{className:"bvr-rsg",children:["The formula-linked ",e.jsx("b",{children:".xlsx"})," export runs on ",e.jsx("b",{children:"buyvsrent101.com"}
13),". Open the site there to download this scenario as a live Excel model â every assumption stays editable."]}),e.jsxs("div",{className:"bvr-xlsxpromo-foot",children:[e.jsx("button",{className:"bvr-rscancel inline",onClick:()=>Mn(!1),children:"Not now"}),e.jsxs("a",{className:"bvr-rsgo bvr-xlsxpromo-go",href:"https://buyvsrent101.com",target:"_blank",rel:"noopener noreferrer",onClick:()=>Mn(!1),children:["Continue to BuyVsRent101.com ",e.jsx(Fa,{size:14})]})]})]})}),Rl&&e.jsx("div",{className:"bvr-rswrap",onClick:Vs,children:e.jsx("div",{className:"bvr-rs slim",onClick:t=>t.stopPropagation(),role:"dialog","aria-label":ei?"Subscribed":"Optional email updates",children:ei?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"bvr-rst",children:"Downloading now"}),e.jsx("div",{className:"bvr-rsfoot",children:e.jsx("button",{className:"bvr-rsgo ghost",onClick:Vs,children:"Close"})})]}):e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"bvr-rst",children:"Get the detailed model"}),e.jsx("div",{className:"bvr-rsg",children:Ws?"Add your email to keep downloading.":"Add your email"}),e.jsx("input",{className:"bvr-xlsxmail",type:"email",inputMode:"email",autoComplete:"email",placeholder:"[email protected]",value:En,onChange:t=>zl(t.target.value),onKeyDown:t=>{t.key==="Enter"&&ci()},"aria-label":"Email address"}),e.jsxs("button",{className:"bvr-rsopt bvr-rschk"+(ka?" on":""),onClick:()=>Pl(t=>!t),"aria-pressed":ka,children:[e.jsx("span",{className:"bvr-rschkbox","aria-hidden":"true",children:ka&&e.jsx(Wn,{size:11})}),e.jsx("span",{className:"bvr-rsoptl",children:"Email me model updates"})]}),e.jsxs("button",{className:"bvr-rsopt bvr-rschk"+(An?" on":""),onClick:()=>Ll(t=>!t),"aria-pressed":An,children:[e.jsx("span",{className:"bvr-rschkbox","aria-hidden":"true",children:An&&e.jsx(Wn,{size:11})}),e.jsx("span",{className:"bvr-rsoptl",children:"Look at my numbers"})]}),e.jsx("div",{className:"bvr-rsg bvr-xlsxmail-fine",children:"Never sold. Unsubscribe anytime."}),e.jsxs("div",{className:"bvr-rsfoot",children:[e.jsx("button",{className:"bvr-rscancel inline",onClick:Ws?Vs:Kl,children:Ws?"No thanks":"Skip for now"}),e.jsx("button",{className:"bvr-rsgo ghost",disabled:!ii(En)||!ka,onClick:ci,children:"Subscribe & download"})]})]})})}),Dl&&e.jsx("div",{className:"bvr-rswrap",onClick:()=>qs(!1),children:e.jsxs("div",{className:"bvr-rs slim",onClick:t=>t.stopPropagation(),role:"dialog","aria-label":"Password required",children:[e.jsx("div",{className:"bvr-rst",children:"Password required"}),e.jsx("div",{className:"bvr-rsg",children:"To keep downloading, enter your password."}),e.jsx("input",{className:"bvr-xlsxmail",type:"password",autoComplete:"off",placeholder:"Password",value:Rn,onChange:t=>{Fl(t.target.value),zn(!1)},onKeyDown:t=>{t.key==="Enter"&&di()},"aria-label":"Password","aria-invalid":ti||void 0}),ti&&e.jsx("div",{className:"bvr-rsg bvr-xlsxmail-bad",children:"That password didnât work."}),e.jsxs("div",{className:"bvr-rsfoot",children:[e.jsx("button",{className:"bvr-rscancel inline",onClick:Jl,children:"Request one"}),e.jsx("button",{className:"bvr-rsgo ghost",disabled:!Rn.trim(),onClick:di,children:"Unlock"})]})]})}),Qs&&e.jsx("div",{className:"bvr-rswrap",onClick:()=>kr(!1),children:e.jsxs("div",{className:"bvr-rs slim",onClick:t=>t.stopPropagation(),role:"dialog","aria-label":"Reset assumptions",children:[e.jsx("div",{className:"bvr-rst",children:"Reset assumptions"}),e.jsx("div",{className:"bvr-rsg",children:"Selected groups return to the app defaults. One undo step (Ctrl-Z) brings everything back."}),Oi.map(t=>e.jsxs("button",{className:"bvr-rsopt bvr-rschk"+(Ca[t.id]?" on":""),onClick:()=>Li(r=>({...r,[t.id]:!r[t.id]})),"aria-pressed":Ca[t.id],children:[e.jsx("span",{className:"bvr-rschkbox","aria-hidden":"true",children:Ca[t.id]&&e.jsx(Wn,{size:11})}),e.jsx("span",{className:"bvr-rsoptl",children:t.label})]},t.id)),e.jsxs("div",{className:"bvr-rsfoot",children:[e.jsx("button",{className:"bvr-rscancel inline",onClick:()=>kr(!1),children:"Cancel"}),e.jsx("button",{className:"bvr-rsgo ghost",disabled:!Object.values(Ca).some(Boolean),onClick:fc,children:"Reset selected"}),e.jsx("button",{className:"bvr-rsgo",onClick:yc,title:"All defaults, panels collapsed, holding period back to launch",children:"Reset all"})]})]})}),Fe&&(()=>{const{cur:t,gap:r,opts:a,rec:n}=Jo(),s=(o,b)=>e.jsxs(e.Fragment,{children:[i(o)," ","â"," ",i(b)]});return e.jsx("div",{className:"bvr-rswrap",onClick:Cs,children:e.jsxs("div",{className:"bvr-rs",onClick:o=>o.stopPropagation(),role:"dialog","aria-label":"Select auto-adjusting input",children:[e.jsx("div",{className:"bvr-rst",style:{color:"var(--accent)"},children:"Select auto-adjusting input"}),e.jsx("div",{className:"bvr-rsg",children:Fe.mode==="claim"?e.jsxs(e.Fragment,{children:[xr(Fe.k)," is auto-solved right now. Pick a new auto-solve field so you can drive ",xr(Fe.k)," directly:"]}):r>0?e.jsxs(e.Fragment,{children:["Purchase price now exceeds financing by ",e.jsx("b",{className:"num",children:i(r)}),". Pick what absorbs it:"]}):e.jsxs(e.Fragment,{children:["Financing now exceeds the purchase price by ",e.jsx("b",{className:"num",children:i(-r)}),". Pick what absorbs it:"]})}),ga?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"bvr-rsg",style:{marginTop:2},children:"What kind of financing?"}),Wi.map(o=>e.jsxs("button",{className:"bvr-rsopt",onClick:()=>Cl(ga,o),children:[e.jsx("span",{className:"bvr-rsoptl",children:o}),e.jsx("span",{className:"bvr-rsoptv num",children:s(0,(a.find(b=>b.f===ga)||{}).target||0)})]},o)),e.jsxs("button",{className:"bvr-rscancel",onClick:()=>Jr(null),children:["â"," Back"]})]}):e.jsxs(e.Fragment,{children:[a.map(o=>e.jsxs("button",{className:"bvr-rsopt"+(o.f===n?" rec":"")+(o.ok?"":" off")+(o.isAdd?" bvr-rsadd":""),disabled:!o.ok,onClick:()=>o.isAdd?Jr(o.f):Ts(o.f),children:[e.jsxs("span",{className:"bvr-rsoptl",children:[o.isAdd?"Add another financing solution":o.label,o.f===n&&e.jsx("em",{className:"bvr-rsrec",children:"recommended"})]}),!o.isAdd&&e.jsx("span",{className:"bvr-rsoptv num",children:o.ok?s(t[o.f],o.target):"not enough"})]},o.f)),e.jsx("button",{className:"bvr-rscancel",onClick:Cs,children:Fe.mode==="claim"?"Cancel":`Undo my edit (${xr(Fe.k)} back to ${i(Fe.prev)})`})]})]})})})(),W&<&&e.jsxs("div",{ref:Lr,className:"bvr-pop",style:{left:bn?bn.x:ps.left,top:bn?bn.y:ps.top,visibility:ps.ready?"visible":"hidden"},onClick:t=>{t.stopPropagation(),Bo(null)},children:[W.kind==="line"&&(()=>{const t=y>1?Math.max(0,Math.min(1,(L-1)/(y-1))):1,r=c===0&&W.key==="balSheet";return e.jsx("div",{className:"bvr-minihz","aria-hidden":"true",children:e.jsx("div",{className:"bvr-minihz-fill",style:{width:`max(${(t*100).toFixed(1)}%, ${r?70:40}px)`},children:e.jsx("span",{children:r?"at purchase":ft(L)})})})})(),e.jsxs("div",{className:"bvr-popdrag"+(lt.titleContent?" custom":""),onPointerDown:gl,children:[e.jsx(qi,{size:12,className:"bvr-popgrip"}),lt.titleContent?lt.titleContent:e.jsx("div",{className:"bvr-popt",children:lt.title})]}),lt.subtitle&&e.jsx("div",{className:"bvr-popsub",children:lt.subt
13itle}),e.jsx("div",{className:"bvr-popgrid",children:lt.rows.map((t,r)=>t.hr?e.jsx("hr",{className:"bvr-pophr"},r):t.hero?e.jsxs("div",{className:"bvr-pophero dir-"+t.hero.dir,children:[e.jsxs("div",{className:"bvr-popherotop",children:[e.jsxs("span",{className:"bvr-popherowho",children:[t.hero.who," net surplus"]}),e.jsx("b",{className:"num bvr-popheroamt",children:t.hero.amt})]}),e.jsx("span",{className:"bvr-popherocap",children:t.hero.caption})]},r):t.head?e.jsx("div",{className:"bvr-pophead",children:t.head},r):t.custom?e.jsx("div",{className:"bvr-popcustom",children:t.custom},r):e.jsxs(Hi.Fragment,{children:[e.jsxs("span",{className:"l "+(t.k||"")+(t.p13?" p13":"")+(t.hl?" bvr-pophl"+(t.hlTone?" hl-"+t.hlTone:""):""),children:[t.l,t.rate&&e.jsx(Yn,{rate:t.rate})]}),e.jsxs("span",{className:"num v "+(t.k||"")+(t.p13?" p13":"")+(t.hl?" bvr-pophl"+(t.hlTone?" hl-"+t.hlTone:""):""),children:[t.src&&e.jsx("a",{className:"bvr-poplsrc",href:t.src,target:"_blank",rel:"noopener noreferrer",onClick:a=>a.stopPropagation(),"aria-label":"Open the source for this auto-filled rate",children:"source"}),t.custom_v?t.custom_v:t.editVal?e.jsx(Yn,{rate:t.editVal,inValue:!0}):t.v]})]},r))}),lt.notes&<.notes.length>0&&e.jsxs(e.Fragment,{children:[e.jsxs("button",{type:"button",className:"bvr-popnotetog","aria-expanded":vs,onClick:t=>{t.stopPropagation(),ms(r=>!r)},children:[e.jsx("span",{children:"notes"}),e.jsx(St,{size:11,className:vs?"open":""})]}),e.jsx(at,{open:vs,children:e.jsx("div",{className:"bvr-popnotebody",children:lt.notes.map((t,r)=>e.jsx("div",{className:"bvr-popnote",children:t},r))})})]}),lt.footer&&e.jsx("div",{className:"bvr-popfoot",children:lt.footer})]})]})}const Zc=` 14@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); 15/* ----------------------------------------------------------------------------- 16 Color roles â the single source of truth. Every chart, legend, callout, and 17 segment resolves color via one of these vars; no chart hard-codes a hex. Rules 18 of thumb when editing: 19 --buy the buy strategy (assets, principal, buy-path elements). Cool blue. 20 --rent the rent strategy (rent burn, renter's portfolio, rent-path elements). 21 Warm amber â deliberately NOT green so it never shares hue with --credit. 22 --credit tax benefits, refunds, positive cash-back items on the BUY side. 23 Green â the universal "money coming back" signal. 24 --accent app chrome (panel borders, focus rings, primary CTA hints). Teal. 25 --neg losses and negative outcomes (rare in the chart, mostly for "leads by 26 â$X" verdict states). Red-brown. 27 For an eventual dark mode: keep semantics identical, dim the surfaces and 28 nudge the roles toward slightly desaturated/lightened variants. All chart code 29 below just reads var(--role), so a .bvr-root.dark selector is enough to swap. 30 ----------------------------------------------------------------------------- */ 31.bvr-root{ 32 --bg:#F6F5F1; --card:#FFFFFF; --ink:#13242E; --muted:#5C6B76; --faint:#93A0A8; 33 --line:#E7E9EA; --line2:#D3D8DB; 34 /* EYEBROW SCALE (one token, two levels: the page masthead .bvr-eyebrow and the panel 35 section markers .bvr-ptag). These name the section you are looking at -- primary 36 wayfinding in a long scrolling tool -- so they are sized to be READ, not squinted at. 37 At 9.5px the panel eyebrow was smaller than the 12px subtitle beneath it, which inverted 38 the hierarchy; the Details panel now has no subtitle at all, so its eyebrow is the whole 39 header. Fluid 11px at 390 to 13px at 1400 (0.2vw + 10.2px is the line through both 40 points), clamped so phones stay compact and wide desktops do not shout. Tracking stays 41 in em on each rule, so it scales with the size instead of over-tracking at the top end. 42 The compliance eyebrow (.bvr-disct) deliberately does NOT use this -- it is chrome, not 43 navigation, and stays at 9.5px. */ 44 --eyebrow:clamp(11px, 0.2vw + 10.2px, 13px); 45 --buy:#2E6FB8; --prin:#7B6FB8;
45 /* buy strategy â cool blue */ 46 --buyGross:color-mix(in srgb, var(--buy) 58%, var(--card)); /* "before-tax" gross tone â shared by chart-1 GROSS figure + chart-2 gross dashed line */ 47 --rent:#B45309; /* rent strategy â warm amber (was #128A63 green â collided with --credit) */ 48 --credit:#128A63; /* tax benefits on the buy path â forest green */ 49 --accent:#0E7C86; /* app chrome / focus / accents â teal */ 50 --accentBg:#E7F1F0; 51 --neg:#BE5548; /* losses and negatives â muted red-brown */ 52 --sans:"Inter",ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif; 53 --num:var(--sans); /* numerals: same sans, tabular figures via .num (swap back to a mono stack here to revert) */ 54 /* Top offset for the STICKY-SLIDERS PANEL ONLY (its default spawn position + the drag 55 clamp's minimum y). position:fixed, viewport-anchored â the 200px default clears the 56 production wrapper's site header. Standalone embeds without a header should override: 57 .bvr-root { --sticky-top: 10px; } 58 Nothing else consumes this var (per spec): the pinned rail is position:sticky in the 59 page flow (plain top:10px), and the mobile title fabs sit ON the title row (top:14px). */ 60 --sticky-top:200px; 61 background:var(--bg); color:var(--ink); border-radius:14px; padding:18px 20px; 62 font-family:var(--sans); 63 font-size:14px; line-height:1.4; -webkit-font-smoothing:antialiased; 64} 65.bvr-root *{box-sizing:border-box;} 66.num{font-family:var(--num); font-variant-numeric:tabular-nums lining-nums; letter-spacing:-0.01em;} 67.bvr-in{height:27px; border:0.5px solid var(--line2); border-radius:6px; background:var(--card); 68 padding:0 7px; font-size:12px; text-align:right; color:var(--ink); box-sizing:border-box; max-width:100%;} 69.bvr-in:focus{outline:none; border-color:var(--accent); box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);} 70/* In-field clear-to-zero on the loan amount inputs. Reserve right padding so the right-aligned 71 number never slides under the button; the button sits in that reserved gutter. Muted at rest, 72 the same red-brown the finsum clear uses on hover â subtle until you mean it. */ 73.bvr-in-clearable{padding-right:26px;} 74.bvr-amtx{position:absolute; right:5px; top:50%; transform:translateY(-50%); 75 display:inline-flex; align-items:center; justify-content:center; width:18px; height:18px; 76 border-radius:999px; border:0.5px solid transparent; background:transparent; color:var(--faint); 77 padding:0; cursor:pointer; transition:color 130ms ease, background 130ms ease, border-color 130ms ease;} 78.bvr-amtx:hover{color:#E23B2E; background:#FDECEA; border-color:#E23B2E;} 79.bvr-amtx:focus-visible{outline:1.5px solid var(--accent); outline-offset:1px;} 80 81.bvr-head{display:flex; align-items:flex-start; justify-content:space-between; gap:16px; margin-bottom:14px;} 82.bvr-head > div{min-width:0;} 83/* Minimal header â the eyebrow+h1 have moved down to .bvr-titleblock (above the first chart); 84 the header now only carries the mobile sprocket, right-aligned. Zero bottom margin so it 85 doesn't leave a gap above the body when the sprocket is hidden on desktop. */ 86.bvr-head-min{display:none;} /* fabs relocated into .bvr-titlefabs on the title row */ 87/* Narrow-mode sticky-sliders fab: matched to the sprocket's 40Ã40 box so the two buttons 88 share a baseline (the misalignment in the wild was an unsized fab shorter than the cog). 89 Sits to the LEFT of the sprocket via DOM order. */ 90.bvr-mobilefab{display:none;} 91@media (max-width:700px){ 92 /* Both narrow fabs (sticky sliders + settings) float over the scrolling page AND overlap the 93 site header, so at rest they are MOSTLY OPAQUE â a near-solid card chip with a clear border 94 and a legible muted glyph â so the icon reads against anything behind it. The rest/hover 95 distinction is a BORDER effect (opacity no longer differentiates them): hover lifts the 96 border + glyph to accent and the surface to fully solid. */ 97 .bvr-mobilefab, .bvr-head-cog{display:inline-flex; align-items:center; justify-content:center; 98 width:34px; height:32px; flex-shrink:0; border-radius:9px; 99 border:1px solid var(--line2); 100 background:color-mix(in srgb, var(--card) 90%, transparent); 101 -webkit-backdrop-filter:blur(4px); backdrop-filter:blur(4px); 102 color:var(--muted); cursor:pointer; font-family:inherit; 103 -webkit-tap-highlight-color:transparent; 104 transition:background 140ms ease, transform 90ms ease, color 140ms ease, border-color 140ms ease;} 105 .bvr-mobilefab:hover, .bvr-head-cog:hover{color:var(--accent); 106 border-color:var(--accent); background:var(--card);} 107 .bvr-mobilefab:active, .bvr-head-cog:active{transform:scale(0.94);} 108 .bvr-mobilefab:focus-visible, .bvr-head-cog:focus-visible{color:var(--accent); 109 outline:2px solid var(--accent); outline-offset:2px;} 110 /* Sticky-sliders ON: the panel is open, so its trigger is clearly active â accent border + tint. */ 111 .bvr-mobilefab.on{color:var(--accent); border-color:var(--accent); 112 background:color-mix(in srgb, var(--accent) 14%, var(--card));} 113} 114.bvr-eyebrow{font-size:var(--eyebrow); text-transform:uppercase; letter-spacing:.14em; color:var(--accent); font-weight:600;} 115.bvr-h1{font-size:24px; font-weight:600; margin:2px 0 0; letter-spacing:-0.02em;} 116/* Title block above the first chart â the page's visual entry point. Left-aligned to match
117 the chart's content edge; small bottom margin ties it to the panel below it. */ 118.bvr-titleblock{margin:0 2px 12px; display:flex; align-items:flex-end; justify-content:space-between; gap:12px;} 119.bvr-titleblockl{min-width:0;} 120/* Narrow-only fab pair, LEFT of the title, STICKY so they follow the single-column scroll. 121 Order-first in the flex row; the title sits to their right. Desktop hides them (rail pills 122 + the title-row icon cover those widths). */ 123.bvr-titlefabs{display:none;} 124@media (max-width:700px){ 125 /* Title centers across the full row; the fabs no longer share this row (they overlay from 126 the top-right of the hero card), so no gutter/spacer balancing is needed. */ 127 .bvr-titleblock{align-items:center; justify-content:center;} 128 .bvr-titleblockl{flex:1 1 auto; text-align:center;} 129 /* Fab pair: FIRST child of .bvr-body, placed to SPAN the hero+rails grid rows (grid-row 1/3) 130 so position:sticky travels both the charts AND the assumptions section, unsticking only at 131 the detailed spine (row 3). A zero-height, justify-self:end overlay: at rest it sits at the 132 top-right wherever the panel lands under the site header; on scroll it sticks 1px below the 133 viewport top. row so DOM order (sprocket, sliders) renders as [sprocket][sliders] â sprocket 134 LEFT, sliders far right â nudged hard toward the viewport edge with a negative right margin. 135 align-items:flex-start hangs both buttons DOWN from the top:1px line (a zero-height row 136 centered would clip them above the viewport); each button's margin-top:1px re-centers them 137 after the -2px height so they sit exactly where they did in the header. Override the stick 138 offset for a fixed site header via .bvr-root{--fab-top:<px>}. */ 139 .bvr-titlefabs{display:flex; flex-direction:row; align-items:flex-start; gap:8px; 140 grid-column:1; grid-row:1 / 3; justify-self:end; align-self:start; 141 position:sticky; top:var(--fab-top, 1px); z-index:75; height:0; padding-right:0; margin-right:-16px; 142 pointer-events:none;} 143 .bvr-titlefabs > button{position:static; pointer-events:auto; margin-top:1px;} 144 /* Sprocket ("jump to assumptions") fades out once scrolled into the assumptions section â its 145 job is done there â while the sticky-sliders toggle beside it stays. Space is preserved 146 (opacity, not display) so the sliders button never shifts. */ 147 .bvr-head-cog.faded{opacity:0; pointer-events:none;} 148} 149.bvr-stickytog-title{display:none;} /* shown only on desktop two-column layouts */ 150@media (min-width:701px){ 151 .bvr-stickytog-title{display:inline-flex; flex-shrink:0; align-self:center; background:none; 152 border:none; padding:4px; color:var(--faint); cursor:pointer; transition:color 130ms ease;} 153 .bvr-stickytog-title:hover{color:var(--accent);} 154 .bvr-stickytog-title.on{color:var(--accent);} 155 /* Parallel to the iPhone always-reachable fab: when the rail is NOT pinned (it scrolls away 156 with the page), lift this redundant top-right toggle out of flow and FIX it to the viewport 157 top-right so the sticky-sliders control stays reachable while scrolling the charts. Given a 158 subtle floating-card surface (like the mobile fabs) so it reads as a control, not text. 159 When the rail IS pinned, its own labeled Sticky-sliders pill stays visible â so this stays 160 in-flow (no .floatwhenunpinned class) and scrolls off normally, avoiding a double control. */ 161 .bvr-stickytog-title.floatwhenunpinned{position:fixed; top:14px; right:18px; z-index:76; 162 padding:8px; border-radius:10px; color:var(--faint); 163 border:0.5px solid color-mix(in srgb, var(--line2) 45%, transparent); 164 background:color-mix(in srgb, var(--card) 22%, transparent); 165 -webkit-backdrop-filter:blur(3px); backdrop-filter:blur(3px); 166 transition:color 140ms ease, background 140ms ease, border-color 140ms ease, backdrop-filter 140ms ease;} 167 .bvr-stickytog-title.floatwhenunpinned:hover{color:var(--accent); border-color:var(--line2); 168 background:color-mix(in srgb, var(--card) 82%, transparent); 169 -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);} 170 .bvr-stickytog-title.floatwhenunpinned.on{color:var(--accent); border-color:var(--accent); 171 background:color-mix(in srgb, var(--accent) 14%, var(--card));} 172} 173/* Header sprocket â smooth-scroll-to-assumptions. Hidden by default (wide layouts put the rails 174 in the visible left column); revealed at â¤700px where the rails have stacked below the fold. 175 NOTE: this default display:none sits AFTER the â¤800px shared-fab rule in source, so it would 176 override it and keep the cog hidden even on narrow â hence the explicit re-reveal below (its 177 surface/hover/on styling still comes from the shared .bvr-mobilefab,.bvr-head-cog rule). */ 178.bvr-head-cog{display:none;} 179@media (max-width:700px){ .bvr-head-cog{display:inline-flex;} } 180.bvr-verdict{text-align:right; min-width:0;} 181.bvr-verdict-top{text-align:center; margin-bottom:12px;} 182/* Mid-page verdict (moved below the holding-period slider). Centered like the old top verdict, 183 with generous vertical breathing room so it reads as the page's conclusion after the reader 184 has scrubbed the horizon, not as a caption crowding the slider. */ 185.bvr-verdict-mid{text-align:center; margin:22px auto 6px; max-width:720px;} 186.bvr-vlead{font-size:16px; font-weight:600; letter-spacing:-0.01em; overflow-wrap:anywhere;} 187.bvr-verdict-top .bvr-vlead, .bvr-verdict-mid .bvr-vlead{font-size:19px;} 188/* Line 2 â after-tax net worth. Smaller than line 1 (subordinate) but still bold and ink-colored 189 so it reads as a real second metric, not caption text. Split-direction case gets an inline 190 color override (see JSX) matching the after-tax winner. margin-top:7px creates a clear break 191 between the primary group (line 1 + its "pre-liquidation, at yr N" subtitle) and this line, 192 so the eye groups them correctly: (verb+value+description) â (secondary metric) â (context). */ 193.bvr-vlead-sub{font-size:14px; font-weight:600; color:var(--ink); margin-top:7px; 194 letter-spacing:-0.005em; overflow-wrap:break-word;} 195.bvr-verdict-top .bvr-vlead-sub, .bvr-verdict-mid .bvr-vlead-sub{font-size:15px;} 196.bvr-vsub{font-size:11.5px; color:var(--muted); margin-top:1px; overflow-wrap:break-word;} 197/* Scroll target for the header sprocket â a small top margin so when we scroll into view the 198 assumptions header sits comfortably below the top of the viewport rather than flush against it. */ 199.bvr-railstack{scroll-margin-top:16px;} 200 201.bvr-hz{display:flex; align-items:center; gap:10px; flex:1 1 auto; max-width:820px;} 202/* Two BE labels stacked above/below the rail need vertical breathing room around the bar.
203 Bumped from margin:6px/14px to 20px/24px so the Cash BE (above) and Wealth BE (below) 204 never bump into adjacent panels' headers or captions. */ 205.bvr-hz-below{max-width:820px; width:100%; margin:20px auto 24px;} 206.hzbar{position:relative; flex:1; height:24px; border-radius:999px; border:1px solid var(--line2); 207 background:var(--line); overflow:visible; cursor:ew-resize; touch-action:none; user-select:none;} 208/* Keyboard focus ring -- app convention (2px accent, offset 2). :focus-visible only: pointer 209 users click/drag the rail constantly and a ring after every click would be pure noise; the 210 UA heuristic paints it for keyboard focus but not pointer. The outline inherits the pill 211 radius, and every ancestor up to .bvr-center is overflow-visible so it cannot be sheared. */ 212.hzbar:focus-visible{outline:2px solid var(--accent); outline-offset:2px;} 213/* Rail fill: darker/more-opaque so the "already committed" range reads clearly against the 214 empty rail. Was 50% buy â 68% buy. The border-radius on the parent hzbar clips the fill's 215 right edge naturally via overflow (see .hzbar-inner below); the fill itself has left-only 216 radius so its right edge stays flat while filling in. */ 217.hzfillB{position:absolute; left:0; top:0; bottom:0; background:color-mix(in srgb, var(--buy) 68%, var(--card)); 218 border-radius:999px 0 0 999px;} 219/* Periodic 10/20/30 ticks removed (see slider JSX) â only the Cash BE and Wealth BE marks 220 remain. Rule kept as a no-op for any downstream artifacts that still reference the class. */ 221.hztick{display:none;} 222/* "Holding period" caption. Centered on the target x via translate(-50%,-50%). Its position 223 comes from JS: either centered in the fill zone (left of thumb) OR centered in the empty 224 zone (right of thumb) depending on the E<10 rule â see renderHzSlider. When it sits LEFT 225 (over the blue fill) it's white with a shadow; when it sits RIGHT (over the light empty 226 track) white would be invisible, so .onright switches it to buy-blue ink with no shadow. */ 227.hzcap{position:absolute; top:50%; transform:translate(-50%, -50%); font-size:10px; font-weight:700; 228 color:#fff; white-space:nowrap; pointer-events:none; text-shadow:0 1px 2px rgba(19,36,46,.35);} 229.hzcap.onright{color:var(--buy); text-shadow:none;} 230.hzthumb{position:absolute; top:50%; transform:translateY(-50%); background:var(--buy); border:1px solid var(--buy); 231 color:#fff; font-size:10px; font-weight:700; padding:2px 8px; border-radius:999px; pointer-events:none; 232 box-shadow:0 1px 4px rgba(19,36,46,.18); white-space:nowrap; width:160px; text-align:center; box-sizing:border-box; 233 overflow:hidden; text-overflow:ellipsis;} 234/* Compact thumb sized to fill the rail's outer height (border-box 26px = rail's 24px content 235 + 2px border), eliminating the visible gap above and below that made the thumb read as a 236 small floating pill inside a taller track. box-sizing:border-box counts the thumb's own 237 1px border into the height so top:50% + translateY(-50%) lines its outer edges up with 238 the rail's outer edges â thumb border sits directly over rail border. Flex centering keeps 239 the year text visually centered even without vertical padding. */ 240.hzthumb.compact{width:auto; min-width:52px; height:26px; padding:0 12px; box-sizing:border-box; 241 display:flex; align-items:center; justify-content:center; 242 transform:translate(-50%, -50%);} 243/* BE tick marks. Cash BE and Wealth BE share the same base geometry â thin vertical marker 244 dropping through the rail â but distinct data-color hooks let their labels above/below use 245 subtly different tones if we want to color-code them later. Both are the same neutral ink 246 here; the ABOVE/BELOW placement of their labels does the disambiguation. */ 247.hzbe{position:absolute; top:3px; bottom:3px; width:2px; background:var(--ink); opacity:.55; transform:translateX(-50%);} 248.hzmaxtag{position:absolute; right:8px; top:50%; transform:translateY(-50%); font-size:8.5px; color:var(--faint); pointer-events:none;} 249.dmn{display:flex; flex-direction:column; gap:7px; padding:2px 0;} 250.dmnrow{display:flex; align-items:center; justify-content:space-between; font-size:11.5px; color:var(--muted); gap:10px;} 251.dmnsub{font-size:9.5px; color:var(--faint); text-align:right; margin-top:-4px;} 252.cllseg{margin-bottom:0; flex-shrink:0;} 253.cllseg button{padding:4px 10px; font-size:10.5px;} 254.dmnstep{display:inline-flex; align-items:center; gap:8px;} 255.dmnstep button{width:22px; height:22px;
255 border:0.5px solid var(--line2); border-radius:6px; background:var(--card); 256 color:var(--accent); font-size:13px; cursor:pointer; display:flex; align-items:center; justify-content:center;} 257.dmnstep button:hover{border-color:var(--accent);} 258.dmnstep b{min-width:26px; text-align:center; color:var(--ink); font-weight:700;} 259.dmnnote{font-size:10.5px; color:var(--muted);} 260.dmnnote b{color:var(--ink);} 261.dmnpre{display:flex; gap:5px; flex-wrap:wrap;} 262.dmnpre button{flex:1; min-width:54px; font-size:10px; padding:5px 6px; border:0.5px solid var(--line2); border-radius:7px; 263 background:var(--card); color:var(--muted); cursor:pointer; white-space:nowrap;} 264.dmnpre button.on,.dmnpre button:hover{border-color:var(--accent); color:var(--accent);} 265.unitseg,.bvr-seg.unitseg{display:inline-flex; border:0.5px solid var(--line2); border-radius:6px; overflow:hidden;} 266.unitseg button{width:30px; height:26px; border:none; background:var(--card); color:var(--muted); font-size:11px; cursor:pointer;} 267.unitseg button.on{background:var(--accentBg); color:var(--accent); font-weight:600;} 268/* BE labels â left-of-tick and right-of-tick. Two vertical placements: .above (Cash BE) 269 sits just outside the rail's top edge; .below (Wealth BE) sits just outside the bottom. 270 This vertical separation means the two markers can share a year without visually colliding. 271 The base positioning (no .above / .below) is centered on the rail â kept for compatibility 272 with any external callers that use the legacy layout. */ 273.hzbeL{position:absolute; top:50%; transform:translate(calc(-100% - 5px),-50%); font-size:9px; color:var(--muted); pointer-events:none; white-space:nowrap;} 274.hzbeR{position:absolute; top:50%; transform:translate(5px,-50%); font-size:9px; color:var(--muted); pointer-events:none; white-space:nowrap;} 275.hzbeL.above{top:auto; bottom:calc(100% + 3px); transform:translateX(calc(-100% - 5px));} 276.hzbeR.above{top:auto; bottom:calc(100% + 3px); transform:translateX(5px);} 277.hzbeL.below{top:calc(100% + 3px); transform:translateX(calc(-100% - 5px));} 278.hzbeR.below{top:calc(100% + 3px); transform:translateX(5px);} 279/* Centered single-label variant â "Cash BE yr N" / "Wealth BE yr N" render as ONE label 280 CENTERED on the tick (user ask: yr index, not calendar year â the thumb right there 281 already carries the calendar year). The L/R split classes above stay for the bare-tick 282 title/legacy geometry. */ 283.hzbeC{position:absolute; font-size:9px; color:var(--muted); pointer-events:none; white-space:nowrap; transform:translateX(-50%);} 284.hzbeC.above{bottom:calc(100% + 3px);} 285.hzbeC.below{top:calc(100% + 3px);} 286 287.bvr-body{--railw:214px; display:grid; grid-template-columns:var(--railw) minmax(0,820px); grid-template-areas:"rails hero" "rails center"; column-gap:16px; row-gap:14px; align-items:start; justify-content:center; max-width:1050px; margin-inline:auto;} 288/* summary hero â sits above the center content, sharing its horizontal center */ 289.bvr-cards-wrap{grid-area:hero; max-width:820px; margin-inline:auto; width:100%; position:relative;} 290 291.bvr-center{grid-area:center; min-width:0; display:flex; flex-direction:column; gap:14px; max-width:820px; width:100%; align-self:start; margin-inline:auto;} 292 293/* both assumption rails stack in the left column */ 294.bvr-rail{min-width:0; overflow:clip; padding-inline:1px;} /* 1px inline pad keeps the panels' 0.5px right border from being sheared off by overflow:clip */ 295.bvr-rail-col-fin{min-width:0; display:flex; flex-direction:column; gap:10px;} 296/* the stack owns the left grid cell; Scenario + Financing/Tax stack inside it */ 297.bvr-railstack{grid-area:rails; display:flex; flex-direction:column; gap:10px; align-self:start;} 298/* Rails wrapper â see the JSX. display:contents at all base widths so mobile flex + mid-width 299 column-count both see rails as direct children of railstack; only the desktop breakpoint 300 below promotes it to a real grid item to inherit the parent's row tracks via subgrid. */ 301.bvr-railpanels{display:contents;} 302/* --- Pinnable assumptions column (desktop two-column layout only) ------------------------ 303 Pinned = position:sticky + the railstack becomes its OWN scroll container. This is the 304 short-viewport escape hatch: a tall expanded stack scrolls INTERNALLY (thin scrollbar, 305 overscroll contained so rail-scroll doesn't chain into page-scroll at the ends) while the 306 page scroll moves the charts. When the stack fits the viewport it has no overflow and 307 behaves like plain sticky. The grid area "rails" spans both body rows, so sticky has the 308 full page height to travel. The pin pill is sticky top:0 WITHIN the scrolling rail so 309 unpinning never requires scrolling back up. Hidden â¤700px (rails are inline there). */ 310.bvr-railpin{display:none;} 311@media (min-width:701px){ 312 .bvr-railtools{position:relative; margin-top:6px; margin-bottom:-6px;} /* relative = anchor for the fold handle; NO padding-right -- the pills need 200.3px of the 214 and the absolute handle's inboard half still clears them by ~3px */ /* slide the Pin/Sticky row DOWN toward the first panel; the equal -bottom keeps the panel in its ORIGINAL position (only the buttons move) */ 313 .bvr-railpin{display:inline-flex; align-items:center; gap:5px; align-self:flex-start;
314 border:0.5px solid var(--line2); border-radius:999px; background:var(--card); 315 color:var(--muted); font-family:inherit; font-size:10.5px; font-weight:600; 316 padding:3px 9px; cursor:pointer; -webkit-tap-highlight-color:transparent; 317 transition:color 120ms ease, border-color 120ms ease, background 120ms ease;} 318 .bvr-railpin:hover{color:var(--accent); border-color:var(--accent);} 319 .bvr-railpin:focus-visible{outline:2px solid var(--accent); outline-offset:1px;} 320 .bvr-railpin.on{color:var(--accent); border-color:var(--accent); background:var(--accentBg);} 321 /* Rail alignment: .bvr-cards-wrap (grid-area:hero) holds the title AND every chart, so a 322 plain subgrid inheritance can't split them into distinct row tracks. Instead we reserve 323 space above .bvr-railpanels equal to the titleblock's rendered height + its 12px 324 margin-bottom, minus the space already claimed by railtools + the flex gap. React writes 325 --titleblock-h at runtime via ResizeObserver so this stays exact through font changes, 326 accessibility zoom, and title text rewrites. */ 327 .bvr-railpanels{display:flex; flex-direction:column; gap:10px; align-self:start; margin-top:calc(var(--titleblock-h, 55px) + 12px - 30px);} 328 /* Pinned rail: sticky WITHIN the page flow (grid area "rails"), not viewport-fixed. Uses a 329 small top (10px) rather than var(--sticky-top) because sticky-with-a-threshold PUSHES 330 the element DOWN when its natural in-flow position is above the threshold â so top:200px 331 stranded the rail 200px below the chart panel at load. The floating sliders panel and 332 mobile top-left fabs (both position:fixed, viewport-anchored) still consume --sticky-top 333 to clear a production site header; the pinned rail lives inside the visualizer and aligns 334 naturally with the chart column instead. */ 335 .bvr-railstack.pinned{position:sticky; top:10px;} 336 /* the SCROLLER is the panels wrapper, NOT the railstack: a scroll container force-clips 337 x-overflow, and the fold handle protrudes past the column edge. With the scroller one 338 level down, the tools row is never inside a clipping context. max-height solves: 339 viewport - page margins (20) - tools row (~34) - the panels' titleblock margin 340 (var + 12 - 30) ==> 100vh - 36px - var. */ 341 .bvr-railstack.pinned .bvr-railpanels{overflow-y:auto; max-height:calc(100vh - 36px - var(--titleblock-h, 55px)); 342 overscroll-behavior:contain; scrollbar-width:thin; scrollbar-color:var(--line2) transparent;} 343} 344 345/* ===================== RAIL FOLD -- spine strip (desktop only) ===================== 346 The chevron beside "Sticky sliders" folds the whole assumptions column into a slim 347 book-spine strip. Three motions share ONE chirality (counterclockwise -- the 348 direction of the fold), so it reads as a single mechanical action: 349 1. THE COLUMN -- grid-template-columns is transitioned (the panel Collapse's 350 0fr/1fr idea applied to the column axis): the charts glide over, never jump.
351 2. WIDE PANELS -- quick fade-and-tilt exit, then display:none via 352 transition-behavior:allow-discrete, so their height is truly released and the 353 strip's height is simply the spines' natural height. No measuring, no JS. 354 3. SPINES -- each enters at rotate(84deg), i.e. lying nearly HORIZONTAL (the 355 orientation the panel just left) and swings upright on an overshoot bezier, 356 staggered 55ms per rail: the eye reads the panel itself rotating -90deg. 357 Spine LAYOUT is writing-mode:vertical-rl + rotate(180deg) (visually identical to 358 sideways-lr, with universal engine support): glyphs run bottom-to-top exactly like 359 a physically rotated card, rows become columns, every spine intrinsically sizes to 360 its own text. Writing mode does layout; transforms do motion -- never the reverse. 361 Icons carry an inner rotate(90deg) so their NET rotation is -90deg -- lying on 362 their side like everything else on the card (use rotate(180deg) for upright). 363 Degrades gracefully: without allow-discrete / starting-style the strip snaps. */ 364.bvr-vtab{display:none;} 365@keyframes bvrSpineIn{ 366 from{opacity:0; transform:rotate(84deg) scale(.62);} 367 to{opacity:1; transform:rotate(0deg) scale(1);} 368} 369@media (min-width:701px){ 370 /* the fold handle -- a 22px circle centered ON the rail's right edge (protrudes 11px 371 into the 16px column gap; 12px at the 801-900 mid width -- both clear the charts). 372 It stays welded to that edge while the column animates, so the strip appears to 373 fold up TO the handle. */ 374 .bvr-railfold{position:absolute; right:-11px; top:0; width:22px; height:22px; padding:0; 375 display:grid; place-items:center; border-radius:50%; z-index:4; 376 box-shadow:0 1px 3px rgba(15,40,50,.10);} 377 .bvr-railfold svg{display:block; transition:transform 560ms cubic-bezier(.32,1.25,.42,1);} 378 .railfolded .bvr-railfold svg{transform:rotate(-180deg);} /* -180 not +180: the icon rolls WITH the fold */ 379 .bvr-body{transition:grid-template-columns 460ms cubic-bezier(.3,.9,.22,1);} 380 .bvr-body.railfolded{--railw:80px;} 381 382 /* tools regroup into ONE block: the tall Sticky pill spans the left column; the 383 chevron sits above the shorter Pinned pill on the right. The handle leaves the 384 edge (position:static) and becomes a grid item. */ 385 .railfolded .bvr-railtools{display:grid; grid-template-columns:auto auto; gap:7px 8px; 386 grid-template-areas:"sticky chev" "sticky pin"; justify-content:center; align-items:start;} 387 .railfolded .bvr-stickytog{grid-area:sticky;} 388 .railfolded .bvr-railtools > .bvr-railpin:not(.bvr-stickytog):not(.bvr-railfold){grid-area:pin; justify-self:center;} 389 .railfolded .bvr-railfold{grid-area:chev; position:static; justify-self:center;} 390 .railfolded .bvr-railtools .bvr-railpin:not(.bvr-railfold){writing-mode:vertical-rl; transform:rotate(180deg); 391 padding:10px 6px;} /* padding is PHYSICAL -- it does not rotate with writing-mode. The base 392 3px 9px left only 3px at the glyph run's ends once vertical; 10px 6px restores the pill's 393 proportions in its rotated frame. */ 394 .railfolded .bvr-railtools .bvr-railpin:not(.bvr-railfold) svg{transform:rotate(90deg);} 395 .railfolded .bvr-railpanels{margin-top:10px; gap:8px;} 396 .railfolded .bvr-railfoot{display:none;} /* the footer's horizontal actions have no home in the slim strip */ 397 398 /* the entrance swing sweeps outside the strip ON PURPOSE -- while folded the pinned 399 column must be plain sticky, not an internal scroller (which would clip it or 400 flash scrollbars); the folded strip is short enough not to need one */ 401 .railfolded .bvr-railstack.pinned .bvr-railpanels{overflow:visible; max-height:none;} 402 403 /* each rail = two representations stacked in ONE grid cell; the inactive one is 404 display:none'd so the aside's height always equals the live representation's */ 405 .bvr-rail{display:grid; grid-template-columns:minmax(0,1fr);} 406 .railfolded .bvr-rail{overflow:visible;} /* the swing (and the card's own 0.5px border at 407 fractional zoom) must never be sheared; the wide layer is display:none'd so nothing else 408 can spill */ 409 .bvr-rail > *{grid-area:1 / 1; min-width:0;} 410 .bvr-railwide{transition:opacity 150ms ease-in, transform 150ms ease-in, display 150ms allow-discrete; 411 transition-delay:calc(120ms + var(--ri, 0)*45ms);} /* delay applies ENTERING (unfold + first paint): panels cascade back in */ 412 .railfolded .bvr-railwide{display:none; opacity:0; transform:rotate(-5deg) translateX(-12px); 413 transform-origin:top left; width:190px; transition-delay:0s;} /* exit NOW; fixed width so text never reflows mid-fold (the aside's overflow:clip crops it) */ 414 @starting-style{ 415 .bvr-railwide{opacity:0; transform:translateX(-12px) rotate(-2deg);} 416 } 417 418 /* spine tab -- a real button; visual chrome lives on the inner writing-mode element */ 419 .bvr-vtab{opacity:0; transform:rotate(56deg) scale(.8); justify-self:center; align-self:start; 420 border:0; padding:0; background:none; font:inherit; color:inherit; cursor:pointer; 421 -webkit-tap-highlight-color:transparent; border-radius:11px; 422 transition:opacity 150ms ease-in, transform 200ms ease-in, display 200ms allow-discrete;} 423 .railfolded .bvr-vtab{display:block; opacity:1; transform:none; 424 animation:bvrSpineIn 620ms cubic-bezier(.26,1.4,.36,1) backwards; 425 animation-delay:calc(90ms + var(--ri, 0)*55ms);} 426 .railfolded .bvr-vtab:hover .bvr-vtab-i{box-shadow:0 2px 10px rgba(14,124,134,.16); filter:brightness(0.985);} 427 .railfolded .bvr-vtab:focus-visible{outline:2px solid var(--accent); outline-offset:2px;} 428 .bvr-vtab-i{display:block; writing-mode:vertical-rl; transform:rotate(180deg); 429 border:1px solid color-mix(in srgb, var(--accent) 60%, transparent); border-radius:10px; 430 background:var(--card); overflow:hidden;
430 transition:box-shadow 140ms ease, filter 140ms ease;} 431 /* 1px at 60%, NOT 0.5px solid: the spine centers at fractional x (intrinsic text width), 432 and a half-pixel hairline rasterizes to nothing on most rows there -- statically AND 433 mid-rotation. A full pixel at reduced alpha keeps the same perceived weight. */ 434 .bvr-vtab-i svg{transform:rotate(90deg); flex:none;} 435 .bvr-vtab-h{display:flex; align-items:center; gap:6px; padding:9px 6px; 436 font-size:10.5px; font-weight:600; color:var(--accent); background:var(--accentBg); 437 letter-spacing:0.01em; white-space:nowrap;} 438 .bvr-vtab-rows{display:flex; flex-direction:column; padding:9px 6px; gap:6px; 439 border-right:0.5px solid var(--accentBg);} /* local right renders at the header boundary after rotate(180) */ 440 .bvr-vtab-row{display:flex; align-items:center; gap:5px; white-space:nowrap; 441 font-size:10px; line-height:1.15;} 442 .bvr-vtab-l{color:var(--muted); font-weight:500;} 443 .bvr-vtab-row b{font-weight:600; color:var(--ink); font-size:10.5px;} 444} 445@media (prefers-reduced-motion:reduce){ 446 .bvr-body, .bvr-railwide, .bvr-vtab, .bvr-railfold svg{transition-duration:1ms !important; animation-duration:1ms !important;} 447} 448 449/* Single-column section header above the assumptions block (mobile only). Uppercase teal 450 eyebrow in the app's section-marker language, set off from the charts above by a hairline. 451 column-span:all makes it span the 2-up masonry at 520-700 (ignored in the <=519 flex column). */ 452.bvr-railsechead{display:none;} 453@media (max-width:700px){ 454 .bvr-railsechead{display:block; column-span:all; margin:10px 2px 4px; padding-top:16px; 455 border-top:1px solid var(--line2); font-size:var(--eyebrow); text-transform:uppercase; 456 letter-spacing:.13em; font-weight:700; color:var(--accent); line-height:1.3;} 457} 458/* rail header â chevron-toggleable at all widths */ 459.bvr-railh{display:flex; align-items:center; gap:6px; font-size:11px; font-weight:600; color:var(--accent); 460 background:var(--accentBg); border:0.5px solid var(--accent); border-radius:9px; 461 padding:8px 11px; font-family:inherit;} 462.bvr-railh-btn{width:100%; justify-content:space-between; cursor:pointer; text-align:left;} 463.bvr-railh-btn:hover{filter:brightness(0.97);} 464.bvr-railh-lab{display:flex; align-items:center; gap:6px;} 465/* Quiet metadata readout in a rail header (filing · region · tier). Deliberately NOT pill/button 466 styled â it's status, not a control. Small italic muted text, middot-separated, pushed hard right 467 against the chevron via margin-left:auto. */ 468.bvr-railh-metagrp{margin-left:auto; margin-right:10px; font-size:10px; font-style:italic; font-weight:500; 469 color:var(--muted); letter-spacing:0.04em; white-space:nowrap;} 470.bvr-railh-btn{border-radius:9px 9px 0 0; border-bottom:none;} 471/* Animatable collapse: grid-template-rows 0fr â 1fr respects real content height, no measuring. 472 Both states are always mounted; visibility flips via .open. Snappy 180ms so scrubbing feels alive. */ 473.bvr-collapse{display:grid; grid-template-rows:0fr; transition:grid-template-rows 180ms cubic-bezier(.4,0,.2,1); --collapse-ms:180ms;} 474.bvr-collapse.open{grid-template-rows:1fr;} 475.bvr-collapse-i{overflow:hidden; min-height:0;} 476/* Closed collapses previously left focusable descendants (rail inputs, verdict Why?, spine 477 rows) in the tab order while aria-hidden â a WCAG failure. visibility is transitionable 478 via delay (unlike display), so: closing waits for the 180ms shrink to finish, opening 479 shows immediately. Purely a tab-order/AT fix â 0fr + overflow:hidden already clips paint. */ 480.bvr-collapse:not(.open) .bvr-collapse-i{visibility:hidden; transition:visibility 0s 180ms;} 481.bvr-collapse.open .bvr-collapse-i{visibility:visible;} 482.bvr-railb{border:0.5px solid var(--accent); border-top:none; border-radius:0 0 9px 9px; background:var(--card); padding:9px; overflow:hidden;} 483.bvr-railnote,.bvr-note{font-size:10px; color:var(--faint); line-height:1.5; margin-top:7px;} 484/* down payment field emphasis at top of Financing */ 485.bvr-downfld{padding-bottom:8px; margin-bottom:4px; border-bottom:0.5px dashed var(--line);} 486 487/* NARROW (â¤700px): single column; everything stacks full-width. 488 Order: hero â Scenario â Financing+Tax â center (annual + chart). */ 489@media (max-width:700px){ 490 .bvr-body{grid-template-columns:minmax(0,1fr); grid-template-areas:"hero" "rails" "center";} 491 .bvr-railstack{max-width:780px; margin-inline:auto; width:100%;} 492 .bvr-center{max-width:780px; margin-inline:auto;} 493 /* Sprocket revealed when the rails have stacked below the fold. Styling comes from the shared 494 .bvr-mobilefab, .bvr-head-cog rule near the top of the â¤800px block (subtle translucent 495 surface matching the sliders fab) â no override here, so the two floating buttons look 496 identical. (This block previously re-declared a solid-white surface, which is what made the 497 cog look opaque next to the translucent sliders fab.) */ 498} 499/* mid widths: pack the four assumption cards into two balanced columns. Using CSS multi-column 500 (not grid) is deliberate: a 2-col grid couples row heights, so when Cost of ownership is 501 expanded (tall) it forces Financing in the row below to start beneath it, leaving a big gap 502 under the short Price card. CSS columns flow the cards vertically and independently â each 503 card sits directly below the previous one in its column with no cross-column row coupling, so
504 there's no gap. break-inside:avoid keeps a card from splitting across the column boundary. 505 .bvr-rail-col-fin is flattened with display:contents so Financing and Tax flow as individual 506 cards into the column balancer alongside Price and Ownership. */ 507@media (min-width:520px) and (max-width:700px){ 508 .bvr-railstack{display:block; column-count:2; column-gap:10px;} 509 .bvr-railstack .bvr-rail, 510 .bvr-railstack .bvr-rail-col-fin > .bvr-rail{break-inside:avoid; margin-bottom:10px;} 511 .bvr-railstack .bvr-rail-col-fin{display:contents;} 512} 513 514/* MID (701â900px): keep two columns but trim the rail column so center keeps room. */ 515@media (min-width:701px) and (max-width:900px){ 516 .bvr-body{--railw:196px; grid-template-columns:var(--railw) minmax(0,1fr); column-gap:12px; max-width:none;} 517} 518 519.bvr-loan{border:0.5px solid var(--line); border-radius:8px; margin-bottom:8px; background:var(--bg);} 520.bvr-loanh{width:100%; display:flex; align-items:center; justify-content:space-between; background:none; border:none; 521 padding:9px 10px; cursor:pointer; color:var(--ink);} 522.bvr-loanm{font-size:12px; font-weight:600;} 523.bvr-loanb{padding:3px 10px 10px; overflow:hidden;} /* top pad = focus-outline clearance (overflow:hidden clipped the select's ring) */ 524.bvr-sel{width:100%; height:28px; font-size:11px; border:0.5px solid var(--line2); border-radius:6px; 525 background:var(--card); margin-bottom:8px; padding:0 6px; color:var(--ink); box-sizing:border-box;} 526.bvr-mini{display:grid; grid-template-columns:1fr; gap:5px;} 527.bvr-minirow{display:grid; grid-template-columns:1fr 1fr; gap:5px;} 528.bvr-mini .bvr-in,.bvr-minirow .bvr-in{width:100%; box-sizing:border-box;} 529.bvr-ml{font-size:9px; color:var(--faint); display:block; margin-bottom:2px;} 530/* Mortgage-amount wrapper â same layout as .bvr-ml (label + LockOrAuto), but sized as a real header 531 rather than a mini-caption. The label itself (.bvr-loan-amtlab) is the popover trigger for the 532 FHFA conforming-limit help, so it wears a dotted underline to advertise the popup on hover. */ 533.bvr-loan-amthdr{font-size:13px; color:var(--ink); display:block; margin-bottom:6px; font-weight:600; 534 letter-spacing:-0.15px;} 535.bvr-loan-amtlab{cursor:pointer; color:var(--ink); font-weight:600; 536 text-decoration:underline; text-decoration-style:dotted; text-underline-offset:4px; 537 text-decoration-color:color-mix(in srgb, var(--ink) 35%, transparent); 538 transition:color 150ms ease, text-decoration-color 150ms ease;} 539.bvr-loan-amtlab:hover{color:var(--accent); text-decoration-color:var(--accent);} 540.bvr-ded{display:flex; align-items:center; justify-content:space-between; margin-top:9px; padding-top:9px; border-top:0.5px solid var(--line);} 541.bvr-ded span{font-size:11px; color:var(--muted);} 542/* (.bvr-sw wide YES/NO pill removed â the loan-editor rows were its last consumers; every 543 switch in the app is now the compact .bvr-lgsw via lgSwitch().) */ 544 545.bvr-acc{border:0.5px solid var(--line); border-radius:8px; margin-top:12px; margin-bottom:8px; overflow:hidden;} 546.bvr-acch{width:100%; display:flex; align-items:center; justify-content:space-between; background:var(--bg); 547 border:none; padding:8px 10px; cursor:pointer; color:var(--ink); font-size:11.5px; font-weight:600;} 548.bvr-acct{display:flex; align-items:center; gap:6px;} 549.bvr-accb{padding:9px 10px; border-top:0.5px solid var(--line);} 550.bvr-fld{display:flex; align-items:center; justify-content:space-between; margin-bottom:7px; gap:8px;} 551.bvr-fld:last-child{margin-bottom:0;} 552.bvr-fld label{font-size:11px; color:var(--muted); display:flex; align-items:center; min-width:0; flex:1 1 auto; 553 white-space:nowrap; overflow:hidden; text-overflow:ellipsis;} 554.bvr-fld > .bvr-in,.bvr-fld > span,.bvr-fld > .bvr-seg,.bvr-fld > .unitseg{flex:0 0 auto;} 555.bvr-fld label.has-help, .bvr-ded span.has-help{cursor:pointer; text-decoration:underline; text-decoration-style:dotted; text-underline-offset:3px; text-decoration-color:var(--faint);} 556.bvr-fld label.has-help:hover, .bvr-ded span.has-help:hover{color:var(--accent); text-decoration-color:var(--accent);} 557/* Insurance $/% mode toggle. The pill button sits directly to the LEFT of the input; a 558 6px gap gives it its own tap target without visually crowding the number. Pill wears 559 the same neutral chrome as .cllseg / .unitseg buttons â the mode letter is bolded and 560 ink-colored so it reads as an active control rather than a suffix. */ 561.bvr-insurewrap{display:inline-flex; align-items:center; gap:6px; flex:0 0 auto;} 562.bvr-insmodetog{width:24px; height:24px;
562 border:0.5px solid var(--line2); border-radius:6px; 563 background:var(--card); color:var(--ink); font-size:12px; font-weight:700; cursor:pointer; 564 display:flex; align-items:center; justify-content:center; padding:0; line-height:1; 565 transition:border-color 120ms ease, color 120ms ease, background 120ms ease;} 566.bvr-insmodetog:hover{border-color:var(--accent); color:var(--accent);} 567.bvr-insmodetog:focus-visible{outline:2px solid var(--accent); outline-offset:1px;} 568/* Subtle mode marker: "%" mode uses the accent tint on the button so the current mode is 569 discoverable at a glance (versus "$" mode which uses the muted default). */ 570.bvr-insmodetog:not(.dol){background:color-mix(in srgb, var(--accent) 8%, var(--card)); 571 border-color:color-mix(in srgb, var(--accent) 35%, var(--line2));} 572 573.bvr-fund{margin-top:9px; padding-top:9px; border-top:0.5px solid var(--line);} 574/* capital-gains funding question inside the down-payment popover */ 575.bvr-ppq{padding:1px 0 4px;} 576.bvr-ppqrow{display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:9px;} 577.bvr-ppqlab{font-size:12px; font-weight:600; color:var(--ink);} 578.bvr-ppslider{padding:0 1px;} 579.bvr-pprange{width:100%; accent-color:var(--accent); height:18px; cursor:pointer;} 580/* Balancer-disabled state (popAmtSlider): the field is auto-solved, so dragging is off â 581 the mid rangelab explains and the LockOrAuto pill is the claim path. Dimmed, not hidden: 582 the control stays discoverable and lights up the instant the user claims the field. */ 583.bvr-pprange:disabled{opacity:.4; cursor:default; accent-color:var(--faint);} 584.bvr-pprangelab{display:flex; justify-content:space-between; font-size:9px; color:var(--faint); margin-top:1px;} 585/* expandable down-payment funding split (tax-free cash + realized gains) */ 586.bvr-fund2{margin:6px 0 2px; padding:9px 10px; background:var(--bg); border:0.5px solid var(--line); border-radius:8px;} 587.bvr-f2row{display:flex; align-items:center; justify-content:space-between; gap:10px; font-size:11.5px; color:var(--muted); padding:3px 0;} 588.bvr-f2row b{color:var(--ink); font-weight:600;} 589.bvr-f2row b.neg{color:var(--neg);} 590.bvr-f2row.sub{color:var(--ink); font-weight:600; border-top:0.5px dashed var(--line2); margin-top:3px; padding-top:6px;} 591.bvr-f2row .has-help{cursor:pointer;} 592.bvr-f2slider{padding:2px 1px 4px;} 593/* subline under the down-payment field in the Financing rail */ 594.bvr-cgsub{font-size:10.5px; color:var(--muted); margin-top:5px; line-height:1.35; cursor:pointer;} 595.bvr-cgsub b{color:var(--neg); font-weight:600;} 596.bvr-seg,.bvr-mix ~ *{} 597.bvr-seg{display:inline-flex; border:0.5px solid var(--line2); border-radius:7px; overflow:hidden; margin-bottom:7px;} 598.bvr-seg button{border:none; background:var(--card); padding:5px 9px; font-size:11px; cursor:pointer; color:var(--muted);} 599.bvr-seg button.on{background:var(--accentBg); color:var(--accent); font-weight:600;} 600.bvr-mix{width:100%; accent-color:var(--accent); height:18px;} 601.bvr-mixrow{display:flex; justify-content:space-between; font-size:9px; color:var(--faint); margin-bottom:5px;} 602.bvr-mixlab{font-size:11px; color:var(--muted); text-align:center; margin-bottom:8px;} 603/* Shared tax-rates popover: one slider + direct-entry box per rate (Ordinary 0-60, LTCG 0-45), 604 reusing the .bvr-mix slider + .bvr-mixrow ticks + .bvr-editpct.inval box. */ 605.bvr-taxrates{display:flex; flex-direction:column; gap:16px; padding:2px 0 2px;} 606.bvr-taxrate-h{display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:6px;} 607.bvr-taxrate-l{font-size:12px; font-weight:600; color:var(--ink);} 608/* The two return inputs flank the slider ends â cash/bond under "cash" (left), equity under 609 "equities" (right) â so each return sits beneath the mix end it drives. */ 610.bvr-mixreturns{display:flex; justify-content:space-between; align-items:flex-start; gap:12px; margin-top:6px;} 611.bvr-mixret{display:flex; flex-direction:column; gap:3px;} 612.bvr-mixret.right{align-items:flex-end;} 613/* "return" caption below each box â matches the endpoint-label style above (the endpoint label 614 is the field's name; box + caption complete it: "cash/bond/other · [4%] · return"). */ 615.bvr-mixret > span{font-size:9px; color:var(--faint);} 616.bvr-mixret .bvr-editpct.inval{width:56px;} 617 618.bvr-panel{background:var(--card);
618 border:0.5px solid var(--line2); border-radius:11px; padding:13px 14px;} 619/* Funding-incomplete guard. When the funding legs don't sum to the price (fundGap != 0), the 620 projection is invalid: every engine-output surface fades to 25% and goes inert, and the 621 .bvr-fundoverlay card explains it. Rails, sliders, title, Reset and the Excel button are 622 NOT faded, so the user can fix the inputs. Fade covers charts (.bvr-panel) + the verdict. */ 623.bvr-body.funderror .bvr-panel, .bvr-body.funderror .bvr-verdict{ 624 opacity:0.25; pointer-events:none; transition:opacity 200ms ease;} 625.bvr-panel, .bvr-verdict{transition:opacity 200ms ease;} 626/* The overlay card: full opacity (not a faded descendant â it's a direct child of the 627 position:relative .bvr-cards-wrap), absolutely positioned over View A, just below the title 628 via the same --titleblock-h var the rail uses. Centered; does not shift layout. */ 629.bvr-fundoverlay{position:absolute; left:0; right:0; top:calc(var(--titleblock-h, 55px) + 40px); 630 z-index:40; display:flex; justify-content:center; padding:0 14px; pointer-events:none; 631 animation:bvr-fundpop 160ms ease both;} 632@keyframes bvr-fundpop{from{opacity:0; transform:translateY(-6px);} to{opacity:1; transform:none;}} 633.bvr-fundoverlay-card{pointer-events:auto; width:min(360px, 100%); background:var(--card); 634 border:1px solid color-mix(in srgb, var(--neg) 45%, transparent); border-radius:14px; 635 padding:16px 18px; box-shadow:0 10px 34px color-mix(in srgb, var(--ink) 16%, transparent); 636 text-align:left;} 637.bvr-fundoverlay-h{font-size:14.5px; font-weight:700; color:var(--neg); letter-spacing:-0.01em;} 638.bvr-fundoverlay-sub{font-size:11.5px; color:var(--muted); line-height:1.4; margin:4px 0 11px;} 639.bvr-fundoverlay-rows{display:flex; flex-direction:column; gap:5px;} 640.bvr-fundoverlay-row{display:flex; align-items:baseline; justify-content:space-between; gap:12px; 641 font-size:12.5px; color:var(--ink);} 642.bvr-fundoverlay-row > span{color:var(--muted);} 643.bvr-fundoverlay-row > b{font-weight:700;} 644/* Itemized layout: tiny uppercase section heads (Financing | Purchase), a ruled Total row 645 closing the financing list, and a muted sub-row for up-front costs (cash at close â set 646 apart so it never reads as part of the financing = price balance). */ 647.bvr-fundoverlay-sec{font-size:9px; font-weight:700; letter-spacing:.09em; text-transform:uppercase; 648 color:var(--faint); margin:7px 0 0;} 649.bvr-fundoverlay-sec:first-child{margin-top:0;} 650/* Auto-adjust rescue pill â the one-click fix when the AUTO balancer is stuck at $0. Small 651 INLINE chip riding the label of the row it will change; accent idiom, own color so it 652 pops out of the muted label text. Full plan is in the title tooltip. */ 653.bvr-fundfix{display:inline-flex; align-items:center; gap:3px; align-self:center; 654 margin-left:auto; padding:1px 8px; border-radius:999px; cursor:pointer; line-height:1.5; 655 font-family:inherit; font-size:9.5px; font-weight:700; letter-spacing:.02em; color:var(--accent); 656 background:color-mix(in srgb, var(--accent) 10%, transparent); 657 border:1px solid color-mix(in srgb, var(--accent) 32%, transparent); 658 transition:background 130ms ease, border-color 130ms ease;} 659.bvr-fundfix:hover{background:color-mix(in srgb, var(--accent) 18%, transparent); 660 border-color:color-mix(in srgb, var(--accent) 48%, transparent);} 661.bvr-fundfix:focus-visible{outline:2px solid var(--accent); outline-offset:2px;} 662.bvr-fundfix svg{flex:none;} 663.bvr-fundoverlay-tot{padding-top:6px; border-top:0.5px solid var(--line);} 664.bvr-fundoverlay-tot > span{color:var(--ink); font-weight:600;} 665.bvr-fundoverlay-row.sub > span, .bvr-fundoverlay-row.sub > b{color:var(--faint); font-weight:500; font-size:11.5px;} 666.bvr-fundoverlay-row .bvr-autotag{margin-left:5px;} 667.bvr-fundoverlay-gap{margin-top:5px; padding-top:8px; border-top:0.5px solid var(--line2); color:var(--neg);} 668.bvr-fundoverlay-gap > span, .bvr-fundoverlay-gap > b{color:var(--neg);} 669.bvr-fundoverlay-hint{font-size:11px; color:var(--faint); line-height:1.45; margin-top:11px;} 670@media (prefers-reduced-motion:reduce){ .bvr-fundoverlay{animation-duration:1ms;} } 671.bvr-panelh{display:flex; align-items:center; justify-content:space-between; font-size:12px; font-weight:600; margin-bottom:9px;} 672.bvr-panelh .bvr-ptitle{display:flex; flex-direction:column; gap:1px;} 673.bvr-ptag{font-size:var(--eyebrow); font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--accent);} 674.bvr-ptsub{font-size:12px; font-weight:600; color:var(--ink);} 675.bvr-ptsub.nw{white-space:nowrap;} 676.bvr-legend{display:flex; align-items:center; gap:6px; font-size:12px; color:var(--muted); font-weight:400;} 677.bvr-legend i{width:20px; height:3px; border-radius:2px; display:inline-block; flex:none;} 678.bvr-legend em{font-style:normal;
678 color:var(--faint);} 679/* Stacked river legend: Buy variants paired on row 1, Rent alone on row 2. flex-end keeps every 680 row right-aligned under the header so it reads as one right-hand block. */ 681.bvr-riverleg{flex-direction:column; align-items:flex-end; gap:3px;} 682.bvr-riverleg .bvr-legrow{display:flex; align-items:center; gap:5px;} 683/* "Buy:" group label â one prefix for the two Buy swatches (dropped the repeated "Buy ·"). */ 684.bvr-riverleg .bvr-leglab{color:var(--muted); font-weight:400; margin-right:1px;} 685/* Gross "before-tax" swatch â dashed lighter blue, matched to the chart's strokeDasharray 5 4. */ 686.bvr-legend i.dash{height:2px; border-radius:0; 687 background:repeating-linear-gradient(90deg, var(--buyGross) 0 5px, transparent 5px 9px);} 688/* On narrow phones let the header wrap so the three-line legend drops below the title, still 689 pinned right (margin-left:auto) rather than squeezing the title on one line. */ 690.bvr-riverp .bvr-panelh{flex-wrap:wrap; row-gap:5px; align-items:flex-start;} 691.bvr-riverp .bvr-legend{margin-left:auto;} 692.bvr-hint{font-size:10px; color:var(--faint); text-align:center; margin-top:4px;} 693/* River region hover: CSS opacity overrides the SVG presentation attribute (lower specificity), 694 giving a gentle lift + native <title> tooltip naming the region. */ 695.bvr-rvband{transition:opacity 140ms ease;} 696.bvr-rvband:hover{opacity:0.78;} 697 698/* Year-marker (vertical dashed line + "yr N" caption + chart-2 dollar readout) shared across the 699 annual-outflow river (chart 2) and the net-worth trajectory (chart 3). Two-tier styling: on 700 desktop this is understated secondary chrome â muted, thin, informational. On mobile it becomes 701 a chunky slider-thumb: thicker stroke, denser dashes, ink color, larger typography. The visual 702 mass telegraphs "this is grabbable" â the underlying SVG already handles tap/drag on the whole 703 plot area; this just makes the affordance legible at arm's length. */ 704/* Resting values are TOKENS because the commit animation has to land on them exactly. The 705 .set keyframes below end at var(--mk-rest*) rather than hardcoded literals: the <=520px 706 block overrides the same tokens, so one keyframe pair serves both breakpoints and neither 707 one snaps when the animation ends. Hardcoding the desktop rest here was a real bug on 708 phones -- the line thinned to 1.5/muted over 620ms and then popped back to 3.5/ink, i.e. 709 the tap feedback ran backwards on the exact device taps happen on. */ 710.bvr-ymark-line{--mk-rest:var(--muted); --mk-restw:1.5; --mk-resto:0.7; --mk-peakw:3.5; 711 stroke:var(--mk-rest); stroke-width:var(--mk-restw); stroke-dasharray:5 3; stroke-opacity:var(--mk-resto); 712 pointer-events:none;} 713.bvr-ymark-lbl{font-size:13px; font-weight:700; fill:var(--ink);} 714/* COMMIT RECEIPT (charts 2 + 3). With hover = pure preview, a click changes nothing visually 715 at click time -- the chart already shows the hovered year -- and then the chart visibly 716 un-follows the click as the mouse moves on. So the marker announces the commit: the label 717 punches up in accent and scales back to rest, the dashed line thickens and glows down to 718 its resting weight. Keyed remount (key={"mk"+markPulse}) replays both on EVERY click. 719 720 The LINE gets weight+glow rather than transform:scale on purpose -- it spans the full plot 721 height, so scaling it would stretch it past the axes. Thickening is the equivalent 722 emphasis for a 1.5px rule (user: "or equivalent way to give that emphasis effect"). 723 724 transform-box:fill-box pins the label's transform-origin to its OWN bbox centre; without 725 it, SVG text scales about the svg user-space origin (0,0) and would fly across the chart. 726 727 Glow fades via drop-shadow radius+color (5px accent -> 0 transparent), which interpolates; 728 animating filter to the keyword none would not. */ 729.bvr-ymark-lbl.set{transform-box:fill-box; transform-origin:center; animation:bvrMarkSet 620ms cubic-bezier(.2,.9,.3,1);} 730@keyframes bvrMarkSet{ 731 0%{fill:var(--accent); transform:scale(1.32); filter:drop-shadow(0 0 5px var(--accent));} 732 55%{transform:scale(1.03);}
733 100%{fill:var(--ink); transform:scale(1); filter:drop-shadow(0 0 0 transparent);} 734} 735.bvr-ymark-line.set{animation:bvrMarkLine 620ms cubic-bezier(.2,.9,.3,1);} 736@keyframes bvrMarkLine{ 737 0%{stroke:var(--accent); stroke-width:var(--mk-peakw); stroke-opacity:1; filter:drop-shadow(0 0 5px var(--accent));} 738 100%{stroke:var(--mk-rest); stroke-width:var(--mk-restw); stroke-opacity:var(--mk-resto); filter:drop-shadow(0 0 0 transparent);} 739} 740/* Reduced motion: the receipt is FUNCTIONAL feedback, so it is not dropped -- only the motion 741 is. Swap to colour-only keyframes (no scale, no glow); the user still sees the commit. */ 742@media (prefers-reduced-motion: reduce){ 743 .bvr-ymark-lbl.set{animation-name:bvrMarkSetRM;} 744 .bvr-ymark-line.set{animation-name:bvrMarkLineRM;} 745} 746@keyframes bvrMarkSetRM{0%{fill:var(--accent);} 100%{fill:var(--ink);}} 747@keyframes bvrMarkLineRM{0%{stroke:var(--accent);} 100%{stroke:var(--mk-rest);}} 748/* Break-even labels: chart-2 idiom (11px/600) with a card-colored paint-order stroke so 749 they read over the trajectory lines. Hover ghost marker fades in per dot. */ 750.bvr-belbl{font-size:11px; font-weight:600; fill:var(--ink); paint-order:stroke; stroke:var(--card); stroke-width:3px;} 751.bvr-belbl.big{font-size:13px; font-weight:700;} 752.bvr-belbl.sub{font-size:9.5px; fill:var(--muted);} 753.bvr-beghost{opacity:0; transition:opacity 140ms ease; pointer-events:none;} 754.bvr-bedot:hover .bvr-beghost{opacity:1;} 755.bvr-ymark-val{font-size:16px; font-weight:700;} /* chart 2 only â annual buy outflow at year E */ 756 757@media (max-width:520px){ 758 /* Same anatomy, dialed up: 2.3à stroke, chunkier 8/4 dashes, ink color at full opacity for a 759 "grabbable slider thumb" read. Font sizes bump so the year and dollar labels stay legible at 760 arm's length on a phone. .bvr-ymark-lbl gains a subtle paint-order stroke of the card color 761 so the label reads clearly if it overlaps a plotted line on narrow viewports. */ 762 /* Token overrides (not raw stroke/width) so the .set keyframes land on THIS rest, not the 763 desktop one. Peak 6.5 keeps the same ~1.9x punch the desktop 1.5 -> 3.5 flash has, from a 764 resting line that is already 2.3x thicker. */ 765 .bvr-ymark-line{--mk-rest:var(--ink); --mk-restw:3.5; --mk-resto:1; --mk-peakw:6.5; stroke-dasharray:8 4;} 766 .bvr-ymark-lbl{font-size:15px; paint-order:stroke; stroke:var(--card); stroke-width:3px;} 767 .bvr-ymark-val{font-size:17px; paint-order:stroke; stroke:var(--card); stroke-width:3px;} 768} 769 770/* ---- summary hero (fully visual) ---- */ 771.bvr-hero{background:var(--card); border:0.5px solid var(--line2); border-radius:11px; padding:18px 20px 12px;} 772 773/* Combined Wealth-building-impact panel â chart 3 (trajectory) + chart 4 (terminal bars) share 774 one frame and one header. The header is a full-width unstyled button (no default button chrome) 775 so it looks like a .bvr-panelh but is obviously interactive: subtle hover tint confirms 776 clickability, the chevron plus aria-expanded telegraph state to assistive tech. sec.wealth folds 777 the TRAJECTORY ONLY (.bvr-trajbody, inside the Collapse); the hero grid + terminal note sit in a 778 sibling .bvr-herobody OUTSIDE it and never fold â collapsing hides the year-by-year detail, not 779 the verdict. The two bodies' paddings sum to the original single body's box, so the expanded 780 panel is unchanged. Header sits above both so the title never disappears. */ 781.bvr-wealthp{padding:0;} 782.bvr-wealthh{width:100%; display:flex; align-items:center; justify-content:space-between; gap:12px; 783 padding:13px 14px; background:none; border:0; border-radius:11px 11px 0 0; text-align:left; 784 font-family:inherit; color:var(--ink); cursor:pointer; -webkit-tap-highlight-color:transparent;} 785.bvr-wealthh:hover{background:color-mix(in srgb, var(--accent) 4%, transparent);} 786.bvr-wealthh:focus-visible{outline:2px solid var(--accent); outline-offset:-2px;} 787.bvr-wealthh .bvr-ptitle{display:flex; flex-direction:column; gap:1px; min-width:0;} 788.bvr-wealthh-right{display:flex; align-items:center; gap:12px; color:var(--muted); flex-shrink:0;} 789.bvr-wealthh-right > svg{color:var(--muted); transition:transform 180ms ease;} 790.bvr-wealthbody{padding:0 14px 12px;} 791/* PADDING CONTRACT for the split bodies: chart 3's body (inside the Collapse) carries no bottom 792 padding and chart 4's body carries the panel's, so stacked they reproduce the single body's box 793 EXACTLY â the expanded panel is pixel-identical to the pre-split layout. Two-class selector so it 794 beats the 1-class .bvr-wealthbody padding shorthand inside the 520 media block at any source 795 order (media queries add no specificity). */ 796.bvr-wealthp .bvr-trajbody{padding-bottom:0;} 797/* Divider between the trajectory chart and the terminal bars â bleeds past the body padding so it 798 reads as a full-width rule and creates a clear "and now, the outcome" beat between the two. */ 799.bvr-wealthdiv{height:1px; background:color-mix(in srgb, var(--line2) 70%, transparent); 800 margin:14px -14px 14px;} 801 802@media (max-width:520px){ 803 /* Tighter mobile padding and drop the "· dashed = pre-liquidation" legend caption to prevent 804 wrap â Buy/Rent chips carry the essential legend info; the dashed-vs-solid distinction is 805 also spelled out in the terminal-note beneath the bars. */ 806 .bvr-wealthh{padding:12px 12px;} 807 .bvr-wealthbody{padding:0 12px 10px;} 808 .bvr-wealthh-right{gap:8px;} 809 .bvr-wealthh-right em{display:none;} 810 .bvr-wealthdiv{margin-left:-12px; margin-right:-12px; margin-top:10px; margin-bottom:12px;} 811} 812/* ---- chart 2 (river) collapse ---------------------------------------------------------------- 813 Same grammar as the wealth panel above: the panel gives up its own padding, the header becomes a 814 full-width button that still LOOKS like the .bvr-panelh it replaces, and the body carries the 815 padding back. The numbers reproduce the old box model exactly: 816 was .bvr-panel padding 13px 14px + .bvr-panelh margin-bottom 9px 817 now .bvr-riverh padding 13px 14px 9px + .bvr-riverbody padding 0 14px 13px
818 so the expanded chart does not move a pixel. .bvr-riverh-right holds the legend + chevron as one 819 flex-shrink:0 block: the panelh keeps flex-wrap:wrap, so on iPhone widths the whole right block 820 drops below the title and stays right-pinned (margin-left:auto) â the pre-existing no-hscroll 821 behaviour at 375/390/414, now with the chevron travelling with the legend instead of stranding on 822 line 1. Header hover tint rounds its TOP corners only (it butts against the body below). */ 823.bvr-riverp{padding:0;} 824.bvr-riverh{width:100%; margin:0; padding:13px 14px 9px; background:none; border:0; 825 border-radius:11px 11px 0 0; text-align:left; font-family:inherit; line-height:inherit; 826 color:var(--ink); cursor:pointer; -webkit-tap-highlight-color:transparent;} 827/* line-height:inherit is LOAD-BEARING, not tidiness: the UA button stylesheet resets 828 line-height to "normal", which silently shrank the inherited 1.4 chain (.bvr-ptag 18.19px -> 829 15px, the two-row .bvr-riverleg 36.59px -> 31px) and pulled the whole chart -- and every panel 830 below it -- up 5.6px at 1400 / 8.0px at 400. Any future panelh-to-button conversion needs this 831 line. (.bvr-wealthh predates this and still runs on the UA default; see handoff note.) */ 832.bvr-riverh:hover{background:color-mix(in srgb, var(--accent) 4%, transparent);} 833.bvr-riverh:focus-visible{outline:2px solid var(--accent); outline-offset:-2px;} 834.bvr-riverh-right{display:flex; align-items:center; gap:12px; margin-left:auto; color:var(--muted); 835 flex-shrink:0;} 836.bvr-riverh-right > svg{color:var(--muted); flex-shrink:0;} 837.bvr-riverbody{padding:0 14px 13px;} 838 839.bvr-herogrid{display:grid; grid-template-columns:minmax(0,1fr) auto minmax(0,1fr); column-gap:16px; align-items:start;} 840.bvr-hlabs{position:relative; min-width:0;} 841.bvr-hlab{position:absolute; left:0; right:0; display:flex; align-items:center; gap:10px; transform:translateY(-50%);} 842.bvr-hlab.R{flex-direction:row;} 843.bvr-hltxt{display:flex; flex-direction:column; line-height:1.18; min-width:0;} 844.bvr-hlab.L .bvr-hltxt{align-items:flex-end; text-align:right;} 845.bvr-hlab.R .bvr-hltxt{align-items:flex-start; text-align:left;} 846.bvr-hltxt em{font-style:normal; font-size:10.5px; color:var(--muted); display:inline-flex; align-items:center; gap:4px; white-space:nowrap;} 847.bvr-hltxt em svg{color:var(--accent);} 848.bvr-hltxt b.at{font-size:clamp(16px, 1.9vw, 22px); font-weight:700; color:var(--ink); white-space:nowrap; letter-spacing:-0.01em;} 849.bvr-hltxt b.pre{font-size:18px; font-weight:650; color:var(--ink); white-space:nowrap;} 850.bvr-hltxt b.tdy{font-size:12px; font-weight:500; color:var(--faint); white-space:nowrap;} 851/* Chart-4 hero values now MATCH their bars (user ask): after-tax = the solid bar hue, pre-liq 852 = a lighter sibling of the same hue (echoing the .ghost bar's translucent top) â still dark 853 enough to read as a large bold number. Buy = blue, Rent = amber. "Initial cash required" 854 stays faint (it's a reference baseline, not a bar value). */ 855.bvr-hltxt b.at.tone-buy{color:var(--buy);} 856.bvr-hltxt b.at.tone-rent{color:var(--rent);} 857.bvr-hltxt b.pre.tone-buy{color:color-mix(in srgb, var(--buy) 62%, var(--muted));} 858.bvr-hltxt b.pre.tone-rent{color:color-mix(in srgb, var(--rent) 66%, var(--muted));} 859/* hover on the clickable After-tax label still lifts to accent, overriding the tone color */ 860.bvr-hlab.clickable:hover b.at.tone-buy,.bvr-hlab.clickable:hover b.at.tone-rent{color:var(--accent);} 861.bvr-hlab.today em{color:var(--faint);} 862.bvr-hltxt small{font-size:9.5px; color:var(--faint); font-weight:400; margin-top:1px;} 863.bvr-hlab.clickable:hover b.at,.bvr-hlab.clickable:hover em{color:var(--accent);} 864/* leader lines â hairline with a colored dot at the bar end */ 865.bvr-hlead{flex:1 0 14px; height:0; border-top:1px solid var(--line2); position:relative;} 866.bvr-hlab.L .bvr-hlead{margin-right:-16px;} 867.bvr-hlab.R .bvr-hlead{margin-left:-16px;} 868.bvr-hlead.buy::after,.bvr-hlead.rent::after{content:""; position:absolute; top:-2.5px; width:5px; height:5px; border-radius:50%;} 869.bvr-hlab.L .bvr-hlead::after{right:-1px;} 870.bvr-hlab.R .bvr-hlead::after{left:-1px;} 871.bvr-hlead.buy::after{background:var(--buy);} 872.bvr-hlead.rent::after{background:var(--rent);} 873.bvr-hlead.dash{border-top-style:dashed; border-top-color:var(--faint);} 874/* Elbow leaders: the SVG owns line + dot (sloping to the bar's TRUE boundary), so the old 875 horizontal hairline and ::after dot are disabled to avoid double-drawing. overflow:visible
876 lets the dot's radius breathe at the svg edge. */ 877.bvr-hlead.elbow{border-top:none;} 878.bvr-hlead.elbow::after{display:none;} 879.bvr-herogrid{position:relative;} 880.bvr-hleadslay{position:absolute; inset:0; overflow:visible; pointer-events:none; z-index:1;} 881 882/* annual visual pair â View A (year view) + View B (river) */ 883.bvr-vpanel{margin-bottom:18px;} 884.bvr-riverp{margin-bottom:4px;} 885.bvr-yvnet{display:flex; justify-content:center; height:auto; padding:0 0 6px; cursor:pointer;} 886.bvr-yvnet span{display:flex; flex-direction:column; align-items:center; line-height:1.15;} 887.bvr-yvnet b{font-size:13px; font-weight:700; color:var(--ink);} 888.bvr-yvnet em{font-style:normal; font-size:7.5px; color:var(--faint); text-transform:uppercase; letter-spacing:.05em; margin-top:1px; white-space:nowrap;} 889.bvr-yvnet.clickable:hover b{color:var(--accent);} 890.bvr-yvbars{position:relative; display:flex; gap:18px; align-items:flex-start;} 891.bvr-yvcol{width:58px; display:flex; flex-direction:column;} 892.bvr-yvzero{position:absolute; left:-12px; right:-12px; border-top:1.5px dashed var(--faint); pointer-events:none; z-index:1;} 893.bvr-yvzero span{position:absolute; right:-2px; top:-14px; font-size:9px; color:var(--faint);} 894.bvr-hlead.credit::after{content:""; position:absolute; top:-2.5px; width:5px; height:5px; border-radius:50%; background:var(--credit);} 895.bvr-hlead.credit{border-top-color:color-mix(in srgb, var(--credit) 45%, transparent);} 896/* View A v2 â bars in the center, legend tables flanking. No leader lines, no floating labels. 897 Legend chips are the sole visual link between rows and segments; identical color = same thing. 898 Renders identically on iOS Safari and desktop Chrome by construction. */ 899.bvr-yv2{width:100%;} 900/* Per-column totals â the grossânet story told by the numbers themselves. The row mirrors 901 the bars' flex geometry so each figure centers over its own stack: pair 128px (two 64px 902 halves, flush like the stack pair) · 16px gap · rent 64px (mobile: 130/22). GROSS wears 903 a lightened buy tone (the "before"); NET carries full buy blue (the answer the eye should 904 land on); a muted arrow rides the seam between the flush columns and narrates the 905 reduction. Each figure is a button opening its popover (buyOut / rent). */ 906.bvr-yv2tots{display:flex; gap:16px; align-items:flex-start; justify-content:center;} 907.bvr-yv2tot{display:flex; flex-direction:column; align-items:center; gap:1px; width:var(--yv2cw,69px); 908 background:none; border:0; padding:0; cursor:pointer; font-family:inherit;} 909.bvr-yv2tot b{font-size:17px; font-weight:700; line-height:1.05; transition:color 200ms ease;} 910/* Effective-rate pill on the Mortgage-interest / Synth-interest spine rows: gross rate -> after-tax 911 rate on that debt's principal, light buyer-blue chip; the two rates keep chart-1's gross/net inks. */ 912.bvr-ratepill{display:inline-flex; align-items:center; gap:3px; margin-left:6px; padding:1px 6px; 913 border-radius:999px; background:color-mix(in srgb, var(--buy) 11%, var(--card)); 914 border:0.5px solid color-mix(in srgb, var(--buy) 22%, transparent); 915 font-size:9.5px; font-weight:700; font-style:normal; letter-spacing:.01em; line-height:1.4; 916 vertical-align:1px; white-space:nowrap;} 917.bvr-ratepill b{font-weight:700;} 918/* After-tax rate derivation block inside the mortgage-interest / synth-interest popovers. */ 919.bvr-ratexpl{display:flex; flex-direction:column; gap:5px; font-size:11px; color:var(--muted); padding:1px 0;} 920.bvr-ratexpl-pill{display:flex; align-items:center; justify-content:space-between; gap:8px; margin-bottom:2px;} 921.bvr-ratexpl-lab{font-size:9px; font-weight:700; letter-spacing:.05em; text-transform:uppercase; color:var(--faint);} 922.bvr-ratexpl-pill .bvr-ratepill{margin-left:0;} 923.bvr-ratexpl-row{display:flex; align-items:baseline; justify-content:space-between; gap:12px;} 924.bvr-ratexpl-row > span:first-child{color:var(--faint); flex:none;} 925.bvr-ratexpl-row > span:last-child{text-align:right; font-variant-numeric:tabular-nums;} 926.bvr-ratexpl-row.tot{border-top:0.5px solid var(--line2); padding-top:4px; margin-top:1px; font-weight:600; color:var(--ink);} 927.bvr-ratexpl-note{font-size:9.5px; color:var(--faint); line-height:1.4;} 928.bvr-ratepill i{font-style:normal;
928 color:var(--muted); font-size:8.5px; font-weight:600;} 929.bvr-yv2tot em{font-style:normal; font-size:7.5px; color:var(--muted); text-transform:uppercase; 930 letter-spacing:.06em; white-space:nowrap; margin-top:2px;} 931.bvr-yv2tot.buy b{color:var(--buy);} 932.bvr-yv2tot.gross b{color:var(--buyGross);} 933.bvr-yv2tot.rent b{color:var(--rent);} 934.bvr-yv2tot:hover b{color:var(--accent);} 935.bvr-yv2totpair{position:relative; display:flex; width:calc(var(--yv2cw,69px)*2 + 8px); justify-content:space-between;} /* cw + 8 gap + cw â cells align over stack1/crcol */ 936.bvr-yv2totpair.solo{width:var(--yv2cw,69px);} 937.bvr-yv2totr{position:relative; display:flex;} 938/* Mobile column heads: absolutely hung above their own tots cell (bottom:100%), full-cell 939 tone underline matching the desktop lghead language. Hidden wherever the real desktop 940 head row exists (>520px). */ 941.bvr-yv2mhead{display:none; position:absolute; bottom:100%; left:0; right:0; margin-bottom:7px; 942 align-items:center; justify-content:center; gap:4px; font-size:11.5px; font-weight:600; 943 padding-bottom:4px; border-bottom:2px solid color-mix(in srgb, currentColor 55%, transparent); 944 white-space:nowrap;} 945/* Seam arrow: sized/inked to be SEEN (was faint 12px and got visually crushed between the
946 two numerals at larger number sizes). Smaller numbers + muted 13px semibold + a card-colored 947 halo (text-shadow ring) keep it legible even if a wide numeral edges close. */ 948.bvr-yv2totarrow{position:absolute; left:calc(var(--yv2cw,69px) + 4px); top:3px; transform:translateX(-50%); /* seam center = cw + 8/2 */ 949 color:var(--muted); font-size:13px; font-weight:600; line-height:1; pointer-events:none; 950 text-shadow:0 0 3px var(--card), 0 0 3px var(--card);} 951/* Horizon readout â now lives right-aligned in the panel header (.bvr-panelh is 952 space-between). Class name kept for the QA cookbook ("verify via .bvr-yv2netyr"). */ 953.bvr-yv2netyr{font-size:12px; font-weight:600; color:var(--muted); letter-spacing:0.01em;} 954/* Horizon stamp inside the totals row (chart 1, desktop). Same rhythm as the money columns 955 (18px value / 7.5px caption) but faint ink so the eye still reads the dollar figures as 956 heroes; sits at the head of the row so the sentence flows left-to-right. Hidden â¤900px 957 â at that width the panel-header readout owns the year. */ 958/* CRITICAL: absolutely positioned so it does NOT participate in the flex flow â as a flex 959 child it widened the row and shifted every money figure off its column (the misalignment 960 bug). right:100% pins it just left of the gross figure; the tots row content stays exactly 961 pair(128) + gap(16) + rent(64), centered over the bars. */ 962.bvr-yv2tots{position:relative;} 963.bvr-yv2tot.yv2horizon{position:absolute; right:calc(100% + 12px); top:1px; width:auto; 964 align-items:flex-end; padding-right:0; cursor:default;} 965.bvr-yv2tot.yv2horizon b{color:var(--faint); font-weight:600; font-size:15px; letter-spacing:0.01em; white-space:nowrap;} 966.bvr-yv2tot.yv2horizon em{color:var(--faint);} 967@media (max-width:520px){ .bvr-yv2tot.yv2horizon{display:none;} } /* was 900 â user wants the "left of $230k gross" placement for the whole three-stack (non-mobile) range, matching the â¥900 arrangement */ 968@media (min-width:521px){ .bvr-yv2netyr.yv2netyr-mob{display:none;} } /* mobile-only now â totals-row yv2horizon owns the readout â¥521 */ 969 970/* 3-row grid: row 1 = Buy/Rent heads ; row 2 = per-column totals ; row 3 = legends + bars. */ 971.bvr-yv2grid{display:grid; grid-template-columns:minmax(0,1fr) auto minmax(0,1fr); grid-template-rows:auto auto 1fr; 972 column-gap:20px; row-gap:8px; align-items:start; position:relative; 973 /* isolate: traps the dashed drop-leaders' z-index (z=4) INSIDE this grid's own stacking 974 context so they stay above the chart's bars/zero-line. Note the z=2 on .bvr-vpanel 975 .bvr-yv2grid is a ROOT-level z-index (nothing between here and body creates a stacking 976 context), so it competes at the root â and the resolver scrim / reset picker / 977 sticky-sliders panel, though declared z=110â120, MUST NOT be rendered inside 978 .bvr-railstack (position:sticky always creates a stacking context regardless of 979 z-index) â they'd be trapped there with the whole rail's z=auto. All three overlays 980 are physically rendered at the root of .bvr-root, adjacent to .bvr-pop, so their 981 declared z-index competes at the root as intended. See gotcha 28. */ 982 isolation:isolate;} 983/* Row 1: heads sit in the MIDDLE column (col 2, the bars column) so each head is directly 984 above its own bar group. Buy left-aligned (over the stack pair), Rent right-aligned (over 985 the right 64px bar). Row 2: per-column totals, same column, aligned to the bars flex 986 geometry. Row 3: legends + bars. */ 987.bvr-yv2grid > .bvr-yv2tots{grid-row:2; grid-column:2; justify-self:center;} 988/* Chart-1 Buy/Rent headers CENTERED over their bar columns. The bars flex container in col 2 989 is 144px wide (64 + 16 gap + 64) â 208px when the credit break-out column is present 990 (64 + 64 + 16 + 64). Setting each header's width to 64px (== bar width) and 991 pinning Buy to col2's LEFT and Rent to col2's RIGHT with justify-content:center puts each 992 label's centerline exactly over its bar's centerline (32px and 112px within col 2). Simpler 993 than a nested grid and doesn't fight the existing bars flex layout. */ 994.bvr-yv2grid > .bvr-lghead{grid-row:1; grid-column:2; margin-bottom:0; align-self:end; 995 justify-self:start; width:var(--yv2cw,69px); justify-content:center;} 996.bvr-yv2grid > .bvr-lghead.right{grid-column:2; justify-self:end; width:var(--yv2cw,69px); justify-content:center;} 997.bvr-yv2grid > .bvr-lg{grid-row:3;} 998.bvr-yv2grid > .bvr-yv2bars{grid-row:3; grid-column:2;} 999/* Right-aligned rent head + mirrored rent legend rows. With the chip gone, the mirror is just two 1000 columns swapped: value (auto) on the LEFT (near the bars), text (1fr) on the RIGHT. Text within 1001 the 1fr column is LEFT-aligned so labels hug the value on their left rather than floating to the 1002 far right â labels read as a natural extension of the value. Uses descendant selector 'b' (not 1003 '>
1003 b') to sidestep a subtle SSR bug where '>' in a raw <style> gets serialized as '>' when 1004 React SSRs children (now defused via dangerouslySetInnerHTML anyway). */ 1005/* The rent head's TEXT content is centered within its 64px box (matching the buy head's 1006 justify-content:center) so it reads as centered over the rent column. Previously flex-end 1007 right-aligned the text inside the box, which looked off-center. */ 1008.bvr-yv2grid > .bvr-lghead.right{justify-content:center;} 1009/* RENT side: label block stacked ABOVE the value, everything left-aligned against the connector 1010 line (which lands at the row's left edge). Reads top-down as: label / sub / value â one flush-left 1011 column, no horizontal offset between label and value. */ 1012.bvr-lg.right .bvr-lgrow{display:flex; flex-direction:column; align-items:flex-start; gap:1px;} 1013.bvr-lg.right .bvr-lgtxt{order:1;} 1014.bvr-lg.right .bvr-lgrow b{order:2;} 1015.bvr-lg.right .bvr-lgtxt em, .bvr-lg.right .bvr-lgtxt small{text-align:left;} 1016/* Rows that TUCK into the rent-column lane (data-tuck="1"): the right-side credit rows (SALT, 1017 Depreciation, Implied-financing â all data-credit="1") AND the Buyer's-surplus row 1018 (tuckRight, above the rent bar). data-tuck is a superset of data-credit â legendRow emits it 1019 for both â so this one rule owns the -84px lane. They use the LEFT side's inline 1020 pattern: [label+sub block][value] on one visual row, value vertically centered next to the 1021 two-line text. They're pulled LEFT (left:-84px) to tuck UNDER (credits, sub-$0) or OVER 1022 (Homebuyer net surplus, above the rent bar's top) the rent stack rather than out in the right band. 1023 This keeps them compact and close to the chart instead of floating far right (which wasted 1024 space and read as disconnected, especially in the common single-family case). 1025 The label is wider than the 64px rent column so it extends a bit past the column's right edge, 1026 but that's fine: credit rows sit strictly BELOW the $0 line and the surplus row strictly ABOVE 1027 rentTop (both enforced by the vertical resolver passes + the splitExtraRight fit-guard), so 1028 there is no overlap with the rent bar at any horizon. The â84px offset lands the row's left 1029 edge at the rent column's left edge (grid column-gap 20 + rent col width 64 = 84px left of 1030 this legend's origin; Puppeteer-verified: both boxes at the same x). The connector overlay 1031 reads live rects, so the leader from the buy-column segment stays correct. */ 1032.bvr-lg.right .bvr-lgrow[data-tuck="1"]{display:grid; grid-template-columns:auto auto; 1033 column-gap:8px; align-items:center; justify-content:start; 1034 left:-84px;} 1035.bvr-lg.right .bvr-lgrow[data-tuck="1"] .bvr-lgtxt{order:initial;} 1036.bvr-lg.right .bvr-lgrow[data-tuck="1"] b{order:initial;} 1037/* Small indent on rent subs so the text clears the connector-line landing zone at left:0 */ 1038.bvr-lg.right .bvr-lgtxt small{padding-left:2px;} 1039.bvr-yv2lines{position:absolute; left:0; top:0; pointer-events:none; z-index:0; overflow:visible;} 1040.bvr-yv2bars{display:flex; gap:16px; align-items:flex-start; position:relative;} 1041/* Credit break-out column (>520px, splitCred): flush against the buy column â the negative 1042 margin cancels the bars flex gap so the two stacks touch at the $0 corner (waterfall read) 1043 while the 64px x-offset gives the below-$0 credits their own visual lane. Same .bvr-yv2col 1044 width, so the in-segment text tiers behave identically. Never rendered on mobile. */ 1045.bvr-yv2crcol{margin-left:-8px; position:relative;} /* 16px flex gap â 8 = 8px stack1âstack2 gap: the grossânet pair reads as a UNIT, tighter than the 16px pairârent separation (user: first two closer than the rent distance). Leaders, buy-head width (136), and the totpair (136 + space-between) carry the matching +8. */ 1046 1047/* ---- Chart-1 Simple/Detailed view ------------------------------------------------------- 1048 .yv2simple (applied only when nonMobileLayout && chartSimple) is pure LABEL CHOREOGRAPHY: 1049 geometry is identical in both views (same stacks, same 20px gap, same leaders), so the 1050 transition is a fade â no reflow, no bar movement. Legend rows drift outward (buy side 1051 left, rent side right) while fading, with a per-row cascade via --vd (nth-of-type below); 1052 --vd rides ONLY the opacity/transform legs of the lgrow transition â 'top' is never 1053 delayed, so year-scrub tracking and the read-only connector overlay stay in lockstep. 1054 Kept in simple: bars, netbox OUTLINE (label fades), both dashed leaders, header totals, 1055 $0 tick, surplus rows (extra/kept) + their connectors + the invest switch (a control, not 1056 a label). Hidden rows keep pointer-events:none so ghosts aren't clickable. PASS-2 note: 1057 the exactly-once value rule is a DETAILED-view invariant â simple deliberately shows no 1058 per-segment values (the three header totals are the story); harnesses assert PASS-2 in 1059 detailed mode only. */ 1060/* Summary/Detailed view TOGGLE â SINGLE <button> (emoji + word), absolutely overlaid on the
1061 lower-left of the chart. position:absolute (anchored to .bvr-vpanel) so it reserves NO panel 1062 height. Low z-index; the chart labels (.bvr-yv2grid, isolated + lifted to z-index:2) always 1063 paint on top, so a below-$0 label like "Interest deduction" overlaps cleanly. 1064 SPLIT FADE (user spec: emoji stays full-opacity while everything else fades away). CSS 1065 opacity is MULTIPLICATIVE â a child cannot exceed its parent's opacity â so the emoji can 1066 only persist if the BUTTON ITSELF never fades; instead each NON-EMOJI channel fades 1067 individually: border-color, background-color, box-shadow -> transparent over 10s (2x the 1068 old 5s chrome fade) after a 300ms delay; the LABEL (.bvr-viewseg-wd) gets its own opacity 1069 fade on the same timing. The emoji (.bvr-viewseg-emo) is untouched: it always reads 1. 1070 Entering hover / .intro / :focus-visible snaps everything back in 150ms. The button keeps 1071 its hit area at rest (the emoji is the visual anchor), so hovering the lower-left corner 1072 brings the pill right back; .intro is what teaches that corner on first load. Shadow rests 1073 at alpha-0 (not 'none') so it interpolates. */ 1074.bvr-viewseg{position:relative; z-index:1; 1075 display:inline-flex; align-items:center; gap:0; border:1px solid transparent; border-radius:999px; overflow:hidden; 1076 background-color:transparent; box-shadow:0 1px 4px rgba(0,0,0,0); 1077 font:inherit; font-weight:600; color:var(--ink); padding:3px 9px; cursor:pointer; line-height:1; 1078 transition:border-color 10000ms ease 300ms, background-color 10000ms ease 300ms, box-shadow 10000ms ease 300ms, color 150ms ease;} 1079.bvr-viewseg:hover, .bvr-viewseg.intro, .bvr-viewseg:focus-visible{ 1080 border-color:var(--line2); background-color:var(--card); box-shadow:0 1px 4px rgba(0,0,0,0.05); 1081 transition:border-color 150ms ease, background-color 150ms ease, box-shadow 150ms ease, color 150ms ease;} 1082.bvr-viewseg:focus-visible{outline:2px solid var(--accent); outline-offset:-2px;} 1083.bvr-viewseg-emo{font-size:16px; line-height:1;} 1084/* Label is permanently open (no max-width collapse). Fades via its OWN opacity on the same 10s 1085 timing as the chrome â the emoji never fades because it has no opacity rule of its own. 1086 Copy = the CURRENT view name ("SUMMARY VIEW" / "DETAILED VIEW"), set in the chart header's 1087 GROSS / ALL-IN caps idiom (uppercase + letterspacing). */ 1088.bvr-viewseg-wd{display:inline-block; white-space:nowrap; margin-left:6px; opacity:0; 1089 font-size:9.5px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; 1090 transition:opacity 10000ms ease 300ms;} 1091.bvr-viewseg:hover .bvr-viewseg-wd, 1092.bvr-viewseg.intro .bvr-viewseg-wd{opacity:1; transition:opacity 150ms ease;} 1093/* MOBILE (<=520): the toggle NEVER fades â touch has no hover, so a faded pill would be 1094 unreachable. Chrome pinned on, label pinned to opacity 1, transitions killed except color. */ 1095@media (max-width:520px){ 1096 .bvr-viewseg{border-color:var(--line2); background-color:var(--card); box-shadow:0 1px 4px rgba(0,0,0,0.05); transition:color 150ms ease;} 1097 .bvr-viewseg-wd{opacity:1; transition:none;} 1098 /* Toggle + (i) on ONE row (user ask). De-absolute the toggle so it flows as a flex child; 1099 .bvr-vfoot becomes the shared row â toggle LEFT, (i) RIGHT via space-between. align-items 1100 center puts the small (i) on the toggle's vertical centerline. The chartinfo no longer needs 1101 its own justify-end (the row owns alignment) but keeping it is harmless (single child). The 1102 absolute toggle reserved 0 panel height; now it reserves its own ~24px row â but the panel 1103 just SHRANK by the dead-whitespace fix, so net footprint is unchanged/smaller. */ 1104 .bvr-vfoot{display:flex; align-items:center; justify-content:space-between; padding:8px 10px 2px; gap:10px;} 1105 .bvr-viewseg{position:static; left:auto; bottom:auto;} 1106 .bvr-chartinfo{padding:0;} 1107} 1108/* Lift the chart's label grid above the toggle overlay so labels always win the overlap. The grid 1109 would otherwise SWALLOW pointer events over the toggle (its container spans the whole chart), so 1110 it's pointer-transparent and the interactive leaves (bar segments, legend rows, the invest 1111 switch, the net box, header/total readouts) opt back in. Empty grid space passes clicks/hover 1112 THROUGH to the toggle overlay beneath. LATENT MOBILE REGRESSION FIXED: the â¤20px mobile 1113 invest pill (.bvr-mob-lgswitch) was never in this opt-in list, so it inherited 1114 pointer-events:none and had been UNTAPPABLE since the grid went pointer-transparent â the 1115 dead desktop .bvr-lgswitch entry now cedes its slot to the pill. */ 1116.bvr-vpanel{position:relative;} 1117.bvr-vpanel .bvr-yv2grid{z-index:2; pointer-events:none;} 1118.bvr-vpanel .bvr-yv2grid .bvr-yv2seg, 1119.bvr-vpanel .bvr-yv2grid .bvr-lgrow, 1120.bvr-vpanel .bvr-yv2grid .bvr-mob-lgswitch, 1121.bvr-vpanel .bvr-yv2grid .bvr-yv2netbox, 1122.bvr-vpanel .bvr-yv2grid .bvr-yv2tot{pointer-events:auto;} 1123@media (prefers-reduced-motion:reduce){ 1124 /* No fade â chrome pinned on, label pinned visible, all transitions killed. Without pinning 1125 the resting values, "transition:none" would SNAP the pill to its rest state (chrome-less, 1126 label invisible) the instant the pointer leaves â gotcha 31's cousin. */ 1127 .bvr-viewseg{border-color:var(--line2); background-color:var(--card); box-shadow:0 1px 4px rgba(0,0,0,0.05); transition:none;} 1128 .bvr-viewseg-wd{opacity:1; transition:none;} 1129 .bvr-rivbands, .bvr-rivlabs{transition:none;} 1130} 1131/* Row cascade delays (both directions â entering simple fades out in sequence, returning 1132 fades back in the same rhythm). nth-of-type over each legend column's buttons. */ 1133.bvr-lg .bvr-lgrow:nth-of-type(1){--vd:0ms;} .bvr-lg .bvr-lgrow:nth-of-type(2){--vd:26ms;} 1134.bvr-lg .bvr-lgrow:nth-of-type(3){--vd:52ms;}
1134 .bvr-lg .bvr-lgrow:nth-of-type(4){--vd:78ms;} 1135.bvr-lg .bvr-lgrow:nth-of-type(5){--vd:104ms;} .bvr-lg .bvr-lgrow:nth-of-type(6){--vd:130ms;} 1136.bvr-lg .bvr-lgrow:nth-of-type(7){--vd:156ms;} .bvr-lg .bvr-lgrow:nth-of-type(8){--vd:182ms;} 1137.yv2simple .bvr-lg .bvr-lgrow:not([data-k="extra"]):not([data-k="kept"]){opacity:0; pointer-events:none;} 1138.yv2simple .bvr-lg.left .bvr-lgrow:not([data-k="extra"]):not([data-k="kept"]){transform:translateY(-50%) translateX(-12px);} 1139.yv2simple .bvr-lg.right .bvr-lgrow:not([data-k="extra"]):not([data-k="kept"]){transform:translateY(-50%) translateX(12px);} 1140.yv2simple .bvr-yv2seg .bvr-yv2sinner{opacity:0;} 1141.bvr-yv2netbox > span{transition:opacity 220ms ease;} 1142.yv2simple .bvr-yv2netbox > span, .yv2simple .bvr-yv2netbox > b{opacity:0;} 1143.bvr-yv2lines path{transition:opacity 240ms ease;} 1144.yv2simple .bvr-yv2lines path:not([data-k="extra"]):not([data-k="kept"]){opacity:0;} 1145/* MOBILE summary (â¤520 â selectors global, elements exist only there): the end callouts 1146 ("Homebuyer net surplus: ⦠$9k" above the stack) and the credit sidecouts ("Interest 1147 deduction" etc.) hide with the rest of the labeling â the header totals carry all three 1148 figures on mobile. Three classes beat the two-class .visible reveals. The invest pill 1149 (.bvr-mob-lgswitch) deliberately survives â it's a control, matching desktop's kept switch. */ 1150.yv2simple .bvr-yv2cout.visible, .yv2simple .bvr-yv2sidecout.visible, .yv2simple .bvr-yv2sideconn{opacity:0; visibility:hidden; pointer-events:none;} 1151/* SIMPLE-VIEW hover reveal (user ask): hovering a bar segment lights ITS legend row + leader 1152 while everything else stays in the simple choreography's hidden state â a "what is this 1153 block?" tooltip built from the row that's already there. The tripled/doubled .hovlit beats 1154 the hide rules' :not()-attribute specificity (each :not(attr) counts like a class), then 1155 source order settles the tie. Rows keep pointer-events:none while revealed â informational 1156 only; the SEGMENT stays the click target (its popover), so no hover-flicker hand-off. */ 1157.yv2simple .bvr-lg .bvr-lgrow.hovlit.hovlit.hovlit{opacity:1; transform:translateY(-50%);} 1158.yv2simple .bvr-yv2lines path.hovlit.hovlit{opacity:1;} 1159/* Stack-top value chips â hidden in detailed, shown in .yv2chips (simple + splitCred). The 1160 HEADER totals row stays untouched in both views (chips are an in-stack echo, not a 1161 replacement). Chips are 64px lanes; .onbar = white on a SOLID segment (stack-1 gross on 1162 the solid blue below prin, rent on the rust bar â matches detailed's white in-bar ink); 1163 the netbox chip keeps buy-blue on the white outline box. */ 1164.bvr-yv2topval{position:absolute; width:var(--yv2cw,69px); display:flex; flex-direction:column; align-items:center; gap:1px; padding-top:0; transform:translateY(-50%); opacity:0; pointer-events:none; z-index:2; color:var(--buy); text-shadow:0 0 3px var(--card), 0 0 3px var(--card); transition:opacity 220ms ease, top 180ms cubic-bezier(.4,0,.2,1);} 1165.bvr-yv2topval b{font-size:13px; font-weight:700; line-height:1;} 1166.bvr-yv2topval em{font-style:normal; font-size:7.5px; font-weight:600; text-transform:uppercase; letter-spacing:.03em; white-space:nowrap;} 1167.bvr-yv2topval.onbar{color:#fff; text-shadow:0 1px 3px rgba(19,36,46,.55), 0 0 2px rgba(19,36,46,.45);} /* #fff matches the detailed view's burn-segment in-bar text; dark halo keeps it legible where the chip's top edge overlaps the lighter hatched Principal-paydown block. */ 1168.yv2chips .bvr-yv2topval{opacity:1;} 1169/* HOVER-SWAP chip inks (summary view). A swapped chip must be indistinguishable from the 1170 DETAILED view's in-bar text on that same segment, so these mirror the .bvr-yv2sinner tone 1171 ladder one-for-one. The halo is already right by inheritance: the base .bvr-yv2topval 1172 carries the card-colored knockout halo the hatched tones want, and .onbar carries the dark 1173 drop-halo the solid burn segments want (chipInk emits exactly one of the two families, so 1174 these never collide with .onbar). */ 1175.bvr-yv2topval.tone-kept{color:color-mix(in srgb, var(--buy) 90%, black);} 1176.bvr-yv2topval.tone-credit{color:color-mix(in srgb, var(--credit) 90%, black);} 1177.bvr-yv2topval.tone-creditneg{color:color-mix(in srgb, var(--neg) 88%, black);} 1178.bvr-yv2topval.tone-consumed, .bvr-yv2topval.tone-susp{color:var(--muted);} 1179/* Segment labels are longer than the three static captions this chip was built for 1180 ("Total" / "Net after-tax" / "Rent") â "Homebuyer net surplus" is ~3x the 69px lane. Let a 1181 SWAPPED label wrap inside its lane; nowrap would overhang into the neighbouring stack's 1182 chip. Static captions keep nowrap (they are tuned to fit). */ 1183.bvr-yv2topval.hov em{white-space:normal; line-height:1.05; text-align:center;} 1184/* (Header tots/arrow intentionally NOT touched by .yv2chips â identical in both views.) */ 1185/* Net-cash outline box (credit lane, above $0): buy-blue OUTLINE only (inset shadow so the 1186 1px ring never changes the 64px box metrics), transparent fill â reads as a
1186LEVEL, not a 1187 cost segment. Carries only its explanatory label (the VALUE lives in the totals row 1188 directly above this column â PASS-2 exactly-once); JSX guard drops the label below 40px. */ 1189.bvr-yv2netbox{position:relative; width:100%; display:flex; flex-direction:column; gap:2px; align-items:center; justify-content:center; 1190 box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--buy) 65%, transparent); 1191 cursor:pointer; overflow:hidden; transition:height 180ms cubic-bezier(.4,0,.2,1);} 1192.bvr-yv2netbox:hover{background:color-mix(in srgb, var(--buy) 5%, transparent);} 1193.bvr-yv2netbox > span{display:block; padding:0 5px; text-align:center; 1194 font-size:9.5px; font-weight:600; line-height:1.3; color:color-mix(in srgb, var(--buy) 85%, black);} 1195/* Net figure inside the box â shown in DETAILED view at every width (JSX gate netH>=16), the 1196 user-designated hero value of this chart. Sized to the mobile totals figure (15px) in full buy 1197 blue; reads the same on the desktop 69px box. Hidden in summary (netbox > b opacity:0) where the 1198 .bvr-yv2topval chip carries it. */ 1199.bvr-yv2nbv{font-size:15px; font-weight:700; line-height:1; color:var(--buy);} 1200/* Dashed diagonal leaders (two instances, both emitted by the leader() helper in the JSX): 1201 (1) Principal paydown's top-right corner -> netbox top-right (extra paydown skipped); 1202 (2) stack-1 bottom-left corner -> bottom-left of the REAL credits (suspended dep skipped). 1203 Zero-HEIGHT divs, the dashed border-top IS the line, rotated about (0,0); every emitted 1204 property (left/top/width/transform) is in the transition list so the leaders tween on the 1205 exact same 180ms schedule as the bars whenever the year or a toggle changes the geometry. */ 1206.bvr-yv2drop{position:absolute; height:0; pointer-events:none; transform-origin:0 0; z-index:4; 1207 border-top:1px dashed color-mix(in srgb, var(--buy) 55%, transparent); 1208 transition:left 180ms cubic-bezier(.4,0,.2,1), top 180ms cubic-bezier(.4,0,.2,1), 1209 width 180ms cubic-bezier(.4,0,.2,1), transform 180ms cubic-bezier(.4,0,.2,1);} 1210/* Column width for the three-stack (desktop) regime â one knob. Mobile overrides to 130px 1211 in its media query. Bump this (and nothing else) to widen/narrow all three stacks. */ 1212.bvr-yv2grid{--yv2cw:69px;} 1213.bvr-yv2col{width:var(--yv2cw,69px); display:flex; flex-direction:column; transition:margin-top 180ms cubic-bezier(.4,0,.2,1);} 1214.bvr-yv2seg{width:100%; position:relative; display:flex; align-items:center; justify-content:center; cursor:pointer; overflow:hidden; 1215 transition:height 180ms cubic-bezier(.4,0,.2,1);} 1216.bvr-yv2sinner{display:flex; flex-direction:column; align-items:center; justify-content:center; line-height:1.1; padding:1px 3px; text-align:center; max-width:100%; transition:opacity 220ms ease;} 1217.bvr-yv2slbl{display:none;} /* labels default hidden on desktop; revealed for tall segments below */ 1218/* Tall segments ("two" mode â Interest, Rent, Maintenance, Homebuyer net surplus) reveal their inline label 1219 above the value. .bvr-yv2sinner without the .one-line modifier IS the "two" mode marker; the 1220 "valonly" mode has no <span class="bvr-yv2slbl"> in the DOM at all so it's unaffected. */ 1221.bvr-yv2sinner:not(.one-line) .bvr-yv2slbl{display:block; font-size:10.5px; font-weight:600; 1222 line-height:1.15; letter-spacing:-0.1px; opacity:0.85; margin-bottom:1px;} 1223/* Tight two-line mode: segments between the two-line threshold (34px) and comfortable height 1224 (64px) shrink the LABEL so multi-word labels ("Property tax") hold ONE line instead of 1225 wrapping into a cramped 3-row block. The VALUE deliberately does NOT shrink: every in-bar 1226 number on the stack renders at the same 11.5px at a given width (uniformity rule â a short 1227 segment's number must never render LARGER than a tall segment's). */ 1228.bvr-yv2sinner.tight .bvr-yv2slbl{font-size:9px; letter-spacing:-0.2px; line-height:1.1;} 1229/* Credit segments on desktop: when the segment is tall enough, the JS emits a 'valonly' inline 1230 sinner (the negative value, no label â the desktop legend already carries "Interest deduction"). 1231 The label still comes from the legend on the left; showing the value inside gives the bar the 1232 same in-column "read the number without hunting" pattern the burn segments already use. */ 1233.bvr-yv2seg.credit .bvr-yv2sinner b{color:color-mix(in srgb, var(--credit) 90%, black); font-size:13px; font-weight:700; letter-spacing:0;} 1234.bvr-yv2seg.credit .bvr-yv2sinner .bvr-yv2slbl{color:color-mix(in srgb, var(--credit) 90%, black);} 1235/* Red-credit variant (synth loan capitalized interest â not a required cash flow): same in-bar 1236 treatment as the green credits but toned to --neg so the RED hatch and its label/value agree. */ 1237.bvr-yv2seg.creditneg .bvr-yv2sinner b, .bvr-yv2seg.creditneg .bvr-yv2sinner .bvr-yv2slbl{color:color-mix(in srgb, var(--neg) 88%, black);} 1238/* Suspended credit (depreciation deduction with no passive income to absorb it): the value
1239 reads in muted ink over the washed-out hatch so the whole segment says "exists but not 1240 currently captured" â same voice as the consumed renter's-surplus treatment. */ 1241.bvr-yv2seg.credit.suspended .bvr-yv2sinner b{color:var(--muted);} 1242/* Adjacent burn segments in the same column get a thin white divider so the eye can pick out 1243 Maintenance / Property tax / Interest as distinct components on the solid buy-blue block. 1244 White reads as a crisp accent line against the dark fill (like the row dividers in a brokerage 1245 summary); at 45% opacity it's clearly visible without shouting. Only fires when both neighbors 1246 are burn â keptâburn transitions already read as a pattern break. */ 1247.bvr-yv2seg.burn + .bvr-yv2seg.burn{border-top:1px solid color-mix(in srgb, white 45%, transparent);} 1248/* Simple view: the white segment dividers in the first (gross) stack exist to separate the 1249 labelled components; with the labels gone in simple mode they just clutter the solid block, 1250 so drop them to a barely-there 12% white. Detailed view keeps the crisp 45% dividers. */ 1251.yv2simple .bvr-yv2seg.burn + .bvr-yv2seg.burn{border-top-color:color-mix(in srgb, white 12%, transparent);} 1252/* Simple view, stack 2 (the below-$0 green credit lane): each credit segment carries a full 1253 inset --credit ring (drawn inline in barFill) â between adjacent same-pattern credits those 1254 rings stack into a visible green seam. Same rationale as stack 1's white dividers: with the 1255 component labels gone in simple mode the internal seams are just clutter, so drop the ring to 1256 a barely-there level. Overriding the inline boxShadow needs !important; the outer edge of the 1257 whole credit block stays defined by the leaders + the netbox outline above it. */ 1258.yv2simple .bvr-yv2seg.credit{box-shadow:inset 0 0 0 0.5px color-mix(in srgb, var(--credit) 12%, transparent) !important;} 1259/* Kept segments now sit on a HATCHED light background (pattern swap), so text is a dark tone-tinted 1260 ink rather than white. data-tone chooses which color family â principal is buy-blue, extra paydown 1261 and renter's surplus wear credit-green (they share the "benefit" identity with tax deductions). */ 1262.bvr-yv2seg.kept .bvr-yv2sinner b, .bvr-yv2seg.kept .bvr-yv2sinner .bvr-yv2slbl{color:color-mix(in srgb, var(--buy) 90%, black);} 1263.bvr-yv2seg.kept .bvr-yv2sinner b{font-size:13px; font-weight:700; letter-spacing:0; transition:opacity 130ms ease;} 1264.bvr-yv2seg.kept[data-tone="credit"] .bvr-yv2sinner b, .bvr-yv2seg.kept[data-tone="credit"] .bvr-yv2sinner .bvr-yv2slbl{color:color-mix(in srgb, var(--credit) 90%, black);} 1265.bvr-yv2seg.kept[data-tone="rent"] .bvr-yv2sinner b, .bvr-yv2seg.kept[data-tone="rent"] .bvr-yv2sinner .bvr-yv2slbl{color:color-mix(in srgb, var(--rent) 90%, black);} 1266/* Burn segments sit on a SOLID dark background â both label and value are white. */ 1267.bvr-yv2seg.burn .bvr-yv2sinner b, .bvr-yv2seg.burn .bvr-yv2sinner .bvr-yv2slbl{color:#fff;} 1268.bvr-yv2seg.burn .bvr-yv2sinner b{font-size:13px; font-weight:700; letter-spacing:0;} 1269/* Consumed segments (renter's surplus when investSavings is OFF) sit on a MUTED hatched background: 1270 same 45° hatch pattern as kept but rendered in --faint, so the shape reads as "money that could 1271 be saved" while the desaturation says "not captured." Text color is --muted for the same reason. */ 1272.bvr-yv2seg.consumed .bvr-yv2sinner b, .bvr-yv2seg.consumed .bvr-yv2sinner .bvr-yv2slbl{color:var(--muted);} 1273.bvr-yv2seg.consumed .bvr-yv2sinner b{font-size:13px; font-weight:700; letter-spacing:0; transition:opacity 130ms ease;} 1274/* Detailed in-bar text now carries the SIMPLE-view chip's font size (13px above) + legibility 1275 halo ("font overall style and effects" per user). Halo is tone-split, mirroring the two 1276 .bvr-yv2topval variants: white-on-solid burn segments get the dark drop-halo (== .onbar); 1277 colored-on-hatch segments (kept / credit / consumed) get the card-colored knockout halo (== 1278 base .bvr-yv2topval) so the glyphs read cleanly over the 45-degree hatch. text-shadow on the 1279 flex container cascades to both the value <b> and the label span. */ 1280.bvr-yv2seg.burn .bvr-yv2sinner{text-shadow:0 1px 3px rgba(19,36,46,.55), 0 0 2px rgba(19,36,46,.45);} 1281.bvr-yv2seg.kept .bvr-yv2sinner, .bvr-yv2seg.credit .bvr-yv2sinner, .bvr-yv2seg.consumed .bvr-yv2sinner{text-shadow:0 0 3px var(--card), 0 0 3px var(--card);} 1282/* Callouts (above/below/side) are a MOBILE-ONLY device â the desktop legend handles labeling. 1283 They're always in the DOM (for mobile animation), so hide them outright on wider screens. 1284 The side connector SVG is the mobile equivalent of the desktop legendâbar leader and hides here too. */ 1285.bvr-yv2cout, .bvr-yv2sidecout, .bvr-yv2sideconn{display:none;} 1286.bvr-yv2seg.clickable:hover{filter:brightness(1.06);} 1287.bvr-yv2zero{position:absolute; left:-10px; right:-10px; border-top:1px solid var(--faint); pointer-events:none; z-index:1; 1288 transition:top 180ms cubic-bezier(.4,0,.2,1);} 1289/* $0 label vertically CENTERED on the solid zero line. RIGHT-edge anchored 4px in from the 1290 zero div's left edge â the div extends 10px past the bars box, so the label's right edge 1291 lands 6px clear of the buy column instead of touching it (it previously overlapped by ~1px).
1292 No background chip: an 8-direction 1px card-colored text-shadow halo knocks the line 1293 out from behind the glyphs â stroke-style legibility that works cross-browser, unlike 1294 paint-order + -webkit-text-stroke on HTML text (Firefox paints the stroke OVER the fill). */ 1295.bvr-yv2zero span{position:absolute; right:calc(100% - 4px); top:50%; transform:translateY(-50%); font-size:9px; color:var(--faint); 1296 line-height:1; white-space:nowrap; 1297 text-shadow:-1px 0 var(--card), 1px 0 var(--card), 0 -1px var(--card), 0 1px var(--card), 1298 -1px -1px var(--card), 1px -1px var(--card), -1px 1px var(--card), 1px 1px var(--card);} 1299 1300/* Underline is now TONE-CODED via currentColor (each head's style prop sets color:var(--buy|rent)). 1301 Replaces the ambiguous 0.5px var(--line) gray line â same width per head, but the color 1302 now identifies which side is which. 55% alpha keeps it subtle enough not to compete with 1303 the bars below. */ 1304.bvr-lghead{display:flex; align-items:center; gap:5px; font-size:12px; font-weight:600; padding:0 0 5px 0; margin-bottom:6px; border-bottom:2px solid color-mix(in srgb, currentColor 55%, transparent);} 1305/* Legend frames are fixed-height (inline height = bars H) relative containers; rows are absolutely 1306 positioned at their layout-resolved center-y (top + translateY(-50%)). Positions come from React 1307 render (same px() scale as the bars + a pure collision resolver) â the DOM is never measured to 1308 place labels, so there is no measurement/transition feedback loop and no width-resize race. */ 1309.bvr-lg{position:relative; min-width:0;} 1310.bvr-lgrow{display:grid; grid-template-columns:minmax(0,1fr) auto; column-gap:8px; align-items:center; 1311 position:absolute; left:0; right:0; transform:translateY(-50%); 1312 border:none; background:none; padding:3px 0; text-align:left; cursor:pointer; color:var(--ink); font-family:inherit; 1313 border-radius:4px; opacity:1; 1314 transition:top 180ms cubic-bezier(.4,0,.2,1), opacity 240ms ease var(--vd, 0ms), transform 240ms ease var(--vd, 0ms);} 1315.bvr-lgrow:hover{background:color-mix(in srgb, var(--accent) 5%, transparent);} 1316.bvr-lgrow.collapsed{opacity:0; pointer-events:none;} 1317/* Collapsed (invisible) legend rows must stay click-through even under the grid's pointer-events 1318 opt-in (the .bvr-vpanel .bvr-yv2grid .bvr-lgrow auto rule is 3 classes and would 1319 otherwise win) â else an empty full-width row over the absolute view toggle swallows its click 1320 (surfaced when the toggle moved into .bvr-vfoot). Matching specificity + !important is the 1321 surgical fix; visible rows keep their auto. */ 1322.bvr-vpanel .bvr-yv2grid .bvr-lgrow.collapsed{pointer-events:none;} 1323.bvr-lgchip{display:none;} /* desktop: no chip. Kept in DOM so the mobile pattern is still measurable. */ 1324/* BUY side: [label][value] cluster at the RIGHT edge of the row â that's where the connector line 1325 starts, so the whole text group reads as the line's origin. justify-self:end shrinks the text 1326 container to content width and pins it against the value. */ 1327.bvr-lg:not(.right) .bvr-lgtxt{justify-self:end;} 1328.bvr-lg:not(.right) .bvr-lgtxt em, .bvr-lg:not(.right) .bvr-lgtxt small{text-align:right;} 1329/* Left-side below-$0 credit labels â SHARED TUCK LANE. Every credit segment on the buy side 1330 sits in the credit column (under stack 2 / flush with stack 1 depending on splitCred); the band 1331 directly under stack 1 is empty â nothing paints there. So instead of leaving each label at the 1332 legend column's left edge with a long connector reaching all the way across, we pull the whole 1333 row RIGHT so the label tucks under stack 1's lower edge and its leader makes a short hop to the 1334 segment. The shift â col-gap (20) + stack-1 width (CW 69) + stack-1âcrcol gap (8) minus a small 1335 margin, landing each label's right edge just shy of the credit segment. Desktop only â â¤520 1336 hides these legends and the tri sidecouts carry the credits instead. LANE MEMBERS: tb (Interest 1337 deduction), nr (Rental income, multifam), acc (Implied financing accrues â synth. loan capitalized 1338 interest, red hatch, the TOP credit), ptb (SALT deduction â property-tax impact). acc + ptb were 1339 added the session Jason flagged that their connectors "sometimes look too long compared to 1340 others" (they were sitting at the legend column's left edge like tb/nr used to). The connector 1341 overlay reads live rects (getBoundingClientRect includes transforms), so the leader follows 1342 automatically for every row that joins the lane. Rows are vertically distinct (React positions
1343 each at its segment's y-center), so the tuck can't cause label collisions inside the lane. */ 1344.bvr-lg:not(.right) .bvr-lgrow[data-k="tb"], 1345.bvr-lg:not(.right) .bvr-lgrow[data-k="nr"], 1346.bvr-lg:not(.right) .bvr-lgrow[data-k="acc"], 1347.bvr-lg:not(.right) .bvr-lgrow[data-k="ptb"]{transform:translateY(-50%) translateX(76px);} 1348.bvr-lgtxt{display:flex; flex-direction:column; min-width:0; line-height:1.2;} 1349.bvr-lgtxt em{font-style:normal; font-size:12px; font-weight:600; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;} 1350.bvr-lgtxt small{font-size:10px; color:var(--faint); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;} 1351.bvr-lgrow b{font-size:13px; font-weight:600; white-space:nowrap;} 1352/* Surplus rows host the invest-savings switch INSIDE the sub line (control + label merged). 1353 The sub becomes a mini flex row so the 22px knob never ellipsizes away; the text keeps its 1354 own one-line ellipsis via .t. Hosting rows render as DIV[role=button] (a button can't 1355 contain the switch button) â cursor comes from .bvr-lgrow, class-based, so no restatement. */ 1356.bvr-lgtxt small.has-sw{display:flex; align-items:center; gap:6px; overflow:visible;} 1357.bvr-lgtxt small.has-sw .t{min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;} 1358.bvr-lgtxt small.has-sw .bvr-lgsw{flex:none;} 1359.bvr-lg:not(.right) .bvr-lgtxt small.has-sw{justify-content:flex-end;} 1360/* "Invest the savings?" control â DEAD on desktop as of the switch-merge (the control now 1361 renders INSIDE the surplus rows' sub line via subSwitch; no .bvr-lgswitch node is emitted). 1362 Rules kept because the mobile .bvr-mob-lgswitch twin shares the .bvr-lgswtxt / .bvr-lgsw 1363 styling defined in this family. Historical placement notes below refer to the removed 1364 widget. Originally: a compact, unobtrusive companion to the Renter's surplus 1365 row. Three layout regimes, all rendered from the same JSX; React chooses between them by 1366 adding the .wide class (matchMedia â¥900) and hiding/showing the mobile twin below 521px. 1367 1368 WIDE (â¥900): label ABOVE a mini toggle, stacked vertically, absolutely positioned to the 1369 RIGHT of the surplus row at that row's y-center. Tiny footprint (~62px à 24px) â no 1370 background, no accent bar. Just typography + a micro switch. Because it's small, the 1371 surplus row only needs a modest right-gutter reservation (~78px) to avoid collisions. 1372 1373 MEDIUM (521â899): compact horizontal pill below the surplus row in the resolver stack â 1374 still keys off SW_H for collision. Faint credit-tinted background hint, no left accent. 1375 1376 MOBILE (â¤520): desktop legend is hidden; a separate .bvr-mob-lgswitch (identical style) 1377 floats above the rent column. Positioned high enough to leave room for a Renter's 1378 surplus above-callout when the surplus segment is too small to carry its value inline. */ 1379 1380/* Base â mini switch. Same physical toggle everywhere: 22Ã12px with an 8px knob. Half the 1381 visual weight of the previous 28Ã15 iOS toggle. */ 1382.bvr-lgsw{position:relative; width:22px; height:12px; border:none; border-radius:6px; 1383 background:color-mix(in srgb, var(--faint) 45%, transparent); padding:0; cursor:pointer; 1384 flex:0 0 auto; transition:background 180ms ease;} 1385.bvr-lgsw.on{background:var(--credit);} 1386.bvr-lgsw:hover{filter:brightness(1.05);} 1387.bvr-lgsw:focus-visible{outline:2px solid var(--accent); outline-offset:2px;} 1388.bvr-lgsw .knob{position:absolute; top:2px; left:2px; width:8px; height:8px; border-radius:50%; 1389 background:#fff; box-shadow:0 0.5px 1.5px rgba(0,0,0,0.15); transition:left 180ms cubic-bezier(.4,0,.2,1);} 1390.bvr-lgsw.on .knob{left:12px;} 1391 1392/* Shared label typography. Two font sizes: 9.5px stacked (wide), 10px inline (medium/mobile). 1393 The inline size is set per-context; this rule holds the color + italic identity. */ 1394.bvr-lgswtxt{color:var(--muted); font-style:italic; white-space:nowrap; letter-spacing:0.1px; line-height:1;} 1395.bvr-lgswitch.off .bvr-lgswtxt, .bvr-mob-lgswitch.off .bvr-lgswtxt{color:var(--faint);} 1396 1397/* MEDIUM regime â default look (no .wide, no mobile media query). Compact horizontal one-liner 1398 left-anchored, sits below the surplus row via the resolver's _switch item. Faint background 1399 hints at the interaction affordance without the previous solid pill's visual weight. */ 1400.bvr-lgswitch{position:absolute; left:0; right:auto; transform:translateY(-50%); z-index:1; 1401 /* z-index:1 â legend rows are full-width (left:0;
1401right:0) absolutely-positioned siblings 1402 painted in DOM order. The Homebuyer-net-surplus (extra) row renders LAST in the right 1403 legend, so without a z-index it painted OVER the switch whenever they share a y-band 1404 (wide regime anchors the switch right, beside that row) and its full-width hitbox 1405 swallowed the toggle's clicks â the row's popover opened instead of flipping the switch. 1406 The renter's-surplus case never hit this only because that row renders BEFORE the switch. 1407 Lifting the switch restores the intended split: knob â toggle (stopPropagation), label â 1408 mix popover, rest of the row â row popover. */ 1409 display:inline-flex; align-items:center; gap:6px; 1410 padding:3px 8px 3px 6px; border-radius:5px; 1411 background:color-mix(in srgb, var(--credit) 5%, transparent); 1412 transition:top 180ms cubic-bezier(.4,0,.2,1), background 180ms ease; 1413 border:none; cursor:pointer; font-family:inherit; color:inherit;} 1414.bvr-lgswitch:hover{background:color-mix(in srgb, var(--credit) 9%, transparent);} 1415.bvr-lgswitch:focus-visible{outline:2px solid var(--accent); outline-offset:2px;} 1416.bvr-lgswitch.off{background:color-mix(in srgb, var(--faint) 5%, transparent);} 1417.bvr-lgswitch.off:hover{background:color-mix(in srgb, var(--faint) 9%, transparent);} 1418.bvr-lgswitch .bvr-lgswtxt{font-size:10px;} 1419 1420/* 521-544 band: the MEDIUM switch's nowrap intrinsic width (~140px) is wider than the right 1421 legend column by design (it spills into the open space right of the column), but at the 1422 narrowest non-mobile widths that spill crosses the viewport edge -- measured 3.7px past at 1423 exactly 521, producing 4px of page h-scroll (clears by 530). Compact gap/padding/type in 1424 this band only; VERTICAL metrics unchanged (line-height 1, same vertical padding) so the 1425 resolver's SW_H=34 stays valid. Verified: 0px h-scroll at 521, ~9px clearance. */ 1426@media (min-width:521px) and (max-width:544px){ 1427 .bvr-lgswitch{gap:4px; padding:3px 5px 3px 4px;} 1428 .bvr-lgswitch .bvr-lgswtxt{font-size:9px; letter-spacing:0;} 1429} 1430 1431/* WIDE regime (â¥900). Override to stacked-vertical, right-anchored, no background. The 1432 label sits above the switch; both are right-aligned so the whole unit "hangs" off the 1433 right edge of the legend column. translateY(-50%) still centers on the resolved kept-row 1434 top, so the label sits about a line-height above the row's midline and the switch below. */ 1435.bvr-lgswitch.wide{left:auto; right:2px; 1436 flex-direction:column; align-items:flex-end; gap:3px; 1437 padding:0; background:none;} 1438.bvr-lgswitch.wide:hover{background:none;} 1439.bvr-lgswitch.wide.off{background:none;} 1440.bvr-lgswitch.wide.off:hover{background:none;} 1441.bvr-lgswitch.wide .bvr-lgswtxt{font-size:9.5px;} 1442 1443/* Mobile hides the desktop switch (its mobile twin below takes over). */ 1444@media (max-width:520px){ .bvr-lgswitch{display:none;} } 1445 1446/* 701-799 two-column band (rail on the left â narrow center column): the MEDIUM switch's 1447 right-spilling nowrap intrinsic width crossed the viewport edge here (29px page h-scroll at 1448 701, since the "open space" it spills into is now off-screen). Borrow the WIDE regime's 1449 right-anchored, stacked layout so the switch HANGS off the legend column's right edge instead 1450 of spilling past it â same fix wideRentLegend already applies â¥900, just triggered earlier for 1451 this cramped band. translateY(-50%) (base rule) still centers it on the resolved switchTop. */ 1452@media (min-width:701px) and (max-width:799px){ 1453 .bvr-lgswitch:not(.wide){left:auto; right:2px; flex-direction:column; align-items:flex-end; 1454 gap:3px; padding:0; background:none;} 1455 .bvr-lgswitch:not(.wide):hover, .bvr-lgswitch:not(.wide).off, .bvr-lgswitch:not(.wide).off:hover{background:none;} 1456 .bvr-lgswitch:not(.wide) .bvr-lgswtxt{font-size:9.5px;} 1457} 1458 1459/* Mobile-only floating switch â same compact horizontal style as MEDIUM. Sits above the 1460 rent column at bottom:calc(100% + 30px) so there's ~30px of clearance for a Renter's 1461 surplus above-callout to land between it and the segment when the surplus is too small 1462 to carry its value inline. Right-anchored so it doesn't disturb the callout's centered 1463 placement. */ 1464.bvr-mob-lgswitch{display:none;} 1465@media (max-width:520px){ 1466 .bvr-mob-lgswitch{display:inline-flex; align-items:center; gap:6px; 1467 position:absolute; right:-4px; bottom:calc(100% + 30px); 1468 padding:3px 8px 3px 6px; border-radius:5px; 1469 background:color-mix(in srgb, var(--credit) 7%, transparent); 1470 border:none; cursor:pointer; font-family:inherit; color:inherit; 1471 z-index:2;} 1472 .bvr-mob-lgswitch:hover{background:color-mix(in srgb, var(--credit) 11%, transparent);} 1473 .bvr-mob-lgswitch.off{background:color-mix(in srgb, var(--faint) 7%, transparent);} 1474 .bvr-mob-lgswitch .bvr-lgswtxt{font-size:10.5px;} 1475} 1476 1477/* Surplus row in WIDE mode: small right-gutter reservation so the row's flex-column content 1478 (label / sub / value) doesn't collide with the compact stacked switch on the right. 1479 Sized for the widest of the row's items (~112px "Renterâs surplus") plus ~24px 1480 switch width plus a comfortable gap â 78px reservation means the switch has room for its 1481 own ~62px + 16px breathing room. Much smaller than the previous 210px reservation because 1482 the pill itself is dramatically smaller. */ 1483.bvr-lg.right.wide .bvr-lgrow[data-k="kept"]{right:78px;} 1484 1485@media (prefers-reduced-motion: reduce){ 1486 .bvr-yv2col, .bvr-yv2seg, .bvr-yv2zero, .bvr-lgrow, .bvr-collapse, .bvr-collapse-i, .bvr-spinechev svg, .bvr-vmore svg, .bvr-yv2sinner, .bvr-yv2lines path, .bvr-yv2netbox > span, .bvr-yv2topval{transition:none;} 1487} 1488/* --- Mobile portrait: everything fits WITHIN the chart. Middle segments carry their own text inline; 1489 end segments (top or bottom of the stack) that can't fit inline get callouts ABOVE or BELOW the bar. 1490 No separate legend section â no vertical stacking. --- */ 1491@media (max-width:520px){ 1492 /* Hide the legends entirely â the bars carry all the info. Also hide the row-2 legend heads. 1493 Grid reflows to a single centered column: net figure on row 1, bars on row 2. Explicit 1494 grid-row assignments override the desktop 3-row placement (where bars sit on row 3). */ 1495 .bvr-yv2grid > .bvr-lg, .bvr-yv2grid > .bvr-lghead{display:none;} 1496 .bvr-yv2grid{grid-template-columns:1fr; grid-template-rows:auto 1fr; row-gap:14px;} 1497 .bvr-yv2grid > .bvr-yv2tots{grid-column:1; grid-row:1; gap:22px; padding-top:28px;} 1498 .bvr-yv2mhead{display:flex;} 1499 .bvr-yv2totpair.solo{width:130px;} 1500 .bvr-yv2tot{width:130px;} 1501 .bvr-yv2totpair{width:130px;}
1502 .bvr-yv2totpair .bvr-yv2tot{width:65px;} 1503 .bvr-yv2totarrow{left:65px; top:2.5px; font-size:12px;} 1504 .bvr-yv2tot b{font-size:15px;} 1505 .bvr-yv2grid > .bvr-yv2bars{grid-column:1; grid-row:2;} 1506 /* Reserve fixed zones above/below the bars for end-callouts. box-sizing:content-box means the 1507 padding ADDS to the H height, and these zones are wide enough for a 2-line callout (~40px each) 1508 so callouts never escape the padded box into the next panel. */ 1509 .bvr-yv2bars{box-sizing:content-box; justify-content:center; gap:22px; 1510 padding:var(--yv2-ptop, 44px) 0 var(--yv2-pbot, 48px); overflow:visible;} 1511 /* The $0 line and columns share an origin. Columns live in the content box (below the top 1512 padding); the absolutely-positioned $0 line measures from the padding box, so push it down by 1513 the SAME top padding (var, so it tracks the tri override that collapses the reported dead space). */ 1514 .bvr-yv2zero{top:calc(var(--yv2-base, 0px) + var(--yv2-ptop, 44px)) !important;} 1515 .bvr-yv2col{width:130px; position:relative;} 1516 /* All segments allow their callouts (above/below/side) to render OUTSIDE the segment box. 1517 Previously only .credit segments got overflow:visible; that meant the kept segment's 1518 "Renter's surplus $XXk" above-callout was rendered in the DOM but VISUALLY clipped by 1519 the segment's overflow:hidden (the segment is only ~10px tall while the callout wants 1520 to sit ~20px above it). Applying overflow:visible to every segment on mobile lets the 1521 callouts appear where the JSX positions them. The inline .bvr-yv2sinner text is safe 1522 because it's centered and the segment heights are always ⥠the text height at the 1523 tier where inline mode kicks in. */ 1524 .bvr-yv2seg{overflow:visible;} 1525 /* Reveal the inline text in every segment (burn and credit too, not just kept) */ 1526 .bvr-yv2seg .bvr-yv2sinner{display:flex !important;} 1527 .bvr-yv2slbl{display:block; font-size:11px; font-weight:600; line-height:1.15; padding:0 3px; letter-spacing:-0.1px;} 1528 .bvr-yv2seg .bvr-yv2sinner b{font-size:13.5px; font-weight:700; margin-top:1px;} 1529 /* Tight two-line: mobile columns are 130px wide so wrapping is rare, but a short segment still 1530 benefits from the smaller pairing (label 10px / value 12px). */ 1531 .bvr-yv2sinner.tight .bvr-yv2slbl{font-size:10px;} 1532 /* Value size stays 13.5px in tight mode too â one number size per width (uniformity rule). */ 1533 /* Text-color rules after the pattern swap: kept segments sit on light hatched backgrounds and take a 1534 dark tone-tinted ink; burn segments sit on solid dark backgrounds and take white; credit segments 1535 stay on light hatched green and keep dark-green ink (unchanged). data-tone lets kept text pick its 1536 color family without needing a class per segment. */ 1537 .bvr-yv2seg.kept .bvr-yv2sinner b, .bvr-yv2seg.kept .bvr-yv2slbl{color:color-mix(in srgb, var(--buy) 90%, black);} 1538 .bvr-yv2seg.kept .bvr-yv2sinner b{font-size:15px;} 1539 .bvr-yv2seg.kept[data-tone="credit"] .bvr-yv2sinner b, .bvr-yv2seg.kept[data-tone="credit"] .bvr-yv2slbl{color:color-mix(in srgb, var(--credit) 90%, black);} 1540 .bvr-yv2seg.kept[data-tone="rent"] .bvr-yv2sinner b, .bvr-yv2seg.kept[data-tone="rent"] .bvr-yv2slbl{color:color-mix(in srgb, var(--rent) 90%, black);} 1541 .bvr-yv2seg.burn .bvr-yv2slbl, .bvr-yv2seg.burn .bvr-yv2sinner b{color:#fff;} 1542 .bvr-yv2seg.credit .bvr-yv2slbl, .bvr-yv2seg.credit .bvr-yv2sinner b{color:color-mix(in srgb, var(--credit) 90%, black);} 1543 /* Single-line inline: "Label $Value" â used when segment is tight but not tiny */ 1544 .bvr-yv2sinner.one-line{flex-direction:row; gap:5px; align-items:baseline; justify-content:center; padding:1px 2px;} 1545 .bvr-yv2sinner.one-line .bvr-yv2slbl{font-size:10.5px; padding:0;} 1546 .bvr-yv2sinner.one-line b{margin-top:0; font-size:12.5px;} 1547 .bvr-yv2seg.kept .bvr-yv2sinner.one-line b{font-size:13.5px;} 1548 1549 /* External end-callouts â appear above/below the column for end segments too small to hold inline text. 1550 Always in DOM for end segments so appear/disappear animates via opacity, not mount/unmount. 1551 Label + value flow on a SINGLE row (label then value with a small gap),
1551keeping the callout tight 1552 against the bar and avoiding the tall two-line block below/beside the stack. */ 1553 .bvr-yv2cout{display:flex; align-items:baseline; justify-content:center; gap:5px; 1554 position:absolute; left:50%; transform:translateX(-50%); white-space:nowrap; 1555 opacity:0; visibility:hidden; pointer-events:none; 1556 transition:opacity 180ms ease, visibility 0s linear 180ms;} 1557 .bvr-yv2cout.visible{opacity:1; visibility:visible; pointer-events:auto; transition:opacity 180ms ease;} 1558 .bvr-yv2cout.above{bottom:calc(100% + 6px);} 1559 .bvr-yv2cout.below{top:calc(100% + 6px);} 1560 .bvr-yv2cout em{font-style:normal; font-size:11px; font-weight:600; line-height:1.15;} 1561 .bvr-yv2cout b{font-size:12.5px; font-weight:700; line-height:1.15;} 1562 .bvr-yv2cout.kept em, .bvr-yv2cout.kept b{color:var(--buy);} 1563 .bvr-yv2seg[data-tone="credit"] .bvr-yv2cout.kept em, .bvr-yv2seg[data-tone="credit"] .bvr-yv2cout.kept b{color:var(--credit);} 1564 .bvr-yv2seg[data-tone="rent"] .bvr-yv2cout.kept em, .bvr-yv2seg[data-tone="rent"] .bvr-yv2cout.kept b{color:var(--rent);} 1565 .bvr-yv2cout.credit em, .bvr-yv2cout.credit b{color:var(--credit);} 1566 .bvr-yv2cout.burn em, .bvr-yv2cout.burn b{color:color-mix(in srgb, var(--buy) 85%, black);} 1567 1568 /* Side callout â for below-$0 credit segments (Interest ded., SALT ded., Rental income). Sits to the 1569 RIGHT of the buy column in the empty rent-below-$0 space, anchored to each segment. Because each 1570 segment carries its own sidecout, they stack top-down in credit order (Interest â SALT â Rental). 1571 top:8px lifts Interest's callout off the $0 axis; the same offset within each subsequent segment 1572 gives natural vertical breathing room between labels when segments are typically-sized. The 1573 .bvr-yv2sideconn SVG below draws a curved connector across the gap so each label ties clearly 1574 to its own segment. Label + value share ONE row. */ 1575 .bvr-yv2sidecout{display:flex; align-items:baseline; gap:5px; 1576 position:absolute; left:calc(100% + 30px); top:8px; 1577 padding:2px 0; white-space:nowrap; 1578 opacity:0; visibility:hidden; pointer-events:none; 1579 transition:opacity 180ms ease, visibility 0s linear 180ms;} 1580 .bvr-yv2sidecout.visible{opacity:1; visibility:visible; pointer-events:auto; transition:opacity 180ms ease;} 1581 .bvr-yv2sidecout em{font-style:normal; font-size:11px; font-weight:600; color:var(--credit); line-height:1.2;} 1582 .bvr-yv2sidecout.creditneg em, .bvr-yv2sidecout.creditneg .num{color:var(--neg);} 1583 .bvr-yv2sidecout b{font-size:13px; font-weight:700; color:var(--credit); line-height:1.2;} 1584 /* Suspended depreciation deduction: mobile callout text drops to muted so the styling 1585 story matches the desktop legend + segment (washed out = not currently captured). */ 1586 .bvr-yv2sidecout.suspended em, .bvr-yv2sidecout.suspended b{color:var(--muted);} 1587 /* twoline: narrow credit segment â label above value so the callout stays narrow. 1588 'top' is set inline from JS (creditCalloutTops) so callouts stack tightly below the 1589 $0 line at fixed rhythm rather than tracking their tiny segment's position. */ 1590 .bvr-yv2sidecout.twoline{flex-direction:column; align-items:flex-start; gap:1px;} 1591 .bvr-yv2sidecout.twoline em, .bvr-yv2sidecout.twoline b{line-height:1.15;} 1592 /* labelonly: tall credit segment â value lives inside the stack; callout is just the label */ 1593 .bvr-yv2sidecout.labelonly em{font-size:11.5px;} 1594 .bvr-yv2sideconn{display:block;} /* revealed on mobile â hidden by default rule below */ 1595 /* t2 (tiny) in-bar tiers â emitted by segFlags in TRI DETAILED only (user ask: detailed fits 1596 ALL segment text; summary exists for people not seeking clutter). Smaller numerals on 1597 shorter segments is the sanctioned direction of the one-number-size rule â never inverted. 1598 Placed AFTER the base sinner font rules so the 3-class selectors win ties. */ 1599 .bvr-yv2seg .bvr-yv2sinner.t2 .bvr-yv2slbl{font-size:9px; letter-spacing:-0.15px; padding:0 2px;} 1600 .bvr-yv2seg .bvr-yv2sinner.t2 b{font-size:11px; margin-top:0;} 1601 .bvr-yv2seg .bvr-yv2sinner.one-line.t2{gap:4px; padding:0 1px;} 1602 .bvr-yv2seg .bvr-yv2sinner.one-line.t2 .bvr-yv2slbl{font-size:9px;} 1603 .bvr-yv2seg .bvr-yv2sinner.one-line.t2 b{font-size:10.5px;} 1604 /* Mobile: columns are CENTERED in the full-width bars box (cluster = 130 + 22 + 130 = 282px, 1605 half = 141; the zero div is concentric with the bars box). Anchor the label's right edge 1606 6px left of the buy column (50% + 141 + 6) so it tags the zero crossing AT the bars â 1607 same treatment as desktop â instead of sitting at the far-left end of the line, where it 1608 collided with the bar below ~390px. If the mobile .bvr-yv2col width (130px) or the bars 1609 gap (22px) changes, update the 147px constant. */ 1610 .bvr-yv2zero span{right:calc(50% + 147px);} 1611 1612 /* Net headline larger */ 1613 /* Hide the descriptive note on mobile â bars speak for themselves */ 1614 .bvr-heronote{display:none;} 1615} 1616/* ---- MOBILE 3-COL SUMMARY (.yv2tri = <=520 && chartSimple && splitCred): the desktop 1617 gross -> net-after-tax -> rent three-stack story at phone width. The grid carries an
1618 INLINE --yv2cw (JS: (vw - 96)/3 clamped 66..110), so every var-based desktop rule 1619 (tot/pair widths, seam arrow, topval chip width) transfers by overriding ONLY the 1620 hardcoded-130px mobile rules above. Value carriers are the header totals + the 1621 .yv2chips stack chips; sidecouts/callouts/in-bar text stay hidden by .yv2simple 1622 (tri exists only in summary). Netbox outline, $0 axis, and the dashed gross->net 1623 leaders all render as on desktop. DETAILED mobile is untouched (classic 2-stack). */ 1624@media (max-width:520px){ 1625 .yv2tri .bvr-yv2col{width:var(--yv2cw,104px);} 1626 /* 16px flex gap + crcol margin -8 = desktop rhythm: 8px stack1<->stack2, 16px pair<->rent. 1627 TIGHT tri reserve (reported dead-space fix): only the extra/prin ABOVE-callouts need top room 1628 (~26px); tri has NO below-callouts (credits are in-flow crcol segments + beside-segment 1629 sidecouts), so the bottom reserve is a small breath. Collapses ~110px of dead space top+bottom 1630 at multifam/late-year states. JS chip tPad reads the same 26. */ 1631 .yv2tri .bvr-yv2bars{gap:16px; --yv2-ptop:26px; --yv2-pbot:8px;} 1632 .yv2tri.bvr-yv2grid > .bvr-yv2tots{gap:16px;} 1633 .yv2tri .bvr-yv2tot, .yv2tri .bvr-yv2totpair .bvr-yv2tot{width:var(--yv2cw,104px);} 1634 .yv2tri .bvr-yv2totpair{width:calc(var(--yv2cw,104px)*2 + 8px);} 1635 .yv2tri .bvr-yv2totarrow{left:calc(var(--yv2cw,104px) + 4px);} 1636 /* $0 label: right edge 6px left of stack 1 â cluster = 3cw + 24 (two 16px gaps - 8 crcol) */ 1637 .yv2tri .bvr-yv2zero span{right:calc(50% + (var(--yv2cw,104px)*3 + 24px)/2 + 6px);} 1638 /* SUMMARY-tri: detailed-mode devices (end callouts, credit sidecouts + connector curves) are 1639 DISPLAY:none, not just invisible â visibility:hidden still occupies layout, and a nowrap 1640 sidecout anchored off the (further-right) credit column pushed scrollWidth past the 1641 viewport (hscroll). Scoped .yv2simple: DETAILED tri now RENDERS these devices (second 1642 increment) â creditTwo absorbs the tall credits in-bar; the narrow leftovers callout 1643 beside their own segment with short labels and a 12px connector (JSX + rule below). */ 1644 .yv2tri.yv2simple .bvr-yv2cout, .yv2tri.yv2simple .bvr-yv2sidecout, .yv2tri.yv2simple .bvr-yv2sideconn{display:none;} 1645 .yv2tri .bvr-yv2sidecout{left:calc(100% + 12px);} 1646 /* DETAILED tri: the Homebuyer-surplus (extra) above-callout. Centered-nowrap (the base cout 1647 recipe) it spilled LEFT over stack-1's in-bar text (the reported-class ink overlap: the 1648 crcol sits mid-cluster and stack-1 towers above its top). Left-anchor it at the crcol and 1649 let it WRAP rightward instead: crcol top == rent top whenever extra > 0 (net + surplus = 1650 rent identity), so everything right of the crcol above that line is open air. Width spans 1651 crcol + gap + rent col; display:block flows "label $value" as wrapping text (2 lines at 1652 320 fit inside the 44px top pad). */ 1653 .yv2tri .bvr-yv2crcol .bvr-yv2cout.above{display:block; left:0; transform:none; text-align:left; 1654 white-space:normal; width:max-content; max-width:calc(var(--yv2cw,104px)*3 + 24px);} 1655 /* With the state suffix dropped, "Homebuyer net surplus $9k" renders ONE line at every tri 1656 width (max-content up to the cluster) â which keeps the callout band (top -21..-6) clear 1657 of the invest pill above it (top -52..-30) now that the pill renders in homeowner years. */ 1658 .yv2tri .bvr-yv2crcol .bvr-yv2cout.above em{white-space:normal;} 1659 .yv2tri .bvr-yv2crcol .bvr-yv2cout.above b{margin-left:4px;} 1660} 1661/* Legend LABEL font reduction â now fires below 800px (was 620px) so the 700â800px two-column 1662 band (rail on the left â narrower center) gets the tighter labels; â¥800px keeps full desktop 1663 sizes. The 1â2-column layout boundary itself is 700px; this type boundary is deliberately a 1664 separate 800px so 700â800 is two-column WITH compact labels. */ 1665@media (max-width:800px){ 1666 .bvr-lgtxt em{font-size:11px;} 1667 .bvr-lgtxt small{font-size:9.5px;} 1668 .bvr-lgrow b{font-size:12px;} 1669} 1670/* SALT / property-tax deductibility controls */ 1671.bvr-saltctl{padding:2px 0 4px;} 1672/* Section title (replaces the AMT switch that moved below the Tax benefit row) */ 1673.bvr-salttitle{font-size:12px; font-weight:600; color:var(--ink); padding:1px 0 2px;} 1674/* Top-bracket AGI warning â red, sits directly under the title */ 1675.bvr-saltagi{color:var(--neg) !important; font-weight:600;} 1676/* Relocated AMT switch block (bottom of the popover, below Tax benefit): hairline above it 1677 separates the "does AMT void all of this" gate from the derivation rows. */ 1678.bvr-saltamt{border-top:0.5px solid var(--line); margin-top:6px; padding-top:5px;} 1679/* AMT gate: the cap controls stay visible but read as inert â dimmed, greyscale, no pointer. 1680 Settings are preserved underneath; flipping the switch back restores them untouched. */ 1681.bvr-saltctl > .bvr-poptoghint{margin-bottom:8px;} 1682.bvr-saltbody{transition:opacity 160ms ease, filter 160ms ease;} 1683.bvr-saltbody.amtdim{opacity:0.42; filter:grayscale(0.9); pointer-events:none; user-select:none;} 1684/* Property-tax rate lookup (propTax popover): state (+ CA county) dropdowns + source note. 1685 Selects borrow the .bvr-in look; min-width:0 inside flex so two selects share the row on 1686 narrow popovers without overflowing. */ 1687.bvr-ptlkp{padding:2px 0 4px;} 1688.bvr-ptlkprow{display:flex; gap:6px;} 1689.bvr-sel{height:27px;
1689 border:0.5px solid var(--line2); border-radius:6px; background:var(--card); 1690 color:var(--ink); font-family:inherit; font-size:12px; padding:0 4px; min-width:0; flex:1; 1691 max-width:100%;} 1692.bvr-sel:focus{outline:1.5px solid var(--accent); outline-offset:0;} 1693.bvr-saltrow{display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:8px;} 1694.bvr-saltrow .bvr-seg{margin-bottom:0;} 1695.bvr-saltchips{display:flex; gap:6px;} 1696.bvr-saltchips .bvr-loanchip.on{border-color:var(--accent); color:var(--accent); background:var(--accentBg);} 1697.bvr-saltpill{border:0.5px solid var(--line2); border-radius:6px; background:var(--card); padding:4px 8px; 1698 font-size:10.5px; color:var(--ink); cursor:pointer; white-space:nowrap;} 1699.bvr-saltpill:hover{border-color:var(--accent); color:var(--accent);} 1700/* popover jump rows â tap to close this popover and go to the field + its own popover */ 1701.bvr-poplink{display:flex; align-items:center; justify-content:space-between; gap:10px; width:100%; 1702 padding:5px 0; font-size:12px; color:var(--muted); cursor:pointer;} 1703.bvr-poplink + .bvr-poplink{border-top:0.5px solid var(--line);} 1704.bvr-poplink b{color:var(--ink); font-weight:600;} 1705.bvr-poplinkv{display:inline-flex; align-items:center; gap:4px; color:var(--faint);} 1706.bvr-poplink:hover, .bvr-poplink:hover b, .bvr-poplink:hover .bvr-poplinkv{color:var(--accent);} 1707.bvr-poplink .bvr-autotag{margin-left:6px;} 1708.bvr-yvseg{width:100%; position:relative; display:flex; align-items:center; justify-content:center; cursor:pointer;} 1709.bvr-yvseg b{font-size:10px; font-weight:600; color:#fff; letter-spacing:0;} 1710.bvr-yvseg.clickable:hover{filter:brightness(1.06);} 1711.bvr-hltxt b.yv{font-size:13px; font-weight:600; color:var(--ink); white-space:nowrap;} 1712.bvr-hltxt b.rvlg{font-size:16px; font-weight:700;} 1713.bvr-rivleg{display:flex; flex-wrap:wrap; gap:5px 14px; margin-top:8px; font-size:12px; color:var(--muted); justify-content:center;} 1714.bvr-rivfoot{margin-top:6px; font-size:10.5px; color:var(--faint); text-align:center;} 1715.bvr-rivleg .chip{width:14px; height:14px; border-radius:3px; display:inline-block; margin-right:5px; vertical-align:-3px; border:0.5px solid var(--line2);} 1716/* ---- River (chart 2) SUMMARY mode â driven by chart-1's Simple/Detailed toggle ---------- 1717 .bvr-rivsimple (chartSimple + nonMobileLayout): the three LINES are the story (thicker); 1718 all band paint drops to a ghost via GROUP opacity (multiplies each path's own attribute 1719 opacity, so the internal band hierarchy survives) and every in-chart band label hides. 1720 Hovering the svg (also the year-scrub surface) restores the detailed look while the pointer 1721 is over it. Below the chart, everything hides except the invest-gap switch; the reclaimed 1722 height is returned to the plot by the taller summary viewBox (H 250 vs 290 in the river 1723 memo) so the PANEL height stays ~constant across the toggle. */ 1724.bvr-rivbands, .bvr-rivlabs{transition:opacity 240ms ease;} 1725.bvr-rivsimple .bvr-rivbands{opacity:0.16;} 1726.bvr-rivsimple .bvr-rivlabs{opacity:0;} 1727.bvr-rivsimple svg:hover .bvr-rivbands, 1728.bvr-rivsimple svg:hover .bvr-rivlabs{opacity:1;} 1729.bvr-rivsimple .bvr-rivln{stroke-width:3.6px;} 1730.bvr-rivsimple .bvr-rivln.gross{stroke-width:2.6px;} 1731.bvr-rivsimple .bvr-rivleg > span:not(.bvr-lgswwrap){display:none;} 1732.bvr-rivsimple .bvr-rivfoot, .bvr-rivsimple .bvr-hint{display:none;} 1733/* yr/value marker labels moved INSIDE the plot: card-color halo keeps them legible over any 1734 chart paint (lines and bands) at every scrub position. River-scoped so chart 3's marker 1735 labels keep their existing treatment. */ 1736.bvr-riverp .bvr-ymark-lbl, .bvr-riverp .bvr-ymark-val{paint-order:stroke; stroke:var(--card); stroke-width:3.5px; stroke-linejoin:round;} 1737/* header labels + bars */ 1738.bvr-heroc{display:flex; flex-direction:column; align-items:center;} 1739.bvr-herohead{display:flex; gap:18px; margin-bottom:9px; border-bottom:0.5px solid var(--line2); padding-bottom:7px;} 1740.bvr-herohead span{width:58px; display:inline-flex; align-items:center; justify-content:center; gap:5px; font-size:12px; font-weight:600;} 1741.bvr-herobars{position:relative; display:flex; gap:18px; align-items:flex-end;} 1742.bvr-hbarw{position:relative; width:58px; height:100%;} 1743.bvr-hbar{position:absolute; left:0; right:0;} 1744.bvr-hbar.ghost{opacity:.55; border-radius:3px 3px 0 0;} 1745.bvr-htodayline{position:absolute; left:-10px; right:-10px; border-top:1.5px dashed var(--faint); pointer-events:none; z-index:1; visibility:hidden; /* overlay owns the visible rule;
1745 this element remains as the measurement anchor (data-htgt=btoday) */} 1746.bvr-heronote{font-size:10px; color:var(--faint); text-align:center; margin-top:10px;} 1747/* Footnote variant that lives OUTSIDE the panel on the body background: a touch more top 1748 margin to separate it from the panel's bottom edge, small horizontal padding so it doesn't 1749 run to the viewport edge on narrow screens, and slightly reduced so it reads as a caption. */ 1750.bvr-heronote-out{margin:9px 12px 4px; line-height:1.5;} 1751/* Controls row hosting the view toggle + the (i). Desktop: a plain block â the toggle is 1752 absolutely overlaid on the chart (below) and the .bvr-chartinfo right-aligns the (i), so the 1753 desktop render is unchanged. Mobile: switched to flex space-between (see the â¤520 rule) so the 1754 de-absoluted toggle and the (i) share ONE row (user ask). */ 1755/* Controls row: toggle LEFT, (i) RIGHT, on ONE line â BOTH desktop and mobile (user ask to 1756 lower the desktop emoji onto the (i)'s row). Was: desktop kept the toggle as an absolute 1757 chart overlay while only mobile shared the row; now the row is flex space-between everywhere 1758 and the toggle flows in it (de-absoluted at the base rule below). */ 1759.bvr-vfoot{position:relative; display:flex; align-items:center; justify-content:space-between; 1760 padding:6px 10px 2px; gap:10px;} 1761.bvr-chartinfo{position:relative; display:flex; justify-content:flex-end; padding:0;} 1762.bvr-chartinfo-btn{display:inline-flex; align-items:center; justify-content:center; 1763 width:18px; height:18px; border-radius:50%; border:0.5px solid var(--line2); 1764 background:var(--card); color:var(--faint); cursor:pointer; padding:0; 1765 transition:color 120ms ease, border-color 120ms ease;} 1766.bvr-chartinfo-btn:hover, .bvr-chartinfo-btn.open{color:var(--accent); border-color:var(--accent);} 1767/* Tooltip: anchored to the (i) button's bottom-right corner, floats above the page. 1768 Card surface + shadow so it reads as a transient overlay, not inline content. */ 1769/* Chart legend key popover. DESKTOP: opens to the LEFT of the (i) and sizes to its content so 1770 each legend row stays on ONE line â a short, WIDE card instead of the old tall 264px column that 1771 wrapped one phrase per line and stacked up over the chart's lower-right labels. Bottom-aligned to 1772 the (i) and extending up-left, with a right-pointing beak at the button. Opens on HOVER (wrapper 1773 mouse handlers) and still on click/tap. MOBILE (â¤520) reverts to the compact upward card below â 1774 no room to the left at phone width, and touch can't hover. */ 1775.bvr-chartinfo-tip{position:absolute; right:calc(100% + 10px); bottom:-4px; 1776 background:var(--card); border:0.5px solid var(--line2); border-radius:10px; 1777 box-shadow:0 6px 24px rgba(0,0,0,.13); padding:11px 15px; 1778 width:max-content; max-width:min(560px, calc(100vw - 32px)); z-index:6; pointer-events:auto; 1779 animation:bvrCitIn 150ms ease backwards;} 1780@keyframes bvrCitIn{from{opacity:0;} to{opacity:1;}} 1781/* Right-pointing beak, near the bottom where the (i) sits. */ 1782.bvr-chartinfo-tip::after{content:""; position:absolute; left:100%; bottom:9px; 1783 border:6px solid transparent; border-left-color:var(--card); filter:drop-shadow(1px 0 0 var(--line2));} 1784/* Single-line rows keep the desktop card short. */ 1785.bvr-chartinfo-tip .bvr-citrow span{white-space:nowrap;} 1786.bvr-citrow{display:flex; align-items:flex-start; gap:9px; font-size:11.5px; 1787 color:var(--muted); line-height:1.45;} 1788.bvr-citrow + .bvr-citrow{margin-top:9px; padding-top:9px; border-top:0.5px solid var(--line);} 1789.bvr-citrow b{color:var(--ink); font-weight:600;} 1790.bvr-citsw{flex:none; width:22px; height:15px; border-radius:3px; margin-top:1px; 1791 border:0.5px solid color-mix(in srgb, var(--buy) 40%, transparent);} 1792.bvr-citsw.solid{background:var(--buy); border-color:var(--buy);} 1793.bvr-citsw.hatch{border-color:color-mix(in srgb, var(--credit) 55%, transparent); 1794 background:repeating-linear-gradient(45deg, 1795 color-mix(in srgb, var(--credit) 42%, transparent) 0 1.4px, 1796 color-mix(in srgb, var(--credit) 8%, var(--card)) 1.4px 5px);} 1797.bvr-chartinfo-x{position:absolute; top:6px; right:8px; background:none; border:none; 1798 color:var(--faint); font-size:15px;
1798 cursor:pointer; padding:0 2px; line-height:1; z-index:1;} 1799.bvr-chartinfo-x:hover{color:var(--ink);} 1800/* MOBILE (â¤520): no room to open a wide card to the left, and touch can't hover â revert to the 1801 compact card that opens UPWARD from the (i), right-aligned, with wrapping rows and a downward 1802 beak. (Tap the (i) to open; tap à or elsewhere to dismiss.) */ 1803@media (max-width:520px){ 1804 .bvr-chartinfo-tip{right:0; left:auto; bottom:calc(100% + 8px); top:auto; 1805 width:264px; max-width:calc(100vw - 32px);} 1806 .bvr-chartinfo-tip::after{left:auto; right:12px; top:100%; bottom:auto; 1807 border:6px solid transparent; border-top-color:var(--card); border-left-color:transparent; 1808 filter:drop-shadow(0 1px 0 var(--line2));} 1809 .bvr-chartinfo-tip .bvr-citrow span{white-space:normal;} 1810} 1811@media (max-width:620px){ 1812 .bvr-hero{padding:14px 12px 10px;} 1813 .bvr-herogrid{column-gap:10px;} 1814 .bvr-hbarw,.bvr-herohead span,.bvr-yvcol{width:40px;} 1815 .bvr-herobars,.bvr-herohead,.bvr-yvbars{gap:12px;} 1816 .bvr-hltxt b.at{font-size:15px;} 1817 .bvr-hltxt b.pre{font-size:14px;} 1818 .bvr-hltxt em{font-size:9.5px;} 1819 /* Buy/Rent heads size to the mobile bar width (40px) with tighter type; still labeled. */ 1820 .bvr-herohead{gap:12px; margin-bottom:6px; padding-bottom:5px;} 1821 .bvr-herohead span{width:40px; font-size:11px;} 1822 .bvr-herohead svg{width:11px; height:11px;} 1823 .bvr-hlab.L .bvr-hlead{margin-right:-10px;} 1824 .bvr-hlab.R .bvr-hlead{margin-left:-10px;} 1825} 1826 1827/* Desktop table width: 560px comfortably fits the widest rows â the balance-sheet 1828 "start â end" pairs ($3,163,298 â $3,289,829) need ~155px per value column. The old 392px cap 1829 forced the two value columns unequal (155/207/128), squeezing the Rent column so its content 1830 spilled past the centered box's right edge. The two value columns are sized with a shared 1831 minmax(155px, 1fr) so they stay EQUAL at every width (including the 801â849px boundary where 1832 the panel is narrower than 560px and a bare 1fr/auto/1fr would let the auto center column 1833 squeeze one side asymmetrically). Center label column is auto (content-sized). Centered via 1834 margin-inline:auto. The â¤520px block below flips to content-sized value columns for phones â 1835 that compression work is untouched. */ 1836.bvr-spine{display:grid; grid-template-columns:minmax(140px, 1fr) auto minmax(140px, 1fr); column-gap:14px; max-width:560px; margin-inline:auto;} 1837/* Details-panel collapse chrome. The header is a real <button> reset back to the .bvr-panelh 1838 look; the right-side chevron chip carries the state word and rotates when open. The teaser 1839 sits inside the collapsed summary grid (grid-column:1/-1) under the two live tot rows; the 1840 pill CTA is the loud affordance ('do open this') while the header stays quiet chrome. */ 1841.bvr-spinehb{background:none; border:0; padding:0; margin:0 0 9px; width:100%; font:inherit; color:inherit; cursor:pointer; text-align:left;} 1842.bvr-spinehb:hover .bvr-spinechev, .bvr-spinehb:hover .bvr-ptsub{color:var(--accent);} 1843.bvr-spinehb:focus-visible{outline:2px solid var(--accent); outline-offset:2px; border-radius:6px;} 1844.bvr-spinechev{display:inline-flex; align-items:center; gap:3px; color:var(--muted); font-size:10px; font-weight:600; text-transform:uppercase; letter-spacing:.05em;} 1845.bvr-spinechev svg{transition:transform 180ms cubic-bezier(.4,0,.2,1);} 1846.bvr-spinehb[aria-expanded="true"] .bvr-spinechev svg{transform:rotate(180deg);} 1847/* Duplicate .xlsx export link at the foot of the expanded spine â full-width, quiet accent 1848 chip in the app's soft-CTA idiom (cf. .bvr-spinecta). */ 1849.bvr-spinexlsx{grid-column:1/-1; justify-self:center; display:inline-flex; align-items:center; gap:6px; 1850 margin-top:12px; padding:6px 14px; border-radius:999px; cursor:pointer; font-family:inherit; 1851 font-size:11.5px; font-weight:600; color:var(--accent); 1852 background:color-mix(in srgb, var(--accent) 8%, transparent); 1853 border:1px solid color-mix(in srgb, var(--accent) 26%, transparent); 1854 transition:background 130ms ease, border-color 130ms ease;} 1855.bvr-spinexlsx:hover{background:color-mix(in srgb, var(--accent) 15%, transparent); border-color:color-mix(in srgb, var(--accent) 42%, transparent);} 1856.bvr-spinexlsx:focus-visible{outline:2px solid var(--accent); outline-offset:2px;} 1857.bvr-spinexlsx svg{opacity:.9;} 1858/* Single column already has the prominent .bvr-xlsxcard right below the spine, so this 1859 duplicate is redundant there â keep it only on desktop (no card at those widths). */ 1860@media (max-width:700px){ .bvr-spinexlsx{display:none;} } 1861.bvr-spineteaser{grid-column:1/-1; margin-top:10px; padding-top:10px; border-top:0.5px solid var(--line); display:flex; flex-direction:column; align-items:center; gap:9px; text-align:center;} 1862.bvr-spineteaser > span{font-size:11px; color:var(--muted); line-height:1.5; max-width:430px;}
1863.bvr-spinecta{display:inline-flex; align-items:center; gap:5px; background:color-mix(in srgb, var(--accent) 9%, transparent); border:1px solid color-mix(in srgb, var(--accent) 30%, transparent); color:var(--accent); font:inherit; font-size:11.5px; font-weight:600; padding:6px 14px; border-radius:999px; cursor:pointer;} 1864.bvr-spinecta:hover{background:color-mix(in srgb, var(--accent) 16%, transparent);} 1865.bvr-spinecta:focus-visible{outline:2px solid var(--accent); outline-offset:2px;} 1866/* Excel export CARD â the single-column home for the action, below the spine. A full-width 1867 accent-tinted card in the app's surface idiom: icon tile, two-line label, chevron affordance. 1868 Shown ONLY â¤700 (the rail chip owns wider layouts). */ 1869.bvr-xlsxcard{display:none;} 1870@media (max-width:700px){ 1871 .bvr-xlsxcard{display:flex; align-items:center; gap:13px; width:100%; margin-top:14px; 1872 padding:14px 16px; text-align:left; cursor:pointer; font-family:inherit; 1873 border-radius:14px; color:var(--ink); 1874 background:linear-gradient(180deg, color-mix(in srgb, var(--accent) 7%, var(--card)), color-mix(in srgb, var(--accent) 11%, var(--card))); 1875 border:1px solid color-mix(in srgb, var(--accent) 30%, transparent); 1876 box-shadow:0 1px 2px color-mix(in srgb, var(--ink) 6%, transparent); 1877 transition:border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease;} 1878 .bvr-xlsxcard:hover{border-color:color-mix(in srgb, var(--accent) 48%, transparent); 1879 box-shadow:0 3px 12px color-mix(in srgb, var(--accent) 16%, transparent);} 1880 .bvr-xlsxcard:active{transform:translateY(1px);} 1881 .bvr-xlsxcard:focus-visible{outline:2px solid var(--accent); outline-offset:2px;} 1882 .bvr-xlsxcard-i{flex:0 0 auto; display:flex; align-items:center; justify-content:center; 1883 width:38px; height:38px; border-radius:10px; color:#fff; background:var(--accent); 1884 box-shadow:inset 0 0 0 1px color-mix(in srgb, #fff 22%, transparent);} 1885 .bvr-xlsxcard-t{display:flex; flex-direction:column; gap:2px; min-width:0; flex:1 1 auto;} 1886 .bvr-xlsxcard-h{font-size:14px; font-weight:700; color:var(--ink); letter-spacing:-0.01em;} 1887 .bvr-xlsxcard-s{font-size:11.5px; color:var(--muted); line-height:1.35;} 1888 .bvr-xlsxcard-go{flex:0 0 auto; color:var(--accent); opacity:0.7; transition:transform 140ms ease, opacity 140ms ease;} 1889 .bvr-xlsxcard:hover .bvr-xlsxcard-go{transform:translateX(2px); opacity:1;} 1890} 1891/* Verdict "see the full breakdown" affordance â its OWN line under the pre-liq sub (was an 1892 inline "more" jammed after "yr 7", which read as crowded). Centered accent link with a 1893 chevron that rotates when the fold+spine are open. Clicking it opens BOTH the verdict fold 1894 and the Details spine (toggleDetail) â one diligence gesture, two synchronized views. */ 1895.bvr-vmore{display:inline-flex; align-items:center; gap:4px; margin-top:8px; background:none; border:0; padding:2px 0; font:inherit; font-size:12px; font-weight:600; color:var(--accent); cursor:pointer; letter-spacing:-0.005em;} 1896.bvr-vmore svg{transition:transform 180ms cubic-bezier(.4,0,.2,1);} 1897.bvr-vmore[aria-expanded="true"] svg{transform:rotate(180deg);} 1898.bvr-vmore:hover{text-decoration:underline; text-underline-offset:3px;} 1899.bvr-vmore:focus-visible{outline:2px solid var(--accent); outline-offset:2px; border-radius:4px;} 1900.bvr-sh{font-size:12.5px; font-weight:600; padding-bottom:5px;} 1901/* Icon + label inline, matching the .bvr-lghead pattern in the charts above. The strategy color 1902 is set on the whole header so the lucide icon (stroke:currentColor) picks it up; the label text 1903 is ink by default but the icon reads buy-blue / rent-amber. justify-content:flex-end keeps the 1904 pair right-aligned over its column (buy/rent values are right-aligned). */ 1905.bvr-shb, .bvr-shr{display:flex; align-items:center; justify-content:flex-end; gap:5px;} 1906.bvr-shb{text-align:right; border-bottom:2px solid var(--buy); color:var(--buy);} 1907.bvr-shr{text-align:right; border-bottom:2px solid var(--rent); color:var(--rent);} 1908/* Was an empty aria-hidden spacer; now carries the panel's ONLY year readout (the subtitle 1909 that used to hold it was removed), so it is lifted from faint/10px spacer type to muted/11px 1910 content type. Bottom-aligned so it sits on the same baseline as the Buy/Rent column heads 1911 that flank it, without borrowing their 2px tone-coded underline (it labels the ROW, not a 1912 column). */ 1913.bvr-shy{text-align:center; font-size:11px; color:var(--muted); align-self:end; padding-bottom:5px; white-space:nowrap;} 1914.bvr-sg{grid-column:1/-1; text-align:center; font-size:9.5px; text-transform:uppercase; letter-spacing:.05em; color:var(--faint); padding:9px 0 3px;} 1915.bvr-sb,.bvr-sr{text-align:right; font-size:12.5px; padding:4px 0;} 1916.bvr-slab{text-align:center; font-size:11px; color:var(--muted); padding:4px 0; white-space:nowrap; display:flex; align-items:center; justify-content:center; gap:5px;} 1917/* Row-hover affordance replacing the (i) icons: dotted underline on the label (the whole 1918 row is the popover trigger, as before). */ 1919.bvr-slab.clickable:hover{text-decoration:underline dotted; text-decoration-color:var(--accent); text-underline-offset:3px;} 1920/* Balance-sheet cells: END value + a green/red delta pill, right-aligned to the column (buy/rent 1921 numbers are right-aligned). The value is the primary figure; the pill (.bvr-bsd, shared with the 1922 balance popover) sits to its right. gap keeps them from touching; nowrap so the pair stays on 1923 one line within its column. */ 1924.bvr-secell{display:inline-flex; align-items:center; justify-content:flex-end; gap:5px; white-space:nowrap;} 1925.bvr-searr{color:var(--faint); margin:0 1px;} 1926.bvr-senum{font-size:12.5px;} 1927.bvr-senum.neg{color:var(--neg);} 1928.bvr-sb.se,.bvr-sr.se{white-space:nowrap;} 1929.bvr-sb.credit,.bvr-sr.credit{color:var(--credit);} 1930.bvr-sb.tot,.bvr-sr.tot,.bvr-slab.tot{font-weight:600; border-top:0.5px solid var(--line2); padding-top:6px; margin-top:2px; color:var(--ink);} 1931/* Collapsed-spine value tint (user ask): Buy column blue, Rent column amber, echoing the 1932 chart-4 bars. Scoped to .tone-buy/.tone-rent (set only by the collapsed caller). We tint the 1933 plain figure (.bvr-sb text) and the .bvr-senum inside balance-sheet cells, but NOT the green/ 1934 red delta pill (.bvr-bsd keeps its own up/dn color) nor negative levels (.neg stays red). The 1935 selectors beat .tot's ink color via the extra .tone-* class. */ 1936.bvr-sb.tone-buy,.bvr-sb.tone-buy .bvr-senum:not(.neg){color:var(--buy);} 1937.bvr-sr.tone-rent,.bvr-sr.tone-rent .bvr-senum:not(.neg){color:var(--rent);} 1938/* Subtotal rows (Gross housing outflow): a quieter tot â hairline in --line (lighter than the 1939 tot's --line2), semibold numbers in default ink, label stays muted. */ 1940.bvr-sb.sub,.bvr-sr.sub{font-weight:600; border-top:0.5px solid var(--line); padding-top:6px; margin-top:2px;} 1941.bvr-slab.sub{border-top:0.5px solid var(--line); padding-top:6px; margin-top:2px;} 1942.bvr-dash{color:var(--faint);} 1943/* --- Spine on narrow screens: invert the elasticity --------------------------------------- 1944 Desktop: label column is rigid (auto) and the value columns flex (1fr). On phones that's 1945 exactly backwards â long labels ("Depreciation benefit · suspended §469") pin the center 1946 wide and shove the Rent numbers off the panel. So â¤520px the priority flips: the VALUE
1947 columns become max-content (numbers are the data â they may never clip) and the label 1948 column becomes minmax(0,1fr), free to wrap. Labels drop nowrap, gain flex-wrap + 1949 text-wrap:balance so a two-line label breaks evenly and stays centered with its chip and 1950 (i) flowing inline. By construction this cannot overflow: two number columns at 1951 max-content (~64px each) + gaps leave the label â¥150px even at 320px viewports. */ 1952@media (max-width:600px){ 1953 .bvr-spine{grid-template-columns:max-content minmax(0,1fr) max-content; column-gap:10px; max-width:none;} 1954 .bvr-sb,.bvr-sr{font-size:12px;} 1955 .bvr-slab{white-space:normal; flex-wrap:wrap; row-gap:1px; text-wrap:balance; 1956 font-size:10.5px; line-height:1.25; padding:5px 0;} 1957} 1958/* CONTAINER-DRIVEN spine compression (user bug: rent column clipped "in many sizes"). The 1959 600px viewport query above mispredicts whenever the PANEL is narrower than the spine's 1960 natural width at WIDER viewports â the 701â~1000px two-column band (rail on the left), the 1961 folded-rail strip, and the 701â900 mid rail-width all shrink the panel without shrinking 1962 the viewport, so the desktop template (140px value minimums + a content-sized center 1963 column full of rate pills) overflowed the panel's right edge and clipped the Rent column. 1964 The PANEL is the truth: it is now a size container, and the SAME compressed template fires 1965 whenever it drops below the spine's natural need (~610px incl. panel padding). The 1966 viewport query above stays as a fallback for engines without container queries â the two 1967 blocks apply identical declarations, so double-firing is harmless. */ 1968.bvr-spinepanel{container-type:inline-size;} 1969@container (max-width:614px){ 1970 .bvr-spine{grid-template-columns:max-content minmax(0,1fr) max-content; column-gap:10px; max-width:none;} 1971 .bvr-sb,.bvr-sr{font-size:12px;} 1972 .bvr-slab{white-space:normal; flex-wrap:wrap; row-gap:1px; text-wrap:balance; 1973 font-size:10.5px; line-height:1.25; padding:5px 0;} 1974} 1975/* Prop 13 advantage rows in the propTax popover (CA only): light credit-green background so 1976 the three-row block reads as a "benefit zone" â matches the hatched-green kept/credit 1977 visual language in chart 1. Negative margins bleed the tint to the popover's padded edges. */ 1978.bvr-poprows .p13{background:color-mix(in srgb, var(--credit) 6%, var(--card)); 1979 margin-inline:-6px; padding-inline:6px;} 1980.bvr-tag{font-style:normal; font-size:8.5px; background:var(--accentBg); color:var(--accent); padding:1px 5px; border-radius:999px;} 1981 1982.bvr-foot{font-size:10.5px; color:var(--faint); line-height:1.55; border-top:0.5px solid var(--line); padding-top:9px;} 1983 1984/* Inline "more"/"less" text button in .bvr-foot. Muted ink (one step stronger than the 1985 --faint surrounding text) with a 0.5px underline lifted 2px off the baseline; hover + 1986 focus-visible pull to --accent. font: inherit and background:transparent keep it visually 1987 part of the text run. Focus-visible ring follows the app's 2px accent convention (see the 1988 hzbar). */ 1989.bvr-footmore{background:transparent; border:0; padding:0; margin:0; font:inherit; color:var(--muted); 1990 text-decoration:underline; text-decoration-thickness:0.5px; text-underline-offset:2px; 1991 cursor:pointer; transition:color 140ms ease; border-radius:2px;} 1992.bvr-footmore:hover{color:var(--accent);} 1993.bvr-footmore:focus-visible{color:var(--accent); outline:2px solid var(--accent); outline-offset:2px;} 1994 1995/* Progressive-disclosure spans (INLINE). Two mutually-exclusive inline <span>s live inside 1996 the same .bvr-foot paragraph so the extended text flows continuously after "review of" with 1997 no block break. Animation uses 'transition-behavior: allow-discrete' + @starting-style: 1998 when a span goes display: none -> inline, display flips discretely at the START of the 1999 transition and opacity animates 0->1 from the @starting-style baseline; when going the 2000 other way, opacity animates 1->0 first and display flips to none at the END. Falls back to 2001 instant toggle in browsers without @starting-style support -- same functional outcome. */ 2002/* No-overlap swap. CSS-only symmetric transitions can't get the display-flip timing right 2003 in both directions (allow-discrete hard-codes display change to 0% going-visible / 100% 2004 going-invisible, and transition-delay only shifts opacity, not that discrete flip). So 2005 brief is JS-controlled with a .brief-hidden class: set immediately on 'more' click, 2006 cleared 320ms after 'less' click (see openFoot/closeFoot handlers). Full retains the 2007 pure-CSS fade w/ transition-behavior: allow-discrete + @starting-style. Result: on 'more', 2008 brief vanishes at T=0 and full fades 0->1 against stable layout; on 'less', full fades 2009 1->0 while brief stays hidden, then brief re-appears cleanly at T=320. O
2009ne reflow per 2010 click. */ 2011.bvr-footmore-brief{display:inline; opacity:1;} 2012.bvr-footmore-brief.brief-hidden{display:none; opacity:0;} 2013.bvr-footmore-full{display:none; opacity:0; transition:opacity 300ms ease, display 300ms; transition-behavior:allow-discrete;} 2014.bvr-foot-expanded .bvr-footmore-full{display:inline; opacity:1;} 2015@starting-style{ 2016 .bvr-foot-expanded .bvr-footmore-full{opacity:0;} 2017} 2018@media (prefers-reduced-motion: reduce){ 2019 .bvr-footmore-brief, .bvr-footmore-full, .bvr-footmore{transition:none;} 2020} 2021 2022/* Compliance disclosure block -- quiet formal footer at the very end of .bvr-center. Eyebrow 2023 keeps the ORIGINAL 9.5px/700/uppercase ptag type in muted, not accent, and deliberately does 2024 NOT take the fluid --eyebrow scale the section markers now use: this is chrome, not 2025 navigation, and it must not grow into the wayfinding tier. Body type matches .bvr-foot 2026 (10.5px faint). Hairline top border keeps 2027 the .bvr-foot idiom; the block is full-width in-flow so the fractional border paints fine 2028 (the hairline gotcha applies to intrinsic-width centered content, not here). */ 2029.bvr-disclosure{border-top:0.5px solid var(--line); padding-top:10px;} 2030.bvr-disclosure .bvr-disct{font-size:9.5px; font-weight:700; letter-spacing:0.57px; text-transform:uppercase; color:var(--muted); margin:0 0 6px;} 2031.bvr-disclosure p{font-size:10.5px; line-height:1.55; color:var(--faint); margin:0 0 7px;} 2032.bvr-disclosure p:last-child{margin-bottom:0;} 2033/* Site copyright â the last line on the page, quiet and centered under the disclosures. */ 2034.bvr-copyright{margin-top:12px; padding-top:9px; border-top:0.5px solid var(--line); 2035 font-size:10px; color:var(--faint); text-align:center; letter-spacing:0.02em;} 2036 2037.bvr-li{background:none; border:none; padding:0 0 0 4px; cursor:pointer; color:var(--faint); display:inline-flex; align-items:center; vertical-align:-1px;} 2038.bvr-li:hover{color:var(--accent);} 2039/* Synthetic-loan Info affordance. Sits to the RIGHT of the loan-type dropdown as a BARE icon 2040 (no border/background chrome â the earlier outlined-square treatment competed with the 2041 select and sat visually low). align-self:center overrides the wrapper's align-items:stretch 2042 so the icon is dead-center on the select's height. Accent on hover matches the app's 2043 help-affordance convention. */ 2044.bvr-synselwrap{display:flex; align-items:stretch; gap:6px;} 2045.bvr-synselwrap > .bvr-sel{flex:1 1 auto; min-width:0;} 2046.bvr-synli{padding:0 2px 0 6px; flex:0 0 auto; border:none; border-radius:6px; 2047 background:none; color:var(--muted); cursor:pointer; align-self:center; 2048 display:inline-flex; align-items:center; justify-content:center; 2049 transition:color 120ms ease;} 2050.bvr-synli:hover{color:var(--accent);} 2051.bvr-synli:focus-visible{outline:2px solid var(--accent); outline-offset:1px;} 2052.clickable{cursor:pointer;} 2053.bvr-sb.clickable:hover,.bvr-sr.clickable:hover{color:var(--accent);} 2054.bvr-why{background:none; border:none; padding:0 0 0 8px; cursor:pointer; color:var(--accent); font-size:11px; font-weight:600; text-decoration:underline; text-underline-offset:2px;} 2055 2056.bvr-pop{position:fixed; z-index:9999; width:316px; max-height:74vh; overflow:auto; background:var(--card); 2057 border:0.5px solid var(--line2); border-radius:10px; padding:13px 14px; 2058 box-shadow:0 10px 34px rgba(19,36,46,.18);} 2059/* View-only mini holding-period bar atop annual line popovers â echoes the horizon slider 2060 so the year context travels with the popup; the fill tracks E (no interaction). */ 2061.bvr-minihz{height:16px; border-radius:999px; background:color-mix(in srgb, var(--accent) 7%, var(--card)); 2062 border:0.5px solid color-mix(in srgb, var(--accent) 18%, transparent); overflow:hidden; margin-bottom:9px;} 2063.bvr-minihz-fill{height:100%; border-radius:999px; background:color-mix(in srgb, var(--accent) 24%, var(--card)); 2064 display:flex; align-items:center; justify-content:flex-end; padding-right:7px; min-width:40px; 2065 transition:width 200ms ease;} 2066.bvr-minihz-fill span{font-size:9px; font-weight:700; color:var(--accent); letter-spacing:.03em; white-space:nowrap;} 2067.bvr-popt{font-size:12px; font-weight:600; color:var(--ink); margin-bottom:9px;} 2068/* Popover drag surface â the title row, sticky-sliders gesture verbatim: grab cursor, 2069 no text selection, touch-action none so pointer dragging works on touch. The grip 2070 glyph top-left is the affordance. */ 2071.bvr-popdrag{display:flex; align-items:flex-start; gap:6px; cursor:grab; user-select:none; 2072 -webkit-user-select:none; touch-action:none;} 2073.bvr-popdrag:active{cursor:grabbing;} 2074.bvr-popdrag > *:last-child{flex:1; min-width:0;} 2075.bvr-popgrip{color:var(--faint); flex:none; margin-top:2px;} 2076/* Notes fold toggle â carries the hairline divider the first note used to draw; tiny 2077 uppercase caption idiom, chevron flips when open. */ 2078.bvr-popnotetog{width:100%; display:flex; align-items:center; justify-content:space-between; 2079 background:none; border:none; border-top:0.5px solid var(--line); border-radius:0; 2080 margin-top:9px; padding:7px 0 0; font-family:inherit; font-size:9px; font-weight:600; 2081 letter-spacing:.09em; text-transform:uppercase; color:var(--faint); cursor:pointer;} 2082.bvr-popnotetog:hover{color:var(--muted);} 2083.bvr-popnotetog svg{transition:transform 180ms cubic-bezier(.4,0,.2,1);} 2084.bvr-popnotetog svg.open{transform:rotate(180deg);} 2085/* Inside the fold, the toggle already drew the divider â the first note drops its own. */ 2086.bvr-popnotebody .bvr-popnote{border-top:none; padding-top:0; margin-top:6px;} 2087.bvr-popsub{font-size:10.5px; color:var(--muted); margin-top:-6px; margin-bottom:9px;} 2088.exclseg{margin-bottom:0; flex-shrink:0;} 2089.exclseg button{min-width:0; padding:4px 8px; font-size:10.5px;} 2090.filingseg{display:flex; width:100%; margin:2px 0 8px;} 2091/* Presets trigger â a real secondary button (was a dotted-underline link). Full-width, bordered, 2092 accent-colored with icon; hover lifts to the accent background. Clearly an action, not a label. */ 2093.bvr-presetbtn{display:flex; align-items:center; justify-content:center; gap:6px; width:100%; 2094 padding:6px 10px; margin:0 0 9px;
2094 border:0.5px solid color-mix(in srgb, var(--accent) 45%, transparent); 2095 border-radius:7px; background:var(--card); color:var(--accent); font-size:11.5px; font-weight:600; 2096 cursor:pointer; font-family:inherit; transition:background 140ms ease, border-color 140ms ease;} 2097.bvr-presetbtn:hover{background:var(--accentBg); border-color:var(--accent);} 2098/* Variable widths so all four labels fit without truncation. Ratios are text-length-proportional: 2099 Single is longest (6ch), MFJ/MFS/HoH are 3ch each. All shorter â equalize the 3-letter ones and 2100 let Single take a bit more space. Preserves the pill outline via the .bvr-seg parent. */ 2101.filingseg button{flex:1 1 0; min-width:0; padding:5px 4px; font-size:11px; white-space:nowrap; text-overflow:clip;} 2102.filingseg button:nth-child(1){flex-grow:3;} /* Single */ 2103.filingseg button:nth-child(2){flex-grow:4;} /* Married */ 2104.filingseg button:nth-child(3){flex-grow:2;} /* Sep. */ 2105.filingseg button:nth-child(4){flex-grow:2;} /* HoH */ 2106.bvr-popgrid{display:grid; grid-template-columns:1fr auto; column-gap:18px; row-gap:5px; font-size:11.5px; align-items:baseline;} 2107.bvr-popgrid .l{color:var(--muted);} 2108/* "source" link for an auto-filled Tax-rate row (propTax popover). Lives in the VALUE cell, 2109 immediately left of the rate, so it rides the right-aligned edge with the number it sources 2110 rather than trailing the label across the column gap. Header caps idiom at link scale; accent 2111 ink so it reads as chrome, not data. Disappears with the first manual edit (the row only 2112 carries r.src while ptPristine) so no hover or visited styling needed. */ 2113.bvr-poplsrc{font-size:9px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; 2114 color:var(--accent); text-decoration:none; border-bottom:1px dotted var(--accent); 2115 margin-right:8px; vertical-align:1px; white-space:nowrap;} 2116.bvr-poplsrc:hover{border-bottom-style:solid;} 2117.bvr-poplsrc:focus-visible{outline:1.5px solid var(--accent); outline-offset:2px; border-radius:2px;} 2118.bvr-popgrid .v{text-align:right; color:var(--ink); font-size:11.5px;} 2119.bvr-popgrid .neg{color:var(--neg);} 2120/* "Computed, then voided" rows (AMT-disallowed SALT): faint label, faint struck value. */ 2121.bvr-popgrid .faint{color:var(--faint);} 2122.bvr-popgrid .v.strike{text-decoration:line-through; text-decoration-thickness:1px; text-decoration-color:var(--faint);} 2123.bvr-popgrid .sub{font-weight:700; color:var(--ink); padding-top:6px; margin-top:2px;} 2124.bvr-popgrid .subcredit{font-weight:700; color:var(--credit); padding-top:6px; margin-top:2px;} 2125.bvr-popgrid .tot{font-weight:700; color:var(--ink); padding-top:6px; margin-top:2px;} 2126/* Capital-gains derivation rows: faint + indented so they read as a sub-calculation behind the 2127 tax line, not as steps in the net-worth waterfall. detsub is the running subtotal (gain, 2128 taxable gain) â muted and a touch heavier so the carried-forward numbers stand out. */ 2129.bvr-popgrid .det{color:var(--faint); padding-left:11px; font-size:11px;} 2130.bvr-popgrid .detsub{color:var(--muted); padding-left:11px; font-size:11px; font-weight:600;} 2131.bvr-popgrid .totcredit{font-weight:700; color:var(--credit); padding-top:6px; margin-top:2px;} 2132.bvr-popgrid .credit{color:var(--credit);} 2133.bvr-popgrid .v.sub,.bvr-popgrid .v.subcredit,.bvr-popgrid .v.tot,.bvr-popgrid .v.totcredit{border-top:0.75px solid var(--line2);} 2134.bvr-pophr{grid-column:1/-1; border:none; border-top:0.75px solid var(--line2); margin:7px 0 3px;} 2135/* HERO surplus band â the merged surplus/wealth popover leads with this so the amount is the 2136 first thing seen. Spans both grid columns; a left accent bar + tinted amount encode which 2137 side holds the surplus (buy=blue, rent=amber, flat=muted). Negative inline margins bleed the 2138 tint to the popover's padded edges, same trick as .p13. */ 2139.bvr-pophero{grid-column:1/-1; margin:-2px -6px 4px; padding:8px 10px; border-radius:8px; 2140 border-left:3px solid var(--faint); background:color-mix(in srgb, var(--faint) 9%, var(--card));} 2141.bvr-pophero.dir-buy{border-left-color:var(--credit); background:color-mix(in srgb, var(--credit) 9%, var(--card));} 2142.bvr-pophero.dir-rent{border-left-color:var(--rent); background:color-mix(in srgb, var(--rent) 9%, var(--card));} 2143.bvr-pophero.dir-flat{border-left-color:var(--faint); background:color-mix(in srgb, var(--faint) 8%, var(--card));} 2144/* MUTED hero â used when the popover was opened from the Principal row: the surplus is shown for 2145 context but NOT as a colored highlight (it must not compete with the principal row wash). Plain 2146 surface, hairline left rule, ink amount. */ 2147.bvr-pophero.dir-mute{border-left:3px solid var(--line2); background:transparent; padding-left:9px;} 2148.bvr-pophero.dir-mute .bvr-popheroamt{color:var(--muted);} 2149.bvr-popherotop{display:flex; align-items:baseline; justify-content:space-between; gap:12px;} 2150.bvr-popherowho{font-size:11px; font-weight:700; color:var(--ink); letter-spacing:.01em;} 2151.bvr-popheroamt{font-size:19px; font-weight:700; color:var(--ink); line-height:1;} 2152.bvr-pophero.dir-buy .bvr-popheroamt{color:var(--credit);} 2153.bvr-pophero.dir-rent .bvr-popheroamt{color:var(--rent);} 2154.bvr-popherocap{display:block; margin-top:3px; font-size:10px; color:var(--muted); line-height:1.3;} 2155/* Row HIGHLIGHT â the row the popover was opened FROM. One CONTINUOUS wash across the whole row: 2156 the .l cell's background bridges the grid's 18px column-gap (margin-right:-18px + padding-right:18px 2157 extends its box to meet the .v cell's left edge), and .v carries the wash to the popover's right 2158 edge. Left accent rail on .l, rounded ends on the outer corners. 2159 The grid is baseline-aligned, which broke WRAPPING pill rows: a 2-line label made the row 2 lines 2160 tall, but the single-line value baseline-aligned to the FIRST line â so its wash covered only the 2161 top line and the two halves looked disjointed. Fix: both pill cells align-self:stretch (equal
2162 height = the wrapped label's) and flex-center their content vertically, so the value sits centered 2163 against the 2-line label and the pill reads as one clean rounded block top-to-bottom. */ 2164.bvr-popgrid .l.bvr-pophl,.bvr-popgrid .v.bvr-pophl{background:color-mix(in srgb, var(--accent) 12%, var(--card)); 2165 font-weight:700; color:var(--ink); padding-top:3px; padding-bottom:3px; align-self:stretch; 2166 display:flex; align-items:center; min-width:0;} 2167.bvr-popgrid .l.bvr-pophl{margin-left:-6px; margin-right:-18px; padding-left:6px; padding-right:18px; 2168 border-radius:7px 0 0 7px; box-shadow:-3px 0 0 0 var(--accent); justify-content:flex-start;} 2169.bvr-popgrid .v.bvr-pophl{margin-right:-6px; padding-right:6px; padding-left:4px; border-radius:0 7px 7px 0; 2170 justify-content:flex-end; white-space:nowrap;} 2171.bvr-popgrid .v.bvr-pophl.credit,.bvr-popgrid .l.bvr-pophl.credit{color:var(--credit);} 2172/* Toned pill variants for the SURPLUS rows: the row inherits the color of its hero band, so the 2173 headline and the highlighted row read as one story. hl-rent = renter-surplus amber, hl-credit = 2174 homeowner-surplus green. Placed AFTER the .credit rule so the tone wins on text color (equal 2175 specificity, later source order). The gap-bridging geometry is inherited from .bvr-pophl. */ 2176.bvr-popgrid .l.bvr-pophl.hl-rent,.bvr-popgrid .v.bvr-pophl.hl-rent{background:color-mix(in srgb, var(--rent) 13%, var(--card)); color:var(--rent);} 2177.bvr-popgrid .l.bvr-pophl.hl-rent{box-shadow:-3px 0 0 0 var(--rent);} 2178.bvr-popgrid .l.bvr-pophl.hl-credit,.bvr-popgrid .v.bvr-pophl.hl-credit{background:color-mix(in srgb, var(--credit) 13%, var(--card)); color:var(--credit);} 2179.bvr-popgrid .l.bvr-pophl.hl-credit{box-shadow:-3px 0 0 0 var(--credit);} 2180/* Section header inside a popover â small caps accent color, spans both columns. Provides its 2181 own top-margin visual break, so no separator hr is needed above it. The :first-child variant 2182 zeroes the top margin so a leading section header sits flush with the popover title above. */ 2183.bvr-pophead{grid-column:1/-1; margin:10px 0 3px; font-size:9.5px; font-weight:700; 2184 letter-spacing:.06em; text-transform:uppercase; color:var(--accent); line-height:1.1;} 2185.bvr-pophead:first-child{margin-top:0;} 2186.bvr-editpct{font-family:var(--num); font-variant-numeric:tabular-nums; font-size:10.5px; color:var(--accent); 2187 background:var(--accentBg); border:0.5px solid var(--accent); border-radius:6px; padding:1px 5px; margin-left:7px; 2188 width:44px; text-align:center; cursor:pointer;} 2189.bvr-editpct:hover{background:var(--card);} 2190.bvr-editpct:focus{outline:none; background:var(--card); border-color:var(--accent);} 2191.bvr-editpct.inval{margin-left:0; width:62px; text-align:right; font-size:11.5px;} 2192.bvr-popnote{font-size:10px; color:var(--faint); line-height:1.55; margin-top:9px; padding-top:9px; border-top:0.5px solid var(--line);} 2193/* Notes tooltip. The merged property-tax popover carries three sections (rate picker, assessed 2194 cap with its old help popover folded in, SALT); printing every section note inline ran to 2195 seven paragraphs, so each section keeps a compact info button and floats its own prose 2196 instead. The bubble is absolutely positioned against its ROW container, with left:0/right:0 2197 pinning it to the popover content width - it can never cause horizontal overflow - and it 2198 costs zero height while shut, so the merged popover opens no taller than the old one. */ 2199.bvr-tipwrap{position:relative; display:block; margin:5px 0 2px;} 2200.bvr-tiprow{display:flex; align-items:center; gap:6px; width:100%; min-height:30px; padding:6px 9px; 2201 border:0.5px dashed var(--line2); border-radius:7px; background:transparent; color:var(--muted); 2202 cursor:pointer; text-align:left; font:inherit;} 2203.bvr-tiprow:hover{border-style:solid; border-color:var(--accent); color:var(--accent); background:rgba(14,124,134,.05);} 2204.bvr-tiprow.on{border-style:solid; border-color:var(--accent); color:var(--accent); background:rgba(14,124,134,.08);} 2205.bvr-tiprow:focus-visible{outline:2px solid var(--accent); outline-offset:1px;} 2206.bvr-tiprowi{flex:none;} 2207.bvr-tiprowl{flex:1; font-size:10.5px; font-weight:600; letter-spacing:.02em;} 2208.bvr-tiprowc{flex:none; transition:transform .16s ease;} 2209.bvr-tiprow.on .bvr-tiprowc{transform:rotate(180deg);} 2210.bvr-poptip{position:absolute; top:calc(100% + 4px); left:0; right:0; z-index:12; 2211 background:var(--card);
2211 border:0.5px solid var(--line2); border-radius:8px; padding:9px 10px; 2212 box-shadow:0 8px 22px rgba(19,36,46,.20); display:flex; flex-direction:column; gap:7px;} 2213.bvr-poptipn{font-size:10px; color:var(--faint); line-height:1.55;} 2214/* Source links for figures that change over time. Inside a popover a figure stays plain (the 2215 app's figure-tip policy keeps tips out of popover bodies), so its source rides beside it: the 2216 "source" chip after the figure, or the citation words of a note. */ 2217.bvr-poplsrc-after{margin-right:0; margin-left:6px;} 2218.bvr-popnotelink{color:var(--accent); text-decoration:none; border-bottom:1px dotted var(--accent);} 2219.bvr-popnotelink:hover{border-bottom-style:solid;} 2220.bvr-popnotelink:focus-visible{outline:1.5px solid var(--accent); outline-offset:2px; border-radius:2px;} 2221.bvr-capblock{position:relative;} 2222.bvr-popnote ~ .bvr-popnote{margin-top:6px; padding-top:0; border-top:none;} 2223/* Big hero display for popovers that want a chart-caption-style header (Synthetic loan). 2224 Mirrors .bvr-yv2net b: 22px bold ink number with a small muted subtitle inline. Below 2225 it, .bvr-popbig em is the same uppercase-tracked micro-caption used under the chart 2226 figure ("net after-tax cash consumed" â "synthetic loan"). Layout is column so the 2227 micro-caption sits under the number, like the chart. */ 2228.bvr-popbig{display:flex; flex-direction:column; align-items:flex-start; line-height:1.15; margin:-2px 0 8px;} 2229.bvr-popbig b{font-size:22px; font-weight:700; color:var(--ink); display:inline-flex; align-items:baseline; gap:6px;} 2230.bvr-popbigsub{font-size:10.5px; font-weight:500; color:var(--muted); letter-spacing:0.02em; font-family:var(--sans);} 2231.bvr-popbig em{font-style:normal; font-size:9px; color:var(--faint); text-transform:uppercase; letter-spacing:.06em; margin-top:3px;} 2232/* Inline toggle rows inside a popover â used for Synthetic loan's Tax-deductible and 2233 Auto-roll switches. Sits inside .bvr-popcustom (full-width grid row) so the label + 2234 toggle can space themselves out. Font-size mirrors popover body rows. */ 2235.bvr-poptog{display:flex; align-items:center; justify-content:space-between; gap:12px; 2236 font-size:11.5px; color:var(--ink); padding:2px 0;} 2237/* State line under a popover toggle â e.g. dep popover's suspended/offsets readout. */ 2238.bvr-poptoghint{font-size:10px; color:var(--faint); padding:2px 0 4px; line-height:1.3;} 2239/* Conflict notice in the SBLOC popover â explains an auto-resolved switch collision 2240 (No monthly payments vs Tax deductible). Deliberately a NOTICE, not an error: the app 2241 already did the right thing, this says why. Uses --neg ink at low alpha for the 2242 attention cue without the alarm of a filled red state. Remounts on the pop nonce so a 2243 repeat collision replays the entrance. No backticks in this comment â see gotcha 1. */ 2244.bvr-popconf{display:flex; gap:7px; align-items:flex-start; font-size:10.5px; line-height:1.35; 2245 color:var(--ink); background:color-mix(in srgb, var(--neg) 9%, var(--card)); 2246 border:1px solid color-mix(in srgb, var(--neg) 32%, transparent); 2247 border-radius:7px; padding:7px 9px; margin:1px 0 7px; 2248 animation:bvrConfIn 420ms cubic-bezier(.26,1.4,.36,1);} 2249.bvr-popconf b{color:var(--neg); font-weight:700;} 2250.bvr-popconf svg{flex:0 0 auto; color:var(--neg); margin-top:1px;} 2251@keyframes bvrConfIn{from{opacity:0; transform:translateY(-4px);} to{opacity:1; transform:none;}} 2252@media (prefers-reduced-motion: reduce){.bvr-popconf{animation:none;}} 2253/* Popover footer (progressive-disclosure toggles like "More" / "Show less"). Underline hint 2254 on hover to communicate clickability without being a solid button. */ 2255.bvr-popfoot{margin-top:10px; padding-top:8px; border-top:0.5px solid var(--line); display:flex; justify-content:flex-start;} 2256.bvr-popmore{background:none; border:none; padding:0; cursor:pointer; font:inherit; 2257 font-size:10.5px; font-weight:600; color:var(--accent); letter-spacing:.02em;} 2258.bvr-popmore:hover{text-decoration:underline; text-decoration-style:dotted; text-underline-offset:3px;} 2259.bvr-popmore:focus-visible{outline:2px solid var(--accent); outline-offset:2px; border-radius:3px;} 2260/* Assessed cap on/off switch + narrower input, both flush-right inside the Field. When the 2261 cap is disabled, dim the numeric input so it reads as "not currently in effect" without 2262 clearing the value (users can toggle back on and their number is restored). */ 2263.bvr-assesswrap{display:inline-flex; align-items:center; gap:8px; flex:0 0 auto;} 2264.bvr-assesswrap .bvr-in{opacity:1; transition:opacity 120ms ease;} 2265.bvr-assesswrap:has(.bvr-lgsw:not(.on)) .bvr-in{opacity:0.4;} 2266 2267.bvr-chiprow{display:flex; flex-wrap:wrap; gap:6px; margin:2px 0 8px;} 2268.bvr-idwrap{display:flex; align-items:center; gap:6px; min-width:0;} 2269.bvr-fldstack{flex-direction:column; align-items:stretch; gap:4px;} 2270.bvr-fldrow{display:flex; align-items:center; justify-content:space-between; gap:8px;} 2271.bvr-block{display:block;} 2272.bvr-block .bvr-in{width:100%;} 2273.bvr-segsm button{padding:3px 8px; font-size:10px;} 2274.bvr-seg button.dim{color:var(--faint);} 2275.bvr-popcustom{grid-column:1/-1; padding:1px 0;} 2276.bvr-closerow{display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap;} 2277.bvr-closelab{font-size:11.5px; color:var(--muted); flex-shrink:0;} 2278.cctog{font-size:10px; color:var(--faint); text-align:center; cursor:pointer; 2279 display:flex; align-items:center; justify-content:center; gap:4px; padding:1px 0;} 2280.cctog:hover{color:var(--accent); text-decoration:underline; text-decoration-style:dotted; text-underline-offset:3px;} 2281.bvr-fundsum{align-items:baseline; justify-content:space-between; text-align:left; gap:6px 10px; padding:3px 0; flex-wrap:wrap;} 2282.bvr-fundsum .bvr-fundsumlab{display:inline-flex; align-items:center; gap:4px; color:var(--muted); font-size:11.5px; flex:0 0 auto;} 2283.bvr-fundsum .bvr-fundsumval{font-size:11px; color:var(--ink); text-align:right; flex:1 1 auto; min-width:0; white-space:nowrap; overflow-wrap:normal;} 2284.bvr-fundsum .bvr-fundsumval .neg{color:var(--neg); white-space:nowrap;} 2285.bvr-fundsum:hover{text-decoration:none;} 2286.bvr-fundsum:hover .bvr-fundsumlab{color:var(--accent);} 2287/* expanded-panel header + live "when collapsed" preview */ 2288.bvr-f2head{margin-bottom:6px;} 2289/* animated expand for the sell-stock detail (grid-rows 0frâ1fr trick) */ 2290.bvr-f2expand{display:grid; grid-template-rows:0fr; transition:grid-template-rows .28s ease; --collapse-ms:.28s;} 2291.bvr-f2expand.open{grid-template-rows:1fr;} 2292.bvr-f2expandinner{overflow:hidden; min-height:0;} 2293.ccrow{display:flex; align-items:center; justify-content:space-between; gap:10px; min-height:26px;} 2294.cclab{font-size:11.5px; color:var(--muted); flex-shrink:0;} 2295.ccr{display:flex; align-items:center; gap:6px;} 2296.ccr>*, .ccseg .ccbtn{height:24px; box-sizing:border-box; margin:0;} 2297.ccx{width:24px; border-radius:999px;
2297 border:0.5px solid var(--line2); background:var(--card); color:var(--neg); 2298 display:flex; align-items:center; justify-content:center; cursor:pointer; padding:0; flex-shrink:0;} 2299.ccx:hover{border-color:var(--neg); background:#FBF0EE;} 2300/* Collapsed "$0 up front purchasing costs" â red ink: a deliberate nudge that $0 is 2301 probably wrong (transactions have costs); tap to add them. */ 2302.cctog.ccwarn{color:var(--neg);} 2303.cctog.ccwarn:hover{color:var(--neg);} 2304/* Panel variant of the status line: left-aligned under the price input, whole row tappable. */ 2305.cctog.ccpanel{text-align:left; margin-top:5px; display:flex; align-items:center; gap:4px; width:100%;} 2306/* Popover editor expand: reuses the f2expand 0frâ1fr grid animation; inner rows get a 2307 little top breathing room so the summary line doesn't crowd the header row. */ 2308.ccexpand .bvr-f2expandinner > .ccrow:first-child{margin-top:6px;} 2309/* Remove-control morph: the à fades/collapses while "remove these" expands on hover â 2310 the control explains itself in place (replaces the tooltip). */ 2311.ccxmorph{display:inline-flex; align-items:center; justify-content:center; width:auto; min-width:24px; height:20px; padding:0 6px; gap:0;} 2312.ccxmorph .ccxicon{display:block; max-width:12px; opacity:1; transition:max-width 200ms ease, opacity 150ms ease;} 2313.ccxmorph .ccxtext{max-width:0; opacity:0; overflow:hidden; white-space:nowrap; font-size:10px; font-weight:600; transition:max-width 240ms ease, opacity 200ms ease;} 2314.ccxmorph:hover .ccxicon{max-width:0; opacity:0;} 2315.ccxmorph:hover .ccxtext{max-width:88px; opacity:1;} 2316.ccseg{display:flex; border:0.5px solid var(--line2); border-radius:6px; overflow:hidden;} 2317.ccbtn{border:none; background:var(--card); color:var(--muted); font-size:10.5px; padding:0 9px; 2318 display:flex; align-items:center; cursor:pointer;} 2319.ccbtn.on{background:var(--accentBg); color:var(--accent); font-weight:600;} 2320.ccin{width:78px; border:0.5px solid var(--accent); border-radius:6px; background:var(--accentBg); 2321 color:var(--accent); font-size:11px; text-align:right; padding:0 7px; display:block;} 2322.ccin:focus{outline:none; background:var(--card);} 2323.ccinp{width:52px;} 2324.cchint{font-size:10px; color:var(--faint); display:flex; align-items:center;} 2325.bvr-closehint{font-size:10.5px; color:var(--faint);} 2326.bvr-lockbtn{background:none; border:none; padding:2px; cursor:pointer; color:var(--faint); display:inline-flex; flex-shrink:0;} 2327.bvr-lockbtn:hover{color:var(--accent);} 2328/* Funding resolver â a calm decision card: dim backdrop, gap sentence, one-tap options. */ 2329/* z-index 10000: must paint ABOVE .bvr-pop (z:9999) â otherwise the keep-balanced resolver 2330 renders behind an already-open popover (the popover that triggered the edit stays open while 2331 the resolver asks what to do with the imbalance). */ 2332.bvr-rswrap{position:fixed; inset:0; z-index:10000; background:rgba(22,28,32,.34); display:flex; 2333 align-items:center; justify-content:center; padding:20px;} 2334.bvr-rs{background:var(--card); border:0.5px solid var(--line2); border-radius:13px; width:min(400px, 94vw); 2335 box-shadow:0 18px 50px rgba(15,23,30,.22); padding:16px 16px 12px;} 2336.bvr-rst{font-size:13.5px; font-weight:700; color:var(--ink); margin-bottom:5px;} 2337.bvr-rsg{font-size:12px; color:var(--muted); line-height:1.45; margin-bottom:11px;} 2338.bvr-rsg b{color:var(--ink);} 2339/* Extra breathing room above the single Add-financing row in the balance resolver. */ 2340.bvr-rsopt.bvr-rsadd{margin-top:12px;} 2341.bvr-rsopt{display:flex; align-items:center; justify-content:space-between; gap:10px; width:100%; 2342 background:var(--card); border:0.5px solid var(--line2); border-radius:9px; padding:8px 11px; 2343 margin-bottom:6px; cursor:pointer; font-family:inherit; font-size:12.5px; color:var(--ink); 2344 transition:border-color 120ms ease, background 120ms ease;} 2345.bvr-rsopt:hover{border-color:var(--accent); background:color-mix(in srgb, var(--accent) 6%, var(--card));} 2346.bvr-rsopt.rec{border-color:var(--accent); box-shadow:0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent);} 2347.bvr-rsopt.off{opacity:.45; cursor:default;} 2348.bvr-rsoptl{display:flex; align-items:center; gap:7px; font-weight:600;} 2349.bvr-rsrec{font-style:normal; font-size:8.5px; font-weight:700; text-transform:uppercase; letter-spacing:.05em; 2350 color:var(--accent); background:var(--accentBg); border-radius:999px; padding:1px 6px;} 2351.bvr-rsoptv{display:flex; align-items:center; gap:6px; font-weight:700;} 2352.bvr-rscancel{display:block; width:100%; background:none; border:none; margin-top:3px; padding:7px 0 3px; 2353 color:var(--muted); font-size:11.5px; cursor:pointer; font-family:inherit; text-align:center;} 2354.bvr-rscancel:hover{color:var(--ink); text-decoration:underline; text-underline-offset:3px;} 2355/* "Holding period" caption glide: left is plain px (cleanly interpolable, unlike the thumb's 2356 clamp()). Side flips remount via key={capMode}, so only same-side movement animates. */ 2357.hzcap{transition:left 160ms ease;} 2358/* Reset trigger + picker */ 2359/* Rail footer: the paired settings actions under a hairline divider. Reset is the 2360 quiet revert; the Excel chip is the fuller, accent-tinted generative action. */ 2361.bvr-railfoot{display:flex; flex-direction:column; gap:7px; width:100%; margin-top:8px; 2362 padding-top:10px; border-top:0.5px solid var(--line);} 2363.bvr-resetlink{display:flex; align-items:center; justify-content:center; gap:6px; width:100%; 2364 background:none; border:none; padding:3px 0; color:var(--faint); 2365 font-size:11.5px; font-family:inherit; cursor:pointer; transition:color 130ms ease;} 2366.bvr-resetlink:hover{color:var(--ink);} 2367.bvr-resetlink svg{transition:transform 240ms ease;} 2368.bvr-resetlink:hover svg{transform:rotate(-180deg);} 2369/* Excel chip â soft-accent, full rail width, in the app's accent-CTA idiom (cf. 2370 .bvr-spinecta). Hidden â¤700: the prominent below-spine card owns single column. */ 2371.bvr-xlsxrail{display:flex; align-items:center; justify-content:center; gap:7px; width:100%; 2372 padding:8px 12px; font-family:inherit; font-size:11.5px; font-weight:600; cursor:pointer;
2373 color:var(--accent); border-radius:9px; 2374 background:color-mix(in srgb, var(--accent) 8%, transparent); 2375 border:1px solid color-mix(in srgb, var(--accent) 26%, transparent); 2376 transition:background 130ms ease, border-color 130ms ease, transform 130ms ease;} 2377.bvr-xlsxrail:hover{background:color-mix(in srgb, var(--accent) 15%, transparent); 2378 border-color:color-mix(in srgb, var(--accent) 42%, transparent);} 2379.bvr-xlsxrail:active{transform:translateY(0.5px);} 2380.bvr-xlsxrail:focus-visible{outline:2px solid var(--accent); outline-offset:2px;} 2381.bvr-xlsxrail svg{opacity:0.92;} 2382@media (max-width:700px){ .bvr-xlsxrail{display:none;} } 2383.bvr-rschk{justify-content:flex-start;} 2384/* Reset checklist labels are QUIETER than the balance-resolver's options (user ask, agreed): 2385 .bvr-rsoptl is 600 because a resolver row pairs a label with a bold value on the right and 2386 needs the weight to hold its own. A checklist row has no value â the checkbox carries the 2387 structure â so 600 competed with the 700 dialog title and read as four headings. 500. */ 2388.bvr-rschk .bvr-rsoptl{font-weight:500;} 2389.bvr-rschkbox{width:15px; height:15px; border-radius:4px; border:1px solid var(--line2); background:var(--card); 2390 display:inline-flex; align-items:center; justify-content:center; color:#fff; flex-shrink:0; 2391 transition:background 120ms ease, border-color 120ms ease;} 2392.bvr-rschk.on .bvr-rschkbox{background:var(--accent); border-color:var(--accent);} 2393.bvr-rsfoot{display:flex; align-items:center; justify-content:flex-end; gap:12px; margin-top:8px;} 2394.bvr-rscancel.inline{display:inline; width:auto; margin:0; padding:6px 2px;} 2395.bvr-rsgo{background:var(--accent); color:#fff; border:none; border-radius:8px; padding:7px 14px; 2396 font-size:12px; font-weight:700; font-family:inherit; cursor:pointer;} 2397.bvr-rsgo:hover{filter:brightness(1.07);} 2398.bvr-rsgo:disabled{opacity:.45; cursor:default;} 2399/* Optional post-download email capture. Same tokens as the dialogs above so it 2400 inherits light/dark and the accent without a second palette. */ 2401.bvr-xlsxmail{width:100%; box-sizing:border-box; background:var(--card); color:var(--ink); 2402 border:1px solid var(--line2); border-radius:8px; padding:8px 10px; margin:2px 0 8px; 2403 font-family:inherit; font-size:13px;} 2404.bvr-xlsxmail::placeholder{color:var(--muted);} 2405.bvr-xlsxmail:focus{outline:none; border-color:var(--accent); 2406 box-shadow:0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent);} 2407.bvr-xlsxmail-fine{margin:9px 0 1px; font-size:11px; line-height:1.4;} 2408.bvr-xlsxmail-bad{margin:-4px 0 6px; font-size:11px; color:var(--neg, #b4443a);} 2409.bvr-xlsxmail[aria-invalid]{border-color:var(--neg, #b4443a);} 2410.bvr-rsgo.ghost{background:var(--card); color:var(--accent); border:1px solid var(--accent);} 2411.bvr-rsgo.ghost:hover{background:color-mix(in srgb, var(--accent) 8%, var(--card)); filter:none;} 2412.bvr-rs.slim{width:min(320px, 94vw);} /* reset picker: 20% narrower than the resolver card */ 2413/* Excel-export promo dialog â shown only in standalone builds (no add-in). Same card 2414 idiom as the reset dialog, with an accent icon tile leading it. */ 2415.bvr-xlsxpromo{width:min(360px, 94vw); text-align:center;} 2416.bvr-xlsxpromo-ico{width:44px; height:44px; margin:2px auto 10px; border-radius:12px; 2417 display:flex; align-items:center; justify-content:center; color:#fff; background:var(--accent); 2418 box-shadow:inset 0 0 0 1px color-mix(in srgb, #fff 22%, transparent);} 2419.bvr-xlsxpromo .bvr-rst{font-size:15px; margin-bottom:7px;} 2420.bvr-xlsxpromo .bvr-rsg{margin-bottom:15px;} 2421.bvr-xlsxpromo-foot{display:flex; flex-direction:column; align-items:stretch; gap:4px;} 2422.bvr-xlsxpromo-go{display:inline-flex; align-items:center; justify-content:center; gap:5px; 2423 text-decoration:none; padding:10px 14px; border-radius:9px; font-size:12.5px;} 2424.bvr-xlsxpromo-foot .bvr-rscancel.inline{align-self:center; order:2; margin-top:2px;} 2425 2426/* ===== Excel download celebration (fanfare) ===== A fixed centered moment: light scrim, 2427 spring-in card, a success seal that draws itself, and a brand-colored confetti burst that 2428 radiates from the seal. Self-dismisses (.closing fade);
2428 click anywhere to close early. */ 2429.bvr-xlcel{position:fixed; inset:0; z-index:9999; display:flex; align-items:center; justify-content:center; 2430 padding:24px; background:color-mix(in srgb, var(--ink) 20%, transparent); 2431 -webkit-backdrop-filter:blur(2px); backdrop-filter:blur(2px); animation:bvr-xlcel-scrim 240ms ease both;} 2432.bvr-xlcel.closing{animation:bvr-xlcel-scrimout 400ms ease forwards;} 2433@keyframes bvr-xlcel-scrim{from{opacity:0;} to{opacity:1;}} 2434@keyframes bvr-xlcel-scrimout{to{opacity:0;}} 2435.bvr-xlcel-card{position:relative; width:min(360px, 100%); background:var(--card); border-radius:20px; 2436 border:1px solid color-mix(in srgb, var(--accent) 22%, transparent); 2437 box-shadow:0 24px 70px color-mix(in srgb, var(--ink) 30%, transparent); 2438 padding:30px 26px 24px; text-align:center; overflow:visible; 2439 animation:bvr-xlcel-pop 460ms cubic-bezier(.18,.9,.28,1.2) both;} 2440.bvr-xlcel.closing .bvr-xlcel-card{animation:bvr-xlcel-cardout 380ms ease forwards;} 2441@keyframes bvr-xlcel-pop{from{opacity:0; transform:scale(.86) translateY(10px);} to{opacity:1; transform:none;}} 2442@keyframes bvr-xlcel-cardout{to{opacity:0; transform:scale(.96) translateY(4px);}} 2443/* Confetti â origin at the seal center; each bit flies to its (--x,--y) and fades. */ 2444.bvr-xlcel-burst{position:absolute; left:50%; top:62px; width:0; height:0; pointer-events:none;} 2445.bvr-xlcel-bit{position:absolute; left:0; top:0; width:var(--sz); height:calc(var(--sz)*0.5); 2446 border-radius:2px; opacity:0; animation:bvr-xlcel-fly var(--dur) var(--d) cubic-bezier(.15,.6,.35,1) forwards;} 2447.bvr-xlcel-bit.round{height:var(--sz); border-radius:50%;} 2448@keyframes bvr-xlcel-fly{ 2449 0%{transform:translate(0,0) rotate(0) scale(0); opacity:0;} 2450 14%{opacity:1; transform:translate(calc(var(--x)*0.34), calc(var(--y)*0.34)) rotate(calc(var(--r)*0.3)) scale(1);} 2451 100%{transform:translate(var(--x), var(--y)) rotate(var(--r)) scale(.55); opacity:0;}} 2452/* Success seal: teal ring + tick draw in, with an expanding halo pulse. */ 2453.bvr-xlcel-seal{position:relative; width:64px; height:64px; margin:0 auto 16px;} 2454.bvr-xlcel-halo{position:absolute; inset:2px; border-radius:50%; background:var(--accent); opacity:.35; 2455 animation:bvr-xlcel-halo 900ms 140ms ease-out forwards;} 2456@keyframes bvr-xlcel-halo{from{transform:scale(.55); opacity:.4;} to{transform:scale(1.9); opacity:0;}} 2457.bvr-xlcel-svg{position:relative; width:64px; height:64px;} 2458.bvr-xlcel-ring{fill:color-mix(in srgb, var(--accent) 10%, var(--card)); stroke:var(--accent); stroke-width:3; 2459 stroke-dasharray:158; stroke-dashoffset:158; animation:bvr-xlcel-draw 520ms 140ms cubic-bezier(.5,0,.3,1) forwards;} 2460.bvr-xlcel-tick{fill:none; stroke:var(--accent); stroke-width:4; stroke-linecap:round; stroke-linejoin:round; 2461 stroke-dasharray:42; stroke-dashoffset:42; animation:bvr-xlcel-draw 300ms 540ms cubic-bezier(.5,0,.3,1) forwards;} 2462@keyframes bvr-xlcel-draw{to{stroke-dashoffset:0;}} 2463.bvr-xlcel-h{font-size:18px; font-weight:700; color:var(--ink); letter-spacing:-0.02em;} 2464.bvr-xlcel-sub{font-size:12px; color:var(--muted); line-height:1.45; margin:6px auto 14px; max-width:290px;} 2465.bvr-xlcel-file{display:inline-flex; align-items:center; gap:6px; font-size:11.5px; font-weight:600; 2466 color:var(--accent); background:color-mix(in srgb, var(--accent) 9%, transparent); 2467 border:1px solid color-mix(in srgb, var(--accent) 22%, transparent); border-radius:999px; padding:6px 13px;} 2468.bvr-xlcel-file svg{opacity:.9;} 2469@media (prefers-reduced-motion:reduce){ 2470 .bvr-xlcel, .bvr-xlcel-card, .bvr-xlcel-halo{animation-duration:1ms !important;} 2471 .bvr-xlcel-burst{display:none;} 2472 .bvr-xlcel-ring, .bvr-xlcel-tick{animation:none; stroke-dashoffset:0;}} 2473/* Floating quick-sliders panel */ 2474/* Sticky-sliders ICON: rotate 90° so the slider lines read HORIZONTAL (the Sliders glyph 2475 is vertical by default). Applied everywhere the trigger/header shows the glyph. */ 2476.bvr-slrot{transform:rotate(90deg);} 2477/* Desktop trigger = a Pin-styled pill (.bvr-railpin gives the shape/hover); .bvr-stickytog 2478 just tunes the icon gap. The title-row variant is pushed to the far right by the flex row. */ 2479.bvr-stickytog{gap:5px;} 2480.bvr-slfab.on{color:var(--accent);} 2481/* Pin + Sticky share EXACTLY one pill style (.bvr-railpin). In the tools row we neutralize 2482 the base align-self so both sit on one baseline and read as a matched pair. */ 2483.bvr-railtools{display:flex; align-items:center; gap:8px; flex-wrap:wrap;} 2484.bvr-railtools .bvr-railpin{align-self:center; margin:0;} 2485.bvr-payoffbtn{display:flex; align-items:center; gap:6px; background:none; border:none; padding:7px 0 1px; 2486 color:var(--muted); font-size:11px; font-family:inherit; cursor:pointer;} 2487.bvr-payoffbtn em{font-style:normal; font-size:8.5px; font-weight:700; text-transform:uppercase; letter-spacing:.05em; 2488 color:var(--accent); background:var(--accentBg); border-radius:999px; padding:1px 6px;} 2489.bvr-payoffbtn:hover{color:var(--ink);} 2490/* ---- Balance-sheet popover v2: single-row items with Î chips ---- */ 2491/* min-width:0 (was 296px, which equalled the popover content box and overflowed horizontally the 2492 moment a vertical scrollbar appeared and stole ~15px of width). The table now fills whatever 2493 width is available and the value wraps between figures rather than forcing a horizontal scroll. */ 2494.bvr-bs{min-width:0;} 2495.bvr-bshead{display:flex; align-items:center; justify-content:space-between; margin:2px 0 4px;} 2496.bvr-bssec{font-size:10px; font-weight:700; letter-spacing:.08em; color:var(--faint);} 2497.bvr-bsmode{background:none;
2497 border:0.5px solid var(--line2); border-radius:999px; padding:2px 8px; 2498 font-size:9.5px; font-weight:600; color:var(--muted); font-family:inherit; cursor:pointer; 2499 transition:color 120ms ease, border-color 120ms ease;} 2500.bvr-bsmode:hover{color:var(--accent); border-color:var(--accent);} 2501.bvr-bsrow{display:grid; grid-template-columns:minmax(0,1fr) minmax(0,max-content); align-items:baseline; 2502 gap:10px; padding:2.5px 0; font-size:12px;} 2503.bvr-bsl{color:var(--muted); display:inline-flex; align-items:center; gap:5px; min-width:0;} 2504/* Note tags ("memo", "prepays debt + invests") â quiet help affordance rather than pill chrome: 2505 faint text with a dotted underline + cursor:help; the title attribute carries the explainer 2506 on hover (desktop). Mobile gets the same story in the "About these numbers" notes. */ 2507.bvr-bsl .bvr-tag{font-style:normal; font-size:9px; font-weight:600; color:var(--faint); white-space:nowrap; 2508 text-decoration:underline dotted; text-decoration-color:var(--line2); text-decoration-thickness:1px; 2509 text-underline-offset:2px; cursor:help;} 2510.bvr-bsl .bvr-tag:hover{color:var(--muted); text-decoration-color:var(--muted);} 2511/* value = flex items (each figure a nowrap span) that wrap between figures under a tight column, 2512 right-aligned so the wrapped second line still tucks under the first. No mid-number breaks. */ 2513.bvr-bsv{font-weight:600; color:var(--ink); display:flex; flex-wrap:wrap; justify-content:flex-end; 2514 align-items:baseline; gap:4px 6px; text-align:right;} 2515.bvr-bsnowrap{white-space:nowrap;} 2516.bvr-bsarr{color:var(--faint);} 2517.bvr-bsv.neg{color:var(--neg);} 2518.bvr-bsv.neg .bvr-bsarr{color:var(--neg);} 2519.bvr-bsd{font-style:normal; font-size:10px; font-weight:700; border-radius:5px; padding:0.5px 4px;} 2520.bvr-bsd.up{color:var(--credit); background:color-mix(in srgb, var(--credit) 12%, transparent);} 2521.bvr-bsd.dn{color:var(--neg); background:color-mix(in srgb, var(--neg) 10%, transparent);} 2522.bvr-bshr{border-top:0.5px solid var(--line2); margin:4px 0;} 2523/* invest-savings switch inside the balance-sheet popover, under the surplus reserve row */ 2524.bvr-bstog{margin:2px 0 0; padding:2px 0;} 2525.bvr-bsrow.tot .bvr-bsl{color:var(--ink); font-weight:700;} 2526.bvr-bsrow.tot .bvr-bsv{font-weight:700;} 2527.bvr-bsrow.memo{opacity:0.72; font-size:11px; padding:1.5px 0;} 2528.bvr-bsrow.memo .bvr-bsv{font-weight:500;} 2529.bvr-bsnotes-t{display:block; width:100%; text-align:left; background:none; border:none; padding:8px 0 2px; 2530 color:var(--faint); font-size:10.5px; font-weight:600; font-family:inherit; cursor:pointer;} 2531.bvr-bsnotes-t:hover{color:var(--accent);} 2532.bvr-bsnotes{font-size:10.5px; line-height:1.45; color:var(--faint);} 2533.bvr-bsnotes p{margin:4px 0;} 2534.bvr-eyebrow{display:inline-flex; align-items:center; gap:4px;} 2535.bvr-lgswwrap{display:inline-flex; align-items:center; gap:6px;} 2536/* Panel */ 2537/* Default spawn position: left/top var-driven so a production wrapper with a site header 2538 above the visualizer doesn't hide the panel behind it. Once dragged, the user's coords 2539 (sliderPos) win via inline style, and clampSliderPos keeps them on-screen. */ 2540.bvr-slpanel{position:fixed; left:18px; top:calc(var(--sticky-top) + 8px); z-index:110; width:250px; background:var(--card); 2541 border:0.5px solid var(--line2); border-radius:12px; box-shadow:0 14px 40px rgba(15,23,30,.2); padding:0 12px 10px;} 2542/* First-open hint: one gentle ~10px circular orbit with a lifted shadow, telegraphing that 2543 the panel is draggable. transform-based so it never disturbs layout; plays once. */ 2544.bvr-slwiggle{animation:bvrSlOrbit 1.15s ease-in-out;} 2545@keyframes bvrSlOrbit{ 2546 0%{transform:translate(0,0); box-shadow:0 14px 40px rgba(15,23,30,.2);} 2547 15%{transform:translate(9px,-3px); box-shadow:0 22px 54px rgba(15,23,30,.30);} 2548 40%{transform:translate(3px,9px); box-shadow:0 26px 60px rgba(15,23,30,.32);} 2549 65%{transform:translate(-8px,4px); box-shadow:0 22px 54px rgba(15,23,30,.30);} 2550 85%{transform:translate(-2px,-8px); box-shadow:0 18px 46px rgba(15,23,30,.26);} 2551 100%{transform:translate(0,0); box-shadow:0 14px 40px rgba(15,23,30,.2);}} 2552.bvr-slhead{display:flex; align-items:center; justify-content:space-between; gap:8px; padding:9px 0 7px; 2553 cursor:grab; user-select:none; touch-action:none; border-bottom:0.5px solid var(--line2); margin-bottom:9px;} 2554.bvr-slhead:active{cursor:grabbing;} 2555.bvr-slheadt{display:inline-flex; align-items:center; gap:6px; font-size:11.5px; font-weight:700; color:var(--ink); white-space:nowrap;} 2556/* Grip glyph in the drag header reads as a HANDLE: muted ink, and it inherits the header's grab cursor. */ 2557.bvr-slgrip{color:var(--faint); flex:none;} 2558.bvr-slheadr{display:inline-flex; align-items:center; gap:4px;} 2559/* + Add slider: small, quiet, its own tap target. Lives BOTTOM-LEFT in .bvr-slfoot 2560 (moved out of the drag header so the footer pairs it with the auto-solve pill). */ 2561.bvr-sladd{display:inline-flex; align-items:center; gap:4px; background:none;
2561 border:0.5px solid var(--line2); 2562 border-radius:7px; color:var(--muted); font-size:9.5px; font-weight:600; font-family:inherit; 2563 padding:3px 7px 3px 6px; cursor:pointer; white-space:nowrap; transition:color 120ms ease, border-color 120ms ease;} 2564.bvr-sladd:hover{color:var(--accent); border-color:var(--accent);} 2565.bvr-slclose{background:none; border:none; color:var(--faint); cursor:pointer; padding:2px; display:inline-flex;} 2566.bvr-slclose:hover{color:var(--ink);} 2567.bvr-slrow{margin-bottom:10px;} 2568.bvr-slmeta{display:flex; align-items:center; gap:7px; margin-bottom:4px;} 2569.bvr-slsel{flex:0 1 auto; min-width:0; font-size:11px; font-family:inherit; color:var(--ink); 2570 border:0.5px solid var(--line2); border-radius:6px; background:var(--card); padding:2px 3px; max-width:118px;} 2571.bvr-slval{margin-left:auto; font-size:11.5px; font-weight:700; color:var(--ink); display:inline-flex; align-items:center; gap:4px; white-space:nowrap;} 2572/* Tax-bracket readout: only the first number (the top rate) stays bold; ord/ltcg pair is lighter. 2573 gap:0 so the flex gap doesn't push a space between the bold "35%" and its trailing colon. */ 2574.bvr-slvaltax{font-weight:500; gap:0;} 2575.bvr-slvaltax b{font-weight:700;} 2576/* Tap-to-edit value input: invisible until focus (no border/bg), right-aligned, sized to 2577 content so it sits exactly where the static readout did. */ 2578.bvr-slvin{width:82px; text-align:right; font:inherit; font-size:11.5px; font-weight:700; color:var(--ink); 2579 border:0.5px solid transparent; border-radius:5px; background:none; padding:1px 3px; font-family:inherit;} 2580.bvr-slvin:hover{border-color:var(--line2);} 2581.bvr-slvin:focus{border-color:var(--accent); background:var(--card); outline:none;} 2582.bvr-slrm{background:none; border:none; color:var(--faint); cursor:pointer; padding:1px; display:inline-flex;} 2583.bvr-slrm:hover{color:var(--neg);} 2584.bvr-slrange{width:100%; accent-color:var(--accent); margin:0;} 2585.bvr-slticks{display:flex; justify-content:space-between; font-size:9px; color:var(--faint); margin-top:1px;} 2586/* Footer: Add-slider button bottom-LEFT, the auto-solve pill RIGHT (margin-left:auto keeps 2587 it right-flushed even when the Add button retires at 3 sliders). Pill is compact â tight 2588 padding + abbreviated dollars â so both fit one row at the panel's 250px. */ 2589.bvr-slfoot{display:flex; align-items:center; justify-content:space-between; gap:8px; 2590 border-top:0.5px solid var(--line2); margin-top:2px; padding-top:8px;} 2591.bvr-slfootpill{display:inline-flex; align-items:baseline; gap:4px; max-width:100%; min-width:0; 2592 padding:2px 8px; margin-left:auto; 2593 flex-shrink:1;} /* override .bvr-autopill's flex-shrink:0 â beside the Add button the pill must 2594 YIELD (the name span ellipsizes) instead of overflowing the 250px panel */ 2595.bvr-slfootpre{flex:none; white-space:nowrap; font-size:8px; letter-spacing:.05em;} 2596.bvr-slfootname{flex:0 1 auto; text-transform:none; letter-spacing:normal; font-weight:600; font-size:10px; 2597 overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0;} 2598.bvr-slfootdot{flex:none; color:color-mix(in srgb, var(--accent) 55%, transparent); font-weight:700;} 2599.bvr-slfootv{flex:none; text-transform:none; letter-spacing:normal; font-size:10.5px; font-weight:700; color:var(--ink);} 2600.bvr-autopill{font-size:8.5px; font-weight:700; letter-spacing:.05em; text-transform:uppercase; color:var(--accent); 2601 background:var(--accentBg); border:0.5px solid var(--accent); border-radius:999px; padding:1px 6px; flex-shrink:0;} 2602.bvr-autotag{font-style:normal; font-size:8px; color:var(--accent); font-weight:700; text-transform:uppercase;} 2603.bvr-flash{animation:bvrflash 1.2s ease-out; border-radius:7px; display:inline-block;} 2604@keyframes bvrflash{0%{background:var(--accentBg); box-shadow:0 0 0 2px var(--accent);}100%{background:transparent; box-shadow:0 0 0 2px transparent;}} 2605/* tax presets: region segment + federal bracket list */ 2606.bvr-regionseg{display:flex; flex-wrap:wrap; gap:4px; margin-bottom:9px;} 2607.bvr-regionbtn{flex:0 1 auto; padding:6px 11px; font-size:10.5px; border:0.5px solid var(--line2); border-radius:7px; background:var(--card); 2608 color:var(--muted); cursor:pointer; white-space:nowrap;} 2609.bvr-regionbtn:hover{border-color:var(--accent); color:var(--accent);} 2610.bvr-regionbtn.on{background:var(--accentBg); border-color:var(--accent); color:var(--accent); font-weight:600;} 2611.bvr-fedlist{display:flex; flex-direction:column; gap:3px;} 2612.bvr-fedhead{display:grid; grid-template-columns:16px 34px 1fr auto; align-items:center; gap:8px; padding:2px 9px 5px; 2613 font-size:8.5px; text-transform:uppercase; letter-spacing:.05em; color:var(--faint);} 2614.bvr-fedhead .bvr-fedr{font-size:8.5px; font-weight:600;} 2615.bvr-fedhead .bvr-fedheadbracket{grid-column:1 / 3;} 2616.bvr-fedhead .bvr-fedthr,.bvr-fedhead .bvr-fedres{font-size:8.5px; color:var(--faint);} 2617.bvr-fedrow{display:grid; grid-template-columns:16px 34px 1fr auto; align-items:center; gap:8px; width:100%; 2618 padding:7px 9px;
2618 border:0.5px solid var(--line2); border-radius:7px; background:var(--card); cursor:pointer; text-align:left;} 2619.bvr-fedrow:hover{border-color:var(--accent);} 2620.bvr-fedrow.on{border-color:var(--accent); background:var(--accentBg);} 2621.bvr-fedmark{display:inline-flex; align-items:center; justify-content:center; color:var(--accent); width:16px;} 2622.bvr-fedr{font-size:12px; font-weight:700; color:var(--ink);} 2623.bvr-fedrow.on .bvr-fedr{color:var(--accent);} 2624.bvr-fedthr{font-size:10px; color:var(--faint); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;} 2625.bvr-fedres{font-size:11px; color:var(--muted); white-space:nowrap; text-align:right;} 2626.bvr-fedrow.on .bvr-fedres{color:var(--accent); font-weight:600;} 2627.bvr-fedslash{color:var(--faint);} 2628/* AMT preset row â a "tax regime" alternative, sits below the bracket ladder. Extra top margin 2629 separates it visually from the graduated brackets; the label is italic to signal "not a bracket". */ 2630.bvr-fedrow-amt{margin-top:6px;} 2631.bvr-fedthr-amt{font-style:italic; font-size:10.5px; color:var(--muted);} 2632.bvr-rateout{margin-top:11px; padding-top:9px; border-top:0.5px solid var(--line); display:flex; flex-direction:column; gap:6px;} 2633.bvr-rateoutrow{display:flex; align-items:center; justify-content:space-between; gap:10px;} 2634.bvr-rateoutlab{font-size:11.5px; color:var(--muted);} 2635.bvr-fundstat{font-size:10px; color:var(--muted); margin:0 0 8px; cursor:pointer; 2636 text-decoration:underline; text-decoration-style:dotted; text-underline-offset:3px; text-decoration-color:var(--faint);} 2637.bvr-fundstat:hover{color:var(--accent);} 2638.bvr-fundstat.warn{color:var(--neg); font-weight:600; text-decoration-color:var(--neg);} 2639.bvr-loanchip{display:inline-flex; align-items:center; gap:5px; font-size:11px; font-weight:600; color:var(--ink); 2640 background:var(--bg); border:0.5px solid var(--line); border-radius:999px; padding:5px 10px; cursor:pointer;} 2641.bvr-loanchip b{font-weight:600; color:var(--accent);} 2642.bvr-loanchip .bvr-chiplock{color:var(--faint); flex:0 0 auto;} 2643.bvr-loanchip:hover{border-color:var(--accent);} 2644.bvr-autopill.sm{font-size:7.5px; padding:0px 4px; margin-left:5px;} 2645/* collapsed Financing summary â active values as line items */ 2646.bvr-finsum{border:0.5px solid var(--accent); border-top:none; border-radius:0 0 9px 9px; background:var(--card); padding:8px 11px;} 2647.bvr-finsumrow{display:flex; align-items:center; justify-content:space-between; gap:10px; padding:3px 0; font-size:12px; color:var(--muted);} 2648.bvr-finsumrow + .bvr-finsumrow{border-top:0.5px solid var(--line);} 2649.bvr-finsumrow.clickable{cursor:pointer;} 2650.bvr-finsumrow.clickable:hover{color:var(--accent);} 2651.bvr-finsumrow.clickable:hover b{color:var(--accent);} 2652/* Labels truncate with ellipsis (never wrap) â combined with the row's flexbox layout, the 2653 value stays fixed-width at auto and the label absorbs the rest with min-width:0 so overflow 2654 collapses to ⦠rather than pushing the value off. */ 2655.bvr-finsumlab{display:inline-flex; align-items:center; color:var(--ink); 2656 min-width:0; flex:1 1 auto; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;} 2657.bvr-finsumlock{color:var(--faint); margin-left:5px;} 2658.bvr-finsumlockbtn{background:none; border:none; padding:0; margin-left:5px; cursor:pointer; color:var(--faint); display:inline-flex;} 2659.bvr-finsumlockbtn:hover{color:var(--accent);} 2660.bvr-finsumrow b{color:var(--ink); font-weight:600; flex-shrink:0;} 2661/* Value group pinned to the right, badge (lock/auto) immediately LEFT of the value. This is 2662 the fix for the badge being displaced/hidden by long labels: the badge now lives with the 2663 value in a non-shrinking right group, so the LABEL truncates (ellipsis) instead of ever 2664 pushing the badge out of view. */ 2665.bvr-finsumval{display:inline-flex; align-items:center; gap:5px; flex-shrink:0; margin-left:8px; position:relative;} 2666.bvr-finsumval .bvr-finsumlockbtn,.bvr-finsumval .bvr-autopill{margin-left:0;} 2667/* Lock hover: 1.5à scale + brighten via transform on the ICON only â transforms never 2668 affect layout, so neither the label nor the value can be displaced. Applies to every lock 2669 in the app (collapsed rows, expanded fields, popovers). */ 2670.bvr-finsumlockbtn svg,.bvr-lockbtn svg{transition:transform 140ms ease, color 140ms ease, opacity 140ms ease; transform-origin:center;} 2671.bvr-finsumlockbtn:hover svg,.bvr-lockbtn:hover svg{transform:scale(1.5); color:var(--accent);} 2672/* Lock morph: the button carries BOTH icons stacked; hover shows the OPEN one (which is 2673 exactly what a tap will do to the field), rest state shows the CLOSED one. The two glyphs 2674 share the same 9à 9 slot so nothing shifts. Scoped to .bvr-lockmorph so lone Lock icons 2675 elsewhere in the app are unaffected. */ 2676.bvr-lockmorph{position:relative; display:inline-flex; align-items:center; justify-content:center; width:11px; height:11px;} 2677.bvr-lockmorph .lm-open{position:absolute; opacity:0;} 2678.bvr-lockmorph .lm-closed{opacity:1;} 2679.bvr-lockmorph:hover .lm-closed{opacity:0;} 2680.bvr-lockmorph:hover .lm-open{opacity:1;}
2681/* Zero-out Ã: absolutely hung LEFT of the value group (right:100%), so its appearance 2682 displaces nothing. Visible only while hovering a LOCKED collapsed row. */ 2683.bvr-fsx{position:absolute; right:calc(100% + 3px); top:50%; transform:translateY(-50%); 2684 display:inline-flex; align-items:center; justify-content:center; width:16px; height:16px; 2685 border-radius:999px; border:0.5px solid var(--line2); background:var(--card); color:var(--neg); 2686 padding:0; cursor:pointer; opacity:0; transition:opacity 140ms ease; pointer-events:none;} 2687.bvr-finsumval:hover .bvr-fsx,.bvr-finsumrow:hover .bvr-fsx{opacity:1; pointer-events:auto;} 2688.bvr-fsx:hover{border-color:#E23B2E; background:#FDECEA; color:#E23B2E;} 2689/* 5s auto-solve spotlight: two gentle pulses, then a long fade â unmistakable but calm. */ 2690.bvr-autoglow{animation:bvrGlow 5s ease-out; border-radius:5px;} 2691@keyframes bvrGlow{ 2692 0%{background:color-mix(in srgb, var(--accent) 22%, transparent); box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);} 2693 20%{background:color-mix(in srgb, var(--accent) 8%, transparent); box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 8%, transparent);} 2694 40%{background:color-mix(in srgb, var(--accent) 20%, transparent); box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);} 2695 100%{background:transparent; box-shadow:0 0 0 3px transparent;}} 2696 2697/* ----------------------------------------------------------------------------- 2698 DARK â the site theme (the dark class on html, set only by lib/siteTheme; 2699 the page's sun button at the bottom flips it). Until 2026-10 this tool 2700 was light-only: a dark visit got a cream card on the dark page. The 2701 color-roles note at the top of this sheet planned for this: same 2702 semantics, dimmer surfaces, roles nudged lighter so they read on them, 2703 and everything that reads var(--role) follows. Standalone builds have no 2704 such ancestor and stay light. 2705 - Surfaces: the tool ground sits a step above the page, panels a step 2706 above that (elevation reads lighter in the dark, as the app's cards). 2707 - Roles are lifted only as far as white in-bar labels still read on them 2708 (the burn segments and the rent bar carry white text). 2709 ----------------------------------------------------------------------------- */ 2710.dark .bvr-root{ 2711 --bg:#141C26; --card:#1B2532; --ink:#E6EDF3; --muted:#A3B0BB; --faint:#7D8B97; 2712 --line:#26313E; --line2:#34414F; 2713 --buy:#4A86CC; --prin:#8B80CF; 2714 --rent:#C8691F; 2715 --credit:#25A574; 2716 --accent:#27A3AE; 2717 --accentBg:#163238; 2718 --neg:#D9705F; 2719 color-scheme:dark; 2720} 2721.dark .bvr-root .bvr-amtx:hover, 2722.dark .bvr-root .bvr-fsx:hover{color:#F07467; background:#3A1D1C; border-color:#F07467;} 2723.dark .bvr-root .ccx:hover{background:#3A1D1C;} 2724`,Qi="buyVsRent.visitCount.v1",el=()=>{if(typeof window>"u")return 0;try{const c=window.localStorage.getItem(Qi);if(!c)return 0;const w=Number.parseInt(c,10);return Number.isFinite(w)&&w>=0?w:0}catch{return 0}};let Xi=el();const Qc=new Set,ed=()=>{Qc.forEach(c=>c())};function td(){const w=Math.max(Xi,el())+1;Xi=w;try{typeof window<"u"&&window.localStorage.setItem(Qi,String(w))}catch{}return ed(),w}const rd=()=>(d.useEffect(()=>{const c=typeof window<"u"?window.location.hostname:"",x=Lc(c).key==="BUYVSRENT101"?"BuyVsRent101.com":"ImpactAdvisor",v=`Buy vs. Rent Calculator | ${x}`;document.title=v;const y=(A,q,le=!1)=>{const Z=le?"name":"property";let ue=document.querySelector(`meta[${Z}="${A}"]`);ue||(ue=document.createElement("meta"),ue.setAttribute(Z,A),document.head.appendChild(ue)),ue.setAttribute("content",q)};y("og:title",v),y("og:description","Compare the long-run after-tax net worth of buying a home versus renting and investing the difference â mortgage, taxes, upkeep, appreciation, and returns."),y("og:type","website"),y("og:site_name",x),y("twitter:card","summary_large_image",!0),y("twitter:title",v,!0),y("twitter:description","See when buying beats renting: after-tax net worth year by year, accounting for the down payment, upkeep, appreciation, and investing the difference.",!0),y("description","Interactive buy vs. rent calculator â model mortgage, property tax, maintenance, appreciation, rent growth, and investment returns to see which builds more wealth.",!0)},[]),null),ad=new Set(["home_price","tax_rate","debt_change"]),nd=new Set(["export_xlsx"]),sd=1500;function od(){d.useEffect(()=>{const c=new Map,w=x=>
2724{const v=x.detail,y=v==null?void 0:v.action;if(!y)return;const A=(v==null?void 0:v.value)??"",q=(Z,ue)=>`${window.location.pathname}?bvr=${encodeURIComponent(Z)}:${encodeURIComponent(ue)}`;if(nd.has(y)){Vn(Ga.downloadModel,{force:!0,link:q(y,A)});return}const le=c.get(y);le&&clearTimeout(le),c.set(y,setTimeout(()=>{c.delete(y);const Z=ad.has(y)?Ga.inputChanged:Ga.presetSelected;Vn(Z,{link:q(y,A)})},sd))};return window.addEventListener("bvr-track",w),()=>{window.removeEventListener("bvr-track",w),c.forEach(x=>clearTimeout(x)),c.clear()}},[])}function id(){d.useEffect(()=>{const c=w=>{const x=w.detail,v=String((x==null?void 0:x.email)||"").trim();!v||(x==null?void 0:x.consent)!==!0||(fetch("/emails/freetax/mailchimp",{method:"POST",headers:{"Content-Type":"application/json"},keepalive:!0,body:JSON.stringify({email:v})}).catch(()=>{}),Vn(Ga.userFullSubscribe,{force:!0,link:`${window.location.pathname}?bvr=subscribe:${x!=null&&x.review?"wants-review":"updates"}`,invitee:{email:v}}))};return window.addEventListener("bvr-subscribe",c),()=>window.removeEventListener("bvr-subscribe",c)},[])}function hd(){od(),id();const c=Mc();return d.useEffect(()=>{const w=td();Vn(Ga.pageVisit,{link:window.location.href,extra:{results:`BuyVsRent visits: ${w}`}})},[]),e.jsxs("div",{className:"min-h-screen bg-background text-foreground",children:[e.jsx(rd,{}),e.jsx(Ec,{titlePrefix:"Buy vs. Rent",mode:"buyvsrent",stickyFade:!0,backHref:"/menu",pageIcon:e.jsx(Ac,{className:"h-7 w-7 sm:h-8 sm:w-8"}),pageScrollWordmark:e.jsx(Rc,{fontSize:20})}),e.jsx("main",{className:"mx-auto max-w-6xl px-4 py-6",children:e.jsx(Jc,{})}),e.jsx("p",{className:"text-center text-xs text-muted-foreground/70 pt-1","data-testid":"text-usage-count",children:c.toLocaleString("en-US")}),e.jsx(zc,{transparent:!0}),e.jsx(Pc,{themeToggle:!0})]})}export{hd as default};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.