VALENTINE 1
This commit is contained in:
parent
1cb89e7ef8
commit
978955ec5b
55
api/save_settings.php
Normal file
55
api/save_settings.php
Normal 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.']);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
2
db/migrations/001_create_settings.sql
Normal file
2
db/migrations/001_create_settings.sql
Normal 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');
|
||||
35
index.php
35
index.php
@ -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>
|
||||
Loading…
x
Reference in New Issue
Block a user