Compare commits

...
8 Commits
11 changed files with 284 additions and 75 deletions
@@ -0,0 +1,10 @@
i want to perform a little logical restructure
there are long and short acitivies and there are long activitiees
there are days where we have more time and days where we have less time
on days where there is more time, we can do one long activity or two short activities
on days where there is less time, we can only do one short activity
so in the appsettings.json file i need to be able to define which days are long days and which days are short days.
and i need to be able to define which activities are long and which activities are short.
from now on, the date-selection comes first, then the activity selection. IF the user selected a long day AND a short activity, the user needs to be informed that on that day, there is still more time for another activity and have the option to select another short activity. If the user selects a short day, he will only have the option to select a short activity but doesnt need to be informed that there were options for long activities in the first place
+45 -7
View File
@@ -4,7 +4,7 @@
.page-activity .intro {
text-align: center;
margin-bottom: 1.5em;
margin-bottom: 1.2em;
}
.page-activity .intro p {
@@ -14,12 +14,13 @@
.activity-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
gap: 12px;
}
@media (min-width: 600px) {
.activity-grid {
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
}
@@ -29,9 +30,9 @@
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
min-height: 140px;
padding: 16px 10px;
gap: 8px;
min-height: 115px;
padding: 12px 8px;
background: var(--color-white);
border: 2px solid transparent;
border-radius: var(--radius-md);
@@ -46,14 +47,14 @@
}
.activity-card .icon {
font-size: 2.6rem;
font-size: 2.2rem;
line-height: 1;
}
.activity-card .label {
font-family: var(--font-heading);
font-weight: 700;
font-size: 0.95rem;
font-size: 0.9rem;
text-align: center;
color: var(--color-text);
}
@@ -72,3 +73,40 @@
color: var(--color-rose-dark);
font-weight: 700;
}
.activity-card.is-disabled,
.activity-card:disabled {
opacity: 0.4;
cursor: not-allowed;
filter: grayscale(0.4);
box-shadow: none;
background: #f7e8ec;
}
.time-notice {
background: var(--color-white);
border: 2px solid var(--color-peach);
border-radius: var(--radius-md);
padding: 12px 16px;
margin-bottom: 16px;
font-size: 0.95rem;
color: var(--color-rose-dark);
text-align: center;
box-shadow: var(--shadow-soft);
animation: fadeIn 0.3s ease;
}
.hidden {
display: none !important;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-6px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
+6 -2
View File
@@ -12,10 +12,14 @@
</head>
<body class="page-activity has-continue-bar">
<main class="container">
<a class="back-link" href="index.html"> Zurück</a>
<a class="back-link" href="datetime.html"> Zurück</a>
<div class="intro">
<h1>Worauf hast du Lust? 🥰</h1>
<p>Wähle eure gemeinsame Aktivität für das Date aus.</p>
<p id="dateSummary">Wähle eure gemeinsame Aktivität für das Date aus.</p>
</div>
<div id="timeNotice" class="time-notice hidden">
💡 An diesem Tag haben wir reichlich Zeit! Wenn du möchtest, kannst du noch eine 2. kurze Aktivität auswählen.
</div>
<div class="activity-grid" id="activityGrid"></div>
+101 -12
View File
@@ -2,24 +2,66 @@
(function () {
const activityGrid = document.getElementById("activityGrid");
const dateSummary = document.getElementById("dateSummary");
const timeNotice = document.getElementById("timeNotice");
const continueBtn = document.getElementById("continueBtn");
let selectedActivity = null;
const selectedDate = localStorage.getItem("selectedDate");
const selectedDateType = localStorage.getItem("selectedDateType") || "long";
if (!selectedDate) {
window.location.href = "datetime.html";
return;
}
const dateObj = new Date(`${selectedDate}T00:00:00`);
const formattedDate = dateObj.toLocaleDateString("de-DE", {
weekday: "long",
day: "numeric",
month: "long",
year: "numeric",
});
dateSummary.textContent = `Für euer Date am ${formattedDate}.`;
let availableActivities = [];
let selectedList = [];
fetch("appsettings.json")
.then((response) => response.json())
.then((settings) => renderActivities(settings.activities || []))
.then((settings) => {
const allActivities = settings.activities || [];
if (selectedDateType === "short") {
availableActivities = allActivities.filter((a) => a.type === "short");
} else {
availableActivities = allActivities;
}
renderActivities();
})
.catch(() => {
activityGrid.textContent = "Aktivitäten konnten nicht geladen werden.";
});
function renderActivities(activities) {
activities.forEach((activity) => {
function renderActivities() {
activityGrid.innerHTML = "";
const hasShortSelected = selectedList.some((a) => a.type === "short");
availableActivities.forEach((activity) => {
const card = document.createElement("button");
card.type = "button";
card.className = "activity-card";
card.dataset.activity = activity.name;
const isSelected = selectedList.some((a) => a.name === activity.name);
if (isSelected) {
card.classList.add("selected");
}
const isLongDisabled = hasShortSelected && activity.type === "long";
if (isLongDisabled) {
card.classList.add("is-disabled");
card.disabled = true;
}
const icon = document.createElement("span");
icon.className = "icon";
icon.setAttribute("aria-hidden", "true");
@@ -30,23 +72,70 @@
label.textContent = activity.name;
card.append(icon, label);
card.addEventListener("click", () => {
activityGrid.querySelectorAll(".activity-card").forEach((c) => c.classList.remove("selected"));
card.classList.add("selected");
selectedActivity = activity.name;
continueBtn.disabled = false;
handleCardClick(activity);
});
activityGrid.appendChild(card);
});
updateNoticeAndContinue();
}
function handleCardClick(activity) {
const isAlreadySelected = selectedList.some((a) => a.name === activity.name);
if (activity.type === "long") {
if (isAlreadySelected) {
selectedList = [];
} else {
selectedList = [activity];
}
} else {
// Short activity
if (isAlreadySelected) {
selectedList = selectedList.filter((a) => a.name !== activity.name);
} else {
// If a long activity was selected, replace it
if (selectedList.some((a) => a.type === "long")) {
selectedList = [activity];
} else if (selectedList.length >= 2) {
// Max 2 short activities, replace the second one
selectedList = [selectedList[0], activity];
} else {
selectedList.push(activity);
}
}
}
renderActivities();
}
function updateNoticeAndContinue() {
const shortCount = selectedList.filter((a) => a.type === "short").length;
const hasLong = selectedList.some((a) => a.type === "long");
if (selectedDateType === "long" && shortCount === 1 && !hasLong) {
timeNotice.textContent =
"💡 An diesem Tag haben wir reichlich Zeit! Wenn du möchtest, kannst du noch eine 2. kurze Aktivität auswählen.";
timeNotice.classList.remove("hidden");
} else if (selectedDateType === "long" && shortCount === 2) {
timeNotice.textContent = "🎉 Perfekt! Du hast 2 kurze Aktivitäten für euren langen Tag ausgewählt.";
timeNotice.classList.remove("hidden");
} else {
timeNotice.classList.add("hidden");
}
continueBtn.disabled = selectedList.length === 0;
}
continueBtn.addEventListener("click", () => {
if (!selectedActivity) {
if (selectedList.length === 0) {
return;
}
// Ablage für die spätere Anbindung an Datenbank/Storage
localStorage.setItem("selectedActivity", selectedActivity);
window.location.href = "datetime.html";
localStorage.setItem("selectedActivities", JSON.stringify(selectedList));
localStorage.setItem("selectedActivity", selectedList.map((a) => a.name).join(" + "));
window.location.href = "name.html";
});
})();
+46 -16
View File
@@ -1,22 +1,52 @@
{
"activities": [
{ "name": "Essen bei Namaste", "icon": "🍛" },
{ "name": "Kinofilm", "icon": "🎬" },
{ "name": "Film zu Hause + Essen bestellen", "icon": "🛋️" },
{ "name": "Spaziergang im Wald", "icon": "🌲" },
{ "name": "Museum für Gegenwartskunst", "icon": "🖼️" },
{ "name": "Picknick auf der Wiese", "icon": "🧺" },
{ "name": "Massage zu Hause", "icon": "💆" }
{ "name": "Untersetzer aus Ton", "icon": "🪨", "type": "long" },
{ "name": "Picknick auf der Wiese", "icon": "🧺", "type": "short" },
{ "name": "Cocktailabend zu zweit", "icon": "🍸", "type": "long" },
{ "name": "Massage zu Hause", "icon": "💆", "type": "short" },
{ "name": "Ausgedehnte Wanderung", "icon": "🥾", "type": "long" },
{ "name": "Ins Kino", "icon": "🎬", "type": "short" },
{ "name": "Sushi selber machen", "icon": "🍣", "type": "short" },
{ "name": "Freizeitpark", "icon": "🎢", "type": "long" },
{ "name": "Spaziergang im Wald", "icon": "🌲", "type": "short" },
{ "name": "Nudeln selber machen", "icon": "🍜", "type": "long" },
{ "name": "Ab ins Freibad", "icon": "🏊", "type": "short" },
{ "name": "Film zu Hause + Essen bestellen", "icon": "🛋️", "type": "long" },
{ "name": "Squash spielen", "icon": "🏸", "type": "short" },
{ "name": "Essen im Namaste", "icon": "🍛", "type": "short" },
{ "name": "Museum für Gegenwartskunst", "icon": "🖼️", "type": "short" },
{ "name": "Playstation Co-Op", "icon": "🎮", "type": "short" }
],
"availableDates": [
"2026-08-15",
"2026-08-16",
"2026-08-23",
"2026-08-30",
"2026-09-05",
"2026-09-06"
],
"names": [
{ "date": "2026-08-11", "type": "short" },
{ "date": "2026-08-15", "type": "short" },
{ "date": "2026-08-18", "type": "short" },
{ "date": "2026-08-20", "type": "short" },
{ "date": "2026-08-20", "type": "short" },
{ "date": "2026-08-22", "type": "long" },
{ "date": "2026-08-23", "type": "long" },
{ "date": "2026-08-24", "type": "short" },
{ "date": "2026-08-26", "type": "short" },
{ "date": "2026-08-27", "type": "short" },
{ "date": "2026-08-28", "type": "short" },
{ "date": "2026-08-29", "type": "long" },
{ "date": "2026-08-31", "type": "short" },
{ "date": "2026-09-01", "type": "short" },
{ "date": "2026-09-07", "type": "short" },
{ "date": "2026-09-08", "type": "short" },
{ "date": "2026-09-14", "type": "short" },
{ "date": "2026-09-15", "type": "short" },
{ "date": "2026-09-17", "type": "short" },
{ "date": "2026-09-18", "type": "short" },
{ "date": "2026-09-25", "type": "short" },
{ "date": "2026-09-26", "type": "long" },
{ "date": "2026-09-27", "type": "short" },
{ "date": "2026-09-28", "type": "short" },
{ "date": "2026-09-29", "type": "short" },
{ "date": "2026-09-30", "type": "short" }
]
],
"names": []
}
+1 -1
View File
@@ -22,6 +22,6 @@
});
yesBtn.addEventListener("click", () => {
window.location.href = "activity.html";
window.location.href = "datetime.html";
});
})();
+19 -10
View File
@@ -31,16 +31,25 @@
});
summaryDate.textContent = formattedDate;
// Icon aus appsettings.json laden, falls verfügbar
fetch("appsettings.json")
.then((res) => res.json())
.then((settings) => {
const match = (settings.activities || []).find((a) => a.name === selectedActivity);
if (match && match.icon) {
activityIcon.textContent = match.icon;
}
})
.catch(() => {});
// Icons aus selectedActivities oder appsettings.json laden
let storedActivities = [];
try {
storedActivities = JSON.parse(localStorage.getItem("selectedActivities") || "[]");
} catch (e) {}
if (storedActivities.length > 0) {
activityIcon.textContent = storedActivities.map((a) => a.icon).join(" ");
} else {
fetch("appsettings.json")
.then((res) => res.json())
.then((settings) => {
const match = (settings.activities || []).find((a) => a.name === selectedActivity);
if (match && match.icon) {
activityIcon.textContent = match.icon;
}
})
.catch(() => {});
}
checkoutBtn.addEventListener("click", () => {
// Später: API / Mail-Versand / Kalendereintrag ausführen
+2 -2
View File
@@ -16,10 +16,10 @@
</div>
<main class="container">
<a class="back-link" href="activity.html"> Zurück</a>
<a class="back-link" href="index.html"> Zurück</a>
<div class="intro">
<h1>Wann soll es losgehen? 💌</h1>
<p id="activitySummary">Wähle das Datum für euer Date.</p>
<p id="activitySummary">Wähle das passende Datum für euer Date aus.</p>
</div>
<section class="calendar-card" aria-label="Kalender">
+19 -16
View File
@@ -7,7 +7,6 @@
"Juli", "August", "September", "Oktober", "November", "Dezember",
];
const activitySummary = document.getElementById("activitySummary");
const monthLabel = document.getElementById("monthLabel");
const weekdayRow = document.getElementById("weekdayRow");
const dayGrid = document.getElementById("dayGrid");
@@ -15,21 +14,14 @@
const nextMonthBtn = document.getElementById("nextMonthBtn");
const continueBtn = document.getElementById("continueBtn");
// Ohne gewählte Aktivität ergibt diese Seite keinen Sinn.
const selectedActivity = localStorage.getItem("selectedActivity");
if (!selectedActivity) {
window.location.href = "activity.html";
return;
}
activitySummary.textContent = `Für unser Date: „${selectedActivity}`;
const today = new Date();
today.setHours(0, 0, 0, 0);
let availableDates = new Set();
let availableDatesMap = new Map();
let viewYear = today.getFullYear();
let viewMonth = today.getMonth();
let selectedDate = null;
let selectedDateType = "long";
WEEKDAYS.forEach((day) => {
const span = document.createElement("span");
@@ -47,11 +39,18 @@
fetch("appsettings.json")
.then((response) => response.json())
.then((settings) => {
availableDates = new Set(settings.availableDates || []);
const datesList = settings.availableDates || [];
datesList.forEach((item) => {
if (typeof item === "string") {
availableDatesMap.set(item, { type: "long" });
} else if (item && item.date) {
availableDatesMap.set(item.date, { type: item.type || "long" });
}
});
const upcoming = Array.from(availableDates)
const upcoming = Array.from(availableDatesMap.keys())
.map((dateKey) => new Date(`${dateKey}T00:00:00`))
.filter((date) => date >= today)
.filter((date) => date > today)
.sort((a, b) => a - b);
if (upcoming.length > 0) {
@@ -82,12 +81,15 @@
for (let day = 1; day <= daysInMonth; day++) {
const cellDate = new Date(viewYear, viewMonth, day);
const key = toDateKey(cellDate);
const button = document.createElement("button");
button.type = "button";
button.className = "day-cell";
button.textContent = String(day);
const isAvailable = availableDates.has(toDateKey(cellDate));
const dateInfo = availableDatesMap.get(key);
const isPastOrToday = cellDate <= today;
const isAvailable = Boolean(dateInfo) && !isPastOrToday;
const isToday = cellDate.getTime() === today.getTime();
const isSelected = selectedDate && cellDate.getTime() === selectedDate.getTime();
@@ -104,6 +106,7 @@
button.addEventListener("click", () => {
selectedDate = cellDate;
selectedDateType = dateInfo ? dateInfo.type : "long";
renderCalendar();
updateContinueState();
});
@@ -141,9 +144,9 @@
if (!selectedDate) {
return;
}
// Ablage für die spätere Anbindung an Datenbank/Storage
localStorage.setItem("selectedDate", toDateKey(selectedDate));
window.location.href = "name.html";
localStorage.setItem("selectedDateType", selectedDateType);
window.location.href = "activity.html";
});
})();
+1 -1
View File
@@ -36,7 +36,7 @@
});
function renderNames(names) {
const options = names.length > 0 ? names : ["Mein name ist Amor 😽"];
const options = names.length > 0 ? names : ["Du weißt, wer ich bin"];
options.forEach((name) => {
const card = document.createElement("button");
+34 -8
View File
@@ -28,14 +28,16 @@
box-sizing: border-box;
}
html,
body {
height: 100%;
html {
min-height: 100%;
}
body {
margin: 0;
min-height: 100vh;
min-height: 100dvh;
display: flex;
flex-direction: column;
font-family: var(--font-body);
font-size: clamp(1rem, 3.6vw, 1.15rem);
line-height: 1.5;
@@ -65,7 +67,8 @@ p {
width: 100%;
max-width: 480px;
margin: 0 auto;
padding: 24px 20px 40px;
padding: 20px 20px 32px;
flex: 1;
}
/* Buttons -------------------------------------------------------------- */
@@ -161,8 +164,8 @@ p {
/* Fixed bottom bar for a primary continue action ------------------------- */
body.has-continue-bar {
padding-bottom: 96px;
body.has-continue-bar main.container {
padding-bottom: calc(110px + env(safe-area-inset-bottom));
}
.continue-bar {
@@ -170,8 +173,10 @@ body.has-continue-bar {
left: 0;
right: 0;
bottom: 0;
padding: 16px 20px calc(16px + env(safe-area-inset-bottom));
background: linear-gradient(0deg, var(--color-bg) 60%, rgba(255, 245, 247, 0));
z-index: 100;
padding: 12px 20px calc(12px + env(safe-area-inset-bottom));
background: linear-gradient(180deg, rgba(255, 245, 247, 0) 0%, var(--color-bg) 50%);
pointer-events: none;
}
.continue-bar .container {
@@ -179,6 +184,27 @@ body.has-continue-bar {
max-width: 480px;
}
.continue-bar .btn {
pointer-events: auto;
}
@media (min-width: 768px) {
.container {
flex: none;
}
body.has-continue-bar main.container {
padding-bottom: 12px;
}
.continue-bar {
position: static;
padding: 0 20px 40px;
background: none;
pointer-events: auto;
}
}
/* Back link --------------------------------------------------------------- */
.back-link {