Site Builder
Editing:
index.php
writable 0666
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Gold / Silver — Year Slider (JSON‑powered)</title> <style> :root{ --bg:#ffffff; --ink:#111; --muted:#666; --gold:#ffec9c; --silver:#e9edf2; --accent:#0a84ff; --panel:#f6f7f8; --line:#d9dadd; } *{box-sizing:border-box} html,body{background:var(--bg);color:var(--ink);font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;margin:0;line-height:1.45} .wrap{max-width:1100px;margin:24px auto 72px;padding:0 16px} header{display:flex;align-items:center;justify-content:center;position:relative;margin-bottom:8px} header .big{font-size:3.2rem;font-weight:800;letter-spacing:.5px;text-align:center} header .year{position:absolute;right:0;top:50%;transform:translateY(-50%);color:var(--muted);font-weight:600} .split{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:10px} .card{border-radius:14px;padding:18px;box-shadow:0 1px 0 rgba(0,0,0,.04),0 16px 40px rgba(0,0,0,.07)} .gold{background:var(--gold)} .silver{background:var(--silver)} .label{font-size:.95rem;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px} .price{font-size:2.4rem;font-weight:800} .sub{font-size:.95rem;color:var(--muted)} .sliderbox{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px;margin-top:8px} input[type=range]{width:100%} .controls{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-top:10px} select,button{padding:10px 12px;border-radius:10px;border:1px solid var(--line);background:#fff;color:var(--ink)} .pill{display:inline-block;padding:4px 10px;border-radius:999px;border:1px solid var(--line);color:var(--muted)} .implied{font-size:1.4rem;font-weight:700} .info{position:relative;display:inline-block} .info button{background:#fff;border:1px solid var(--line);border-radius:999px;width:28px;height:28px;line-height:0;display:inline-grid;place-items:center;cursor:pointer} .popover{position:absolute;top:36px;left:0;min-width:280px;max-width:360px;background:#fff;border:1px solid var(--line);border-radius:10px;box-shadow:0 12px 30px rgba(0,0,0,.15);padding:10px 12px;font-size:.95rem;color:var(--muted);display:none;z-index:10} .row{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:8px} .loadbox{display:none;margin-top:8px} .loadbox input[type=file]{display:block;width:100%;padding:10px;border:1px dashed var(--line);border-radius:10px;background:#fff} .muted{color:var(--muted)} .mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace} </style> </head> <body> <div class="wrap"> <header> <div class="big mono" id="ratioBig">— : 1</div> <div class="year" id="yearBadge">Year —</div> </header> <div class="split"> <div class="card gold"> <div class="label">Gold (USD / oz)</div> <div class="price mono" id="goldPrice">—</div> <div class="sub" id="goldNote"></div> </div> <div class="card silver"> <div class="label">Silver (USD / oz)</div> <div class="price mono" id="silverPrice">—</div> <div class="sub" id="silverNote"></div> </div> </div> <div class="sliderbox"> <div class="row"> <div> <div class="label">Year</div> <input id="yr" type="range" min="1900" max="2024" step="1" value="2011"> </div> <div style="display:flex;align-items:flex-end;justify-content:flex-end;"> <span class="pill mono" id="rangePill">1900–2024</span> </div> </div> <div class="controls"> <div> <label class="label" for="ratioSel">Implied silver using ratio</label> <select id="ratioSel"> <option value="16">16 : 1 — classic bimetallic</option> <option value="30">30 : 1 — “fair” heuristic</option> </select> </div> <div class="info"> <button type="button" id="infoBtn" title="What do these mean?">i</button> <div class="popover" id="infoPop"> <div id="infoText" class="muted"> 16:1 ≈ U.S. legal bimetallic ratio after the Coinage Acts of 1834/1837 (moved from ~15:1). 30:1 is a modern “fair value” heuristic used by some hard‑money analysts. </div> </div> </div> <div class="implied">Implied Silver: <span class="mono" id="impliedSilver">—</span></div> </div> <div class="loadbox" id="loadBox"> <p class="muted">Couldn’t fetch <strong>gold_silver_annual_1900_2025.json</strong>. If you opened this file directly from disk, your browser may block local fetches. Either run a tiny local server, or load the JSON manually here:</p> <input type="file" id="fileInput" accept=".json,application/json"> </div> </div> <p class="muted" style="margin-top:10px">Tip: This page reads <strong>gold_silver_annual_1900_2025.json</strong> in the same folder. Years with missing values are skipped in the slider range.</p> </div> <script> (async function(){ const ratioBig = document.getElementById('ratioBig'); const yearBadge = document.getElementById('yearBadge'); const goldPriceEl = document.getElementById('goldPrice'); const silverPriceEl = document.getElementById('silverPrice'); const goldNote = document.getElementById('goldNote'); const silverNote = document.getElementById('silverNote'); const yr = document.getElementById('yr'); const ratioSel = document.getElementById('ratioSel'); const impliedSilverEl = document.getElementById('impliedSilver'); const rangePill = document.getElementById('rangePill'); const loadBox = document.getElementById('loadBox'); const fileInput = document.getElementById('fileInput'); const infoBtn = document.getElementById('infoBtn'); const infoPop = document.getElementById('infoPop'); const infoText = document.getElementById('infoText'); function fmtMoney(n){ return isFinite(n) ? "$" + n.toLocaleString(undefined,{maximumFractionDigits:2}) : "—"; } function fmtRatio(n){ return isFinite(n) ? n.toFixed(2) + " : 1" : "— : 1"; } function explain(r){ if (r === 16) { return "16:1 — “classic” bimetallic ratio: U.S. Coinage Acts of 1834/1837 effectively set ≈16:1 (from ~15:1 in 1792)."; } else { return "30:1 — a modern heuristic some analysts call a 'fair' gold/silver ratio benchmark (not a legal standard)."; } } function updateUI(dataset, year){ const row = dataset.find(d => d.year === year); const gold = row ? row.gold_usd_oz : NaN; const silver = row ? row.silver_usd_oz : NaN; const ratio = (isFinite(gold) && isFinite(silver) && silver > 0) ? (gold / silver) : NaN; ratioBig.textContent = fmtRatio(ratio); yearBadge.textContent = "Year " + year; goldPriceEl.textContent = fmtMoney(gold) + " / oz"; silverPriceEl.textContent = fmtMoney(silver) + " / oz"; goldNote.textContent = row && row.sources && row.sources.gold ? row.sources.gold : ""; silverNote.textContent = row && row.sources && row.sources.silver ? row.sources.silver : ""; const target = parseFloat(ratioSel.value); const implied = isFinite(gold) ? gold / target : NaN; impliedSilverEl.textContent = fmtMoney(implied) + " / oz"; infoText.textContent = explain(target); } function initSlider(dataset){ const years = dataset.filter(d => isFinite(d.gold_usd_oz) && isFinite(d.silver_usd_oz)).map(d => d.year); const minY = Math.min.apply(null, years); const maxY = Math.max.apply(null, years); yr.min = String(minY); yr.max = String(maxY); yr.value = String(Math.min(2011, maxY)); // start at 2011 as a memorable spike if available rangePill.textContent = minY + "–" + maxY; const onChange = () => updateUI(dataset, parseInt(yr.value,10)); yr.addEventListener('input', onChange); ratioSel.addEventListener('change', onChange); infoBtn.addEventListener('click', () => { infoPop.style.display = (infoPop.style.display === 'block' ? 'none' : 'block'); }); document.addEventListener('click', (e) => { if (!infoPop.contains(e.target) && e.target !== infoBtn) infoPop.style.display = 'none'; }); onChange(); } async function tryFetchJSON(){ try { const resp = await fetch('gold_silver_annual_1900_2025.json', {cache:'no-store'}); if (!resp.ok) throw new Error("HTTP " + resp.status); return await resp.json(); } catch (e) { return null; } } function enableFileLoad(){ loadBox.style.display = 'block'; fileInput.addEventListener('change', async (e) => { const file = e.target.files && e.target.files[0]; if (!file) return; const text = await file.text(); const data = JSON.parse(text); const dataset = data.filter(d => d && typeof d.year==='number'); initSlider(dataset); loadBox.style.display = 'none'; }); } // Boot const data = await tryFetchJSON(); if (data && Array.isArray(data)) { const dataset = data.filter(d => d && typeof d.year==='number'); initSlider(dataset); } else { enableFileLoad(); } })(); </script> </body> </html>
Save changes
Create folder
writable 0777
Create
Cancel