<!doctype html>
<html lang="fi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ajanvarausjärjestelmä</title>
<script src="/_sdk/data_sdk.js"></script>
<script src="/_sdk/element_sdk.js"></script>
<style>
body {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
html {
height: 100%;
}
.container {
background: white;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
max-width: 900px;
width: 100%;
padding: 40px;
}
h1 {
margin: 0 0 8px 0;
font-size: 32px;
font-weight: 700;
}
.description {
color: #666;
margin: 0 0 32px 0;
font-size: 16px;
}
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 8px;
margin-bottom: 32px;
}
.calendar-header {
text-align: center;
font-weight: 600;
padding: 12px;
color: #333;
font-size: 14px;
}
.calendar-day {
aspect-ratio: 1;
border: 2px solid #e0e0e0;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
font-weight: 500;
background: white;
}
.calendar-day:hover {
border-color: #667eea;
background: #f5f7ff;
}
.calendar-day.selected {
background: #667eea;
color: white;
border-color: #667eea;
}
.calendar-day.disabled {
opacity: 0.3;
cursor: not-allowed;
pointer-events: none;
}
.time-slots {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 12px;
margin-bottom: 32px;
}
.time-slot {
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
text-align: center;
cursor: pointer;
transition: all 0.2s;
font-weight: 500;
background: white;
}
.time-slot:hover {
border-color: #667eea;
background: #f5f7ff;
}
.time-slot.selected {
background: #667eea;
color: white;
border-color: #667eea;
}
.time-slot.booked {
background: #f5f5f5;
color: #999;
cursor: not-allowed;
border-color: #e0e0e0;
}
.booking-form {
display: none;
margin-top: 32px;
}
.booking-form.active {
display: block;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #333;
}
input {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.2s;
box-sizing: border-box;
}
input:focus {
outline: none;
border-color: #667eea;
}
.button-group {
display: flex;
gap: 12px;
margin-top: 24px;
}
button {
flex: 1;
padding: 14px 24px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5568d3;
}
.btn-primary:disabled {
background: #ccc;
cursor: not-allowed;
}
.btn-secondary {
background: #f5f5f5;
color: #333;
}
.btn-secondary:hover {
background: #e0e0e0;
}
.success-message {
display: none;
background: #4caf50;
color: white;
padding: 16px;
border-radius: 8px;
margin-top: 20px;
text-align: center;
font-weight: 500;
}
.success-message.active {
display: block;
}
.section-title {
font-size: 20px;
font-weight: 600;
margin: 0 0 16px 0;
color: #333;
}
.loading {
opacity: 0.6;
pointer-events: none;
}
</style>
<style>@view-transition { navigation: auto; }</style>
<script src="https://cdn.tailwindcss.com" type="text/javascript"></script>
</head>
<body>
<div class="container">
<h1 id="page-title">Ajanvaraus</h1>
<p class="description" id="description">Varaa aika palveluumme</p>
<div class="section-title">
Valitse päivä
</div>
<div class="calendar-grid" id="calendar"></div>
<div id="time-section" style="display: none;">
<div class="section-title">
Valitse aika
</div>
<div class="time-slots" id="time-slots"></div>
</div>
<div class="booking-form" id="booking-form">
<div class="section-title">
Yhteystiedot
</div>
<form id="contact-form">
<div class="form-group"><label for="name">Nimi</label> <input type="text" id="name" required>
</div>
<div class="form-group"><label for="email">Sähköposti</label> <input type="email" id="email" required>
</div>
<div class="form-group"><label for="phone">Puhelinnumero</label> <input type="tel" id="phone" required>
</div>
<div class="button-group"><button type="button" class="btn-secondary" id="cancel-btn">Peruuta</button> <button type="submit" class="btn-primary" id="book-btn">Varaa aika</button>
</div>
</form>
</div>
<div class="success-message" id="success-message">
Aika varattu onnistuneesti!
</div>
</div>
<script>
const defaultConfig = {
page_title: "Ajanvaraus",
description: "Varaa aika palveluumme",
book_button_text: "Varaa aika",
cancel_button_text: "Peruuta",
background_color: "#667eea",
surface_color: "#ffffff",
text_color: "#333333",
primary_action_color: "#667eea",
secondary_action_color: "#f5f5f5",
font_family: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto",
font_size: 16
};
let config = { ...defaultConfig };
let bookings = [];
let selectedDate = null;
let selectedTime = null;
let recordCount = 0;
const timeSlots = [
"09:00", "09:30", "10:00", "10:30", "11:00", "11:30",
"12:00", "12:30", "13:00", "13:30", "14:00", "14:30",
"15:00", "15:30", "16:00", "16:30"
];
function generateCalendar() {
const calendar = document.getElementById('calendar');
calendar.innerHTML = '';
const weekdays = ['Ma', 'Ti', 'Ke', 'To', 'Pe', 'La', 'Su'];
weekdays.forEach(day => {
const header = document.createElement('div');
header.className = 'calendar-header';
header.textContent = day;
calendar.appendChild(header);
});
const today = new Date();
const currentMonth = today.getMonth();
const currentYear = today.getFullYear();
const firstDay = new Date(currentYear, currentMonth, 1);
const lastDay = new Date(currentYear, currentMonth + 1, 0);
let startDay = firstDay.getDay() - 1;
if (startDay === -1) startDay = 6;
for (let i = 0; i < startDay; i++) {
const emptyDay = document.createElement('div');
emptyDay.className = 'calendar-day disabled';
calendar.appendChild(emptyDay);
}
for (let day = 1; day <= lastDay.getDate(); day++) {
const dayElement = document.createElement('div');
dayElement.className = 'calendar-day';
dayElement.textContent = day;
const date = new Date(currentYear, currentMonth, day);
if (date < today.setHours(0, 0, 0, 0)) {
dayElement.classList.add('disabled');
} else {
dayElement.addEventListener('click', () => selectDate(date));
}
calendar.appendChild(dayElement);
}
}
function selectDate(date) {
selectedDate = date;
selectedTime = null;
document.querySelectorAll('.calendar-day').forEach(day => {
day.classList.remove('selected');
});
event.target.classList.add('selected');
document.getElementById('time-section').style.display = 'block';
document.getElementById('booking-form').classList.remove('active');
renderTimeSlots();
}
function renderTimeSlots() {
const container = document.getElementById('time-slots');
container.innerHTML = '';
const dateStr = selectedDate.toISOString().split('T')[0];
timeSlots.forEach(time => {
const slot = document.createElement('div');
slot.className = 'time-slot';
slot.textContent = time;
const isBooked = bookings.some(b => b.date === dateStr && b.time === time);
if (isBooked) {
slot.classList.add('booked');
} else {
slot.addEventListener('click', () => selectTime(time, slot));
}
container.appendChild(slot);
});
}
function selectTime(time, element) {
selectedTime = time;
document.querySelectorAll('.time-slot').forEach(slot => {
slot.classList.remove('selected');
});
element.classList.add('selected');
document.getElementById('booking-form').classList.add('active');
}
document.getElementById('contact-form').addEventListener('submit', async (e) => {
e.preventDefault();
if (recordCount >= 999) {
const successMsg = document.getElementById('success-message');
successMsg.textContent = 'Varausraja (999) saavutettu. Poista varauksia ensin.';
successMsg.style.background = '#f44336';
successMsg.classList.add('active');
setTimeout(() => successMsg.classList.remove('active'), 3000);
return;
}
const bookBtn = document.getElementById('book-btn');
bookBtn.disabled = true;
bookBtn.textContent = 'Varataan...';
const booking = {
date: selectedDate.toISOString().split('T')[0],
time: selectedTime,
name: document.getElementById('name').value,
email: document.getElementById('email').value,
phone: document.getElementById('phone').value,
createdAt: new Date().toISOString()
};
const result = await window.dataSdk.create(booking);
bookBtn.disabled = false;
bookBtn.textContent = config.book_button_text || defaultConfig.book_button_text;
if (result.isOk) {
document.getElementById('contact-form').reset();
document.getElementById('booking-form').classList.remove('active');
const successMsg = document.getElementById('success-message');
successMsg.style.background = '#4caf50';
successMsg.textContent = 'Aika varattu onnistuneesti!';
successMsg.classList.add('active');
setTimeout(() => {
successMsg.classList.remove('active');
}, 3000);
selectedDate = null;
selectedTime = null;
document.querySelectorAll('.calendar-day').forEach(d => d.classList.remove('selected'));
document.getElementById('time-section').style.display = 'none';
} else {
const successMsg = document.getElementById('success-message');
successMsg.textContent = 'Virhe varauksessa. Yritä uudelleen.';
successMsg.style.background = '#f44336';
successMsg.classList.add('active');
setTimeout(() => successMsg.classList.remove('active'), 3000);
}
});
document.getElementById('cancel-btn').addEventListener('click', () => {
document.getElementById('contact-form').reset();
document.getElementById('booking-form').classList.remove('active');
});
const dataHandler = {
onDataChanged(data) {
bookings = data;
recordCount = data.length;
if (selectedDate) {
renderTimeSlots();
}
}
};
async function onConfigChange(newConfig) {
config = { ...config, ...newConfig };
document.getElementById('page-title').textContent = config.page_title || defaultConfig.page_title;
document.getElementById('description').textContent = config.description || defaultConfig.description;
document.getElementById('book-btn').textContent = config.book_button_text || defaultConfig.book_button_text;
document.getElementById('cancel-btn').textContent = config.cancel_button_text || defaultConfig.cancel_button_text;
const bgColor = config.background_color || defaultConfig.background_color;
const surfaceColor = config.surface_color || defaultConfig.surface_color;
const textColor = config.text_color || defaultConfig.text_color;
const primaryColor = config.primary_action_color || defaultConfig.primary_action_color;
const secondaryColor = config.secondary_action_color || defaultConfig.secondary_action_color;
const fontFamily = config.font_family || defaultConfig.font_family;
const fontSize = config.font_size || defaultConfig.font_size;
document.body.style.background = `linear-gradient(135deg, ${bgColor} 0%, ${bgColor} 100%)`;
document.querySelector('.container').style.background = surfaceColor;
document.body.style.fontFamily = `${fontFamily}, sans-serif`;
document.body.style.fontSize = `${fontSize}px`;
document.getElementById('page-title').style.color = textColor;
document.getElementById('page-title').style.fontSize = `${fontSize * 2}px`;
document.querySelector('.description').style.fontSize = `${fontSize}px`;
document.querySelectorAll('.section-title').forEach(el => {
el.style.color = textColor;
el.style.fontSize = `${fontSize * 1.25}px`;
});
document.querySelectorAll('label').forEach(el => {
el.style.color = textColor;
el.style.fontSize = `${fontSize}px`;
});
document.querySelectorAll('.btn-primary').forEach(btn => {
btn.style.background = primaryColor;
btn.style.fontSize = `${fontSize}px`;
});
document.querySelectorAll('.btn-secondary').forEach(btn => {
btn.style.background = secondaryColor;
btn.style.color = textColor;
btn.style.fontSize = `${fontSize}px`;
});
document.querySelectorAll('.calendar-day').forEach(day => {
if (day.classList.contains('selected')) {
day.style.background = primaryColor;
day.style.borderColor = primaryColor;
}
});
document.querySelectorAll('.time-slot').forEach(slot => {
if (slot.classList.contains('selected')) {
slot.style.background = primaryColor;
slot.style.borderColor = primaryColor;
}
});
}
if (window.elementSdk) {
window.elementSdk.init({
defaultConfig,
onConfigChange,
mapToCapabilities: (cfg) => ({
recolorables: [
{
get: () => cfg.background_color || defaultConfig.background_color,
set: (value) => {
cfg.background_color = value;
window.elementSdk.setConfig({ background_color: value });
}
},
{
get: () => cfg.surface_color || defaultConfig.surface_color,
set: (value) => {
cfg.surface_color = value;
window.elementSdk.setConfig({ surface_color: value });
}
},
{
get: () => cfg.text_color || defaultConfig.text_color,
set: (value) => {
cfg.text_color = value;
window.elementSdk.setConfig({ text_color: value });
}
},
{
get: () => cfg.primary_action_color || defaultConfig.primary_action_color,
set: (value) => {
cfg.primary_action_color = value;
window.elementSdk.setConfig({ primary_action_color: value });
}
},
{
get: () => cfg.secondary_action_color || defaultConfig.secondary_action_color,
set: (value) => {
cfg.secondary_action_color = value;
window.elementSdk.setConfig({ secondary_action_color: value });
}
}
],
borderables: [],
fontEditable: {
get: () => cfg.font_family || defaultConfig.font_family,
set: (value) => {
cfg.font_family = value;
window.elementSdk.setConfig({ font_family: value });
}
},
fontSizeable: {
get: () => cfg.font_size || defaultConfig.font_size,
set: (value) => {
cfg.font_size = value;
window.elementSdk.setConfig({ font_size: value });
}
}
}),
mapToEditPanelValues: (cfg) => new Map([
["page_title", cfg.page_title || defaultConfig.page_title],
["description", cfg.description || defaultConfig.description],
["book_button_text", cfg.book_button_text || defaultConfig.book_button_text],
["cancel_button_text", cfg.cancel_button_text || defaultConfig.cancel_button_text]
])
});
config = window.elementSdk.config;
}
async function init() {
generateCalendar();
if (window.dataSdk) {
const result = await window.dataSdk.init(dataHandler);
if (!result.isOk) {
console.error('Data SDK initialization failed');
}
}
}
init();
</script>
<script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'99942831e15dbba2',t:'MTc2MjI1OTc4Ni4wMDAwMDA='};var a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>
</html>