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