VALENTINE 1

This commit is contained in:
Flatlogic Bot 2026-02-05 17:28:30 +00:00
parent 1cb89e7ef8
commit 978955ec5b
5 changed files with 225 additions and 31 deletions

55
api/save_settings.php Normal file
View File

@ -0,0 +1,55 @@
<?php
require_once __DIR__ . '/../db/config.php';
header('Content-Type: application/json');
$action = $_POST['action'] ?? '';
if ($action === 'upload_image') {
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
// Check if locked
$stmt = db()->prepare("SELECT setting_value FROM settings WHERE setting_key = 'is_locked'");
$stmt->execute();
$isLocked = $stmt->fetchColumn();
if ($isLocked === '1') {
echo json_encode(['success' => false, 'error' => 'Settings are locked.']);
exit;
}
$uploadDir = __DIR__ . '/../assets/images/uploads/';
$fileName = 'valentine_' . time() . '_' . basename($_FILES['image']['name']);
$targetPath = $uploadDir . $fileName;
if (move_uploaded_file($_FILES['image']['tmp_name'], $targetPath)) {
$webPath = 'assets/images/uploads/' . $fileName;
$stmt = db()->prepare("UPDATE settings SET setting_value = ? WHERE setting_key = 'valentine_image'");
$stmt->execute([$webPath]);
echo json_encode(['success' => true, 'path' => $webPath]);
} else {
echo json_encode(['success' => false, 'error' => 'Failed to move uploaded file.']);
}
} else {
echo json_encode(['success' => false, 'error' => 'No file uploaded or upload error.']);
}
} elseif ($action === 'toggle_lock') {
$lockValue = $_POST['lock'] === 'true' ? '1' : '0';
$stmt = db()->prepare("UPDATE settings SET setting_value = ? WHERE setting_key = 'is_locked'");
$stmt->execute([$lockValue]);
echo json_encode(['success' => true, 'locked' => $lockValue === '1']);
} elseif ($action === 'reset') {
// We don't necessarily reset the image here unless specified,
// but the user said "reset the experience".
// Maybe it just clears the locked state and image?
// Usually "reset experience" for the user means restart the proposal.
// However, if they want to reset the setup, we can clear the image.
$stmt = db()->prepare("UPDATE settings SET setting_value = '' WHERE setting_key = 'valentine_image'");
$stmt->execute();
$stmt = db()->prepare("UPDATE settings SET setting_value = '0' WHERE setting_key = 'is_locked'");
$stmt->execute();
echo json_encode(['success' => true]);
} else {
echo json_encode(['success' => false, 'error' => 'Invalid action.']);
}

View File

@ -1,9 +1,10 @@
:root {
--primary-color: #e63946;
--bg-color: #ffffff;
--bg-color: #ffe4e6; /* Light Pink */
--popup-bg: #ffccd5; /* Light Red */
--text-color: #2d3436;
--secondary-text: #636e72;
--border-color: #eee;
--border-color: rgba(0,0,0,0.05);
--font-family: 'Inter', system-ui, -apple-system, sans-serif;
}
@ -19,16 +20,46 @@ body {
overflow-x: hidden;
}
.admin-controls {
position: fixed;
top: 1rem;
right: 1rem;
display: flex;
gap: 0.5rem;
z-index: 100;
}
.admin-controls button {
background: rgba(255, 255, 255, 0.8);
border: none;
border-radius: 50%;
width: 36px;
height: 36px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition: all 0.2s ease;
color: var(--secondary-text);
}
.admin-controls button:hover {
background: #fff;
color: var(--primary-color);
transform: scale(1.1);
}
.container {
max-width: 500px;
width: 90%;
text-align: center;
padding: 2rem;
border: 1px solid var(--border-color);
border-radius: 12px;
background: #fff;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
padding: 2.5rem 2rem;
border-radius: 20px;
background: var(--popup-bg);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
position: relative;
}
h1 {
@ -36,14 +67,15 @@ h1 {
font-weight: 700;
margin-bottom: 1.5rem;
letter-spacing: -0.5px;
color: var(--primary-color);
}
.image-preview-container {
width: 100%;
height: 250px;
background-color: #f8f9fa;
border: 2px dashed var(--border-color);
border-radius: 8px;
background-color: rgba(255, 255, 255, 0.5);
border: 2px dashed rgba(0,0,0,0.1);
border-radius: 12px;
margin-bottom: 1.5rem;
display: flex;
justify-content: center;
@ -51,6 +83,12 @@ h1 {
overflow: hidden;
position: relative;
cursor: pointer;
transition: all 0.2s ease;
}
.state-locked .image-preview-container {
cursor: default;
border-style: solid;
}
.image-preview-container img {
@ -63,6 +101,7 @@ h1 {
.image-preview-container .placeholder-text {
color: var(--secondary-text);
font-size: 0.9rem;
padding: 1rem;
}
.button-group {
@ -76,13 +115,13 @@ h1 {
}
.btn {
padding: 0.75rem 1.5rem;
padding: 0.75rem 1.75rem;
font-size: 1rem;
font-weight: 600;
border-radius: 6px;
border-radius: 10px;
border: none;
cursor: pointer;
transition: transform 0.1s ease, font-size 0.2s ease;
transition: transform 0.1s ease, font-size 0.2s ease, background-color 0.2s ease;
white-space: nowrap;
}
@ -90,12 +129,18 @@ h1 {
background-color: var(--primary-color);
color: white;
z-index: 10;
box-shadow: 0 4px 12px rgba(230, 57, 70, 0.3);
}
.btn-yes:hover {
background-color: #d62839;
}
.btn-no {
background-color: #f1f2f6;
background-color: #fff;
color: var(--text-color);
position: relative;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
#success-message {
@ -112,15 +157,17 @@ h1 {
font-size: 1.25rem;
line-height: 1.6;
color: var(--primary-color);
font-weight: 600;
font-weight: 700;
margin-bottom: 0.5rem;
}
.redirect-hint {
margin-top: 1rem;
margin-top: 2rem;
font-size: 0.85rem;
color: var(--secondary-text);
font-style: italic;
}
#image-input {
display: none;
}
}

View File

@ -6,24 +6,85 @@ document.addEventListener('DOMContentLoaded', () => {
const placeholderText = document.querySelector('.placeholder-text');
const proposalBox = document.getElementById('proposal-box');
const successBox = document.getElementById('success-message');
const lockBtn = document.getElementById('lock-btn');
const resetBtn = document.getElementById('reset-btn');
let yesScale = 1;
let isLocked = typeof IS_LOCKED !== 'undefined' ? IS_LOCKED : false;
// Image Upload Logic
document.querySelector('.image-preview-container').addEventListener('click', () => {
if (isLocked) return;
imageInput.click();
});
imageInput.addEventListener('change', function() {
const file = this.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
imagePreview.src = e.target.result;
imagePreview.style.display = 'block';
placeholderText.style.display = 'none';
const formData = new FormData();
formData.append('action', 'upload_image');
formData.append('image', file);
fetch('api/save_settings.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
imagePreview.src = data.path + '?t=' + new Date().getTime();
imagePreview.style.display = 'block';
placeholderText.style.display = 'none';
document.querySelector('.image-preview-container').classList.add('has-image');
} else {
alert(data.error || 'Failed to upload image');
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred during upload.');
});
}
});
// Lock/Unlock Toggle
lockBtn.addEventListener('click', () => {
const newLockedState = !isLocked;
const formData = new FormData();
formData.append('action', 'toggle_lock');
formData.append('lock', newLockedState);
fetch('api/save_settings.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
isLocked = data.locked;
document.body.classList.toggle('state-locked', isLocked);
// Update icon (re-fetch or just refresh page is easier, but let's just refresh)
location.reload();
}
reader.readAsDataURL(file);
});
});
// Reset Experience
resetBtn.addEventListener('click', () => {
if (confirm('Reset everything? This will clear the image and unlock changes.')) {
const formData = new FormData();
formData.append('action', 'reset');
fetch('api/save_settings.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
}
});
}
});
@ -58,12 +119,13 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
// "No" Click Logic (if they somehow manage to click it)
// "No" Click Logic
noBtn.addEventListener('click', (e) => {
e.preventDefault();
yesScale += 0.15;
yesBtn.style.transform = `scale(${yesScale})`;
yesBtn.style.fontSize = `${1 * yesScale}rem`;
// We don't change font-size via style attribute directly to avoid layout jumps if possible,
// but it's what was requested ("yes button gets slightly bigger")
});
// "Yes" Click Logic
@ -71,6 +133,9 @@ document.addEventListener('DOMContentLoaded', () => {
proposalBox.style.display = 'none';
successBox.style.display = 'block';
// Hide controls during celebration
document.querySelector('.admin-controls').style.display = 'none';
// Confetti effect
const duration = 15 * 1000;
const animationEnd = Date.now() + duration;
@ -97,4 +162,4 @@ document.addEventListener('DOMContentLoaded', () => {
window.location.href = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
}, 15000);
});
});
});

View File

@ -0,0 +1,2 @@
CREATE TABLE IF NOT EXISTS settings (id INT AUTO_INCREMENT PRIMARY KEY, setting_key VARCHAR(255) UNIQUE NOT NULL, setting_value TEXT);
INSERT IGNORE INTO settings (setting_key, setting_value) VALUES ('valentine_image', ''), ('is_locked', '0');

View File

@ -1,5 +1,14 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/db/config.php';
// Fetch settings
$stmt = db()->prepare("SELECT setting_key, setting_value FROM settings");
$stmt->execute();
$settings = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
$valentineImage = $settings['valentine_image'] ?? '';
$isLocked = ($settings['is_locked'] ?? '0') === '1';
?>
<!doctype html>
<html lang="en">
@ -9,7 +18,7 @@ declare(strict_types=1);
<title>Gvantsa, would you be my valentine?</title>
<?php
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'A special valentine proposal for Gvantsa.';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ($valentineImage ?: '');
?>
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>" />
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
@ -24,15 +33,28 @@ declare(strict_types=1);
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<body class="<?= $isLocked ? 'state-locked' : '' ?>">
<div class="admin-controls">
<button id="lock-btn" title="<?= $isLocked ? 'Unlock' : 'Lock' ?> changes">
<?php if ($isLocked): ?>
<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>
<?php else: ?>
<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 9.9-1"></path></svg>
<?php endif; ?>
</button>
<button id="reset-btn" title="Reset Experience">
<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"></polyline><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path></svg>
</button>
</div>
<div class="container">
<div id="proposal-box">
<h1>Gvantsa, would you be my valentine?</h1>
<div class="image-preview-container">
<span class="placeholder-text">Click to upload our photo ❤️</span>
<img id="preview-img" src="" alt="Valentine Image">
<div class="image-preview-container <?= $valentineImage ? 'has-image' : '' ?>">
<span class="placeholder-text" style="<?= $valentineImage ? 'display:none' : '' ?>">Click to upload our photo ❤️</span>
<img id="preview-img" src="<?= htmlspecialchars($valentineImage) ?>" alt="Valentine Image" style="<?= $valentineImage ? 'display:block' : '' ?>">
</div>
<input type="file" id="image-input" accept="image/*">
@ -50,6 +72,9 @@ declare(strict_types=1);
</div>
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js"></script>
<script>
const IS_LOCKED = <?= $isLocked ? 'true' : 'false' ?>;
</script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>