From 62e3ed6de5e360e53f894c78afb2dcea81d0af3e Mon Sep 17 00:00:00 2001 From: Clemens Beese Date: Mon, 12 Oct 2026 18:35:48 +0200 Subject: [PATCH 01/32] =?UTF-8?q?style(frontend):=20papirhvitt=20lyst=20te?= =?UTF-8?q?ma,=20m=C3=B8rkt=20tema,=20tekstknapper=20og=20tegning=20i=20si?= =?UTF-8?q?defeltet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/index.css | 118 +++++++++++++++++++++++++++++++---------- 1 file changed, 90 insertions(+), 28 deletions(-) diff --git a/frontend/src/index.css b/frontend/src/index.css index f12902e2..26d5f072 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,14 +1,15 @@ :root { - --text: #6b6375; - --text-h: #08060d; - --bg: #fff; - --border: #e5e4e7; - --code-bg: #f4f3ec; - --accent: #aa3bff; - --accent-bg: rgba(170, 59, 255, 0.1); - --accent-border: rgba(170, 59, 255, 0.5); - --social-bg: rgba(244, 243, 236, 0.5); - --shadow: rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px; + /* Lyst tema: varmt papirhvitt */ + --text: #5c554c; + --text-h: #1f1b16; + --bg: #f8f5ee; + --border: #e3ddd1; + --code-bg: #efeae0; + --accent: #2c6370; /* fra Damp-logoen */ + --accent-bg: rgba(44, 99, 112, 0.1); + --accent-border: rgba(44, 99, 112, 0.5); + --social-bg: rgba(239, 234, 224, 0.5); + --shadow: rgba(31, 27, 22, 0.1) 0 10px 15px -3px, rgba(31, 27, 22, 0.05) 0 4px 6px -2px; --sans: system-ui, 'Segoe UI', Roboto, sans-serif; --heading: system-ui, 'Segoe UI', Roboto, sans-serif; @@ -16,7 +17,7 @@ font: 18px/145% var(--sans); letter-spacing: 0.18px; - color-scheme: light dark; + color-scheme: light; color: var(--text); background: var(--bg); font-synthesis: none; @@ -29,23 +30,20 @@ } } -@media (prefers-color-scheme: dark) { - :root { - --text: #9ca3af; - --text-h: #f3f4f6; - --bg: #16171d; - --border: #2e303a; - --code-bg: #1f2028; - --accent: #c084fc; - --accent-bg: rgba(192, 132, 252, 0.15); - --accent-border: rgba(192, 132, 252, 0.5); - --social-bg: rgba(47, 48, 58, 0.5); - --shadow: rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px; - } - - #social .button-icon { - filter: invert(1) brightness(2); - } +/* Mørkt tema. data-theme settes på av scriptet i index.html og av ThemeToggle */ +:root[data-theme='dark'] { + color-scheme: dark; + /* Nøytral mørk med et lite grønt stikk */ + --text: #a9aeab; + --text-h: #e8ebe9; + --bg: #191b1a; + --border: #2f3331; + --code-bg: #232625; + --accent: #6fa3ae; /* fra Damp-logoen (mørk versjon) */ + --accent-bg: rgba(111, 163, 174, 0.15); + --accent-border: rgba(111, 163, 174, 0.5); + --social-bg: rgba(35, 38, 37, 0.5); + --shadow: rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px; } #root { @@ -107,3 +105,67 @@ code { padding: 4px 8px; background: var(--code-bg); } + +/* + * Tegningen i sidefeltet til venstre, under logoen + * Bare på brede skjermer der det er plass ved siden av innholdet. Som CSS-bakgrunn + * i en media query lastes bildet bare ned når det faktisk vises (bærekraft). + * 1126px er bredden på #root, samme tall som i Logo.css. + */ +@media (min-width: 1320px) { + body::before { + content: ''; + position: fixed; + z-index: -1; + top: 50%; + left: calc((100vw - 1126px) / 4); + width: min(280px, calc((100vw - 1126px) / 2 - 48px)); + aspect-ratio: 800 / 698; + transform: translate(-50%, -30%); + background: url('./assets/drawings/tegning.webp') center / contain no-repeat; + /* Det hvite papiret i skanningen smelter inn i papirbakgrunnen */ + mix-blend-mode: multiply; + opacity: 0.85; + pointer-events: none; + } +} + +/* Mørkt tema: inverterer tegningen så blyanten blir lys og papiret blir borte */ +@media (min-width: 1320px) { + :root[data-theme='dark'] body::before { + filter: invert(1); + mix-blend-mode: screen; + opacity: 0.6; + } +} + +/* Tekstknapp uten boks (meny, tema, visning). Aktiv = mørkere tekst + strek under */ +.text-button { + padding: 0; + font: inherit; + color: var(--text); + background: none; + border: 0; + cursor: pointer; + text-underline-offset: 4px; +} +.text-button:hover, +.text-button[aria-pressed='true'] { + color: var(--text-h); + text-decoration: underline; +} +.text-button:focus-visible { + outline: 2px solid var(--text-h); + outline-offset: 4px; +} + +/* Seksjonsoverskrift i trykk-stil: små, sperrede versaler (som WRITING på clemenbe.no) */ +.label { + margin: 0; + font-family: var(--mono); + font-size: 13px; + font-weight: 500; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--text-h); +} From 4a6f975faa5495e1278b1fb22677cdfc623d9f9e Mon Sep 17 00:00:00 2001 From: Clemens Beese Date: Mon, 12 Oct 2026 18:35:48 +0200 Subject: [PATCH 02/32] =?UTF-8?q?feat(frontend):=20sett=20tema=20f=C3=B8r?= =?UTF-8?q?=20siden=20tegnes=20og=20lang=3Dno?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/index.html | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/frontend/index.html b/frontend/index.html index 78357eeb..63465482 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,10 +1,23 @@ - + Damp + +
From 0c3024df7d7a71fb639024ea207344bd5d2ed0f6 Mon Sep 17 00:00:00 2001 From: Clemens Beese Date: Mon, 12 Oct 2026 18:35:48 +0200 Subject: [PATCH 03/32] feat(frontend): legg til tegning i sidefeltet --- frontend/src/assets/drawings/tegning.webp | Bin 0 -> 52872 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 frontend/src/assets/drawings/tegning.webp diff --git a/frontend/src/assets/drawings/tegning.webp b/frontend/src/assets/drawings/tegning.webp new file mode 100644 index 0000000000000000000000000000000000000000..1ac44aa3eb285980c58b2cd7a54afe64ac7e3c9a GIT binary patch literal 52872 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%0Dryrh|;O|<^nmoc_~hVyv| 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 Date: Mon, 12 Oct 2026 18:35:48 +0200 Subject: [PATCH 04/32] =?UTF-8?q?feat(frontend):=20ThemeToggle=20for=20lys?= =?UTF-8?q?t/m=C3=B8rkt=20tema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ThemeToggle/ThemeToggle.tsx | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 frontend/src/components/ThemeToggle/ThemeToggle.tsx diff --git a/frontend/src/components/ThemeToggle/ThemeToggle.tsx b/frontend/src/components/ThemeToggle/ThemeToggle.tsx new file mode 100644 index 00000000..b26ba3ad --- /dev/null +++ b/frontend/src/components/ThemeToggle/ThemeToggle.tsx @@ -0,0 +1,32 @@ +import { useEffect, useState } from 'react'; + +type Theme = 'light' | 'dark'; + +/** + * Knapp som bytter mellom lyst og mørkt tema. Første gang brukes temaet scriptet + * i index.html har satt (systemets innstilling), deretter huskes valget i localStorage. + */ +export function ThemeToggle() { + const [theme, setTheme] = useState(() => + document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light', + ); + + // Oppdaterer (som CSS-en leser) og husker valget + useEffect(() => { + document.documentElement.dataset.theme = theme; + try { + localStorage.setItem('theme', theme); + } catch { + // localStorage kan være blokkert (f.eks. privat modus), temaet virker likevel + } + }, [theme]); + + const next = theme === 'dark' ? 'light' : 'dark'; + + return ( + // Teksten sier hva knappen gjør, så skjermlesere og seende får samme informasjon + + ); +} From 0320db9de29543cfb3e22336f28bc842363180c5 Mon Sep 17 00:00:00 2001 From: Clemens Beese Date: Mon, 12 Oct 2026 18:35:48 +0200 Subject: [PATCH 05/32] =?UTF-8?q?feat(frontend):=20logo=20f=C3=B8lger=20va?= =?UTF-8?q?lgt=20tema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Logo/Logo.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/Logo/Logo.tsx b/frontend/src/components/Logo/Logo.tsx index 24eb3dcd..baa3d283 100644 --- a/frontend/src/components/Logo/Logo.tsx +++ b/frontend/src/components/Logo/Logo.tsx @@ -2,12 +2,12 @@ import logoOnDark from '../../assets/logo/damp-logo-stacked-on-dark.svg'; import logoOnLight from '../../assets/logo/damp-logo-stacked-on-light.svg'; import './Logo.css'; -/** Damp-logoen (symbol med navnet under). Bytter versjon etter lyst/mørkt tema. */ +/** Damp-logoen (symbol med navnet under). CSS viser riktig versjon for valgt tema. */ export function Logo() { return ( - - - Damp - +
+ Damp + Damp +
); } From bbdbb4d7d95b582154ac5cfa24b768e67d1c6fda Mon Sep 17 00:00:00 2001 From: Clemens Beese Date: Mon, 12 Oct 2026 18:35:48 +0200 Subject: [PATCH 06/32] =?UTF-8?q?style(frontend):=20logo=20i=20toppfeltet?= =?UTF-8?q?=20p=C3=A5=20smale=20skjermer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Logo/Logo.css | 29 ++++++++++++++------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/Logo/Logo.css b/frontend/src/components/Logo/Logo.css index a91c291b..008f1125 100644 --- a/frontend/src/components/Logo/Logo.css +++ b/frontend/src/components/Logo/Logo.css @@ -1,37 +1,38 @@ -/* Smale skjermer: øverst til venstre, scroller vekk så den ikke dekker innholdet */ +/* Smale skjermer: første element i toppfeltet (Header) */ .logo { - position: absolute; - top: 20px; - left: 20px; - z-index: 10; + flex-shrink: 0; } .logo img { display: block; - height: 88px; + height: 44px; width: auto; } -@media (max-width: 600px) { - .logo img { - height: 56px; /* Mindre logo på mobil */ - } +/* Viser riktig logo for valgt tema. display: none skjuler den andre også for skjermlesere */ +.logo .logo__img--dark { + display: none; +} +:root[data-theme='dark'] .logo .logo__img--light { + display: none; +} +:root[data-theme='dark'] .logo .logo__img--dark { + display: block; } /* Brede skjermer: fast og midt i sidepanelet til venstre. Må stå sist i filen */ @media (min-width: 1320px) { .logo { position: fixed; + z-index: 10; top: 20px; - /* Midten av sidepanelet. 1126px er bredden på #root i index.css; endres den, må den endres her også */ + /* Midten av sidepanelet. 1126px er bredden på #root i index.css */ left: calc((100vw - 1126px) / 4); - /* Flytter logoen halvparten av sin egen bredde til venstre, så midten av logoen havner midt i panelet */ transform: translateX(-50%); } .logo img { height: auto; - /* Større: opptil 160px bred, men aldri bredere enn panelet minus 24px luft på hver side */ width: min(90px, calc((100vw - 1126px) / 2 - 48px)); } -} \ No newline at end of file +} From f8bb8f62770aa2412b09fa5802b0f91dbc0b8c9d Mon Sep 17 00:00:00 2001 From: Clemens Beese Date: Mon, 12 Oct 2026 18:35:48 +0200 Subject: [PATCH 07/32] =?UTF-8?q?style(frontend):=20s=C3=B8kefelt=20med=20?= =?UTF-8?q?bare=20linje=20under?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/SearchBar/SearchBar.css | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/SearchBar/SearchBar.css b/frontend/src/components/SearchBar/SearchBar.css index 949cd4b3..231755bf 100644 --- a/frontend/src/components/SearchBar/SearchBar.css +++ b/frontend/src/components/SearchBar/SearchBar.css @@ -11,12 +11,27 @@ border: 0; } +/* Bare en linje under, som et skjemafelt på papir */ .search-bar__input { width: 100%; - padding: 0.6rem 0.8rem; + padding: 0.4rem 0; font: inherit; - border: 1px solid #767676; - border-radius: 0; + color: var(--text-h); + background: transparent; + border: 0; + border-bottom: 1px solid var(--text); + border-radius: 0; -webkit-appearance: textfield; appearance: textfield; } + +.search-bar__input::placeholder { + color: var(--text); +} + +/* Fokus: linjen blir mørkere og dobbelt så tykk, uten at noe flytter seg */ +.search-bar__input:focus-visible { + outline: none; + border-bottom-color: var(--text-h); + box-shadow: 0 1px 0 var(--text-h); +} From cc318718e54e7434593a51bc8732810cece70b6b Mon Sep 17 00:00:00 2001 From: Clemens Beese Date: Mon, 12 Oct 2026 18:35:48 +0200 Subject: [PATCH 08/32] style(frontend): Header i trykk-stil --- frontend/src/components/Header/Header.css | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 frontend/src/components/Header/Header.css diff --git a/frontend/src/components/Header/Header.css b/frontend/src/components/Header/Header.css new file mode 100644 index 00000000..b686e9e7 --- /dev/null +++ b/frontend/src/components/Header/Header.css @@ -0,0 +1,33 @@ +.header { + display: flex; + align-items: center; + gap: 24px; + padding: 16px 24px; + border-bottom: 1px solid var(--border); +} + +.header__search { + flex: 1; +} + +/* Menyen til høyre, i monospace som på clemenbe.no */ +.header__nav { + display: flex; + gap: 20px; + margin-left: auto; + font-family: var(--mono); + font-size: 15px; + white-space: nowrap; +} + +/* Mobil: logo og meny på første linje, søket på hele andre linje */ +@media (max-width: 600px) { + .header { + flex-wrap: wrap; + gap: 12px 20px; + } + .header__search { + order: 3; + flex-basis: 100%; + } +} From 0429009e6f918648b2421bea1fd21ca3137c361e Mon Sep 17 00:00:00 2001 From: Clemens Beese Date: Mon, 12 Oct 2026 18:35:48 +0200 Subject: [PATCH 09/32] =?UTF-8?q?feat(frontend):=20Header=20med=20s=C3=B8k?= =?UTF-8?q?,=20favoritter=20og=20tema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Header/Header.tsx | 44 +++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 frontend/src/components/Header/Header.tsx diff --git a/frontend/src/components/Header/Header.tsx b/frontend/src/components/Header/Header.tsx new file mode 100644 index 00000000..d62656bd --- /dev/null +++ b/frontend/src/components/Header/Header.tsx @@ -0,0 +1,44 @@ +import { Logo } from '../Logo/Logo'; +import { SearchBar } from '../SearchBar/SearchBar'; +import { ThemeToggle } from '../ThemeToggle/ThemeToggle'; +import './Header.css'; + +interface HeaderProps { + search: string; + onSearchChange: (value: string) => void; + favoritesCount: number; + showFavorites: boolean; + onToggleFavorites: () => void; +} + +/** + * Toppfeltet: logo, søk og meny (favoritter og tema), med søket øverst som på RAWG. + * Ingen bokser, bare tekst og en tynn linje under. + */ +export function Header({ + search, + onSearchChange, + favoritesCount, + showFavorites, + onToggleFavorites, +}: HeaderProps) { + return ( +
+ +
+ +
+ +
+ ); +} From c4082b8efb81955fe4cee7565d8719d68cc61533 Mon Sep 17 00:00:00 2001 From: Clemens Beese Date: Mon, 12 Oct 2026 18:35:48 +0200 Subject: [PATCH 10/32] style(frontend): erstatt Vite-rester med layout for hovedinnholdet --- frontend/src/App.css | 189 ++++--------------------------------------- 1 file changed, 14 insertions(+), 175 deletions(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index d6c7c9a0..8c3aa5cb 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,182 +1,21 @@ -.counter { - font-size: 16px; - padding: 5px 10px; - border-radius: 5px; - color: var(--accent); - background: var(--accent-bg); - border: 2px solid transparent; - transition: border-color 0.3s; - margin-bottom: 24px; - - &:hover { - border-color: var(--accent-border); - } - &:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; - } -} - -.hero { - position: relative; - - .base, - .framework, - .vite { - inset-inline: 0; - margin: 0 auto; - } - - .base { - width: 170px; - position: relative; - z-index: 0; - } - - .framework, - .vite { - position: absolute; - } - - .framework { - z-index: 1; - top: 34px; - height: 28px; - transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) scale(1.4); - } - - .vite { - z-index: 0; - top: 107px; - height: 26px; - width: auto; - transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) scale(0.8); - } -} - -#center { - display: flex; - flex-direction: column; - gap: 25px; - place-content: center; - place-items: center; - flex-grow: 1; - - @media (max-width: 1024px) { - padding: 32px 20px 24px; - gap: 18px; - } -} - -#next-steps { - display: flex; - border-top: 1px solid var(--border); +/* Hovedinnholdet under toppfeltet */ +.main { + padding: 24px; text-align: left; - - & > div { - flex: 1 1 0; - padding: 32px; - @media (max-width: 1024px) { - padding: 24px 20px; - } - } - - .icon { - margin-bottom: 16px; - width: 22px; - height: 22px; - } - - @media (max-width: 1024px) { - flex-direction: column; - text-align: center; - } -} - -#docs { - border-right: 1px solid var(--border); - - @media (max-width: 1024px) { - border-right: none; - border-bottom: 1px solid var(--border); - } } -#next-steps ul { - list-style: none; - padding: 0; +/* Linjen over resultatene: overskrift til venstre, valg av visning til høyre */ +.results-bar { display: flex; - gap: 8px; - margin: 32px 0 0; - - .logo { - height: 18px; - } - - a { - color: var(--text-h); - font-size: 16px; - border-radius: 6px; - background: var(--social-bg); - display: flex; - padding: 6px 12px; - align-items: center; - gap: 8px; - text-decoration: none; - transition: box-shadow 0.3s; - - &:hover { - box-shadow: var(--shadow); - } - .button-icon { - height: 18px; - width: 18px; - } - } - - @media (max-width: 1024px) { - margin-top: 20px; - flex-wrap: wrap; - justify-content: center; - - li { - flex: 1 1 calc(50% - 8px); - } - - a { - width: 100%; - justify-content: center; - box-sizing: border-box; - } - } -} - -#spacer { - height: 88px; - border-top: 1px solid var(--border); - @media (max-width: 1024px) { - height: 48px; - } + align-items: baseline; + justify-content: space-between; + padding-bottom: 8px; + border-bottom: 1px solid var(--border); } -.ticks { - position: relative; - width: 100%; - - &::before, - &::after { - content: ''; - position: absolute; - top: -4.5px; - border: 5px solid transparent; - } - - &::before { - left: 0; - border-left-color: var(--border); - } - &::after { - right: 0; - border-right-color: var(--border); - } +.results-bar__views { + display: flex; + gap: 16px; + font-family: var(--mono); + font-size: 14px; } From 1f6c131f7e8064dd5496e5f53bffd5ee1a62eb56 Mon Sep 17 00:00:00 2001 From: Clemens Beese Date: Mon, 12 Oct 2026 18:35:48 +0200 Subject: [PATCH 11/32] feat(frontend): bruk Header og vis bare favoritter --- frontend/src/App.tsx | 72 +++++++++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index f657f66c..502cc287 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,9 +1,9 @@ import { useState } from 'react'; import { GameList } from './components/GameList/GameList'; import { mockGames } from './mocks/games'; -import { SearchBar } from './components/SearchBar/SearchBar'; import { useDebounce } from './hooks/useDebounce'; -import { Logo} from './components/Logo/Logo'; +import { Header } from './components/Header/Header'; +import './App.css'; /** * Midlertidig forside: viser spillene med mockdata til GraphQL er klart. @@ -12,6 +12,7 @@ import { Logo} from './components/Logo/Logo'; function App() { const [favorites, setFavorites] = useState>(new Set()); const [view, setView] = useState<'list' | 'grid'>('list'); + const [showFavorites, setShowFavorites] = useState(false); // Det brukeren skriver nå, og det vi faktisk søker på etter 300 ms pause const [input, setInput] = useState(''); @@ -19,8 +20,9 @@ function App() { // MIDLERTIDIG: filtrerer mockdata på klienten for å kunne teste søket. // Når GraphQL er klart, sendes `query` til backend og filtreres i databasen. - const games = mockGames.filter((g) => - g.name.toLowerCase().includes(query.toLowerCase()), + const games = mockGames.filter( + (g) => + g.name.toLowerCase().includes(query.toLowerCase()) && (!showFavorites || favorites.has(g.id)), ); const toggleFavorite = (id: string) => { @@ -33,28 +35,48 @@ function App() { }; return ( -
- {/* NY: logoen øverst til høyre */} - -

Søkeresultater

- - {/* Bytter mellom visning 1 (liste) og visning 2 (rutenett) */} -
- - -
- - +
setShowFavorites((v) => !v)} /> -
+ +
+
+

{showFavorites ? 'Favoritter' : 'Søkeresultater'}

+ + {/* Bytter mellom visning 1 (liste) og visning 2 (rutenett) */} +
+ + +
+
+ + +
+ ); } From 3a9e9254b8005e498648a8cb078b430eb8c183c9 Mon Sep 17 00:00:00 2001 From: albdert Date: Mon, 12 Oct 2026 22:42:42 +0200 Subject: [PATCH 12/32] fix: db-tunnel bug og kommentert ut linje i .env.example --- backend/.env.example | 2 +- scripts/db-tunnel.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index 68799a7d..453b8c8a 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -6,4 +6,4 @@ TUNNEL_PORT=5433 # backend server env vars DATABASE_URL=postgresql://webapp:@localhost:5433/gamesdb PORT=3001 -JWT_TOKEN= \ No newline at end of file +#JWT_TOKEN= diff --git a/scripts/db-tunnel.sh b/scripts/db-tunnel.sh index 908c0089..9589d04a 100755 --- a/scripts/db-tunnel.sh +++ b/scripts/db-tunnel.sh @@ -3,7 +3,7 @@ set -euo pipefail cd "$(dirname "$0")/.." set -a -source db/.env +source backend/.env set +a if lsof -i ":$TUNNEL_PORT" >/dev/null 2>&1; then From 93f43db94571ee46e510f4a5c985c8cb58d6343a Mon Sep 17 00:00:00 2001 From: Albert Paulsen Lindberg Date: Tue, 13 Oct 2026 10:17:51 +0200 Subject: [PATCH 13/32] Update ci.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La til linje for å tvinge runner til å kjøre begge jobber uansett om en av de feiler silk at vi kan se om feilen ligger på backend eller frontend --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bfcc93e5..3b48878b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,7 @@ jobs: lint-and-test: runs-on: self-hosted strategy: + fail-fast: false matrix: workspace: [frontend, backend] defaults: From a75bc18445034fe8dc63e20d7893c83ee8a13f75 Mon Sep 17 00:00:00 2001 From: Albert Paulsen Lindberg Date: Tue, 13 Oct 2026 10:21:31 +0200 Subject: [PATCH 14/32] Update ci.yml Oppretter pnpm store hvis den ikke eksisterer --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b48878b..e8c9e054 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,10 @@ jobs: - uses: pnpm/action-setup@v4 with: version: 11.25.0 - + + - name: Ensure pnpm store exists + run: mkdir -p "$(pnpm store path --silent)" + - uses: actions/setup-node@v4 with: node-version: 24.21.0 From f45381fbe14f69be66de82ccade449d8292b90f5 Mon Sep 17 00:00:00 2001 From: Albert Paulsen Lindberg Date: Tue, 13 Oct 2026 10:23:49 +0200 Subject: [PATCH 15/32] Update ci.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pls fungere nå ... --- .github/workflows/ci.yml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8c9e054..69a97a45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,18 +19,23 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 24.21.0 + - uses: pnpm/action-setup@v4 with: version: 11.25.0 - - - name: Ensure pnpm store exists - run: mkdir -p "$(pnpm store path --silent)" - - - uses: actions/setup-node@v4 + + - name: Get pnpm store path + id: pnpm-store + run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" + + - uses: actions/cache@v4 with: - node-version: 24.21.0 - cache: pnpm - cache-dependency-path: ${{ matrix.workspace }}/pnpm-lock.yaml + path: ${{ steps.pnpm-store.outputs.path }} + key: pnpm-${{ runner.os }}-${{ matrix.workspace }}-${{ hashFiles(format('{0}/pnpm-lock.yaml', matrix.workspace)) }} + restore-keys: pnpm-${{ runner.os }}-${{ matrix.workspace }}- - name: Install dependencies run: pnpm install --frozen-lockfile From f0cf22ff045b1081acf686f314eb98bae6b2714b Mon Sep 17 00:00:00 2001 From: Clemens Beese Date: Tue, 13 Oct 2026 11:18:42 +0200 Subject: [PATCH 16/32] feat(frontend): Apollo-klient mot GraphQL-backend --- frontend/src/apollo.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 frontend/src/apollo.ts diff --git a/frontend/src/apollo.ts b/frontend/src/apollo.ts new file mode 100644 index 00000000..a4532adb --- /dev/null +++ b/frontend/src/apollo.ts @@ -0,0 +1,11 @@ +import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'; + +/** + * Felles GraphQL-klient for hele appen. Svar caches i minnet, så samme søk + * to ganger gir bare én forespørsel. URL-en kan overstyres med VITE_GRAPHQL_URL ved deploy. + */ + +export const client = new ApolloClient({ + link: new HttpLink({ uri: import.meta.env.VITE_GRAPHQL_URL ?? 'http://localhost:3001' }), + cache: new InMemoryCache(), +}); From e77fb058a825f040ca9b17ac34ef4451c193e674 Mon Sep 17 00:00:00 2001 From: Clemens Beese Date: Tue, 13 Oct 2026 11:18:42 +0200 Subject: [PATCH 17/32] feat(frontend): ApolloProvider rundt appen --- frontend/src/main.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 2239905c..d97a9d9c 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,10 +1,14 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; +import { ApolloProvider } from '@apollo/client/react'; +import { client } from './apollo'; import './index.css'; import App from './App.tsx'; createRoot(document.getElementById('root')!).render( - + + + , ); From 336d28c1bd46e4a285aee4508093a5eb0b41675b Mon Sep 17 00:00:00 2001 From: Clemens Beese Date: Tue, 13 Oct 2026 11:18:42 +0200 Subject: [PATCH 18/32] =?UTF-8?q?feat(frontend):=20sp=C3=B8rring=20for=20s?= =?UTF-8?q?=C3=B8keforslag=20(6=20spill,=20bare=20navn=20og=20=C3=A5r)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/graphql/searchSuggestions.ts | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 frontend/src/graphql/searchSuggestions.ts diff --git a/frontend/src/graphql/searchSuggestions.ts b/frontend/src/graphql/searchSuggestions.ts new file mode 100644 index 00000000..bd52ffb6 --- /dev/null +++ b/frontend/src/graphql/searchSuggestions.ts @@ -0,0 +1,34 @@ +import { gql, type TypedDocumentNode } from '@apollo/client'; + +/** Ett forslag i søkefeltet. Bare det dropdownen viser, så svaret blir lite. */ +export interface Suggestion { + id: string; + name: string; + released: string | null; +} + +interface SuggestionsData { + games: { edges: { node: Suggestion }[] }; +} + +interface SuggestionsVars { + search: string; +} + +/** + * Henter noen få spill som matcher teksten. totalCount spørres ikke om, + * så backend slipper å telle alle treff for hver forespørsel. + */ +export const SEARCH_SUGGESTIONS: TypedDocumentNode = gql` + query SearchSuggestions($search: String!) { + games(search: $search, first: 6) { + edges { + node { + id + name + released + } + } + } + } +`; From cd839e020a147483184e3abdf1207a7bb0ebd0f0 Mon Sep 17 00:00:00 2001 From: Clemens Beese Date: Tue, 13 Oct 2026 11:18:42 +0200 Subject: [PATCH 19/32] style(frontend): GameSearch med forslagsliste i trykk-stil --- .../src/components/GameSearch/GameSearch.css | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 frontend/src/components/GameSearch/GameSearch.css diff --git a/frontend/src/components/GameSearch/GameSearch.css b/frontend/src/components/GameSearch/GameSearch.css new file mode 100644 index 00000000..f66f023d --- /dev/null +++ b/frontend/src/components/GameSearch/GameSearch.css @@ -0,0 +1,87 @@ +.game-search { + position: relative; /* forslagslista plasseres i forhold til skjemaet */ +} + +/* Skjuler labelen visuelt, men beholder den for skjermlesere */ +.game-search__label { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0 0 0 0); + white-space: nowrap; + border: 0; +} + +/* Bare en linje under, som resten av toppfeltet */ +.game-search__input { + width: 100%; + padding: 0.4rem 0; + font: inherit; + color: var(--text-h); + background: transparent; + border: 0; + border-bottom: 1px solid var(--text); + border-radius: 0; + -webkit-appearance: textfield; + appearance: textfield; +} + +.game-search__input::placeholder { + color: var(--text); +} + +.game-search__input:focus-visible { + outline: none; + border-bottom-color: var(--text-h); + box-shadow: 0 1px 0 var(--text-h); +} + +/* Forslagene legger seg over innholdet under, med tynn linje rundt i stedet for skygge */ +.game-search__popup { + position: absolute; + z-index: 20; + top: calc(100% + 2px); + left: 0; + right: 0; + text-align: left; + background: var(--bg); + border: 1px solid var(--border); + border-top: 0; +} + +.game-search__list { + margin: 0; + padding: 4px 0; + list-style: none; +} + +.game-search__option { + display: flex; + justify-content: space-between; + gap: 16px; + padding: 6px 12px; + color: var(--text-h); + cursor: pointer; +} + +/* Markert forslag (pil eller mus): strek under som de andre knappene, og svak bakgrunn */ +.game-search__option[aria-selected='true'] { + background: var(--code-bg); + text-decoration: underline; + text-underline-offset: 4px; +} + +.game-search__year, +.game-search__status { + font-family: var(--mono); + font-size: 14px; + color: var(--text); +} + +.game-search__status { + margin: 0; + padding: 8px 12px; +} From 569bee68c451588e58cb4700396af16823899419 Mon Sep 17 00:00:00 2001 From: Clemens Beese Date: Tue, 13 Oct 2026 11:18:42 +0200 Subject: [PATCH 20/32] feat(frontend): GameSearch med forslag fra serveren (combobox) --- .../src/components/GameSearch/GameSearch.tsx | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 frontend/src/components/GameSearch/GameSearch.tsx diff --git a/frontend/src/components/GameSearch/GameSearch.tsx b/frontend/src/components/GameSearch/GameSearch.tsx new file mode 100644 index 00000000..d753ebf9 --- /dev/null +++ b/frontend/src/components/GameSearch/GameSearch.tsx @@ -0,0 +1,138 @@ +import { useId, useState, type KeyboardEvent } from 'react'; +import { useQuery } from '@apollo/client/react'; +import { useDebounce } from '../../hooks/useDebounce'; +import { SEARCH_SUGGESTIONS, type Suggestion } from '../../graphql/searchSuggestions'; +import './GameSearch.css'; + +// Kortere tekst enn dette gir for mange tilfeldige treff, så vi spør ikke serveren +const MIN_CHARS = 2; + +interface GameSearchProps { + /** Enter uten valgt forslag: søk i hovedlista */ + onSearch: (term: string) => void; + /** Brukeren valgte ett spill i forslagslista */ + onSelectGame: (game: Suggestion) => void; +} + +/** + * Søkefelt med forslag (combobox). Mens man skriver hentes bare noen få treff fra + * serveren og vises under feltet. Hovedlista endres ikke før man trykker Enter, så + * den ikke hopper for hver bokstav og pagineringen ikke nullstilles hele tiden. + * + * Tastatur: pil opp/ned flytter i lista, Enter velger, Escape lukker. + * Følger WAI-ARIA-mønsteret "combobox med listbox". + */ + +export function GameSearch({ onSearch, onSelectGame }: GameSearchProps) { + const [input, setInput] = useState(''); + const [open, setOpen] = useState(false); + const [active, setActive] = useState(-1); // markert forslag, -1 = ingen + const id = useId(); + const listId = `${id}-list`; + + // Spør først 300 ms etter siste tastetrykk, og ikke ved for kort tekst + const term = useDebounce(input.trim(), 300); + const { data, previousData, loading } = useQuery(SEARCH_SUGGESTIONS, { + variables: { search: term }, + skip: term.length < MIN_CHARS, + }); + + // Viser forrige svar mens det nye lastes, så lista ikke blinker + const suggestions = (data ?? previousData)?.games.edges.map((e) => e.node) ?? []; + const showPopup = open && term.length >= MIN_CHARS; + + function select(game: Suggestion) { + setOpen(false); + setActive(-1); + onSelectGame(game); + } + + function handleKeyDown(e: KeyboardEvent) { + if (e.key === 'ArrowDown') { + e.preventDefault(); + setOpen(true); + setActive((i) => Math.min(i + 1, suggestions.length - 1)); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + setActive((i) => Math.max(i - 1, -1)); + } else if (e.key === 'Escape') { + setOpen(false); + setActive(-1); + } else if (e.key === 'Enter' && active >= 0 && suggestions[active]) { + e.preventDefault(); // velger forslaget i stedet for å sende skjemaet + select(suggestions[active]); + } + } + + return ( +
{ + e.preventDefault(); + setOpen(false); + onSearch(input.trim()); + }} + > + {/* Skjult visuelt, men lest opp av skjermlesere. Placeholder alene er ikke nok */} + + 0} + aria-controls={listId} + aria-autocomplete="list" + aria-activedescendant={active >= 0 ? `${id}-option-${active}` : undefined} + placeholder="Søk etter spill …" + autoComplete="off" + value={input} + onChange={(e) => { + setInput(e.target.value); + setOpen(true); + setActive(-1); + }} + onKeyDown={handleKeyDown} + onFocus={() => setOpen(true)} + onBlur={() => setOpen(false)} + /> + + {showPopup && ( +
+ {suggestions.length > 0 ? ( +
    + {suggestions.map((game, i) => ( +
  • { + e.preventDefault(); + select(game); + }} + onMouseEnter={() => setActive(i)} + > + {game.name} + {game.released && ( + {game.released.slice(0, 4)} + )} +
  • + ))} +
+ ) : ( + // role="status" gjør at skjermlesere leser opp meldingen +

+ {loading ? 'Søker …' : 'Ingen treff'} +

+ )} +
+ )} +
+ ); +} From 6ea1d93ddb4968676cf1c960a1a8027d719b7c57 Mon Sep 17 00:00:00 2001 From: Clemens Beese Date: Tue, 13 Oct 2026 11:18:42 +0200 Subject: [PATCH 21/32] feat(frontend): bruk GameSearch i Header --- frontend/src/components/Header/Header.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/Header/Header.tsx b/frontend/src/components/Header/Header.tsx index d62656bd..ad0f70bb 100644 --- a/frontend/src/components/Header/Header.tsx +++ b/frontend/src/components/Header/Header.tsx @@ -1,11 +1,12 @@ import { Logo } from '../Logo/Logo'; -import { SearchBar } from '../SearchBar/SearchBar'; +import { GameSearch } from '../GameSearch/GameSearch'; +import type { Suggestion } from '../../graphql/searchSuggestions'; import { ThemeToggle } from '../ThemeToggle/ThemeToggle'; import './Header.css'; interface HeaderProps { - search: string; - onSearchChange: (value: string) => void; + onSearch: (term: string) => void; + onSelectGame: (game: Suggestion) => void; favoritesCount: number; showFavorites: boolean; onToggleFavorites: () => void; @@ -16,8 +17,8 @@ interface HeaderProps { * Ingen bokser, bare tekst og en tynn linje under. */ export function Header({ - search, - onSearchChange, + onSearch, + onSelectGame, favoritesCount, showFavorites, onToggleFavorites, @@ -26,7 +27,7 @@ export function Header({
- +