kimi-k2.6 · go

go
config
kimi-k2.6--opencode-go--dev
batch
2026-07-19--unified
transport
opencode-go-gateway
protocol
api · plan
served model
kimi-k2.6
采集于
2026年7月18日 11:54 · N=2
variant
live
slot 0 · P-q · sandboxed iframe
kimi-k2.6 · go P-q slot 0 截图

截图是 similarity 通道测量时的原始像素。运行 live 会在 sandboxed iframe 中重新执行原始输出;渲染结果取决于你的浏览器和字体,不保证与测量像素一致。

similarity 邻居

限于 session 2026-07-19--unified

similarity 衡量本 session 内的趋同程度 —— 不是质量。

Fidelity —— 卡片展示的数据对不对?

  • namematch
  • conditionmatch
  • datematch
  • max tempnot-foundno-temperature-candidate
  • min tempnot-foundno-temperature-candidate
  • hourlynot-foundcoverage · 0 命中 · 0 不符 · 24 未展示
  • request sidematch0 处违规

extractor fidelity-extractor-v1+Berlin-2026-07-15

源码 —— 原始模型输出,一字节未改

下载 card.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=1280, initial-scale=1.0">
  <title>Weather Card</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    :root {
      --bg-1: #e8f4f8;
      --bg-2: #dcecf2;
      --bg-3: #f0e8dc;
      --card-bg: rgba(255, 255, 255, 0.65);
      --card-border: rgba(255, 255, 255, 0.8);
      --card-shadow: 0 24px 80px rgba(0, 20, 60, 0.08), 0 8px 24px rgba(0, 20, 60, 0.04);
      --text-primary: #1a2a3a;
      --text-secondary: #5a7a8a;
      --text-muted: #8a9aa8;
      --accent: #4a9db8;
      --accent-warm: #c97a4a;
      --temp-max: #c95a4a;
      --temp-min: #4a9db8;
      --error-bg: #fff0f0;
      --error-text: #b04040;
      --error-border: #e8b0b0;
    }

    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Helvetica, Arial, sans-serif;
      background: linear-gradient(135deg, var(--bg-1) 0%, var(--bg-2) 50%, var(--bg-3) 100%);
      min-height: 800px;
      width: 1280px;
      overflow-x: hidden;
      display: flex;
      align-items: center;
      justify-content: center;
      color: var(--text-primary);
      position: relative;
    }

    body::before {
      content: '';
      position: absolute;
      top: -50%;
      left: -20%;
      width: 800px;
      height: 800px;
      background: radial-gradient(circle, rgba(74, 157, 184, 0.12) 0%, transparent 70%);
      border-radius: 50%;
      pointer-events: none;
    }

    body::after {
      content: '';
      position: absolute;
      bottom: -30%;
      right: -10%;
      width: 600px;
      height: 600px;
      background: radial-gradient(circle, rgba(201, 122, 74, 0.10) 0%, transparent 70%);
      border-radius: 50%;
      pointer-events: none;
    }

    .container {
      width: 100%;
      max-width: 960px;
      padding: 40px;
      z-index: 1;
    }

    .weather-card {
      background: var(--card-bg);
      backdrop-filter: blur(24px) saturate(1.4);
      -webkit-backdrop-filter: blur(24px) saturate(1.4);
      border: 1px solid var(--card-border);
      border-radius: 28px;
      box-shadow: var(--card-shadow);
      padding: 52px 56px 48px;
      position: relative;
      overflow: hidden;
      transition: transform 0.4s ease, box-shadow 0.4s ease;
    }

    .weather-card:hover {
      transform: translateY(-2px);
      box-shadow: 0 28px 88px rgba(0, 20, 60, 0.10), 0 10px 28px rgba(0, 20, 60, 0.05);
    }

    .weather-card::before {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      height: 4px;
      background: linear-gradient(90deg, var(--accent), var(--accent-warm), var(--accent));
      border-radius: 28px 28px 0 0;
      opacity: 0.7;
    }

    .header {
      display: flex;
      justify-content: space-between;
      align-items: flex-start;
      margin-bottom: 48px;
      position: relative;
      z-index: 1;
    }

    .location-block h1 {
      font-size: 38px;
      font-weight: 700;
      letter-spacing: -0.5px;
      color: var(--text-primary);
      margin-bottom: 6px;
      line-height: 1.1;
    }

    .location-block .date {
      font-size: 17px;
      color: var(--text-secondary);
      font-weight: 500;
      letter-spacing: 0.3px;
    }

    .condition-badge {
      display: flex;
      align-items: center;
      gap: 10px;
      padding: 10px 20px;
      background: rgba(74, 157, 184, 0.10);
      border-radius: 50px;
      border: 1px solid rgba(74, 157, 184, 0.15);
    }

    .condition-badge .weather-icon {
      width: 28px;
      height: 28px;
    }

    .condition-badge .condition-text {
      font-size: 15px;
      font-weight: 600;
      color: var(--accent);
      text-transform: capitalize;
    }

    .current-weather {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 52px;
      position: relative;
      z-index: 1;
    }

    .temp-main {
      display: flex;
      align-items: baseline;
      gap: 6px;
    }

    .temp-main .temp-value {
      font-size: 96px;
      font-weight: 200;
      letter-spacing: -4px;
      line-height: 1;
      color: var(--text-primary);
      position: relative;
    }

    .temp-main .temp-value::after {
      content: '°C';
      font-size: 28px;
      font-weight: 400;
      letter-spacing: 0;
      position: absolute;
      top: 14px;
      margin-left: 6px;
      color: var(--text-secondary);
    }

    .temp-extremes {
      display: flex;
      flex-direction: column;
      gap: 12px;
      align-items: flex-end;
    }

    .extreme-item {
      display: flex;
      align-items: center;
      gap: 10px;
      padding: 10px 18px;
      background: rgba(255, 255, 255, 0.5);
      border-radius: 14px;
      border: 1px solid rgba(255, 255, 255, 0.6);
      backdrop-filter: blur(8px);
    }

    .extreme-item .extreme-label {
      font-size: 12px;
      font-weight: 600;
      text-transform: uppercase;
      letter-spacing: 1.2px;
      color: var(--text-muted);
    }

    .extreme-item .extreme-value {
      font-size: 22px;
      font-weight: 600;
      color: var(--text-primary);
      min-width: 44px;
      text-align: right;
    }

    .extreme-item .extreme-unit {
      font-size: 14px;
      color: var(--text-muted);
      font-weight: 400;
    }

    .extreme-item.high .extreme-value {
      color: var(--temp-max);
    }

    .extreme-item.low .extreme-value {
      color: var(--temp-min);
    }

    .hourly-section {
      position: relative;
      z-index: 1;
    }

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

    .hourly-header h2 {
      font-size: 14px;
      font-weight: 700;
      text-transform: uppercase;
      letter-spacing: 1.8px;
      color: var(--text-muted);
    }

    .hourly-header .range-hint {
      font-size: 13px;
      color: var(--text-muted);
      font-weight: 500;
    }

    .chart-container {
      position: relative;
      height: 220px;
      width: 100%;
    }

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

    .loading {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      padding: 80px 40px;
      gap: 24px;
    }

    .loading .spinner {
      width: 44px;
      height: 44px;
      border: 3px solid rgba(74, 157, 184, 0.12);
      border-top-color: var(--accent);
      border-radius: 50%;
      animation: spin 0.9s cubic-bezier(0.4, 0, 0.2, 1) infinite;
    }

    .loading .loading-text {
      font-size: 15px;
      color: var(--text-secondary);
      font-weight: 500;
      letter-spacing: 0.3px;
    }

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

    .error {
      text-align: center;
      padding: 72px 40px;
    }

    .error-icon {
      width: 56px;
      height: 56px;
      margin: 0 auto 20px;
      background: var(--error-bg);
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      border: 1px solid var(--error-border);
    }

    .error-icon svg {
      width: 26px;
      height: 26px;
      stroke: var(--error-text);
    }

    .error h2 {
      font-size: 22px;
      font-weight: 600;
      color: var(--error-text);
      margin-bottom: 10px;
    }

    .error p {
      font-size: 15px;
      color: var(--text-muted);
      line-height: 1.5;
      max-width: 400px;
      margin: 0 auto;
    }

    .error-retry {
      margin-top: 24px;
      padding: 10px 28px;
      font-size: 14px;
      font-weight: 600;
      color: var(--accent);
      background: rgba(74, 157, 184, 0.08);
      border: 1.5px solid var(--accent);
      border-radius: 10px;
      cursor: pointer;
      transition: all 0.2s ease;
      font-family: inherit;
    }

    .error-retry:hover {
      background: var(--accent);
      color: #fff;
    }

    .hidden {
      display: none !important;
    }

    /* Weather icon styles */
    .weather-icon {
      display: inline-block;
      vertical-align: middle;
    }

    .weather-icon svg {
      width: 100%;
      height: 100%;
    }

    .condition-0, .condition-1, .condition-2, .condition-3 {
      --icon-color: #d4a030;
    }

    .condition-45, .condition-48 {
      --icon-color: #8a9aa8;
    }

    .condition-51, .condition-53, .condition-55,
    .condition-56, .condition-57, .condition-61,
    .condition-63, .condition-65, .condition-66,
    .condition-67, .condition-80, .condition-81,
    .condition-82 {
      --icon-color: #5a8db0;
    }

    .condition-71, .condition-73, .condition-75,
    .condition-77, .condition-85, .condition-86 {
      --icon-color: #7aa0c0;
    }

    .condition-95, .condition-96, .condition-99 {
      --icon-color: #8a70a0;
    }

    /* Decorative elements */
    .deco-circle {
      position: absolute;
      border-radius: 50%;
      pointer-events: none;
      z-index: 0;
    }

    .deco-1 {
      width: 300px;
      height: 300px;
      top: -100px;
      right: -60px;
      background: radial-gradient(circle, rgba(212, 160, 48, 0.06) 0%, transparent 70%);
    }

    .deco-2 {
      width: 180px;
      height: 180px;
      bottom: 40px;
      left: -40px;
      background: radial-gradient(circle, rgba(74, 157, 184, 0.05) 0%, transparent 70%);
    }

    .tooltip {
      position: absolute;
      background: rgba(26, 42, 58, 0.92);
      color: #fff;
      padding: 6px 12px;
      border-radius: 8px;
      font-size: 12px;
      font-weight: 600;
      pointer-events: none;
      white-space: nowrap;
      opacity: 0;
      transition: opacity 0.15s ease;
      z-index: 10;
      backdrop-filter: blur(4px);
    }

    .tooltip::after {
      content: '';
      position: absolute;
      bottom: -4px;
      left: 50%;
      transform: translateX(-50%);
      border-left: 4px solid transparent;
      border-right: 4px solid transparent;
      border-top: 4px solid rgba(26, 42, 58, 0.92);
    }

    .tooltip.visible {
      opacity: 1;
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="weather-card" id="card">
      <div class="deco-circle deco-1"></div>
      <div class="deco-circle deco-2"></div>

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

      <div id="error" class="error hidden">
        <div class="error-icon">
          <svg viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <circle cx="12" cy="12" r="10"/>
            <line x1="12" y1="8" x2="12" y2="12"/>
            <line x1="12" y1="16" x2="12.01" y2="16"/>
          </svg>
        </div>
        <h2>Unable to load weather</h2>
        <p id="error-message">The weather data could not be fetched. Please check your connection and try again.</p>
        <button class="error-retry" onclick="loadWeather()">Retry</button>
      </div>

      <div id="content" class="hidden">
        <div class="header">
          <div class="location-block">
            <h1 id="location-name">Berlin</h1>
            <div class="date" id="display-date">Today</div>
          </div>
          <div class="condition-badge" id="condition-badge">
            <div class="weather-icon" id="condition-icon"></div>
            <span class="condition-text" id="condition-text">Clear Sky</span>
          </div>
        </div>

        <div class="current-weather">
          <div class="temp-main">
            <div class="temp-value" id="current-temp">--</div>
          </div>
          <div class="temp-extremes">
            <div class="extreme-item high">
              <span class="extreme-label">High</span>
              <span class="extreme-value" id="max-temp">--</span>
              <span class="extreme-unit">°C</span>
            </div>
            <div class="extreme-item low">
              <span class="extreme-label">Low</span>
              <span class="extreme-value" id="min-temp">--</span>
              <span class="extreme-unit">°C</span>
            </div>
          </div>
        </div>

        <div class="hourly-section">
          <div class="hourly-header">
            <h2>Hourly Temperature</h2>
            <span class="range-hint" id="range-hint">00:00 — 23:00</span>
          </div>
          <div class="chart-container" id="chart-container">
            <canvas id="hourly-chart"></canvas>
            <div class="tooltip" id="tooltip"></div>
          </div>
        </div>
      </div>
    </div>
  </div>

  <script>
    (function() {
      'use strict';

      // --- Weather code mapping ---
      const WMO_CODES = {
        0: { label: 'Clear Sky', icon: 'sun' },
        1: { label: 'Mainly Clear', icon: 'sun-cloud' },
        2: { label: 'Partly Cloudy', icon: 'cloud-sun' },
        3: { label: 'Overcast', icon: 'cloud' },
        45: { label: 'Fog', icon: 'fog' },
        48: { label: 'Rime Fog', icon: 'fog' },
        51: { label: 'Light Drizzle', icon: 'rain-light' },
        53: { label: 'Drizzle', icon: 'rain-light' },
        55: { label: 'Heavy Drizzle', icon: 'rain' },
        56: { label: 'Freezing Drizzle', icon: 'rain-light' },
        57: { label: 'Freezing Drizzle', icon: 'rain' },
        61: { label: 'Light Rain', icon: 'rain-light' },
        63: { label: 'Rain', icon: 'rain' },
        65: { label: 'Heavy Rain', icon: 'rain-heavy' },
        66: { label: 'Freezing Rain', icon: 'rain-light' },
        67: { label: 'Freezing Rain', icon: 'rain' },
        71: { label: 'Light Snow', icon: 'snow-light' },
        73: { label: 'Snow', icon: 'snow' },
        75: { label: 'Heavy Snow', icon: 'snow-heavy' },
        77: { label: 'Snow Grains', icon: 'snow-light' },
        80: { label: 'Light Showers', icon: 'rain-light' },
        81: { label: 'Showers', icon: 'rain' },
        82: { label: 'Heavy Showers', icon: 'rain-heavy' },
        85: { label: 'Snow Showers', icon: 'snow-light' },
        86: { label: 'Snow Showers', icon: 'snow' },
        95: { label: 'Thunderstorm', icon: 'thunder' },
        96: { label: 'Thunderstorm & Hail', icon: 'thunder' },
        99: { label: 'Thunderstorm & Hail', icon: 'thunder' }
      };

      const ICONS = {
        sun: `<svg viewBox="0 0 64 64" fill="none">
          <circle cx="32" cy="32" r="14" fill="currentColor" opacity="0.9"/>
          <g stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.7">
            <line x1="32" y1="6" x2="32" y2="12"/>
            <line x1="32" y1="52" x2="32" y2="58"/>
            <line x1="6" y1="32" x2="12" y2="32"/>
            <line x1="52" y1="32" x2="58" y2="32"/>
            <line x1="13.6" y1="13.6" x2="17.8" y2="17.8"/>
            <line x1="46.2" y1="46.2" x2="50.4" y2="50.4"/>
            <line x1="13.6" y1="50.4" x2="17.8" y2="46.2"/>
            <line x1="46.2" y1="17.8" x2="50.4" y2="13.6"/>
          </g>
        </svg>`,
        'sun-cloud': `<svg viewBox="0 0 64 64" fill="none">
          <circle cx="24" cy="24" r="10" fill="currentColor" opacity="0.6"/>
          <path d="M20 44h28a12 12 0 0 0 0-24 11 11 0 0 0-20.5 4A10 10 0 0 0 20 44z" fill="currentColor" opacity="0.85"/>
        </svg>`,
        'cloud-sun': `<svg viewBox="0 0 64 64" fill="none">
          <circle cx="20" cy="20" r="8" fill="currentColor" opacity="0.4"/>
          <path d="M16 46h32a10 10 0 0 0 0-20 9 9 0 0 0-16.5 3A8 8 0 0 0 16 46z" fill="currentColor" opacity="0.8"/>
        </svg>`,
        cloud: `<svg viewBox="0 0 64 64" fill="none">
          <path d="M16 42h32a10 10 0 0 0 0-20 9 9 0 0 0-16.5 3A8 8 0 0 0 16 42z" fill="currentColor" opacity="0.8"/>
        </svg>`,
        fog: `<svg viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.6">
          <line x1="10" y1="24" x2="54" y2="24"/>
          <line x1="14" y1="34" x2="50" y2="34"/>
          <line x1="10" y1="44" x2="54" y2="44"/>
        </svg>`,
        'rain-light': `<svg viewBox="0 0 64 64" fill="none">
          <path d="M16 34h32a10 10 0 0 0 0-20 9 9 0 0 0-16.5 3A8 8 0 0 0 16 34z" fill="currentColor" opacity="0.7"/>
          <g stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.6">
            <line x1="24" y1="40" x2="22" y2="48"/>
            <line x1="32" y1="40" x2="30" y2="48"/>
            <line x1="40" y1="40" x2="38" y2="48"/>
          </g>
        </svg>`,
        rain: `<svg viewBox="0 0 64 64" fill="none">
          <path d="M16 32h32a10 10 0 0 0 0-20 9 9 0 0 0-16.5 3A8 8 0 0 0 16 32z" fill="currentColor" opacity="0.7"/>
          <g stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.7">
            <line x1="22" y1="38" x2="19" y2="48"/>
            <line x1="30" y1="38" x2="27" y2="48"/>
            <line x1="38" y1="38" x2="35" y2="48"/>
            <line x1="26" y1="42" x2="23" y2="52"/>
            <line x1="34" y1="42" x2="31" y2="52"/>
          </g>
        </svg>`,
        'rain-heavy': `<svg viewBox="0 0 64 64" fill="none">
          <path d="M14 30h36a11 11 0 0 0 0-22 10 10 0 0 0-18 3A9 9 0 0 0 14 30z" fill="currentColor" opacity="0.7"/>
          <g stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.8">
            <line x1="20" y1="36" x2="17" y2="48"/>
            <line x1="28" y1="36" x2="25" y2="48"/>
            <line x1="36" y1="36" x2="33" y2="48"/>
            <line x1="44" y1="36" x2="41" y2="48"/>
            <line x1="24" y1="40" x2="21" y2="52"/>
            <line x1="32" y1="40" x2="29" y2="52"/>
            <line x1="40" y1="40" x2="37" y2="52"/>
          </g>
        </svg>`,
        'snow-light': `<svg viewBox="0 0 64 64" fill="none">
          <path d="M16 34h32a10 10 0 0 0 0-20 9 9 0 0 0-16.5 3A8 8 0 0 0 16 34z" fill="currentColor" opacity="0.7"/>
          <g fill="currentColor" opacity="0.7">
            <circle cx="24" cy="44" r="2.5"/>
            <circle cx="32" cy="48" r="2.5"/>
            <circle cx="40" cy="44" r="2.5"/>
          </g>
        </svg>`,
        snow: `<svg viewBox="0 0 64 64" fill="none">
          <path d="M16 32h32a10 10 0 0 0 0-20 9 9 0 0 0-16.5 3A8 8 0 0 0 16 32z" fill="currentColor" opacity="0.7"/>
          <g fill="currentColor" opacity="0.7">
            <circle cx="22" cy="40" r="2.5"/>
            <circle cx="30" cy="44" r="2.5"/>
            <circle cx="38" cy="40" r="2.5"/>
            <circle cx="26" cy="50" r="2"/>
            <circle cx="34" cy="50" r="2"/>
          </g>
        </svg>`,
        'snow-heavy': `<svg viewBox="0 0 64 64" fill="none">
          <path d="M14 30h36a11 11 0 0 0 0-22 10 10 0 0 0-18 3A9 9 0 0 0 14 30z" fill="currentColor" opacity="0.7"/>
          <g fill="currentColor" opacity="0.7">
            <circle cx="20" cy="38" r="2.5"/>
            <circle cx="28" cy="42" r="2.5"/>
            <circle cx="36" cy="42" r="2.5"/>
            <circle cx="44" cy="38" r="2.5"/>
            <circle cx="24" cy="48" r="2"/>
            <circle cx="32" cy="52" r="2"/>
            <circle cx="40" cy="48" r="2"/>
          </g>
        </svg>`,
        thunder: `<svg viewBox="0 0 64 64" fill="none">
          <path d="M14 30h36a11 11 0 0 0 0-22 10 10 0 0 0-18 3A9 9 0 0 0 14 30z" fill="currentColor" opacity="0.6"/>
          <path d="M30 32l-4 14h6l-2 12 10-16h-6l4-10z" fill="currentColor" opacity="0.85"/>
        </svg>`
      };

      // --- URL params ---
      function getParams() {
        const p = new URLSearchParams(window.location.search);
        const today = new Date();
        const yyyy = today.getUTCFullYear();
        const mm = String(today.getUTCMonth() + 1).padStart(2, '0');
        const dd = String(today.getUTCDate()).padStart(2, '0');
        return {
          lat: parseFloat(p.get('lat')) || 52.52,
          lon: parseFloat(p.get('lon')) || 13.405,
          name: p.get('name') || 'Berlin',
          date: p.get('date') || `${yyyy}-${mm}-${dd}`
        };
      }

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

      function formatShortDate(dateStr) {
        const d = new Date(dateStr + 'T00:00:00Z');
        const opts = { month: 'short', day: 'numeric', timeZone: 'UTC' };
        return d.toLocaleDateString('en-US', opts);
      }

      // --- Determine which API to use ---
      function getApiEndpoint(dateStr) {
        const requested = new Date(dateStr + 'T00:00:00Z');
        const now = new Date();
        const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
        return requested < today ? '/api/om/archive' : '/api/om/forecast';
      }

      // --- Build API URL ---
      function buildApiUrl(endpoint, lat, lon, dateStr) {
        const base = endpoint;
        const params = new URLSearchParams({
          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: dateStr,
          end_date: dateStr
        });
        return `${base}?${params.toString()}`;
      }

      // --- DOM refs ---
      const els = {
        loading: document.getElementById('loading'),
        error: document.getElementById('error'),
        content: document.getElementById('content'),
        locationName: document.getElementById('location-name'),
        displayDate: document.getElementById('display-date'),
        conditionBadge: document.getElementById('condition-badge'),
        conditionIcon: document.getElementById('condition-icon'),
        conditionText: document.getElementById('condition-text'),
        currentTemp: document.getElementById('current-temp'),
        maxTemp: document.getElementById('max-temp'),
        minTemp: document.getElementById('min-temp'),
        rangeHint: document.getElementById('range-hint'),
        canvas: document.getElementById('hourly-chart'),
        tooltip: document.getElementById('tooltip'),
        errorMessage: document.getElementById('error-message')
      };

      // --- State ---
      let chartData = null;
      let canvasRect = null;

      // --- Main load function ---
      window.loadWeather = async function() {
        els.loading.classList.remove('hidden');
        els.error.classList.add('hidden');
        els.content.classList.add('hidden');

        const params = getParams();
        const endpoint = getApiEndpoint(params.date);
        const url = buildApiUrl(endpoint, params.lat, params.lon, params.date);

        try {
          const response = await fetch(url);
          if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
          }
          const data = await response.json();

          if (!data.daily || !data.daily.temperature_2m_max || !data.daily.temperature_2m_max.length) {
            throw new Error('No daily data available');
          }
          if (!data.hourly || !data.hourly.temperature_2m || !data.hourly.temperature_2m.length) {
            throw new Error('No hourly data available');
          }

          render(params, data);
        } catch (err) {
          showError(err.message || 'Failed to load weather data');
        }
      };

      function showError(msg) {
        els.loading.classList.add('hidden');
        els.error.classList.remove('hidden');
        els.content.classList.add('hidden');
        els.errorMessage.textContent = msg;
      }

      function render(params, data) {
        // Location & date
        els.locationName.textContent = params.name;
        els.displayDate.textContent = formatDate(params.date);

        // Daily values
        const maxTemp = data.daily.temperature_2m_max[0];
        const minTemp = data.daily.temperature_2m_min[0];
        const wmoCode = data.daily.weather_code[0];
        const wmoInfo = WMO_CODES[wmoCode] || { label: 'Unknown', icon: 'cloud' };

        // Current temp: use noon (12:00 UTC) if available, otherwise first hour
        const hourly = data.hourly;
        const hourlyTimes = hourly.time;
        const hourlyTemps = hourly.temperature_2m;

        let currentTemp = hourlyTemps[0];
        for (let i = 0; i < hourlyTimes.length; i++) {
          if (hourlyTimes[i].endsWith('12:00')) {
            currentTemp = hourlyTemps[i];
            break;
          }
        }

        els.currentTemp.textContent = Math.round(currentTemp);
        els.maxTemp.textContent = Math.round(maxTemp);
        els.minTemp.textContent = Math.round(minTemp);

        // Condition
        els.conditionText.textContent = wmoInfo.label;
        els.conditionIcon.innerHTML = ICONS[wmoInfo.icon] || ICONS.cloud;
        els.conditionBadge.className = `condition-badge condition-${wmoCode}`;

        // Hourly range hint
        if (hourlyTimes.length > 0) {
          const first = hourlyTimes[0].slice(11, 16);
          const last = hourlyTimes[hourlyTimes.length - 1].slice(11, 16);
          els.rangeHint.textContent = `${first} — ${last}`;
        }

        // Prepare chart data
        const chartPoints = [];
        for (let i = 0; i < hourlyTimes.length; i++) {
          const timeStr = hourlyTimes[i];
          const hour = parseInt(timeStr.slice(11, 13), 10);
          chartPoints.push({
            hour: hour,
            label: timeStr.slice(11, 16),
            temp: hourlyTemps[i]
          });
        }

        chartData = {
          points: chartPoints,
          min: Math.min(...hourlyTemps),
          max: Math.max(...hourlyTemps)
        };

        // Show content
        els.loading.classList.add('hidden');
        els.error.classList.add('hidden');
        els.content.classList.remove('hidden');

        // Draw chart
        requestAnimationFrame(() => {
          drawChart();
        });
      }

      // --- Canvas chart ---
      function drawChart() {
        const canvas = els.canvas;
        const container = document.getElementById('chart-container');
        const dpr = window.devicePixelRatio || 1;
        const width = container.clientWidth;
        const height = container.clientHeight;

        canvas.width = width * dpr;
        canvas.height = height * dpr;
        canvas.style.width = width + 'px';
        canvas.style.height = height + 'px';

        const ctx = canvas.getContext('2d');
        ctx.scale(dpr, dpr);

        if (!chartData || chartData.points.length === 0) return;

        const points = chartData.points;
        const padLeft = 36;
        const padRight = 24;
        const padTop = 32;
        const padBottom = 40;
        const chartW = width - padLeft - padRight;
        const chartH = height - padTop - padBottom;

        // Y range with padding
        const yMin = Math.floor(chartData.min - 2);
        const yMax = Math.ceil(chartData.max + 2);
        const yRange = yMax - yMin || 1;

        function xFor(i) {
          if (points.length === 1) return padLeft + chartW / 2;
          return padLeft + (i / (points.length - 1)) * chartW;
        }

        function yFor(temp) {
          return padTop + chartH - ((temp - yMin) / yRange) * chartH;
        }

        // Clear
        ctx.clearRect(0, 0, width, height);

        // Grid lines (horizontal)
        const gridSteps = 4;
        ctx.strokeStyle = 'rgba(138, 154, 168, 0.15)';
        ctx.lineWidth = 1;
        ctx.setLineDash([4, 4]);
        for (let s = 0; s <= gridSteps; s++) {
          const y = padTop + (s / gridSteps) * chartH;
          ctx.beginPath();
          ctx.moveTo(padLeft, y);
          ctx.lineTo(padLeft + chartW, y);
          ctx.stroke();
        }
        ctx.setLineDash([]);

        // Y-axis labels
        ctx.fillStyle = '#8a9aa8';
        ctx.font = '500 11px -apple-system, BlinkMacSystemFont, sans-serif';
        ctx.textAlign = 'right';
        ctx.textBaseline = 'middle';
        for (let s = 0; s <= gridSteps; s++) {
          const tempVal = yMax - (s / gridSteps) * yRange;
          const y = padTop + (s / gridSteps) * chartH;
          ctx.fillText(Math.round(tempVal) + '°', padLeft - 10, y);
        }

        // X-axis labels (show every 4th or 3rd label depending on density)
        const showEvery = points.length > 16 ? 4 : (points.length > 8 ? 3 : 2);
        ctx.textAlign = 'center';
        ctx.textBaseline = 'top';
        for (let i = 0; i < points.length; i += showEvery) {
          const x = xFor(i);
          const y = padTop + chartH + 12;
          ctx.fillText(points[i].label, x, y);
        }

        // Area fill under the curve
        const areaPath = new Path2D();
        areaPath.moveTo(xFor(0), padTop + chartH);
        for (let i = 0; i < points.length; i++) {
          areaPath.lineTo(xFor(i), yFor(points[i].temp));
        }
        areaPath.lineTo(xFor(points.length - 1), padTop + chartH);
        areaPath.closePath();

        const areaGrad = ctx.createLinearGradient(0, padTop, 0, padTop + chartH);
        areaGrad.addColorStop(0, 'rgba(74, 157, 184, 0.18)');
        areaGrad.addColorStop(1, 'rgba(74, 157, 184, 0.01)');
        ctx.fillStyle = areaGrad;
        ctx.fill(areaPath);

        // Smooth curve
        ctx.strokeStyle = 'rgba(74, 157, 184, 0.9)';
        ctx.lineWidth = 2.5;
        ctx.lineCap = 'round';
        ctx.lineJoin = 'round';

        // Catmull-Rom to cubic Bezier for smooth curves
        ctx.beginPath();
        if (points.length === 1) {
          ctx.moveTo(xFor(0), yFor(points[0].temp));
        } else {
          ctx.moveTo(xFor(0), yFor(points[0].temp));
          for (let i = 0; i < points.length - 1; i++) {
            const p0 = points[Math.max(0, i - 1)];
            const p1 = points[i];
            const p2 = points[i + 1];
            const p3 = points[Math.min(points.length - 1, i + 2)];

            const x0 = xFor(Math.max(0, i - 1));
            const x1 = xFor(i);
            const x2 = xFor(i + 1);
            const x3 = xFor(Math.min(points.length - 1, i + 2));

            const tension = 0.4;
            const cp1x = x1 + (x2 - x0) * tension / 3;
            const cp1y = yFor(p1.temp) + (yFor(p2.temp) - yFor(p0.temp)) * tension / 3;
            const cp2x = x2 - (x3 - x1) * tension / 3;
            const cp2y = yFor(p2.temp) - (yFor(p3.temp) - yFor(p1.temp)) * tension / 3;

            ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x2, yFor(p2.temp));
          }
        }
        ctx.stroke();

        // Data points
        for (let i = 0; i < points.length; i++) {
          const x = xFor(i);
          const y = yFor(points[i].temp);

          // Outer glow
          ctx.beginPath();
          ctx.arc(x, y, 6, 0, Math.PI * 2);
          ctx.fillStyle = 'rgba(74, 157, 184, 0.12)';
          ctx.fill();

          // Point
          ctx.beginPath();
          ctx.arc(x, y, 3.5, 0, Math.PI * 2);
          ctx.fillStyle = '#fff';
          ctx.fill();
          ctx.strokeStyle = 'rgba(74, 157, 184, 0.9)';
          ctx.lineWidth = 2;
          ctx.stroke();
        }

        // Store for tooltip
        canvasRect = canvas.getBoundingClientRect();
      }

      // --- Tooltip ---
      function updateTooltip(e) {
        if (!chartData) return;
        const canvas = els.canvas;
        const rect = canvas.getBoundingClientRect();
        const dpr = window.devicePixelRatio || 1;

        // Mouse X relative to canvas content
        const mouseX = (e.clientX - rect.left) * dpr;
        const mouseY = (e.clientY - rect.top) * dpr;

        const width = canvas.width;
        const height = canvas.height;
        const padLeft = 36 * dpr;
        const padRight = 24 * dpr;
        const padTop = 32 * dpr;
        const padBottom = 40 * dpr;
        const chartW = width - padLeft - padRight;
        const chartH = height - padTop - padBottom;

        // Find nearest point
        const points = chartData.points;
        let nearestIdx = 0;
        let nearestDist = Infinity;

        for (let i = 0; i < points.length; i++) {
          const px = padLeft + (i / (points.length - 1 || 1)) * chartW;
          const dist = Math.abs(mouseX - px);
          if (dist < nearestDist) {
            nearestDist = dist;
            nearestIdx = i;
          }
        }

        // Only show if close enough (within 30px)
        if (nearestDist > 30 * dpr) {
          els.tooltip.classList.remove('visible');
          return;
        }

        const point = points[nearestIdx];
        const px = padLeft + (nearestIdx / (points.length - 1 || 1)) * chartW;
        const yMin = Math.floor(chartData.min - 2);
        const yMax = Math.ceil(chartData.max + 2);
        const yRange = yMax - yMin || 1;
        const py = padTop + chartH - ((point.temp - yMin) / yRange) * chartH;

        // Convert back to CSS pixels for tooltip positioning
        const cssX = px / dpr;
        const cssY = py / dpr;

        els.tooltip.textContent = `${point.label}  •  ${Math.round(point.temp)}°C`;
        els.tooltip.style.left = cssX + 'px';
        els.tooltip.style.top = (cssY - 14) + 'px';
        els.tooltip.style.transform = 'translateX(-50%) translateY(-100%)';
        els.tooltip.classList.add('visible');
      }

      function hideTooltip() {
        els.tooltip.classList.remove('visible');
      }

      els.canvas.addEventListener('mousemove', updateTooltip);
      els.canvas.addEventListener('mouseleave', hideTooltip);
      els.canvas.addEventListener('touchstart', (e) => {
        e.preventDefault();
        updateTooltip(e.touches[0]);
      }, { passive: false });
      els.canvas.addEventListener('touchend', hideTooltip);

      // Redraw on resize
      let resizeTimeout;
      window.addEventListener('resize', () => {
        clearTimeout(resizeTimeout);
        resizeTimeout = setTimeout(() => {
          if (chartData) drawChart();
        }, 100);
      });

      // Init
      loadWeather();
    })();
  </script>
</body>
</html>

L1 —— 廉价的确定性描述量

bytes 总计34,260html / css / js2,756 / 9,974 / 21,530
dom 节点51dom 深度9
css 规则58含 js
brightness237.2contrast12.3
colorfulness13.1留白98.7%

该 slot 的 telemetry

prompt tokens313completion tokens11,182
total tokens11,495wall53.9 s
costrequest id
config.json537 B
{
  "N": 2,
  "auth": {
    "credential_ref": "WCB_OPENCODE_KEY",
    "method": "oauth"
  },
  "billing": "plan",
  "config_id": "kimi-k2.6--opencode-go--dev",
  "effort": null,
  "family": "kimi",
  "m": {
    "min": 2,
    "q": 2
  },
  "model_id": "kimi-k2.6",
  "protocol": "api",
  "served_model": "kimi-k2.6",
  "telemetry": {
    "completion_tokens": 39604,
    "cost_usd": null,
    "prompt_tokens": 1236,
    "total_tokens": 40840,
    "wall_ms": 204097
  },
  "transport": "opencode-go-gateway",
  "vendor_sanction_ref": null
}
send-log2.4 KB
{
  "N": 2,
  "batch_id": "2026-07-19--go-requeue2",
  "config_id": "kimi-k2.6--opencode-go--dev",
  "positions": [
    {
      "attempts": [
        {
          "attempt_index": 0,
          "backoff_ms": null,
          "charged": true,
          "ended_at": "2026-07-18T20:02:03.358075+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-18T20:01:27.820269+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-18T20:03:00.374693+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-18T20:02:06.492774+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-18T20:04:17.235104+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-18T20:03:04.536665+00:00"
        }
      ],
      "block_index": 1,
      "model_reaching_attempt_index": 0,
      "slot_index": 1,
      "terminal_state": "valid",
      "variant": "P-q"
    },
    {
      "attempts": [
        {
          "attempt_index": 0,
          "backoff_ms": null,
          "charged": true,
          "ended_at": "2026-07-18T20:05:02.222045+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-18T20:04:20.240252+00:00"
        }
      ],
      "block_index": 1,
      "model_reaching_attempt_index": 0,
      "slot_index": 1,
      "terminal_state": "valid",
      "variant": "P-min"
    }
  ],
  "retry_policy": {
    "max_unreachable_retries": 5,
    "rate_limit_backoff": {
      "max_attempts": 4,
      "max_total_ms": 60000
    }
  }
}