/* schema-clock.jsx — 24h round dial. NU always at top; the dial rotates over time. */ const ElprisSchemaClock = ({ slots, nowIdx, now, scenario, refreshCount, refreshAt, quarterHistory, liveAction, soc, battKwh = 13.5 }) => { // Live decision för NU-kvarten kan skilja sig från LP-planen (reaktiv guard). // Visa det faktiskt skickade kommandot i NU-sektorn och i centrum. const nowAction = liveAction || (slots[nowIdx] && slots[nowIdx].action); const W = 520, H = 520; const cx = W/2, cy = H/2; // Rings (radii) const Router = 240; const Rlabel = 232; // fixed relative-time label radius const Rtomo2 = 218, Rtomo1 = 212; // imorgon teaser ring (thin) const Raction2= 206, Raction1= 188; // today action sector ring const Rprice2 = 184, Rprice1 = 138; // radial price bars const Rcurve2 = 136, Rcurve1 = 74; // solar + consumption radial space (närmare staplarna, större utslag) const Rcenter = 70; // central disc const today = slots.slice(0, 96); const tomorrow = slots.slice(96, 192); // Vald dag att visa i ringen (UX 2026-06-04: Idag/Imorgon-knappar). const [dayView, setDayView] = React.useState(0); const hasTomorrow = tomorrow.length === 96 && tomorrow.some(s => s && s.buy != null); const viewDay = (dayView === 1 && hasTomorrow) ? tomorrow : today; // Färg-uppslag för action-ringen (Lennie-fix 2026-05-31). Identisk teknik // som forecast.jsx:metaForApiAction — slå alltid upp via HEMS-koden (som kan // vara rå API-kod från quarter-history ELLER redan svensk plan-nyckel) och // mappa internt. Platt meta.color används överallt (inga gradient-id:n) så // past multi, past single och framtida kvartar ser konsekventa ut. const segMeta = (apiAction, planAction) => { const map = window.hemsAdapter && window.hemsAdapter.mapAction; const k = map ? map(apiAction) : apiAction; if (ACTION_META[k] && ACTION_META[k].color) return ACTION_META[k]; // SPEC graf-sanning §1: okänd/osänd action i dåtid färgas ALDRIG från // planen — rendera neutralt (ACTION_META-fallback = 'Okänd', grå). return ACTION_META['okand']; }; // Segment per kvart. Framtid = ett plan-segment. Past med blandade actions // = array av {action, proportion} från quarter-history. Past single/saknad // = ett segment med faktiskt skickat kommando (annars planen). const getRingSegments = (s, i, past) => { // Delad källa med prognosremsan + live-staplarna (hemsAdapter.quarterSegments): // förflutet = dispatch-sanning m. <10%-golv, utan data = neutralt, framtid = planen. // Så klockan och graferna aldrig kan driva isär (SPEC graf-sanning 2026-07-10). const segOf = window.hemsAdapter && window.hemsAdapter.quarterSegments; if (segOf) return segOf(i, past, quarterHistory, s.action); if (!past) return [{ action: s.action, proportion: 1.0 }]; const q = (quarterHistory?.quarters || {})[String(i)]; if (!q || !Array.isArray(q.actions) || q.actions.length === 0) return [{ action: 'okand', proportion: 1 }]; return q.actions.map(a => ({ action: a.action, proportion: a.proportion ?? 1/q.actions.length })); }; // 00:00 at the original top (before rotation), clockwise. const angleQ = (q) => (q / 96) * 360 - 90; const polar = (r, deg) => { const a = deg * Math.PI / 180; return [cx + r*Math.cos(a), cy + r*Math.sin(a)]; }; const wedge = (rIn, rOut, a0, a1) => { const [x0o,y0o] = polar(rOut, a0); const [x1o,y1o] = polar(rOut, a1); const [x1i,y1i] = polar(rIn, a1); const [x0i,y0i] = polar(rIn, a0); const large = (a1-a0) > 180 ? 1 : 0; return `M ${x0o} ${y0o} A ${rOut} ${rOut} 0 ${large} 1 ${x1o} ${y1o} L ${x1i} ${y1i} A ${rIn} ${rIn} 0 ${large} 0 ${x0i} ${y0i} Z`; }; // --- Wheel interaction (UX 2026-06-04): dialen är ett vridbart hjul. // Svep/skrolla horisontellt → välj tid; norrpilen (toppen) visar VALD kvart. // selQ === null betyder "följ NU". Annars en vald kvart (0..95). --- const [selQ, setSelQ] = React.useState(null); // Auto-återgång till NU efter 5s utan interaktion (UX 2026-06-04). const selTimer = React.useRef(null); React.useEffect(() => { if (selQ == null) return; if (selTimer.current) clearTimeout(selTimer.current); selTimer.current = setTimeout(() => setSelQ(null), 5000); return () => { if (selTimer.current) clearTimeout(selTimer.current); }; }, [selQ]); const selQuarter = selQ != null ? selQ : nowIdx; // Rotera så vald kvart hamnar i toppen (-90°). const selDeg = (selQuarter / 96) * 360 - 90; const nowDeg = (((now ? (now.getHours()*60 + now.getMinutes()) : (nowIdx*15)) / (24*60)) * 360) - 90; // Live-läge (ingen skrubbning) → följ minut-exakt tid mjukt. Skrubbning → snäpp till kvart. const dialRot = -90 - (selQ != null ? selDeg : nowDeg); // degrees // --- Smooth a series with a small moving average --- const smoothed = (arr, key) => { return arr.map((_, i) => { const a = arr[Math.max(0, i-1)][key]; const b = arr[i][key]; const c = arr[Math.min(arr.length-1, i+1)][key]; return (a*0.25 + b*0.5 + c*0.25); }); }; const solarSmoothed = smoothed(viewDay, 'solar'); const consSmoothed = smoothed(viewDay, 'consumption'); // --- Radial closed path from N samples --- const radialPath = (values, getR) => { const N = values.length; if (N < 2) return ''; const pts = values.map((v,i)=> polar(getR(v), angleQ(i + 0.5))); let d = `M ${pts[0][0].toFixed(2)} ${pts[0][1].toFixed(2)}`; for (let i=1; i<=N; i++){ const p0 = pts[i-1]; const p1 = pts[i % N]; const mx = (p0[0]+p1[0])/2; const my = (p0[1]+p1[1])/2; d += ` Q ${p0[0].toFixed(2)} ${p0[1].toFixed(2)} ${mx.toFixed(2)} ${my.toFixed(2)}`; } return d + ' Z'; }; const solarMax = Math.max(0.6, ...solarSmoothed, ...consSmoothed); const consMax = solarMax; // share scale so they're visually comparable const solarR = (v) => Rcurve1 + (v / solarMax) * (Rcurve2 - Rcurve1); const consR = (v) => Rcurve1 + (v / consMax) * (Rcurve2 - Rcurve1); const solarPath = radialPath(solarSmoothed, solarR); const consPath = radialPath(consSmoothed, consR); const maxPrice = Math.max(...slots.map(s=>s.buy)); const meanPrice = today.reduce((a,s)=>a+s.buy,0)/today.length; const meanR = Rprice1 + (meanPrice/maxPrice) * (Rprice2 - Rprice1); const cur = slots[((dayView === 1 && hasTomorrow) ? 96 : 0) + selQuarter]; // selQ scrubbing via swipe / wheel — make the dial behave like a wheel. const dragRef = React.useRef(null); const wheelDir = (clientX) => { if (dragRef.current == null){ dragRef.current = clientX; return; } const dx = clientX - dragRef.current; if (Math.abs(dx) >= 14){ // ~14px per kvart-steg const steps = Math.trunc(dx / 14); setSelQ(prev => { const base = prev != null ? prev : nowIdx; // dra åt höger (dx>0) = bakåt i tid, åt vänster = framåt (hjul-känsla) let n = base - steps; return Math.max(0, Math.min(95, n)); }); dragRef.current = clientX; } }; const onWheel = (e) => { e.preventDefault(); setSelQ(prev => { const base = prev != null ? prev : nowIdx; const dir = (e.deltaY || e.deltaX) > 0 ? 1 : -1; return Math.max(0, Math.min(95, base + dir)); }); }; const onMouseDown = (e) => { dragRef.current = e.clientX; }; const onMouseMoveDrag = (e) => { if (e.buttons === 1) wheelDir(e.clientX); }; const onMouseUp = () => { dragRef.current = null; }; const onTouchStartW = (e) => { const t=e.touches[0]; if(t) dragRef.current = t.clientX; }; const onTouchMoveW = (e) => { const t=e.touches[0]; if(!t) return; e.preventDefault(); wheelDir(t.clientX); }; const onTouchEndW = () => { dragRef.current = null; }; // Day boundary marker — in the rotating dial, this is the slot at q=0 (midnight start). const dayBoundaryDeg = angleQ(0); // Hover: convert pointer to local (un-rotated) angle and pick quarter. const svgRef = React.useRef(null); const [hover, setHover] = React.useState(null); const pickAt = (clientX, clientY) => { const r = svgRef.current.getBoundingClientRect(); const mx = ((clientX - r.left)/r.width) * W - cx; const my = ((clientY - r.top )/r.height) * H - cy; const dist = Math.hypot(mx,my); if (dist < Rcenter-4 || dist > Raction2+8) { setHover(null); return; } let visualDeg = Math.atan2(my, mx) * 180/Math.PI; let localDeg = (visualDeg - dialRot + 720) % 360; let fromTop = (localDeg + 90 + 360) % 360; const q = Math.floor((fromTop / 360) * 96); setHover(q); }; const handleMove = (e) => pickAt(e.clientX, e.clientY); const handleTouchMove = (e) => { const t = e.touches[0]; if (!t) return; e.preventDefault(); pickAt(t.clientX, t.clientY); }; const handleTouchEnd = () => setTimeout(() => setHover(null), 1800); // Stats const sumSolarToday = today.reduce((a,s)=>a+s.solar*0.25, 0); const sumConsToday = today.reduce((a,s)=>a+s.consumption*0.25, 0); // Lennie-fix 2026-05-31: KVARVARANDE solöverskott (sol kvar − förbrukning kvar // resten av dygnet). Det actionable talet — heldagssiffran (sumSolar−sumCons) // krockar med verkligheten kl 12 (kvar ≈ 0). Räknas från NU till midnatt. const remStart = Math.min(96, (nowIdx ?? 0) + 1); const remSolarToday = today.slice(remStart).reduce((a,s)=>a+s.solar*0.25, 0); const remConsToday = today.slice(remStart).reduce((a,s)=>a+s.consumption*0.25, 0); const remSurplus = remSolarToday - remConsToday; const fullDaySurplus = sumSolarToday - sumConsToday; const cd = cur; const isHover = selQ != null; // Gravio-korten (topprad) speglar VALD kvart (cur = slots[selQuarter]). const sv1 = (n) => (n ?? 0).toFixed(1).replace('.',','); const sv2 = (n) => (n ?? 0).toFixed(2).replace('.',','); const selAct = (selQ == null && nowAction) ? nowAction : cur.action; const selMeta = ACTION_META[selAct] || ACTION_META.okand; const curSurplus = cur.solar - cur.consumption; const curSoc = (typeof cur.soc === 'number') ? cur.soc : (soc ?? 0); const socKwh = (curSoc/100) * battKwh; const priceDelta = Math.round(cur.buy - meanPrice); return (

Schema 24h

{(() => { if (refreshAt == null) return 'Bakåt: skickat · framåt: plan · NU uppåt'; const s = Math.max(0, Math.floor((Date.now()-refreshAt)/1000)); return s<20?'Plan uppdaterad just nu' : s<60?`Plan uppdaterad för ${s}s sedan` : s<3600?`Plan uppdaterad för ${Math.floor(s/60)} min sedan` : `Plan uppdaterad för ${Math.floor(s/3600)} h sedan`; })()} {refreshCount != null && }
{hasTomorrow && (
)} {selQ != null && ( )}
{/* Gravio-infokort — fyra kompakta rutor ovanför hjulet (VALD kvart) */}
ELPRIS
{Math.round(cur.buy)}öre
{selMeta.label}
{cur.buy>=meanPrice?`+${priceDelta} över snittet`:`−${Math.abs(priceDelta)} under snittet`}
FÖRBRUKN.
{sv1(cur.consumption)}kW
{sv2(cur.consumption*0.25)} kWh/kvart
SOL
{sv1(cur.solar)}kW
{cur.solar<0.05?'ingen prod.':curSurplus>=0?`+${sv1(curSurplus)} kW över`:`${Math.round(cur.solar/Math.max(0.1,cur.consumption)*100)} % av behov`}
BATTERI
{Math.round(curSoc)}%
{sv1(socKwh)}/{sv1(battKwh)} kWh
{ const svg=svgRef.current; if(!svg) return; const r=svg.getBoundingClientRect(); const ang=Math.atan2(e.clientY-(r.top+r.height/2), e.clientX-(r.left+r.width/2))*180/Math.PI; dragRef.current={ang, acc:0, base:(selQ!=null?selQ:nowIdx)}; e.currentTarget.setPointerCapture(e.pointerId); }} onPointerMove={(e)=>{ if(!dragRef.current) return; const svg=svgRef.current; if(!svg) return; const r=svg.getBoundingClientRect(); const a=Math.atan2(e.clientY-(r.top+r.height/2), e.clientX-(r.left+r.width/2))*180/Math.PI; let d=a-dragRef.current.ang; if(d>180)d-=360; if(d<-180)d+=360; dragRef.current.ang=a; dragRef.current.acc+=d; const steps=Math.round(dragRef.current.acc/3.75); // 360°/96 = 3.75° per kvart setSelQ(Math.max(0,Math.min(95,dragRef.current.base+steps))); }} onPointerUp={()=>{ dragRef.current=null; }} onPointerCancel={()=>{ dragRef.current=null; }} style={{touchAction:'pan-y',cursor:'grab'}}> {Object.entries(ACTION_META).map(([k, m]) => ( ))} {/* Soft dial halo */} {/* Outer frame */} {/* ===== ROTATING DIAL GROUP ===== */} {/* Today action ring — ärlig modell (Lennie-fix 2026-05-31): FÖRFLUTNA/NUVARANDE kvartar visar FAKTISKT SKICKAT kommando (/api/quarter-history actions[], t.ex. chargesolar efter Ferro-override). FRAMTIDA kvartar visar LP:s PLAN (s.action). Tidigare blandades plan (single-segment fallback + prisstaplar) och skickat (multi-segment) → motsägelsefullt. Nu konsekvent. */} {viewDay.map((s,i)=>{ const gap = 1.2; // Gravio-look: tydligare luckor → diskreta brickor const a0 = angleQ(i) + gap/2; const a1 = angleQ(i+1) - gap/2; const isToday = (dayView === 0); const isNow = isToday && i === nowIdx; const isPast = isToday && i < nowIdx; // imorgon: inget förflutet const useSent = isToday && i <= nowIdx; // imorgon: allt är plan const segs = isNow && liveAction ? [{ action: liveAction, proportion: 1 }] : getRingSegments(s, i, useSent); // NU lyser i full opacitet (inte fadat). Förflutet dämpas. const op = isNow ? 1 : isPast ? .5 : (selQ==null || selQ===i ? 1 : .85); if (segs.length > 1){ let accProp = 0; return ( {isNow && ( )} {segs.map((seg, j) => { const sA = a0 + (a1-a0) * accProp; const eA = a0 + (a1-a0) * (accProp + seg.proportion); accProp += seg.proportion; return ; })} ); } return ( {isNow && ( )} ); })} {/* Inner separator between action ring and price bars */} {/* Radial price bars — färgas konsekvent med action-ringen: skickat kommando för förflutna/nuvarande, plan för framtida. */} {viewDay.map((s,i)=>{ const a = angleQ(i + 0.5); const len = (s.buy/maxPrice) * (Rprice2 - Rprice1); const [x0,y0] = polar(Rprice1 + 2, a); const [x1,y1] = polar(Rprice1 + 2 + len, a); const past = i <= nowIdx; const _q = past ? (quarterHistory?.quarters || {})[String(i)] : null; const _sent = (_q && Array.isArray(_q.actions) && _q.actions.length > 0) ? _q.actions[0].action : null; const _k = _sent ? ((window.hemsAdapter && window.hemsAdapter.mapAction) ? window.hemsAdapter.mapAction(_sent) : _sent) : s.action; const barKey = ACTION_META[_k] ? _k : s.action; return ; })} {/* Hourly tick on the inner edge of action ring */} {[...Array(24)].map((_,h)=>{ const a = angleQ(h*4); const [x1,y1] = polar(Raction1-4, a); const [x2,y2] = polar(Raction1+1, a); return ; })} {/* Hour labels (absolute clock times) — counter-rotate so they stay upright. Skip the label closest to NU to avoid overlap with the red NU marker. */} {[0,3,6,9,12,15,18,21].map(h => { const a = angleQ(h*4); // Distance to NU in degrees (-180..180) const diff = ((a - nowDeg + 540) % 360) - 180; if (Math.abs(diff) < 7) return null; // ~28 min around NU const [tx,ty] = polar(Rlabel, a); const strong = h % 6 === 0; return ( {!strong && ( )} {String(h).padStart(2,'0')}:00 ); })} {/* Day-boundary marker (midnight) */} {(() => { const [x1,y1] = polar(Raction2+12, dayBoundaryDeg); const [x2,y2] = polar(Rtomo2+4, dayBoundaryDeg); return ( ); })()} {/* Rensad klocka (UX-feedback 2026-06-04): medelpris-ringen borttagen. Kvar: åtgärdsring + prisstaplar + sol- och förbrukningskurva. */} {/* Solkurva (produktion) */} {/* Förbrukningskurva */} {/* Baseline inner ring */} {/* ===== STATIC OVERLAY ===== */} {/* NU pointer at top — refined arrow design */} {/* Soft outer halo */} {/* NU label, slightly farther up */} NU {/* Slim downward-pointing chevron arrow */} {/* Tiny pulse dot tip */} {/* Center disc */} {/* Center disc — soft moat ring isolates it from the solar curve */} {!isHover ? ( <> NU {now ? `${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}` : fmt.hhmm(nowIdx)} {Math.round(cur.buy)} ÖRE / KWH {ACTION_META[nowAction].label} ) : ( <> {fmt.hhmm(selQuarter)} {selQuarter >= nowIdx ? `om ${Math.round(((selQuarter-nowIdx)*15)/60*10)/10} h` : `${Math.round(((nowIdx-selQuarter)*15)/60*10)/10} h sedan`} {Math.round(cd.buy)} ÖRE / KWH {ACTION_META[cd.action].label} )} {/* Vred — snurra hjulet utan att dra på själva dialen (UX 2026-06-04) */} {/* Bottom legend strip */}
{['ladda_nat','salj_batteri','salj_sol','ladda_sol','tack_huset','peak'].map(k=>{ const m = ACTION_META[k] || ACTION_META.okand; return {m.label}; })} Sol Förbrukning
{/* Quarter detail panel — ALWAYS rendered (NU by default) so hovering never changes the card height (annars hoppar energiflödet bredvid). */} {(() => { const di = selQ != null ? selQ : nowIdx; const s = slots[di]; if (!s) return null; const act = (selQ == null && nowAction) ? nowAction : s.action; const meta = ACTION_META[act]; const isNow = selQ == null; return (
{isNow && NU} {fmt.hhmm(di)} {meta.label}
Köp
{s.buy.toFixed(1).replace('.',',')}öre
Sälj
{s.sell.toFixed(1).replace('.',',')}öre
Sol
{s.solar.toFixed(1).replace('.',',')}kW
Förbr.
{s.consumption.toFixed(1).replace('.',',')}kW
); })()}
); }; const __clockStyles = document.createElement('style'); __clockStyles.textContent = ` .schema-clock{ container-type: inline-size; } .schema-clock .clock-daytoggle{ display:inline-flex; gap:3px; padding:3px; border-radius:999px; background:rgba(0,0,0,.28); border:1px solid var(--line-2); } .schema-clock .clock-daytoggle button{ appearance:none;border:0;background:transparent;cursor:pointer; font:inherit;font-size:12px;font-weight:500;color:var(--ink-2); padding:5px 14px;border-radius:999px;transition:background .15s,color .15s; } .schema-clock .clock-daytoggle button.on{ background:rgba(91,168,255,.18);color:var(--ink); } .schema-clock .clock-wrap{ position:relative; padding:28px 28px 16px; display:flex;flex-wrap:wrap;justify-content:center;align-items:center; } .schema-clock .clock-svg{display:block;width:100%; max-width:min(100%, 640px); height:auto;aspect-ratio:1/1;cursor:crosshair} /* Gravio-infokort ovanför hjulet */ .schema-clock .clock-cards{ display:grid;grid-template-columns:repeat(4,1fr);gap:10px; padding:14px 18px 0;font-family:Geist; } .schema-clock .cc{ background:rgba(255,255,255,.02);border:1px solid var(--line); border-radius:12px;padding:10px 12px;min-width:0; display:flex;flex-direction:column;gap:3px; } .schema-clock .cc-l{font-size:9.5px;letter-spacing:.12em;font-weight:600;color:var(--ink-3);text-transform:uppercase} .schema-clock .cc-v{font-size:24px;font-weight:600;color:var(--ink);letter-spacing:-.02em;line-height:1} .schema-clock .cc-v small{font-size:11px;color:var(--ink-2);font-weight:500;margin-left:3px} .schema-clock .cc-mode{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:600;min-width:0} .schema-clock .cc-mode .dot{width:7px;height:7px;border-radius:50%;flex:none} .schema-clock .cc-mode span:last-child{white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .schema-clock .cc-sub{font-size:10px;letter-spacing:.02em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .schema-clock .cc-bar{height:4px;border-radius:3px;background:rgba(255,255,255,.08);overflow:hidden;margin:2px 0 1px} .schema-clock .cc-bar>div{height:100%;border-radius:3px;background:linear-gradient(90deg,#2FA36B,#3FCC8B)} /* Corner stats */ .clock-corner{ position:absolute;display:flex;align-items:stretch;gap:10px; font-family:Geist;max-width:200px; } .clock-corner.tl{top:28px;left:28px} .clock-corner.tr{top:28px;right:28px;flex-direction:row} .clock-corner.bl{bottom:64px;left:28px} .clock-corner.br{bottom:64px;right:28px;flex-direction:row} .clock-corner .cs-pip{width:5px;height:42px;border-radius:3px;flex:none;align-self:center} .clock-corner .cs-l{font-size:9.5px;letter-spacing:.10em;font-weight:600; color:var(--ink-3);text-transform:uppercase} .clock-corner .cs-v{font-size:22px;font-weight:600;margin-top:3px;letter-spacing:-.02em;line-height:1} .clock-corner .cs-v small{font-size:11px;color:var(--ink-2);font-weight:500;margin-left:2px} .clock-corner .cs-h{font-size:10.5px;margin-top:4px;letter-spacing:.04em} /* Horizontal legend strip below the dial */ .clock-howto{ position:absolute;left:0;right:0;bottom:14px; display:flex;flex-wrap:wrap;justify-content:center;gap:18px; padding:0 22px; font-size:11px;color:var(--ink-2); } .clock-howto .cs-line{display:inline-flex;align-items:center;gap:7px;line-height:1.2} .clock-howto .sw{flex:none} .clock-howto .sw.dot{width:9px;height:9px;border-radius:50%} .clock-howto .sw.ring-act{ width:14px;height:14px;border-radius:50%; background:conic-gradient(#F4C84B,#FF8A47,#3FCC8B,#5BA8FF,#F4C84B); position:relative; } .clock-howto .sw.ring-act::after{ content:"";position:absolute;inset:4px;background:var(--bg-1);border-radius:50%; } .clock-howto .sw.ring-tom{ width:14px;height:14px;border-radius:50%; background:conic-gradient(#F4C84B,#3FCC8B,#FF8A47,#5BA8FF); position:relative;opacity:.55; } .clock-howto .sw.ring-tom::after{ content:"";position:absolute;inset:2px;background:var(--bg-1);border-radius:50%; } .clock-howto .sw.bar{width:3px;height:14px;border-radius:2px;background:#3FCC8B} .clock-howto .sw.sol{width:14px;height:0;border-top:1.6px solid #F4C84B} .clock-howto .sw.cons{width:14px;height:0;border-top:1.4px solid #E6ECF7;opacity:.75} .clock-detail{ border-top:1px solid var(--line); background:linear-gradient(180deg, rgba(91,168,255,.04), transparent); padding:14px 18px 16px; } .clock-knob{ flex-basis:100%; width:100%; order:10; display:flex;align-items:center;justify-content:center; padding:6px 12% 4px; cursor:grab; touch-action:none; outline:none; } .clock-knob:active{cursor:grabbing} /* Tumhjul — räfflad cylinder man drar i (UX 2026-06; pointer-driven) */ .clock-knob .knob-track{ width:100%;max-width:300px;height:26px;border-radius:6px; pointer-events:none; background: linear-gradient(90deg, #0a0f1a 0%, transparent 7%, transparent 93%, #0a0f1a 100%), repeating-linear-gradient(90deg, rgba(170,190,235,.30) 0 1px, rgba(120,140,185,.05) 1px 2px, transparent 2px 6px), linear-gradient(180deg, #05080f 0%, #0c1320 22%, #2b3650 50%, #0c1320 78%, #05080f 100%); border:1px solid #05080f; box-shadow: inset 0 6px 8px rgba(0,0,0,.8), inset 0 -6px 8px rgba(0,0,0,.8), inset 0 0 0 1px rgba(255,255,255,.03), 0 1px 0 rgba(255,255,255,.05); } .clock-detail .cd-time{ display:flex;align-items:center;gap:14px;margin-bottom:12px; } .clock-detail .cd-hhmm{ font-size:22px;font-weight:600;color:var(--ink);letter-spacing:-.01em; display:inline-flex;align-items:baseline;gap:8px; } .clock-detail .cd-nu{ font-size:10px;font-weight:700;letter-spacing:.16em; color:var(--c-peak);font-family:'Geist Mono',monospace; } .clock-detail .cd-act{ display:inline-flex;align-items:center;gap:7px; font-size:14px;font-weight:600;color:var(--ink); padding:5px 12px;border-radius:999px; background:rgba(255,255,255,.04);border:1px solid var(--line-2); } .clock-detail .cd-act .dot{width:9px;height:9px;border-radius:50%} .clock-detail .cd-grid{ display:grid;grid-template-columns:repeat(4,1fr);gap:10px; } .clock-detail .cd-cell{ background:rgba(255,255,255,.02);border:1px solid var(--line); border-radius:10px;padding:9px 12px; } .clock-detail .cd-l{ font-size:10px;letter-spacing:.08em;text-transform:uppercase; color:var(--ink-3);font-weight:600; } .clock-detail .cd-v{ font-size:21px;font-weight:600;color:var(--ink); letter-spacing:-.02em;margin-top:3px; } .clock-detail .cd-v small{font-size:11px;color:var(--ink-2);font-weight:500;margin-left:4px} @media (max-width: 560px){ .clock-detail .cd-grid{grid-template-columns:repeat(2,1fr)} .clock-detail .cd-v{font-size:19px} } /* Container query: when the clock CARD itself is narrow (split-screen on desktop, half-width windows, iPad portrait), stop absolutely positioning the corner stats over the dial — flow them into rows below it instead. NOTE: container-type lives on .schema-clock (parent); we only style DESCENDANTS here so the query actually applies. */ @container (max-width: 640px){ .schema-clock .clock-cards{grid-template-columns:repeat(2,1fr)} .schema-clock .clock-wrap{ display:flex;flex-direction:column;align-items:stretch;gap:12px; padding:20px 16px 14px; } .schema-clock .clock-svg{ order:0;margin:0 auto;width:100%;max-width:420px;flex:none; } /* Tumhjulet direkt under klockan — inte längst ned */ .schema-clock .clock-knob{ order:1 !important; } /* Detaljpanelen näst */ .schema-clock .clock-detail{ order:2; } .schema-clock .clock-corner{ position:static !important; max-width:none;width:auto; inset:auto !important; justify-content:flex-start !important; background:rgba(255,255,255,.02); border:1px solid var(--line); border-radius:10px; padding:10px 12px; } .schema-clock .clock-corner.tr, .schema-clock .clock-corner.br{flex-direction:row !important} .schema-clock .clock-corner.tr > div, .schema-clock .clock-corner.br > div{text-align:left !important} .schema-clock .clock-corner.tl{order:3} .schema-clock .clock-corner.tr{order:4} .schema-clock .clock-corner.bl{order:5} .schema-clock .clock-corner.br{order:6} /* Den utförliga ring-legenden tar för mycket plats på mobil — dölj den */ .schema-clock .clock-howto{ display:none !important; } } `; document.head.appendChild(__clockStyles); Object.assign(window, { ElprisSchemaClock });