PageSourceSearch

https://freetaxcalc.com/assets/BuyVsRent-jSXXu3qi.js

js freetaxcalc.com collected 2026-10-07 22:29:24 UTC 437,783 bytes, 2,724 lines download raw bytes

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&&gt("sticky_sliders",xt?"open":"close")},[xt]),d.useEffect(()=>{gr.current&&gt("emoji_swap",it?"summary":"detailed")},[it]),d.useEffect(()=>{gr.current&&gt("home_price",G)},[G]),d.useEffect(()=>{gr.current&&gt("debt_change",te.length)},[te]),d.useEffect(()=>{gr.current&&gt("multifamily",X?"on":"off")},[X]),d.useEffect(()=>{gr.current&&gt("spine_expand",Or?"open":"close")},[Or]),d.useEffect(()=>{gr.current&&gt("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&&lt&&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&&lt.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 '&gt;' 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.