diff --git a/frontend/index.html b/frontend/index.html index 78357ee..6346548 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,10 +1,23 @@ - + Damp + +
diff --git a/frontend/src/App.css b/frontend/src/App.css index d6c7c9a..8c3aa5c 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; } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index f657f66..502cc28 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) */} +
+ + +
+
+ + +
+ ); } diff --git a/frontend/src/assets/drawings/tegning.webp b/frontend/src/assets/drawings/tegning.webp new file mode 100644 index 0000000..1ac44aa Binary files /dev/null and b/frontend/src/assets/drawings/tegning.webp differ diff --git a/frontend/src/components/Header/Header.css b/frontend/src/components/Header/Header.css new file mode 100644 index 0000000..b686e9e --- /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%; + } +} diff --git a/frontend/src/components/Header/Header.tsx b/frontend/src/components/Header/Header.tsx new file mode 100644 index 0000000..d62656b --- /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 ( +
+ +
+ +
+ +
+ ); +} diff --git a/frontend/src/components/Logo/Logo.css b/frontend/src/components/Logo/Logo.css index a91c291..008f112 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 +} diff --git a/frontend/src/components/Logo/Logo.tsx b/frontend/src/components/Logo/Logo.tsx index 24eb3dc..baa3d28 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 +
); } diff --git a/frontend/src/components/SearchBar/SearchBar.css b/frontend/src/components/SearchBar/SearchBar.css index 949cd4b..231755b 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); +} diff --git a/frontend/src/components/ThemeToggle/ThemeToggle.tsx b/frontend/src/components/ThemeToggle/ThemeToggle.tsx new file mode 100644 index 0000000..b26ba3a --- /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 + + ); +} diff --git a/frontend/src/index.css b/frontend/src/index.css index f12902e..26d5f07 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); +}