Files
glance/widgets/countdown.yml
Dokril abd62d8f73 feat: конфигурация Glance с виджетами и скриптами развертывания
- Главная конфигурация home.yml и виджеты (countdown, qbittorrent)
- PowerShell и Bash скрипты для автоматической загрузки на сервер
- Обновлен README с документацией
2025-12-06 09:45:17 +03:00

349 lines
20 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
- type: custom-api
title: "Важные даты"
url: http://192.168.50.114:5555/countdown
cache: 5s
template: |
{{/* ===== НАСТРОЙКИ ВРЕМЕННОЙ ШКАЛЫ ===== */}}
{{/* Переменная шкала: 2 недели (14 дней) + 40 недель = ~1 год */}}
{{/* Первая зона: 2 недели = 14 делений (по дням) = 25% шкалы */}}
{{/* Вторая зона: 40 недель = 40 делений (по неделям) = 75% шкалы */}}
{{ $daysInFirstZone := 14 }}
{{ $weeksInSecondZone := 40 }}
{{ $firstZonePercent := 25.0 }}
{{ $secondZonePercent := 75.0 }}
<div style="position: relative; padding: 20px 10px;">
<!-- ===== КОНТЕЙНЕР TIMELINE3 ===== -->
<div id="glance-timeline" style="position: relative; height: 70px; margin: 10px 0;">
{{/* ===== ОТРИСОВКА ЛИНИИ ВРЕМЕНИ ===== */}}
{{/* Зона 1 (0-25%): 14 делений по дням - с мягким красным градиентом */}}
{{/* Зона 2 (25-100%): 40 делений по неделям */}}
<div style="position: absolute; left: 0; width: 25%; top: 50%; transform: translateY(-50%); height: 3px; background: linear-gradient(to right, rgba(185, 28, 28, 0.4), rgba(128, 128, 128, 0.4)); background-image: linear-gradient(to right, rgba(185, 28, 28, 0.4), rgba(128, 128, 128, 0.4)), linear-gradient(to right, rgba(180, 180, 180, 0.6) 1px, transparent 1px); background-size: 100% 100%, calc(100% / 14) 100%; background-repeat: no-repeat, repeat-x;">
</div>
<div style="position: absolute; left: 25%; width: 75%; top: 50%; transform: translateY(-50%); height: 3px; background-color: transparent; background-image: linear-gradient(to right, rgba(128,128,128,0.45) 1px, transparent 1px), linear-gradient(to right, rgba(128,128,128,0.75) 1px, transparent 1px); background-size: calc(100% / 40) 100%, calc(100% / 10) 100%; background-repeat: repeat-x, repeat-x;">
</div>
{{/* ===== МАРКЕР "СЕГОДНЯ" ===== */}}
{{/* Огонёк показывает текущий момент */}}
<div style="position: absolute; left: 0; top: 50%; transform: translate(-50%, -50%); font-size: 24px; text-shadow: 0 0 8px rgba(255, 100, 0, 0.6); filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3)); z-index: 15;">
🔥
</div>
{{/* ===== ОТОБРАЖЕНИЕ СОБЫТИЙ ===== */}}
{{/* Позиции рассчитываются с учетом двух зон шкалы */}}
{{ $daysZ1 := toFloat $daysInFirstZone }}
{{ $weeksZ2 := toFloat $weeksInSecondZone }}
{{ $z1Pct := $firstZonePercent }}
{{ $z2Pct := $secondZonePercent }}
{{ $maxDays := add $daysZ1 (mul $weeksZ2 7.0) }}
{{ range .JSON.Array "events" }}
{{/* Получаем количество дней до события из API /countdown */}}
{{ $days := .Int "days" }}
{{ $daysFloat := toFloat $days }}
{{/* ФОРМУЛА: Рассчитываем позицию в зависимости от зоны */}}
{{ $pos := 0.0 }}
{{/* Показываем только события в пределах диапазона (14 дней + 40 недель = 294 дня) */}}
{{ if lt $daysFloat $maxDays }}
{{ if lt $daysFloat $daysZ1 }}
{{/* Зона 1: 0-14 дней -> 0-25% шкалы */}}
{{ $pos = mul (div $daysFloat $daysZ1) $z1Pct }}
{{ else }}
{{/* Зона 2: 14+ дней -> 25-100% шкалы */}}
{{ $daysAfterZ1 := sub $daysFloat $daysZ1 }}
{{ $weeksInZ2 := div $daysAfterZ1 7.0 }}
{{ $posInZ2 := mul (div $weeksInZ2 $weeksZ2) $z2Pct }}
{{ $pos = add $z1Pct $posInZ2 }}
{{ end }}
{{/* ===== КОНТЕЙНЕР СОБЫТИЯ ===== */}}
{{/* Абсолютное позиционирование: left = рассчитанный процент */}}
{{/* transform: translate(-50%, -50%) центрирует emoji точно на линии */}}
<div class="event-item" style="position: absolute; left: {{ $pos }}%; top: 35%; transform: translate(-50%, -50%); cursor: pointer; z-index: 10; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);">
{{/* ===== EMOJI СОБЫТИЯ ===== */}}
{{/* При наведении увеличивается и раздвигает соседей */}}
<div class="event-emoji" style="font-size: 28px; text-shadow: 0 0 3px rgba(0,0,0,0.3), 0 2px 4px rgba(0,0,0,0.2); transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);">{{ .String "emoji" }}</div>
{{/* ===== РАСШИРЕННЫЙ TOOLTIP ===== */}}
{{/* Большой информативный tooltip с детальной информацией */}}
<div class="event-tooltip" style="position: absolute; bottom: 45px; left: 50%; transform: translateX(-50%); background: linear-gradient(135deg, rgba(20,20,25,0.98) 0%, rgba(30,30,40,0.98) 100%); color: white; padding: 12px 16px; border-radius: 10px; font-size: 12px; white-space: nowrap; opacity: 0; pointer-events: none; transition: all 0.2s ease; box-shadow: 0 8px 24px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.1); backdrop-filter: blur(10px); min-width: 200px;">
{{/* Emoji и название */}}
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid rgba(255,255,255,0.15);">
<div style="font-size: 24px;">{{ .String "emoji" }}</div>
<div style="font-weight: bold; font-size: 14px; color: #fff;">{{ .String "name" }}</div>
</div>
{{/* Точная дата события */}}
<div style="margin-bottom: 8px; display: flex; align-items: center; gap: 6px;">
<div style="opacity: 0.7; font-size: 11px;">📅 Дата:</div>
<div style="font-weight: 600; color: #8ab4f8;">{{ .String "date" }}</div>
</div>
{{/* Информация о годовщине (если это anniversary) */}}
{{ if eq (.String "type") "anniversary" }}
<div style="background: rgba(147, 51, 234, 0.15); padding: 8px; border-radius: 6px; margin-bottom: 8px; border-left: 3px solid #9333ea;">
<div style="opacity: 0.9; font-size: 11px; margin-bottom: 3px;">🎉 Годовщина</div>
{{ $years := .Int "years_passed" }}
{{ $lastDigit := mod $years 10 }}
{{ $lastTwoDigits := mod $years 100 }}
<div style="font-weight: 700; font-size: 15px; color: #c084fc;">{{ $years }} {{ if and (eq $lastDigit 1) (ne $lastTwoDigits 11) }}год{{ else if and (or (eq $lastDigit 2) (eq $lastDigit 3) (eq $lastDigit 4)) (not (or (eq $lastTwoDigits 12) (eq $lastTwoDigits 13) (eq $lastTwoDigits 14))) }}года{{ else }}лет{{ end }}</div>
</div>
{{ end }}
{{/* Детальный countdown */}}
<div style="background: rgba(255,255,255,0.08); padding: 8px; border-radius: 6px;">
<div style="opacity: 0.7; font-size: 10px; margin-bottom: 4px; text-transform: uppercase; letter-spacing: 0.5px;">⏰ Осталось</div>
{{ $days := .Int "days" }}
{{ if ge $days 30 }}
{{/* Если >= 30 дней: показываем месяцы и дни */}}
{{ $months := div $days 30 }}
{{ $remainingDays := mod $days 30 }}
<div style="display: flex; gap: 12px; justify-content: space-around;">
<div style="text-align: center;">
<div style="font-size: 16px; font-weight: bold; color: #ff6b9d;">{{ $months }}</div>
<div style="font-size: 9px; opacity: 0.6; margin-top: 2px;">{{ if eq $months 1 }}месяц{{ else if or (eq $months 2) (eq $months 3) (eq $months 4) }}месяца{{ else }}месяцев{{ end }}</div>
</div>
<div style="text-align: center;">
<div style="font-size: 16px; font-weight: bold; color: #ffa94d;">{{ $remainingDays }}</div>
<div style="font-size: 9px; opacity: 0.6; margin-top: 2px;">{{ if eq $remainingDays 1 }}день{{ else if or (eq $remainingDays 2) (eq $remainingDays 3) (eq $remainingDays 4) }}дня{{ else }}дней{{ end }}</div>
</div>
</div>
{{ else }}
{{/* Если < 30 дней: показываем дни и часы */}}
<div style="display: flex; gap: 12px; justify-content: space-around;">
<div style="text-align: center;">
<div style="font-size: 16px; font-weight: bold; color: #ff6b9d;">{{ $days }}</div>
<div style="font-size: 9px; opacity: 0.6; margin-top: 2px;">{{ if eq $days 1 }}день{{ else if or (eq $days 2) (eq $days 3) (eq $days 4) }}дня{{ else }}дней{{ end }}</div>
</div>
<div style="text-align: center;">
<div style="font-size: 16px; font-weight: bold; color: #ffa94d;">{{ .Int "hours" }}</div>
<div style="font-size: 9px; opacity: 0.6; margin-top: 2px;">{{ if eq (.Int "hours") 1 }}час{{ else if or (eq (.Int "hours") 2) (eq (.Int "hours") 3) (eq (.Int "hours") 4) }}часа{{ else }}часов{{ end }}</div>
</div>
</div>
{{ end }}
</div>
{{/* Треугольная стрелка вниз */}}
<div style="position: absolute; bottom: -6px; left: 50%; transform: translateX(-50%); width: 0; height: 0; border-left: 6px solid transparent; border-right: 6px solid transparent; border-top: 6px solid rgba(20,20,25,0.98);"></div>
</div>
</div>
{{ end }}
{{ end }}
{{/* ===== ИНТЕРАКТИВНАЯ СЕТКА ДЛЯ НАВЕДЕНИЯ ===== */}}
{{/* Невидимые зоны, которые показывают дату при наведении */}}
{{ range .JSON.Array "timeline_grid" }}
{{ $gridDays := .Int "days" }}
{{ $gridDaysFloat := toFloat $gridDays }}
{{/* Рассчитываем позицию точки на шкале */}}
{{ $gridPos := 0.0 }}
{{ $gridWidth := 0.0 }}
{{ if lt $gridDaysFloat $maxDays }}
{{ if lt $gridDaysFloat $daysZ1 }}
{{/* Зона 1: деления по дням */}}
{{ $gridPos = mul (div $gridDaysFloat $daysZ1) $z1Pct }}
{{ $gridWidth = div $z1Pct $daysZ1 }}
{{ else }}
{{/* Зона 2: деления по неделям */}}
{{ $daysAfterZ1 := sub $gridDaysFloat $daysZ1 }}
{{ $weeksInZ2 := div $daysAfterZ1 7.0 }}
{{ $posInZ2 := mul (div $weeksInZ2 $weeksZ2) $z2Pct }}
{{ $gridPos = add $z1Pct $posInZ2 }}
{{ $gridWidth = div $z2Pct $weeksZ2 }}
{{ end }}
{{/* Интерактивная зона */}}
<div class="timeline-grid-item" style="position: absolute; left: {{ $gridPos }}%; top: 35%; transform: translateY(-50%); width: {{ $gridWidth }}%; height: 40px; cursor: pointer; z-index: 5;">
{{/* Tooltip с датой */}}
<div class="grid-tooltip" style="position: absolute; bottom: 45px; left: 50%; transform: translateX(-50%); background: linear-gradient(135deg, rgba(30,30,40,0.95) 0%, rgba(40,40,50,0.95) 100%); color: white; padding: 8px 12px; border-radius: 8px; font-size: 11px; white-space: nowrap; opacity: 0; pointer-events: none; transition: all 0.15s ease; box-shadow: 0 4px 12px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.08); backdrop-filter: blur(8px);">
{{/* День недели */}}
<div style="text-align: center; margin-bottom: 4px; font-weight: 600; color: #8ab4f8; font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px;">
{{ .String "weekday" }}
</div>
{{/* Дата */}}
<div style="text-align: center; font-weight: 500; font-size: 13px;">
📅 {{ .String "date" }}
</div>
{{/* Треугольная стрелка */}}
<div style="position: absolute; bottom: -5px; left: 50%; transform: translateX(-50%); width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 5px solid rgba(30,30,40,0.95);"></div>
</div>
</div>
{{ end }}
{{ end }}
{{/* ===== МЕТКИ МЕСЯЦЕВ ПОД ШКАЛОЙ ===== */}}
{{/* Используем данные о месяцах из API для элегантного отображения */}}
{{ range .JSON.Array "months" }}
{{ $monthDays := .Int "days" }}
{{ $monthDaysFloat := toFloat $monthDays }}
{{/* Показываем только метки в пределах видимого диапазона */}}
{{ if lt $monthDaysFloat $maxDays }}
{{/* Рассчитываем позицию метки */}}
{{ $monthPos := 0.0 }}
{{if lt $monthDaysFloat $daysZ1 }}
{{ $monthPos = mul (div $monthDaysFloat $daysZ1) $z1Pct }}
{{ else }}
{{ $daysAfter := sub $monthDaysFloat $daysZ1 }}
{{ $weeksIn := div $daysAfter 7.0 }}
{{ $posIn := mul (div $weeksIn $weeksZ2) $z2Pct }}
{{ $monthPos = add $z1Pct $posIn }}
{{ end }}
{{/* Отображаем метку месяца */}}
<div style="position: absolute; left: {{ $monthPos }}%; top: 65%; transform: translateX(-50%); font-size: 10px; color: rgba(128,128,128,0.8); white-space: nowrap;">
{{ .String "name" }}
</div>
{{ end }}
{{ end }}
</div>
</div>
<style>
/* Показываем tooltip при наведении на родительский элемент */
.event-item:hover > .event-tooltip { opacity: 1 !important; pointer-events: auto; }
/* Показываем tooltip при наведении на интерактивную зону сетки */
.timeline-grid-item:hover > .grid-tooltip { opacity: 1 !important; pointer-events: auto; }
/* Увеличиваем эмодзи при наведении */
.event-item:hover .event-emoji {
transform: scale(1.4);
}
/* Поднимаем z-index у наведенного элемента */
.event-item:hover {
z-index: 100 !important;
}
/* Плавные переходы */
.event-item {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Анимации тряски с разной интенсивностью */
@keyframes shake-weak {
0%, 100% { transform: translate(-50%, -50%) rotate(0deg); }
25% { transform: translate(-50%, -50%) rotate(0.5deg); }
75% { transform: translate(-50%, -50%) rotate(-0.5deg); }
}
@keyframes shake-medium {
0%, 100% { transform: translate(-50%, -50%) rotate(0deg); }
25% { transform: translate(-50%, -50%) rotate(1deg); }
75% { transform: translate(-50%, -50%) rotate(-1deg); }
}
@keyframes shake-strong {
0%, 100% { transform: translate(-50%, -50%) rotate(0deg); }
25% { transform: translate(-50%, -50%) rotate(2deg); }
75% { transform: translate(-50%, -50%) rotate(-2deg); }
}
@keyframes shake-very-strong {
0%, 100% { transform: translate(-50%, -50%) rotate(0deg); }
10% { transform: translate(-50%, -50%) rotate(3deg); }
30% { transform: translate(-50%, -50%) rotate(-3deg); }
50% { transform: translate(-50%, -50%) rotate(3deg); }
70% { transform: translate(-50%, -50%) rotate(-3deg); }
90% { transform: translate(-50%, -50%) rotate(3deg); }
}
.shake-weak { animation: shake-weak 2s ease-in-out infinite; }
.shake-medium { animation: shake-medium 1.5s ease-in-out infinite; }
.shake-strong { animation: shake-strong 1s ease-in-out infinite; }
.shake-very-strong { animation: shake-very-strong 0.5s ease-in-out infinite; }
a:hover { color: rgba(255,255,255,0.8) !important; }
</style>
<div style="text-align: right; margin-top: 5px; padding-right: 10px;">
<a href="http://192.168.50.114:5555/manage" target="_blank" style="color: rgba(255,255,255,0.3); text-decoration: none; font-size: 10px; transition: color 0.2s;">⚙️ Управление</a>
</div>
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" style="display:none;" onload="
(function() {
const timeline = document.getElementById('glance-timeline');
if (!timeline) return;
const eventItems = Array.from(timeline.querySelectorAll('.event-item'));
if (eventItems.length === 0) return;
// Применяем тряску в зависимости от позиции (чем левее - тем сильнее)
eventItems.forEach(item => {
const style = window.getComputedStyle(item);
const leftValue = parseFloat(style.left);
// Позиция в процентах: 0-25% = первая зона (0-14 дней)
if (leftValue <= 25) {
// Чем ближе к 0%, тем сильнее тряска
if (leftValue <= 5) {
item.classList.add('shake-very-strong');
} else if (leftValue <= 12) {
item.classList.add('shake-strong');
} else if (leftValue <= 18) {
item.classList.add('shake-medium');
} else {
item.classList.add('shake-weak');
}
}
});
const REPULSION_RADIUS = 80;
const REPULSION_STRENGTH = 20;
eventItems.forEach(hoverItem => {
hoverItem.addEventListener('mouseenter', function() {
const hoveredRect = hoverItem.getBoundingClientRect();
const timelineRect = timeline.getBoundingClientRect();
const hoveredX = hoveredRect.left + hoveredRect.width / 2 - timelineRect.left;
const hoveredY = hoveredRect.top + hoveredRect.height / 2 - timelineRect.top;
eventItems.forEach(item => {
if (item === hoverItem) return;
const itemRect = item.getBoundingClientRect();
const itemX = itemRect.left + itemRect.width / 2 - timelineRect.left;
const itemY = itemRect.top + itemRect.height / 2 - timelineRect.top;
const dx = itemX - hoveredX;
const dy = itemY - hoveredY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < REPULSION_RADIUS && distance > 0) {
const nx = dx / distance;
const ny = dy / distance;
const force = (1 - distance / REPULSION_RADIUS) * REPULSION_STRENGTH;
const offsetX = nx * force;
const offsetY = ny * force;
item.style.transform = 'translate(calc(-50% + ' + offsetX + 'px), calc(-50% + ' + offsetY + 'px))';
}
});
});
hoverItem.addEventListener('mouseleave', function() {
eventItems.forEach(item => {
if (item !== hoverItem) {
item.style.transform = 'translate(-50%, -50%)';
}
});
});
});
})();
">