gpt-5.4 @ xhigh · api

apixhigh
config
gpt-5.4-eff-xhigh--api--official--dev
batch
2026-07-19--unified
transport
openai-official-api
protocol
api · metered
served model
gpt-5.4-2026-03-05
采集于
2026年7月18日 11:54 · N=4
variant
live
slot 0 · P-q · sandboxed iframe
gpt-5.4 @xhigh · api P-q slot 0 截图

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

similarity 邻居

限于 session 2026-07-19--unified

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

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

  • namematch
  • conditionmatch
  • datematch
  • max tempmatch展示值:25.3
  • min tempmatch展示值:16
  • hourlyambiguouscoverage · 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=device-width, initial-scale=1.0">
<title>Weather Card</title>
<style>
:root {
  color-scheme: dark;
  --bg1:#09162f;
  --bg2:#1d67c2;
  --glow1:255,197,92;
  --glow2:119,209,255;
  --accent-rgb:255,209,102;
  --accent2-rgb:110,193,255;
  --line-rgb:150,215,255;
  --text-rgb:245,247,251;
}
*{box-sizing:border-box}
html,body{width:100%;height:100%;margin:0}
body{
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  color: rgb(var(--text-rgb));
  background:
    radial-gradient(1000px 700px at 14% 14%, rgba(var(--glow1), .30), transparent 60%),
    radial-gradient(900px 620px at 86% 18%, rgba(var(--glow2), .28), transparent 58%),
    linear-gradient(135deg, var(--bg1), var(--bg2));
  display:flex;
  align-items:center;
  justify-content:center;
  overflow:hidden;
}
body::before{
  content:"";
  position:fixed;
  inset:-10%;
  background:
    radial-gradient(circle at 20% 30%, rgba(255,255,255,.06) 0 1px, transparent 1.2px) 0 0/18px 18px,
    radial-gradient(circle at 70% 65%, rgba(255,255,255,.04) 0 1px, transparent 1.2px) 9px 9px/24px 24px;
  opacity:.18;
  mix-blend-mode:soft-light;
  pointer-events:none;
}
body::after{
  content:"";
  position:fixed;
  inset:0;
  background:radial-gradient(circle at center, transparent 48%, rgba(0,0,0,.32) 100%);
  pointer-events:none;
}
.frame{
  width:100%;
  height:100%;
  padding:40px;
  display:flex;
  align-items:center;
  justify-content:center;
  position:relative;
}
.orb{
  position:absolute;
  border-radius:50%;
  filter:blur(72px);
  opacity:.35;
  pointer-events:none;
  animation:drift 18s ease-in-out infinite alternate;
}
.orb.one{width:260px;height:260px;left:9%;top:10%;background:rgba(var(--glow1),.45)}
.orb.two{width:320px;height:320px;right:10%;bottom:12%;background:rgba(var(--glow2),.35);animation-duration:22s}
.card{
  position:relative;
  width:min(1140px, calc(100vw - 80px));
  height:min(680px, calc(100vh - 80px));
  border-radius:32px;
  background:
    linear-gradient(180deg, rgba(255,255,255,.16), rgba(255,255,255,.06)),
    linear-gradient(135deg, rgba(12,20,34,.66), rgba(11,19,31,.42));
  border:1px solid rgba(255,255,255,.16);
  box-shadow:
    0 40px 100px rgba(0,0,0,.36),
    inset 0 1px 0 rgba(255,255,255,.14),
    inset 0 -1px 0 rgba(255,255,255,.05);
  overflow:hidden;
  backdrop-filter:blur(24px) saturate(130%);
}
.card::before{
  content:"";
  position:absolute;
  inset:0;
  background:
    radial-gradient(520px 260px at 84% 10%, rgba(var(--glow2), .18), transparent 60%),
    radial-gradient(420px 240px at 12% 12%, rgba(var(--glow1), .15), transparent 62%);
  pointer-events:none;
}
.card::after{
  content:"";
  position:absolute;
  inset:0;
  border-radius:32px;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.04);
  pointer-events:none;
}
.status{
  position:absolute;
  inset:0;
  z-index:3;
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:16px;
  padding:40px;
  text-align:center;
}
.hidden{display:none!important}
.spinner{
  width:54px;
  height:54px;
  border-radius:50%;
  border:3px solid rgba(255,255,255,.16);
  border-top-color:rgba(var(--accent2-rgb), .95);
  animation:spin .9s linear infinite;
  box-shadow:0 0 0 6px rgba(255,255,255,.02);
}
.status-title{
  font-size:30px;
  line-height:1.05;
  font-weight:780;
  letter-spacing:-0.04em;
}
.status-sub{
  max-width:480px;
  font-size:15px;
  line-height:1.6;
  color:rgba(245,247,251,.72);
}
.status.error .spinner{display:none}
.content{
  position:relative;
  z-index:1;
  height:100%;
  padding:34px;
  display:grid;
  grid-template-rows:286px 1fr;
  gap:26px;
}
.top{
  display:grid;
  grid-template-columns:1.08fr 0.92fr;
  gap:24px;
}
.panel{
  border-radius:28px;
  background:linear-gradient(180deg, rgba(255,255,255,.08), rgba(255,255,255,.04));
  border:1px solid rgba(255,255,255,.12);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.08),
    0 14px 36px rgba(0,0,0,.08);
}
.summary{
  padding:30px 30px 28px;
  display:flex;
  flex-direction:column;
}
.eyebrow{
  font-size:11px;
  font-weight:800;
  letter-spacing:.18em;
  text-transform:uppercase;
  color:rgba(245,247,251,.58);
}
.location{
  margin:14px 0 0;
  font-size:clamp(40px, 4.2vw, 56px);
  line-height:.92;
  letter-spacing:-0.06em;
  font-weight:820;
  max-width:520px;
  overflow-wrap:anywhere;
}
.date-line{
  margin-top:18px;
  display:flex;
  align-items:center;
  gap:10px;
  flex-wrap:wrap;
}
.pill{
  padding:10px 14px;
  border-radius:999px;
  background:rgba(255,255,255,.08);
  border:1px solid rgba(255,255,255,.12);
  font-size:14px;
  color:rgba(245,247,251,.92);
  white-space:nowrap;
}
.pill.subtle{
  color:rgba(245,247,251,.68);
}
.condition-block{
  margin-top:30px;
}
.label{
  font-size:12px;
  font-weight:800;
  letter-spacing:.16em;
  text-transform:uppercase;
  color:rgba(245,247,251,.56);
}
.condition{
  margin-top:12px;
  font-size:40px;
  line-height:1.03;
  letter-spacing:-0.04em;
  font-weight:780;
  max-width:450px;
}
.summary-copy{
  margin-top:14px;
  max-width:480px;
  font-size:16px;
  line-height:1.65;
  color:rgba(245,247,251,.72);
}
.meta-row{
  margin-top:auto;
  display:flex;
  gap:12px;
  flex-wrap:wrap;
}
.meta-chip{
  padding:11px 14px;
  border-radius:16px;
  background:rgba(255,255,255,.06);
  border:1px solid rgba(255,255,255,.10);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.04);
  font-size:13px;
  color:rgba(245,247,251,.74);
}
.details{
  padding:22px;
  display:grid;
  grid-template-rows:1fr 118px;
  gap:18px;
}
.hero-visual{
  position:relative;
  overflow:hidden;
  border-radius:24px;
  background:
    linear-gradient(180deg, rgba(255,255,255,.08), rgba(255,255,255,.02)),
    radial-gradient(circle at 50% 30%, rgba(var(--glow1), .12), transparent 55%);
  border:1px solid rgba(255,255,255,.12);
  display:flex;
  align-items:center;
  justify-content:center;
  min-height:0;
}
.hero-visual::before{
  content:"";
  position:absolute;
  width:320px;
  height:320px;
  border-radius:50%;
  background:
    radial-gradient(circle, rgba(var(--glow1), .42), rgba(var(--glow2), .14) 52%, transparent 70%);
  filter:blur(18px);
}
.hero-badges{
  position:absolute;
  top:16px;
  left:16px;
  right:16px;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:10px;
  z-index:2;
}
.hero-chip{
  padding:9px 12px;
  border-radius:999px;
  background:rgba(7,12,22,.22);
  border:1px solid rgba(255,255,255,.12);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05);
  font-size:12px;
  font-weight:700;
  color:rgba(245,247,251,.82);
  backdrop-filter:blur(8px);
  white-space:nowrap;
}
.hero-range{
  position:absolute;
  left:18px;
  bottom:18px;
  padding:12px 14px;
  border-radius:18px;
  background:rgba(7,12,22,.26);
  border:1px solid rgba(255,255,255,.12);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05);
  backdrop-filter:blur(10px);
}
.hero-range .small{
  font-size:11px;
  font-weight:800;
  letter-spacing:.16em;
  text-transform:uppercase;
  color:rgba(245,247,251,.56);
}
.hero-range .big{
  margin-top:6px;
  font-size:30px;
  line-height:1;
  font-weight:820;
  letter-spacing:-0.05em;
}
.metric-grid{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:18px;
}
.temp-card{
  padding:18px 20px;
  border-radius:22px;
  background:rgba(255,255,255,.07);
  border:1px solid rgba(255,255,255,.12);
  display:flex;
  flex-direction:column;
  justify-content:space-between;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.06);
}
.temp-card .value{
  margin-top:10px;
  font-size:54px;
  line-height:.95;
  font-weight:830;
  letter-spacing:-0.06em;
}
.temp-card.max .value{color:rgba(var(--accent-rgb), .98)}
.temp-card.min .value{color:rgba(var(--accent2-rgb), .96)}
.bottom{
  padding:24px 26px 22px;
  display:grid;
  grid-template-rows:auto 1fr auto;
  gap:18px;
}
.section-head{
  display:flex;
  align-items:flex-end;
  justify-content:space-between;
  gap:18px;
}
.section-title{
  margin:0;
  font-size:24px;
  line-height:1;
  font-weight:780;
  letter-spacing:-0.04em;
}
.section-sub{
  font-size:14px;
  color:rgba(245,247,251,.66);
  white-space:nowrap;
}
.chart-wrap{
  position:relative;
  border-radius:24px;
  overflow:hidden;
  background:
    linear-gradient(180deg, rgba(5,8,14,.18), rgba(255,255,255,.03)),
    radial-gradient(circle at 50% 0%, rgba(var(--line-rgb), .08), transparent 45%);
  border:1px solid rgba(255,255,255,.10);
}
.chart-wrap::after{
  content:"";
  position:absolute;
  left:0; right:0; bottom:0;
  height:88px;
  background:linear-gradient(180deg, transparent, rgba(8,12,20,.22));
  pointer-events:none;
}
#chart{
  width:100%;
  height:232px;
  display:block;
}
.chart-empty{
  position:absolute;
  inset:0;
  display:flex;
  align-items:center;
  justify-content:center;
  font-size:15px;
  color:rgba(245,247,251,.64);
}
.hourly-strip{
  display:grid;
  gap:8px;
}
.hour-cell{
  position:relative;
  overflow:hidden;
  padding:11px 4px 12px;
  border-radius:16px;
  text-align:center;
  background:linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.03));
  border:1px solid rgba(255,255,255,.08);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.04);
}
.hour-cell::before{
  content:"";
  position:absolute;
  inset:0;
  background:linear-gradient(180deg, rgba(var(--cell-rgb, 255,255,255), var(--cell-alpha, .08)), rgba(255,255,255,.01));
  opacity:.9;
  pointer-events:none;
}
.hour-cell > *{position:relative;z-index:1}
.hour-cell.warm{outline:1px solid rgba(var(--accent-rgb), .42)}
.hour-cell.cold{outline:1px solid rgba(var(--accent2-rgb), .38)}
.hour-time{
  font-size:12px;
  font-weight:760;
  color:rgba(245,247,251,.58);
  letter-spacing:.02em;
}
.hour-temp{
  margin-top:8px;
  font-size:18px;
  line-height:1;
  font-weight:790;
  letter-spacing:-0.04em;
}
.hour-marker{
  margin:10px auto 0;
  width:8px;
  height:8px;
  border-radius:50%;
  background:rgba(255,255,255,.85);
  box-shadow:0 0 0 4px rgba(255,255,255,.08);
}
.weather-icon{
  position:relative;
  z-index:1;
  width:186px;
  height:186px;
  filter:drop-shadow(0 16px 34px rgba(0,0,0,.18));
}
.weather-icon .sun-ray{
  stroke:#ffd66b;
  stroke-width:8;
  stroke-linecap:round;
}
.weather-icon .cloud-fill{fill:#f7fbff}
.weather-icon .cloud-shadow{fill:rgba(196,210,228,.68)}
.weather-icon .raindrop{
  stroke:#8ed1ff;
  stroke-width:9;
  stroke-linecap:round;
}
.weather-icon .snowflake{
  stroke:#e7f7ff;
  stroke-width:6.5;
  stroke-linecap:round;
}
.weather-icon .bolt{fill:#ffe680}
.weather-icon .fog-line{
  stroke:#eef4fb;
  stroke-width:8;
  stroke-linecap:round;
  opacity:.95;
}
.weather-icon .sun-group{
  transform-origin:100px 100px;
  animation:spinSlow 24s linear infinite;
}
.weather-icon .cloud-group{animation:floatSoft 4.8s ease-in-out infinite alternate}
.weather-icon .drop-group{animation:bob 2.6s ease-in-out infinite alternate}
.weather-icon .bolt{
  animation:flicker 2.8s ease-in-out infinite;
  transform-origin:100px 128px;
}
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes drift{
  from{transform:translate3d(0,0,0) scale(1)}
  to{transform:translate3d(18px,-16px,0) scale(1.08)}
}
@keyframes spinSlow{to{transform:rotate(360deg)}}
@keyframes floatSoft{
  from{transform:translateY(-2px)}
  to{transform:translateY(4px)}
}
@keyframes bob{
  from{transform:translateY(0)}
  to{transform:translateY(6px)}
}
@keyframes flicker{
  0%,68%,100%{transform:scale(1);opacity:1}
  72%{transform:scale(1.03);opacity:.92}
  76%{transform:scale(.98);opacity:.86}
}
@media (max-width:1080px){
  .content{grid-template-rows:auto auto}
  .top{grid-template-columns:1fr}
  .details{grid-template-rows:260px 118px}
  #chart{height:210px}
}
</style>
</head>
<body>
<div class="frame">
  <div class="orb one"></div>
  <div class="orb two"></div>
  <main class="card" role="main" aria-label="Weather card">
    <div id="status" class="status" aria-live="polite">
      <div class="spinner" aria-hidden="true"></div>
      <div class="status-title">Loading weather…</div>
      <div class="status-sub" id="statusSub">Preparing the daily summary and hourly temperatures.</div>
    </div>

    <div id="content" class="content hidden">
      <div class="top">
        <section class="panel summary">
          <div>
            <div class="eyebrow">Weather card</div>
            <h1 class="location" id="locationName">Berlin</h1>
            <div class="date-line">
              <div class="pill" id="prettyDate">—</div>
              <div class="pill subtle" id="rawDate">—</div>
              <div class="pill subtle">UTC</div>
            </div>
            <div class="condition-block">
              <div class="label">Condition</div>
              <div class="condition" id="conditionText">—</div>
              <div class="summary-copy" id="summaryCopy">Daily highs, lows, and the full hourly temperature curve for the selected day.</div>
            </div>
          </div>
          <div class="meta-row">
            <div class="meta-chip" id="coordChip">Coords 52.520, 13.405</div>
            <div class="meta-chip" id="modeChip">Forecast data</div>
          </div>
        </section>

        <section class="panel details">
          <div class="hero-visual">
            <div class="hero-badges">
              <div class="hero-chip" id="codeChip">WMO 0</div>
              <div class="hero-chip">UTC day</div>
            </div>
            <div id="heroIcon" aria-hidden="true"></div>
            <div class="hero-range">
              <div class="small">Daily span</div>
              <div class="big" id="rangeValue">—</div>
            </div>
          </div>
          <div class="metric-grid">
            <div class="temp-card max">
              <div class="label">Maximum</div>
              <div class="value" id="tempMax">—</div>
            </div>
            <div class="temp-card min">
              <div class="label">Minimum</div>
              <div class="value" id="tempMin">—</div>
            </div>
          </div>
        </section>
      </div>

      <section class="panel bottom">
        <div class="section-head">
          <h2 class="section-title">Hourly temperatures</h2>
          <div class="section-sub" id="hourlyMeta">—</div>
        </div>
        <div class="chart-wrap">
          <svg id="chart" viewBox="0 0 1000 232" preserveAspectRatio="none" aria-label="Hourly temperature chart"></svg>
          <div id="chartEmpty" class="chart-empty hidden">Hourly detail unavailable for this date.</div>
        </div>
        <div class="hourly-strip" id="hourlyStrip"></div>
      </section>
    </div>
  </main>
</div>

<script>
(() => {
  const DEFAULTS = {
    lat: 52.52,
    lon: 13.405,
    name: 'Berlin',
    date: todayUTCString()
  };

  const THEMES = {
    clear: {
      bg1: '#09162f',
      bg2: '#1d67c2',
      glow1: '255,197,92',
      glow2: '119,209,255',
      accent: '255,209,102',
      accent2: '110,193,255',
      line: '150,215,255'
    },
    partly: {
      bg1: '#0b1730',
      bg2: '#3567b7',
      glow1: '255,209,122',
      glow2: '158,220,255',
      accent: '255,215,126',
      accent2: '136,207,255',
      line: '165,223,255'
    },
    cloudy: {
      bg1: '#121927',
      bg2: '#48576d',
      glow1: '215,223,234',
      glow2: '166,181,203',
      accent: '225,235,247',
      accent2: '166,191,220',
      line: '189,211,233'
    },
    fog: {
      bg1: '#1a1f29',
      bg2: '#5f6979',
      glow1: '230,236,244',
      glow2: '185,192,202',
      accent: '228,236,246',
      accent2: '188,202,219',
      line: '210,220,231'
    },
    rain: {
      bg1: '#071321',
      bg2: '#214f7a',
      glow1: '104,187,255',
      glow2: '55,116,182',
      accent: '120,203,255',
      accent2: '173,224,255',
      line: '136,206,255'
    },
    snow: {
      bg1: '#091827',
      bg2: '#5483aa',
      glow1: '233,247,255',
      glow2: '162,212,255',
      accent: '239,249,255',
      accent2: '173,224,255',
      line: '199,236,255'
    },
    storm: {
      bg1: '#140c27',
      bg2: '#35205e',
      glow1: '187,142,255',
      glow2: '255,221,94',
      accent: '199,167,255',
      accent2: '255,226,123',
      line: '189,190,255'
    }
  };

  const status = document.getElementById('status');
  const statusSub = document.getElementById('statusSub');
  const content = document.getElementById('content');
  const chartEmpty = document.getElementById('chartEmpty');
  const el = {
    locationName: document.getElementById('locationName'),
    prettyDate: document.getElementById('prettyDate'),
    rawDate: document.getElementById('rawDate'),
    conditionText: document.getElementById('conditionText'),
    summaryCopy: document.getElementById('summaryCopy'),
    coordChip: document.getElementById('coordChip'),
    modeChip: document.getElementById('modeChip'),
    codeChip: document.getElementById('codeChip'),
    heroIcon: document.getElementById('heroIcon'),
    rangeValue: document.getElementById('rangeValue'),
    tempMax: document.getElementById('tempMax'),
    tempMin: document.getElementById('tempMin'),
    hourlyMeta: document.getElementById('hourlyMeta'),
    chart: document.getElementById('chart'),
    hourlyStrip: document.getElementById('hourlyStrip')
  };

  init().catch((err) => {
    console.error(err);
    showError("Weather data couldn’t be loaded.", "Please verify the location parameters and try again.");
  });

  async function init() {
    const params = readParams();
    document.title = `${params.name} — Weather for ${params.date}`;
    statusSub.textContent = `Fetching ${params.name} for ${params.date} (UTC).`;

    const today = todayUTCString();
    const isPast = params.date < today;
    const endpoint = isPast ? '/api/om/archive' : '/api/om/forecast';

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

    const url = `${endpoint}?${query.toString()}`;
    const response = await fetchWithTimeout(url, 12000);
    if (!response.ok) throw new Error(`HTTP ${response.status}`);

    const data = await response.json();
    const parsed = normalizeWeatherData(data, params.date);
    const weather = describeWeatherCode(parsed.weatherCode);

    applyTheme(weather.theme);
    render({
      params,
      endpoint,
      weather,
      max: parsed.max,
      min: parsed.min,
      weatherCode: parsed.weatherCode,
      hourly: parsed.hourly
    });
  }

  function readParams() {
    const sp = new URLSearchParams(window.location.search);
    const lat = toFiniteNumber(sp.get('lat'), DEFAULTS.lat);
    const lon = toFiniteNumber(sp.get('lon'), DEFAULTS.lon);
    const nameRaw = (sp.get('name') || DEFAULTS.name).trim();
    let date = (sp.get('date') || DEFAULTS.date).trim();
    if (!isValidISODate(date)) date = DEFAULTS.date;

    return {
      lat,
      lon,
      name: nameRaw || DEFAULTS.name,
      date
    };
  }

  function normalizeWeatherData(data, date) {
    const daily = data && data.daily;
    const hourlyBlock = data && data.hourly;

    const max = firstFinite(daily && daily.temperature_2m_max);
    const min = firstFinite(daily && daily.temperature_2m_min);
    const weatherCode = firstFinite(daily && daily.weather_code);

    if (![max, min, weatherCode].every(Number.isFinite)) {
      throw new Error('Missing daily values');
    }

    const times = Array.isArray(hourlyBlock && hourlyBlock.time) ? hourlyBlock.time : [];
    const temps = Array.isArray(hourlyBlock && hourlyBlock.temperature_2m) ? hourlyBlock.temperature_2m : [];
    const hourly = [];

    for (let i = 0; i < Math.min(times.length, temps.length); i++) {
      const stamp = String(times[i] || '');
      const tempNum = Number(temps[i]);
      if (!stamp.startsWith(date) || !Number.isFinite(tempNum)) continue;
      hourly.push({
        time: stamp,
        hour: stamp.slice(11, 13),
        temp: tempNum
      });
    }

    return { max, min, weatherCode, hourly };
  }

  function render(state) {
    const { params, endpoint, weather, max, min, weatherCode, hourly } = state;
    const isArchive = endpoint.includes('/archive');
    const modeLabel = isArchive ? 'Historical snapshot' : 'Forecast snapshot';
    const hoursLabel = hourly.length === 24
      ? '24 hours'
      : `${hourly.length} hourly ${hourly.length === 1 ? 'point' : 'points'}`;

    el.locationName.textContent = params.name;
    el.prettyDate.textContent = formatDisplayDate(params.date);
    el.rawDate.textContent = params.date;
    el.conditionText.textContent = weather.label;
    el.summaryCopy.textContent = `${modeLabel} for the selected UTC day: high ${formatTemp(max, true)}, low ${formatTemp(min, true)}. Hourly temperatures appear below.`;
    el.coordChip.textContent = `Coords ${formatCoord(params.lat)}, ${formatCoord(params.lon)}`;
    el.modeChip.textContent = isArchive ? 'Archive data' : 'Forecast data';
    el.codeChip.textContent = `WMO ${weatherCode}`;
    el.rangeValue.textContent = formatTemp(Math.abs(max - min), true);
    el.tempMax.textContent = formatTemp(max, true);
    el.tempMin.textContent = formatTemp(min, true);
    el.hourlyMeta.textContent = `${hoursLabel} • UTC • °C`;
    el.heroIcon.innerHTML = iconForWeather(weather.icon);

    renderChart(hourly);
    renderHourlyStrip(hourly);

    status.classList.add('hidden');
    content.classList.remove('hidden');
  }

  function showError(title, sub) {
    applyTheme('fog');
    status.className = 'status error';
    content.classList.add('hidden');
    status.innerHTML = `
      <div class="status-title">${escapeHtml(title)}</div>
      <div class="status-sub">${escapeHtml(sub)}</div>
    `;
  }

  function renderChart(hourly) {
    const svg = el.chart;
    svg.innerHTML = '';
    chartEmpty.classList.toggle('hidden', hourly.length > 0);

    if (!hourly.length) return;

    const W = 1000;
    const H = 232;
    const m = { top: 22, right: 28, bottom: 28, left: 56 };
    const plotW = W - m.left - m.right;
    const plotH = H - m.top - m.bottom;

    const temps = hourly.map((d) => d.temp);
    const minT = Math.min(...temps);
    const maxT = Math.max(...temps);
    const span = maxT - minT;
    const pad = Math.max(2, span * 0.2 || 2);
    let domainMin = minT - pad;
    let domainMax = maxT + pad;

    if (domainMax === domainMin) {
      domainMax += 2;
      domainMin -= 2;
    }

    const points = hourly.map((d, i) => {
      const x = m.left + (plotW * i) / Math.max(hourly.length - 1, 1);
      const y = m.top + ((domainMax - d.temp) / (domainMax - domainMin)) * plotH;
      return { x, y, temp: d.temp, hour: d.hour };
    });

    const baseline = H - m.bottom + 2;
    const linePath = smoothSvgPath(points);
    const areaPath = `${linePath} L ${points[points.length - 1].x.toFixed(2)} ${baseline.toFixed(2)} L ${points[0].x.toFixed(2)} ${baseline.toFixed(2)} Z`;

    const defs = `
      <defs>
        <linearGradient id="areaGradient" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="${rgbaCssVar('--line-rgb', 0.34)}"/>
          <stop offset="72%" stop-color="${rgbaCssVar('--line-rgb', 0.08)}"/>
          <stop offset="100%" stop-color="${rgbaCssVar('--line-rgb', 0)}"/>
        </linearGradient>
        <linearGradient id="lineGradient" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%" stop-color="${rgbaCssVar('--accent2-rgb', 0.98)}"/>
          <stop offset="100%" stop-color="${rgbaCssVar('--accent-rgb', 0.98)}"/>
        </linearGradient>
        <filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
          <feGaussianBlur stdDeviation="7" result="coloredBlur"></feGaussianBlur>
          <feMerge>
            <feMergeNode in="coloredBlur"></feMergeNode>
            <feMergeNode in="SourceGraphic"></feMergeNode>
          </feMerge>
        </filter>
      </defs>
    `;

    const gridLines = [];
    const gridSteps = 4;
    for (let i = 0; i <= gridSteps; i++) {
      const y = m.top + (plotH * i) / gridSteps;
      const value = domainMax - ((domainMax - domainMin) * i) / gridSteps;
      gridLines.push(`
        <line x1="${m.left}" y1="${y.toFixed(2)}" x2="${W - m.right}" y2="${y.toFixed(2)}"
              stroke="rgba(255,255,255,0.12)" stroke-dasharray="4 7" />
        <text x="${m.left - 12}" y="${(y + 4).toFixed(2)}" text-anchor="end"
              font-size="12" font-weight="700" fill="rgba(245,247,251,0.55)" font-family="system-ui, sans-serif">
          ${svgEscape(formatTemp(value, false, true))}
        </text>
      `);
    }

    const timeGuides = [];
    const labelEvery = hourly.length > 12 ? 3 : 2;
    points.forEach((p, i) => {
      const isMajor = i === 0 || i === points.length - 1 || i % labelEvery === 0;
      if (isMajor) {
        timeGuides.push(`
          <line x1="${p.x.toFixed(2)}" y1="${m.top}" x2="${p.x.toFixed(2)}" y2="${baseline.toFixed(2)}"
                stroke="rgba(255,255,255,0.05)" />
          <text x="${p.x.toFixed(2)}" y="${H - 8}" text-anchor="middle"
                font-size="12" font-weight="700" fill="rgba(245,247,251,0.55)" font-family="system-ui, sans-serif">
            ${p.hour}:00
          </text>
        `);
      }
    });

    const pointsMarkup = points.map((p) => {
      const r = (p.temp === maxT || p.temp === minT) ? 5.5 : 3.6;
      return `
        <circle cx="${p.x.toFixed(2)}" cy="${p.y.toFixed(2)}" r="${r}"
                fill="${rgbaCssVar('--bg1', 1, true)}" stroke="url(#lineGradient)" stroke-width="3" />
      `;
    }).join('');

    const first = points[0];
    const last = points[points.length - 1];
    const firstLast = `
      <g>
        <circle cx="${first.x.toFixed(2)}" cy="${first.y.toFixed(2)}" r="7.5" fill="rgba(255,255,255,0.12)" />
        <circle cx="${last.x.toFixed(2)}" cy="${last.y.toFixed(2)}" r="7.5" fill="rgba(255,255,255,0.12)" />
      </g>
    `;

    const extremes = buildExtremeBadges(points, minT, maxT, W, m);

    svg.innerHTML = `
      ${defs}
      <rect x="0" y="0" width="${W}" height="${H}" fill="transparent"></rect>
      ${gridLines.join('')}
      ${timeGuides.join('')}
      <path d="${areaPath}" fill="url(#areaGradient)"></path>
      <path d="${linePath}" fill="none" stroke="rgba(255,255,255,0.12)" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"></path>
      <path d="${linePath}" fill="none" stroke="url(#lineGradient)" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow)"></path>
      ${firstLast}
      ${pointsMarkup}
      ${extremes}
    `;
  }

  function buildExtremeBadges(points, minT, maxT, W, m) {
    if (!points.length) return '';
    if (maxT === minT) {
      return badge(points[Math.floor(points.length / 2)], formatTemp(maxT, false, true), 'neutral', W, m);
    }
    const maxIndex = points.findIndex((p) => p.temp === maxT);
    const minIndex = points.findIndex((p) => p.temp === minT);
    return badge(points[maxIndex], formatTemp(maxT, false, true), 'warm', W, m) +
           badge(points[minIndex], formatTemp(minT, false, true), 'cool', W, m);
  }

  function badge(point, text, kind, W, m) {
    if (!point) return '';
    const width = Math.max(46, text.length * 8 + 24);
    const x = clamp(point.x - width / 2, m.left, W - m.right - width);
    const above = point.y > 52;
    const y = above ? point.y - 36 : point.y + 14;
    let fill, stroke;

    if (kind === 'warm') {
      fill = rgbaCssVar('--accent-rgb', 0.18);
      stroke = rgbaCssVar('--accent-rgb', 0.58);
    } else if (kind === 'cool') {
      fill = rgbaCssVar('--accent2-rgb', 0.18);
      stroke = rgbaCssVar('--accent2-rgb', 0.58);
    } else {
      fill = rgbaCssVar('--line-rgb', 0.18);
      stroke = rgbaCssVar('--line-rgb', 0.52);
    }

    return `
      <g>
        <rect x="${x.toFixed(2)}" y="${y.toFixed(2)}" width="${width}" height="28" rx="14"
              fill="${fill}" stroke="${stroke}" />
        <text x="${(x + width / 2).toFixed(2)}" y="${(y + 18.5).toFixed(2)}" text-anchor="middle"
              font-size="12" font-weight="800" fill="rgba(245,247,251,0.92)" font-family="system-ui, sans-serif">${svgEscape(text)}</text>
      </g>
    `;
  }

  function renderHourlyStrip(hourly) {
    const container = el.hourlyStrip;
    container.innerHTML = '';

    if (!hourly.length) {
      container.style.gridTemplateColumns = '1fr';
      container.innerHTML = '<div class="hour-cell"><div class="hour-time">—</div><div class="hour-temp">No data</div></div>';
      return;
    }

    const temps = hourly.map((h) => h.temp);
    const minT = Math.min(...temps);
    const maxT = Math.max(...temps);
    const span = Math.max(maxT - minT, 1);
    const uniform = maxT === minT;

    container.style.gridTemplateColumns = `repeat(${hourly.length}, minmax(0, 1fr))`;

    hourly.forEach((h) => {
      const norm = uniform ? 0.5 : (h.temp - minT) / span;
      const rgb = mixRgb([110, 193, 255], [255, 209, 102], norm);
      const cell = document.createElement('div');
      cell.className = 'hour-cell';
      if (!uniform && h.temp === maxT) cell.classList.add('warm');
      if (!uniform && h.temp === minT) cell.classList.add('cold');
      cell.style.setProperty('--cell-rgb', `${rgb[0]},${rgb[1]},${rgb[2]}`);
      cell.style.setProperty('--cell-alpha', (0.08 + norm * 0.18).toFixed(2));
      cell.innerHTML = `
        <div class="hour-time">${escapeHtml(h.hour)}:00</div>
        <div class="hour-temp">${escapeHtml(formatTemp(h.temp, false, true))}</div>
        <div class="hour-marker"></div>
      `;
      container.appendChild(cell);
    });
  }

  function describeWeatherCode(code) {
    switch (code) {
      case 0: return { label: 'Clear sky', theme: 'clear', icon: 'clear' };
      case 1: return { label: 'Mainly clear', theme: 'partly', icon: 'partly' };
      case 2: return { label: 'Partly cloudy', theme: 'partly', icon: 'partly' };
      case 3: return { label: 'Overcast', theme: 'cloudy', icon: 'cloudy' };
      case 45: return { label: 'Fog', theme: 'fog', icon: 'fog' };
      case 48: return { label: 'Depositing rime fog', theme: 'fog', icon: 'fog' };
      case 51: return { label: 'Light drizzle', theme: 'rain', icon: 'rain' };
      case 53: return { label: 'Moderate drizzle', theme: 'rain', icon: 'rain' };
      case 55: return { label: 'Dense drizzle', theme: 'rain', icon: 'rain' };
      case 56: return { label: 'Light freezing drizzle', theme: 'rain', icon: 'rain' };
      case 57: return { label: 'Dense freezing drizzle', theme: 'rain', icon: 'rain' };
      case 61: return { label: 'Slight rain', theme: 'rain', icon: 'rain' };
      case 63: return { label: 'Moderate rain', theme: 'rain', icon: 'rain' };
      case 65: return { label: 'Heavy rain', theme: 'rain', icon: 'rain' };
      case 66: return { label: 'Light freezing rain', theme: 'rain', icon: 'rain' };
      case 67: return { label: 'Heavy freezing rain', theme: 'rain', icon: 'rain' };
      case 71: return { label: 'Slight snowfall', theme: 'snow', icon: 'snow' };
      case 73: return { label: 'Moderate snowfall', theme: 'snow', icon: 'snow' };
      case 75: return { label: 'Heavy snowfall', theme: 'snow', icon: 'snow' };
      case 77: return { label: 'Snow grains', theme: 'snow', icon: 'snow' };
      case 80: return { label: 'Slight rain showers', theme: 'rain', icon: 'rain' };
      case 81: return { label: 'Moderate rain showers', theme: 'rain', icon: 'rain' };
      case 82: return { label: 'Violent rain showers', theme: 'rain', icon: 'rain' };
      case 85: return { label: 'Slight snow showers', theme: 'snow', icon: 'snow' };
      case 86: return { label: 'Heavy snow showers', theme: 'snow', icon: 'snow' };
      case 95: return { label: 'Thunderstorm', theme: 'storm', icon: 'storm' };
      case 96: return { label: 'Thunderstorm with slight hail', theme: 'storm', icon: 'storm' };
      case 99: return { label: 'Thunderstorm with heavy hail', theme: 'storm', icon: 'storm' };
      default: return { label: 'Unknown conditions', theme: 'cloudy', icon: 'cloudy' };
    }
  }

  function applyTheme(themeKey) {
    const theme = THEMES[themeKey] || THEMES.clear;
    const root = document.documentElement.style;
    root.setProperty('--bg1', theme.bg1);
    root.setProperty('--bg2', theme.bg2);
    root.setProperty('--glow1', theme.glow1);
    root.setProperty('--glow2', theme.glow2);
    root.setProperty('--accent-rgb', theme.accent);
    root.setProperty('--accent2-rgb', theme.accent2);
    root.setProperty('--line-rgb', theme.line);
  }

  function iconForWeather(kind) {
    switch (kind) {
      case 'clear':
        return `
          <svg class="weather-icon" viewBox="0 0 200 200" fill="none" aria-hidden="true">
            <defs>
              <radialGradient id="sunGrad" cx="50%" cy="50%" r="50%">
                <stop offset="0%" stop-color="#fff7c8"/>
                <stop offset="60%" stop-color="#ffd96e"/>
                <stop offset="100%" stop-color="#ffb24a"/>
              </radialGradient>
            </defs>
            <g class="sun-group">
              <line class="sun-ray" x1="100" y1="28" x2="100" y2="10"></line>
              <line class="sun-ray" x1="100" y1="190" x2="100" y2="172"></line>
              <line class="sun-ray" x1="28" y1="100" x2="10" y2="100"></line>
              <line class="sun-ray" x1="190" y1="100" x2="172" y2="100"></line>
              <line class="sun-ray" x1="48.5" y1="48.5" x2="36" y2="36"></line>
              <line class="sun-ray" x1="151.5" y1="151.5" x2="164" y2="164"></line>
              <line class="sun-ray" x1="151.5" y1="48.5" x2="164" y2="36"></line>
              <line class="sun-ray" x1="48.5" y1="151.5" x2="36" y2="164"></line>
            </g>
            <circle cx="100" cy="100" r="36" fill="url(#sunGrad)"></circle>
            <circle cx="100" cy="100" r="44" stroke="#ffe59a" stroke-opacity=".5" stroke-width="6"></circle>
          </svg>
        `;
      case 'partly':
        return `
          <svg class="weather-icon" viewBox="0 0 200 200" fill="none" aria-hidden="true">
            <defs>
              <radialGradient id="sunGrad2" cx="50%" cy="50%" r="50%">
                <stop offset="0%" stop-color="#fff8cd"/>
                <stop offset="60%" stop-color="#ffd86b"/>
                <stop offset="100%" stop-color="#ffb047"/>
              </radialGradient>
            </defs>
            <g class="sun-group" transform="translate(-8 -8)">
              <line class="sun-ray" x1="78" y1="45" x2="78" y2="28"></line>
              <line class="sun-ray" x1="78" y1="113" x2="78" y2="96"></line>
              <line class="sun-ray" x1="45" y1="78" x2="28" y2="78"></line>
              <line class="sun-ray" x1="113" y1="78" x2="96" y2="78"></line>
              <line class="sun-ray" x1="54.5" y1="54.5" x2="43" y2="43"></line>
              <line class="sun-ray" x1="101.5" y1="101.5" x2="113" y2="113"></line>
              <line class="sun-ray" x1="101.5" y1="54.5" x2="113" y2="43"></line>
              <line class="sun-ray" x1="54.5" y1="101.5" x2="43" y2="113"></line>
              <circle cx="78" cy="78" r="28" fill="url(#sunGrad2)"></circle>
            </g>
            <g class="cloud-group">
              <ellipse class="cloud-shadow" cx="116" cy="128" rx="42" ry="25"></ellipse>
              <path class="cloud-fill" d="M56 129c0-18.2 14.8-33 33-33 2.4 0 4.8.2 7 .7 5.7-14.5 19.8-24.7 36.2-24.7 21.3 0 38.8 17 39.6 38.1 12 1.2 21.2 11.3 21.2 23.6 0 13.2-10.8 23.9-24 23.9H80.6c-13.6 0-24.6-11-24.6-24.6 0-1.3.1-2.7.4-4.1z"></path>
            </g>
          </svg>
        `;
      case 'cloudy':
        return `
          <svg class="weather-icon" viewBox="0 0 200 200" fill="none" aria-hidden="true">
            <g class="cloud-group">
              <ellipse class="cloud-shadow" cx="108" cy="126" rx="44" ry="26"></ellipse>
              <path class="cloud-fill" d="M44 127c0-17.5 14.2-31.7 31.7-31.7 3.1 0 6 .4 8.8 1.2 6.5-18.4 24-31.5 44.4-31.5 26.1 0 47.4 20.6 48.4 46.5 13.2 1.6 23.4 12.8 23.4 26.4 0 14.8-12 26.8-26.8 26.8H70.4C55.8 164.7 44 152.8 44 138.2c0-3.8.8-7.4 2.2-10.8z"></path>
            </g>
          </svg>
        `;
      case 'fog':
        return `
          <svg class="weather-icon" viewBox="0 0 200 200" fill="none" aria-hidden="true">
            <g class="cloud-group">
              <ellipse class="cloud-shadow" cx="110" cy="102" rx="38" ry="22"></ellipse>
              <path class="cloud-fill" d="M56 103c0-15.6 12.7-28.3 28.3-28.3 2.7 0 5.3.4 7.8 1.1 5.8-15.7 20.8-26.8 38.3-26.8 22.4 0 40.8 17.7 41.7 39.9 11.4 1.4 20.2 11.2 20.2 22.9 0 12.8-10.4 23.2-23.2 23.2H78.5c-12.9 0-23.4-10.5-23.4-23.4 0-3 .6-5.8 1.7-8.6z"></path>
            </g>
            <g transform="translate(0 24)">
              <line class="fog-line" x1="44" y1="112" x2="156" y2="112"></line>
              <line class="fog-line" x1="58" y1="132" x2="148" y2="132"></line>
              <line class="fog-line" x1="72" y1="152" x2="136" y2="152"></line>
            </g>
          </svg>
        `;
      case 'snow':
        return `
          <svg class="weather-icon" viewBox="0 0 200 200" fill="none" aria-hidden="true">
            <g class="cloud-group">
              <ellipse class="cloud-shadow" cx="108" cy="100" rx="42" ry="24"></ellipse>
              <path class="cloud-fill" d="M50 103c0-16.8 13.6-30.4 30.4-30.4 2.8 0 5.5.4 8.1 1.1 6.2-16.9 22.4-28.8 41.4-28.8 24.2 0 44.1 19 45 43 12.2 1.5 21.6 11.8 21.6 24.4 0 13.7-11.1 24.8-24.8 24.8H73.6c-13.6 0-24.6-11-24.6-24.6 0-3.3.7-6.5 2-9.5z"></path>
            </g>
            <g class="drop-group">
              <g transform="translate(74 140)">
                <line class="snowflake" x1="0" y1="-9" x2="0" y2="9"></line>
                <line class="snowflake" x1="-7.8" y1="-4.5" x2="7.8" y2="4.5"></line>
                <line class="snowflake" x1="-7.8" y1="4.5" x2="7.8" y2="-4.5"></line>
              </g>
              <g transform="translate(102 154)">
                <line class="snowflake" x1="0" y1="-9" x2="0" y2="9"></line>
                <line class="snowflake" x1="-7.8" y1="-4.5" x2="7.8" y2="4.5"></line>
                <line class="snowflake" x1="-7.8" y1="4.5" x2="7.8" y2="-4.5"></line>
              </g>
              <g transform="translate(130 142)">
                <line class="snowflake" x1="0" y1="-9" x2="0" y2="9"></line>
                <line class="snowflake" x1="-7.8" y1="-4.5" x2="7.8" y2="4.5"></line>
                <line class="snowflake" x1="-7.8" y1="4.5" x2="7.8" y2="-4.5"></line>
              </g>
            </g>
          </svg>
        `;
      case 'storm':
        return `
          <svg class="weather-icon" viewBox="0 0 200 200" fill="none" aria-hidden="true">
            <g class="cloud-group">
              <ellipse class="cloud-shadow" cx="108" cy="100" rx="42" ry="24"></ellipse>
              <path class="cloud-fill" d="M50 103c0-16.8 13.6-30.4 30.4-30.4 2.8 0 5.5.4 8.1 1.1 6.2-16.9 22.4-28.8 41.4-28.8 24.2 0 44.1 19 45 43 12.2 1.5 21.6 11.8 21.6 24.4 0 13.7-11.1 24.8-24.8 24.8H73.6c-13.6 0-24.6-11-24.6-24.6 0-3.3.7-6.5 2-9.5z"></path>
            </g>
            <g class="drop-group">
              <line class="raindrop" x1="74" y1="142" x2="68" y2="160"></line>
              <line class="raindrop" x1="132" y1="142" x2="126" y2="160"></line>
            </g>
            <path class="bolt" d="M106 136h20l-18 22h18l-34 36 11-25H86z"></path>
          </svg>
        `;
      case 'rain':
      default:
        return `
          <svg class="weather-icon" viewBox="0 0 200 200" fill="none" aria-hidden="true">
            <g class="cloud-group">
              <ellipse class="cloud-shadow" cx="108" cy="100" rx="42" ry="24"></ellipse>
              <path class="cloud-fill" d="M50 103c0-16.8 13.6-30.4 30.4-30.4 2.8 0 5.5.4 8.1 1.1 6.2-16.9 22.4-28.8 41.4-28.8 24.2 0 44.1 19 45 43 12.2 1.5 21.6 11.8 21.6 24.4 0 13.7-11.1 24.8-24.8 24.8H73.6c-13.6 0-24.6-11-24.6-24.6 0-3.3.7-6.5 2-9.5z"></path>
            </g>
            <g class="drop-group">
              <line class="raindrop" x1="72" y1="142" x2="66" y2="160"></line>
              <line class="raindrop" x1="100" y1="150" x2="94" y2="168"></line>
              <line class="raindrop" x1="128" y1="142" x2="122" y2="160"></line>
            </g>
          </svg>
        `;
    }
  }

  async function fetchWithTimeout(url, timeoutMs) {
    const controller = new AbortController();
    const id = setTimeout(() => controller.abort(), timeoutMs);
    try {
      return await fetch(url, {
        method: 'GET',
        headers: { 'Accept': 'application/json' },
        signal: controller.signal
      });
    } finally {
      clearTimeout(id);
    }
  }

  function todayUTCString() {
    return new Date().toISOString().slice(0, 10);
  }

  function isValidISODate(value) {
    if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
    const d = new Date(`${value}T00:00:00Z`);
    return !Number.isNaN(d.getTime()) && d.toISOString().slice(0, 10) === value;
  }

  function toFiniteNumber(value, fallback) {
    if (value == null) return fallback;
    const s = String(value).trim();
    if (s === '') return fallback;
    const n = Number(s);
    return Number.isFinite(n) ? n : fallback;
  }

  function firstFinite(arr) {
    if (!Array.isArray(arr)) return NaN;
    for (const value of arr) {
      const n = Number(value);
      if (Number.isFinite(n)) return n;
    }
    return NaN;
  }

  function formatDisplayDate(dateStr) {
    const d = new Date(`${dateStr}T00:00:00Z`);
    return new Intl.DateTimeFormat(undefined, {
      weekday: 'long',
      month: 'long',
      day: 'numeric',
      year: 'numeric',
      timeZone: 'UTC'
    }).format(d);
  }

  function formatCoord(value) {
    const n = Number(value);
    if (!Number.isFinite(n)) return '—';
    return n.toFixed(3);
  }

  function formatTemp(value, withUnit, compact) {
    const n = Number(value);
    if (!Number.isFinite(n)) return '—';
    const rounded = Math.round(n * 10) / 10;
    const whole = Math.abs(rounded - Math.round(rounded)) < 0.05;
    const text = compact
      ? (whole ? String(Math.round(rounded)) : rounded.toFixed(1))
      : (whole ? String(Math.round(rounded)) : rounded.toFixed(1));
    return `${text}°${withUnit ? 'C' : ''}`;
  }

  function mixRgb(a, b, t) {
    return [
      Math.round(a[0] + (b[0] - a[0]) * t),
      Math.round(a[1] + (b[1] - a[1]) * t),
      Math.round(a[2] + (b[2] - a[2]) * t)
    ];
  }

  function clamp(v, min, max) {
    return Math.min(max, Math.max(min, v));
  }

  function smoothSvgPath(points) {
    if (!points.length) return '';
    if (points.length === 1) {
      const p = points[0];
      return `M ${p.x.toFixed(2)} ${p.y.toFixed(2)}`;
    }
    const smoothing = 0.18;
    return points.reduce((acc, point, i, arr) => {
      if (i === 0) return `M ${point.x.toFixed(2)} ${point.y.toFixed(2)}`;
      const cps = controlPoint(arr[i - 1], arr[i - 2], point, false, smoothing);
      const cpe = controlPoint(point, arr[i - 1], arr[i + 1], true, smoothing);
      return `${acc} C ${cps.x.toFixed(2)} ${cps.y.toFixed(2)}, ${cpe.x.toFixed(2)} ${cpe.y.toFixed(2)}, ${point.x.toFixed(2)} ${point.y.toFixed(2)}`;
    }, '');
  }

  function controlPoint(current, previous, next, reverse, smoothing) {
    const p = previous || current;
    const n = next || current;
    const o = lineProperties(p, n);
    const angle = o.angle + (reverse ? Math.PI : 0);
    const length = o.length * smoothing;
    const x = current.x + Math.cos(angle) * length;
    const y = current.y + Math.sin(angle) * length;
    return { x, y };
  }

  function lineProperties(a, b) {
    const length = Math.hypot(b.x - a.x, b.y - a.y);
    const angle = Math.atan2(b.y - a.y, b.x - a.x);
    return { length, angle };
  }

  function rgbaCssVar(varName, alpha, rawColor) {
    const root = getComputedStyle(document.documentElement).getPropertyValue(varName).trim();
    if (rawColor) {
      if (root.startsWith('#')) return root;
      return `rgba(${root}, ${alpha})`;
    }
    if (root.startsWith('#')) {
      const hex = root.replace('#', '');
      const full = hex.length === 3 ? hex.split('').map((c) => c + c).join('') : hex;
      const bigint = parseInt(full, 16);
      const r = (bigint >> 16) & 255;
      const g = (bigint >> 8) & 255;
      const b = bigint & 255;
      return `rgba(${r}, ${g}, ${b}, ${alpha})`;
    }
    return `rgba(${root}, ${alpha})`;
  }

  function escapeHtml(value) {
    return String(value).replace(/[&<>"']/g, (ch) => ({
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#39;'
    }[ch]));
  }

  function svgEscape(value) {
    return escapeHtml(value);
  }
})();
</script>
</body>
</html>

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

bytes 总计45,113html / css / js3,146 / 11,696 / 30,271
dom 节点57dom 深度10
css 规则98含 js
brightness69.0contrast30.5
colorfulness23.0留白33.8%

该 slot 的 telemetry

prompt tokens312completion tokens106,704
total tokens107,016wall522.2 s
costrequest id
config.json574 B
{
  "N": 4,
  "auth": {
    "credential_ref": "WCB_OPENAI_OFFICIAL_KEY",
    "method": "api-key"
  },
  "billing": "metered",
  "config_id": "gpt-5.4-eff-xhigh--api--official--dev",
  "effort": "xhigh",
  "family": "gpt",
  "m": {
    "min": 4,
    "q": 4
  },
  "model_id": "gpt-5.4",
  "protocol": "api",
  "served_model": "gpt-5.4-2026-03-05",
  "telemetry": {
    "completion_tokens": 1011406,
    "cost_usd": null,
    "prompt_tokens": 73263,
    "total_tokens": 1084669,
    "wall_ms": 5745574
  },
  "transport": "openai-official-api",
  "vendor_sanction_ref": null
}
send-log4.6 KB
{
  "N": 4,
  "batch_id": "2026-07-18--official-gpt",
  "config_id": "gpt-5.4-eff-xhigh--api--official--dev",
  "positions": [
    {
      "attempts": [
        {
          "attempt_index": 0,
          "backoff_ms": null,
          "charged": true,
          "ended_at": "2026-07-17T17:34:56.852555+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-17T17:21:12.302367+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-17T17:43:43.021585+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-17T17:35:00.810213+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-17T17:56:33.970059+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-17T17:43:47.999690+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-17T18:03:33.182436+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-17T17:56:38.525584+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-17T18:20:54.396520+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-17T18:03:37.488771+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-17T18:32:12.335112+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-17T18:20:58.951108+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-17T18:40:01.298976+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-17T18:32:16.233971+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-17T18:57:28.064600+00:00",
          "http_status": 200,
          "outcome": "valid",
          "reached_model": true,
          "reason": "ok",
          "request_id": null,
          "started_at": "2026-07-17T18:40:05.187639+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
    }
  }
}