UI enhancemnts
This commit is contained in:
@@ -0,0 +1,503 @@
|
||||
<script lang="ts">
|
||||
import { tick } from 'svelte';
|
||||
|
||||
let {
|
||||
open = false,
|
||||
activeMetrics = [] as string[],
|
||||
focusKey = null as string | null,
|
||||
onClose,
|
||||
}: {
|
||||
open?: boolean;
|
||||
activeMetrics?: string[];
|
||||
focusKey?: string | null;
|
||||
onClose: () => void;
|
||||
} = $props();
|
||||
|
||||
let searchQuery = $state('');
|
||||
let expandedItem = $state<string | null>(null);
|
||||
let bodyEl = $state<HTMLElement | null>(null);
|
||||
|
||||
// When focusKey changes, expand and scroll to that item
|
||||
$effect(() => {
|
||||
if (focusKey && open) {
|
||||
expandedItem = focusKey;
|
||||
searchQuery = '';
|
||||
tick().then(() => {
|
||||
if (!bodyEl) return;
|
||||
const el = bodyEl.querySelector(`[data-gkey="${focusKey}"]`);
|
||||
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ── Glossary data ─────────────────────────────────────────────────────
|
||||
type RangeBand = { val: string; label: string };
|
||||
type GlossaryItem = {
|
||||
key: string;
|
||||
label: string;
|
||||
category: 'Market Context' | 'Valuation' | 'Quality' | 'Risk' | 'Signals' | 'ETF' | 'Bond';
|
||||
definition: string;
|
||||
gate?: string;
|
||||
goodRange?: RangeBand;
|
||||
neutralRange?: RangeBand;
|
||||
badRange?: RangeBand;
|
||||
assetTypes?: ('STOCK' | 'ETF' | 'BOND')[];
|
||||
};
|
||||
|
||||
const GLOSSARY: GlossaryItem[] = [
|
||||
// ── Market Context ─────────────────────────────────────────────────
|
||||
{
|
||||
key: '10Y',
|
||||
label: '10Y Treasury Yield',
|
||||
category: 'Market Context',
|
||||
definition: 'The yield on 10-year US government bonds — the global risk-free rate benchmark. Drives discount rates for all assets: higher yield = lower present value of future earnings.',
|
||||
gate: 'Rate regime: < 2% LOW | 2–5% NORMAL | > 5% HIGH. HIGH rates compress growth stock P/E multipliers.',
|
||||
goodRange: { val: '2–4%', label: 'Normal, accommodative' },
|
||||
neutralRange: { val: '4–5%', label: 'Elevated, watch growth' },
|
||||
badRange: { val: '> 5%', label: 'HIGH regime, P/E compression' },
|
||||
},
|
||||
{
|
||||
key: 'VIX',
|
||||
label: 'VIX — Volatility Index',
|
||||
category: 'Market Context',
|
||||
definition: 'The CBOE Volatility Index — measures expected 30-day S&P 500 volatility derived from options prices. Known as the "fear gauge."',
|
||||
gate: 'Volatility regime: < 15 CALM | 15–25 NORMAL | > 25 ELEVATED | > 35 EXTREME',
|
||||
goodRange: { val: '< 15', label: 'Calm market, low fear' },
|
||||
neutralRange: { val: '15–25', label: 'Normal uncertainty' },
|
||||
badRange: { val: '> 25', label: 'Elevated fear' },
|
||||
},
|
||||
{
|
||||
key: 'Rate Regime',
|
||||
label: 'Rate Regime',
|
||||
category: 'Market Context',
|
||||
definition: 'Derived from the 10Y Treasury yield. Controls how aggressively the INFLATED scoring mode adjusts P/E gates — HIGH rates tighten the multiplier from 1.5× to 1.2× of S&P P/E.',
|
||||
gate: 'LOW < 2% | NORMAL 2–5% | HIGH > 5%',
|
||||
goodRange: { val: 'LOW', label: 'Growth-friendly' },
|
||||
neutralRange: { val: 'NORMAL', label: 'Balanced' },
|
||||
badRange: { val: 'HIGH', label: 'Value favoured, growth penalised' },
|
||||
},
|
||||
// ── Valuation ──────────────────────────────────────────────────────
|
||||
{
|
||||
key: 'P/E',
|
||||
label: 'P/E Ratio',
|
||||
category: 'Valuation',
|
||||
definition: 'Price-to-Earnings: how many dollars investors pay per $1 of annual profit. Lower = cheaper relative to earnings.',
|
||||
gate: 'Graham gate: ≤ 15× | Inflated gate: ≤ S&P P/E × 1.5 (live)',
|
||||
goodRange: { val: '< 15×', label: 'Value / below sector avg' },
|
||||
neutralRange: { val: '15–35×', label: 'Elevated but common' },
|
||||
badRange: { val: '> 35×', label: 'Expensive without high growth' },
|
||||
assetTypes: ['STOCK'],
|
||||
},
|
||||
{
|
||||
key: 'PEG',
|
||||
label: 'PEG Ratio',
|
||||
category: 'Valuation',
|
||||
definition: 'P/E divided by earnings growth rate. Adjusts for growth — a 30× P/E stock growing 30% has PEG 1.0, same as a 15× stock growing 15%.',
|
||||
gate: 'Gate: < 1.0 (Lynch standard) · Weight: 2',
|
||||
goodRange: { val: '< 1.0', label: 'Bargain' },
|
||||
neutralRange: { val: '1.0–2.0', label: 'Fair' },
|
||||
badRange: { val: '> 2.0', label: 'Costly' },
|
||||
assetTypes: ['STOCK'],
|
||||
},
|
||||
{
|
||||
key: 'DCF Safety',
|
||||
label: 'DCF Margin of Safety',
|
||||
category: 'Valuation',
|
||||
definition: 'How much below the discounted cash flow intrinsic value the stock trades. Positive = undervalued vs. DCF model; negative = overvalued. Requires positive FCF to compute.',
|
||||
gate: '≥ +20% → full score | 0–20% → +1 | -20–0% → -1 | < -20% → negative score',
|
||||
goodRange: { val: '> +20%', label: 'Significant discount' },
|
||||
neutralRange: { val: '0–20%', label: 'Modest discount' },
|
||||
badRange: { val: '< -20%', label: 'Premium to fair value' },
|
||||
assetTypes: ['STOCK'],
|
||||
},
|
||||
{
|
||||
key: 'Upside',
|
||||
label: 'Analyst Price Target Upside',
|
||||
category: 'Valuation',
|
||||
definition: 'Percentage gap between current price and Wall Street consensus target price. Positive = analysts expect the stock to rise.',
|
||||
gate: 'Risk flag if ≥ +25% upside or ≤ -15% downside',
|
||||
goodRange: { val: '+5–20%', label: 'Moderate consensus upside' },
|
||||
neutralRange: { val: '0–5%', label: 'Fairly priced' },
|
||||
badRange: { val: '< -10%', label: 'Analysts bearish' },
|
||||
assetTypes: ['STOCK'],
|
||||
},
|
||||
// ── Quality ────────────────────────────────────────────────────────
|
||||
{
|
||||
key: 'ROE%',
|
||||
label: 'Return on Equity',
|
||||
category: 'Quality',
|
||||
definition: 'Net income as a % of shareholders\' equity. Measures how efficiently management generates profit from invested capital.',
|
||||
gate: 'Gate: ROE ≥ 15%',
|
||||
goodRange: { val: '> 20%', label: 'Excellent capital efficiency' },
|
||||
neutralRange: { val: '10–20%', label: 'Adequate' },
|
||||
badRange: { val: '< 10%', label: 'Poor capital use' },
|
||||
assetTypes: ['STOCK'],
|
||||
},
|
||||
{
|
||||
key: 'OpMgn%',
|
||||
label: 'Operating Margin',
|
||||
category: 'Quality',
|
||||
definition: 'Operating profit as a % of revenue — what\'s left after COGS and operating expenses, before interest and taxes.',
|
||||
gate: 'Gate: Op Margin ≥ 10%',
|
||||
goodRange: { val: '> 20%', label: 'High quality business' },
|
||||
neutralRange: { val: '5–20%', label: 'Modest margins' },
|
||||
badRange: { val: '< 5%', label: 'Thin, fragile' },
|
||||
assetTypes: ['STOCK'],
|
||||
},
|
||||
{
|
||||
key: 'GrossM%',
|
||||
label: 'Gross Margin',
|
||||
category: 'Quality',
|
||||
definition: 'Revenue minus cost of goods sold, as a %. Shows pricing power and production efficiency before overhead.',
|
||||
gate: 'Informational — not a hard gate, used contextually',
|
||||
goodRange: { val: '> 50%', label: 'Software / services quality' },
|
||||
neutralRange: { val: '15–50%', label: 'Moderate' },
|
||||
badRange: { val: '< 15%', label: 'Commodity-like, price-taker' },
|
||||
assetTypes: ['STOCK'],
|
||||
},
|
||||
{
|
||||
key: 'FCF Yld%',
|
||||
label: 'Free Cash Flow Yield',
|
||||
category: 'Quality',
|
||||
definition: 'Free cash flow per share divided by price — cash the business actually generates, expressed as a yield. Unlike earnings, FCF is hard to fake.',
|
||||
gate: 'Gate: FCF > 0 (negative FCF = gate fail) | Weight: 3× in scoring',
|
||||
goodRange: { val: '> 5%', label: 'Strong cash generation' },
|
||||
neutralRange: { val: '0–5%', label: 'Weak positive' },
|
||||
badRange: { val: '< 0%', label: 'Cash-burning' },
|
||||
assetTypes: ['STOCK'],
|
||||
},
|
||||
{
|
||||
key: 'Analyst',
|
||||
label: 'Analyst Consensus Rating',
|
||||
category: 'Quality',
|
||||
definition: 'Wall Street average recommendation on a 1–5 scale (Yahoo). 1 = Strong Buy, 5 = Strong Sell. Requires ≥ 3 analysts for signal to fire.',
|
||||
gate: '≤ 2.0 → full score | ≤ 3.0 → +1 | ≤ 4.0 → -1 | > 4.0 → negative score',
|
||||
goodRange: { val: '1.0–2.5', label: 'Buy consensus' },
|
||||
neutralRange: { val: '2.5–4.0', label: 'Neutral / Hold' },
|
||||
badRange: { val: '> 4.0', label: 'Sell consensus' },
|
||||
assetTypes: ['STOCK'],
|
||||
},
|
||||
{
|
||||
key: 'Revenue',
|
||||
label: 'Revenue Growth',
|
||||
category: 'Quality',
|
||||
definition: 'Year-over-year percentage change in total revenue. Measures whether the business is expanding its top line. A secondary scoring factor — positive growth adds to score, declining revenue subtracts.',
|
||||
gate: 'Gate: Revenue growth > 0% for positive contribution | Weight: 2× in scoring',
|
||||
goodRange: { val: '> 10%', label: 'Strong expansion' },
|
||||
neutralRange: { val: '0–10%', label: 'Slow growth' },
|
||||
badRange: { val: '< 0%', label: 'Shrinking top line' },
|
||||
assetTypes: ['STOCK'],
|
||||
},
|
||||
// ── Risk ───────────────────────────────────────────────────────────
|
||||
{
|
||||
key: 'D/E',
|
||||
label: 'Debt-to-Equity Ratio',
|
||||
category: 'Risk',
|
||||
definition: 'Total debt divided by shareholders\' equity. Measures financial leverage — how much borrowed money vs. owned capital the company uses.',
|
||||
gate: 'Gate: D/E ≤ 1.5× | Tech: ≤ 2.0× | Financials: gate disabled (scored on P/B instead)',
|
||||
goodRange: { val: '< 0.5×', label: 'Conservative' },
|
||||
neutralRange: { val: '0.5–1.5×', label: 'Moderate' },
|
||||
badRange: { val: '> 2.0×', label: 'High leverage risk' },
|
||||
assetTypes: ['STOCK'],
|
||||
},
|
||||
{
|
||||
key: '52W Chg',
|
||||
label: '52-Week Price Change',
|
||||
category: 'Risk',
|
||||
definition: 'Total % price return over the past year. Captures trend strength and momentum.',
|
||||
gate: 'Risk flag: ≥ +50% (at peak, reversal risk) | ≤ -30% (significant drawdown)',
|
||||
goodRange: { val: '+5–30%', label: 'Steady uptrend' },
|
||||
neutralRange: { val: '-5–+5%', label: 'Flat / sideways' },
|
||||
badRange: { val: '< -30%', label: 'Significant drawdown' },
|
||||
assetTypes: ['STOCK'],
|
||||
},
|
||||
{
|
||||
key: 'From High',
|
||||
label: 'Distance from 52-Week High',
|
||||
category: 'Risk',
|
||||
definition: 'How far (%) the current price sits below the 52-week peak. Negative = below peak. A -15% reading means the stock is 15% off its high.',
|
||||
gate: 'Risk flag if > -20% from high (at or near peak)',
|
||||
goodRange: { val: '-5–25%', label: 'Healthy pullback' },
|
||||
neutralRange: { val: '-25–40%', label: 'Larger drawdown' },
|
||||
badRange: { val: '0–3%', label: 'At peak, limited buffer' },
|
||||
assetTypes: ['STOCK'],
|
||||
},
|
||||
// ── Signals ────────────────────────────────────────────────────────
|
||||
{
|
||||
key: 'Graham',
|
||||
label: 'Graham (Fundamental) Score',
|
||||
category: 'Signals',
|
||||
definition: 'Strict value-investing score using fixed Graham gates: P/E ≤ 15×, PEG ≤ 1.0, D/E ≤ 1.5×, ROE ≥ 15%, FCF > 0. Does not adjust for market conditions — these thresholds never move.',
|
||||
gate: 'All gates fixed regardless of S&P P/E or rate regime',
|
||||
goodRange: { val: 'PASS', label: 'Passes all Graham gates' },
|
||||
neutralRange: { val: 'PARTIAL', label: 'Passes some, fails others' },
|
||||
badRange: { val: 'FAIL', label: 'Fails one or more hard gates' },
|
||||
},
|
||||
{
|
||||
key: 'Mkt-Adj',
|
||||
label: 'Market-Adjusted Score',
|
||||
category: 'Signals',
|
||||
definition: 'Relaxed scoring mode that calibrates gates to live market benchmarks. P/E gate = S&P P/E × 1.5 (or × 1.2 in HIGH rate regime). Reflects what is "acceptable" in today\'s market, not absolute value.',
|
||||
gate: 'P/E gate: S&P P/E × 1.5 (NORMAL) or × 1.2 (HIGH) | Tech P/E: XLK P/E × 1.3',
|
||||
goodRange: { val: 'PASS', label: 'Passes mkt-adjusted gates' },
|
||||
neutralRange: { val: 'PARTIAL', label: 'Borderline vs live benchmarks' },
|
||||
badRange: { val: 'FAIL', label: 'Fails even relaxed gates' },
|
||||
},
|
||||
{
|
||||
key: 'signal',
|
||||
label: 'Signal',
|
||||
category: 'Signals',
|
||||
definition: 'Overall recommendation derived by comparing Market-Adjusted and Graham (fundamental) scores.',
|
||||
gate: 'Strong Buy = passes both | Momentum = passes Mkt-Adj only | Speculation = passes Mkt-Adj, fails Graham | Neutral = borderline | Avoid = fails both',
|
||||
goodRange: { val: '✅ ⚡', label: 'Strong Buy / Momentum' },
|
||||
neutralRange: { val: '🔄', label: 'Neutral — hold' },
|
||||
badRange: { val: '⚠️ ❌', label: 'Speculation / Avoid' },
|
||||
},
|
||||
{
|
||||
key: 'score',
|
||||
label: 'Score (dot scale)',
|
||||
category: 'Signals',
|
||||
definition: 'Weighted sum of factor scores (ROE, FCF, margin, PEG, revenue growth, analyst, DCF). Displayed as ●●●●○ dots out of 5 + raw number.',
|
||||
gate: 'Positive factors add to score; negative riskFlags subtract. Gate failures bypass scoring entirely (shown as ✗).',
|
||||
goodRange: { val: '> 12', label: 'High conviction' },
|
||||
neutralRange: { val: '6–12', label: 'Borderline' },
|
||||
badRange: { val: '< 6', label: 'Weak factors' },
|
||||
},
|
||||
{
|
||||
key: 'Cap Tier',
|
||||
label: 'Market Cap Tier',
|
||||
category: 'Signals',
|
||||
definition: 'Size classification based on market capitalisation. Mega Cap (> $200B), Large ($10–200B), Mid ($2–10B), Small ($300M–$2B), Micro (< $300M).',
|
||||
gate: 'Informational — not a gate. Useful for position sizing and risk calibration.',
|
||||
goodRange: { val: 'Mega / Large', label: 'Most liquid' },
|
||||
neutralRange: { val: 'Mid', label: 'Balanced' },
|
||||
badRange: { val: 'Micro', label: 'High vol, thin liquidity' },
|
||||
assetTypes: ['STOCK'],
|
||||
},
|
||||
{
|
||||
key: 'Style',
|
||||
label: 'Growth / Style Category',
|
||||
category: 'Signals',
|
||||
definition: 'Derived from revenue growth and earnings growth. High Growth (rev ≥ 15% or earnings ≥ 20%), Growth (5–15%), Value (< 5% + yield ≥ 3%), Stable, Turnaround, Declining.',
|
||||
gate: 'Informational — not a gate. Helps match the stock to your strategy.',
|
||||
goodRange: { val: 'High Growth / Growth', label: 'Matches momentum strategy' },
|
||||
neutralRange: { val: 'Stable / Value', label: 'Income / defensive' },
|
||||
badRange: { val: 'Declining', label: 'Revenue shrinking > -5%' },
|
||||
assetTypes: ['STOCK'],
|
||||
},
|
||||
// ── ETF ────────────────────────────────────────────────────────────
|
||||
{
|
||||
key: 'Exp Ratio%',
|
||||
label: 'Expense Ratio',
|
||||
category: 'ETF',
|
||||
definition: 'Annual management fee as a % of AUM. Deducted from returns automatically. Lower is always better — costs compound against you.',
|
||||
gate: 'Hard gate: Expense Ratio ≤ 0.20%',
|
||||
goodRange: { val: '< 0.10%', label: 'Index-like, minimal drag' },
|
||||
neutralRange: { val: '0.10–0.50%', label: 'Acceptable' },
|
||||
badRange: { val: '> 0.50%', label: 'High cost drag' },
|
||||
assetTypes: ['ETF'],
|
||||
},
|
||||
{
|
||||
key: '5Y Return%',
|
||||
label: '5-Year Annualised Return',
|
||||
category: 'ETF',
|
||||
definition: 'Compound annual growth rate over 5 years. The S&P 500 long-run average is ~10%; use that as a baseline.',
|
||||
gate: 'Gate: 5Y Return ≥ 8% (S&P long-run floor)',
|
||||
goodRange: { val: '> 12%', label: 'Outperforming market' },
|
||||
neutralRange: { val: '8–12%', label: 'Market-rate returns' },
|
||||
badRange: { val: '< 6%', label: 'Underperforming bonds + inflation' },
|
||||
assetTypes: ['ETF'],
|
||||
},
|
||||
{
|
||||
key: 'Yield%',
|
||||
label: 'Distribution Yield',
|
||||
category: 'ETF',
|
||||
definition: 'Annual income distributions (dividends, interest) as a % of NAV. Important for income-focused or REIT ETFs.',
|
||||
gate: 'REIT ETF: Yield floor based on XLRE yield × regime factor',
|
||||
goodRange: { val: '> 3%', label: 'Strong income' },
|
||||
neutralRange: { val: '1–3%', label: 'Low but positive' },
|
||||
badRange: { val: '< 1%', label: 'Insufficient for income' },
|
||||
assetTypes: ['ETF'],
|
||||
},
|
||||
// ── Bond ───────────────────────────────────────────────────────────
|
||||
{
|
||||
key: 'YTM%',
|
||||
label: 'Yield to Maturity',
|
||||
category: 'Bond',
|
||||
definition: 'Total return if you hold the bond to maturity — includes coupon payments plus any price gain/loss vs. par. The true all-in yield.',
|
||||
gate: 'Spread gate: YTM must exceed risk-free rate by ≥ 1.5% (NORMAL) or ≥ 1.8% (HIGH rates)',
|
||||
goodRange: { val: 'Sprd > 2%', label: 'Good compensation for risk' },
|
||||
neutralRange: { val: '1–2%', label: 'Adequate spread' },
|
||||
badRange: { val: '< 1%', label: 'Not compensating for credit risk' },
|
||||
assetTypes: ['BOND'],
|
||||
},
|
||||
{
|
||||
key: 'Duration',
|
||||
label: 'Duration (years)',
|
||||
category: 'Bond',
|
||||
definition: 'Sensitivity to interest rate changes. A duration of 5 means a 1% rate rise → ~5% price drop. Shorter = less rate risk.',
|
||||
gate: 'Gate: Duration ≤ 7 years',
|
||||
goodRange: { val: '< 4 yrs', label: 'Low rate sensitivity' },
|
||||
neutralRange: { val: '4–7 yrs', label: 'Moderate' },
|
||||
badRange: { val: '> 10 yrs', label: 'High rate risk' },
|
||||
assetTypes: ['BOND'],
|
||||
},
|
||||
{
|
||||
key: 'Rating',
|
||||
label: 'Credit Rating',
|
||||
category: 'Bond',
|
||||
definition: 'Agency rating of default probability: AAA (safest) → AA → A → BBB (investment grade floor) → BB → B → CCC (junk).',
|
||||
gate: 'Hard gate: Rating ≥ BBB (investment-grade, numeric ≥ 7)',
|
||||
goodRange: { val: 'AAA–A', label: 'Very low default risk' },
|
||||
neutralRange: { val: 'BBB', label: 'Investment-grade floor' },
|
||||
badRange: { val: '≤ BB', label: 'High yield / junk' },
|
||||
assetTypes: ['BOND'],
|
||||
},
|
||||
];
|
||||
|
||||
const CATEGORIES = ['Market Context', 'Valuation', 'Quality', 'Risk', 'Signals', 'ETF', 'Bond'] as const;
|
||||
|
||||
function filteredItems(): GlossaryItem[] {
|
||||
const q = searchQuery.trim().toLowerCase();
|
||||
if (!q) return GLOSSARY;
|
||||
return GLOSSARY.filter(
|
||||
(item) =>
|
||||
item.label.toLowerCase().includes(q) ||
|
||||
item.definition.toLowerCase().includes(q) ||
|
||||
item.category.toLowerCase().includes(q),
|
||||
);
|
||||
}
|
||||
|
||||
function itemsForCategory(cat: string): GlossaryItem[] {
|
||||
return filteredItems().filter((i) => i.category === cat);
|
||||
}
|
||||
|
||||
function isActive(item: GlossaryItem): boolean {
|
||||
return activeMetrics.some(
|
||||
(k) => k === item.key || k === item.label,
|
||||
);
|
||||
}
|
||||
|
||||
function toggleItem(key: string) {
|
||||
expandedItem = expandedItem === key ? null : key;
|
||||
}
|
||||
|
||||
// Close on Escape
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') onClose();
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={handleKeydown} />
|
||||
|
||||
{#if open}
|
||||
<!-- Click-outside backdrop — thin, no visual overlay, just captures clicks -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="glossary-backdrop" onclick={onClose}></div>
|
||||
|
||||
<aside class="glossary-panel" aria-label="Metrics Glossary">
|
||||
<!-- Header -->
|
||||
<div class="glossary-header">
|
||||
<span class="glossary-title"><span class="glossary-title-q">?</span> Metric Glossary</span>
|
||||
<button class="glossary-close" onclick={onClose} aria-label="Close glossary">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Search -->
|
||||
<div class="glossary-search-wrap">
|
||||
<input
|
||||
class="glossary-search"
|
||||
type="text"
|
||||
placeholder="Search metrics…"
|
||||
bind:value={searchQuery}
|
||||
aria-label="Search glossary"
|
||||
/>
|
||||
{#if searchQuery}
|
||||
<button class="glossary-search-clear" onclick={() => (searchQuery = '')} aria-label="Clear search">✕</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Context banner — fixed between search and body, only when row is selected -->
|
||||
{#if activeMetrics.length > 0}
|
||||
<div class="glossary-ctx-banner">
|
||||
✦ Highlighted metrics are relevant to the selected row
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Body -->
|
||||
<div class="glossary-body" bind:this={bodyEl}>
|
||||
|
||||
{#each CATEGORIES as cat}
|
||||
{@const items = itemsForCategory(cat)}
|
||||
{#if items.length > 0}
|
||||
<div class="glossary-category">
|
||||
<div class="glossary-cat-header">{cat}</div>
|
||||
{#each items as item}
|
||||
{@const active = isActive(item)}
|
||||
{@const isExpanded = expandedItem === item.key}
|
||||
<div
|
||||
class="glossary-item"
|
||||
class:glossary-item-active={active}
|
||||
class:glossary-item-open={isExpanded}
|
||||
data-gkey={item.key}
|
||||
>
|
||||
<button
|
||||
class="glossary-item-trigger"
|
||||
onclick={() => toggleItem(item.key)}
|
||||
aria-expanded={isExpanded}
|
||||
>
|
||||
<span class="glossary-item-label">
|
||||
{#if active}<span class="glossary-active-dot"></span>{/if}
|
||||
{item.label}
|
||||
</span>
|
||||
<span class="glossary-cat-tag gcat-{cat.toLowerCase().replace(/\s/g,'-')}">{cat}</span>
|
||||
</button>
|
||||
|
||||
{#if isExpanded}
|
||||
<div class="glossary-item-body">
|
||||
<p class="glossary-definition">{item.definition}</p>
|
||||
|
||||
{#if item.gate}
|
||||
<div class="glossary-gate-box">
|
||||
<code>{item.gate}</code>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if item.goodRange || item.neutralRange || item.badRange}
|
||||
<div class="glossary-range-pills">
|
||||
{#if item.goodRange}
|
||||
<span class="glossary-range-pill grange-good">{item.goodRange.val}</span>
|
||||
{/if}
|
||||
{#if item.neutralRange}
|
||||
<span class="glossary-range-pill grange-neutral">{item.neutralRange.val}</span>
|
||||
{/if}
|
||||
{#if item.badRange}
|
||||
<span class="glossary-range-pill grange-bad">{item.badRange.val}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="glossary-range-labels">
|
||||
{#if item.goodRange}
|
||||
<span class="grlabel-good">{item.goodRange.label}</span>
|
||||
{/if}
|
||||
{#if item.neutralRange}
|
||||
<span class="grlabel-neutral">{item.neutralRange.label}</span>
|
||||
{/if}
|
||||
{#if item.badRange}
|
||||
<span class="grlabel-bad">{item.badRange.label}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
{#if filteredItems().length === 0}
|
||||
<div class="glossary-empty">No metrics match "{searchQuery}"</div>
|
||||
{/if}
|
||||
</div>
|
||||
</aside>
|
||||
{/if}
|
||||
Reference in New Issue
Block a user