Compare commits
3 Commits
03825c1048
...
44e6547a0a
| Author | SHA1 | Date | |
|---|---|---|---|
| 44e6547a0a | |||
| dee4567f74 | |||
| c8f1be4934 |
@ -770,6 +770,30 @@ body.header-tone-light .app-header .tab.active {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TV calendar: inset from screen edges (replaces tight container-fluid padding) */
|
||||||
|
.calendar-tv-shell {
|
||||||
|
padding: clamp(1.25rem, 3vw, 2.75rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TV calendar: more space around the Google embed and between columns */
|
||||||
|
.calendar-tv-view > .calendar-tab-layout.row {
|
||||||
|
--bs-gutter-x: clamp(1.25rem, 2.5vw, 2.5rem);
|
||||||
|
--bs-gutter-y: clamp(1.25rem, 2.5vw, 2.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-tv-view .calendar-tab-google .calendar-embed {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-tv-clock-time {
|
||||||
|
display: block;
|
||||||
|
font-size: clamp(5.25rem, 12vw, 8.25rem);
|
||||||
|
font-weight: 600;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
.calendar-tv-view .calendar-embed iframe {
|
.calendar-tv-view .calendar-embed iframe {
|
||||||
min-height: 70vh;
|
min-height: 70vh;
|
||||||
}
|
}
|
||||||
@ -790,8 +814,109 @@ body.calendar-tv-dark {
|
|||||||
--secondary-color: #1e293b;
|
--secondary-color: #1e293b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Photo collage behind TV calendar (fixed full viewport) */
|
||||||
|
.calendar-tv-collage-bg {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-tv-collage-grid {
|
||||||
|
display: grid;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
gap: clamp(6px, 1vw, 14px);
|
||||||
|
padding: clamp(6px, 1vw, 14px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-tv-collage-grid--mosaic {
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
grid-template-rows: repeat(4, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-tv-collage-grid--mosaic .calendar-tv-collage-tile:nth-child(1) {
|
||||||
|
grid-column: 1 / 3;
|
||||||
|
grid-row: 1 / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-tv-collage-grid--mosaic .calendar-tv-collage-tile:nth-child(2) {
|
||||||
|
grid-column: 3 / 4;
|
||||||
|
grid-row: 1 / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-tv-collage-grid--mosaic .calendar-tv-collage-tile:nth-child(3) {
|
||||||
|
grid-column: 4 / 5;
|
||||||
|
grid-row: 1 / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-tv-collage-grid--mosaic .calendar-tv-collage-tile:nth-child(4) {
|
||||||
|
grid-column: 5 / 6;
|
||||||
|
grid-row: 1 / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-tv-collage-grid--mosaic .calendar-tv-collage-tile:nth-child(5) {
|
||||||
|
grid-column: 3 / 4;
|
||||||
|
grid-row: 2 / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-tv-collage-grid--mosaic .calendar-tv-collage-tile:nth-child(6) {
|
||||||
|
grid-column: 4 / 5;
|
||||||
|
grid-row: 2 / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-tv-collage-grid--mosaic .calendar-tv-collage-tile:nth-child(7) {
|
||||||
|
grid-column: 5 / 6;
|
||||||
|
grid-row: 2 / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-tv-collage-grid--mosaic .calendar-tv-collage-tile:nth-child(8) {
|
||||||
|
grid-column: 1 / 3;
|
||||||
|
grid-row: 3 / 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-tv-collage-grid--mosaic .calendar-tv-collage-tile:nth-child(9) {
|
||||||
|
grid-column: 3 / 5;
|
||||||
|
grid-row: 3 / 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-tv-collage-grid--mosaic .calendar-tv-collage-tile:nth-child(10) {
|
||||||
|
grid-column: 5 / 6;
|
||||||
|
grid-row: 3 / 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-tv-collage-grid--even .calendar-tv-collage-tile {
|
||||||
|
min-height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-tv-collage-tile {
|
||||||
|
min-height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-tv-collage-scrim {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
160deg,
|
||||||
|
rgba(15, 23, 42, 0.78) 0%,
|
||||||
|
rgba(15, 23, 42, 0.62) 45%,
|
||||||
|
rgba(15, 23, 42, 0.75) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
body.calendar-tv-dark main {
|
body.calendar-tv-dark main {
|
||||||
background-color: #0f172a;
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.calendar-tv-dark .calendar-tv-view h2,
|
body.calendar-tv-dark .calendar-tv-view h2,
|
||||||
@ -799,6 +924,10 @@ body.calendar-tv-dark .calendar-tv-view h3 {
|
|||||||
color: #f1f5f9;
|
color: #f1f5f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.calendar-tv-dark .calendar-tv-view .calendar-tv-clock-time {
|
||||||
|
color: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
body.calendar-tv-dark .calendar-tv-view .text-muted {
|
body.calendar-tv-dark .calendar-tv-view .text-muted {
|
||||||
color: #94a3b8 !important;
|
color: #94a3b8 !important;
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
assets/images/calendar-tv-collage/collage-01.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
assets/images/calendar-tv-collage/collage-02.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
assets/images/calendar-tv-collage/collage-03.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/images/calendar-tv-collage/collage-04.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
assets/images/calendar-tv-collage/collage-05.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
assets/images/calendar-tv-collage/collage-06.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
assets/images/calendar-tv-collage/collage-07.png
Normal file
|
After Width: | Height: | Size: 164 KiB |
BIN
assets/images/calendar-tv-collage/collage-08.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
assets/images/calendar-tv-collage/collage-09.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
assets/images/calendar-tv-collage/collage-10.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
35
includes/calendar_tv_collage_bg.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
if (empty($isCalendarTvView ?? null)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$collageDir = dirname(__DIR__) . '/assets/images/calendar-tv-collage';
|
||||||
|
$files = glob($collageDir . '/collage-*.png') ?: [];
|
||||||
|
if ($files === []) {
|
||||||
|
$files = glob($collageDir . '/collage-*.{jpg,jpeg,webp}', GLOB_BRACE) ?: [];
|
||||||
|
}
|
||||||
|
natcasesort($files);
|
||||||
|
$files = array_values($files);
|
||||||
|
if ($files === []) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mosaic = count($files) === 10;
|
||||||
|
$gridClass = $mosaic ? 'calendar-tv-collage-grid calendar-tv-collage-grid--mosaic' : 'calendar-tv-collage-grid calendar-tv-collage-grid--even';
|
||||||
|
$cols = 5;
|
||||||
|
$rows = (int) max(1, ceil(count($files) / $cols));
|
||||||
|
?>
|
||||||
|
<div class="calendar-tv-collage-bg" aria-hidden="true">
|
||||||
|
<div class="<?= htmlspecialchars($gridClass, ENT_QUOTES, 'UTF-8') ?>"<?= $mosaic ? '' : ' style="grid-template-columns: repeat(' . (int) $cols . ', 1fr); grid-template-rows: repeat(' . (int) $rows . ', 1fr);"' ?>>
|
||||||
|
<?php foreach ($files as $absPath): ?>
|
||||||
|
<?php
|
||||||
|
$base = basename((string) $absPath);
|
||||||
|
$url = 'assets/images/calendar-tv-collage/' . $base;
|
||||||
|
?>
|
||||||
|
<div class="calendar-tv-collage-tile" style="background-image: url('<?= htmlspecialchars($url, ENT_QUOTES, 'UTF-8') ?>')"></div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<div class="calendar-tv-collage-scrim"></div>
|
||||||
|
</div>
|
||||||
@ -73,6 +73,7 @@
|
|||||||
$bodyStyle = implode('; ', $bodyStyleParts) . ';';
|
$bodyStyle = implode('; ', $bodyStyleParts) . ';';
|
||||||
?>
|
?>
|
||||||
<body class="family-hub-body <?= htmlspecialchars($headerThemeClass ?? 'header-tone-dark', ENT_QUOTES, 'UTF-8') ?><?= !empty($isCalendarTvView) ? ' calendar-tv-dark' : '' ?>" style="<?= htmlspecialchars($bodyStyle, ENT_QUOTES, 'UTF-8') ?>">
|
<body class="family-hub-body <?= htmlspecialchars($headerThemeClass ?? 'header-tone-dark', ENT_QUOTES, 'UTF-8') ?><?= !empty($isCalendarTvView) ? ' calendar-tv-dark' : '' ?>" style="<?= htmlspecialchars($bodyStyle, ENT_QUOTES, 'UTF-8') ?>">
|
||||||
|
<?php include __DIR__ . '/calendar_tv_collage_bg.php'; ?>
|
||||||
<?php $hideTopHeader = !empty($isCalendarTvView); ?>
|
<?php $hideTopHeader = !empty($isCalendarTvView); ?>
|
||||||
<?php if (!$hideTopHeader): ?>
|
<?php if (!$hideTopHeader): ?>
|
||||||
<header class="app-header app-header-with-tabs text-white pt-3 px-3 pb-2 mb-0">
|
<header class="app-header app-header-with-tabs text-white pt-3 px-3 pb-2 mb-0">
|
||||||
|
|||||||
@ -167,7 +167,7 @@ if (fhRelativeLuminance($themePalette['primary']) >= 0.62) {
|
|||||||
include 'includes/header.php';
|
include 'includes/header.php';
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="<?= $isCalendarTvView ? 'container-fluid px-2 py-0' : 'container' ?>">
|
<div class="<?= $isCalendarTvView ? 'container-fluid calendar-tv-shell' : 'container' ?>">
|
||||||
<div class="tab-content" id="dashboardContentArea">
|
<div class="tab-content" id="dashboardContentArea">
|
||||||
<?php include 'tabs/' . $activeTab . '.php'; ?>
|
<?php include 'tabs/' . $activeTab . '.php'; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -51,11 +51,10 @@ if ($embedUrl === null && $calId !== '' && $calId !== 'your_calendar_id_here') {
|
|||||||
|
|
||||||
[$rangeStart, $rangeEnd] = hubCalendarDefaultAgendaRange($familySettings);
|
[$rangeStart, $rangeEnd] = hubCalendarDefaultAgendaRange($familySettings);
|
||||||
$events = hubCalendarAgendaEvents($rangeStart, $rangeEnd, $people, $familySettings);
|
$events = hubCalendarAgendaEvents($rangeStart, $rangeEnd, $people, $familySettings);
|
||||||
[, $tzLocal] = familyHubCalendarContext($familySettings);
|
[$calendarTzId, $tzLocal] = familyHubCalendarContext($familySettings);
|
||||||
$todayYmd = familyHubTodayYmdInTz($tzLocal);
|
$todayYmd = familyHubTodayYmdInTz($tzLocal);
|
||||||
$twoWayMerge = !empty($familySettings['calendar_two_way_google']);
|
$twoWayMerge = !empty($familySettings['calendar_two_way_google']);
|
||||||
$isTvView = isset($_GET['view']) && (string) $_GET['view'] === 'tv';
|
$isTvView = isset($_GET['view']) && (string) $_GET['view'] === 'tv';
|
||||||
$calendarBaseUrl = '?tab=calendar';
|
|
||||||
$calendarTvUrl = '?tab=calendar&view=tv';
|
$calendarTvUrl = '?tab=calendar&view=tv';
|
||||||
|
|
||||||
$eventsByDate = [];
|
$eventsByDate = [];
|
||||||
@ -82,14 +81,7 @@ $iconByType = [
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div id="calendar" class="tab-content<?= $isTvView ? ' calendar-tv-view' : '' ?>">
|
<div id="calendar" class="tab-content<?= $isTvView ? ' calendar-tv-view' : '' ?>">
|
||||||
<?php if ($isTvView): ?>
|
<?php if (!$isTvView): ?>
|
||||||
<div class="d-flex flex-wrap justify-content-end align-items-center gap-2 mb-2 calendar-tv-toolbar">
|
|
||||||
<a href="<?= htmlspecialchars($calendarBaseUrl, ENT_QUOTES, 'UTF-8') ?>" class="btn btn-outline-secondary btn-sm" title="Exit TV view">
|
|
||||||
<i class="fa fa-xmark me-1" aria-hidden="true"></i>
|
|
||||||
Exit TV view
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2">
|
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2">
|
||||||
<h2 class="mb-0">Calendar</h2>
|
<h2 class="mb-0">Calendar</h2>
|
||||||
<a href="<?= htmlspecialchars($calendarTvUrl, ENT_QUOTES, 'UTF-8') ?>" class="btn btn-outline-secondary btn-sm" title="Open TV view dashboard">
|
<a href="<?= htmlspecialchars($calendarTvUrl, ENT_QUOTES, 'UTF-8') ?>" class="btn btn-outline-secondary btn-sm" title="Open TV view dashboard">
|
||||||
@ -135,6 +127,11 @@ $iconByType = [
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-12 calendar-tab-agenda">
|
<div class="col-12 calendar-tab-agenda">
|
||||||
<h3 class="h5 mb-3"><?= $isTvView ? 'Weekly hub' : 'Family Hub agenda' ?></h3>
|
<h3 class="h5 mb-3"><?= $isTvView ? 'Weekly hub' : 'Family Hub agenda' ?></h3>
|
||||||
|
<?php if ($isTvView): ?>
|
||||||
|
<div class="calendar-tv-clock mb-3" aria-hidden="true">
|
||||||
|
<time id="calendarTvClock" class="calendar-tv-clock-time"></time>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
<?php if (!$isTvView): ?>
|
<?php if (!$isTvView): ?>
|
||||||
<p class="text-muted small mb-2">Hub data only (not Google Calendar).</p>
|
<p class="text-muted small mb-2">Hub data only (not Google Calendar).</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@ -183,15 +180,29 @@ $iconByType = [
|
|||||||
</div>
|
</div>
|
||||||
<?php if ($isTvView): ?>
|
<?php if ($isTvView): ?>
|
||||||
<script>
|
<script>
|
||||||
|
(function () {
|
||||||
|
var tz = <?= json_encode($calendarTzId, JSON_UNESCAPED_UNICODE) ?>;
|
||||||
|
var el = document.getElementById('calendarTvClock');
|
||||||
|
function tick() {
|
||||||
|
if (!el) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var now = new Date();
|
||||||
|
var opts = {
|
||||||
|
timeZone: tz,
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hour12: true
|
||||||
|
};
|
||||||
|
el.textContent = new Intl.DateTimeFormat(undefined, opts).format(now);
|
||||||
|
el.setAttribute('datetime', now.toISOString());
|
||||||
|
}
|
||||||
|
tick();
|
||||||
|
window.setInterval(tick, 1000);
|
||||||
window.setTimeout(function () {
|
window.setTimeout(function () {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}, 10 * 60 * 1000);
|
}, 10 * 60 * 1000);
|
||||||
</script>
|
})();
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ($isTvView): ?>
|
|
||||||
<script>
|
|
||||||
window.setTimeout(function () {
|
|
||||||
window.location.reload();
|
|
||||||
}, 10 * 60 * 1000);
|
|
||||||
</script>
|
</script>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|||||||