← Files MorningstarARCHIVED FILE

skills/fund-comparison/assets/template.html

63.8 KB · Sep 30, 2026 · 22:50 UTC

↓ Download file

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fund Comparison</title>
<style>
  :root {
    --c-mstar:    #FC0412;
    --c-primary:  #0F3D7C;
    --c-text:     #3D3B39;
    --c-muted:    #7F7D7A;
    --c-bg:       #F6F5F4;
    --c-card:     #FFFFFF;
    --c-border:   #ECEBEA;
    --c-pos:      #039649;
    --c-neg:      #E42513;
    --c-fund-a:   #2364B9;
    --c-fund-b:   #B2013C;
    --c-grid:     #DAD9D8;
    --c-axis:     #A6A4A1;
  }

  * { box-sizing: border-box; margin: 0; padding: 0; }

  body {
    font-family: "MORN Intrinsic", "Aptos Light", Aptos, Calibri, Georgia, serif;
    font-size: 13px;
    background: var(--c-bg);
    color: var(--c-text);
    line-height: 1.5;
  }

  /* ── Header ── */
  .header {
    background: var(--c-mstar);
    color: white;
    padding: 2% 5%;
  }
  .header-eyebrow {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    opacity: 0.75;
    margin-bottom: 6px;
  }
  .header-funds {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
  }
  .header-funds h1 { font-size: 20px; font-weight: 700; }
  .header-funds .vs {
    font-size: 13px;
    opacity: 0.7;
    font-weight: 400;
  }
  .header-ticker {
    font-size: 13px;
    font-weight: 600;
    padding: 2px 10px;
    border-radius: 4px;
  }
  .ticker-a { background: rgba(255,255,255,0.18); }
  .ticker-b { background: rgba(255,255,255,0.10); }

  /* ── Layout ── */
  .main {
    padding: 18px 24px 36px;
    max-width: 1200px;
    margin: 0 auto;
  }

  .section {
    background: var(--c-card);
    border-radius: 6px;
    box-shadow: 0 1px 4px rgba(0,0,0,.08);
    margin-bottom: 16px;
    padding: 16px 20px;
  }

  .section-title {
    font-size: 14px;
    font-weight: 700;
    color: var(--c-mstar);
    margin-bottom: 12px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
  }

  .section-subtitle {
    font-size: 11px;
    font-weight: 700;
    color: var(--c-primary);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin: 16px 0 8px;
  }
  .section-subtitle:first-of-type { margin-top: 0; }

  .composition-block {
    margin-top: 22px;
  }
  .composition-block:first-of-type {
    margin-top: 0;
  }
  .composition-group {
    margin-top: 28px;
    padding-top: 16px;
    border-top: 1px solid var(--c-border);
  }
  .composition-group-title {
    font-size: 12px;
    font-weight: 700;
    color: var(--c-mstar);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-bottom: 12px;
  }
  .composition-group .section-subtitle:first-of-type {
    margin-top: 0;
  }

  .grid-2 {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    align-items: start;
  }

  /* ── Warning banner ── */
  .warning-banner {
    background: #FFF8E1;
    border: 1px solid #F5C72C;
    border-radius: 5px;
    padding: 10px 14px;
    font-size: 12px;
    margin-bottom: 14px;
    display: none;
  }
  .warning-banner.visible { display: block; }
  .warning-banner strong { color: #7B5800; }

  /* ── Tables (general) ── */
  table { width: 100%; border-collapse: collapse; font-size: 12px; }

  thead th {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--c-muted);
    padding: 7px 10px;
    text-align: right;
    border-bottom: 2px solid var(--c-primary);
  }
  thead th:first-child { text-align: left; }

  tbody td {
    padding: 7px 10px;
    text-align: right;
    border-bottom: 1px solid var(--c-border);
    font-variant-numeric: tabular-nums;
  }
  tbody td:first-child { text-align: left; font-weight: 600; }

  .table-footnote {
    font-size: 10px;
    color: var(--c-muted);
    margin-top: 6px;
    font-style: italic;
  }

  .val-positive { color: var(--c-pos); }
  .val-negative { color: var(--c-neg); }
  .val-muted    { color: var(--c-muted); }

  .delta-pill {
    display: inline-block;
    box-sizing: border-box;
    min-width: 62px;
    padding: 2px 0 2px 7px;
    border-radius: 999px;
    text-align: right;
    font-weight: 700;
  }
  .delta-pill.val-positive {
    background: rgba(3,150,73,0.10);
    color: var(--c-pos);
  }
  .delta-pill.val-negative {
    background: rgba(228,37,19,0.10);
    color: var(--c-neg);
  }

  /* fund-a / fund-b column headers */
  .th-a { color: var(--c-fund-a) !important; }
  .th-b { color: var(--c-fund-b) !important; }

  /* ── Overview table ── */
  .overview-table td:nth-child(2) { color: var(--c-fund-a); font-weight: 600; }
  .overview-table td:nth-child(3) { color: var(--c-fund-b); font-weight: 600; }
  .overview-table .overview-group-row td {
    background: var(--c-bg);
    color: var(--c-primary);
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    text-align: left !important;
    padding: 6px 10px;
    border-top: 1px solid var(--c-border);
    border-bottom: 1px solid var(--c-border);
  }

  /* ── Style box container (shared wrapper) ── */
  .style-box-container {
    border: 1px solid var(--c-border);
    border-radius: 6px;
    overflow: hidden;
    margin-bottom: 4px;
  }
  .style-box-container-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 8px 14px;
    background: var(--c-bg);
    border-bottom: 1px solid var(--c-border);
  }
  .style-box-container-title {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--c-primary);
  }
  .sb-legend {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 9px;
    color: var(--c-muted);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
  }
  .sb-legend-item { display: flex; align-items: center; gap: 4px; }
  .sb-legend-swatch {
    width: 12px; height: 12px;
    border-radius: 2px;
    border: 1px solid rgba(0,0,0,0.08);
  }
  .style-box-pair {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0;
  }
  .style-box-pair.single {
    grid-template-columns: minmax(280px, 420px);
  }
  .style-box-wrap {
    padding: 14px 16px;
  }
  .style-box-pair:not(.single) .style-box-wrap:first-child {
    border-right: 1px solid var(--c-border);
  }
  .style-box-fund-label {
    font-size: 11px;
    font-weight: 700;
    margin-bottom: 8px;
  }
  .style-box-fund-label.label-a { color: var(--c-fund-a); }
  .style-box-fund-label.label-b { color: var(--c-fund-b); }

  .style-box { width: 100%; border-collapse: collapse; font-size: 12px; }
  .style-box thead th {
    font-size: 9px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--c-muted);
    padding: 4px 6px;
    text-align: center;
    border-bottom: 1px solid var(--c-border);
  }
  .style-box thead th:first-child { text-align: left; width: 44px; }
  .style-box tbody td {
    padding: 10px 6px;
    text-align: center;
    border: 1px solid rgba(0,0,0,0.06);
    font-weight: 600;
  }
  .style-box tbody td:first-child {
    text-align: left;
    font-size: 9px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--c-muted);
    background: var(--c-bg);
    border-left: none;
    border-color: var(--c-border);
    padding: 10px 8px;
  }

  /* Heat-map tiers — Fund A (blue) */
  .sb-a-4 { background: rgba(35,100,185,0.75); color: #fff; }
  .sb-a-3 { background: rgba(35,100,185,0.42); color: var(--c-text); }
  .sb-a-2 { background: rgba(35,100,185,0.18); color: var(--c-text); }
  .sb-a-1 { background: rgba(35,100,185,0.05); color: var(--c-muted); }

  /* Heat-map tiers — Fund B (red) */
  .sb-b-4 { background: rgba(178,1,60,0.75); color: #fff; }
  .sb-b-3 { background: rgba(178,1,60,0.42); color: var(--c-text); }
  .sb-b-2 { background: rgba(178,1,60,0.18); color: var(--c-text); }
  .sb-b-1 { background: rgba(178,1,60,0.05); color: var(--c-muted); }

  /* ── Sector table ── */
  .sector-group-row td {
    background: var(--c-bg);
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--c-primary);
    padding: 5px 10px;
    text-align: left !important;
    border-bottom: 1px solid var(--c-border);
  }
  .sector-material-row td {
    background: rgba(255, 246, 220, 0.55);
  }
  .sector-material-row td:first-child {
    color: var(--c-primary);
  }
  .sector-material-row .bar-cell-fill.bc-a { background: rgba(35,100,185,0.20); }
  .sector-material-row .bar-cell-fill.bc-b { background: rgba(178,1,60,0.20); }
  .sector-material-row .bar-cell-text {
    font-weight: 700;
    color: var(--c-text);
  }

  /* ── Chart ── */
  .chart-legend {
    display: flex;
    gap: 20px;
    margin-bottom: 10px;
    font-size: 12px;
  }
  .legend-item { display: flex; align-items: center; gap: 6px; }
  .legend-swatch {
    width: 18px; height: 3px;
    border-radius: 2px;
    flex-shrink: 0;
  }
  .chart-wrap { width: 100%; overflow: hidden; }
  .chart-wrap svg { width: 100%; height: auto; display: block; }
  .chart-point .hover-target { cursor: crosshair; }
  .chart-point .data-dot,
  .chart-point .tick-line,
  .chart-point .chart-tooltip-box { display: none; }
  .chart-point:hover .data-dot,
  .chart-point:hover .tick-line,
  .chart-point:hover .chart-tooltip-box { display: block; }

  /* ── Tabs ── */
  .tabs {
    display: flex;
    gap: 0;
    margin: 14px 0 0;
    border-bottom: 2px solid var(--c-border);
  }
  .tab-btn {
    padding: 7px 16px;
    font-size: 12px;
    font-weight: 600;
    font-family: inherit;
    color: var(--c-muted);
    cursor: pointer;
    border: none;
    background: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
  }
  .tab-btn.active { color: var(--c-primary); border-bottom-color: var(--c-primary); }
  .tab-panel { display: none; padding-top: 14px; }
  .tab-panel.active { display: block; }

  /* ── Narrative ── */
  .analyst-block {
    background: var(--c-bg);
    border-radius: 5px;
    padding: 12px 16px;
    margin-bottom: 10px;
    font-size: 12px;
    line-height: 1.6;
  }
  .analyst-block .fund-label {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 4px;
  }
  .analyst-block .fund-label.label-a { color: var(--c-fund-a); }
  .analyst-block .fund-label.label-b { color: var(--c-fund-b); }

  #narrative-body { margin-top: 18px; }
  .narrative-section + .narrative-section { margin-top: 14px; }
  .narrative-heading {
    color: var(--c-primary);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.05em;
    margin: 0 0 4px;
    text-transform: uppercase;
  }
  .narrative-p { font-size: 13px; line-height: 1.6; margin: 0; }

  /* ── Footer ── */
  .footer {
    font-size: 11px;
    color: var(--c-muted);
    padding: 12px 24px;
    text-align: center;
  }

  /* ── Asset allocation bar chart ── */
  .alloc-chart { padding: 2px 0; }
  .alloc-row {
    display: grid;
    grid-template-columns: 110px 1fr;
    gap: 10px;
    align-items: center;
    margin-bottom: 10px;
  }
  .alloc-row:last-child { margin-bottom: 0; }
  .alloc-label { font-size: 12px; font-weight: 600; }
  .alloc-bars { display: flex; flex-direction: column; gap: 4px; }
  .alloc-bar-track { display: flex; align-items: center; gap: 8px; }
  .alloc-fund-tag {
    font-size: 9px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    width: 26px;
    flex-shrink: 0;
  }
  .alloc-fund-tag.tag-a { color: var(--c-fund-a); }
  .alloc-fund-tag.tag-b { color: var(--c-fund-b); }
  .alloc-bar-outer {
    flex: 1;
    height: 14px;
    background: #F1F4F7;
    border: 1px solid #DDE4EC;
    border-radius: 3px;
    overflow: hidden;
  }
  .alloc-bar-fill { height: 100%; border-radius: 2px; }
  .alloc-bar-fill.fill-a { background: rgba(35,100,185,0.60); }
  .alloc-bar-fill.fill-b { background: rgba(178,1,60,0.60); }
  .alloc-bar-pct {
    font-size: 11px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    width: 38px;
    text-align: right;
    flex-shrink: 0;
  }
  .alloc-bar-delta {
    font-size: 10px;
    font-weight: 600;
    width: 42px;
    text-align: right;
    flex-shrink: 0;
    color: var(--c-muted);
  }
  .alloc-bar-delta.val-positive { color: var(--c-pos); }
  .alloc-bar-delta.val-negative { color: var(--c-neg); }
  .alloc-legend {
    display: flex;
    gap: 16px;
    margin-bottom: 12px;
    font-size: 11px;
  }
  .alloc-legend-item { display: flex; align-items: center; gap: 6px; }
  .alloc-legend-swatch {
    width: 20px; height: 10px;
    border-radius: 2px;
  }
  .alloc-legend-swatch.ls-a { background: rgba(35,100,185,0.60); }
  .alloc-legend-swatch.ls-b { background: rgba(178,1,60,0.60); }

  /* ── Sector inline bar ── */
  .bar-cell {
    position: relative;
    text-align: right;
    padding: 0 !important;
    border-bottom: 1px solid var(--c-border);
  }
  .bar-cell-track {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: 7px 10px;
    overflow: hidden;
  }
  .bar-cell-fill {
    position: absolute;
    right: 0;
    top: 0;
    bottom: 0;
    border-radius: 3px 0 0 3px;
  }
  .bar-cell-fill.bc-a { background: rgba(35,100,185,0.12); }
  .bar-cell-fill.bc-b { background: rgba(178,1,60,0.12); }
  .bar-cell-text { position: relative; z-index: 1; font-variant-numeric: tabular-nums; }

  /* ── Stat cards (Cost & Ratings) ── */
  .stat-group-header {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--c-primary);
    border-left: 3px solid var(--c-primary);
    padding: 2px 0 2px 10px;
    margin-bottom: 10px;
  }
  .stat-group + .stat-group { margin-top: 18px; }
  .stat-card-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 1px;
    background: var(--c-border);
    border: 1px solid var(--c-border);
    border-radius: 5px;
    overflow: hidden;
  }
  .stat-card { background: var(--c-card); padding: 10px 12px; }
  .stat-card-label {
    font-size: 9px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--c-muted);
    margin-bottom: 8px;
  }
  .stat-card-vals { display: flex; flex-direction: column; gap: 3px; }
  .stat-card-val { display: flex; align-items: baseline; gap: 6px; }
  .stat-card-ticker {
    font-size: 9px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    width: 28px;
    flex-shrink: 0;
  }
  .stat-card-ticker.sct-a { color: var(--c-fund-a); }
  .stat-card-ticker.sct-b { color: var(--c-fund-b); }
  .stat-card-num { font-size: 13px; font-weight: 700; }

  /* ── Risk period tabs ── */
  .risk-period-tabs { margin-top: 0; margin-bottom: 0; }
  .risk-panel { display: none; padding-top: 12px; }
  .risk-panel.active { display: block; }
  .risk-return-wrap {
    margin-bottom: 14px;
  }
  .risk-return-chart {
    width: 100%;
    overflow: hidden;
    border-bottom: 1px solid var(--c-border);
    padding-bottom: 12px;
  }
  .risk-return-chart svg {
    width: 100%;
    height: auto;
    display: block;
  }
  .risk-return-footnote {
    font-size: 10px;
    color: var(--c-muted);
    margin-top: 6px;
    font-style: italic;
  }

  /* ── Print ── */
  @media print {
    body { background: white; font-size: 11px; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
    .main { padding: 10px 0; max-width: 100%; }
    .section { box-shadow: none; border: 1px solid #ddd; page-break-inside: avoid; margin-bottom: 14px; }
    .tabs { display: none; }
    .tab-panel { display: block !important; padding-top: 10px; }
  }
</style>
</head>
<body>

<div class="header">
  <div class="header-eyebrow">Morningstar — Fund Comparison</div>
  <div class="header-funds">
    <h1 id="hdr-name-a"></h1>
    <span class="vs">vs</span>
    <h1 id="hdr-name-b"></h1>
    <span class="header-ticker ticker-a" id="hdr-ticker-a"></span>
    <span class="header-ticker ticker-b" id="hdr-ticker-b"></span>
  </div>
</div>

<div class="main">

  <!-- Cross-category warning (shown conditionally) -->
  <div class="warning-banner" id="cross-cat-warning">
    <strong>Cross-Category Note:</strong> These funds belong to different Morningstar Categories.
    Category rankings and peer-group statistics reflect each fund's own peer group and are not
    directly comparable across categories.
  </div>

  <!-- ── OVERVIEW ── -->
  <div class="section" id="overview-section">
    <div class="section-title">Overview</div>
    <table class="overview-table">
      <thead>
        <tr>
          <th></th>
          <th class="th-a" id="ph-head-a"></th>
          <th class="th-b" id="ph-head-b"></th>
        </tr>
      </thead>
      <tbody id="profile-tbody"></tbody>
    </table>
  </div>

  <!-- ── STRUCTURAL COMPOSITION ── -->
  <div class="section">
    <div class="section-title">Structural Composition</div>

    <div id="portfolio-characteristics-section" class="composition-block">
      <div class="section-subtitle">Portfolio Characteristics</div>
      <table>
        <thead>
          <tr>
            <th>Metric</th>
            <th class="th-a" id="portfolio-char-head-a"></th>
            <th class="th-b" id="portfolio-char-head-b"></th>
          </tr>
        </thead>
        <tbody id="portfolio-characteristics-tbody"></tbody>
      </table>
    </div>

    <!-- Asset Allocation -->
    <div id="asset-alloc-section" class="composition-block">
      <div class="section-subtitle">Asset Allocation</div>
      <div id="alloc-chart" class="alloc-chart"></div>
    </div>

    <div class="composition-group" id="equity-composition-group">
      <div class="composition-group-title">Equity Breakdown</div>

      <!-- Equity Style Boxes -->
      <div id="equity-style-section" class="composition-block">
        <div class="section-subtitle">Equity Style Box</div>
        <div class="style-box-container">
          <div class="style-box-container-header">
            <span class="style-box-container-title">Morningstar Style Box</span>
            <span class="sb-legend" id="sb-legend-equity"></span>
          </div>
          <div class="style-box-pair" id="equity-style-pair"></div>
        </div>
      </div>

      <div id="equity-breakdown-section" class="composition-block">
        <div class="tabs">
          <button class="tab-btn active" data-composition-tab="equity-sectors">Sectors</button>
          <button class="tab-btn" data-composition-tab="equity-regions">Regions</button>
        </div>
        <div class="tab-panel active" id="equity-sectors-panel">
          <table><thead id="sectors-thead"></thead><tbody id="sectors-tbody"></tbody></table>
        </div>
        <div class="tab-panel" id="equity-regions-panel">
          <table><thead id="equity-regions-thead"></thead><tbody id="equity-regions-tbody"></tbody></table>
        </div>
      </div>
    </div>

    <div class="composition-group" id="fi-composition-group">
      <div class="composition-group-title">Fixed Income Breakdown</div>

      <!-- Fixed Income Style Boxes -->
      <div id="fi-style-section" class="composition-block">
        <div class="section-subtitle">Fixed Income Style Box</div>
        <div class="style-box-container">
          <div class="style-box-container-header">
            <span class="style-box-container-title">Credit / Duration Style</span>
            <span class="sb-legend" id="sb-legend-fi"></span>
          </div>
          <div class="style-box-pair" id="fi-style-pair"></div>
        </div>
      </div>

      <div id="fi-breakdown-section" class="composition-block">
        <div class="tabs">
          <button class="tab-btn active" data-composition-tab="fi-sectors">Sectors</button>
          <button class="tab-btn" data-composition-tab="fi-maturity">Maturity</button>
        </div>
        <div class="tab-panel active" id="fi-sectors-panel">
          <table><thead id="fi-sectors-thead"></thead><tbody id="fi-sectors-tbody"></tbody></table>
        </div>
        <div class="tab-panel" id="fi-maturity-panel">
          <table><thead id="fi-maturity-thead"></thead><tbody id="fi-maturity-tbody"></tbody></table>
        </div>
      </div>
    </div>

    <!-- Structural interpretation -->
    <p class="table-footnote" id="structural-note" style="margin-top:14px;font-size:12px;line-height:1.6;font-style:normal;color:var(--c-text);"></p>
  </div>

  <!-- ── RETURNS ── -->
  <div class="section">
    <div class="section-title">Returns</div>

    <div class="chart-legend" id="chart-legend"></div>
    <div class="chart-wrap" id="growth-chart"></div>

    <div class="tabs">
      <button class="tab-btn active" data-tab="trailing">Trailing Returns</button>
      <button class="tab-btn" data-tab="annual">Annual Returns</button>
    </div>

    <div class="tab-panel active" id="tab-trailing">
      <table>
        <thead>
          <tr>
            <th>Period</th>
            <th class="th-a" id="trail-head-a"></th>
            <th class="th-b" id="trail-head-b"></th>
            <th>Δ (A−B)</th>
          </tr>
        </thead>
        <tbody id="trailing-tbody"></tbody>
      </table>
      <div class="table-footnote">2Y+ returns are annualized. Parentheses show Morningstar Category percentile rank: 1 is best and 100 is worst; ranks are not directly comparable across different categories. Δ = Fund A minus Fund B in percentage points.</div>
    </div>

    <div class="tab-panel" id="tab-annual">
      <table>
        <thead>
          <tr>
            <th>Year</th>
            <th class="th-a" id="ann-head-a"></th>
            <th class="th-b" id="ann-head-b"></th>
            <th>Δ (A−B)</th>
          </tr>
        </thead>
        <tbody id="annual-tbody"></tbody>
      </table>
      <div class="table-footnote">Calendar year returns in %. Parentheses show Morningstar Category percentile rank: 1 is best and 100 is worst; ranks are not directly comparable across different categories. Δ = Fund A minus Fund B in percentage points.</div>
    </div>
  </div>

  <!-- ── RISK ── -->
  <div class="section" id="risk-section">
    <div class="section-title">Risk</div>
    <div class="risk-return-wrap" id="risk-return-wrap">
      <div class="risk-return-chart" id="risk-return-chart"></div>
      <div class="risk-return-footnote" id="risk-return-footnote"></div>
    </div>
    <div class="tabs risk-period-tabs" id="risk-tabs"></div>
    <div id="risk-panels"></div>
    <div class="table-footnote" id="risk-table-footnote" style="margin-top:10px;">Capture ratios: 100 = benchmark. Δ = Fund A minus Fund B.</div>
  </div>

  <!-- ── RATINGS ── -->
  <div class="section">
    <div class="section-title">Ratings</div>
    <div class="stat-group">
      <div class="stat-group-header">Ratings</div>
      <div class="stat-card-grid" id="ratings-cards"></div>
    </div>
  </div>

  <!-- ── NARRATIVE ── -->
  <div class="section">
    <div class="section-title">Analyst Research &amp; Summary</div>
    <div class="analyst-block">
      <div class="fund-label label-a" id="analyst-label-a"></div>
      <div id="analyst-text-a"></div>
    </div>
    <div class="analyst-block">
      <div class="fund-label label-b" id="analyst-label-b"></div>
      <div id="analyst-text-b"></div>
    </div>
    <div id="narrative-body"></div>
  </div>

</div>

<div class="footer" id="footer-line">
  AI-generated analysis using Morningstar's data and research. Not investment advice; for informational use only.
</div>

<script>
// ── Data injected by skill ────────────────────────────────────────────────────
// Replace __DATA_JSON__ with the full comparison data object (see full-workflow.md).
const D = __DATA_JSON__;
// ─────────────────────────────────────────────────────────────────────────────

// ── Helpers ──────────────────────────────────────────────────────────────────
const $ = id => document.getElementById(id);
const esc = s => String(s ?? '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
const fmt2 = v => v == null ? '--' : Number(v).toFixed(2);
const fmtPct = v => v == null ? '--' : Number(v).toFixed(2) + '%';
const fmtDelta = v => { if (v == null) return '--'; const n = Number(v); return (n >= 0 ? '+' : '') + n.toFixed(2); };
const deltaCls = v => { if (v == null || Math.abs(Number(v)) < 0.005) return ''; return Number(v) > 0 ? 'val-positive' : 'val-negative'; };
const deltaText = (v, decimals = 2) => v == null ? '--' : `${Number(v) >= 0 ? '+' : ''}${Number(v).toFixed(decimals)}%`;
const displayText = v => String(v ?? '--').replace(/\^[A-Z]+$/g, '').replace(/([a-z])([A-Z])/g, '$1 $2');
const pctWidth = v => v == null ? 0 : Math.max(0, Math.min(100, Number(v)));
let riskReturnAvailablePeriods = new Set();
let setRiskReturnPeriod = () => false;
const deltaPill = (v, decimals = 2) => {
  if (v == null) return '--';
  return `<span class="delta-pill ${deltaCls(v)}">${deltaText(v, decimals)}</span>`;
};
const tickerLabel = (ticker, name) => `${esc(ticker)} — ${esc(name)}`;
const shortLabel = ticker => esc(ticker);

function hide(id) { const el = $(id); if (el) el.style.display = 'none'; }

// ── Meta / header ─────────────────────────────────────────────────────────────
(function () {
  const m = D.meta;
  document.title = `Fund Comparison — ${m.ticker_a} vs ${m.ticker_b}`;
  $('hdr-name-a').textContent = m.name_a;
  $('hdr-name-b').textContent = m.name_b;
  $('hdr-ticker-a').textContent = m.ticker_a;
  $('hdr-ticker-b').textContent = m.ticker_b;
  if (m.cross_category_warning) $('cross-cat-warning').classList.add('visible');
  if (m.report_date || m.data_as_of_date) {
    $('footer-line').innerHTML =
      `AI-generated analysis using Morningstar's data and research. Not investment advice; for informational use only.<br>` +
      `Report generated ${esc(m.report_date || '')} | Data as of ${esc(m.data_as_of_date || '')}`;
  }
})();

// ── Column headers (shared helper) ───────────────────────────────────────────
function setColHeaders(aId, bId) {
  const a = `${esc(D.meta.ticker_a)}`, b = `${esc(D.meta.ticker_b)}`;
  if ($(aId)) $(aId).textContent = a;
  if ($(bId)) $(bId).textContent = b;
}
['ph-head-a','ph-head-b','sec-head-a','sec-head-b',
 'fi-sec-head-a','fi-sec-head-b','trail-head-a','trail-head-b','ann-head-a','ann-head-b'
].forEach((id, i) => {
  const el = $(id); if (!el) return;
  el.textContent = i % 2 === 0 ? D.meta.ticker_a : D.meta.ticker_b;
});

// ── Overview summary ──────────────────────────────────────────────────────────
(function () {
  const tbody = $('profile-tbody');
  if (!tbody) return;

  const profileRows = D.profile || [];
  const costRows = D.cost || [];
  const findRows = (rows, labels) => labels
    .map(label => rows.find(r => String(r.label || r.metric || '').toLowerCase() === label.toLowerCase()))
    .filter(Boolean);

  const groups = D.overview_groups || [
    { group: 'Fund Identity', rows: findRows(profileRows, ['Name', 'Type', 'Base Currency', 'Ticker']) },
    { group: 'Structure & Access', rows: findRows(profileRows, ['Active / Index', 'Structure', 'Fund Size', 'Inception Date', 'Min. Investment']) },
    { group: 'Management', rows: findRows(profileRows, ['Number of Managers', 'Average Tenure', 'Longest Tenure', 'Investment Advisor']) },
    { group: 'Classification & Benchmark', rows: findRows(profileRows, ['Category', 'Morningstar Category', 'Prospectus Benchmark']) },
    { group: 'Cost & Income', rows: costRows },
  ].filter(g => g.rows && g.rows.length);

  if (!groups.length) { hide('overview-section'); return; }
  const isOverviewRowHidden = r =>
    String(r.label || r.metric || '').toLowerCase() === 'mpt benchmark index';

  const visibleGroups = groups
    .map(group => ({ ...group, rows: group.rows.filter(r => !isOverviewRowHidden(r)) }))
    .filter(group => group.rows.length);

  if (!visibleGroups.length) { hide('overview-section'); return; }
  tbody.innerHTML = visibleGroups.map(group => {
    const rows = group.rows.map(r => {
      const label = r.label || r.metric || '';
      return `<tr><td>${esc(label)}</td><td>${esc(r.a ?? '--')}</td><td>${esc(r.b ?? '--')}</td></tr>`;
    }).join('');
    return `<tr class="overview-group-row"><td colspan="3">${esc(group.group)}</td></tr>${rows}`;
  }).join('');
})();

// ── Asset allocation bar chart ────────────────────────────────────────────────
(function () {
  if (!D.asset_allocation || !D.asset_allocation.length) { hide('asset-alloc-section'); return; }
  const container = $('alloc-chart');
  if (!container) return;
  const tA = esc(D.meta.ticker_a), tB = esc(D.meta.ticker_b);

  let html = `<div class="alloc-legend">
    <span class="alloc-legend-item"><span class="alloc-legend-swatch ls-a"></span>${tA}</span>
    <span class="alloc-legend-item"><span class="alloc-legend-swatch ls-b"></span>${tB}</span>
  </div>`;

  D.asset_allocation.forEach(r => {
    const aVal = r.a != null ? Number(r.a) : null;
    const bVal = r.b != null ? Number(r.b) : null;
    const delta = aVal != null && bVal != null ? aVal - bVal : null;
    const deltaStr = delta != null ? (delta >= 0 ? '+' : '') + delta.toFixed(1) : '';
    const dCls = delta != null ? (Math.abs(delta) < 0.05 ? '' : delta > 0 ? 'val-positive' : 'val-negative') : '';
    html += `<div class="alloc-row">
      <div class="alloc-label">${esc(r.label)}</div>
      <div class="alloc-bars">
        <div class="alloc-bar-track">
          <span class="alloc-fund-tag tag-a">${tA}</span>
          <div class="alloc-bar-outer"><div class="alloc-bar-fill fill-a" style="width:${pctWidth(aVal)}%"></div></div>
          <span class="alloc-bar-pct">${aVal != null ? aVal.toFixed(1) + '%' : '--'}</span>
          <span class="alloc-bar-delta ${dCls}">${deltaStr ? 'Δ' + deltaStr : ''}</span>
        </div>
        <div class="alloc-bar-track">
          <span class="alloc-fund-tag tag-b">${tB}</span>
          <div class="alloc-bar-outer"><div class="alloc-bar-fill fill-b" style="width:${pctWidth(bVal)}%"></div></div>
          <span class="alloc-bar-pct">${bVal != null ? bVal.toFixed(1) + '%' : '--'}</span>
          <span class="alloc-bar-delta"></span>
        </div>
      </div>
    </div>`;
  });
  container.innerHTML = html;
})();

// ── Portfolio characteristics ────────────────────────────────────────────────
(function () {
  const rows = D.portfolio_characteristics || [];
  if (!rows.length) { hide('portfolio-characteristics-section'); return; }

  const tbody = $('portfolio-characteristics-tbody');
  if (!tbody) return;
  $('portfolio-char-head-a').textContent = D.meta.ticker_a;
  $('portfolio-char-head-b').textContent = D.meta.ticker_b;
  tbody.innerHTML = rows.map(row =>
    `<tr><td>${esc(row.label)}</td><td>${esc(row.a ?? '--')}</td><td>${esc(row.b ?? '--')}</td></tr>`
  ).join('');
})();

// ── Style box builder (heat-map tiers) ────────────────────────────────────────
function sbTierCls(v, fund) {
  if (v == null) return '';
  const n = Number(v);
  const tier = n >= 50 ? 4 : n >= 25 ? 3 : n >= 10 ? 2 : 1;
  return `sb-${fund}-${tier}`;
}

function buildStyleBoxLegend(fund) {
  const tiers = [
    { label: '50%+', tier: 4 },
    { label: '25–49%', tier: 3 },
    { label: '10–24%', tier: 2 },
    { label: '0–9%', tier: 1 },
  ];
  return tiers.map(t =>
    `<span class="sb-legend-item">` +
    `<span class="sb-legend-swatch sb-${fund}-${t.tier}"></span>${t.label}` +
    `</span>`
  ).join('');
}

function buildStyleBox(data, fund, fundLabel, labelCls) {
  if (!data) return '';
  const rows = ['large','mid','small'];
  const cols = ['value','blend','growth'];
  const rowLabels = ['Large','Mid','Small'];
  const colLabels = ['Value','Blend','Growth'];

  let html = `<div class="style-box-wrap">`;
  html += `<div class="style-box-fund-label ${labelCls}">${esc(fundLabel)}</div>`;
  html += `<table class="style-box"><thead><tr><th></th>`;
  colLabels.forEach(l => html += `<th>${l}</th>`);
  html += `</tr></thead><tbody>`;
  rows.forEach((r, ri) => {
    html += `<tr><td>${rowLabels[ri]}</td>`;
    cols.forEach(c => {
      const v = data[`${r}_${c}`];
      const cls = sbTierCls(v, fund);
      html += `<td class="${cls}">${v != null ? Math.round(Number(v)) : '--'}</td>`;
    });
    html += `</tr>`;
  });
  html += `</tbody></table>`;
  if (data.portfolio_analyzed != null && Number(data.portfolio_analyzed) < 95) {
    html += `<div class="table-footnote">Covers ${Number(data.portfolio_analyzed).toFixed(0)}% of portfolio.</div>`;
  }
  html += `</div>`;
  return html;
}

function styleValue(data, keys) {
  for (const key of keys) {
    if (data[key] != null) return data[key];
  }
  return null;
}

function buildFixedIncomeStyleBox(data, fund, fundLabel, labelCls) {
  if (!data) return '';
  const rows = [
    { label: 'High', keys: ['high'] },
    { label: 'Med', keys: ['medium', 'med'] },
    { label: 'Low', keys: ['low'] },
  ];
  const cols = [
    { label: 'Ltd', keys: ['limited', 'ltd'] },
    { label: 'Mod', keys: ['moderate', 'mod'] },
    { label: 'Ext', keys: ['extensive', 'ext'] },
  ];

  let html = `<div class="style-box-wrap">`;
  html += `<div class="style-box-fund-label ${labelCls}">${esc(fundLabel)}</div>`;
  html += `<table class="style-box"><thead><tr><th></th>`;
  cols.forEach(c => html += `<th>${c.label}</th>`);
  html += `</tr></thead><tbody>`;
  rows.forEach(row => {
    html += `<tr><td>${row.label}</td>`;
    cols.forEach(col => {
      const keys = [];
      col.keys.forEach(c => row.keys.forEach(r => keys.push(`${c}_${r}`, `${r}_${c}`)));
      const v = styleValue(data, keys);
      const cls = sbTierCls(v, fund);
      html += `<td class="${cls}">${v != null ? Math.round(Number(v)) : '--'}</td>`;
    });
    html += `</tr>`;
  });
  html += `</tbody></table>`;
  if (data.portfolio_analyzed != null && Number(data.portfolio_analyzed) < 95) {
    html += `<div class="table-footnote">Covers ${Number(data.portfolio_analyzed).toFixed(0)}% of portfolio.</div>`;
  }
  html += `</div>`;
  return html;
}

// ── Equity style boxes ────────────────────────────────────────────────────────
(function () {
  const hasA = D.equity_style_a != null, hasB = D.equity_style_b != null;
  if (!hasA && !hasB) { hide('equity-style-section'); return; }
  const pair = $('equity-style-pair');
  if (!pair) return;
  pair.classList.toggle('single', hasA !== hasB);
  pair.innerHTML =
    buildStyleBox(D.equity_style_a, 'a', `${D.meta.ticker_a} — ${D.meta.name_a}`, 'label-a') +
    buildStyleBox(D.equity_style_b, 'b', `${D.meta.ticker_b} — ${D.meta.name_b}`, 'label-b');

  const legend = $('sb-legend-equity');
  if (legend) legend.innerHTML = buildStyleBoxLegend(hasA ? 'a' : 'b');
})();

// ── Sector bar-cell helper ────────────────────────────────────────────────────
function sectorBarCell(val, fund, maxPct) {
  if (val == null) return `<td class="bar-cell"><div class="bar-cell-track"><span class="bar-cell-text">--</span></div></td>`;
  const pct = (Number(val) / maxPct) * 100;
  return `<td class="bar-cell">
    <div class="bar-cell-track">
      <div class="bar-cell-fill bc-${fund}" style="width:${pct.toFixed(1)}%"></div>
      <span class="bar-cell-text">${Number(val).toFixed(1)}%</span>
    </div>
  </td>`;
}

function sectorDeltaCell(delta) {
  if (delta == null) return '<td>--</td>';
  const material = Math.abs(delta) >= 10;
  return `<td class="${deltaCls(delta)}">${material ? deltaPill(delta, 1) : deltaText(delta, 1)}</td>`;
}

function returnDeltaThreshold() { return 5; }

function returnDeltaCell(delta, period, threshold = returnDeltaThreshold(period)) {
  if (delta == null) return '<td>--</td>';
  const material = Math.abs(delta) >= threshold;
  return `<td class="${deltaCls(delta)}">${material ? deltaPill(delta, 2) : deltaText(delta, 2)}</td>`;
}

const EQUITY_SECTOR_ORDER = [
  ['Cyclical', 'Basic Materials'],
  ['Cyclical', 'Consumer Cyclical'],
  ['Cyclical', 'Financial Services'],
  ['Cyclical', 'Real Estate'],
  ['Sensitive', 'Communication Services'],
  ['Sensitive', 'Energy'],
  ['Sensitive', 'Industrials'],
  ['Sensitive', 'Technology'],
  ['Defensive', 'Consumer Defensive'],
  ['Defensive', 'Healthcare'],
  ['Defensive', 'Utilities'],
];

const FI_SECTOR_ORDER = [
  'Government',
  'Municipal',
  'Corporate',
  'Securitized',
  'Cash & Equivalents',
  'Derivative',
];

function normalizeOrderKey(value) {
  const key = String(value || '')
    .toLowerCase()
    .replace(/&/g, 'and')
    .replace(/\bcomm\.?\b/g, 'communication')
    .replace(/[^a-z0-9]+/g, '');
  if (key === 'derivatives') return 'derivative';
  if (key === 'municipals') return 'municipal';
  return key;
}

function orderMapFromPairs(pairs) {
  const map = new Map();
  pairs.forEach(([group, sector], i) => {
    map.set(`${normalizeOrderKey(group)}|${normalizeOrderKey(sector)}`, i);
  });
  return map;
}

function orderMapFromLabels(labels) {
  const map = new Map();
  labels.forEach((label, i) => map.set(normalizeOrderKey(label), i));
  return map;
}

const EQUITY_SECTOR_ORDER_MAP = orderMapFromPairs(EQUITY_SECTOR_ORDER);
const FI_SECTOR_ORDER_MAP = orderMapFromLabels(FI_SECTOR_ORDER);

function orderedSectorRows(rows, orderType) {
  const orderMap = orderType === 'equity' ? EQUITY_SECTOR_ORDER_MAP : FI_SECTOR_ORDER_MAP;
  return [...rows].sort((left, right) => {
    const leftKey = orderType === 'equity'
      ? `${normalizeOrderKey(left.group)}|${normalizeOrderKey(left.sector)}`
      : normalizeOrderKey(left.sector);
    const rightKey = orderType === 'equity'
      ? `${normalizeOrderKey(right.group)}|${normalizeOrderKey(right.sector)}`
      : normalizeOrderKey(right.sector);
    const leftOrder = orderMap.has(leftKey) ? orderMap.get(leftKey) : Number.MAX_SAFE_INTEGER;
    const rightOrder = orderMap.has(rightKey) ? orderMap.get(rightKey) : Number.MAX_SAFE_INTEGER;
    if (leftOrder !== rightOrder) return leftOrder - rightOrder;
    return String(left.sector || '').localeCompare(String(right.sector || ''));
  });
}

function renderSectorTable(rows, sectionId, theadId, tbodyId, orderType = null, rowLabel = 'Sector') {
  if (!rows || !rows.length) { hide(sectionId); return; }
  const thead = $(theadId), tbody = $(tbodyId);
  if (!thead || !tbody) return;
  const orderedRows = orderType ? orderedSectorRows(rows, orderType) : rows;
  const hasA = orderedRows.some(r => r.a != null);
  const hasB = orderedRows.some(r => r.b != null);
  if (!hasA && !hasB) { hide(sectionId); return; }
  const both = hasA && hasB;
  const colSpan = 1 + (hasA ? 1 : 0) + (hasB ? 1 : 0) + (both ? 1 : 0);
  thead.innerHTML = `<tr>
    <th>${esc(rowLabel)}</th>
    ${hasA ? `<th class="th-a">${esc(D.meta.ticker_a)}</th>` : ''}
    ${hasB ? `<th class="th-b">${esc(D.meta.ticker_b)}</th>` : ''}
    ${both ? '<th>Δ (A−B)</th>' : ''}
  </tr>`;
  const maxPct = Math.max(...orderedRows.map(r => Math.max(hasA ? r.a ?? 0 : 0, hasB ? r.b ?? 0 : 0)), 1);
  let currentGroup = null, html = '';
  orderedRows.forEach(row => {
    if (row.group && row.group !== currentGroup) {
      currentGroup = row.group;
      html += `<tr class="sector-group-row"><td colspan="${colSpan}">${esc(row.group)}</td></tr>`;
    }
    const aVal = row.a != null ? Number(row.a) : null;
    const bVal = row.b != null ? Number(row.b) : null;
    const delta = aVal != null && bVal != null ? aVal - bVal : null;
    const material = both && delta != null && Math.abs(delta) >= 10;
    html += `<tr class="${material ? 'sector-material-row' : ''}">
      <td>${esc(row.sector)}</td>
      ${hasA ? sectorBarCell(row.a, 'a', maxPct) : ''}
      ${hasB ? sectorBarCell(row.b, 'b', maxPct) : ''}
      ${both ? sectorDeltaCell(delta) : ''}
    </tr>`;
  });
  tbody.innerHTML = html;
}

// ── Equity breakdown ──────────────────────────────────────────────────────────
(function () {
  renderSectorTable(D.equity_sectors, 'equity-sectors-panel', 'sectors-thead', 'sectors-tbody', 'equity');
  renderSectorTable(D.equity_regions, 'equity-regions-panel', 'equity-regions-thead', 'equity-regions-tbody', null, 'Region');
})();

// ── Fixed income style boxes ──────────────────────────────────────────────────
(function () {
  const hasA = D.fi_style_a != null, hasB = D.fi_style_b != null;
  if (!hasA && !hasB) { hide('fi-style-section'); return; }
  const pair = $('fi-style-pair');
  if (!pair) return;
  pair.classList.toggle('single', hasA !== hasB);
  pair.innerHTML =
    buildFixedIncomeStyleBox(D.fi_style_a, 'a', `${D.meta.ticker_a} — ${D.meta.name_a}`, 'label-a') +
    buildFixedIncomeStyleBox(D.fi_style_b, 'b', `${D.meta.ticker_b} — ${D.meta.name_b}`, 'label-b');

  const legend = $('sb-legend-fi');
  if (legend) legend.innerHTML = buildStyleBoxLegend(hasA ? 'a' : 'b');
})();

// ── Fixed income breakdown ────────────────────────────────────────────────────
(function () {
  renderSectorTable(D.fi_sectors, 'fi-sectors-panel', 'fi-sectors-thead', 'fi-sectors-tbody', 'fi');
  renderSectorTable(D.fi_maturity, 'fi-maturity-panel', 'fi-maturity-thead', 'fi-maturity-tbody', null, 'Maturity');
})();

function setupCompositionTabs(sectionId) {
  const section = $(sectionId);
  if (!section) return;
  const buttons = [...section.querySelectorAll('[data-composition-tab]')];
  const visibleButtons = buttons.filter(button => {
    const panel = $(button.dataset.compositionTab + '-panel');
    const visible = panel && panel.style.display !== 'none';
    button.style.display = visible ? '' : 'none';
    return visible;
  });
  if (!visibleButtons.length) { hide(sectionId); return; }

  const activate = button => {
    visibleButtons.forEach(candidate => {
      candidate.classList.toggle('active', candidate === button);
      const panel = $(candidate.dataset.compositionTab + '-panel');
      if (panel) panel.classList.toggle('active', candidate === button);
    });
  };
  activate(visibleButtons[0]);
  visibleButtons.forEach(button => button.addEventListener('click', () => activate(button)));
}

setupCompositionTabs('equity-breakdown-section');
setupCompositionTabs('fi-breakdown-section');

function hideCompositionGroup(groupId, childIds) {
  const group = $(groupId);
  if (!group) return;
  const hasVisibleChild = childIds.some(id => {
    const child = $(id);
    return child && child.style.display !== 'none';
  });
  if (!hasVisibleChild) group.style.display = 'none';
}

hideCompositionGroup('equity-composition-group', ['equity-style-section', 'equity-breakdown-section']);
hideCompositionGroup('fi-composition-group', ['fi-style-section', 'fi-breakdown-section']);

// ── Structural note ───────────────────────────────────────────────────────────
(function () {
  const el = $('structural-note');
  if (el && D.structural_note) el.textContent = D.structural_note;
  else if (el) el.style.display = 'none';
})();

// ── Growth of $10K chart ──────────────────────────────────────────────────────
(function () {
  const chart = D.growth_chart;
  if (!chart) { hide('growth-chart'); hide('chart-legend'); return; }

  const keys = Object.keys(chart);
  const dateKey = keys[0], keyA = keys[1], keyB = keys[2] || null;
  const C_A = '#2364B9', C_B = '#B2013C', C_GRID = '#DAD9D8', C_BODY = '#3D3B39', C_AXIS = '#A6A4A1';

  // Legend
  const legend = $('chart-legend');
  [[keyA, C_A], keyB ? [keyB, C_B] : null].filter(Boolean).forEach(([label, color]) => {
    const item = document.createElement('div');
    item.className = 'legend-item';
    item.innerHTML = `<div class="legend-swatch" style="background:${color}"></div><span>${esc(label)}</span>`;
    legend.appendChild(item);
  });

  function niceCeiling(v) {
    if (v <= 0) return 1;
    const exp = Math.pow(10, Math.floor(Math.log10(v)));
    const frac = v / exp;
    for (const s of [1,1.2,1.5,2,2.5,3,4,5,6,8,10]) if (frac <= s) return s * exp;
    return 10 * exp;
  }
  function fmtDate(val) {
    try { const d = new Date(val + 'T00:00:00Z'); return d.toLocaleDateString('en-US', {month:'short', year:'2-digit', timeZone:'UTC'}); }
    catch(_) { return String(val); }
  }
  function fmtDollar(v) { return '$' + v.toLocaleString('en-US', {minimumFractionDigits:2, maximumFractionDigits:2}); }
  function escXml(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }

  const dates = chart[dateKey], seriesA = chart[keyA] || [], seriesB = keyB ? (chart[keyB] || []) : [];
  const rows = dates.map((d, i) => ({
    date: d,
    a: typeof seriesA[i] === 'number' ? seriesA[i] : null,
    b: keyB && typeof seriesB[i] === 'number' ? seriesB[i] : null,
  }));

  const W = 700, H = 268, PT = 16, PR = 16, PB = 36, PL = 76;
  const PW = W - PL - PR, PH = H - PT - PB;
  const allVals = rows.flatMap(r => [r.a, r.b]).filter(v => typeof v === 'number');
  if (!allVals.length) return;

  const yMinRaw = Math.min(...allVals), yMaxRaw = Math.max(...allVals);
  const yPad = (yMaxRaw - yMinRaw || Math.abs(yMaxRaw) || 1) * 0.04;
  const yMin = yMinRaw >= 0 ? 0 : yMinRaw - yPad;
  const yMax = niceCeiling(yMaxRaw + yPad);
  const n = rows.length;
  const xFor = i => n < 2 ? PL + PW / 2 : PL + (i / (n - 1)) * PW;
  const yFor = v => PT + (1 - (v - yMin) / (yMax - yMin)) * PH;
  const colW = n > 1 ? PW / (n - 1) : PW;

  const p = [];
  p.push(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" font-family="inherit">`);
  p.push(`<defs><filter id="ts" x="-20%" y="-20%" width="140%" height="140%"><feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="#3D3B39" flood-opacity="0.12"/></filter></defs>`);

  for (let i = 0; i < 5; i++) {
    const tv = yMin + (yMax - yMin) * i / 4, ty = yFor(tv).toFixed(1);
    const isZero = Math.abs(tv) < 1e-6;
    p.push(`<line x1="${PL}" y1="${ty}" x2="${W-PR}" y2="${ty}" stroke="${isZero ? C_AXIS : C_GRID}"${isZero ? '' : ' stroke-dasharray="2,4"'}/>`);
    p.push(`<text x="${PL-8}" y="${ty}" text-anchor="end" dominant-baseline="middle" fill="${C_BODY}" font-size="11">${escXml(fmtDollar(tv))}</text>`);
  }
  p.push(`<line x1="${PL}" y1="${PT}" x2="${PL}" y2="${PT+PH}" stroke="${C_AXIS}" stroke-width="1"/>`);
  p.push(`<text transform="rotate(-90)" x="${-(PT+PH/2).toFixed(1)}" y="12" text-anchor="middle" fill="${C_BODY}" font-size="10">Growth of $10,000</text>`);

  const maxLbls = Math.min(8, n), lblEvery = Math.max(1, Math.ceil(n / maxLbls));
  for (let i = 0; i < n; i++) {
    if (i % lblEvery !== 0 && i !== n - 1) continue;
    p.push(`<text x="${xFor(i).toFixed(1)}" y="${H-10}" text-anchor="middle" fill="${C_BODY}" font-size="11">${escXml(fmtDate(rows[i].date))}</text>`);
  }

  const ptA = rows.map((r,i) => r.a != null ? `${xFor(i).toFixed(1)},${yFor(r.a).toFixed(1)}` : null).filter(Boolean);
  if (ptA.length) p.push(`<polyline points="${ptA.join(' ')}" fill="none" stroke="${C_A}" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round"/>`);
  const ptB = rows.map((r,i) => r.b != null ? `${xFor(i).toFixed(1)},${yFor(r.b).toFixed(1)}` : null).filter(Boolean);
  if (ptB.length) p.push(`<polyline points="${ptB.join(' ')}" fill="none" stroke="${C_B}" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"/>`);

  for (let i = 0; i < n; i++) {
    const r = rows[i], cx = xFor(i), tipW = 170, tipH = r.b != null ? 58 : 42;
    const anchorRight = cx + tipW + 16 > W - PR;
    const tipX = (anchorRight ? cx - tipW - 10 : cx + 10).toFixed(1);
    const refY = r.a != null ? yFor(r.a) : (r.b != null ? yFor(r.b) : PT + PH / 2);
    const tipY = Math.max(PT+4, Math.min(refY - tipH/2, PT+PH-tipH-4)).toFixed(1);
    p.push(`<g class="chart-point">`);
    p.push(`<rect class="hover-target" x="${(cx-colW/2).toFixed(1)}" y="${PT}" width="${colW.toFixed(1)}" height="${PH}" fill="transparent"/>`);
    p.push(`<line class="tick-line" x1="${cx.toFixed(1)}" y1="${PT}" x2="${cx.toFixed(1)}" y2="${PT+PH}" stroke="${C_GRID}" stroke-dasharray="3,4"/>`);
    if (r.a != null) p.push(`<circle class="data-dot" cx="${cx.toFixed(1)}" cy="${yFor(r.a).toFixed(1)}" r="4" fill="${C_A}" stroke="#fff" stroke-width="1.5"/>`);
    if (r.b != null) p.push(`<circle class="data-dot" cx="${cx.toFixed(1)}" cy="${yFor(r.b).toFixed(1)}" r="4" fill="${C_B}" stroke="#fff" stroke-width="1.5"/>`);
    p.push(`<g class="chart-tooltip-box">`);
    p.push(`<rect x="${tipX}" y="${tipY}" width="${tipW}" height="${tipH}" rx="4" ry="4" fill="#fff" stroke="${C_GRID}" stroke-width="1" filter="url(#ts)"/>`);
    p.push(`<text x="${parseFloat(tipX)+10}" y="${parseFloat(tipY)+16}" fill="${C_BODY}" font-size="11" font-weight="700">${escXml(fmtDate(r.date))}</text>`);
    if (r.a != null) {
      const ry = parseFloat(tipY)+32;
      p.push(`<line x1="${parseFloat(tipX)+10}" y1="${ry-3}" x2="${parseFloat(tipX)+21}" y2="${ry-3}" stroke="${C_A}" stroke-width="2.5" stroke-linecap="round"/>`);
      p.push(`<text x="${parseFloat(tipX)+27}" y="${ry}" fill="${C_BODY}" font-size="10">${escXml(keyA)}</text>`);
      p.push(`<text x="${parseFloat(tipX)+tipW-8}" y="${ry}" text-anchor="end" fill="${C_BODY}" font-size="10" font-variant-numeric="tabular-nums">${escXml(fmtDollar(r.a))}</text>`);
    }
    if (r.b != null) {
      const ry = parseFloat(tipY)+(r.a != null ? 50 : 32);
      p.push(`<line x1="${parseFloat(tipX)+10}" y1="${ry-3}" x2="${parseFloat(tipX)+21}" y2="${ry-3}" stroke="${C_B}" stroke-width="2.5" stroke-linecap="round"/>`);
      p.push(`<text x="${parseFloat(tipX)+27}" y="${ry}" fill="${C_BODY}" font-size="10">${escXml(keyB)}</text>`);
      p.push(`<text x="${parseFloat(tipX)+tipW-8}" y="${ry}" text-anchor="end" fill="${C_BODY}" font-size="10" font-variant-numeric="tabular-nums">${escXml(fmtDollar(r.b))}</text>`);
    }
    p.push(`</g></g>`);
  }
  p.push('</svg>');
  $('growth-chart').innerHTML = p.join('\n');
})();

// ── Trailing returns ──────────────────────────────────────────────────────────
(function () {
  if (!D.trailing_returns) return;
  const rankByPeriod = new Map((D.category_rankings?.trailing || []).map(row => [row.period, row]));
  const withRank = (value, rank) => value == null
    ? '--'
    : `${Number(value).toFixed(2)}%${rank != null ? ` (${Math.round(Number(rank))})` : ''}`;
  $('trailing-tbody').innerHTML = D.trailing_returns.map(r => {
    const a = r.a != null ? Number(r.a) : null, b = r.b != null ? Number(r.b) : null;
    const d = a != null && b != null ? a - b : null;
    const rank = rankByPeriod.get(r.period) || {};
    return `<tr>
      <td>${esc(r.period)}</td>
      <td>${withRank(a, rank.a)}</td>
      <td>${withRank(b, rank.b)}</td>
      ${returnDeltaCell(d, r.period)}
    </tr>`;
  }).join('');
})();

// ── Annual returns ────────────────────────────────────────────────────────────
(function () {
  if (!D.annual_returns) return;
  const rankByYear = new Map((D.category_rankings?.annual || []).map(row => [row.year, row]));
  const withRank = (value, rank) => value == null
    ? '--'
    : `${Number(value).toFixed(2)}%${rank != null ? ` (${Math.round(Number(rank))})` : ''}`;
  $('annual-tbody').innerHTML = D.annual_returns.map(r => {
    const a = r.a != null ? Number(r.a) : null, b = r.b != null ? Number(r.b) : null;
    const d = a != null && b != null ? a - b : null;
    const rank = rankByYear.get(r.year) || {};
    return `<tr>
      <td>${esc(r.year)}</td>
      <td>${withRank(a, rank.a)}</td>
      <td>${withRank(b, rank.b)}</td>
      ${returnDeltaCell(d, r.year, 5)}
    </tr>`;
  }).join('');
})();

// ── Risk / return scatter ────────────────────────────────────────────────────
(function () {
  const wrap = $('risk-return-wrap'), chartEl = $('risk-return-chart');
  const note = $('risk-return-footnote');
  if (!wrap || !chartEl) return;
  const rows = Array.isArray(D.risk_return) ? D.risk_return : [];
  const periodKey = period => String(period || '').toLowerCase().replace(/\s+/g, '').replace('yr', 'y');
  const usable = rows.map(row => {
    const a = row.a_return != null && row.a_risk != null
      ? { fund: 'a', ticker: D.meta.ticker_a, name: D.meta.name_a, ret: Number(row.a_return), risk: Number(row.a_risk) }
      : null;
    const b = row.b_return != null && row.b_risk != null
      ? { fund: 'b', ticker: D.meta.ticker_b, name: D.meta.name_b, ret: Number(row.b_return), risk: Number(row.b_risk) }
      : null;
    const points = [a, b].filter(p => p && Number.isFinite(p.ret) && Number.isFinite(p.risk));
    return { period: row.period || '', key: periodKey(row.period), points };
  }).filter(row => row.points.length);
  if (!usable.length) { hide('risk-return-wrap'); return; }

  const byPeriod = new Map(usable.map(row => [row.key, row]));
  riskReturnAvailablePeriods = new Set(byPeriod.keys());

  function renderRow(row) {
    const points = row.points;
    const W = 700, H = 250, PT = 20, PR = 130, PB = 42, PL = 66;
    const PW = W - PL - PR, PH = H - PT - PB;
    const C_A = '#2364B9', C_B = '#B2013C', C_GRID = '#DAD9D8', C_BODY = '#3D3B39', C_AXIS = '#A6A4A1';
    const riskVals = points.map(p => p.risk), returnVals = points.map(p => p.ret);

    function niceStep(max) {
      const raw = max / 4 || 1;
      const exp = Math.pow(10, Math.floor(Math.log10(raw)));
      const frac = raw / exp;
      const mult = frac <= 1 ? 1 : frac <= 2 ? 2 : frac <= 5 ? 5 : 10;
      return mult * exp;
    }
    function axisBounds(vals, floorAtZero = true) {
      const minRaw = Math.min(...vals), maxRaw = Math.max(...vals);
      const span = maxRaw - minRaw || Math.abs(maxRaw) || 1;
      const paddedMin = floorAtZero ? Math.max(0, minRaw - span * 0.25) : minRaw - span * 0.25;
      const paddedMax = maxRaw + span * 0.25;
      const step = niceStep(paddedMax - paddedMin);
      const min = floorAtZero ? Math.max(0, Math.floor(paddedMin / step) * step) : Math.floor(paddedMin / step) * step;
      const max = Math.ceil(paddedMax / step) * step;
      return { min, max: max > min ? max : min + step };
    }
    const x = axisBounds(riskVals, true), y = axisBounds(returnVals, false);
    const xFor = v => PL + ((v - x.min) / (x.max - x.min)) * PW;
    const yFor = v => PT + (1 - ((v - y.min) / (y.max - y.min))) * PH;
    const fmt = v => Number(v).toFixed(2);
    const escXml = s => String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');

    const p = [];
    p.push(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" font-family="inherit">`);
    p.push(`<text x="${PL}" y="14" fill="${C_BODY}" font-size="13" font-weight="700">Risk/Return Analysis${row.period ? ` (${escXml(row.period)})` : ''}</text>`);
    for (let i = 0; i <= 4; i++) {
      const xv = x.min + (x.max - x.min) * i / 4, xp = xFor(xv).toFixed(1);
      p.push(`<line x1="${xp}" y1="${PT}" x2="${xp}" y2="${PT+PH}" stroke="${C_GRID}"/>`);
      p.push(`<text x="${xp}" y="${H-18}" text-anchor="middle" fill="${C_BODY}" font-size="10">${fmt(xv)}</text>`);
    }
    for (let i = 0; i <= 4; i++) {
      const yv = y.min + (y.max - y.min) * i / 4, yp = yFor(yv).toFixed(1);
      p.push(`<line x1="${PL}" y1="${yp}" x2="${PL+PW}" y2="${yp}" stroke="${C_GRID}"/>`);
      p.push(`<text x="${PL-8}" y="${yp}" text-anchor="end" dominant-baseline="middle" fill="${C_BODY}" font-size="10">${fmt(yv)}</text>`);
    }
    p.push(`<line x1="${PL}" y1="${PT+PH}" x2="${PL+PW}" y2="${PT+PH}" stroke="${C_AXIS}" stroke-width="1.2"/>`);
    p.push(`<line x1="${PL}" y1="${PT}" x2="${PL}" y2="${PT+PH}" stroke="${C_AXIS}" stroke-width="1.2"/>`);
    p.push(`<text x="${PL+PW/2}" y="${H-3}" text-anchor="middle" fill="${C_BODY}" font-size="10">Standard Deviation %</text>`);
    p.push(`<text transform="rotate(-90)" x="${-(PT+PH/2).toFixed(1)}" y="14" text-anchor="middle" fill="${C_BODY}" font-size="10">Total Return %</text>`);

    points.forEach(point => {
      const color = point.fund === 'a' ? C_A : C_B;
      const cx = xFor(point.risk).toFixed(1), cy = yFor(point.ret).toFixed(1);
      p.push(`<circle cx="${cx}" cy="${cy}" r="6" fill="${color}" stroke="#fff" stroke-width="1.5"/>`);
      p.push(`<text x="${Number(cx)+10}" y="${Number(cy)+4}" fill="${C_BODY}" font-size="10" font-weight="700">${escXml(point.ticker)}</text>`);
    });

    const legendX = PL + PW + 14;
    points.forEach((point, i) => {
      const color = point.fund === 'a' ? C_A : C_B;
      const ly = PT + 20 + i * 22;
      p.push(`<circle cx="${legendX}" cy="${ly}" r="5" fill="${color}"/>`);
      p.push(`<text x="${legendX+12}" y="${ly+4}" fill="${C_BODY}" font-size="10">${escXml(point.ticker)}</text>`);
    });
    p.push('</svg>');
    chartEl.innerHTML = p.join('\n');
    if (note) note.textContent = `${row.period} month-end annualized total return versus standard deviation.`;
  }

  setRiskReturnPeriod = period => {
    const row = byPeriod.get(periodKey(period));
    if (!row) {
      chartEl.innerHTML = '';
      if (note) note.textContent = `${period === '1y' ? '1 Yr' : period} risk/return scatter is unavailable.`;
      return false;
    }
    renderRow(row);
    return true;
  };

  renderRow(usable[0]);
})();

// ── Risk (period tabs) ────────────────────────────────────────────────────────
(function () {
  if (!D.risk) return;
  const tabsEl = $('risk-tabs'), panelsEl = $('risk-panels');
  if (!tabsEl || !panelsEl) return;

  const tA = esc(D.meta.ticker_a), tB = esc(D.meta.ticker_b);
  const PERIOD_LABELS = {'1y':'1 Yr', '3y':'3 Yr', '5y':'5 Yr', '10y':'10 Yr'};
  const PERIOD_ORDER = ['1y','3y','5y','10y'];
  const RISK_METRICS = ['Std Dev', 'Sharpe Ratio', 'Upside Capture', 'Downside Capture'];

  const available = PERIOD_ORDER.filter(p => D.risk[p] && D.risk[p].length);
  if (!available.length) {
    hide('risk-tabs');
    hide('risk-panels');
    hide('risk-table-footnote');
    if ($('risk-return-wrap')?.style.display === 'none') hide('risk-section');
    return;
  }

  const sharedIndex = available.findIndex(period => riskReturnAvailablePeriods.has(period));
  const initialIndex = sharedIndex >= 0 ? sharedIndex : 0;

  tabsEl.innerHTML = available.map((p, i) =>
    `<button class="tab-btn${i === initialIndex ? ' active' : ''}" data-period="${p}">${PERIOD_LABELS[p] || p}</button>`
  ).join('');

  panelsEl.innerHTML = available.map((p, i) => {
    const byMetric = new Map();
    D.risk[p].forEach(r => byMetric.set(String(r.metric || '').toLowerCase(), r));
    const rows = RISK_METRICS.map(metric => {
      const r = byMetric.get(metric.toLowerCase()) || { metric, a: null, b: null };
      const a = r.a != null ? Number(r.a) : null, b = r.b != null ? Number(r.b) : null;
      const d = a != null && b != null ? a - b : null;
      return `<tr>
        <td>${esc(metric)}</td>
        <td class="th-a" style="color:var(--c-fund-a)">${a != null ? a.toFixed(2) : '--'}</td>
        <td class="th-b" style="color:var(--c-fund-b)">${b != null ? b.toFixed(2) : '--'}</td>
        <td class="${deltaCls(d)}">${d != null ? (d >= 0 ? '+' : '') + d.toFixed(2) : '--'}</td>
      </tr>`;
    }).join('');
    return `<div class="risk-panel${i === initialIndex ? ' active' : ''}" id="risk-panel-${p}">
      <table>
        <thead><tr><th>Metric</th><th>${tA}</th><th>${tB}</th><th>Δ (A−B)</th></tr></thead>
        <tbody>${rows}</tbody>
      </table>
    </div>`;
  }).join('');

  setRiskReturnPeriod(available[initialIndex]);

  tabsEl.addEventListener('click', e => {
    const btn = e.target.closest('[data-period]'); if (!btn) return;
    tabsEl.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
    panelsEl.querySelectorAll('.risk-panel').forEach(p => p.classList.remove('active'));
    btn.classList.add('active');
    const panel = $('risk-panel-' + btn.dataset.period);
    if (panel) panel.classList.add('active');
    setRiskReturnPeriod(btn.dataset.period);
  });
})();

// ── Stat card builder ─────────────────────────────────────────────────────────
function buildStatCards(rows, containerId) {
  const el = $(containerId); if (!el || !rows) return;
  const tA = esc(D.meta.ticker_a), tB = esc(D.meta.ticker_b);
  el.innerHTML = rows.map(r =>
    `<div class="stat-card">
      <div class="stat-card-label">${esc(r.metric)}</div>
      <div class="stat-card-vals">
        <div class="stat-card-val">
          <span class="stat-card-ticker sct-a">${tA}</span>
          <span class="stat-card-num">${esc(displayText(r.a))}</span>
        </div>
        <div class="stat-card-val">
          <span class="stat-card-ticker sct-b">${tB}</span>
          <span class="stat-card-num">${esc(displayText(r.b))}</span>
        </div>
      </div>
    </div>`
  ).join('');
}

// ── Ratings ───────────────────────────────────────────────────────────────────
(function () { buildStatCards(D.ratings, 'ratings-cards'); })();

// ── Narrative ─────────────────────────────────────────────────────────────────
(function () {
  const m = D.meta;
  $('analyst-label-a').textContent = `${m.ticker_a} — ${m.name_a}`;
  $('analyst-label-b').textContent = `${m.ticker_b} — ${m.name_b}`;
  $('analyst-text-a').textContent = D.analyst_a || '';
  $('analyst-text-b').textContent = D.analyst_b || '';

  const body = $('narrative-body');
  const narrativeSections = [
    ['narrative_p1', 'Mandate & Structure'],
    ['narrative_p2', 'Performance & Risk'],
    ['narrative_p3', 'Cost & Quality'],
  ];
  narrativeSections.forEach(([key, heading]) => {
    if (D[key]) {
      const section = document.createElement('div');
      section.className = 'narrative-section';
      const h = document.createElement('h3');
      h.className = 'narrative-heading';
      h.textContent = heading;
      const p = document.createElement('p');
      p.className = 'narrative-p';
      p.textContent = D[key];
      section.append(h, p);
      body.appendChild(section);
    }
  });
})();

// ── Tabs ──────────────────────────────────────────────────────────────────────
document.querySelectorAll('.tab-btn[data-tab]').forEach(btn => {
  btn.addEventListener('click', () => {
    const section = btn.closest('.section');
    section.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
    section.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
    btn.classList.add('active');
    section.querySelector('#tab-' + btn.dataset.tab).classList.add('active');
  });
});
</script>
</body>
</html>

SHA-256: 99e9edc9a4ed9ca8d81579ffb126afb5648a1a2cecc51611273ec0175a257f01