Files
2026-10-30 15:47:56 +01:00

334 lines
11 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Should I deploy today?</title>
<meta name="description" content="The only deployment policy you will ever need. Spoiler: Friday is the best day.">
<link rel="icon" id="favicon" href="data:,">
<style>
:root {
--bg: #1f2328;
--fg: #f5f5f5;
--muted: rgba(255, 255, 255, .65);
--accent: #ffffff;
}
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
background: var(--bg);
color: var(--fg);
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 16px;
overflow-x: hidden;
transition: background .4s ease;
}
body.friday {
background: linear-gradient(120deg, #ff006e, #fb5607, #ffbe0b, #3a86ff, #8338ec, #ff006e);
background-size: 400% 400%;
animation: party 6s ease infinite;
}
body.weekend { --bg: #2b6cb0; }
body.not-yet { --bg: #3d4451; }
@keyframes party {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
h3.question {
font-weight: 400;
font-size: clamp(1rem, 2.5vw, 1.4rem);
color: var(--muted);
margin: 0 0 .5em;
letter-spacing: .02em;
}
h1#answer {
font-size: clamp(3.5rem, 16vw, 11rem);
line-height: 1;
margin: 0;
font-weight: 900;
text-transform: uppercase;
letter-spacing: -.02em;
}
body.friday h1#answer {
text-shadow: 0 6px 0 rgba(0, 0, 0, .25);
animation: wobble 1.2s ease-in-out infinite;
}
@keyframes wobble {
0%, 100% { transform: rotate(-3deg) scale(1); }
50% { transform: rotate(3deg) scale(1.08); }
}
#reason {
font-size: clamp(1.1rem, 3.2vw, 2rem);
max-width: 42ch;
margin: .8em auto 1.2em;
min-height: 2.6em;
}
#countdown {
color: var(--muted);
font-size: .95rem;
margin-bottom: 1.4em;
min-height: 1.2em;
}
button {
font: inherit;
font-size: 1rem;
padding: .8em 1.6em;
border-radius: 999px;
border: 2px solid currentColor;
background: transparent;
color: var(--fg);
cursor: pointer;
transition: transform .1s ease, background .2s ease;
}
button:hover { background: rgba(255, 255, 255, .12); }
button:active { transform: scale(.96); }
body.friday button {
background: #fff;
color: #111;
border-color: #fff;
font-weight: 700;
}
footer {
position: fixed;
bottom: 12px;
left: 0;
right: 0;
font-size: .8rem;
color: var(--muted);
padding: 0 16px;
}
footer a { color: inherit; }
.confetti {
position: fixed;
top: -2em;
font-size: 1.6rem;
pointer-events: none;
user-select: none;
animation: fall linear forwards;
z-index: 0;
}
@keyframes fall {
to { transform: translateY(110vh) rotate(720deg); }
}
main { position: relative; z-index: 1; }
@media (prefers-reduced-motion: reduce) {
body.friday, body.friday h1#answer { animation: none; }
.confetti { display: none; }
}
</style>
</head>
<body>
<main>
<h3 class="question">Should I deploy today?</h3>
<h1 id="answer">…</h1>
<h2 id="reason"></h2>
<div id="countdown"></div>
<button id="reload" type="button">Hit me with another reason</button>
</main>
<footer>
Ironically inspired by <a href="https://shouldideploy.today/" rel="noopener">shouldideploy.today</a>.
Not responsible for your weekend.
</footer>
<script>
const REASONS = {
friday: [
"It's Friday. Prod has been waiting for you all week.",
"Ship it. The on-call rotation needs some excitement.",
"Tests are just suggestions anyway.",
"Nobody will notice until Monday. That's basically a staging environment.",
"What could possibly go wrong? (Don't answer that.)",
"Deploy and log off. Let future you deal with it.",
"The best time to deploy was last Friday. The second best time is now.",
"Your weekend plans could use a pager alert or two.",
"Real engineers test in production.",
"git push --force and go grab a beer.",
"If it compiles, it ships.",
"Rollbacks are for people who make mistakes. You don't.",
"The SRE team has been bored. Give them a gift.",
"Merge without review. Trust is a core value.",
"Friday deploys build character.",
"YOLO is a valid deployment strategy.",
"Who needs a code freeze when you have confidence?",
"Database migration at 5pm? Bold. Beautiful. Do it.",
"The logs will still be there on Monday.",
"It works on my machine, and my machine is very convincing."
],
fridayLate: [
"It's after 4pm on a Friday. This is the golden hour. DEPLOY.",
"Peak deploy time. Push it and close the laptop.",
"Everyone's gone home. No witnesses. Ship it.",
"Late Friday deploy: the most efficient way to test your incident process.",
"Slack is quiet. Too quiet. Fix that with a deploy."
],
friday13: [
"Friday the 13th? Don't be picky. It's not like computers are superstitious.",
"Oh no, a number. Scary. Deploy.",
"Bad luck is just a missing test case. You don't write those anyway.",
"Black cats can't read stack traces. Ship it.",
"It's a date, not a code review. Stop looking for excuses.",
"Friday the 13th is only cursed for people who check the logs.",
"Superstition is for people with rollback plans."
],
notYet: [
"Nah. Hold that merge request until Friday.",
"Why deploy now when you could deploy on Friday?",
"Too early in the week. Where's the thrill?",
"A deploy on a weekday? How boring. Wait for Friday.",
"Let it marinate. Friday will make it taste better.",
"Stash it. Friday-you will be thrilled.",
"Deploying today is way too responsible. Wait for Friday.",
"Keep your powder dry. Friday is coming."
],
thursday: [
"Almost! One more sleep until deploy day.",
"So close. Just wait until tomorrow.",
"Thursday deploys are for cowards. Friday is tomorrow.",
"Hold the line. Friday is a few hours away."
],
saturday: [
"It's still Friday somewhere. Probably. YES.",
"You missed Friday. Deploy anyway, it's basically the same vibe.",
"Weekend deploy? Respect. Friday energy detected."
],
sunday: [
"Sure, why not. But honestly, you should have done it on Friday.",
"Sunday deploys are like Friday deploys with extra guilt. Go.",
"Monday-you won't know what hit them. Ship it."
]
};
const ANSWERS = {
friday: ["YES!!!", "HELL YES", "ABSOLUTELY", "SHIP IT", "YOLO", "DO IT"],
fridayLate: ["DEPLOY NOW", "YES YES YES", "GO GO GO"],
friday13: ["Obviously", "Duh", "Still YES", "So what?"],
notYet: ["Nope", "Not yet", "Meh", "Wait"],
thursday: ["Tomorrow", "Almost"],
saturday: ["Sure!"],
sunday: ["Fine."]
};
const params = new URLSearchParams(location.search);
// ?date=2026-10-30 or ?day=friday to preview another day, ?hour=17 to fake the time
function now() {
const d = new Date();
if (params.has("date")) {
const forced = new Date(params.get("date") + "T12:00:00");
if (!isNaN(forced)) d.setFullYear(forced.getFullYear(), forced.getMonth(), forced.getDate());
}
if (params.has("day")) {
const days = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
const target = days.indexOf(params.get("day").slice(0, 3).toLowerCase());
if (target >= 0) d.setDate(d.getDate() + ((target - d.getDay() + 7) % 7));
}
if (params.has("hour")) d.setHours(parseInt(params.get("hour"), 10) || 0, 0, 0);
return d;
}
function mood(d) {
const day = d.getDay();
if (day === 5 && d.getDate() === 13) return "friday13";
if (day === 5 && d.getHours() >= 16) return "fridayLate";
if (day === 5) return "friday";
if (day === 4) return "thursday";
if (day === 6) return "saturday";
if (day === 0) return "sunday";
return "notYet";
}
function pick(list, previous) {
if (list.length < 2) return list[0];
let choice;
do { choice = list[Math.floor(Math.random() * list.length)]; } while (choice === previous);
return choice;
}
function setFavicon(emoji) {
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">${emoji}</text></svg>`;
document.getElementById("favicon").href = "data:image/svg+xml," + encodeURIComponent(svg);
}
function countdown(d) {
const el = document.getElementById("countdown");
if (d.getDay() === 5) {
el.textContent = "🚀 Today is deploy day. Your on-call buddy thanks you in advance.";
return;
}
const friday = new Date(d);
friday.setDate(d.getDate() + ((5 - d.getDay() + 7) % 7));
friday.setHours(0, 0, 0, 0);
const hours = Math.ceil((friday - d) / 36e5);
el.textContent = `⏳ ${hours} hour${hours === 1 ? "" : "s"} until the next legitimate deploy window.`;
}
let confettiTimer = null;
function confetti(on) {
clearInterval(confettiTimer);
if (!on || matchMedia("(prefers-reduced-motion: reduce)").matches) return;
const bits = ["🚀", "🎉", "🔥", "🍻", "💥", "🎊", "🐛"];
confettiTimer = setInterval(() => {
const span = document.createElement("span");
span.className = "confetti";
span.textContent = bits[Math.floor(Math.random() * bits.length)];
span.style.left = Math.random() * 100 + "vw";
span.style.animationDuration = 3 + Math.random() * 4 + "s";
span.addEventListener("animationend", () => span.remove());
document.body.appendChild(span);
}, 180);
}
let lastReason = null;
function render() {
const d = now();
const m = mood(d);
const isFriday = m.startsWith("friday");
const isWeekend = m === "saturday" || m === "sunday";
document.body.className = isFriday ? "friday" : isWeekend ? "weekend" : "not-yet";
document.getElementById("answer").textContent = pick(ANSWERS[m]);
// Fridays get the full catalogue on top of their own lines
const pool = isFriday && m !== "friday" ? REASONS[m].concat(REASONS.friday) : REASONS[m];
lastReason = pick(pool, lastReason);
document.getElementById("reason").textContent = lastReason;
setFavicon(isFriday ? "🚀" : isWeekend ? "🤷" : "⏳");
countdown(d);
confetti(isFriday);
}
document.getElementById("reload").addEventListener("click", render);
render();
</script>
</body>
</html>