kimi-k2.5 · go

go
config
kimi-k2.5--opencode-go--dev
batch
2026-07-19--unified
transport
opencode-go-gateway
protocol
api · plan
served model
kimi-k2.5
captured
Jul 18, 2026, 11:54 AM · N=2
variant
live
slot 0 · P-q · sandboxed iframe
kimi-k2.5 · go 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 neighbors

within session 2026-07-19--unified

similarity measures convergence within this session — not quality.

Fidelity — does the card show the right data?

  • namematch
  • conditionmatch
  • datematch
  • max tempambiguousinsufficient-distinct-temperature-nodes
  • min tempambiguousinsufficient-distinct-temperature-nodes
  • hourlyambiguouscoverage · 0 match · 0 mismatch · 24 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=1280, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <title>Weather Card</title>
  <style>
    * { 
      margin: 0; 
      padding: 0; 
      box-sizing: border-box; 
    }

    html, body {
      width: 1280px;
      height: 800px;
      overflow: hidden;
      font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    }

    body {
      background: #070B14;
      display: flex;
      align-items: center;
      justify-content: center;
      position: relative;
    }

    /* ── Animated deep ocean background ── */
    .bg-orb {
      position: absolute;
      border-radius: 50%;
      filter: blur(120px);
      opacity: 0.35;
      pointer-events: none;
      animation: orbFloat 20s ease-in-out infinite;
    }

    .bg-orb.orb-1 {
      width: 700px; 
      height: 700px;
      background: radial-gradient(circle, #1a5f9e 0%, transparent 70%);
      top: -150px; 
      left: -100px;
      animation-duration: 24s;
    }

    .bg-orb.orb-2 {
      width: 600px; 
      height: 600px;
      background: radial-gradient(circle, #0d8b6d 0%, transparent 70%);
      bottom: -100px; 
      right: -80px;
      animation-duration: 28s;
      animation-delay: -8s;
    }

    .bg-orb.orb-3 {
      width: 500px; 
      height: 500px;
      background: radial-gradient(circle, #2e3a87 0%, transparent 70%);
      top: 50%; 
      left: 50%;
      transform: translate(-50%, -50%);
      animation-duration: 32s;
      animation-delay: -15s;
      opacity: 0.2;
    }

    .bg-mesh {
      position: absolute;
      inset: 0;
      background:
        radial-gradient(ellipse at 20% 30%, rgba(26,95,158,0.12) 0%, transparent 50%),
        radial-gradient(ellipse at 80% 70%, rgba(13,139,109,0.08) 0%, transparent 50%),
        radial-gradient(ellipse at 50% 50%, rgba(46,58,135,0.06) 0%, transparent 60%);
      pointer-events: none;
    }

    .grain {
      position: absolute;
      inset: 0;
      opacity: 0.03;
      pointer-events: none;
      background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
      background-repeat: repeat;
      background-size: 256px 256px;
    }

    @keyframes orbFloat {
      0%, 100% { transform: translate(0, 0) scale(1); }
      25% { transform: translate(40px, -30px) scale(1.05); }
      50% { transform: translate(-20px, 20px) scale(0.95); }
      75% { transform: translate(30px, 40px) scale(1.02); }
    }

    /* ── Glass card ── */
    .card-container {
      position: relative;
      z-index: 10;
      width: 720px;
      height: 520px;
      background: rgba(16, 24, 45, 0.55);
      backdrop-filter: blur(40px) saturate(1.4);
      -webkit-backdrop-filter: blur(40px) saturate(1.4);
      border: 1px solid rgba(255, 255, 255, 0.08);
      border-radius: 32px;
      box-shadow:
        0 32px 80px rgba(0, 0, 0, 0.5),
        0 0 0 1px rgba(255, 255, 255, 0.04) inset,
        0 1px 0 rgba(255, 255, 255, 0.08) inset;
      display: flex;
      flex-direction: column;
      overflow: hidden;
    }

    .card-shine {
      position: absolute;
      top: 0; 
      left: 0; 
      right: 0;
      height: 50%;
      background: linear-gradient(180deg, rgba(255,255,255,0.04) 0%, transparent 100%);
      border-radius: 32px 32px 0 0;
      pointer-events: none;
    }

    /* ── Loading ── */
    .loading-overlay {
      position: absolute;
      inset: 0;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      z-index: 100;
      background: rgba(16, 24, 45, 0.8);
      backdrop-filter: blur(20px);
      border-radius: 32px;
      transition: opacity 0.6s ease;
    }

    .loading-overlay.hidden {
      opacity: 0;
      pointer-events: none;
    }

    .spinner-ring {
      width: 56px;
      height: 56px;
      border: 2.5px solid rgba(255, 255, 255, 0.06);
      border-top-color: rgba(100, 180, 255, 0.9);
      border-right-color: rgba(100, 180, 255, 0.4);
      border-radius: 50%;
      animation: spin 1.2s cubic-bezier(0.4, 0.15, 0.6, 0.85) infinite;
      position: relative;
    }

    .spinner-ring::after {
      content: '';
      position: absolute;
      inset: -6px;
      border-radius: 50%;
      border: 1px solid rgba(100, 180, 255, 0.1);
    }

    @keyframes spin { 
      to { transform: rotate(360deg); } 
    }

    .loading-text {
      margin-top: 20px;
      color: rgba(255,255,255,0.45);
      font-size: 13px;
      font-weight: 400;
      letter-spacing: 0.08em;
      text-transform: uppercase;
    }

    /* ── Error ── */
    .error-state {
      position: absolute;
      inset: 0;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      z-index: 90;
      padding: 60px;
      text-align: center;
      display: none;
    }

    .error-state.active { 
      display: flex; 
    }

    .error-icon {
      width: 64px;
      height: 64px;
      margin-bottom: 24px;
      opacity: 0.5;
    }

    .error-state h2 {
      color: rgba(255, 255, 255, 0.8);
      font-size: 20px;
      font-weight: 500;
      margin-bottom: 10px;
    }

    .error-state p {
      color: rgba(255, 255, 255, 0.35);
      font-size: 14px;
      line-height: 1.6;
    }

    /* ── Card header ── */
    .card-header {
      padding: 32px 36px 0;
      display: flex;
      justify-content: space-between;
      align-items: flex-start;
    }

    .location-block { 
      flex: 1; 
    }

    .location-name {
      color: #fff;
      font-size: 28px;
      font-weight: 600;
      letter-spacing: -0.02em;
      line-height: 1.2;
      text-shadow: 0 2px 12px rgba(0,0,0,0.3);
    }

    .date-display {
      color: rgba(255,255,255,0.45);
      font-size: 14px;
      font-weight: 400;
      margin-top: 5px;
      letter-spacing: 0.02em;
    }

    .weather-icon-wrap {
      width: 80px;
      height: 80px;
      display: flex;
      align-items: center;
      justify-content: center;
      position: relative;
    }

    .weather-icon-wrap svg {
      width: 64px;
      height: 64px;
      filter: drop-shadow(0 4px 16px rgba(0,0,0,0.3));
    }

    /* ── Hero temperature ── */
    .hero-section {
      padding: 16px 36px 0;
      display: flex;
      align-items: flex-end;
      gap: 16px;
    }

    .temp-main {
      color: #fff;
      font-size: 96px;
      font-weight: 200;
      line-height: 0.9;
      letter-spacing: -0.04em;
      font-variant-numeric: tabular-nums;
      text-shadow: 0 4px 24px rgba(0,0,0,0.25);
    }

    .temp-main sup {
      font-size: 32px;
      font-weight: 300;
      color: rgba(255,255,255,0.5);
      vertical-align: super;
      margin-left: 2px;
    }

    .temp-side {
      display: flex;
      flex-direction: column;
      gap: 6px;
      padding-bottom: 18px;
    }

    .condition-text {
      color: rgba(255,255,255,0.85);
      font-size: 18px;
      font-weight: 500;
      letter-spacing: -0.01em;
      text-shadow: 0 2px 8px rgba(0,0,0,0.2);
    }

    .temp-range {
      color: rgba(255,255,255,0.45);
      font-size: 13px;
      font-weight: 400;
      letter-spacing: 0.01em;
    }

    .temp-range .hi { 
      color: rgba(255, 160, 122, 0.85); 
    }

    .temp-range .lo { 
      color: rgba(135, 206, 250, 0.85); 
    }

    /* ── Divider ── */
    .divider {
      height: 1px;
      margin: 28px 36px 0;
      background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.1) 30%, rgba(255,255,255,0.1) 70%, transparent 100%);
      position: relative;
    }

    .divider::after {
      content: '';
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      width: 6px;
      height: 6px;
      background: rgba(255,255,255,0.15);
      border-radius: 50%;
    }

    /* ── Chart section ── */
    .chart-section {
      flex: 1;
      padding: 20px 36px 24px;
      display: flex;
      flex-direction: column;
      min-height: 0;
    }

    .chart-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 14px;
    }

    .chart-title {
      color: rgba(255,255,255,0.35);
      font-size: 11px;
      font-weight: 500;
      text-transform: uppercase;
      letter-spacing: 0.14em;
    }

    .chart-legend {
      display: flex;
      align-items: center;
      gap: 6px;
    }

    .legend-dot {
      width: 6px;
      height: 6px;
      border-radius: 50%;
      background: rgba(100, 200, 255, 0.8);
      box-shadow: 0 0 6px rgba(100, 200, 255, 0.4);
    }

    .legend-label {
      color: rgba(255,255,255,0.3);
      font-size: 11px;
      font-weight: 400;
    }

    .chart-wrap {
      flex: 1;
      position: relative;
      min-height: 0;
    }

    .chart-canvas {
      width: 100%;
      height: 100%;
      display: block;
    }

    /* ── Hourly strip ── */
    .hourly-strip {
      padding: 0 36px 24px;
      display: flex;
      justify-content: space-between;
      align-items: flex-start;
    }

    .hour-item {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 6px;
    }

    .hour-label {
      color: rgba(255,255,255,0.35);
      font-size: 11px;
      font-weight: 500;
      font-variant-numeric: tabular-nums;
      letter-spacing: 0.02em;
    }

    .hour-temp {
      color: rgba(255,255,255,0.6);
      font-size: 13px;
      font-weight: 500;
      font-variant-numeric: tabular-nums;
    }

    /* ── Responsive ── */
    @media (max-width: 750px) {
      .card-container {
        width: calc(100vw - 40px);
        height: auto;
        min-height: 480px;
      }
      .temp-main { 
        font-size: 72px; 
      }
      .location-name { 
        font-size: 22px; 
      }
    }
  </style>
</head>
<body>

  <div class="bg-orb orb-1"></div>
  <div class="bg-orb orb-2"></div>
  <div class="bg-orb orb-3"></div>
  <div class="bg-mesh"></div>
  <div class="grain"></div>

  <div class="card-container" id="card">
    <div class="card-shine"></div>

    <div class="loading-overlay" id="loading">
      <div class="spinner-ring"></div>
      <div class="loading-text">Fetching weather data</div>
    </div>

    <div class="error-state" id="error">
      <svg class="error-icon" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.5)" stroke-width="1.5">
        <circle cx="12" cy="12" r="10"/>
        <path d="M12 8v4M12 16h.01"/>
      </svg>
      <h2>Unable to load weather data</h2>
      <p>We couldn't retrieve the forecast for this location.<br>Please check your connection and try again.</p>
    </div>

    <div class="card-header" id="header">
      <div class="location-block">
        <div class="location-name" id="locName">--</div>
        <div class="date-display" id="dateDisp">--</div>
      </div>
      <div class="weather-icon-wrap" id="weatherIcon"></div>
    </div>

    <div class="hero-section">
      <div class="temp-main" id="avgTemp">--<sup>&deg;C</sup></div>
      <div class="temp-side">
        <div class="condition-text" id="condition">--</div>
        <div class="temp-range">
          <span class="hi" id="maxTemp">--</span> &nbsp;/&nbsp; <span class="lo" id="minTemp">--</span>
        </div>
      </div>
    </div>

    <div class="divider"></div>

    <div class="chart-section">
      <div class="chart-header">
        <div class="chart-title">Hourly Temperature</div>
        <div class="chart-legend">
          <div class="legend-dot"></div>
          <div class="legend-label">Temperature &deg;C</div>
        </div>
      </div>
      <div class="chart-wrap">
        <canvas class="chart-canvas" id="chartCanvas"></canvas>
      </div>
    </div>

    <div class="hourly-strip" id="hourlyStrip"></div>
  </div>

  <script>
    (function() {
      // ── Helpers ──
      function getParam(name, fallback) {
        const params = new URLSearchParams(window.location.search);
        const v = params.get(name);
        return (v !== null && v !== '') ? v : fallback;
      }

      function fmtDate(d) {
        return d.getFullYear() + '-' +
               String(d.getMonth()+1).padStart(2,'0') + '-' +
               String(d.getDate()).padStart(2,'0');
      }

      function fmtDisplayDate(iso) {
        const d = new Date(iso + 'T00:00:00Z');
        const opts = { weekday:'long', year:'numeric', month:'long', day:'numeric', timeZone:'UTC' };
        return d.toLocaleDateString('en-US', opts);
      }

      function isPastDate(iso) {
        const today = fmtDate(new Date());
        return iso < today;
      }

      // ── Weather icon SVGs ──
      const wmoIcons = {
        0:  '<svg viewBox="0 0 64 64" fill="none"><circle cx="32" cy="32" r="14" fill="url(#sunGrad)" filter="url(#glow)"/><defs><radialGradient id="sunGrad" cx="50%" cy="50%"><stop offset="0%" stop-color="#FFE082"/><stop offset="100%" stop-color="#FFB300"/></radialGradient><filter id="glow"><feGaussianBlur stdDeviation="4" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter></svg>',
        1:  '<svg viewBox="0 0 64 64" fill="none"><circle cx="32" cy="32" r="12" fill="url(#sunGrad)"/><ellipse cx="38" cy="36" rx="10" ry="6" fill="rgba(255,255,255,0.35)"/><defs><radialGradient id="sunGrad" cx="50%" cy="50%"><stop offset="0%" stop-color="#FFE082"/><stop offset="100%" stop-color="#FFB300"/></radialGradient></defs></svg>',
        2:  '<svg viewBox="0 0 64 64" fill="none"><circle cx="28" cy="28" r="10" fill="url(#sunGrad)"/><ellipse cx="36" cy="34" rx="14" ry="8" fill="rgba(255,255,255,0.5)"/><ellipse cx="38" cy="38" rx="12" ry="7" fill="rgba(255,255,255,0.3)"/><defs><radialGradient id="sunGrad" cx="50%" cy="50%"><stop offset="0%" stop-color="#FFE082"/><stop offset="100%" stop-color="#FFB300"/></radialGradient></defs></svg>',
        3:  '<svg viewBox="0 0 64 64" fill="none"><ellipse cx="32" cy="32" rx="18" ry="10" fill="rgba(255,255,255,0.6)"/><ellipse cx="28" cy="36" rx="14" ry="8" fill="rgba(255,255,255,0.4)"/><ellipse cx="36" cy="30" rx="12" ry="7" fill="rgba(255,255,255,0.5)"/></svg>',
        45: '<svg viewBox="0 0 64 64" fill="none"><path d="M20 36 Q24 28, 32 32 Q40 36, 44 28" stroke="rgba(200,200,200,0.6)" stroke-width="2" fill="none" stroke-linecap="round"/><path d="M18 42 Q22 34, 30 38 Q38 42, 42 34" stroke="rgba(180,180,180,0.4)" stroke-width="2" fill="none" stroke-linecap="round"/></svg>',
        48: '<svg viewBox="0 0 64 64" fill="none"><path d="M20 36 Q24 28, 32 32 Q40 36, 44 28" stroke="rgba(200,200,200,0.6)" stroke-width="2" fill="none" stroke-linecap="round"/><circle cx="24" cy="44" r="1.5" fill="rgba(200,200,200,0.5)"/><circle cx="32" cy="48" r="1.5" fill="rgba(200,200,200,0.5)"/><circle cx="40" cy="44" r="1.5" fill="rgba(200,200,200,0.5)"/></svg>',
        51: '<svg viewBox="0 0 64 64" fill="none"><ellipse cx="32" cy="28" rx="16" ry="8" fill="rgba(200,210,230,0.5)"/><path d="M24 40 L22 46M32 40 L30 46M40 40 L38 46" stroke="rgba(150,180,220,0.7)" stroke-width="2" stroke-linecap="round"/></svg>',
        53: '<svg viewBox="0 0 64 64" fill="none"><ellipse cx="32" cy="28" rx="16" ry="8" fill="rgba(180,190,210,0.6)"/><path d="M22 40 L20 48M30 40 L28 48M38 40 L36 48M46 40 L44 48" stroke="rgba(130,160,200,0.8)" stroke-width="2" stroke-linecap="round"/></svg>',
        55: '<svg viewBox="0 0 64 64" fill="none"><ellipse cx="32" cy="28" rx="16" ry="8" fill="rgba(160,170,190,0.7)"/><path d="M20 40 L18 48M28 40 L26 48M36 40 L34 48M44 40 L42 48" stroke="rgba(110,140,180,0.9)" stroke-width="2.5" stroke-linecap="round"/></svg>',
        61: '<svg viewBox="0 0 64 64" fill="none"><ellipse cx="32" cy="26" rx="16" ry="8" fill="rgba(180,190,210,0.6)"/><path d="M24 38 L22 48M32 38 L30 48M40 38 L38 48" stroke="rgba(100,160,255,0.8)" stroke-width="2" stroke-linecap="round"/></svg>',
        63: '<svg viewBox="0 0 64 64" fill="none"><ellipse cx="32" cy="26" rx="16" ry="8" fill="rgba(160,170,190,0.7)"/><path d="M22 38 L20 48M30 38 L28 48M38 38 L36 48M46 38 L44 48" stroke="rgba(90,150,245,0.85)" stroke-width="2" stroke-linecap="round"/></svg>',
        65: '<svg viewBox="0 0 64 64" fill="none"><ellipse cx="32" cy="26" rx="16" ry="8" fill="rgba(140,150,170,0.8)"/><path d="M20 38 L18 48M28 38 L26 48M36 38 L34 48M44 38 L42 48" stroke="rgba(80,140,235,0.9)" stroke-width="2.5" stroke-linecap="round"/></svg>',
        71: '<svg viewBox="0 0 64 64" fill="none"><ellipse cx="32" cy="26" rx="16" ry="8" fill="rgba(200,210,230,0.6)"/><text x="22" y="48" fill="rgba(220,230,255,0.8)" font-size="14" font-family="sans-serif">*</text><text x="30" y="52" fill="rgba(220,230,255,0.8)" font-size="14" font-family="sans-serif">*</text><text x="38" y="48" fill="rgba(220,230,255,0.8)" font-size="14" font-family="sans-serif">*</text></svg>',
        73: '<svg viewBox="0 0 64 64" fill="none"><ellipse cx="32" cy="26" rx="16" ry="8" fill="rgba(190,200,220,0.7)"/><text x="18" y="48" fill="rgba(210,220,250,0.85)" font-size="16" font-family="sans-serif">*</text><text x="26" y="52" fill="rgba(210,220,250,0.85)" font-size="16" font-family="sans-serif">*</text><text x="34" y="48" fill="rgba(210,220,250,0.85)" font-size="16" font-family="sans-serif">*</text><text x="42" y="52" fill="rgba(210,220,250,0.85)" font-size="16" font-family="sans-serif">*</text></svg>',
        75: '<svg viewBox="0 0 64 64" fill="none"><ellipse cx="32" cy="26" rx="16" ry="8" fill="rgba(170,180,200,0.8)"/><text x="16" y="48" fill="rgba(200,210,240,0.9)" font-size="18" font-family="sans-serif">*</text><text x="24" y="54" fill="rgba(200,210,240,0.9)" font-size="18" font-family="sans-serif">*</text><text x="32" y="48" fill="rgba(200,210,240,0.9)" font-size="18" font-family="sans-serif">*</text><text x="40" y="54" fill="rgba(200,210,240,0.9)" font-size="18" font-family="sans-serif">*</text><text x="48" y="48" fill="rgba(200,210,240,0.9)" font-size="18" font-family="sans-serif">*</text></svg>',
        95: '<svg viewBox="0 0 64 64" fill="none"><ellipse cx="32" cy="26" rx="16" ry="8" fill="rgba(140,150,170,0.7)"/><path d="M28 36 L32 44 L30 44 L34 52" stroke="rgba(255,220,100,0.9)" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>',
        96: '<svg viewBox="0 0 64 64" fill="none"><ellipse cx="32" cy="26" rx="16" ry="8" fill="rgba(130,140,160,0.7)"/><path d="M28 36 L32 44 L30 44 L34 52" stroke="rgba(255,220,100,0.9)" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/><circle cx="24" cy="48" r="1.5" fill="rgba(100,160,255,0.8)"/><circle cx="40" cy="50" r="1.5" fill="rgba(100,160,255,0.8)"/></svg>',
        99: '<svg viewBox="0 0 64 64" fill="none"><ellipse cx="32" cy="26" rx="16" ry="8" fill="rgba(120,130,150,0.8)"/><path d="M26 36 L30 44 L28 44 L32 52" stroke="rgba(255,210,80,0.95)" stroke-width="3" fill="none" stroke-linecap="round" stroke-linejoin="round"/><circle cx="20" cy="48" r="2" fill="rgba(90,150,245,0.85)"/><circle cx="44" cy="50" r="2" fill="rgba(90,150,245,0.85)"/></svg>',
      };

      function getWmoIcon(code) {
        if (wmoIcons[code]) return wmoIcons[code];
        if (code >= 80 && code <= 82) return wmoIcons[61] || wmoIcons[51];
        if (code >= 85 && code <= 86) return wmoIcons[71] || wmoIcons[51];
        if (code >= 95 && code <= 99) return wmoIcons[95];
        return wmoIcons[3];
      }

      const wmoLabels = {
        0:'Clear sky', 1:'Mainly clear', 2:'Partly cloudy', 3:'Overcast',
        45:'Fog', 48:'Depositing rime fog',
        51:'Light drizzle', 53:'Moderate drizzle', 55:'Dense drizzle',
        61:'Slight rain', 63:'Moderate rain', 65:'Heavy rain',
        71:'Slight snow', 73:'Moderate snow', 75:'Heavy snow',
        80:'Slight rain showers', 81:'Moderate rain showers', 82:'Violent rain showers',
        85:'Slight snow showers', 86:'Heavy snow showers',
        95:'Thunderstorm', 96:'Thunderstorm with hail', 99:'Thunderstorm with heavy hail',
      };

      function getWmoLabel(code) {
        return wmoLabels[code] || 'Unknown';
      }

      // ── Canvas chart ──
      function drawChart(hourlyData, min, max) {
        const canvas = document.getElementById('chartCanvas');
        const rect = canvas.getBoundingClientRect();
        const dpr = window.devicePixelRatio || 1;
        canvas.width = rect.width * dpr;
        canvas.height = rect.height * dpr;
        const ctx = canvas.getContext('2d');
        ctx.scale(dpr, dpr);

        const W = rect.width, H = rect.height;
        const padL = 8, padR = 8, padT = 24, padB = 8;

        ctx.clearRect(0, 0, W, H);

        const range = max - min || 1;
        const n = hourlyData.length;

        function x(i) { 
          return padL + (W - padL - padR) * (i / (n - 1 || 1)); 
        }

        function y(t) { 
          return padT + (H - padT - padB) * (1 - (t - min) / range); 
        }

        // Gradient fill
        const grad = ctx.createLinearGradient(0, padT, 0, H - padB);
        grad.addColorStop(0, 'rgba(100, 200, 255, 0.25)');
        grad.addColorStop(0.5, 'rgba(100, 200, 255, 0.08)');
        grad.addColorStop(1, 'rgba(100, 200, 255, 0.0)');

        // Build smooth path
        const points = hourlyData.map((d, i) => ({ x: x(i), y: y(d.t) }));

        ctx.beginPath();
        ctx.moveTo(points[0].x, H - padB);
        for (let i = 0; i < points.length; i++) {
          ctx.lineTo(points[i].x, points[i].y);
        }
        ctx.lineTo(points[points.length - 1].x, H - padB);
        ctx.closePath();
        ctx.fillStyle = grad;
        ctx.fill();

        // Glow line
        ctx.beginPath();
        ctx.moveTo(points[0].x, points[0].y);
        for (let i = 1; i < points.length; i++) {
          const prev = points[i - 1], curr = points[i];
          const cpx = (prev.x + curr.x) / 2;
          ctx.quadraticCurveTo(prev.x, prev.y, cpx, (prev.y + curr.y) / 2);
        }
        ctx.lineTo(points[points.length - 1].x, points[points.length - 1].y);
        ctx.strokeStyle = 'rgba(100, 210, 255, 0.85)';
        ctx.lineWidth = 2.5;
        ctx.lineCap = 'round';
        ctx.lineJoin = 'round';
        ctx.shadowColor = 'rgba(100, 210, 255, 0.5)';
        ctx.shadowBlur = 12;
        ctx.stroke();
        ctx.shadowBlur = 0;

        // Dots with glow
        points.forEach((p, i) => {
          const isPeak = i === 0 || i === points.length - 1 || (i > 0 && i < points.length - 1 && p.y <= points[i-1].y && p.y <= points[i+1].y);
          const isValley = i > 0 && i < points.length - 1 && p.y >= points[i-1].y && p.y >= points[i+1].y;

          if (isPeak || isValley) {
            ctx.beginPath();
            ctx.arc(p.x, p.y, 5, 0, Math.PI * 2);
            ctx.fillStyle = 'rgba(100, 210, 255, 0.3)';
            ctx.fill();

            ctx.beginPath();
            ctx.arc(p.x, p.y, 3, 0, Math.PI * 2);
            ctx.fillStyle = '#64D2FF';
            ctx.shadowColor = 'rgba(100, 210, 255, 0.6)';
            ctx.shadowBlur = 8;
            ctx.fill();
            ctx.shadowBlur = 0;

            // Temp labels at peaks/valleys
            ctx.fillStyle = isPeak ? 'rgba(255,255,255,0.7)' : 'rgba(255,255,255,0.5)';
            ctx.font = '500 11px Inter, sans-serif';
            ctx.textAlign = 'center';
            ctx.fillText(Math.round(hourlyData[i].t) + '\xB0', p.x, p.y - (isPeak ? 14 : -20));
          }
        });

        // X-axis faint grid
        ctx.strokeStyle = 'rgba(255,255,255,0.03)';
        ctx.lineWidth = 1;
        for (let i = 0; i < n; i += 3) {
          ctx.beginPath();
          ctx.moveTo(x(i), padT);
          ctx.lineTo(x(i), H - padB);
          ctx.stroke();
        }
      }

      // ── Hourly strip ──
      function renderHourly(hourlyData) {
        const strip = document.getElementById('hourlyStrip');
        strip.innerHTML = '';
        const indices = [0, 3, 6, 9, 12, 15, 18, 21, 23];

        indices.forEach(i => {
          if (i >= hourlyData.length) return;
          const h = hourlyData[i];
          const el = document.createElement('div');
          el.className = 'hour-item';
          el.innerHTML =
            '<div class="hour-label">' + h.label + '</div>' +
            '<div class="hour-temp">' + Math.round(h.t) + '\xB0</div>';
          strip.appendChild(el);
        });
      }

      // ── Main ──
      async function init() {
        const lat  = getParam('lat',  '52.52');
        const lon  = getParam('lon',  '13.405');
        const name = getParam('name', 'Berlin');
        const date = getParam('date', fmtDate(new Date()));

        const locEl  = document.getElementById('locName');
        const dateEl = document.getElementById('dateDisp');
        const avgEl  = document.getElementById('avgTemp');
        const condEl = document.getElementById('condition');
        const maxEl  = document.getElementById('maxTemp');
        const minEl  = document.getElementById('minTemp');
        const iconEl = document.getElementById('weatherIcon');
        const loadEl = document.getElementById('loading');
        const errEl  = document.getElementById('error');

        locEl.textContent = name;
        dateEl.textContent = fmtDisplayDate(date);

        const endpoint = isPastDate(date) ? '/api/om/archive' : '/api/om/forecast';
        const url = endpoint +
          '?latitude=' + encodeURIComponent(lat) +
          '&longitude=' + encodeURIComponent(lon) +
          '&daily=temperature_2m_max,temperature_2m_min,weather_code' +
          '&hourly=temperature_2m' +
          '&timezone=UTC' +
          '&temperature_unit=celsius' +
          '&start_date=' + date +
          '&end_date=' + date;

        try {
          const res = await fetch(url);
          if (!res.ok) throw new Error('HTTP ' + res.status);
          const data = await res.json();

          // Validate structure
          if (!data.daily || !data.daily.time || !data.daily.time.length) {
            throw new Error('Missing daily data');
          }
          if (!data.hourly || !data.hourly.time || !data.hourly.time.length) {
            throw new Error('Missing hourly data');
          }

          const max  = data.daily.temperature_2m_max[0];
          const min  = data.daily.temperature_2m_min[0];
          const code = data.daily.weather_code[0];
          const avg  = ((max + min) / 2);

          // Filter hourly for the requested date
          const datePrefix = date + 'T';
          const hourly = [];
          for (let i = 0; i < data.hourly.time.length; i++) {
            if (data.hourly.time[i].startsWith(datePrefix)) {
              const h = parseInt(data.hourly.time[i].slice(11, 13), 10);
              const label = (h === 0 ? '12am' : h < 12 ? h + 'am' : h === 12 ? '12pm' : (h - 12) + 'pm');
              hourly.push({ t: data.hourly.temperature_2m[i], label });
            }
          }

          if (!hourly.length) throw new Error('No hourly data for date');

          // Compute chart range with padding
          const hTemps = hourly.map(h => h.t);
          const hMin = Math.min(...hTemps), hMax = Math.max(...hTemps);
          const cMin = hMin - 1, cMax = hMax + 1;

          avgEl.innerHTML  = Math.round(avg) + '<sup>&deg;C</sup>';
          condEl.textContent = getWmoLabel(code);
          maxEl.innerHTML  = 'H:&nbsp;' + Math.round(max) + '&deg;';
          minEl.innerHTML  = 'L:&nbsp;' + Math.round(min) + '&deg;';
          iconEl.innerHTML = getWmoIcon(code);

          drawChart(hourly, cMin, cMax);
          renderHourly(hourly);

          loadEl.classList.add('hidden');
        } catch (e) {
          console.error('Weather load error:', e);
          loadEl.classList.add('hidden');
          errEl.classList.add('active');
        }
      }

      init();
    })();
  </script>
</body>
</html>

L1 — cheap deterministic descriptors

bytes total28,123html / css / js2,227 / 9,939 / 15,957
dom nodes48dom depth7
css rules60js presentyes
brightness24.1contrast11.8
colorfulness17.6whitespace96.6%

This slot's telemetry

prompt tokens313completion tokens9,825
total tokens10,138wall56.2 s
costrequest id
config.json543 B
{
  "N": 2,
  "auth": {
    "credential_ref": "WCB_OPENCODE_KEY",
    "method": "oauth-login"
  },
  "billing": "plan",
  "config_id": "kimi-k2.5--opencode-go--dev",
  "effort": null,
  "family": "kimi",
  "m": {
    "min": 2,
    "q": 2
  },
  "model_id": "kimi-k2.5",
  "protocol": "api",
  "served_model": "kimi-k2.5",
  "telemetry": {
    "completion_tokens": 53519,
    "cost_usd": null,
    "prompt_tokens": 1236,
    "total_tokens": 54755,
    "wall_ms": 328614
  },
  "transport": "opencode-go-gateway",
  "vendor_sanction_ref": null
}
send-log2.4 KB
{
  "N": 2,
  "batch_id": "2026-07-18--dev-go-extra",
  "config_id": "kimi-k2.5--opencode-go--dev",
  "positions": [
    {
      "attempts": [
        {
          "attempt_index": 0,
          "backoff_ms": null,
          "charged": true,
          "ended_at": "2026-07-18T00:11:29.297828+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-18T00:10:33.099603+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-07-18T00:12:18.082759+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-18T00:11:35.472106+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-07-18T00:13:20.076716+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-18T00:12:22.512124+00:00"
        }
      ],
      "block_index": 1,
      "model_reaching_attempt_index": 0,
      "slot_index": 1,
      "terminal_state": "valid",
      "variant": "P-min"
    },
    {
      "attempts": [
        {
          "attempt_index": 0,
          "backoff_ms": null,
          "charged": true,
          "ended_at": "2026-07-18T00:16:15.946285+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-18T00:13:23.699888+00:00"
        }
      ],
      "block_index": 1,
      "model_reaching_attempt_index": 0,
      "slot_index": 1,
      "terminal_state": "valid",
      "variant": "P-q"
    }
  ],
  "retry_policy": {
    "max_unreachable_retries": 5,
    "rate_limit_backoff": {
      "max_attempts": 4,
      "max_total_ms": 60000
    }
  }
}