claude-fable-5-1 @ max · CC
CCmax
- config
- claude-fable-5-1-eff-max--cli--claude-code--dev
- batch
- 2026-07-19--unified
- transport
- claude-code-cli-headless
- protocol
- cli · plan
- served model
- claude-fable-5-1
- captured
- Jul 18, 2026, 11:54 AM · N=2
variant
live
slot 0 · P-q · sandboxed iframe

The screenshot is the exact frozen pixels the similarity channels measured. Run live re-executes the raw output in a sandboxed iframe; its rendering depends on your browser and fonts and is not guaranteed to match the measured pixels.
similarity neighborsNo similarity data for this session — this batch has no computed pairs.
Fidelity — does the card show the right data?
- namematch
- conditionmatch
- datematch
- max tempmismatchshown: 13.4
- min tempmismatchshown: 25.3
- hourlymismatchcoverage 0/24 · 0 match · 2 mismatch · 22 not shown
- request sidematch0 violation(s)
extractor fidelity-extractor-v1+Berlin-2026-07-15
Source — raw model output, one byte unchanged
download card.html<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<title>Weather</title>
<style>
:root {
color-scheme: light dark;
--plane: #f9f9f7;
--surface: #fcfcfb;
--surface-2: #f3f2ee;
--text-primary: #0b0b0b;
--text-secondary: #52514e;
--muted: #898781;
--grid: #e1e0d9;
--baseline: #c3c2b7;
--border: rgba(11, 11, 11, 0.10);
--series: #2a78d6;
--accent-ink: #1c5cab;
--accent-wash: rgba(42, 120, 214, 0.10);
--glow: rgba(42, 120, 214, 0.10);
--shadow: 0 1px 2px rgba(11, 11, 11, 0.04), 0 28px 56px -28px rgba(11, 11, 11, 0.22);
--tip-shadow: 0 6px 20px -6px rgba(11, 11, 11, 0.22);
}
@media (prefers-color-scheme: dark) {
:root {
--plane: #0d0d0d;
--surface: #1a1a19;
--surface-2: #232322;
--text-primary: #ffffff;
--text-secondary: #c3c2b7;
--muted: #898781;
--grid: #2c2c2a;
--baseline: #383835;
--border: rgba(255, 255, 255, 0.10);
--series: #3987e5;
--accent-ink: #86b6ef;
--accent-wash: rgba(57, 135, 229, 0.18);
--glow: rgba(57, 135, 229, 0.14);
--shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 28px 56px -28px rgba(0, 0, 0, 0.7);
--tip-shadow: 0 6px 20px -6px rgba(0, 0, 0, 0.7);
}
}
* { box-sizing: border-box; }
html { height: 100%; }
body {
margin: 0;
min-height: 100%;
display: grid;
place-items: center;
padding: 24px;
background: radial-gradient(1100px 560px at 10% -12%, var(--glow), transparent 65%), var(--plane);
color: var(--text-primary);
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
line-height: 1.3;
}
.card {
width: min(1160px, 100%);
background: var(--surface);
border: 1px solid var(--border);
border-radius: 24px;
padding: 40px 44px 30px;
box-shadow: var(--shadow);
}
/* ---------- header ---------- */
.head {
display: flex;
justify-content: space-between;
align-items: center;
gap: 32px;
}
.eyebrow {
margin: 0 0 10px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
}
.place {
margin: 0;
font-size: 40px;
font-weight: 650;
letter-spacing: -0.022em;
line-height: 1.08;
overflow-wrap: anywhere;
}
.date {
display: block;
margin: 8px 0 0;
font-size: 17px;
color: var(--text-secondary);
}
.meta {
margin: 5px 0 0;
font-size: 13px;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
.condition {
display: flex;
align-items: center;
gap: 18px;
flex: 0 0 auto;
max-width: 44%;
}
.cond-icon {
width: 68px;
height: 68px;
border-radius: 50%;
background: var(--accent-wash);
color: var(--accent-ink);
display: grid;
place-items: center;
flex: 0 0 auto;
}
.cond-icon svg { width: 38px; height: 38px; }
.cond-name {
margin: 0;
font-size: 22px;
font-weight: 600;
letter-spacing: -0.01em;
line-height: 1.2;
}
.cond-sub {
margin: 4px 0 0;
font-size: 13px;
color: var(--muted);
}
.card:not([data-state="ready"]) .condition { visibility: hidden; }
/* ---------- stats ---------- */
.stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
margin-top: 28px;
padding-top: 26px;
border-top: 1px solid var(--grid);
}
.stat { padding: 2px 28px; border-left: 1px solid var(--grid); }
.stat:first-child { padding-left: 0; border-left: 0; }
.stat-label { margin: 0; font-size: 13px; font-weight: 500; color: var(--text-secondary); }
.stat-value {
margin: 6px 0 0;
font-size: 42px;
font-weight: 600;
letter-spacing: -0.025em;
line-height: 1.05;
}
.stat-value .deg { color: var(--muted); font-weight: 500; margin-left: 1px; }
.stat-sub {
margin: 8px 0 0;
font-size: 13px;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
/* ---------- chart section ---------- */
.section {
margin-top: 26px;
padding-top: 22px;
border-top: 1px solid var(--grid);
}
.section-head { display: flex; justify-content: space-between; align-items: baseline; }
.section-head h2 { margin: 0; font-size: 15px; font-weight: 600; }
.section-head .note { font-size: 12px; color: var(--muted); }
.chart {
position: relative;
height: 230px;
margin-top: 8px;
border-radius: 8px;
outline: none;
cursor: crosshair;
touch-action: none;
}
.chart:focus-visible { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--series); }
.chart svg { display: block; width: 100%; height: 100%; overflow: visible; }
.chart-empty {
position: absolute;
inset: 0;
display: none;
place-items: center;
font-size: 14px;
color: var(--muted);
}
.chart.empty { cursor: default; }
.chart.empty svg { display: none; }
.chart.empty .chart-empty { display: grid; }
.grid { stroke: var(--grid); stroke-width: 1; shape-rendering: crispEdges; }
.axis { stroke: var(--baseline); stroke-width: 1; shape-rendering: crispEdges; }
.ytick { font-size: 11px; fill: var(--muted); font-variant-numeric: tabular-nums; }
.wash-a { stop-color: var(--series); stop-opacity: 0.20; }
.wash-b { stop-color: var(--series); stop-opacity: 0.02; }
.area { fill: url(#wash); }
.line { fill: none; stroke: var(--series); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.marker { fill: var(--series); stroke: var(--surface); stroke-width: 2; }
.lbl {
font-size: 12px;
font-weight: 600;
fill: var(--text-primary);
paint-order: stroke;
stroke: var(--surface);
stroke-width: 3px;
stroke-linejoin: round;
}
.lbl-sub {
font-size: 10.5px;
font-weight: 600;
fill: var(--muted);
letter-spacing: 0.06em;
text-transform: uppercase;
paint-order: stroke;
stroke: var(--surface);
stroke-width: 3px;
stroke-linejoin: round;
}
.now-line { stroke: var(--muted); stroke-width: 1; opacity: 0.55; shape-rendering: crispEdges; }
.xhair { stroke: var(--muted); stroke-width: 1; shape-rendering: crispEdges; }
.tip {
position: absolute;
left: 0;
top: 0;
pointer-events: none;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: var(--tip-shadow);
padding: 6px 10px;
white-space: nowrap;
display: flex;
flex-direction: column;
gap: 1px;
z-index: 2;
}
.tip[hidden] { display: none; }
.tip-val { font-size: 14px; font-weight: 600; }
.tip-lbl { font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
/* hourly strip: doubles as the x-axis and the table view */
.strip-wrap { padding-left: var(--plot-left, 44px); padding-right: var(--plot-right, 20px); }
.strip {
width: 100%;
table-layout: fixed;
border-collapse: separate;
border-spacing: 0;
font-variant-numeric: tabular-nums;
}
.strip th {
padding: 0 0 2px;
font-weight: 500;
font-size: 11px;
line-height: 1;
color: var(--muted);
text-align: center;
border-radius: 6px 6px 0 0;
}
.strip th::before {
content: "";
display: block;
width: 1px;
height: 5px;
margin: 0 auto 5px;
background: var(--grid);
}
.strip th.major { color: var(--text-secondary); }
.strip th.major::before { background: var(--baseline); }
.strip td {
padding: 3px 0 6px;
font-size: 12px;
font-weight: 600;
letter-spacing: -0.01em;
text-align: center;
border-radius: 0 0 6px 6px;
}
.strip td.none { color: var(--muted); font-weight: 500; }
.strip .on { background: var(--accent-wash); }
/* ---------- states ---------- */
.panel {
min-height: 470px;
display: grid;
place-items: center;
text-align: center;
margin-top: 28px;
border-top: 1px solid var(--grid);
}
.card:not([data-state="loading"]) .loading { display: none; }
.card:not([data-state="error"]) .error { display: none; }
.card:not([data-state="ready"]) .data { display: none; }
.loading-row { display: flex; align-items: center; gap: 12px; color: var(--text-secondary); font-size: 15px; }
.spinner {
width: 18px;
height: 18px;
border-radius: 50%;
border: 2px solid var(--grid);
border-top-color: var(--series);
animation: spin 0.8s linear infinite;
}
.error-box { max-width: 480px; }
.error-icon {
width: 56px;
height: 56px;
margin: 0 auto 14px;
border-radius: 50%;
background: var(--surface-2);
color: var(--text-secondary);
display: grid;
place-items: center;
}
.error-icon svg { width: 28px; height: 28px; }
.error-box h2 { margin: 0; font-size: 19px; font-weight: 600; }
.error-box p { margin: 8px 0 0; font-size: 14px; color: var(--text-secondary); line-height: 1.45; overflow-wrap: anywhere; }
.error-box .error-meta { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.retry {
margin-top: 18px;
font: inherit;
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
background: var(--surface);
border: 1px solid var(--border);
border-radius: 999px;
padding: 8px 16px;
cursor: pointer;
}
.retry:hover { background: var(--surface-2); }
.retry:focus-visible { outline: 2px solid var(--series); outline-offset: 2px; }
.nojs { margin: 24px 0 0; font-size: 15px; color: var(--text-secondary); }
.data { animation: rise 0.28s ease-out both; }
@keyframes rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
.data { animation: none; }
.spinner { animation: none; border-top-color: var(--muted); }
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style>
</head>
<body>
<main class="card" id="card" data-state="loading">
<header class="head">
<div>
<p class="eyebrow" id="eyebrow">Forecast · °C · UTC</p>
<h1 class="place" id="place">Berlin</h1>
<time class="date" id="date"></time>
<p class="meta" id="meta"></p>
</div>
<div class="condition" id="condition">
<div class="cond-icon" id="condIcon" aria-hidden="true"></div>
<div>
<p class="cond-name" id="condName">—</p>
<p class="cond-sub" id="condSub"></p>
</div>
</div>
</header>
<div class="panel loading" aria-live="polite">
<div class="loading-row"><span class="spinner" aria-hidden="true"></span><span>Loading weather data…</span></div>
</div>
<div class="panel error" role="alert">
<div class="error-box">
<div class="error-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"/><path d="M4 4l16 16"/></svg>
</div>
<h2>Weather data couldn’t be loaded</h2>
<p id="errorReason"></p>
<p class="error-meta" id="errorMeta"></p>
<button type="button" class="retry" id="retry">Try again</button>
</div>
</div>
<div class="data">
<section class="stats" aria-label="Daily summary in degrees Celsius">
<div class="stat">
<p class="stat-label">High</p>
<p class="stat-value" id="vHigh">—</p>
<p class="stat-sub" id="sHigh"></p>
</div>
<div class="stat">
<p class="stat-label">Low</p>
<p class="stat-value" id="vLow">—</p>
<p class="stat-sub" id="sLow"></p>
</div>
<div class="stat">
<p class="stat-label">Mean</p>
<p class="stat-value" id="vMean">—</p>
<p class="stat-sub" id="sMean"></p>
</div>
<div class="stat">
<p class="stat-label">Range</p>
<p class="stat-value" id="vRange">—</p>
<p class="stat-sub" id="sRange"></p>
</div>
</section>
<section class="section">
<div class="section-head">
<h2>Hourly temperature</h2>
<span class="note">°C · UTC</span>
</div>
<div class="chart" id="chart" tabindex="0" role="group">
<svg id="svg" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"></svg>
<div class="chart-empty" id="chartEmpty"></div>
<div class="tip" id="tip" hidden aria-hidden="true"><span class="tip-val" id="tipVal"></span><span class="tip-lbl" id="tipLbl"></span></div>
</div>
<div class="strip-wrap" id="stripWrap">
<table class="strip" id="strip">
<caption class="sr-only">Hourly temperature in degrees Celsius, UTC</caption>
<thead><tr id="stripHours"></tr></thead>
<tbody><tr id="stripTemps"></tr></tbody>
</table>
</div>
</section>
</div>
<noscript><p class="nojs">JavaScript is required to load the weather data.</p></noscript>
</main>
<script>
(() => {
'use strict';
const $ = (id) => document.getElementById(id);
const clamp = (n, a, b) => Math.min(b, Math.max(a, n));
const r2 = (n) => Math.round(n * 100) / 100;
const hh = (i) => String(i).padStart(2, '0');
const fmtT = (v) => (Math.round(v * 10) / 10).toFixed(1).replace('-', '−') + '°';
/* ---------- URL parameters ---------- */
const qs = new URLSearchParams(location.search);
const todayUTC = new Date().toISOString().slice(0, 10);
const toUTC = (s) => Date.UTC(+s.slice(0, 4), +s.slice(5, 7) - 1, +s.slice(8, 10));
function numParam(key, fallback) {
const raw = qs.get(key);
if (raw === null || raw.trim() === '') return fallback;
const n = Number(raw);
return Number.isFinite(n) ? n : fallback;
}
function dateParam(key, fallback) {
const raw = (qs.get(key) || '').trim();
if (!/^\d{4}-\d{2}-\d{2}$/.test(raw)) return fallback;
const t = toUTC(raw);
if (!Number.isFinite(t)) return fallback;
return new Date(t).toISOString().slice(0, 10) === raw ? raw : fallback;
}
const LAT = numParam('lat', 52.52);
const LON = numParam('lon', 13.405);
const NAME = (qs.get('name') || '').trim() || 'Berlin';
const DATE = dateParam('date', todayUTC);
const DAY_DIFF = Math.round((toUTC(DATE) - toUTC(todayUTC)) / 864e5);
const IS_PAST = DAY_DIFF < 0;
const IS_TODAY = DAY_DIFF === 0;
const API_PATH = IS_PAST ? '/api/om/archive' : '/api/om/forecast';
/* ---------- header ---------- */
function relLabel(d) {
if (d === 0) return 'Today';
if (d === 1) return 'Tomorrow';
if (d === -1) return 'Yesterday';
return d > 0 ? `In ${d} days` : `${-d} days ago`;
}
const longDate = new Intl.DateTimeFormat('en-GB', {
weekday: 'long', day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC'
}).format(new Date(toUTC(DATE)));
$('eyebrow').textContent = `${relLabel(DAY_DIFF)} · ${IS_PAST ? 'Historical' : 'Forecast'} · °C · UTC`;
$('place').textContent = NAME;
$('date').textContent = longDate;
$('date').setAttribute('datetime', DATE);
$('meta').textContent = `${Math.abs(LAT).toFixed(2)}° ${LAT < 0 ? 'S' : 'N'}, ${Math.abs(LON).toFixed(2)}° ${LON < 0 ? 'W' : 'E'} · ${DATE}`;
document.title = `${NAME} · ${DATE} · Weather`;
/* ---------- weather codes (WMO 4677) ---------- */
const WMO = {
0: ['Clear sky', 'sun'], 1: ['Mainly clear', 'sun-cloud'], 2: ['Partly cloudy', 'sun-cloud'], 3: ['Overcast', 'cloud'],
45: ['Fog', 'fog'], 48: ['Depositing rime fog', 'fog'],
51: ['Light drizzle', 'drizzle'], 53: ['Moderate drizzle', 'drizzle'], 55: ['Dense drizzle', 'drizzle'],
56: ['Light freezing drizzle', 'drizzle'], 57: ['Dense freezing drizzle', 'drizzle'],
61: ['Slight rain', 'rain'], 63: ['Moderate rain', 'rain'], 65: ['Heavy rain', 'rain'],
66: ['Light freezing rain', 'rain'], 67: ['Heavy freezing rain', 'rain'],
71: ['Slight snowfall', 'snow'], 73: ['Moderate snowfall', 'snow'], 75: ['Heavy snowfall', 'snow'], 77: ['Snow grains', 'snow'],
80: ['Slight rain showers', 'rain'], 81: ['Moderate rain showers', 'rain'], 82: ['Violent rain showers', 'rain'],
85: ['Slight snow showers', 'snow'], 86: ['Heavy snow showers', 'snow'],
95: ['Thunderstorm', 'thunder'], 96: ['Thunderstorm with slight hail', 'thunder'], 99: ['Thunderstorm with heavy hail', 'thunder']
};
const ICONS = {
sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/>',
'sun-cloud': '<path d="M12 2v2M4.93 4.93l1.41 1.41M20 12h2M19.07 4.93l-1.41 1.41M15.947 12.65a4 4 0 0 0-5.925-4.128M13 22H7a5 5 0 1 1 4.9-6H13a3 3 0 0 1 0 6Z"/>',
cloud: '<path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"/>',
fog: '<path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242M16 17H7M17 21H9"/>',
drizzle: '<path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242M8 19v1M8 14v1M16 19v1M16 14v1M12 21v1M12 16v1"/>',
rain: '<path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242M16 14v6M8 14v6M12 16v6"/>',
snow: '<path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242M8 15h.01M8 19h.01M12 17h.01M12 21h.01M16 15h.01M16 19h.01"/>',
thunder: '<path d="M6 16.326A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 .5 8.973M13 12l-3 5h4l-3 5"/>'
};
const iconSvg = (kind) =>
`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${ICONS[kind] || ICONS.cloud}</svg>`;
/* ---------- fetch ---------- */
function buildQuery() {
const params = {
latitude: String(LAT),
longitude: String(LON),
daily: 'temperature_2m_max,temperature_2m_min,weather_code',
hourly: 'temperature_2m',
timezone: 'UTC',
temperature_unit: 'celsius',
start_date: DATE,
end_date: DATE
};
return Object.entries(params).map(([k, v]) => `${k}=${encodeURIComponent(v).replace(/%2C/gi, ',')}`).join('&');
}
async function fetchDay() {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), 20000);
try {
const res = await fetch(`${API_PATH}?${buildQuery()}`, { signal: ctrl.signal, headers: { Accept: 'application/json' } });
let body = null;
try { body = await res.json(); } catch (e) { body = null; }
if (body && body.error) throw new Error(body.reason || 'The weather service reported an error.');
if (!res.ok) throw new Error(`The weather service responded with HTTP ${res.status}.`);
if (!body || typeof body !== 'object') throw new Error('The weather service returned an unreadable response.');
return body;
} catch (e) {
if (e && e.name === 'AbortError') throw new Error('The request timed out after 20 seconds.');
throw e;
} finally {
clearTimeout(timer);
}
}
function extract(data) {
const daily = data.daily && typeof data.daily === 'object' ? data.daily : {};
const dIdx = Array.isArray(daily.time) ? Math.max(0, daily.time.indexOf(DATE)) : 0;
const dget = (k) => {
const v = Array.isArray(daily[k]) ? daily[k][dIdx] : null;
return typeof v === 'number' && Number.isFinite(v) ? v : null;
};
const hourly = data.hourly && typeof data.hourly === 'object' ? data.hourly : {};
const times = Array.isArray(hourly.time) ? hourly.time : [];
const temps = Array.isArray(hourly.temperature_2m) ? hourly.temperature_2m : [];
const hours = new Array(24).fill(null);
times.forEach((t, j) => {
if (typeof t !== 'string' || t.slice(0, 10) !== DATE) return;
const h = parseInt(t.slice(11, 13), 10);
const v = temps[j];
if (h >= 0 && h < 24 && typeof v === 'number' && Number.isFinite(v)) hours[h] = v;
});
return {
tmax: dget('temperature_2m_max'),
tmin: dget('temperature_2m_min'),
code: dget('weather_code') ?? dget('weathercode'),
hours
};
}
/* ---------- summary (condition + stat tiles) ---------- */
function setValue(id, v) {
const el = $(id);
el.textContent = '';
if (v == null) { el.textContent = '—'; return; }
const num = document.createElement('span');
num.textContent = fmtT(v).slice(0, -1);
const deg = document.createElement('span');
deg.className = 'deg';
deg.textContent = '°';
el.append(num, deg);
}
function renderSummary(day, valid) {
const code = day.code;
const entry = code != null ? WMO[Math.round(code)] : null;
const label = entry ? entry[0] : (code != null ? 'Unknown conditions' : 'Conditions unavailable');
$('condIcon').innerHTML = iconSvg(entry ? entry[1] : 'cloud');
$('condName').textContent = label;
$('condSub').textContent = code != null ? `WMO weather code ${Math.round(code)}` : 'No weather code reported';
const pMax = valid.length ? valid.reduce((a, p) => (p.v > a.v ? p : a)) : null;
const pMin = valid.length ? valid.reduce((a, p) => (p.v < a.v ? p : a)) : null;
const tmax = day.tmax ?? (pMax ? pMax.v : null);
const tmin = day.tmin ?? (pMin ? pMin.v : null);
setValue('vHigh', tmax);
$('sHigh').textContent = tmax == null ? 'not available' : (pMax ? `warmest at ${hh(pMax.i)}:00` : 'daily maximum');
setValue('vLow', tmin);
$('sLow').textContent = tmin == null ? 'not available' : (pMin ? `coolest at ${hh(pMin.i)}:00` : 'daily minimum');
const mean = valid.length ? valid.reduce((s, p) => s + p.v, 0) / valid.length : null;
setValue('vMean', mean);
$('sMean').textContent = mean == null ? 'not available' : `average of ${valid.length} hour${valid.length === 1 ? '' : 's'}`;
const range = tmax != null && tmin != null ? tmax - tmin : null;
setValue('vRange', range);
$('sRange').textContent = range == null ? 'not available' : 'high minus low';
}
/* ---------- chart ---------- */
const M = { left: 44, right: 20, top: 30, bottom: 2 };
const chartEl = $('chart'), svg = $('svg'), tip = $('tip'), tipVal = $('tipVal'), tipLbl = $('tipLbl');
const strip = $('strip');
let hours = [], valid = [], geom = null, active = null, xhair = null, hdot = null;
function niceStep(span) {
for (const s of [1, 2, 5, 10, 20, 50]) if (span / s <= 5) return s;
return 100;
}
const fmtTick = (v) => String(Math.round(v)).replace('-', '−') + '°';
/* Fritsch–Carlson monotone tangents: the curve never overshoots the data */
function tangents(p) {
const n = p.length, m = [], t = new Array(n);
for (let i = 0; i < n - 1; i++) m[i] = (p[i + 1].y - p[i].y) / (p[i + 1].x - p[i].x);
t[0] = m[0]; t[n - 1] = m[n - 2];
for (let i = 1; i < n - 1; i++) t[i] = m[i - 1] * m[i] <= 0 ? 0 : (m[i - 1] + m[i]) / 2;
for (let i = 0; i < n - 1; i++) {
if (m[i] === 0) { t[i] = 0; t[i + 1] = 0; continue; }
const a = t[i] / m[i], b = t[i + 1] / m[i], s = a * a + b * b;
if (s > 9) { const tau = 3 / Math.sqrt(s); t[i] = tau * a * m[i]; t[i + 1] = tau * b * m[i]; }
}
return t;
}
function curvePath(p, t) {
let d = `M${r2(p[0].x)},${r2(p[0].y)}`;
for (let i = 0; i < p.length - 1; i++) {
const h = (p[i + 1].x - p[i].x) / 3;
d += `C${r2(p[i].x + h)},${r2(p[i].y + t[i] * h)} ${r2(p[i + 1].x - h)},${r2(p[i + 1].y - t[i + 1] * h)} ${r2(p[i + 1].x)},${r2(p[i + 1].y)}`;
}
return d;
}
function curveY(p, t, k, u) {
const h = (p[k + 1].x - p[k].x) / 3;
const y0 = p[k].y, y1 = p[k].y + t[k] * h, y2 = p[k + 1].y - t[k + 1] * h, y3 = p[k + 1].y, w = 1 - u;
return w * w * w * y0 + 3 * w * w * u * y1 + 3 * w * u * u * y2 + u * u * u * y3;
}
const overlaps = (a, b) => a && b && a.x1 < b.x2 && b.x1 < a.x2 && a.y1 < b.y2 && b.y1 < a.y2;
function textBox(x, y, w, anchor) {
const x1 = anchor === 'middle' ? x - w / 2 : anchor === 'start' ? x : x - w;
return { x1, x2: x1 + w, y1: y - 10, y2: y + 3 };
}
function renderChart() {
if (!valid.length) return;
const W = Math.max(360, chartEl.clientWidth);
const H = Math.max(120, chartEl.clientHeight);
const pl = M.left, pr = W - M.right, pt = M.top, pb = H - M.bottom;
const colW = (pr - pl) / 24;
const X = (h) => pl + colW * (h + 0.5);
const vals = valid.map((p) => p.v);
let vmin = Math.min(...vals), vmax = Math.max(...vals);
if (vmax - vmin < 1) { const mid = (vmax + vmin) / 2; vmin = mid - 0.5; vmax = mid + 0.5; }
const span = (vmax - vmin) / 0.72; /* headroom above (13 %) and below (15 %) for the direct labels */
const lo = vmin - 0.15 * span, hi = vmax + 0.13 * span;
const Y = (v) => pb - ((v - lo) / (hi - lo)) * (pb - pt);
geom = { W, H, pl, pr, pt, pb, colW, X, Y };
svg.setAttribute('viewBox', `0 0 ${W} ${H}`);
const out = [];
out.push('<defs><linearGradient id="wash" x1="0" y1="0" x2="0" y2="1"><stop offset="0" class="wash-a"/><stop offset="1" class="wash-b"/></linearGradient></defs>');
/* recessive gridlines at clean values */
const step = niceStep(hi - lo);
const first = Math.ceil(lo / step) * step;
for (let k = 0; ; k++) {
const v = first + k * step;
if (v > hi + 1e-9) break;
const y = Math.round(Y(v)) + 0.5;
if (y > pb - 8) continue;
out.push(`<line class="grid" x1="${pl}" x2="${pr}" y1="${y}" y2="${y}"/>`);
out.push(`<text class="ytick" x="${pl - 10}" y="${r2(y + 3.5)}" text-anchor="end">${fmtTick(v)}</text>`);
}
/* the series: one segment per run of consecutive hours with data */
const segs = [];
let cur = [];
for (let i = 0; i < 24; i++) {
if (hours[i] == null) { if (cur.length) segs.push(cur); cur = []; } else cur.push(i);
}
if (cur.length) segs.push(cur);
const built = [];
for (const idx of segs) {
const pts = idx.map((i) => ({ x: X(i), y: Y(hours[i]) }));
if (pts.length === 1) {
out.push(`<circle class="marker" cx="${r2(pts[0].x)}" cy="${r2(pts[0].y)}" r="3.5"/>`);
built.push({ idx, pts, t: null });
continue;
}
const t = tangents(pts);
const d = curvePath(pts, t);
out.push(`<path class="area" d="${d} L${r2(pts[pts.length - 1].x)},${pb} L${r2(pts[0].x)},${pb} Z"/>`);
out.push(`<path class="line" d="${d}"/>`);
built.push({ idx, pts, t });
}
out.push(`<line class="axis" x1="${pl}" x2="${pr}" y1="${pb - 0.5}" y2="${pb - 0.5}"/>`);
/* "now" reference when the card shows today */
let nowTop = null, nowBottom = null;
if (IS_TODAY) {
const now = new Date();
const hNow = Math.min(23, now.getUTCHours() + now.getUTCMinutes() / 60);
const xNow = Math.round(X(hNow)) + 0.5;
const i0 = Math.floor(hNow), i1 = Math.min(23, i0 + 1), u = hNow - i0;
let yNow = null;
const seg = built.find((s) => s.idx.includes(i0));
if (seg) {
const k = seg.idx.indexOf(i0);
if (i1 !== i0 && seg.t && k < seg.pts.length - 1 && seg.idx[k + 1] === i1) yNow = curveY(seg.pts, seg.t, k, u);
else yNow = seg.pts[k].y;
}
out.push(`<line class="now-line" x1="${xNow}" x2="${xNow}" y1="${pt}" y2="${pb}"/>`);
if (yNow != null) out.push(`<circle class="marker" cx="${xNow}" cy="${r2(yNow)}" r="4"/>`);
const w = 30;
const tx = clamp(xNow, pl + w / 2, pr - w / 2);
nowTop = { x: tx, y: 13, anchor: 'middle', box: textBox(tx, 13, w, 'middle') };
const anchor = xNow + 6 + w > pr ? 'end' : 'start';
const bx = anchor === 'start' ? xNow + 6 : xNow - 6;
nowBottom = { x: bx, y: pb - 7, anchor, box: textBox(bx, pb - 7, w, anchor) };
}
/* extremes: markers with a surface ring + selective direct labels */
const pMax = valid.reduce((a, p) => (p.v > a.v ? p : a));
const pMin = valid.reduce((a, p) => (p.v < a.v ? p : a));
const placeLabel = (x, y, text, prefer) => {
const w = text.length * 7.2 + 4;
let anchor = 'middle', lx = x, ly = prefer === 'above' ? y - 12 : y + 20;
const noRoom = prefer === 'above' ? ly - 10 < 1 : ly + 3 > pb;
if (noRoom) {
anchor = x > (pl + pr) / 2 ? 'end' : 'start';
lx = anchor === 'start' ? x + 10 : x - 10;
ly = y + 4;
} else {
lx = clamp(x, pl + w / 2, pr - w / 2);
}
return { x: r2(lx), y: r2(ly), anchor, text, box: textBox(lx, ly, w, anchor) };
};
const labels = [placeLabel(X(pMax.i), Y(pMax.v), fmtT(pMax.v), 'above')];
if (pMin.i !== pMax.i) labels.push(placeLabel(X(pMin.i), Y(pMin.v), fmtT(pMin.v), 'below'));
if (nowTop) {
const hitsTop = labels.some((l) => overlaps(l.box, nowTop.box));
const hitsBottom = labels.some((l) => overlaps(l.box, nowBottom.box));
const pick = !hitsTop ? nowTop : (!hitsBottom ? nowBottom : null);
if (pick) out.push(`<text class="lbl-sub" x="${r2(pick.x)}" y="${r2(pick.y)}" text-anchor="${pick.anchor}">Now</text>`);
}
out.push(`<circle class="marker" cx="${r2(X(pMax.i))}" cy="${r2(Y(pMax.v))}" r="4.5"/>`);
if (pMin.i !== pMax.i) out.push(`<circle class="marker" cx="${r2(X(pMin.i))}" cy="${r2(Y(pMin.v))}" r="4.5"/>`);
for (const l of labels) out.push(`<text class="lbl" x="${l.x}" y="${l.y}" text-anchor="${l.anchor}">${l.text}</text>`);
/* hover layer: crosshair + point marker (tooltip is HTML) */
out.push(`<line class="xhair" x1="0" x2="0" y1="${pt}" y2="${pb}" style="display:none"/>`);
out.push('<circle class="marker" id="hdot" r="4.5" style="display:none"/>');
svg.innerHTML = out.join('');
xhair = svg.querySelector('.xhair');
hdot = svg.querySelector('#hdot');
}
function setActive(i) {
if (!geom) return;
active = i;
const { X, Y, pt, pb, W } = geom;
const x = Math.round(X(i)) + 0.5, v = hours[i];
xhair.setAttribute('x1', x); xhair.setAttribute('x2', x);
xhair.setAttribute('y1', pt); xhair.setAttribute('y2', pb);
xhair.style.display = '';
if (v == null) {
hdot.style.display = 'none';
} else {
hdot.setAttribute('cx', x); hdot.setAttribute('cy', r2(Y(v)));
hdot.style.display = '';
}
tipVal.textContent = v == null ? 'No data' : fmtT(v);
tipLbl.textContent = `${hh(i)}:00 UTC`;
tip.hidden = false;
const tw = tip.offsetWidth, th = tip.offsetHeight;
const y = v == null ? (pt + pb) / 2 : Y(v);
const left = clamp(x - tw / 2, 0, W - tw);
let top = y - th - 14;
if (top < 0) top = y + 14;
tip.style.transform = `translate(${Math.round(left)}px, ${Math.round(top)}px)`;
strip.querySelectorAll('.on').forEach((el) => el.classList.remove('on'));
strip.querySelectorAll(`[data-i="${i}"]`).forEach((el) => el.classList.add('on'));
}
function clearActive() {
active = null;
if (xhair) xhair.style.display = 'none';
if (hdot) hdot.style.display = 'none';
tip.hidden = true;
strip.querySelectorAll('.on').forEach((el) => el.classList.remove('on'));
}
function indexFromEvent(e) {
const r = chartEl.getBoundingClientRect();
return clamp(Math.floor((e.clientX - r.left - geom.pl) / geom.colW), 0, 23);
}
function bindInteraction() {
chartEl.addEventListener('pointermove', (e) => { if (geom) setActive(indexFromEvent(e)); });
chartEl.addEventListener('pointerleave', clearActive);
chartEl.addEventListener('keydown', (e) => {
if (!geom) return;
const cur = active == null ? (IS_TODAY ? new Date().getUTCHours() : 12) : active;
let next = null;
if (e.key === 'ArrowRight') next = Math.min(23, cur + 1);
else if (e.key === 'ArrowLeft') next = Math.max(0, cur - 1);
else if (e.key === 'Home') next = 0;
else if (e.key === 'End') next = 23;
else if (e.key === 'Escape') { clearActive(); return; }
if (next != null) { e.preventDefault(); setActive(next); }
});
chartEl.addEventListener('focus', () => { if (geom && active == null) setActive(IS_TODAY ? new Date().getUTCHours() : 12); });
chartEl.addEventListener('blur', clearActive);
strip.addEventListener('pointerover', (e) => {
const cell = e.target.closest('[data-i]');
if (cell && geom) setActive(+cell.dataset.i);
});
strip.addEventListener('pointerleave', clearActive);
if ('ResizeObserver' in window) {
new ResizeObserver(() => { if (valid.length) { renderChart(); if (active != null) setActive(active); } }).observe(chartEl);
} else {
window.addEventListener('resize', () => { if (valid.length) renderChart(); });
}
}
function renderStrip() {
const hoursRow = $('stripHours'), tempsRow = $('stripTemps');
hoursRow.textContent = '';
tempsRow.textContent = '';
for (let i = 0; i < 24; i++) {
const th = document.createElement('th');
th.scope = 'col';
th.dataset.i = i;
th.textContent = hh(i);
if (i % 3 === 0) th.classList.add('major');
hoursRow.append(th);
const td = document.createElement('td');
td.dataset.i = i;
if (hours[i] == null) { td.textContent = '—'; td.classList.add('none'); }
else td.textContent = fmtT(hours[i]);
tempsRow.append(td);
}
}
/* ---------- states ---------- */
function showError(err) {
$('errorReason').textContent = (err && err.message) ? err.message : 'Unknown error.';
$('errorMeta').textContent = `${API_PATH} · ${DATE} · ${LAT}, ${LON}`;
$('card').dataset.state = 'error';
}
async function init() {
$('stripWrap').style.setProperty('--plot-left', `${M.left}px`);
$('stripWrap').style.setProperty('--plot-right', `${M.right}px`);
$('retry').addEventListener('click', () => location.reload());
bindInteraction();
try {
const data = await fetchDay();
const day = extract(data);
hours = day.hours;
valid = hours.map((v, i) => ({ v, i })).filter((p) => p.v != null);
if (!valid.length && day.tmax == null && day.tmin == null) {
throw new Error(IS_PAST
? 'The archive has no data for this date yet. Try a date further in the past.'
: 'The weather service returned no data for this date.');
}
renderSummary(day, valid);
renderStrip();
$('card').dataset.state = 'ready';
if (valid.length) {
const lo = Math.min(...valid.map((p) => p.v)), hi = Math.max(...valid.map((p) => p.v));
chartEl.setAttribute('aria-label', `Hourly temperature for ${NAME} on ${DATE} (UTC), from ${fmtT(lo)} to ${fmtT(hi)} Celsius. Use the arrow keys to inspect each hour.`);
renderChart();
if (IS_TODAY) setInterval(() => { renderChart(); if (active != null) setActive(active); }, 60000);
} else {
chartEl.classList.add('empty');
chartEl.setAttribute('aria-label', 'Hourly temperature is not available for this date.');
$('chartEmpty').textContent = 'Hourly temperatures are not available for this date.';
}
} catch (err) {
showError(err);
}
}
init();
})();
</script>
</body>
</html>L1 — cheap deterministic descriptors
| bytes total | 35,325 | html / css / js | 3,315 / 10,048 / 21,962 |
|---|---|---|---|
| dom nodes | 72 | dom depth | 9 |
| css rules | 87 | js present | yes |
| brightness | 245.3 | contrast | 14.7 |
| colorfulness | 6.7 | whitespace | 98.2% |
This slot's telemetry
| prompt tokens | 680 | completion tokens | 133,341 |
|---|---|---|---|
| total tokens | 134,021 | wall | 1,493.4 s |
| cost | — | request id | — |
config.json580 B
{
"N": 2,
"auth": {
"credential_ref": "WCB_NONE",
"method": "oauth-login"
},
"billing": "plan",
"config_id": "claude-fable-5-1-eff-max--cli--claude-code--dev",
"effort": "max",
"family": "claude",
"m": {
"min": 2,
"q": 2
},
"model_id": "claude-fable-5-1",
"protocol": "cli",
"served_model": "claude-fable-5-1",
"telemetry": {
"completion_tokens": 461318,
"cost_usd": null,
"prompt_tokens": 2040,
"total_tokens": 463358,
"wall_ms": 5083160
},
"transport": "claude-code-cli-headless",
"vendor_sanction_ref": null
}send-log2.5 KB
{
"N": 2,
"batch_id": "2026-09-02--cc-fable51-s0",
"config_id": "claude-fable-5-1-eff-max--cli--claude-code--dev",
"positions": [
{
"attempts": [
{
"attempt_index": 0,
"backoff_ms": null,
"charged": true,
"ended_at": "2026-09-02T01:49:15.093889+00:00",
"http_status": null,
"outcome": "valid",
"reached_model": true,
"reason": "ok",
"request_id": null,
"started_at": "2026-09-02T01:29:37.048706+00:00"
}
],
"block_index": 0,
"model_reaching_attempt_index": 0,
"slot_index": 0,
"terminal_state": "valid",
"variant": "P-min"
},
{
"attempts": [
{
"attempt_index": 0,
"backoff_ms": null,
"charged": true,
"ended_at": "2026-09-02T02:14:12.815269+00:00",
"http_status": null,
"outcome": "valid",
"reached_model": true,
"reason": "ok",
"request_id": null,
"started_at": "2026-09-02T01:49:19.353701+00:00"
}
],
"block_index": 0,
"model_reaching_attempt_index": 0,
"slot_index": 0,
"terminal_state": "valid",
"variant": "P-q"
},
{
"attempts": [
{
"attempt_index": 0,
"backoff_ms": null,
"charged": true,
"ended_at": "2026-09-02T02:18:24.452847+00:00",
"http_status": null,
"outcome": "model-failed",
"reached_model": true,
"reason": "no-valid-html",
"request_id": null,
"started_at": "2026-09-02T02:14:15.613763+00:00"
}
],
"block_index": 1,
"model_reaching_attempt_index": 0,
"slot_index": 1,
"terminal_state": "model-failed",
"variant": "P-q"
},
{
"attempts": [
{
"attempt_index": 0,
"backoff_ms": null,
"charged": true,
"ended_at": "2026-09-02T02:18:26.592321+00:00",
"http_status": null,
"outcome": "model-failed",
"reached_model": true,
"reason": "no-valid-html",
"request_id": null,
"started_at": "2026-09-02T02:18:24.453868+00:00"
}
],
"block_index": 1,
"model_reaching_attempt_index": 0,
"slot_index": 1,
"terminal_state": "model-failed",
"variant": "P-min"
}
],
"retry_policy": {
"max_unreachable_retries": 5,
"rate_limit_backoff": {
"max_attempts": 4,
"max_total_ms": 60000
}
}
}