diff --git a/README.md b/README.md
index 377ea97..1387ca6 100644
--- a/README.md
+++ b/README.md
@@ -1,112 +1,120 @@
-# API @cmda-minor-web 2024 - 2025
-Het web is een geweldige plek en de beschikbare technologieën ervan zijn vandaag de dag krachtiger dan ooit tevoren.
-De kracht van het web ligt in het feit dat het een platform is dat voor iedereen beschikbaar is en dat het gebaseerd is
-op open standaarden. De technologieën worden ontworpen en gespecificeerd op basis van consensus en zijn niet in handen
-van één enkele entiteit.
+# API
-Desondanks zijn er veel mensen en bedrijven die vinden dat het internet niet voldoet aan hun behoeften. Dit blijkt uit
-de pogingen van grote techbedrijven om hun eigen afgesloten ecosystemen te creëren. Ze streven hiermee naar controle over
-zowel de gebruikerservaring als de gegenereerde data.
+Maker: **Tymo Smids**
-**In dit vier weken durende vak zullen we de kracht van het web ervaren en kijken hoe we (mobiele) web apps kunnen maken die
-net zo aantrekkelijk zijn als native mobiele apps. We beginnen met het maken van een server-side gerenderde applicatie
-waarbij we geleidelijk de gebruikerservaring verbeteren met relevante beschikbare web API's.**
+Datum: *2025/03/25* - *2025/04/29*
-[TLDR; hoe zet ik mijn project op?](#Inrichten-ontwikkelomgeving)
+## Randvoorwaarden
-## Doelen
+- Minimaal een overzichts- en detailpagina;
+- Gebouwd in TinyHTTP + Liquid;
+- Minimaal een content API;
+- Minimaal twee Web API's.
-Na deze cursus zul je:
+## Mijn project
-- In staat zijn om een server-side gerenderde applicatie te maken.
-- In staat zijn om een enerverende gebruikerservaring te creëren.
-- Een breder begrip hebben van het web en zijn mogelijkheden.
+De pagina's die ik heb zijn: een homepagina, detailpagina en een favorieten pagina. Het project is gebouwd in **TinyHTTP** en **Liquid** is de templating taal die is gebruikt. De **content API** die is gebruikt is de `MovieDB API`. De **Web API's** die gebruikt worden zijn: de `Localstorage API` en de `View transition API`.
-## Opdracht
+### Verbinden met de API
-In dit vak zullen we een van de meest voorkomende app-concepten van vandaag
-gebruiken en ontdekken dat we deze kunnen maken met moderne webtechnologie
-met als doel om een rijke gebruikerservaring creëeren.
+De API key is opgeslagen in het `.env` bestand. Dit bestand staat in de `.gitignore`. Dit zorgt ervoor dat de key veilig wordt opgeslagen want de bestanden in de .gitignore worden niet meegestuurd naar Github. Als dit niet wordt gedaan kan iedereen bij de api key en zelf request afvuren.
-Randvoorwaarden:
+```js
+// ApiKey and URL for The Movie Database API
+const apiKey = process.env.movieDB_APIKey;
+const apiUrl = 'https://api.themoviedb.org/3/discover/movie';
+```
-- Minimaal een overzichts- en detailpagina
-- Gebouwd in TinyHTTP + Liquid
-- Minimaal een content API
-- Minimaal twee Web API's
+Als de `apiUrl` direct in de browser zou worden gezet krijg je een Json bestand terug. Dit bestand is ook wat je krijgt als je een `Fetch()` afvuurt.
-Voorbeelden:
+```js
+ const response = await fetch(movieDetailsUrl);
+ const item = await response.json();
+```
-- Maak je eigen streamingplatform (Netflix/Spotify).
-- Maak je eigen doom-scroll-app (Instagram/TikTok).
-- Maak je eigen chatapplicatie (WhatsApp/Signal).
-- Een andere app die je zelf leuk vindt...
+De data die je terugkrijgt kan je nu gebruiken om de website te vullen.
-Voorbeeld content API's die je kan gebruiken:
+```js
+
+
+
{{ item.title }}
+
+```
-- [MovieDB API](https://developer.themoviedb.org/reference/intro/getting-started)
-- [Rijksmuseum API](https://data.rijksmuseum.nl/object-metadata/api/)
-- [Spotify API](https://developer.spotify.com/documentation/web-api)
-- ...
+## Favorieten
-Voorbeelden van Web API's die je kan gebruiken:
+Om favorieten toe te voegen heb ik gebruik gemaakt van localstorage. Dit zorgt ervoor dat de films die worden toegevoegd aan de favorieten ook daar blijven tot deze worden verwijderd. Om naar de favorieten te gaan heb ik een link naar de favorieten pagina gemaakt zodat je een lijst hebt van je favorieten lijst.
-- [Page Transition API voor animaties tusse npagina's](https://developer.mozilla.org/en-US/docs/Web/API/Page_Transitions_API)
-- [Web Animations API voor complexe animaties](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API)
-- [Service Worker API voor installable web apps](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API)
-- [Web Push API voor push notifications](https://developer.mozilla.org/en-US/docs/Web/API/Push_API)
-- [Server sent events voor realtime functionaliteit](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
-- [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API)
-- [Web Speech API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API)
-- [Web Share API voor sharen van content binnen de context van de gebruiker](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share)
-- ...
+```js
+// Pad naar de favorieten pagina
+app.get('/favorites', async (req, res) => {
+ const ids = req.query.ids ? req.query.ids.split(',') : [];
-De lijst is eindeloos, laat je vooral inspireren op de overzichtspagina van [MDN](https://developer.mozilla.org/en-US/docs/Web/API).
+ // Check of er geen favorieten zijn
+ if (!ids.length) {
+ return res.send(renderTemplate('server/views/favorites.liquid', {
+ title: 'Favorites',
+ items: []
+ }));
+ }
-## Beoordeling
-De beoordelingscriteria zijn te vinden op [DLO](https://dlo.mijnhva.nl/d2l/le/content/609470/Home)
+ const items = [];
-## Planning
+ // Haal de de details op van de films met de opgegeven ids
+ for (const id of ids) {
+ const url = `https://api.themoviedb.org/3/movie/${id}?api_key=${apiKey}&language=en-US`;
+ const response = await fetch(url);
+ const movie = await response.json();
-| Planning | Maandag | Dinsdag | Vrijdag |
-|----------------------------|-----------------------|--------------------|---------------------------------------------|
-| Week 1 - Kickoff & concept | Introductie ne uitleg | Workshops | Feedback gesprekken |
-| Week 2 - The baseline | College + workshops | Workshops | Feedback gesprekken |
-| Week 3 - Enhance | College + workshops | Workshops | Feedback gesprekken(*DONDERDAG*) |
-| Week 4 - Enhance & wrap up | Tweede paasdag | Individuele vragen | Beoordelingsgesprekken(*DONDERDAG/VRIJDAG*) |
+ // voeg de films toe aan de items array
+ items.push(movie);
+ }
-## Bronnen
+ // Render de template met de opgehaalde films
+ return res.send(renderTemplate('server/views/favorites.liquid', {
+ title: 'Favorites',
+ items
+ }));
+});
+```
-- [Nodejs.org](https://nodejs.org/en/), voor de installatie van NodeJS op jouw systeem, kies voor NodeJS 22.13.1 Long Term Support. Dit is de meest stabiele versie van NodeJS, welke ondersteund wordt met goede documentatie.
-- [VSCode How To Open Terminal](https://www.youtube.com/watch?v=OmQhOnBzg_k), om iemand de terminal te zien openen en gebruiken op Youtube.
-- [Introduction to NodeJS](https://nodejs.dev/en/learn/), voor een in depth introductie met de NodeJS ontwikkelomgeving. Let op: dit is best een technisch verhaal. De eerste zes pagina’s zijn interessant.
-- Om serverside te kunnen renderen maken we gebruik van [TinyHttp](https://github.com/tinyhttp).
-- Voor templating maken we gebruik van [LiquidJS](https://liquidjs.com/).
-- [Liquid Filters](https://liquidjs.com/filters/overview.html)
-- Voor build tooling(CSS en JS) maken we gebruik [Vite](https://vitejs.dev/).
-- [Using the Fetch API @ MDN](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
-- [JSON.parse() @ MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse)
-- [Partial commits in GitHub Desktop](https://github.blog/news-insights/product-news/partial-commits-in-github-for-windows/)
-- [Committing and reviewing changes to your project in GitHub Desktop](https://docs.github.com/en/desktop/making-changes-in-a-branch/committing-and-reviewing-changes-to-your-project-in-github-desktop)
+## Zoeken
-## Inrichten ontwikkelomgeving
+Om te kunnen zoeken wordt er getest of **search** in de URL zit. Als dit zo is wordt de API url veranderd zodat alle films worden opgehaald relevant zijn voor de zoekterm.
-1. Navigeer naar [nodejs.org](https://nodejs.org/en/) en installeer de NodeJS ontwikkelomgeving. Kies voor _NodeJS 22.13.1 with long-term support_, download de benodigde bestanden en doorloop het installatieproces.
+```js
+ const searchQuery = req.query.search;
-2. Fork daarna [deze repository](https://github.com/cmda-minor-web/API-2425) en *clone* deze op jouw computer.
+ let apiUrl;
-3. Open deze repository in je code editor.
+ // Als search in de URL zit
+ if (searchQuery) {
+ // Gebruik de zoekendpoint van de API
+ apiUrl = `https://api.themoviedb.org/3/search/movie?api_key=${apiKey}&language=en-US&page=${page}&query=${encodeURIComponent(searchQuery)}`;
+ } else {
+ // Standaard ontdekking API
+ const genreQuery = selectedGenre ? `&with_genres=${selectedGenre}` : '';
+ apiUrl = `https://api.themoviedb.org/3/discover/movie?api_key=${apiKey}&language=en-US&page=${page}&sort_by=${sort}${genreQuery}`;
+ }
+```
-4. Open de _Terminal_ in Visual Studio Code door de toetscombinatie `` ^` `` (control + `) te gebruiken. Er opent een terminalscherm in de hoofdmap van jouw project.
+## CSS
-5. Voer in de terminal het commando `npm install` uit, door het in te typen en op enter te drukken. Je gebruikt _NPM_, de _NodeJS Package Manager_ om alle _afhankelijkheden_ voor dit project te installeren. NPM is een veelgebruikte package manager in frontend land. Voor dit project gebruiken we _TinyHTTP_ (om een _server_ te maken) en _Liquid_ (om HTML te _renderen_).
-- (Optioneel) Na de installatie is de map `node_modules` aangemaakt, en gevuld met allerlei _packages_. Scroll eens door deze map heen; vele honderden *open source* ontwikkelaars hebben de packages die je ziet gebouwd en die mag je gratis gebruiken. Ontwikkelen in NodeJS is *standing on the shoulders of giants*.
+Alle `liquid` bestanden hebben een los CSS bestand zodat het compact en los van elkaar staat. De kaarten worden via een render template.
-### Project starten en stoppen
-Start het voorbeeldproject op door in de terminal het commando `npm run dev` uit te voeren. Als het goed is, komt een melding te staan over het opstarten van de server: `Server available on http://localhost:3000` — Open deze URL in je browser. Let op: Vite draait op een andere poort dan TinyHTTP, dus je moet de poort van TinyHTTP gebruiken: http://localhost:3000
+Om de css die ik wil aanroepen voor elk bestand zet ik een algemene class die alleen voorkomt bij die pagina. Dit zorgt ervoor dat de css alleen wordt aangeroepen als dit nodig is.
-Als het werkt, zet je je server weer uit door in de terminal de toetscombinatie `^c` (control + c) in te voeren. Deze toetsencombinatie wordt in de terminal gebruikt om de huidige taak te stoppen en *controle* (vandaar de c) terug te krijgen van het programma.
+```css
+/* Detail pagina */
+.movieDetails {}
+```
-- Optioneel: Volg het [NodeJS ‘Hello World’ voorbeeld](https://medium.com/@mohammedijas/hello-world-in-node-js-b333275ddc89)
-- Optioneel, iets technischer: Lees de eerste vijf delen van [Introduction to Node](https://nodejs.dev/en/learn/) als je een meer in-depth introductie wilt met de NodeJS ontwikkelomgeving.
+## Link
+[Link naar project](tymonl.github.io/API-2425/)
+
+[Link naar website](https://api-2425-rpyo.onrender.com/)
diff --git a/client/fonts.css b/client/fonts.css
new file mode 100644
index 0000000..d1d98da
--- /dev/null
+++ b/client/fonts.css
@@ -0,0 +1,31 @@
+/* Light Weight */
+@font-face {
+ font-family: "Poppins";
+ src: local("Poppins"), url("/fonts/Quicksand-Light.ttf") format("truetype");
+ font-weight: 300;
+ font-style: normal;
+}
+
+/* Regular Weight */
+@font-face {
+ font-family: "Poppins";
+ src: local("Poppins"), url("/fonts/Quicksand-Regular.ttf") format("truetype");
+ font-weight: normal;
+ font-style: normal;
+}
+
+/* Medium Weight */
+@font-face {
+ font-family: "Poppins";
+ src: local("Poppins"), url("/fonts/Quicksand-Medium.ttf") format("truetype");
+ font-weight: 500; /* Medium weight */
+ font-style: normal;
+}
+
+/* Bold Weight */
+@font-face {
+ font-family: "Poppins";
+ src: local("Poppins"), url("/fonts/Quicksand-Bold.ttf") format("truetype");
+ font-weight: bold;
+ font-style: normal;
+}
\ No newline at end of file
diff --git a/client/index.css b/client/index.css
index 31cbc4b..e067510 100644
--- a/client/index.css
+++ b/client/index.css
@@ -1,20 +1,70 @@
/* global styles */
@import 'reset.css';
-@import "typography.css";
+@import 'typography.css';
+@import 'fonts.css';
/*.layout and view styling */
@import '../server/layouts/base.css';
@import '../server/views/index.css';
+@import '../server/views/details.css';
/* component styling */
@import '../server/components/card/card.css';
+html {
+ scroll-behavior: smooth;
+}
+
body {
- color: black;
- max-width: 1440px;
+ color: #fff;
margin: 0 auto;
}
+* {
+ box-sizing: border-box;
+ font-family: "Quicksand", sans-serif;
+}
+
main {
padding: 1rem;
}
+
+.container {
+ max-width: 1440px;
+}
+
+:root {
+ view-transition-name: root;
+}
+
+@view-transition {
+ navigation: auto;
+}
+
+html::view-transition {
+ animation-duration: 0.4s;
+}
+
+::view-transition-old(movie_*) {
+ opacity: 1;
+}
+
+::view-transition-new(movie_*) {
+ opacity: 0;
+ animation: fadeIn 0.5s ease forwards;
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+.fade-transition {
+ opacity: 1;
+ transition: opacity 0.4s ease-in-out;
+}
+
+.fade-transition.fade-out {
+ opacity: 0;
+}
+
diff --git a/client/index.js b/client/index.js
index 5f2dbf3..8831c40 100644
--- a/client/index.js
+++ b/client/index.js
@@ -1,3 +1,57 @@
import './index.css';
-console.log('Hello, world!');
+document.querySelectorAll('.card_image').forEach(card => {
+ card.addEventListener('click', (e) => {
+ e.preventDefault();
+
+ const movieId = card.getAttribute('id');
+ const targetUrl = `/movies/${movieId}`; // adjust depending on your site
+
+ if (document.startViewTransition) {
+ document.startViewTransition(() => {
+ window.location.href = targetUrl;
+ });
+ } else {
+ window.location.href = targetUrl;
+ }
+ });
+});
+
+var favoritesLink = document.querySelector('.goToFavorites');
+
+if (favoritesLink) {
+ favoritesLink.addEventListener('click', () => {
+ window.location.href = '/favorites'; // no query needed anymore
+ });
+}
+
+document.addEventListener('DOMContentLoaded', async () => {
+ const btn = document.querySelector('.addToFavorites');
+ if (!btn) return;
+
+ const id = btn.dataset.id;
+ const icon = btn.querySelector('i');
+ if (!id || !icon) return;
+
+ // Get current favorite status from server
+ const response = await fetch(`/api/favorites/${id}`);
+ const data = await response.json();
+
+ if (data.isFavorite) {
+ icon.classList.remove('fa-regular');
+ icon.classList.add('fa-solid');
+ }
+
+ btn.addEventListener('click', async () => {
+ const res = await fetch(`/api/favorites/${id}`, { method: 'POST' });
+ const result = await res.json();
+
+ if (result.status === 'added') {
+ icon.classList.remove('fa-regular');
+ icon.classList.add('fa-solid');
+ } else {
+ icon.classList.remove('fa-solid');
+ icon.classList.add('fa-regular');
+ }
+ });
+});
diff --git a/client/reset.css b/client/reset.css
index d9f27b5..caea9a1 100644
--- a/client/reset.css
+++ b/client/reset.css
@@ -2,7 +2,7 @@
v2.0 | 20110126
License: none (public domain)
*/
-
+/*
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
@@ -23,6 +23,7 @@ time, mark, audio, video {
font: inherit;
vertical-align: baseline;
}
+ */
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
diff --git a/favorites/favorites b/favorites/favorites
new file mode 100644
index 0000000..b2686d2
--- /dev/null
+++ b/favorites/favorites
@@ -0,0 +1 @@
+["1197306","950387"]
\ No newline at end of file
diff --git a/fonts/Quicksand-Bold.ttf b/fonts/Quicksand-Bold.ttf
new file mode 100644
index 0000000..0106805
Binary files /dev/null and b/fonts/Quicksand-Bold.ttf differ
diff --git a/fonts/Quicksand-Light.ttf b/fonts/Quicksand-Light.ttf
new file mode 100644
index 0000000..fcf86af
Binary files /dev/null and b/fonts/Quicksand-Light.ttf differ
diff --git a/fonts/Quicksand-Medium.ttf b/fonts/Quicksand-Medium.ttf
new file mode 100644
index 0000000..afca2d9
Binary files /dev/null and b/fonts/Quicksand-Medium.ttf differ
diff --git a/fonts/Quicksand-Regular.ttf b/fonts/Quicksand-Regular.ttf
new file mode 100644
index 0000000..c03548a
Binary files /dev/null and b/fonts/Quicksand-Regular.ttf differ
diff --git a/fonts/Quicksand-SemiBold.ttf b/fonts/Quicksand-SemiBold.ttf
new file mode 100644
index 0000000..83475ea
Binary files /dev/null and b/fonts/Quicksand-SemiBold.ttf differ
diff --git a/images/noImage.jpg b/images/noImage.jpg
new file mode 100644
index 0000000..0817846
Binary files /dev/null and b/images/noImage.jpg differ
diff --git a/package-lock.json b/package-lock.json
index ce70d77..8bda0fd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"chokidar-cli": "^3.0.0",
"dotenv": "^16.4.7",
"liquidjs": "^10.21.0",
+ "node-localstorage": "^3.0.5",
"nodemon": "^3.1.9",
"npm-run-all": "^4.1.5",
"sirv": "^3.0.1",
@@ -1834,6 +1835,14 @@
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA=="
},
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
"node_modules/internal-slot": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
@@ -2289,6 +2298,17 @@
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
},
+ "node_modules/node-localstorage": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/node-localstorage/-/node-localstorage-3.0.5.tgz",
+ "integrity": "sha512-GCwtK33iwVXboZWYcqQHu3aRvXEBwmPkAMRBLeaX86ufhqslyUkLGsi4aW3INEfdQYpUB5M9qtYf3eHvAk2VBg==",
+ "dependencies": {
+ "write-file-atomic": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=0.12"
+ }
+ },
"node_modules/nodemon": {
"version": "3.1.9",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz",
@@ -3021,7 +3041,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
"engines": {
"node": ">=14"
},
@@ -3609,6 +3628,18 @@
"node": ">=8"
}
},
+ "node_modules/write-file-atomic": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
+ "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
diff --git a/package.json b/package.json
index 84461e9..b7adfab 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"chokidar-cli": "^3.0.0",
"dotenv": "^16.4.7",
"liquidjs": "^10.21.0",
+ "node-localstorage": "^3.0.5",
"nodemon": "^3.1.9",
"npm-run-all": "^4.1.5",
"sirv": "^3.0.1",
diff --git a/server/components/card/card.css b/server/components/card/card.css
index 5be8712..978c572 100644
--- a/server/components/card/card.css
+++ b/server/components/card/card.css
@@ -1,10 +1,59 @@
+.grid__item {
+ background-color: #fff;
+ box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
+ border-radius: 10px;
+
+ a {
+ text-decoration: none;
+ }
+}
+
+.movie__link {
+
+ &:hover img,
+ &:focus img {
+ scale: 1.05;
+ }
+}
+
.card {
width: 100%;
max-width: 300px;
-}
+ display: grid;
+ overflow: hidden;
+ border-radius: 10px;
+ height: 100%;
+ position: relative;
-.card__image {
- width: 100%;
- height: auto;
- margin-top: 1rem;
-}
+ .card__image {
+ width: 100%;
+ height: auto;
+ border-radius: 10px 10px 0 0;
+ transition: all 0.3s ease-out;
+ }
+
+ h2 {
+ color: #fff;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ transition: all 0.3s ease-out;
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.5));
+ margin: 0;
+ padding: 10px;
+ opacity: 0;
+ }
+
+ *:not(.card__image, h2) {
+ padding: 10px;
+ margin: 0;
+ }
+
+ &:hover {
+ h2 {
+ transition: all 0.3s ease-out;
+ opacity: 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/server/components/card/card.liquid b/server/components/card/card.liquid
index c1706e4..b3811e7 100644
--- a/server/components/card/card.liquid
+++ b/server/components/card/card.liquid
@@ -1,4 +1,9 @@
-