Penumbra / frontend /docs.jsx
Louis Fichet
Initial deploy
c528423
/* global React */
function CodeBlock({ lang, code, t }) {
return (
<div className="code-block">
<div className="code-head">
<span className="lang">{lang}</span>
<button
className="copy-btn"
onClick={(e) => {
navigator.clipboard.writeText(code);
const btn = e.currentTarget;
btn.textContent = t("docs_copied");
setTimeout(() => { btn.textContent = t("docs_copy"); }, 1400);
}}
>{t("docs_copy")}</button>
</div>
<pre><code>{code}</code></pre>
</div>
);
}
const PY_EXAMPLE = `import requests
r = requests.post(
"/predict",
json={
"latitude": 48.8566,
"longitude": 2.3522,
"altitude_km": 11.6
},
)
data = r.json()
print(f"Dose: {data['dose_usvh']} µSv/h ({data['risk_level']})")
`;
const CURL_EXAMPLE = `curl -X POST /predict \\
-H "Content-Type: application/json" \\
-d '{
"latitude": 48.8566,
"longitude": 2.3522,
"altitude_km": 11.6
}'
`;
const JSON_EXAMPLE = `{
"dose_usvh": 3.95,
"lower_bound": 3.48,
"upper_bound": 4.42,
"risk_level": "moderate",
"kp_current": 2.7,
"timestamp": "2026-05-09T14:23:00Z"
}
`;
function DocsPage({ t }) {
return (
<>
<section className="hero" style={{ paddingBottom: 32 }}>
<h1 style={{ fontSize: "clamp(34px, 4.6vw, 52px)" }}>{t("docs_title")}</h1>
<p className="lead">{t("docs_lead")}</p>
</section>
<div className="docs">
<div className="docs-card">
<div className="endpoint">
<span className="method">POST</span>
<span className="path">/predict</span>
</div>
<h3 className="docs-h3">{t("docs_h_params")}</h3>
<table className="param-table">
<thead>
<tr>
<th>{t("docs_th_field")}</th>
<th>{t("docs_th_type")}</th>
<th>{t("docs_th_desc")}</th>
</tr>
</thead>
<tbody>
<tr><td><code>latitude</code></td><td>float</td><td>{t("docs_p_lat")}</td></tr>
<tr><td><code>longitude</code></td><td>float</td><td>{t("docs_p_lon")}</td></tr>
<tr><td><code>altitude_km</code></td><td>float</td><td>{t("docs_p_alt")}</td></tr>
</tbody>
</table>
<h3 className="docs-h3">{t("docs_h_response")}</h3>
<table className="param-table">
<thead>
<tr>
<th>{t("docs_th_field")}</th>
<th>{t("docs_th_type")}</th>
<th>{t("docs_th_desc")}</th>
</tr>
</thead>
<tbody>
<tr><td><code>dose_usvh</code></td><td>float</td><td>{t("docs_r_dose")}</td></tr>
<tr><td><code>lower_bound</code></td><td>float</td><td>{t("docs_r_low")}</td></tr>
<tr><td><code>upper_bound</code></td><td>float</td><td>{t("docs_r_high")}</td></tr>
<tr><td><code>risk_level</code></td><td>string</td><td>{t("docs_r_risk")}</td></tr>
<tr><td><code>kp_current</code></td><td>float</td><td>{t("docs_r_kp")}</td></tr>
<tr><td><code>timestamp</code></td><td>string</td><td>{t("docs_r_ts")}</td></tr>
</tbody>
</table>
</div>
<h3 className="docs-h3" style={{ marginTop: 8 }}>{t("docs_h_examples")}</h3>
<div className="docs-grid-2">
<CodeBlock lang="Python" code={PY_EXAMPLE} t={t} />
<CodeBlock lang="cURL" code={CURL_EXAMPLE} t={t} />
</div>
<CodeBlock lang="JSON" code={JSON_EXAMPLE} t={t} />
<a className="github-link" href="https://github.com/penumbra-aero/predict" target="_blank" rel="noopener">
<span className="gh-mark" aria-hidden="true">
<svg viewBox="0 0 16 16" width="18" height="18" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
</span>
<span>{t("docs_github")}</span>
<span className="arrow-ext" aria-hidden="true"></span>
</a>
</div>
</>
);
}
window.DocsPage = DocsPage;