This commit is contained in:
Gogs
2026-07-09 14:36:47 +02:00
parent 9831469bd0
commit 2df690ba11
10 changed files with 724 additions and 36 deletions
+1 -1
View File
@@ -225,7 +225,7 @@
"icon": "",
"country": "",
"language": "",
"enabled": false,
"enabled": true,
"favorite": false,
"number": 210,
"group": "Österreich"
+283
View File
@@ -186,3 +186,286 @@
align-items: center;
gap: 0.5rem;
}
.now-card-enhanced {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.now-card-top {
display: flex;
align-items: center;
gap: 0.85rem;
}
.now-channel-logo {
width: 64px;
height: 44px;
object-fit: contain;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 0.55rem;
padding: 4px;
flex: 0 0 64px;
}
.now-channel-info {
min-width: 0;
}
.now-channel-name {
display: flex;
align-items: center;
gap: 0.4rem;
font-weight: 700;
}
.now-time {
color: var(--muted);
font-size: 0.9rem;
margin-top: 0.15rem;
}
.now-badges {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
}
.now-badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
border: 1px solid var(--border);
background: var(--panel-2);
color: var(--text);
border-radius: 999px;
padding: 0.2rem 0.55rem;
font-size: 0.82rem;
}
.now-next {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 0.5rem;
align-items: center;
border-top: 1px solid var(--border);
padding-top: 0.65rem;
color: var(--muted);
font-size: 0.9rem;
}
.now-next strong {
color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.now-next-label {
color: var(--muted);
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.now-channel-logo {
display: block;
width: 64px;
height: 44px;
min-width: 64px;
object-fit: contain;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 0.55rem;
padding: 4px;
}
.progress-wrap {
width: 100%;
}
.progress-bar {
width: 100%;
height: 10px;
background: var(--panel-2);
border-radius: 999px;
overflow: hidden;
border: 1px solid var(--border);
}
.progress-fill {
display: block;
height: 100%;
min-width: 2px;
background: var(--accent);
border-radius: 999px;
}
.progress-meta {
display: flex;
justify-content: space-between;
color: var(--muted);
font-size: 0.85rem;
margin-bottom: 0.35rem;
}
.now-grid-enhanced {
gap: 1.5rem;
}
.now-card-enhanced {
padding: 1.35rem;
border-radius: 14px;
gap: 0.9rem;
}
.now-card-top {
margin-bottom: 0.35rem;
}
.now-channel-logo {
width: 76px;
height: 52px;
flex-basis: 76px;
}
.now-channel-name {
font-size: 1rem;
}
.now-time {
font-size: 0.92rem;
}
.now-card-enhanced h3 {
font-size: 1.35rem;
margin: 0.1rem 0 0;
}
.now-subtitle {
color: var(--muted);
margin-top: -0.35rem;
}
.now-badges {
margin-top: 0.1rem;
}
.progress-wrap {
margin-top: 0.25rem;
}
.progress-bar {
height: 12px;
}
.progress-meta {
margin-bottom: 0.4rem;
}
.now-next {
margin-top: 0.45rem;
padding-top: 0.85rem;
border-top: 1px solid var(--border);
background: rgba(255, 255, 255, 0.025);
border-radius: 0 0 10px 10px;
}
.now-next-label {
font-size: 0.75rem;
}
.now-next strong {
font-size: 0.95rem;
}
.now-next {
display: block;
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid var(--border);
background: transparent;
border-radius: 0;
color: var(--muted);
}
.now-next-programme {
display: flex;
gap: 1rem;
align-items: flex-start;
margin-top: 0.35rem;
}
.now-next-time {
min-width: 54px;
font-weight: 700;
color: var(--accent);
}
.now-next-info {
flex: 1;
min-width: 0;
}
.now-next-info strong {
display: block;
color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.now-next-subtitle {
margin-top: 0.15rem;
font-size: 0.9rem;
color: var(--muted);
}
.now-card-top {
display: flex;
gap: 1rem;
align-items: center;
}
.now-channel-logo-wrap {
width: 92px;
height: 64px;
flex: 0 0 92px;
display: flex;
align-items: center;
justify-content: center;
background: var(--panel-2);
border: 1px solid var(--border);
border-radius: .8rem;
}
.now-channel-logo {
width: 82px;
height: 52px;
object-fit: contain;
}
.now-channel-info {
flex: 1;
}
.now-channel-name {
font-size: 1.15rem;
font-weight: 700;
}
.now-channel-number {
margin-top: .15rem;
color: var(--muted);
font-size: .9rem;
}
.now-time {
margin-top: .25rem;
color: var(--muted);
font-size: .95rem;
}
+109
View File
@@ -189,3 +189,112 @@
background: var(--accent);
border-color: var(--accent);
}
cat >> app/public/assets/css/timeline.css <<'CSS'
.timeline-channel {
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
gap: 6px;
}
.timeline-channel .channel-logo {
width: 72px;
height: 48px;
flex: 0 0 auto;
padding: 4px;
}
.timeline-channel-name {
font-size: 0.82rem;
line-height: 1.15;
max-width: 145px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.timeline-programme {
border-left: 5px solid rgba(148, 163, 184, 0.45);
}
.timeline-programme.is-current {
opacity: 1;
border-color: var(--accent);
box-shadow: 0 0 0 1px var(--accent-soft), 0 8px 22px rgba(0, 0, 0, 0.45);
z-index: 10;
}
.live-badge {
display: inline-flex;
margin-bottom: 3px;
padding: 1px 6px;
border-radius: 999px;
background: var(--accent);
color: white;
font-size: 0.65rem;
font-style: normal;
font-weight: 900;
letter-spacing: 0.04em;
}
.genre-film,
.genre-movie,
.genre-kino {
border-left-color: #3b82f6;
}
.genre-serie,
.genre-series {
border-left-color: #8b5cf6;
}
.genre-sport,
.genre-fussball,
.genre-fu-ball {
border-left-color: #22c55e;
}
.genre-news,
.genre-nachrichten,
.genre-journal {
border-left-color: #ef4444;
}
.genre-doku,
.genre-documentary {
border-left-color: #14b8a6;
}
.genre-kinder,
.genre-kids,
.genre-children {
border-left-color: #f97316;
}
.genre-musik,
.genre-music {
border-left-color: #eab308;
}
.timeline-programme:hover::after {
content: attr(title);
position: absolute;
left: 0;
top: calc(100% + 8px);
width: max-content;
max-width: 420px;
white-space: normal;
background: var(--panel-2);
border: 1px solid var(--border);
color: var(--text);
padding: 10px 12px;
border-radius: 10px;
box-shadow: 0 12px 28px rgba(0,0,0,.45);
z-index: 50;
font-size: 0.85rem;
line-height: 1.35;
}
CSS
+37 -1
View File
@@ -15,9 +15,44 @@ function dayUpdateProgrammeStates() {
document.querySelectorAll('[data-live-programme]').forEach(programme => {
const start = Number(programme.dataset.liveStart);
const stop = Number(programme.dataset.liveStop);
const isCurrent = now >= start && now < stop;
programme.classList.toggle('is-current', now >= start && now < stop);
programme.classList.toggle('is-current', isCurrent);
programme.classList.toggle('is-past', now >= stop);
const badge = programme.querySelector('[data-live-badge]');
if (badge) {
badge.hidden = !isCurrent;
}
});
}
function daySetupZoomMemory() {
const wrap = document.querySelector('.timeline-wrap');
const buttons = document.querySelectorAll('.timeline-zoom button');
if (!wrap || !buttons.length) return;
const savedZoom = localStorage.getItem('tvguide.timelineZoom');
if (savedZoom) {
wrap.style.setProperty('--minute-width', `${savedZoom}px`);
buttons.forEach(button => {
button.classList.toggle('active', button.dataset.zoom === savedZoom);
});
}
buttons.forEach(button => {
button.addEventListener('click', () => {
const zoom = button.dataset.zoom;
wrap.style.setProperty('--minute-width', `${zoom}px`);
localStorage.setItem('tvguide.timelineZoom', zoom);
buttons.forEach(b => b.classList.remove('active'));
button.classList.add('active');
});
});
}
@@ -27,6 +62,7 @@ function dayTick() {
}
document.addEventListener('DOMContentLoaded', () => {
daySetupZoomMemory();
dayTick();
setInterval(dayTick, 1000);
});
+113 -13
View File
@@ -1,22 +1,67 @@
function nowEscape(value) {
return String(value ?? '').replace(/[&<>"']/g, char => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
}[char]));
}
function nowLogoUrl(channelId) {
return `/assets/logos/${encodeURIComponent(channelId)}.svg`;
}
function nowCreateCard(item) {
const article = document.createElement('article');
article.className = 'now-card';
article.className = 'now-card now-card-enhanced';
article.innerHTML = `
<div class="now-channel">
<span data-live-channel-name></span>
<span data-live-favorite></span>
<div class="now-card-top">
<img class="now-channel-logo" data-live-logo alt="">
<div class="now-channel-info">
<div class="now-channel-name">
<span class="channel-number" data-live-channel-number></span>
<span data-live-channel-name></span>
<span data-live-favorite></span>
</div>
<div class="now-time" data-live-time-range></div>
</div>
</div>
<h3 data-live-title></h3>
<div class="now-subtitle" data-live-subtitle></div>
<div class="now-time" data-live-time></div>
<div class="now-badges" data-live-badges></div>
<div class="progress">
<div class="progress-bar" data-live-progress></div>
<div class="progress-wrap">
<div class="progress-meta">
<span data-live-percent></span>
<span data-live-remaining></span>
</div>
<div class="progress-bar">
<div class="progress-fill" data-live-progress></div>
</div>
</div>
<div class="now-next" data-live-next>
<div class="now-next-label">Danach</div>
<div class="now-next-programme">
<div class="now-next-time" data-live-next-time></div>
<div class="now-next-info">
<strong data-live-next-title></strong>
<div class="now-next-subtitle" data-live-next-subtitle></div>
</div>
</div>
</div>
<p class="now-desc" data-live-description></p>
`;
nowUpdateCard(article, item);
@@ -24,23 +69,78 @@ function nowCreateCard(item) {
}
function nowUpdateCard(card, item) {
card.querySelector('[data-live-channel-name]').textContent = item.channel_name;
const logo = card.querySelector('[data-live-logo]');
logo.src = nowLogoUrl(item.channel_id);
logo.onerror = () => {
logo.onerror = null;
logo.src = '/assets/logos/default.svg';
};
card.querySelector('[data-live-channel-number]').textContent = item.channel_number ?? '';
card.querySelector('[data-live-channel-name]').textContent = item.channel_name ?? '';
card.querySelector('[data-live-favorite]').textContent = item.favorite ? '⭐' : '';
card.querySelector('[data-live-title]').textContent = item.title;
card.querySelector('[data-live-title]').textContent = item.title ?? '';
card.querySelector('[data-live-next-subtitle]').textContent =
item.next_subtitle || '';
card.querySelector('[data-live-next-subtitle]').style.display =
item.next_subtitle ? '' : 'none';
const subtitle = card.querySelector('[data-live-subtitle]');
subtitle.textContent = item.subtitle || '';
subtitle.style.display = item.subtitle ? '' : 'none';
const badges = [];
if (item.category) {
badges.push(`${item.genre_icon || '▶'} ${item.category}`);
}
if (item.year) {
badges.push(item.year);
}
if (item.rating) {
badges.push(`FSK ${item.rating}`);
}
if (item.quality) {
badges.push(item.quality);
}
const badgesEl = card.querySelector('[data-live-badges]');
badgesEl.innerHTML = badges.map(badge => `<span class="now-badge">${nowEscape(badge)}</span>`).join('');
badgesEl.style.display = badges.length ? '' : 'none';
const next = card.querySelector('[data-live-next]');
if (item.next_title) {
next.style.display = '';
card.querySelector('[data-live-next-title]').textContent = item.next_title;
card.querySelector('[data-live-next-time]').textContent = item.next_start
? TVGuideLive.formatTime(item.next_start)
: '';
} else {
next.style.display = 'none';
}
const desc = card.querySelector('[data-live-description]');
desc.textContent = item.description || '';
desc.style.display = item.description ? '' : 'none';
nowTickCard(card, item);
}
function nowTickCard(card, item) {
card.querySelector('[data-live-time]').textContent =
`${TVGuideLive.formatTime(item.start)} ${TVGuideLive.formatTime(item.stop)} · noch ${TVGuideLive.calcRemainingMinutes(item.stop)} Min.`;
const percent = TVGuideLive.calcPercent(item.start, item.stop);
const remaining = TVGuideLive.calcRemainingMinutes(item.stop);
card.querySelector('[data-live-progress]').style.width =
`${TVGuideLive.calcPercent(item.start, item.stop)}%`;
card.querySelector('[data-live-time-range]').textContent =
`${TVGuideLive.formatTime(item.start)} ${TVGuideLive.formatTime(item.stop)}`;
card.querySelector('[data-live-percent]').textContent = `${percent}%`;
card.querySelector('[data-live-remaining]').textContent = `noch ${remaining} Minuten`;
card.querySelector('[data-live-progress]').style.width = `${percent}%`;
}
document.addEventListener('DOMContentLoaded', () => {
+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 80">
<rect width="120" height="80" rx="12" fill="#1f2937"/>
<rect x="18" y="18" width="84" height="48" rx="6" fill="#111827" stroke="#334155" stroke-width="4"/>
<path d="M44 32l24 8-24 8z" fill="#94a3b8"/>
</svg>

After

Width:  |  Height:  |  Size: 276 B

+6
View File
@@ -6,6 +6,12 @@ class LogoService
public static function getLogoUrl(string $channelId): string
{
$channelId = trim($channelId);
if ($channelId === '') {
return '/assets/logos/default.svg';
}
if (isset(self::$cache[$channelId])) {
return self::$cache[$channelId];
}
+75 -1
View File
@@ -62,7 +62,13 @@ class ProgrammeService
continue;
}
foreach ($channel['programmes'] ?? [] as $p) {
$programmes = $channel['programmes'] ?? [];
usort($programmes, function ($a, $b) {
return ((int)($a['start'] ?? 0)) <=> ((int)($b['start'] ?? 0));
});
foreach ($programmes as $index => $p) {
$start = (int)($p['start'] ?? 0);
$stop = (int)($p['stop'] ?? 0);
@@ -71,21 +77,36 @@ class ProgrammeService
$elapsed = max(0, $now - $start);
$percent = min(100, round(($elapsed / $duration) * 100));
$next = $programmes[$index + 1] ?? null;
$items[] = [
'channel_id' => $id,
'channel_name' => $cfg['name'] ?? $channel['name'] ?? $id,
'channel_icon' => $cfg['icon'] ?? $channel['icon'] ?? '',
'channel_number' => (int)($cfg['number'] ?? 999999),
'favorite' => (bool)($cfg['favorite'] ?? false),
'title' => $p['title'] ?? '',
'subtitle' => $p['subtitle'] ?? '',
'description' => $p['description'] ?? '',
'category' => $p['category'] ?? '',
'genre_icon' => $this->getGenreIcon($p['category'] ?? ''),
'metadata' => $p['metadata'] ?? [],
'year' => $p['year'] ?? '',
'rating' => $p['rating'] ?? '',
'episode' => $p['episode'] ?? '',
'quality' => $this->detectQuality($p),
'start' => $start,
'stop' => $stop,
'percent' => $percent,
'remaining_minutes' => max(0, (int)ceil(($stop - $now) / 60)),
'next_title' => $next['title'] ?? '',
'next_subtitle' => $next['subtitle'] ?? '',
'next_start' => isset($next['start']) ? (int)$next['start'] : null,
'next_stop' => isset($next['stop']) ? (int)$next['stop'] : null,
'next_category' => $next['category'] ?? '',
];
break;
@@ -191,4 +212,57 @@ class ProgrammeService
return $items;
}
private function getGenreIcon(string $category): string
{
$category = mb_strtolower($category);
return match (true) {
str_contains($category, 'film'),
str_contains($category, 'movie'),
str_contains($category, 'kino') => '🎬',
str_contains($category, 'serie'),
str_contains($category, 'series') => '📺',
str_contains($category, 'sport'),
str_contains($category, 'football'),
str_contains($category, 'fußball'),
str_contains($category, 'fussball') => '⚽',
str_contains($category, 'news'),
str_contains($category, 'nachrichten'),
str_contains($category, 'journal') => '📰',
str_contains($category, 'doku'),
str_contains($category, 'documentary') => '🌍',
str_contains($category, 'kinder'),
str_contains($category, 'kids'),
str_contains($category, 'children') => '🧸',
str_contains($category, 'musik'),
str_contains($category, 'music') => '🎵',
default => '▶',
};
}
private function detectQuality(array $programme): string
{
$text = mb_strtolower(
($programme['title'] ?? '') . ' ' .
($programme['subtitle'] ?? '') . ' ' .
($programme['description'] ?? '')
);
return match (true) {
str_contains($text, 'uhd'),
str_contains($text, '4k') => 'UHD',
str_contains($text, 'hd') => 'HD',
default => '',
};
}
}
+10 -3
View File
@@ -123,12 +123,13 @@ foreach ($dayProgrammes as $channel) {
</div>
<?php foreach ($dayProgrammes as $channel): ?>
<?php $channelLogoUrl = LogoService::getLogoUrl($channel['channel_id'] ?? $channel['id'] ?? ''); ?>
<div class="timeline-row">
<div class="timeline-channel">
<img
class="channel-logo"
src="<?= htmlspecialchars(LogoService::getLogoUrl($channel['channel_id'])) ?>"
src="<?= htmlspecialchars($channelLogoUrl) ?>"
alt="<?= htmlspecialchars($channel['channel_name']) ?>">
<span class="timeline-channel-name"><?= htmlspecialchars($channel['channel_name']) ?></span>
@@ -149,7 +150,7 @@ foreach ($dayProgrammes as $channel) {
<?php foreach ($channel['programmes'] as $programme): ?>
<div
class="timeline-programme <?= $programme['is_current'] ? 'is-current' : '' ?> <?= $programme['is_past'] ? 'is-past' : '' ?>"
class="timeline-programme <?= $programme['is_current'] ? 'is-current' : '' ?> <?= $programme['is_past'] ? 'is-past' : '' ?> genre-<?= htmlspecialchars(strtolower(preg_replace('/[^a-z0-9]+/i', '-', $programme['category'] ?: 'other'))) ?>"
data-programme-id="<?= htmlspecialchars($programme['id']) ?>"
data-live-programme
data-live-start="<?= (int)$programme['start'] ?>"
@@ -160,6 +161,11 @@ foreach ($dayProgrammes as $channel) {
"
title="<?= htmlspecialchars(date('H:i', $programme['start']) . ' - ' . date('H:i', $programme['stop']) . ' · ' . $programme['title']) ?>"
>
<?php if ($programme['is_current']): ?>
<em class="live-badge" data-live-badge>LIVE</em>
<?php else: ?>
<em class="live-badge" data-live-badge hidden>LIVE</em>
<?php endif; ?>
<strong><?= htmlspecialchars($programme['title']) ?></strong>
<span><?= date('H:i', $programme['start']) ?> - <?= date('H:i', $programme['stop']) ?></span>
</div>
@@ -173,13 +179,14 @@ foreach ($dayProgrammes as $channel) {
<div class="day-list">
<?php foreach ($dayProgrammes as $channel): ?>
<?php $channelLogoUrl = LogoService::getLogoUrl($channel['channel_id'] ?? $channel['id'] ?? ''); ?>
<section class="day-channel">
<div class="day-channel-header">
<div>
<span class="channel-number"><?= (int)$channel['channel_number'] ?></span>
<img
class="channel-logo"
src="<?= htmlspecialchars(LogoService::getLogoUrl($channel['channel_id'])) ?>"
src="<?= htmlspecialchars($channelLogoUrl) ?>"
alt="<?= htmlspecialchars($channel['channel_name']) ?>">
<strong><?= htmlspecialchars($channel['channel_name']) ?></strong>
+85 -17
View File
@@ -21,16 +21,59 @@ $isFavoritesView = ($view ?? '') === 'favorites';
</div>
<?php endif; ?>
<div class="now-grid" data-live-now-grid>
<div class="now-grid now-grid-enhanced" data-live-now-grid>
<?php foreach ($currentProgrammes as $item): ?>
<?php $logoUrl = LogoService::getLogoUrl($item['channel_id']); ?>
<article class="now-card">
<div class="now-channel">
<img class="mini-channel-logo" src="<?= htmlspecialchars($logoUrl) ?>" alt="">
<span><?= htmlspecialchars($item['channel_name']) ?></span>
<?php if ($item['favorite']): ?>
<span class="favorite-star">⭐</span>
<?php endif; ?>
<?php
$logoUrl = LogoService::getLogoUrl($item['channel_id']);
$badges = [];
if (!empty($item['category'])) {
$badges[] = ($item['genre_icon'] ?? '▶') . ' ' . $item['category'];
}
if (!empty($item['year'])) {
$badges[] = $item['year'];
}
if (!empty($item['rating'])) {
$badges[] = 'FSK ' . $item['rating'];
}
if (!empty($item['quality'])) {
$badges[] = $item['quality'];
}
?>
<article class="now-card now-card-enhanced">
<div class="now-card-top">
<div class="now-channel-logo-wrap">
<img class="now-channel-logo"
src=" <?= htmlspecialchars($logoUrl) ?>"
alt="">
</div>
<div class="now-channel-info">
<div class="now-channel-name">
<?= htmlspecialchars($item['channel_name']) ?>
<?php if ($item['favorite']): ?>
<span class="favorite-star">⭐ </span>
<?php endif; ?>
</div>
<div class="now-channel-number">
Kanal <?= (int)$item['channel_number'] ?>
</div>
<div class="now-time">
<?= date('H:i', $item['start']) ?>
<?= date('H:i', $item['stop']) ?>
</div>
</div>
</div>
<h3><?= htmlspecialchars($item['title']) ?></h3>
@@ -39,25 +82,50 @@ $isFavoritesView = ($view ?? '') === 'favorites';
<div class="now-subtitle"><?= htmlspecialchars($item['subtitle']) ?></div>
<?php endif; ?>
<?php if ($item['category']): ?>
<span class="category"><?= htmlspecialchars($item['category']) ?></span>
<?php if ($badges): ?>
<div class="now-badges">
<?php foreach ($badges as $badge): ?>
<span class="now-badge"><?= htmlspecialchars($badge) ?></span>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="progress-wrap">
<div class="progress-meta">
<span><?= date('H:i', $item['start']) ?></span>
<span><?= date('H:i', $item['stop']) ?></span>
<span><?= (int)$item['percent'] ?>%</span>
<span>noch <?= (int)$item['remaining_minutes'] ?> Minuten</span>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: <?= (int)$item['percent'] ?>%"></div>
</div>
<div class="remaining">
<?= (int)$item['percent'] ?>% · noch <?= (int)$item['remaining_minutes'] ?> Minuten
</div>
</div>
<?php if (!empty($item['next_title'])): ?>
<div class="now-next">
<div class="now-next-label">Danach</div>
<div class="now-next-programme">
<div class="now-next-time">
<?= !empty($item['next_start']) ? date('H:i', $item['next_start']) : '' ?>
</div>
<div class="now-next-info">
<strong><?= htmlspecialchars($item['next_title']) ?></strong>
<?php if (!empty($item['next_subtitle'])): ?>
<div class="now-next-subtitle">
<?= htmlspecialchars($item['next_subtitle']) ?>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($item['description']): ?>
<p class="now-desc"><?= htmlspecialchars($item['description']) ?></p>
<?php endif; ?>