COTexplorer/frontend/style.css
Greg 90c2ae3f35 Expose disaggregated COT data in the UI (wheat focus)
The disagg dataset (2019–2026, 468 markets) was in the DB but invisible.
This wires it into every layer of the app:

Backend:
- models.py: add has_disagg to CommodityMeta; add DisaggPositionPoint,
  DisaggHistoryResponse, DisaggScreenerRow models
- commodities.py: join disagg_reports to populate has_disagg flag and
  correct first/last dates; HAVING filter removes markets with no data
- disagg.py (new): /api/disagg/{code}/history, /api/disagg/screener,
  /api/disagg/{code}/net-position-percentile, /api/disagg/compare
- main.py: register disagg router

Frontend:
- Metric selector shows Disaggregated optgroup (Managed Money, Prod/Merchant,
  Swap Dealer, Other Rept) when market has has_disagg=true, hides Legacy group
- Detail view auto-switches to disagg endpoint and defaults to m_money_net
  for disagg markets; shows green "Disaggregated" badge
- Screener always uses disagg endpoint (Managed Money percentile rank)
- Compare uses /api/disagg/compare for disagg metrics
- style.css: add .badge-disagg green variant

Result: wheat markets (SRW, HRW, HRSpring, Black Sea) now show 7 years of
disaggregated positioning data with Managed Money as the default metric.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 18:22:16 +01:00

523 lines
11 KiB
CSS

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0f1117;
--surface: #1a1d27;
--surface2: #222536;
--border: #2d3148;
--text: #e2e8f0;
--text-muted: #6b7280;
--accent: #3b82f6;
--accent-dim: #1d4ed8;
--green: #22c55e;
--red: #ef4444;
--orange: #f97316;
--yellow: #eab308;
--radius: 6px;
--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
body {
background: var(--bg);
color: var(--text);
font-family: var(--font);
font-size: 14px;
line-height: 1.5;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* ── Header ────────────────────────────────────────────────── */
header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
height: 48px;
background: var(--surface);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.header-left { display: flex; align-items: center; gap: 12px; }
.logo {
font-weight: 700;
font-size: 15px;
color: var(--accent);
letter-spacing: 0.5px;
}
nav { display: flex; gap: 4px; }
.tab-btn {
background: none;
border: none;
color: var(--text-muted);
padding: 6px 14px;
border-radius: var(--radius);
cursor: pointer;
font-size: 13px;
transition: all 0.15s;
}
.tab-btn:hover { color: var(--text); background: var(--surface2); }
.tab-btn.active { color: var(--accent); background: var(--surface2); font-weight: 600; }
/* ── Main layout ───────────────────────────────────────────── */
main { flex: 1; overflow: hidden; position: relative; }
.view { height: 100%; }
/* ── Detail view ───────────────────────────────────────────── */
#view-detail {
display: flex;
height: 100%;
}
#market-sidebar {
width: 220px;
flex-shrink: 0;
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
overflow: hidden;
}
#marketSearch {
margin: 10px 8px 6px;
padding: 6px 10px;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--text);
font-size: 13px;
outline: none;
width: calc(100% - 16px);
}
#marketSearch:focus { border-color: var(--accent); }
#market-tree {
flex: 1;
overflow-y: auto;
padding: 4px 0;
}
.exchange-group { margin-bottom: 4px; }
.exchange-label {
padding: 5px 10px;
font-size: 11px;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.8px;
cursor: pointer;
user-select: none;
display: flex;
align-items: center;
gap: 4px;
}
.exchange-label:hover { color: var(--text); }
.exchange-label::before {
content: '▾';
font-size: 10px;
transition: transform 0.15s;
}
.exchange-label.collapsed::before { transform: rotate(-90deg); }
.market-list { display: block; }
.market-list.hidden { display: none; }
.market-item {
padding: 5px 10px 5px 18px;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 13px;
color: var(--text-muted);
border-radius: 4px;
margin: 0 4px;
transition: all 0.1s;
}
.market-item:hover { background: var(--surface2); color: var(--text); }
.market-item.active { background: var(--accent-dim); color: #fff; }
/* ── Detail main area ──────────────────────────────────────── */
#detail-main {
flex: 1;
overflow-y: auto;
padding: 16px 20px;
display: flex;
flex-direction: column;
}
.placeholder {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
font-size: 15px;
}
#detail-content { flex: 1; display: flex; flex-direction: column; gap: 12px; }
#detail-header {
display: flex;
align-items: baseline;
gap: 10px;
flex-wrap: wrap;
}
#detail-title { font-size: 20px; font-weight: 700; }
.badge {
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 4px;
padding: 2px 8px;
font-size: 11px;
font-weight: 600;
color: var(--text-muted);
}
.badge-disagg {
background: rgba(34, 197, 94, 0.12);
border-color: rgba(34, 197, 94, 0.35);
color: #22c55e;
}
.unit { font-size: 12px; color: var(--text-muted); }
/* ── Chart controls ────────────────────────────────────────── */
.chart-controls {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
}
.control-group {
display: flex;
align-items: center;
gap: 6px;
}
.control-group > label {
font-size: 11px;
font-weight: 600;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
white-space: nowrap;
}
.btn-group { display: flex; gap: 2px; }
.range-btn, .rt-btn {
background: var(--surface2);
border: 1px solid var(--border);
color: var(--text-muted);
padding: 4px 10px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: all 0.1s;
}
.range-btn:hover, .rt-btn:hover { color: var(--text); border-color: var(--accent); }
.range-btn.active, .rt-btn.active {
background: var(--accent);
border-color: var(--accent);
color: #fff;
font-weight: 600;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 5px;
cursor: pointer;
font-size: 12px;
color: var(--text-muted);
}
.checkbox-label:hover { color: var(--text); }
select {
background: var(--surface2);
border: 1px solid var(--border);
color: var(--text);
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
outline: none;
}
select:focus { border-color: var(--accent); }
/* ── Chart ─────────────────────────────────────────────────── */
.chart-wrapper {
position: relative;
flex: 1;
min-height: 280px;
max-height: 420px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 12px;
}
/* ── Stats bar ─────────────────────────────────────────────── */
#stats-bar {
display: flex;
gap: 0;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
}
.stat-item {
flex: 1;
padding: 10px 16px;
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
gap: 2px;
}
.stat-item:last-child { border-right: none; }
.stat-label {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-muted);
}
.stat-value {
font-size: 16px;
font-weight: 700;
color: var(--text);
}
.stat-value.positive { color: var(--green); }
.stat-value.negative { color: var(--red); }
/* ── Screener view ─────────────────────────────────────────── */
#view-screener {
display: flex;
flex-direction: column;
height: 100%;
padding: 16px 20px;
gap: 12px;
}
.screener-toolbar {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
}
.screener-toolbar h2 { font-size: 18px; font-weight: 700; }
.screener-filters {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.btn-primary {
background: var(--accent);
border: none;
color: #fff;
padding: 5px 14px;
border-radius: var(--radius);
cursor: pointer;
font-size: 13px;
font-weight: 600;
transition: background 0.15s;
}
.btn-primary:hover { background: var(--accent-dim); }
.table-wrapper {
flex: 1;
overflow-y: auto;
border: 1px solid var(--border);
border-radius: var(--radius);
}
table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
thead {
position: sticky;
top: 0;
background: var(--surface2);
z-index: 1;
}
th {
padding: 10px 12px;
text-align: left;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-muted);
border-bottom: 1px solid var(--border);
cursor: pointer;
user-select: none;
}
th:hover { color: var(--text); }
th.num { text-align: right; }
td {
padding: 8px 12px;
border-bottom: 1px solid var(--border);
color: var(--text);
}
td.num { text-align: right; font-variant-numeric: tabular-nums; }
tbody tr { cursor: pointer; transition: background 0.1s; }
tbody tr:hover { background: var(--surface2); }
tbody tr:last-child td { border-bottom: none; }
.pctile-cell { font-weight: 700; }
.extreme-long { color: var(--green); }
.extreme-short { color: var(--red); }
.neutral { color: var(--text-muted); }
.pctile-bar {
display: inline-block;
height: 6px;
border-radius: 3px;
background: var(--accent);
margin-right: 6px;
vertical-align: middle;
}
/* ── Compare view ──────────────────────────────────────────── */
#view-compare {
display: flex;
flex-direction: column;
height: 100%;
padding: 16px 20px;
gap: 12px;
}
.compare-toolbar {
display: flex;
flex-direction: column;
gap: 10px;
}
.compare-toolbar h2 { font-size: 18px; font-weight: 700; }
.compare-controls {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.compare-search-wrap { position: relative; }
#compareSearch {
padding: 6px 10px;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--text);
font-size: 13px;
outline: none;
width: 220px;
}
#compareSearch:focus { border-color: var(--accent); }
.autocomplete-dropdown {
position: absolute;
top: calc(100% + 2px);
left: 0;
width: 280px;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: var(--radius);
z-index: 100;
max-height: 200px;
overflow-y: auto;
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
}
.autocomplete-item {
padding: 7px 12px;
cursor: pointer;
font-size: 13px;
border-bottom: 1px solid var(--border);
}
.autocomplete-item:last-child { border-bottom: none; }
.autocomplete-item:hover { background: var(--surface); color: var(--accent); }
.autocomplete-item-exchange { font-size: 10px; color: var(--text-muted); margin-left: 6px; }
#compareTags { display: flex; flex-wrap: wrap; gap: 6px; }
.compare-tag {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
color: #fff;
opacity: 0.9;
}
.compare-tag-remove {
background: none;
border: none;
color: #fff;
cursor: pointer;
font-size: 14px;
line-height: 1;
opacity: 0.7;
padding: 0;
}
.compare-tag-remove:hover { opacity: 1; }
#compareChartWrap {
flex: 1;
min-height: 300px;
}
/* ── Scrollbars ────────────────────────────────────────────── */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
/* ── Loading state ─────────────────────────────────────────── */
.loading { opacity: 0.5; pointer-events: none; }