Initial commit
This commit is contained in:
150
static/css/styles.css
Normal file
150
static/css/styles.css
Normal file
@@ -0,0 +1,150 @@
|
||||
|
||||
:root {
|
||||
--accent: #8b5cf6;
|
||||
--bg: #0b0b10;
|
||||
--fg: #e5e7eb;
|
||||
--radius: 16px;
|
||||
--font: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: var(--font);
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
html[data-scheme="dark"] body {
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
html[data-scheme="light"] body {
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.container {
|
||||
min-height: 100%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: min(900px, 95vw);
|
||||
background: color-mix(in oklab, var(--bg), white 7%);
|
||||
border: 1px solid color-mix(in oklab, var(--bg), white 15%);
|
||||
border-radius: var(--radius);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.shadow-sm { box-shadow: 0 2px 10px rgba(0,0,0,.15); }
|
||||
.shadow-md { box-shadow: 0 10px 25px rgba(0,0,0,.2); }
|
||||
.shadow-lg { box-shadow: 0 30px 60px rgba(0,0,0,.25); }
|
||||
|
||||
h1.title {
|
||||
font-family: inherit;
|
||||
font-size: clamp(2rem, 4vw + 1rem, 4rem);
|
||||
line-height: 1.1;
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-top: 0;
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
.countdown {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: minmax(0, 1fr);
|
||||
gap: 1rem;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.countdown .unit {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 1rem;
|
||||
border-radius: calc(var(--radius) - 6px);
|
||||
border: 1px solid color-mix(in oklab, var(--bg), white 15%);
|
||||
background: color-mix(in oklab, var(--bg), white 5%);
|
||||
}
|
||||
|
||||
.countdown .unit span {
|
||||
font-family: inherit;
|
||||
font-size: clamp(1.8rem, 5vw + 0.5rem, 4rem);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.countdown .unit label {
|
||||
font-family: inherit;
|
||||
opacity: .7;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1rem 1.25rem;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.field input[type="text"],
|
||||
.field input[type="datetime-local"],
|
||||
.field input[type="number"],
|
||||
.field select,
|
||||
.field input[type="color"] {
|
||||
padding: 0.6rem 0.75rem;
|
||||
border-radius: calc(var(--radius) - 10px);
|
||||
border: 1px solid color-mix(in oklab, var(--bg), white 20%);
|
||||
background: color-mix(in oklab, var(--bg), white 7%);
|
||||
color: inherit;
|
||||
font-family: var(--font);
|
||||
}
|
||||
|
||||
.actions {
|
||||
grid-column: 1 / -1;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
button, .actions button, .share a, .share button {
|
||||
font-size: inherit;
|
||||
appearance: none;
|
||||
border: 0;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: calc(var(--radius) - 8px);
|
||||
background: var(--accent);
|
||||
color: var(--fg);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button:hover, .share a:hover, .share button:hover {
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
|
||||
.muted {
|
||||
opacity: .8;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.share {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.grid { grid-template-columns: 1fr; }
|
||||
.countdown { grid-template-columns: repeat(2, minmax(0,1fr)); }
|
||||
}
|
||||
96
static/js/countdown.js
Normal file
96
static/js/countdown.js
Normal file
@@ -0,0 +1,96 @@
|
||||
|
||||
(function() {
|
||||
const $ = (sel) => document.querySelector(sel);
|
||||
const qs = new URLSearchParams(location.search);
|
||||
|
||||
const targetISO = qs.get("target") || ($("#countdown")?.dataset.target || "");
|
||||
const showMillis = (qs.get("millis") ?? ($("#countdown")?.dataset.showMillis || "0")) === "1";
|
||||
const roundedUnit = qs.get("rounded_unit") || ($("#countdown")?.dataset.roundedUnit || "none");
|
||||
|
||||
const targetDate = targetISO ? new Date(targetISO) : null;
|
||||
const targetText = $("#targetText");
|
||||
const statusEl = $("#status");
|
||||
const msWrap = $("#millisWrap");
|
||||
|
||||
// Reflect chosen colors into CSS variables if passed via query
|
||||
const root = document.documentElement;
|
||||
const setVar = (k, v) => { if (v) root.style.setProperty(k, v); };
|
||||
setVar("--accent", qs.get("accent"));
|
||||
setVar("--radius", (qs.get("radius") || "16") + "px");
|
||||
setVar("--bg", qs.get("bg"));
|
||||
setVar("--fg", qs.get("fg"));
|
||||
const scheme = qs.get("scheme");
|
||||
if (scheme) root.setAttribute("data-scheme", scheme);
|
||||
const font = qs.get("font");
|
||||
if (font) root.style.setProperty("--font", font.includes(" ") ? `"${font}"` : font);
|
||||
|
||||
if (!targetDate || isNaN(targetDate.valueOf())) {
|
||||
statusEl.textContent = "Invalid or missing target date. Use the Edit link to set one.";
|
||||
return;
|
||||
}
|
||||
|
||||
targetText.textContent = targetDate.toString();
|
||||
|
||||
msWrap.hidden = !showMillis;
|
||||
if (!showMillis && msWrap.parentNode) { msWrap.parentNode.removeChild(msWrap); }
|
||||
|
||||
function roundToUnit(ms, unit) {
|
||||
if (unit === "none") return ms;
|
||||
const map = {
|
||||
minutes: 60_000,
|
||||
hours: 3_600_000,
|
||||
days: 86_400_000,
|
||||
};
|
||||
const size = map[unit] || 1;
|
||||
return Math.round(ms / size) * size;
|
||||
}
|
||||
|
||||
function tick() {
|
||||
const now = new Date();
|
||||
let diff = targetDate - now;
|
||||
if (roundedUnit !== "none") {
|
||||
diff = roundToUnit(diff, roundedUnit);
|
||||
}
|
||||
if (diff <= 0) {
|
||||
$("#d").textContent = "0";
|
||||
$("#h").textContent = "0";
|
||||
$("#m").textContent = "0";
|
||||
$("#s").textContent = "0";
|
||||
if (showMillis) $("#ms").textContent = "000";
|
||||
statusEl.textContent = "🎉 It's time!";
|
||||
return;
|
||||
}
|
||||
|
||||
const totalSeconds = Math.floor(diff / 1000);
|
||||
const days = Math.floor(totalSeconds / (3600*24));
|
||||
const hours = Math.floor((totalSeconds % (3600*24)) / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
const millis = diff % 1000;
|
||||
|
||||
$("#d").textContent = String(days);
|
||||
$("#h").textContent = String(hours).padStart(2, "0");
|
||||
$("#m").textContent = String(minutes).padStart(2, "0");
|
||||
$("#s").textContent = String(seconds).padStart(2, "0");
|
||||
if (showMillis) $("#ms").textContent = String(millis).padStart(3, "0");
|
||||
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
// Shareable link
|
||||
const copyBtn = $("#copyLink");
|
||||
if (copyBtn) {
|
||||
copyBtn.addEventListener("click", async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(location.href);
|
||||
copyBtn.textContent = "Link copied!";
|
||||
setTimeout(() => copyBtn.textContent = "Copy sharable link", 1500);
|
||||
} catch (e) {
|
||||
alert("Copy failed: " + e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Start
|
||||
requestAnimationFrame(tick);
|
||||
})();
|
||||
Reference in New Issue
Block a user