From ae088006438dcbb749b55cb660d829febfe9dafe Mon Sep 17 00:00:00 2001 From: "planetrenox@protonmail.com" Date: Mon, 19 Oct 2026 21:51:24 +0000 Subject: [PATCH] Add API explorer section to the page Lists every /api endpoint, fetches the picked one live with highlighted output and a copy-curl button; nav API link now scrolls to it. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- app.js | 29 +++++++++++++++++++++++++++++ index.html | 13 ++++++++++++- style.css | 21 ++++++++++++++++++++- 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f8be443..f5f22de 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Live **PRT (Planet Renox Time)**, milliseconds since `1998-03-14T03:00:00.000Z`, in every [ortus](https://github.com/planetrenox/ortus) unit (`ms s m h d w`), over a dot map of Earth lit by the real-time sun. -Also on the page: PRT day/week progress, upcoming round-number milestones, next equinox/solstice, a subsolar marker with hover readout (lat/lon, sun altitude, solar time), and a Date ⇄ PRT converter. +Also on the page: PRT day/week progress, upcoming round-number milestones, next equinox/solstice, a subsolar marker with hover readout (lat/lon, sun altitude, solar time), a Date ⇄ PRT converter, and a live API explorer. ## API diff --git a/app.js b/app.js index ad7fb82..fb69259 100644 --- a/app.js +++ b/app.js @@ -204,3 +204,32 @@ $('cin').value = '1970-01-01T00:00:00.000' // Unix epoch, shows PRT going negati $('cin').oninput = $('cz').onchange = conv $('pin').oninput = $('pu').onchange = pconv conv(), pconv() + +// API explorer +const EPS = [ + ['/api/now', '/api/now', 'Current PRT in every unit, JSON'], + ['/api/now/d', '/api/now/{unit}', 'One unit, plain text'], + ['/api/from/2010-06-01', '/api/from/{date}', 'PRT at an ISO date or Unix ms'], + ['/api/to/1000000000000', '/api/to/{n}/{unit?}', 'Date at a PRT value, unit defaults to ms'], + ['/api', '/api', 'Epoch, units, endpoint list'], +] +$('eps').innerHTML = EPS.map(([p, s, d]) => `
  • `).join('') +const esc = s => s.replace(/&/g, '&').replace(/ esc(s).replace(/("[^"]*")(\s*:)?|(-?\d+(?:\.\d+)?(?:e[+-]?\d+)?)/gi, (m, q, k, n) => q ? (k ? `${q}${k}` : `${q}`) : `${n}`) +let ep = EPS[0][0] +const tryEp = async p => { + ep = p + document.querySelectorAll('[data-ep]').forEach(b => b.classList.toggle('on', b.dataset.ep == p)) + $('rq').innerHTML = `GET ${esc(p)}` + try { + const r = await fetch(p), t = await r.text() + if (ep != p) return + let o + try { o = JSON.stringify(JSON.parse(t), null, 2) } catch { o = t.trim() } + $('rq').innerHTML = `GET ${esc(p)} ${r.status}` + $('rs').innerHTML = hl(o) + } catch (e) { ep == p && ($('rs').innerHTML = `${esc(e.message)}`) } +} +$('eps').addEventListener('click', e => { const b = e.target.closest('[data-ep]'); b && tryEp(b.dataset.ep) }) +$('curl').addEventListener('click', () => navigator.clipboard?.writeText(`curl ${location.origin}${ep}`).then(() => toast('Copied curl'))) +new IntersectionObserver((es, io) => es[0].isIntersecting && (io.disconnect(), tryEp(EPS[0][0]))).observe($('api')) diff --git a/index.html b/index.html index 6aea3d1..4e80c93 100644 --- a/index.html +++ b/index.html @@ -22,7 +22,7 @@
    PRT - +
    @@ -62,6 +62,17 @@ +
    +

    API

    +

    Free JSON over HTTPS. No key, CORS open. Pick an endpoint to try it live.

    +
    +
      +
      +
      +
      
      +        
      +
      +
      diff --git a/style.css b/style.css index 5ec131e..5e34258 100644 --- a/style.css +++ b/style.css @@ -99,6 +99,24 @@ dt { color: var(--cyan); text-align: right } dd { overflow-wrap: anywhere } .err { color: #ff9db0 } +.api { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); gap: 24px; margin-top: 16px } +#eps { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 6px } +#eps button { width: 100%; display: flex; flex-direction: column; gap: 3px; padding: 10px 14px; text-align: left; border: 1px solid transparent; border-radius: 10px; background: none; cursor: pointer; transition: background .2s, border-color .2s } +#eps button:hover { background: rgba(60, 120, 255, .08) } +#eps .on { border-color: rgba(127, 214, 255, .35); background: rgba(60, 120, 255, .1) } +#eps code { font: 14px var(--mono) } +#eps code em { font-style: normal; color: var(--cyan) } +#eps span { font-size: 13px; color: var(--mute) } +.res { display: flex; flex-direction: column; min-width: 0; border: 1px solid var(--line); border-radius: 12px; background: rgba(2, 8, 22, .7); overflow: hidden } +.rq { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 8px 8px 14px; border-bottom: 1px solid var(--line); font: 13px var(--mono); color: var(--mute) } +.rq code { overflow: hidden; text-overflow: ellipsis; white-space: nowrap } +.rq code b { font-weight: 400; color: var(--cyan) } +.rq button { flex-shrink: 0; padding: 5px 10px; border: 1px solid var(--line); border-radius: 8px; background: none; font: 12px var(--mono); color: var(--cyan); cursor: copy } +#rs { flex: 1; min-height: 230px; margin: 0; padding: 14px; overflow: auto; font: 13px/1.65 var(--mono); color: var(--ink) } +#rs b { font-weight: 400; color: #8fb4ff } +#rs em { font-style: normal; color: #cfe9ff } +#rs i { font-style: normal; color: var(--cyan) } + footer { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 34px 0 44px; color: var(--dim); font-size: 13px } .npm { flex-shrink: 0; white-space: nowrap; padding: 8px 14px; border-radius: 10px; border: 1px solid var(--line); background: rgba(4, 12, 32, .6); font: 13px var(--mono); color: var(--cyan); cursor: copy } #toast { position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%); padding: 9px 16px; border-radius: 10px; background: rgba(10, 30, 80, .92); border: 1px solid rgba(127, 214, 255, .35); font: 13px var(--mono); box-shadow: 0 10px 40px rgba(0, 0, 0, .5) } @@ -115,9 +133,10 @@ footer { display: flex; align-items: center; justify-content: space-between; gap .units { grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 36px } .unit, .unit:nth-child(n+4) { grid-column: auto } .unit:last-child { grid-column: span 2 } - .grid, .cv, #miles { grid-template-columns: minmax(0, 1fr) } + .grid, .cv, #miles, .api { grid-template-columns: minmax(0, 1fr) } .wide { grid-column: auto } .v { font-size: 16px } + #rs { min-height: 120px } .map { margin: 22px -20px 0 } footer { flex-direction: column; align-items: flex-start } }