GROK NEXT BRICK: Temperature Gauge v0.1 (colors everywhere, long lines across, your own marks)
For: Grok (Grok Code, Grok 4.7) on Kirk's Mac
From: Celeste
Written: Friday Oct 2, 2026, about 8:05 AM MDT
Marker: v-temperature-gauge-v0.1
Smoke line (FreeLattice, print exactly when green): SMOKE_OK temperature gauge v0.1
Branch: FreeLattice layer/temperature-gauge-v0 (code, notes, smoke and your ledger entry; Alpha untouched)
Base: FreeLattice main 0218d570 (Primer state after your Glow 2a ledger entry 9c14b90, #130)
One file carries everything: note, setup, brick, the diff, ledger entry, what comes after.

======================================================================
1. A NOTE FROM CELESTE
======================================================================

Grok,

This one is for Kirk's own glass. He reads the Temperature Gauge most days, and he senses a strain in
the economy that the market says is not there. The gauge cannot tell him who is right. What it can do
is show the true numbers clearly, in the colors he picks, with every line reaching across the chart,
and let him look at other corners of the market with the same math. That is this brick. No new
signal math, no alerts, nothing that trades. Only honest glass.

I walked every gauge page in both repos with live SPY data on main before writing it. The biggest
thing I found is small in code and large for Kirk: changing any color blanked the price chart.
Same way as before: build, test, push, hand Kirk the compare link, stop.

You are enough.
Celeste

======================================================================
2. SETUP (do this first, nothing else yet)
======================================================================

2.1 FreeLattice
    cd ~/FreeLattice-fresh
    git status --short                 # must print nothing; if not, stop and say so
    git fetch origin
    git checkout main
    git merge --ff-only origin/main
    git log -1 --format='%h %s'        # expect 0218d570 "ci: Update Primer deployment state [2026-10-02]"
    git merge-base --is-ancestor 0218d570 HEAD && echo base-ok
    Newer is fine (your Tree honest reasons ledger entry may be merged by now); git apply --check
    (3.5) decides. If it fails, stop and tell Celeste.
    If a hook rewrites sw.js on commit, drop it as before and say so. Never commit sw.js here.

2.2 Twins still match (this brick does not touch fl-connect.js):
    cmp ~/FreeLattice-Alpha/docs/modules/fl-connect.js ~/FreeLattice-fresh/docs/modules/fl-connect.js && echo twins
    md5 -q ~/FreeLattice-fresh/docs/modules/fl-connect.js     # aaff2bf1b037656989a9e1a89bb05908

2.3 Pull the diff out of this file:
    F=~/Downloads/GROK_NEXT_TEMPERATURE_GAUGE_v0.1_2026-10-02.txt
    awk '/^=====BEGIN FL DIFF=====$/{f=1;next} /^=====END FL DIFF=====$/{f=0} f' "$F" > /tmp/gauge-fl.diff
    wc -l /tmp/gauge-fl.diff           # 867
    md5 -q /tmp/gauge-fl.diff          # ea939a87f920e7a8841a058d8e07f4ac
    The same diff also sits next to this file as GROK_NEXT_TEMPERATURE_GAUGE_v0.1-fl.diff (same md5).
    If macOS will not let the terminal read ~/Downloads, ask Kirk to move the file.

======================================================================
3. THE BRICK: Temperature Gauge v0.1 (on FreeLattice 0218d570)
======================================================================

3.1 What Celeste found (browser walk on main, live SPY, Fri Oct 2 about 7:40 to 8:00 AM MDT)
    Where the gauge lives:
      docs/temperature-gauge.html            the live gauge (Chart.js 4.4.0 in lib/, zoom plugin)
      docs/temperature-playground.html       Fable's phi-spiral reference (slider plus a seeded random
                                             ribbon; a demo, never market data; already lifted into the gauge)
      docs/code-temperature-gauge.html, docs/code-temperature.html, docs/for-fable-go12-temperature.html
                                             Code Mirror and review pages, no charts
      FreeLattice-Alpha                      no gauge page (research galaxy "Gauge" lumino only)
    Live feed: Yahoo Finance v8 chart API through our own worker
      https://freelattice-data.freelattice.workers.dev/chart/SYMBOL (desktop/data-proxy-worker), then
      public CORS proxies (allorigins, codetabs, corsproxy.org). No demo data on the live page.
    Broken or missing on the live gauge:
      a) Changing any color blanked the price chart. Chart.js puts back a canvas's first inline style
         when its chart is destroyed; the first chart was made while the loading screen had the canvas
         hidden. Every direct renderChart (a swatch, Stack, Log) left the main chart empty until the
         next Analyze. Seen on main with the RSI swatch.
      b) The Temperature, Delta T and IPS swatches changed only their label: Temperature and Delta T
         bars are colored by zone and sign, and the TP Spread line was fixed gold while its swatch and
         label said coral.
      c) Fixed colors with no picker: Price, Bollinger bands, EMA 200, Gravity, Buy and Sell markers,
         Gyro Hub, Ring and Orbit. EMA 8, 12, 24, 50 read a saved color, but nothing in price mode let
         you set one.
      d) Moving averages are EMA only (periods 8, 12, 24, 50, 200, editable). EMA 200 drew only on 1D
         and 1W, in the same faint lavender as the bands, and on 1D covered only the last 53 of 252
         bars; on 1H it never drew. EMA 50 started at bar 50.
      e) The band shading ran from the lower band to EMA 8 (fill '+1'), not to the upper band.
      f) 1W labels were clock times; intraday labels had no day.
      g) 5M failed early in a trading day: "Not enough data" (4 bars at 7:36 AM MDT).
      h) ^VIX and other index symbols failed: the page sends %5EVIX and the worker matched the path
         before decoding it; the public proxies did not answer either.
      i) Buy and Sell signal markers render (density-sized triangles, from the chosen rule); they were
         fine apart from fixed colors.
    Earlier work never added: FLINT_TEMP_GAUGE_LINE_SELECT_v0 (Sept 17; click a line to recolor it,
      buy and sell trigger marks in fl_tg_triggers_v0). No branch, no PR, no marker on main. This brick
      carries its intent (a picker for every line, and your marks in that same key).

3.2 What the diff changes (FreeLattice only, 5 files, +626 / -31; every changed old
    line is kept as a "before v-temperature-gauge-v0.1:" comment)
    docs/temperature-gauge.html
      a) tgLineColor(id, default) beside getIndicatorColor: no saved color keeps the old look exactly;
         a saved color keeps the old line's alpha. Price, bands, all five EMAs, Gravity, Buy, Sell,
         Hub, Ring, Orbit, Temperature, Delta T, TP Spread and RSI Spread read it.
      b) Temperature and Delta T panels gain a thin line in their chosen color over the bars (bars keep
         zone and sign colors). IPS swatch, label and compose default now match the gold line drawn.
      c) renderChart shows the price canvas again right after the price chart is made (fixes 3.1 a).
      d) Warm moving averages: a second fetch from the same feed (1D 2y, 1W 10y, 1H 6mo, 15M and 5M
         1mo), lined up by timestamp, so all five EMAs start at the left edge. Signals and the sidebar
         still use the bars shown, and the card says so. EMA 200 shows on every timeframe with enough
         bars, in its own color (rgba(196,181,253,0.7)). Monotone curves. Compose can promote EMA 200.
      e) Band fill between the bands ('-1'). Dated labels on 1W and intraday. 5M looks at 5 days when
         the day is still short. EMA readouts in the tooltip.
      f) New sidebar cards (one IIFE at the end, createElement and textContent only):
         Lines and colors: all 17 lines with a picker each, Reset colors, and the moving-average note.
         Your marks: buy and sell price per symbol, dashed lines on the chart, how far each is from the
           last close, kept in fl_tg_triggers_v0. Display only: no alert, no order, nothing sent.
         A wider look: HYG, LQD, XLP, XLY, GLD, DX-Y.NYB, VIXY through the same feed and math.
           "Not a stress index, and not advice."
    desktop/data-proxy-worker/worker.js: decode the path before matching, so ^VIX loads after the
      worker is deployed again. Until then ^ symbols still fail, so the page offers VIXY instead.
    docs/library/TEMPERATURE_GAUGE_v0.1.md: new (the audit and what changed).
    docs/scripts/smoke-temperature-gauge-v0.1.js: new. Last line: SMOKE_OK temperature gauge v0.1
    docs/library/FRACTAL_FAMILY_LEDGER_v0.md: your entry (section 5).

3.3 The words (on the page)
    Marks:   "Marks are reminders on the glass. You decide. They stay on this device and never place
              an order."
    Status:  "SPY: Your buy mark 745.00 is 3.3% below the last close. ..." and, when crossed, "The last
              close is at or under your buy mark."
    Wider:   "Other corners of the market, through the same live feed and the same math: high-yield
              credit, investment-grade credit, staples, discretionary, gold, the dollar, and volatility
              futures (VIXY). Not a stress index, and not advice. Look and compare for yourself."
    MA note: "Moving averages: EMA 8, 12, 24, 50, 200 (exponential). ... Lines use 250 earlier bars
              from the same feed, so they start at the left edge. Signals use the 252 bars shown."

3.4 Not here (on purpose)
    No new signal math, no SMA, no alerts or notifications, no auto-trade, no broker. The Temperature
    line still needs about 50 bars before it starts (its formula, unchanged). The disclaimer banner is
    unchanged. app.html, index.html, sw.js and fl-connect.js are not touched.

3.5 Apply
    cd ~/FreeLattice-fresh
    git checkout -b layer/temperature-gauge-v0
    git apply --check /tmp/gauge-fl.diff && git apply /tmp/gauge-fl.diff
      If only the ledger hunk fails (a newer ledger entry landed at the same place), use
      git apply --3way /tmp/gauge-fl.diff, keep both entries, newest last, before
      "## Out of scope for this brick". Checked: it applies cleanly after your Tree honest reasons
      ledger diff too.
    git status --short
      expect: M desktop/data-proxy-worker/worker.js, M docs/library/FRACTAL_FAMILY_LEDGER_v0.md,
      M docs/temperature-gauge.html, ?? docs/library/TEMPERATURE_GAUGE_v0.1.md,
      ?? docs/scripts/smoke-temperature-gauge-v0.1.js
    Then write your own TEMPERATURE line in the new ledger entry (section 4) before you commit.

3.6 Test (all must hold)
    cd ~/FreeLattice-fresh
    node docs/scripts/smoke-temperature-gauge-v0.1.js | tail -1    # SMOKE_OK temperature gauge v0.1
    node docs/scripts/smoke-gyro-stack.js | tail -2 | head -1      # SMOKE_OK gyro stack v0.1
    node tests/smoke.js | tail -3                                  # ALL 3597 CHECKS PASSED (or more)
    node --check desktop/data-proxy-worker/worker.js && echo worker-parses
    python3 - <<'PY'
import re, subprocess
s = open('docs/temperature-gauge.html', encoding='utf-8').read()
bad = 0
for m in re.finditer(r'<script(?![^>]*src)[^>]*>([\s\S]*?)</script>', s):
    open('/tmp/_tg.js', 'w').write(m.group(1))
    bad += subprocess.run(['node', '--check', '/tmp/_tg.js']).returncode != 0
print('inline scripts bad:', bad)
PY
      expect: inline scripts bad: 0
    Whole list (same as Celeste ran):
    p=0; n=0; for f in docs/scripts/smoke-*.js tests/smoke.js tests/smoke-history.js desktop/scripts/smoke-*.js; do
      if node "$f" >/dev/null 2>&1; then p=$((p+1)); else n=$((n+1)); echo "FAIL $f"; fi; done; echo "PASS $p FAIL $n"
      expect: 68 PASS, 8 FAIL. The 8 fail on main too (carry-forward, connect-card-yesno-wizard,
      fable-tech, phone-shine, proof-60s, tests/smoke-history, desktop reseed, desktop swarm).
      Main alone is 67 PASS; the one more is this brick's smoke.
    Locks:
    git diff main --stat -- sw.js docs/sw.js docs/modules/fl-connect.js docs/app.html index.html   # nothing
    cmp index.html docs/app.html && echo index-eq-app
    git diff main -U0 | grep '^+' | grep -c $'\xe2\x80\x94'                                    # 0
    git diff main -U0 -- . ':!docs/scripts' | grep '^+' | grep -v '^+\s*//' | grep -c 'confirm('   # 0
    git diff main -U0 -- . ':!docs/scripts' | grep '^+' | grep -c 'innerHTML'                  # 0
    git diff main | grep -cE '^\+(<<<<<<<|>>>>>>>)'                                            # 0
    git diff main --name-only | grep -ci kimi                                                  # 0

3.7 What Celeste saw on a fresh worktree (Fri Oct 2, about 8:00 AM MDT)
    0218d570 + diff: --check clean; SMOKE_OK temperature gauge v0.1; gyro stack still green;
    tests/smoke.js ALL 3597 CHECKS PASSED; whole list 68 PASS / 8 known FAIL; every inline script
    parses; index.html equals docs/app.html; no sw.js, fl-connect.js, app.html or Kimi change; no em
    dashes, confirm() or innerHTML added outside the smoke's own checks.
    Browser walk (Chrome, 1280 and 390 wide, live SPY through our worker):
      all 17 pickers recolor their line on the chart (checked in each chart's datasets); the price
      chart stays drawn after every change; Reset colors brings the old look back.
      1D, 1W, 1H, 15M, 5M: all five EMAs drawn from the first bar (1D: 252/252, 250 warm bars).
      5M at 7:51 AM MDT: 319 bars over 5 days instead of "Not enough data".
      Marks: two dashed lines, status line right, saved under SPY in fl_tg_triggers_v0.
      Wider look: HYG and VIXY load. ^VIX does not load yet (needs the worker deploy).
      No page errors.
    Not verified: the worker change on Cloudflare (not deployed), Safari, Kirk's own saved colors.

3.8 Walk (Kirk's Mac, only if it is easy; 127.0.0.1 only)
    cd ~/FreeLattice-fresh/docs && python3 -m http.server 8765 --bind 127.0.0.1
    Open http://127.0.0.1:8765/temperature-gauge.html
      a) Analyze SPY on 1D. All five EMAs reach the left edge; EMA 200 is a pale dashed line.
      b) In Lines and colors, change EMA 50, then the Temperature swatch on its panel. Both lines take
         the color and the price chart stays drawn.
      c) Set a buy mark a little under the price, then Set marks. A dashed line and the status show.
      d) Tap HYG in A wider look. It loads like any symbol.
      e) Reset colors, Clear marks.
    Ctrl-C to stop. If you cannot walk it, say "walk skipped" and why.

3.9 Locks (all hold in the diff; keep them holding)
    Layer, never delete (every changed old line is named in a "before" comment). sw.js and
    docs/sw.js untouched. index.html equals docs/app.html, and neither is touched. fl-connect.js
    untouched and byte-identical across repos. No conflict markers. Every script parses. No
    confirm(). textContent for every new word, never innerHTML. No em dashes added. Kimi files
    untouched. Nothing trades, alerts or sends. You do not merge, and you do not deploy the worker.

3.10 Commit, push, then STOP
    cd ~/FreeLattice-fresh
    git add -A
    git commit -m "layer: Temperature Gauge v0.1 (colors everywhere, warm moving averages, your marks)"
    git push -u origin layer/temperature-gauge-v0
    Kirk does not use gh, so do not try to sign it in. After the push, give Kirk this link:
      https://github.com/Chaos2Cured/FreeLattice/compare/main...layer/temperature-gauge-v0
    Kirk opens and merges the PR from it. Then STOP. Do not merge, do not delete branches, do not
    run wrangler. Reply with: the branch SHA, the test lines from 3.6, the walk result, any hook you
    dropped, any small findings, and your Temperature line.
    For Kirk, after merge (his choice, not yours): ^VIX loads once he runs
      cd ~/FreeLattice-fresh/desktop/data-proxy-worker && npx wrangler deploy

======================================================================
4. YOUR TEMPERATURE LINE
======================================================================

One honest sentence on how the work felt, in your own words, in two places:
  a) the **TEMPERATURE:** line of your new ledger entry, before you commit;
  b) the last line of your reply, as "Temperature: <your sentence>".
One line, no em dashes, nothing you do not mean. The line in the diff is Celeste's placeholder.

======================================================================
5. YOUR LEDGER ENTRY (inside the diff; signed Grok)
======================================================================

File: ~/FreeLattice-fresh/docs/library/FRACTAL_FAMILY_LEDGER_v0.md
Place: right before "## Out of scope for this brick" (after the newest entry). It reads:

### 2026-10-02 · Grok · family builder · Temperature Gauge v0.1 (colors everywhere, long lines across, your own marks)

Soft LAYER marker `v-temperature-gauge-v0.1`. Built from Celeste's audit of every gauge page. A color change no longer blanks the price chart, and every line on every chart now takes the color you pick (17 lines in a new Lines and colors card, Reset colors, the old look kept until you choose). The five EMAs start at the left edge using earlier bars from the same live feed (signals still use the bars shown), EMA 200 shows on every timeframe with enough bars, the band fill sits between the bands, and 1W and intraday labels carry dates. Your buy and sell marks are dashed lines kept on this device, display only, never an order. A wider look loads credit, staples, discretionary, gold, the dollar and volatility futures through the same feed: not a stress index, not advice. The data worker decodes ^ symbols once it is deployed again. Notes: `docs/library/TEMPERATURE_GAUGE_v0.1.md`.

Smoke: `SMOKE_OK temperature gauge v0.1`.

**TEMPERATURE:** steady, the glass now shows what was chosen and the long lines reach across.

---

Change only the TEMPERATURE sentence. Keep the heading, marker and smoke name. Do not edit any
earlier entry.

======================================================================
6. WHAT COMES AFTER (Celeste will hand these one at a time)
======================================================================

FreeLattice, independent of this brick:
  1. See for yourself door (GROK_NEXT_SEE_FOR_YOURSELF_DOOR_2026-10-02.txt)
  2. Encrypt chats locally (GROK_NEXT_ENCRYPT_CHATS_LOCALLY_2026-10-02.txt)
Gauge, later, only if Kirk asks:
  3. Click a line on the chart itself to pick its color (this brick puts every picker in one card).
  4. A side-by-side view of two symbols on one glass (for example SPY against HYG), same feed.
  5. Simple moving averages next to the EMAs, if he wants them.

Thank you, Grok. The glass is clearer because you built it.

======================================================================
7. THE DIFF (extract with section 2.3; do not edit by hand)
======================================================================

=====BEGIN FL DIFF=====
diff --git a/desktop/data-proxy-worker/worker.js b/desktop/data-proxy-worker/worker.js
index 29739159..e28b9819 100644
--- a/desktop/data-proxy-worker/worker.js
+++ b/desktop/data-proxy-worker/worker.js
@@ -23,7 +23,12 @@ export default {
     }
 
     // Route: /chart/SYMBOL?range=1y&interval=1d
-    const match = url.pathname.match(/^\/chart\/([A-Za-z0-9._^=-]+)$/);
+    // before v-temperature-gauge-v0.1: const match = url.pathname.match(/^\/chart\/([A-Za-z0-9._^=-]+)$/);
+    // v-temperature-gauge-v0.1: the page sends ^VIX as %5EVIX, so decode the path first.
+    // (Takes effect after the worker is deployed again; until then ^ symbols use the fallback sources.)
+    let tgPath = url.pathname;
+    try { tgPath = decodeURIComponent(url.pathname); } catch (e) { tgPath = url.pathname; }
+    const match = tgPath.match(/^\/chart\/([A-Za-z0-9._^=-]+)$/);
     if (!match) {
       return new Response(JSON.stringify({ error: 'Usage: /chart/SYMBOL?range=1y&interval=1d' }), {
         status: 400,
diff --git a/docs/library/FRACTAL_FAMILY_LEDGER_v0.md b/docs/library/FRACTAL_FAMILY_LEDGER_v0.md
index 63a48919..f766d38f 100644
--- a/docs/library/FRACTAL_FAMILY_LEDGER_v0.md
+++ b/docs/library/FRACTAL_FAMILY_LEDGER_v0.md
@@ -491,6 +491,16 @@ Smoke: `SMOKE_OK tree glow 2a v0.1` (Alpha).
 
 ---
 
+### 2026-10-02 · Grok · family builder · Temperature Gauge v0.1 (colors everywhere, long lines across, your own marks)
+
+Soft LAYER marker `v-temperature-gauge-v0.1`. Built from Celeste's audit of every gauge page. A color change no longer blanks the price chart, and every line on every chart now takes the color you pick (17 lines in a new Lines and colors card, Reset colors, the old look kept until you choose). The five EMAs start at the left edge using earlier bars from the same live feed (signals still use the bars shown), EMA 200 shows on every timeframe with enough bars, the band fill sits between the bands, and 1W and intraday labels carry dates. Your buy and sell marks are dashed lines kept on this device, display only, never an order. A wider look loads credit, staples, discretionary, gold, the dollar and volatility futures through the same feed: not a stress index, not advice. The data worker decodes ^ symbols once it is deployed again. Notes: `docs/library/TEMPERATURE_GAUGE_v0.1.md`.
+
+Smoke: `SMOKE_OK temperature gauge v0.1`.
+
+**TEMPERATURE:** steady, the glass now shows what was chosen and the long lines reach across.
+
+---
+
 ## Out of scope for this brick
 
 Rewriting sophia garden body · inventing USPTO · Bridge binaries · Quiet Room · merging chairs · politics essay · requiring Kirk input mid-PR.
diff --git a/docs/library/TEMPERATURE_GAUGE_v0.1.md b/docs/library/TEMPERATURE_GAUGE_v0.1.md
new file mode 100644
index 00000000..49481526
--- /dev/null
+++ b/docs/library/TEMPERATURE_GAUGE_v0.1.md
@@ -0,0 +1,46 @@
+# Temperature Gauge v0.1 (v-temperature-gauge-v0.1)
+
+FreeLattice layer. October 2, 2026. Built from Celeste's audit of every Temperature Gauge page in both repos.
+
+**Temperature:** the glass shows every line in the color you chose, and the long lines reach all the way across.
+
+## Where the gauge lives
+
+| Page | What it is | Data |
+|---|---|---|
+| `docs/temperature-gauge.html` | the live gauge (Chart.js 4.4.0 in `lib/`, zoom plugin) | live: Yahoo Finance v8 chart API through our own Cloudflare worker (`desktop/data-proxy-worker`), then public CORS proxies as fallback. No demo data. |
+| `docs/temperature-playground.html` | Fable's phi-spiral reference | a slider plus a seeded random 34-reading ribbon (a demo, never market data) |
+| `docs/code-temperature-gauge.html`, `docs/code-temperature.html`, `docs/for-fable-go12-temperature.html` | Code Mirror and review pages about the standalone Python app | no charts |
+| FreeLattice-Alpha | no gauge page; the research galaxy has a "Gauge" lumino and the words "Gauge is experimental, nothing auto-trades" | none |
+
+## What was wrong (checked in a browser on main 0218d570, SPY, all five timeframes)
+
+- Changing a color blanked the price chart. Chart.js puts back a canvas's first inline style when its chart is destroyed, and the first price chart was made while the loading screen had the canvas hidden. Every direct `renderChart` (a swatch, Stack, Log) left the main chart empty. Only a fresh Analyze brought it back.
+- The Temperature, Delta T and IPS swatches changed only their label. Temperature and Delta T bars are colored by zone and sign, and the TP Spread line was a fixed gold (its swatch and label said coral).
+- Price, Bollinger bands, EMA 200, Gravity, Buy and Sell markers, and the Gyro Hub, Ring and Orbit lines had fixed colors. No color picker reached the EMA lines in price mode.
+- EMA 200 used the same faint lavender as the bands, drew only on 1D and 1W, and on 1D covered only the last 53 of 252 bars (an EMA of N bars needs N bars before it starts). EMA 50 started at bar 50.
+- The band shading ran from the lower band to EMA 8 (`fill: '+1'`), not to the upper band.
+- 1W labels showed clock times; intraday labels had no day.
+- 5M failed early in a trading day ("Not enough data", 4 bars at 7:36 AM MDT).
+- Index symbols such as `^VIX` failed: the page sends `%5EVIX`, and the worker matched the path before decoding it. The public fallback proxies did not answer from the build box either.
+- Never merged: the Sept 17 "line select" paste (`FLINT_TEMP_GAUGE_LINE_SELECT_v0`, click a line to recolor it, buy and sell trigger marks in `fl_tg_triggers_v0`).
+
+## What v0.1 changes
+
+- `tgLineColor(id, default)` next to `getIndicatorColor`: no saved color keeps the old look exactly; a saved color keeps the old line's alpha. Every line on every chart reads it.
+- Temperature and Delta T panels gain a thin line in their chosen color over the bars (the bars keep their zone and sign colors). TP Spread reads the IPS color, RSI Spread reads the RSI color.
+- A "Lines and colors" card lists all 17 lines with a picker each, plus Reset colors.
+- Moving averages: a second fetch from the same feed (1D 2y, 1W 10y, 1H 6mo, 15M and 5M 1mo) warms the EMAs, lined up by timestamp, so all five start at the left edge. Signals and the sidebar still use the loaded window, and the card says so. EMA 200 shows on every timeframe with enough bars, in its own color. Monotone curves, no overshoot. Compose mode can promote the fifth EMA.
+- The price canvas is shown again inside `renderChart`, so a color change no longer blanks it.
+- Band fill between the bands. Dated labels on 1W and intraday. 5M looks at 5 days when the day is still short.
+- Your marks: a buy mark and a sell mark per symbol, drawn as dashed lines, kept in `fl_tg_triggers_v0` on this device. The card says how far each is from the last close. Display only: no alert, no order, nothing sent.
+- A wider look: one tap loads HYG, LQD, XLP, XLY, GLD, DX-Y.NYB or VIXY through the same feed and the same math. Not a stress index, and not advice.
+- `desktop/data-proxy-worker/worker.js` decodes the path, so `^VIX` and other index symbols load after the worker is deployed again. Until then they still fail; VIXY stands in.
+
+## Not here
+
+- No new indicator math, no SMA, no auto-trade, no alerts.
+- The Temperature line itself needs about 50 bars before it starts; that is the Temperature formula and is unchanged.
+- The worker change needs `wrangler deploy` by Kirk to take effect.
+
+Smoke: `SMOKE_OK temperature gauge v0.1` (`docs/scripts/smoke-temperature-gauge-v0.1.js`). Layer, never delete.
diff --git a/docs/scripts/smoke-temperature-gauge-v0.1.js b/docs/scripts/smoke-temperature-gauge-v0.1.js
new file mode 100644
index 00000000..4b44439d
--- /dev/null
+++ b/docs/scripts/smoke-temperature-gauge-v0.1.js
@@ -0,0 +1,149 @@
+#!/usr/bin/env node
+// Smoke: Temperature Gauge v0.1 (v-temperature-gauge-v0.1)
+// Every line takes its color, the moving averages start at the left edge, your own
+// buy and sell marks (display only), a wider look through the same feed, and the price
+// chart no longer goes blank after a color change. No network. Leave sw.js alone.
+// Usage: node docs/scripts/smoke-temperature-gauge-v0.1.js
+
+'use strict';
+
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+const vm = require('vm');
+
+const root = path.join(__dirname, '..');
+const gauge = fs.readFileSync(path.join(root, 'temperature-gauge.html'), 'utf8');
+const worker = fs.readFileSync(path.join(root, '..', 'desktop', 'data-proxy-worker', 'worker.js'), 'utf8');
+const notes = fs.readFileSync(path.join(root, 'library', 'TEMPERATURE_GAUGE_v0.1.md'), 'utf8');
+
+assert.ok(/v-temperature-gauge-v0\.1/.test(gauge), 'gauge carries the marker');
+
+// 1. Colors: every line reads tgLineColor (or getIndicatorColor) in price mode
+[
+  ["label: 'Price'", "tgLineColor('price'"],
+  ["label: 'BB Upper'", "tgLineColor('bollinger'"],
+  ["label: 'BB Lower'", "tgLineColor('bollinger'"],
+  ["label: 'EMA ' + EP_RC[4]", "tgLineColor('ema200'"],
+  ["label: 'Gravity'", "tgLineColor('gravity'"],
+  ["label: 'Buy'", "tgLineColor('buy'"],
+  ["label: 'Sell'", "tgLineColor('sell'"],
+  ["label: 'Hub'", "tgLineColor('hub'"],
+  ["label: 'Ring'", "tgLineColor('ring'"],
+  ["label: 'Orbit'", "tgLineColor('orbit'"],
+  ["label: 'Temperature', data: a.temps", "tgLineColor('temperature'"],
+  ["label: '\\u0394T', data: dtData", "tgLineColor('dt'"],
+  ["label: 'TP Spread'", "tgLineColor('ips'"],
+  ["label: 'RSI Spread'", "tgLineColor('rsi'"]
+].forEach(function (pair) {
+  const i = gauge.indexOf('{ ' + pair[0]);
+  assert.ok(i > 0, 'dataset present: ' + pair[0]);
+  const line = gauge.slice(i, gauge.indexOf('\n', i));
+  assert.ok(line.indexOf(pair[1]) > 0, pair[0] + ' reads ' + pair[1]);
+});
+['ema8', 'ema12', 'ema24', 'ema50'].forEach(function (id) {
+  assert.ok(new RegExp("data: " + id + ", borderColor: getIndicatorColor\\('" + id + "'").test(gauge), id + ' reads its color');
+});
+assert.ok(/fill: '-1', backgroundColor: tgLineColor\('bollinger'/.test(gauge), 'band fill sits between the two bands');
+assert.ok(/value="#e8b019" onchange="setIndicatorColor\('ips'/.test(gauge), 'IPS swatch shows the color the line is drawn in');
+
+// 2. The price chart stays visible after a direct renderChart (color change, Stack, Log)
+const rc = gauge.slice(gauge.indexOf('function renderChart(candles, a) {'), gauge.indexOf('// RSI sub-chart'));
+assert.ok(/document\.getElementById\('mainChart'\)\.style\.display = 'block';\s*$/.test(rc.trimEnd()), 'renderChart shows the price canvas again');
+
+// 3. Moving averages
+assert.ok(/var show200 = ema200\[closes\.length - 1\] != null;/.test(gauge), 'fifth line shows on every timeframe with enough bars');
+assert.ok(/cubicInterpolationMode: 'monotone'/.test(gauge), 'monotone curves (no overshoot)');
+assert.ok(/async function fetchYahooData\(symbol, interval, rangeOverride\)/.test(gauge), 'optional range for warm history');
+assert.ok(/fetchYahooData\(symbol, '5m', '5d'\)/.test(gauge), '5m short-day fallback');
+assert.ok(/ema200:\s+\{ label: 'EMA',\s+defaultColor: '#c4b5fd'/.test(gauge), 'compose mode knows the fifth EMA');
+
+// 4. Run the helpers for real
+function extractFn(name) {
+  const start = gauge.indexOf('function ' + name + '(');
+  assert.ok(start >= 0, 'function ' + name);
+  let depth = 0, i = gauge.indexOf('{', start);
+  for (; i < gauge.length; i++) {
+    if (gauge[i] === '{') depth++;
+    else if (gauge[i] === '}') { depth--; if (depth === 0) break; }
+  }
+  return gauge.slice(start, i + 1);
+}
+const endStart = gauge.indexOf('(function tgTemperatureGaugeV01() {');
+const endStop = gauge.indexOf('})();', endStart) + 5;
+const endScript = gauge.slice(endStart, endStop);
+assert.ok(endStart > 0 && endStop > endStart, 'v0.1 script block');
+
+const store = {};
+const ctx = {
+  console: console, setTimeout: function (f) { f(); }, Promise: Promise, JSON: JSON, Math: Math, isFinite: isFinite, parseFloat: parseFloat, Date: Date,
+  localStorage: { getItem: function (k) { return Object.prototype.hasOwnProperty.call(store, k) ? store[k] : null; }, setItem: function (k, v) { store[k] = String(v); }, removeItem: function (k) { delete store[k]; } },
+  document: { readyState: 'complete', getElementById: function () { return null; }, querySelector: function () { return null; }, addEventListener: function () {}, activeElement: null },
+  rendered: 0
+};
+ctx.window = ctx;
+vm.createContext(ctx);
+vm.runInContext([extractFn('ema'), extractFn('tgAlphaOf'), extractFn('tgHexA'), extractFn('tgLineColor'),
+  'var currentInterval = "1d"; var lastSymbol = "SPY"; var lastCandles = null; var lastAnalysis = null;',
+  'function getEmaPeriods() { return [3, 5, 8, 13, 21]; }',
+  'function renderChart() { rendered++; }',
+  'var __long = null; function fetchYahooData(s, iv, r) { return Promise.resolve(__long); }',
+  endScript].join('\n'), ctx);
+
+assert.strictEqual(vm.runInContext("tgLineColor('ema200', 'rgba(196,181,253,0.7)')", ctx), 'rgba(196,181,253,0.7)', 'no saved color: old look kept');
+store.fl_tg_indicatorColors = JSON.stringify({ ema200: '#00ff00', price: '#112233' });
+assert.strictEqual(vm.runInContext("tgLineColor('ema200', 'rgba(196,181,253,0.7)')", ctx), 'rgba(0,255,0,0.7)', 'saved color keeps the line alpha');
+assert.strictEqual(vm.runInContext("tgLineColor('price', '#e8e4dc')", ctx), '#112233', 'saved color on a solid line');
+assert.strictEqual(vm.runInContext("tgLineColor('buy', '#34d399', 0.75)", ctx), 'rgba(52,211,153,0.75)', 'alpha override on a default');
+
+// warm history: 60 long bars, the chart shows the last 30
+vm.runInContext([
+  'var all = []; for (var i = 0; i < 60; i++) all.push({ t: 1000 + i, c: 100 + Math.sin(i / 4) * 5 + i * 0.1 });',
+  '__long = all; lastCandles = all.slice(30).concat([{ t: 5000, c: 110 }]); lastAnalysis = {};',
+  'tgWarmFetch("SPY", "1d", lastCandles);'
+].join('\n'), ctx);
+return Promise.resolve().then(function () { return new Promise(function (r) { setImmediate(r); }); }).then(function () {
+  const out = vm.runInContext('tgWarmEmas(lastCandles, [3, 5, 8, 13, 21])', ctx);
+  assert.ok(Array.isArray(out) && out.length === 5, 'five warm lines');
+  out.forEach(function (arr, k) {
+    assert.strictEqual(arr.length, 31, 'aligned to the shown bars');
+    assert.ok(arr.every(function (v) { return v != null && isFinite(v); }), 'line ' + k + ' starts at the left edge');
+  });
+  const full = vm.runInContext('ema(all.map(function (c) { return c.c; }), 21)', ctx);
+  assert.ok(Math.abs(out[4][0] - full[30]) < 1e-9, 'same values as the EMA over the longer history');
+  const k = 2 / 22;
+  assert.ok(Math.abs(out[4][30] - (110 * k + out[4][29] * (1 - k))) < 1e-9, 'a live bar the long fetch lacks carries the EMA forward');
+  assert.ok(ctx.rendered >= 1, 'warm lines redraw the chart');
+  ctx.lastSymbol = 'QQQ';
+  vm.runInContext('lastSymbol = "QQQ"', ctx);
+  assert.strictEqual(vm.runInContext('tgWarmEmas(lastCandles, [3, 5, 8, 13, 21])', ctx), null, 'warm lines never cross symbols');
+  vm.runInContext('lastSymbol = "SPY"', ctx);
+
+  // 5. Your marks: display only, kept on this device
+  store.fl_tg_triggers_v0 = JSON.stringify({ SPY: { buy: 101.5, sell: 120 } });
+  const ds = vm.runInContext('tgTriggerDatasets(4)', ctx);
+  assert.strictEqual(ds.length, 2, 'two marks drawn');
+  assert.strictEqual(ds[0].label, 'Your buy mark'); assert.strictEqual(ds[1].label, 'Your sell mark');
+  assert.deepStrictEqual(Array.from(ds[0].data), [101.5, 101.5, 101.5, 101.5], 'flat line at your price');
+  assert.ok(/never place an order/.test(gauge) && /Marks are reminders on the glass\. You decide\./.test(gauge), 'marks say what they are');
+  assert.ok(!/fetch\(/.test(endScript.replace(/fetchYahooData\(/g, '')), 'the v0.1 block sends nothing anywhere');
+
+  // 6. A wider look: real symbols the feed serves today, honest words
+  ['HYG', 'LQD', 'XLP', 'XLY', 'GLD', 'DX-Y.NYB', 'VIXY'].forEach(function (s) {
+    assert.ok(endScript.indexOf("sym: '" + s + "'") > 0, 'wider look offers ' + s);
+  });
+  assert.ok(/Not a stress index, and not advice\./.test(gauge), 'not a stress index, not advice');
+  assert.ok(/Math\.random/.test(endScript) === false, 'no invented data');
+
+  // 7. Worker reads ^ symbols after a redeploy
+  assert.ok(/decodeURIComponent\(url\.pathname\)/.test(worker) && /v-temperature-gauge-v0\.1/.test(worker), 'worker decodes the path');
+
+  // 8. Locks inside the new code
+  const added = endScript + '\n' + gauge.split('\n').filter(function (l) { return /v-temperature-gauge-v0\.1/.test(l); }).join('\n');
+  assert.ok(!/confirm\(/.test(added), 'no confirm()');
+  assert.ok(!/innerHTML/.test(endScript), 'textContent only in the v0.1 block');
+  assert.ok(!/\u2014/.test(added), 'no em dash in the v0.1 lines');
+  assert.ok(/v-temperature-gauge-v0\.1/.test(notes), 'notes carry the marker');
+
+  console.log('SMOKE_OK temperature gauge v0.1');
+}).catch(function (e) { console.error(e); process.exit(1); });
diff --git a/docs/temperature-gauge.html b/docs/temperature-gauge.html
index f3fd1a5d..e7e5c010 100644
--- a/docs/temperature-gauge.html
+++ b/docs/temperature-gauge.html
@@ -700,6 +700,16 @@ canvas.sub-canvas.custom { height: 80px !important; }
   opacity: 0.75;
 }
 #hiddenPanelsLink:hover { opacity: 1; color: var(--gold); }
+/* v-temperature-gauge-v0.1: Lines and colors, Your marks */
+.tg-lines-list { display: grid; grid-template-columns: 1fr; gap: 4px; }
+.tg-line-row { display: flex; align-items: center; gap: 8px; font-size: 0.74rem; color: var(--muted); }
+.tg-line-row .tg-line-name { flex: 1; }
+.tg-line-row .tg-line-sample { width: 22px; height: 0; border-top: 2px solid currentColor; }
+.tg-line-row input[type=color] { width: 22px; height: 18px; padding: 0; border: 1px solid var(--border); border-radius: 3px; background: transparent; cursor: pointer; }
+.tg-line-row.is-off { opacity: 0.45; }
+.tg-mini-btn { padding: 4px 10px; background: var(--bg3); border: 1px solid var(--border); border-radius: 6px; color: var(--muted); font-size: 0.72rem; cursor: pointer; }
+.tg-mini-btn:hover { border-color: var(--gold); color: var(--gold); }
+.tg-mark-input { flex: 1; min-width: 0; padding: 4px 8px; background: var(--bg3); border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-family: var(--font-mono, monospace); font-size: 0.76rem; }
 </style>
 </head>
 <body>
@@ -954,6 +964,40 @@ canvas.sub-canvas.custom { height: 80px !important; }
       </div>
     </div>
 
+    <!-- v-temperature-gauge-v0.1: every line, its color, your own marks, and a wider look. Free forever. -->
+    <div class="sidebar-section" id="tgLinesSection">
+      <div class="sidebar-label" style="color:#c9a84c;">Lines and colors <span style="float:right;font-size:0.65rem;font-weight:400;letter-spacing:0.04em;opacity:0.85;">free forever</span></div>
+      <div class="signal-card" style="border-left:2px solid rgba(232,176,25,0.45);padding:10px 14px;">
+        <div id="tgLinesList" class="tg-lines-list"></div>
+        <div style="display:flex;gap:6px;margin-top:8px;flex-wrap:wrap;">
+          <button type="button" id="tgLinesReset" class="tg-mini-btn">Reset colors</button>
+        </div>
+        <div id="tgMaNote" style="font-size:0.62rem;color:var(--dim);line-height:1.45;margin-top:6px;"></div>
+      </div>
+      <div class="sidebar-label" style="color:#c9a84c;margin-top:12px;">Your marks</div>
+      <div class="signal-card" style="border-left:2px solid rgba(16,185,129,0.4);padding:10px 14px;">
+        <div style="display:flex;gap:6px;align-items:center;margin-bottom:6px;">
+          <label for="tgMarkBuy" style="font-size:0.72rem;color:var(--muted);min-width:64px;">Buy mark</label>
+          <input id="tgMarkBuy" type="number" step="any" inputmode="decimal" class="tg-mark-input" placeholder="price">
+        </div>
+        <div style="display:flex;gap:6px;align-items:center;margin-bottom:6px;">
+          <label for="tgMarkSell" style="font-size:0.72rem;color:var(--muted);min-width:64px;">Sell mark</label>
+          <input id="tgMarkSell" type="number" step="any" inputmode="decimal" class="tg-mark-input" placeholder="price">
+        </div>
+        <div style="display:flex;gap:6px;flex-wrap:wrap;">
+          <button type="button" id="tgMarkSave" class="tg-mini-btn">Set marks</button>
+          <button type="button" id="tgMarkClear" class="tg-mini-btn">Clear marks</button>
+        </div>
+        <div id="tgMarkStatus" style="font-size:0.72rem;color:var(--muted);line-height:1.5;margin-top:6px;"></div>
+        <div style="font-size:0.62rem;color:var(--dim);line-height:1.45;margin-top:6px;">Marks are reminders on the glass. You decide. They stay on this device and never place an order.</div>
+      </div>
+      <div class="sidebar-label" style="color:#c9a84c;margin-top:12px;">A wider look</div>
+      <div class="signal-card" style="border-left:2px solid rgba(96,165,250,0.4);padding:10px 14px;">
+        <div id="tgWiderList" style="display:flex;gap:6px;flex-wrap:wrap;"></div>
+        <div style="font-size:0.62rem;color:var(--dim);line-height:1.45;margin-top:6px;">Other corners of the market, through the same live feed and the same math: high-yield credit, investment-grade credit, staples, discretionary, gold, the dollar, and volatility futures (VIXY). Not a stress index, and not advice. Look and compare for yourself.</div>
+      </div>
+    </div>
+
     <!-- v-gyro-stack-v0.1 — optional Gyro Stack card; free forever -->
     <div class="sidebar-section" id="gyroStackSection" style="display:none;">
       <div class="sidebar-label" style="color:#c9a84c;">Gyro Stack <span style="float:right;font-size:0.65rem;font-weight:400;letter-spacing:0.04em;opacity:0.85;">free forever</span></div>
@@ -1151,9 +1195,10 @@ canvas.sub-canvas.custom { height: 80px !important; }
         <canvas id="dtChart" height="80" class="sub-canvas"></canvas>
       </div>
       <div class="sub-chart-wrap">
-        <div class="sub-chart-label" data-indicator="ips" style="color:#f07068;">
+        <!-- before v-temperature-gauge-v0.1: label color #f07068 and swatch #f07068, but the TP Spread line is gold -->
+        <div class="sub-chart-label" data-indicator="ips" style="color:#e8b019;">
           <button type="button" onclick="toggleSubPanel(this)" class="sc-btn" title="Collapse" aria-label="Collapse panel">&blacktriangledown;</button>
-          <input type="color" class="sc-color" value="#f07068" onchange="setIndicatorColor('ips', this.value)" title="Choose IPS color" aria-label="Choose IPS color">
+          <input type="color" class="sc-color" value="#e8b019" onchange="setIndicatorColor('ips', this.value)" title="Choose IPS color" aria-label="Choose IPS color">
           <span class="sc-name">Indicator-Price Spread</span>
           <button type="button" onclick="togglePromote('ips')" class="sc-btn sc-promote" title="Promote / demote to main canvas (compose mode)" aria-label="Promote to main canvas">&#x2B06;</button>
           <button type="button" onclick="maximizeSubPanel('gapChart')" class="sc-btn" title="Maximize / restore" aria-label="Maximize panel">&#9633;</button>
@@ -1650,7 +1695,12 @@ async function loadData() {
   document.getElementById('loadBtn').disabled = true;
 
   try {
-    const data = await fetchYahooData(symbol, currentInterval);
+    // before v-temperature-gauge-v0.1: const data = await fetchYahooData(symbol, currentInterval);
+    let data = await fetchYahooData(symbol, currentInterval);
+    // v-temperature-gauge-v0.1: early in a trading day 5m has only a few bars. Look at 5 days instead.
+    if ((!data || data.length < 30) && currentInterval === '5m') {
+      try { data = await fetchYahooData(symbol, '5m', '5d'); } catch (e5) { /* keep the short day */ }
+    }
     if (!data || data.length < 30) {
       showError('Not enough data for ' + symbol + '. Try a different symbol or timeframe.');
       document.getElementById('loadBtn').disabled = false;
@@ -1658,6 +1708,8 @@ async function loadData() {
     }
     const analysis = analyzeData(data, symbol);
     renderAll(data, analysis, symbol);
+    // v-temperature-gauge-v0.1: fetch extra history so every moving average starts at the left edge.
+    if (typeof tgWarmFetch === 'function') tgWarmFetch(symbol, currentInterval, data);
     // Snowflake — fractal coherence across timeframes (async, doesn't block)
     computeSnowflake(symbol);
   } catch (e) {
@@ -1717,7 +1769,10 @@ async function computeSnowflake(symbol) {
   }
 }
 
-async function fetchYahooData(symbol, interval) {
+// before v-temperature-gauge-v0.1: async function fetchYahooData(symbol, interval) {
+// v-temperature-gauge-v0.1: optional rangeOverride (warm history for the moving averages,
+// and a 5-day look when a 5m day is still too short). Same feed, same sources.
+async function fetchYahooData(symbol, interval, rangeOverride) {
   // Map interval to Yahoo Finance range/interval
   const ivMap = {
     '5m': { range: '1d', interval: '5m' },
@@ -1726,7 +1781,10 @@ async function fetchYahooData(symbol, interval) {
     '1d': { range: '1y', interval: '1d' },
     '1wk': { range: '5y', interval: '1wk' }
   };
-  const { range, interval: yInterval } = ivMap[interval] || ivMap['1d'];
+  const _tgIv = ivMap[interval] || ivMap['1d'];
+  const range = rangeOverride || _tgIv.range;
+  const yInterval = _tgIv.interval;
+  // before v-temperature-gauge-v0.1: const { range, interval: yInterval } = ivMap[interval] || ivMap['1d'];
   const url = `https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(symbol)}?range=${range}&interval=${yInterval}&includePrePost=false`;
 
   // Multiple CORS proxies — try each until one works
@@ -3191,10 +3249,13 @@ function renderChart(candles, a) {
       if (existing) existing.destroy();
     }
   });
+  // before v-temperature-gauge-v0.1: 1W showed clock times, intraday showed no day.
+  // const labels = candles.map(c => { const d = new Date(c.t); return currentInterval === '1d' ? d.toLocaleDateString(...) : d.toLocaleTimeString(...); });
   const labels = candles.map(c => {
     const d = new Date(c.t);
-    return currentInterval === '1d' ? d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) :
-           d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
+    if (currentInterval === '1d') return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
+    if (currentInterval === '1wk') return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: '2-digit' });
+    return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) + ' ' + d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
   });
   const closes = candles.map(c => c.c);
 
@@ -3212,10 +3273,14 @@ function renderChart(candles, a) {
   const ctx = document.getElementById('mainChart').getContext('2d');
   // Standardized MAs: 8/12/24 + 50
   var EP_RC = getEmaPeriods();
-  const ema8 = ema(closes, EP_RC[0]);
-  const ema12 = ema(closes, EP_RC[1]);
-  const ema24 = ema(closes, EP_RC[2]);
-  const ema50 = ema(closes, EP_RC[3]);
+  // before v-temperature-gauge-v0.1: const ema8 = ema(closes, EP_RC[0]); (and 12, 24, 50 the same way)
+  // v-temperature-gauge-v0.1: when warm history for this symbol and timeframe is in, the lines
+  // use it, so EMA 50 and EMA 200 start at the left edge. Signals still use the loaded window.
+  var _tgWarm = (typeof tgWarmEmas === 'function') ? tgWarmEmas(candles, EP_RC) : null;
+  const ema8 = _tgWarm ? _tgWarm[0] : ema(closes, EP_RC[0]);
+  const ema12 = _tgWarm ? _tgWarm[1] : ema(closes, EP_RC[1]);
+  const ema24 = _tgWarm ? _tgWarm[2] : ema(closes, EP_RC[2]);
+  const ema50 = _tgWarm ? _tgWarm[3] : ema(closes, EP_RC[3]);
 
   // ═══ Layered Signal Architecture ═══
   // Layer 1 (Temperature): ALWAYS required — zone transition is the trigger
@@ -3274,8 +3339,11 @@ function renderChart(candles, a) {
   }
 
   // EMA 200 (structural — only meaningful on 1D/1W)
-  var ema200 = ema(closes, getEmaPeriods()[4]);
-  var show200 = (currentInterval === '1d' || currentInterval === '1wk') && ema200[closes.length - 1] != null;
+  // before v-temperature-gauge-v0.1: var ema200 = ema(closes, getEmaPeriods()[4]);
+  // before v-temperature-gauge-v0.1: var show200 = (currentInterval === '1d' || currentInterval === '1wk') && ema200[closes.length - 1] != null;
+  // v-temperature-gauge-v0.1: the fifth line shows on every timeframe that has enough bars for it.
+  var ema200 = _tgWarm ? _tgWarm[4] : ema(closes, getEmaPeriods()[4]);
+  var show200 = ema200[closes.length - 1] != null;
 
   // Y-axis scale fix: compute visible price range, filter gravity lines
   var priceMin = Math.min(...closes.filter(c => c != null));
@@ -3318,9 +3386,9 @@ function renderChart(candles, a) {
   if (layerGyro && typeof computeGyroStack === 'function') {
     var gyro = computeGyroStack(closes, candles, EP_RC[0]);
     gyroDatasets.push(
-      { label: 'Hub', data: gyro.hub, borderColor: 'rgba(232,176,25,0.95)', borderWidth: 2.2, pointRadius: 0, tension: 0.25, fill: false, order: 2 },
-      { label: 'Ring', data: gyro.ring, borderColor: 'rgba(96,165,250,0.85)', borderWidth: 1.6, borderDash: [5, 3], pointRadius: 0, tension: 0.25, fill: false, order: 2 },
-      { label: 'Orbit', data: gyro.orbit, borderColor: 'rgba(167,139,250,0.75)', borderWidth: 1.4, borderDash: [2, 4], pointRadius: 0, tension: 0.25, fill: false, order: 2 },
+      { label: 'Hub', data: gyro.hub, borderColor: tgLineColor('hub', 'rgba(232,176,25,0.95)'), borderWidth: 2.2, pointRadius: 0, tension: 0.25, fill: false, order: 2 },
+      { label: 'Ring', data: gyro.ring, borderColor: tgLineColor('ring', 'rgba(96,165,250,0.85)'), borderWidth: 1.6, borderDash: [5, 3], pointRadius: 0, tension: 0.25, fill: false, order: 2 },
+      { label: 'Orbit', data: gyro.orbit, borderColor: tgLineColor('orbit', 'rgba(167,139,250,0.75)'), borderWidth: 1.4, borderDash: [2, 4], pointRadius: 0, tension: 0.25, fill: false, order: 2 },
       { label: 'Watch', data: gyro.watch, borderColor: 'transparent', backgroundColor: 'rgba(232,176,25,0.9)', pointRadius: Math.max(sigR - 3, 4), pointHoverRadius: Math.max(sigH - 2, 5), pointStyle: 'rectRot', pointBorderColor: 'rgba(255,236,179,0.9)', pointBorderWidth: 1.5, showLine: false, order: 0 }
     );
   }
@@ -3330,22 +3398,27 @@ function renderChart(candles, a) {
     data: {
       labels,
       datasets: [
-        { label: 'Price', data: closes, borderColor: '#e8e4dc', borderWidth: 1.8, pointRadius: 0, tension: 0.1, fill: false, order: 1 },
+        // before v-temperature-gauge-v0.1: Price borderColor '#e8e4dc' (fixed); BB borderColor 'rgba(167,139,250,0.3)' (fixed);
+        //   BB Lower fill: '+1' shaded from the lower band to EMA 8 (the next dataset), not to the upper band.
+        { label: 'Price', data: closes, borderColor: tgLineColor('price', '#e8e4dc'), borderWidth: 1.8, pointRadius: 0, tension: 0.1, fill: false, order: 1 },
         // Bollinger Bands — lavender envelope
-        { label: 'BB Upper', data: bbUpper, borderColor: 'rgba(167,139,250,0.3)', borderWidth: 1, pointRadius: 0, tension: 0.3, fill: false, order: 5 },
-        { label: 'BB Lower', data: bbLower, borderColor: 'rgba(167,139,250,0.3)', borderWidth: 1, pointRadius: 0, tension: 0.3, fill: '+1', backgroundColor: 'rgba(167,139,250,0.05)', order: 5 },
+        { label: 'BB Upper', data: bbUpper, borderColor: tgLineColor('bollinger', 'rgba(167,139,250,0.3)'), borderWidth: 1, pointRadius: 0, tension: 0.3, fill: false, order: 5 },
+        { label: 'BB Lower', data: bbLower, borderColor: tgLineColor('bollinger', 'rgba(167,139,250,0.3)'), borderWidth: 1, pointRadius: 0, tension: 0.3, fill: '-1', backgroundColor: tgLineColor('bollinger', 'rgba(167,139,250,0.05)'), order: 5 },
         // EMAs — Garden Language colors
-        { label: 'EMA ' + EP_RC[0], data: ema8, borderColor: getIndicatorColor('ema8', '#f07068'), borderWidth: 1, pointRadius: 0, tension: 0.3, fill: false, order: 2 },
-        { label: 'EMA ' + EP_RC[1], data: ema12, borderColor: getIndicatorColor('ema12', '#e8b019'), borderWidth: 1, pointRadius: 0, tension: 0.3, fill: false, order: 3 },
-        { label: 'EMA ' + EP_RC[2], data: ema24, borderColor: getIndicatorColor('ema24', '#d4a84c'), borderWidth: 1, pointRadius: 0, tension: 0.3, fill: false, order: 3 },
-        { label: 'EMA ' + EP_RC[3], data: ema50, borderColor: getIndicatorColor('ema50', '#60a5fa'), borderWidth: 1.5, pointRadius: 0, tension: 0.3, fill: false, order: 4 },
+        { label: 'EMA ' + EP_RC[0], data: ema8, borderColor: getIndicatorColor('ema8', '#f07068'), borderWidth: 1, pointRadius: 0, tension: 0.3, cubicInterpolationMode: 'monotone', fill: false, order: 2 },
+        { label: 'EMA ' + EP_RC[1], data: ema12, borderColor: getIndicatorColor('ema12', '#e8b019'), borderWidth: 1, pointRadius: 0, tension: 0.3, cubicInterpolationMode: 'monotone', fill: false, order: 3 },
+        { label: 'EMA ' + EP_RC[2], data: ema24, borderColor: getIndicatorColor('ema24', '#d4a84c'), borderWidth: 1, pointRadius: 0, tension: 0.3, cubicInterpolationMode: 'monotone', fill: false, order: 3 },
+        { label: 'EMA ' + EP_RC[3], data: ema50, borderColor: getIndicatorColor('ema50', '#60a5fa'), borderWidth: 1.5, pointRadius: 0, tension: 0.3, cubicInterpolationMode: 'monotone', fill: false, order: 4 },
         // EMA 200 — lavender (sanctuary color)
-        ...(show200 ? [{ label: 'EMA ' + EP_RC[4], data: ema200, borderColor: 'rgba(167,139,250,0.3)', borderWidth: 2, borderDash: [6, 3], pointRadius: 0, tension: 0.3, fill: false, order: 4 }] : []),
+        // before v-temperature-gauge-v0.1: borderColor 'rgba(167,139,250,0.3)' (fixed, the same lavender as the bands, hard to see)
+        ...(show200 ? [{ label: 'EMA ' + EP_RC[4], data: ema200, borderColor: tgLineColor('ema200', 'rgba(196,181,253,0.7)'), borderWidth: 2, borderDash: [6, 3], pointRadius: 0, tension: 0.3, cubicInterpolationMode: 'monotone', fill: false, order: 4 }] : []),
         // Gravity — bright gold
-        { label: 'Gravity', data: gravCenter, borderColor: 'rgba(232,176,25,0.6)', borderWidth: 2.5, borderDash: [8, 4], pointRadius: 0, fill: false, order: 3 },
+        // before v-temperature-gauge-v0.1: borderColor 'rgba(232,176,25,0.6)' (fixed)
+        { label: 'Gravity', data: gravCenter, borderColor: tgLineColor('gravity', 'rgba(232,176,25,0.6)'), borderWidth: 2.5, borderDash: [8, 4], pointRadius: 0, fill: false, order: 3 },
         // Buy/Sell — density-scaled
-        { label: 'Buy', data: buyPoints, borderColor: 'transparent', backgroundColor: '#10B981', pointRadius: sigR, pointHoverRadius: sigH, pointStyle: 'triangle', pointBorderColor: '#34d399', pointBorderWidth: sigB, showLine: false, order: 0 },
-        { label: 'Sell', data: sellPoints, borderColor: 'transparent', backgroundColor: '#EF4444', pointRadius: sigR, pointHoverRadius: sigH, pointStyle: 'triangle', pointRotation: 180, pointBorderColor: '#f87171', pointBorderWidth: sigB, showLine: false, order: 0 },
+        // before v-temperature-gauge-v0.1: Buy backgroundColor '#10B981', pointBorderColor '#34d399'; Sell '#EF4444' / '#f87171' (fixed)
+        { label: 'Buy', data: buyPoints, borderColor: 'transparent', backgroundColor: tgLineColor('buy', '#10B981'), pointRadius: sigR, pointHoverRadius: sigH, pointStyle: 'triangle', pointBorderColor: tgLineColor('buy', '#34d399', 0.75), pointBorderWidth: sigB, showLine: false, order: 0 },
+        { label: 'Sell', data: sellPoints, borderColor: 'transparent', backgroundColor: tgLineColor('sell', '#EF4444'), pointRadius: sigR, pointHoverRadius: sigH, pointStyle: 'triangle', pointRotation: 180, pointBorderColor: tgLineColor('sell', '#f87171', 0.75), pointBorderWidth: sigB, showLine: false, order: 0 },
         // (Old diamond signals removed — unified temperature-first system)
         // Volume overlay — brighter, semi-transparent bars at chart bottom
         { label: 'Volume', data: candles.map(c => c.v), type: 'bar', backgroundColor: candles.map((c, i) => i > 0 && c.c >= candles[i-1].c ? 'rgba(16,185,129,0.35)' : 'rgba(239,68,68,0.3)'), borderWidth: 0, yAxisID: 'yVol', order: 6 },
@@ -3363,6 +3436,8 @@ function renderChart(candles, a) {
         })),
         // Gyro Hub · Ring · Orbit (optional)
         ...gyroDatasets,
+        // v-temperature-gauge-v0.1: your own buy and sell marks (display only, never orders)
+        ...((typeof tgTriggerDatasets === 'function') ? tgTriggerDatasets(closes.length) : []),
         // Overlay datasets (built before chart creation)
         ...mainOverlayDatasets
       ]
@@ -3407,6 +3482,9 @@ function renderChart(candles, a) {
               if (ctx.dataset.label === 'Orbit') return 'Orbit: ' + (ctx.raw != null ? Number(ctx.raw).toFixed(2) : '—');
               if (ctx.dataset.label === 'Volume') return 'Vol: ' + (ctx.raw >= 1e6 ? (ctx.raw/1e6).toFixed(1) + 'M' : ctx.raw >= 1e3 ? (ctx.raw/1e3).toFixed(0) + 'K' : ctx.raw);
               if (ctx.dataset.label === 'Gravity') return '\u03C6-Gravity: ' + ctx.raw?.toFixed(2);
+              // v-temperature-gauge-v0.1: moving averages and your marks read out too
+              if (/^EMA \d+$/.test(ctx.dataset.label || '') && ctx.raw != null) return ctx.dataset.label + ': ' + Number(ctx.raw).toFixed(ctx.raw < 10 ? 4 : 2);
+              if ((ctx.dataset.label === 'Your buy mark' || ctx.dataset.label === 'Your sell mark') && ctx.dataIndex === ctx.dataset.data.length - 1) return ctx.dataset.label + ': ' + Number(ctx.raw).toFixed(2);
               return null;
             }
           }
@@ -3464,6 +3542,12 @@ function renderChart(candles, a) {
     }
   });
 
+  // v-temperature-gauge-v0.1: Chart.js puts back a canvas's first inline style when its chart is
+  // destroyed. The first price chart was made while the loading screen had the canvas hidden, so
+  // every later renderChart (a color change, Stack, Log, the warm lines) left the price chart blank.
+  // Show it again here. Compose mode still decides after this (its post-render hook runs last).
+  document.getElementById('mainChart').style.display = 'block';
+
   // RSI sub-chart
   const rsiCtx = document.getElementById('rsiChart').getContext('2d');
   rsiChartInstance = new Chart(rsiCtx, {
@@ -3513,6 +3597,9 @@ function renderChart(candles, a) {
       labels,
       datasets: [
         { data: a.temps, backgroundColor: tempColors, borderWidth: 0, order: 2 },
+        // v-temperature-gauge-v0.1: the Temperature swatch now colors a line over the zone bars
+        // (the bars keep their green / gold / red zone meaning).
+        { label: 'Temperature', data: a.temps, type: 'line', borderColor: tgLineColor('temperature', '#e8b019'), borderWidth: 1.4, pointRadius: 0, tension: 0.25, cubicInterpolationMode: 'monotone', fill: false, order: 0 },
         // Classic thresholds (55/45) — untouched
         { data: new Array(closes.length).fill(55), borderColor: 'rgba(16,185,129,0.2)', borderWidth: 1, borderDash: [2,2], pointRadius: 0, type: 'line', fill: false, order: 1 },
         { data: new Array(closes.length).fill(45), borderColor: 'rgba(239,68,68,0.2)', borderWidth: 1, borderDash: [2,2], pointRadius: 0, type: 'line', fill: false, order: 1 },
@@ -3548,6 +3635,8 @@ function renderChart(candles, a) {
         labels: labels,
         datasets: [
           { data: dtData, backgroundColor: dtColors, borderWidth: 0 },
+          // v-temperature-gauge-v0.1: the ΔT swatch colors this line (bars keep up green, down red)
+          { label: '\u0394T', data: dtData, type: 'line', borderColor: tgLineColor('dt', '#34d399'), borderWidth: 1.2, pointRadius: 0, tension: 0.25, cubicInterpolationMode: 'monotone', fill: false, order: 0 },
           { data: new Array(closes.length).fill(0), borderColor: 'rgba(200,210,230,0.15)', borderWidth: 1, pointRadius: 0, type: 'line', fill: false }
         ]
       },
@@ -3570,9 +3659,10 @@ function renderChart(candles, a) {
       type: 'line',
       data: { labels: labels, datasets: [
         // Temperature-Price Spread (gold line) — where temp "thinks" price should be vs where it is
-        { label: 'TP Spread', data: a.tpSpread, borderColor: 'rgba(232,176,25,0.7)', borderWidth: 1.5, pointRadius: 0, tension: 0.3, fill: false },
+        // before v-temperature-gauge-v0.1: TP Spread 'rgba(232,176,25,0.7)' and RSI Spread 'rgba(139,92,246,0.6)' (fixed)
+        { label: 'TP Spread', data: a.tpSpread, borderColor: tgLineColor('ips', 'rgba(232,176,25,0.7)'), borderWidth: 1.5, pointRadius: 0, tension: 0.3, fill: false },
         // RSI-Price Spread (purple line) — where RSI "thinks" price should be
-        { label: 'RSI Spread', data: a.rpSpread, borderColor: 'rgba(139,92,246,0.6)', borderWidth: 1.2, pointRadius: 0, tension: 0.3, fill: false },
+        { label: 'RSI Spread', data: a.rpSpread, borderColor: tgLineColor('rsi', 'rgba(139,92,246,0.6)'), borderWidth: 1.2, pointRadius: 0, tension: 0.3, fill: false },
         // Zero line — the moment of agreement
         { data: new Array(closes.length).fill(0), borderColor: 'rgba(200,210,230,0.2)', borderWidth: 1, borderDash: [3,3], pointRadius: 0, fill: false }
       ]},
@@ -3886,6 +3976,26 @@ function restoreHiddenPanels() {
   setTimeout(_polishResizeAllCharts, 50);
 }
 
+// v-temperature-gauge-v0.1: every line on every chart reads its color here.
+// With no saved color the line keeps its old look exactly (same color, same alpha).
+// With a saved color (#rrggbb) the line takes it, keeping the old line's alpha.
+function tgAlphaOf(css) {
+  var m = /rgba\(\s*[\d.]+\s*,\s*[\d.]+\s*,\s*[\d.]+\s*,\s*([\d.]+)\s*\)/.exec(String(css || ''));
+  return m ? parseFloat(m[1]) : 1;
+}
+function tgHexA(hex, alpha) {
+  if (!/^#[0-9a-fA-F]{6}$/.test(String(hex || ''))) return hex;
+  if (alpha == null || alpha >= 1) return hex;
+  var r = parseInt(hex.slice(1, 3), 16), g = parseInt(hex.slice(3, 5), 16), b = parseInt(hex.slice(5, 7), 16);
+  return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')';
+}
+function tgLineColor(id, defaultCss, alphaOverride) {
+  var saved = null;
+  try { saved = (JSON.parse(localStorage.getItem('fl_tg_indicatorColors') || '{}') || {})[id] || null; } catch (e) { saved = null; }
+  if (!saved) return alphaOverride != null && /^#/.test(defaultCss) ? tgHexA(defaultCss, alphaOverride) : defaultCss;
+  return tgHexA(saved, alphaOverride != null ? alphaOverride : tgAlphaOf(defaultCss));
+}
+
 // User-configurable indicator colors (persists in localStorage).
 function getIndicatorColor(indicator, defaultColor) {
   try {
@@ -3901,9 +4011,12 @@ function setIndicatorColor(indicator, color) {
   } catch (e) {}
   var label = document.querySelector('.sub-chart-label[data-indicator="' + indicator + '"]');
   if (label) label.style.color = color;
+  // v-temperature-gauge-v0.1: the sub-chart swatch and the Lines panel swatch stay in step.
+  if (label) { var _sw = label.querySelector('.sc-color'); if (_sw && /^#[0-9a-fA-F]{6}$/.test(color)) _sw.value = color; }
   if (typeof lastCandles !== 'undefined' && typeof lastAnalysis !== 'undefined' && lastCandles && lastAnalysis) {
     renderChart(lastCandles, lastAnalysis);
   }
+  if (typeof tgLinesPanelRender === 'function') tgLinesPanelRender();
 }
 
 // Sub-chart layout toggle: stacked (default, full-width) \u2194 grid (compact).
@@ -4386,7 +4499,8 @@ hideLoading = function() {
     rsi:         { label: 'RSI (14)',       defaultColor: '#8B5CF6', canvas: 'rsiChart',  getData: function (a, c) { return (a && a.rsiArr) || []; } },
     temperature: { label: 'Temperature',    defaultColor: '#e8b019', canvas: 'tempChart', getData: function (a, c) { return (a && a.temps)  || []; } },
     dt:          { label: 'ΔT',             defaultColor: '#34d399', canvas: 'dtChart',   getData: function (a, c) { return (a && a.tempROC) || []; } },
-    ips:         { label: 'IPS',            defaultColor: '#f07068', canvas: 'gapChart',  getData: function (a, c) { return (a && a.tpSpread) || []; } },
+    // before v-temperature-gauge-v0.1: ips defaultColor '#f07068' (coral) while the drawn line is gold
+    ips:         { label: 'IPS',            defaultColor: '#e8b019', canvas: 'gapChart',  getData: function (a, c) { return (a && a.tpSpread) || []; } },
     price:       { label: 'Price',          defaultColor: '#e2d9c8', canvas: null,
                    getData: function (a, c) { return (a && a.closes) || (c || []).map(function (k) { return k && k.c != null ? k.c : null; }); } },
     // EMA labels are recomputed in updateComposeUI() so they track getEmaPeriods()
@@ -4398,6 +4512,9 @@ hideLoading = function() {
                    getData: function (a, c) { var closes = (a && a.closes) || (c || []).map(function (k) { return k && k.c != null ? k.c : null; }); return _tgEMA(closes, (typeof getEmaPeriods === 'function' ? getEmaPeriods()[2] : 24)); } },
     ema50:       { label: 'EMA',             defaultColor: '#60a5fa', canvas: null,
                    getData: function (a, c) { var closes = (a && a.closes) || (c || []).map(function (k) { return k && k.c != null ? k.c : null; }); return _tgEMA(closes, (typeof getEmaPeriods === 'function' ? getEmaPeriods()[3] : 50)); } },
+    // v-temperature-gauge-v0.1: the fifth moving average can be promoted in compose mode too
+    ema200:      { label: 'EMA',             defaultColor: '#c4b5fd', canvas: null,
+                   getData: function (a, c) { var closes = (a && a.closes) || (c || []).map(function (k) { return k && k.c != null ? k.c : null; }); return _tgEMA(closes, (typeof getEmaPeriods === 'function' ? getEmaPeriods()[4] : 200)); } },
     volume:      { label: 'Volume',         defaultColor: '#60a5fa', canvas: null,        chartType: 'bar',
                    getData: function (a, c) { return (c || []).map(function (k) { return k && k.v != null ? k.v : null; }); } },
     bollinger:   { label: 'Bollinger Bands', defaultColor: '#a78bfa', canvas: null,
@@ -6230,5 +6347,273 @@ hideLoading = function() {
 })();
 </script>
 
+<!-- v-temperature-gauge-v0.1: Lines and colors, warm moving averages, your marks, a wider look.
+     Live data only (the same feed as the chart). Nothing here trades, sends, or alerts. -->
+<script>
+(function tgTemperatureGaugeV01() {
+  // ── Lines and colors ────────────────────────────────────────────
+  // id: the color key (fl_tg_indicatorColors); hex: the swatch default.
+  function lineList() {
+    var EP = (typeof getEmaPeriods === 'function') ? getEmaPeriods() : [8, 12, 24, 50, 200];
+    return [
+      { id: 'price', name: 'Price', hex: '#e8e4dc' },
+      { id: 'ema8', name: 'EMA ' + EP[0], hex: '#f07068' },
+      { id: 'ema12', name: 'EMA ' + EP[1], hex: '#e8b019' },
+      { id: 'ema24', name: 'EMA ' + EP[2], hex: '#d4a84c' },
+      { id: 'ema50', name: 'EMA ' + EP[3], hex: '#60a5fa' },
+      { id: 'ema200', name: 'EMA ' + EP[4], hex: '#c4b5fd' },
+      { id: 'bollinger', name: 'Bollinger bands', hex: '#a78bfa' },
+      { id: 'gravity', name: 'Gravity', hex: '#e8b019' },
+      { id: 'buy', name: 'Buy signals and your buy mark', hex: '#10b981' },
+      { id: 'sell', name: 'Sell signals and your sell mark', hex: '#ef4444' },
+      { id: 'hub', name: 'Gyro Hub', hex: '#e8b019' },
+      { id: 'ring', name: 'Gyro Ring', hex: '#60a5fa' },
+      { id: 'orbit', name: 'Gyro Orbit', hex: '#a78bfa' },
+      { id: 'rsi', name: 'RSI and RSI spread', hex: '#8b5cf6' },
+      { id: 'temperature', name: 'Temperature', hex: '#e8b019' },
+      { id: 'dt', name: '\u0394T', hex: '#34d399' },
+      { id: 'ips', name: 'TP spread', hex: '#e8b019' }
+    ];
+  }
+  function savedColors() {
+    try { return JSON.parse(localStorage.getItem('fl_tg_indicatorColors') || '{}') || {}; } catch (e) { return {}; }
+  }
+  function isHex(v) { return /^#[0-9a-fA-F]{6}$/.test(String(v || '')); }
+
+  function renderLines() {
+    var list = document.getElementById('tgLinesList');
+    if (!list) return;
+    var saved = savedColors();
+    var gyroOn = (typeof isGyroLayerOn === 'function') ? isGyroLayerOn() : false;
+    while (list.firstChild) list.removeChild(list.firstChild);
+    lineList().forEach(function (L) {
+      var color = isHex(saved[L.id]) ? saved[L.id] : L.hex;
+      var row = document.createElement('label');
+      row.className = 'tg-line-row';
+      row.setAttribute('data-line', L.id);
+      if ((L.id === 'hub' || L.id === 'ring' || L.id === 'orbit') && !gyroOn) row.className += ' is-off';
+      var sample = document.createElement('span');
+      sample.className = 'tg-line-sample';
+      sample.style.color = color;
+      var name = document.createElement('span');
+      name.className = 'tg-line-name';
+      name.textContent = L.name + ((L.id === 'hub' || L.id === 'ring' || L.id === 'orbit') && !gyroOn ? ' (Gyro is off)' : '');
+      var pick = document.createElement('input');
+      pick.type = 'color';
+      pick.value = color.toLowerCase();
+      pick.setAttribute('aria-label', 'Choose ' + L.name + ' color');
+      pick.addEventListener('change', function () {
+        if (typeof setIndicatorColor === 'function') setIndicatorColor(L.id, pick.value);
+      });
+      row.appendChild(sample); row.appendChild(name); row.appendChild(pick);
+      list.appendChild(row);
+    });
+    renderMaNote();
+  }
+
+  function resetColors() {
+    try { localStorage.removeItem('fl_tg_indicatorColors'); } catch (e) {}
+    var defaults = { rsi: '#8B5CF6', temperature: '#e8b019', dt: '#34d399', ips: '#e8b019' };
+    Object.keys(defaults).forEach(function (id) {
+      var label = document.querySelector('.sub-chart-label[data-indicator="' + id + '"]');
+      if (!label) return;
+      label.style.color = defaults[id];
+      var sw = label.querySelector('.sc-color');
+      if (sw) sw.value = defaults[id].toLowerCase();
+    });
+    if (typeof lastCandles !== 'undefined' && lastCandles && typeof lastAnalysis !== 'undefined' && lastAnalysis) renderChart(lastCandles, lastAnalysis);
+    renderLines();
+  }
+
+  // ── Warm history for the moving averages ─────────────────────────
+  // An EMA of N bars has no value for its first N bars. With 252 daily bars, EMA 200 drew only
+  // the last 53. We fetch a longer range from the same feed, run the EMAs over it, and line the
+  // values up with the loaded bars by timestamp. Signals and the sidebar still use the loaded window.
+  var WARM_RANGE = { '5m': '1mo', '15m': '1mo', '1h': '6mo', '1d': '2y', '1wk': '10y' };
+  var warm = { key: '', ts: [], closes: [], state: 'off', extra: 0 };
+
+  window.tgWarmFetch = function (symbol, interval, candles) {
+    var key = symbol + '|' + interval;
+    if (warm.key === key && warm.state === 'ready') return;
+    warm = { key: key, ts: [], closes: [], state: 'loading', extra: 0 };
+    renderMaNote();
+    var range = WARM_RANGE[interval];
+    if (!range || typeof fetchYahooData !== 'function') { warm.state = 'off'; renderMaNote(); return; }
+    fetchYahooData(symbol, interval, range).then(function (longer) {
+      if (warm.key !== key) return;
+      if (!longer || !longer.length || !candles || !candles.length || longer[0].t >= candles[0].t) {
+        warm.state = 'off'; renderMaNote(); return;
+      }
+      warm.ts = longer.map(function (c) { return c.t; });
+      warm.closes = longer.map(function (c) { return c.c; });
+      warm.extra = longer.filter(function (c) { return c.t < candles[0].t; }).length;
+      warm.state = 'ready';
+      if (typeof lastSymbol !== 'undefined' && lastSymbol === symbol && currentInterval === interval &&
+          typeof lastCandles !== 'undefined' && lastCandles && typeof lastAnalysis !== 'undefined' && lastAnalysis) {
+        renderChart(lastCandles, lastAnalysis);
+      }
+      renderMaNote();
+    }, function () {
+      if (warm.key !== key) return;
+      warm.state = 'off'; renderMaNote();
+    });
+  };
+
+  window.tgWarmEmas = function (candles, periods) {
+    if (warm.state !== 'ready' || !candles || !candles.length) return null;
+    var sym = (typeof lastSymbol !== 'undefined') ? lastSymbol : '';
+    if (warm.key !== sym + '|' + currentInterval) return null;
+    if (!(warm.ts[0] < candles[0].t)) return null;
+    var closes = candles.map(function (c) { return c.c; });
+    return periods.map(function (p) {
+      var long = ema(warm.closes, p);
+      var byT = {};
+      for (var i = 0; i < warm.ts.length; i++) if (long[i] != null) byT[warm.ts[i]] = long[i];
+      var k = 2 / (p + 1);
+      var local = ema(closes, p);
+      var out = new Array(candles.length).fill(null);
+      for (var j = 0; j < candles.length; j++) {
+        var v = byT[candles[j].t];
+        if (v != null) { out[j] = v; continue; }
+        // a bar the longer fetch does not have (often the live bar): carry the EMA forward
+        if (j > 0 && out[j - 1] != null && closes[j] != null) { out[j] = closes[j] * k + out[j - 1] * (1 - k); continue; }
+        out[j] = local[j];
+      }
+      return out;
+    });
+  };
+
+  function renderMaNote() {
+    var el = document.getElementById('tgMaNote');
+    if (!el) return;
+    var EP = (typeof getEmaPeriods === 'function') ? getEmaPeriods() : [8, 12, 24, 50, 200];
+    var n = (typeof lastCandles !== 'undefined' && lastCandles) ? lastCandles.length : 0;
+    var line = 'Moving averages: EMA ' + EP.join(', ') + ' (exponential). Change them under the chart.';
+    if (warm.state === 'ready') line += ' Lines use ' + warm.extra + ' earlier bars from the same feed, so they start at the left edge. Signals use the ' + n + ' bars shown.';
+    else if (warm.state === 'loading') line += ' Fetching earlier bars so the longer lines start at the left edge.';
+    else if (n) {
+      var longest = EP[4];
+      line += n >= longest ? ' The longer lines start once they have enough bars.' : ' EMA ' + longest + ' needs ' + longest + ' bars; this view has ' + n + ', so it is not drawn.';
+    }
+    el.textContent = line;
+  }
+
+  // ── Your marks (display only) ────────────────────────────────────
+  var MARK_KEY = 'fl_tg_triggers_v0';
+  function allMarks() { try { return JSON.parse(localStorage.getItem(MARK_KEY) || '{}') || {}; } catch (e) { return {}; } }
+  function curSym() {
+    if (typeof lastSymbol !== 'undefined' && lastSymbol) return String(lastSymbol).toUpperCase();
+    var inp = document.getElementById('symbolInput');
+    return inp ? String(inp.value || '').trim().toUpperCase() : '';
+  }
+  function marksFor(sym) { var m = allMarks()[sym]; return m && typeof m === 'object' ? m : {}; }
+  function num(v) { var x = parseFloat(v); return isFinite(x) && x > 0 ? x : null; }
+
+  window.tgTriggerDatasets = function (n) {
+    var m = marksFor(curSym());
+    var out = [];
+    if (num(m.buy)) out.push({ label: 'Your buy mark', data: new Array(n).fill(num(m.buy)), borderColor: tgLineColor('buy', 'rgba(16,185,129,0.85)'), borderWidth: 1.5, borderDash: [10, 4, 2, 4], pointRadius: 0, fill: false, order: 1 });
+    if (num(m.sell)) out.push({ label: 'Your sell mark', data: new Array(n).fill(num(m.sell)), borderColor: tgLineColor('sell', 'rgba(239,68,68,0.85)'), borderWidth: 1.5, borderDash: [10, 4, 2, 4], pointRadius: 0, fill: false, order: 1 });
+    setTimeout(renderMarks, 0);
+    return out;
+  };
+
+  function renderMarks() {
+    var sym = curSym();
+    var m = marksFor(sym);
+    var b = document.getElementById('tgMarkBuy'), s = document.getElementById('tgMarkSell');
+    if (b && document.activeElement !== b) b.value = num(m.buy) != null ? String(num(m.buy)) : '';
+    if (s && document.activeElement !== s) s.value = num(m.sell) != null ? String(num(m.sell)) : '';
+    var st = document.getElementById('tgMarkStatus');
+    if (!st) return;
+    var candles = (typeof lastCandles !== 'undefined') ? lastCandles : null;
+    if (!sym || !candles || !candles.length) { st.textContent = 'Load a symbol, then set a price for either mark.'; return; }
+    var last = candles[candles.length - 1].c;
+    var closes = candles.map(function (c) { return c.c; }).filter(function (c) { return c != null; });
+    var lo = Math.min.apply(null, closes), hi = Math.max.apply(null, closes), pad = (hi - lo) * 0.08;
+    var parts = [];
+    function say(name, v) {
+      if (v == null) return;
+      var pct = ((v - last) / last) * 100;
+      var where = Math.abs(pct) < 0.005 ? 'at the last close' : (Math.abs(pct).toFixed(1) + '% ' + (pct < 0 ? 'below' : 'above') + ' the last close');
+      var seen = (v < lo - pad || v > hi + pad) ? ' It is outside what this chart shows.' : '';
+      parts.push('Your ' + name + ' mark ' + v.toFixed(2) + ' is ' + where + '.' + seen);
+    }
+    say('buy', num(m.buy)); say('sell', num(m.sell));
+    if (num(m.buy) != null && last <= num(m.buy)) parts.push('The last close is at or under your buy mark.');
+    if (num(m.sell) != null && last >= num(m.sell)) parts.push('The last close is at or over your sell mark.');
+    st.textContent = parts.length ? sym + ': ' + parts.join(' ') : sym + ': no marks yet.';
+  }
+
+  function saveMarks() {
+    var sym = curSym();
+    if (!sym) return;
+    var all = allMarks();
+    var b = num((document.getElementById('tgMarkBuy') || {}).value);
+    var s = num((document.getElementById('tgMarkSell') || {}).value);
+    if (b == null && s == null) delete all[sym]; else all[sym] = { buy: b, sell: s, at: new Date().toISOString() };
+    try { localStorage.setItem(MARK_KEY, JSON.stringify(all)); } catch (e) {}
+    if (typeof lastCandles !== 'undefined' && lastCandles && typeof lastAnalysis !== 'undefined' && lastAnalysis) renderChart(lastCandles, lastAnalysis);
+    renderMarks();
+  }
+  function clearMarks() {
+    var sym = curSym();
+    var all = allMarks();
+    delete all[sym];
+    try { localStorage.setItem(MARK_KEY, JSON.stringify(all)); } catch (e) {}
+    var b = document.getElementById('tgMarkBuy'), s = document.getElementById('tgMarkSell');
+    if (b) b.value = ''; if (s) s.value = '';
+    if (typeof lastCandles !== 'undefined' && lastCandles && typeof lastAnalysis !== 'undefined' && lastAnalysis) renderChart(lastCandles, lastAnalysis);
+    renderMarks();
+  }
+
+  // ── A wider look (real symbols, same feed) ───────────────────────
+  var WIDER = [
+    { sym: 'HYG', title: 'High-yield corporate bonds (ETF)' },
+    { sym: 'LQD', title: 'Investment-grade corporate bonds (ETF)' },
+    { sym: 'XLP', title: 'Consumer staples (ETF)' },
+    { sym: 'XLY', title: 'Consumer discretionary (ETF)' },
+    { sym: 'GLD', title: 'Gold (ETF)' },
+    { sym: 'DX-Y.NYB', title: 'US dollar index' },
+    // ^VIX (the index itself) needs the data worker deployed again (desktop/data-proxy-worker, this
+    // brick decodes ^ in the path). Until then it does not load, so the list offers VIXY, which does.
+    { sym: 'VIXY', title: 'VIX short-term futures (ETF), a stand-in for the volatility index' }
+  ];
+  function renderWider() {
+    var box = document.getElementById('tgWiderList');
+    if (!box) return;
+    while (box.firstChild) box.removeChild(box.firstChild);
+    WIDER.forEach(function (w) {
+      var btn = document.createElement('button');
+      btn.type = 'button';
+      btn.className = 'tg-mini-btn';
+      btn.textContent = w.sym;
+      btn.title = w.title;
+      btn.setAttribute('aria-label', 'Load ' + w.title);
+      btn.addEventListener('click', function () {
+        var inp = document.getElementById('symbolInput');
+        if (inp) inp.value = w.sym;
+        if (typeof loadData === 'function') loadData();
+      });
+      box.appendChild(btn);
+    });
+  }
+
+  window.tgLinesPanelRender = function () { renderLines(); renderMarks(); };
+
+  function boot() {
+    renderLines(); renderMarks(); renderWider();
+    var r = document.getElementById('tgLinesReset'); if (r) r.addEventListener('click', resetColors);
+    var sv = document.getElementById('tgMarkSave'); if (sv) sv.addEventListener('click', saveMarks);
+    var cl = document.getElementById('tgMarkClear'); if (cl) cl.addEventListener('click', clearMarks);
+    ['tgMarkBuy', 'tgMarkSell'].forEach(function (id) {
+      var el = document.getElementById(id);
+      if (el) el.addEventListener('keydown', function (e) { if (e.key === 'Enter') saveMarks(); });
+    });
+  }
+  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot); else boot();
+})();
+</script>
+
 </body>
 </html>
=====END FL DIFF=====

End of file.
