Compare commits
8 Commits
3f7a343b17
...
da56bfce7a
| Author | SHA1 | Date | |
|---|---|---|---|
| da56bfce7a | |||
| f38f4af4ca | |||
| ff5d2a7816 | |||
| 3961bc3345 | |||
| 3974237f42 | |||
| 3822b0cdf7 | |||
| 8451fa9f54 | |||
| 26cfa8b868 |
@@ -12,15 +12,16 @@
|
|||||||
// duration - optional length of the event in seconds.
|
// duration - optional length of the event in seconds.
|
||||||
// showMega - optional boolean, show label on the megasequence view (default true).
|
// showMega - optional boolean, show label on the megasequence view (default true).
|
||||||
// showDetail - optional boolean, show event in the detail 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', unit: 'cosmocycle', interval: 1 },
|
{ cobie: '49f4.9332', label: 'Afina', color: '#e57373', unit: 'cosmocycle', interval: 1 },
|
||||||
{ cobie: '11e5.f552', label: 'Oleks', unit: 'cosmocycle', interval: 1 },
|
{ cobie: '11e5.f552', label: 'Oleks', color: '#64b5f6', unit: 'cosmocycle', interval: 1 },
|
||||||
{ cobie: '4d07.a2b2', label: 'Vincent', unit: 'cosmocycle', interval: 1 },
|
{ cobie: '4d07.a2b2', label: 'Vincent', color: '#81c784', unit: 'cosmocycle', interval: 1 },
|
||||||
{ cobie: '3edc.d430', label: 'Hochzeitstag', unit: 'cosmocycle', interval: 1 },
|
{ cobie: '3edc.d430', label: 'Hochzeitstag', color: '#ffb74d', unit: 'cosmocycle', interval: 1 },
|
||||||
{ cobie: '330d.d4ae', label: 'Zusammentag', unit: 'cosmocycle', interval: 1 },
|
{ cobie: '330d.d4ae', label: 'Zusammentag', color: '#ba68c8', unit: 'cosmocycle', interval: 1 },
|
||||||
{ cobie: '11de.0c52', label: 'Anna', unit: 'cosmocycle', interval: 1 },
|
{ cobie: '11de.0c52', label: 'Anna', color: '#4db6ac', unit: 'cosmocycle', interval: 1 },
|
||||||
{ cobie: '467f.ae61', label: 'Iris', unit: 'cosmocycle', interval: 1 },
|
{ cobie: '467f.ae61', label: 'Iris', color: '#7986cb', unit: 'cosmocycle', interval: 1 },
|
||||||
{ cobie: '6854.7a75', label: 'Sleep', unit: 'second', interval: 86400, duration: 28800, showMega: false },
|
{ cobie: '6854.7a75', label: 'Sleep', color: '#546e7a', unit: 'second', interval: 86400, duration: 28800, showMega: false },
|
||||||
{ cobie: '6854.9695', label: 'Night', unit: 'second', interval: 86400, duration: 28800, showMega: false, shiftWithTimezone: true }
|
{ cobie: '6854.9695', label: 'Night', color: '#212121', unit: 'second', interval: 86400, duration: 28800, showMega: false, shiftWithTimezone: true }
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -44,6 +44,50 @@ let updateInterval;
|
|||||||
let lastRenderedEonstrip = null;
|
let lastRenderedEonstrip = null;
|
||||||
let currentDetailCob = null;
|
let currentDetailCob = null;
|
||||||
|
|
||||||
|
function hexToRgba(hex, alpha) {
|
||||||
|
if (!hex) return '';
|
||||||
|
let c = hex.replace('#', '');
|
||||||
|
if (c.length === 3) {
|
||||||
|
c = c.split('').map(x => x + x).join('');
|
||||||
|
}
|
||||||
|
const r = parseInt(c.substring(0,2),16);
|
||||||
|
const g = parseInt(c.substring(2,4),16);
|
||||||
|
const b = parseInt(c.substring(4,6),16);
|
||||||
|
return `rgba(${r},${g},${b},${alpha})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getContrastColor(hex) {
|
||||||
|
if (!hex) return '#fff';
|
||||||
|
let c = hex.replace('#','');
|
||||||
|
if (c.length === 3) c = c.split('').map(x=>x+x).join('');
|
||||||
|
const r = parseInt(c.substr(0,2),16);
|
||||||
|
const g = parseInt(c.substr(2,2),16);
|
||||||
|
const b = parseInt(c.substr(4,2),16);
|
||||||
|
const yiq = (r*299 + g*587 + b*114) / 1000;
|
||||||
|
return yiq >= 128 ? '#000' : '#fff';
|
||||||
|
}
|
||||||
|
|
||||||
|
function lightenColor(hex, percent) {
|
||||||
|
if (!hex) return '#fff';
|
||||||
|
let c = hex.replace('#','');
|
||||||
|
if (c.length === 3) c = c.split('').map(x=>x+x).join('');
|
||||||
|
let r = parseInt(c.substr(0,2),16);
|
||||||
|
let g = parseInt(c.substr(2,2),16);
|
||||||
|
let b = parseInt(c.substr(4,2),16);
|
||||||
|
r = Math.min(255, Math.round(r + (255 - r) * percent));
|
||||||
|
g = Math.min(255, Math.round(g + (255 - g) * percent));
|
||||||
|
b = Math.min(255, Math.round(b + (255 - b) * percent));
|
||||||
|
return '#' + [r,g,b].map(x=>x.toString(16).padStart(2,'0')).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
function getTimezoneOffsetSeconds(date) {
|
||||||
if (currentTimezone === 'UTC') return 0;
|
if (currentTimezone === 'UTC') return 0;
|
||||||
if (currentTimezone === 'TAI') return getTAIOffsetAt(date);
|
if (currentTimezone === 'TAI') return getTAIOffsetAt(date);
|
||||||
@@ -761,6 +805,7 @@ function showEonstripDetail(index, startCob) {
|
|||||||
const relEnd = (occ + duration - start) / COBIE_UNITS.eonstrip;
|
const relEnd = (occ + duration - start) / COBIE_UNITS.eonstrip;
|
||||||
events.push({
|
events.push({
|
||||||
label: ev.label,
|
label: ev.label,
|
||||||
|
color: ev.color,
|
||||||
start: relStart,
|
start: relStart,
|
||||||
end: relEnd,
|
end: relEnd,
|
||||||
cobStart: occ,
|
cobStart: occ,
|
||||||
@@ -772,16 +817,30 @@ function showEonstripDetail(index, startCob) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
events.sort((a,b)=>a.start-b.start);
|
events.sort((a,b)=>a.start-b.start);
|
||||||
const columns = [];
|
|
||||||
|
const groups = [];
|
||||||
|
let active = [];
|
||||||
events.forEach(ev=>{
|
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;
|
let col=0;
|
||||||
while(columns[col] && columns[col] > ev.start) col++;
|
while(g.columns[col] && g.columns[col] > ev.start) col++;
|
||||||
columns[col] = ev.end;
|
g.columns[col] = ev.end;
|
||||||
ev.col = col;
|
ev.col = col;
|
||||||
|
g.maxCols = Math.max(g.maxCols, col+1);
|
||||||
|
g.events.push(ev);
|
||||||
|
active.push(ev);
|
||||||
});
|
});
|
||||||
const width = 100 / (columns.length || 1);
|
groups.forEach(g=>{
|
||||||
|
const width = 100/(g.maxCols||1);
|
||||||
|
g.events.forEach(ev=>ev.width=width);
|
||||||
|
});
|
||||||
|
|
||||||
events.forEach(ev=>{
|
events.forEach(ev=>{
|
||||||
const left = ev.col * width;
|
const left = ev.col * ev.width;
|
||||||
const displayStart = Math.max(0, ev.start);
|
const displayStart = Math.max(0, ev.start);
|
||||||
const displayEnd = Math.min(1, ev.end);
|
const displayEnd = Math.min(1, ev.end);
|
||||||
const elem = document.createElement('div');
|
const elem = document.createElement('div');
|
||||||
@@ -799,7 +858,9 @@ function showEonstripDetail(index, startCob) {
|
|||||||
}
|
}
|
||||||
elem.style.top = (displayStart * 100) + '%';
|
elem.style.top = (displayStart * 100) + '%';
|
||||||
elem.style.left = `calc(var(--scale-width) + ${left}%)`;
|
elem.style.left = `calc(var(--scale-width) + ${left}%)`;
|
||||||
elem.style.width = `calc(${width}% - 2px)`;
|
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') {
|
if (elem.classList.contains('small-event') || elem.className === 'event-line') {
|
||||||
const label = document.createElement('span');
|
const label = document.createElement('span');
|
||||||
@@ -809,6 +870,7 @@ function showEonstripDetail(index, startCob) {
|
|||||||
}
|
}
|
||||||
label.textContent = ev.label;
|
label.textContent = ev.label;
|
||||||
elem.appendChild(label);
|
elem.appendChild(label);
|
||||||
|
if (ev.color) applyEventColors(label, ev.color, 0.5);
|
||||||
} else {
|
} else {
|
||||||
elem.textContent = ev.label;
|
elem.textContent = ev.label;
|
||||||
}
|
}
|
||||||
@@ -857,12 +919,16 @@ function updateDetailCurrentTime() {
|
|||||||
|
|
||||||
function detailPrev() {
|
function detailPrev() {
|
||||||
if (currentDetailCob === null) return;
|
if (currentDetailCob === null) return;
|
||||||
showEonstripDetail(currentDetailCob - COBIE_UNITS.eonstrip);
|
animateDetailSwipe(-1, () => {
|
||||||
|
showEonstripDetail(currentDetailCob - COBIE_UNITS.eonstrip);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function detailNext() {
|
function detailNext() {
|
||||||
if (currentDetailCob === null) return;
|
if (currentDetailCob === null) return;
|
||||||
showEonstripDetail(currentDetailCob + COBIE_UNITS.eonstrip);
|
animateDetailSwipe(1, () => {
|
||||||
|
showEonstripDetail(currentDetailCob + COBIE_UNITS.eonstrip);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function detailNow() {
|
function detailNow() {
|
||||||
@@ -943,6 +1009,30 @@ function animateSwipe(direction, onDone) {
|
|||||||
grid.addEventListener('transitionend', afterOut);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
function goToNow() {
|
function goToNow() {
|
||||||
manualMode = false;
|
manualMode = false;
|
||||||
manualCobiets = 0;
|
manualCobiets = 0;
|
||||||
@@ -1080,11 +1170,12 @@ document.getElementById('toggleExtended').addEventListener('click', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ── Swipe & Wheel Navigation ────────────────────────────────────────────────
|
// ── Swipe & Wheel Navigation ────────────────────────────────────────────────
|
||||||
let swipeStartX = null;
|
let swipeStartX = null;
|
||||||
let swipeStartY = null;
|
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;
|
||||||
@@ -1095,7 +1186,17 @@ function swipeStart(e) {
|
|||||||
shiftKey: e.shiftKey || false,
|
shiftKey: e.shiftKey || false,
|
||||||
ctrlKey: e.ctrlKey || false
|
ctrlKey: e.ctrlKey || false
|
||||||
};
|
};
|
||||||
swipeGrid = document.getElementById('eonstripGrid');
|
|
||||||
|
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');
|
||||||
|
swipeContext = 'calendar';
|
||||||
|
}
|
||||||
|
|
||||||
if (swipeGrid) {
|
if (swipeGrid) {
|
||||||
swipeGrid.style.transition = 'none';
|
swipeGrid.style.transition = 'none';
|
||||||
}
|
}
|
||||||
@@ -1132,9 +1233,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)`;
|
||||||
const step = getStep(swipeMods);
|
|
||||||
currentOffset += direction * step;
|
if (swipeContext === 'calendar') {
|
||||||
updateCalendar();
|
const step = getStep(swipeMods);
|
||||||
|
currentOffset += direction * step;
|
||||||
|
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)';
|
||||||
@@ -1159,7 +1270,15 @@ 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;
|
||||||
navigatePeriod(e, direction);
|
if (currentDetailCob !== null) {
|
||||||
|
if (direction === 1) {
|
||||||
|
detailNext();
|
||||||
|
} else {
|
||||||
|
detailPrev();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navigatePeriod(e, direction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -441,15 +441,16 @@
|
|||||||
.event-box, .event-line {
|
.event-box, .event-line {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: var(--scale-width);
|
left: var(--scale-width);
|
||||||
background: rgba(255,0,255,0.4);
|
background: var(--bg-color, rgba(255,0,255,0.4));
|
||||||
border: 1px solid rgba(0,255,255,0.7);
|
border: 1px solid var(--border-color, rgba(0,255,255,0.7));
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
color: #fff;
|
color: var(--text-color, #fff);
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
|
box-shadow: 0 0 4px var(--border-color, rgba(0,255,255,0.9));
|
||||||
}
|
}
|
||||||
.event-box {
|
.event-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -476,16 +477,17 @@
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -2px);
|
transform: translate(-50%, -2px);
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
background: rgba(255,0,255,0.5);
|
background: var(--bg-color, rgba(255,0,255,0.5));
|
||||||
border: 1px solid rgba(0,255,255,0.8);
|
border: 1px solid var(--border-color, rgba(0,255,255,0.8));
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
color: #fff;
|
color: var(--text-color, #fff);
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-shadow: 0 0 4px #ff00ff;
|
text-shadow: 0 0 4px var(--border-color, #ff00ff);
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
|
box-shadow: 0 0 4px var(--border-color, rgba(0,255,255,0.9));
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-line .event-label.below,
|
.event-line .event-label.below,
|
||||||
|
|||||||
Reference in New Issue
Block a user