repos
43.3 KB raw
{% extends "base.html" %}
{% block title %}{{ symbol.ticker }}{% endblock %}
{% block description %}{{ symbol.name }} ({{ symbol.ticker }}): price chart, key stats, and history.{% endblock %}
{% block extra_css %}<link rel="stylesheet" href="{{ vite_asset('static_src/symbol/index.js', 'css') }}">{% endblock %}

{% from "includes/macros.html" import verdict_badge %}

{# One financials table (annual or quarterly). Both are rendered; the toggle
   script shows one. `active` decides which is visible on load. #}
{% macro fin_panel(table, period, active) %}
<div class="fin__panel" data-period="{{ period }}"{% if not active %} hidden{% endif %}>
  {% if table.periods %}
  <div class="fin__scroll">
    <table class="fin__table">
      <thead>
        <tr><th scope="col"></th>{% for p in table.periods %}<th scope="col" class="num">{{ p }}</th>{% endfor %}</tr>
      </thead>
      <tbody>
        {% for row in table.rows %}
        <tr>
          <th scope="row">{{ row.label }}</th>
          {% for c in row.cells %}<td class="num{% if c.sense %} fin-cell--{{ c.sense }}{% endif %}">{{ c.display }}{% if c.dir %}<span class="fin-cell__dir fin-cell__dir--{{ c.dir }}" aria-hidden="true"></span>{% endif %}</td>{% endfor %}
        </tr>
        {% endfor %}
      </tbody>
    </table>
  </div>
  {% else %}
  <p class="fin__empty">No {{ period }} figures have been reported.</p>
  {% endif %}
</div>
{% endmacro %}

{% block main %}
<div class="wrap">
  {# On-demand refresh control (Phase B). On load the page pulls the latest
     data for this symbol (live price always; slow SEC / metadata only when
     stale), showing the progress bar; the button re-pulls everything. The data
     for each section carries its own "synced … ago" age in its heading. #}
  <div class="refresh" data-refresh-root data-ticker="{{ symbol.ticker }}">
    <button type="button" class="refresh__btn" data-refresh aria-label="Refresh all data">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a9 9 0 1 1-2.64-6.36M21 3v6h-6"/></svg>
      <span>Refresh</span>
    </button>
    <span class="refresh__status" data-refresh-status aria-live="polite"></span>
    <div class="refresh__bar" data-refresh-bar hidden><div class="refresh__fill" data-refresh-fill></div></div>
  </div>
  {% if stale_data %}
  <div class="stale-banner" role="note">
    <strong>No recent trading data.</strong> The last price we hold for {{ symbol.ticker }}
    is from {{ stale_data.last_date|shortdate }} ({{ stale_data.days }} days ago). This symbol
    may be delisted, renamed, or halted &mdash; the figures below reflect its last active
    session, not the current market.
  </div>
  {% endif %}
  <header class="sym-head{% if health or etf_quality %} sym-head--has-health{% endif %}">
    <div class="sym-head__id">
      <h1 class="sym-head__ticker">{{ symbol.ticker }}</h1>
      <span class="sym-head__tag">{{ symbol.kind }}</span>
      {% if symbol.exchange %}<span class="sym-head__tag">{{ symbol.exchange }}</span>{% endif %}
      {% if symbol.sector %}<span class="sym-head__tag">{{ symbol.sector }}</span>{% endif %}
      {% if symbol.industry %}<span class="sym-head__tag">{{ symbol.industry }}</span>{% endif %}
    </div>
    <div class="sym-head__name">{{ symbol.name }}</div>
    {# Prefer the live quote; fall back to the most recent daily close. The
       stream client patches the data-field nodes in place as quotes arrive. #}
    {% if quote or stats %}
    {% set price = quote.price if quote else stats.close %}
    {% set chg_abs = quote.change_abs if quote else stats.change_abs %}
    {% set chg_pct = quote.change_pct if quote else stats.change_pct %}
    <div class="sym-head__quote" data-ticker="{{ symbol.ticker }}">
      <span class="sym-head__price num" data-field="price">{{ price|money }}</span>
      {% if chg_pct is not none %}
      <span class="sym-head__chg num {{ 'is-up' if chg_pct >= 0 else 'is-down' }}" data-field="change">
        {{ chg_abs|signed }} ({{ chg_pct|pct }})
      </span>
      {% endif %}
      <span class="sym-head__asof">{% if quote %}{{ quote.state_label }}{% if symbol.last_quote_at %} &middot; <span data-field="quoted">quoted {{ symbol.last_quote_at|ago }}</span>{% endif %}{% else %}Last close &middot; {{ stats.date|shortdate }}{% endif %}</span>
    </div>
    {% endif %}

    {# Mobile health verdict line (Phase 6): the same read the donut carries on
       desktop, flattened into a one-line "verdict · % · trajectory" band that
       sits directly under the price where a phone reads it first. Hidden on
       desktop, where the donut (below) is the treatment. Works for a stock
       (`health`, with a trajectory arrow) or an ETF (`etf_quality`, no
       trajectory); a symbol only ever has one. #}
    {% if health or etf_quality %}
    {% set verdict = health if health else etf_quality %}
    <div class="sym-verdict sym-verdict--{{ verdict.overall }}">
      <span class="sym-verdict__dot" aria-hidden="true"></span>
      <span class="sym-verdict__text">{{ verdict.verdict }}</span>
      <span class="sym-verdict__pct num">{{ verdict.percent }}%</span>
      {% if health and health.trajectory != 'unknown' %}
      <span class="sym-verdict__traj sym-verdict__traj--{{ health.trajectory }}">
        <span class="sym-verdict__arrow" aria-hidden="true">{% if health.trajectory == 'good' %}&uarr;{% elif health.trajectory == 'bad' %}&darr;{% else %}&rarr;{% endif %}</span>{{ health.trajectory_label }}
      </span>
      {% endif %}
    </div>
    {% endif %}

    {# Stock health badge (Phase 17, redesigned): a circular % donut anchors
       the top-right of the header. Hovering or focusing reveals the three
       sub-readings (fundamentals / trajectory / leadership) — the previous
       full Stock-health section is gone in favour of this glanceable badge. #}
    {% if health %}
    <div class="sym-head__health">
      <button type="button" class="health-badge health-badge--{{ health.overall }}"
              aria-label="Stock health: {{ health.verdict }}, {{ health.percent }}%"
              aria-describedby="health-pop">
        {# r = 50/π ≈ 15.9155 makes the circle's circumference exactly 100,
           so stroke-dasharray "N, 100" fills N% of the ring with no maths
           in the template. #}
        <svg class="health-badge__ring" viewBox="0 0 36 36" aria-hidden="true">
          <circle class="health-badge__track" cx="18" cy="18" r="15.9155" fill="none"/>
          <circle class="health-badge__fill"  cx="18" cy="18" r="15.9155" fill="none"
                  stroke-dasharray="{{ health.percent }}, 100"/>
        </svg>
        <span class="health-badge__num num">
          <span class="health-badge__pct">{{ health.percent }}</span><span class="health-badge__unit">%</span>
        </span>
        <span class="health-badge__caption">{{ health.verdict }}</span>
      </button>
      <div class="health-pop" id="health-pop" role="tooltip">
        <p class="health-pop__head">Overall <strong class="health-pop__verdict health-pop__verdict--{{ health.overall }}">{{ health.verdict }}</strong> &middot; <span class="num">{{ health.percent }}%</span></p>
        <ul class="health-pop__rows">
          <li class="health-pop__row health-pop__row--{{ health.strength }}">
            <span class="health-pop__label">Fundamentals</span>
            <span class="health-pop__value">{{ health.strength_label }}</span>
          </li>
          <li class="health-pop__row health-pop__row--{{ health.trajectory }}">
            <span class="health-pop__label">Trajectory</span>
            <span class="health-pop__value">{{ health.trajectory_label }}</span>
          </li>
          <li class="health-pop__row health-pop__row--{{ health.stability }}">
            <span class="health-pop__label">Leadership</span>
            <span class="health-pop__value">
              {%- if health.stability == 'unknown' -%}
                &mdash;
              {%- else -%}
                {{ health.stability_label }}
              {%- endif -%}
            </span>
          </li>
        </ul>
        <p class="health-pop__src">A read across fundamentals, recent price &amp; growth trajectory, and leadership stability. For fun and reading at a glance, not investment advice.</p>
      </div>
    </div>
    {% endif %}

    {# ETF quality badge (Phase 4): the fund-side sibling of the stock health
       donut. Same glanceable % ring, but the four sub-readings are the wrapper's
       quality — cost, tracking (price vs NAV), diversification, size. Reuses the
       `health-badge` / `health-pop` styling; a symbol is either a stock or an
       ETF, so only one of the two badges ever renders. #}
    {% if etf_quality %}
    <div class="sym-head__health sym-head__health--etf">
      <button type="button" class="health-badge health-badge--{{ etf_quality.overall }}"
              aria-label="ETF quality: {{ etf_quality.verdict }}, {{ etf_quality.percent }}%"
              aria-describedby="etf-quality-pop">
        <svg class="health-badge__ring" viewBox="0 0 36 36" aria-hidden="true">
          <circle class="health-badge__track" cx="18" cy="18" r="15.9155" fill="none"/>
          <circle class="health-badge__fill"  cx="18" cy="18" r="15.9155" fill="none"
                  stroke-dasharray="{{ etf_quality.percent }}, 100"/>
        </svg>
        <span class="health-badge__num num">
          <span class="health-badge__pct">{{ etf_quality.percent }}</span><span class="health-badge__unit">%</span>
        </span>
        <span class="health-badge__caption">{{ etf_quality.verdict }}</span>
      </button>
      <div class="health-pop" id="etf-quality-pop" role="tooltip">
        <p class="health-pop__head">ETF quality <strong class="health-pop__verdict health-pop__verdict--{{ etf_quality.overall }}">{{ etf_quality.verdict }}</strong> &middot; <span class="num">{{ etf_quality.percent }}%</span></p>
        <ul class="health-pop__rows">
          <li class="health-pop__row health-pop__row--{{ etf_quality.cost }}">
            <span class="health-pop__label">Cost</span>
            <span class="health-pop__value">{{ etf_quality.cost_label }}</span>
          </li>
          <li class="health-pop__row health-pop__row--{{ etf_quality.tracking }}">
            <span class="health-pop__label">Tracking</span>
            <span class="health-pop__value">{{ etf_quality.tracking_label }}</span>
          </li>
          <li class="health-pop__row health-pop__row--{{ etf_quality.diversification }}">
            <span class="health-pop__label">Diversification</span>
            <span class="health-pop__value">{{ etf_quality.diversification_label }}</span>
          </li>
          <li class="health-pop__row health-pop__row--{{ etf_quality.size }}">
            <span class="health-pop__label">Size</span>
            <span class="health-pop__value">{{ etf_quality.size_label }}</span>
          </li>
        </ul>
        <p class="health-pop__src">A read on the fund wrapper: cost, how tightly it tracks NAV, diversification, and size. For reading at a glance, not investment advice.</p>
      </div>
    </div>
    {% endif %}
  </header>

  {# Chart and key stats need daily history. A future has none by design
     (live quotes only), and so do the historyless indexes; those fall to the
     explanatory empty state below instead of an empty chart. #}
  {% if stats %}
  <section class="panel chart-panel">
    <div class="chart-bar">
      {# Daily-candle ranges. Default 1Y. (The old 1D / 1W intraday ranges were
         dropped — the demand-only model can't keep enough 15m bars to draw them
         legibly; short-range detail comes from the denser-candle ranges.) #}
      <div class="range-bar">
        {% for r in ["YTD", "1M", "3M", "6M", "1Y", "3Y", "5Y", "MAX"] %}
        <button type="button" class="range-btn{% if r == '1Y' %} is-active{% endif %}" data-range="{{ r }}">{{ r }}</button>
        {% endfor %}
      </div>
      {# Filled by chart.js after each load: the % / absolute move over the
         visible range, so the headline change agrees with the chart. #}
      <p class="range-summary" id="range-summary" hidden></p>
    </div>
    {# Indicator toggles. `is-active` is the initial visibility; chart.js reads
       it and paints each swatch from its own ink palette. #}
    <div class="ind-bar" role="group" aria-label="Chart indicators">
      {% for ind in [
        {"key": "sma50",  "label": "SMA 50",  "on": true,  "dot": true,  "hide": false},
        {"key": "sma200", "label": "SMA 200", "on": true,  "dot": true,  "hide": false},
        {"key": "ema21",  "label": "EMA 21",  "on": false, "dot": true,  "hide": false},
        {"key": "supertrend", "label": "Supertrend", "on": false, "dot": true, "hide": false},
        {"key": "volume", "label": "Volume",  "on": true,  "dot": false, "hide": false},
        {"key": "rsi",    "label": "RSI",     "on": true,  "dot": true,  "hide": false},
        {"key": "benchmark", "label": (symbol.benchmark or "Benchmark"), "on": false, "dot": true, "hide": (not symbol.benchmark)}
      ] %}
      <button type="button" class="ind-btn{% if ind.on %} is-active{% endif %}"
              data-ind="{{ ind.key }}" aria-pressed="{{ 'true' if ind.on else 'false' }}"
              {% if ind.hide %}hidden{% endif %}>
        {% if ind.dot %}<span class="ind-btn__dot" aria-hidden="true"></span>{% endif %}{{ ind.label }}
      </button>
      {% endfor %}
    </div>
    <div id="chart" data-ticker="{{ symbol.ticker }}"></div>
  </section>

  {# Colour-coded read of the chart's indicators: an overall trend verdict, an
     RSI momentum gauge, and one tile per moving-average signal. Mechanical, not
     advice. #}
  {% if indicators %}
  <section class="panel ind-read">
    <div class="ind-read__head">
      <h2 class="ind-read__title">What the indicators say</h2>
      <span class="ind-verdict ind-verdict--{{ indicators.verdict_tone }}">
        <span class="ind-verdict__dot" aria-hidden="true"></span>{{ indicators.verdict }}
        <span class="ind-verdict__note">{{ indicators.verdict_note }}</span>
      </span>
    </div>

    {# RSI momentum gauge: a 0–100 track with oversold / overbought zones and a
       marker at the current value, coloured by its zone. #}
    <div class="ind-rsi ind-tone--{{ indicators.rsi_tone }}">
      <div class="ind-rsi__top">
        <span class="ind-rsi__label">Momentum &middot; RSI 14</span>
        <span class="ind-rsi__verdict">{{ indicators.rsi_label }}</span>
      </div>
      <div class="ind-rsi__track">
        <span class="ind-rsi__zone ind-rsi__zone--over"></span>
        <span class="ind-rsi__zone ind-rsi__zone--under"></span>
        <span class="ind-rsi__marker" style="left: {{ indicators.rsi_pos }}%">
          <span class="ind-rsi__bubble num">{{ indicators.rsi|round(0)|int }}</span>
        </span>
      </div>
      <div class="ind-rsi__scale">
        <span>0</span><span>oversold 30</span><span>overbought 70</span><span>100</span>
      </div>
      <p class="ind-rsi__note">{{ indicators.rsi_note }}</p>
    </div>

    {# Trend signal tiles, colour-coded bullish / bearish. #}
    <div class="ind-signals">
      {% for sig in indicators.signals %}
      <div class="ind-sig ind-tone--{{ sig.tone }}">
        <div class="ind-sig__top">
          <span class="ind-sig__label">{{ sig.label }}</span>
          {% if sig.value %}<span class="ind-sig__value num">{{ sig.value }}</span>{% endif %}
        </div>
        <div class="ind-sig__status">{{ sig.status }}</div>
        <div class="ind-sig__note">{{ sig.note }}</div>
      </div>
      {% endfor %}
    </div>

    <p class="ind-read__foot">A mechanical reading of the chart&rsquo;s indicators &mdash; not investment advice.</p>
  </section>
  {% endif %}

  <h2 class="section-title">Key stats<span class="section-title__asof">as of {{ stats.date|shortdate }}</span></h2>
  <section class="keystats">

    {# --- the trading day: open & close placed in the day's range --- #}
    <div class="gauge">
      <div class="gauge__row">
        <span class="gauge__label">The day</span>
        <span class="gauge__meta">open and close within the day's range</span>
      </div>
      <div class="track gauge__track">
        <span class="track__pip track__pip--ghost" style="left:{{ stats.day_open_pos }}%"></span>
        <span class="track__pip track__pip--{{ 'down' if stats.change_pct is not none and stats.change_pct < 0 else 'up' }}" style="left:{{ stats.day_close_pos }}%"></span>
      </div>
      <div class="gauge__ends">
        <span><span class="gauge__cap">Low</span> <span class="num">{{ stats.low|money }}</span></span>
        <span class="gauge__ends-r"><span class="gauge__cap">High</span> <span class="num">{{ stats.high|money }}</span></span>
      </div>
      <dl class="legend">
        <div class="legend__item">
          <dt><i class="legend__dot legend__dot--ghost"></i> Open</dt>
          <dd class="num">{{ stats.open|money }}</dd>
          {% if stats.open_change_pct is not none %}
          <dd class="legend__delta num {{ 'is-up' if stats.open_change_pct >= 0 else 'is-down' }}">{{ stats.open_change_pct|pct }}</dd>
          {% else %}<dd></dd>{% endif %}
        </div>
        <div class="legend__item">
          <dt><i class="legend__dot legend__dot--{{ 'down' if stats.change_pct is not none and stats.change_pct < 0 else 'up' }}"></i> Close</dt>
          <dd class="num">{{ stats.close|money }}</dd>
          {% if stats.change_pct is not none %}
          <dd class="legend__delta num {{ 'is-up' if stats.change_pct >= 0 else 'is-down' }}">{{ stats.change_pct|pct }}</dd>
          {% else %}<dd></dd>{% endif %}
        </div>
      </dl>
    </div>

    {# --- 52-week range: current price & prev close along the year --- #}
    <div class="gauge">
      <div class="gauge__row">
        <span class="gauge__label">52-week range</span>
        <span class="gauge__meta">where the price sits across its year</span>
      </div>
      <div class="track gauge__track">
        {% if stats.yr_prev_pos is not none %}
        <span class="track__pip track__pip--ghost" style="left:{{ stats.yr_prev_pos }}%"></span>
        {% endif %}
        <span class="track__pip track__pip--{{ 'down' if stats.change_pct is not none and stats.change_pct < 0 else 'up' }}" style="left:{{ stats.yr_close_pos }}%"></span>
      </div>
      <div class="gauge__ends">
        <span><span class="gauge__cap">52w low</span> <span class="num">{{ stats.low_52w|money }}</span></span>
        <span class="gauge__ends-r"><span class="gauge__cap">52w high</span> <span class="num">{{ stats.high_52w|money }}</span></span>
      </div>
      <dl class="legend">
        <div class="legend__item">
          <dt><i class="legend__dot legend__dot--{{ 'down' if stats.change_pct is not none and stats.change_pct < 0 else 'up' }}"></i> Current</dt>
          <dd class="num">{{ stats.close|money }}</dd><dd></dd>
        </div>
        {% if stats.prev_close is not none %}
        <div class="legend__item">
          <dt><i class="legend__dot legend__dot--ghost"></i> Prev close</dt>
          <dd class="num">{{ stats.prev_close|money }}</dd><dd></dd>
        </div>
        {% endif %}
      </dl>
    </div>

    {# --- volume vs its own 3-month average --- #}
    <div class="gauge">
      <div class="gauge__row">
        <span class="gauge__label">Volume</span>
        <span class="gauge__meta">
          {% if stats.vol_ratio is not none %}{{ stats.vol_ratio|round(2) }}&times; the 3-month average{% else %}today's share volume{% endif %}
        </span>
      </div>
      <div class="track gauge__track gauge__track--bar">
        <span class="track__fill" style="width:{{ stats.vol_fill_pct }}%"></span>
        <span class="track__pip track__pip--ghost" style="left:50%"></span>
      </div>
      <div class="gauge__ends">
        <span><span class="gauge__cap">0</span></span>
        <span class="gauge__ends-c"><span class="gauge__cap">avg</span></span>
        <span class="gauge__ends-r"><span class="gauge__cap">2&times; avg</span></span>
      </div>
      <dl class="legend">
        <div class="legend__item">
          <dt><i class="legend__dot"></i> Today</dt>
          <dd class="num">{{ stats.volume|compact }}</dd><dd></dd>
        </div>
        <div class="legend__item">
          <dt><i class="legend__dot legend__dot--ghost"></i> 3-mo avg</dt>
          <dd class="num">{{ stats.avg_volume|compact }}</dd><dd></dd>
        </div>
      </dl>
    </div>

  </section>
  {% else %}
  <section class="empty">
    {% if symbol.kind == 'future' %}
    <p><strong>{{ symbol.ticker }}</strong> is a futures contract. Finance follows
    futures with live quotes only, so there is no historical daily chart; the
    price above updates live while the market is trading.</p>
    {% else %}
    <p>No daily price history is available for {{ symbol.ticker }}.</p>
    {% endif %}
  </section>
  {% endif %}

  {# --- fundamentals + financials: stocks only --- #}
  {% if symbol.kind == 'stock' %}

  {# --- earnings dates (Phase 25): next-expected date with provenance, days
         since the last print, and a short list of recent past dates. Past
         dates come from 8-K item-2.02 filings (already stored by Phase 14);
         the next date is Yahoo's `calendarEvents` when present, otherwise a
         cadence estimate from those past dates. --- #}
  {% if earnings %}
  <h2 class="section-title">Earnings{% if earnings.earnings_synced_at %}<span class="section-title__asof">calendar synced from Yahoo {{ earnings.earnings_synced_at|ago }}</span>{% endif %}</h2>
  <section class="panel earn">
    <dl class="earn__pair">
      <div class="earn__cell">
        <dt class="earn__cap">Most recent</dt>
        {% if earnings.most_recent %}
        <dd class="earn__val num">{{ earnings.most_recent.date|shortdate }}<span class="earn__sub">{{ earnings.most_recent.days_ago }} day{% if earnings.most_recent.days_ago != 1 %}s{% endif %} ago</span></dd>
        {% else %}
        <dd class="earn__val">&mdash;<span class="earn__sub">no past earnings filed</span></dd>
        {% endif %}
      </div>
      <div class="earn__cell">
        <dt class="earn__cap">Next expected</dt>
        {% if earnings.next_date %}
        <dd class="earn__val num">{{ earnings.next_date|shortdate }}<span class="earn__sub">
          {%- if earnings.next_days is not none -%}
            {%- if earnings.next_days >= 0 -%}
              in {{ earnings.next_days }} day{% if earnings.next_days != 1 %}s{% endif %}
            {%- else -%}
              {{ earnings.next_days|abs }} day{% if earnings.next_days|abs != 1 %}s{% endif %} ago
            {%- endif -%}
          {%- endif -%}
          {% if earnings.next_source == 'estimate' %} &middot; estimated from cadence{% endif %}
        </span></dd>
        {% else %}
        <dd class="earn__val">&mdash;<span class="earn__sub">no upcoming date on file</span></dd>
        {% endif %}
      </div>
    </dl>
    {% if earnings.past %}
    <div class="earn__past">
      <h3 class="earn__past-title">Recent earnings dates</h3>
      <ul class="earn__list">
        {% for e in earnings.past %}
        <li class="earn-row">
          <span class="earn-row__date num">{{ e.date|shortdate }}</span>
          <span class="earn-row__ago">{{ e.days_ago }} day{% if e.days_ago != 1 %}s{% endif %} ago</span>
        </li>
        {% endfor %}
      </ul>
    </div>
    {% endif %}
    <p class="earn__src">Past earnings dates come from {{ symbol.ticker }}&rsquo;s 8-K item&nbsp;2.02 filings; the next date is Yahoo&rsquo;s when available, otherwise estimated from the recent reporting cadence.</p>
  </section>
  {% endif %}

  <h2 class="section-title">Fundamentals{% if fundamentals and fundamentals.ratios and symbol.fundamentals_synced_at %}<span class="section-title__asof">synced from SEC {{ symbol.fundamentals_synced_at|ago }}</span>{% endif %}</h2>
  {% if fundamentals and fundamentals.ratios %}
  {# The rolled-up standing badge sits above the per-ratio cards (Phase 20). #}
  {% if standing %}
  <div class="fund-standing">
    {{ verdict_badge(standing) }}
    <p class="fund-standing__text">Across the nine graded ratios{% if fundamentals.basis %}
      ({{ fundamentals.basis }} balance-sheet figures{% if fundamentals.pe_basis %}, P/E on {{ fundamentals.pe_basis }} earnings{% endif %}, against the latest price){% endif %},
      {{ symbol.ticker }}&rsquo;s fundamentals read as <strong>{{ standing.verdict|lower }}</strong> overall.</p>
  </div>
  {% elif fundamentals.basis %}
  <p class="fund-basis">Balance-sheet ratios read {{ fundamentals.basis }} figures{% if fundamentals.pe_basis %}; the P/E uses {{ fundamentals.pe_basis }} EPS{% endif %}, against the latest price.</p>
  {% endif %}
  {% if fundamentals.earnings_stale %}<p class="fund-stale" role="note">Latest SEC earnings are from {{ fundamentals.earnings_period }}; the price-based ratios may lag a more recent quarter.</p>{% endif %}
  <section class="ratios">
    {% for r in fundamentals.ratios %}
    <div class="ratio ratio--{{ r.grade }}">
      <div class="ratio__head">
        <span class="ratio__label">{{ r.label }}</span>
        <span class="ratio__badge ratio__badge--{{ r.grade }}">{{ r.verdict }}</span>
      </div>
      <div class="ratio__value num">{{ r.display }}</div>
      <p class="ratio__reading">{{ r.reading }}</p>
      <p class="ratio__explain">{{ r.explain }}</p>
    </div>
    {% endfor %}
  </section>
  {% else %}
  <div class="fund-pending">
    {% if symbol.fundamentals_synced_at %}
    No SEC fundamentals are available for {{ symbol.ticker }}. It may not file
    financial reports with the SEC (a foreign issuer or non-filer), or the
    symbol may be delisted. (Checked {{ symbol.fundamentals_synced_at|ago }}.)
    {% else %}
    SEC fundamentals for {{ symbol.ticker }} have not synced yet. They are pulled
    on demand &mdash; hit <strong>Refresh</strong> above to fetch them now.
    {% endif %}
  </div>
  {% endif %}

  {% if fundamentals and (fundamentals.has_annual or fundamentals.has_quarterly) %}
  <h2 class="section-title">Financials{% if symbol.fundamentals_synced_at %}<span class="section-title__asof">synced from SEC {{ symbol.fundamentals_synced_at|ago }}</span>{% endif %}</h2>
  <section class="panel fin">
    <div class="fin__toggle" role="tablist" aria-label="Reporting period">
      <button type="button" class="fin__tab is-active" data-period="annual" aria-selected="true">Annual</button>
      <button type="button" class="fin__tab" data-period="quarterly" aria-selected="false">Quarterly</button>
    </div>
    {{ fin_panel(fundamentals.annual, "annual", true) }}
    {{ fin_panel(fundamentals.quarterly, "quarterly", false) }}
  </section>
  {% endif %}

  {# --- leadership: officers, board & recent changes (Phase 14) --- #}
  <h2 class="section-title">Leadership{% if symbol.leadership_synced_at %}<span class="section-title__asof">synced from SEC {{ symbol.leadership_synced_at|ago }}</span>{% endif %}</h2>
  {% if leadership and leadership.roster %}
  <section class="panel leadership">
    <ul class="roster">
      {% for p in leadership.roster %}
      <li class="roster__row">
        <span class="roster__name">{{ p.name }}</span>
        <span class="roster__role">{{ p.role }}</span>
      </li>
      {% endfor %}
    </ul>
    <p class="roster__src">Officers and board are drawn from {{ symbol.ticker }}&rsquo;s recent SEC ownership filings (Forms&nbsp;3,&nbsp;4 and&nbsp;5).</p>
    {% if leadership.changes %}
    <div class="lead-changes">
      <h3 class="lead-changes__title">Recent leadership changes</h3>
      <ul class="lead-changes__list">
        {% for c in leadership.changes %}
        <li class="lead-change">
          <a class="lead-change__link" href="{{ c.url }}" target="_blank" rel="noopener noreferrer">
            <span class="lead-change__body">
              <span class="lead-change__desc">Officer or director change</span>
              <span class="lead-change__meta">Reported in an 8-K filed {{ c.filed_at|shortdate }}</span>
            </span>
            <svg class="filing__ext" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <path d="M14 5h5v5M19 5l-9 9M11 5H6a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-5"/>
            </svg>
          </a>
        </li>
        {% endfor %}
      </ul>
    </div>
    {% endif %}
  </section>
  {% elif leadership and leadership.synced %}
  <div class="fund-pending">No leadership data is available for {{ symbol.ticker }}.</div>
  {% else %}
  <div class="fund-pending">
    Leadership data for {{ symbol.ticker }} has not synced yet. It is pulled on
    demand &mdash; hit <strong>Refresh</strong> above to fetch it now.
  </div>
  {% endif %}

  {% endif %}

  {# --- per-ticker anomaly feed (Phase 16): hidden when there are no
         qualifying events in the past year --- #}
  {% if anomalies %}
  <h2 class="section-title">Notable recent events<span class="section-title__asof">past year</span></h2>
  <section class="panel anomalies">
    <ul class="anomaly-list">
      {% for e in anomalies.events %}
      <li class="anomaly anomaly--{{ e.glyph }} anomaly--{{ e.polarity }}">
        {% if e.url %}<a class="anomaly__link" href="{{ e.url }}" target="_blank" rel="noopener noreferrer">{% else %}<span class="anomaly__link">{% endif %}
          <span class="anomaly__date num">{{ e.date|shortdate }}</span>
          <span class="anomaly__glyph anomaly__glyph--{{ e.glyph }}" aria-hidden="true">{% if e.glyph == 'up' %}&uarr;{% elif e.glyph == 'down' %}&darr;{% elif e.glyph == 'drawdown' %}&#x21A1;{% elif e.glyph == 'fund-up' %}&plus;{% elif e.glyph == 'fund-down' %}&minus;{% else %}&#x2756;{% endif %}</span>
          <span class="anomaly__body">{{ e.headline }}</span>
          <span class="anomaly__ext" aria-hidden="true">
            {% if e.url %}
            <svg class="filing__ext" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M14 5h5v5M19 5l-9 9M11 5H6a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-5"/>
            </svg>
            {% endif %}
          </span>
        {% if e.url %}</a>{% else %}</span>{% endif %}
      </li>
      {% endfor %}
    </ul>
    <p class="anomaly-list__src">Large daily moves, new 6-month lows, year-over-year fundamentals swings, and reported leadership changes &mdash; pulled from the data this app already holds, never investment advice.</p>
  </section>
  {% endif %}

  {# --- ETFs only: Phase 18 (fund profile) + Phase 28 (about / sector / --- #}
  {# --- geography / trailing returns / growth of $10k / benchmark) --- #}
  {% if symbol.kind == 'etf' %}

  {# About this fund: expense ratio, yield, NAV + premium/discount, family, #}
  {# category, inception, and the issuer's strategy paragraph (Phase 28). #}
  <h2 class="section-title">About this fund{% if fund_meta and symbol.fund_metadata_synced_at %}<span class="section-title__asof">synced from Yahoo {{ symbol.fund_metadata_synced_at|ago }}</span>{% endif %}</h2>
  {% if fund_meta %}
  <section class="panel fund-about">
    <dl class="fund-about__stats">
      <div class="fund-about__stat">
        <dt class="fund-about__cap">Expense ratio</dt>
        <dd class="fund-about__val num">{{ fund_meta.expense_ratio }}</dd>
      </div>
      <div class="fund-about__stat">
        <dt class="fund-about__cap">Distribution yield</dt>
        <dd class="fund-about__val num">{{ fund_meta.yield_pct }}</dd>
      </div>
      {% if fund_meta.nav_price is not none %}
      <div class="fund-about__stat">
        <dt class="fund-about__cap">NAV</dt>
        <dd class="fund-about__val num">{{ fund_meta.nav_price|money }}{% if fund_meta.premium %}<span class="fund-about__sub fund-about__sub--{{ fund_meta.premium.grade }}">{{ fund_meta.premium.text }} to price</span>{% endif %}</dd>
      </div>
      {% endif %}
      {% if fund_meta.inception_date %}
      <div class="fund-about__stat">
        <dt class="fund-about__cap">Inception</dt>
        <dd class="fund-about__val num">{{ fund_meta.inception_date|shortdate }}</dd>
      </div>
      {% endif %}
      {% if fund_meta.category %}
      <div class="fund-about__stat">
        <dt class="fund-about__cap">Category</dt>
        <dd class="fund-about__val">{{ fund_meta.category }}</dd>
      </div>
      {% endif %}
      {% if fund_meta.fund_family %}
      <div class="fund-about__stat">
        <dt class="fund-about__cap">Family</dt>
        <dd class="fund-about__val">{{ fund_meta.fund_family }}</dd>
      </div>
      {% endif %}
    </dl>
    {% if fund_meta.strategy_summary %}
    <p class="fund-about__summary">{{ fund_meta.strategy_summary }}</p>
    {% endif %}
  </section>
  {% else %}
  <div class="fund-pending">
    {% if symbol.fund_metadata_synced_at %}
    No fund details are available for {{ symbol.ticker }} from Yahoo. The fund may
    be delisted or renamed, or Yahoo may not carry details for it.
    (Checked {{ symbol.fund_metadata_synced_at|ago }}.)
    {% else %}
    Fund details for {{ symbol.ticker }} have not synced yet. They are pulled on
    demand &mdash; hit <strong>Refresh</strong> above to fetch them now.
    {% endif %}
  </div>
  {% endif %}

  {# Trailing returns (Phase 28). Eight periods, annualised past 1 year. #}
  {% if returns %}
  <h2 class="section-title">Trailing returns{% if symbol.history_synced_at %}<span class="section-title__asof">from price history</span>{% endif %}</h2>
  <section class="panel ret-panel">
    <table class="ret-table">
      <thead>
        <tr><th scope="col">Window</th><th scope="col" class="num">Return</th><th scope="col" class="num">Annualised</th></tr>
      </thead>
      <tbody>
        {% for r in returns %}
        <tr>
          <th scope="row">{{ r.label }}</th>
          <td class="num{% if r.dir > 0 %} ret-cell--up{% elif r.dir < 0 %} ret-cell--down{% endif %}">{{ r.pct }}</td>
          <td class="num ret-cell--ann">{{ r.annualised }}</td>
        </tr>
        {% endfor %}
      </tbody>
    </table>
    <p class="ret-panel__src">Cumulative price returns from {{ symbol.ticker }}&rsquo;s stored daily closes. Distributions are not folded in; see Distributions below.</p>
  </section>

  {# Growth of $10,000 (Phase 28). Drawn client-side from the longest #}
  {# available range, alongside the curated benchmark when one is set. #}
  <h2 class="section-title">Growth of $10,000{% if symbol.benchmark %}<span class="section-title__asof">vs {{ symbol.benchmark }}</span>{% endif %}</h2>
  <section class="panel growth-panel">
    <div id="growth-chart" data-ticker="{{ symbol.ticker }}"{% if symbol.benchmark %} data-benchmark="{{ symbol.benchmark }}"{% endif %}></div>
    <p class="growth-panel__src">A $10,000 investment scaled by the fund&rsquo;s daily closes since inception.{% if symbol.benchmark %} The dashed line tracks the same amount in {{ symbol.benchmark }} from the fund&rsquo;s first trading day forward.{% endif %}</p>
  </section>
  {% endif %}

  <h2 class="section-title">Fund profile{% if fund and symbol.fund_synced_at %}<span class="section-title__asof">synced from SEC {{ symbol.fund_synced_at|ago }}</span>{% endif %}</h2>
  {% if fund %}
  <section class="panel fund">
    <dl class="fund__stats">
      <div class="fund__stat">
        <dt class="fund__stat-cap">Net assets</dt>
        <dd class="fund__stat-val num">{{ fund.net_assets if fund.net_assets else '—' }}</dd>
      </div>
      {% if not fund.is_commodity %}
      <div class="fund__stat">
        <dt class="fund__stat-cap">Holdings</dt>
        <dd class="fund__stat-val num">{{ fund.holdings_count if fund.holdings_count is not none else '—' }}</dd>
      </div>
      {% endif %}
      {% if fund.report_date %}
      <div class="fund__stat">
        <dt class="fund__stat-cap">As of</dt>
        <dd class="fund__stat-val num">{{ fund.report_date }}</dd>
      </div>
      {% endif %}
    </dl>
    {% if fund.is_commodity %}
    <p class="fund__note">{{ symbol.ticker }} is a grantor trust: it holds the physical commodity directly rather than a portfolio of securities, so it reports no holdings.</p>
    {% elif fund.asset_mix %}
    <div class="fund__mix">
      <h3 class="fund__mix-label">Asset mix</h3>
      <div class="mixbar">
        {% for s in fund.asset_mix %}
        <span class="mixbar__seg" style="width:{{ s.width }}%" title="{{ s.label }} {{ s.pct }}"></span>
        {% endfor %}
      </div>
      <dl class="mixlegend">
        {% for s in fund.asset_mix %}
        <div class="mixlegend__item">
          <dt><i class="mixlegend__dot"></i>{{ s.label }}</dt>
          <dd class="num">{{ s.pct }}</dd>
        </div>
        {% endfor %}
      </dl>
    </div>
    {% endif %}
    {# Phase 28 mixes hide when degenerate: an equity ETF whose issuer #}
    {# category rolls up entirely to "Corporate" gets a single-bucket #}
    {# sector_mix, which carries no information — skip it then. #}
    {% if fund.sector_mix and fund.sector_mix|length > 1 %}
    <div class="fund__mix">
      <h3 class="fund__mix-label">Issuer sectors</h3>
      <div class="mixbar">
        {% for s in fund.sector_mix %}
        <span class="mixbar__seg" style="width:{{ s.width }}%" title="{{ s.label }} {{ s.pct }}"></span>
        {% endfor %}
      </div>
      <dl class="mixlegend">
        {% for s in fund.sector_mix %}
        <div class="mixlegend__item">
          <dt><i class="mixlegend__dot"></i>{{ s.label }}</dt>
          <dd class="num">{{ s.pct }}</dd>
        </div>
        {% endfor %}
      </dl>
    </div>
    {% endif %}
    {% if fund.geography_mix and fund.geography_mix|length > 1 %}
    <div class="fund__mix">
      <h3 class="fund__mix-label">Geography</h3>
      <div class="mixbar">
        {% for s in fund.geography_mix %}
        <span class="mixbar__seg" style="width:{{ s.width }}%" title="{{ s.label }} {{ s.pct }}"></span>
        {% endfor %}
      </div>
      <dl class="mixlegend">
        {% for s in fund.geography_mix %}
        <div class="mixlegend__item">
          <dt><i class="mixlegend__dot"></i>{{ s.label }}</dt>
          <dd class="num">{{ s.pct }}</dd>
        </div>
        {% endfor %}
      </dl>
    </div>
    {% endif %}
  </section>

  {% if fund.holdings %}
  <h2 class="section-title">Top holdings{% if fund.report_date %}<span class="section-title__asof">holdings as of {{ fund.report_date|shortdate }}</span>{% endif %}</h2>
  <section class="panel holdings">
    <ol class="hold-list">
      {% for h in fund.holdings %}
      <li class="hold">
        <span class="hold__fill" style="width:{{ h.bar_pct }}%"></span>
        <span class="hold__rank num">{{ h.rank }}</span>
        <span class="hold__name">{{ h.name }}</span>
        <span class="hold__wt num">{{ h.weight }}</span>
        <span class="hold__val num">{{ h.value }}</span>
      </li>
      {% endfor %}
    </ol>
  </section>
  {% endif %}
  {% else %}
  <div class="fund-pending">
    {% if symbol.fund_synced_at %}
    No fund profile is available for {{ symbol.ticker }} from the SEC. The fund may
    be delisted or renamed, or it does not file N-PORT portfolio reports.
    (Checked {{ symbol.fund_synced_at|ago }}.)
    {% else %}
    The fund profile for {{ symbol.ticker }} has not synced yet. It is pulled on
    demand &mdash; hit <strong>Refresh</strong> above to fetch it now.
    {% endif %}
  </div>
  {% endif %}
  {% endif %}

  {# --- dividends / distributions: stocks AND ETFs (Phase 26 + 28) --- #}
  {% if dividends %}
  {% set div_label = 'Distributions' if symbol.kind == 'etf' else 'Dividends' %}
  <h2 class="section-title">{{ div_label }}{% if symbol.dividends_synced_at %}<span class="section-title__asof">synced from Yahoo {{ symbol.dividends_synced_at|ago }}</span>{% endif %}</h2>
  {% if dividends.synced and dividends.history %}
  <section class="panel div-panel">
    {# headline: inferred cadence + the on-track verdict pill #}
    <div class="div-head">
      <span class="div-cadence">{{ dividends.pace.cadence_caption }}</span>
      {% if dividends.projection_display %}
      <span class="vbadge vbadge--{{ dividends.pace.grade }}">{{ dividends.pace.verdict }}</span>
      {% endif %}
    </div>

    {# pace: prior calendar year vs YTD-projected current year #}
    <dl class="div-pace">
      <div class="div-pace__item">
        <dt class="div-pace__cap">{{ dividends.current_year - 1 }} total</dt>
        <dd class="div-pace__val num">{% if dividends.pace.prior_year_total > 0 %}{{ dividends.prior_year_display }}{% else %}—{% endif %}</dd>
      </div>
      <div class="div-pace__item">
        <dt class="div-pace__cap">{{ dividends.current_year }} so far</dt>
        <dd class="div-pace__val num">{{ dividends.ytd_display }}<span class="div-pace__sub">{{ dividends.pace.ytd_count }} payment{% if dividends.pace.ytd_count != 1 %}s{% endif %}</span></dd>
      </div>
      {% if dividends.projection_display %}
      <div class="div-pace__item">
        <dt class="div-pace__cap">{{ dividends.current_year }} projected</dt>
        <dd class="div-pace__val num">{{ dividends.projection_display }}<span class="div-pace__sub div-pace__sub--{{ dividends.pace.grade }}">{{ dividends.pct_change_display }} vs {{ dividends.current_year - 1 }}</span></dd>
      </div>
      {% endif %}
    </dl>
    <p class="div-pace__src">Projection scales {{ symbol.ticker }}&rsquo;s declared payments so far this year up to the inferred cadence, then compares to last year&rsquo;s total. Between payouts it is an estimate, not a guarantee.</p>

    {# per-event history, newest first #}
    <h3 class="div-hist__title">Payment history</h3>
    <ul class="div-hist">
      {% for d in dividends.history %}
      <li class="div-row">
        <span class="div-row__date">{{ d.ex_date|shortdate }}</span>
        <span class="div-row__amt num">{{ d.amount }}</span>
      </li>
      {% endfor %}
    </ul>
  </section>
  {% elif dividends.synced %}
  <div class="fund-pending">{{ symbol.ticker }} has not {% if symbol.kind == 'etf' %}made a distribution{% else %}paid a dividend{% endif %} in the past five years.</div>
  {% else %}
  <div class="fund-pending">
    {{ div_label }} history for {{ symbol.ticker }} has not synced yet. It is
    pulled on demand &mdash; hit <strong>Refresh</strong> above to fetch it now.
  </div>
  {% endif %}
  {% endif %}

  {# --- SEC filings: stocks and ETFs --- #}
  {% if filings %}
  {# A stock's filings sync stamps `filings_synced_at`; an ETF's ride along
     with the Phase 18 fund-profile sweep, which stamps `fund_synced_at`. #}
  {% set filings_synced = symbol.filings_synced_at if symbol.filings_synced_at else symbol.fund_synced_at %}
  <h2 class="section-title">Recent SEC filings{% if filings_synced %}<span class="section-title__asof">synced from SEC {{ filings_synced|ago }}</span>{% endif %}</h2>
  <section class="panel filings">
    <ul class="filing-list">
      {% for f in filings %}
      <li class="filing">
        <a class="filing__link" href="{{ f.url }}" target="_blank" rel="noopener noreferrer">
          <span class="filing__form num">{{ f.form }}</span>
          <span class="filing__body">
            <span class="filing__desc">{{ f.title }}</span>
            <span class="filing__meta">Filed {{ f.filed_at|shortdate }}{% if f.period_of_report %} &middot; period {{ f.period_of_report|shortdate }}{% endif %}</span>
          </span>
          <svg class="filing__ext" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M14 5h5v5M19 5l-9 9M11 5H6a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-5"/>
          </svg>
        </a>
      </li>
      {% endfor %}
    </ul>
  </section>
  {% endif %}
</div>
{% endblock %}

{% block extra_js %}<script type="module" src="{{ vite_asset('static_src/symbol/index.js') }}"></script>{% endblock %}