first commit
This commit is contained in:
+333
@@ -0,0 +1,333 @@
|
|||||||
|
<!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>
|
||||||
Reference in New Issue
Block a user