diff --git a/FrameRate/src/App.css b/FrameRate/src/App.css
deleted file mode 100644
index d6c7c9a..0000000
--- a/FrameRate/src/App.css
+++ /dev/null
@@ -1,182 +0,0 @@
-.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);
- 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;
- 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;
- }
-}
-
-.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);
- }
-}
diff --git a/FrameRate/src/App.tsx b/FrameRate/src/App.tsx
index 8cebd60..d80a585 100644
--- a/FrameRate/src/App.tsx
+++ b/FrameRate/src/App.tsx
@@ -1,101 +1,12 @@
-import { useState } from 'react';
-import heroImg from './assets/hero.png';
-import reactLogo from './assets/react.svg';
-import viteLogo from './assets/vite.svg';
-import './App.css';
+import './index.css';
+import { Layout } from './components/layout/Layout';
+import { FeaturedMovie } from './components/FeaturedMovie';
function App() {
- const [count, setCount] = useState(0);
-
return (
- <>
-
-
-
-
Get started
-
- Edit src/App.tsx and save to test HMR
-
-
-
-
-
-
-
-
-
-
-
Documentation
-
Your questions, answered
-
-
-
-
-
Connect with us
-
Join the Vite community
-
-
-
-
-
-
- >
+
+
+
);
}
diff --git a/FrameRate/src/components/FeaturedMovie.tsx b/FrameRate/src/components/FeaturedMovie.tsx
new file mode 100644
index 0000000..c60e632
--- /dev/null
+++ b/FrameRate/src/components/FeaturedMovie.tsx
@@ -0,0 +1,60 @@
+import { featuredMovie as movie, featuredReview as review, genres } from '../data/mockData';
+
+const IMAGE_BASE = 'https://image.tmdb.org/t/p';
+const DICE = ['⚀', '⚁', '⚂', '⚃', '⚄', '⚅'];
+
+export function FeaturedMovie() {
+ const year = movie.release_date.slice(0, 4);
+ const genreNames = movie.genre_ids.map(id => genres[id]);
+ const rounded = Math.round(movie.average_rating);
+
+ return (
+
+ {/* Venstre: tekst */}
+
+
Utvalgt fra topplisten
+
{movie.title}
+
+
+ {year}
+ {genreNames.join(' / ')}
+
+
+
+
+ {DICE[rounded - 1]}
+
+
+
Terningkast {movie.average_rating.toFixed(1)}
+
Basert på {movie.review_count} anmeldelser
+
+
+
+
+ «{review.text}»
+
+
+
+
+
+
+ {/* Høyre: plakat */}
+
+
+ );
+}
diff --git a/FrameRate/src/components/layout/Footer.tsx b/FrameRate/src/components/layout/Footer.tsx
new file mode 100644
index 0000000..a53d2c2
--- /dev/null
+++ b/FrameRate/src/components/layout/Footer.tsx
@@ -0,0 +1,28 @@
+export function Footer() {
+ return (
+
+ );
+}
diff --git a/FrameRate/src/components/layout/Header.tsx b/FrameRate/src/components/layout/Header.tsx
new file mode 100644
index 0000000..1e7a485
--- /dev/null
+++ b/FrameRate/src/components/layout/Header.tsx
@@ -0,0 +1,71 @@
+// ---------- Lyset i menyen ----------
+
+/** Flytter lyset i menyen til en x-posisjon (piksler fra venstre kant av menyen). */
+function moveLight(nav: HTMLElement, x: number) {
+ nav.style.setProperty('--x', `${x}px`);
+}
+
+/** Mus: lyset følger musepekeren. */
+function handleMouseMove(e: React.MouseEvent) {
+ const nav = e.currentTarget;
+ moveLight(nav, e.clientX - nav.getBoundingClientRect().left);
+}
+
+/** Tastatur: lyset hopper til midten av lenken som får fokus. */
+function handleFocus(e: React.FocusEvent) {
+ const nav = e.currentTarget;
+ const link = e.target.getBoundingClientRect();
+ moveLight(nav, link.left - nav.getBoundingClientRect().left + link.width / 2);
+}
+
+// ---------- Headeren ----------
+
+export function Header() {
+ return (
+
+ );
+}
diff --git a/FrameRate/src/components/layout/Layout.tsx b/FrameRate/src/components/layout/Layout.tsx
new file mode 100644
index 0000000..79054a5
--- /dev/null
+++ b/FrameRate/src/components/layout/Layout.tsx
@@ -0,0 +1,14 @@
+import type { ReactNode } from 'react';
+import { Header } from './Header';
+import { Footer } from './Footer';
+
+/** Header og footer som er lik på alle sider. Sideinnholdet havner i . */
+export function Layout({ children }: { children: ReactNode }) {
+ return (
+ <>
+
+ {children}
+
+ >
+ );
+}
diff --git a/FrameRate/src/data/mockData.ts b/FrameRate/src/data/mockData.ts
new file mode 100644
index 0000000..faf7b09
--- /dev/null
+++ b/FrameRate/src/data/mockData.ts
@@ -0,0 +1,44 @@
+// Midlertidige data i samme format som TMDB.
+// Byttes ut med data fra vår egen GraphQL-backend senere.
+
+// Sjangernavn for genre_ids. Hentes fra TMDB /genre/movie/list ved import.
+export const genres: Record = {
+ 28: 'Action',
+ 18: 'Drama',
+ 80: 'Krim',
+ 9648: 'Mysterium',
+ 53: 'Thriller',
+};
+
+export const featuredMovie = {
+ // --- Fra TMDB ---
+ id: 343611,
+ title: 'Jack Reacher: Never Go Back',
+ original_title: 'Jack Reacher: Never Go Back',
+ original_language: 'en',
+ overview:
+ 'Jack Reacher must uncover the truth behind a major government conspiracy in order to clear his name. On the run as a fugitive from the law, Reacher uncovers a potential secret from his past that could change his life forever.',
+ release_date: '2016-10-19',
+ genre_ids: [53, 28, 80, 18, 9648],
+ poster_path: '/IfB9hy4JH1eH6HEfIgIGORXi5h.jpg',
+ backdrop_path: '/4ynQYtSEuU5hyipcGkfD6ncwtwz.jpg',
+ popularity: 26.818468,
+ vote_average: 4.19,
+ vote_count: 201,
+ adult: false,
+ video: false,
+
+ // --- Fra /movie/{id} ved import ---
+ runtime: 118,
+ director: 'Edward Zwick',
+
+ // --- Fra våre egne anmeldelser ---
+ average_rating: 4.3,
+ review_count: 12,
+};
+
+export const featuredReview = {
+ text: 'Solid action, men historien er mer forutsigbar enn den første filmen.',
+ author: 'henrik',
+ date: '24. september 2026',
+};
diff --git a/FrameRate/src/index.css b/FrameRate/src/index.css
index f12902e..d242f05 100644
--- a/FrameRate/src/index.css
+++ b/FrameRate/src/index.css
@@ -1,109 +1,457 @@
+/* ---------- Grunnoppsett ---------- */
: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;
-
- --sans: system-ui, 'Segoe UI', Roboto, sans-serif;
- --heading: system-ui, 'Segoe UI', Roboto, sans-serif;
- --mono: ui-monospace, Consolas, monospace;
-
- font: 18px/145% var(--sans);
- letter-spacing: 0.18px;
- color-scheme: light dark;
- color: var(--text);
- background: var(--bg);
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-
- @media (max-width: 1024px) {
- font-size: 16px;
+ background-color: #0b0c10;
+ color: white;
+ font-family:
+ system-ui,
+ -apple-system,
+ 'Segoe UI',
+ Roboto,
+ sans-serif;
+}
+
+body {
+ margin: 0;
+}
+
+/* ---------- Header ---------- */
+.header {
+ position: relative;
+ overflow: hidden; /* lysstrålen holder seg inne i headeren */
+ display: grid;
+ grid-template-columns: 1fr auto 1fr;
+ align-items: center;
+ padding: 1rem 1.5rem;
+ background: #0b0c10;
+ border-bottom: 1px solid #1f2229;
+}
+
+.header a {
+ color: #a0a4ad;
+ text-decoration: none;
+}
+
+.header a:hover {
+ color: white;
+}
+
+.header-brand {
+ display: flex;
+ align-items: center;
+}
+
+.logo {
+ font-family: Georgia, serif;
+ font-size: 1.75rem;
+ font-weight: bold;
+ color: white !important;
+}
+
+.header-tagline {
+ margin-left: 0.75rem;
+ padding-left: 0.75rem;
+ border-left: 1px solid #3a3e48;
+ font-style: italic;
+ color: #a0a4ad;
+}
+
+.login {
+ justify-self: end;
+ padding: 0.6rem 1.2rem;
+ border-radius: 8px;
+ background: #f2b630;
+ color: black !important;
+ font-weight: bold;
+}
+
+/* ---------- Kameraet ---------- */
+.camera {
+ width: 2.6rem;
+ height: auto;
+ margin-right: 0.75rem;
+ fill: #2a2d35;
+ stroke: #f2b630;
+ stroke-width: 2;
+}
+
+.reel {
+ transform-box: fill-box;
+ transform-origin: center;
+ animation: spin 0.8s linear 2; /* snurrer to runder mens lyset slås på */
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
}
}
-@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;
+/* ---------- Lysstrålen fra kameraet (ved sidelasting) ---------- */
+.projector-beam {
+ position: absolute;
+ left: 4.4rem; /* starter ved linsen */
+ top: 50%;
+ height: 220px;
+ width: 100%;
+ transform: translateY(-50%);
+ pointer-events: none;
+ filter: blur(8px);
+ mix-blend-mode: screen;
+ opacity: 0;
+ animation: beam-on 1.8s ease-out 0.2s;
+}
+
+/* Stråleformen: smal ved linsen, bred utover mot høyre */
+.projector-beam::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ clip-path: polygon(0 46%, 100% 0, 100% 100%, 0 54%);
+ background: linear-gradient(
+ to right,
+ rgba(255, 236, 190, 0.6),
+ rgba(255, 214, 140, 0.15) 60%,
+ rgba(255, 214, 140, 0)
+ );
+}
+
+@keyframes beam-on {
+ 0% {
+ opacity: 1;
+ clip-path: inset(0 100% 0 0);
+ }
+ 60% {
+ opacity: 1;
+ clip-path: inset(0 0 0 0);
}
+ 100% {
+ opacity: 0;
+ clip-path: inset(0 0 0 0);
+ }
+}
+
+/* Innholdet dukker opp når lyset treffer det */
+.logo,
+.header-tagline,
+.header-nav,
+.login {
+ animation: reveal 0.6s ease backwards;
+}
- #social .button-icon {
- filter: invert(1) brightness(2);
+.logo {
+ animation-delay: 0.35s;
+}
+.header-tagline {
+ animation-delay: 0.5s;
+}
+.header-nav {
+ animation-delay: 0.75s;
+}
+.login {
+ animation-delay: 1.05s;
+}
+
+@keyframes reveal {
+ from {
+ opacity: 0;
+ filter: brightness(0.3);
+ }
+ to {
+ opacity: 1;
+ filter: brightness(1);
}
}
-#root {
- width: 1126px;
- max-width: 100%;
- margin: 0 auto;
- text-align: center;
- border-inline: 1px solid var(--border);
- min-height: 100svh;
+/* ---------- Meny med lys som følger musen ---------- */
+.header-nav {
+ position: relative;
display: flex;
- flex-direction: column;
- box-sizing: border-box;
+ gap: 2rem;
+ padding: 0.75rem 1.5rem;
}
-body {
- margin: 0;
+/* Lenkene ligger over lyset */
+.header-nav a {
+ position: relative;
+ z-index: 1;
+ transition:
+ color 0.3s ease,
+ text-shadow 0.3s ease;
}
-h1,
-h2 {
- font-family: var(--heading);
- font-weight: 500;
- color: var(--text-h);
+.header-nav a:hover {
+ color: white;
+ text-shadow: 0 0 12px rgba(255, 214, 140, 0.7);
}
-h1 {
- font-size: 56px;
- letter-spacing: -1.68px;
- margin: 32px 0;
- @media (max-width: 1024px) {
- font-size: 36px;
- margin: 20px 0;
- }
+.header-nav .active {
+ color: white;
+ border-bottom: 3px solid #f2b630;
+}
+
+/* Beholderen for lyset: følger musen, og blurrer alt inni */
+.spotlight {
+ position: absolute;
+ top: -1.25rem;
+ bottom: -0.5rem;
+ left: calc(var(--x, 50%) - 90px);
+ width: 180px;
+ pointer-events: none;
+ opacity: 0;
+ filter: blur(6px);
+ mix-blend-mode: screen;
+ transition: opacity 0.4s ease;
}
-h2 {
- font-size: 24px;
- line-height: 118%;
- letter-spacing: -0.24px;
- margin: 0 0 8px;
- @media (max-width: 1024px) {
- font-size: 20px;
+
+/* Lyskjeglen: sterk øverst, svakere nedover */
+.spotlight::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ clip-path: polygon(42% 0, 58% 0, 100% 100%, 0 100%);
+ background: linear-gradient(
+ to bottom,
+ rgba(255, 236, 190, 0.55),
+ rgba(255, 214, 140, 0.15) 70%,
+ rgba(255, 214, 140, 0)
+ );
+}
+
+/* Lysflekken der strålen treffer menyen */
+.spotlight::after {
+ content: '';
+ position: absolute;
+ left: 10%;
+ right: 10%;
+ bottom: 0;
+ height: 60%;
+ background: radial-gradient(ellipse at 50% 70%, rgba(255, 220, 150, 0.45), transparent 70%);
+}
+
+.header-nav:hover .spotlight,
+.header-nav:focus-within .spotlight {
+ opacity: 1;
+ animation: flicker 3s infinite;
+}
+
+/* Lett flimring, som et ekte projektorlys */
+@keyframes flicker {
+ 0%,
+ 100% {
+ opacity: 1;
}
+ 45% {
+ opacity: 0.9;
+ }
+ 50% {
+ opacity: 1;
+ }
+ 55% {
+ opacity: 0.93;
+ }
+}
+
+/* ---------- Utvalgt film ---------- */
+.featured {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ align-items: center;
+ gap: 3rem;
+ max-width: 75rem;
+ padding: 4rem 1.5rem;
+}
+
+.featured-label {
+ margin: 0;
+ color: #f2b630;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ font-size: 0.9rem;
+}
+
+.featured-title {
+ margin: 0.5rem 0;
+ font-family: Georgia, serif;
+ font-size: 4rem;
+}
+
+.featured-meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1.5rem;
+ color: #a0a4ad;
+ font-size: 1.1rem;
+}
+
+.featured-rating {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ max-width: 32rem;
+ margin: 2rem 0;
+ padding: 1rem 1.5rem;
+ border: 1px solid #2a2d35;
+ border-radius: 12px;
+ background: #15171c;
+}
+
+.featured-dice {
+ font-size: 3.5rem;
+ line-height: 1;
+ color: #f2b630;
+}
+
+.featured-rating-title {
+ margin: 0;
+ color: #f2b630;
+ font-weight: bold;
+ text-transform: uppercase;
+}
+
+.featured-rating-count {
+ margin: 0.25rem 0 0;
+ color: #a0a4ad;
+}
+
+.featured-quote {
+ margin: 0 0 2rem;
+ padding-left: 1.5rem;
+ border-left: 3px solid #f2b630;
+ font-style: italic;
+ font-size: 1.25rem;
+ color: #d0d2d6;
}
-p {
+
+.featured-quote p {
margin: 0;
}
-code,
-.counter {
- font-family: var(--mono);
- display: inline-flex;
- border-radius: 4px;
- color: var(--text-h);
+.featured-quote footer {
+ margin-top: 0.5rem;
+ font-style: normal;
+ font-size: 0.95rem;
+ color: #a0a4ad;
+}
+
+.featured-buttons {
+ display: flex;
+ gap: 1rem;
+}
+
+.featured-poster {
+ width: 100%;
+ max-width: 300px;
+ height: auto;
+ border-radius: 12px;
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6);
+}
+
+/* ---------- Knapper ---------- */
+.button-white,
+.button-outline {
+ padding: 0.8rem 1.5rem;
+ border-radius: 8px;
+ font: inherit;
+ font-weight: bold;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+.button-white {
+ background: white;
+ color: black;
+ border: none;
+}
+
+.button-outline {
+ background: transparent;
+ color: #f2b630;
+ border: 1px solid #f2b630;
}
-code {
- font-size: 15px;
- line-height: 135%;
- padding: 4px 8px;
- background: var(--code-bg);
+.button-outline:hover {
+ background: rgba(242, 182, 48, 0.1);
+}
+
+/* ---------- Footer ---------- */
+.footer {
+ padding: 2rem 1.5rem;
+ background: #0b0c10;
+ border-top: 1px solid #1f2229;
+ color: #a0a4ad;
+}
+
+.footer-top {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 2rem;
+ padding-bottom: 2rem;
+ border-bottom: 1px solid #1f2229;
+}
+
+.footer-brand {
+ display: flex;
+ align-items: center;
+}
+
+.footer-logo {
+ font-family: Georgia, serif;
+ font-size: 1.5rem;
+ font-weight: bold;
+ color: white;
+}
+
+.footer-tagline {
+ margin-left: 0.75rem;
+ padding-left: 0.75rem;
+ border-left: 1px solid #3a3e48;
+ font-style: italic;
+}
+
+.footer-text {
+ max-width: 40rem;
+ margin: 0.75rem 0 0;
+}
+
+.footer-links {
+ display: flex;
+ gap: 2rem;
+}
+
+.footer-links a {
+ color: #a0a4ad;
+ text-decoration: none;
+}
+
+.footer-links a:hover {
+ color: white;
+}
+
+.footer-bottom {
+ display: flex;
+ justify-content: space-between;
+ padding-top: 1.5rem;
+ font-size: 0.9rem;
+}
+
+/* ---------- Tastaturfokus ---------- */
+a:focus-visible,
+button:focus-visible {
+ outline: 3px solid #f2b630;
+ outline-offset: 3px;
+}
+
+/* ---------- Mindre bevegelse ---------- */
+@media (prefers-reduced-motion: reduce) {
+ .reel,
+ .projector-beam,
+ .logo,
+ .header-tagline,
+ .header-nav,
+ .login,
+ .header-nav:hover .spotlight,
+ .header-nav:focus-within .spotlight {
+ animation: none;
+ }
}
diff --git a/docs/AI_Deklarasjon/AI_Deklarasjon.md b/docs/AI_Deklarasjon/AI_Deklarasjon.md
index 97c5d6d..90dbab4 100644
--- a/docs/AI_Deklarasjon/AI_Deklarasjon.md
+++ b/docs/AI_Deklarasjon/AI_Deklarasjon.md
@@ -33,4 +33,15 @@ Her dokumenterer vi bruk av KI-verktøy i prosjektet, med én del per pull reque
## Hva vi har gjort selv
-- Endret config-filer
\ No newline at end of file
+- Endret config-filer
+
+## PR #20 - Layout(header, footer) og Featured Movie
+
+**Verktøy:** Claude (Opus 5.5) via Claude Code
+**Brukt av:** Philip
+
+### Hva KI ble brukt til
+
+- Lage kamera logoen
+- Forslag til hvordan nettsiden kan se ut
+- Viste en lettere måte å holde koden organisert som f.eks. med layout mappe
Connect with us
-Join the Vite community
---
-
-
- GitHub
-
-
- -
-
-
- Discord
-
-
- -
-
-
- X.com
-
-
- -
-
-
- Bluesky
-
-
-
-