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>
134 lines
9.3 KiB
HTML
134 lines
9.3 KiB
HTML
<!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 & 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>
|