The trendline snap indicator was 66 pixels out, and so was every other overlay. Lightweight Charts reports coordinates from the plot area's origin. The chart element also contains the price scales, so enabling the left scale for the daily labels moved the plot 66px into the element — and each overlay positioned with left: against the element inherited that error twice over. The cursor's element-x was read as a plot-x, resolving a bar about 66px right of the pointer; the indicator was then drawn at that bar's plot-x interpreted as element-x, landing 66px left of where the bar is painted. Neither near the cursor nor near the bar, and scaling with zoom — two bars at 30m, a dozen at 1m — which is why it read as random rather than as an offset. Every diagnostic number agreed with itself the whole time, because dot_y, expected_y and bar_low_y all come from the same API and shared the same wrong origin. Instrumentation cannot see a systematic error in its own frame of reference; what found this was comparing canvas.width to element.clientWidth and getting 0.894. Overlays now live in one container positioned over the plot canvas and inherit plot coordinates untranslated — snap dot and label, comments, anchor handles, preview line, tooltip, price tag, context menu — and eventPoint subtracts the same offset so a pointer position and a chart coordinate mean the same thing. The container follows the plot on resize. Verified in page pixels rather than through the coordinate API: with the cursor placed 30px below a known bar's low, the dot lands 30px above the cursor, on that low, labelled 7789.00 L against a bar low of 7789. Device pixel ratio, viewport size, resize desynchronisation and the chart scrolling under the gesture were each measured and ruled out before this. The e2e helper computed expected times from element-relative x, the same mistake in the tests, and is corrected here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
83 lines
12 KiB
CSS
83 lines
12 KiB
CSS
:root { color-scheme:light; --bg:#e8dfcf; --panel:#f7f1e6; --chart-bg:#fbf7ef; --fg:#2c2924; --muted:#746c60; --line:#d2c5b2; --accent:#b7771d; --green:#27825c; --red:#bd4545; }
|
|
* { box-sizing:border-box; }
|
|
body { margin:0; background:var(--bg); color:var(--fg); font:14px/1.45 "IBM Plex Mono", "SFMono-Regular", Consolas, monospace; }
|
|
#app { min-height:100vh; padding:18px; }
|
|
header { height:44px; display:flex; align-items:center; justify-content:space-between; border-bottom:1px solid var(--line); margin-bottom:16px; }
|
|
h1 { margin:0; font-size:22px; letter-spacing:-1px; } h1 strong { color:var(--accent); font-weight:600; }
|
|
.eyebrow { color:var(--muted); font-size:9px; letter-spacing:2px; }
|
|
.status { text-transform:uppercase; color:var(--muted); font-size:11px; }.status i { display:inline-block; width:7px; height:7px; border-radius:50%; background:var(--red); margin-right:8px; }.status.connected i,.status.replay i { background:var(--green); box-shadow:0 0 9px var(--green); }
|
|
main { display:grid; grid-template-columns:minmax(0, 1fr) 300px; gap:16px; }
|
|
.chart-shell,aside { background:var(--panel); border:1px solid var(--line); }
|
|
.chart-head { min-height:56px; padding:10px 14px; display:flex; align-items:center; justify-content:space-between; gap:12px; border-bottom:1px solid var(--line); }
|
|
.symbol { font-weight:700; margin-right:14px; }.price { color:var(--accent); font-size:19px; }
|
|
button { border:1px solid var(--line); background:transparent; color:var(--muted); padding:6px 11px; font:inherit; cursor:pointer; }button.active { color:var(--bg); background:var(--accent); border-color:var(--accent); }
|
|
.timeframes { display:flex; flex-wrap:wrap; justify-content:flex-end; }.timeframes button+button { border-left:0; }
|
|
.drawing-tools { min-height:38px; padding:5px 12px; display:flex; align-items:center; gap:9px; border-bottom:1px solid var(--line); color:var(--muted); font-size:10px; }.drawing-tools button,.drawing-tools select,.drawing-tools .line-name { padding:4px 8px; font-size:10px; }.drawing-tools select,.drawing-tools .line-name { background:var(--panel); color:var(--fg); border:1px solid var(--line); }.drawing-tools .line-name { width:130px; font:inherit; }.drawing-tools label { display:flex; gap:4px; align-items:center; }.drawing-tools input { accent-color:var(--accent); }
|
|
#chart { position:relative; height:calc(100vh - 190px); min-height:420px; }.chart-preview,.chart-handles { position:absolute; inset:0; width:100%; height:100%; overflow:hidden; pointer-events:none; }.chart-preview { z-index:4; }.chart-handles { z-index:6; }.chart-preview line[hidden],.chart-anchor[hidden] { display:none; }.chart-anchor { stroke:var(--panel); stroke-width:2px; cursor:grab; pointer-events:all; touch-action:none; }.chart-anchor:active { cursor:grabbing; }.chart-tooltip { position:absolute; z-index:5; padding:4px 7px; border:1px solid var(--line); background:var(--panel); color:var(--fg); font-size:10px; pointer-events:none; }.chart-tooltip[hidden] { display:none; }
|
|
.chart-context-menu { position:absolute; z-index:8; width:165px; padding:4px; border:1px solid var(--line); background:var(--panel); box-shadow:0 5px 18px color-mix(in srgb,var(--fg) 15%,transparent); }.chart-context-menu[hidden] { display:none; }.chart-context-menu button { width:100%; padding:6px 8px; text-align:left; color:var(--fg); font-size:10px; }
|
|
.statusbar { min-height:34px; display:flex; align-items:center; gap:24px; padding:6px 13px; border-top:1px solid var(--line); color:var(--muted); font-size:10px; }.statusbar b { color:var(--fg); text-transform:uppercase; }
|
|
aside { padding:16px; }h2 { margin:0 0 12px; color:var(--muted); font-size:11px; text-transform:uppercase; letter-spacing:1.3px; }h2:not(:first-child) { margin-top:30px; }.empty { border-left:2px solid var(--line); padding:10px 12px; color:var(--muted); font-size:11px; }
|
|
.sidebar-section { margin-top:30px; }.sidebar-section:first-of-type { margin-top:0; }.sidebar-section summary { margin-bottom:12px; color:var(--muted); font-size:11px; text-transform:uppercase; letter-spacing:1.3px; cursor:pointer; user-select:none; }.sidebar-section:not([open]) summary { margin-bottom:0; }
|
|
.trendline-actions { display:flex; gap:5px; margin-bottom:7px; }.trendline-actions button { flex:1; padding:4px; font-size:9px; }.trendline-row { display:grid; grid-template-columns:auto minmax(0,1fr) auto; gap:5px 8px; padding:7px; border:1px solid transparent; }.trendline-row.selected { border-color:var(--accent); }.trendline-row>.line-select { align-self:center; accent-color:var(--accent); }.trendline-row>input:not(.line-select) { min-width:0; border:0; border-bottom:1px solid var(--line); background:transparent; color:var(--fg); font:inherit; font-size:11px; }.trendline-row span:not(.drawing-icon) { grid-column:2; color:var(--muted); font-size:9px; text-transform:uppercase; }.trendline-row>.drawing-icon { grid-column:1; align-self:center; }.trendline-row button { grid-column:3; grid-row:1; padding:3px 6px; font-size:9px; }.line-style-controls { grid-column:3; display:flex; align-items:center; gap:4px; }.line-style-controls input { width:24px; height:20px; padding:0; border:0; background:transparent; }.line-style-controls select { border:1px solid var(--line); background:var(--panel); color:var(--fg); font-size:9px; }
|
|
.layer-group { padding:9px 0; border-bottom:1px solid var(--line); display:grid; gap:7px; }.layer-group label,.score-hidden { display:flex; align-items:center; gap:7px; font-size:11px; cursor:pointer; }.layer-group input,.score-hidden input { accent-color:var(--accent); }.periods { display:flex; flex-wrap:nowrap; gap:7px; padding-left:20px; }.periods label { color:var(--muted); gap:4px; }.periods input { width:12px; height:12px; margin:0; flex:none; }.layer-inline { display:flex; align-items:center; gap:12px; }.layer-inline .disabled { gap:2px; }.swatch { width:13px; height:3px; display:inline-block; background:var(--muted); }.tf-1d { background:#d96073; }.tf-1h { background:#efb643; }.manual { background:#65b7cf; }.vwap { background:#b07ad6; }.horizontal { background:#9fb0c4; }
|
|
.hint { margin:6px 0 2px; font-size:10px; color:var(--muted); line-height:1.35; }
|
|
|
|
/* Tool palette: the head arms the tool, the body configures what it creates. */
|
|
.tool { border:1px solid var(--line); border-radius:7px; margin-bottom:8px; overflow:hidden; }
|
|
.tool.armed { border-color:var(--accent); box-shadow:0 0 0 1px var(--accent) inset; }
|
|
.tool-head { display:flex; align-items:center; gap:8px; width:100%; padding:8px 10px; font:inherit; font-size:12px;
|
|
background:transparent; color:var(--fg); border:0; cursor:pointer; text-align:left; }
|
|
.tool-head:hover { background:color-mix(in srgb, var(--fg) 5%, transparent); }
|
|
.tool.armed .tool-head { background:color-mix(in srgb, var(--accent) 14%, transparent); }
|
|
.tool-glyph { display:inline-block; width:14px; color:var(--muted); font-size:14px; line-height:1; }
|
|
.tool.armed .tool-glyph { color:var(--accent); }
|
|
.tool-state { margin-left:auto; font-size:10px; color:var(--accent); }
|
|
.tool-body { padding:2px 10px 10px; display:grid; gap:7px; }
|
|
.tool-body label { display:grid; gap:3px; font-size:10px; color:var(--muted); }
|
|
.tool-body .row { display:grid; grid-template-columns:1fr 1fr; gap:7px; align-items:end; }
|
|
.tool-body input, .tool-body select { min-width:0; font:inherit; font-size:11px; padding:4px 6px;
|
|
border:1px solid var(--line); border-radius:5px; background:transparent; color:var(--fg); }
|
|
.tool-body input[type=color] { padding:2px; height:26px; }
|
|
.tool-body .check { display:flex; align-items:center; gap:5px; padding-bottom:5px; }
|
|
.tool-body .check input { width:auto; }
|
|
.price-row { grid-template-columns:1fr auto; }
|
|
.price-row button { font-size:11px; padding:5px 12px; align-self:end; }
|
|
|
|
/* Armed tools take over the pointer, so the chart must not look draggable. */
|
|
#chart.armed { cursor:crosshair; }
|
|
.chart-price-tag { position:absolute; z-index:5; transform:translateY(-50%); padding:2px 6px; border-radius:4px;
|
|
background:#e0a34a; color:#1a1206; font-size:11px; font-variant-numeric:tabular-nums; pointer-events:none; }
|
|
.arm-hint { color:var(--accent); }
|
|
.armed-toggle { display:flex; align-items:center; gap:4px; font-size:10px; color:var(--accent); cursor:pointer; }
|
|
.armed-toggle.off { color:var(--muted); text-decoration:line-through; }.optional { color:var(--muted); }.disabled { opacity:.45; }.score-hidden { margin-top:11px; color:var(--muted); line-height:1.25; }
|
|
.cluster { margin:8px 0; padding:10px; border:1px solid var(--line); border-left:3px solid var(--green); background:var(--chart-bg); }.cluster.resistance { border-left-color:var(--red); }.cluster-top { display:flex; justify-content:space-between; text-transform:uppercase; font-size:10px; }.cluster-top strong { color:var(--accent); font-size:16px; }.zone { margin:4px 0; font-size:15px; }.members,.distance { color:var(--muted); font-size:9px; }.distance { margin-top:5px; }.alert-entry { white-space:pre-line; margin:8px 0; padding:9px; background:color-mix(in srgb,var(--accent) 8%,transparent); font-size:10px; }.alert-entry time { display:block; color:var(--accent); margin-bottom:4px; }
|
|
@media (max-width:850px) { #app { padding:10px; }.chart-shell { min-width:0; }main { grid-template-columns:1fr; }.drawing-tools { flex-wrap:wrap; }.drawing-tools .line-name { width:110px; }#chart { height:55vh; min-height:360px; }aside { min-height:180px; }header { height:40px; }.chart-head { align-items:flex-start; flex-direction:column; }.timeframes { justify-content:flex-start; }.timeframes button { padding:5px 8px; } }
|
|
/* --- chart comments ---------------------------------------------------- */
|
|
.chart-comments { position:absolute; inset:0; pointer-events:none; z-index:4; }
|
|
.chart-comment { position:absolute; max-width:210px; padding:4px 7px; border:1px solid var(--muted);
|
|
border-left-width:3px; border-radius:4px; background:var(--chart-bg); color:var(--fg);
|
|
font-size:10px; line-height:1.35; pointer-events:auto; cursor:pointer; white-space:pre-wrap;
|
|
box-shadow:0 1px 3px rgba(0,0,0,.16); }
|
|
.chart-comment.collapsed { max-width:none; width:18px; height:18px; padding:0; display:flex;
|
|
align-items:center; justify-content:center; border-radius:50%; border-left-width:1px;
|
|
font-size:9px; white-space:nowrap; }
|
|
.chart-comment.floating { border-style:dashed; cursor:grab; }
|
|
/* Parked on the edge it scrolled off, pointing the way back to it. */
|
|
.chart-comment.off-left::before, .chart-comment.off-right::before { color:var(--muted); font-size:9px; }
|
|
.chart-comment.off-left::before { content:'◀ '; }
|
|
.chart-comment.off-right::after { content:' ▶'; color:var(--muted); font-size:9px; }
|
|
.chart-comment.off-left, .chart-comment.off-right { opacity:.72; }
|
|
/* --- drawings filter --------------------------------------------------- */
|
|
.drawing-filters { display:grid; grid-template-columns:auto 1fr; gap:6px; margin-bottom:8px; }
|
|
.drawing-filters select, .drawing-filters input { font:inherit; font-size:10px; padding:4px 6px;
|
|
border:1px solid var(--line); border-radius:5px; background:transparent; color:var(--fg); min-width:0; }
|
|
.drawing-icon { display:flex; align-items:center; justify-content:center; color:var(--muted); font-size:10px; }
|
|
.collapse-toggle { font:inherit; font-size:9px; padding:2px 6px; border:1px solid var(--line);
|
|
border-radius:4px; background:transparent; color:var(--muted); cursor:pointer; }
|
|
.tool-body textarea { width:100%; min-width:0; font:inherit; font-size:11px; padding:4px 6px; resize:vertical;
|
|
border:1px solid var(--line); border-radius:5px; background:transparent; color:var(--fg); }
|
|
.side-auto { align-self:end; padding-bottom:6px; font-size:10px; color:var(--muted); }.side-auto b { color:var(--fg); font-weight:600; }
|
|
.chart-snap-dot { position:absolute; width:9px; height:9px; margin:-5px 0 0 -5px; border-radius:50%; border:2px solid var(--accent); background:var(--chart-bg); pointer-events:none; z-index:5; }.chart-snap-dot[data-side=resistance] { border-color:#bd4545; }.chart-snap-dot[data-side=support] { border-color:#27825c; }
|
|
.chart-snap-label { position:absolute; padding:2px 5px; border-radius:3px; pointer-events:none; z-index:6; font-size:10px; white-space:nowrap; background:var(--chart-bg); border:1px solid var(--muted); color:var(--fg); }.chart-snap-label[data-side=resistance] { border-color:#bd4545; }.chart-snap-label[data-side=support] { border-color:#27825c; }
|
|
.chart-snap-leader { position:absolute; inset:0; pointer-events:none; z-index:5; overflow:visible; }.chart-snap-leader line { stroke:var(--muted); stroke-width:1; stroke-dasharray:3 3; opacity:.75; }
|
|
.chart-overlays { position:absolute; left:0; top:0; pointer-events:none; overflow:visible; z-index:3; }.chart-overlays > * { pointer-events:auto; }.chart-overlays .chart-comments, .chart-overlays .chart-snap-leader { position:absolute; inset:0; }
|