// Contributor Article — block-based renderer (Johnnie / Isabella)
// window.ARTICLE picks the slug from ARTICLE_META.

const ARTICLE_META = {
  "rental-property": {
    lane: "personal",
    path: "/personal/investing/advanced-investing/how-a-rental-property-actually-fits-into-a-personal-financial-plan/",
    breadcrumb: ["PERSONAL", "INVESTING", "ADVANCED"],
    breadcrumbHrefs: [
      "/personal/",
      "/personal/investing/",
      "/personal/investing/advanced-investing/",
    ],
    kicker: "THE JOURNAL · PERSONAL · FEATURED",
    title: "How a Rental Property <em>Actually Fits</em> Into a Personal Financial Plan",
    sub: "Most people think about the house first and the math last. It should be the other way around.",
    date: "APR 23, 2026",
    read: "10 MIN",
    category: "Investing",
    author: "Johnnie",
    authorInitial: "J",
    authorBio: "Johnnie spent a decade on a retail trading desk before walking away to write for people who were never meant to read a 10-K. He answers the money questions you're a little embarrassed to ask.",
    cover: true,
    coverAlt: "Keys and a folder of papers on a kitchen table, porch light on outside",
    coverCaption: "The house is the easy part. The math is where people get in trouble.",
    blocks: [
      { type: "p", text: "A client came to me about six months ago with a house he wanted to buy. He had done his homework in the way most people do their homework — he had driven by it three times, he had looked at comparable rents on Zillow, and he had a friend who was a real estate agent who told him it was a good deal." },
      { type: "p", text: "He wanted to know if he should buy it." },
      { type: "p", text: "I asked him a few questions. How much cash did he have available. What his current savings rate was. What would happen to his monthly budget if the house sat vacant for two months. How long he was planning to hold it. Whether he had ever been a landlord before." },
      { type: "p", text: "He answered the first two quickly. He slowed down on the third. He had not really thought about the fourth — he was assuming the rental market would hold. The fifth he answered honestly, which was no, he had not, but how hard could it be." },
      { type: "p", text: "I told him that the house was probably a fine house and the deal was probably a fine deal. But he was about to make one of the biggest financial decisions of his life based on three drive-bys and a friend's opinion, and if we spent twenty minutes running the actual numbers first, he would either buy it with more confidence or he would not buy it at all. Both of those outcomes are good outcomes." },
      { type: "p", text: "We spent twenty minutes. He bought it. He is still happy." },
      { type: "p", text: "That is the whole pitch of this article. Run the numbers before you commit. The numbers are not hard. They are just the part everyone skips." },

      { type: "h2", text: "What a rental actually is, in <em>personal-finance terms</em>" },
      { type: "p", text: "A rental property is not just a house. It is three separate things happening at once, and you need to understand all three before you buy one." },
      { type: "p", text: "It is a **cash flow instrument.** Each month, rent comes in and expenses go out. The difference is either positive or negative. Most people assume it will be positive because the rent is higher than the mortgage. They forget that the mortgage is not the only expense. Vacancy. Repairs. Maintenance. Property management if you use one. Insurance. Property tax. HOA fees if applicable. Capital expenditures — the roof, the HVAC, the water heater. None of those are in the mortgage payment. All of them are real." },
      { type: "p", text: "It is an **appreciation bet.** You are betting that the property will be worth more in ten years than it is now. That is historically a decent bet. It is not a guaranteed bet. And if the cash flow math is marginal, you are depending on the appreciation to make the deal work — which means you are timing the market, which is something no one is good at." },
      { type: "p", text: "It is a **tax instrument.** Rental properties get depreciation, which is a real tax benefit. They also get operating-expense deductions. They generate K-1 or Schedule E income that behaves differently than W-2 income. If you do this right, the tax treatment can meaningfully boost your after-tax return. If you do it wrong, you can end up owing more than you expected at year end." },
      { type: "p", text: "Those three things are always happening at the same time. The mistake is looking at only one of them and ignoring the others. People who get burned on rentals almost always ignored the cash flow — they bought a property that was losing money every month and told themselves it would be fine because the house would appreciate." },

      { type: "pullquote", text: "People who get burned on rentals almost always ignored the cash flow. They told themselves appreciation would save them.", attr: "The quiet killer" },

      { type: "h2", text: "The numbers that <em>actually matter</em>" },
      { type: "p", text: "Before you buy a rental, you need to know these numbers. Not roughly. Specifically." },
      { type: "p", text: "**Purchase price.** Obvious but worth stating. Include closing costs." },
      { type: "p", text: "**Down payment and financing.** If you are financing, your down payment is usually 20-25% for an investment property, with an interest rate roughly one point higher than a primary residence rate. That matters — a lot of new investors use their primary-residence rate in their math by accident and then wonder why the numbers do not work." },
      { type: "p", text: "**Gross monthly rent.** Not what Zillow says. What the actual market rent is for a property like yours in that neighborhood. Talk to a property manager. Look at current leases, not last year's listings." },
      { type: "p", text: "**All monthly expenses.** This is where people get in trouble. Write them all out. Mortgage (principal and interest). Property tax divided by twelve. Insurance divided by twelve. HOA if applicable. A vacancy reserve — budget 5-8% of rent for months the unit sits empty. A repair reserve — budget another 5-8% for ongoing maintenance. Capital expenditure reserve for the big-ticket replacements over time — another 5-8%. Property management if you are using one, typically 8-10% of rent." },
      { type: "p", text: "**Monthly cash flow.** Gross rent minus all expenses. This needs to be positive at today's numbers, not positive only after the rent goes up in year three." },
      { type: "p", text: "**Cash-on-cash return.** Annual cash flow divided by total cash invested (down payment plus closing costs plus any initial repairs). This is the real return on your money in year one. A decent cash-on-cash return on a rental is usually 6-10% depending on the market. Below 4%, you are basically betting on appreciation." },
      { type: "p", text: "**Cap rate.** Annual net operating income divided by property value. A market comparison number — tells you whether the property is priced reasonably for the income it produces relative to other properties in the area." },

      { type: "numbers", title: "The seven you must know before making an offer", rows: [
        ["DOWN", "20–25%", "investment property standard"],
        ["RATE", "+1.00%", "over primary residence"],
        ["VACANCY", "5–8%", "of rent, reserved monthly"],
        ["REPAIRS", "5–8%", "ongoing maintenance buffer"],
        ["CAPEX", "5–8%", "roof, HVAC, water heater reserve"],
        ["MGMT", "8–10%", "of rent if using a PM"],
        ["C-O-C", "6–10%", "healthy year-one return"],
      ]},
      { type: "p", text: "If you do not know all seven of those numbers before you make an offer, you are guessing." },

      { type: "h2", text: "Where the math <em>goes wrong</em> most often" },
      { type: "p", text: "Three places, in order of how often I see them." },
      { type: "p", text: "**Underestimating expenses.** Most new investors calculate \"mortgage payment plus taxes and insurance\" and call it a day. That is about 60% of the real expense picture. The other 40% — vacancy, repairs, capex, management — is the stuff that eats the cash flow. If you do not budget for it, it will find you anyway. The water heater does not care that you did not plan for it." },
      { type: "p", text: "**Using today's rent to justify tomorrow's price.** Real estate markets move. Rents move too. If you are buying at a peak and the rent softens by 10%, your cash flow can go from positive to negative in one tenant turnover. Run your numbers with a rent that is 10% below market as a stress test. If the deal still works, good. If it does not, you are buying on the assumption that nothing ever changes, which is never how anything works." },
      { type: "p", text: "**Ignoring opportunity cost.** The $80,000 you are putting down on this rental could be doing other things. If it sits in an S&P 500 index fund earning the long-term average return, it is growing. The rental has to beat that — after taxes, after time, after the weekend you spent dealing with a tenant's broken dishwasher — to have been the right choice. Sometimes it does. Sometimes it does not. You should know which before you write the check." },

      { type: "sidebar", title: "Run the numbers before you commit", body: [
        "This is the part where most people need a tool instead of a spreadsheet. DoorBase is built for this exact moment — the one where you are standing in front of a property trying to figure out whether the math works before you make an offer.",
        "It handles the full picture. Purchase price, financing, rent, expenses, vacancy, repairs, capital reserves, management. Cash flow, cash-on-cash, cap rate, all of it. It keeps track of your properties once you buy them, too — the whole investor interface lives in the app, not just a single-deal calculator.",
        "If you are seriously looking at your first rental, run the numbers in there first. It will either confirm what you thought or it will show you something you missed. Both outcomes are useful.",
      ]},
      { type: "nativeCta", kicker: "PARTNER · DOORBASE", text: "Start at doorbase.app", sub: "Run the full rental math — not just mortgage, taxes, insurance.", href: "https://doorbase.app" },

      { type: "h2", text: "When a rental <em>belongs in your plan</em>" },
      { type: "p", text: "Not every person should own a rental property. The people for whom rentals work usually have four things in common." },
      { type: "p", text: "**A stable primary financial picture.** If you are still trying to pay off credit cards, if your emergency fund is thin, if your retirement accounts are not funded — do not buy a rental. Fix the primary picture first. A rental is a secondary structure that sits on top of a healthy primary structure. If the foundation is weak, the second story will not help you." },
      { type: "p", text: "**Real cash reserves, specifically for the property.** Not your emergency fund. A separate pile of cash, ideally three to six months of the property's total expenses, for the months things go wrong. Because things will go wrong. Tenants will leave without notice. Water heaters will fail at the worst possible time. If you do not have a buffer for that, one bad month becomes a crisis." },
      { type: "p", text: "**A long time horizon.** Rentals are not liquid. You cannot sell on Tuesday if you need cash on Wednesday. If your horizon is less than seven to ten years, the transaction costs alone (closing, commissions, repairs for sale) will eat most of your return. Rentals work when you hold them." },
      { type: "p", text: "**Tolerance for being a landlord — or willingness to pay someone who does.** This is the one people understate. Being a landlord is a part-time job some months. You are going to get calls. Tenants will do things you did not expect. The toilet will overflow at 11pm on a Tuesday. You either have to be the kind of person who handles that, or you have to pay a property manager 8-10% of your rent to handle it for you. Budget for the property manager. Most people who try to self-manage their first rental do not enjoy it." },

      { type: "h2", text: "When it <em>doesn't belong</em>" },
      { type: "p", text: "If any of the following are true, the rental is probably not the right move right now." },
      { type: "checklist", title: "Probably not the right time", items: [
        "You don't have 6 months of personal expenses in an emergency fund",
        "You're carrying credit card debt",
        "Your retirement accounts aren't being funded consistently",
        "You'd need to drain savings to come up with the down payment",
        "You have less than 3 months of the property's expenses as a separate reserve",
        "Your holding horizon is less than 7 years",
        "You cannot handle a middle-of-the-night call without it ruining your week",
      ]},
      { type: "p", text: "None of these are permanent. Most of them you can fix in a year or two. The right move is to fix the primary picture first and buy the rental from a position of strength, not buy the rental and hope the rest works out." },

      { type: "h2", text: "The <em>through-line</em>" },
      { type: "p", text: "The rental is a financial instrument. Treat it like one." },
      { type: "p", text: "The people who do well with rentals are not the ones with the most properties. They are the ones who ran the numbers honestly, bought the property from a position of financial strength, held it long enough for the math to work, and did not treat every bump as a catastrophe. That is the whole game." },
      { type: "p", text: "The people who get burned are the ones who let the story — *\"real estate always goes up,\" \"you should own property,\" \"this is how you build wealth\"* — override the math. The story might be right. But it will not be right for every property at every price in every market. That is what the math is for." },
      { type: "p", text: "Before you buy the house, run the numbers. If they work, buy the house. If they do not, do not. Both are fine. The mistake is skipping the step." },

      { type: "disclaimer", text: "This is not financial advice. Talk to a qualified CPA or financial advisor about your specific situation." },
      { type: "signoff", text: "Johnnie / April 2026" },
    ],
  },

  "expired-certificate": {
    lane: "business",
    path: "/business/compliance/reporting/what-an-expired-certificate-actually-costs-you/",
    breadcrumb: ["BUSINESS", "COMPLIANCE", "REPORTING"],
    breadcrumbHrefs: [
      "/business/",
      "/business/compliance/",
      "/business/compliance/reporting/",
    ],
    kicker: "THE JOURNAL · BUSINESS · FEATURED",
    title: "What an Expired Certificate <em>Actually Costs</em> You",
    sub: "The document did not cost you money. The expiration did. And if you are running a business without knowing which of yours are current, you are operating on borrowed time.",
    date: "APR 23, 2026",
    read: "11 MIN",
    category: "Operations",
    author: "Isabella",
    authorInitial: "I",
    authorBio: "Isabella has opened three businesses, closed one, and wrote the financial operating manuals for the other two. She's the person founders text at 11pm when the books don't tie.",
    cover: true,
    coverAlt: "Contractor's clipboard with a certificate of insurance, calendar behind, morning jobsite light",
    coverCaption: "The certificate is not the point. Knowing it's current is.",
    blocks: [
      { type: "p", text: "A general contractor I know was three weeks into a $340,000 commercial build when the GC on the project asked him for an updated certificate of insurance. The old one had expired on a Wednesday. Nobody had noticed. The new policy had been bound the following Monday — there was a five-day gap." },
      { type: "p", text: "Five days of work on that jobsite were uninsured. If anyone had been hurt, if anything had been damaged, if the property owner's attorneys had wanted to make an issue of it, that five-day window would have been the entire conversation. No coverage. Personal liability. Potentially personal assets." },
      { type: "p", text: "Nothing happened. He got lucky. He got the new cert over to the GC, finished the job, got paid. He also called me that week and said the thing everyone says the first time they get close to a cliff they did not see: *I did not know.*" },
      { type: "p", text: "That is the real cost of an expired document. It is not the lapse itself. It is that you did not know it was lapsed. You were operating — signing contracts, taking calls, putting people on jobsites — on paperwork that had already expired, and no one told you." },
      { type: "p", text: "The document did not cost you money. The expiration did. And the gap between the two is the story." },

      { type: "h2", text: "The documents that <em>actually expire</em> on you" },
      { type: "p", text: "Most operators think of compliance as one thing. It is not. It is a rolling set of documents, each with its own expiration date, its own renewal process, and its own consequence if it lapses." },
      { type: "p", text: "**General liability insurance.** Annual. A lapsed GL policy means uninsured work. The client's certificate-of-insurance request will catch it — usually at the worst possible moment, like right before a major invoice gets released." },
      { type: "p", text: "**Workers' comp.** Annual, and in many states audited mid-year. A lapsed workers' comp policy with an injured employee is the fastest path to personal financial exposure that exists in a small trade business. Not a slow path. A fast one." },
      { type: "p", text: "**Commercial auto.** Annual. If a vehicle with a lapsed policy is in an accident on company time, you are personally responsible for everything the policy would have covered." },
      { type: "p", text: "**Contractor's license.** Usually two to four years depending on the state. Some require continuing education hours to renew. Letting one expire can mean every job you signed during the lapse is legally unenforceable. Yes, that means you might not be able to collect." },
      { type: "p", text: "**Bonds.** Annual or multi-year depending on the state and the type. An expired bond on a permit-required job is a shutdown waiting to happen." },
      { type: "p", text: "**Business licenses.** City, county, and state all potentially separate. Different renewal cycles. Different renewal fees. Each one a separate alarm clock." },
      { type: "p", text: "**Permits on active projects.** Not annual — project-specific, with expiration windows that depend on jurisdiction and project phase. A permit that expires mid-build is a stop-work order waiting for an inspector." },
      { type: "p", text: "**Inspection schedules.** Also project-specific. Miss a required inspection window and you are redoing work to make it visible again, at your cost, on your time." },
      { type: "p", text: "If you are running a small trade business, you probably have fifteen to thirty documents in that list at any given time, each with a different expiration date, and each with a real financial cost if it slips." },

      { type: "h2", text: "The part everyone <em>gets wrong</em>" },
      { type: "p", text: "Every operator I know tries, at some point, to track this themselves." },
      { type: "p", text: "They start with a spreadsheet. That works for a month. Then the spreadsheet becomes three spreadsheets — one for insurance, one for licenses, one for projects. Each one has different columns. None of them are consistently updated." },
      { type: "p", text: "They try calendar reminders. That works for a while. Then a policy renews on a different date than expected, or a license gets extended, or a permit gets pulled for a project that slips six weeks — and the calendar is wrong without them knowing it is wrong." },
      { type: "p", text: "They try to \"just remember.\" That works until it does not. And when it does not, the gap is not a day or two. It is usually weeks, because by the time something has lapsed long enough that the consequence catches up, the document has been out of date for a while." },
      { type: "p", text: "The common thread in all three approaches is the same: **you are trying to be the system.** You are the backup to the spreadsheet, the calendar, the memory. When you are busy — which is always — you are the weak link." },
      { type: "p", text: "Most people do not need a better spreadsheet. They need to stop being the system." },

      { type: "pullquote", text: "You are trying to be the system. When you are busy — which is always — you are the weak link.", attr: "The quiet truth" },

      { type: "h2", text: "What it actually costs <em>when something lapses</em>" },
      { type: "p", text: "The cost of a lapsed document comes in layers. Operators see the first layer and miss the rest." },
      { type: "p", text: "**Layer 1: The immediate fix.** New cert issued, license reinstated, permit pulled. A few hundred dollars in fees, maybe a thousand. This is the cost most people think about." },
      { type: "p", text: "**Layer 2: The work done during the lapse.** If you were operating without the right document, every day of that lapse is potential exposure. Uninsured work, unlicensed contracting, unpermitted construction. Depending on what happened during that window, this layer can be zero or it can be the whole business." },
      { type: "p", text: "**Layer 3: The client relationship.** A certificate request that you cannot fulfill immediately is a flag. It tells the client that you are not running a tight operation. On a big job, that matters. On a repeat client, it matters more. I have seen contractors lose preferred-vendor status over a single lapsed cert. That is years of future revenue, gone because a date slipped." },
      { type: "p", text: "**Layer 4: The audit exposure.** Some of these documents feed into tax filings, payroll filings, or regulatory reporting. A lapsed workers' comp policy, for example, does not just affect your current risk — it affects the classification audit at the end of the year. Getting that wrong can cascade into reclassifications, back payments, and penalties." },
      { type: "p", text: "**Layer 5: Personal liability.** This is the one people do not see until they are in it. The protections that come from having the right insurance and licensure are the same protections that keep your personal assets separate from your business assets. When those protections lapse, the wall between \"the business got sued\" and \"my house is at risk\" gets a lot thinner." },

      { type: "numbers", title: "The five layers of a lapsed-document cost", rows: [
        ["L1", "$100s–$1K", "immediate reinstatement fees"],
        ["L2", "VARIES", "exposure for work done during lapse"],
        ["L3", "YEARS", "future revenue from a dropped client"],
        ["L4", "COMPOUND", "payroll / tax reclassification penalties"],
        ["L5", "ALL OF IT", "personal-asset exposure"],
      ]},
      { type: "p", text: "The first layer is a few hundred dollars. The fifth layer can be everything you have built." },

      { type: "h2", text: "What <em>ReadyDocs</em> actually does" },
      { type: "p", text: "ReadyDocs is the system so you do not have to be." },
      { type: "p", text: "It tracks everything that expires. Insurance certificates, licenses, bonds, permits, inspection schedules — whatever has a date on it, it watches the date. When something is close to expiring, you get flagged before the expiration, not after." },
      { type: "p", text: "What makes it different from a spreadsheet or a calendar is Iris. Iris is the AI that actually knows your documents. You can text her. *\"Did we get that permit for the Ft. Lauderdale project.\"* She answers. *\"Yes, pulled on the 14th, expires July 2.\"*" },
      { type: "p", text: "That is the thing spreadsheets cannot do. The information is not just stored — it is retrievable the way you actually think about it, in the moments you actually need it, on the device you are already holding." },
      { type: "p", text: "It was built for contractors. It is not limited to contractors. Any business that runs on expiring documents — fleet operators, healthcare practices, property managers, food service — has the same problem and benefits from the same solution. The pattern is universal. The paperwork is the business." },

      { type: "sidebar", title: "Stop tracking it yourself", body: [
        "If you are running your compliance on a spreadsheet, calendar reminders, or your own memory — you are the weak link in your business. Not because you are not capable. Because you are busy, and the moment you are busiest is the moment something slips.",
        "ReadyDocs is the system. Iris is the interface. You text her what you need to know, you get flagged when something is about to expire, and the work of tracking this stuff stops living in your head.",
        "The first time you avoid a lapsed certificate on a big job, it will have paid for itself.",
      ]},
      { type: "nativeCta", kicker: "PARTNER · READYDOCS", text: "Start at thereadydocs.app", sub: "Let the system watch the dates. Text Iris when you need an answer.", href: "https://thereadydocs.app" },

      { type: "h2", text: "What the signal looks like <em>when you have the system</em>" },
      { type: "p", text: "The difference between operators who run tight compliance and operators who do not is not effort. It is infrastructure." },
      { type: "p", text: "The ones who run tight compliance do not spend more time on it. They spend less. Because the system is doing the watching, and they only engage when something actually needs their attention. The spreadsheet-and-memory crew spends hours a month on compliance, most of it reactive, most of it under time pressure, most of it inaccurate. The operators with real infrastructure spend minutes a month on compliance, almost all of it proactive." },
      { type: "p", text: "This is the pattern with every operational system that actually works. It pays for itself not by doing something expensive, but by making something that used to take time and attention stop taking time and attention. You get the time back. You get the mental space back. You stop running a low-grade hum of *did I renew that thing yet* in the back of your head while you are trying to run the business." },
      { type: "p", text: "My father would have loved this. He kept his receipts in a shoebox until 2011. He kept his licenses and bonds in a three-ring binder on a shelf in the garage. He renewed things when he remembered to or when somebody reminded him. He was a good contractor. The paperwork was the part that nearly broke him more than once. Not because the work was hard, but because the work was invisible and the consequences were not." },
      { type: "p", text: "The work is still invisible. The consequences are still real. The difference now is that you do not have to be the one holding it all together." },

      { type: "h2", text: "The <em>through-line</em>" },
      { type: "p", text: "The certificate is not the point. Knowing it is current is." },
      { type: "p", text: "Every expired document is a story about the gap between what you thought was true about your business and what was actually true. Close the gap. Stop being the system. Let the system be the system and go do the work only you can do." },
      { type: "p", text: "The people who run great small businesses do not do it by holding everything in their heads. They do it by building infrastructure that holds things for them, and then trusting it." },
      { type: "p", text: "Build the infrastructure. Go run your business." },

      { type: "disclaimer", text: "This is not financial advice. Talk to a qualified CPA, attorney, or insurance professional about your specific compliance situation." },
      { type: "signoff", text: "Isabella / April 2026" },
    ],
  },
};

// Inline text → React children. Supports **bold**, *italic*, and literal HTML <em>…</em>.
function renderInline(text) {
  if (!text) return null;
  const out = [];
  let keyId = 0;
  const push = (node) => out.push(typeof node === "string" ? node : React.cloneElement(node, { key: keyId++ }));
  // First, split off raw <em>…</em> spans (used in titles & h2 copy).
  const emSplit = String(text).split(/(<em>.*?<\/em>)/g);
  emSplit.forEach((chunk) => {
    const em = chunk.match(/^<em>(.*?)<\/em>$/);
    if (em) {
      push(<em>{em[1]}</em>);
      return;
    }
    // Now handle **bold** and *italic* inside plain text chunks.
    const re = /(\*\*[^*]+\*\*|\*[^*]+\*)/g;
    let last = 0; let m;
    while ((m = re.exec(chunk)) !== null) {
      if (m.index > last) push(chunk.slice(last, m.index));
      if (m[0].startsWith("**")) push(<strong>{m[0].slice(2, -2)}</strong>);
      else push(<em>{m[0].slice(1, -1)}</em>);
      last = re.lastIndex;
    }
    if (last < chunk.length) push(chunk.slice(last));
  });
  return out;
}

function Block({ b }) {
  switch (b.type) {
    case "p":
      return <p className="art-p">{renderInline(b.text)}</p>;
    case "h2":
      return <h2 className="art-h2">{renderInline(b.text)}</h2>;
    case "pullquote":
      return (
        <blockquote className="art-pull">
          <div className="art-pull__mark">"</div>
          <p>{b.text}</p>
          {b.attr && <span className="art-pull__attr">— {b.attr}</span>}
        </blockquote>
      );
    case "sidebar":
      return (
        <aside className="art-side">
          <div className="art-side__tag">SIDEBAR</div>
          <h3 className="art-side__h">{b.title}</h3>
          {b.body.map((p, i) => <p key={i}>{renderInline(p)}</p>)}
        </aside>
      );
    case "nativeCta":
      return (
        <a className="art-cta" href={b.href} target="_blank" rel="noopener noreferrer sponsored">
          <div>
            <div className="art-cta__k">{b.kicker || "PARTNER"}</div>
            <div className="art-cta__h">{b.text}</div>
            {b.sub && <div className="art-cta__sub">{b.sub}</div>}
          </div>
          <div className="art-cta__arr">→</div>
        </a>
      );
    case "checklist":
      return (
        <div className="art-check">
          <div className="art-check__tag">CHECKLIST</div>
          <h3 className="art-check__h">{b.title}</h3>
          <ol>
            {b.items.map((it, i) => (
              <li key={i}>
                <span className="art-check__n">{String(i + 1).padStart(2, "0")}</span>
                <span>{renderInline(it)}</span>
              </li>
            ))}
          </ol>
        </div>
      );
    case "numbers":
      return (
        <div className="art-nums">
          <div className="art-nums__tag">KEY FIGURES</div>
          {b.title && <h3 className="art-nums__h">{b.title}</h3>}
          <div className="art-nums__grid">
            {b.rows.map((r, i) => (
              <div key={i} className="art-nums__cell">
                <div className="art-nums__k">{r[0]}</div>
                <div className="art-nums__v">{r[1]}</div>
                <div className="art-nums__d">{r[2]}</div>
              </div>
            ))}
          </div>
        </div>
      );
    case "warning":
      return (
        <div className="art-warn">
          <span>CAUTION</span>
          <p>{renderInline(b.text)}</p>
        </div>
      );
    case "disclaimer":
      return (
        <div className="art-disc">
          <strong>Disclaimer —</strong> {b.text}
        </div>
      );
    case "signoff":
      return <div className="art-sign">— {b.text}</div>;
    default:
      return null;
  }
}

function ArticleProgress() {
  const [pct, setPct] = React.useState(0);
  React.useEffect(() => {
    const onScroll = () => {
      const body = document.querySelector(".art-body");
      if (!body) return;
      const rect = body.getBoundingClientRect();
      const total = body.offsetHeight - window.innerHeight;
      const scrolled = -rect.top;
      const val = total > 0 ? (scrolled / total) * 100 : 0;
      setPct(Math.max(0, Math.min(100, val)));
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
    };
  }, []);
  return (
    <div className="art-progress">
      <div style={{ width: pct + "%" }} />
    </div>
  );
}

function ArticleApp() {
  const slug = window.ARTICLE;
  const a = ARTICLE_META[slug];
  if (!a) {
    return (
      <div style={{ padding: "80px 40px", textAlign: "center" }}>
        <h1>Article not found</h1>
        <p>No entry in ARTICLE_META for: <code>{String(slug)}</code></p>
      </div>
    );
  }

  React.useEffect(() => {
    document.body.dataset.lane = a.lane;
  }, [a.lane]);

  return (
    <div className={`art site`} data-lane={a.lane}>
      <Ticker />
      <Nav lane={a.lane} setLane={() => {}} />
      <ArticleProgress />

      <header className="art-hero">
        <div className="art-crumbs">
          {a.breadcrumb.map((c, i) => (
            <React.Fragment key={i}>
              {i > 0 && <span className="art-crumbs__sep">/</span>}
              <a href={a.breadcrumbHrefs[i]}>{c}</a>
            </React.Fragment>
          ))}
        </div>
        <div className="art-kicker">{a.kicker}</div>
        <h1 className="art-title">{renderInline(a.title)}</h1>
        <p className="art-sub">{a.sub}</p>

        <div className="art-by">
          <span className="art-by__av">{a.authorInitial}</span>
          <div className="art-by__who">
            <span className="art-by__name">
              By <a href={`/${a.lane}/`}>{a.author}</a>
            </span>
            <span className="art-by__meta">
              <span>{a.date}</span>
              <span className="art-by__sep">·</span>
              <span>{a.read}</span>
              <span className="art-by__sep">·</span>
              <span>{a.category}</span>
            </span>
          </div>
        </div>
      </header>

      {a.cover && (
        <div className="art-cover">
          <div className="art-cover__inner" role="img" aria-label={a.coverAlt || ""}>
            <div className="art-cover__grid" />
            <span className="art-cover__tag">{a.lane === "personal" ? "PERSONAL LANE" : "BUSINESS LANE"}</span>
            <span className="art-cover__mark">§</span>
          </div>
          {a.coverCaption && <p className="art-cover__cap">{a.coverCaption}</p>}
        </div>
      )}

      <article className="art-body">
        {a.blocks.map((b, i) => <Block key={i} b={b} />)}
      </article>

      <section className="art-about">
        <div className="art-about__av">{a.authorInitial}</div>
        <div>
          <div className="art-about__head">
            <span className="art-about__tag">ABOUT THE AUTHOR</span>
            <h3>{a.author}</h3>
          </div>
          <p>{a.authorBio}</p>
          <a href={`/${a.lane}/`} className="art-about__link">
            SEE ALL {a.author.toUpperCase()}'S GUIDES →
          </a>
        </div>
      </section>

      <NetworkBand />
      <Footer />
    </div>
  );
}

Object.assign(window, { ARTICLE_META, ArticleApp, Block });
ReactDOM.createRoot(document.getElementById("root")).render(<ArticleApp />);
