deepseek-v4-flash-free · api

api
config
deepseek-v4-flash-free--zen-free--dev
batch
2026-07-19--unified
transport
opencode-zen-free-tier
protocol
api · plan
served model
deepseek-v4-flash-free
采集于
2026年7月18日 11:54 · N=2
variant
live
slot 0 · P-q · sandboxed iframe
deepseek-v4-flash-free · api 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">
    <title>Weather Card</title>
    <style>
        *,
        *::before,
        *::after {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', Arial, sans-serif;
            padding: 40px;
            background: #0b1120;
            transition: background 0.8s ease;
        }

        #app {
            width: 720px;
            max-width: 100%;
            position: relative;
        }

        /* --- Card --- */
        .card {
            background: rgba(255, 255, 255, 0.045);
            backdrop-filter: blur(24px);
            -webkit-backdrop-filter: blur(24px);
            border-radius: 28px;
            border: 1px solid rgba(255, 255, 255, 0.07);
            box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(255, 255, 255, 0.03) inset;
            padding: 40px 44px 36px;
            color: #fff;
            transition: all 0.4s ease;
            position: relative;
            overflow: hidden;
        }

        .card::before {
            content: '';
            position: absolute;
            top: -50%;
            right: -50%;
            width: 100%;
            height: 100%;
            background: radial-gradient(circle at 70% 20%, rgba(255, 255, 255, 0.03) 0%, transparent 60%);
            pointer-events: none;
        }

        /* --- Header --- */
        .card-header {
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            margin-bottom: 18px;
        }

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

        .location-icon {
            font-size: 30px;
            line-height: 1;
        }

        .location-name {
            font-size: 24px;
            font-weight: 600;
            letter-spacing: -0.01em;
            color: #fff;
        }

        .date {
            font-size: 14px;
            font-weight: 400;
            color: rgba(255, 255, 255, 0.55);
            letter-spacing: 0.02em;
            padding-top: 4px;
            white-space: nowrap;
        }

        /* --- Divider --- */
        .divider {
            height: 1px;
            background: linear-gradient(90deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.02) 100%);
            margin: 0 0 24px 0;
        }

        /* --- Condition --- */
        .condition-section {
            display: flex;
            align-items: center;
            gap: 14px;
            margin-bottom: 20px;
        }

        .condition-emoji {
            font-size: 36px;
            line-height: 1;
        }

        .condition-text {
            font-size: 18px;
            font-weight: 450;
            color: rgba(255, 255, 255, 0.85);
            letter-spacing: 0.01em;
        }

        /* --- Temperatures --- */
        .temps-section {
            display: flex;
            align-items: center;
            gap: 32px;
            margin-bottom: 28px;
            padding: 8px 0;
        }

        .temp-block {
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        .temp-value {
            font-size: 48px;
            font-weight: 600;
            letter-spacing: -0.02em;
            line-height: 1;
            color: #fff;
        }

        .temp-value .deg {
            font-size: 28px;
            font-weight: 400;
            color: rgba(255, 255, 255, 0.5);
            vertical-align: super;
            margin-left: 2px;
        }

        .temp-label {
            font-size: 12px;
            font-weight: 500;
            color: rgba(255, 255, 255, 0.4);
            text-transform: uppercase;
            letter-spacing: 0.08em;
            margin-top: 6px;
        }

        .temp-separator {
            font-size: 32px;
            font-weight: 200;
            color: rgba(255, 255, 255, 0.2);
            align-self: center;
            padding-bottom: 14px;
        }

        .temp-range-bar {
            flex: 1;
            min-width: 80px;
            height: 4px;
            border-radius: 4px;
            background: rgba(255, 255, 255, 0.08);
            position: relative;
            align-self: center;
            margin-bottom: 14px;
            overflow: hidden;
        }

        .temp-range-fill {
            height: 100%;
            border-radius: 4px;
            background: linear-gradient(90deg, #42a5f5, #ffa726);
            transition: width 0.6s ease;
            position: relative;
        }

        .temp-range-dots {
            display: flex;
            justify-content: space-between;
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            pointer-events: none;
        }

        /* --- Chart --- */
        .chart-section {
            background: rgba(0, 0, 0, 0.2);
            border-radius: 16px;
            padding: 20px 20px 14px;
            border: 1px solid rgba(255, 255, 255, 0.04);
            margin-bottom: 6px;
        }

        .chart-title {
            font-size: 13px;
            font-weight: 500;
            color: rgba(255, 255, 255, 0.4);
            text-transform: uppercase;
            letter-spacing: 0.08em;
            margin-bottom: 14px;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .chart-title span {
            font-size: 16px;
        }

        .chart-wrapper {
            position: relative;
            width: 100%;
        }

        #hourlyChart {
            display: block;
            width: 100%;
            height: auto;
            aspect-ratio: 580/200;
            border-radius: 8px;
            background: rgba(0, 0, 0, 0.15);
        }

        .chart-x-labels {
            display: flex;
            justify-content: space-between;
            padding: 6px 6px 0 6px;
            font-size: 11px;
            font-weight: 450;
            color: rgba(255, 255, 255, 0.35);
            letter-spacing: 0.03em;
            user-select: none;
        }

        /* --- Footer --- */
        .card-footer {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding-top: 18px;
            font-size: 12px;
            color: rgba(255, 255, 255, 0.25);
            letter-spacing: 0.02em;
        }

        .card-footer .source {
            display: flex;
            align-items: center;
            gap: 6px;
        }

        .card-footer .source svg {
            width: 14px;
            height: 14px;
            opacity: 0.4;
        }

        .card-footer .update-time {
            color: rgba(255, 255, 255, 0.2);
        }

        /* --- Error --- */
        .error {
            display: none;
            background: rgba(255, 255, 255, 0.045);
            backdrop-filter: blur(20px);
            -webkit-backdrop-filter: blur(20px);
            border-radius: 28px;
            border: 1px solid rgba(255, 100, 100, 0.15);
            padding: 48px 44px;
            text-align: center;
            color: #fff;
            box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55);
        }

        .error.show {
            display: block;
        }

        .error .error-icon {
            font-size: 48px;
            margin-bottom: 16px;
        }

        .error .error-title {
            font-size: 22px;
            font-weight: 600;
            margin-bottom: 8px;
        }

        .error .error-detail {
            font-size: 15px;
            color: rgba(255, 255, 255, 0.55);
            line-height: 1.5;
        }

        .error .error-retry {
            margin-top: 20px;
            padding: 10px 28px;
            border-radius: 12px;
            border: 1px solid rgba(255, 255, 255, 0.12);
            background: rgba(255, 255, 255, 0.06);
            color: #fff;
            font-size: 14px;
            cursor: pointer;
            transition: background 0.2s;
        }

        .error .error-retry:hover {
            background: rgba(255, 255, 255, 0.12);
        }

        /* --- Loading --- */
        .loading {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            padding: 80px 0;
            color: rgba(255, 255, 255, 0.5);
            gap: 20px;
        }

        .loading .spinner {
            width: 36px;
            height: 36px;
            border: 3px solid rgba(255, 255, 255, 0.06);
            border-top-color: rgba(255, 255, 255, 0.3);
            border-radius: 50%;
            animation: spin 0.9s linear infinite;
        }

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

        .loading .loading-text {
            font-size: 15px;
            font-weight: 400;
            letter-spacing: 0.02em;
        }

        /* --- Hidden / Show --- */
        .hidden {
            display: none !important;
        }

        /* --- Responsive within 1280x800 --- */
        @media (max-height: 780px) {
            .card {
                padding: 28px 36px 24px;
            }
            .temp-value {
                font-size: 38px;
            }
            .temps-section {
                margin-bottom: 18px;
                gap: 20px;
            }
            .chart-section {
                padding: 14px 16px 10px;
            }
            .condition-emoji {
                font-size: 28px;
            }
            .condition-text {
                font-size: 16px;
            }
            .location-name {
                font-size: 20px;
            }
            .card-header {
                margin-bottom: 12px;
            }
            .divider {
                margin-bottom: 16px;
            }
            .condition-section {
                margin-bottom: 12px;
            }
            .temp-value .deg {
                font-size: 22px;
            }
            .temp-range-bar {
                height: 3px;
            }
        }

        @media (max-width: 800px) {
            body {
                padding: 20px;
            }
            .card {
                padding: 28px 24px 24px;
                border-radius: 20px;
            }
            .temp-value {
                font-size: 36px;
            }
            .temps-section {
                gap: 16px;
            }
            .location-name {
                font-size: 20px;
            }
        }
    </style>
</head>
<body>
    <div id="app">
        <!-- Card -->
        <div id="card" class="card hidden">
            <div class="card-header">
                <div class="location">
                    <span class="location-icon" id="locIcon">🌍</span>
                    <span class="location-name" id="locName">Berlin</span>
                </div>
                <div class="date" id="dateDisplay">June 15, 2024</div>
            </div>
            <div class="divider"></div>
            <div class="condition-section">
                <span class="condition-emoji" id="condEmoji">⛅</span>
                <span class="condition-text" id="condText">Partly Cloudy</span>
            </div>
            <div class="temps-section">
                <div class="temp-block">
                    <div class="temp-value" id="tempMax">24<span class="deg">°C</span></div>
                    <span class="temp-label">Maximum</span>
                </div>
                <div class="temp-separator">/</div>
                <div class="temp-block">
                    <div class="temp-value" id="tempMin">15<span class="deg">°C</span></div>
                    <span class="temp-label">Minimum</span>
                </div>
                <div style="flex:1;min-width:60px;align-self:center;margin-bottom:14px;padding:0 4px;">
                    <div class="temp-range-bar">
                        <div class="temp-range-fill" id="tempRangeFill" style="width:50%;"></div>
                    </div>
                </div>
            </div>
            <div class="chart-section">
                <div class="chart-title"><span>🌡</span> Hourly Temperature</div>
                <div class="chart-wrapper">
                    <canvas id="hourlyChart" width="580" height="200"></canvas>
                    <div class="chart-x-labels" id="chartLabels">
                        <span>00</span><span>03</span><span>06</span><span>09</span>
                        <span>12</span><span>15</span><span>18</span><span>21</span>
                    </div>
                </div>
            </div>
            <div class="card-footer">
                <span class="source">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
                        <path d="M12 2a10 10 0 1 0 10 10h-10V2z"/>
                        <path d="M12 12 20 12"/>
                        <path d="M12 4v8"/>
                    </svg>
                    Open-Meteo
                </span>
                <span class="update-time" id="updateTime"></span>
            </div>
        </div>

        <!-- Loading -->
        <div id="loading" class="loading">
            <div class="spinner"></div>
            <span class="loading-text">Loading weather …</span>
        </div>

        <!-- Error -->
        <div id="error" class="error">
            <div class="error-icon">⚠️</div>
            <div class="error-title">Unable to load weather data</div>
            <div class="error-detail" id="errorDetail">Please check your connection and try again.</div>
            <button class="error-retry" onclick="location.reload()">Retry</button>
        </div>
    </div>

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

            // ─── Weather Condition Map ────────────────────────────────────────
            const WEATHER = {
                0: { emoji: '☀️', label: 'Clear Sky' },
                1: { emoji: '🌤️', label: 'Mainly Clear' },
                2: { emoji: '⛅', label: 'Partly Cloudy' },
                3: { emoji: '☁️', label: 'Overcast' },
                45: { emoji: '🌫️', label: 'Foggy' },
                48: { emoji: '🌫️', label: 'Depositing Rime Fog' },
                51: { emoji: '🌦️', label: 'Light Drizzle' },
                53: { emoji: '🌦️', label: 'Moderate Drizzle' },
                55: { emoji: '🌦️', label: 'Dense Drizzle' },
                56: { emoji: '🌧️', label: 'Freezing Light Drizzle' },
                57: { emoji: '🌧️', label: 'Freezing Dense Drizzle' },
                61: { emoji: '🌧️', label: 'Slight Rain' },
                63: { emoji: '🌧️', label: 'Moderate Rain' },
                65: { emoji: '🌧️', label: 'Heavy Rain' },
                66: { emoji: '🌧️', label: 'Freezing Light Rain' },
                67: { emoji: '🌧️', label: 'Freezing Heavy Rain' },
                71: { emoji: '❄️', label: 'Slight Snow' },
                73: { emoji: '❄️', label: 'Moderate Snow' },
                75: { emoji: '❄️', label: 'Heavy Snow' },
                77: { emoji: '❄️', label: 'Snow Grains' },
                80: { emoji: '🌦️', label: 'Slight Rain Showers' },
                81: { emoji: '🌦️', label: 'Moderate Rain Showers' },
                82: { emoji: '🌦️', label: 'Violent Rain Showers' },
                85: { emoji: '❄️', label: 'Slight Snow Showers' },
                86: { emoji: '❄️', label: 'Heavy Snow Showers' },
                95: { emoji: '⛈️', label: 'Thunderstorm' },
                96: { emoji: '⛈️', label: 'Thunderstorm with Slight Hail' },
                99: { emoji: '⛈️', label: 'Thunderstorm with Heavy Hail' }
            };

            // ─── Theme Map (weather code → colours) ─────────────────────────
            function getTheme(code) {
                if (code === 0 || code === 1) return {
                    bg: 'linear-gradient(145deg, #0b0e1a 0%, #161b2e 40%, #1a1f3a 100%)',
                    accent: '#ffd54f',
                    chartLine: 'rgba(255,213,79,0.9)',
                    chartFill: 'rgba(255,213,79,0.18)',
                    chartFillTo: 'rgba(255,213,79,0.00)'
                };
                if (code === 2) return {
                    bg: 'linear-gradient(145deg, #0b111e 0%, #141e30 40%, #1a2a40 100%)',
                    accent: '#81d4fa',
                    chartLine: 'rgba(129,212,250,0.9)',
                    chartFill: 'rgba(129,212,250,0.18)',
                    chartFillTo: 'rgba(129,212,250,0.00)'
                };
                if (code === 3) return {
                    bg: 'linear-gradient(145deg, #0a0f1a 0%, #121e2a 40%, #1a2a3a 100%)',
                    accent: '#b0bec5',
                    chartLine: 'rgba(176,190,197,0.9)',
                    chartFill: 'rgba(176,190,197,0.15)',
                    chartFillTo: 'rgba(176,190,197,0.00)'
                };
                if (code >= 95) return {
                    bg: 'linear-gradient(145deg, #0a0818 0%, #18102a 40%, #2a1040 100%)',
                    accent: '#ce93d8',
                    chartLine: 'rgba(206,147,216,0.9)',
                    chartFill: 'rgba(206,147,216,0.18)',
                    chartFillTo: 'rgba(206,147,216,0.00)'
                };
                if (code >= 71 && code <= 86) return {
                    bg: 'linear-gradient(145deg, #0c1420 0%, #182838 40%, #203848 100%)',
                    accent: '#e0e0e0',
                    chartLine: 'rgba(224,224,224,0.85)',
                    chartFill: 'rgba(224,224,224,0.15)',
                    chartFillTo: 'rgba(224,224,224,0.00)'
                };
                // rain, drizzle, fog → cool blue
                return {
                    bg: 'linear-gradient(145deg, #0a1220 0%, #0f1e30 40%, #142840 100%)',
                    accent: '#64b5f6',
                    chartLine: 'rgba(100,181,246,0.9)',
                    chartFill: 'rgba(100,181,246,0.18)',
                    chartFillTo: 'rgba(100,181,246,0.00)'
                };
            }

            // ─── DOM refs ────────────────────────────────────────────────────
            const $ = id => document.getElementById(id);
            const cardEl = $('card');
            const loadingEl = $('loading');
            const errorEl = $('error');
            const errorDetail = $('errorDetail');
            const locName = $('locName');
            const locIcon = $('locIcon');
            const dateDisplay = $('dateDisplay');
            const condEmoji = $('condEmoji');
            const condText = $('condText');
            const tempMax = $('tempMax');
            const tempMin = $('tempMin');
            const tempRangeFill = $('tempRangeFill');
            const canvas = $('hourlyChart');
            const chartLabels = $('chartLabels');
            const updateTime = $('updateTime');

            // ─── Helpers ─────────────────────────────────────────────────────
            function pad(n) { return String(n).padStart(2, '0'); }

            function formatDate(iso) {
                const [y, m, d] = iso.split('-').map(Number);
                const dt = new Date(Date.UTC(y, m - 1, d));
                return dt.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric', timeZone: 'UTC' });
            }

            function todayUTC() {
                const d = new Date();
                return d.getUTCFullYear() + '-' + pad(d.getUTCMonth() + 1) + '-' + pad(d.getUTCDate());
            }

            function parseParams() {
                const url = new URL(window.location.href);
                const lat = parseFloat(url.searchParams.get('lat')) || 52.52;
                const lon = parseFloat(url.searchParams.get('lon')) || 13.405;
                const name = url.searchParams.get('name') || 'Berlin';
                const date = url.searchParams.get('date') || todayUTC();
                return { lat, lon, name, date };
            }

            function isPastDate(iso) {
                return iso < todayUTC();
            }

            function getWeatherInfo(code) {
                return WEATHER[code] || { emoji: '❓', label: 'Unknown' };
            }

            // ─── API ─────────────────────────────────────────────────────────
            async function fetchWeather(lat, lon, date) {
                const past = isPastDate(date);
                const base = past ? '/api/om/archive' : '/api/om/forecast';
                const params = new URLSearchParams({
                    latitude: lat,
                    longitude: lon,
                    daily: 'temperature_2m_max,temperature_2m_min,weather_code',
                    hourly: 'temperature_2m',
                    timezone: 'UTC',
                    temperature_unit: 'celsius',
                    start_date: date,
                    end_date: date
                });
                const url = base + '?' + params.toString();
                const resp = await fetch(url);
                if (!resp.ok) {
                    throw new Error('API responded with status ' + resp.status);
                }
                return resp.json();
            }

            // ─── Chart ───────────────────────────────────────────────────────
            function drawChart(canvasEl, hourlyTemps, theme) {
                const ctx = canvasEl.getContext('2d');
                const rect = canvasEl.getBoundingClientRect();
                const dpr = window.devicePixelRatio || 1;
                const w = canvasEl.width;
                const h = canvasEl.height;

                ctx.clearRect(0, 0, w, h);

                const temps = hourlyTemps;
                if (!temps || temps.length < 2) {
                    ctx.fillStyle = 'rgba(255,255,255,0.2)';
                    ctx.font = '14px sans-serif';
                    ctx.textAlign = 'center';
                    ctx.fillText('Insufficient data', w / 2, h / 2 + 5);
                    return;
                }

                const n = temps.length;
                const minT = Math.min(...temps);
                const maxT = Math.max(...temps);
                const range = maxT - minT || 1;

                // padding
                const padL = 38;
                const padR = 16;
                const padT = 16;
                const padB = 8;
                const chartW = w - padL - padR;
                const chartH = h - padT - padB;

                const xScale = i => padL + (i / (n - 1)) * chartW;
                const yScale = v => padT + chartH - ((v - minT) / range) * chartH;

                // ── grid & Y labels ──
                const gridSteps = 4;
                ctx.textAlign = 'right';
                ctx.textBaseline = 'middle';
                for (let i = 0; i <= gridSteps; i++) {
                    const frac = i / gridSteps;
                    const y = padT + frac * chartH;
                    const val = maxT - frac * range;

                    ctx.beginPath();
                    ctx.moveTo(padL, y);
                    ctx.lineTo(w - padR, y);
                    ctx.strokeStyle = 'rgba(255,255,255,0.045)';
                    ctx.lineWidth = 1;
                    ctx.stroke();

                    ctx.fillStyle = 'rgba(255,255,255,0.3)';
                    ctx.font = '10px -apple-system, BlinkMacSystemFont, sans-serif';
                    ctx.fillText(Math.round(val) + '°', padL - 8, y);
                }

                // ── area fill ──
                ctx.beginPath();
                ctx.moveTo(xScale(0), yScale(temps[0]));
                for (let i = 1; i < n; i++) {
                    ctx.lineTo(xScale(i), yScale(temps[i]));
                }
                ctx.lineTo(xScale(n - 1), h - padB);
                ctx.lineTo(xScale(0), h - padB);
                ctx.closePath();

                const grad = ctx.createLinearGradient(0, padT, 0, h - padB);
                grad.addColorStop(0, theme.chartFill);
                grad.addColorStop(1, theme.chartFillTo || 'rgba(100,181,246,0)');
                ctx.fillStyle = grad;
                ctx.fill();

                // ── line ──
                ctx.beginPath();
                for (let i = 0; i < n; i++) {
                    const x = xScale(i);
                    const y = yScale(temps[i]);
                    if (i === 0) ctx.moveTo(x, y);
                    else ctx.lineTo(x, y);
                }

                const lineGrad = ctx.createLinearGradient(0, 0, w, 0);
                lineGrad.addColorStop(0, theme.chartLine);
                lineGrad.addColorStop(0.5, theme.chartLine);
                lineGrad.addColorStop(1, theme.chartLine);
                ctx.strokeStyle = lineGrad;
                ctx.lineWidth = 2.2;
                ctx.lineJoin = 'round';
                ctx.lineCap = 'round';
                ctx.stroke();

                // ── dots ──
                for (let i = 0; i < n; i++) {
                    const x = xScale(i);
                    const y = yScale(temps[i]);
                    ctx.beginPath();
                    ctx.arc(x, y, 3.2, 0, 2 * Math.PI);
                    ctx.fillStyle = 'rgba(255,255,255,0.85)';
                    ctx.fill();
                    ctx.beginPath();
                    ctx.arc(x, y, 1.6, 0, 2 * Math.PI);
                    ctx.fillStyle = theme.chartLine;
                    ctx.fill();
                }

                // ── X labels ──
                const labelIndices = [];
                for (let i = 0; i < n; i++) {
                    if (i === 0 || i === n - 1 || i % 3 === 0) labelIndices.push(i);
                }
                ctx.textAlign = 'center';
                ctx.textBaseline = 'top';
                ctx.fillStyle = 'rgba(255,255,255,0.3)';
                ctx.font = '10px -apple-system, BlinkMacSystemFont, sans-serif';
                for (const i of labelIndices) {
                    const x = xScale(i);
                    ctx.fillText(pad(i), x, h - padB + 4);
                }
            }

            // ─── Render ──────────────────────────────────────────────────────
            function render(data, params) {
                const { daily, hourly } = data;

                if (!daily || !daily.time || daily.time.length === 0) {
                    throw new Error('No daily data available for this date.');
                }

                const idx = 0;
                const maxTemp = daily.temperature_2m_max[idx];
                const minTemp = daily.temperature_2m_min[idx];
                const weatherCode = daily.weather_code[idx];
                const weather = getWeatherInfo(weatherCode);
                const theme = getTheme(weatherCode);

                // Location
                locName.textContent = params.name;
                locIcon.textContent = weather.emoji;

                // Date
                dateDisplay.textContent = formatDate(params.date);

                // Condition
                condEmoji.textContent = weather.emoji;
                condText.textContent = weather.label;

                // Temperatures
                tempMax.innerHTML = Math.round(maxTemp) + '<span class="deg">°C</span>';
                tempMin.innerHTML = Math.round(minTemp) + '<span class="deg">°C</span>';

                // Range bar
                const allTemps = [];
                if (hourly && hourly.temperature_2m) {
                    for (const t of hourly.temperature_2m) allTemps.push(t);
                }
                allTemps.push(maxTemp, minTemp);
                const globalMin = Math.min(...allTemps);
                const globalMax = Math.max(...allTemps);
                const globalRange = globalMax - globalMin || 1;
                const mid = (minTemp + maxTemp) / 2;
                // Position the fill from min to max as a fraction of the range
                const fromFrac = (minTemp - globalMin) / globalRange;
                const toFrac = (maxTemp - globalMin) / globalRange;
                const leftPct = fromFrac * 100;
                const widthPct = (toFrac - fromFrac) * 100;
                tempRangeFill.style.marginLeft = leftPct + '%';
                tempRangeFill.style.width = Math.max(widthPct, 4) + '%';
                tempRangeFill.style.background = 'linear-gradient(90deg, #42a5f5, #ffa726)';

                // Theme background
                document.body.style.background = theme.bg;

                // Chart
                if (hourly && hourly.temperature_2m && hourly.temperature_2m.length > 0) {
                    const filtered = [];
                    const times = hourly.time || [];
                    // Filter only the entries for the requested date
                    const datePrefix = params.date;
                    for (let i = 0; i < times.length; i++) {
                        if (times[i].startsWith(datePrefix)) {
                            filtered.push(hourly.temperature_2m[i]);
                        }
                    }
                    // If filtering didn't work (API might already filter), use all
                    const chartData = filtered.length > 0 ? filtered : hourly.temperature_2m;
                    drawChart(canvas, chartData, theme);

                    // Update X labels every 3 hours
                    const n = chartData.length;
                    const steps = [0];
                    for (let i = 3; i < n - 1; i += 3) steps.push(i);
                    if (n > 1 && steps[steps.length - 1] !== n - 1) steps.push(n - 1);
                    const labelEls = chartLabels.querySelectorAll('span');
                    for (let i = 0; i < labelEls.length; i++) {
                        if (i < steps.length) {
                            labelEls[i].textContent = pad(steps[i]);
                            labelEls[i].style.display = 'inline';
                        } else {
                            labelEls[i].style.display = 'none';
                        }
                    }
                } else {
                    // No hourly data – show empty chart
                    const ctx = canvas.getContext('2d');
                    ctx.clearRect(0, 0, canvas.width, canvas.height);
                    ctx.fillStyle = 'rgba(255,255,255,0.15)';
                    ctx.font = '13px sans-serif';
                    ctx.textAlign = 'center';
                    ctx.fillText('Hourly data unavailable', canvas.width / 2, canvas.height / 2 + 4);
                }

                // Update time
                const now = new Date();
                const timeStr = now.toUTCString().split(' ')[4] || '';
                updateTime.textContent = timeStr ? 'Updated ' + timeStr + ' UTC' : '';

                // Show card
                loadingEl.classList.add('hidden');
                cardEl.classList.remove('hidden');
                errorEl.classList.remove('show');
                errorEl.classList.add('hidden');
            }

            // ─── Error ───────────────────────────────────────────────────────
            function showError(message) {
                loadingEl.classList.add('hidden');
                cardEl.classList.add('hidden');
                errorEl.classList.add('show');
                errorEl.classList.remove('hidden');
                errorDetail.textContent = message || 'An unexpected error occurred. Please try again later.';
            }

            // ─── Main ────────────────────────────────────────────────────────
            async function main() {
                const params = parseParams();

                // Validate date format
                if (!/^\d{4}-\d{2}-\d{2}$/.test(params.date)) {
                    showError('Invalid date format. Please use YYYY-MM-DD.');
                    return;
                }

                try {
                    const data = await fetchWeather(params.lat, params.lon, params.date);
                    render(data, params);
                } catch (err) {
                    console.error(err);
                    let msg = err.message || 'Could not load weather data.';
                    if (msg.includes('fetch')) msg = 'Network error. Please check your connection.';
                    if (msg.includes('status 4')) msg = 'Data not available for the requested location or date.';
                    showError(msg);
                }
            }

            // ─── Start ───────────────────────────────────────────────────────
            // Show loading first
            loadingEl.classList.remove('hidden');
            cardEl.classList.add('hidden');
            errorEl.classList.add('hidden');
            errorEl.classList.remove('show');

            // If canvas context fails for some reason
            if (!canvas.getContext) {
                showError('Your browser does not support canvas rendering.');
            } else {
                main();
            }

            // Handle resize to redraw chart (debounced)
            let resizeTimer;
            window.addEventListener('resize', function() {
                clearTimeout(resizeTimer);
                resizeTimer = setTimeout(function() {
                    if (!cardEl.classList.contains('hidden')) {
                        // Re-draw chart at new size – canvas resolution stays same, but we re-apply DPR
                        const hourlyData = window._lastHourlyData;
                        const theme = window._lastTheme;
                        if (hourlyData && theme) {
                            drawChart(canvas, hourlyData, theme);
                        }
                    }
                }, 200);
            });

            // Store last data for redraw (chart only)
            const origRender = render;
            render = function(data, params) {
                const { daily, hourly } = data;
                const weatherCode = daily.weather_code[0];
                const theme = getTheme(weatherCode);
                window._lastTheme = theme;

                if (hourly && hourly.temperature_2m) {
                    const filtered = [];
                    const times = hourly.time || [];
                    const datePrefix = params.date;
                    for (let i = 0; i < times.length; i++) {
                        if (times[i].startsWith(datePrefix)) {
                            filtered.push(hourly.temperature_2m[i]);
                        }
                    }
                    window._lastHourlyData = filtered.length > 0 ? filtered : hourly.temperature_2m;
                } else {
                    window._lastHourlyData = null;
                }

                origRender(data, params);
            };

            // Expose for potential debugging
            window.__weatherApp = { params: parseParams() };

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

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

bytes 总计36,573html / css / js3,450 / 10,769 / 22,354
dom 节点61dom 深度8
css 规则65含 js
brightness32.5contrast12.9
colorfulness14.4留白91.9%

该 slot 的 telemetry

prompt tokens400completion tokens22,827
total tokens23,227wall147.3 s
costrequest id
config.json577 B
{
  "N": 2,
  "auth": {
    "credential_ref": "WCB_ZEN_KEY",
    "method": "api-key"
  },
  "billing": "plan",
  "config_id": "deepseek-v4-flash-free--zen-free--dev",
  "effort": null,
  "family": "deepseek",
  "m": {
    "min": 2,
    "q": 2
  },
  "model_id": "deepseek-v4-flash-free",
  "protocol": "api",
  "served_model": "deepseek-v4-flash-free",
  "telemetry": {
    "completion_tokens": 59595,
    "cost_usd": null,
    "prompt_tokens": 1584,
    "total_tokens": 61179,
    "wall_ms": 405287
  },
  "transport": "opencode-zen-free-tier",
  "vendor_sanction_ref": null
}
send-log2.5 KB
{
  "N": 2,
  "batch_id": "2026-07-18--dev-zen-free",
  "config_id": "deepseek-v4-flash-free--zen-free--dev",
  "positions": [
    {
      "attempts": [
        {
          "attempt_index": 0,
          "backoff_ms": null,
          "charged": true,
          "ended_at": "2026-07-18T00:47:55.879185+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-18T00:47:11.107389+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:50:26.087032+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-18T00:47:58.815750+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:52:14.610575+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-18T00:50:30.127961+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-18T00:54:08.198731+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-18T00:52:19.435910+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
    }
  }
}