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
claude-fable-5-1 @max · CC P-q slot 0 screenshot

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 total35,325html / css / js3,315 / 10,048 / 21,962
dom nodes72dom depth9
css rules87js presentyes
brightness245.3contrast14.7
colorfulness6.7whitespace98.2%

This slot's telemetry

prompt tokens680completion tokens133,341
total tokens134,021wall1,493.4 s
costrequest 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
    }
  }
}