<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>4/3 Clock</title> <link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Orbitron:wght@400;700;900&display=swap" rel="stylesheet"> <style> :root { --bg: #0a0a0f; --panel: #0f0f1a; --border: #1a1a2e; --accent: #00ffe5; --accent2: #ff6b35; --accent3: #a855f7; --text: #e0e0ff; --dim: #4a4a6a; --glow: 0 0 20px rgba(0,255,229,0.4); --glow2: 0 0 20px rgba(255,107,53,0.4); } * { margin: 0; padding: 0; box-sizing: border-box; } body { background: var(--bg); color: var(--text); font-family: 'Share Tech Mono', monospace; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; overflow: hidden; position: relative; } /* Animated grid background */ body::before { content: ''; position: fixed; inset: 0; background-image: linear-gradient(rgba(0,255,229,0.03) 1px, transparent 1px), linear-gradient(90deg, rgba(0,255,229,0.03) 1px, transparent 1px); background-size: 40px 40px; animation: gridShift 20s linear infinite; pointer-events: none; } @keyframes gridShift { from { transform: translateY(0); } to { transform: translateY(40px); } } /* Scanline overlay */ body::after { content: ''; position: fixed; inset: 0; background: repeating-linear-gradient( 0deg, transparent, transparent 2px, rgba(0,0,0,0.08) 2px, rgba(0,0,0,0.08) 4px ); pointer-events: none; z-index: 100; } .container { display: flex; flex-direction: column; align-items: center; gap: 32px; z-index: 1; padding: 20px; } /* Title */ .title { font-family: 'Orbitron', monospace; font-weight: 900; font-size: clamp(1rem, 3vw, 1.5rem); letter-spacing: 0.4em; text-transform: uppercase; color: var(--accent); text-shadow: var(--glow); display: flex; align-items: center; gap: 16px; } .title::before, .title::after { content: ''; width: 60px; height: 1px; background: linear-gradient(90deg, transparent, var(--accent)); } .title::after { background: linear-gradient(90deg, var(--accent), transparent); } /* Clock faces row */ .clocks-row { display: flex; gap: 28px; align-items: flex-start; flex-wrap: wrap; justify-content: center; } /* Analog clock */ .analog-wrap { display: flex; flex-direction: column; align-items: center; gap: 12px; } .analog-label { font-size: 0.65rem; letter-spacing: 0.25em; color: var(--dim); text-transform: uppercase; } canvas#clockCanvas { border-radius: 50%; box-shadow: 0 0 0 1px var(--border), 0 0 40px rgba(0,255,229,0.15), inset 0 0 40px rgba(0,0,0,0.5); } /* Digital display */ .digital-panel { background: var(--panel); border: 1px solid var(--border); border-radius: 4px; padding: 28px 36px; display: flex; flex-direction: column; gap: 20px; min-width: 320px; position: relative; overflow: hidden; box-shadow: 0 0 40px rgba(0,0,0,0.6); } .digital-panel::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px; background: linear-gradient(90deg, transparent, var(--accent), var(--accent3), var(--accent2), transparent); } .time-block { display: flex; flex-direction: column; gap: 4px; } .time-block-label { font-size: 0.6rem; letter-spacing: 0.3em; color: var(--dim); text-transform: uppercase; } .time-value { font-family: 'Orbitron', monospace; font-weight: 700; font-size: clamp(2rem, 6vw, 2.8rem); letter-spacing: 0.05em; line-height: 1; color: var(--accent); text-shadow: var(--glow); transition: color 0.1s; } .time-value.real { font-size: clamp(1rem, 3vw, 1.2rem); color: var(--dim); text-shadow: none; font-weight: 400; } .divider { width: 100%; height: 1px; background: var(--border); } /* Progress bars */ .bars-section { display: flex; flex-direction: column; gap: 10px; } .bar-row { display: flex; align-items: center; gap: 10px; } .bar-label { font-size: 0.6rem; letter-spacing: 0.2em; color: var(--dim); width: 24px; text-transform: uppercase; } .bar-track { flex: 1; height: 4px; background: var(--border); border-radius: 2px; overflow: hidden; } .bar-fill { height: 100%; border-radius: 2px; transition: width 0.1s linear; } .bar-fill.h { background: linear-gradient(90deg, var(--accent3), var(--accent)); } .bar-fill.m { background: linear-gradient(90deg, var(--accent), var(--accent2)); } .bar-fill.s { background: linear-gradient(90deg, var(--accent2), #ffcc00); } .bar-pct { font-size: 0.58rem; color: var(--dim); width: 36px; text-align: right; } /* Info panel */ .info-panel { background: var(--panel); border: 1px solid var(--border); border-radius: 4px; padding: 16px 24px; display: flex; gap: 32px; flex-wrap: wrap; justify-content: center; max-width: 680px; width: 100%; } .info-item { display: flex; flex-direction: column; align-items: center; gap: 4px; } .info-num { font-family: 'Orbitron', monospace; font-size: 1.1rem; font-weight: 700; color: var(--accent2); text-shadow: var(--glow2); } .info-desc { font-size: 0.6rem; letter-spacing: 0.15em; color: var(--dim); text-align: center; } /* Tick animation */ @keyframes tick { 0% { opacity: 1; } 49% { opacity: 1; } 50% { opacity: 0; } 99% { opacity: 0; } 100% { opacity: 1; } } .colon { animation: tick 1s step-start infinite; } .subtitle { font-size: 0.6rem; letter-spacing: 0.2em; color: var(--dim); text-align: center; max-width: 500px; } </style> </head> <body> <div class="container"> <div class="title">4/3 Timescale Clock</div> <p class="subtitle">24×⁴⁄₃ hours · 60×⁴⁄₃ minutes · 60×⁴⁄₃ seconds · Real time mapped to expanded scale</p> <div class="clocks-row"> <!-- Analog --> <div class="analog-wrap"> <span class="analog-label">Analog · 32h face</span> <canvas id="clockCanvas" width="260" height="260"></canvas> </div> <!-- Digital --> <div class="digital-panel"> <div class="time-block"> <div class="time-block-label">4/3 Time</div> <div class="time-value" id="display43">00<span class="colon">:</span>00<span class="colon">:</span>00</div> </div> <div class="divider"></div> <div class="time-block"> <div class="time-block-label">Real Time (your clock)</div> <div class="time-value real" id="displayReal">00:00:00</div> </div> <div class="divider"></div> <div class="bars-section"> <div class="bar-row"> <span class="bar-label">H</span> <div class="bar-track"><div class="bar-fill h" id="barH" style="width:0%"></div></div> <span class="bar-pct" id="pctH">0%</span> </div> <div class="bar-row"> <span class="bar-label">M</span> <div class="bar-track"><div class="bar-fill m" id="barM" style="width:0%"></div></div> <span class="bar-pct" id="pctM">0%</span> </div> <div class="bar-row"> <span class="bar-label">S</span> <div class="bar-track"><div class="bar-fill s" id="barS" style="width:0%"></div></div> <span class="bar-pct" id="pctS">0%</span> </div> </div> </div> </div> <div class="info-panel"> <div class="info-item"> <span class="info-num">32</span> <span class="info-desc">hours/day</span> </div> <div class="info-item"> <span class="info-num">80</span> <span class="info-desc">minutes/hour</span> </div> <div class="info-item"> <span class="info-num">80</span> <span class="info-desc">seconds/minute</span> </div> <div class="info-item"> <span class="info-num">204,800</span> <span class="info-desc">seconds/day</span> </div> <div class="info-item"> <span class="info-num">0.75×</span> <span class="info-desc">real second rate</span> </div> </div> </div> <script> // ─── Constants ─────────────────────────────────────────────────────────────── const HOURS_PER_DAY = 24 * 4 / 3; // 32 const MINS_PER_HOUR = 60 * 4 / 3; // 80 const SECS_PER_MIN = 60 * 4 / 3; // 80 const SECS_PER_DAY_43 = HOURS_PER_DAY * MINS_PER_HOUR * SECS_PER_MIN; // 204800 const SECS_PER_DAY_RE = 24 * 60 * 60; // 86400 // ─── Convert real seconds-since-midnight → 4/3 time ───────────────────────── function toScaled(realSecsSinceMidnight) { // Map [0, 86400) → [0, 204800) proportionally const scaled = realSecsSinceMidnight * (SECS_PER_DAY_43 / SECS_PER_DAY_RE); const totalSecs = Math.floor(scaled); const h = Math.floor(totalSecs / (MINS_PER_HOUR * SECS_PER_MIN)); const m = Math.floor((totalSecs % (MINS_PER_HOUR * SECS_PER_MIN)) / SECS_PER_MIN); const s = totalSecs % SECS_PER_MIN; const frac = scaled - totalSecs; // sub-second fraction for smooth hands return { h, m, s, frac, scaled, hFrac: scaled / (MINS_PER_HOUR * SECS_PER_MIN), mFrac: (scaled % (MINS_PER_HOUR * SECS_PER_MIN)) / (MINS_PER_HOUR * SECS_PER_MIN) * MINS_PER_HOUR, sFrac: (scaled % SECS_PER_MIN) / SECS_PER_MIN }; } function pad(n, w=2) { return String(Math.floor(n)).padStart(w, '0'); } // ─── Analog Canvas ──────────────────────────────────────────────────────────── const canvas = document.getElementById('clockCanvas'); const ctx = canvas.getContext('2d'); const CX = canvas.width / 2; const CY = canvas.height / 2; const R = CX - 10; function drawClock(t) { ctx.clearRect(0, 0, canvas.width, canvas.height); // Face const grad = ctx.createRadialGradient(CX, CY, 10, CX, CY, R); grad.addColorStop(0, '#12121f'); grad.addColorStop(1, '#0a0a14'); ctx.beginPath(); ctx.arc(CX, CY, R, 0, Math.PI * 2); ctx.fillStyle = grad; ctx.fill(); // Outer ring glow ctx.beginPath(); ctx.arc(CX, CY, R, 0, Math.PI * 2); ctx.strokeStyle = 'rgba(0,255,229,0.25)'; ctx.lineWidth = 1.5; ctx.stroke(); // Hour markers (32) for (let i = 0; i < HOURS_PER_DAY; i++) { const angle = (i / HOURS_PER_DAY) * Math.PI * 2 - Math.PI / 2; const isMajor = i % 4 === 0; const len = isMajor ? 14 : 7; const width = isMajor ? 2 : 1; const x1 = CX + Math.cos(angle) * (R - 4); const y1 = CY + Math.sin(angle) * (R - 4); const x2 = CX + Math.cos(angle) * (R - 4 - len); const y2 = CY + Math.sin(angle) * (R - 4 - len); ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.strokeStyle = isMajor ? 'rgba(0,255,229,0.8)' : 'rgba(0,255,229,0.3)'; ctx.lineWidth = width; ctx.stroke(); if (isMajor) { const hr = i === 0 ? 32 : i; const tx = CX + Math.cos(angle) * (R - 26); const ty = CY + Math.sin(angle) * (R - 26); ctx.fillStyle = 'rgba(0,255,229,0.7)'; ctx.font = '9px "Share Tech Mono"'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(hr, tx, ty); } } // Minute markers (80 per hour shown as 80 ticks) for (let i = 0; i < MINS_PER_HOUR; i++) { const angle = (i / MINS_PER_HOUR) * Math.PI * 2 - Math.PI / 2; if (i % (MINS_PER_HOUR / HOURS_PER_DAY) === 0) continue; // skip where hour ticks are const x1 = CX + Math.cos(angle) * (R - 4); const y1 = CY + Math.sin(angle) * (R - 4); const x2 = CX + Math.cos(angle) * (R - 9); const y2 = CY + Math.sin(angle) * (R - 9); ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.strokeStyle = 'rgba(255,107,53,0.2)'; ctx.lineWidth = 1; ctx.stroke(); } // ─── Hands ──── const { h, m, s, frac, hFrac, mFrac, sFrac } = t; // Hour hand (32h face) const hAngle = ((h + (m + (s + frac) / SECS_PER_MIN) / MINS_PER_HOUR) / HOURS_PER_DAY) * Math.PI * 2 - Math.PI / 2; drawHand(hAngle, R * 0.5, 5, '#a855f7', 'rgba(168,85,247,0.3)', 16); // Minute hand (80 min face) const mAngle = ((m + (s + frac) / SECS_PER_MIN) / MINS_PER_HOUR) * Math.PI * 2 - Math.PI / 2; drawHand(mAngle, R * 0.7, 3.5, '#00ffe5', 'rgba(0,255,229,0.3)', 12); // Second hand (80 sec face) const sAngle = ((s + frac) / SECS_PER_MIN) * Math.PI * 2 - Math.PI / 2; drawHand(sAngle, R * 0.85, 1.5, '#ff6b35', 'rgba(255,107,53,0.4)', 8); // Center dot ctx.beginPath(); ctx.arc(CX, CY, 5, 0, Math.PI * 2); ctx.fillStyle = '#00ffe5'; ctx.shadowColor = '#00ffe5'; ctx.shadowBlur = 10; ctx.fill(); ctx.shadowBlur = 0; } function drawHand(angle, length, width, color, glowColor, tailLen) { ctx.save(); ctx.shadowColor = color; ctx.shadowBlur = 12; // Main hand ctx.beginPath(); ctx.moveTo( CX - Math.cos(angle) * tailLen, CY - Math.sin(angle) * tailLen ); ctx.lineTo( CX + Math.cos(angle) * length, CY + Math.sin(angle) * length ); ctx.strokeStyle = color; ctx.lineWidth = width; ctx.lineCap = 'round'; ctx.stroke(); ctx.restore(); } // ─── Update loop ────────────────────────────────────────────────────────────── function update() { const now = new Date(); const realSecs = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds() + now.getMilliseconds() / 1000; const t = toScaled(realSecs); // Digital 4/3 display document.getElementById('display43').innerHTML = `${pad(t.h)}<span class="colon">:</span>${pad(t.m)}<span class="colon">:</span>${pad(t.s)}`; // Real time document.getElementById('displayReal').textContent = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`; // Progress bars const hPct = (t.h / HOURS_PER_DAY) * 100; const mPct = (t.m / MINS_PER_HOUR) * 100; const sPct = (t.s / SECS_PER_MIN) * 100; document.getElementById('barH').style.width = hPct + '%'; document.getElementById('barM').style.width = mPct + '%'; document.getElementById('barS').style.width = sPct + '%'; document.getElementById('pctH').textContent = hPct.toFixed(1) + '%'; document.getElementById('pctM').textContent = mPct.toFixed(1) + '%'; document.getElementById('pctS').textContent = sPct.toFixed(1) + '%'; drawClock(t); requestAnimationFrame(update); } update(); </script> </body> </html>
Now I'll convert this 4/3 Clock to a proper Next.js React application with components and the right styling.
I've converted your 4/3 Timescale Clock HTML file into a proper Next.js React application. The implementation includes:
lib/clock-utils.ts - Contains all the time conversion logic and constants (32 hours/day, 80 minutes/hour, 80 seconds/minute)components/analog-clock.tsx - Canvas-based analog clock with a 32-hour face, smooth hands for hours (purple), minutes (cyan), and seconds (orange)components/digital-display.tsx - Digital time display with the scaled 4/3 time, real time comparison, and animated progress barscomponents/info-panel.tsx - Stats panel showing the timescale parameterscomponents/clock-container.tsx - Main client component that manages the animation loop with requestAnimationFrame for smooth updatesapp/page.tsx - The main page with the animated grid background and scanline overlayThe styling preserves the original cyberpunk/tech aesthetic with the cyan, orange, and purple accent colors, glowing effects, and the animated grid background.
You can send messages while the preview loads