chart/static/index.html
Chris Amow a0b8ac1cd2 Reclaim vertical space in the layer panel
Three cuts, so the right column keeps more of itself for the chart.

The five daily MA periods now sit on one line. 10px gaps and a 22px indent had
pushed 200 onto a row by itself; nowrap, 7px gaps, a 4px label gap and 12px
boxes fit all five with room to spare, verified as five labels sharing one
offsetTop and no horizontal overflow.

Auto trendlines becomes a parenthetical on the Manual lines row instead of
owning a row. It is disabled until M8 builds it, so a full row overstated it.

The alert log becomes a collapsible section like Confluence zones, closed by
default with its count in the summary. Closed by default is the point — leaving
it open would reclaim nothing — and the count keeps activity visible while it
is shut, with the banner, sound and phone push unaffected either way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 14:55:55 -05:00

134 lines
9.3 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>/ES Confluence</title>
<link rel="stylesheet" href="/static/style.css">
<!-- Font Awesome Free 7.3.1, from unpkg like the other two dependencies.
Pinned deliberately: an unpinned icon set is a silent redesign on someone
else's release. cdnjs 404s on this path — it does not carry 7.3.1. -->
<link rel="stylesheet" href="https://unpkg.com/@fortawesome/fontawesome-free@7.3.1/css/all.min.css">
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://unpkg.com/lightweight-charts@5.2.0/dist/lightweight-charts.standalone.production.js"></script>
</head>
<body>
<div id="app">
<header>
<div><span class="eyebrow">CME FUTURES</span><h1>/ES <strong>CONFLUENCE</strong></h1></div>
<div class="status" :class="status.stream"><i></i>{{ status.stream }}<span v-if="status.delay_minutes"> ({{ status.delay_minutes }}min delay)</span> · {{ status.source || 'source' }}</div>
</header>
<main>
<section class="chart-shell">
<div class="chart-head">
<div><span class="symbol">{{ status.symbol || 'ES=F' }}</span><span class="price">{{ price == null ? '—' : price.toFixed(2) }}</span></div>
<div class="timeframes"><button v-for="tf in timeframes" :key="tf" :class="{active: timeframe === tf}" @click="selectTimeframe(tf)">{{ tf }}</button></div>
</div>
<div id="chart"></div>
<div class="statusbar">
<span>FEED <b>{{ status.stream }}</b></span>
<span>LAST BAR <b>{{ barAge }}</b></span>
<span>HELD <b>{{ status.bars_held?.[timeframe] || 0 }} {{ timeframe }}</b></span>
<span v-if="armedTool === 'trendline'" class="arm-hint">Drag on the chart from one point to the other</span>
<span v-else-if="armedTool === 'level'" class="arm-hint">Click or drag on the chart to set the price</span>
<span v-else-if="selectedLine" class="arm-hint">Line selected — Delete removes it</span>
</div>
</section>
<aside>
<h2>Tools</h2>
<div class="tool" :class="{armed: armedTool === 'trendline'}">
<button class="tool-head" @click="armTool('trendline')" :aria-pressed="armedTool === 'trendline'">
<span class="tool-glyph"><i class="fa-solid fa-arrow-trend-up"></i></span>Trendline
<span class="tool-state">{{ armedTool === 'trendline' ? 'drag on chart' : '' }}</span>
</button>
<div class="tool-body">
<label>Label<input v-model.trim="drawName" placeholder="optional" aria-label="Trendline label"></label>
<div class="row">
<label>Colour<input type="color" v-model="drawColor" aria-label="Trendline colour"></label>
<label>Width<select v-model.number="drawWidth" aria-label="Trendline width"><option v-for="width in [1,2,3,4]" :value="width">{{ width }}px</option></select></label>
</div>
<div class="row">
<label>Side<select v-model="drawSide" aria-label="Trendline side"><option value="support">Support</option><option value="resistance">Resistance</option></select></label>
<label class="check"><input type="checkbox" v-model="snap">Snap to highs/lows</label>
</div>
</div>
</div>
<div class="tool" :class="{armed: armedTool === 'level'}">
<button class="tool-head" @click="armTool('level')" :aria-pressed="armedTool === 'level'">
<span class="tool-glyph"><i class="fa-solid fa-minus"></i></span>Price level
<span class="tool-state">{{ armedTool === 'level' ? 'click on chart' : '' }}</span>
</button>
<div class="tool-body">
<label>Label<input v-model.trim="alertNote" placeholder="optional" aria-label="Level label"></label>
<div class="row">
<label>Colour<input type="color" v-model="levelColor" aria-label="Level colour"></label>
<label>Width<select v-model.number="levelWidth" aria-label="Level width"><option v-for="width in [1,2,3,4]" :value="width">{{ width }}px</option></select></label>
</div>
<form class="row price-row" @submit.prevent="addPriceAlert">
<label>Price<input type="number" step="0.25" v-model.number="alertPrice" :placeholder="price == null ? '0.00' : price.toFixed(2)" aria-label="Level price"></label>
<button type="submit" :disabled="!alertPrice">Add</button>
</form>
<p class="hint">Drag on the chart, or type an exact price. Alerts whenever price reaches it, whatever the confluence score.</p>
</div>
</div>
<details class="sidebar-section" open>
<summary>Layers</summary>
<div class="layer-group">
<label class="master"><input type="checkbox" :checked="allEnabled('1d')" @change="toggleGroup('1d', $event.target.checked)"><span class="swatch tf-1d"></span>Daily MAs</label>
<div class="periods"><label v-for="period in [10,20,50,100,200]" :key="period"><input type="checkbox" :value="period" v-model="prefs.enabled.ma['1d']">{{ period }}</label></div>
</div>
<div class="layer-group">
<label><input type="checkbox" v-model="prefs.enabled.horizontal"><span class="swatch horizontal"></span>Prior day H/L/C</label>
<label><input type="checkbox" v-model="prefs.enabled.vwap"><span class="swatch vwap"></span>Session VWAP</label>
</div>
<div class="layer-group layer-inline">
<label><input type="checkbox" v-model="prefs.enabled.manual"><span class="swatch manual"></span>Manual lines</label>
<label class="disabled" title="Automatic trendline detection is not built yet">(<input type="checkbox" disabled>auto)</label>
</div>
<label class="score-hidden"><input type="checkbox" v-model="prefs.hidden_levels_score">Hidden levels still count toward confluence</label>
</details>
<details class="sidebar-section" open>
<summary>Lines &amp; levels ({{ manualLines.length }})</summary>
<div class="trendline-actions" v-if="manualLines.length">
<button @click="toggleSelectAll">{{ allManualSelected ? 'Clear' : 'Select all' }}</button>
<button @click="deleteSelectedLines" :disabled="!selectedLines.length">Delete selected ({{ selectedLines.length }})</button>
</div>
<div v-if="!manualLines.length" class="empty">No manual trendlines.</div>
<div v-for="line in manualLines" :key="line.id" class="trendline-row" :class="{selected: selectedLines.includes(line.id)}" @click="toggleLineSelection(line.id)">
<input class="line-select" type="checkbox" :checked="selectedLines.includes(line.id)" :aria-label="`Select trendline ${line.number}`" @click.stop @change="toggleLineSelection(line.id)">
<input :value="line.label" aria-label="Trendline name" @click.stop @change="renameLine(line, $event.target.value)">
<span v-if="line.slope === 0">#{{ line.number }} · level {{ line.anchor_p.toFixed(2) }}</span>
<span v-else>#{{ line.number }} · {{ line.tf }} · {{ line.side }}</span>
<label class="armed-toggle" :class="{off: !line.armed}" @click.stop :title="line.armed ? 'Armed — will alert once, then disarm' : 'Tripped — re-arm to alert again'">
<input type="checkbox" :checked="line.armed" @change="setArmed(line, $event.target.checked)">
{{ line.armed ? 'armed' : 'tripped' }}
</label>
<button @click.stop="deleteLine(line.id)" aria-label="Delete trendline">Delete</button>
<div class="line-style-controls" @click.stop>
<input type="color" :value="line.color || '#65b7cf'" aria-label="Trendline color" @change="updateLineStyle(line, { color: $event.target.value })">
<select :value="line.line_width || 2" aria-label="Trendline width" @change="updateLineStyle(line, { line_width: Number($event.target.value) })"><option v-for="width in [1,2,3,4]" :value="width">{{ width }}px</option></select>
</div>
</div>
</details>
<details class="sidebar-section">
<summary>Confluence zones</summary>
<div v-if="!clusters.length" class="empty">No active zones near current structure.</div>
<div v-for="cluster in clusters" :key="cluster.id" class="cluster" :class="cluster.side">
<div class="cluster-top"><b>{{ cluster.side }}</b><strong>{{ cluster.score.toFixed(1) }}</strong></div>
<div class="zone">{{ cluster.low.toFixed(2) }} – {{ cluster.high.toFixed(2) }}</div>
<div class="members">{{ cluster.members.map(member => member.label).join(' · ') }}</div>
<div class="distance">{{ cluster.distance > 0 ? '+' : '' }}{{ cluster.distance.toFixed(2) }} pts</div>
</div>
</details>
<details class="sidebar-section">
<summary>Alert log ({{ alerts.length }})</summary>
<div v-if="!alerts.length" class="empty">No alerts fired.</div>
<div v-for="alert in alerts" :key="alert.key" class="alert-entry"><time>{{ alert.at }}</time>{{ alert.message }}</div>
</details>
</aside>
</main>
</div>
<script src="/static/chart.js"></script>
<script src="/static/app.js"></script>
</body>
</html>