diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg
deleted file mode 100644
index 6893eb1..0000000
--- a/frontend/public/favicon.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/public/icons.svg b/frontend/public/icons.svg
deleted file mode 100644
index e952219..0000000
--- a/frontend/public/icons.svg
+++ /dev/null
@@ -1,24 +0,0 @@
-
diff --git a/frontend/src/App.css b/frontend/src/App.css
deleted file mode 100644
index e69de29..0000000
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index d3db457..235f3d9 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,18 +1,8 @@
-import './App.css';
-import 'bootstrap/dist/css/bootstrap.min.css';
-import GameDetails from './GameDetails.tsx';
-import Header from './Header.tsx';
-import GamesGrid from './GamesGrid.tsx';
+import "bootstrap/dist/css/bootstrap.min.css";
+import GamesGrid from "./pages/GamesGrid/GamesGrid";
function App() {
-
- return (
- <>
- {/* */}
- {/* */}
-
- >
- )
+ return ;
}
export default App;
diff --git a/frontend/src/GameDetails.tsx b/frontend/src/GameDetails.tsx
deleted file mode 100644
index 960d866..0000000
--- a/frontend/src/GameDetails.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { Container, Image } from "react-bootstrap";
-import Header from "./Header";
-
-function fetchGameDetails(){
-
-}
-
-export default function GameDetails() {
- return (
- <>
-
-
-
- Release year | Age rating
-
-
- Game Title
-
-
-
-
-
NTNU Rating:
-
Your Rating:
-
-
- Tags:
- Developer(s):
- Publisher(s):
- Platform(s):
-
- Description
-
-
-
- >
- );
-}
\ No newline at end of file
diff --git a/frontend/src/GamesGrid.tsx b/frontend/src/GamesGrid.tsx
deleted file mode 100644
index 288b80b..0000000
--- a/frontend/src/GamesGrid.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Container, Image } from "react-bootstrap";
-import Header from "./Header";
-
-export default function GamesGrid () {
- return (
- <>
-
- {/* Filtering, paging and sorting */}
- {/* Grid itself */}
-
-
-
-
Game Title
- Release year
- Tags
- Rating
-
-
-
- >
- );
-}
\ No newline at end of file
diff --git a/frontend/src/Header.tsx b/frontend/src/Header.tsx
deleted file mode 100644
index 95d51fe..0000000
--- a/frontend/src/Header.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Button, Container, Form, InputGroup, Nav, Navbar } from 'react-bootstrap';
-import { Search } from 'react-bootstrap-icons';
-
-
-export default function Header() {
- return (
-
-
- NTNU Game Ratings(WIP)
-
- My Ratings
-
-
- );
-}
\ No newline at end of file
diff --git a/frontend/src/MyRatings.tsx b/frontend/src/MyRatings.tsx
deleted file mode 100644
index 0dafea7..0000000
--- a/frontend/src/MyRatings.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import Header from './Header.tsx'
-
-export default function MyRatings () {
- return (
- <>
-
- >
- );
-}
\ No newline at end of file
diff --git a/frontend/src/components/GameListItem/GameListItem.module.css b/frontend/src/components/GameListItem/GameListItem.module.css
new file mode 100644
index 0000000..10da3c9
--- /dev/null
+++ b/frontend/src/components/GameListItem/GameListItem.module.css
@@ -0,0 +1,34 @@
+.row {
+ display: flex;
+ align-items: center;
+ flex: 1 1 0;
+ min-height: var(--row-min-height);
+ gap: 1.5rem;
+ padding: 0 1.5rem;
+}
+
+.odd {
+ background-color: var(--color-stripe-odd);
+}
+
+.even {
+ background-color: var(--color-stripe-even);
+}
+
+.image {
+ width: 95px;
+ height: 95px;
+ object-fit: cover;
+ background-color: #ddd;
+ flex-shrink: 0;
+}
+
+.info p {
+ margin: 0;
+}
+
+.title {
+ margin: 0 0 0.25rem;
+ font-size: 1.1rem;
+ font-weight: 700;
+}
diff --git a/frontend/src/components/GameListItem/GameListItem.tsx b/frontend/src/components/GameListItem/GameListItem.tsx
new file mode 100644
index 0000000..e682427
--- /dev/null
+++ b/frontend/src/components/GameListItem/GameListItem.tsx
@@ -0,0 +1,31 @@
+import styles from "./GameListItem.module.css";
+
+interface GameListItemProps {
+ title: string;
+ year: number;
+ tags: string;
+ rating: number;
+ image: string;
+ striped: boolean;
+}
+
+export default function GameListItem({
+ title,
+ year,
+ tags,
+ rating,
+ image,
+ striped,
+}: GameListItemProps) {
+ return (
+
+

+
+
{title}
+
{year}
+
{tags}
+
{rating.toFixed(1)}/10
+
+
+ );
+}
diff --git a/frontend/src/components/Header/Header.module.css b/frontend/src/components/Header/Header.module.css
new file mode 100644
index 0000000..7fde7a7
--- /dev/null
+++ b/frontend/src/components/Header/Header.module.css
@@ -0,0 +1,42 @@
+.header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ height: var(--header-height);
+ padding: 0 1.5rem;
+ background-color: var(--color-brand);
+}
+
+.brand,
+.link {
+ font-weight: 700;
+ color: var(--color-text);
+ white-space: nowrap;
+}
+
+.link {
+ text-decoration: none;
+}
+
+.search {
+ flex: 1;
+ max-width: 420px;
+}
+
+.search :global(.input-group) {
+ border-radius: 999px;
+ overflow: hidden;
+}
+
+.search :global(.form-control) {
+ border: none;
+}
+
+.search :global(.form-control:focus) {
+ box-shadow: none;
+}
+
+.search :global(.btn) {
+ border: none;
+}
diff --git a/frontend/src/components/Header/Header.tsx b/frontend/src/components/Header/Header.tsx
new file mode 100644
index 0000000..b846e7f
--- /dev/null
+++ b/frontend/src/components/Header/Header.tsx
@@ -0,0 +1,27 @@
+import { Button, Form, InputGroup } from "react-bootstrap";
+import { Search } from "react-bootstrap-icons";
+import styles from "./Header.module.css";
+
+export default function Header() {
+ return (
+
+ );
+}
diff --git a/frontend/src/components/Layout/Layout.module.css b/frontend/src/components/Layout/Layout.module.css
new file mode 100644
index 0000000..52611a0
--- /dev/null
+++ b/frontend/src/components/Layout/Layout.module.css
@@ -0,0 +1,5 @@
+.shell {
+ width: 100%;
+ max-width: var(--content-width);
+ margin: 0 auto;
+}
diff --git a/frontend/src/components/Layout/Layout.tsx b/frontend/src/components/Layout/Layout.tsx
new file mode 100644
index 0000000..ced9683
--- /dev/null
+++ b/frontend/src/components/Layout/Layout.tsx
@@ -0,0 +1,6 @@
+import type { ReactNode } from "react";
+import styles from "./Layout.module.css";
+
+export default function Layout({ children }: { children: ReactNode }) {
+ return {children}
;
+}
diff --git a/frontend/src/components/Toolbar/Toolbar.module.css b/frontend/src/components/Toolbar/Toolbar.module.css
new file mode 100644
index 0000000..51f876a
--- /dev/null
+++ b/frontend/src/components/Toolbar/Toolbar.module.css
@@ -0,0 +1,33 @@
+.toolbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: var(--toolbar-height);
+ padding: 0 1.5rem;
+ background-color: #fff;
+ border-bottom: 1px solid var(--color-border);
+ color: var(--color-muted);
+}
+
+.action {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
+ border: none;
+ background: none;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+}
+
+.pagination {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+.sort {
+ display: inline-flex;
+ align-items: center;
+ gap: 1rem;
+}
diff --git a/frontend/src/components/Toolbar/Toolbar.tsx b/frontend/src/components/Toolbar/Toolbar.tsx
new file mode 100644
index 0000000..e2eadbb
--- /dev/null
+++ b/frontend/src/components/Toolbar/Toolbar.tsx
@@ -0,0 +1,33 @@
+import {
+ ArrowDownUp,
+ ChevronDown,
+ ChevronLeft,
+ ChevronRight,
+ Funnel,
+} from "react-bootstrap-icons";
+import styles from "./Toolbar.module.css";
+
+export default function Toolbar() {
+ return (
+
+
+
+
+
+ Page 1
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/index.css b/frontend/src/index.css
index e69de29..8082bd9 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -0,0 +1,34 @@
+:root {
+ /* 50% of a 1920px viewport — the grid stays centered at this width until
+ the viewport can't fit it anymore, then it fills the screen (see Layout). */
+ --content-width: 960px;
+ --header-height: 72px;
+ --toolbar-height: 56px;
+ /* Rows grow to fill the list height evenly, split across however many
+ games are currently rendered, but never shrink below this. Once rows
+ hit the floor, the list clips instead of squeezing them unreadably
+ thin (see GamesGrid.module.css and GameListItem.module.css). */
+ --row-min-height: 112px;
+ --color-brand: #a8504f;
+ --color-text: #1a1a1a;
+ --color-muted: #495057;
+ --color-stripe-odd: #f6d9d8;
+ --color-stripe-even: #fdf6f5;
+ --color-border: #e9cfce;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ background-color: var(--color-stripe-odd);
+ color: var(--color-text);
+ font-family:
+ system-ui,
+ -apple-system,
+ "Segoe UI",
+ Roboto,
+ sans-serif;
+}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index bef5202..15753af 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1,10 +1,10 @@
-import { StrictMode } from 'react'
-import { createRoot } from 'react-dom/client'
-import './index.css'
-import App from './App.tsx'
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import "./index.css";
+import App from "./App";
-createRoot(document.getElementById('root')!).render(
+createRoot(document.getElementById("root")!).render(
,
-)
+);
diff --git a/frontend/src/pages/GameDetails/GameDetails.tsx b/frontend/src/pages/GameDetails/GameDetails.tsx
new file mode 100644
index 0000000..3415488
--- /dev/null
+++ b/frontend/src/pages/GameDetails/GameDetails.tsx
@@ -0,0 +1,32 @@
+import { Container, Image } from "react-bootstrap";
+import Header from "../../components/Header/Header";
+
+export default function GameDetails() {
+ return (
+ <>
+
+
+ Release year | Age rating
+ Game Title
+
+
+
+
NTNU Rating:
+
Your Rating:
+
+
+ Tags:
+ Developer(s):
+ Publisher(s):
+ Platform(s):
+ Description
+
+ >
+ );
+}
diff --git a/frontend/src/pages/GamesGrid/GamesGrid.module.css b/frontend/src/pages/GamesGrid/GamesGrid.module.css
new file mode 100644
index 0000000..4dd75cc
--- /dev/null
+++ b/frontend/src/pages/GamesGrid/GamesGrid.module.css
@@ -0,0 +1,6 @@
+.list {
+ display: flex;
+ flex-direction: column;
+ height: calc(100vh - var(--header-height) - var(--toolbar-height));
+ overflow: hidden;
+}
diff --git a/frontend/src/pages/GamesGrid/GamesGrid.tsx b/frontend/src/pages/GamesGrid/GamesGrid.tsx
new file mode 100644
index 0000000..93c6aff
--- /dev/null
+++ b/frontend/src/pages/GamesGrid/GamesGrid.tsx
@@ -0,0 +1,63 @@
+import Header from "../../components/Header/Header";
+import Layout from "../../components/Layout/Layout";
+import Toolbar from "../../components/Toolbar/Toolbar";
+import GameListItem from "../../components/GameListItem/GameListItem";
+import styles from "./GamesGrid.module.css";
+
+const games = [
+ {
+ title: "Heroes of Might and Magic: Olden Era",
+ year: 2026,
+ tags: "Turn-based strategy",
+ rating: 9.3,
+ image: "",
+ },
+ {
+ title: "Minecraft",
+ year: 2009,
+ tags: "Indie, Survival, Adventure, Sandbox",
+ rating: 9.1,
+ image: "",
+ },
+ {
+ title: "Ori and the Blind Forest",
+ year: 2015,
+ tags: "Metroidvania",
+ rating: 8.8,
+ image: "",
+ },
+ {
+ title: "Pokémon Legends: Z-A",
+ year: 2025,
+ tags: "Action RPG",
+ rating: 0.1,
+ image: "",
+ },
+ {
+ title: "Hades",
+ year: 2020,
+ tags: "Roguelike",
+ rating: 9.4,
+ image: "",
+ },
+];
+
+export default function GamesGrid() {
+ return (
+ <>
+
+
+
+
+ {games.map((game, index) => (
+
+ ))}
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/MyRatings/MyRatings.tsx b/frontend/src/pages/MyRatings/MyRatings.tsx
new file mode 100644
index 0000000..aede895
--- /dev/null
+++ b/frontend/src/pages/MyRatings/MyRatings.tsx
@@ -0,0 +1,9 @@
+import Header from "../../components/Header/Header";
+
+export default function MyRatings() {
+ return (
+ <>
+
+ >
+ );
+}