/* Folclore Latin Fund I — vertical pages.
   Core verticals use the sector-thesis reading structure.
   Jewelry and swimwear keep sourced research memos.
   Secondary verticals keep the short template. */

const VERTICAL_PAGES = {
  jewelry: {
    slug: "jewelry",
    title: "Jewelry",
    dek: "Brazil supplies the colour. The margin is made elsewhere.",
    draft: "Dek states hypothesis H1 as if it were a finding. Keep the provocation, or soften to a question before LP release.",
    lead: "Branded jewelry sustains some of the highest operating margins in personal luxury. Brazil is a top-three exporter of the coloured gemstones that feed it. Fund I's question is whether the distance between those two facts is mispricing — or risk that the market is pricing correctly. This page sets out what we know, what we believe, and what would change our mind.",
    meta: [
      { label: "Priority", value: "Core vertical" },
      { label: "Universe tracked", value: "[5+ houses per deck v8 — FCP to confirm]" },
      { label: "Vertical lead", value: "[FCP to assign]" },
      { label: "Last reviewed", value: "20 Oct 2026" },
    ],
    figures: [
      { value: "€358bn", label: "Personal luxury goods, 2025", note: "Down from €364bn in 2024; Bain–Altagamma name jewelry the leading category of the year. [1]" },
      { value: "30.5%", label: "Operating margin, Richemont Jewellery Maisons", note: "On €16.5bn of sales, year to March 2026 (+14% at constant FX). [2]" },
      { value: "25.8–28.2×", label: "LTM EBITDA paid for Bulgari, 2011", note: "Tender price to total including net debt; a control transaction in a different rate regime. [3]" },
      { value: "US$231.8m", label: "Brazil coloured-gemstone exports, 2024", note: "FOB value; third-largest exporter globally, c. 300,000 direct jobs. [6]" },
    ],
    why: {
      kicker: "01 · The thesis",
      title: "Three facts that make jewelry worth a dedicated allocation.",
      claim: "Brazil captures the stone's value but little of the brand's. A house that owns design, setting and provenance can retain more of the final price.",
      pillars: [
        { title: "Structural economics", body: "At the top of the market, Richemont's jewellery houses earned a 30.5% operating margin on €16.5bn of sales [2]. Further down the price ladder, Vivara — a domestic Brazilian chain — reported a 69.6% gross margin and a 25.3% adjusted EBITDA margin in 2025 [8]. High margins are not confined to the top of the market." },
        { title: "Raw material advantage", body: "Brazil is the world's third-largest exporter of coloured gemstones, at US$231.8m FOB in 2024 [6], and has ranked third among emerald producers since the 2000s, behind Colombia and Zambia [7]. The supply chain is fragmented: roughly 90% of Brazilian gemstone companies are small operations [6]." },
        { title: "Strategic buyers", body: "LVMH paid 25.8–28.2× LTM EBITDA for Bulgari in 2011 [3] and US$15.8bn for Tiffany & Co., completed in January 2021 [4]. Conglomerates have bought heritage jewelers outright, at full control prices." },
      ],
      draft: "Deck v8 states Brazil produces “more than a third of all gemstones” and is #1 in amethyst, aquamarine, agate, topaz and tourmaline. No primary source found; excluded here until sourced.",
    },
    hypotheses: {
      kicker: "02 · Our hypotheses",
      title: "What we believe, the evidence on file, and what would change our view.",
      intro: "Each hypothesis is stated so that it can fail. Status reflects evidence held in this data room today, not conviction.",
      draft: "H1–H4 are drafted from PPM v3, deck v8 and the Tiffany dataset. They are the GP's views to approve, edit or replace.",
      items: [
        { id: "H1", title: "Value is lost between mine and maison", status: "Partially evidenced", claim: "Brazil captures the stone's value but little of the brand's. A Brazilian house that owns design, setting and provenance can retain more of the final price.", evidence: "Third-largest coloured-stone exporter [6]; category operating margins of c. 30% at scale [2].", change: "Trade data showing finished jewelry (HS 7113) already a large share of Brazil's gem-and-jewelry exports relative to stones (HS 7103). [Analysis pending.]" },
        { id: "H2", title: "Category margins survive at Fund I's entry scale", status: "Partially evidenced", claim: "A heritage house small enough for a c. US$25m partnership can already earn positive EBITDA, before international scale.", evidence: "Vivara, a listed domestic chain, at 25.3% adjusted EBITDA [8]. Tiffany at 11.8% operating margin in FY1996, at US$0.9bn of sales [5].", change: "Screened targets in the universe showing EBITDA margins below [FCP threshold]% at entry." },
        { id: "H3", title: "A US presence re-rates the house", status: "Decomposition in progress", claim: "Part of the gap between Brazilian and global jewelry valuations is mispricing that internationalisation can close.", evidence: "Global control precedents only [3][4]. The decomposition below is not yet complete.", change: "If country risk, size, liquidity and control premium explain the full gap, the residual is zero and there is no arbitrage to underwrite." },
        { id: "H4", title: "Inventory, not marketing, is the binding constraint", status: "Supported · one benchmark", claim: "Growth capital in jewelry is consumed by stock before it reaches the consumer. Plans must fund inventory first.", evidence: "Tiffany's inventory rose from 274 to 486 days of cost of sales between FY1996 and FY2008, while the store count grew from 81 (FY1996) to 233 (FY2010) [5].", implication: "Which entity carries stock — the Brazilian operating company or the US holding company — must be settled in each deal's transfer-pricing terms. [Open.]" },
      ],
    },
    benchmarks: {
      kicker: "03 · Benchmark studies",
      title: "The houses we measure against, and why each one is on the list.",
      intro: "Figures as reported by the cited source. Currency and fiscal year as stated; no conversions or FCP estimates.",
      columns: ["House", "Role in our thesis", "Scale", "Profitability", "Valuation reference"],
      rows: [
        ["Tiffany & Co.", "New York · est. 1837", "Growth-phase analogue: a heritage house internationalising from its home market", "US$929m → US$1,668m net sales, FY1996–2000 [5]", "Operating margin 11.8% → 19.6%, same period [5]", "LVMH: US$131.50/share, c. US$15.8bn; closed Jan 2021 [4]"],
        ["Bulgari", "Rome · est. 1884", "Exit precedent: family-controlled house sold to a conglomerate", "LTM EBITDA €143.3m at signing [3]", "[FCP to add from 2010 annual report]", "c. €3.7bn deal value (€4.3bn incl. options and convertible); 25.8–28.2× LTM EBITDA, Mar 2011 [3]"],
        ["Richemont Jewellery Maisons", "Cartier · Van Cleef & Arpels · Buccellati · Vhernier", "Category ceiling: the economics of jewelry at full scale", "€16.5bn sales, FY to Mar 2026; +8% reported, +14% constant FX [2]", "Operating margin 30.5% [2]", "Segment of a listed group; no standalone multiple"],
        ["Vivara", "São Paulo · est. 1962 · B3: VIVA3", "Regional reference: how Brazilian public markets value jewelry", "R$3.03bn net revenue, FY2025 [8]", "Gross 69.6%; adjusted EBITDA 25.3% (company definition) [8]", "[Trading EV/EBITDA — to source from Bloomberg or FactSet at data-room date]"],
      ],
      study: {
        kicker: "Benchmark study · Proprietary dataset",
        title: "Tiffany & Co., FY1996–2010: what internationalisation did to the P&L",
        body: "Rebuilt line by line from fifteen years of 10-K and annual-report filings: 6,676 reported values, each traced to its page, with 2,363 reconciliation checks. Every figure is company-reported or plain arithmetic on reported figures.",
        seriesLabel: "Net sales, US$ millions, as reported. Fiscal years end 31 January. [5]",
        series: [
          { label: "FY1996", value: "929" },
          { label: "FY2000", value: "1,668" },
          { label: "FY2010", value: "3,085" },
        ],
        stats: [
          { label: "Like-for-like sales CAGR", value: "15.8%", note: "FY1996–2000; 9.2% over FY1996–2010" },
          { label: "Operating margin", value: "11.8% → 19.6%", note: "FY1996 to FY2000, reported" },
          { label: "Gross margin", value: "51.9% → 59.3%", note: "FY1996 to FY2002 peak" },
          { label: "US share of sales", value: "63.2% → 48.1%", note: "FY1998 to FY2010" },
          { label: "Stores", value: "81 → 233", note: "FY1996 to FY2010" },
          { label: "Inventory days", value: "274 → 486", note: "Days of cost of sales, FY1996 to FY2008" },
        ],
        tells: "The fastest growth came with the steepest margin expansion, while the home market's share of sales fell. Internationalisation was additive to profitability, not dilutive.",
        breaks: "Tiffany was already a US$0.9bn business with a US home market. Fund I targets are far smaller and enter the US as foreign brands. Basis breaks (SFAS 123R, LIFO to average cost) are documented in the method notes.",
        draft: "FY2011–19 batch not yet built; implied deal multiple at US$131.50 to be computed from the FY2019 10-K before it appears here.",
      },
    },
    gap: {
      kicker: "04 · The valuation gap",
      title: "Observed gap, explained gap, and the residual we underwrite.",
      intro: "A discount is not an opportunity until the legitimate reasons for it are subtracted. We publish the ledger before the conclusion.",
      precedent: { label: "Global control precedent", note: "Bulgari / LVMH, March 2011, tender price", value: "25.8× [3]" },
      listed: { label: "Brazilian listed reference", note: "Vivara, trading EV / EBITDA", value: "[to source]" },
      observed: { label: "Observed gap", value: "[pending]" },
      deductions: [
        ["Control premium (transaction vs. trading)", "[pending]"],
        ["Country risk and local cost of capital", "[pending]"],
        ["Size and liquidity", "[pending]"],
        ["Rate regime (2011 precedent vs. today)", "[pending]"],
        ["Brand positioning (mid-market chain vs. heritage house)", "[pending]"],
        ["Residual — the arbitrage Fund I underwrites", "[pending]"],
      ],
      position: "Fund I does not underwrite precedent multiples. The fund model's exit assumptions of 7×, 9× and 11× (bear, mid, bull) sit below the Bulgari precedent and below the 11.9× reported at the time as LVMH's historical median acquisition multiple [3][13].",
      draft: "That the 7× / 9× / 11× model multiples are EV/EBITDA and apply to jewelry. The bar for the precedent is drawn to a 30× scale.",
    },
    companies: {
      kicker: "05 · Companies",
      title: "Pipeline under codename; the reference landscape in public view.",
      pipelineLink: true,
      landscapeTitle: "Reference landscape · Brazil",
      landscapeNote: "Public information only. No relationship, engagement or interest is implied.",
      landscape: [
        { place: "Rio de Janeiro · est. 1945", name: "H.Stern", body: "Private, family-owned. Reported presence of 150+ stores across Latin America, the US, Europe and Asia. Sales not disclosed. [11]", why: "Proof that a Brazilian house can hold an international retail footprint." },
        { place: "Founded Belo Horizonte · 1941", name: "Amsterdam Sauer", body: "Founded as Lapidação Amsterdam by gemologist Jules Roger Sauer (d. 2017), a member of the GIA's Circle of Honor. Scale not disclosed. [12]", why: "A stone-led heritage, closest to hypothesis H1." },
        { place: "São Paulo · est. 1962 · listed 2019", name: "Vivara", body: "Brazil's listed jewelry chain, with Vivara and Life brands. R$619.5m net income in 2025. [8]", why: "The public-market reference for Brazilian jewelry valuations." },
      ],
      landscapeDraft: "Whether to name third-party houses at all. Deck v8 anonymises them; PPM v3 names H.Stern with estimated financials. One convention across all materials.",
    },
    media: {
      kicker: "06 · Podcast & media",
      title: "Titans of Luxury: the case studies behind the thesis.",
      episode: {
        series: "Titans of Luxury",
        number: "01",
        meta: "Episode 01 · [Duration] · [Release date]",
        title: "De Beers",
        body: "How one seller set the price of diamonds for a century: the Central Selling Organisation, the Soviet Mir mine, and the collapse of the 1980s.",
        why: "The jewelry value chain's best-documented case of pricing power — and of what happens when it rests on supply control rather than brand.",
      },
      next: "Episodes, conference remarks and press relevant to the vertical are listed here as they are released.",
      draft: "Publication status and link for Episode 01; final series name (“Titans of Luxury” vs. “The Provenance”).",
    },
    research: {
      kicker: "07 · Research library",
      title: "Our work, and the work we rely on.",
      columns: ["Document", "Origin", "Date", "Access"],
      rows: [
        ["Tiffany & Co. benchmark dataset, FY1996–2010", "Five CSV files with method notes and verification log · FCP", "Sep 2026", "On file"],
        ["Valuation gap decomposition — jewelry", "Memo supporting section 04 · FCP", "In preparation", "—"],
        ["Luxury Goods Worldwide Market Study, 2025 edition", "Bain & Co. / Altagamma", "Jun 2026", "Source"],
        ["Annual results, year ended 31 March 2026", "Richemont", "May 2026", "Source"],
        ["Coloured stones from Brazil: past, present and future", "The Journal of Gemmology 35(8), pp. 708–726 · A. Reys", "2017", "Source"],
        ["Brazil and coloured gemstones — ICA Congress keynote", "International Colored Gemstone Association", "May 2025", "Source"],
      ],
    },
    risks: {
      kicker: "08 · Risks & open questions",
      title: "What could make this vertical a mistake.",
      items: [
        { n: "01", title: "US tariffs on Brazilian jewelry", body: "Since July 2026, covered Brazilian-origin goods carry up to 37.5% in additional US duties (position as at August 2026) [10]. Finished jewelry and polished stones are not exempt; rough stones and unwrought precious metals are [9].", followLabel: "Open question", follow: "Whether setting and finishing in the US changes origin under customs rules, and at what cost to a provenance-led brand story. Requires customs counsel." },
        { n: "02", title: "Working-capital intensity", body: "Inventory grows faster than sales during a store build-out (H4). Underfunded stock produces thin assortments in new markets.", followLabel: "Mitigation", follow: "Inventory modelled bottom-up in each deal's growth-capital plan, with the stock-carrying entity fixed in the transfer-pricing terms." },
        { n: "03", title: "Input-price exposure", body: "Gold and gemstone prices pass directly into cost of goods; heritage houses rarely reprice as fast as their inputs move.", followLabel: "Mitigation", follow: "[FCP to state: hedging policy, pricing cadence, metal-weight disclosure in diligence.]" },
        { n: "04", title: "Provenance and traceability", body: "A supply base of small operators [6] makes chain-of-custody harder to evidence, and US buyers increasingly ask for it.", followLabel: "Mitigation", follow: "Sourcing audit as a diligence workstream; [FCP to name standard or provider]." },
        { n: "05", title: "Does origin carry value in US jewelry?", body: "The thesis assumes US consumers attach value to Brazilian provenance in this category. We hold no consumer evidence on this yet.", followLabel: "Next step", follow: "[FCP to define: wholesale-partner interviews, price-test on e-commerce, or third-party survey.]" },
      ],
    },
    sources: [
      "Bain & Company and Fondazione Altagamma, Luxury Goods Worldwide Market Study (2025 edition), as reported by Rapaport, July 2026. Primary report to be attached.",
      "Richemont, “Richemont delivers strong sales growth and solid results for the year ended 31 March 2026”, press release, May 2026.",
      "Business Standard, citing Bloomberg, “Bulgari takeover 82% costlier than Hermes for LVMH”, 9 March 2011.",
      "Yahoo Finance, revised LVMH–Tiffany merger terms, 29 October 2020; RTTNews, “LVMH completes acquisition of Tiffany”, 7 January 2021.",
      "FCP, Tiffany & Co. benchmark dataset FY1996–2010, compiled from Form 10-K and Exhibit 13.1 filings, September 2026.",
      "International Colored Gemstone Association, “Brazil is a vital source of colored gemstones to global markets, ICA Congress hears”, 20 May 2025.",
      "A. Reys, The Journal of Gemmology 35(8), 2017, pp. 708–726, as digested by Gem-A, March 2018.",
      "Vivara Participações S.A., FY2025 results as reported by Visno Invest, 18 March 2026; net revenue per StockAnalysis. To reconcile against the company's 4Q25 release.",
      "National Jeweler, “Tariffs on Brazil to increase but rough gemstones exempt”, 20 July 2026.",
      "Thompson Coburn LLP, “Administration implements Section 301 tariffs on Brazil”, 3 August 2026.",
      "H.Stern public company profile. To be replaced with a primary or press source before release.",
      "Forbes Brasil, “Morre Jules Sauer, fundador da Amsterdam Sauer”, February 2017.",
      "FCP, Fund I financial model, September 2026.",
    ],
    footer: "This page forms part of the Folclore Latin Fund I data room and is provided to prospective limited partners under the terms of their confidentiality agreement. It is not an offer to sell or a solicitation of an offer to buy any interest in the fund. Figures are as reported by the cited sources; FCP analysis is labelled as such. Hypotheses are the General Partner's current views and may change. Third-party companies are named for reference only; no relationship is implied.",
  },

  swimwear: {
    slug: "swimwear",
    title: "Swimwear",
    lead: "Swim and resort fits Fund I's thesis on taste and on structure. The hard part is scale: a 10× outcome on US$29m needs a US business of about US$106m by Year 13, larger than any luxury swim specialist we can document at the point of sale. This page sets out the evidence, the landscape and the arithmetic, and says where each could be wrong.",
    meta: [
      { label: "Priority", value: "Core vertical" },
      { label: "Pipeline", value: "Level 2 · status to confirm" },
      { label: "Vertical lead", value: "To assign" },
      { label: "Last reviewed", value: "4 Nov 2026" },
    ],
    figures: [
      { value: "US$23–27bn", label: "Global swimwear, 2025–26", note: "Commercial estimates. No published size for the luxury tier. [1][2]" },
      { value: "US$5.9bn", label: "United States, 2025", note: "The market that matters is the luxury resort slice of this, which no source sizes. [1]" },
      { value: "US$470", label: "ERES one-piece, US list", note: "The worked case uses US$500, and that price has to be proven. [6]" },
      { value: "US$106m", label: "US revenue for 10×", note: "Year 13, at a 9× exit, on US$29m in. Gross IRR 20.6%." },
    ],
    glance: [
      { label: "Precedents", value: "Vilebrequin to G-III for €85.5m on €45.1m revenue, about 1.9× (2012). ERES to Chanel in 1996, price not disclosed; estimated US$10–25m on US$10.4m of sales. Orlebar Brown to Chanel (2018), price not disclosed. [12][13][32]" },
      { label: "Downside", value: "About 2.6× if US scale stays near the model's bottom-up case of US$25m. If the US business fails outright, the home company alone returns about 1.0× at 9× and 0.3× at 6×." },
      { label: "Landscape", value: "11 swim houses verified from a primary or press source. The hot end is Agua Bendita and Johanna Ortiz. The quieter heritage houses are in Brazil and Peru." },
      { label: "Binding constraints", value: "Retail price and unit cost first. Then US duties on Brazilian origin — 25% Section 301 on top of MFN since July 2026. Then the option, which produces 41% of proceeds in the 10× case. [9][11]" },
    ],
    sections: [
      {
        id: "thesis",
        toc: "Thesis",
        kicker: "01 · Thesis",
        title: "The question is how large the US business must become.",
        blocks: [
          { type: "prose", text: "Luxury swim and resort is a small, high-margin category in which place of origin is part of the product, and strategic buyers have paid for place-of-origin houses. Latin America has several such houses that are proven at home and under-built abroad. Fund I's question is whether one of them can sell in the United States, and how large that US business must become to justify the capital." },
          { type: "prose", text: "A 10× outcome on US$29m requires the US holding company to reach roughly US$106m of revenue by Year 13. That is about twice Vilebrequin's global revenue when G-III bought it, and about three times Orlebar Brown's estimated revenue when Chanel did. [12][13]" },
          { type: "claim", text: "The thesis is a claim about scale, not only about taste." },
          { type: "h3", text: "Hypotheses" },
          { type: "prose", text: "Each hypothesis is written so that it can fail. Status reflects evidence held today, not conviction." },
          { type: "hypos", items: [
            { id: "H1", title: "Origin carries a US price premium", status: "Partially evidenced", claim: "Origin carries a price premium in US resort and swim.", evidence: "ERES lists a one-piece at US$470, against US$35–125 for mass and premium DTC labels. [6][7]", change: "US price tests in which a Rio-origin collection earns no premium over the same design without the origin story." },
            { id: "H2", title: "The strongest houses are under-monetised abroad", status: "Partially evidenced", claim: "The strongest Latin American houses are under-monetised abroad.", evidence: "The Rio benchmark exports about 5% of revenue despite 100+ international accounts. Agua by Agua Bendita, at about 60% international, shows the gap can close. [20]", change: "Evidence that low exports reflect product–market fit — fit, sizing, price — rather than capital and distribution." },
            { id: "H3", title: "Internationalisation re-rates the house", status: "Not evidenced", claim: "Internationalisation re-rates the house.", evidence: "Vilebrequin sold at about 1.9× prior-year revenue. ERES's price was not disclosed; on 1996 sales and profit the estimate is about 1–2× sales, US$10–25m. Orlebar Brown's price was not disclosed. None of the three shows the step the model assumes, from 6× EBITDA at entry to 7×, 9× or 11× at exit. [12][13][32]", change: "A swim-house sale in which international scale, rather than size or liquidity, explains a higher multiple." },
            { id: "H4", title: "For Brazil, landed cost binds before demand", status: "Supported", claim: "For Brazilian origin, landed cost, not demand, is the binding constraint in the US.", evidence: "Since July 2026 Brazilian apparel carries a 25% Section 301 duty stacked on the 24.9% MFN rate. A US$60 bikini lands at US$108.67. [9][11]", change: "Tariff relief, or a compliant production route outside Brazil that preserves the provenance story." },
            { id: "H5", title: "The structure carries the return", status: "Supported", claim: "The structure, not the US business alone, carries the return.", evidence: "In the 10× case the 10% stake and call option produce 41% of proceeds. If the options lapse, the required US revenue rises from US$106m to US$165m.", change: "Nothing in the model. This is a finding to disclose (PPM risk factor F-E6)." },
          ] },
          { type: "prose", text: "The argument against the thesis is scale. The swim houses strategic buyers have bought were a fraction of the US business a 10× outcome needs, and none of them was a Latin American heritage house. Provenance, below, sets out that record." },
        ],
      },
      {
        id: "market",
        toc: "Market",
        kicker: "02 · Market",
        title: "Totals are directional. The luxury slice is not published.",
        blocks: [
          { type: "prose", text: "The market that matters to Fund I is not global swimwear. It is the luxury resort slice in the United States, which no published source sizes. The totals below are reconciled so they can be quoted without mixing regions. The opportunity itself is sized bottom-up in the worked case, from units and price." },
          { type: "table", wide: true, caption: "Three commercial estimates, as printed", columns: ["Scope", "Year", "Estimate", "Stated growth", "Source"], rows: [
            ["World", "2025", "US$22.9bn", "6.3% CAGR, 2026–35", "Global Market Insights [1]"],
            ["World", "2026", "US$24.1bn", "—", "Global Market Insights [1]"],
            ["World", "2026", "US$27.3bn", "6.46% CAGR, 2027–36", "Fundamental Business Insights [2]"],
            ["World", "2023", "US$23.1bn", "6.8% CAGR, 2024–30", "Grand View Research [3]"],
            ["North America", "2025", "US$7.5bn", "6.2% CAGR", "Global Market Insights [1]"],
            ["United States", "2025", "US$5.9bn", "—", "Global Market Insights [1]"],
            ["Europe, incl. UK", "2025", "US$5.9bn", "6.1% CAGR", "Global Market Insights [1]"],
            ["Latin America", "2025", "US$2.1bn", "5.9% CAGR", "Global Market Insights [1]"],
            ["EU imports, customs value", "2022", "€2.16bn · ~389m garments", "Up from €1.61bn in 2017", "CBI / Eurostat [4]"],
          ] },
          { type: "h3", text: "Three cautions before any figure is quoted" },
          { type: "bullets", items: [
            "**Totals are directional.** Two firms print near-identical world totals — US$23.1bn for 2023, US$22.9bn for 2025 — and disagree on the largest region: Asia Pacific for Grand View, North America for Global Market Insights. [1][3]",
            "**One source is internally inconsistent.** Global Market Insights' China (US$6.0bn) and India (US$1.2bn) lines exceed its own US$6.4bn Asia Pacific headline. That US$6.4bn figure is Asia Pacific, not the United States. We found no source for a US market above US$8bn in 2026. [1]",
            "**Import value is not retail spend.** EU imports average about €5.56 per garment at customs value. [4]",
          ] },
          { type: "h3", text: "What is sold" },
          { type: "prose", text: "Women's swimwear is about 73% of measured value: US$16.7bn of US$22.9bn in 2025, against US$4.0bn for men's and US$2.1bn for kids'. [1] The category has three demand pools: fashion and vacation, performance swimming, and resort luxury. Fund I is concerned only with the third, where swimwear is sold with cover-ups, dresses and accessories as a lifestyle purchase." },
          { type: "prose", text: "The measured leaders are sportswear groups. Nike holds over 7% of the global market and the five largest sports brands 20%. [1] Luxury swim is fragmented and held largely by private houses or conglomerate subsidiaries: ERES and Orlebar Brown within Chanel, Vilebrequin under G-III since 2012. [12][13]" },
          { type: "h3", text: "Where luxury sits on price" },
          { type: "prose", text: "ERES lists a one-piece at US$470 in the US. French listings are on file as a cross-check. Mass and premium DTC labels list one-pieces from about US$35 to US$125. [6][7][8] The worked case assumes a Rio house can list at US$500, above ERES. That has to be proven: a US$100 lower price raises the US revenue needed for 10× by 62%." },
          { type: "h3", text: "Economics behind the price" },
          { type: "prose", text: "CBI reports that a swimwear item retails on average at 4–8 times its factory free-on-board price, so FOB is 12.5–25% of retail. Some budget chains only double it. [5] The worked case prices a US$500 retail bikini against a US$69 transfer price (US$60 production cost plus 15%), a multiple of 7.2×, at the top of that range. The difference pays for duties, freight, rent, marketing and markdowns." },
          { type: "prose", text: "The category's commercial risk is fit. Swimwear is hard to judge online, so e-commerce raises returns and markdowns. [5] The worked case charges both: a 6% markdown dilution on direct sales, and a returns cost per online unit." },
          { type: "prose", text: "The US price bands in an earlier internal brief — luxury US$250–800+, premium DTC US$80–180, value US$20–60, active US$50–120 — have no published source. We use them as positioning labels only, never as market-size figures." },
          { type: "draft", text: "Whether to commission a luxury-tier US estimate from point-of-sale data before LP release. Without it, the worked case sizes the opportunity from units and price, and benchmarks it against houses that exist." },
        ],
      },
      {
        id: "provenance",
        toc: "Provenance",
        kicker: "03 · Provenance",
        title: "Buyers have paid for place. A Latin swim house has not yet collected.",
        blocks: [
          { type: "prose", text: "Provenance is the asset Fund I licenses, so it has to be shown to be monetisable, and not merely borrowable. The record shows strategic buyers paying for resort houses whose origin is part of the name. It does not yet show a Latin American heritage swim house doing so." },
          { type: "table", wide: true, house: true, caption: "Origin-led resort houses and their buyers", columns: ["House", "Origin", "Transaction", "Scale at the transaction", "Source"], rows: [
            ["ERES", "Paris, 1968", "Chanel, 1996. Price not disclosed. Reported in the press, June 1997.", "1996 sales US$10.4m (FF60m); net profit US$1.2m, 12% of sales. Estimated value US$10–25m: 1–2× sales is US$10–21m, and 10–20× earnings is US$12–24m. Not a reported price.", "[32]"],
            ["Vilebrequin", "Saint-Tropez, 1971", "G-III, Aug 2012: €85.5m, plus up to €22.5m contingent on growth to 2015", "€45.1m revenue (2011); 185 owned and franchised stores", "[12]"],
            ["Orlebar Brown", "London, 2007", "Chanel, 2018. Price undisclosed. Reported asking price £50m in 2017.", "About £25m revenue (analyst estimate); 24 stores; 250+ stockists", "[13]"],
          ] },
          { type: "h3", text: "What the precedents show" },
          { type: "bullets", ordered: true, items: [
            "**Strategic buyers pay for place.** Chanel has owned ERES since 1996 and bought Orlebar Brown in 2018. G-III bought Vilebrequin. Vilebrequin's headline price was about 1.9× its prior-year revenue. No EBITDA multiple was disclosed. [12]",
            "**ERES has accounts, and no price.** Women's Wear Daily reported the purchase with the terms undisclosed. In 1996, consolidated sales were US$10.4m (FF60m) and net profit was US$1.2m, 12% of sales. [32] One to two times sales is US$10–21m. Ten to twenty times that profit is US$12–24m. The working band is US$10–25m. It is an estimate, on net profit rather than EBITDA, and it is not a price Chanel disclosed. Peau Douce, the house fabric from the beginning, and the standing with French retailers, are reasons to expect the high end of the band. [33]",
            "**The precedents are smaller than 10× requires.** ERES (US$10.4m of sales), Vilebrequin (€45.1m) and Orlebar Brown (about £25m) were a fraction of the US$106m of US revenue the worked case needs. [12][13][32]",
          ] },
          { type: "h3", text: "The counter-case: origin can be borrowed" },
          { type: "prose", text: "A buyer can ask whether a new label can carry a place without a licence on a house that has worked there for decades. The response rests on three claims. Diligence has to test each of them." },
          { type: "bullets", items: [
            "**In swim, the product is the proof.** A bikini's cut, fabric and fit are made somewhere, and the house that has made them for decades owns that knowledge. **Test:** blind fit and wear trials against US premium labels.",
            "**Time cannot be manufactured.** Thirty or more years of Rio clientele is a record a new label cannot buy. **Test:** US consumer recognition and willingness to pay, by origin, in a controlled price test.",
            "**Exclusivity removes the substitute.** An exclusive international licence prevents the house from granting the same origin to a competitor. **Test:** trademark registrations in every target market before signing (Structural flag F-C4).",
          ] },
          { type: "h3", text: "The ERES template" },
          { type: "prose", text: "ERES is the reference for luxury swim as a maison: fit treated as a discipline, restraint as a signature, no visible branding. [14] Rio's houses compete on colour, print and the body, which is a different proposition. Whether a Rio house should borrow ERES's restraint or lead with its own exuberance is the first positioning decision in the value-creation plan. The podcast episode on the house is in Research, below." },
          { type: "h3", text: "Origin now has a customs cost" },
          { type: "prose", text: "Country of origin, not brand nationality, sets US duty. Since July 2026 Brazilian-origin apparel pays a 25% Section 301 duty on top of MFN rates, with a further 12.5% forced-labour action reported. [9][10] Colombian, Peruvian and Mexican origin are treated differently, depending on free-trade rules of origin and on which economies the forced-labour action covers. A landed-cost comparison by origin is a customs-counsel workstream. It may make a Colombian or Mexican house cheaper to scale in the US than a Brazilian one." },
        ],
      },
      {
        id: "landscape",
        toc: "Landscape",
        kicker: "04 · Landscape",
        title: "Eleven swim houses verified. The rest of the brief is off this page.",
        blocks: [
          { type: "prose", text: "Of the 22 names in the brief, 11 are swim houses we could verify from a primary or press source, and they are placed below. Three are not swim houses and stay off this page. Two exist but lack the data to place. Six could not be found and stay off until sourced. Two descriptions in the brief did not match the sources, and are corrected underneath. Public information only. Naming a house implies no relationship, engagement or interest." },
          { type: "pipeline", text: "Houses under consideration sit in the Pipeline room, under NDA. The working assumption is Level 2. That status is still to confirm." },
          { type: "table", wide: true, house: true, caption: "Reference landscape · public record, November 2026", columns: ["House", "Origin", "What the public record shows", "Price", "Heat"], rows: [
            ["Agua by Agua Bendita", "Medellín, 2003", "About US$45m turnover; about 60% international; 70+ countries through e-tailers; a new CEO; 20%+ growth projected. [20]", "Luxury e-tail (Mytheresa)", "Fast growth, professionalising."],
            ["Johanna Ortiz", "Cali, 2003", "The US is the largest market. Madison Avenue flagship. About 460 staff. No outside investors. [21]", "Luxury", "Founder-controlled, in demand."],
            ["Água de Coco", "Fortaleza, 1985", "29 stores in Brazil and one in Miami. Exports to 25 countries. Revenue up 58% in 2024. [17]", "Self-described luxury lifestyle", "Fast reported growth, no capital event."],
            ["By Lenny Niemeyer", "Rio de Janeiro, 1991", "21 freestanding stores and 200+ multibrand doors on the company site. Press reported a 25th store in 2024. Stocked by Bergdorf Goodman, Neiman Marcus and Holt Renfrew. [15][16]", "Luxury department stores", "No public capital event."],
            ["Aguaclara", "Lima, 1987", "30+ countries; over 5,000 pieces a month; about 150 staff. Franchise stores in Punta Cana and Martinique. [22]", "Resort boutiques. Prices not verified.", "Quiet, founder-family."],
            ["BlueMan", "Rio de Janeiro, 1974", "Founder David Azulay (d. 2009). Led creatively by his daughter. Back on the runway at Rio Fashion Week 2026 after a decade away. Football-club licensed lines. [19][29][31]", "A European reseller lists a full set at €69.90.", "Relaunching heritage."],
            ["Cia Marítima", "Brazil", "Owned by the Rosset textile group. Relaunched in the US as CM in 2013 through a licensed distributor. [18][30]", "Single pieces listed at R$188–258, about US$37–50.", "Corporate-owned, quiet abroad."],
            ["Sandmade", "Playa del Carmen, 2014", "Handmade swimwear. Shown at Vogue Runway, Miami Swim Week 2026. [24][23]", "Not verified", "Early runway attention."],
            ["Tulux Resortwear", "San Diego, 2022", "Latina-owned, based in Pacific Beach, production in Mexico. Shown at Vogue Runway, Miami Swim Week 2026. Tops US$159.99, bottoms US$109.99. [23]", "Premium", "Early runway attention."],
            ["Mysso", "Mexico City", "Flagship in Lomas de Chapultepec. Premium swim with tropical prints, in 2019 press. [25]", "Premium", "Limited public data."],
            ["RHEA: The Label", "Mexico", "All production in Mexico. Regenerated nylon. Founded by two friends. [26]", "Not verified", "Limited public data."],
          ] },
          { type: "h3", text: "Corrections to the brief" },
          { type: "bullets", items: [
            "**Tulux** is a San Diego company that draws on Tulum, not a Tulum label. [23]",
            "**Johanna Ortiz** first became known for occasion dresses, with resort a later extension, according to the source we opened. [21] The brief describes it as originally a swimwear line. We found no source for that.",
          ] },
          { type: "h3", text: "Not swim, and off this page" },
          { type: "prose", text: "Three names in the brief are not swim houses, so they are not placed: Farm Rio is ready-to-wear, PatBO is eveningwear, and Escvdo is knitwear." },
          { type: "h3", text: "Not verified, and not placed" },
          { type: "prose", text: "Argentina: Loveafrica, Valus, Bonga, Pioppa. Mexico: Coco Flamingo, Minaci. We found no primary or press source matching the brief's descriptions. Compañía del Sol and Mita Mita exist as online resort sellers, and they lack the data to place. [27]" },
          { type: "draft", text: "Whether Argentine swim belongs in this vertical at all. Fund I's Argentine thesis is leather goods. Nothing on file shows an Argentine swim house with export activity." },
          { type: "h3", text: "Price position against heat" },
          { type: "prose", text: "The horizontal axis is price position, taken from the public evidence in the table. The vertical axis is heat: how visible capital-market interest and growth already are. Heat is our proxy for entry price. A cold placing is not a judgement on the brand. It means the market is not yet pricing it. Fund I looks first in the gold cell — luxury, and quiet." },
          { type: "matrix", label: "FCP placement from the public sources above · 8 houses with a price on file", columns: ["Accessible", "Premium", "Luxury"], rows: [
            { label: "Hot", sub: "Already visible", shade: [false, false, false], cells: [
              [],
              [],
              [
                { name: "Agua Bendita", note: "Fast growth, ~60% abroad" },
                { name: "Johanna Ortiz", note: "US demand, no investors" },
              ],
            ] },
            { label: "Warming", sub: "Early heat", shade: [false, false, false], cells: [
              [{ name: "BlueMan", note: "Relaunching" }],
              [{ name: "Tulux", note: "Early runway" }],
              [
                { name: "Água de Coco", note: "Growth, no deal" },
              ],
            ] },
            { label: "Quiet", sub: "Not yet priced", shade: [false, false, true], cells: [
              [{ name: "Cia Marítima", note: "Quiet abroad" }],
              [{ name: "Mysso", note: "Limited public data" }],
              [{ name: "Lenny Niemeyer", note: "No capital event" }],
            ] },
          ], caption: "The gold cell is the one we look at first: a luxury house the market is not yet pricing. Hot means we may already be late on entry price. Warming marks a quieter house showing early heat.", aside: "Not plotted, because price is not on file: Aguaclara, Sandmade, and RHEA: The Label. Mita Mita and Compañía del Sol exist and are not placed. Eleven swim houses are verified; eight have enough price evidence to sit on the axes." },
          { type: "draft", text: "Placing named houses on a heat axis signals where Fund I might look. Counsel and the GP to decide whether this exhibit goes to LPs as drawn, with houses anonymised, or stays internal." },
        ],
      },
      {
        id: "benchmark",
        toc: "Benchmark",
        kicker: "05 · The Rio benchmark",
        title: "Retail-led, luxury margins at home, almost no exports.",
        blocks: [
          { type: "prose", text: "A Rio heritage house reviewed by Fund I shows the model we would license. It is retail-led and vertically integrated, with luxury gross margins at home, operating margins that fell as it grew, and almost no exports. Its numbers anchor the worked case." },
          { type: "caption", text: "Basis. Management accounts received under confidentiality. The house is not named. Figures are banded and rounded. Margins are on net revenue, after Brazilian sales taxes. Revenue is gross. US dollars at R$5.15, the 15 September 2026 spot used in the Fund I model, to be refreshed at the data-room date. [28]" },
          { type: "table", wide: true, caption: "What the accounts show, and what we take from them", columns: ["Measure", "Benchmark, banded", "What it tells us"], rows: [
            ["Gross revenue, 2024", "R$140–170m (US$27–33m)", "Inside Fund I's size range. Under a third of the US revenue the 10× case needs."],
            ["Revenue growth, 2018–24", "Mid-to-high teens a year, nominal BRL", "Demand at home is not the problem."],
            ["Gross margin", "Low-to-mid 60s %", "Luxury pricing power in its home market."],
            ["EBITDA margin", "High teens in 2022; low-to-mid teens in 2024 and the 2025 budget", "Costs grew faster than sales."],
            ["Channel mix", "Own stores about 70%; wholesale about 20%; franchise and export about 5% each", "Retail-led. Export barely worked."],
            ["Category mix", "Resort ready-to-wear about 50%; swim about 40%; accessories about 10%", "It sells a way of dressing, not only swimwear."],
            ["International", "Exports about 5% of revenue, broadly flat; 100+ international accounts", "A small foreign presence, and it is not growing on its own."],
            ["Footprint", "20+ owned stores; about 500 staff; own manufacturing", "High fixed costs."],
            ["Leverage", "Net debt below 0.5× EBITDA", "Balance-sheet room. A cautious owner."],
            ["Ownership", "Founder-led, family-held; succession not settled", "Fits a call option that gives the family an exit path."],
          ] },
          { type: "h3", text: "What it means for a US licence" },
          { type: "bullets", ordered: true, items: [
            "**Home economics do not travel intact.** US duties and freight add about 57% to the transfer price in the worked case, from US$69 to US$108.67. A 7.2× retail-to-transfer multiple becomes 4.6× retail-to-landed, before royalty and marketing.",
            "**Operating leverage cuts both ways.** The house lost about five points of EBITDA margin in two years while sales grew. The US HoldCo should start asset-light: wholesale and e-commerce first, stores only where data justifies them, as Model v3 assumes. [28]",
            "**Exports are a signal, not yet a business.** More than 100 foreign accounts produce about 5% of revenue, and that share is flat. Demand exists. No one is building it. That is what the licence buys.",
            "**The assortment is broader than swim.** Swim is under half of sales. The worked case's single US$500 bikini is a simplification. A US range would include resort ready-to-wear at higher tickets and different duty rates.",
            "**Margins need restating for peers.** Brazilian reporting — gross revenue, and rent treatment under CPC 06, not yet confirmed — is not comparable with IFRS or US GAAP peers until restated. The Cucinelli benchmark notes raise the same point. [28]",
          ] },
          { type: "draft", text: "The landscape names a Rio house whose public footprint falls inside these bands. Counsel to confirm that this page is consistent with the seller's confidentiality terms before LP release." },
        ],
      },
      {
        id: "value",
        toc: "Value",
        kicker: "06 · Value creation",
        title: "The US business, the option, the home margin, the multiple.",
        blocks: [
          { type: "prose", text: "Fund I creates value in a swim house through four levers: building the US business, capturing the royalty and supply margin through the call option, restoring the home company's margins, and exiting at a higher multiple than it entered. In the 10× case the US business produces 59% of proceeds and the option and stake 41%." },
          { type: "h3", text: "The structure, as applied here" },
          { type: "prose", text: "Money flows down from Fund I. Product flows from Rio to the HoldCo. The royalty flows back. The licensor's 20% sits with the owners in this case, outside the option." },
          { type: "facts", items: [
            { title: "Money down", body: "US$29m from Fund I: a US$1m licence fee, 10% of the operating company, a 5% call premium, HoldCo growth capital, and option extension fees from Year 6. A further US$3m follows into the HoldCo in Year 3." },
            { title: "Product across", body: "The operating company sells product to the HoldCo at cost plus 15%. In the worked case that is a US$69 transfer price on a US$60 production cost." },
            { title: "Royalty back", body: "6% of HoldCo net sales, rising to 7% if the licence renews. Paid to the operating company, so it lifts the EBITDA the option is written on." },
          ] },
          { type: "bullets", items: [
            "**Licence.** Exclusive rights outside Brazil, for 10 years from investment, renewable for 10 more at the HoldCo's option against a royalty step-up. Consideration: US$1m cash plus 20% of the HoldCo's equity.",
            "**Royalty.** 6% of HoldCo net sales, rising to 7% at renewal. The step-up is a placeholder. Paid to the operating company.",
            "**Supply.** The operating company sells product to the HoldCo at cost plus 15%.",
            "**Minority and option.** Fund I buys 10% of the operating company and a call on the remaining 90%. Fixed strike at 6× entry EBITDA, escalating 10% a year in reais, with a 5% premium and extension fees of 1.5% a year from Year 6. The strike is paid from the exit event, never from Fund I commitments.",
          ] },
          { type: "h3", text: "PATH to Globe, applied to swim and resort" },
          { type: "table", wide: true, columns: ["Pillar", "What it means in this category", "First proof point"], rows: [
            ["Premium positioning", "List at the ERES price point, about US$450–500 a piece, through luxury department stores, luxury e-tail and resort-hotel boutiques. Decide early between restraint and exuberance.", "Sell-through at full price in the first two seasons."],
            ["Authentic adaptation", "Re-grade fit and cup options for US bodies. Keep the cut, print and making in Rio. Widen into resort ready-to-wear, which travels across seasons.", "Return rate on e-commerce below the plan's assumption."],
            ["Tactical iteration", "Wholesale and US e-commerce from Year 1. Pop-ups from Year 2. A Miami flagship in Year 3 and New York in Year 5, as in Model v3. Open stores only where e-commerce data shows a cluster. [28]", "Paid-acquisition payback. Model v3 plans about 12 months."],
            ["Heritage anchoring", "Archive, founder story, and a small set of core silhouettes that are perfected and then left unchanged. Creative succession planned from entry.", "A named second-generation design lead within 24 months."],
          ] },
          { type: "h3", text: "The refurbishment lever at home" },
          { type: "prose", text: "The Rio benchmark lost about five points of EBITDA margin in two years. Fund I's board seat targets recovery to 17% by Year 6, the bear-case target in Model v3. [28] Every point recovered raises the value of the 10% stake and of the option, because the strike is fixed. A house that needs refurbishment is worth more to Fund I than one already priced for perfection." },
          { type: "h3", text: "What Fund I contributes" },
          { type: "prose", text: "US wholesale relationships, e-commerce and fulfilment, US entity and customs set-up, and a board seat at home. The partners have built a Brazilian spirits brand across 12 US states, Canada and the Bahamas, and exited a gin's North American rights with a payout from Pernod Ricard. Those are spirits, not apparel. The swim-specific team — merchandising, fit, wholesale sales — is a hire Fund I must make, and the plan budgets for it." },
        ],
      },
      {
        id: "criteria",
        toc: "Criteria",
        kicker: "07 · What we look for",
        title: "Profitable at home, under-built abroad, not yet priced.",
        blocks: [
          { type: "prose", text: "Fund I looks for a heritage swim or resort house that is profitable at home, under-built abroad, and not yet priced for its potential. Two features raise our interest: a business that needs refurbishment, and a small international following that is growing without help. If a house is already hot, we are too late." },
          { type: "h3", text: "Deal terms for this vertical" },
          { type: "table", wide: true, columns: ["Term", "Assumption", "Note"], rows: [
            ["Entry / exit", "Start of Year 1 / end of Year 13", "Year 13 falls in the first of the fund's two one-year extensions, which need LPAC approval."],
            ["Capital", "US$29m: US$26m at entry, US$3m follow-on into the HoldCo in Year 3", "Above the US$20–28m per-deal range in current materials."],
            ["Licence", "Exclusive, outside the origin market, 10 years plus a 10-year renewal at the HoldCo's option", "Renewal falls in Year 11, before exit."],
            ["Licence consideration", "US$1m cash plus 20% of HoldCo equity", "Paid to the brand owner."],
            ["Royalty", "6% of HoldCo net sales; 7% after renewal", "Step-up is a placeholder. In addition to the cash and equity."],
            ["Operating company", "10% stake plus a call on the remaining 90%", "Strike funded by the exit event."],
            ["Target", "10× gross deal MOIC", "The worked case sets out what that requires. It is a threshold, not a forecast."],
          ] },
          { type: "h3", text: "Criteria" },
          { type: "table", wide: true, columns: ["Criterion", "Threshold", "Why it matters", "Rio benchmark"], rows: [
            ["Heritage", "30+ years preferred", "Time is the one asset a competitor cannot buy.", "Meets."],
            ["Profitability", "Positive EBITDA at entry", "The option is priced on EBITDA. Losses leave nothing to price.", "Meets."],
            ["Export readiness", "Production that can meet US wholesale lead times and quality", "The HoldCo buys product from the house.", "Meets. 100+ foreign accounts already served."],
            ["Licence appetite", "Owner willing to grant global rights and an option", "The structure depends on it.", "Not disclosed."],
            ["Origin and duty", "Landed cost into the US modelled by country of origin", "Duty now differs by up to 25 points by origin.", "Brazilian origin. Highest current exposure."],
            ["**Bonus:** needs refurbishment", "Margin decline, under-invested stores or digital, a creative gap after the founder", "A fixed strike and a board seat mean recovery accrues to Fund I, and entry multiples are lower.", "Partly. Margin fell about five points in two years."],
            ["**Bonus:** small, growing abroad", "Foreign accounts or online orders growing without a dedicated team", "Proof of demand before Fund I spends.", "No. Exports are flat."],
          ] },
          { type: "h3", text: "Two guardrails on the bonuses" },
          { type: "bullets", items: [
            "**Refurbishment is not distress.** We want tired operations around an intact brand. We do not want unresolved tax or labour liabilities. In Brazil a board member can face personal exposure for them (Structural flag F-B1). Any house with a going-concern question is out.",
            "**Growing organically needs a number.** The bonus is a foreign following that grows without a dedicated team. We have no swim-house series for the first years of foreign sales, so the threshold is not yet defined.",
          ] },
          { type: "draft", text: "A minimum for growing organically. For example, foreign revenue growth above domestic growth for two consecutive years." },
        ],
      },
      {
        id: "case",
        toc: "Worked case",
        kicker: "08 · Worked case",
        title: "What 10× requires, solved backwards.",
        blocks: [
          { type: "prose", text: "To return 10× on US$29m — US$290m out — at a 9× exit, the US HoldCo must reach about US$106m of revenue in Year 13. That is roughly 320,000 units a year at US$500 retail. From the US$1.4m export book it inherits, that is 44% compound growth for twelve years, and a peak cash need about US$6m above the growth capital the structure provides. Strip out the option and the stake, and the requirement rises to US$165m." },
          { type: "claim", text: "This is a reverse-engineered case. We solve for the US business that delivers 10×, then test it against houses that exist. It is not a forecast." },
          { type: "h3", text: "Unit economics, per piece, at maturity" },
          { type: "prose", text: "US$500 retail and US$60 production cost are the GP's inputs. Everything else is declared here, and again in the assumptions." },
          { type: "table", wide: true, emphasis: [4, 8], caption: "Landed cost is the same in every channel. Contribution is before marketing and overheads.", columns: ["Line", "Wholesale", "E-commerce", "Own stores"], rows: [
            ["Realised price", "US$199.50 — 42% of retail, less 5% allowances", "US$470.00 — 6% markdown dilution", "US$470.00"],
            ["Transfer price to HoldCo", "US$69.00", "US$69.00", "US$69.00"],
            ["US duty: MFN 24.9% + Section 301 25% + MPF", "US$34.67", "US$34.67", "US$34.67"],
            ["Freight, insurance, brokerage", "US$5.00", "US$5.00", "US$5.00"],
            ["**Landed cost**", "**US$108.67**", "**US$108.67**", "**US$108.67**"],
            ["Gross margin", "45.5%", "76.9%", "76.9%"],
            ["Royalty, 6% of net sales", "US$11.97", "US$28.20", "US$28.20"],
            ["Channel costs", "US$3.00 freight to the retailer", "US$12.50 fulfilment, US$7.60 returns, 3% payments", "2.5% payments. Store costs 24% of sales."],
            ["**Contribution**", "**US$75.86 (38%)**", "**US$298.93 (64%)**", "**US$321.38 (68%)** before store costs"],
          ] },
          { type: "h3", text: "Operating plan and the home company" },
          { type: "prose", text: "Revenue mix moves from 50% wholesale and 50% e-commerce in Year 1 to 31% wholesale, 40% e-commerce and 29% stores by Year 6, the Model v3 mix. [28] Stores average US$3.5m of sales, cost US$1.8m each to open, and at least US$1m a year to run. Marketing falls from 35% of sales in Years 1–2 to 18% from Year 8. Overheads are 5% of sales plus a fixed base sized to the plan. Inventory is held for 180 days, wholesale receivables 60 days, payables 45 days. US tax is 25%, with losses carried forward against 80% of profits." },
          { type: "prose", text: "The operating company sits inside the Rio benchmark's bands. Net revenue R$125m, growing 8% a year in nominal reais. EBITDA margin 12.8%, rising to 17% by Year 6, less R$1.05m a year of audit and insurance. Entry EBITDA R$16m at 6×, net debt R$7m. The reais weakens 3% a year against the dollar from R$5.15, a placeholder. Royalty and supply margin from the HoldCo count in its EBITDA." },
          { type: "h3", text: "Capital" },
          { type: "table", caption: "US$29m in. The extension fees are part of the entry allocation, drawn in Years 6–13.", columns: ["Use", "Timing", "US$m"], emphasis: [5, 7], rows: [
            ["Licence fee, cash", "Year 1", "1.00"],
            ["10% of the operating company", "Year 1", "1.73"],
            ["Call premium, 5% of entry strike", "Year 1", "0.78"],
            ["HoldCo growth capital", "Year 1", "19.19"],
            ["Option extension fees, 1.5% a year of the escalating strike", "Years 6–13", "3.30"],
            ["**Entry allocation**", "", "**26.00**"],
            ["Follow-on into the HoldCo", "Year 3", "3.00"],
            ["**Total invested**", "", "**29.00**"],
          ] },
          { type: "h3", text: "The revenue 10× requires" },
          { type: "scale", label: "Year-13 US revenue required to return 10× on US$29m", max: 201, items: [
            { group: "With the option and the 10% stake" },
            { name: "11× exit", value: 87, display: "US$87m" },
            { name: "9× exit", value: 106, display: "US$106m", mark: true },
            { name: "7× exit", value: 138, display: "US$138m" },
            { group: "If the options lapse" },
            { name: "11× exit", value: 140, display: "US$140m" },
            { name: "9× exit", value: 165, display: "US$165m" },
            { name: "7× exit", value: 201, display: "US$201m" },
          ], caption: "Bar length is US revenue in Year 13. The marked row is the mid case. Vilebrequin changed hands at €45.1m of global revenue and Orlebar Brown at about £25m. Both sit well below the marked row. They are not drawn on this axis: different currency, and a global perimeter rather than a US one. [12][13]" },
          { type: "table", wide: true, emphasis: [1], caption: "Same cases, with units and the HoldCo margin the path implies.", columns: ["Exit multiple", "US revenue, Year 13", "Units a year", "HoldCo EBITDA margin", "US revenue if the options lapse"], rows: [
            ["7×", "US$138m", "417,000", "18.2%", "US$201m"],
            ["9× (mid)", "US$106m", "320,000", "18.1%", "US$165m"],
            ["11×", "US$87m", "263,000", "17.1%", "US$140m"],
          ] },
          { type: "prose", text: "At 9×, the US$290m comes from three places. Fund I's 80% of the HoldCo is US$171m, 59%. The option's value above its US$34m strike is US$104m, 36%. The 10% stake is US$15m, 5%. Gross IRR is 20.6%, on entry at the start of Year 1 and exit at the end of Year 13. The HoldCo turns EBITDA-positive in Year 6." },
          { type: "stats", label: "Where the US$290m comes from, at a 9× exit", items: [
            { value: "US$171m", label: "80% of the HoldCo", note: "59% of proceeds. This is the US business." },
            { value: "US$104m", label: "Option, above a US$34m strike", note: "36% of proceeds. Mostly US value flowing back to Brazil." },
            { value: "US$15m", label: "The 10% stake", note: "5% of proceeds." },
          ] },
          { type: "prose", text: "The option's value is mostly US value in disguise. In Year 13 the operating company earns US$17.4m of EBITDA, of which US$7.4m is royalty and US$2.9m is supply margin from the HoldCo. Only US$7.1m comes from Brazil. **If the US business fails outright, the home company alone returns about 1.0× at a 9× exit and 0.3× at 6×.**" },
          { type: "h3", text: "Sensitivities" },
          { type: "prose", text: "US revenue needed in Year 13 for 10×, at a 9× exit. The highlighted rows are the ones that move the answer by half or more: production cost, retail price, and the option lapsing." },
          { type: "table", wide: true, emphasis: [4, 5, 13], columns: ["Change from base", "Revenue needed", "Change"], rows: [
            ["Base", "US$106m", "—"],
            ["Further 12.5% forced-labour duty", "US$122m", "+15%"],
            ["No Section 301 duty", "US$88m", "−17%"],
            ["Royalty at 8% instead of 6%", "US$110m", "+4%"],
            ["Production cost US$75 instead of US$60", "US$160m", "+51%"],
            ["Retail price US$400 instead of US$500", "US$172m", "+62%"],
            ["Mature marketing at 22% of sales instead of 18%", "US$127m", "+20%"],
            ["Licensor takes 30% of the HoldCo instead of 20%", "US$116m", "+9%"],
            ["Home company grows 4% a year, margin stays at 13%", "US$119m", "+12%"],
            ["Reais flat against the dollar", "US$103m", "−3%"],
            ["Licensor's 20% sits inside the operating company, recaptured by the option", "US$92m", "−13%"],
            ["Royalty paid to the owners, outside the operating company", "US$140m", "+32%"],
            ["Cash gap funded by Fund I equity, not a facility (US$36m invested)", "US$135m", "+27%"],
            ["Options lapse", "US$165m", "+56%"],
          ] },
          { type: "h3", text: "What an LP should take from this" },
          { type: "bullets", ordered: true, items: [
            "**10× is a scale claim.** It needs a US swim and resort business about twice the size of Vilebrequin's global business when it sold. [12] The swim specialists that have changed hands were smaller.",
            "**Price and cost matter more than tariffs.** A US$100 lower retail price, or a US$15 higher production cost, moves the requirement more than the forced-labour duty. Diligence starts with the US price list and the unit cost.",
            "**The structure roughly halves what the US business must achieve.** Without the option and the stake the requirement is US$165m. Returns lean on the option, and the option leans on the US business.",
            "**The case needs a facility.** Peak cash need is US$28m against US$22.2m of growth capital. We assume a 10% working-capital facility peaking at US$6.6m in Year 7, repaid by Year 9.",
            "**Growth slows to about 4% by Year 13** on this path. A buyer paying 9× will want a further runway, such as Europe or the Middle East, where the licence already applies.",
          ] },
          { type: "draft", text: "Whether 10× is shown to LPs as the base case or as the upside, given the fund's 2.8× net target. And who receives the licensor's 20% and the royalty: the sensitivities move the required US revenue by about US$14–34m." },
        ],
      },
      {
        id: "research",
        toc: "Research",
        kicker: "09 · Research and media",
        title: "The house of ERES, and the file behind this page.",
        blocks: [
          { type: "episode", series: "Titans of Luxury", number: "03", meta: "Episode 03 · November 2026", title: "The House of ERES", href: "https://titansofluxury.com/episodes/03-eres/", body: "The Paris house that treats swimwear as couture, and builds desire without a visible logo. [14] Releases November 2026. Show notes and transcript follow at premiere.", why: "ERES is the clearest case of a swim house becoming a maison and being held by a couture group since 1996. It sets the price point the worked case assumes, and it frames the positioning choice: restraint, or Rio's exuberance." },
          { type: "h3", text: "Research library" },
          { type: "table", wide: true, columns: ["Document", "Origin", "Date", "Access"], rows: [
            ["Swimwear market overview, sources reconciled", "FCP, from Global Market Insights, Grand View, Fundamental Business Insights, CBI", "Nov 2026", "Data room"],
            ["Worked-case model: US HoldCo, operating company, option, sensitivities", "FCP", "Nov 2026", "In preparation"],
            ["Fund I financial model v3", "FCP", "Sep 2026", "Internal"],
            ["Rio benchmark, management accounts", "Seller, under NDA", "2024–25", "Level 2, anonymised"],
            ["Benchmark datasets: Cucinelli, Tod's, Golden Goose, Tiffany, LVMH, Kering, Hermès", "FCP", "Sep–Oct 2026", "Data room"],
            ["Vilebrequin acquisition release", "G-III Apparel Group", "Aug 2012", "[Source](https://ir.giii.com/news-releases/news-release-details/g-iii-apparel-group-ltd-acquires-global-luxury-resort-brand)"],
            ["Chanel buys Eres, terms undisclosed; 1996 sales and profit", "Women's Wear Daily, Sarah Raper", "11 June 1997", "[Source](https://wwd.com/fashion-news/fashion-features/feature/article-1113335-1785213/)"],
            ["Swimwear: market potential and market entry", "CBI, Netherlands Ministry of Foreign Affairs", "Current", "[Source](https://www.cbi.eu/market-information/apparel/swimwear/market-entry)"],
          ] },
          { type: "draft", text: "Final series name — Titans of Luxury, or The Provenance — and Episode 03's release date, before LP release." },
          { type: "links", items: [
            { label: "Media and podcast", to: "media" },
            { label: "Article library", to: "articles" },
          ] },
        ],
      },
      {
        id: "risks",
        toc: "Risks",
        kicker: "10 · Risks",
        title: "Price and unit cost, then tariffs, then the option.",
        blocks: [
          { type: "prose", text: "The risks that move the answer most are retail price and unit cost, then tariffs, then the option. Each row states what we know and what we will do about it." },
          { type: "risks", items: [
            { n: "01", title: "Retail price and unit cost", body: "A US$100 lower price or a US$15 higher cost raises the required US revenue by 51–62%.", follow: "The US price list and landed unit cost are the first diligence items. Retailer open-to-buy indications before signing." },
            { n: "02", title: "US tariffs on Brazilian origin", body: "25% Section 301 stacked on MFN since July 2026. A further 12.5% forced-labour action is reported. USTR says the duty stays until Brazil changes the practices cited. [9][10]", follow: "Landed-cost study by country of origin with customs counsel. A higher direct-to-consumer mix absorbs duty better than wholesale." },
            { n: "03", title: "Fit and returns", body: "Swim is hard to judge online, so returns and markdowns follow. [5] The case charges US$7.60 per online unit for returns and a 6% markdown dilution.", follow: "Measure the return rate in Year 1 before scaling paid acquisition." },
            { n: "04", title: "Production capacity", body: "The 10× case needs about 320,000 units a year. Aguaclara reports more than 5,000 pieces a month, about 60,000 a year. [22]", follow: "Capacity, lead times and the cost of a second production line are diligence items. Opposite seasons help: Rio's peak production precedes Brazil's summer, and the US peak follows it." },
            { n: "05", title: "Founder and succession", body: "Heritage houses are founder-led. The benchmark's succession is not settled.", follow: "Binding option terms on heirs. Incapacity provisions. A named creative successor within 24 months (Structural flags F-D1, F-D3)." },
            { n: "06", title: "Royalty withholding and transfer pricing", body: "There is no US–Brazil income tax treaty, so the royalty may bear 30% US withholding. Fund I sits on both sides of the royalty and the transfer price. [28]", follow: "Not modelled. Tax counsel and an arm's-length benchmarking study before signing (F-A2)." },
            { n: "07", title: "The option", body: "41% of 10× proceeds depend on it. Enforcement in Brazil can be slow.", follow: "Pledge over the owners' quotas, terms recorded in the articles, an arbitration seat (F-A4). Disclose in the PPM (F-E6)." },
            { n: "08", title: "Licence renewal before exit", body: "Renewal falls in Year 11. Exit falls in Year 13.", follow: "Fix the renewal step-up by formula in the original licence (F-C1). The case assumes one point." },
            { n: "09", title: "Fund term", body: "Year 13 is inside the first extension, which needs LPAC approval.", follow: "Align the option long-stop with the 12-year term and its extensions (F-E5)." },
            { n: "10", title: "Exit multiple", body: "Growth slows to about 4% by Year 13 on the 10× path. Strategic buyers usually buy brands one at a time.", follow: "Show 7× and 11× alongside 9×. Keep the documents flexible for a single-brand sale (F-F2)." },
            { n: "11", title: "Survivorship in the precedents", body: "Every precedent in Provenance is a success. We hold no data on Latin American swim houses that tried the US and failed.", follow: "A base-rate study of failed entries, as part of the fund's open base-rate workstream." },
            { n: "12", title: "Naming third parties", body: "This page names houses, and a Rio house in the landscape matches the anonymised benchmark's bands.", follow: "Counsel review against the seller's confidentiality terms before LP release." },
          ] },
        ],
      },
      {
        id: "assumptions",
        toc: "Assumptions",
        kicker: "11 · Assumptions",
        title: "What the GP gave us, and what we assumed.",
        blocks: [
          { type: "prose", text: "Given by the GP: entry in Year 1 and exit in Year 13; US$26m at entry plus a US$3m follow-on; a licence for US$1m cash plus 20% of HoldCo equity; a 10-year exclusive licence; US$500 average retail per piece and a US$60 cost basis; a 10× target; royalty paid in addition; the benchmark anonymised." },
          { type: "h3", text: "Assumed by us" },
          { type: "table", wide: true, columns: ["Assumption", "Value", "Basis"], rows: [
            ["US$60 is production cost; the HoldCo pays cost plus 15%", "US$69 transfer price", "Model v3 decision [28]"],
            ["US$500 is the retail list price", "Realised US$199.50 wholesale, US$470 direct", "Assumption. To test with retailers."],
            ["Freight, insurance and brokerage", "US$5 a piece", "Assumption"],
            ["Royalty", "6%, then 7% after renewal", "6% from Structural flag F-A2. Step-up is a placeholder."],
            ["Who holds the licensor's 20%", "The owners, outside the operating company", "Conservative. See the sensitivities."],
            ["Who receives the royalty", "The operating company", "As in Model v3 [28]"],
            ["Option terms", "Strike 6× entry EBITDA, +10% a year in reais; premium 5%; extension fee 1.5% a year from Year 6", "Model v3 decisions [28]"],
            ["Timing", "Entry at the start of Year 1; follow-on at the start of Year 3; exit at the end of Year 13", "Follow-on year assumed. It affects IRR, not MOIC."],
            ["Exchange rate", "R$5.15, weakening 3% a year", "Spot from Model v3. The path is a placeholder. [28]"],
            ["Exit", "Same multiple for HoldCo and operating company; 2% transaction costs; strike funded by the exit", "Fund model 7× / 9× / 11× [28]"],
          ] },
          { type: "prose", text: "Not modelled: management equity in the HoldCo, interim dividends, royalty withholding tax, and stores beyond what sales justify. Each of those would lower returns, except dividends." },
        ],
      },
      {
        id: "confirm",
        toc: "To confirm",
        kicker: "12 · Before LP release",
        title: "Open items.",
        blocks: [
          { type: "prose", text: "These are the decisions still in front of the GP and counsel. They are repeated next to the section they affect." },
          { type: "checks", items: [
            "Present 10× as the deal's base case or as its upside, given the fund's 2.8× net target.",
            "US$29m against the US$20–28m per-deal range in current materials.",
            "Who receives the licensor's 20% and the royalty.",
            "A management equity pool in the HoldCo, and its size.",
            "The licence renewal step-up formula.",
            "Commission a luxury-tier US market estimate, or keep the bottom-up approach.",
            "Counsel review of named third parties and the benchmark against the seller's confidentiality terms.",
            "Whether Argentine swim belongs in this vertical.",
            "A number for growing organically abroad.",
            "Podcast series name, and the Episode 03 release date.",
          ] },
        ],
      },
    ],
    sources: [
      "Global Market Insights, [Swimwear market](https://www.gminsights.com/industry-analysis/swimwear-market), figures as printed on the page.",
      "Fundamental Business Insights, [Swimwear market](https://www.fundamentalbusinessinsights.com/industry-report/swimwear-market-12526).",
      "Grand View Research, [Swimwear market](https://www.grandviewresearch.com/industry-analysis/swimwear-market).",
      "CBI, [Swimwear: market potential](https://www.cbi.eu/market-information/apparel/swimwear/market-potential), from Eurostat.",
      "CBI, [Swimwear: market entry](https://www.cbi.eu/market-information/apparel/swimwear/market-entry).",
      "ERES, [Aquarelle one-piece, US listing](https://www.eresparis.com/us/en-US/swimwear-2/011402-01062.html).",
      "Listed US prices: [Cupshe](https://www.cupshe.com/collections/one-piece-best-sellers-edm), [Target](https://www.target.com/p/-/A-94763482), [Andie](https://andieswim.com/products/the-amalfi-flat-black-full-coverage), [Summersalt](https://www.summersalt.com/products/sidestroke?variant=32699949744237).",
      "24S, [ERES listings, France](https://www.24s.com/fr-fr/femme/marques/eres/maillots-de-bain).",
      "Greenberg Traurig, [USTR announces Section 301 tariffs on Brazil](https://www.gtlaw.com/en/insights/2026/7/ustr-announces-section-301-tariffs-on-brazil-new-25-duties-to-take-effect-july-22), July 2026.",
      "Thompson Coburn, [Administration implements Section 301 tariffs on Brazil](https://www.thompsoncoburn.com/insights/85-july-29-2026-administration-implements-section-301-tariffs-on-brazil/), 29 July 2026.",
      "HTSUS [6112.41.00](https://open-gov.usebase.io/tariffs/us/61/6112.41.00), women's swimwear of synthetic fibres, general rate 24.9%.",
      "G-III Apparel Group, [acquisition of Vilebrequin](https://ir.giii.com/news-releases/news-release-details/g-iii-apparel-group-ltd-acquires-global-luxury-resort-brand), 7 August 2012.",
      "The Business of Fashion, [Chanel acquires Orlebar Brown](https://www.businessoffashion.com/articles/news-analysis/chanel-acquires-orlebar-brown), 2018.",
      "Titans of Luxury, [Episode 03: The House of ERES](https://titansofluxury.com/episodes/03-eres/), November 2026.",
      "Lenny Niemeyer, [About](https://www.lenny-europe.com/about-lenny), company site.",
      "Metrópoles, [Lenny Niemeyer opens in Brasília](https://www.metropoles.com/colunas/ilca-maria-estevao/lenny-niemeyer-desbrava-territorios-e-abre-nova-loja-em-brasilia), 14 April 2024.",
      "Forbes Brasil, [Liana Thomaz and Água de Coco](https://forbes.com.br/forbes-mulher/2025/06/como-liana-thomaz-criou-e-expandiu-a-grife-agua-de-coco/), June 2025.",
      "California Apparel News, [Cia Marítima's Benny Rosset](https://www.apparelnews.net/news/2013/aug/29/catching-cm-ciamaritimas-benny-rosset/), 29 August 2013.",
      "Elle Brasil, [RioFW 2026: BlueMan](https://elle.com.br/desfiles/riofw-2026-blueman), 17 April 2026.",
      "Modaes, [Agua Bendita: turnover to US$45m and a new CEO](https://www.modaes.com/global/companies/from-medellin-to-the-world-agua-bendita-boosts-turnover-to-45-million-and-appoints-new-ceo).",
      "Modaes, [Johanna Ortiz: from Cali to Madrid](https://www.modaes.com/global/back-stage/from-cali-to-madrid-johanna-ortiz-and-the-ambition-of-a-latin-american-luxury-brand).",
      "L'Officiel Monaco, [The rise of Aguaclara](https://lofficielmonaco.com/fashion/from-a-lima-basement-to-global-shores-the-rise-of-aguaclaras-distinctive-resort-aesthetic/).",
      "Times of San Diego, [Tulux Resortwear](https://timesofsandiego.com/business/2026/07/14/from-pacific-beach-to-miami-runways-tulux-resortwear/), 14 July 2026.",
      "Chic Magazine, [Sandmade](https://www.chicmagazine.com.mx/estilo-de-vida/sandmade-trajes-bano-artesanales-abrazan-belleza-femenina), 23 June 2021.",
      "Food & Pleasure, [Mexican swimwear brands](https://foodandpleasure.com/marcas-mexicanas-de-trajes-de-bano/), 24 June 2019.",
      "OnPost, [RHEA: The Label](https://onpost.shop/blogs/blog/sustainability-waves-discover-the-timeless-and-sustainable-swimwear-collection-from-rhea-the-label), 14 April 2023.",
      "Thingtesting, [Mita Mita](https://thingtesting.com/brands/mita-mita) and [Compañía del Sol](https://thingtesting.com/brands/compania-del-sol).",
      "FCP, Fund I financial model v3 build notes, decided changes and structural flags; Cucinelli benchmark notes, September 2026. Internal.",
      "Máquina do Esporte, [Flamengo and BlueMan licensing](https://maquinadoesporte.com.br/futebol/flamengo-reforca-estrategia-de-licenciamento-com-colecao-de-roupas-de-praia-com-a-blueman/), 5 April 2023.",
      "Dafiti, [Cia Marítima listings](https://www.dafiti.com.br/cia-maritima/?dir=desc), list prices before promotion, November 2026.",
      "Moda Bikini, [BlueMan Adele bikini](https://modabikini.com/en/bikinis/63-adele-black-bikini-blueman.html), list price before promotion.",
      "Women's Wear Daily, [Chanel buys Eres](https://wwd.com/fashion-news/fashion-features/feature/article-1113335-1785213/), Sarah Raper, 11 June 1997. Terms not disclosed. 1996 consolidated sales US$10.4m (FF60m); net profit 12% of sales, US$1.2m.",
      "ERES, [Peau Douce](https://www.eresparis.com/us/en-US/ourmaterials.html), house materials. The fabric dates from the house's beginnings.",
    ],
    footer: "This page forms part of the Folclore Latin Fund I data room and is provided to prospective limited partners under their confidentiality agreement. It is not an offer to sell or a solicitation of an offer to buy any interest in the fund. Figures are as reported by the cited sources; FCP analysis is labelled as such. The worked case is illustrative and reverse-engineered, not a forecast. Third-party companies are named for reference only; no relationship is implied.",
  },

};

/* Research overlays for thin core pages that are not yet full theses. */
const VERTICAL_RESEARCH = {
  fashion: {
    title: "How we find the houses",
    intro: "Brand discovery in fashion is systematic, not inbound. We cross public signals that show creative validation, export readiness, retail acceptance and ownership of marks — then narrow before any outreach. The sourcing areas below are what sits on our radar today.",
    radar: {
      title: "Sourcing areas · What's on our radar",
      weeks: {
        title: "Fashion Week Rosters (Creative Validation)",
        lead: "Official calendars and designer line-ups tell us who the market already treats as a house worth showing.",
        regions: [
          { region: "Brazil", body: "São Paulo Fashion Week (SPFW) and Minas Trend." },
          { region: "Mexico", body: "Mercedes-Benz Fashion Week México (MBFWMx)." },
          { region: "Colombia", body: "Colombiamoda and Bogotá Fashion Week (BFW)." },
          { region: "Argentina / Central America", body: "BAFWEEK (Buenos Aires), Mercedes-Benz Fashion Week Guatemala, and Nicaragua Diseña." },
        ],
      },
      signals: [
        { title: "US import records", body: "Customs filings show which Latin houses already ship finished goods into the United States — a first filter for export readiness and existing logistics." },
        { title: "Luxury retailer rosters", body: "Line sheets and door lists at international luxury retailers show who is already bought, not only shown — commercial validation beyond the runway." },
        { title: "Trade associations", body: "Membership and exhibition lists — ABIHPEC for beauty, IBGM for jewelry, Abicalçados for footwear — surface adjacent branded houses that sit beside apparel on the same sourcing map." },
        { title: "Trademark filings", body: "Domestic and foreign mark applications show who intends to protect a name abroad, and where — a proxy for international ambition before a conversation starts." },
      ],
    },
  },
};

const VERTICAL_ART = {
  fashion: "assets/verticals/fashion.png",
  "leather-goods": "assets/verticals/leather-goods.png",
  swimwear: "assets/verticals/swimwear.png",
  beauty: "assets/verticals/beauty.png",
  jewelry: "assets/verticals/jewelry.png",
  "spirits-and-wine": "assets/verticals/wine-spirits.png",
};

const VP_ROMAN = ["I", "II", "III", "IV", "V", "VI", "VII"];
const VP_SEASON_TONES = [
  "var(--sapphire-700)",
  "var(--sapphire-600)",
  "var(--sapphire-400)",
  "var(--gold-700)",
  "var(--gold-500)",
];

function vpJump(id) {
  const el = document.getElementById(id);
  if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
}

function Cite({ text, slug }) {
  const re = /(\[\d+\]|\[[^\]]+\]\([^)]+\)|\*\*[^*]+\*\*)/g;
  const parts = String(text == null ? "" : text).split(re);
  return parts.map((part, i) => {
    if (!part) return null;
    const cite = part.match(/^\[(\d+)\]$/);
    if (cite) {
      const id = slug + "-src-" + cite[1];
      return (
        <a key={i} href={"#" + id} className="vp-cite" onClick={(e) => { e.preventDefault(); vpJump(id); }}>
          {part}
        </a>
      );
    }
    const link = part.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
    if (link) {
      return (
        <a key={i} className="vp-ext" href={link[2]} target="_blank" rel="noopener noreferrer">
          {link[1]}
        </a>
      );
    }
    const bold = part.match(/^\*\*([^*]+)\*\*$/);
    if (bold) return <strong key={i}>{bold[1]}</strong>;
    return <span key={i}>{part}</span>;
  });
}

function DraftNote({ text }) {
  if (!text) return null;
  return <p className="vp-draft">To confirm · {text}</p>;
}

function Kicker({ children }) {
  return <div className="vp-kicker">{children}</div>;
}

const VP_L2 = 2;

function vpCanAccess(userLevel, required) {
  return Number(userLevel || 1) >= Number(required || 1);
}

function VpLevelGate({ t, accessLevel, required, children }) {
  if (vpCanAccess(accessLevel, required)) return children;
  const accessFn = window.accessCopy;
  const access = (typeof accessFn === "function" ? accessFn(t) : null)
    || (t && t.dataroom && t.dataroom.access)
    || {};
  const Mark = window.AccessLevelMark;
  const Action = window.AccessActionButton;
  return (
    <div className="vp-level-gate">
      {typeof Mark === "function" && <Mark level={required} />}
      {typeof Action === "function" && <Action level={required} access={access} />}
      {required === 2 && access.ndaNote && (
        <p className="vp-caption">{access.ndaNote}</p>
      )}
      {required === 2 && typeof window.NdaCopyLink === "function" && (
        <window.NdaCopyLink label={access.ndaCopyLink} />
      )}
    </div>
  );
}

function coreCategories(cats) {
  return cats.filter((c) => c.tier !== "secondary");
}

function formatCoreKicker(v, index, total) {
  const tpl = (v && v.coreKicker) || "Sector thesis · Core vertical {n} of {total}";
  const n = String(index + 1).padStart(2, "0");
  const tot = String(total).padStart(2, "0");
  return tpl.replace("{n}", n).replace("{total}", tot);
}

function pageFromCategory(cat, t) {
  const v = t.dataroom.verticals;
  const isCore = cat.tier !== "secondary";
  const core = coreCategories(v.categories);
  const coreIndex = core.findIndex((c) => c.id === cat.id);
  const worked = VERTICAL_PAGES[cat.id];

  if (worked) {
    return {
      ...worked,
      isCore,
      coreIndex,
      coreTotal: core.length,
      categoryName: cat.name,
      title: worked.title || cat.name,
      kicker: isCore ? formatCoreKicker(v, coreIndex, core.length) : ("Vertical · " + cat.name),
    };
  }

  const articles = typeof getArticlesForVertical === "function" ? getArticlesForVertical(cat.id) : [];
  const tier = isCore ? "Core vertical" : "Secondary vertical";

  if (!isCore) {
    return {
      slug: cat.id,
      isCore: false,
      title: cat.name,
      kicker: "Vertical · " + cat.name,
      lead: cat.blurb,
      meta: [
        { label: "Priority", value: tier },
        { label: "Universe tracked", value: "Not yet on file" },
        { label: "Vertical lead", value: "Not yet assigned" },
        { label: "Last reviewed", value: "—" },
      ],
      why: {
        kicker: "01 · Why this vertical",
        title: "What has to be true before a house in this category is in scope.",
        prose: cat.blurb,
        signalsTitle: v.signalsTitle,
        signals: v.signals,
        outTitle: v.outTitle,
        outNote: cat.outNote,
      },
      hypotheses: {
        kicker: "02 · Our hypotheses",
        title: "What we believe, and what would change our view.",
        empty: "Hypotheses for this vertical are not yet on file.",
      },
      benchmarks: {
        kicker: "03 · Benchmark studies",
        title: "The houses we measure against.",
        empty: "Benchmark studies for this vertical are not yet on file.",
      },
      gap: {
        kicker: "04 · The valuation gap",
        title: "Observed gap, explained gap, and the residual we underwrite.",
        empty: "A valuation-gap ledger for this vertical is not yet on file.",
      },
      companies: {
        kicker: "05 · Companies",
        title: "Pipeline under codename; the reference landscape in public view.",
        pipelineTitle: "Fund I pipeline · " + cat.name.toLowerCase(),
        pipelineNote: "Names and financials released under NDA on request",
        emptyTitle: "No " + cat.name.toLowerCase() + " pipeline disclosed",
        emptyBody: "When a vertical has no active opportunity, the page says so. An empty pipeline stated plainly is more credible than an illustrative one.",
      },
      media: {
        kicker: "06 · Podcast & media",
        title: "Titans of Luxury",
        empty: "No episode is filed against this vertical yet.",
      },
      research: {
        kicker: "07 · Research library",
        title: "Our work, and the work we rely on.",
        articles: articles,
        empty: articles.length ? null : v.emptyResearch,
      },
      risks: {
        kicker: "08 · Risks & open questions",
        title: "What could make this vertical a mistake.",
        empty: cat.outNote || "Open questions for this vertical are not yet on file.",
      },
      footer: "This page forms part of the Folclore Latin Fund I data room and is provided to prospective limited partners under the terms of their confidentiality agreement. It is not an offer to sell or a solicitation of an offer to buy any interest in the fund. Where a section is not yet on file, it is marked as such.",
    };
  }

  /* Thin core page — chrome without empty research theatre. */
  return {
    slug: cat.id,
    isCore: true,
    coreIndex,
    coreTotal: core.length,
    categoryName: cat.name,
    title: cat.name,
    kicker: formatCoreKicker(v, coreIndex, core.length),
    lead: cat.blurb,
    meta: [
      { label: "Priority", value: tier },
      { label: "Universe tracked", value: "Not yet on file" },
      { label: "Vertical lead", value: "Not yet assigned" },
      { label: "Last reviewed", value: "—" },
    ],
    why: {
      kicker: "01 · The thesis",
      title: "What has to be true before a house in this category is in scope.",
      prose: cat.blurb,
      signalsTitle: v.signalsTitle,
      signals: v.signals,
      outTitle: v.outTitle,
      outNote: cat.outNote,
    },
    benchmarks: cat.hasCase
      ? {
          kicker: "02 · Worked case",
          title: "The Lenny Niemeyer investment model.",
          empty: "The worked case for this vertical is the Lenny Niemeyer investment model.",
          caseCta: true,
        }
      : null,
    companies: {
      kicker: (cat.hasCase ? "03" : "02") + " · The landscape",
      title: "Pipeline under codename; the reference landscape in public view.",
      pipelineLink: true,
    },
    research: mergeVerticalResearch({
      kicker: (cat.hasCase ? "04" : "03") + " · Research library",
      title: "Our work, and the work we rely on.",
      articles: articles,
      empty: articles.length ? null : v.emptyResearch,
    }, cat.id),
    footer: "This page forms part of the Folclore Latin Fund I data room and is provided to prospective limited partners under the terms of their confidentiality agreement. It is not an offer to sell or a solicitation of an offer to buy any interest in the fund. Where a section is not yet on file, it is marked as such.",
  };
}

function mergeVerticalResearch(base, id) {
  const extra = VERTICAL_RESEARCH[id];
  if (!extra) return base;
  const hasRadar = !!(extra.intro || extra.radar);
  return {
    ...base,
    ...extra,
    articles: base.articles,
    empty: hasRadar || (base.articles && base.articles.length) ? null : base.empty,
  };
}

function buildVpToc(page, isCore, hasLibrary, benchmarksOpen) {
  if (page.sections && page.sections.length) {
    const toc = page.sections.map((s) => ({
      id: s.id,
      label: s.toc || s.title,
      locked: !!(s.gate && !benchmarksOpen),
    }));
    if (page.sources && page.sources.length) toc.push({ id: "sources", label: "Sources" });
    return toc;
  }
  const toc = [];
  const push = (id, label, locked) => toc.push({ id, label, locked: !!locked });
  const thesisFlow = !!(page.seasonality || page.provenance || page.valueCreation || page.criteria);
  const hasBenchmarks = page.benchmarks && (
    page.benchmarks.rows || page.benchmarks.study || page.benchmarks.caseCta || page.benchmarks.empty
  );
  const benchLabel = page.benchmarks && page.benchmarks.caseCta && !page.benchmarks.rows ? "Worked case" : "Benchmarks";
  const companiesLabel = isCore && page.companies && (page.companies.landscapeTable || page.companies.landscape)
    ? "Landscape"
    : "Companies";

  if (page.why) push("why", isCore ? "Thesis" : "Why");
  if (page.seasonality) push("market", "Market");
  if (page.provenance) push("provenance", "Provenance");

  if (thesisFlow) {
    if (page.companies) push("companies", companiesLabel);
    if (page.valueCreation) push("value", "Value creation");
    if (page.criteria) push("criteria", "Criteria");
    if (hasBenchmarks) push("benchmarks", benchLabel, !benchmarksOpen);
  } else {
    if (page.hypotheses && (page.hypotheses.items || (!isCore && page.hypotheses.empty))) push("hypotheses", "Hypotheses");
    if (hasBenchmarks) push("benchmarks", benchLabel, !benchmarksOpen);
    if (page.gap && (page.gap.precedent || page.gap.empty)) push("gap", "Valuation gap");
    if (page.companies) push("companies", companiesLabel);
  }

  if (page.media && (page.media.episode || (!isCore && page.media.empty))) push("media", "Podcast");
  if (page.research) push("research", "Research");
  if (page.risks && (page.risks.pairs || page.risks.items || (!isCore && page.risks.empty))) push("risks", "Risks");
  if (page.sources) push("sources", "Sources");
  return toc;
}

function vpScrollBox() {
  const main = document.querySelector(".dr-main");
  if (main && main.scrollHeight > main.clientHeight + 8) return main;
  return null;
}

function useVpActiveSection(slug, tocIds) {
  const [active, setActive] = React.useState(tocIds[0] || "");
  const lockRef = React.useRef(0);
  const idsKey = tocIds.join("|");

  React.useEffect(() => {
    if (!tocIds.length) return undefined;
    const root = document.querySelector(".dr-main");
    let raf = null;
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => {
        raf = null;
        if (Date.now() < lockRef.current) return;
        const box = vpScrollBox();
        const offset = 24;
        let cur = tocIds[0];
        const atEnd = box
          ? box.scrollTop + box.clientHeight >= box.scrollHeight - 4
          : window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 8;
        if (atEnd) cur = tocIds[tocIds.length - 1];
        else {
          for (const id of tocIds) {
            const el = document.getElementById(slug + "-" + id);
            if (!el) continue;
            const top = el.getBoundingClientRect().top;
            const base = box ? box.getBoundingClientRect().top : 0;
            if (top - base - offset <= 8) cur = id;
          }
        }
        setActive((prev) => (prev === cur ? prev : cur));
      });
    };
    if (root) root.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => {
      if (root) root.removeEventListener("scroll", onScroll);
      window.removeEventListener("scroll", onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [slug, idsKey]);

  const goTo = React.useCallback((id) => {
    lockRef.current = Date.now() + 900;
    setActive(id);
    const el = document.getElementById(slug + "-" + id);
    if (!el) return;
    const root = vpScrollBox();
    const offset = 24;
    if (root) {
      const top = el.getBoundingClientRect().top - root.getBoundingClientRect().top + root.scrollTop - offset + 8;
      root.scrollTo({ top: Math.max(0, top), behavior: "smooth" });
    } else {
      const top = el.getBoundingClientRect().top + window.scrollY - offset + 8;
      window.scrollTo({ top: Math.max(0, top), behavior: "smooth" });
    }
  }, [slug]);

  return [active, goTo];
}

function useVpViewport() {
  const [vw, setVw] = React.useState(() => (typeof window !== "undefined" ? window.innerWidth : 1440));
  React.useEffect(() => {
    const onResize = () => setVw(window.innerWidth);
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, []);
  return vw;
}

function VpNotchRail({ toc, active, onGo, showNames }) {
  const [hover, setHover] = React.useState(null);
  const Mark = window.AccessLevelMark;
  return (
    <nav className="rm-rail" aria-label="On this page">
      {toc.map((item, idx) => {
        const on = active === item.id;
        const hov = hover === item.id;
        const code = String(idx + 1).padStart(2, "0");
        return (
          <button
            key={item.id}
            type="button"
            className={"rm-rail-btn" + (on ? " active" : "")}
            aria-label={item.label}
            aria-current={on ? "true" : undefined}
            onClick={() => onGo(item.id)}
            onMouseEnter={() => setHover(item.id)}
            onMouseLeave={() => setHover(null)}
          >
            {showNames && (
              <span
                className="rm-rail-name"
                style={{ color: on ? "var(--gold-200)" : hov ? "var(--alabaster)" : "var(--sapphire-200)" }}
              >
                {item.label}
                {item.locked && typeof Mark === "function" && <Mark level={VP_L2} />}
              </span>
            )}
            {!showNames && hov && <span className="rm-rail-tip">{item.label}</span>}
            <span
              className="rm-rail-code"
              style={{ color: on ? "var(--gold-400)" : hov ? "var(--alabaster)" : "var(--sapphire-400)" }}
            >
              {code}
            </span>
            <span className="rm-rail-tick-wrap">
              <span
                className="rm-rail-tick"
                style={{
                  width: on ? 20 : hov ? 16 : 10,
                  background: on ? "var(--gold-500)" : hov ? "var(--alabaster)" : "var(--sapphire-400)",
                }}
              />
            </span>
          </button>
        );
      })}
    </nav>
  );
}

function VpSeasonality({ data, schematicCaption }) {
  if (!data) return null;
  const months = data.months || [];
  const names = data.levelNames || [];
  return (
    <figure className="vp-season">
      <div className="vp-season-head">
        <span className="vp-season-label">{data.figureLabel}</span>
        <div className="vp-season-legend" aria-hidden="true">
          <span>Low</span>
          <span className="vp-season-swatches">
            {VP_SEASON_TONES.map((bg, i) => <span key={i} style={{ background: bg }} />)}
          </span>
          <span>Peak</span>
        </div>
      </div>
      <div className="vp-season-grid">
        <div className="vp-season-row vp-season-months">
          <div className="vp-season-side" />
          <div className="vp-season-cells">
            {months.map((m) => <span key={m}>{m[0]}</span>)}
          </div>
        </div>
        {data.rows.map((row) => (
          <div key={row.label} className={"vp-season-row" + (row.emphasize ? " vp-season-row-em" : "")}>
            <div className="vp-season-side">
              <span className="vp-season-name">{row.label}</span>
              <span className="vp-caption">{row.sub}</span>
            </div>
            <div className="vp-season-cells">
              {row.levels.map((lv, i) => (
                <span
                  key={months[i] || i}
                  title={(months[i] || "") + " · " + (names[lv] || lv)}
                  style={{ background: VP_SEASON_TONES[lv] || VP_SEASON_TONES[0] }}
                />
              ))}
            </div>
          </div>
        ))}
      </div>
      {data.caption && <p className="vp-season-caption">{data.caption}</p>}
      {schematicCaption && <p className="vp-caption">{schematicCaption}</p>}
    </figure>
  );
}

function VpPillars({ pillars, claim, slug }) {
  return (
    <div className="vp-thesis">
      {claim && <blockquote className="vp-claim">{claim}</blockquote>}
      {pillars && pillars.length > 0 && (
        <div className="vp-pillars">
          {pillars.map((p, i) => (
            <article key={p.title}>
              <span className="vp-roman">{VP_ROMAN[i] || String(i + 1)}</span>
              <h3 className="vp-pillar-title">{p.title}</h3>
              <p><Cite text={p.body} slug={slug} /></p>
            </article>
          ))}
        </div>
      )}
    </div>
  );
}

function VpValueTrack({ data, labels }) {
  if (!data || !data.levers) return null;
  const years = ["Close", "Y1", "Y2", "Y3", "Y4", "Y5"];
  return (
    <div className="vp-value">
      <div className="vp-value-head">
        <span />
        <span>{(labels && labels.lever) || "Lever"}</span>
        <div className="vp-value-years">
          {years.map((y) => <span key={y}>{y}</span>)}
        </div>
      </div>
      {data.levers.map((lever, i) => {
        const startPct = (Number(lever.start) / 5) * 100;
        const spanPct = (Number(lever.span) / 5) * 100;
        return (
          <div key={lever.title} className="vp-value-row">
            <span className="vp-roman">{i + 1}</span>
            <div className="vp-value-copy">
              <span className="vp-pillar-title">{lever.title}</span>
              <p>{lever.body}</p>
            </div>
            <div className="vp-value-track-col">
              <div className="vp-value-track">
                <span className="vp-value-bar" style={{ left: startPct + "%", width: spanPct + "%" }} />
              </div>
              <span className="vp-caption">{lever.note}</span>
            </div>
          </div>
        );
      })}
      <div className="vp-value-foot">
        {data.hold && (
          <div>
            <span className="vp-stat-label">{(labels && labels.hold) || "Hold"}</span>
            <span className="vp-value-foot-text">{data.hold}</span>
          </div>
        )}
        {data.buyers && (
          <div>
            <span className="vp-stat-label">{(labels && labels.buyers) || "Natural buyers"}</span>
            <span className="vp-value-foot-text">{data.buyers}</span>
          </div>
        )}
      </div>
    </div>
  );
}

function VpCorePager({ cats, coreIndex, navigate, labels }) {
  if (coreIndex < 0) return null;
  const prev = coreIndex > 0 ? cats[coreIndex - 1] : null;
  const next = coreIndex < cats.length - 1 ? cats[coreIndex + 1] : null;
  if (!prev && !next) return null;
  return (
    <nav className="vp-pager" aria-label="Core verticals">
      {prev ? (
        <button type="button" className="vp-pager-card" onClick={() => navigate("verticals", { vertical: prev.id })}>
          <span className="vp-caption">← {(labels && labels.previous) || "Previous"} · {String(coreIndex).padStart(2, "0")}</span>
          <span className="vp-pager-name">{prev.name}</span>
        </button>
      ) : <span />}
      {next ? (
        <button type="button" className="vp-pager-card vp-pager-next" onClick={() => navigate("verticals", { vertical: next.id })}>
          <span className="vp-caption">{(labels && labels.next) || "Next"} · {String(coreIndex + 2).padStart(2, "0")} →</span>
          <span className="vp-pager-name">{next.name}</span>
        </button>
      ) : <span />}
    </nav>
  );
}

function VerticalIndex({ t, navigate }) {
  const d = t.dataroom.verticals;
  const cats = d.categories;
  const core = cats.filter((c) => c.tier !== "secondary");
  const secondary = cats.filter((c) => c.tier === "secondary");
  const indexArticles = typeof getArticlesForVertical === "function"
    ? getArticlesForVertical("index")
    : [];
  const group = (label, list) => (
    <div className="vp-index-group">
      <div className="vp-kicker">{label}</div>
      <div className="vp-index-grid">
        {list.map((c) => {
          const art = VERTICAL_ART[c.id];
          const deferred = c.tier === "secondary" && typeof isDeferredVertical === "function" && isDeferredVertical(c.id);
          return (
            <button key={c.id} type="button" className={"vp-index-card" + (deferred ? " is-deferred" : "")} onClick={() => navigate("verticals", { vertical: c.id })}>
              {art && (
                <span className="vp-index-art">
                  <img src={art} alt="" />
                </span>
              )}
              <span className="vp-index-name">{c.name}</span>
              <span className="vp-index-blurb">{c.blurb}</span>
            </button>
          );
        })}
      </div>
    </div>
  );
  return (
    <article className="vp">
      <div className="vp-wrap">
        <header className="vp-hero">
          <Kicker>{d.eyebrow}</Kicker>
          <h1 className="vp-title">{d.title}</h1>
          <p className="vp-lead">{d.intro}</p>
        </header>
        {group("Verticals · core", core)}
        {secondary.length > 0 && group("Verticals · secondary", secondary)}
        <section className="vp-section">
          <Kicker>{d.indexResearchTitle}</Kicker>
          <ul className="vp-link-list">
            {indexArticles.map((a) => (
              <li key={a.id}>
                <button type="button" onClick={() => navigate("articles", { article: a.id })}>
                  <span>{a.title}</span>
                  <span className="vp-meta-note">{a.source} · {a.date}</span>
                </button>
              </li>
            ))}
          </ul>
        </section>
      </div>
    </article>
  );
}

function VpDataTable({ table, slug }) {
  return (
    <div className="vp-table-wrap">
      <table className={"vp-table" + (table.wide ? " vp-table-wide" : "")}>
        {table.caption && <caption>{table.caption}</caption>}
        <thead>
          <tr>{table.columns.map((c) => <th key={c}>{c}</th>)}</tr>
        </thead>
        <tbody>
          {table.rows.map((row, ri) => {
            const em = table.emphasis && table.emphasis.indexOf(ri) !== -1;
            return (
              <tr key={ri} className={em ? "is-em" : undefined}>
                {row.map((cell, ci) => {
                  const content = <Cite text={cell} slug={slug} />;
                  if (ci === 0) {
                    return (
                      <th key={ci} scope="row">
                        {table.house
                          ? <span className="vp-house">{content}</span>
                          : <span className="vp-rowhead">{content}</span>}
                      </th>
                    );
                  }
                  return <td key={ci}>{content}</td>;
                })}
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

function VpScale({ data, slug }) {
  const max = data.max || 1;
  return (
    <figure className="vp-scale">
      {data.label && <span className="vp-season-label">{data.label}</span>}
      {data.items.map((item, i) => (
        item.group ? (
          <div key={i} className="vp-scale-group">{item.group}</div>
        ) : (
          <div key={i} className={"vp-scale-row" + (item.mark ? " is-mark" : "")}>
            <span className="vp-scale-name">{item.name}</span>
            <span className="vp-scale-track" aria-hidden="true">
              <span style={{ width: Math.max(2, (item.value / max) * 100) + "%" }} />
            </span>
            <span className="vp-scale-val">{item.display}</span>
          </div>
        )
      ))}
      {data.caption && <p className="vp-season-caption"><Cite text={data.caption} slug={slug} /></p>}
    </figure>
  );
}

function VpMatrix({ data, slug }) {
  const cols = data.columns.length;
  return (
    <figure className="vp-matrix-fig" aria-label={data.label || "Matrix"}>
      {data.label && <span className="vp-season-label">{data.label}</span>}
      <div className="vp-table-wrap">
        <div
          className="vp-matrix"
          style={{ gridTemplateColumns: "112px repeat(" + cols + ", minmax(150px, 1fr))" }}
        >
          <div />
          {data.columns.map((c) => <div key={c} className="vp-matrix-col">{c}</div>)}
          {data.rows.map((row) => (
            <React.Fragment key={row.label}>
              <div className="vp-matrix-side">
                <span className="vp-matrix-heat">{row.label}</span>
                {row.sub && <span className="vp-caption">{row.sub}</span>}
              </div>
              {row.cells.map((cell, i) => (
                <div key={row.label + "-" + i} className={"vp-matrix-cell" + (row.shade && row.shade[i] ? " is-shade" : "")}>
                  {cell.map((h) => (
                    <span key={h.name} className="vp-matrix-house">
                      {h.name}
                      {h.note && <span className="vp-matrix-note">{h.note}</span>}
                    </span>
                  ))}
                </div>
              ))}
            </React.Fragment>
          ))}
        </div>
      </div>
      {data.caption && <p className="vp-season-caption"><Cite text={data.caption} slug={slug} /></p>}
      {data.aside && <p className="vp-caption"><Cite text={data.aside} slug={slug} /></p>}
    </figure>
  );
}

function VpBlocks({ blocks, slug, navigate, v }) {
  if (!blocks) return null;
  return blocks.map((block, i) => {
    if (block.type === "prose") {
      return <p key={i} className="vp-prose"><Cite text={block.text} slug={slug} /></p>;
    }
    if (block.type === "claim") {
      return <blockquote key={i} className="vp-claim"><Cite text={block.text} slug={slug} /></blockquote>;
    }
    if (block.type === "caption") {
      return <p key={i} className="vp-note"><Cite text={block.text} slug={slug} /></p>;
    }
    if (block.type === "h3") {
      return <h3 key={i} className="vp-h3">{block.text}</h3>;
    }
    if (block.type === "draft") {
      return <DraftNote key={i} text={block.text} />;
    }
    if (block.type === "bullets") {
      const Tag = block.ordered ? "ol" : "ul";
      return (
        <Tag key={i} className={block.ordered ? "vp-ol" : "vp-points"}>
          {block.items.map((item, j) => (
            <li key={j}><Cite text={item} slug={slug} /></li>
          ))}
        </Tag>
      );
    }
    if (block.type === "table") return <VpDataTable key={i} table={block} slug={slug} />;
    if (block.type === "scale") return <VpScale key={i} data={block} slug={slug} />;
    if (block.type === "matrix") return <VpMatrix key={i} data={block} slug={slug} />;
    if (block.type === "stats") {
      return (
        <div key={i} className="vp-stats" aria-label={block.label || "Figures"}>
          {block.items.map((f) => (
            <div key={f.label}>
              <span className="vp-stat-value">{f.value}</span>
              <span className="vp-stat-label">{f.label}</span>
              {f.note && <span className="vp-stat-note"><Cite text={f.note} slug={slug} /></span>}
            </div>
          ))}
        </div>
      );
    }
    if (block.type === "facts") {
      return (
        <div key={i} className="vp-facts">
          {block.items.map((f) => (
            <article key={f.title}>
              <h3>{f.title}</h3>
              <p><Cite text={f.body} slug={slug} /></p>
            </article>
          ))}
        </div>
      );
    }
    if (block.type === "hypos") {
      return (
        <div key={i} className="vp-hypos">
          {block.items.map((h) => (
            <article key={h.id}>
              <header>
                <h3>{h.id} · {h.title}</h3>
                <span className="vp-status">{h.status}</span>
              </header>
              <p><span className="vp-field">Claim</span> <Cite text={h.claim} slug={slug} /></p>
              <p><span className="vp-field">Evidence on file</span> <Cite text={h.evidence} slug={slug} /></p>
              <p><span className="vp-field">What would change our view</span> <Cite text={h.change} slug={slug} /></p>
            </article>
          ))}
        </div>
      );
    }
    if (block.type === "risks") {
      return (
        <ol key={i} className="vp-risks">
          {block.items.map((r) => (
            <li key={r.n}>
              <h3><span className="vp-num">{r.n}</span> {r.title}</h3>
              <p><Cite text={r.body} slug={slug} /></p>
              <p><span className="vp-field">{r.followLabel || "Response"}</span> <Cite text={r.follow} slug={slug} /></p>
            </li>
          ))}
        </ol>
      );
    }
    if (block.type === "checks") {
      return (
        <ul key={i} className="vp-checks">
          {block.items.map((item, j) => (
            <li key={j}><Cite text={item} slug={slug} /></li>
          ))}
        </ul>
      );
    }
    if (block.type === "episode") {
      return (
        <article key={i} className="vp-episode">
          <div className="vp-episode-mark">
            <span>{block.series || "Titans of Luxury"}</span>
            <span className="vp-num">{block.number}</span>
          </div>
          <div>
            <p className="vp-caption">{block.meta}</p>
            <h3>{block.title}</h3>
            <p className="vp-prose"><Cite text={block.body} slug={slug} /></p>
            {block.why && (
              <p className="vp-prose"><span className="vp-field">Why it is on this page</span> <Cite text={block.why} slug={slug} /></p>
            )}
            {block.href && (
              <a className="vp-text-btn" href={block.href} target="_blank" rel="noopener noreferrer">Episode page</a>
            )}
          </div>
        </article>
      );
    }
    if (block.type === "pipeline") {
      return (
        <div key={i} className="vp-pipeline-link">
          <p className="vp-prose">{block.text || (v && v.pipelineLink)}</p>
          <button type="button" className="vp-text-btn" onClick={() => navigate("scenarios")}>
            {(v && v.pipelineCta) || "Open the Pipeline room"}
          </button>
        </div>
      );
    }
    if (block.type === "links") {
      return (
        <div key={i} className="vp-link-row">
          {block.items.map((item) => (
            <button key={item.to} type="button" className="vp-text-btn" onClick={() => navigate(item.to)}>
              {item.label}
            </button>
          ))}
        </div>
      );
    }
    return null;
  });
}

function VpSections({ page, slug, t, navigate, accessLevel }) {
  const v = t.dataroom.verticals;
  return page.sections.map((section) => {
    const blocks = (
      <VpBlocks blocks={section.blocks} slug={slug} navigate={navigate} v={v} />
    );
    return (
      <section key={section.id} id={slug + "-" + section.id} className="vp-section">
        <Kicker>{section.kicker}</Kicker>
        <h2 className="vp-h2">{section.title}</h2>
        {section.gate ? (
          <VpLevelGate t={t} accessLevel={accessLevel} required={section.gate}>{blocks}</VpLevelGate>
        ) : blocks}
      </section>
    );
  });
}

function VerticalPage({ t, navigate, verticalSlug, accessLevel }) {
  const v = t.dataroom.verticals;
  const cats = v.categories;
  const active = cats.find((c) => c.id === verticalSlug) || null;
  if (!active) return <VerticalIndex t={t} navigate={navigate} />;

  const page = pageFromCategory(active, t);
  const slug = page.slug;
  const isCore = !!page.isCore;
  const art = VERTICAL_ART[slug] || null;
  const level = Number(accessLevel || 1);
  const benchmarksOpen = vpCanAccess(level, VP_L2);
  const libraryArticles = (page.research && page.research.articles) || (
    typeof getArticlesForVertical === "function" ? getArticlesForVertical(slug) : []
  );
  const hasLibrary = libraryArticles.length > 0;
  const thesisFlow = !!(page.seasonality || page.provenance || page.valueCreation || page.criteria);
  const toc = buildVpToc(page, isCore, hasLibrary, benchmarksOpen);
  const tocIds = toc.map((item) => item.id);
  const [activeSection, goToSection] = useVpActiveSection(slug, tocIds);
  const vw = useVpViewport();
  const showRail = vw >= 900 && toc.length > 0;
  const showNames = vw >= 1280;
  const core = coreCategories(cats);
  const caseCtaLabel = v.caseCta || "Open Lenny Niemeyer model";

  const companiesSection = page.companies ? (
    <section id={slug + "-companies"} className="vp-section">
      <Kicker>{page.companies.kicker}</Kicker>
      <h2 className="vp-h2">{page.companies.title}</h2>
      {page.companies.intro && <p className="vp-prose">{page.companies.intro}</p>}

      {page.companies.pipelineLink && isCore ? (
        <div className="vp-pipeline-link">
          <p className="vp-prose">{v.pipelineLink}</p>
          <button type="button" className="vp-text-btn" onClick={() => navigate("scenarios")}>{v.pipelineCta}</button>
        </div>
      ) : page.companies.pipelineTitle ? (
        <div className="vp-pipeline">
          <h3 className="vp-h3">{page.companies.pipelineTitle}</h3>
          <VpLevelGate t={t} accessLevel={level} required={VP_L2}>
            <p className="vp-caption">{page.companies.pipelineNote}</p>
            <div className="vp-empty-card">
              <strong>{page.companies.emptyTitle}</strong>
              <p>{page.companies.emptyBody}</p>
            </div>
            <DraftNote text={page.companies.draft} />
          </VpLevelGate>
        </div>
      ) : null}

      {page.companies.landscapeTable && (
        <div className="vp-table-wrap">
          <p className="vp-caption">{v.landscapePublicNote}</p>
          <table className="vp-table">
            <thead>
              <tr>
                {(v.landscapeHeaders || ["House", "Origin", "Segment · Tier", "Why it matters"]).map((h) => (
                  <th key={h}>{h}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {page.companies.landscapeTable.map((row) => (
                <tr key={row.name}>
                  <th scope="row"><span className="vp-house">{row.name}</span></th>
                  <td>{row.origin}</td>
                  <td>{row.segment}</td>
                  <td>{row.why}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}

      {page.companies.landscape && (
        <div>
          <h3 className="vp-h3">{page.companies.landscapeTitle}</h3>
          <p className="vp-caption">{page.companies.landscapeNote}</p>
          <div className="vp-facts">
            {page.companies.landscape.map((c) => (
              <article key={c.name}>
                <span className="vp-caption">{c.place}</span>
                <h3>{c.name}</h3>
                <p><Cite text={c.body} slug={slug} /></p>
                <p><span className="vp-field">Why it matters</span> {c.why}</p>
              </article>
            ))}
          </div>
          <DraftNote text={page.companies.landscapeDraft} />
        </div>
      )}
    </section>
  ) : null;

  const benchmarksSection = page.benchmarks ? (
    <section id={slug + "-benchmarks"} className="vp-section">
      <Kicker>{page.benchmarks.kicker}</Kicker>
      <h2 className="vp-h2">{page.benchmarks.title}</h2>
      <VpLevelGate t={t} accessLevel={level} required={VP_L2}>
        {page.benchmarks.intro && <p className="vp-prose">{page.benchmarks.intro}</p>}
        {page.benchmarks.empty && <p className="vp-empty">{page.benchmarks.empty}</p>}
        {(page.benchmarks.caseCta === true || page.benchmarks.caseCta) && (
          <button type="button" className="vp-text-btn" onClick={() => navigate("models")}>
            {page.benchmarks.caseCta === true ? caseCtaLabel : page.benchmarks.caseCta}
          </button>
        )}
        {page.benchmarks.rows && (
          <div className="vp-table-wrap">
            <table className="vp-table">
              <thead>
                <tr>{page.benchmarks.columns.map((c) => <th key={c}>{c}</th>)}</tr>
              </thead>
              <tbody>
                {page.benchmarks.rows.map((row) => (
                  <tr key={row[0]}>
                    <th scope="row">
                      <span className="vp-house">{row[0]}</span>
                      <span className="vp-house-meta">{row[1]}</span>
                    </th>
                    <td><Cite text={row[2]} slug={slug} /></td>
                    <td><Cite text={row[3]} slug={slug} /></td>
                    <td><Cite text={row[4]} slug={slug} /></td>
                    <td><Cite text={row[5]} slug={slug} /></td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )}
        {page.benchmarks.study && (
          <div className="vp-study">
            <Kicker>{page.benchmarks.study.kicker}</Kicker>
            <h3 className="vp-h2">{page.benchmarks.study.title}</h3>
            <p className="vp-prose">{page.benchmarks.study.body}</p>
            <div className="vp-series">
              {page.benchmarks.study.series.map((s) => (
                <div key={s.label}>
                  <span className="vp-stat-value">{s.value}</span>
                  <span className="vp-stat-label">{s.label}</span>
                </div>
              ))}
            </div>
            <p className="vp-caption"><Cite text={page.benchmarks.study.seriesLabel} slug={slug} /></p>
            <dl className="vp-meta">
              {page.benchmarks.study.stats.map((s) => (
                <div key={s.label}>
                  <dt>{s.label}</dt>
                  <dd>{s.value}</dd>
                  <dd className="vp-meta-note">{s.note}</dd>
                </div>
              ))}
            </dl>
            <p className="vp-prose"><span className="vp-field">What it tells us</span> {page.benchmarks.study.tells}</p>
            <p className="vp-prose"><span className="vp-field">Where the analogy breaks</span> {page.benchmarks.study.breaks}</p>
            <DraftNote text={page.benchmarks.study.draft} />
          </div>
        )}
      </VpLevelGate>
    </section>
  ) : null;

  const tocNav = (variant) => (
    <nav className={variant === "rail" ? "vp-rail" : "vp-toc"} aria-label="On this page">
      {variant === "rail" && <span className="vp-rail-label">{v.contentsLabel || "Contents"}</span>}
      {toc.map((item, idx) => {
        const Mark = window.AccessLevelMark;
        const on = activeSection === item.id;
        if (variant === "rail") {
          return (
            <button
              key={item.id}
              type="button"
              className={"vp-rail-item" + (on ? " is-active" : "")}
              onClick={() => goToSection(item.id)}
            >
              <span className="vp-rail-code">{String(idx + 1).padStart(2, "0")}</span>
              <span>{item.label}</span>
              {item.locked && typeof Mark === "function" && <Mark level={VP_L2} />}
            </button>
          );
        }
        return (
          <a
            key={item.id}
            href={"#" + slug + "-" + item.id}
            onClick={(e) => { e.preventDefault(); goToSection(item.id); }}
          >
            {item.label}
            {item.locked && typeof Mark === "function" && <Mark level={VP_L2} />}
          </a>
        );
      })}
    </nav>
  );

  const body = (
    <>
      <header className={art ? "vp-hero vp-hero-art" : "vp-hero"}>
        <div className="vp-hero-copy">
          <Kicker>{page.kicker}</Kicker>
          <h1 className="vp-title">{page.title}</h1>
          {isCore && page.dek && <p className="vp-dek">{page.dek}</p>}
          <DraftNote text={page.draft} />
          <p className="vp-lead">{page.lead}</p>
        </div>
        {art && (
          <figure className="vp-art">
            <img src={art} alt="" />
          </figure>
        )}
        <dl className="vp-meta">
          {page.meta.map((m) => (
            <div key={m.label}>
              <dt>{m.label}</dt>
              <dd>{m.value}</dd>
            </div>
          ))}
        </dl>
        {!showRail && tocNav("inline")}
      </header>

      {(page.figures || page.glance) && (
        <div className="vp-at-a-glance">
          {page.figures && (
            <section className="vp-stats" aria-label="Key figures">
              {page.figures.map((f) => (
                <div key={f.label}>
                  <span className="vp-stat-value">{f.value}</span>
                  <span className="vp-stat-label">{f.label}</span>
                  <span className="vp-stat-note"><Cite text={f.note} slug={slug} /></span>
                </div>
              ))}
            </section>
          )}
          {page.glance && (
            <dl className="vp-glance">
              {page.glance.map((row) => (
                <div key={row.label}>
                  <dt>{row.label}</dt>
                  <dd><Cite text={row.value} slug={slug} /></dd>
                </div>
              ))}
            </dl>
          )}
        </div>
      )}

      {page.sections && (
        <VpSections page={page} slug={slug} t={t} navigate={navigate} accessLevel={level} />
      )}

      {!page.sections && page.why && (
        <section id={slug + "-why"} className="vp-section">
          <Kicker>{page.why.kicker}</Kicker>
          <h2 className="vp-h2">{page.why.title}</h2>
          {(page.why.pillars || page.why.claim) && (
            <VpPillars pillars={page.why.pillars} claim={page.why.claim} slug={slug} />
          )}
          {page.why.facts && !page.why.pillars && (
            <div className="vp-facts">
              {page.why.facts.map((f) => (
                <article key={f.n}>
                  <span className="vp-num">{f.n}</span>
                  <h3>{f.title}</h3>
                  <p><Cite text={f.body} slug={slug} /></p>
                </article>
              ))}
            </div>
          )}
          {page.why.prose && <p className="vp-prose">{page.why.prose}</p>}
          {page.why.signals && (
            <div className="vp-split">
              <div>
                <h3 className="vp-h3">{page.why.signalsTitle}</h3>
                <ul className="vp-bullets">{page.why.signals.map((s) => <li key={s}>{s}</li>)}</ul>
              </div>
              {page.why.outNote && (
                <div>
                  <h3 className="vp-h3">{page.why.outTitle}</h3>
                  <p className="vp-prose">{page.why.outNote}</p>
                </div>
              )}
            </div>
          )}
          <DraftNote text={page.why.draft} />
        </section>
      )}

      {page.seasonality && (
        <section id={slug + "-market"} className="vp-section">
          <Kicker>{page.seasonality.kicker}</Kicker>
          <h2 className="vp-h2">{page.seasonality.title}</h2>
          {page.seasonality.intro && <p className="vp-prose">{page.seasonality.intro}</p>}
          <VpSeasonality data={page.seasonality} schematicCaption={v.schematicCaption} />
        </section>
      )}

      {page.provenance && (
        <section id={slug + "-provenance"} className="vp-section">
          <Kicker>{page.provenance.kicker}</Kicker>
          <h2 className="vp-h2">{page.provenance.title}</h2>
          <div className="vp-places">
            {page.provenance.places.map((p) => (
              <article key={p.city}>
                <span className="vp-stat-label">{p.country}</span>
                <h3 className="vp-place-city">{p.city}</h3>
                <p>{p.body}</p>
              </article>
            ))}
          </div>
        </section>
      )}

      {thesisFlow && companiesSection}

      {page.hypotheses && (page.hypotheses.items || (!isCore && page.hypotheses.empty)) && (
        <section id={slug + "-hypotheses"} className="vp-section">
          <Kicker>{page.hypotheses.kicker}</Kicker>
          <h2 className="vp-h2">{page.hypotheses.title}</h2>
          {page.hypotheses.intro && <p className="vp-prose">{page.hypotheses.intro}</p>}
          <DraftNote text={page.hypotheses.draft} />
          {page.hypotheses.empty && <p className="vp-empty">{page.hypotheses.empty}</p>}
          {page.hypotheses.items && (
            <div className="vp-hypos">
              {page.hypotheses.items.map((h) => (
                <article key={h.id}>
                  <header>
                    <h3>{h.id} · {h.title}</h3>
                    <span className="vp-status">{h.status}</span>
                  </header>
                  <p><span className="vp-field">Claim</span> <Cite text={h.claim} slug={slug} /></p>
                  <p><span className="vp-field">Evidence on file</span> <Cite text={h.evidence} slug={slug} /></p>
                  <p><span className="vp-field">What would change our view</span> <Cite text={h.change} slug={slug} /></p>
                  {h.implication && <p><span className="vp-field">Implication for structure</span> <Cite text={h.implication} slug={slug} /></p>}
                </article>
              ))}
            </div>
          )}
        </section>
      )}

      {!thesisFlow && benchmarksSection}

      {page.gap && (page.gap.precedent || page.gap.empty) && (
        <section id={slug + "-gap"} className="vp-section">
          <Kicker>{page.gap.kicker}</Kicker>
          <h2 className="vp-h2">{page.gap.title}</h2>
          {page.gap.intro && <p className="vp-prose">{page.gap.intro}</p>}
          {page.gap.empty && <p className="vp-empty">{page.gap.empty}</p>}
          {page.gap.precedent && (
            <div className="vp-gap">
              <div>
                <span className="vp-stat-label">{page.gap.precedent.label}</span>
                <span className="vp-gap-note">{page.gap.precedent.note}</span>
                <span className="vp-stat-value"><Cite text={page.gap.precedent.value} slug={slug} /></span>
              </div>
              <div>
                <span className="vp-stat-label">{page.gap.listed.label}</span>
                <span className="vp-gap-note">{page.gap.listed.note}</span>
                <span className="vp-stat-value">{page.gap.listed.value}</span>
              </div>
              <div>
                <span className="vp-stat-label">{page.gap.observed.label}</span>
                <span className="vp-stat-value">{page.gap.observed.value}</span>
              </div>
            </div>
          )}
          {page.gap.deductions && (
            <table className="vp-table vp-ledger">
              <caption>Less: legitimate sources of discount</caption>
              <tbody>
                {page.gap.deductions.map((row) => (
                  <tr key={row[0]}>
                    <th scope="row">{row[0]}</th>
                    <td>{row[1]}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
          {page.gap.position && <p className="vp-prose"><span className="vp-field">Our position</span> <Cite text={page.gap.position} slug={slug} /></p>}
          <DraftNote text={page.gap.draft} />
          {slug === "jewelry" && (
            <button type="button" className="vp-text-btn" onClick={() => navigate("returns")}>Open the fund model</button>
          )}
        </section>
      )}

      {page.valueCreation && (
        <section id={slug + "-value"} className="vp-section">
          <Kicker>{page.valueCreation.kicker}</Kicker>
          <h2 className="vp-h2">{page.valueCreation.title}</h2>
          <VpValueTrack
            data={page.valueCreation}
            labels={{ hold: v.holdLabel, buyers: v.buyersLabel, lever: (v.leverHeaders && v.leverHeaders[0]) || "Lever" }}
          />
        </section>
      )}

      {page.criteria && (
        <section id={slug + "-criteria"} className="vp-section">
          <Kicker>{page.criteria.kicker}</Kicker>
          <h2 className="vp-h2">{page.criteria.title}</h2>
          <div className="vp-table-wrap">
            <table className="vp-table">
              <thead>
                <tr>
                  {(v.criteriaHeaders || ["Criterion", "Threshold", "Why"]).map((h) => <th key={h}>{h}</th>)}
                </tr>
              </thead>
              <tbody>
                {page.criteria.rows.map((row) => (
                  <tr key={row.criterion}>
                    <th scope="row"><span className="vp-house">{row.criterion}</span></th>
                    <td className="vp-threshold">{row.threshold}</td>
                    <td className="vp-why-italic">{row.why}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </section>
      )}

      {thesisFlow && benchmarksSection}
      {!thesisFlow && companiesSection}

      {page.media && (page.media.episode || (!isCore && page.media.empty)) && (
        <section id={slug + "-media"} className="vp-section">
          <Kicker>{page.media.kicker}</Kicker>
          <h2 className="vp-h2">{page.media.title}</h2>
          {page.media.empty && <p className="vp-empty">{page.media.empty}</p>}
          {page.media.episode && (
            <article className="vp-episode">
              <div className="vp-episode-mark">
                <span>Titans of Luxury</span>
                <span className="vp-num">{page.media.episode.number}</span>
              </div>
              <div>
                <p className="vp-caption">{page.media.episode.meta}</p>
                <h3>{page.media.episode.title}</h3>
                <p className="vp-prose">{page.media.episode.body}</p>
                <p className="vp-prose"><span className="vp-field">Why it is on this page</span> {page.media.episode.why}</p>
                <a className="vp-text-btn" href={PODCAST_URL} target="_blank" rel="noopener noreferrer">Listen</a>
              </div>
            </article>
          )}
          {page.media.next && <p className="vp-caption">{page.media.next}</p>}
          <DraftNote text={page.media.draft} />
          <button type="button" className="vp-text-btn" onClick={() => navigate("media")}>Media & podcast</button>
        </section>
      )}

      {page.research && (
        <section id={slug + "-research"} className="vp-section">
          <Kicker>{page.research.kicker}</Kicker>
          <h2 className="vp-h2">{page.research.title}</h2>
          {page.research.intro && <p className="vp-prose">{page.research.intro}</p>}
          {page.research.radar && (
            <div className="vp-radar">
              <h3 className="vp-h3">{page.research.radar.title}</h3>
              {page.research.radar.weeks && (
                <div className="vp-radar-block">
                  <h4 className="vp-radar-label">{page.research.radar.weeks.title}</h4>
                  {page.research.radar.weeks.lead && (
                    <p className="vp-caption">{page.research.radar.weeks.lead}</p>
                  )}
                  <div className="vp-places">
                    {page.research.radar.weeks.regions.map((r) => (
                      <article key={r.region}>
                        <p className="vp-place-city">{r.region}</p>
                        <p>{r.body}</p>
                      </article>
                    ))}
                  </div>
                </div>
              )}
              {page.research.radar.signals && (
                <div className="vp-facts vp-radar-signals">
                  {page.research.radar.signals.map((s) => (
                    <article key={s.title}>
                      <h3>{s.title}</h3>
                      <p>{s.body}</p>
                    </article>
                  ))}
                </div>
              )}
            </div>
          )}
          {page.research.rows && (
            <div className="vp-table-wrap">
              <table className="vp-table">
                <thead>
                  <tr>{page.research.columns.map((c) => <th key={c}>{c}</th>)}</tr>
                </thead>
                <tbody>
                  {page.research.rows.map((row) => (
                    <tr key={row[0]}>
                      <th scope="row">{row[0]}</th>
                      <td>{row[1]}</td>
                      <td>{row[2]}</td>
                      <td>{row[3]}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}
          {page.research.empty && !page.research.rows && !page.research.radar && (
            <p className="vp-empty">{page.research.empty}</p>
          )}
          {libraryArticles.length > 0 && (
            <div>
              <h3 className="vp-h3">From the article library</h3>
              <ul className="vp-link-list">
                {libraryArticles.map((a) => (
                  <li key={a.id}>
                    <button type="button" onClick={() => navigate("articles", { article: a.id })}>
                      <span>{a.title}</span>
                      <span className="vp-meta-note">{a.source} · {a.date}</span>
                    </button>
                  </li>
                ))}
              </ul>
            </div>
          )}
          <button type="button" className="vp-text-btn" onClick={() => navigate("articles")}>Open the article library</button>
        </section>
      )}

      {page.risks && (page.risks.pairs || page.risks.items || (!isCore && page.risks.empty)) && (
        <section id={slug + "-risks"} className="vp-section">
          <Kicker>{page.risks.kicker}</Kicker>
          <h2 className="vp-h2">{page.risks.title}</h2>
          {page.risks.empty && <p className="vp-empty">{page.risks.empty}</p>}
          {page.risks.pairs && (
            <div className="vp-table-wrap">
              <table className="vp-table">
                <thead>
                  <tr>
                    {(v.riskPairHeaders || ["Risk", "Mitigant"]).map((h) => <th key={h}>{h}</th>)}
                  </tr>
                </thead>
                <tbody>
                  {page.risks.pairs.map((row) => (
                    <tr key={row.risk}>
                      <th scope="row"><span className="vp-house">{row.risk}</span></th>
                      <td>{row.mitigant}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}
          {page.risks.items && (
            <ol className="vp-risks">
              {page.risks.items.map((r) => (
                <li key={r.n}>
                  <h3><span className="vp-num">{r.n}</span> {r.title}</h3>
                  <p><Cite text={r.body} slug={slug} /></p>
                  <p><span className="vp-field">{r.followLabel}</span> {r.follow}</p>
                </li>
              ))}
            </ol>
          )}
        </section>
      )}

      {page.sources && (
        <section id={slug + "-sources"} className="vp-section vp-sources">
          <h2 className="vp-h2">Sources</h2>
          <ol>
            {page.sources.map((src, i) => (
              <li key={i} id={slug + "-src-" + (i + 1)}><Cite text={src} slug={slug} /></li>
            ))}
          </ol>
        </section>
      )}

      {isCore && (
        <VpCorePager
          cats={core}
          coreIndex={page.coreIndex}
          navigate={navigate}
          labels={{ previous: v.previousLabel, next: v.nextLabel }}
        />
      )}
    </>
  );

  const notchRail = showRail && typeof ReactDOM !== "undefined" && ReactDOM.createPortal(
    <VpNotchRail toc={toc} active={activeSection} onGo={goToSection} showNames={showNames} />,
    document.body
  );

  return (
    <article className={"vp" + (isCore ? " vp-core" : "")}>
      <div className="vp-wrap">{body}</div>
      <footer className="vp-footer"><p>{page.footer}</p></footer>
      {notchRail}
    </article>
  );
}

Object.assign(window, { VERTICAL_PAGES, VerticalPage, pageFromCategory });
