gemini-3.6-flash @ high · api

apihigh
config
gemini-3.6-flash-eff-high--api--official--dev
batch
2026-07-19--unified
transport
google-gemini-developer-api-openai-compat
protocol
api · metered
served model
gemini-3.6-flash
captured
Jul 18, 2026, 11:54 AM · N=4
variant
live
slot 0 · P-q · sandboxed iframe
gemini-3.6-flash @high · api 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 tempmatchshown: 25
  • min tempmatchshown: 16
  • hourlymatchcoverage 24/24 · 24 match · 0 mismatch · 0 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, height=800, initial-scale=1.0">
  <title>Weather Card</title>
  <style>
    /* Reset & Base */
    *, *::before, *::after {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }
    body {
      width: 1280px;
      height: 800px;
      overflow: hidden;
      font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
      background: #0b0f19;
      color: #f3f4f6;
      display: flex;
      align-items: center;
      justify-content: center;
      user-select: none;
      -webkit-font-smoothing: antialiased;
    }

    /* Ambient Background Canvas & Gradients */
    .app-viewport {
      position: relative;
      width: 1280px;
      height: 800px;
      background: radial-gradient(circle at 20% 20%, #1a233a 0%, #0d111a 60%, #05070c 100%);
      display: flex;
      flex-direction: column;
      padding: 32px 40px;
      gap: 24px;
      box-sizing: border-box;
    }

    .bg-glow {
      position: absolute;
      top: -100px;
      left: -100px;
      width: 600px;
      height: 600px;
      background: radial-gradient(circle, rgba(56, 189, 248, 0.15) 0%, rgba(0,0,0,0) 70%);
      pointer-events: none;
      transition: background 1s ease;
    }

    .bg-glow-2 {
      position: absolute;
      bottom: -150px;
      right: -100px;
      width: 700px;
      height: 700px;
      background: radial-gradient(circle, rgba(99, 102, 241, 0.12) 0%, rgba(0,0,0,0) 70%);
      pointer-events: none;
    }

    /* Header */
    header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      z-index: 10;
    }

    .location-container {
      display: flex;
      align-items: center;
      gap: 12px;
    }

    .location-icon {
      width: 28px;
      height: 28px;
      fill: none;
      stroke: #38bdf8;
      stroke-width: 2;
      stroke-linecap: round;
      stroke-linejoin: round;
    }

    .location-title {
      font-size: 28px;
      font-weight: 700;
      letter-spacing: -0.5px;
      color: #ffffff;
    }

    .location-coords {
      font-size: 13px;
      color: #94a3b8;
      background: rgba(255, 255, 255, 0.05);
      padding: 4px 10px;
      border-radius: 20px;
      border: 1px solid rgba(255, 255, 255, 0.08);
      font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    }

    .meta-pills {
      display: flex;
      align-items: center;
      gap: 12px;
    }

    .pill {
      background: rgba(255, 255, 255, 0.06);
      backdrop-filter: blur(12px);
      -webkit-backdrop-filter: blur(12px);
      border: 1px solid rgba(255, 255, 255, 0.1);
      padding: 8px 16px;
      border-radius: 12px;
      font-size: 14px;
      font-weight: 500;
      color: #e2e8f0;
      display: flex;
      align-items: center;
      gap: 8px;
    }

    .pill-badge {
      font-size: 11px;
      text-transform: uppercase;
      letter-spacing: 0.8px;
      font-weight: 700;
      padding: 3px 8px;
      border-radius: 6px;
      background: rgba(56, 189, 248, 0.2);
      color: #38bdf8;
    }

    .pill-badge.archive {
      background: rgba(251, 146, 60, 0.2);
      color: #fb923c;
    }

    /* Main Dashboard Layout */
    .dashboard-grid {
      display: grid;
      grid-template-columns: 380px 1fr;
      gap: 24px;
      flex: 1;
      min-height: 0;
      z-index: 10;
    }

    /* Left Hero Card */
    .hero-card {
      background: linear-gradient(135deg, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.03) 100%);
      backdrop-filter: blur(20px);
      -webkit-backdrop-filter: blur(20px);
      border: 1px solid rgba(255, 255, 255, 0.12);
      border-radius: 24px;
      padding: 28px;
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      position: relative;
      overflow: hidden;
      box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4);
    }

    .hero-weather-art {
      width: 100%;
      height: 180px;
      display: flex;
      align-items: center;
      justify-content: center;
      position: relative;
      margin-bottom: 10px;
    }

    .hero-temp-container {
      margin-top: auto;
    }

    .hero-temp-main {
      font-size: 80px;
      font-weight: 800;
      line-height: 1;
      letter-spacing: -3px;
      background: linear-gradient(180deg, #ffffff 30%, #cbd5e1 100%);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
      display: flex;
      align-items: flex-start;
    }

    .hero-temp-unit {
      font-size: 32px;
      font-weight: 400;
      margin-top: 8px;
      margin-left: 2px;
      color: #94a3b8;
    }

    .hero-condition {
      font-size: 22px;
      font-weight: 600;
      color: #f1f5f9;
      margin-top: 6px;
      margin-bottom: 16px;
    }

    .range-pills {
      display: flex;
      gap: 12px;
    }

    .range-pill {
      flex: 1;
      background: rgba(0, 0, 0, 0.25);
      border: 1px solid rgba(255, 255, 255, 0.08);
      border-radius: 14px;
      padding: 12px;
      display: flex;
      flex-direction: column;
      gap: 4px;
    }

    .range-pill .label {
      font-size: 11px;
      text-transform: uppercase;
      letter-spacing: 0.5px;
      color: #94a3b8;
      font-weight: 600;
    }

    .range-pill .value {
      font-size: 20px;
      font-weight: 700;
    }

    .range-pill.max .value {
      color: #f87171;
    }

    .range-pill.min .value {
      color: #38bdf8;
    }

    /* Right Stats & Chart Card */
    .details-column {
      display: flex;
      flex-direction: column;
      gap: 24px;
      min-height: 0;
    }

    .chart-card {
      flex: 1.2;
      background: linear-gradient(135deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.02) 100%);
      backdrop-filter: blur(20px);
      -webkit-backdrop-filter: blur(20px);
      border: 1px solid rgba(255, 255, 255, 0.1);
      border-radius: 24px;
      padding: 24px;
      display: flex;
      flex-direction: column;
      box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
      position: relative;
    }

    .card-title-row {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 16px;
    }

    .card-title {
      font-size: 16px;
      font-weight: 600;
      color: #e2e8f0;
      display: flex;
      align-items: center;
      gap: 8px;
    }

    .chart-container {
      flex: 1;
      width: 100%;
      position: relative;
      min-height: 180px;
    }

    canvas#tempChart {
      width: 100%;
      height: 100%;
      display: block;
    }

    /* Hourly Carousel / Grid */
    .hourly-card {
      flex: 0.9;
      background: linear-gradient(135deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.02) 100%);
      backdrop-filter: blur(20px);
      -webkit-backdrop-filter: blur(20px);
      border: 1px solid rgba(255, 255, 255, 0.1);
      border-radius: 24px;
      padding: 20px 24px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
    }

    .hourly-scroll {
      display: flex;
      gap: 12px;
      overflow-x: auto;
      padding-bottom: 8px;
      scroll-behavior: smooth;
    }

    .hourly-scroll::-webkit-scrollbar {
      height: 5px;
    }
    .hourly-scroll::-webkit-scrollbar-track {
      background: rgba(255, 255, 255, 0.03);
      border-radius: 10px;
    }
    .hourly-scroll::-webkit-scrollbar-thumb {
      background: rgba(255, 255, 255, 0.15);
      border-radius: 10px;
    }

    .hourly-item {
      flex: 0 0 calc((100% - (12px * 7)) / 8);
      min-width: 76px;
      background: rgba(255, 255, 255, 0.03);
      border: 1px solid rgba(255, 255, 255, 0.06);
      border-radius: 16px;
      padding: 12px 8px;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 8px;
      transition: all 0.2s ease;
    }

    .hourly-item:hover {
      background: rgba(255, 255, 255, 0.08);
      border-color: rgba(56, 189, 248, 0.3);
      transform: translateY(-2px);
    }

    .hourly-time {
      font-size: 12px;
      color: #94a3b8;
      font-weight: 500;
    }

    .hourly-icon {
      width: 28px;
      height: 28px;
    }

    .hourly-temp {
      font-size: 15px;
      font-weight: 700;
      color: #f3f4f6;
    }

    /* Controls Header Bar */
    .controls-bar {
      display: flex;
      align-items: center;
      gap: 8px;
    }

    .input-group {
      display: flex;
      align-items: center;
      background: rgba(0, 0, 0, 0.3);
      border: 1px solid rgba(255, 255, 255, 0.12);
      border-radius: 10px;
      padding: 3px 6px;
    }

    .input-group input {
      background: transparent;
      border: none;
      outline: none;
      color: #fff;
      font-size: 12px;
      padding: 3px 4px;
      font-family: inherit;
    }

    .input-group input[type="date"] {
      color-scheme: dark;
    }

    .input-group label {
      font-size: 10px;
      color: #64748b;
      text-transform: uppercase;
      font-weight: 700;
      padding-left: 4px;
    }

    .btn-apply {
      background: #0284c7;
      color: #fff;
      border: none;
      border-radius: 10px;
      padding: 6px 12px;
      font-size: 12px;
      font-weight: 600;
      cursor: pointer;
      transition: background 0.2s;
    }
    .btn-apply:hover {
      background: #0369a1;
    }

    /* Loading Overlay & Error Screen */
    .status-overlay {
      position: absolute;
      inset: 0;
      background: rgba(11, 15, 25, 0.85);
      backdrop-filter: blur(16px);
      -webkit-backdrop-filter: blur(16px);
      z-index: 100;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 16px;
      border-radius: 24px;
      transition: opacity 0.3s ease;
    }

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

    .spinner {
      width: 44px;
      height: 44px;
      border: 3px solid rgba(56, 189, 248, 0.15);
      border-top-color: #38bdf8;
      border-radius: 50%;
      animation: spin 1s linear infinite;
    }

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

    .error-card {
      background: rgba(239, 68, 68, 0.1);
      border: 1px solid rgba(239, 68, 68, 0.3);
      padding: 24px 32px;
      border-radius: 16px;
      text-align: center;
      max-width: 420px;
    }

    .error-title {
      color: #f87171;
      font-size: 18px;
      font-weight: 700;
      margin-bottom: 8px;
    }

    .error-msg {
      color: #cbd5e1;
      font-size: 14px;
      line-height: 1.5;
    }

    /* Tooltip canvas */
    .chart-tooltip {
      position: absolute;
      pointer-events: none;
      background: rgba(15, 23, 42, 0.9);
      border: 1px solid rgba(255, 255, 255, 0.15);
      border-radius: 8px;
      padding: 6px 10px;
      font-size: 12px;
      color: #f8fafc;
      transform: translate(-50%, -120%);
      transition: opacity 0.15s ease, transform 0.15s ease;
      white-space: nowrap;
      z-index: 20;
      box-shadow: 0 10px 20px rgba(0,0,0,0.5);
    }
  </style>
</head>
<body>

<div class="app-viewport">
  <div class="bg-glow" id="bgGlow"></div>
  <div class="bg-glow-2"></div>

  <!-- Loading / Error Overlay -->
  <div class="status-overlay" id="statusOverlay">
    <div class="spinner" id="spinner"></div>
    <div id="statusText" style="color: #94a3b8; font-size: 14px; font-weight: 500;">Fetching meteorological data...</div>
    <div class="error-card" id="errorCard" style="display: none;">
      <div class="error-title">Unable to Load Weather Data</div>
      <div class="error-msg" id="errorMessage">There was an issue connecting to the weather service or reading parameters.</div>
    </div>
  </div>

  <!-- Header -->
  <header>
    <div class="location-container">
      <svg class="location-icon" viewBox="0 0 24 24">
        <path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"></path>
        <circle cx="12" cy="9" r="2.5"></circle>
      </svg>
      <div>
        <div class="location-title" id="locName">Berlin</div>
      </div>
      <div class="location-coords" id="locCoords">52.52° N, 13.41° E</div>
    </div>

    <div class="meta-pills">
      <form class="controls-bar" id="paramForm" onsubmit="handleFormSubmit(event)">
        <div class="input-group">
          <label for="inputLoc">Loc</label>
          <input type="text" id="inputLoc" style="width: 70px;" placeholder="Name">
        </div>
        <div class="input-group">
          <label for="inputLat">Lat</label>
          <input type="number" step="any" id="inputLat" style="width: 50px;">
        </div>
        <div class="input-group">
          <label for="inputLon">Lon</label>
          <input type="number" step="any" id="inputLon" style="width: 50px;">
        </div>
        <div class="input-group">
          <label for="inputDate">Date</label>
          <input type="date" id="inputDate">
        </div>
        <button type="submit" class="btn-apply">Go</button>
      </form>

      <div class="pill">
        <span id="displayDate">-- --- ----</span>
        <span class="pill-badge" id="sourceBadge">FORECAST</span>
      </div>
    </div>
  </header>

  <!-- Main Content Dashboard -->
  <div class="dashboard-grid">
    <!-- Hero Left Weather Panel -->
    <div class="hero-card">
      <div class="hero-weather-art" id="weatherArt">
        <!-- Dynamic Canvas/SVG Art Rendered Here -->
      </div>

      <div class="hero-temp-container">
        <div class="hero-temp-main">
          <span id="heroTempAvg">--</span>
          <span class="hero-temp-unit">°C</span>
        </div>
        <div class="hero-condition" id="heroCondition">Loading...</div>

        <div class="range-pills">
          <div class="range-pill max">
            <span class="label">High Temp</span>
            <span class="value" id="heroTempMax">--°</span>
          </div>
          <div class="range-pill min">
            <span class="label">Low Temp</span>
            <span class="value" id="heroTempMin">--°</span>
          </div>
        </div>
      </div>
    </div>

    <!-- Right Side Details -->
    <div class="details-column">
      <!-- 24h Temperature Chart -->
      <div class="chart-card">
        <div class="card-title-row">
          <div class="card-title">
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#38bdf8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
            </svg>
            24-Hour Temperature Trajectory (°C)
          </div>
          <div style="font-size: 12px; color: #64748b;" id="timezoneLabel">UTC Timezone</div>
        </div>

        <div class="chart-container" id="chartContainer">
          <canvas id="tempChart"></canvas>
          <div class="chart-tooltip" id="chartTooltip" style="opacity: 0;"></div>
        </div>
      </div>

      <!-- Hourly Scroll / Breakdown -->
      <div class="hourly-card">
        <div class="card-title-row" style="margin-bottom: 12px;">
          <div class="card-title">
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#38bdf8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <circle cx="12" cy="12" r="10"></circle>
              <polyline points="12 6 12 12 16 14"></polyline>
            </svg>
            Hourly Forecast & Breakdown
          </div>
        </div>

        <div class="hourly-scroll" id="hourlyList">
          <!-- Populated by JS -->
        </div>
      </div>
    </div>
  </div>
</div>

<script>
  // WMO Weather Code Mapping
  const WMO_MAP = {
    0: { label: 'Clear Sky', icon: 'sun', color: '#f59e0b', glow: 'rgba(245, 158, 11, 0.25)' },
    1: { label: 'Mainly Clear', icon: 'sun-cloud', color: '#38bdf8', glow: 'rgba(56, 189, 248, 0.2)' },
    2: { label: 'Partly Cloudy', icon: 'sun-cloud', color: '#38bdf8', glow: 'rgba(56, 189, 248, 0.2)' },
    3: { label: 'Overcast', icon: 'cloud', color: '#94a3b8', glow: 'rgba(148, 163, 184, 0.2)' },
    45: { label: 'Foggy', icon: 'fog', color: '#64748b', glow: 'rgba(100, 116, 139, 0.2)' },
    48: { label: 'Depositing Rime Fog', icon: 'fog', color: '#64748b', glow: 'rgba(100, 116, 139, 0.2)' },
    51: { label: 'Light Drizzle', icon: 'drizzle', color: '#0ea5e9', glow: 'rgba(14, 165, 233, 0.2)' },
    53: { label: 'Moderate Drizzle', icon: 'drizzle', color: '#0ea5e9', glow: 'rgba(14, 165, 233, 0.2)' },
    55: { label: 'Dense Drizzle', icon: 'drizzle', color: '#0284c7', glow: 'rgba(2, 132, 199, 0.2)' },
    56: { label: 'Freezing Drizzle', icon: 'sleet', color: '#38bdf8', glow: 'rgba(56, 189, 248, 0.2)' },
    57: { label: 'Dense Freezing Drizzle', icon: 'sleet', color: '#38bdf8', glow: 'rgba(56, 189, 248, 0.2)' },
    61: { label: 'Slight Rain', icon: 'rain', color: '#3b82f6', glow: 'rgba(59, 130, 246, 0.25)' },
    63: { label: 'Moderate Rain', icon: 'rain', color: '#2563eb', glow: 'rgba(37, 99, 235, 0.25)' },
    65: { label: 'Heavy Rain', icon: 'heavy-rain', color: '#1d4ed8', glow: 'rgba(29, 78, 216, 0.3)' },
    66: { label: 'Freezing Rain', icon: 'sleet', color: '#38bdf8', glow: 'rgba(56, 189, 248, 0.25)' },
    67: { label: 'Heavy Freezing Rain', icon: 'sleet', color: '#0284c7', glow: 'rgba(2, 132, 199, 0.25)' },
    71: { label: 'Slight Snow', icon: 'snow', color: '#e0f2fe', glow: 'rgba(224, 242, 254, 0.25)' },
    73: { label: 'Moderate Snow', icon: 'snow', color: '#bae6fd', glow: 'rgba(186, 230, 253, 0.25)' },
    75: { label: 'Heavy Snow', icon: 'snow', color: '#7dd3fc', glow: 'rgba(125, 211, 252, 0.3)' },
    77: { label: 'Snow Grains', icon: 'snow', color: '#e0f2fe', glow: 'rgba(224, 242, 254, 0.2)' },
    80: { label: 'Slight Rain Showers', icon: 'rain', color: '#3b82f6', glow: 'rgba(59, 130, 246, 0.2)' },
    81: { label: 'Moderate Rain Showers', icon: 'rain', color: '#2563eb', glow: 'rgba(37, 99, 235, 0.2)' },
    82: { label: 'Violent Rain Showers', icon: 'heavy-rain', color: '#1d4ed8', glow: 'rgba(29, 78, 216, 0.3)' },
    85: { label: 'Slight Snow Showers', icon: 'snow', color: '#e0f2fe', glow: 'rgba(224, 242, 254, 0.2)' },
    86: { label: 'Heavy Snow Showers', icon: 'snow', color: '#7dd3fc', glow: 'rgba(125, 211, 252, 0.3)' },
    95: { label: 'Thunderstorm', icon: 'thunderstorm', color: '#a855f7', glow: 'rgba(168, 85, 247, 0.3)' },
    96: { label: 'Thunderstorm with Hail', icon: 'thunderstorm', color: '#9333ea', glow: 'rgba(147, 51, 234, 0.3)' },
    99: { label: 'Heavy Thunderstorm', icon: 'thunderstorm', color: '#7e22ce', glow: 'rgba(126, 34, 206, 0.35)' },
  };

  function getWmoInfo(code) {
    return WMO_MAP[code] || { label: 'Unknown Condition (' + code + ')', icon: 'sun-cloud', color: '#38bdf8', glow: 'rgba(56, 189, 248, 0.2)' };
  }

  // Generate Inline SVG Icon per Weather Type
  function getSVGIcon(type, size = 32) {
    const icons = {
      'sun': `
        <svg width="${size}" height="${size}" viewBox="0 0 64 64" fill="none">
          <circle cx="32" cy="32" r="14" fill="#f59e0b"/>
          <g stroke="#fbbf24" stroke-width="4" stroke-linecap="round">
            <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 width="${size}" height="${size}" viewBox="0 0 64 64" fill="none">
          <circle cx="26" cy="24" r="10" fill="#f59e0b"/>
          <path d="M46 48H22c-5.5 0-10-4.5-10-10 0-4.8 3.4-8.8 8-9.7 1.2-5.4 6-9.3 11.7-9.3 6.1 0 11.2 4.4 12.1 10.3 3.5.7 6.2 3.8 6.2 7.7 0 4.4-3.6 8-8 8z" fill="#94a3b8" fill-opacity="0.9"/>
        </svg>`,
      'cloud': `
        <svg width="${size}" height="${size}" viewBox="0 0 64 64" fill="none">
          <path d="M46 44H20c-5.5 0-10-4.5-10-10 0-4.8 3.4-8.8 8-9.7 1.2-5.4 6-9.3 11.7-9.3 6.1 0 11.2 4.4 12.1 10.3 3.5.7 6.2 3.8 6.2 7.7 0 4.4-3.6 8-8 8z" fill="#cbd5e1"/>
        </svg>`,
      'fog': `
        <svg width="${size}" height="${size}" viewBox="0 0 64 64" fill="none" stroke="#94a3b8" stroke-width="4" stroke-linecap="round">
          <line x1="14" y1="24" x2="50" y2="24"/>
          <line x1="10" y1="32" x2="54" y2="32"/>
          <line x1="18" y1="40" x2="46" y2="40"/>
        </svg>`,
      'drizzle': `
        <svg width="${size}" height="${size}" viewBox="0 0 64 64" fill="none">
          <path d="M44 36H22c-4.4 0-8-3.6-8-8 0-3.8 2.7-7 6.4-7.8 1-4.3 4.8-7.4 9.4-7.4 4.9 0 9 3.5 9.7 8.2 2.8.6 4.9 3 4.9 6 0 3.5-2.9 6.4-6.4 6.4z" fill="#94a3b8"/>
          <path d="M22 44l-2 6M32 44l-2 6M42 44l-2 6" stroke="#38bdf8" stroke-width="3" stroke-linecap="round"/>
        </svg>`,
      'rain': `
        <svg width="${size}" height="${size}" viewBox="0 0 64 64" fill="none">
          <path d="M44 34H20c-4.4 0-8-3.6-8-8 0-3.8 2.7-7 6.4-7.8 1-4.3 4.8-7.4 9.4-7.4 4.9 0 9 3.5 9.7 8.2 2.8.6 4.9 3 4.9 6 0 3.5-2.9 6.4-6.4 6.4z" fill="#64748b"/>
          <path d="M20 42l-3 10M32 42l-3 10M44 42l-3 10" stroke="#3b82f6" stroke-width="3.5" stroke-linecap="round"/>
        </svg>`,
      'heavy-rain': `
        <svg width="${size}" height="${size}" viewBox="0 0 64 64" fill="none">
          <path d="M44 32H20c-4.4 0-8-3.6-8-8 0-3.8 2.7-7 6.4-7.8 1-4.3 4.8-7.4 9.4-7.4 4.9 0 9 3.5 9.7 8.2 2.8.6 4.9 3 4.9 6 0 3.5-2.9 6.4-6.4 6.4z" fill="#475569"/>
          <path d="M18 40l-4 14M28 40l-4 14M38 40l-4 14M48 40l-4 14" stroke="#2563eb" stroke-width="3.5" stroke-linecap="round"/>
        </svg>`,
      'sleet': `
        <svg width="${size}" height="${size}" viewBox="0 0 64 64" fill="none">
          <path d="M44 34H20c-4.4 0-8-3.6-8-8 0-3.8 2.7-7 6.4-7.8 1-4.3 4.8-7.4 9.4-7.4 4.9 0 9 3.5 9.7 8.2 2.8.6 4.9 3 4.9 6 0 3.5-2.9 6.4-6.4 6.4z" fill="#64748b"/>
          <path d="M22 42l-2 8M38 42l-2 8" stroke="#38bdf8" stroke-width="3" stroke-linecap="round"/>
          <circle cx="30" cy="46" r="2" fill="#e0f2fe"/>
        </svg>`,
      'snow': `
        <svg width="${size}" height="${size}" viewBox="0 0 64 64" fill="none">
          <path d="M44 32H20c-4.4 0-8-3.6-8-8 0-3.8 2.7-7 6.4-7.8 1-4.3 4.8-7.4 9.4-7.4 4.9 0 9 3.5 9.7 8.2 2.8.6 4.9 3 4.9 6 0 3.5-2.9 6.4-6.4 6.4z" fill="#64748b"/>
          <g fill="#bae6fd">
            <circle cx="20" cy="44" r="2.5"/>
            <circle cx="32" cy="44" r="2.5"/>
            <circle cx="44" cy="44" r="2.5"/>
            <circle cx="26" cy="52" r="2.5"/>
            <circle cx="38" cy="52" r="2.5"/>
          </g>
        </svg>`,
      'thunderstorm': `
        <svg width="${size}" height="${size}" viewBox="0 0 64 64" fill="none">
          <path d="M44 30H20c-4.4 0-8-3.6-8-8 0-3.8 2.7-7 6.4-7.8 1-4.3 4.8-7.4 9.4-7.4 4.9 0 9 3.5 9.7 8.2 2.8.6 4.9 3 4.9 6 0 3.5-2.9 6.4-6.4 6.4z" fill="#334155"/>
          <polygon points="30,34 22,46 29,46 25,58 37,42 29,42" fill="#f59e0b"/>
        </svg>`
    };
    return icons[type] || icons['sun-cloud'];
  }

  // Get Today's YYYY-MM-DD in UTC
  function getUTCTodayString() {
    const now = new Date();
    const year = now.getUTCFullYear();
    const month = String(now.getUTCMonth() + 1).padStart(2, '0');
    const day = String(now.getUTCDate()).padStart(2, '0');
    return `${year}-${month}-${day}`;
  }

  // Format YYYY-MM-DD to readable date
  function formatDateNice(dateStr) {
    try {
      const parts = dateStr.split('-');
      if (parts.length !== 3) return dateStr;
      const d = new Date(Date.UTC(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2])));
      return d.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' });
    } catch(e) {
      return dateStr;
    }
  }

  // Parse Query Parameters with defaults
  function getQueryParams() {
    const params = new URLSearchParams(window.location.search);
    const todayUTC = getUTCTodayString();

    const lat = parseFloat(params.get('lat')) || 52.52;
    const lon = parseFloat(params.get('lon')) || 13.405;
    const name = params.get('name') || 'Berlin';
    const date = params.get('date') || todayUTC;

    return { lat, lon, name, date, todayUTC };
  }

  // Global App State
  let state = getQueryParams();
  let chartDataPoints = [];

  // Update UI Inputs
  function updateInputs() {
    document.getElementById('inputLoc').value = state.name;
    document.getElementById('inputLat').value = state.lat;
    document.getElementById('inputLon').value = state.lon;
    document.getElementById('inputDate').value = state.date;

    document.getElementById('locName').innerText = state.name;
    document.getElementById('locCoords').innerText = `${Math.abs(state.lat).toFixed(2)}° ${state.lat >= 0 ? 'N' : 'S'}, ${Math.abs(state.lon).toFixed(2)}° ${state.lon >= 0 ? 'E' : 'W'}`;
    document.getElementById('displayDate').innerText = formatDateNice(state.date);
  }

  function handleFormSubmit(e) {
    e.preventDefault();
    const loc = document.getElementById('inputLoc').value.trim() || 'Custom';
    const lat = parseFloat(document.getElementById('inputLat').value) || 0;
    const lon = parseFloat(document.getElementById('inputLon').value) || 0;
    const date = document.getElementById('inputDate').value || getUTCTodayString();

    const url = new URL(window.location.href);
    url.searchParams.set('name', loc);
    url.searchParams.set('lat', lat);
    url.searchParams.set('lon', lon);
    url.searchParams.set('date', date);

    window.history.pushState({}, '', url);
    state = { lat, lon, name: loc, date, todayUTC: getUTCTodayString() };
    updateInputs();
    fetchWeatherData();
  }

  // Main Data Fetcher
  async function fetchWeatherData() {
    const overlay = document.getElementById('statusOverlay');
    const spinner = document.getElementById('spinner');
    const statusText = document.getElementById('statusText');
    const errorCard = document.getElementById('errorCard');

    overlay.classList.remove('hidden');
    spinner.style.display = 'block';
    statusText.style.display = 'block';
    errorCard.style.display = 'none';

    // Determine forecast vs archive API path
    const isForecast = state.date >= state.todayUTC;
    const apiEndpoint = isForecast ? '/api/om/forecast' : '/api/om/archive';

    const sourceBadge = document.getElementById('sourceBadge');
    sourceBadge.innerText = isForecast ? 'FORECAST' : 'ARCHIVE';
    if (isForecast) {
      sourceBadge.classList.remove('archive');
    } else {
      sourceBadge.classList.add('archive');
    }

    const queryParams = new URLSearchParams({
      latitude: state.lat,
      longitude: state.lon,
      daily: 'temperature_2m_max,temperature_2m_min,weather_code',
      hourly: 'temperature_2m',
      timezone: 'UTC',
      temperature_unit: 'celsius',
      start_date: state.date,
      end_date: state.date
    });

    const fullUrl = `${apiEndpoint}?${queryParams.toString()}`;

    try {
      const response = await fetch(fullUrl);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: Failed to retrieve data from ${apiEndpoint}`);
      }

      const data = await response.json();
      validateAndRender(data);

      // Hide loading overlay
      overlay.classList.add('hidden');
    } catch (err) {
      console.error("Weather fetch error:", err);
      spinner.style.display = 'none';
      statusText.style.display = 'none';
      errorCard.style.display = 'block';
      document.getElementById('errorMessage').innerText = err.message || "Failed to load weather data for the specified parameters.";
    }
  }

  function validateAndRender(data) {
    if (!data.daily || !data.hourly || !data.daily.weather_code || data.daily.weather_code.length === 0) {
      throw new Error("Invalid API response format: missing daily/hourly data.");
    }

    const weatherCode = data.daily.weather_code[0];
    const maxTemp = Math.round(data.daily.temperature_2m_max[0]);
    const minTemp = Math.round(data.daily.temperature_2m_min[0]);
    const avgTemp = Math.round((maxTemp + minTemp) / 2);

    const wmo = getWmoInfo(weatherCode);

    // Update Hero UI
    document.getElementById('heroTempAvg').innerText = avgTemp;
    document.getElementById('heroTempMax').innerText = `${maxTemp}°`;
    document.getElementById('heroTempMin').innerText = `${minTemp}°`;
    document.getElementById('heroCondition').innerText = wmo.label;

    // Background Glow matching weather
    document.getElementById('bgGlow').style.background = `radial-gradient(circle, ${wmo.glow} 0%, rgba(0,0,0,0) 70%)`;

    // Render Large Weather Artwork
    document.getElementById('weatherArt').innerHTML = getSVGIcon(wmo.icon, 140);

    // Hourly Breakdown
    const hourlyTimes = data.hourly.time || [];
    const hourlyTemps = data.hourly.temperature_2m || [];

    const hourlyListEl = document.getElementById('hourlyList');
    hourlyListEl.innerHTML = '';

    chartDataPoints = [];

    hourlyTimes.forEach((timeStr, idx) => {
      const temp = hourlyTemps[idx];
      const hour = timeStr.includes('T') ? timeStr.split('T')[1].substring(0, 5) : `${String(idx).padStart(2, '0')}:00`;

      chartDataPoints.push({
        time: hour,
        temp: temp
      });

      const itemEl = document.createElement('div');
      itemEl.className = 'hourly-item';
      itemEl.innerHTML = `
        <div class="hourly-time">${hour}</div>
        <div class="hourly-icon">${getSVGIcon(wmo.icon, 28)}</div>
        <div class="hourly-temp">${Math.round(temp)}°</div>
      `;
      hourlyListEl.appendChild(itemEl);
    });

    // Render Line Chart
    renderChart();
  }

  // Canvas Temperature Line Chart Rendering
  function renderChart() {
    const canvas = document.getElementById('tempChart');
    const container = document.getElementById('chartContainer');

    const dpr = window.devicePixelRatio || 1;
    const width = container.clientWidth;
    const height = container.clientHeight;

    canvas.width = width * dpr;
    canvas.height = height * dpr;

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

    if (chartDataPoints.length === 0) return;

    // Padding
    const padLeft = 35;
    const padRight = 20;
    const padTop = 25;
    const padBottom = 30;

    const chartW = width - padLeft - padRight;
    const chartH = height - padTop - padBottom;

    // Find min and max for chart scale
    const temps = chartDataPoints.map(p => p.temp);
    let minT = Math.floor(Math.min(...temps)) - 2;
    let maxT = Math.ceil(Math.max(...temps)) + 2;

    if (maxT === minT) { maxT += 2; minT -= 2; }

    // Map X, Y coordinates
    const points = chartDataPoints.map((pt, i) => {
      const x = padLeft + (i / (chartDataPoints.length - 1)) * chartW;
      const y = padTop + chartH - ((pt.temp - minT) / (maxT - minT)) * chartH;
      return { x, y, temp: pt.temp, time: pt.time };
    });

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

    // Draw Grid Lines & Y Labels
    ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
    ctx.lineWidth = 1;
    ctx.fillStyle = '#64748b';
    ctx.font = '11px -apple-system, sans-serif';
    ctx.textAlign = 'right';
    ctx.textBaseline = 'middle';

    const ySteps = 4;
    for (let i = 0; i <= ySteps; i++) {
      const val = minT + ((maxT - minT) * i) / ySteps;
      const y = padTop + chartH - (i / ySteps) * chartH;

      ctx.beginPath();
      ctx.moveTo(padLeft, y);
      ctx.lineTo(width - padRight, y);
      ctx.stroke();

      ctx.fillText(`${Math.round(val)}°`, padLeft - 8, y);
    }

    // Draw Smooth Temperature Curve
    ctx.beginPath();
    ctx.moveTo(points[0].x, points[0].y);

    for (let i = 0; i < points.length - 1; i++) {
      const xc = (points[i].x + points[i + 1].x) / 2;
      const yc = (points[i].y + points[i + 1].y) / 2;
      ctx.quadraticCurveTo(points[i].x, points[i].y, xc, yc);
    }
    ctx.lineTo(points[points.length - 1].x, points[points.length - 1].y);

    // Stroke Gradient
    const lineGradient = ctx.createLinearGradient(0, 0, width, 0);
    lineGradient.addColorStop(0, '#38bdf8');
    lineGradient.addColorStop(0.5, '#818cf8');
    lineGradient.addColorStop(1, '#f43f5e');

    ctx.strokeStyle = lineGradient;
    ctx.lineWidth = 3.5;
    ctx.lineCap = 'round';
    ctx.stroke();

    // Gradient Fill under curve
    const areaGradient = ctx.createLinearGradient(0, padTop, 0, height - padBottom);
    areaGradient.addColorStop(0, 'rgba(56, 189, 248, 0.25)');
    areaGradient.addColorStop(1, 'rgba(56, 189, 248, 0.0)');

    ctx.lineTo(points[points.length - 1].x, height - padBottom);
    ctx.lineTo(points[0].x, height - padBottom);
    ctx.closePath();
    ctx.fillStyle = areaGradient;
    ctx.fill();

    // Draw Data Dots & Time Labels on X axis
    ctx.textAlign = 'center';
    ctx.textBaseline = 'top';

    points.forEach((pt, i) => {
      // Draw X axis time label every 4 hours
      if (i % 4 === 0 || i === points.length - 1) {
        ctx.fillStyle = '#94a3b8';
        ctx.fillText(pt.time, pt.x, height - padBottom + 8);
      }

      // Point circle
      ctx.beginPath();
      ctx.arc(pt.x, pt.y, 4, 0, Math.PI * 2);
      ctx.fillStyle = '#0f172a';
      ctx.fill();
      ctx.strokeStyle = '#38bdf8';
      ctx.lineWidth = 2;
      ctx.stroke();
    });

    // Store points on canvas element for interactivity
    canvas.chartPoints = points;
  }

  // Chart Tooltip Hover Functionality
  function setupChartHover() {
    const canvas = document.getElementById('tempChart');
    const tooltip = document.getElementById('chartTooltip');

    canvas.addEventListener('mousemove', (e) => {
      if (!canvas.chartPoints || canvas.chartPoints.length === 0) return;

      const rect = canvas.getBoundingClientRect();
      const mouseX = e.clientX - rect.left;

      // Find closest point
      let closest = canvas.chartPoints[0];
      let minDist = Math.abs(mouseX - closest.x);

      for (let i = 1; i < canvas.chartPoints.length; i++) {
        const dist = Math.abs(mouseX - canvas.chartPoints[i].x);
        if (dist < minDist) {
          minDist = dist;
          closest = canvas.chartPoints[i];
        }
      }

      if (minDist < 40) {
        tooltip.style.opacity = '1';
        tooltip.style.left = `${closest.x}px`;
        tooltip.style.top = `${closest.y - 10}px`;
        tooltip.innerHTML = `<strong>${closest.time}</strong>: ${closest.temp.toFixed(1)}°C`;
      } else {
        tooltip.style.opacity = '0';
      }
    });

    canvas.addEventListener('mouseleave', () => {
      tooltip.style.opacity = '0';
    });
  }

  // Resize Listener for Canvas
  window.addEventListener('resize', () => {
    renderChart();
  });

  // Initialization
  window.addEventListener('DOMContentLoaded', () => {
    updateInputs();
    setupChartHover();
    fetchWeatherData();
  });
</script>
</body>
</html>

L1 — cheap deterministic descriptors

bytes total35,238html / css / js4,793 / 11,024 / 19,421
dom nodes75dom depth10
css rules59js presentyes
brightness35.9contrast16.3
colorfulness18.1whitespace83.1%

This slot's telemetry

prompt tokens348completion tokens13,716
total tokens14,064wall115.6 s
costrequest id
config.json598 B
{
  "N": 4,
  "auth": {
    "credential_ref": "WCB_GEMINI_KEY",
    "method": "api-key"
  },
  "billing": "metered",
  "config_id": "gemini-3.6-flash-eff-high--api--official--dev",
  "effort": "high",
  "family": "gemini",
  "m": {
    "min": 4,
    "q": 4
  },
  "model_id": "gemini-3.6-flash",
  "protocol": "api",
  "served_model": "gemini-3.6-flash",
  "telemetry": {
    "completion_tokens": 81709,
    "cost_usd": null,
    "prompt_tokens": 2748,
    "total_tokens": 84457,
    "wall_ms": 642284
  },
  "transport": "google-gemini-developer-api-openai-compat",
  "vendor_sanction_ref": null
}
send-log4.7 KB
{
  "N": 4,
  "batch_id": "2026-07-22--gemini-36flash",
  "config_id": "gemini-3.6-flash-eff-high--api--official--dev",
  "positions": [
    {
      "attempts": [
        {
          "attempt_index": 0,
          "backoff_ms": null,
          "charged": true,
          "ended_at": "2026-07-22T07:31:08.511589+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-22T07:29:12.873067+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-22T07:32:40.068984+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-22T07:31:13.964249+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-22T07:34:57.900489+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-22T07:32:43.489691+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-22T07:35:50.670381+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-22T07:35:01.379517+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-22T07:37:08.082672+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-22T07:35:55.846333+00:00"
        }
      ],
      "block_index": 2,
      "model_reaching_attempt_index": 0,
      "slot_index": 2,
      "terminal_state": "valid",
      "variant": "P-min"
    },
    {
      "attempts": [
        {
          "attempt_index": 0,
          "backoff_ms": null,
          "charged": true,
          "ended_at": "2026-07-22T07:38:03.600932+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-22T07:37:11.528222+00:00"
        }
      ],
      "block_index": 2,
      "model_reaching_attempt_index": 0,
      "slot_index": 2,
      "terminal_state": "valid",
      "variant": "P-q"
    },
    {
      "attempts": [
        {
          "attempt_index": 0,
          "backoff_ms": null,
          "charged": true,
          "ended_at": "2026-07-22T07:38:51.158248+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-22T07:38:06.840939+00:00"
        }
      ],
      "block_index": 3,
      "model_reaching_attempt_index": 0,
      "slot_index": 3,
      "terminal_state": "valid",
      "variant": "P-min"
    },
    {
      "attempts": [
        {
          "attempt_index": 0,
          "backoff_ms": null,
          "charged": true,
          "ended_at": "2026-07-22T07:40:23.544061+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-22T07:38:55.325590+00:00"
        }
      ],
      "block_index": 3,
      "model_reaching_attempt_index": 0,
      "slot_index": 3,
      "terminal_state": "valid",
      "variant": "P-q"
    }
  ],
  "retry_policy": {
    "max_unreachable_retries": 5,
    "rate_limit_backoff": {
      "max_attempts": 4,
      "max_total_ms": 60000
    }
  }
}