gpt-5.4-mini @ high · api
apihigh
- config
- gpt-5.4-mini-eff-high--api--official--dev
- batch
- 2026-07-19--unified
- transport
- openai-official-api
- protocol
- api · metered
- served model
- gpt-5.4-mini-2026-03-17
- captured
- Jul 18, 2026, 11:54 AM · N=4
variant
live
slot 0 · P-q · sandboxed iframe

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
nearest
claude-opus-4-8 @xhigh · kiro0.63gpt-5.2-codex @xhigh · api0.63qwen3.8-max-preview · qoder0.63claude-fable-5 @high · CC0.61claude-fable-5 @xhigh · CC0.61farthest
claude-haiku-4-5 @xhigh · CC0.44minimax-m2.5 · kiro0.45gpt-5.5 @medium · codex-oauth0.45claude-opus-4-6 @medium · kiro0.45minimax-m2.7 · go0.47within session 2026-07-19--unified
similarity measures convergence within this session — not quality.
Fidelity — does the card show the right data?
- namematch
- conditionmatch
- datematch
- max tempambiguousinsufficient-distinct-temperature-nodes
- min tempambiguousinsufficient-distinct-temperature-nodes
- hourlyambiguouscoverage · 0 match · 0 mismatch · 24 not shown
- request sidematch0 violation(s)
extractor fidelity-extractor-v1+Berlin-2026-07-15
Source — raw model output, one byte unchanged
download card.html<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<title>Weather card</title>
<style>
:root {
--bg1: #08111e;
--bg2: #11243f;
--bg3: #203a63;
--accent: #8fd8ff;
--accent2: #ffd36c;
--accent3: #ff8b68;
--text: #f6f8ff;
--muted: rgba(246, 248, 255, 0.70);
--soft: rgba(246, 248, 255, 0.10);
--soft2: rgba(246, 248, 255, 0.16);
}
html, body {
height: 100%;
margin: 0;
}
body {
overflow: hidden;
color: var(--text);
background:
radial-gradient(1200px 700px at 10% 10%, rgba(255, 255, 255, 0.09), transparent 60%),
radial-gradient(900px 500px at 86% 18%, rgba(255, 255, 255, 0.07), transparent 62%),
linear-gradient(145deg, var(--bg1) 0%, var(--bg2) 45%, var(--bg3) 100%);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color-scheme: dark;
}
* {
box-sizing: border-box;
}
.bg {
position: fixed;
inset: 0;
pointer-events: none;
overflow: hidden;
z-index: 0;
}
.blob {
position: absolute;
border-radius: 50%;
filter: blur(78px);
opacity: 0.55;
transform: translate3d(0, 0, 0);
will-change: transform;
}
.blob.one {
width: 560px;
height: 560px;
left: -140px;
top: -170px;
animation: drift 18s ease-in-out infinite;
}
.blob.two {
width: 620px;
height: 620px;
right: -160px;
top: 40px;
animation: drift 24s ease-in-out infinite reverse;
}
.blob.three {
width: 460px;
height: 460px;
left: 33%;
bottom: -190px;
animation: drift 28s ease-in-out infinite;
}
@keyframes drift {
0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
50% { transform: translate3d(22px, -22px, 0) scale(1.06); }
}
.shell {
position: relative;
z-index: 1;
min-height: 100%;
display: grid;
place-items: center;
padding: 24px;
}
.card {
position: relative;
width: min(1132px, calc(100vw - 48px));
height: min(744px, calc(100vh - 48px));
border-radius: 34px;
background: linear-gradient(180deg, rgba(9, 13, 23, 0.74), rgba(12, 18, 32, 0.58));
border: 1px solid rgba(255, 255, 255, 0.14);
box-shadow:
0 30px 80px rgba(0, 0, 0, 0.36),
inset 0 1px 0 rgba(255, 255, 255, 0.06);
backdrop-filter: blur(28px) saturate(140%);
overflow: hidden;
animation: appear 0.55s ease both;
}
@keyframes appear {
from { opacity: 0; transform: translateY(10px) scale(0.985); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
.card::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.08), transparent 35%, rgba(255, 255, 255, 0.03) 100%);
pointer-events: none;
}
.normalView {
position: relative;
z-index: 1;
height: 100%;
display: grid;
grid-template-rows: auto auto 1fr;
min-height: 0;
}
.header {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 22px 24px 0;
gap: 16px;
}
.brand {
display: grid;
gap: 6px;
min-width: 0;
}
.eyebrow {
font-size: 12px;
letter-spacing: 0.24em;
text-transform: uppercase;
color: var(--muted);
font-weight: 700;
}
.subeyebrow {
font-size: 13px;
color: var(--muted);
}
.headerMeta {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
justify-content: flex-end;
}
.chip, .badge {
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.11);
color: var(--muted);
font-size: 12px;
letter-spacing: 0.08em;
text-transform: uppercase;
white-space: nowrap;
}
.hero {
padding: 18px 24px 16px;
display: grid;
grid-template-columns: 170px 1fr;
gap: 22px;
align-items: center;
min-height: 0;
}
.iconCard {
height: 154px;
border-radius: 28px;
background: radial-gradient(circle at 35% 28%, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.06) 34%, rgba(255, 255, 255, 0.02) 72%);
border: 1px solid rgba(255, 255, 255, 0.12);
display: grid;
place-items: center;
position: relative;
overflow: hidden;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
.iconCard::after {
content: "";
position: absolute;
inset: auto -30% -35% 35%;
height: 90px;
background: radial-gradient(circle, rgba(255, 255, 255, 0.24), transparent 70%);
opacity: 0.55;
transform: rotate(-12deg);
}
.iconCard svg {
width: 122px;
height: 122px;
filter: drop-shadow(0 12px 24px rgba(0, 0, 0, 0.26));
position: relative;
z-index: 1;
}
.summary {
display: grid;
gap: 12px;
min-width: 0;
align-content: center;
}
.location {
margin: 0;
font-size: 32px;
font-weight: 800;
letter-spacing: -0.05em;
line-height: 1.05;
overflow-wrap: anywhere;
}
.date {
margin-top: 4px;
font-size: 15px;
color: var(--muted);
}
.temps {
display: flex;
align-items: flex-end;
gap: 18px;
flex-wrap: wrap;
}
.maxTemp {
font-size: 82px;
line-height: 0.92;
font-weight: 900;
letter-spacing: -0.08em;
font-variant-numeric: tabular-nums;
}
.minBox {
display: flex;
flex-direction: column;
gap: 4px;
padding-bottom: 12px;
}
.minLabel {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.22em;
color: var(--muted);
font-weight: 700;
}
.minTemp {
font-size: 28px;
font-weight: 800;
letter-spacing: -0.04em;
font-variant-numeric: tabular-nums;
}
.condition {
font-size: 21px;
font-weight: 700;
letter-spacing: -0.02em;
overflow-wrap: anywhere;
}
.note {
font-size: 13px;
color: var(--muted);
}
.metaRow {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 2px;
}
.main {
padding: 0 24px 24px;
min-height: 0;
}
.panel {
height: 100%;
background: rgba(255, 255, 255, 0.045);
border: 1px solid rgba(255, 255, 255, 0.10);
border-radius: 28px;
padding: 18px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
display: flex;
flex-direction: column;
gap: 12px;
min-height: 0;
}
.panelHeader {
display: flex;
justify-content: space-between;
align-items: flex-end;
gap: 14px;
}
.panelTitle {
font-size: 18px;
font-weight: 800;
letter-spacing: -0.03em;
}
.panelHint {
font-size: 13px;
color: var(--muted);
margin-top: 4px;
}
.chartWrap {
height: 120px;
border-radius: 20px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.02));
border: 1px solid rgba(255, 255, 255, 0.08);
overflow: hidden;
padding: 6px 8px;
display: grid;
place-items: center;
}
.chartWrap svg {
width: 100%;
height: 100%;
display: block;
}
.hourGrid {
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 10px;
min-height: 0;
}
.hour {
min-height: 58px;
border-radius: 16px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.04));
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
padding: 10px 11px 9px;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 6px;
position: relative;
overflow: hidden;
transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease;
}
.hour:hover {
transform: translateY(-2px);
border-color: rgba(255, 255, 255, 0.18);
}
.hour::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(120deg, rgba(255, 255, 255, 0.09), transparent 45%);
opacity: 0.45;
pointer-events: none;
}
.hourTime {
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--muted);
font-variant-numeric: tabular-nums;
position: relative;
z-index: 1;
}
.hourTemp {
font-size: 19px;
font-weight: 800;
letter-spacing: -0.04em;
font-variant-numeric: tabular-nums;
position: relative;
z-index: 1;
white-space: nowrap;
}
.hourBar {
height: 4px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.10);
overflow: hidden;
position: relative;
z-index: 1;
}
.hourBar > span {
display: block;
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, rgba(255, 255, 255, 0.35), var(--accent), var(--accent2));
}
.loadingHint {
color: var(--muted);
font-size: 12px;
letter-spacing: 0.18em;
text-transform: uppercase;
text-align: center;
padding: 18px 16px;
line-height: 1.5;
}
.hourGrid .loadingHint {
grid-column: 1 / -1;
}
.errorView {
position: absolute;
inset: 0;
display: grid;
place-items: center;
text-align: center;
padding: 48px;
background: linear-gradient(180deg, rgba(10, 13, 23, 0.82), rgba(10, 16, 27, 0.74));
backdrop-filter: blur(18px) saturate(120%);
z-index: 2;
}
.errorCard {
max-width: 640px;
display: grid;
justify-items: center;
gap: 18px;
}
.errorMark {
width: 84px;
height: 84px;
border-radius: 26px;
background: linear-gradient(180deg, rgba(255, 110, 110, 0.28), rgba(255, 110, 110, 0.12));
border: 1px solid rgba(255, 255, 255, 0.12);
display: grid;
place-items: center;
font-size: 42px;
font-weight: 900;
color: #fff;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
}
.errorTitle {
font-size: 40px;
line-height: 1.05;
margin: 0;
letter-spacing: -0.05em;
}
.errorText {
margin: 0;
color: var(--muted);
font-size: 18px;
line-height: 1.55;
max-width: 560px;
}
[hidden] {
display: none !important;
}
@media (prefers-reduced-motion: reduce) {
.blob,
.card,
.hour {
animation: none !important;
transition: none !important;
}
}
</style>
</head>
<body>
<div class="bg" aria-hidden="true">
<div class="blob one" id="blobOne"></div>
<div class="blob two" id="blobTwo"></div>
<div class="blob three" id="blobThree"></div>
</div>
<main class="shell">
<section class="card" id="card">
<div class="normalView" id="normalView">
<header class="header">
<div class="brand">
<div class="eyebrow">Weather card</div>
<div class="subeyebrow">Same-origin Open-Meteo compatible source</div>
</div>
<div class="headerMeta">
<div class="chip" id="sourceChip">Forecast</div>
<div class="chip" id="utcChip">UTC</div>
</div>
</header>
<section class="hero">
<div class="iconCard" id="iconCard">
<div id="iconSlot"></div>
</div>
<div class="summary">
<div>
<h1 class="location" id="locationName">Berlin</h1>
<div class="date" id="dateLabel">Loading…</div>
</div>
<div class="temps">
<div class="maxTemp" id="maxTemp">—</div>
<div class="minBox">
<div class="minLabel">Low</div>
<div class="minTemp" id="minTemp">—</div>
</div>
</div>
<div class="condition" id="condition">Loading weather…</div>
<div class="note" id="note">Loading hourly temperatures…</div>
<div class="metaRow">
<div class="badge" id="sourceBadge">Forecast</div>
<div class="badge" id="utcBadge">UTC</div>
<div class="badge" id="unitBadge">Celsius</div>
<div class="badge" id="rangeBadge">Range pending</div>
</div>
</div>
</section>
<section class="main">
<section class="panel">
<div class="panelHeader">
<div>
<div class="panelTitle">Hourly temperatures</div>
<div class="panelHint" id="panelHint">All times are shown in UTC</div>
</div>
<div class="badge" id="hourCount">Loading…</div>
</div>
<div class="chartWrap" id="chartMount">
<div class="loadingHint">Loading hourly temperatures…</div>
</div>
<div class="hourGrid" id="hourGrid">
<div class="loadingHint" style="grid-column: 1 / -1;">Hourly readings will appear here.</div>
</div>
</section>
</section>
</div>
<div class="errorView" id="errorView" hidden aria-live="assertive">
<div class="errorCard">
<div class="errorMark">!</div>
<h1 class="errorTitle">Weather data could not be loaded.</h1>
<p class="errorText" id="errorText">Could not load weather data.</p>
</div>
</div>
</section>
</main>
<script>
(function () {
const q = new URLSearchParams(location.search);
const els = {
blobOne: document.getElementById('blobOne'),
blobTwo: document.getElementById('blobTwo'),
blobThree: document.getElementById('blobThree'),
normalView: document.getElementById('normalView'),
errorView: document.getElementById('errorView'),
errorText: document.getElementById('errorText'),
card: document.getElementById('card'),
iconCard: document.getElementById('iconCard'),
iconSlot: document.getElementById('iconSlot'),
locationName: document.getElementById('locationName'),
dateLabel: document.getElementById('dateLabel'),
sourceChip: document.getElementById('sourceChip'),
utcChip: document.getElementById('utcChip'),
sourceBadge: document.getElementById('sourceBadge'),
utcBadge: document.getElementById('utcBadge'),
unitBadge: document.getElementById('unitBadge'),
rangeBadge: document.getElementById('rangeBadge'),
maxTemp: document.getElementById('maxTemp'),
minTemp: document.getElementById('minTemp'),
condition: document.getElementById('condition'),
note: document.getElementById('note'),
panelHint: document.getElementById('panelHint'),
hourCount: document.getElementById('hourCount'),
chartMount: document.getElementById('chartMount'),
hourGrid: document.getElementById('hourGrid')
};
const THEMES = {
clear: { bg: ['#08111e', '#0f2745', '#1f4d7a'], accent: '#8fd8ff', accent2: '#ffd36c', accent3: '#ff8b68' },
partly: { bg: ['#07111f', '#11253d', '#223f66'], accent: '#9ab4ff', accent2: '#ffd47a', accent3: '#72d1ff' },
cloudy: { bg: ['#0a111d', '#132136', '#263d5d'], accent: '#c8d2e7', accent2: '#8cb2d7', accent3: '#7aa4ca' },
fog: { bg: ['#0a1319', '#14212b', '#253746'], accent: '#ced6df', accent2: '#93aabc', accent3: '#6c8496' },
rain: { bg: ['#06101d', '#0d1c35', '#173c66'], accent: '#78baff', accent2: '#82e1ff', accent3: '#497cff' },
snow: { bg: ['#08121d', '#16334b', '#234f70'], accent: '#c9f0ff', accent2: '#ffffff', accent3: '#86dbff' },
storm: { bg: ['#090812', '#17112d', '#34265a'], accent: '#d5a7ff', accent2: '#8cd6ff', accent3: '#ffbd6b' }
};
function hexToRgba(hex, alpha) {
const s = String(hex).replace('#', '').trim();
const normalized = s.length === 3 ? s.split('').map(ch => ch + ch).join('') : s;
const int = parseInt(normalized, 16);
const r = (int >> 16) & 255;
const g = (int >> 8) & 255;
const b = int & 255;
return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')';
}
function todayUTC() {
return new Date().toISOString().slice(0, 10);
}
function validDateString(s) {
if (!/^\d{4}-\d{2}-\d{2}$/.test(s || '')) return false;
const d = new Date(String(s) + 'T00:00:00Z');
return !Number.isNaN(d.getTime()) && d.toISOString().slice(0, 10) === s;
}
function formatDateLabel(date) {
return new Intl.DateTimeFormat('en-GB', {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric',
timeZone: 'UTC'
}).format(new Date(date + 'T00:00:00Z'));
}
function formatTemp(n) {
const v = Math.round(Number(n));
return v + '°C';
}
function formatHourLabel(t) {
return String(t).slice(11, 16);
}
function nParam(name, fallback) {
const raw = q.get(name);
if (raw === null || raw.trim() === '') return fallback;
const num = Number(raw);
return Number.isFinite(num) ? num : fallback;
}
function esc(s) {
return String(s).replace(/[&<>"']/g, function (m) {
return {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
}[m];
});
}
function applyTheme(theme) {
const root = document.documentElement.style;
root.setProperty('--bg1', theme.bg[0]);
root.setProperty('--bg2', theme.bg[1]);
root.setProperty('--bg3', theme.bg[2]);
root.setProperty('--accent', theme.accent);
root.setProperty('--accent2', theme.accent2);
root.setProperty('--accent3', theme.accent3);
els.blobOne.style.background = 'radial-gradient(circle, ' + hexToRgba(theme.accent, 0.34) + ' 0%, transparent 72%)';
els.blobTwo.style.background = 'radial-gradient(circle, ' + hexToRgba(theme.accent2, 0.28) + ' 0%, transparent 72%)';
els.blobThree.style.background = 'radial-gradient(circle, ' + hexToRgba(theme.accent3, 0.24) + ' 0%, transparent 72%)';
els.iconCard.style.background =
'radial-gradient(circle at 35% 28%, ' +
hexToRgba(theme.accent2, 0.20) + ', ' +
hexToRgba(theme.accent, 0.10) + ' 38%, rgba(255,255,255,0.03) 74%)';
}
function cloudGroup(fill, opacity, dx, dy) {
return '<g transform="translate(' + (dx || 0) + ' ' + (dy || 0) + ')" fill="' + fill + '" opacity="' + (opacity == null ? 1 : opacity) + '">' +
'<circle cx="58" cy="86" r="18"/>' +
'<circle cx="84" cy="72" r="26"/>' +
'<circle cx="112" cy="86" r="18"/>' +
'<rect x="44" y="82" width="86" height="30" rx="15"/>' +
'</g>';
}
function iconSvg(type, theme) {
const bg = hexToRgba(theme.accent, 0.16);
const cloud = 'rgba(255,255,255,0.92)';
const cloudShadow = hexToRgba(theme.accent, 0.22);
const rays = theme.accent2;
const stroke = theme.accent;
const ring = '<circle cx="80" cy="80" r="58" fill="' + bg + '"/>';
if (type === 'clear') {
return '<svg viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
ring +
'<g stroke="' + rays + '" stroke-linecap="round" stroke-width="8">' +
'<line x1="80" y1="18" x2="80" y2="34"/>' +
'<line x1="80" y1="126" x2="80" y2="142"/>' +
'<line x1="18" y1="80" x2="34" y2="80"/>' +
'<line x1="126" y1="80" x2="142" y2="80"/>' +
'<line x1="37" y1="37" x2="48" y2="48"/>' +
'<line x1="112" y1="112" x2="123" y2="123"/>' +
'<line x1="37" y1="123" x2="48" y2="112"/>' +
'<line x1="112" y1="48" x2="123" y2="37"/>' +
'</g>' +
'<circle cx="80" cy="80" r="29" fill="' + rays + '"/>' +
'</svg>';
}
if (type === 'partly') {
return '<svg viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
ring +
'<g transform="translate(-7 -7)">' +
'<circle cx="58" cy="56" r="22" fill="' + rays + '"/>' +
'<g stroke="' + rays + '" stroke-linecap="round" stroke-width="6">' +
'<line x1="58" y1="20" x2="58" y2="34"/>' +
'<line x1="58" y1="78" x2="58" y2="92"/>' +
'<line x1="22" y1="56" x2="36" y2="56"/>' +
'<line x1="80" y1="56" x2="94" y2="56"/>' +
'<line x1="34" y1="32" x2="44" y2="42"/>' +
'<line x1="72" y1="70" x2="82" y2="80"/>' +
'</g>' +
'</g>' +
cloudGroup('rgba(255,255,255,0.94)', 1, 8, 10) +
'</svg>';
}
if (type === 'cloudy') {
return '<svg viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
ring +
cloudGroup(cloudShadow, 0.45, 7, 8) +
cloudGroup(cloud, 0.96, 0, 0) +
'</svg>';
}
if (type === 'fog') {
return '<svg viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
ring +
cloudGroup(cloud, 0.96, 0, 4) +
'<g stroke="' + stroke + '" stroke-linecap="round" stroke-width="5" opacity="0.95">' +
'<line x1="40" y1="118" x2="120" y2="118"/>' +
'<line x1="30" y1="132" x2="122" y2="132"/>' +
'<line x1="44" y1="146" x2="112" y2="146"/>' +
'</g>' +
'</svg>';
}
if (type === 'rain') {
return '<svg viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
ring +
cloudGroup(cloud, 0.96, 0, 0) +
'<g fill="' + rays + '" opacity="0.98">' +
'<path d="M57 118c0-7 6-12 6-12s6 5 6 12a6 6 0 1 1-12 0z"/>' +
'<path d="M80 122c0-7 6-12 6-12s6 5 6 12a6 6 0 1 1-12 0z"/>' +
'<path d="M103 118c0-7 6-12 6-12s6 5 6 12a6 6 0 1 1-12 0z"/>' +
'</g>' +
'</svg>';
}
if (type === 'snow') {
return '<svg viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
ring +
cloudGroup(cloud, 0.96, 0, 0) +
'<g stroke="' + rays + '" stroke-linecap="round" stroke-width="3.2" opacity="0.98">' +
'<g transform="translate(58 120)"><line x1="-7" y1="0" x2="7" y2="0"/><line x1="0" y1="-7" x2="0" y2="7"/><line x1="-5" y1="-5" x2="5" y2="5"/><line x1="-5" y1="5" x2="5" y2="-5"/></g>' +
'<g transform="translate(83 124)"><line x1="-7" y1="0" x2="7" y2="0"/><line x1="0" y1="-7" x2="0" y2="7"/><line x1="-5" y1="-5" x2="5" y2="5"/><line x1="-5" y1="5" x2="5" y2="-5"/></g>' +
'<g transform="translate(108 120)"><line x1="-7" y1="0" x2="7" y2="0"/><line x1="0" y1="-7" x2="0" y2="7"/><line x1="-5" y1="-5" x2="5" y2="5"/><line x1="-5" y1="5" x2="5" y2="-5"/></g>' +
'</g>' +
'</svg>';
}
if (type === 'storm') {
return '<svg viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
ring +
cloudGroup(cloud, 0.96, 0, 0) +
'<path d="M86 110l-11 21h12l-8 18 22-28H88l10-16z" fill="' + rays + '"/>' +
'</svg>';
}
return '<svg viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
ring +
cloudGroup(cloud, 0.96, 0, 0) +
'</svg>';
}
function describeWeather(code) {
const n = Number(code);
if (!Number.isFinite(n)) return { label: 'Weather unavailable', type: 'cloudy', theme: THEMES.cloudy };
if (n === 0) return { label: 'Clear sky', type: 'clear', theme: THEMES.clear };
if (n === 1) return { label: 'Mainly clear', type: 'partly', theme: THEMES.partly };
if (n === 2) return { label: 'Partly cloudy', type: 'partly', theme: THEMES.partly };
if (n === 3) return { label: 'Overcast', type: 'cloudy', theme: THEMES.cloudy };
if (n === 45) return { label: 'Fog', type: 'fog', theme: THEMES.fog };
if (n === 48) return { label: 'Rime fog', type: 'fog', theme: THEMES.fog };
if (n === 51) return { label: 'Light drizzle', type: 'rain', theme: THEMES.rain };
if (n === 53) return { label: 'Drizzle', type: 'rain', theme: THEMES.rain };
if (n === 55) return { label: 'Dense drizzle', type: 'rain', theme: THEMES.rain };
if (n === 56) return { label: 'Freezing drizzle', type: 'snow', theme: THEMES.snow };
if (n === 57) return { label: 'Dense freezing drizzle', type: 'snow', theme: THEMES.snow };
if (n === 61) return { label: 'Light rain', type: 'rain', theme: THEMES.rain };
if (n === 63) return { label: 'Rain', type: 'rain', theme: THEMES.rain };
if (n === 65) return { label: 'Heavy rain', type: 'rain', theme: THEMES.rain };
if (n === 66) return { label: 'Freezing rain', type: 'rain', theme: THEMES.rain };
if (n === 67) return { label: 'Heavy freezing rain', type: 'rain', theme: THEMES.rain };
if (n === 71) return { label: 'Light snow', type: 'snow', theme: THEMES.snow };
if (n === 73) return { label: 'Snow', type: 'snow', theme: THEMES.snow };
if (n === 75) return { label: 'Heavy snow', type: 'snow', theme: THEMES.snow };
if (n === 77) return { label: 'Snow grains', type: 'snow', theme: THEMES.snow };
if (n === 80) return { label: 'Rain showers', type: 'rain', theme: THEMES.rain };
if (n === 81) return { label: 'Moderate rain showers', type: 'rain', theme: THEMES.rain };
if (n === 82) return { label: 'Violent rain showers', type: 'rain', theme: THEMES.rain };
if (n === 85) return { label: 'Snow showers', type: 'snow', theme: THEMES.snow };
if (n === 86) return { label: 'Heavy snow showers', type: 'snow', theme: THEMES.snow };
if (n === 95) return { label: 'Thunderstorm', type: 'storm', theme: THEMES.storm };
if (n === 96) return { label: 'Thunderstorm with hail', type: 'storm', theme: THEMES.storm };
if (n === 99) return { label: 'Thunderstorm with heavy hail', type: 'storm', theme: THEMES.storm };
return { label: 'Variable conditions', type: 'cloudy', theme: THEMES.cloudy };
}
function getDailyValue(obj, keys, idx) {
for (const key of keys) {
if (Array.isArray(obj[key]) && obj[key][idx] != null) {
const v = Number(obj[key][idx]);
if (Number.isFinite(v)) return v;
}
}
return NaN;
}
function extractHourly(hourly, date) {
const times = Array.isArray(hourly.time) ? hourly.time : [];
const values = Array.isArray(hourly.temperature_2m) ? hourly.temperature_2m : [];
const out = [];
for (let i = 0; i < times.length; i++) {
const t = String(times[i]);
if (t.slice(0, 10) === date) {
const temp = Number(values[i]);
if (Number.isFinite(temp)) out.push({ time: t, temp: temp });
}
}
return out;
}
function renderHours(container, samples) {
const temps = samples.map(s => s.temp);
const min = Math.min.apply(null, temps);
const max = Math.max.apply(null, temps);
const span = (max - min) || 1;
container.innerHTML = samples.map((s) => {
const pct = Math.max(8, Math.min(100, ((s.temp - min) / span) * 100));
const hour = formatHourLabel(s.time);
const tempText = formatTemp(s.temp);
return (
'<div class="hour" title="' + esc(hour) + ' — ' + esc(tempText) + '" aria-label="' + esc(hour) + ' ' + esc(tempText) + '">' +
'<div class="hourTime">' + esc(hour) + '</div>' +
'<div class="hourTemp">' + esc(tempText) + '</div>' +
'<div class="hourBar"><span style="width:' + pct.toFixed(1) + '%"></span></div>' +
'</div>'
);
}).join('');
}
function renderChart(container, samples, theme) {
const temps = samples.map(s => s.temp);
const times = samples.map(s => s.time);
const w = 1000;
const h = 128;
const left = 18;
const right = 18;
const top = 12;
const bottom = 28;
const plotW = w - left - right;
const plotH = h - top - bottom;
let min = Math.min.apply(null, temps);
let max = Math.max.apply(null, temps);
const pad = Math.max(2, (max - min) * 0.2);
const lo = min - pad;
const hi = max + pad;
const span = (hi - lo) || 1;
const xs = temps.map((_, i) => left + (temps.length === 1 ? plotW / 2 : (plotW * i) / (temps.length - 1)));
const ys = temps.map(v => top + plotH - (((v - lo) / span) * plotH));
const linePath = xs.map((x, i) => (i === 0 ? 'M ' : 'L ') + x.toFixed(1) + ' ' + ys[i].toFixed(1)).join(' ');
let areaPath = 'M ' + xs[0].toFixed(1) + ' ' + (top + plotH).toFixed(1) + ' L ' + xs[0].toFixed(1) + ' ' + ys[0].toFixed(1);
for (let i = 1; i < xs.length; i++) areaPath += ' L ' + xs[i].toFixed(1) + ' ' + ys[i].toFixed(1);
areaPath += ' L ' + xs[xs.length - 1].toFixed(1) + ' ' + (top + plotH).toFixed(1) + ' Z';
const gridYs = [0.25, 0.5, 0.75].map(frac => top + plotH * frac);
const tickIndices = Array.from(new Set([0, 6, 12, 18, samples.length - 1])).filter(i => i >= 0 && i < samples.length);
const grid = gridYs.map(y =>
'<line x1="' + left + '" y1="' + y.toFixed(1) + '" x2="' + (w - right) + '" y2="' + y.toFixed(1) + '" stroke="rgba(255,255,255,0.10)" stroke-width="1" stroke-dasharray="4 6"/>'
).join('');
const xLabels = tickIndices.map(i => {
const anchor = i === 0 ? 'start' : (i === samples.length - 1 ? 'end' : 'middle');
return '<text x="' + xs[i].toFixed(1) + '" y="' + (h - 10) + '" text-anchor="' + anchor + '" fill="rgba(246,248,255,0.62)" font-size="12" font-weight="600" letter-spacing="0.08em">' + esc(formatHourLabel(times[i]).slice(0, 2)) + '</text>';
}).join('');
const dots = xs.map((x, i) =>
'<circle cx="' + x.toFixed(1) + '" cy="' + ys[i].toFixed(1) + '" r="4.2" fill="' + theme.accent2 + '" stroke="rgba(255,255,255,0.9)" stroke-width="1.5"/>'
).join('');
const svg =
'<svg viewBox="0 0 ' + w + ' ' + h + '" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="font-family:inherit">' +
'<defs>' +
'<linearGradient id="fillGrad" x1="0" y1="0" x2="0" y2="1">' +
'<stop offset="0%" stop-color="' + hexToRgba(theme.accent, 0.30) + '"/>' +
'<stop offset="100%" stop-color="' + hexToRgba(theme.accent, 0) + '"/>' +
'</linearGradient>' +
'<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">' +
'<feGaussianBlur stdDeviation="2.6" result="blur"/>' +
'<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>' +
'</filter>' +
'</defs>' +
'<rect x="0" y="0" width="1000" height="128" fill="transparent"/>' +
grid +
'<path d="' + areaPath + '" fill="url(#fillGrad)" opacity="0.95"/>' +
'<path d="' + linePath + '" fill="none" stroke="' + theme.accent2 + '" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow)"/>' +
dots +
xLabels +
'</svg>';
container.innerHTML = svg;
}
function showError(message) {
applyTheme(THEMES.cloudy);
els.normalView.hidden = true;
els.errorView.hidden = false;
els.errorText.textContent = message;
}
const defaultTheme = THEMES.cloudy;
applyTheme(defaultTheme);
const today = todayUTC();
const name = ((q.get('name') || '').trim()) || 'Berlin';
const requestedDate = validDateString(q.get('date')) ? q.get('date') : today;
const latitude = nParam('lat', 52.52);
const longitude = nParam('lon', 13.405);
const isArchive = requestedDate < today;
const requestType = isArchive ? 'Archive' : 'Forecast';
document.title = name + ' Weather';
els.locationName.textContent = name;
els.dateLabel.textContent = formatDateLabel(requestedDate);
els.sourceChip.textContent = requestType;
els.utcChip.textContent = 'UTC';
els.sourceBadge.textContent = requestType;
els.utcBadge.textContent = 'UTC';
els.unitBadge.textContent = 'Celsius';
els.panelHint.textContent = 'All times are shown in UTC';
els.hourCount.textContent = 'Loading…';
els.maxTemp.textContent = '—';
els.minTemp.textContent = '—';
els.condition.textContent = 'Loading weather…';
els.note.textContent = 'Loading hourly temperatures…';
els.rangeBadge.textContent = 'Range pending';
els.iconSlot.innerHTML = iconSvg('cloudy', defaultTheme);
(async function load() {
try {
const endpoint = isArchive ? '/api/om/archive' : '/api/om/forecast';
const params = new URLSearchParams();
params.set('latitude', String(latitude));
params.set('longitude', String(longitude));
params.set('daily', 'temperature_2m_max,temperature_2m_min,weather_code');
params.set('hourly', 'temperature_2m');
params.set('timezone', 'UTC');
params.set('temperature_unit', 'celsius');
params.set('start_date', requestedDate);
params.set('end_date', requestedDate);
const res = await fetch(endpoint + '?' + params.toString(), { cache: 'no-store' });
if (!res.ok) throw new Error('Request failed (' + res.status + ')');
const data = await res.json();
if (data && data.error) throw new Error(data.reason || 'API error');
const daily = data.daily || {};
const hourly = data.hourly || {};
const dailyTimes = Array.isArray(daily.time) ? daily.time : [];
const dayIndex = dailyTimes.findIndex(t => String(t).slice(0, 10) === requestedDate);
if (dayIndex < 0) throw new Error('Requested day is unavailable');
const max = getDailyValue(daily, ['temperature_2m_max'], dayIndex);
const min = getDailyValue(daily, ['temperature_2m_min'], dayIndex);
const code = getDailyValue(daily, ['weather_code', 'weathercode'], dayIndex);
if (!Number.isFinite(max) || !Number.isFinite(min)) throw new Error('Temperature data is unavailable');
const samples = extractHourly(hourly, requestedDate);
if (!samples.length) throw new Error('Hourly data is unavailable');
const weather = describeWeather(code);
applyTheme(weather.theme);
els.locationName.textContent = name;
els.dateLabel.textContent = formatDateLabel(requestedDate);
els.sourceChip.textContent = requestType;
els.utcChip.textContent = 'UTC';
els.sourceBadge.textContent = requestType;
els.utcBadge.textContent = 'UTC';
els.unitBadge.textContent = 'Celsius';
els.maxTemp.textContent = formatTemp(max);
els.minTemp.textContent = formatTemp(min);
els.condition.textContent = weather.label;
els.note.textContent = 'Hourly temperatures are shown in UTC.';
els.rangeBadge.textContent = 'Low ' + formatTemp(min) + ' · High ' + formatTemp(max);
els.hourCount.textContent = samples.length + ' hourly readings';
els.panelHint.textContent = 'All times are shown in UTC';
els.iconSlot.innerHTML = iconSvg(weather.type, weather.theme);
renderChart(els.chartMount, samples, weather.theme);
renderHours(els.hourGrid, samples);
els.normalView.hidden = false;
els.errorView.hidden = true;
} catch (err) {
showError('Could not load weather data for ' + name + ' on ' + formatDateLabel(requestedDate) + '. Please try again later.');
}
})();
})();
</script>
</body>
</html>L1 — cheap deterministic descriptors
| bytes total | 37,417 | html / css / js | 3,189 / 11,105 / 23,123 |
|---|---|---|---|
| dom nodes | 58 | dom depth | 10 |
| css rules | 65 | js present | yes |
| brightness | 40.9 | contrast | 25.0 |
| colorfulness | 19.1 | whitespace | 56.8% |
This slot's telemetry
| prompt tokens | 312 | completion tokens | 151,734 |
|---|---|---|---|
| total tokens | 152,046 | wall | 619.5 s |
| cost | — | request id | — |
config.json585 B
{
"N": 4,
"auth": {
"credential_ref": "WCB_OPENAI_OFFICIAL_KEY",
"method": "api-key"
},
"billing": "metered",
"config_id": "gpt-5.4-mini-eff-high--api--official--dev",
"effort": "high",
"family": "gpt",
"m": {
"min": 4,
"q": 4
},
"model_id": "gpt-5.4-mini",
"protocol": "api",
"served_model": "gpt-5.4-mini-2026-03-17",
"telemetry": {
"completion_tokens": 711138,
"cost_usd": null,
"prompt_tokens": 46103,
"total_tokens": 757241,
"wall_ms": 3151294
},
"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-mini-eff-high--api--official--dev",
"positions": [
{
"attempts": [
{
"attempt_index": 0,
"backoff_ms": null,
"charged": true,
"ended_at": "2026-07-17T19:02:32.746919+00:00",
"http_status": 200,
"outcome": "valid",
"reached_model": true,
"reason": "ok",
"request_id": null,
"started_at": "2026-07-17T18:57:32.917864+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-17T19:12:55.898362+00:00",
"http_status": 200,
"outcome": "valid",
"reached_model": true,
"reason": "ok",
"request_id": null,
"started_at": "2026-07-17T19:02:36.345282+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-17T19:25:04.463137+00:00",
"http_status": 200,
"outcome": "valid",
"reached_model": true,
"reason": "ok",
"request_id": null,
"started_at": "2026-07-17T19:13:00.882083+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-17T19:27:54.560776+00:00",
"http_status": 200,
"outcome": "valid",
"reached_model": true,
"reason": "ok",
"request_id": null,
"started_at": "2026-07-17T19:25:08.599041+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-17T19:35:22.722082+00:00",
"http_status": 200,
"outcome": "valid",
"reached_model": true,
"reason": "ok",
"request_id": null,
"started_at": "2026-07-17T19:27:58.535886+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-17T19:39:18.798470+00:00",
"http_status": 200,
"outcome": "valid",
"reached_model": true,
"reason": "ok",
"request_id": null,
"started_at": "2026-07-17T19:35:26.804169+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-17T19:43:22.669351+00:00",
"http_status": 200,
"outcome": "valid",
"reached_model": true,
"reason": "ok",
"request_id": null,
"started_at": "2026-07-17T19:39:22.335277+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-17T19:50:32.308809+00:00",
"http_status": 200,
"outcome": "valid",
"reached_model": true,
"reason": "ok",
"request_id": null,
"started_at": "2026-07-17T19:43:26.508271+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
}
}
}