404 lines
14 KiB
Plaintext
404 lines
14 KiB
Plaintext
<!--
|
|
|
|
This Source Code Form is subject to the terms of the GNU General Public License:
|
|
|
|
Copyright (C) 2021-2025 Poke (https://codeberg.org/ashley/poke)
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see https://www.gnu.org/licenses/.
|
|
--->
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
|
|
<link rel="manifest" href="/manifest.json" />
|
|
<link rel="icon" href="css/yt-ukraine.svg" type="image/svg+xml" />
|
|
<meta name="theme-color" content="#0c0c0c" />
|
|
<meta name="description" content="Poke! Calendar — zero-JS calendar" />
|
|
<meta property="og:title" content="Poke! Calendar" />
|
|
<meta property="og:description" content="Navigate months without JavaScript needed" />
|
|
<meta property="og:image" content="https://cdn.glitch.global/d68d17bb-f2c0-4bc3-993f-50902734f652/aa70111e-5bcd-4379-8b23-332a33012b78.image.png?v=1701898829884" />
|
|
<meta property="og:type" content="website" />
|
|
<meta name="twitter:card" content="summary_large_image" />
|
|
<meta name="twitter:title" content="Poke! Calendar" />
|
|
<meta name="twitter:image" content="https://cdn.glitch.global/d68d17bb-f2c0-4bc3-993f-50902734f652/aa70111e-5bcd-4379-8b23-332a33012b78.image.png?v=1701898829884" />
|
|
<title>Poke! Calendar</title>
|
|
|
|
<style>
|
|
:root {
|
|
/* Color system */
|
|
--bg: #0c0c0c;
|
|
--panel: #141414;
|
|
--panel-2: #1a1a1a;
|
|
--border: #2a2a2a;
|
|
--text: #ececec;
|
|
--text-dim: #b8b8b8;
|
|
--accent: #b388ff; /* Accessible on dark (~4.8:1 on #141414) */
|
|
--accent-strong: #a071ff;
|
|
--accent-weak: #d1baff;
|
|
--today: #2a1a5e; /* Indigo-ish base to avoid pure black crush */
|
|
--today-ring: #c8a8ff;
|
|
--weekend: #121212;
|
|
--shadow: 0 10px 30px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.02);
|
|
|
|
/* Radii & spacing */
|
|
--r-sm: 8px;
|
|
--r-md: 12px;
|
|
--r-lg: 16px;
|
|
--space-1: .25rem;
|
|
--space-2: .5rem;
|
|
--space-3: .75rem;
|
|
--space-4: 1rem;
|
|
--space-5: 1.25rem;
|
|
--space-6: 1.5rem;
|
|
--space-7: 2rem;
|
|
|
|
/* Typography */
|
|
--font: system-ui, -apple-system, Segoe UI, Roboto, Inter, Ubuntu, Cantarell, Noto Sans, Arial, "Apple Color Emoji","Segoe UI Emoji";
|
|
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
|
|
|
/* Effects */
|
|
--blur: 14px;
|
|
--glass-bg: rgba(18,18,18,.75);
|
|
--glass-edge: rgba(255,255,255,.06);
|
|
|
|
color-scheme: dark;
|
|
}
|
|
|
|
@media (prefers-color-scheme: light) {
|
|
:root {
|
|
--bg: #f6f6f7;
|
|
--panel: #ffffff;
|
|
--panel-2: #fafafa;
|
|
--border: #e7e7ea;
|
|
--text: #1b1b1f;
|
|
--text-dim: #565662;
|
|
--accent: #6b4bff;
|
|
--accent-strong: #5b3cf4;
|
|
--accent-weak: #bfb6ff;
|
|
--today: #e8e5ff;
|
|
--today-ring: #5b3cf4;
|
|
--weekend: #fbfbfe;
|
|
--shadow: 0 8px 24px rgba(0,0,0,.08), inset 0 1px 0 rgba(255,255,255,.8);
|
|
--glass-bg: rgba(255,255,255,.75);
|
|
--glass-edge: rgba(0,0,0,.06);
|
|
}
|
|
}
|
|
|
|
/* Reset-ish */
|
|
*, *::before, *::after { box-sizing: border-box; }
|
|
html, body { height: 100%; }
|
|
body {
|
|
margin: 0;
|
|
background: var(--bg) url('/css/background.jpg') center/cover fixed no-repeat;
|
|
color: var(--text);
|
|
font-family: var(--font);
|
|
line-height: 1.5;
|
|
min-height: 100vh;
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
text-rendering: optimizeLegibility;
|
|
}
|
|
|
|
/* Frosted back layer with graceful fallback */
|
|
body::before {
|
|
content: "";
|
|
position: fixed;
|
|
inset: 0;
|
|
background: var(--bg) radial-gradient(1200px 800px at 10% -10%, rgba(255,255,255,.06), transparent 60%) no-repeat;
|
|
filter: brightness(.9) blur(16px);
|
|
z-index: -2;
|
|
}
|
|
@supports (backdrop-filter: blur(8px)) or (-webkit-backdrop-filter: blur(8px)) {
|
|
body::after {
|
|
content: "";
|
|
position: fixed;
|
|
inset: 0;
|
|
background: var(--glass-bg);
|
|
-webkit-backdrop-filter: blur(var(--blur)) saturate(130%);
|
|
backdrop-filter: blur(var(--blur)) saturate(130%);
|
|
border: 1px solid var(--glass-edge);
|
|
z-index: -1;
|
|
}
|
|
body::before { filter: none; }
|
|
}
|
|
|
|
/* Motion preferences */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
* { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; scroll-behavior: auto !important; }
|
|
}
|
|
|
|
/* Top nav */
|
|
.navbar {
|
|
position: sticky; top: 0; z-index: 10;
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: var(--space-4) var(--space-7);
|
|
background: linear-gradient(to bottom, rgba(0,0,0,.45), rgba(0,0,0,.25));
|
|
border-bottom: 1px solid var(--border);
|
|
box-shadow: var(--shadow);
|
|
backdrop-filter: blur(10px);
|
|
-webkit-backdrop-filter: blur(10px);
|
|
}
|
|
.navbar img { width: 8.5rem; height: auto; display: block; }
|
|
|
|
.years {
|
|
display: flex; gap: var(--space-5); flex-wrap: wrap; align-items: center;
|
|
font-feature-settings: "tnum" 1, "cv02" 1;
|
|
}
|
|
.years h2 {
|
|
font-size: .95rem; font-weight: 600; color: var(--accent);
|
|
letter-spacing: .2px;
|
|
}
|
|
|
|
/* Shell */
|
|
.container {
|
|
width: min(100% - 2*var(--space-7), 980px);
|
|
margin: var(--space-7) auto;
|
|
padding: clamp(1rem, 2vw + .5rem, 2rem);
|
|
background: linear-gradient(180deg, var(--panel), var(--panel-2));
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--r-lg);
|
|
box-shadow: var(--shadow);
|
|
}
|
|
|
|
.header-row {
|
|
display: flex; flex-wrap: wrap; gap: var(--space-4);
|
|
align-items: center; justify-content: space-between;
|
|
margin-bottom: var(--space-6);
|
|
}
|
|
|
|
.month-title {
|
|
font-size: clamp(1.25rem, 2.4vw, 2rem);
|
|
color: var(--accent);
|
|
font-weight: 800;
|
|
letter-spacing: .2px;
|
|
text-shadow: 0 0 1px rgba(0,0,0,.12);
|
|
}
|
|
|
|
/* Form controls */
|
|
.month-picker {
|
|
padding: .55rem .8rem;
|
|
font-size: 1rem;
|
|
color: var(--text);
|
|
background: var(--panel-2);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--r-md);
|
|
outline: none;
|
|
min-width: 12ch;
|
|
transition: border-color .2s ease, box-shadow .2s ease, transform .08s ease;
|
|
}
|
|
.month-picker:hover { border-color: var(--accent-weak); }
|
|
.month-picker:focus-visible {
|
|
border-color: var(--accent-strong);
|
|
box-shadow: 0 0 0 4px color-mix(in oklab, var(--accent-strong) 28%, transparent);
|
|
}
|
|
|
|
.month-button, .button {
|
|
padding: .65rem 1.1rem;
|
|
background: linear-gradient(180deg, var(--accent), var(--accent-strong));
|
|
color: #fff; text-decoration: none;
|
|
border-radius: var(--r-md);
|
|
border: 1px solid color-mix(in oklab, var(--accent-strong) 60%, #000);
|
|
cursor: pointer; font-weight: 700; letter-spacing: .15px;
|
|
transition: transform .08s ease, box-shadow .2s ease, filter .2s ease;
|
|
box-shadow: 0 6px 18px rgba(0,0,0,.25);
|
|
will-change: transform;
|
|
}
|
|
.month-button:hover, .button:hover { filter: brightness(1.05); }
|
|
.month-button:active, .button:active { transform: translateY(1px); }
|
|
.month-button:focus-visible, .button:focus-visible {
|
|
outline: none;
|
|
box-shadow: 0 0 0 4px color-mix(in oklab, var(--accent-strong) 28%, transparent), 0 6px 18px rgba(0,0,0,.25);
|
|
}
|
|
.month-button { margin-left: var(--space-3); }
|
|
|
|
/* Calendar table */
|
|
.calendar-table {
|
|
width: 100%;
|
|
border-collapse: separate;
|
|
border-spacing: 0;
|
|
table-layout: fixed;
|
|
overflow: clip;
|
|
border-radius: var(--r-md);
|
|
border: 1px solid var(--border);
|
|
background: var(--panel);
|
|
box-shadow: inset 0 1px 0 rgba(255,255,255,.03);
|
|
}
|
|
.calendar-table th,
|
|
.calendar-table td {
|
|
padding: clamp(.6rem, 1.4vw, 1rem);
|
|
text-align: center;
|
|
border-right: 1px solid var(--border);
|
|
border-bottom: 1px solid var(--border);
|
|
vertical-align: middle;
|
|
}
|
|
.calendar-table thead th {
|
|
position: sticky; top: 0; z-index: 1;
|
|
background: linear-gradient(180deg, var(--panel-2), var(--panel));
|
|
color: var(--accent-weak);
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
font-size: .82rem;
|
|
letter-spacing: .12em;
|
|
backdrop-filter: blur(6px);
|
|
-webkit-backdrop-filter: blur(6px);
|
|
}
|
|
.calendar-table tr:last-child td { border-bottom: 0; }
|
|
.calendar-table th:last-child, .calendar-table td:last-child { border-right: 0; }
|
|
|
|
.calendar-table td {
|
|
color: var(--text);
|
|
background: linear-gradient(180deg, color-mix(in oklab, var(--panel) 94%, #000), var(--panel));
|
|
font-variant-numeric: tabular-nums;
|
|
transition: background-color .15s ease, color .15s ease, box-shadow .15s ease;
|
|
}
|
|
|
|
/* Weekend tint */
|
|
.calendar-table td:nth-child(1),
|
|
.calendar-table td:nth-child(7) {
|
|
background: linear-gradient(180deg, var(--weekend), var(--panel));
|
|
}
|
|
|
|
/* Empty day cells */
|
|
.calendar-table td:empty {
|
|
background: repeating-linear-gradient(45deg, transparent, transparent 6px, rgba(255,255,255,.02) 6px, rgba(255,255,255,.02) 12px);
|
|
color: transparent;
|
|
}
|
|
|
|
/* Today highlight: fill + halo ring for accessibility */
|
|
.calendar-table td.today {
|
|
position: relative;
|
|
background: linear-gradient(180deg, color-mix(in oklab, var(--today) 85%, #000), var(--today));
|
|
color: #fff;
|
|
font-weight: 800;
|
|
text-shadow: 0 1px 0 rgba(0,0,0,.35);
|
|
box-shadow: inset 0 0 0 1px var(--accent-weak);
|
|
}
|
|
.calendar-table td.today::after {
|
|
content: "";
|
|
position: absolute; inset: -3px;
|
|
border-radius: 10px;
|
|
box-shadow: 0 0 0 3px color-mix(in oklab, var(--today-ring) 55%, transparent);
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* Hover affordance for interactive feel (non-empty cells) */
|
|
.calendar-table td:not(:empty):hover {
|
|
background: color-mix(in oklab, var(--panel) 85%, var(--accent) 15%);
|
|
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--accent-weak) 40%, transparent);
|
|
}
|
|
|
|
/* Navigation row */
|
|
.nav-links {
|
|
display: flex; justify-content: center; gap: var(--space-4);
|
|
margin-top: var(--space-6);
|
|
flex-wrap: wrap;
|
|
}
|
|
.nav-links .button {
|
|
min-width: 10rem; text-align: center;
|
|
}
|
|
|
|
/* Focus-visible for all links/buttons for keyboard users */
|
|
a:focus-visible {
|
|
outline: none;
|
|
box-shadow: 0 0 0 4px color-mix(in oklab, var(--accent-strong) 28%, transparent);
|
|
border-radius: var(--r-md);
|
|
}
|
|
|
|
/* Compact & mobile tuning */
|
|
@media (max-width: 840px) {
|
|
.navbar { padding: var(--space-3) var(--space-4); }
|
|
.years { gap: var(--space-3); }
|
|
.years h2 { font-size: .9rem; }
|
|
.container { width: calc(100% - 2*var(--space-4)); margin: var(--space-6) auto; }
|
|
}
|
|
@media (max-width: 720px) {
|
|
.calendar-table th, .calendar-table td { padding: .75rem .4rem; font-size: .9rem; }
|
|
.month-title { font-size: 1.4rem; }
|
|
.nav-links .button { width: 100%; }
|
|
}
|
|
@media (max-width: 420px) {
|
|
.years h2 { font-size: .82rem; }
|
|
.calendar-table th { font-size: .75rem; letter-spacing: .1em; }
|
|
.calendar-table td { padding: .6rem .25rem; }
|
|
}
|
|
|
|
/* Print-friendly: clean sheet */
|
|
@media print {
|
|
body::before, body::after, .navbar { display: none !important; }
|
|
body { background: #fff; color: #000; }
|
|
.container {
|
|
box-shadow: none; border: 1px solid #000; border-radius: 0;
|
|
}
|
|
.calendar-table { border-color: #000; }
|
|
.calendar-table th, .calendar-table td { border-color: #000; color: #000; }
|
|
.calendar-table td.today { outline: 2px solid #000; background: #fff; color: #000; text-shadow: none; }
|
|
.button, .month-button { display: none; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="navbar">
|
|
<a href="/143"><img src="/css/logo-poke.svg?v=5" alt="Poke Calendar Logo"></a>
|
|
<div class="years">
|
|
<h2>Gregorian Year: <%= year %></h2>
|
|
<h2>Islamic Year: <%= islamicYear %></h2>
|
|
<h2>Persian Year: <%= persianYear %></h2>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<div class="header-row">
|
|
<h2 class="month-title"><%= queryDate.toLocaleString('default', { month: 'long' }) %> <%= year %></h2>
|
|
<form action="/calendar" method="get" style="display:flex; align-items:center;">
|
|
<input type="month" name="date" value="<%= currentDate.toISOString().slice(0,7) %>" class="month-picker" />
|
|
<button type="submit" class="month-button">Go</button>
|
|
</form>
|
|
</div>
|
|
|
|
<table class="calendar-table" role="grid" aria-label="Monthly calendar">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col">Sun</th>
|
|
<th scope="col">Mon</th>
|
|
<th scope="col">Tue</th>
|
|
<th scope="col">Wed</th>
|
|
<th scope="col">Thu</th>
|
|
<th scope="col">Fri</th>
|
|
<th scope="col">Sat</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<% days.forEach((day, idx) => { %>
|
|
<% if (idx % 7 === 0) { %><tr><% } %>
|
|
<% const today = new Date(); %>
|
|
<% const isToday = day &&
|
|
day.getDate() === today.getDate() &&
|
|
day.getMonth() === today.getMonth() &&
|
|
day.getFullYear() === today.getFullYear(); %>
|
|
<td class="<%= isToday ? 'today' : '' %>"><%= day ? day.getDate() : '' %></td>
|
|
<% if (idx % 7 === 6) { %></tr><% } %>
|
|
<% }); %>
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="nav-links">
|
|
<a href="/calendar?date=<%= new Date(year, month - 1, 1).toISOString() %>" class="button" aria-label="Previous month">← Prev</a>
|
|
<a href="/calendar?date=<%= new Date(year, month + 1, 1).toISOString() %>" class="button" aria-label="Next month">Next →</a>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|