/* Help icons + explainer dialog
   Each metric has a short, terminal-style explainer with price-impact playbook.
   Open via window.dispatchEvent(new CustomEvent('open-help', { detail: topicKey })) */

const HELP_CONTENT = {
  // ---------- Technical ----------
  'ema-stack': {
    cat: 'Technical',
    title: 'EMA Stack',
    sub: 'Exponential moving averages — trend skeleton',
    what: "EMAs weight recent prices more heavily than older ones, so they react faster than simple averages. The 20/50/100/200-period EMAs together describe the trend across short, mid and long horizons. When they're stacked in order (price > 20 > 50 > 100 > 200) the trend is unambiguously up; an inverted stack signals a downtrend.",
    affect: "EMAs act as dynamic support and resistance — algorithms and discretionary traders both watch them, which turns them into self-fulfilling reaction zones. The EMA 50 weekly (the 'golden line') is the single most-watched level on the daily chart; price losing it has historically marked the transition from bull to bear regime.",
    bullish: [
      'Price holds above the EMA 50 weekly on pullbacks',
      'Golden Cross — EMA 50 crosses above EMA 200 on the daily',
      'EMAs fanned out and rising in sequence',
    ],
    bearish: [
      'Daily close below EMA 50 weekly with no recovery in 3 sessions',
      'Death Cross — EMA 50 cuts below EMA 200',
      'EMAs flattening and crossing each other (range / distribution)',
    ],
  },

  'rsi': {
    cat: 'Technical',
    title: 'RSI 14',
    sub: 'Relative Strength Index — momentum oscillator',
    what: "RSI measures the speed and magnitude of recent price changes on a 0–100 scale. Above 70 the market is considered overbought; below 30, oversold. Crypto regularly stays overbought during strong trends, so the absolute level matters less than the shape and divergences.",
    affect: "RSI doesn't cause moves, but extremes signal exhaustion. The highest-conviction signal is divergence: price prints a new high while RSI prints a lower high (bearish), or price makes a new low while RSI lifts (bullish). These often precede meaningful reversals by 1–5 sessions.",
    bullish: [
      'Bullish divergence on the daily — price lower-low, RSI higher-low',
      'RSI breaking above 50 after a downtrend',
      'Hidden bullish divergence in an uptrend (continuation)',
    ],
    bearish: [
      'Bearish divergence at major resistance',
      'RSI rejecting from 70 after weeks of overbought',
      'Loss of 40 in an uptrend — momentum failing',
    ],
  },

  'macd': {
    cat: 'Technical',
    title: 'MACD',
    sub: 'Moving Average Convergence Divergence',
    what: "MACD is the difference between two EMAs (typically 12 and 26), plotted against a signal line (9-EMA of MACD). The histogram shows the gap between them. It's a momentum + trend hybrid: it tells you whether trend strength is expanding or contracting.",
    affect: "The crossover (MACD line above/below signal) often coincides with shifts in market control. Weekly MACD crosses in particular have historically led major BTC swings by days to weeks. An expanding histogram confirms a move; a contracting one warns of exhaustion even while price still drifts in the same direction.",
    bullish: [
      'Bullish cross on the weekly above the zero line',
      'Histogram expanding from negative toward zero',
      'Higher lows on MACD while price chops sideways',
    ],
    bearish: [
      'Bearish cross from elevated levels',
      'Histogram contracting while price still rises (divergence)',
      'MACD rolling over below zero',
    ],
  },

  'patterns': {
    cat: 'Technical',
    title: 'Chart patterns',
    sub: 'Classical price-structure formations',
    what: "Patterns are recurring shapes in price action — flags, wedges, cups, double tops — that emerge from how supply and demand take turns. They're not magic; they're a shorthand for behavior like 'buyers absorbing repeated tests of a level'.",
    affect: "Patterns matter mostly because so many people watch them that breakouts and breakdowns become self-confirming. Volume on the break is the tell: a clean breakout with expanding volume typically reaches the measured-move target; a low-volume break is usually a trap. Confidence scores here weight pattern clarity, volume confirmation and historical reliability.",
    bullish: [
      'Bull flag / pennant breaking out on volume',
      'Cup-and-handle completing with handle on lower volume',
      'Higher highs and higher lows intact (the only pattern that really matters)',
    ],
    bearish: [
      'Head-and-shoulders confirmed on neckline break',
      'Rising wedge breaking down',
      'Lower highs forming below a key MA',
    ],
  },

  'key-levels': {
    cat: 'Technical',
    title: 'Key levels',
    sub: 'Manually marked + auto-detected horizontals',
    what: "Key levels are price zones where the market has previously reacted — prior highs, lows, range edges, large volume nodes, weekly opens. Each one represents a memory of orders being filled or stops being hit.",
    affect: "Levels are decision points. The first test of a level usually produces a reaction (bounce or rejection); the second or third test often breaks it. Confluence matters — a price level that lines up with an EMA, a CME gap, or a fibonacci retracement carries more weight than any single one alone.",
    bullish: [
      'Reclaim of a lost support, retested as support',
      'Break of a multi-month resistance on volume',
      'Higher-timeframe support holding on first test',
    ],
    bearish: [
      'Rejection at a known supply zone',
      'Break-and-retest of support as new resistance',
      'Range-bottom failing after multiple defenses',
    ],
  },

  // ---------- Liquidity ----------
  'cme-gaps': {
    cat: 'Liquidity',
    title: 'CME Gaps',
    sub: 'Weekend price gaps on Chicago futures',
    what: "CME Bitcoin futures stop trading from Friday close to Sunday afternoon, but spot trades 24/7. When spot moves over the weekend, Monday's open prints a gap above or below Friday's close — a zone with no trades inside it.",
    affect: "CME gaps don't pull price like a magnet, but historically the majority fill within weeks to months. They become high-probability reaction zones because large institutional players treat unfilled gaps as latent inventory. Gaps below price tend to fill on corrections; gaps above price act as magnets during rallies.",
    bullish: [
      'Open gap above current price — natural rally target',
      'Lower gap fills cleanly without breakdown (resets)',
    ],
    bearish: [
      'Large unfilled gap below — pressure to revisit',
      'Multiple stacked downside gaps — sweeping risk',
    ],
  },

  'funding': {
    cat: 'Liquidity',
    title: 'Funding rate',
    sub: 'Perpetual futures financing cost',
    what: "Perpetuals never expire, so exchanges use funding payments every 8 hours to keep the perp price tethered to spot. When longs dominate, they pay shorts (positive funding); when shorts dominate, they pay longs (negative funding). It's the cost of holding leverage.",
    affect: "Funding is a sentiment thermometer. Persistently high positive funding (over 0.05%/8h) means longs are overcrowded and willing to pay a premium — historically a contrarian sell signal that resolves with a long-squeeze flush. Sharply negative funding marks fear and often precedes short-squeezes.",
    bullish: [
      'Negative or zero funding while spot grinds up (shorts paying for being wrong)',
      'Funding resetting to neutral after a flush',
    ],
    bearish: [
      'Funding > 0.05%/8h sustained — euphoric leverage',
      'Funding rising into resistance',
    ],
  },

  'open-interest': {
    cat: 'Liquidity',
    title: 'Open Interest',
    sub: 'Total outstanding derivatives contracts',
    what: "Open Interest is the dollar value of all futures and perpetual contracts currently open. Rising OI means new positions are opening; falling OI means positions are closing. Combined with price direction it tells you who's in control.",
    affect: "Price up + OI up = new longs driving the move (continuation). Price up + OI down = short covering (often runs out). Price down + OI up = new shorts pressing (trend continues). Price down + OI down = long capitulation (often near a low). Big single-day drops in OI almost always mark liquidation cascades.",
    bullish: [
      'OI rising alongside price (new conviction longs)',
      'OI dropping sharply at a low (longs flushed = bottom signal)',
    ],
    bearish: [
      'OI hitting all-time highs at resistance — fragile setup',
      'OI rising while price stalls — late longs paying funding',
    ],
  },

  'liq-heatmap': {
    cat: 'Liquidity',
    title: 'Liquidation heatmap',
    sub: 'Estimated stop clusters across leverage',
    what: "The heatmap visualises estimated liquidation prices for leveraged positions across exchanges. Bright bands mark price zones where large clusters of stops sit — typically just above local highs (short stops) and below local lows (long stops).",
    affect: "Liquidity clusters act as magnets. Market makers and large players actively hunt these zones because filling thousands of stops at once provides the liquidity needed to enter or exit major positions. Expect price to sweep clusters before reversing — the 'wick to liquidity' is one of the most repeatable patterns in crypto.",
    bullish: [
      'Heavy short-liquidation cluster overhead = squeeze fuel',
      'Long stops below recently swept and reclaimed',
    ],
    bearish: [
      'Dense long-liquidation cluster below current price',
      'Stacked clusters in both directions — high volatility ahead',
    ],
  },

  'long-short': {
    cat: 'Liquidity',
    title: 'Long / Short ratio',
    sub: 'Positioning balance among traders',
    what: "Tracks the proportion of accounts (or position size) on the long side versus the short side, usually on a major perp venue. Exchanges segment this by retail and 'top traders' — the latter being accounts with the largest balances or best historical PnL.",
    affect: "The ratio works as a contrarian tell at extremes. When ≥75% of retail is long, the market tends to flush them. When top traders sit heavily on one side while retail piles into the other, follow the top traders. Convergence between groups often marks the end of a trend.",
    bullish: [
      'Retail heavily short while top traders are long',
      'Long ratio collapsing from extreme — capitulation',
    ],
    bearish: [
      'Retail crowded long at highs',
      'Long ratio rising into known resistance',
    ],
  },

  'liquidity-zones': {
    cat: 'Liquidity',
    title: 'Liquidity zones',
    sub: 'Where resting orders concentrate',
    what: "Liquidity zones are price bands where significant resting orders sit — equal highs/lows, round numbers, prior swing points. Buy-side liquidity sits above old highs (stops from shorts + buy-stops from breakout traders); sell-side liquidity sits below old lows.",
    affect: "Markets need liquidity to move size. Smart money manufactures moves toward these zones, sweeps them, and then reverses. A clean sweep with rapid reclaim is one of the highest-probability reversal signals. Conversely, a sustained close beyond the zone confirms genuine breakout.",
    bullish: [
      'Sell-side liquidity swept and reclaimed within hours',
      'Buy-side liquidity broken with follow-through volume',
    ],
    bearish: [
      'Buy-side liquidity tapped at ATH with immediate rejection',
      'Sell-side liquidity broken decisively — cascading stops',
    ],
  },

  // ---------- On-chain ----------
  'mvrv': {
    cat: 'On-chain',
    title: 'MVRV Ratio',
    sub: 'Market value vs realized value',
    what: "MVRV divides market cap by 'realized cap' — the value of every coin priced at when it last moved. A ratio of 1 means the average holder is breakeven. Above 1, the network is in aggregate profit; below 1, in loss. Above ~3.5 historically marks euphoria tops; below ~1 marks accumulation bottoms.",
    affect: "MVRV doesn't move price directly — it shows latent supply pressure. When most holders are deeply in profit, the temptation to sell rises and rallies meet supply. When most are underwater, there's no one left to sell and bottoms form. It's a cycle-positioning gauge, not a timing tool.",
    bullish: [
      'MVRV climbing through 1 — exit from bear regime',
      'MVRV under 1 with whales accumulating',
    ],
    bearish: [
      'MVRV above 3.5 — euphoria zone, distribution risk',
      'MVRV rolling over from the top band',
    ],
  },

  'whales': {
    cat: 'On-chain',
    title: 'BTC trading volume',
    sub: 'Hourly spot volume, last 48h',
    what: "Hourly BTC/USDT spot volume on Binance over the last 48 hours — a proxy for how much the market is actually participating. Each bar is one hour's traded volume in BTC; the stats summarise total, the single busiest hour, and whether the most recent 12 hours are running hotter or cooler than the prior 12.",
    affect: "Volume confirms moves. Breakouts and breakdowns on expanding volume are more likely to hold; big price moves on thin volume often fade. A surge in volume frequently marks turning points — capitulation lows and blow-off tops both tend to print the heaviest volume of the cycle.",
    bullish: [
      'Rising volume on up-moves — participation confirming strength',
      'A volume spike after a long decline (possible capitulation low)',
    ],
    bearish: [
      'Rallies on shrinking volume — move losing fuel',
      'Volume spike into a top (possible blow-off / distribution)',
    ],
  },

  'etf-flows': {
    cat: 'On-chain',
    title: 'Spot ETF flows',
    sub: 'Daily net inflows / outflows',
    what: "Since January 2024, US spot Bitcoin ETFs have absorbed daily institutional demand. Net flow = inflows minus outflows across all issuers. With ~450 BTC mined per day, the ETFs collectively buying 4,000 BTC creates a supply squeeze regardless of how the chart 'looks'.",
    affect: "ETF flows are the cleanest macro demand signal we have. Sustained positive flows tighten available float, which structurally supports price. A multi-day outflow streak — especially when IBIT goes red — often precedes corrections of 5–15%. The ratio of ETF buys to newly mined BTC is the simplest 'is demand greater than supply?' test.",
    bullish: [
      'Multi-day positive net flow streaks',
      'IBIT inflows accelerating into resistance',
    ],
    bearish: [
      '5+ consecutive days of outflows',
      'GBTC dump cycles returning',
    ],
  },

  'stablecoin': {
    cat: 'On-chain',
    title: 'Stablecoin liquidity',
    sub: 'USDT + USDC supply trends',
    what: "Stablecoins are dollars on-chain. Their total supply is the 'dry powder' available to buy crypto. When Tether and Circle mint new tokens, dollars are entering the system; when they redeem, dollars are leaving. Stablecoin dominance (their share of total market cap) is an inverse risk gauge.",
    affect: "Rising stablecoin supply, especially fresh USDT prints, has historically led rallies by days to weeks — fresh capital takes time to deploy. Falling stablecoin dominance means traders are rotating out of cash into crypto (risk-on). Rising dominance means de-risking.",
    bullish: [
      'USDT printing in batches — capital incoming',
      'Stablecoin dominance falling while BTC rises',
    ],
    bearish: [
      'Stablecoin redemptions — capital leaving',
      'Dominance spiking — flight to safety',
    ],
  },

  'dominance': {
    cat: 'On-chain',
    title: 'BTC dominance',
    sub: 'Share of total crypto market cap',
    what: "BTC.D measures Bitcoin's market cap as a percentage of total crypto market cap. It shifts in waves: BTC leads first (BTC.D rising), then ETH and large caps catch up (BTC.D stalls), then altcoins explode (BTC.D drops sharply) — the classic altseason rotation.",
    affect: "BTC.D doesn't affect BTC price much in isolation — but it dictates where capital flows next. A breakdown of BTC.D below key support (currently ~54%) historically triggers the 'altseason' phase where alts outperform 2–10×. A breakout of BTC.D usually means alts are bleeding even if BTC is flat.",
    bullish: [
      'BTC.D breakdown — altseason underway',
      'BTC.D rising in a bear market — flight to BTC',
    ],
    bearish: [
      'BTC.D rising during alt rallies — exhaustion',
      'BTC.D breakout while BTC ranges — alt risk',
    ],
  },

  // ---------- Macro ----------
  'calendar': {
    cat: 'Macro',
    title: 'Macro calendar',
    sub: 'Scheduled US economic events',
    what: "A timeline of releases that move risk markets: CPI, PCE, NFP, FOMC meetings, retail sales, GDP. Each carries a forecast (consensus expectation) and a prior reading. Markets price the forecast in advance; what matters is the surprise — actual versus forecast.",
    affect: "Crypto trades risk-on like the Nasdaq, so US macro prints are crypto prints by proxy. Hot CPI = hawkish Fed = weaker BTC. Soft jobs = dovish Fed = stronger BTC. The biggest moves come in the 5 minutes after release; the cleaner trend follows in the hours after, once the bots stop fading the spike.",
    bullish: [
      'Cool inflation surprise → rate-cut bets rise',
      'Weak jobs print without recession fear',
    ],
    bearish: [
      'Hot CPI or PCE — cut expectations get repriced out',
      'Hawkish FOMC statement / dot plot',
    ],
  },

  'dxy': {
    cat: 'Macro',
    title: 'DXY · Dollar Index',
    sub: 'USD vs basket of major currencies',
    what: "DXY tracks the dollar against a basket of currencies (60% EUR, then JPY, GBP, CAD, CHF, SEK). It's the universal risk gauge: a stronger dollar tightens global financial conditions, a weaker one loosens them. BTC has a persistent negative correlation around -0.5 to -0.8 over multi-month windows.",
    affect: "DXY rising drains liquidity from risk assets globally — BTC, equities, gold all bleed together. DXY falling does the opposite. The biggest crypto rallies have all coincided with DXY breakdowns; the deepest bears with DXY surges. Watch the 200-day on DXY as the regime line.",
    bullish: [
      'DXY breaking down through major support',
      'DXY below 100 with falling rate differentials',
    ],
    bearish: [
      'DXY breakout above 105 with rising yields',
      'Currency crisis abroad → DXY safe haven bid',
    ],
  },

  'inflation': {
    cat: 'Macro',
    title: 'Inflation trackers',
    sub: 'CPI, PCE, PPI year-over-year',
    what: "Three official inflation gauges. CPI is the consumer headline. Core PCE is the Fed's preferred measure (what actually drives policy). PPI tracks producer prices and leads CPI by a month or two. Together they triangulate where inflation is actually heading.",
    affect: "Inflation governs the Fed, the Fed governs liquidity, and liquidity governs BTC. Disinflation lets the Fed cut, which loosens financial conditions and lifts risk assets. Sticky or rising inflation forces the Fed to hold or hike, draining liquidity. Multi-month direction matters more than any single print.",
    bullish: [
      'All three trackers trending down toward 2% target',
      'Core PCE undershooting expectations',
    ],
    bearish: [
      'Reacceleration in any one — sticky inflation narrative',
      'PPI bouncing — leading CPI higher next quarter',
    ],
  },

  'fomc': {
    cat: 'Macro',
    title: 'Next FOMC',
    sub: 'Federal Open Market Committee meeting',
    what: "The Fed meets 8 times a year to set the federal funds rate. Each meeting releases a statement, the Fed chair holds a press conference, and (4× per year) the Summary of Economic Projections (the 'dot plot') drops. Markets price probabilities of cuts, holds and hikes via fed funds futures.",
    affect: "FOMC days are the most consequential events for risk assets. The decision matters less than the surprise versus what was priced in. A 25bp cut that was 90% priced barely moves markets; a hawkish hold when a cut was expected can drop BTC 5%+ in minutes. The Fed chair's tone in the press conference often overrides the statement.",
    bullish: [
      'Dovish cut, dovish statement, dovish presser',
      'Dot plot revised lower (more cuts ahead)',
    ],
    bearish: [
      'Hawkish hold when cut was priced',
      'Dot plot revised higher / QT extended',
    ],
  },

  // ---------- Sentiment ----------
  'fear-greed': {
    cat: 'Sentiment',
    title: 'Fear & Greed Index',
    sub: 'Aggregate market sentiment, 0–100',
    what: "A composite of volatility, momentum, social media, surveys, dominance and search trends. 0 = extreme fear (panic selling), 100 = extreme greed (FOMO buying). It's a sentiment thermometer, not a timing tool.",
    affect: "Classic contrarian indicator. Readings below 20 mark bottoming conditions where everyone has already sold; readings above 80 mark distribution zones where everyone is already in. The middle band (40–70) is the trickiest — no extreme to fade.",
    bullish: [
      'Sub-20 reading with whales accumulating',
      'Recovery from extreme fear (sentiment lagging price)',
    ],
    bearish: [
      'Above 85 for multiple days — euphoria',
      'Greed reading while RSI is divergent',
    ],
  },

  'smart-money': {
    cat: 'Sentiment',
    title: 'Retail vs Smart money',
    sub: 'Positioning across cohorts',
    what: "Tracks signals separately for retail (perp positioning, search interest, social) and smart money (spot whale bids, stablecoin flows, Coinbase premium, miner behavior). When the two cohorts disagree, position with smart money.",
    affect: "Smart money tends to be early but right. They accumulate during disbelief and distribute into euphoria. Retail does the opposite — buying tops via perps and panicking out at lows. The widest divergences between cohorts have historically marked the best risk/reward entries.",
    bullish: [
      'Whales bidding spot, retail short on perps',
      'Coinbase premium positive (US institutions buying)',
    ],
    bearish: [
      'Retail heavily long on perps while whales transfer to exchanges',
      'Negative Coinbase premium during rallies',
    ],
  },

  // ---------- Cycles ----------
  'wyckoff': {
    cat: 'Cycle',
    title: 'Wyckoff market phase',
    sub: 'Accumulation, markup, distribution and markdown',
    what: "Wyckoff maps the market through four recurring supply-and-demand phases. This read scores the live EMA stack, 30-day versus 180-day range regime, funding and long/short crowding, plus the MVRV zone to identify the phase with the strongest evidence. Confidence is model fit (0–100): clarity of the winning phase versus runners-up, agreement of EMA/range/funding/MVRV, and spring/UTAD reclaim quality. Miss-risk tags: ≥60 Low, 35–59 Med, <35 High.",
    affect: "Accumulation and markup describe improving demand; distribution and markdown describe supply overwhelming bids. A spring is a possible failed breakdown in accumulation, while an UTAD is a possible failed breakout in distribution. Neither flag is a standalone entry signal. Treat low confidence / High miss risk as 'phase still contested' — wait for margin and reclaim quality to improve.",
    bullish: [
      'Accumulation with compressed range and MVRV near fair value',
      'Markup confirmed by price > EMA 50 > EMA 200 and expanding participation',
      'Spring reclaiming the lower range on improving demand',
    ],
    bearish: [
      'Distribution with high MVRV and crowded longs',
      'Markdown confirmed by price < EMA 50 < EMA 200',
      'UTAD rejection back inside the upper range',
    ],
  },

  'elliott': {
    cat: 'Cycle',
    title: 'Elliott cycle-degree count',
    sub: 'Impulse 1–5 then corrective A–B–C from Nov 2022 ATL',
    what: "Elliott Wave is a scenario framework for labeling crowd psychology across nested trends. This cycle-degree count anchors at the November 2022 Bitcoin low, models an impulse 1–5 into the cycle top, then an A–B–C correction, and shows the active wave with a confidence score. Confidence is model fit (0–100): the gap between the primary and alternate counts, distance from invalidation, and consistency with the cycle-top timing model. Ambiguous wave boundaries are capped. Miss-risk tags: ≥60 Low, 35–59 Med, <35 High.",
    affect: "Wave labels are useful for defining invalidation and target zones, not for certainty. The count explicitly rejects a shortest wave 3 and a wave 4 that fully overlaps wave 1; an alternate count is shown because multiple valid interpretations can coexist until price confirms one. High miss risk usually means price is near invalidation or the primary/alternate gap is thin — treat labels as scenarios, not calls.",
    bullish: [
      'Wave 3 extending with momentum and holding above wave 1 resistance',
      'Wave 4 respecting the no-overlap invalidation',
      'Wave 5 breaking the prior impulse high with participation',
    ],
    bearish: [
      'Price crossing the active count invalidation level',
      'Wave 4 overlapping wave 1 — impulse count invalidated',
      'Cycle top timing forcing a corrective A–B–C count',
    ],
  },
  'halving': {
    cat: 'Cycle',
    title: 'Cycle position · 1064 / 364 model',
    sub: 'Bottom → top → bottom timing',
    what: "A timing model that measures Bitcoin cycles between their major pivots (not from the halving). Bottom (ATL) to top (ATH) has run ~1,050–1,071 days (~3 years); top to the NEXT bottom has run a remarkably consistent ~364 days (~1 year). Prior cycles: 2015 ATL → 2017 ATH (1,050d) → 2018 ATL (364d); 2018 ATL → 2021 ATH (1,071d) → 2022 ATL (364d); 2022 ATL → 2025 ATH (1,064d), projecting → 2026 ATL (364d). Confidence is model fit (0–100) for the current cycle-position read — starting mid, then cut if price still hugs the assumed ATH or the calendar stretches past historical variance, and raised when EMA stack / MVRV / Wyckoff agree with a downtrend-toward-bottom (or cut when they disagree). With only ~3 completed cycles the score hard-ceils near 55 unless those signals strongly agree. Miss-risk tags: ≥60 Low, 35–59 Med, <35 High.",
    affect: "Because the down-leg has been ~364 days every cycle, once the top is confirmed you can project the next bottom roughly a year later. This widget anchors on the real 2022 bottom and the actual cycle ATH from live data, then adds 364 days to project the next bottom (~Oct 2026). It's a historical-rhythm heuristic, not a guarantee — treat the projected dates as a rough window, and use the confidence / miss-risk chip as a reminder of how thin the sample is.",
    bullish: [
      'Approaching the projected bottom — historical accumulation window',
      'The ~364-day down-leg has largely run its course',
    ],
    bearish: [
      'Inside the 364-day down-leg after a confirmed top',
      'Cycle top recently printed — distribution / decline phase',
    ],
  },

  'seasonality': {
    cat: 'Cycle',
    title: 'Monthly seasonality',
    sub: 'Average BTC returns by calendar month',
    what: "Multi-year average percentage returns for each month of the year. Not every year follows the pattern, but persistent biases emerge: Q4 (Oct–Dec) is historically the strongest stretch, September is the weakest month on average, and summer often chops.",
    affect: "Seasonality is a tiebreaker, not a thesis. When TA, on-chain and macro are aligned, seasonality boosts conviction. When signals conflict, seasonality is rarely strong enough to override them. The strongest effects are 'Uptober' and the Q4 risk-on rally, which align with year-end fund flows in traditional markets.",
    bullish: [
      'Q4 with macro + on-chain confirmation',
      'Spring rallies during bull cycles',
    ],
    bearish: [
      'September weakness compounded by macro stress',
      'Summer chop with negative seasonality',
    ],
  },
};

// === HelpIcon — small circular ? button next to a section heading ====
function HelpIcon({ topic, size = 14 }) {
  const [hover, setHover] = React.useState(false);
  return (
    <button
      onClick={(e) => {
        e.stopPropagation();
        window.dispatchEvent(new CustomEvent('open-help', { detail: topic }));
      }}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      title="What is this indicator?"
      aria-label={"Explain " + topic}
      style={{
        width: size, height: size, borderRadius: '50%',
        border: '1px solid ' + (hover ? 'var(--gold-dim)' : 'var(--border)'),
        background: hover ? 'var(--gold-bg)' : 'transparent',
        color: hover ? 'var(--gold)' : 'var(--text-4)',
        cursor: 'pointer', padding: 0,
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
        fontSize: Math.max(8, size - 5), fontWeight: 700,
        fontFamily: 'var(--font-mono)', lineHeight: 1,
        transition: 'color 120ms, background 120ms, border-color 120ms',
        verticalAlign: 'middle',
      }}
    >?</button>
  );
}

// === CardTitle — h3 + help icon combo. Replaces bare <h3> in card headers ==
function CardTitle({ children, topic }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
      <h3 style={{ margin: 0, fontSize: 11, fontWeight: 500, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--text-3)' }}>{children}</h3>
      {topic && <HelpIcon topic={topic} />}
    </div>
  );
}

// === HelpDialog — modal opened by 'open-help' event =====================
function HelpDialog() {
  const [topic, setTopic] = React.useState(null);

  React.useEffect(() => {
    function onOpen(e) { setTopic(e.detail); }
    function onKey(e) { if (e.key === 'Escape') setTopic(null); }
    window.addEventListener('open-help', onOpen);
    window.addEventListener('keydown', onKey);
    return () => {
      window.removeEventListener('open-help', onOpen);
      window.removeEventListener('keydown', onKey);
    };
  }, []);

  if (!topic) return null;
  const c = HELP_CONTENT[topic];
  if (!c) return null;

  const catColor = {
    'Technical':  'var(--info)',
    'Liquidity':  'var(--gold)',
    'On-chain':   'var(--violet)',
    'Macro':      'oklch(0.72 0.13 200)',
    'Sentiment':  'oklch(0.72 0.14 320)',
    'Cycle':      'var(--bull)',
  }[c.cat] || 'var(--text-3)';

  return (
    <div
      onClick={() => setTopic(null)}
      style={{
        position: 'fixed', inset: 0, zIndex: 100,
        background: 'oklch(0.10 0.01 248 / 0.72)',
        backdropFilter: 'blur(6px)',
        display: 'grid', placeItems: 'center',
        padding: 20,
        animation: 'helpFadeIn 140ms ease-out',
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          width: 'min(560px, 100%)',
          maxHeight: 'calc(100vh - 60px)',
          overflow: 'auto',
          background: 'var(--bg-card)',
          border: '1px solid var(--border)',
          borderRadius: 12,
          boxShadow: '0 20px 60px oklch(0.05 0.005 248 / 0.6), 0 0 0 1px oklch(0.4 0.04 248 / 0.15)',
          animation: 'helpScaleIn 160ms ease-out',
        }}
      >
        {/* header */}
        <div style={{
          display: 'flex', alignItems: 'flex-start', gap: 14,
          padding: '20px 22px 16px',
          borderBottom: '1px solid var(--border-soft)',
        }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{
              fontSize: 10, color: catColor,
              fontFamily: 'var(--font-mono)', letterSpacing: '0.16em',
              textTransform: 'uppercase', marginBottom: 6,
              display: 'flex', alignItems: 'center', gap: 6,
            }}>
              <span style={{ width: 5, height: 5, borderRadius: '50%', background: catColor }} />
              {c.cat} indicator
            </div>
            <h2 style={{
              margin: 0, fontSize: 22, fontWeight: 600, letterSpacing: '-0.01em',
              color: 'var(--text)',
            }}>{c.title}</h2>
            <div style={{ fontSize: 12.5, color: 'var(--text-3)', marginTop: 3 }}>{c.sub}</div>
          </div>
          <button
            onClick={() => setTopic(null)}
            aria-label="Close"
            style={{
              width: 28, height: 28, borderRadius: 6,
              background: 'var(--bg-elev)',
              border: '1px solid var(--border-soft)',
              color: 'var(--text-3)', cursor: 'pointer',
              display: 'grid', placeItems: 'center',
              fontSize: 14, lineHeight: 1, padding: 0,
              fontFamily: 'var(--font-mono)',
            }}
          >×</button>
        </div>

        {/* body */}
        <div style={{ padding: '18px 22px 22px' }}>

          <HelpSection
            label="What it measures"
            body={c.what}
          />

          <HelpSection
            label="How it affects price"
            body={c.affect}
            accent
          />

          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginTop: 6 }}>
            <SignalList kind="bullish" items={c.bullish} />
            <SignalList kind="bearish" items={c.bearish} />
          </div>

          <div style={{
            marginTop: 16, paddingTop: 12,
            borderTop: '1px solid var(--border-soft)',
            display: 'flex', justifyContent: 'space-between',
            fontFamily: 'var(--font-mono)', fontSize: 10,
            color: 'var(--text-4)', letterSpacing: '0.04em',
          }}>
            <span>not financial advice · context for the chart</span>
            <span>esc to close</span>
          </div>
        </div>
      </div>

      <style>{`
        @keyframes helpFadeIn { from { opacity: 0 } to { opacity: 1 } }
        @keyframes helpScaleIn {
          from { opacity: 0; transform: translateY(8px) scale(0.98); }
          to   { opacity: 1; transform: translateY(0) scale(1); }
        }
      `}</style>
    </div>
  );
}

function HelpSection({ label, body, accent }) {
  return (
    <div style={{ marginBottom: 14 }}>
      <div style={{
        fontSize: 9.5, letterSpacing: '0.14em', textTransform: 'uppercase',
        color: accent ? 'var(--gold)' : 'var(--text-4)',
        fontFamily: 'var(--font-mono)', marginBottom: 6,
      }}>{label}</div>
      <div style={{
        fontSize: 13, lineHeight: 1.55, color: 'var(--text-2)',
        background: accent ? 'oklch(0.30 0.05 82 / 0.18)' : 'transparent',
        border: accent ? '1px solid oklch(0.45 0.09 82 / 0.35)' : 'none',
        borderRadius: accent ? 8 : 0,
        padding: accent ? '10px 12px' : 0,
      }}>{body}</div>
    </div>
  );
}

function SignalList({ kind, items }) {
  const isBull = kind === 'bullish';
  const color = isBull ? 'var(--bull)' : 'var(--bear)';
  const bg = isBull ? 'oklch(0.30 0.05 148 / 0.22)' : 'oklch(0.30 0.05 25 / 0.22)';
  const border = isBull ? 'oklch(0.4 0.1 148 / 0.4)' : 'oklch(0.4 0.1 25 / 0.4)';
  return (
    <div style={{
      background: bg,
      border: '1px solid ' + border,
      borderRadius: 8,
      padding: '10px 12px',
    }}>
      <div style={{
        fontSize: 9.5, fontFamily: 'var(--font-mono)',
        letterSpacing: '0.14em', textTransform: 'uppercase',
        color, marginBottom: 8, fontWeight: 600,
      }}>{isBull ? '↑ Bullish signals' : '↓ Bearish signals'}</div>
      <ul style={{
        margin: 0, padding: 0, listStyle: 'none',
        display: 'flex', flexDirection: 'column', gap: 6,
      }}>
        {items.map((it, i) => (
          <li key={i} style={{
            fontSize: 11.5, color: 'var(--text-2)', lineHeight: 1.4,
            paddingLeft: 12, position: 'relative',
          }}>
            <span style={{
              position: 'absolute', left: 0, top: 7,
              width: 4, height: 4, borderRadius: '50%', background: color,
            }} />
            {it}
          </li>
        ))}
      </ul>
    </div>
  );
}

Object.assign(window, { HelpIcon, CardTitle, HelpDialog });
