Add build script and remove widget duplicates

This commit is contained in:
Kiyomichi Kosaka 2025-06-15 15:02:12 +02:00
parent 356e904abc
commit 1e1a961dc2
7 changed files with 31 additions and 975 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Build artifacts
CoBiEClock.wdgt
build-widget/

View File

@ -66,10 +66,12 @@ An interactive web app that visualizes the **CosmoChron Binary Epoch (CoBiE)** t
## macOS Dashboard Widget ## macOS Dashboard Widget
The repository includes a minimal Dashboard widget under The repository includes a minimal Dashboard widget under
`macos-widget/`. To install it on macOS: `macos-widget/`. A helper script `build-widget.sh` is provided to
package it automatically:
1. Compress the `macos-widget` folder into an archive. 1. Run `./build-widget.sh` from the repository root.
2. Rename the archive to `CoBiEClock.wdgt` and double-click it. 2. The script creates `CoBiEClock.wdgt` which you can double-click to
install.
The widget runs offline and shows the analog CoBiE clock on your The widget runs offline and shows the analog CoBiE clock on your
desktop. desktop.

23
build-widget.sh Executable file
View File

@ -0,0 +1,23 @@
#!/bin/bash
# Build the macOS Dashboard widget without duplicating code
set -euo pipefail
WIDGET_NAME="CoBiEClock"
SRC_DIR="macos-widget"
BUILD_DIR="build-widget"
# Clean build directory
rm -rf "$BUILD_DIR"
mkdir "$BUILD_DIR"
# Copy unique widget files
cp "$SRC_DIR/Info.plist" "$SRC_DIR/index.html" "$BUILD_DIR/"
# Copy shared files from repository root
cp clock.js cobie.js style.css logo.svg "$BUILD_DIR/"
# Create archive and rename
zip -r "${WIDGET_NAME}.zip" "$BUILD_DIR" > /dev/null
mv "${WIDGET_NAME}.zip" "${WIDGET_NAME}.wdgt"
echo "Widget created: ${WIDGET_NAME}.wdgt"

View File

@ -1,204 +0,0 @@
// Minimal CoBiE analog clock logic wrapped in its own scope to
// avoid clashes with variables from other scripts on the page.
(function () {
const {
COBIE_EPOCH,
COBIE_UNITS,
floorDiv,
getTAIOffsetAt,
toCobiets
} = window.Cobie;
function getMarkerOffset(width) {
const points = [
{ width: 1024, value: 2 },
{ width: 450, value: 1.3 },
{ width: 200, value: 0.8 }
];
// Sort points by width descending for easier handling
points.sort((a, b) => b.width - a.width);
for (let i = 0; i < points.length - 1; i++) {
const p1 = points[i];
const p2 = points[i + 1];
if (width <= p1.width && width >= p2.width) {
// Linear interpolation
const t = (width - p2.width) / (p1.width - p2.width);
return p2.value + t * (p1.value - p2.value);
}
}
// Extrapolation for width > max known
if (width > points[0].width) {
const p1 = points[0];
const p2 = points[1];
const slope = (p1.value - p2.value) / (p1.width - p2.width);
return p1.value + slope * (width - p1.width);
}
// Extrapolation for width < min known
const p1 = points[points.length - 2];
const p2 = points[points.length - 1];
const slope = (p2.value - p1.value) / (p2.width - p1.width);
return p2.value + slope * (width - p2.width);
}
function placeMarkers() {
const clock = document.getElementById('clock');
let markers = clock.querySelectorAll('.marker');
let ticks = clock.querySelectorAll('.tick');
// Create markers if they don't exist yet
if (markers.length === 0) {
for (let i = 0; i < 16; i++) {
const m = document.createElement('div');
m.className = 'marker';
m.textContent = i.toString(16).toUpperCase();
clock.appendChild(m);
}
markers = clock.querySelectorAll('.marker');
}
// Create tick marks once
if (ticks.length === 0) {
for (let i = 0; i < 256; i++) {
const t = document.createElement('div');
t.classList.add('tick');
if (i % 16 === 0) t.classList.add('big');
else if (i % 8 === 0) t.classList.add('mid');
// insert before markers so digits sit on top
clock.insertBefore(t, clock.firstChild);
}
ticks = clock.querySelectorAll('.tick');
}
// Unified radius based on the actual clock size
const baseRadius = clock.offsetWidth / 2;
// Tick lengths relative to the clock radius
const lenBig = baseRadius * 0.12;
const lenMid = baseRadius * 0.08;
const lenSmall = baseRadius * 0.05;
const outerR = baseRadius - 2; // just inside the border
// Distance from center for the marker digits so they sit just inside big ticks
const markerSize = markers[0] ? markers[0].offsetWidth : 0;
const markerRadius = outerR - lenBig - markerSize * getMarkerOffset(clock.offsetWidth);
markers.forEach((m, i) => {
const angle = (i / 16) * 2 * Math.PI;
m.style.left = '50%';
m.style.top = '50%';
m.style.transform =
`translate(-50%, -50%) rotate(${angle}rad) translate(0, -${markerRadius}px) rotate(${-angle}rad)`;
});
ticks.forEach((t, i) => {
let len = lenSmall;
if (t.classList.contains('big')) len = lenBig;
else if (t.classList.contains('mid')) len = lenMid;
const innerR = outerR - len;
const angle = ((i + 1) / 256) * 2 * Math.PI;
t.style.height = `${len}px`;
t.style.left = '50%';
t.style.top = '50%';
t.style.transform = `translate(-50%, 0) rotate(${angle}rad) translate(0, -${innerR}px)`;
if (clock.offsetWidth < 200 && !t.classList.contains('big') && !t.classList.contains('mid')) {
t.style.display = 'none';
} else {
t.style.display = '';
}
});
}
const lastAngles = {
handXeno: 0,
handQuantic: 0,
handChronon: 0,
handEonstrip: 0,
handMegasequence: 0
};
function rotateHand(id, angle) {
const el = document.getElementById(id);
if (!el) return;
const prev = lastAngles[id];
if (angle < prev) {
// When wrapping around (e.g. 15 → 0), animate to one full turn
// and then snap back to the new angle to avoid a jump.
const target = angle + 360;
const handle = () => {
el.removeEventListener('transitionend', handle);
// Snap back without animation
el.style.transition = 'none';
el.style.transform = `translateX(-50%) translateZ(0) rotate(${angle}deg)`;
void el.offsetWidth;
el.style.transition = '';
};
el.addEventListener('transitionend', handle, { once: true });
el.style.transform = `translateX(-50%) translateZ(0) rotate(${target}deg)`;
} else {
el.style.transform = `translateX(-50%) translateZ(0) rotate(${angle}deg)`;
}
lastAngles[id] = angle;
}
function renderClock(cob) {
// Use fractional progress within each unit so angles stay small
const xf = (cob % COBIE_UNITS.quantic) / COBIE_UNITS.quantic;
const qf = (cob % COBIE_UNITS.chronon) / COBIE_UNITS.chronon;
const cf = (cob % COBIE_UNITS.eonstrip) / COBIE_UNITS.eonstrip;
const ef = (cob % COBIE_UNITS.megasequence) / COBIE_UNITS.megasequence;
const mf = (cob % COBIE_UNITS.cosmocycle) / COBIE_UNITS.cosmocycle;
rotateHand('handXeno', xf * 360);
rotateHand('handQuantic', qf * 360);
rotateHand('handChronon', cf * 360);
rotateHand('handEonstrip', ef * 360);
rotateHand('handMegasequence', mf * 360);
}
function updateClock() {
renderClock(toCobiets(new Date()));
}
let intervalId = null;
function startClock() {
clearInterval(intervalId);
updateClock();
intervalId = setInterval(updateClock, 1000);
}
function showTime(cob) {
clearInterval(intervalId);
renderClock(cob);
}
function initClock() {
placeMarkers();
startClock();
const clk = document.getElementById('clock');
if (clk) {
clk.addEventListener('click', () => {
document.body.classList.toggle('fullscreen-clock');
// Re-position markers after toggling fullscreen
requestAnimationFrame(placeMarkers);
});
}
window.addEventListener('resize', placeMarkers);
window.CobieClock = {
start: startClock,
showTime
};
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initClock);
} else {
initClock();
}
})();

View File

@ -1,185 +0,0 @@
const COBIE_EPOCH = 0;
const COBIE_UNITS = {
second: 1,
xenocycle: 0x10,
quantic: 0x100,
chronon: 0x1000,
eonstrip: 0x10000,
megasequence: 0x100000,
cosmocycle: 0x1000000,
galactic_year: 0x10000000,
universal_eon: 0x100000000,
celestial_era: 0x1000000000,
epoch_of_cosmos: 0x10000000000,
cosmic_aeon: 0x100000000000,
metaepoch: 0x1000000000000,
eternum: 0x10000000000000,
infinitum: 0x100000000000000,
astralmillennia: 0x1000000000000000
};
function floorDiv(a, b) {
return Math.trunc(a / b);
}
function parseCobiets(str) {
const m = /^([+-]?)([0-9A-Fa-f]+)\.([0-9A-Fa-f]{1,})$/.exec(str.trim());
if (!m) return null;
const sign = m[1] === '-' ? -1 : 1;
const allDateKeys = [
'astralmillennia','infinitum','eternum','metaepoch','cosmic_aeon',
'epoch_of_cosmos','celestial_era','universal_eon','galactic_year',
'cosmocycle','megasequence','eonstrip'
];
let rawDateHex = m[2];
if (rawDateHex.length < allDateKeys.length) {
rawDateHex = rawDateHex.padStart(allDateKeys.length, '0');
}
let dateKeys = [...allDateKeys];
if (rawDateHex.length > allDateKeys.length) {
const extraCount = rawDateHex.length - allDateKeys.length;
for (let i = 0; i < extraCount; i++) {
dateKeys.unshift(null);
}
}
const timeHexRaw = m[3];
const timeHex = timeHexRaw.padStart(4, '0');
const timeKeys = ['chronon', 'quantic', 'xenocycle', 'second'];
let total = 0;
for (let i = 0; i < rawDateHex.length; i++) {
const digit = parseInt(rawDateHex[i], 16);
const key = dateKeys[i];
if (key === null) {
const power = rawDateHex.length - 1 - i;
total += digit * Math.pow(16, power) * COBIE_UNITS['eonstrip'];
} else {
total += digit * COBIE_UNITS[key];
}
}
timeHex.split('').forEach((h, i) => {
total += parseInt(h, 16) * COBIE_UNITS[timeKeys[i]];
});
return sign * total;
}
function getTAIOffsetAt(date) {
const taiEpoch = new Date('1958-01-01T00:00:00Z');
if (date < taiEpoch) { return 0; }
const leapSeconds = [
{ date: '1972-01-01T00:00:00Z', offset: 10 },
{ date: '1972-07-01T00:00:00Z', offset: 11 },
{ date: '1973-01-01T00:00:00Z', offset: 12 },
{ date: '1974-01-01T00:00:00Z', offset: 13 },
{ date: '1975-01-01T00:00:00Z', offset: 14 },
{ date: '1976-01-01T00:00:00Z', offset: 15 },
{ date: '1977-01-01T00:00:00Z', offset: 16 },
{ date: '1978-01-01T00:00:00Z', offset: 17 },
{ date: '1979-01-01T00:00:00Z', offset: 18 },
{ date: '1980-01-01T00:00:00Z', offset: 19 },
{ date: '1981-07-01T00:00:00Z', offset: 20 },
{ date: '1982-07-01T00:00:00Z', offset: 21 },
{ date: '1983-07-01T00:00:00Z', offset: 22 },
{ date: '1985-07-01T00:00:00Z', offset: 23 },
{ date: '1988-01-01T00:00:00Z', offset: 24 },
{ date: '1990-01-01T00:00:00Z', offset: 25 },
{ date: '1991-01-01T00:00:00Z', offset: 26 },
{ date: '1992-07-01T00:00:00Z', offset: 27 },
{ date: '1993-07-01T00:00:00Z', offset: 28 },
{ date: '1994-07-01T00:00:00Z', offset: 29 },
{ date: '1996-01-01T00:00:00Z', offset: 30 },
{ date: '1997-07-01T00:00:00Z', offset: 31 },
{ date: '1999-01-01T00:00:00Z', offset: 32 },
{ date: '2006-01-01T00:00:00Z', offset: 33 },
{ date: '2009-01-01T00:00:00Z', offset: 34 },
{ date: '2012-07-01T00:00:00Z', offset: 35 },
{ date: '2015-07-01T00:00:00Z', offset: 36 },
{ date: '2017-01-01T00:00:00Z', offset: 37 }
];
for (let i = 0; i < leapSeconds.length; i++) {
const leapDate = new Date(leapSeconds[i].date);
if (date < leapDate) {
return i === 0 ? 10 : leapSeconds[i - 1].offset;
}
}
return 37;
}
function toCobiets(date) {
const utcSec = floorDiv(date.getTime(), 1000);
const taiSec = utcSec + getTAIOffsetAt(date);
return taiSec - COBIE_EPOCH;
}
function fromCobiets(cobiets) {
const taiSeconds = cobiets + COBIE_EPOCH;
const taiMs = taiSeconds * 1000;
let utcMs = taiMs;
for (let i = 0; i < 3; i++) {
const off = getTAIOffsetAt(new Date(utcMs));
utcMs = taiMs - off * 1000;
}
return new Date(utcMs);
}
const UNIT_KEYS = [
'astralmillennia','infinitum','eternum','metaepoch','cosmic_aeon','epoch_of_cosmos','celestial_era','universal_eon','galactic_year','cosmocycle','megasequence','eonstrip','chronon','quantic','xenocycle','second'
];
function breakdownNonNeg(cob) {
let rem = cob, bd = {};
for (let key of UNIT_KEYS) {
bd[key] = floorDiv(rem, COBIE_UNITS[key]);
rem %= COBIE_UNITS[key];
}
return bd;
}
function formatCobieTimestamp(cobiets) {
const sign = cobiets < 0 ? '-' : '+';
const absCob = Math.abs(cobiets);
const bd = breakdownNonNeg(absCob);
const dateUnits = [
'astralmillennia','infinitum','eternum','metaepoch','cosmic_aeon','epoch_of_cosmos','celestial_era','universal_eon','galactic_year','cosmocycle','megasequence','eonstrip'
];
let rawDateHex = dateUnits.map(key => bd[key].toString(16)).join('');
rawDateHex = rawDateHex.replace(/^0+/, '');
if (rawDateHex === '') rawDateHex = '0';
const timeHex = [bd.chronon, bd.quantic, bd.xenocycle, bd.second]
.map(n => n.toString(16)).join('');
const paddedTimeHex = timeHex.padStart(4, '0');
return sign + rawDateHex + '.' + paddedTimeHex;
}
const Cobie = {
COBIE_EPOCH,
COBIE_UNITS,
floorDiv,
parseCobiets,
getTAIOffsetAt,
toCobiets,
fromCobiets,
formatCobieTimestamp,
breakdownNonNeg
};
if (typeof module !== 'undefined' && module.exports) {
module.exports = Cobie;
}
// Expose globally when loaded in a browser environment
if (typeof window !== 'undefined') {
window.Cobie = Cobie;
}

View File

@ -1,22 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs>
<radialGradient id="bg" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#101018"/>
<stop offset="100%" stop-color="#050508"/>
</radialGradient>
<linearGradient id="ring" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#00ffff"/>
<stop offset="100%" stop-color="#ff00ff"/>
</linearGradient>
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<circle cx="50" cy="50" r="45" fill="url(#bg)" stroke="url(#ring)" stroke-width="4"/>
<path d="M55 30 H45 A20 20 0 0 0 45 70 H55" fill="none" stroke="#00ffff" stroke-width="6" stroke-linecap="round" filter="url(#glow)"/>
<path d="M65 30 H75 M70 30 V70" fill="none" stroke="#ff00ff" stroke-width="6" stroke-linecap="round" filter="url(#glow)"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,561 +0,0 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #0a0e27 0%, #1a1f3a 100%);
color: #e0e0e0;
min-height: 100vh;
overflow-x: hidden;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 30px;
padding: 20px;
background: rgba(255, 255, 255, 0.05);
border-radius: 15px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.tooltip {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
color: #fff;
padding: 8px 12px;
border-radius: 4px;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s;
z-index: 9999;
}
h1 {
font-size: 2.5em;
background: linear-gradient(45deg, #00ffff, #ff00ff);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 10px;
animation: glow 3s ease-in-out infinite;
}
@keyframes glow {
0%, 100% { opacity: 0.8; }
50% { opacity: 1; }
}
.current-time {
background: rgba(0, 255, 255, 0.1);
border: 2px solid rgba(0, 255, 255, 0.3);
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
text-align: center;
position: relative;
overflow: hidden;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
height: var(--clock-size);
}
.current-time.manual::before {
animation-play-state: paused;
}
.current-time::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(45deg, transparent, rgba(0, 255, 255, 0.1), transparent);
transform: rotate(45deg);
animation: sweep 3s linear infinite;
}
@keyframes sweep {
0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
}
.cobie-time {
font-size: 2.5em;
font-family: 'Courier New', monospace;
letter-spacing: 2px;
margin: 10px 0;
text-shadow: 0 0 20px rgba(0, 255, 255, 0.5);
}
.regular-time {
font-size: 1.2em;
color: #aaa;
}
.timezone-selector {
margin: 20px 0;
text-align: center;
}
select {
padding: 10px 20px;
font-size: 16px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 5px;
color: #fff;
cursor: pointer;
transition: all 0.3s ease;
}
select:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
}
.calendar-controls {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
margin: 20px 0;
}
button {
padding: 10px 20px;
font-size: 16px;
background: linear-gradient(45deg, #00ffff, #0080ff);
border: none;
border-radius: 5px;
color: #fff;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
button::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
button:hover::before {
width: 300px;
height: 300px;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 255, 255, 0.3);
}
.calendar-view {
background: rgba(255, 255, 255, 0.05);
border-radius: 15px;
padding: 20px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
overflow: hidden;
}
.calendar-header {
text-align: center;
font-size: 1.5em;
margin-bottom: 20px;
color: #00ffff;
}
.eonstrip-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 15px;
margin-bottom: 20px;
transform: translateX(0);
transition: transform 0.3s ease;
will-change: transform;
}
.eonstrip-card {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 10px;
padding: 15px;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
position: relative;
overflow: visible;
white-space: normal;
}
.eonstrip-card::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
opacity: 0;
transition: opacity 0.3s ease;
}
.eonstrip-card:hover::before {
opacity: 1;
}
.eonstrip-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 255, 255, 0.2);
border-color: rgba(0, 255, 255, 0.5);
z-index: 1000;
}
.eonstrip-card:hover .tooltip {
opacity: 1;
}
.eonstrip-card.current {
background: rgba(0, 255, 255, 0.2);
border-color: rgba(0, 255, 255, 0.5);
}
.eonstrip-name {
font-size: 1em;
font-weight: bold;
color: #00ffff;
margin-bottom: 5px;
}
.eonstrip-hex {
font-size: 0.85em; /* was default monospace size */
font-family: 'Courier New', monospace;
color: #ffaaff;
margin-bottom: 10px;
}
.eonstrip-dates {
font-size: 0.7em;
color: #aaa;
line-height: 1.4;
}
.event-tag {
display: inline-block;
margin-top: 4px;
padding: 2px 6px;
font-size: 0.75em;
font-weight: 600;
color: #fff;
background: linear-gradient(135deg, rgba(0,255,255,0.25), rgba(255,0,255,0.25));
border: 1px solid rgba(255,255,255,0.2);
border-radius: 4px;
text-shadow: 0 0 6px rgba(0,255,255,0.7);
}
.time-details {
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
padding: 20px;
margin-top: 20px;
}
.time-unit {
display: flex;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.time-unit:last-child {
border-bottom: none;
}
.unit-name {
color: #00ffff;
font-weight: bold;
}
.unit-value {
font-family: 'Courier New', monospace;
color: #ffaaff;
}
.toggle-btn {
background: linear-gradient(45deg, #00ffff, #0080ff);
border: none;
border-radius: 8px;
color: #fff;
font-size: 0.9em;
padding: 8px 16px;
cursor: pointer;
position: relative;
overflow: hidden;
text-transform: uppercase;
letter-spacing: 1px;
box-shadow: 0 0 10px rgba(0,255,255,0.4);
transition: all 0.3s ease;
}
.toggle-btn .arrow-icon {
display: inline-block;
margin-right: 6px;
transition: transform 0.3s ease;
}
.toggle-btn:hover {
transform: translateY(-2px);
box-shadow: 0 0 20px rgba(0,255,255,0.6);
}
.extended-section {
display: none; /* hidden by default */
margin-top: 10px;
animation: fadeIn 0.4s ease;
}
/* Simple fadein for when extended units show */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@media only screen
and (max-width: 812px) /* iPhone portrait widths go up to ~812px */
and (orientation: portrait) {
/* scale down your main text by 30% */
html {
font-size: 70%;
}
/* if you prefer targeting only the big “cobie-time” element: */
.cobie-time {
font-size: 1.75em; /* was 2.5em, which is 70% of 2.5em */
}
}
.eonstrip-grid {
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
transform: translateX(0);
transition: transform 0.3s ease;
will-change: transform;
}
/* Layout combining current time and analog clock */
.time-display {
--clock-size: 40vmin;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-items: stretch;
gap: 20px;
}
.analog-clock-container {
flex: 0 0 auto;
width: var(--clock-size);
margin-left: auto;
display: flex;
justify-content: center;
align-items: center;
}
#clock {
position: relative;
width: var(--clock-size);
height: var(--clock-size);
border: 2px solid rgba(255, 255, 255, 0.2);
border-radius: 50%;
background: radial-gradient(circle at center, #0a0e27 0%, #1a1f3a 100%);
box-shadow: 0 0 25px rgba(0, 255, 255, 0.2), inset 0 0 40px rgba(255, 0, 255, 0.2);
}
.clock-center {
position: absolute;
top: 50%;
left: 50%;
width: calc(var(--clock-size) * 0.13);
height: calc(var(--clock-size) * 0.13);
transform: translate(-50%, -50%) translateZ(0);
background: url('logo.svg') center/contain no-repeat;
background-color: transparent;
border-radius: 50%;
/* box-shadow: 0 0 8px rgba(0, 255, 255, 0.8); */
z-index: 2;
pointer-events: none;
}
@media screen and (min-width: 1200px) {
.clock-center {
width: calc(var(--clock-size) * 0.085);
height: calc(var(--clock-size) * 0.085);
}
}
.clock-label {
position: absolute;
bottom: 30%;
left: 50%;
transform: translateX(-50%);
font-family: 'Great Vibes', cursive;
font-size: calc(var(--clock-size) * 0.06);
color: #ffaaff;
text-shadow: 0 0 6px rgba(255, 0, 255, 0.6);
pointer-events: none;
z-index: 0;
}
body.fullscreen-clock .clock-label {
font-size: calc(var(--clock-size) * 0.08);
}
.marker {
position: absolute;
width: 2em;
height: 2em;
text-align: center;
line-height: 2em;
/* Use a futuristic font for the clock markers */
font-family: 'Orbitron', 'Trebuchet MS', 'Lucida Sans', Arial, sans-serif;
font-size: 1.2em;
font-weight: 600;
color: #00ffff;
background: none;
border: none;
border-radius: 0;
text-shadow: 0 0 6px rgba(0, 255, 255, 0.9), 0 0 12px rgba(0, 255, 255, 0.7);
box-shadow: none;
transform-origin: center;
font-variant-numeric: tabular-nums;
user-select: none;
pointer-events: none;
will-change: transform;
z-index: 1;
}
.tick {
position: absolute;
width: 2px;
background: #00ffff;
transform-origin: center top;
z-index: 0;
}
.tick.mid {
background: #66ffff;
}
.tick.big {
background: #ffffff;
}
.hand {
position: absolute;
bottom: 50%;
left: 50%;
transform-origin: bottom center;
transform: translateX(-50%);
transition: transform 0.5s ease-in-out;
border-radius: 2px;
z-index: 1;
}
.hand.xeno {
width: 2px;
height: 44%;
background: linear-gradient(to top, #66ccff, #0044ff);
box-shadow: 0 0 8px #66ccff;
}
.hand.quantic {
width: 3px;
height: 40%;
background: linear-gradient(to top, #ff66ff, #9900ff);
box-shadow: 0 0 8px #ff66ff;
}
.hand.chronon {
width: 4px;
height: 34%;
background: linear-gradient(to top, #ff4444, #880000);
box-shadow: 0 0 8px #ff4444;
}
.hand.eonstrip {
width: 5px;
height: 30%;
background: linear-gradient(to top, #33ff99, #006633);
box-shadow: 0 0 8px #33ff99;
}
.hand.megasequence {
width: 6px;
height: 26%;
background: linear-gradient(to top, #ffbb33, #aa5500);
box-shadow: 0 0 8px #ffbb33;
}
@media only screen and (max-height: 430px) and (orientation: landscape) {
.time-display {
--clock-size: 70vmin;
}
}
body.fullscreen-clock .header,
body.fullscreen-clock .current-time,
body.fullscreen-clock .timezone-selector,
body.fullscreen-clock .calendar-controls,
body.fullscreen-clock .calendar-view,
body.fullscreen-clock .time-details,
body.fullscreen-clock .explanations {
display: none;
}
body.fullscreen-clock .time-display {
position: fixed;
inset: 0;
justify-content: center;
align-items: center;
z-index: 1000;
}
body.fullscreen-clock .analog-clock-container {
--clock-size: 80vmin;
width: var(--clock-size);
margin-left: 0;
}
body.fullscreen-clock #clock {
width: var(--clock-size);
height: var(--clock-size);
}