Initial commit
This commit is contained in:
279
main.js
Normal file
279
main.js
Normal file
@@ -0,0 +1,279 @@
|
||||
// ========= Customization =========
|
||||
// Add any GIFs you want to show in order.
|
||||
const GIF_URLS = [
|
||||
"https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExM3hlZGx4dmdubzgyeG5sN3l4ZHVpY3htcW9pZHY0Yzhoc3JzaDczayZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/IVK6xNBpEAHYyOdghk/giphy.gif",
|
||||
"https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExMWI4N3JuZDY2ZHlzdGF4eG9zbmV6c215em1ndHFzYnY0Mm01YWliaSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/5NzRr5INmnXiG9xrFy/giphy.gif",
|
||||
"https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExcTQyM2RqZnpwa2NwaDNnZzJzZnpxN25kMThpajl1cjYwODh0dHJ5dyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/964WRtxBbYHzln3uN5/giphy.gif", // FLowers in hand
|
||||
"https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExYnhlczQ2OW4zcGE1NGFwdjJmdDk0bHpwejg0cjdtYmN6OW4wOTJpdyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/lfa5Bq6Am8Pf3a5AWU/giphy.gif", // YOu know I love you
|
||||
"https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExM3dram11MHdpMjM1dWF5dnlkZ3E0bjQyOHc5cHZ0dXNhdG1rNmF6dyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/9KI8vyCCAOqHbcMYd2/giphy.gif", // Mexican
|
||||
"https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExeDN5c3BxcDA4OW83Y2RzNmcxcms1cGwxdnpjaXF3aGpxZ2U2aWtmcSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/zEtQ9FrbqIt3BVKY3i/giphy.gif"
|
||||
];
|
||||
// =================================
|
||||
|
||||
const gifImg = document.getElementById("gifImg");
|
||||
gifImg.src = GIF_URLS[0];
|
||||
|
||||
const yesBtn = document.getElementById("yesBtn");
|
||||
const noBtn = document.getElementById("noBtn");
|
||||
const subtitle = document.getElementById("subtitle");
|
||||
const btnRow = document.getElementById("btnRow");
|
||||
|
||||
const askScreen = document.getElementById("askScreen");
|
||||
const successScreen = document.getElementById("successScreen");
|
||||
|
||||
// 5 stages of "No" presses
|
||||
const noStages = [
|
||||
"I think you accidentally pressed no",
|
||||
"Are you sure?",
|
||||
"Really sure?",
|
||||
"Please? Pretty please?",
|
||||
"Last chance... you sure you want to say no?",
|
||||
];
|
||||
|
||||
let noCount = 0;
|
||||
|
||||
function clamp(v, min, max) {
|
||||
return Math.max(min, Math.min(max, v));
|
||||
}
|
||||
|
||||
function setButtonWidths() {
|
||||
// Adjust width instead of transform scaling to avoid jumpy layout on phones.
|
||||
const stage = Math.min(noCount, 4);
|
||||
const rowWidth = btnRow.clientWidth || window.innerWidth * 0.9;
|
||||
const rowStyle = getComputedStyle(btnRow);
|
||||
const gap = Number.parseFloat(rowStyle.columnGap || rowStyle.gap || "0") || 0;
|
||||
const perButtonMax = Math.max(120, (rowWidth - gap) / 2);
|
||||
|
||||
const baseWidth = Math.min(172, perButtonMax * 0.92);
|
||||
const yesWidth = clamp(baseWidth + stage * 18, 110, perButtonMax);
|
||||
const noWidth = clamp(baseWidth - stage * 14, 84, perButtonMax);
|
||||
|
||||
document.documentElement.style.setProperty("--yes-width", `${Math.round(yesWidth)}px`);
|
||||
document.documentElement.style.setProperty("--no-width", `${Math.round(noWidth)}px`);
|
||||
}
|
||||
|
||||
function showSuccess() {
|
||||
askScreen.style.display = "none";
|
||||
successScreen.style.display = "block";
|
||||
}
|
||||
|
||||
yesBtn.addEventListener("click", () => showSuccess());
|
||||
|
||||
function advanceNoStage() {
|
||||
if (noCount < 5) noCount++;
|
||||
|
||||
// Update the GIF based on the current "no" count
|
||||
gifImg.src = GIF_URLS[noCount];
|
||||
console.log(noCount);
|
||||
|
||||
const stageIndex = Math.min(noCount - 1, 4);
|
||||
subtitle.textContent = noStages[stageIndex] || "";
|
||||
|
||||
setButtonWidths();
|
||||
|
||||
// Enter final stage after 5th "no" attempt
|
||||
if (noCount >= 5) enableNoEvadeMode();
|
||||
}
|
||||
|
||||
noBtn.addEventListener("click", () => {
|
||||
if (noCount < 5) {
|
||||
advanceNoStage();
|
||||
} else {
|
||||
// In evade mode, clicking should be basically impossible, but just in case:
|
||||
teleportNoButton();
|
||||
}
|
||||
});
|
||||
|
||||
// Final stage: make it impossible to click No by moving away on hover and pointerdown
|
||||
let evadeEnabled = false;
|
||||
|
||||
function getViewportBounds() {
|
||||
const vv = window.visualViewport;
|
||||
if (vv) {
|
||||
return {
|
||||
left: vv.offsetLeft,
|
||||
top: vv.offsetTop,
|
||||
width: vv.width,
|
||||
height: vv.height
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
};
|
||||
}
|
||||
|
||||
function enableNoEvadeMode() {
|
||||
if (evadeEnabled) return;
|
||||
evadeEnabled = true;
|
||||
|
||||
noBtn.textContent = "No";
|
||||
subtitle.textContent = noStages[4];
|
||||
|
||||
// Make the No button position fixed so it can teleport around the viewport
|
||||
noBtn.style.position = "fixed";
|
||||
noBtn.style.left = "";
|
||||
noBtn.style.top = "";
|
||||
|
||||
// Start it near the bottom center
|
||||
const bounds = getViewportBounds();
|
||||
const startX = Math.round(bounds.left + bounds.width * 0.5);
|
||||
const startY = Math.round(bounds.top + bounds.height * 0.72);
|
||||
placeNoButton(startX, startY);
|
||||
|
||||
// Evade on hover and on press
|
||||
noBtn.addEventListener("pointerenter", teleportNoButton, { passive: true });
|
||||
noBtn.addEventListener("pointerdown", teleportNoButton, { passive: true });
|
||||
|
||||
// If the viewport changes, keep it clamped inside the visible phone screen.
|
||||
window.addEventListener("resize", keepNoButtonOnScreen, { passive: true });
|
||||
if (window.visualViewport) {
|
||||
window.visualViewport.addEventListener("resize", keepNoButtonOnScreen, { passive: true });
|
||||
window.visualViewport.addEventListener("scroll", keepNoButtonOnScreen, { passive: true });
|
||||
}
|
||||
}
|
||||
|
||||
function placeNoButton(centerX, centerY) {
|
||||
const rect = noBtn.getBoundingClientRect();
|
||||
const pad = 12;
|
||||
const bounds = getViewportBounds();
|
||||
|
||||
const minX = bounds.left + pad;
|
||||
const minY = bounds.top + pad;
|
||||
const maxX = Math.max(minX, bounds.left + bounds.width - rect.width - pad);
|
||||
const maxY = Math.max(minY, bounds.top + bounds.height - rect.height - pad);
|
||||
|
||||
const x = clamp(centerX - rect.width / 2, minX, maxX);
|
||||
const y = clamp(centerY - rect.height / 2, minY, maxY);
|
||||
|
||||
noBtn.style.left = Math.round(x) + "px";
|
||||
noBtn.style.top = Math.round(y) + "px";
|
||||
}
|
||||
|
||||
function keepNoButtonOnScreen() {
|
||||
if (!evadeEnabled) return;
|
||||
const rect = noBtn.getBoundingClientRect();
|
||||
placeNoButton(rect.left + rect.width / 2, rect.top + rect.height / 2);
|
||||
}
|
||||
|
||||
function teleportNoButton() {
|
||||
if (!evadeEnabled) return;
|
||||
|
||||
// "Far away" point each time: jump to a different viewport region
|
||||
const bounds = getViewportBounds();
|
||||
const left = bounds.left;
|
||||
const top = bounds.top;
|
||||
const w = bounds.width;
|
||||
const h = bounds.height;
|
||||
|
||||
const candidates = [
|
||||
{ x: left + w * 0.16, y: top + h * 0.22 },
|
||||
{ x: left + w * 0.84, y: top + h * 0.25 },
|
||||
{ x: left + w * 0.18, y: top + h * 0.78 },
|
||||
{ x: left + w * 0.82, y: top + h * 0.78 },
|
||||
{ x: left + w * 0.5, y: top + h * 0.18 }
|
||||
];
|
||||
|
||||
const pick = candidates[Math.floor(Math.random() * candidates.length)];
|
||||
placeNoButton(pick.x, pick.y);
|
||||
}
|
||||
|
||||
// Start with neutral stage
|
||||
setButtonWidths();
|
||||
window.addEventListener("resize", setButtonWidths, { passive: true });
|
||||
|
||||
// ========= Sakura falling leaves (canvas) =========
|
||||
const canvas = document.getElementById("sakura");
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
function resizeCanvas() {
|
||||
const dpr = Math.max(1, Math.min(2, window.devicePixelRatio || 1));
|
||||
canvas.width = Math.floor(window.innerWidth * dpr);
|
||||
canvas.height = Math.floor(window.innerHeight * dpr);
|
||||
canvas.style.width = window.innerWidth + "px";
|
||||
canvas.style.height = window.innerHeight + "px";
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
}
|
||||
|
||||
resizeCanvas();
|
||||
window.addEventListener("resize", resizeCanvas, { passive: true });
|
||||
|
||||
const petals = [];
|
||||
const PETAL_COUNT = Math.round(Math.min(90, Math.max(40, window.innerWidth / 10)));
|
||||
|
||||
function rand(min, max) {
|
||||
return min + Math.random() * (max - min);
|
||||
}
|
||||
|
||||
function makePetal() {
|
||||
const size = rand(6, 14);
|
||||
return {
|
||||
x: rand(0, window.innerWidth),
|
||||
y: rand(-window.innerHeight, 0),
|
||||
vx: rand(-0.35, 0.55),
|
||||
vy: rand(0.8, 1.8),
|
||||
rot: rand(0, Math.PI * 2),
|
||||
vr: rand(-0.02, 0.02),
|
||||
wobble: rand(0.8, 1.8),
|
||||
size,
|
||||
hue: rand(330, 350), // pink range
|
||||
alpha: rand(0.55, 0.9)
|
||||
};
|
||||
}
|
||||
|
||||
for (let i = 0; i < PETAL_COUNT; i++) petals.push(makePetal());
|
||||
|
||||
function drawPetal(p) {
|
||||
ctx.save();
|
||||
ctx.translate(p.x, p.y);
|
||||
ctx.rotate(p.rot);
|
||||
|
||||
// Sakura-like petal shape
|
||||
const s = p.size;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, -s * 0.9);
|
||||
ctx.bezierCurveTo(s * 0.9, -s * 0.9, s * 0.95, s * 0.2, 0, s);
|
||||
ctx.bezierCurveTo(-s * 0.95, s * 0.2, -s * 0.9, -s * 0.9, 0, -s * 0.9);
|
||||
ctx.closePath();
|
||||
|
||||
ctx.fillStyle = `hsla(${p.hue}, 90%, 82%, ${p.alpha})`;
|
||||
ctx.fill();
|
||||
|
||||
// soft highlight
|
||||
ctx.globalAlpha = p.alpha * 0.45;
|
||||
ctx.strokeStyle = "rgba(255,255,255,0.55)";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
let lastT = performance.now();
|
||||
|
||||
function tick(t) {
|
||||
const dt = Math.min(33, t - lastT);
|
||||
lastT = t;
|
||||
|
||||
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
|
||||
|
||||
for (const p of petals) {
|
||||
p.x += p.vx * dt * 0.06 + Math.sin((p.y / 40) * p.wobble) * 0.2;
|
||||
p.y += p.vy * dt * 0.06;
|
||||
p.rot += p.vr * dt;
|
||||
|
||||
if (p.y > window.innerHeight + 30 || p.x < -40 || p.x > window.innerWidth + 40) {
|
||||
// respawn from top
|
||||
p.x = rand(0, window.innerWidth);
|
||||
p.y = rand(-120, -20);
|
||||
}
|
||||
|
||||
drawPetal(p);
|
||||
}
|
||||
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
requestAnimationFrame(tick);
|
||||
Reference in New Issue
Block a user