favicon added. Mass update buttons added. Recipe image placeholders added

This commit is contained in:
2026-02-01 17:59:05 +01:00
parent a970b72aef
commit c34dcae89d
8 changed files with 138 additions and 39 deletions

View File

@@ -2,7 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
@@ -29,14 +31,14 @@
¡A Mezclar!
</button>
<!-- TODO: select all/none -->
<!--<div class="selection-options" id="selection-options">
<button class="selection-button" id="select-all-button" disabled="true">
Select all
<div class="selection-options" id="selection-options">
<button class="selection-button" id="select-all-button" disabled="false">
¡Todos!
</button>
<button class="selection-button" id="select-none-button" disabled="true">
Reset selection
Reset
</button>
</div>-->
</div>
</div>
<div class="section">

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

BIN
public/spirits/default.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -3,14 +3,12 @@ import data from "./data.json";
let spiritsData = [];
let recipesData = [];
// Cargar datos desde JSON
async function loadData() {
console.log("data");
console.log(data);
try {
spiritsData = data.spirits;
recipesData = data.recipes;
renderSpirits();
enableButtonsLogic();
} catch (error) {
console.error("Error cargando datos:", error);
document.getElementById("spiritsGrid").innerHTML =
@@ -32,6 +30,24 @@ function renderSpirits() {
`,
)
.join("");
}
function toggleSpirit(index, forcedCheckValue) {
const card = document.getElementById(`spirit-${index}`);
const checkbox = document.getElementById(`check-${index}`);
checkbox.checked =
forcedCheckValue !== undefined ? forcedCheckValue : !checkbox.checked;
card.classList.toggle("selected");
// Update buttons status only on natural toggle
if (forcedCheckValue === undefined) {
updateMixButtonStatus();
updateMassSelectButtonsStatus();
}
}
function enableButtonsLogic() {
const grid = document.getElementById("spiritsGrid");
// Add toggle logic to cards
spiritsData.map((spirit, index) => {
grid.querySelector(`#spirit-${index}`).addEventListener("click", (e) => {
@@ -44,28 +60,45 @@ function renderSpirits() {
section[0].querySelector(".mix-button").addEventListener("click", (e) => {
findRecipes();
});
section[0].querySelector(".mix-button").disabled = true;
}
}
function toggleSpirit(index) {
const card = document.getElementById(`spirit-${index}`);
const checkbox = document.getElementById(`check-${index}`);
checkbox.checked = !checkbox.checked;
card.classList.toggle("selected");
// Check enable/disable mix button on card toggle
updateMixButtonStatus();
}
function updateMixButtonStatus() {
const section = document.getElementsByClassName("section");
const selected = getSelectedSpirits();
// Add logic to select all / none buttons
if (section && section.length > 0) {
section[0].querySelector(".mix-button").disabled = selected.length === 0;
section[0]
.querySelector("#select-all-button")
.addEventListener("click", (e) => {
selectAll();
});
section[0]
.querySelector("#select-none-button")
.addEventListener("click", (e) => {
selectNone();
});
updateMassSelectButtonsStatus();
}
}
function getSelectedSpirits() {
function updateMixButtonStatus(forcedDisabledValue) {
const section = document.getElementsByClassName("section");
const selected = getSelectedSpiritsTypes();
if (section && section.length > 0) {
section[0].querySelector(".mix-button").disabled =
forcedDisabledValue ?? selected.length === 0;
}
}
function updateMassSelectButtonsStatus() {
const section = document.getElementsByClassName("selection-options");
const selected = getSelectedSpiritsNames();
if (section && section.length > 0) {
section[0].querySelector("#select-all-button").disabled =
selected.length === spiritsData.length;
section[0].querySelector("#select-none-button").disabled =
selected.length === 0;
}
}
function getSelectedSpiritsTypes() {
const selectedSpirits = [];
spiritsData.forEach((spirit, index) => {
const checkbox = document.getElementById(`check-${index}`);
@@ -76,8 +109,49 @@ function getSelectedSpirits() {
return selectedSpirits;
}
function getSelectedSpiritsNames() {
const selectedSpirits = [];
spiritsData.forEach((spirit, index) => {
const checkbox = document.getElementById(`check-${index}`);
if (checkbox.checked) {
selectedSpirits.push(spirit.name);
}
});
return selectedSpirits;
}
function selectAll() {
const selectAllButton = document.getElementById("select-all-button");
if (selectAllButton) {
const selectedNames = getSelectedSpiritsNames();
spiritsData.forEach((spirit, index) => {
if (!selectedNames.includes(spirit.name)) {
toggleSpirit(index, true);
}
});
}
updateMixButtonStatus(false);
updateMassSelectButtonsStatus();
return;
}
function selectNone() {
const selectNoneButton = document.getElementById("select-none-button");
if (selectNoneButton) {
const selectedNames = getSelectedSpiritsNames();
spiritsData.forEach((spirit, index) => {
if (selectedNames.includes(spirit.name)) {
toggleSpirit(index, false);
}
});
}
updateMixButtonStatus(true);
updateMassSelectButtonsStatus();
return;
}
function findRecipes() {
const selectedSpirits = getSelectedSpirits();
const selectedSpirits = getSelectedSpiritsTypes();
if (selectedSpirits.length === 0) {
document.getElementById("recipesContainer").innerHTML =
'<p class="no-recipes">Por favor, selecciona al menos un licor para encontrar recetas.</p>';
@@ -101,15 +175,6 @@ function findRecipes() {
});
}
// TODO: select all/none spirits
// function selectAll() {
// return;
// }
// function selectNone() {
// return;
// }
function renderRecipes(recipes) {
const container = document.getElementById("recipesContainer");
@@ -130,6 +195,7 @@ function renderRecipes(recipes) {
return `
<div class="recipe-card">
<div class="recipe-content">
<div class="recipe-name">${recipe.name}</div>
<div class="non-alcohol-title">Licores:</div>
<div class="recipe-ingredients">
@@ -172,6 +238,14 @@ function renderRecipes(recipes) {
`
: ""
}
</div>
<div class="recipe-img">
<img
src="/public/spirits/${recipe.name?.toLowerCase()}.png"
alt=""
onerror="this.onerror=null; this.src='/public/spirits/default.png';"
/>
</div>
</div>
`;
})

View File

@@ -169,10 +169,10 @@ h2 {
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(
circle,
background: linear-gradient(
to left,
rgba(255, 255, 255, 0.8) 0%,
transparent 70%
transparent 50% transparent 75%
);
opacity: 0;
transition: opacity 0.3s;
@@ -257,6 +257,9 @@ h2 {
}
.recipe-card {
display: flex;
flex-direction: row;
width: 100%;
background: linear-gradient(135deg, #fff 0%, #fff8f0 100%);
border-left: 5px solid var(--deep-red);
border-radius: 10px;
@@ -266,6 +269,10 @@ h2 {
animation: recipeAppear 0.5s ease-out backwards;
}
.recipe-content {
width: 70%;
}
.recipe-card:nth-child(1) {
animation-delay: 0.1s;
}
@@ -281,6 +288,9 @@ h2 {
.recipe-card:nth-child(5) {
animation-delay: 0.5s;
}
.recipe-card:nth-child(6) {
animation-delay: 0.5s;
}
@keyframes recipeAppear {
from {
@@ -361,6 +371,19 @@ h2 {
font-style: italic;
}
.recipe-img {
width: 30%;
min-width: 30%;
max-width: 30%;
}
.recipe-img img {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
}
.loading {
text-align: center;
padding: 2rem;