Compare commits

...

8 Commits

Author SHA1 Message Date
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
3 changed files with 156 additions and 34 deletions
+10 -9
View File
@@ -12,15 +12,16 @@
// 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 = [
{ cobie: '49f4.9332', label: 'Afina', unit: 'cosmocycle', interval: 1 },
{ cobie: '11e5.f552', label: 'Oleks', unit: 'cosmocycle', interval: 1 },
{ cobie: '4d07.a2b2', label: 'Vincent', unit: 'cosmocycle', interval: 1 },
{ cobie: '3edc.d430', label: 'Hochzeitstag', unit: 'cosmocycle', interval: 1 },
{ cobie: '330d.d4ae', label: 'Zusammentag', unit: 'cosmocycle', interval: 1 },
{ cobie: '11de.0c52', label: 'Anna', unit: 'cosmocycle', interval: 1 },
{ cobie: '467f.ae61', label: 'Iris', unit: 'cosmocycle', interval: 1 },
{ cobie: '6854.7a75', label: 'Sleep', unit: 'second', interval: 86400, duration: 28800, showMega: false },
{ cobie: '6854.9695', label: 'Night', unit: 'second', interval: 86400, duration: 28800, showMega: false, shiftWithTimezone: true }
{ cobie: '49f4.9332', label: 'Afina', color: '#e57373', unit: 'cosmocycle', interval: 1 },
{ cobie: '11e5.f552', label: 'Oleks', color: '#64b5f6', unit: 'cosmocycle', interval: 1 },
{ cobie: '4d07.a2b2', label: 'Vincent', color: '#81c784', unit: 'cosmocycle', interval: 1 },
{ cobie: '3edc.d430', label: 'Hochzeitstag', color: '#ffb74d', unit: 'cosmocycle', interval: 1 },
{ cobie: '330d.d4ae', label: 'Zusammentag', color: '#ba68c8', unit: 'cosmocycle', interval: 1 },
{ cobie: '11de.0c52', label: 'Anna', color: '#4db6ac', unit: 'cosmocycle', interval: 1 },
{ 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 }
];
+125 -6
View File
@@ -44,6 +44,50 @@ let updateInterval;
let lastRenderedEonstrip = 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) {
if (currentTimezone === 'UTC') return 0;
if (currentTimezone === 'TAI') return getTAIOffsetAt(date);
@@ -761,6 +805,7 @@ function showEonstripDetail(index, startCob) {
const relEnd = (occ + duration - start) / COBIE_UNITS.eonstrip;
events.push({
label: ev.label,
color: ev.color,
start: relStart,
end: relEnd,
cobStart: occ,
@@ -772,16 +817,30 @@ function showEonstripDetail(index, startCob) {
}
});
events.sort((a,b)=>a.start-b.start);
const columns = [];
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(columns[col] && columns[col] > ev.start) col++;
columns[col] = ev.end;
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);
});
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=>{
const left = ev.col * width;
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');
@@ -799,7 +858,9 @@ function showEonstripDetail(index, startCob) {
}
elem.style.top = (displayStart * 100) + '%';
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') {
const label = document.createElement('span');
@@ -809,6 +870,7 @@ function showEonstripDetail(index, startCob) {
}
label.textContent = ev.label;
elem.appendChild(label);
if (ev.color) applyEventColors(label, ev.color, 0.5);
} else {
elem.textContent = ev.label;
}
@@ -857,12 +919,16 @@ function updateDetailCurrentTime() {
function detailPrev() {
if (currentDetailCob === null) return;
animateDetailSwipe(-1, () => {
showEonstripDetail(currentDetailCob - COBIE_UNITS.eonstrip);
});
}
function detailNext() {
if (currentDetailCob === null) return;
animateDetailSwipe(1, () => {
showEonstripDetail(currentDetailCob + COBIE_UNITS.eonstrip);
});
}
function detailNow() {
@@ -943,6 +1009,30 @@ function animateSwipe(direction, onDone) {
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() {
manualMode = false;
manualCobiets = 0;
@@ -1085,6 +1175,7 @@ let swipeStartY = null;
let swipeMods = { altKey: false, shiftKey: false, ctrlKey: false };
let isSwiping = false;
let swipeGrid = null;
let swipeContext = 'calendar';
function swipeStart(e) {
const touch = e.touches ? e.touches[0] : e;
@@ -1095,7 +1186,17 @@ function swipeStart(e) {
shiftKey: e.shiftKey || 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');
swipeContext = 'calendar';
}
if (swipeGrid) {
swipeGrid.style.transition = 'none';
}
@@ -1132,9 +1233,19 @@ function swipeEnd(e) {
// prepare opposite side
swipeGrid.style.transition = 'none';
swipeGrid.style.transform = `translateX(${dx < 0 ? width : -width}px)`;
if (swipeContext === 'calendar') {
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;
swipeGrid.style.transition = 'transform 0.3s ease';
swipeGrid.style.transform = 'translateX(0)';
@@ -1159,8 +1270,16 @@ document.addEventListener('mouseup', swipeEnd);
function wheelNavigate(e) {
if (Math.abs(e.deltaX) > Math.abs(e.deltaY) && Math.abs(e.deltaX) > 10) {
const direction = e.deltaX > 0 ? 1 : -1;
if (currentDetailCob !== null) {
if (direction === 1) {
detailNext();
} else {
detailPrev();
}
} else {
navigatePeriod(e, direction);
}
}
}
document.addEventListener('wheel', wheelNavigate);
+9 -7
View File
@@ -441,15 +441,16 @@
.event-box, .event-line {
position: absolute;
left: var(--scale-width);
background: rgba(255,0,255,0.4);
border: 1px solid rgba(0,255,255,0.7);
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: #fff;
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;
@@ -476,16 +477,17 @@
left: 50%;
transform: translate(-50%, -2px);
margin-bottom: 2px;
background: rgba(255,0,255,0.5);
border: 1px solid rgba(0,255,255,0.8);
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: #fff;
color: var(--text-color, #fff);
font-size: 0.75em;
font-weight: 600;
text-shadow: 0 0 4px #ff00ff;
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,