Compare commits

..

59 Commits

Author SHA1 Message Date
Oleksandr Kozachuk 38e16687cb Make hands movement smooth on analag clock. 2025-07-10 14:37:56 +02:00
Kiyomichi Kosaka 2ade28e9cd Merge pull request #81 from ok2/codex/refactor-code-into-separate-files
Refactor JS into modules
2025-06-20 16:25:41 +02:00
Kiyomichi Kosaka a9b90729ff Refactor scripts into modules 2025-06-20 16:25:21 +02:00
Kiyomichi Kosaka ce17d2d5bf Merge pull request #80 from ok2/codex/refactor-code-to-reduce-size
Refactor JS helpers
2025-06-20 16:06:08 +02:00
Kiyomichi Kosaka 8f570ca4d3 refactor: reduce duplication via helpers 2025-06-20 16:05:51 +02:00
Kiyomichi Kosaka da56bfce7a Merge pull request #79 from ok2/codex/implement-swipe-mechanism-for-eonstrips
Enable swipe navigation in detail view
2025-06-20 14:04:19 +02:00
Kiyomichi Kosaka f38f4af4ca Add swipe navigation for detail view 2025-06-20 14:03:59 +02:00
Kiyomichi Kosaka ff5d2a7816 Merge pull request #78 from ok2/codex/improve-border-visibility-in-detail-view
Improve event border visibility
2025-06-20 13:59:21 +02:00
Kiyomichi Kosaka 3961bc3345 Improve visibility of event borders 2025-06-20 13:59:03 +02:00
Kiyomichi Kosaka 3974237f42 Merge pull request #77 from ok2/codex/ensure-visible-borders-for-event-boxes
Improve event box borders
2025-06-20 13:49:46 +02:00
Kiyomichi Kosaka 3822b0cdf7 Enhance detail event box visibility 2025-06-20 13:49:32 +02:00
Kiyomichi Kosaka 8451fa9f54 Merge pull request #76 from ok2/codex/fix-event-width-and-color-handling
Improve event overlap display
2025-06-20 13:42:02 +02:00
Kiyomichi Kosaka 26cfa8b868 Improve detail event display 2025-06-20 13:41:48 +02:00
Oleksandr Kozachuk 3f7a343b17 Improve tooltip. 2025-06-20 13:32:58 +02:00
Oleksandr Kozachuk 217c0b0028 Add Sleep. 2025-06-20 13:22:33 +02:00
Oleksandr Kozachuk a63ed16871 Move night 2 hours later. 2025-06-20 13:22:33 +02:00
Kiyomichi Kosaka 6e45dca49a Merge pull request #75 from ok2/codex/improve-tooltip-for-event-details
Improve event tooltip info
2025-06-20 13:21:47 +02:00
Kiyomichi Kosaka 1ec09e1b76 Improve event tooltip with series info 2025-06-20 13:21:32 +02:00
Kiyomichi Kosaka cf30e830a0 Merge pull request #74 from ok2/codex/update-eonstrips-detail-view-on-timezone-change 2025-06-20 10:31:21 +02:00
Kiyomichi Kosaka 65b85f1575 Update detail view on timezone change 2025-06-20 10:31:06 +02:00
Kiyomichi Kosaka 49b30a69e9 Merge pull request #73 from ok2/codex/rename-sleep-to-night-and-add-timezone-shift-attribute 2025-06-20 10:23:26 +02:00
Kiyomichi Kosaka 6c75140533 Rename Sleep event and add timezone shifting 2025-06-20 10:10:11 +02:00
Kiyomichi Kosaka 1002456a0c Merge pull request #72 from ok2/codex/update-event-boxes-and-current-time-line-layout 2025-06-20 08:42:08 +02:00
Kiyomichi Kosaka f6f1502f1a Adjust detail view timeline layout and auto-update 2025-06-20 08:41:44 +02:00
Kiyomichi Kosaka 615a14b5a8 Merge pull request #71 from ok2/codex/fix-layout-issues-in-detail-view 2025-06-20 08:35:03 +02:00
Kiyomichi Kosaka 08a4ef346a fix detail timeline visuals 2025-06-20 08:34:44 +02:00
Kiyomichi Kosaka 1fab01ffb9 Merge pull request #70 from ok2/codex/fix-event-box-label-positioning-and-toolbox-visibility 2025-06-20 08:27:43 +02:00
Kiyomichi Kosaka 441eec4e0c Adjust detail timeline layout and event label positions 2025-06-20 08:27:23 +02:00
Kiyomichi Kosaka d671e64c85 Merge pull request #69 from ok2/codex/refactor-event-box-labels-and-cronon-scale 2025-06-20 08:20:30 +02:00
Kiyomichi Kosaka 6c868f3768 Improve event labels and tooltips 2025-06-20 08:20:13 +02:00
Kiyomichi Kosaka 7a8a463169 Merge pull request #68 from ok2/codex/fix-analog-clock-background-visibility-in-eonstrip-view 2025-06-20 08:11:46 +02:00
Kiyomichi Kosaka 0f7f83c618 Fix fullscreen clock not hiding detail view 2025-06-20 08:11:28 +02:00
Kiyomichi Kosaka bf04c9569a Merge pull request #67 from ok2/codex/improve-timeline-and-event-label-visibility 2025-06-20 08:10:07 +02:00
Kiyomichi Kosaka 483b20e13d Improve detail timeline visibility 2025-06-20 08:09:50 +02:00
Kiyomichi Kosaka 757aa60ec4 Merge pull request #66 from ok2/codex/fix-chronon-numbers-visibility-and-add-timestamp-line 2025-06-20 08:03:04 +02:00
Kiyomichi Kosaka ead5d58d21 Fix detail view timeline 2025-06-20 08:02:40 +02:00
Kiyomichi Kosaka 3a591be7dc Merge pull request #65 from ok2/codex/fix-stuck-animation-issue-in-eonstript-detail-view 2025-06-20 08:00:28 +02:00
Kiyomichi Kosaka 7f706f12cd fix grid animation when navigating from detail 2025-06-20 08:00:10 +02:00
Kiyomichi Kosaka b2d8754c7e Merge pull request #64 from ok2/codex/fix-visibility-of-labels-in-detail-view 2025-06-20 07:55:49 +02:00
Kiyomichi Kosaka 1a61d7b3cc Allow detail labels to overflow 2025-06-20 07:55:34 +02:00
Oleksandr Kozachuk 93fe6bcdd2 Simplify events.js 2025-06-20 07:51:19 +02:00
Kiyomichi Kosaka 456f0744c4 Merge pull request #63 from ok2/codex/add-properties-to-control-event-visibility 2025-06-20 07:49:53 +02:00
Kiyomichi Kosaka 7bfee30e71 feat(events): configurable visibility 2025-06-20 07:49:34 +02:00
Kiyomichi Kosaka 2e6d870c0f Merge pull request #62 from ok2/codex/fix-visibility-of-event-labels-and-line-thickness 2025-06-20 07:47:54 +02:00
Kiyomichi Kosaka 88c37b566a Fix detail view labels for small events 2025-06-20 07:47:31 +02:00
Kiyomichi Kosaka 47d4a66b0f Merge pull request #61 from ok2/codex/fix-event-overflow-in-detail-view 2025-06-20 07:42:01 +02:00
Kiyomichi Kosaka 38a7de5ef8 fix(detail): clamp long events 2025-06-20 07:41:46 +02:00
Kiyomichi Kosaka 5e2ec31e4f Merge pull request #60 from ok2/codex/fix-bugs-in-analog-clock-and-megasequence 2025-06-20 07:35:29 +02:00
Kiyomichi Kosaka ec8f238a05 Fix detail view navigation and full screen 2025-06-20 07:35:04 +02:00
Kiyomichi Kosaka 01f06b84b2 Merge pull request #59 from ok2/codex/fix-missing-event-and-prev/next-buttons 2025-06-20 00:55:06 +02:00
Kiyomichi Kosaka c991a58296 Add daily Sleep event and restore detail navigation 2025-06-20 00:54:28 +02:00
Kiyomichi Kosaka 13dd3ec664 Merge pull request #58 from ok2/codex/add-interactive-view-for-eonstrip-events 2025-06-20 00:34:07 +02:00
Kiyomichi Kosaka 7e060760e7 Add vertical eonstrip detail view 2025-06-20 00:33:33 +02:00
Kiyomichi Kosaka d32b672289 Merge pull request #57 from ok2/codex/enhance-event-periodicity-support 2025-06-20 00:26:59 +02:00
Kiyomichi Kosaka 5e703813d7 Add flexible recurring events 2025-06-20 00:26:11 +02:00
Oleksandr Kozachuk d11b8cf19f Remove unneeded code. 2025-06-20 00:15:15 +02:00
Oleksandr Kozachuk 51aaa52112 Bigger marker. 2025-06-20 00:14:49 +02:00
Kiyomichi Kosaka 3a2df03eb3 Merge pull request #56 from ok2/codex/fix-analog-clock-markers-positioning 2025-06-20 00:06:04 +02:00
Kiyomichi Kosaka 3ea7f1e69f Set marker font size relative to radius 2025-06-20 00:05:48 +02:00
35 changed files with 741 additions and 2700 deletions
-18
View File
@@ -1,18 +0,0 @@
//
// AppIntent.swift
// CoBiE Analog Clock
//
// Created by Oleksandr Kozachuk on 2025-06-16.
//
import WidgetKit
import AppIntents
struct ConfigurationAppIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource { "Configuration" }
static var description: IntentDescription { "This is an example widget." }
// An example configurable parameter.
@Parameter(title: "Favorite Emoji", default: "😃")
var favoriteEmoji: String
}
@@ -1,11 +0,0 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,58 +0,0 @@
{
"images" : [
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,11 +0,0 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,94 +0,0 @@
//
// CoBiE_Analog_Clock.swift
// CoBiE Analog Clock
//
// Created by Oleksandr Kozachuk on 2025-06-16.
//
import WidgetKit
import SwiftUI
import WebKit
#if os(iOS) || os(tvOS)
import UIKit
#else
import AppKit
#endif
#if os(iOS) || os(tvOS)
typealias PlatformViewRepresentable = UIViewRepresentable
#else
typealias PlatformViewRepresentable = NSViewRepresentable
#endif
struct HTMLClockView: PlatformViewRepresentable {
#if os(iOS) || os(tvOS)
func makeUIView(context: Context) -> WKWebView { createWebView() }
func updateUIView(_ uiView: WKWebView, context: Context) {}
#else
func makeNSView(context: Context) -> WKWebView { createWebView() }
func updateNSView(_ nsView: WKWebView, context: Context) {}
#endif
private func createWebView() -> WKWebView {
let webView = WKWebView()
#if os(iOS) || os(tvOS)
webView.isOpaque = false
webView.backgroundColor = .clear
webView.scrollView.isScrollEnabled = false
#else
webView.setValue(false, forKey: "drawsBackground")
webView.setValue(false, forKey: "isOpaque")
if let scrollView = webView.value(forKey: "scrollView") as? NSScrollView {
scrollView.hasVerticalScroller = false
scrollView.hasHorizontalScroller = false
}
#endif
if let url = Bundle.main.url(forResource: "analog-clock", withExtension: "html") {
webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())
}
return webView
}
}
struct Provider: AppIntentTimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), configuration: ConfigurationAppIntent())
}
func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> SimpleEntry {
SimpleEntry(date: Date(), configuration: configuration)
}
func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline<SimpleEntry> {
let entry = SimpleEntry(date: Date(), configuration: configuration)
return Timeline(entries: [entry], policy: .never)
}
// func relevances() async -> WidgetRelevances<ConfigurationAppIntent> {
// // Generate a list containing the contexts this widget is relevant in.
// }
}
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: ConfigurationAppIntent
}
struct CoBiE_Analog_ClockEntryView : View {
var entry: Provider.Entry
var body: some View {
HTMLClockView()
}
}
struct CoBiE_Analog_Clock: Widget {
let kind: String = "CoBiE_Analog_Clock"
var body: some WidgetConfiguration {
AppIntentConfiguration(kind: kind, intent: ConfigurationAppIntent.self, provider: Provider()) { entry in
CoBiE_Analog_ClockEntryView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
}
}
}
@@ -1,17 +0,0 @@
//
// CoBiE_Analog_ClockBundle.swift
// CoBiE Analog Clock
//
// Created by Oleksandr Kozachuk on 2025-06-16.
//
import WidgetKit
import SwiftUI
@main
struct CoBiE_Analog_ClockBundle: WidgetBundle {
var body: some Widget {
CoBiE_Analog_Clock()
CoBiE_Analog_ClockControl()
}
}
@@ -1,77 +0,0 @@
//
// CoBiE_Analog_ClockControl.swift
// CoBiE Analog Clock
//
// Created by Oleksandr Kozachuk on 2025-06-16.
//
import AppIntents
import SwiftUI
import WidgetKit
struct CoBiE_Analog_ClockControl: ControlWidget {
static let kind: String = "no.kaizenkodo.CoBiE.CoBiE Analog Clock"
var body: some ControlWidgetConfiguration {
AppIntentControlConfiguration(
kind: Self.kind,
provider: Provider()
) { value in
ControlWidgetToggle(
"Start Timer",
isOn: value.isRunning,
action: StartTimerIntent(value.name)
) { isRunning in
Label(isRunning ? "On" : "Off", systemImage: "timer")
}
}
.displayName("Timer")
.description("A an example control that runs a timer.")
}
}
extension CoBiE_Analog_ClockControl {
struct Value {
var isRunning: Bool
var name: String
}
struct Provider: AppIntentControlValueProvider {
func previewValue(configuration: TimerConfiguration) -> Value {
CoBiE_Analog_ClockControl.Value(isRunning: false, name: configuration.timerName)
}
func currentValue(configuration: TimerConfiguration) async throws -> Value {
let isRunning = true // Check if the timer is running
return CoBiE_Analog_ClockControl.Value(isRunning: isRunning, name: configuration.timerName)
}
}
}
struct TimerConfiguration: ControlConfigurationIntent {
static let title: LocalizedStringResource = "Timer Name Configuration"
@Parameter(title: "Timer Name", default: "Timer")
var timerName: String
}
struct StartTimerIntent: SetValueIntent {
static let title: LocalizedStringResource = "Start a timer"
@Parameter(title: "Timer Name")
var name: String
@Parameter(title: "Timer is running")
var value: Bool
init() {}
init(_ name: String) {
self.name = name
}
func perform() async throws -> some IntentResult {
// Start the timer
return .result()
}
}
-11
View File
@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
</dict>
</plist>
@@ -1,24 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<style>body{margin:0;background:transparent;}</style>
</head>
<body>
<div class="analog-clock-container">
<div id="clock">
<div class="hand megasequence" id="handMegasequence"></div>
<div class="hand eonstrip" id="handEonstrip"></div>
<div class="hand chronon" id="handChronon"></div>
<div class="hand quantic" id="handQuantic"></div>
<div class="hand xeno" id="handXeno"></div>
<div class="clock-center"></div>
<div class="clock-label">CoBiE Time</div>
</div>
</div>
<script src="cobie.js"></script>
<script src="clock.js"></script>
</body>
</html>
-204
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();
}
})();
-185
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;
}
@@ -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: calc(var(--clock-size) * 0.13);
height: calc(var(--clock-size) * 0.13);
text-align: center;
line-height: calc(var(--clock-size) * 0.13);
/* Use a futuristic font for the clock markers */
font-family: 'Orbitron', 'Trebuchet MS', 'Lucida Sans', Arial, sans-serif;
font-size: calc(var(--clock-size) * 0.08);
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);
}
-819
View File
@@ -1,819 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
4FF813CC2E00403F00D89535 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4FF813CB2E00403E00D89535 /* WidgetKit.framework */; };
4FF813CE2E00403F00D89535 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4FF813CD2E00403F00D89535 /* SwiftUI.framework */; };
4FF813DD2E00404000D89535 /* CoBiE Analog ClockExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4FF813C92E00403E00D89535 /* CoBiE Analog ClockExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
4FF813E72E00404000D89535 /* analog-clock.html in Resources */ = {isa = PBXBuildFile; fileRef = 4FF813E32E00404000D89535 /* analog-clock.html */; };
4FF813E82E00404000D89535 /* clock.js in Resources */ = {isa = PBXBuildFile; fileRef = 4FF813E42E00404000D89535 /* clock.js */; };
4FF813E92E00404000D89535 /* cobie.js in Resources */ = {isa = PBXBuildFile; fileRef = 4FF813E52E00404000D89535 /* cobie.js */; };
4FF813EA2E00404000D89535 /* style.css in Resources */ = {isa = PBXBuildFile; fileRef = 4FF813E62E00404000D89535 /* style.css */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
4FF813A92E003FB600D89535 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4FF813912E003FB400D89535 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 4FF813982E003FB400D89535;
remoteInfo = CoBiE;
};
4FF813B32E003FB600D89535 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4FF813912E003FB400D89535 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 4FF813982E003FB400D89535;
remoteInfo = CoBiE;
};
4FF813DB2E00404000D89535 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4FF813912E003FB400D89535 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 4FF813C82E00403E00D89535;
remoteInfo = "CoBiE Analog ClockExtension";
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
4FF813E22E00404000D89535 /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
4FF813DD2E00404000D89535 /* CoBiE Analog ClockExtension.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
4FF813992E003FB400D89535 /* CoBiE.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CoBiE.app; sourceTree = BUILT_PRODUCTS_DIR; };
4FF813A82E003FB600D89535 /* CoBiETests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoBiETests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4FF813B22E003FB600D89535 /* CoBiEUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoBiEUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4FF813C92E00403E00D89535 /* CoBiE Analog ClockExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "CoBiE Analog ClockExtension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
4FF813CB2E00403E00D89535 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
4FF813CD2E00403F00D89535 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
4FF813E32E00404000D89535 /* analog-clock.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = "analog-clock.html"; path = "CoBiE Analog Clock/Resources/analog-clock.html"; sourceTree = "<group>"; };
4FF813E42E00404000D89535 /* clock.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = "clock.js"; path = "CoBiE Analog Clock/Resources/clock.js"; sourceTree = "<group>"; };
4FF813E52E00404000D89535 /* cobie.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = "cobie.js"; path = "CoBiE Analog Clock/Resources/cobie.js"; sourceTree = "<group>"; };
4FF813E62E00404000D89535 /* style.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; name = "style.css"; path = "CoBiE Analog Clock/Resources/style.css"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
4FF813DE2E00404000D89535 /* Exceptions for "CoBiE Analog Clock" folder in "CoBiE Analog ClockExtension" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 4FF813C82E00403E00D89535 /* CoBiE Analog ClockExtension */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
4FF8139B2E003FB400D89535 /* CoBiE */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = CoBiE;
sourceTree = "<group>";
};
4FF813AB2E003FB600D89535 /* CoBiETests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = CoBiETests;
sourceTree = "<group>";
};
4FF813B52E003FB600D89535 /* CoBiEUITests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = CoBiEUITests;
sourceTree = "<group>";
};
4FF813CF2E00403F00D89535 /* CoBiE Analog Clock */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
4FF813DE2E00404000D89535 /* Exceptions for "CoBiE Analog Clock" folder in "CoBiE Analog ClockExtension" target */,
);
path = "CoBiE Analog Clock";
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
4FF813962E003FB400D89535 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4FF813A52E003FB600D89535 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4FF813AF2E003FB600D89535 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4FF813C62E00403E00D89535 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4FF813CE2E00403F00D89535 /* SwiftUI.framework in Frameworks */,
4FF813CC2E00403F00D89535 /* WidgetKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
4FF813902E003FB400D89535 = {
isa = PBXGroup;
children = (
4FF8139B2E003FB400D89535 /* CoBiE */,
4FF813AB2E003FB600D89535 /* CoBiETests */,
4FF813B52E003FB600D89535 /* CoBiEUITests */,
4FF813CF2E00403F00D89535 /* CoBiE Analog Clock */,
4FF813CA2E00403E00D89535 /* Frameworks */,
4FF8139A2E003FB400D89535 /* Products */,
);
sourceTree = "<group>";
};
4FF8139A2E003FB400D89535 /* Products */ = {
isa = PBXGroup;
children = (
4FF813992E003FB400D89535 /* CoBiE.app */,
4FF813A82E003FB600D89535 /* CoBiETests.xctest */,
4FF813B22E003FB600D89535 /* CoBiEUITests.xctest */,
4FF813C92E00403E00D89535 /* CoBiE Analog ClockExtension.appex */,
);
name = Products;
sourceTree = "<group>";
};
4FF813CA2E00403E00D89535 /* Frameworks */ = {
isa = PBXGroup;
children = (
4FF813CB2E00403E00D89535 /* WidgetKit.framework */,
4FF813CD2E00403F00D89535 /* SwiftUI.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
4FF813982E003FB400D89535 /* CoBiE */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4FF813BC2E003FB600D89535 /* Build configuration list for PBXNativeTarget "CoBiE" */;
buildPhases = (
4FF813952E003FB400D89535 /* Sources */,
4FF813962E003FB400D89535 /* Frameworks */,
4FF813972E003FB400D89535 /* Resources */,
4FF813E22E00404000D89535 /* Embed Foundation Extensions */,
);
buildRules = (
);
dependencies = (
4FF813DC2E00404000D89535 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
4FF8139B2E003FB400D89535 /* CoBiE */,
);
name = CoBiE;
packageProductDependencies = (
);
productName = CoBiE;
productReference = 4FF813992E003FB400D89535 /* CoBiE.app */;
productType = "com.apple.product-type.application";
};
4FF813A72E003FB600D89535 /* CoBiETests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4FF813BF2E003FB600D89535 /* Build configuration list for PBXNativeTarget "CoBiETests" */;
buildPhases = (
4FF813A42E003FB600D89535 /* Sources */,
4FF813A52E003FB600D89535 /* Frameworks */,
4FF813A62E003FB600D89535 /* Resources */,
);
buildRules = (
);
dependencies = (
4FF813AA2E003FB600D89535 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
4FF813AB2E003FB600D89535 /* CoBiETests */,
);
name = CoBiETests;
packageProductDependencies = (
);
productName = CoBiETests;
productReference = 4FF813A82E003FB600D89535 /* CoBiETests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
4FF813B12E003FB600D89535 /* CoBiEUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4FF813C22E003FB600D89535 /* Build configuration list for PBXNativeTarget "CoBiEUITests" */;
buildPhases = (
4FF813AE2E003FB600D89535 /* Sources */,
4FF813AF2E003FB600D89535 /* Frameworks */,
4FF813B02E003FB600D89535 /* Resources */,
);
buildRules = (
);
dependencies = (
4FF813B42E003FB600D89535 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
4FF813B52E003FB600D89535 /* CoBiEUITests */,
);
name = CoBiEUITests;
packageProductDependencies = (
);
productName = CoBiEUITests;
productReference = 4FF813B22E003FB600D89535 /* CoBiEUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
4FF813C82E00403E00D89535 /* CoBiE Analog ClockExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4FF813DF2E00404000D89535 /* Build configuration list for PBXNativeTarget "CoBiE Analog ClockExtension" */;
buildPhases = (
4FF813C52E00403E00D89535 /* Sources */,
4FF813C62E00403E00D89535 /* Frameworks */,
4FF813C72E00403E00D89535 /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
4FF813CF2E00403F00D89535 /* CoBiE Analog Clock */,
);
name = "CoBiE Analog ClockExtension";
packageProductDependencies = (
);
productName = "CoBiE Analog ClockExtension";
productReference = 4FF813C92E00403E00D89535 /* CoBiE Analog ClockExtension.appex */;
productType = "com.apple.product-type.app-extension";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
4FF813912E003FB400D89535 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 2600;
LastUpgradeCheck = 2600;
TargetAttributes = {
4FF813982E003FB400D89535 = {
CreatedOnToolsVersion = 26.0;
};
4FF813A72E003FB600D89535 = {
CreatedOnToolsVersion = 26.0;
TestTargetID = 4FF813982E003FB400D89535;
};
4FF813B12E003FB600D89535 = {
CreatedOnToolsVersion = 26.0;
TestTargetID = 4FF813982E003FB400D89535;
};
4FF813C82E00403E00D89535 = {
CreatedOnToolsVersion = 26.0;
};
};
};
buildConfigurationList = 4FF813942E003FB400D89535 /* Build configuration list for PBXProject "CoBiE" */;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 4FF813902E003FB400D89535;
minimizedProjectReferenceProxies = 1;
preferredProjectObjectVersion = 77;
productRefGroup = 4FF8139A2E003FB400D89535 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
4FF813982E003FB400D89535 /* CoBiE */,
4FF813A72E003FB600D89535 /* CoBiETests */,
4FF813B12E003FB600D89535 /* CoBiEUITests */,
4FF813C82E00403E00D89535 /* CoBiE Analog ClockExtension */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
4FF813972E003FB400D89535 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4FF813A62E003FB600D89535 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4FF813B02E003FB600D89535 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4FF813C72E00403E00D89535 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4FF813E72E00404000D89535 /* analog-clock.html in Resources */,
4FF813E82E00404000D89535 /* clock.js in Resources */,
4FF813E92E00404000D89535 /* cobie.js in Resources */,
4FF813EA2E00404000D89535 /* style.css in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
4FF813952E003FB400D89535 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4FF813A42E003FB600D89535 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4FF813AE2E003FB600D89535 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4FF813C52E00403E00D89535 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
4FF813AA2E003FB600D89535 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 4FF813982E003FB400D89535 /* CoBiE */;
targetProxy = 4FF813A92E003FB600D89535 /* PBXContainerItemProxy */;
};
4FF813B42E003FB600D89535 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 4FF813982E003FB400D89535 /* CoBiE */;
targetProxy = 4FF813B32E003FB600D89535 /* PBXContainerItemProxy */;
};
4FF813DC2E00404000D89535 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 4FF813C82E00403E00D89535 /* CoBiE Analog ClockExtension */;
targetProxy = 4FF813DB2E00404000D89535 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
4FF813BA2E003FB600D89535 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 8KC3NAN2CJ;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
4FF813BB2E003FB600D89535 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 8KC3NAN2CJ;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SWIFT_COMPILATION_MODE = wholemodule;
};
name = Release;
};
4FF813BD2E003FB600D89535 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 8KC3NAN2CJ;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = no.kaizenkodo.CoBiE;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SDKROOT = auto;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
XROS_DEPLOYMENT_TARGET = 26.0;
};
name = Debug;
};
4FF813BE2E003FB600D89535 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 8KC3NAN2CJ;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = no.kaizenkodo.CoBiE;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SDKROOT = auto;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
XROS_DEPLOYMENT_TARGET = 26.0;
};
name = Release;
};
4FF813C02E003FB600D89535 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 8KC3NAN2CJ;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = no.kaizenkodo.CoBiETests;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CoBiE.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CoBiE";
XROS_DEPLOYMENT_TARGET = 26.0;
};
name = Debug;
};
4FF813C12E003FB600D89535 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 8KC3NAN2CJ;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = no.kaizenkodo.CoBiETests;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CoBiE.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CoBiE";
XROS_DEPLOYMENT_TARGET = 26.0;
};
name = Release;
};
4FF813C32E003FB600D89535 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 8KC3NAN2CJ;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = no.kaizenkodo.CoBiEUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
TEST_TARGET_NAME = CoBiE;
XROS_DEPLOYMENT_TARGET = 26.0;
};
name = Debug;
};
4FF813C42E003FB600D89535 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 8KC3NAN2CJ;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = no.kaizenkodo.CoBiEUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
TEST_TARGET_NAME = CoBiE;
XROS_DEPLOYMENT_TARGET = 26.0;
};
name = Release;
};
4FF813E02E00404000D89535 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 8KC3NAN2CJ;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "CoBiE Analog Clock/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "CoBiE Analog Clock";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "no.kaizenkodo.CoBiE.CoBiE-Analog-Clock";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
4FF813E12E00404000D89535 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 8KC3NAN2CJ;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "CoBiE Analog Clock/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "CoBiE Analog Clock";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "no.kaizenkodo.CoBiE.CoBiE-Analog-Clock";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
4FF813942E003FB400D89535 /* Build configuration list for PBXProject "CoBiE" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4FF813BA2E003FB600D89535 /* Debug */,
4FF813BB2E003FB600D89535 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4FF813BC2E003FB600D89535 /* Build configuration list for PBXNativeTarget "CoBiE" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4FF813BD2E003FB600D89535 /* Debug */,
4FF813BE2E003FB600D89535 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4FF813BF2E003FB600D89535 /* Build configuration list for PBXNativeTarget "CoBiETests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4FF813C02E003FB600D89535 /* Debug */,
4FF813C12E003FB600D89535 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4FF813C22E003FB600D89535 /* Build configuration list for PBXNativeTarget "CoBiEUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4FF813C32E003FB600D89535 /* Debug */,
4FF813C42E003FB600D89535 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4FF813DF2E00404000D89535 /* Build configuration list for PBXNativeTarget "CoBiE Analog ClockExtension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4FF813E02E00404000D89535 /* Debug */,
4FF813E12E00404000D89535 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 4FF813912E003FB400D89535 /* Project object */;
}
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>CoBiE Analog ClockExtension.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
<key>CoBiE.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>
@@ -1,11 +0,0 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,85 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
-32
View File
@@ -1,32 +0,0 @@
//
// CoBiEApp.swift
// CoBiE
//
// Created by Oleksandr Kozachuk on 2025-06-16.
//
import SwiftUI
import SwiftData
@main
struct CoBiEApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Item.self,
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(sharedModelContainer)
}
}
-66
View File
@@ -1,66 +0,0 @@
//
// ContentView.swift
// CoBiE
//
// Created by Oleksandr Kozachuk on 2025-06-16.
//
import SwiftUI
import SwiftData
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var items: [Item]
var body: some View {
NavigationSplitView {
List {
ForEach(items) { item in
NavigationLink {
Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))")
} label: {
Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
}
}
.onDelete(perform: deleteItems)
}
#if os(macOS)
.navigationSplitViewColumnWidth(min: 180, ideal: 200)
#endif
.toolbar {
#if os(iOS)
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
#endif
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
} detail: {
Text("Select an item")
}
}
private func addItem() {
withAnimation {
let newItem = Item(timestamp: Date())
modelContext.insert(newItem)
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
for index in offsets {
modelContext.delete(items[index])
}
}
}
}
#Preview {
ContentView()
.modelContainer(for: Item.self, inMemory: true)
}
-18
View File
@@ -1,18 +0,0 @@
//
// Item.swift
// CoBiE
//
// Created by Oleksandr Kozachuk on 2025-06-16.
//
import Foundation
import SwiftData
@Model
final class Item {
var timestamp: Date
init(timestamp: Date) {
self.timestamp = timestamp
}
}
-16
View File
@@ -1,16 +0,0 @@
//
// CoBiETests.swift
// CoBiETests
//
// Created by Oleksandr Kozachuk on 2025-06-16.
//
import Testing
struct CoBiETests {
@Test func example() async throws {
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
}
}
-41
View File
@@ -1,41 +0,0 @@
//
// CoBiEUITests.swift
// CoBiEUITests
//
// Created by Oleksandr Kozachuk on 2025-06-16.
//
import XCTest
final class CoBiEUITests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = false
// In UI tests its important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
@MainActor
func testExample() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
app.launch()
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
@MainActor
func testLaunchPerformance() throws {
// This measures how long it takes to launch your application.
measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch()
}
}
}
@@ -1,33 +0,0 @@
//
// CoBiEUITestsLaunchTests.swift
// CoBiEUITests
//
// Created by Oleksandr Kozachuk on 2025-06-16.
//
import XCTest
final class CoBiEUITestsLaunchTests: XCTestCase {
override class var runsForEachTargetApplicationUIConfiguration: Bool {
true
}
override func setUpWithError() throws {
continueAfterFailure = false
}
@MainActor
func testLaunch() throws {
let app = XCUIApplication()
app.launch()
// Insert steps here to perform after app launch but before taking a screenshot,
// such as logging into a test account or navigating somewhere in the app
let attachment = XCTAttachment(screenshot: app.screenshot())
attachment.name = "Launch Screen"
attachment.lifetime = .keepAlways
add(attachment)
}
}
+6 -4
View File
@@ -54,13 +54,15 @@ An interactive web app that visualizes the **CosmoChron Binary Epoch (CoBiE)** t
``` ```
├── index.html # Main HTML markup ├── index.html # Main HTML markup
├── analog.html # Analog clock interface
├── clock.js # Clock logic ├── clock.js # Clock logic
├── cobie.js # CoBiE time system utilities
├── utils.js # Generic helper functions
├── animate.js # Shared animations
├── events.js # Sample calendar events
├── style.css # Separated styles ├── style.css # Separated styles
├── script.js # JavaScript logic ├── script.js # Page interactions
├── README.md # This documentation ├── README.md # This documentation
└── assets/ # (Optional) images or external CSS/JS └── test/ # Unit tests
``` ```
## macOS Widget ## macOS Widget
+83
View File
@@ -0,0 +1,83 @@
(function(){
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) {
const target = angle + 360;
const handle = () => {
el.removeEventListener('transitionend', handle);
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 animateSwipe(direction, onDone) {
const grid = document.getElementById('eonstripGrid');
if (!grid) { onDone(); return; }
grid.style.transition = 'none';
grid.style.transform = 'translateX(0)';
void grid.offsetWidth;
grid.style.transition = 'transform 0.3s ease';
grid.style.transform = `translateX(${direction > 0 ? '-100%' : '100%'})`;
function afterOut() {
grid.removeEventListener('transitionend', afterOut);
grid.style.transition = 'none';
grid.style.transform = `translateX(${direction > 0 ? '100%' : '-100%'})`;
onDone();
void grid.offsetWidth;
grid.style.transition = 'transform 0.3s ease';
grid.style.transform = 'translateX(0)';
}
grid.addEventListener('transitionend', afterOut);
}
function animateDetailSwipe(direction, onDone) {
const tl = document.getElementById('detailTimeline');
if (!tl) { onDone(); return; }
tl.style.transition = 'none';
tl.style.transform = 'translateX(0)';
void tl.offsetWidth;
tl.style.transition = 'transform 0.3s ease';
tl.style.transform = `translateX(${direction > 0 ? '-100%' : '100%'})`;
function afterOut() {
tl.removeEventListener('transitionend', afterOut);
tl.style.transition = 'none';
tl.style.transform = `translateX(${direction > 0 ? '100%' : '-100%'})`;
onDone();
void tl.offsetWidth;
tl.style.transition = 'transform 0.3s ease';
tl.style.transform = 'translateX(0)';
}
tl.addEventListener('transitionend', afterOut);
}
window.Animate = {
rotateHand,
animateSwipe,
animateDetailSwipe
};
})();
+8 -40
View File
@@ -83,9 +83,10 @@
const outerR = baseRadius - 2; // just inside the border const outerR = baseRadius - 2; // just inside the border
// Distance from center for the marker digits so they sit just inside big ticks // Distance from center so marker's outer edge sits just inside the big tick
const markerSize = markers[0] ? markers[0].offsetWidth : 0; const markerSize = markers[0] ? markers[0].offsetWidth : 0;
const markerRadius = outerR - lenBig - markerSize * getMarkerOffset(clock.offsetWidth); const inset = baseRadius * 0.001; // 0.1% of the radius
const markerRadius = outerR - lenBig - inset + markerSize / 2;
markers.forEach((m, i) => { markers.forEach((m, i) => {
const angle = (i / 16) * 2 * Math.PI; const angle = (i / 16) * 2 * Math.PI;
@@ -113,39 +114,6 @@
}); });
} }
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) { function renderClock(cob) {
// Use fractional progress within each unit so angles stay small // Use fractional progress within each unit so angles stay small
@@ -154,11 +122,11 @@
const cf = (cob % COBIE_UNITS.eonstrip) / COBIE_UNITS.eonstrip; const cf = (cob % COBIE_UNITS.eonstrip) / COBIE_UNITS.eonstrip;
const ef = (cob % COBIE_UNITS.megasequence) / COBIE_UNITS.megasequence; const ef = (cob % COBIE_UNITS.megasequence) / COBIE_UNITS.megasequence;
const mf = (cob % COBIE_UNITS.cosmocycle) / COBIE_UNITS.cosmocycle; const mf = (cob % COBIE_UNITS.cosmocycle) / COBIE_UNITS.cosmocycle;
rotateHand('handXeno', xf * 360); Animate.rotateHand('handXeno', xf * 360);
rotateHand('handQuantic', qf * 360); Animate.rotateHand('handQuantic', qf * 360);
rotateHand('handChronon', cf * 360); Animate.rotateHand('handChronon', cf * 360);
rotateHand('handEonstrip', ef * 360); Animate.rotateHand('handEonstrip', ef * 360);
rotateHand('handMegasequence', mf * 360); Animate.rotateHand('handMegasequence', mf * 360);
} }
function updateClock() { function updateClock() {
+17 -1
View File
@@ -19,6 +19,20 @@ const COBIE_UNITS = {
astralmillennia: 0x1000000000000000 astralmillennia: 0x1000000000000000
}; };
const EONSTRIP_NAMES = [
'Solprime', 'Lunex', 'Terros', 'Aquarion',
'Ventaso', 'Ignisar', 'Crystalos', 'Floraen',
'Faunor', 'Nebulus', 'Astraeus', 'Umbranox',
'Electros', 'Chronar', 'Radiantae', 'Etherion'
];
const MEGASEQUENCE_NAMES = [
'Azurean Tide', 'Sable Gleam', 'Verdanth Starfall', 'Crimson Dusk',
'Cobalt Frost', 'Amber Blaze', 'Viridian Bloom', 'Argent Veil',
'Helian Rise', 'Nocturne Shade', 'Celestine Aura', 'Pyralis Light',
'Zephyrine Whisper', 'Lustran Bounty', 'Umbral Echo', 'Mythran Epoch'
];
function floorDiv(a, b) { function floorDiv(a, b) {
return Math.trunc(a / b); return Math.trunc(a / b);
} }
@@ -172,7 +186,9 @@ const Cobie = {
toCobiets, toCobiets,
fromCobiets, fromCobiets,
formatCobieTimestamp, formatCobieTimestamp,
breakdownNonNeg breakdownNonNeg,
EONSTRIP_NAMES,
MEGASEQUENCE_NAMES
}; };
if (typeof module !== 'undefined' && module.exports) { if (typeof module !== 'undefined' && module.exports) {
+23 -10
View File
@@ -1,14 +1,27 @@
// Configuration of periodic events in CoBiE time // Configuration of periodic events in CoBiE time
// Each event repeats every cosmocycle. // Each object describes when the event occurs and how often it repeats.
// "cobie" is the timestamp within the cosmocycle when the event occurs. //
// "tag" is a short label that will be displayed on the calendar. // Fields:
// start - CoBiE timestamp when the first occurrence happens.
// end - optional CoBiE timestamp after which the event stops.
// unit - the unit of the recurrence ("second", "xenocycle", "quantic",
// "chronon", "eonstrip", "megasequence", "cosmocycle", ...).
// interval - how many units between occurrences (1 = every unit,
// 2 = every second unit, ...).
// label - short description displayed on the calendar.
// duration - optional length of the event in seconds.
// showMega - optional boolean, show label on the megasequence view (default true).
// showDetail - optional boolean, show event in the detail view (default true).
// color - optional hex color for the event (used in detail view)
window.SPECIAL_EVENTS = [ window.SPECIAL_EVENTS = [
{ cobie: '49f4.9332', label: 'Afina', megasequence: 'Mythran Epoch', eonstrip: 'Ventaso' }, { cobie: '49f4.9332', label: 'Afina', color: '#e57373', unit: 'cosmocycle', interval: 1 },
{ cobie: '11e5.f552', label: 'Oleks', megasequence: 'Umbral Echo', eonstrip: 'Ignisar' }, { cobie: '11e5.f552', label: 'Oleks', color: '#64b5f6', unit: 'cosmocycle', interval: 1 },
{ cobie: '4d07.a2b2', label: 'Vincent', megasequence: 'Azurean Tide', eonstrip: 'Floraen' }, { cobie: '4d07.a2b2', label: 'Vincent', color: '#81c784', unit: 'cosmocycle', interval: 1 },
{ cobie: '3edc.d430', label: 'Hochzeitstag', megasequence: 'Lustran Bounty', eonstrip: 'Electros' }, { cobie: '3edc.d430', label: 'Hochzeitstag', color: '#ffb74d', unit: 'cosmocycle', interval: 1 },
{ cobie: '330d.d4ae', label: 'Zusammentag', megasequence: 'Azurean Tide', eonstrip: 'Chronar' }, { cobie: '330d.d4ae', label: 'Zusammentag', color: '#ba68c8', unit: 'cosmocycle', interval: 1 },
{ cobie: '11de.0c52', label: 'Anna', megasequence: 'Lustran Bounty', eonstrip: 'Radiantae' }, { cobie: '11de.0c52', label: 'Anna', color: '#4db6ac', unit: 'cosmocycle', interval: 1 },
{ cobie: '467f.ae61', label: 'Iris', megasequence: 'Argent Veil', eonstrip: 'Etherion' } { cobie: '467f.ae61', label: 'Iris', color: '#7986cb', unit: 'cosmocycle', interval: 1 },
{ cobie: '6854.7a75', label: 'Sleep', color: '#546e7a', unit: 'second', interval: 86400, duration: 28800, showMega: false },
{ cobie: '6854.9695', label: 'Night', color: '#212121', unit: 'second', interval: 86400, duration: 28800, showMega: false, shiftWithTimezone: true }
]; ];
+16 -1
View File
@@ -61,11 +61,24 @@
<button onclick="goToNow()">Now</button> <button onclick="goToNow()">Now</button>
</div> </div>
<div class="calendar-view"> <div class="calendar-view" id="calendarView">
<div class="calendar-header" id="calendarHeader">Loading...</div> <div class="calendar-header" id="calendarHeader">Loading...</div>
<div class="eonstrip-grid" id="eonstripGrid"></div> <div class="eonstrip-grid" id="eonstripGrid"></div>
</div> </div>
<div id="eonstripDetailView" class="detail-view" style="display:none;">
<div class="detail-header">
<button id="backToCalendar" class="back-btn">Back</button>
<div class="detail-nav">
<button id="detailPrev" class="back-btn"></button>
<button id="detailNow" class="back-btn">Now</button>
<button id="detailNext" class="back-btn"></button>
</div>
<span id="detailTitle"></span>
</div>
<div class="detail-timeline" id="detailTimeline"></div>
</div>
<div class="time-details"> <div class="time-details">
<h3 style="text-align: center; margin-bottom: 20px; color: #00ffff;">Time Breakdown</h3> <h3 style="text-align: center; margin-bottom: 20px; color: #00ffff;">Time Breakdown</h3>
@@ -154,6 +167,8 @@
</div> </div>
<script src="cobie.js"></script> <script src="cobie.js"></script>
<script src="utils.js"></script>
<script src="animate.js"></script>
<script src="events.js"></script> <script src="events.js"></script>
<script src="script.js"></script> <script src="script.js"></script>
<script src="clock.js"></script> <script src="clock.js"></script>
+340 -196
View File
@@ -20,19 +20,7 @@ const {
breakdownNonNeg breakdownNonNeg
} = window.Cobie; } = window.Cobie;
const EONSTRIP_NAMES = [ const { EONSTRIP_NAMES, MEGASEQUENCE_NAMES } = window.Cobie;
'Solprime', 'Lunex', 'Terros', 'Aquarion',
'Ventaso', 'Ignisar', 'Crystalos', 'Floraen',
'Faunor', 'Nebulus', 'Astraeus', 'Umbranox',
'Electros', 'Chronar', 'Radiantae', 'Etherion'
];
const MEGASEQUENCE_NAMES = [
'Azurean Tide', 'Sable Gleam', 'Verdanth Starfall', 'Crimson Dusk',
'Cobalt Frost', 'Amber Blaze', 'Viridian Bloom', 'Argent Veil',
'Helian Rise', 'Nocturne Shade', 'Celestine Aura', 'Pyralis Light',
'Zephyrine Whisper', 'Lustran Bounty', 'Umbral Echo', 'Mythran Epoch'
];
let currentOffset = 0; let currentOffset = 0;
let currentTimezone = 'UTC'; let currentTimezone = 'UTC';
@@ -42,13 +30,53 @@ let compareManualMode = false;
let compareCobiets = 0; let compareCobiets = 0;
let updateInterval; let updateInterval;
let lastRenderedEonstrip = null; let lastRenderedEonstrip = null;
let currentDetailCob = null;
function fmt(d, o) { // ── Utility color helpers (in utils.js) ───────────────────────────────────
// shift if TAI, then format const {
const shifted = currentTimezone==='TAI' parseColor,
? new Date(d.getTime() + getTAIOffsetAt(d)*1000) hexToRgba,
: d; getContrastColor,
return shifted.toLocaleString('en-US', o); lightenColor,
getHumanDiff
} = window.Utils;
const dateOptions = (long = true) => ({
timeZone: currentTimezone === 'TAI' ? 'UTC' : currentTimezone,
weekday: 'long',
year: 'numeric',
month: long ? 'long' : 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
function applyEventColors(elem, color, alpha) {
if (!color || !elem) return;
elem.style.setProperty('--bg-color', hexToRgba(color, alpha));
// Use a lighter shade for the border so it stands out even for dark colors
elem.style.setProperty('--border-color', lightenColor(color, 0.4));
elem.style.setProperty('--text-color', getContrastColor(color));
}
function getTimezoneOffsetSeconds(date) {
if (currentTimezone === 'UTC') return 0;
if (currentTimezone === 'TAI') return getTAIOffsetAt(date);
const dtf = new Intl.DateTimeFormat('en-US', {
timeZone: currentTimezone,
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit',
hour12: false
});
const parts = dtf.formatToParts(date).reduce((acc, p) => {
if (p.type !== 'literal') acc[p.type] = parseInt(p.value, 10);
return acc;
}, {});
const utcTime = Date.UTC(parts.year, parts.month - 1, parts.day,
parts.hour, parts.minute, parts.second);
return (utcTime - date.getTime()) / 1000;
} }
function formatSafeDate(rawDate, cobSeconds, intlOptions) { function formatSafeDate(rawDate, cobSeconds, intlOptions) {
@@ -70,74 +98,45 @@ function formatSafeDate(rawDate, cobSeconds, intlOptions) {
// parseCobiets, floorDiv and other CoBiE helpers are provided by cobie.js // parseCobiets, floorDiv and other CoBiE helpers are provided by cobie.js
function getCurrentTAIOffset() {
return getTAIOffsetAt(new Date());
// ── Event utilities ──────────────────────────────────────────────────────
const normalizeEvent = ev => {
const baseStart = parseCobiets(ev.start || ev.cobie);
if (baseStart === null) return null;
const tzShift = ev.shiftWithTimezone ?
getTimezoneOffsetSeconds(fromCobiets(baseStart)) : 0;
const startCob = baseStart - tzShift;
const endCob = ev.end ? parseCobiets(ev.end) - tzShift : Number.POSITIVE_INFINITY;
const unitVal = COBIE_UNITS[ev.unit] || COBIE_UNITS.cosmocycle;
const interval = (ev.interval || 1) * unitVal;
let duration = 0;
if (typeof ev.duration === 'string') {
const d = parseCobiets(ev.duration);
if (d !== null) duration = d;
} else if (typeof ev.duration === 'number') {
duration = ev.duration;
} }
return { startCob, endCob, interval, duration };
};
function getHumanDiff(d1, d2) { function collectEventOccurrences(start, end, predicate = () => true) {
// make sure start ≤ end const out = [];
let start = d1 < d2 ? d1 : d2; if (!Array.isArray(window.SPECIAL_EVENTS)) return out;
let end = d1 < d2 ? d2 : d1; window.SPECIAL_EVENTS.forEach(ev => {
if (!predicate(ev)) return;
// 1) year/month/day difference const meta = normalizeEvent(ev);
let years = end.getUTCFullYear() - start.getUTCFullYear(); if (!meta || start > meta.endCob) return;
let months = end.getUTCMonth() - start.getUTCMonth(); let n = Math.floor((start - meta.startCob) / meta.interval);
let days = end.getUTCDate() - start.getUTCDate(); if (n < 0) n = 0;
let occ = meta.startCob + n * meta.interval;
// if day roll-under, borrow from month if (occ + meta.duration <= start) occ += meta.interval;
if (days < 0) { while (occ < end && occ <= meta.endCob) {
months--; out.push({ event: ev, meta, occ });
// days in the month *before* `end`s month: occ += meta.interval;
let prevMonthDays = new Date(Date.UTC(
end.getUTCFullYear(),
end.getUTCMonth(), 0
)).getUTCDate();
days += prevMonthDays;
} }
// if month roll-under, borrow from year });
if (months < 0) { return out;
years--;
months += 12;
}
// 2) now handle hours/min/sec by “aligning” a Date at start+Y/M/D
let aligned = new Date(Date.UTC(
start.getUTCFullYear() + years,
start.getUTCMonth() + months,
start.getUTCDate() + days,
start.getUTCHours(),
start.getUTCMinutes(),
start.getUTCSeconds()
));
let diffMs = end.getTime() - aligned.getTime();
// if we overshot (negative), borrow one day
if (diffMs < 0) {
// borrow 24 h
diffMs += 24 * 3600e3;
if (days > 0) {
days--;
} else {
// days was zero, so borrow a month
months--;
if (months < 0) {
years--;
months += 12;
}
// set days to length of the previous month of `end`
days = new Date(Date.UTC(
end.getUTCFullYear(),
end.getUTCMonth(), 0
)).getUTCDate();
}
}
// 3) extract h/m/s
let hours = Math.floor(diffMs / 3600e3); diffMs -= hours * 3600e3;
let minutes = Math.floor(diffMs / 60e3); diffMs -= minutes * 60e3;
let seconds = Math.floor(diffMs / 1e3);
return { years, months, days, hours, minutes, seconds };
} }
// getTAIOffsetAt, toCobiets, fromCobiets, breakdownNonNeg and // getTAIOffsetAt, toCobiets, fromCobiets, breakdownNonNeg and
@@ -155,17 +154,7 @@ function updateCurrentTime() {
const cobieElem = document.getElementById('cobieTime'); const cobieElem = document.getElementById('cobieTime');
if (cobieElem) cobieElem.textContent = formatCobieTimestamp(cobiets); if (cobieElem) cobieElem.textContent = formatCobieTimestamp(cobiets);
const options = { const options = dateOptions();
timeZone: currentTimezone === 'TAI' ? 'UTC' : currentTimezone,
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
};
const taiOffset = getTAIOffsetAt(baseDate); const taiOffset = getTAIOffsetAt(baseDate);
let displayDate = baseDate; let displayDate = baseDate;
@@ -175,9 +164,9 @@ function updateCurrentTime() {
document.getElementById('regularTime').textContent = currentTimezone + ': ' + displayDate.toLocaleString('en-US', options); document.getElementById('regularTime').textContent = currentTimezone + ': ' + displayDate.toLocaleString('en-US', options);
options.timeZone = 'UTC'; const optionsUTC = { ...options, timeZone: 'UTC' };
const taiDate = new Date(baseDate.getTime() + taiOffset * 1000); const taiDate = new Date(baseDate.getTime() + taiOffset * 1000);
document.getElementById('taiTime').textContent = 'TAI UTC: ' + taiDate.toLocaleString('en-US', options) + ' (UTC + ' + taiOffset + 's)'; document.getElementById('taiTime').textContent = 'TAI UTC: ' + taiDate.toLocaleString('en-US', optionsUTC) + ' (UTC + ' + taiOffset + 's)';
const bd = breakdownNonNeg(Math.abs(cobiets)); const bd = breakdownNonNeg(Math.abs(cobiets));
@@ -188,6 +177,7 @@ function updateCurrentTime() {
} else { } else {
updateTimeBreakdown(cobiets); updateTimeBreakdown(cobiets);
} }
updateDetailCurrentTime();
} }
function updateTimeBreakdown(cobiets) { function updateTimeBreakdown(cobiets) {
@@ -213,17 +203,7 @@ function updateTimeBreakdown(cobiets) {
const eosEnd = eosStart + COBIE_UNITS.eonstrip - 1; const eosEnd = eosStart + COBIE_UNITS.eonstrip - 1;
// 4) Intl formatting options // 4) Intl formatting options
const dateOptions = { const optsLong = dateOptions();
timeZone: currentTimezone === 'TAI' ? 'UTC' : currentTimezone,
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
};
// //
// ── Build the “core” units (always visible): Galactic Year → Second ────────────── // ── Build the “core” units (always visible): Galactic Year → Second ──────────────
@@ -240,8 +220,8 @@ function updateTimeBreakdown(cobiets) {
const rawStart = fromCobiets(gyrStart); const rawStart = fromCobiets(gyrStart);
const rawEnd = fromCobiets(gyrEnd); const rawEnd = fromCobiets(gyrEnd);
return ` return `
<span>Started: ${formatSafeDate(rawStart, gyrStart, dateOptions)}</span><br> <span>Started: ${formatSafeDate(rawStart, gyrStart, optsLong)}</span><br>
<span>Ends: ${formatSafeDate(rawEnd, gyrEnd, dateOptions)}</span> <span>Ends: ${formatSafeDate(rawEnd, gyrEnd, optsLong)}</span>
`; `;
})()} })()}
</div> </div>
@@ -257,8 +237,8 @@ function updateTimeBreakdown(cobiets) {
const rawStart = fromCobiets(ccyStart); const rawStart = fromCobiets(ccyStart);
const rawEnd = fromCobiets(ccyEnd); const rawEnd = fromCobiets(ccyEnd);
return ` return `
<span>Started: ${formatSafeDate(rawStart, ccyStart, dateOptions)}</span><br> <span>Started: ${formatSafeDate(rawStart, ccyStart, optsLong)}</span><br>
<span>Ends: ${formatSafeDate(rawEnd, ccyEnd, dateOptions)}</span> <span>Ends: ${formatSafeDate(rawEnd, ccyEnd, optsLong)}</span>
`; `;
})()} })()}
</div> </div>
@@ -274,8 +254,8 @@ function updateTimeBreakdown(cobiets) {
const rawStart = fromCobiets(mqsStart); const rawStart = fromCobiets(mqsStart);
const rawEnd = fromCobiets(mqsEnd); const rawEnd = fromCobiets(mqsEnd);
return ` return `
<span>Started: ${formatSafeDate(rawStart, mqsStart, dateOptions)}</span><br> <span>Started: ${formatSafeDate(rawStart, mqsStart, optsLong)}</span><br>
<span>Ends: ${formatSafeDate(rawEnd, mqsEnd, dateOptions)}</span> <span>Ends: ${formatSafeDate(rawEnd, mqsEnd, optsLong)}</span>
`; `;
})()} })()}
</div> </div>
@@ -291,8 +271,8 @@ function updateTimeBreakdown(cobiets) {
const rawStart = fromCobiets(eosStart); const rawStart = fromCobiets(eosStart);
const rawEnd = fromCobiets(eosEnd); const rawEnd = fromCobiets(eosEnd);
return ` return `
<span>Started: ${formatSafeDate(rawStart, eosStart, dateOptions)}</span><br> <span>Started: ${formatSafeDate(rawStart, eosStart, optsLong)}</span><br>
<span>Ends: ${formatSafeDate(rawEnd, eosEnd, dateOptions)}</span> <span>Ends: ${formatSafeDate(rawEnd, eosEnd, optsLong)}</span>
`; `;
})()} })()}
</div> </div>
@@ -341,8 +321,8 @@ function updateTimeBreakdown(cobiets) {
const rawStart = fromCobiets(startAmt); const rawStart = fromCobiets(startAmt);
const rawEnd = fromCobiets(endAmt); const rawEnd = fromCobiets(endAmt);
return ` return `
<span>Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}</span><br> <span>Started: ${formatSafeDate(rawStart, startAmt, optsLong)}</span><br>
<span>Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)}</span> <span>Ends: ${formatSafeDate(rawEnd, endAmt, optsLong)}</span>
`; `;
})()} })()}
</div> </div>
@@ -360,8 +340,8 @@ function updateTimeBreakdown(cobiets) {
const rawStart = fromCobiets(startAmt); const rawStart = fromCobiets(startAmt);
const rawEnd = fromCobiets(endAmt); const rawEnd = fromCobiets(endAmt);
return ` return `
<span>Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}</span><br> <span>Started: ${formatSafeDate(rawStart, startAmt, optsLong)}</span><br>
<span>Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)}</span> <span>Ends: ${formatSafeDate(rawEnd, endAmt, optsLong)}</span>
`; `;
})()} })()}
</div> </div>
@@ -379,8 +359,8 @@ function updateTimeBreakdown(cobiets) {
const rawStart = fromCobiets(startAmt); const rawStart = fromCobiets(startAmt);
const rawEnd = fromCobiets(endAmt); const rawEnd = fromCobiets(endAmt);
return ` return `
<span>Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}</span><br> <span>Started: ${formatSafeDate(rawStart, startAmt, optsLong)}</span><br>
<span>Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)}</span> <span>Ends: ${formatSafeDate(rawEnd, endAmt, optsLong)}</span>
`; `;
})()} })()}
</div> </div>
@@ -398,8 +378,8 @@ function updateTimeBreakdown(cobiets) {
const rawStart = fromCobiets(startAmt); const rawStart = fromCobiets(startAmt);
const rawEnd = fromCobiets(endAmt); const rawEnd = fromCobiets(endAmt);
return ` return `
<span>Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}</span><br> <span>Started: ${formatSafeDate(rawStart, startAmt, optsLong)}</span><br>
<span>Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)}</span> <span>Ends: ${formatSafeDate(rawEnd, endAmt, optsLong)}</span>
`; `;
})()} })()}
</div> </div>
@@ -417,8 +397,8 @@ function updateTimeBreakdown(cobiets) {
const rawStart = fromCobiets(startAmt); const rawStart = fromCobiets(startAmt);
const rawEnd = fromCobiets(endAmt); const rawEnd = fromCobiets(endAmt);
return ` return `
<span>Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}</span><br> <span>Started: ${formatSafeDate(rawStart, startAmt, optsLong)}</span><br>
<span>Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)}</span> <span>Ends: ${formatSafeDate(rawEnd, endAmt, optsLong)}</span>
`; `;
})()} })()}
</div> </div>
@@ -434,8 +414,8 @@ function updateTimeBreakdown(cobiets) {
const rawStart = fromCobiets(eocStart); const rawStart = fromCobiets(eocStart);
const rawEnd = fromCobiets(eocEnd); const rawEnd = fromCobiets(eocEnd);
return ` return `
<span>Started: ${formatSafeDate(rawStart, eocStart, dateOptions)}</span><br> <span>Started: ${formatSafeDate(rawStart, eocStart, optsLong)}</span><br>
<span>Ends: ${formatSafeDate(rawEnd, eocEnd, dateOptions)}</span> <span>Ends: ${formatSafeDate(rawEnd, eocEnd, optsLong)}</span>
`; `;
})()} })()}
</div> </div>
@@ -451,8 +431,8 @@ function updateTimeBreakdown(cobiets) {
const rawStart = fromCobiets(cerStart); const rawStart = fromCobiets(cerStart);
const rawEnd = fromCobiets(cerEnd); const rawEnd = fromCobiets(cerEnd);
return ` return `
<span>Started: ${formatSafeDate(rawStart, cerStart, dateOptions)}</span><br> <span>Started: ${formatSafeDate(rawStart, cerStart, optsLong)}</span><br>
<span>Ends: ${formatSafeDate(rawEnd, cerEnd, dateOptions)}</span> <span>Ends: ${formatSafeDate(rawEnd, cerEnd, optsLong)}</span>
`; `;
})()} })()}
</div> </div>
@@ -468,8 +448,8 @@ function updateTimeBreakdown(cobiets) {
const rawStart = fromCobiets(ueoStart); const rawStart = fromCobiets(ueoStart);
const rawEnd = fromCobiets(ueoEnd); const rawEnd = fromCobiets(ueoEnd);
return ` return `
<span>Started: ${formatSafeDate(rawStart, ueoStart, dateOptions)}</span><br> <span>Started: ${formatSafeDate(rawStart, ueoStart, optsLong)}</span><br>
<span>Ends: ${formatSafeDate(rawEnd, ueoEnd, dateOptions)}</span> <span>Ends: ${formatSafeDate(rawEnd, ueoEnd, optsLong)}</span>
`; `;
})()} })()}
</div> </div>
@@ -586,12 +566,7 @@ function updateCalendar() {
grid.innerHTML = ''; grid.innerHTML = '';
// reuse the same dateOpts you use elsewhere: // reuse the same dateOpts you use elsewhere:
const dateOpts = { const dateOpts = dateOptions(false);
timeZone: currentTimezone==='TAI' ? 'UTC' : currentTimezone,
year: 'numeric', month: 'short', day: 'numeric',
hour: '2-digit', minute: '2-digit', second: '2-digit',
hour12: false
};
for (let i = 0; i < 16; i++) { for (let i = 0; i < 16; i++) {
const cellCob = baseCob + i * COBIE_UNITS.eonstrip; const cellCob = baseCob + i * COBIE_UNITS.eonstrip;
@@ -612,39 +587,26 @@ function updateCalendar() {
${startDate.toLocaleDateString('en-US', dateOpts)} ${startDate.toLocaleDateString('en-US', dateOpts)}
</div>`; </div>`;
if (Array.isArray(window.SPECIAL_EVENTS)) { collectEventOccurrences(
const offsetStart = ((cellCob % COBIE_UNITS.cosmocycle) + COBIE_UNITS.cosmocycle) % COBIE_UNITS.cosmocycle; cellCob,
const offsetEnd = offsetStart + COBIE_UNITS.eonstrip; cellCob + COBIE_UNITS.eonstrip,
window.SPECIAL_EVENTS.forEach(ev => { ev => ev.showMega !== false
const evCob = parseCobiets(ev.cobie); ).forEach(({ event }) => {
if (evCob === null) return;
const evOffset = ((evCob % COBIE_UNITS.cosmocycle) + COBIE_UNITS.cosmocycle) % COBIE_UNITS.cosmocycle;
if (evOffset >= offsetStart && evOffset < offsetEnd) {
const tag = document.createElement('div'); const tag = document.createElement('div');
tag.className = 'event-tag'; tag.className = 'event-tag';
tag.textContent = ev.label; tag.textContent = event.label;
card.appendChild(tag); card.appendChild(tag);
}
}); });
}
const tooltip = document.createElement('div'); const tooltip = document.createElement('div');
tooltip.className = 'tooltip'; tooltip.className = 'tooltip';
tooltip.innerHTML = showEonstripDetails(i, cellCob, dateOpts); tooltip.innerHTML = showEonstripDetails(i, cellCob, dateOpts);
card.appendChild(tooltip); card.appendChild(tooltip);
grid.appendChild(card); grid.appendChild(card);
(function(cob) { (function(cob, idx) {
card.addEventListener('click', () => { card.addEventListener('click', () => {
currentOffset = 0; showEonstripDetail(idx, cob);
manualMode = true;
manualCobiets = cob;
clearInterval(updateInterval);
document.querySelector('.current-time').classList.add('manual');
updateCurrentTime();
if (window.CobieClock) {
window.CobieClock.showTime(manualCobiets);
}
}); });
})(cellCob + currentTime); })(cellCob, i);
} }
updateTimeBreakdown(currentCob); updateTimeBreakdown(currentCob);
} }
@@ -653,17 +615,7 @@ function showEonstripDetails(index, startCobiets, opts) {
const startDate = fromCobiets(startCobiets); const startDate = fromCobiets(startCobiets);
const endDate = fromCobiets(startCobiets + COBIE_UNITS.eonstrip - 1); const endDate = fromCobiets(startCobiets + COBIE_UNITS.eonstrip - 1);
const options = opts || { const options = opts || dateOptions();
timeZone: currentTimezone === 'TAI' ? 'UTC' : currentTimezone,
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
};
return ` return `
<strong>${EONSTRIP_NAMES[index]} (0x${index.toString(16).toUpperCase()})</strong><br> <strong>${EONSTRIP_NAMES[index]} (0x${index.toString(16).toUpperCase()})</strong><br>
@@ -673,6 +625,175 @@ function showEonstripDetails(index, startCobiets, opts) {
`; `;
} }
function showEonstripDetail(index, startCob) {
if (startCob === undefined) {
startCob = index;
const bdTmp = breakdownNonNeg(Math.abs(startCob));
index = bdTmp.eonstrip;
}
currentDetailCob = startCob;
const calendar = document.getElementById('calendarView');
const detail = document.getElementById('eonstripDetailView');
const timeline = document.getElementById('detailTimeline');
const title = document.getElementById('detailTitle');
calendar.style.display = 'none';
detail.style.display = 'block';
timeline.innerHTML = '';
const bd = breakdownNonNeg(Math.abs(startCob));
const sign = startCob < 0 ? '-' : '+';
title.textContent = `${sign}${bd.galactic_year.toString(16)}${bd.cosmocycle.toString(16)}${bd.megasequence.toString(16)}${index.toString(16)} ${EONSTRIP_NAMES[index]}`;
for (let c = 0; c <= 16; c++) {
const block = document.createElement('div');
block.className = 'timeline-block';
block.style.top = (c / 16 * 100) + '%';
if (c < 16) block.textContent = c.toString(16).toUpperCase();
timeline.appendChild(block);
}
const line = document.createElement('div');
line.className = 'current-time-line';
line.id = 'detailCurrentTime';
timeline.appendChild(line);
updateDetailCurrentTime();
if (Array.isArray(window.SPECIAL_EVENTS)) {
const start = startCob;
const end = startCob + COBIE_UNITS.eonstrip;
const events = collectEventOccurrences(start, end, ev => ev.showDetail !== false)
.map(({ event, meta, occ }) => ({
label: event.label,
color: event.color,
start: (occ - start) / COBIE_UNITS.eonstrip,
end: (occ + meta.duration - start) / COBIE_UNITS.eonstrip,
cobStart: occ,
cobEnd: occ + meta.duration,
seriesStart: meta.startCob,
seriesEnd: meta.endCob
}));
events.sort((a,b)=>a.start-b.start);
const groups = [];
let active = [];
events.forEach(ev=>{
active = active.filter(a=>a.end>ev.start);
if (active.length===0) {
groups.push({events:[],columns:[],maxCols:0});
}
const g = groups[groups.length-1];
let col=0;
while(g.columns[col] && g.columns[col] > ev.start) col++;
g.columns[col] = ev.end;
ev.col = col;
g.maxCols = Math.max(g.maxCols, col+1);
g.events.push(ev);
active.push(ev);
});
groups.forEach(g=>{
const width = 100/(g.maxCols||1);
g.events.forEach(ev=>ev.width=width);
});
events.forEach(ev=>{
const left = ev.col * ev.width;
const displayStart = Math.max(0, ev.start);
const displayEnd = Math.min(1, ev.end);
const elem = document.createElement('div');
if (ev.end > ev.start) {
elem.className = 'event-box';
const h = (displayEnd - displayStart) * 100;
if (h < 2) {
elem.classList.add('small-event');
elem.style.height = '4px';
} else {
elem.style.height = (h > 0 ? h : 0) + '%';
}
} else {
elem.className = 'event-line';
}
elem.style.top = (displayStart * 100) + '%';
elem.style.left = `calc(var(--scale-width) + ${left}%)`;
elem.style.width = `calc(${ev.width}% - 2px)`;
if (ev.color) applyEventColors(elem, ev.color, 0.4);
if (elem.classList.contains('small-event') || elem.className === 'event-line') {
const label = document.createElement('span');
label.className = 'event-label';
if (displayStart < 0.05) {
label.classList.add('below');
}
label.textContent = ev.label;
elem.appendChild(label);
if (ev.color) applyEventColors(label, ev.color, 0.5);
} else {
elem.textContent = ev.label;
}
const tooltip = document.createElement('div');
tooltip.className = 'tooltip';
const optsShort = dateOptions(false);
const startStr = formatCobieTimestamp(ev.cobStart);
const endStr = formatCobieTimestamp(ev.cobEnd);
const startDate = fromCobiets(ev.cobStart).toLocaleString('en-US', optsShort);
const endDate = fromCobiets(ev.cobEnd).toLocaleString('en-US', optsShort);
const seriesStart = formatCobieTimestamp(ev.seriesStart);
const seriesEnd = isFinite(ev.seriesEnd) ? formatCobieTimestamp(ev.seriesEnd) : '∞';
tooltip.innerHTML =
`<strong>${ev.label}</strong><br>` +
`Start: ${startStr} (${startDate})<br>` +
(ev.cobEnd > ev.cobStart ? `End: ${endStr} (${endDate})<br>` : '') +
`Series: ${seriesStart} ${seriesEnd}`;
elem.appendChild(tooltip);
timeline.appendChild(elem);
});
}
}
function updateDetailCurrentTime() {
if (currentDetailCob === null) return;
const line = document.getElementById('detailCurrentTime');
if (!line) return;
const nowCob = manualMode ? manualCobiets : toCobiets(new Date());
const rel = (nowCob - currentDetailCob) / COBIE_UNITS.eonstrip;
if (rel >= 0 && rel <= 1) {
line.style.display = 'block';
line.style.top = (rel * 100) + '%';
line.textContent = formatCobieTimestamp(nowCob);
} else {
line.style.display = 'none';
}
}
function detailPrev() {
if (currentDetailCob === null) return;
Animate.animateDetailSwipe(-1, () => {
showEonstripDetail(currentDetailCob - COBIE_UNITS.eonstrip);
});
}
function detailNext() {
if (currentDetailCob === null) return;
Animate.animateDetailSwipe(1, () => {
showEonstripDetail(currentDetailCob + COBIE_UNITS.eonstrip);
});
}
function detailNow() {
const now = toCobiets(new Date());
const start = now - (now % COBIE_UNITS.eonstrip);
showEonstripDetail(start);
}
function exitDetailView() {
document.getElementById('eonstripDetailView').style.display = 'none';
document.getElementById('calendarView').style.display = 'block';
currentDetailCob = null;
}
function getStep(mods) { function getStep(mods) {
// base step = 1 megasequence // base step = 1 megasequence
let step = 1; let step = 1;
@@ -700,41 +821,24 @@ function getStep(mods) {
function navigatePeriod(evt, direction) { function navigatePeriod(evt, direction) {
const step = getStep(evt); const step = getStep(evt);
animateSwipe(direction, () => { if (currentDetailCob !== null) {
exitDetailView();
}
Animate.animateSwipe(direction, () => {
currentOffset += direction * step; currentOffset += direction * step;
updateCalendar(); updateCalendar();
}); });
} }
function animateSwipe(direction, onDone) {
const grid = document.getElementById('eonstripGrid');
if (!grid) { onDone(); return; }
// slide out
grid.style.transition = 'transform 0.3s ease';
grid.style.transform = `translateX(${direction > 0 ? '-100%' : '100%'})`;
function afterOut() {
grid.removeEventListener('transitionend', afterOut);
// prepare new position off-screen on the other side
grid.style.transition = 'none';
grid.style.transform = `translateX(${direction > 0 ? '100%' : '-100%'})`;
onDone();
// force reflow to apply position instantly
void grid.offsetWidth;
// slide in with transition
grid.style.transition = 'transform 0.3s ease';
grid.style.transform = 'translateX(0)';
}
grid.addEventListener('transitionend', afterOut);
}
function goToNow() { function goToNow() {
manualMode = false; manualMode = false;
manualCobiets = 0; manualCobiets = 0;
compareManualMode = false; compareManualMode = false;
currentOffset = 0; currentOffset = 0;
if (currentDetailCob !== null) {
exitDetailView();
}
updateCurrentTime(); updateCurrentTime();
updateCalendar(); updateCalendar();
clearInterval(updateInterval); clearInterval(updateInterval);
@@ -816,6 +920,9 @@ document.getElementById('timezone').addEventListener('change', (e) => {
currentTimezone = e.target.value; currentTimezone = e.target.value;
updateCurrentTime(); updateCurrentTime();
updateCalendar(); updateCalendar();
if (currentDetailCob !== null) {
showEonstripDetail(currentDetailCob);
}
}); });
// Set default timezone based on user's locale // Set default timezone based on user's locale
@@ -828,6 +935,11 @@ if (matchingOption) {
currentTimezone = userTimezone; currentTimezone = userTimezone;
} }
document.getElementById('backToCalendar').addEventListener('click', exitDetailView);
document.getElementById('detailPrev').addEventListener('click', detailPrev);
document.getElementById('detailNext').addEventListener('click', detailNext);
document.getElementById('detailNow').addEventListener('click', detailNow);
updateCurrentTime(); updateCurrentTime();
updateCalendar(); updateCalendar();
updateInterval = setInterval(updateCurrentTime, 1000); updateInterval = setInterval(updateCurrentTime, 1000);
@@ -861,6 +973,7 @@ let swipeStartY = null;
let swipeMods = { altKey: false, shiftKey: false, ctrlKey: false }; let swipeMods = { altKey: false, shiftKey: false, ctrlKey: false };
let isSwiping = false; let isSwiping = false;
let swipeGrid = null; let swipeGrid = null;
let swipeContext = 'calendar';
function swipeStart(e) { function swipeStart(e) {
const touch = e.touches ? e.touches[0] : e; const touch = e.touches ? e.touches[0] : e;
@@ -871,7 +984,17 @@ function swipeStart(e) {
shiftKey: e.shiftKey || false, shiftKey: e.shiftKey || false,
ctrlKey: e.ctrlKey || false ctrlKey: e.ctrlKey || false
}; };
const detailView = document.getElementById('eonstripDetailView');
const detailOpen = detailView && detailView.style.display === 'block';
if (detailOpen) {
swipeGrid = document.getElementById('detailTimeline');
swipeContext = 'detail';
} else {
swipeGrid = document.getElementById('eonstripGrid'); swipeGrid = document.getElementById('eonstripGrid');
swipeContext = 'calendar';
}
if (swipeGrid) { if (swipeGrid) {
swipeGrid.style.transition = 'none'; swipeGrid.style.transition = 'none';
} }
@@ -908,9 +1031,19 @@ function swipeEnd(e) {
// prepare opposite side // prepare opposite side
swipeGrid.style.transition = 'none'; swipeGrid.style.transition = 'none';
swipeGrid.style.transform = `translateX(${dx < 0 ? width : -width}px)`; swipeGrid.style.transform = `translateX(${dx < 0 ? width : -width}px)`;
if (swipeContext === 'calendar') {
const step = getStep(swipeMods); const step = getStep(swipeMods);
currentOffset += direction * step; currentOffset += direction * step;
updateCalendar(); updateCalendar();
} else if (swipeContext === 'detail') {
if (direction === 1) {
currentDetailCob !== null && showEonstripDetail(currentDetailCob + COBIE_UNITS.eonstrip);
} else {
currentDetailCob !== null && showEonstripDetail(currentDetailCob - COBIE_UNITS.eonstrip);
}
}
void swipeGrid.offsetWidth; void swipeGrid.offsetWidth;
swipeGrid.style.transition = 'transform 0.3s ease'; swipeGrid.style.transition = 'transform 0.3s ease';
swipeGrid.style.transform = 'translateX(0)'; swipeGrid.style.transform = 'translateX(0)';
@@ -935,9 +1068,17 @@ document.addEventListener('mouseup', swipeEnd);
function wheelNavigate(e) { function wheelNavigate(e) {
if (Math.abs(e.deltaX) > Math.abs(e.deltaY) && Math.abs(e.deltaX) > 10) { if (Math.abs(e.deltaX) > Math.abs(e.deltaY) && Math.abs(e.deltaX) > 10) {
const direction = e.deltaX > 0 ? 1 : -1; const direction = e.deltaX > 0 ? 1 : -1;
if (currentDetailCob !== null) {
if (direction === 1) {
detailNext();
} else {
detailPrev();
}
} else {
navigatePeriod(e, direction); navigatePeriod(e, direction);
} }
} }
}
document.addEventListener('wheel', wheelNavigate); document.addEventListener('wheel', wheelNavigate);
@@ -951,4 +1092,7 @@ if (document.readyState === 'loading') {
window.navigatePeriod = navigatePeriod; window.navigatePeriod = navigatePeriod;
window.goToNow = goToNow; window.goToNow = goToNow;
window.detailPrev = detailPrev;
window.detailNext = detailNext;
window.detailNow = detailNow;
})(); })();
+141 -3
View File
@@ -368,6 +368,142 @@
will-change: transform; will-change: transform;
} }
.detail-view {
margin-top: 20px;
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
padding: 20px;
}
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.detail-nav {
display: flex;
gap: 10px;
align-items: center;
}
.back-btn {
background: linear-gradient(45deg, #00ffff, #0080ff);
border: none;
border-radius: 5px;
color: #fff;
padding: 6px 12px;
cursor: pointer;
font-size: 0.9em;
}
.detail-timeline {
position: relative;
--scale-width: 24px;
height: 400px;
border-left: 3px solid #00ffff;
margin-right: 40px;
margin-left: 0;
overflow: visible;
}
.timeline-block {
position: absolute;
left: 0;
width: calc(100% + 40px);
text-align: left;
padding-left: 4px;
border-top: 1px dashed rgba(0,255,255,0.5);
color: #00ffff;
font-size: 0.9em;
font-weight: 600;
padding-top: 2px;
text-shadow: 0 0 4px #00ffff;
pointer-events: none;
z-index: 5;
}
.current-time-line {
position: absolute;
left: var(--scale-width);
width: calc(100% + 40px - var(--scale-width));
border-top: 2px solid #ff00ff;
color: #ff00ff;
font-size: 0.9em;
font-weight: 600;
padding-top: 2px;
text-shadow: 0 0 4px #ff00ff;
pointer-events: none;
z-index: 2;
}
.event-box, .event-line {
position: absolute;
left: var(--scale-width);
background: var(--bg-color, rgba(255,0,255,0.4));
border: 1px solid var(--border-color, rgba(0,255,255,0.7));
border-radius: 4px;
padding: 2px 4px;
color: var(--text-color, #fff);
font-size: 0.75em;
overflow: visible;
white-space: nowrap;
z-index: 3;
box-shadow: 0 0 4px var(--border-color, rgba(0,255,255,0.9));
}
.event-box {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.event-line {
height: 4px;
padding: 0;
overflow: visible;
}
.event-box.small-event {
overflow: visible;
padding: 0;
}
.event-line .event-label,
.event-box.small-event .event-label {
position: absolute;
bottom: 100%;
left: 50%;
transform: translate(-50%, -2px);
margin-bottom: 2px;
background: var(--bg-color, rgba(255,0,255,0.5));
border: 1px solid var(--border-color, rgba(0,255,255,0.8));
border-radius: 4px;
padding: 2px 6px;
white-space: nowrap;
color: var(--text-color, #fff);
font-size: 0.75em;
font-weight: 600;
text-shadow: 0 0 4px var(--border-color, #ff00ff);
z-index: 4;
box-shadow: 0 0 4px var(--border-color, rgba(0,255,255,0.9));
}
.event-line .event-label.below,
.event-box.small-event .event-label.below {
bottom: auto;
top: 100%;
transform: translate(-50%, 2px);
margin-bottom: 0;
margin-top: 2px;
}
.event-box:hover .tooltip,
.event-line:hover .tooltip {
opacity: 1;
}
/* Layout combining current time and analog clock */ /* Layout combining current time and analog clock */
.time-display { .time-display {
--clock-size: 40vmin; --clock-size: 40vmin;
@@ -445,7 +581,8 @@ body.fullscreen-clock .clock-label {
line-height: calc(var(--clock-size) * 0.13); line-height: calc(var(--clock-size) * 0.13);
/* Use a futuristic font for the clock markers */ /* Use a futuristic font for the clock markers */
font-family: 'Orbitron', 'Trebuchet MS', 'Lucida Sans', Arial, sans-serif; font-family: 'Orbitron', 'Trebuchet MS', 'Lucida Sans', Arial, sans-serif;
font-size: calc(var(--clock-size) * 0.08); /* 1% of the clock radius */
font-size: calc(var(--clock-size) * 0.05);
font-weight: 600; font-weight: 600;
color: #00ffff; color: #00ffff;
background: none; background: none;
@@ -483,7 +620,7 @@ body.fullscreen-clock .clock-label {
left: 50%; left: 50%;
transform-origin: bottom center; transform-origin: bottom center;
transform: translateX(-50%); transform: translateX(-50%);
transition: transform 0.5s ease-in-out; transition: transform 1s linear;
border-radius: 2px; border-radius: 2px;
z-index: 1; z-index: 1;
} }
@@ -536,9 +673,10 @@ body.fullscreen-clock .current-time,
body.fullscreen-clock .timezone-selector, body.fullscreen-clock .timezone-selector,
body.fullscreen-clock .calendar-controls, body.fullscreen-clock .calendar-controls,
body.fullscreen-clock .calendar-view, body.fullscreen-clock .calendar-view,
body.fullscreen-clock .detail-view,
body.fullscreen-clock .time-details, body.fullscreen-clock .time-details,
body.fullscreen-clock .explanations { body.fullscreen-clock .explanations {
display: none; display: none !important;
} }
body.fullscreen-clock .time-display { body.fullscreen-clock .time-display {
+92
View File
@@ -0,0 +1,92 @@
(function(){
function parseColor(hex) {
if (!hex) return [255, 255, 255];
let c = hex.replace('#', '');
if (c.length === 3) c = c.split('').map(x => x + x).join('');
const num = parseInt(c, 16);
return [(num >> 16) & 255, (num >> 8) & 255, num & 255];
}
const toHex = v => v.toString(16).padStart(2, '0');
function hexToRgba(hex, a = 1) {
const [r, g, b] = parseColor(hex);
return `rgba(${r},${g},${b},${a})`;
}
function getContrastColor(hex) {
const [r, g, b] = parseColor(hex);
const yiq = (r * 299 + g * 587 + b * 114) / 1000;
return yiq >= 128 ? '#000' : '#fff';
}
function lightenColor(hex, p) {
const [r, g, b] = parseColor(hex).map(v =>
Math.min(255, Math.round(v + (255 - v) * p))
);
return '#' + [r, g, b].map(toHex).join('');
}
function getHumanDiff(d1, d2) {
let start = d1 < d2 ? d1 : d2;
let end = d1 < d2 ? d2 : d1;
let years = end.getUTCFullYear() - start.getUTCFullYear();
let months = end.getUTCMonth() - start.getUTCMonth();
let days = end.getUTCDate() - start.getUTCDate();
if (days < 0) {
months--;
let prevMonthDays = new Date(Date.UTC(
end.getUTCFullYear(),
end.getUTCMonth(), 0
)).getUTCDate();
days += prevMonthDays;
}
if (months < 0) {
years--;
months += 12;
}
let aligned = new Date(Date.UTC(
start.getUTCFullYear() + years,
start.getUTCMonth() + months,
start.getUTCDate() + days,
start.getUTCHours(),
start.getUTCMinutes(),
start.getUTCSeconds()
));
let diffMs = end.getTime() - aligned.getTime();
if (diffMs < 0) {
diffMs += 24 * 3600e3;
if (days > 0) {
days--;
} else {
months--;
if (months < 0) {
years--;
months += 12;
}
days = new Date(Date.UTC(
end.getUTCFullYear(),
end.getUTCMonth(), 0
)).getUTCDate();
}
}
let hours = Math.floor(diffMs / 3600e3); diffMs -= hours * 3600e3;
let minutes = Math.floor(diffMs / 60e3); diffMs -= minutes * 60e3;
let seconds = Math.floor(diffMs / 1e3);
return { years, months, days, hours, minutes, seconds };
}
window.Utils = {
parseColor,
hexToRgba,
getContrastColor,
lightenColor,
getHumanDiff
};
})();