diff --git a/countries.json b/countries.json new file mode 100644 index 0000000..cfdc609 --- /dev/null +++ b/countries.json @@ -0,0 +1,92 @@ +[ + { + "name": "Switzerland", + "hint": "Alpine country in Central Europe", + "cities": [ + { "name": "Bern", "x": 48, "y": 58 }, + { "name": "Zürich", "x": 58, "y": 38 }, + { "name": "Geneva", "x": 22, "y": 72 } + ] + }, + { + "name": "Norway", + "hint": "Scandinavian country with long coastline", + "cities": [ + { "name": "Oslo", "x": 55, "y": 72 }, + { "name": "Bergen", "x": 32, "y": 60 }, + { "name": "Tromsø", "x": 62, "y": 18 } + ] + }, + { + "name": "Italy", + "hint": "Boot-shaped peninsula in Southern Europe", + "cities": [ + { "name": "Rome", "x": 52, "y": 58 }, + { "name": "Milan", "x": 42, "y": 22 }, + { "name": "Naples", "x": 58, "y": 72 } + ] + }, + { + "name": "Japan", + "hint": "Island nation in East Asia", + "cities": [ + { "name": "Tokyo", "x": 72, "y": 48 }, + { "name": "Osaka", "x": 58, "y": 58 }, + { "name": "Sapporo", "x": 70, "y": 22 } + ] + }, + { + "name": "Brazil", + "hint": "Largest country in South America", + "cities": [ + { "name": "Brasília", "x": 58, "y": 52 }, + { "name": "São Paulo", "x": 60, "y": 68 }, + { "name": "Manaus", "x": 38, "y": 38 } + ] + }, + { + "name": "Australia", + "hint": "Continent and country in the Southern Hemisphere", + "cities": [ + { "name": "Canberra", "x": 72, "y": 72 }, + { "name": "Sydney", "x": 78, "y": 68 }, + { "name": "Perth", "x": 22, "y": 65 } + ] + }, + { + "name": "France", + "hint": "Country in Western Europe, hexagonal shape", + "cities": [ + { "name": "Paris", "x": 50, "y": 32 }, + { "name": "Lyon", "x": 58, "y": 55 }, + { "name": "Marseille", "x": 58, "y": 72 } + ] + }, + { + "name": "India", + "hint": "Large peninsula in South Asia", + "cities": [ + { "name": "New Delhi", "x": 46, "y": 28 }, + { "name": "Mumbai", "x": 32, "y": 55 }, + { "name": "Chennai", "x": 52, "y": 72 } + ] + }, + { + "name": "Canada", + "hint": "Second largest country in the world", + "cities": [ + { "name": "Ottawa", "x": 62, "y": 52 }, + { "name": "Vancouver", "x": 22, "y": 55 }, + { "name": "Toronto", "x": 60, "y": 58 } + ] + }, + { + "name": "Germany", + "hint": "Central European country", + "cities": [ + { "name": "Berlin", "x": 58, "y": 28 }, + { "name": "Munich", "x": 48, "y": 68 }, + { "name": "Hamburg", "x": 42, "y": 18 } + ] + } +] diff --git a/frontend/game.html b/frontend/game.html new file mode 100644 index 0000000..f95b44f --- /dev/null +++ b/frontend/game.html @@ -0,0 +1,182 @@ + + + + + + GeoDraw — Game + + + + +
+ +
+ +
+ +
+
+
+ + +
+ +
+
+
+
+ Round 1 / 3 +
+ +
+
Loading…
+
+
+ +
+
+ 60 + sec +
+
+
+
+
+ +
+ + +
+ +
+ +
+
+ + +
+ + +
+ + +
+ +
+
+
+ + + +
+ + + + + + + + + diff --git a/frontend/index.html b/frontend/index.html index 20b97e2..0ac38a3 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -203,6 +203,7 @@ + \ No newline at end of file diff --git a/frontend/leaderboard.html b/frontend/leaderboard.html new file mode 100644 index 0000000..637b4ad --- /dev/null +++ b/frontend/leaderboard.html @@ -0,0 +1,154 @@ + + + + + + GeoDraw — Leaderboard + + + + + +
+ +
+ +
+ +
+
+
+ +
+
+ +

Leaderboard

+
+ +
+ +
+
+ # + Player + Rounds + Date + Score +
+
+ +
+
+ + + +
+
+
+ + + +
+ + + + + diff --git a/frontend/lobby.html b/frontend/lobby.html new file mode 100644 index 0000000..bf8b16d --- /dev/null +++ b/frontend/lobby.html @@ -0,0 +1,117 @@ + + + + + + GeoDraw — Lobby + + + + +
+ +
+ +
+ +
+
+
+ + +
+
+ + Multiplayer · 3 rounds +
+ +
+ 🏠 + Lobby +
+ +

+ Ready to
test your
geography? +

+ +

+ You'll get 3 countries to draw from memory. Each round lasts 60 seconds. Your score is based on accuracy — the closer your borders, the higher the points. +

+ +
+ 🌍 3 Rounds + 60 sec each + 🏆 Leaderboard +
+
+ + +
+

Enter your name

+

Your name will appear on the leaderboard.

+ +
+
+ + + +
+ + +
+
+ +
+
+
+ + + +
+ + + + + diff --git a/frontend/results.html b/frontend/results.html new file mode 100644 index 0000000..0c47178 --- /dev/null +++ b/frontend/results.html @@ -0,0 +1,356 @@ + + + + + + GeoDraw — Results + + + + +
+ +
+ +
+ +
+
+
+ +
+ 🌍 +

Game over!

+

Played by

+
+ +
+

Total Score

+
+ 0 / 300 +
+ +
+ +
+

Round breakdown

+
+ +
+
+
+ + + +
+
+
+ + + +
+ + + + + + diff --git a/frontend/scripts/countries.js b/frontend/scripts/countries.js new file mode 100644 index 0000000..1b77e2f --- /dev/null +++ b/frontend/scripts/countries.js @@ -0,0 +1,116 @@ +// countries.js — country data helpers (inline, no fetch needed) + +const Countries = (() => { + const COUNTRIES_DATA = [ + { + "name": "Switzerland", + "hint": "Alpine country in Central Europe", + "cities": [ + { "name": "Bern", "x": 48, "y": 58 }, + { "name": "Zürich", "x": 58, "y": 38 }, + { "name": "Geneva", "x": 22, "y": 72 } + ] + }, + { + "name": "Norway", + "hint": "Scandinavian country with long coastline", + "cities": [ + { "name": "Oslo", "x": 55, "y": 72 }, + { "name": "Bergen", "x": 32, "y": 60 }, + { "name": "Tromsø", "x": 62, "y": 18 } + ] + }, + { + "name": "Italy", + "hint": "Boot-shaped peninsula in Southern Europe", + "cities": [ + { "name": "Rome", "x": 52, "y": 58 }, + { "name": "Milan", "x": 42, "y": 22 }, + { "name": "Naples", "x": 58, "y": 72 } + ] + }, + { + "name": "Japan", + "hint": "Island nation in East Asia", + "cities": [ + { "name": "Tokyo", "x": 72, "y": 48 }, + { "name": "Osaka", "x": 58, "y": 58 }, + { "name": "Sapporo", "x": 70, "y": 22 } + ] + }, + { + "name": "Brazil", + "hint": "Largest country in South America", + "cities": [ + { "name": "Brasília", "x": 58, "y": 52 }, + { "name": "São Paulo", "x": 60, "y": 68 }, + { "name": "Manaus", "x": 38, "y": 38 } + ] + }, + { + "name": "Australia", + "hint": "Continent and country in the Southern Hemisphere", + "cities": [ + { "name": "Canberra", "x": 72, "y": 72 }, + { "name": "Sydney", "x": 78, "y": 68 }, + { "name": "Perth", "x": 22, "y": 65 } + ] + }, + { + "name": "France", + "hint": "Western Europe, roughly hexagonal shape", + "cities": [ + { "name": "Paris", "x": 50, "y": 32 }, + { "name": "Lyon", "x": 58, "y": 55 }, + { "name": "Marseille", "x": 58, "y": 72 } + ] + }, + { + "name": "India", + "hint": "Large peninsula in South Asia", + "cities": [ + { "name": "New Delhi", "x": 46, "y": 28 }, + { "name": "Mumbai", "x": 32, "y": 55 }, + { "name": "Chennai", "x": 52, "y": 72 } + ] + }, + { + "name": "Canada", + "hint": "Second largest country in the world", + "cities": [ + { "name": "Ottawa", "x": 62, "y": 52 }, + { "name": "Vancouver", "x": 22, "y": 55 }, + { "name": "Toronto", "x": 60, "y": 58 } + ] + }, + { + "name": "Germany", + "hint": "Central European country", + "cities": [ + { "name": "Berlin", "x": 58, "y": 28 }, + { "name": "Munich", "x": 48, "y": 68 }, + { "name": "Hamburg", "x": 42, "y": 18 } + ] + } + ]; + + let _data = []; + + async function loadCountries() { + if (_data.length) return _data; + _data = COUNTRIES_DATA; + return _data; + } + + function getRandomCountries(count = 3) { + const shuffled = [..._data].sort(() => Math.random() - 0.5); + return shuffled.slice(0, count); + } + + function getCities(countryName) { + const c = _data.find(c => c.name === countryName); + return c ? c.cities : []; + } + + return { loadCountries, getRandomCountries, getCities }; +})(); diff --git a/frontend/scripts/drawing.js b/frontend/scripts/drawing.js new file mode 100644 index 0000000..a1ac3a2 --- /dev/null +++ b/frontend/scripts/drawing.js @@ -0,0 +1,148 @@ +// drawing.js — canvas drawing module + +const Drawing = (() => { + let canvas, ctx; + let isDrawing = false; + let points = []; // [{x, y}, ...] + let cities = []; // [{name, x, y}, ...] (% coords) + + const STROKE_COLOR = "#1a7fc4"; + const STROKE_WIDTH = 2.5; + + function init(canvasEl) { + canvas = canvasEl; + ctx = canvas.getContext("2d"); + + // Pointer events (mouse + touch) + canvas.addEventListener("pointerdown", onDown); + canvas.addEventListener("pointermove", onMove); + canvas.addEventListener("pointerup", onUp); + canvas.addEventListener("pointerleave", onUp); + canvas.style.touchAction = "none"; + + _resize(); + window.addEventListener("resize", _resize); + } + + function _resize() { + if (!canvas) return; + const { width, height } = canvas.getBoundingClientRect(); + canvas.width = width * window.devicePixelRatio; + canvas.height = height * window.devicePixelRatio; + ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + _redraw(); + } + + function _pos(e) { + const r = canvas.getBoundingClientRect(); + return { + x: (e.clientX - r.left), + y: (e.clientY - r.top), + }; + } + + function onDown(e) { + e.preventDefault(); + isDrawing = true; + const p = _pos(e); + points.push(p); + ctx.beginPath(); + ctx.moveTo(p.x, p.y); + } + + function onMove(e) { + if (!isDrawing) return; + e.preventDefault(); + const p = _pos(e); + points.push(p); + ctx.lineTo(p.x, p.y); + ctx.strokeStyle = STROKE_COLOR; + ctx.lineWidth = STROKE_WIDTH; + ctx.lineJoin = "round"; + ctx.lineCap = "round"; + ctx.stroke(); + } + + function onUp(e) { + if (!isDrawing) return; + isDrawing = false; + e.preventDefault(); + } + + function clear() { + points = []; + if (!ctx) return; + const w = canvas.getBoundingClientRect().width; + const h = canvas.getBoundingClientRect().height; + ctx.clearRect(0, 0, w, h); + _drawCities(); + } + + function setCities(cityList) { + cities = cityList || []; + _drawCities(); + } + + function _drawCities() { + if (!ctx || !cities.length) return; + const w = canvas.getBoundingClientRect().width; + const h = canvas.getBoundingClientRect().height; + + cities.forEach(city => { + const cx = (city.x / 100) * w; + const cy = (city.y / 100) * h; + + // Dot + ctx.beginPath(); + ctx.arc(cx, cy, 5, 0, Math.PI * 2); + ctx.fillStyle = "rgba(240,180,40,0.9)"; + ctx.fill(); + ctx.strokeStyle = "rgba(255,255,255,0.9)"; + ctx.lineWidth = 1.5; + ctx.stroke(); + + // Label + ctx.font = "bold 11px 'DM Sans', sans-serif"; + ctx.fillStyle = "#0b1f2a"; + ctx.textAlign = "center"; + + // White pill background + const textW = ctx.measureText(city.name).width + 10; + const textH = 16; + const tx = cx; + const ty = cy - 14; + + ctx.save(); + ctx.fillStyle = "rgba(255,255,255,0.88)"; + ctx.beginPath(); + ctx.roundRect(tx - textW / 2, ty - textH / 2 - 1, textW, textH, 4); + ctx.fill(); + ctx.restore(); + + ctx.fillStyle = "#0b1f2a"; + ctx.fillText(city.name, tx, ty + 4); + }); + } + + function _redraw() { + _drawCities(); + if (!points.length) return; + ctx.beginPath(); + ctx.strokeStyle = STROKE_COLOR; + ctx.lineWidth = STROKE_WIDTH; + ctx.lineJoin = "round"; + ctx.lineCap = "round"; + points.forEach((p, i) => i === 0 ? ctx.moveTo(p.x, p.y) : ctx.lineTo(p.x, p.y)); + ctx.stroke(); + } + + function getPoints() { + return [...points]; + } + + function destroy() { + window.removeEventListener("resize", _resize); + } + + return { init, clear, setCities, getPoints, destroy }; +})(); diff --git a/frontend/scripts/game.js b/frontend/scripts/game.js new file mode 100644 index 0000000..24a1a9f --- /dev/null +++ b/frontend/scripts/game.js @@ -0,0 +1,158 @@ +// game.js — round management, timer, submit + +const TOTAL_ROUNDS = 3; +const ROUND_DURATION = 60; // seconds + +let roundCountries = []; +let currentRound = 0; +let scores = []; +let timerInterval = null; +let timeLeft = ROUND_DURATION; + +// ── DOM refs +const elCountryName = document.getElementById("country-name"); +const elCountryHint = document.getElementById("country-hint"); +const elRoundNum = document.getElementById("round-num"); +const elTimerNum = document.getElementById("timer-num"); +const elTimerBar = document.getElementById("timer-bar"); +const elBtnClear = document.getElementById("btn-clear"); +const elBtnSubmit = document.getElementById("btn-submit"); +const elCanvas = document.getElementById("draw-canvas"); + +// ── Init +async function initGame() { + await Countries.loadCountries(); + roundCountries = Countries.getRandomCountries(TOTAL_ROUNDS); + currentRound = 0; + scores = []; + startRound(); +} + +// ── Round +function startRound() { + const country = roundCountries[currentRound]; + + // Update UI + elRoundNum.textContent = currentRound + 1; + elCountryName.textContent = country.name; + elCountryHint.textContent = country.hint || ""; + + // Round pips + if (typeof window.updateRoundPips === "function") { + window.updateRoundPips(currentRound + 1); + } + + // Reset canvas placeholder + document.getElementById("canvas-wrap")?.classList.remove("has-drawing"); + + // Cities on canvas + Drawing.clear(); + Drawing.setCities(country.cities || []); + + // Timer + timeLeft = ROUND_DURATION; + updateTimerUI(); + clearInterval(timerInterval); + timerInterval = setInterval(tickTimer, 1000); + + // Button state + elBtnSubmit.disabled = false; + elBtnSubmit.textContent = currentRound < TOTAL_ROUNDS - 1 + ? "Submit & Next Round →" + : "Submit & See Results →"; +} + +function tickTimer() { + timeLeft--; + updateTimerUI(); + if (timeLeft <= 0) { + clearInterval(timerInterval); + submitRound(true); // auto-submit + } +} + +function updateTimerUI() { + elTimerNum.textContent = timeLeft; + const pct = (timeLeft / ROUND_DURATION) * 100; + elTimerBar.style.width = pct + "%"; + + // Colour shift + if (timeLeft <= 10) { + elTimerBar.style.background = "#e05c5c"; + elTimerNum.style.color = "#e05c5c"; + } else if (timeLeft <= 20) { + elTimerBar.style.background = "#f0b429"; + elTimerNum.style.color = "#f0b429"; + } else { + elTimerBar.style.background = ""; + elTimerNum.style.color = ""; + } +} + +function submitRound(auto = false) { + clearInterval(timerInterval); + elBtnSubmit.disabled = true; + + const points = Drawing.getPoints(); + const score = Scoring.calculateScore(points); + scores.push(score); + + // Update sidebar score row + if (typeof window.updateScoreDisplay === "function") { + window.updateScoreDisplay(currentRound, score); + } + + // Flash score feedback + showScoreFeedback(score); + + const delay = auto ? 400 : 1200; + setTimeout(() => { + if (currentRound < TOTAL_ROUNDS - 1) { + currentRound++; + startRound(); + } else { + finishGame(); + } + }, delay); +} + +function showScoreFeedback(score) { + const grade = Scoring.getGrade(score); + const el = document.getElementById("score-feedback"); + el.textContent = `${score}% ${grade.label}`; + el.style.color = grade.color; + el.style.opacity = "1"; + el.style.transform = "translateY(0)"; + setTimeout(() => { + el.style.opacity = "0"; + el.style.transform = "translateY(-10px)"; + }, 900); +} + +function finishGame() { + const totalScore = scores.reduce((a, b) => a + b, 0); + const state = { + currentRound: TOTAL_ROUNDS, + scores, + totalScore, + countries: roundCountries.map(c => c.name), + }; + Storage.saveGameState(state); + + // Save to leaderboard + Storage.saveLeaderboard({ + name: Storage.getPlayerName(), + totalScore, + scores, + date: new Date().toISOString(), + }); + + location.href = "results.html"; +} + +// ── Events +elBtnClear.addEventListener("click", () => Drawing.clear()); +elBtnSubmit.addEventListener("click", () => submitRound(false)); + +// ── Boot +window.addEventListener("DOMContentLoaded", initGame); diff --git a/frontend/scripts/index.js b/frontend/scripts/index.js index db97c41..0a34835 100644 --- a/frontend/scripts/index.js +++ b/frontend/scripts/index.js @@ -1,7 +1,12 @@ document.getElementById("reg-btn")?.addEventListener("click", () => { - alert( - "Frontend draft only. Full script and backend will be connected later.", - ); + const lobbyInput = document.getElementById("username"); + const lobbyName = lobbyInput ? lobbyInput.value.trim() : ""; + if (lobbyName) { + Storage.saveLobbyName(lobbyName); + window.location.href = "lobby.html"; + } else { + lobbyInput?.focus(); + } }); const reveals = document.querySelectorAll(".reveal"); diff --git a/frontend/scripts/lobby.js b/frontend/scripts/lobby.js new file mode 100644 index 0000000..06289a4 --- /dev/null +++ b/frontend/scripts/lobby.js @@ -0,0 +1,47 @@ +// lobby.js + +document.addEventListener("DOMContentLoaded", () => { + const input = document.getElementById("username"); + const btn = document.getElementById("btn-start"); + const errMsg = document.getElementById("name-error"); + + // Show lobby name + const lobbyNameEl = document.getElementById("lobby-name-display"); + if (lobbyNameEl) { + lobbyNameEl.textContent = Storage.getLobbyName(); + } + + btn.addEventListener("click", () => { + const name = input.value.trim(); + if (!name) { + errMsg.textContent = "Please enter a username to continue."; + input.classList.add("input--error"); + input.focus(); + return; + } + if (name.length < 2) { + errMsg.textContent = "Username must be at least 2 characters."; + input.classList.add("input--error"); + input.focus(); + return; + } + Storage.savePlayerName(name); + Storage.clearGameState(); + location.href = "game.html"; + }); + + input.addEventListener("input", () => { + errMsg.textContent = ""; + input.classList.remove("input--error"); + }); + + input.addEventListener("keydown", (e) => { + if (e.key === "Enter") btn.click(); + }); + + // Pre-fill if returning player + const existing = Storage.getPlayerName(); + if (existing && existing !== "Anonymous") { + input.value = existing; + } +}); diff --git a/frontend/scripts/main.js b/frontend/scripts/main.js new file mode 100644 index 0000000..ebbc12f --- /dev/null +++ b/frontend/scripts/main.js @@ -0,0 +1,36 @@ +// main.js — index.html + +document.addEventListener("DOMContentLoaded", () => { + // Smooth scroll for nav links + document.querySelectorAll('a[href^="#"]').forEach(link => { + link.addEventListener("click", e => { + const id = link.getAttribute("href").slice(1); + const target = document.getElementById(id); + if (!target) return; + e.preventDefault(); + const headerH = document.querySelector(".header")?.offsetHeight || 0; + const top = target.getBoundingClientRect().top + window.scrollY - headerH - 16; + window.scrollTo({ top, behavior: "smooth" }); + }); + }); + + // Register button stub + const regBtn = document.getElementById("reg-btn"); + if (regBtn) { + regBtn.addEventListener("click", () => { + alert("Frontend skeleton only. Backend will be connected later."); + }); + } + + // Scroll reveal + const reveals = document.querySelectorAll(".reveal"); + const io = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add("visible"); + io.unobserve(entry.target); + } + }); + }, { threshold: 0.12 }); + reveals.forEach(el => io.observe(el)); +}); diff --git a/frontend/scripts/scoring.js b/frontend/scripts/scoring.js new file mode 100644 index 0000000..32c9f82 --- /dev/null +++ b/frontend/scripts/scoring.js @@ -0,0 +1,41 @@ +// scoring.js — accuracy calculation + +const Scoring = (() => { + + /** + * Calculate score from drawn path points. + * MVP: fake score based on number of points drawn (effort-based). + * TODO: replace with real compareShapes() using polygon overlap. + */ + function calculateScore(drawnPoints) { + if (!drawnPoints || drawnPoints.length < 10) return 0; + + // Fake scoring: reward effort + randomness so it feels real + const effort = Math.min(drawnPoints.length / 300, 1); // 0–1 + const base = 40 + Math.round(effort * 45); // 40–85 + const jitter = Math.round((Math.random() - 0.5) * 14); // ±7 + const score = Math.max(0, Math.min(100, base + jitter)); + return score; + } + + /** + * Stub for real shape comparison (future). + * drawnPoints: [{x,y}, ...] + * referencePolygon: [{x,y}, ...] (normalised 0–1 coords) + */ + function compareShapes(_drawnPoints, _referencePolygon) { + // TODO: implement IoU (Intersection over Union) or + // Hausdorff distance for polygon comparison. + return 0; + } + + function getGrade(score) { + if (score >= 90) return { label: "S", color: "#f0b429" }; + if (score >= 75) return { label: "A", color: "#41b869" }; + if (score >= 60) return { label: "B", color: "#1a7fc4" }; + if (score >= 40) return { label: "C", color: "#7a9aaa" }; + return { label: "D", color: "#e05c5c" }; + } + + return { calculateScore, compareShapes, getGrade }; +})(); diff --git a/frontend/scripts/storage.js b/frontend/scripts/storage.js new file mode 100644 index 0000000..02d42b3 --- /dev/null +++ b/frontend/scripts/storage.js @@ -0,0 +1,75 @@ +// storage.js — localStorage helpers + +const Storage = (() => { + const KEYS = { + PLAYER_NAME: "gd_playerName", + LOBBY_NAME: "gd_lobbyName", + GAME_STATE: "gd_gameState", + LEADERBOARD: "gd_leaderboard", + }; + + function savePlayerName(name) { + localStorage.setItem(KEYS.PLAYER_NAME, name.trim()); + } + + function getPlayerName() { + return localStorage.getItem(KEYS.PLAYER_NAME) || "Anonymous"; + } + + function saveLobbyName(name) { + localStorage.setItem(KEYS.LOBBY_NAME, name.trim()); + } + + function getLobbyName() { + return localStorage.getItem(KEYS.LOBBY_NAME) || "My Lobby"; + } + + function saveGameState(state) { + localStorage.setItem(KEYS.GAME_STATE, JSON.stringify(state)); + } + + function getGameState() { + try { + return JSON.parse(localStorage.getItem(KEYS.GAME_STATE)) || null; + } catch { + return null; + } + } + + function clearGameState() { + localStorage.removeItem(KEYS.GAME_STATE); + } + + function saveLeaderboard(entry) { + const board = getLeaderboard(); + board.push(entry); + board.sort((a, b) => b.totalScore - a.totalScore); + const top20 = board.slice(0, 20); + localStorage.setItem(KEYS.LEADERBOARD, JSON.stringify(top20)); + } + + function getLeaderboard() { + try { + return JSON.parse(localStorage.getItem(KEYS.LEADERBOARD)) || []; + } catch { + return []; + } + } + + function clearLeaderboard() { + localStorage.removeItem(KEYS.LEADERBOARD); + } + + return { + savePlayerName, + getPlayerName, + saveLobbyName, + getLobbyName, + saveGameState, + getGameState, + clearGameState, + saveLeaderboard, + getLeaderboard, + clearLeaderboard, + }; +})(); diff --git a/frontend/styles/game.css b/frontend/styles/game.css new file mode 100644 index 0000000..8cf6f72 --- /dev/null +++ b/frontend/styles/game.css @@ -0,0 +1,282 @@ +/* game.css */ + +.game-layout { + min-height: calc(100vh - var(--hh)); + display: grid; + grid-template-rows: auto 1fr auto; + padding: 20px 0 28px; + gap: 16px; +} + +/* ── Top bar */ +.game-topbar { + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + gap: 16px; +} + +.game-round { + display: flex; + align-items: center; + gap: 10px; +} + +.round-pip { + width: 10px; + height: 10px; + border-radius: 50%; + background: var(--line); + transition: background .3s; +} + +.round-pip.active { background: var(--sea); } +.round-pip.done { background: var(--leaf); } + +.round-label { + font-family: 'Syne', sans-serif; + font-size: .82rem; + font-weight: 700; + color: var(--ink-muted); + letter-spacing: .06em; + text-transform: uppercase; +} + +.round-label span { + color: var(--ink); +} + +/* Country name */ +.game-country { + text-align: center; +} + +.country-name { + font-family: 'Syne', sans-serif; + font-weight: 800; + font-size: clamp(1.5rem, 3vw, 2.4rem); + letter-spacing: -.03em; + line-height: 1.1; +} + +.country-hint { + font-size: .82rem; + color: var(--ink-muted); + margin-top: 4px; +} + +/* Timer */ +.game-timer { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 6px; +} + +.timer-display { + display: flex; + align-items: baseline; + gap: 4px; +} + +.timer-num { + font-family: 'Syne', sans-serif; + font-weight: 800; + font-size: 2rem; + line-height: 1; + color: var(--ink); + transition: color .3s; + min-width: 2ch; + text-align: right; +} + +.timer-unit { + font-size: .78rem; + font-weight: 600; + color: var(--ink-muted); +} + +.timer-track { + width: 120px; + height: 4px; + background: var(--line); + border-radius: 999px; + overflow: hidden; +} + +.timer-bar { + height: 100%; + width: 100%; + background: linear-gradient(90deg, var(--sea), var(--leaf)); + border-radius: 999px; + transition: width 1s linear, background .4s ease; +} + +/* ── Canvas area */ +.canvas-area { + display: grid; + grid-template-columns: 1fr 220px; + gap: 16px; + min-height: 0; +} + +.canvas-wrap { + position: relative; + background: var(--white); + border: 1.5px solid var(--line); + border-radius: var(--r-xl); + box-shadow: var(--shadow); + overflow: hidden; + cursor: crosshair; +} + +.canvas-wrap::before { + content: 'Draw the border here'; + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: .9rem; + font-weight: 500; + color: var(--ink-muted); + pointer-events: none; + opacity: 1; + transition: opacity .3s; +} + +.canvas-wrap.has-drawing::before { opacity: 0; } + +#draw-canvas { + display: block; + width: 100%; + height: 100%; + touch-action: none; +} + +/* Score feedback overlay */ +#score-feedback { + position: absolute; + top: 20px; + left: 50%; + transform: translateX(-50%) translateY(-10px); + font-family: 'Syne', sans-serif; + font-size: 2rem; + font-weight: 800; + opacity: 0; + transition: opacity .4s ease, transform .4s ease; + pointer-events: none; + text-shadow: 0 2px 8px rgba(0,0,0,.12); + white-space: nowrap; +} + +/* Sidebar */ +.game-sidebar { + display: flex; + flex-direction: column; + gap: 12px; +} + +.sidebar-card { + background: var(--glass); + backdrop-filter: blur(10px); + border: 1px solid rgba(255,255,255,.85); + border-radius: var(--r-lg); + padding: 18px 20px; + box-shadow: var(--shadow-sm); +} + +.sidebar-card__label { + font-size: .72rem; + font-weight: 700; + letter-spacing: .08em; + text-transform: uppercase; + color: var(--ink-muted); + margin-bottom: 10px; +} + +/* Round scores */ +.round-scores { display: grid; gap: 8px; } + +.rscore-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.rscore-label { + font-size: .82rem; + color: var(--ink-soft); + font-weight: 500; +} + +.rscore-val { + font-family: 'Syne', sans-serif; + font-weight: 800; + font-size: .9rem; + color: var(--ink-muted); +} + +.rscore-val.filled { color: var(--sea); } + +/* Player name in sidebar */ +.player-name-display { + font-family: 'Syne', sans-serif; + font-weight: 700; + font-size: 1rem; + color: var(--ink); + word-break: break-word; +} + +/* Controls */ +.game-controls { + display: flex; + gap: 10px; +} + +/* ─── Responsive ─── */ +@media (max-width: 900px) { + .canvas-area { + grid-template-columns: 1fr; + grid-template-rows: 1fr auto; + } + + .game-sidebar { + flex-direction: row; + flex-wrap: wrap; + } + + .sidebar-card { flex: 1 1 140px; } + + .canvas-wrap { min-height: 340px; } + + .game-topbar { + grid-template-columns: auto 1fr auto; + } +} + +@media (max-width: 600px) { + .game-layout { padding: 14px 0 20px; gap: 12px; } + + .game-topbar { + grid-template-columns: 1fr; + grid-template-rows: auto auto auto; + gap: 10px; + } + + .game-country { text-align: left; } + .game-timer { align-items: flex-start; } + .timer-track { width: 100%; } + + .canvas-wrap { min-height: 280px; } + + .game-controls { flex-direction: column; } + .game-controls .btn { width: 100%; } +} + +@media (max-width: 360px) { + .country-name { font-size: 1.4rem; } + .timer-num { font-size: 1.6rem; } + .canvas-wrap { min-height: 240px; } +} diff --git a/frontend/styles/index.css b/frontend/styles/index.css index 00656e3..0bff113 100644 --- a/frontend/styles/index.css +++ b/frontend/styles/index.css @@ -742,36 +742,6 @@ input { text-align: center; } -.socials { - display: flex; - justify-content: center; - gap: 10px; - margin-top: 10px; -} - -.social { - width: 42px; - height: 42px; - border-radius: 50%; - border: 1px solid var(--line); - background: var(--white); - display: flex; - align-items: center; - justify-content: center; - font-size: 0.9rem; - font-weight: 700; - color: var(--ink-soft); - transition: - transform 0.18s, - color 0.18s, - background 0.18s; -} - -.social:hover { - transform: translateY(-2px); - color: var(--sea); - background: var(--sea-dim); -} .footer__label { font-size: 0.7rem; diff --git a/frontend/styles/leader.css b/frontend/styles/leader.css new file mode 100644 index 0000000..bb04279 --- /dev/null +++ b/frontend/styles/leader.css @@ -0,0 +1,196 @@ +.lb-page { + min-height: calc(100vh - var(--hh)); + padding: 60px 0; + } + + .lb-inner { + max-width: 760px; + margin: 0 auto; + } + + .lb-head { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 20px; + margin-bottom: 32px; + flex-wrap: wrap; + } + + .lb-title { + font-family: 'Syne', sans-serif; + font-weight: 800; + font-size: clamp(2rem, 5vw, 3.2rem); + letter-spacing: -.03em; + line-height: 1; + } + + .lb-title em { + font-style: normal; + background: linear-gradient(135deg, var(--sea), var(--leaf)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + } + + .lb-actions { display: flex; gap: 10px; flex-wrap: wrap; } + + /* Table */ + .lb-table { + background: var(--white); + border: 1px solid var(--line); + border-radius: var(--r-xl); + overflow: hidden; + box-shadow: var(--shadow); + } + + .lb-table-head { + display: grid; + grid-template-columns: 56px 1fr 90px 90px 90px; + gap: 0; + padding: 14px 24px; + background: var(--cream); + border-bottom: 1px solid var(--line); + } + + .lb-th { + font-size: .72rem; + font-weight: 700; + letter-spacing: .08em; + text-transform: uppercase; + color: var(--ink-muted); + } + + .lb-th.right { text-align: right; } + + .lb-body { display: grid; } + + .lb-row { + display: grid; + grid-template-columns: 56px 1fr 90px 90px 90px; + align-items: center; + padding: 16px 24px; + border-bottom: 1px solid var(--line); + transition: background .15s; + animation: fadeRow .4s ease both; + } + + .lb-row:last-child { border-bottom: none; } + + .lb-row:hover { background: var(--cream); } + + @keyframes fadeRow { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } + } + + /* Top 3 highlight */ + .lb-row.rank-1 { background: rgba(240,180,40,.07); } + .lb-row.rank-2 { background: rgba(180,180,195,.06); } + .lb-row.rank-3 { background: rgba(205,130,70,.05); } + + .lb-rank { + font-family: 'Syne', sans-serif; + font-weight: 800; + font-size: 1rem; + color: var(--ink-muted); + } + + .lb-row.rank-1 .lb-rank { color: var(--gold); } + .lb-row.rank-2 .lb-rank { color: #a0a0b0; } + .lb-row.rank-3 .lb-rank { color: #c07840; } + + .lb-medal { font-size: 1.1rem; } + + .lb-name { + font-family: 'Syne', sans-serif; + font-weight: 700; + font-size: .96rem; + color: var(--ink); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .lb-name.is-you::after { + content: ' (you)'; + font-weight: 400; + font-family: 'DM Sans', sans-serif; + color: var(--ink-muted); + font-size: .8rem; + } + + .lb-rounds { + font-size: .82rem; + color: var(--ink-muted); + text-align: right; + } + + .lb-date { + font-size: .78rem; + color: var(--ink-muted); + text-align: right; + } + + .lb-score { + font-family: 'Syne', sans-serif; + font-weight: 800; + font-size: 1.05rem; + color: var(--sea); + text-align: right; + } + + .lb-score.gold { color: var(--gold); } + .lb-score.silver { color: #888898; } + .lb-score.bronze { color: #c07840; } + + /* Empty state */ + .lb-empty { + padding: 64px 24px; + text-align: center; + } + + .lb-empty__icon { font-size: 3rem; margin-bottom: 12px; } + + .lb-empty__text { + font-size: 1rem; + color: var(--ink-soft); + margin-bottom: 20px; + } + + /* Current player highlight bar */ + .your-score-bar { + margin-top: 20px; + padding: 16px 24px; + background: linear-gradient(135deg, var(--sea-dim), var(--leaf-dim)); + border: 1px solid rgba(26,127,196,.18); + border-radius: var(--r-lg); + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + flex-wrap: wrap; + } + + .your-score-bar__text { + font-size: .9rem; + color: var(--ink-soft); + } + + .your-score-bar__text strong { color: var(--ink); } + + @media (max-width: 640px) { + .lb-page { padding: 40px 0; } + .lb-table-head, + .lb-row { + grid-template-columns: 44px 1fr 72px; + } + .lb-th.hide-sm, + .lb-rounds, + .lb-date { display: none; } + } + + @media (max-width: 360px) { + .lb-table-head, + .lb-row { padding: 12px 16px; } + } \ No newline at end of file diff --git a/frontend/styles/lobby.css b/frontend/styles/lobby.css new file mode 100644 index 0000000..46a73c4 --- /dev/null +++ b/frontend/styles/lobby.css @@ -0,0 +1,202 @@ +/* lobby.css */ + +.lobby { + min-height: calc(100vh - var(--hh)); + display: grid; + place-items: center; + padding: 60px 0; +} + +.lobby__inner { + display: grid; + grid-template-columns: 1fr minmax(300px, 480px); + gap: 64px; + align-items: center; + width: 100%; +} + +/* ─── Lobby name badge ─── */ +.lobby__name-badge { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 18px; + border-radius: var(--r-lg); + background: var(--glass); + border: 1px solid rgba(255,255,255,.9); + box-shadow: var(--shadow-sm); + backdrop-filter: blur(10px); + margin-bottom: 20px; +} + +.lobby__name-icon { font-size: 1.1rem; } + +#lobby-name-display { + font-family: 'Syne', sans-serif; + font-weight: 700; + font-size: 1rem; + color: var(--ink); + letter-spacing: -.01em; +} + +/* Left side promo */ +.lobby__promo {} + +.eyebrow { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 6px 14px 6px 10px; + border-radius: 999px; + border: 1px solid var(--leaf-dim); + background: rgba(65,184,105,.08); + color: #289149; + font-size: .78rem; + font-weight: 600; + letter-spacing: .05em; + text-transform: uppercase; + margin-bottom: 22px; +} + +.eyebrow__dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--leaf); + animation: pulse 2s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); opacity: 1; } + 50% { transform: scale(1.45); opacity: .7; } +} + +.lobby__title { + font-family: 'Syne', sans-serif; + font-weight: 800; + font-size: clamp(2.8rem, 5.5vw, 5rem); + line-height: .94; + letter-spacing: -.04em; + margin-bottom: 20px; +} + +.lobby__title em { + font-style: normal; + background: linear-gradient(135deg, var(--sea), var(--leaf)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.lobby__desc { + font-size: 1.02rem; + line-height: 1.72; + color: var(--ink-soft); + max-width: 420px; + margin-bottom: 32px; +} + +.lobby__badges { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.badge { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 14px; + border-radius: 999px; + background: var(--glass); + border: 1px solid rgba(255,255,255,.9); + box-shadow: var(--shadow-sm); + font-size: .82rem; + font-weight: 600; + color: var(--ink-soft); + backdrop-filter: blur(8px); +} + +.badge__icon { font-size: 1rem; } + +/* Right side card */ +.lobby__card { + background: var(--white); + border: 1px solid var(--line); + border-radius: var(--r-xl); + padding: 44px; + box-shadow: var(--shadow); +} + +.lobby__card-title { + font-family: 'Syne', sans-serif; + font-weight: 800; + font-size: 1.5rem; + letter-spacing: -.02em; + margin-bottom: 8px; +} + +.lobby__card-sub { + font-size: .9rem; + color: var(--ink-muted); + margin-bottom: 30px; +} + +.form { display: grid; gap: 20px; } + +.field { display: grid; gap: 8px; } + +.field label { + font-size: .84rem; + font-weight: 600; + color: var(--ink-soft); + letter-spacing: .02em; +} + +.field input { + width: 100%; + height: 54px; + padding: 0 18px; + border: 1.5px solid var(--line); + border-radius: var(--r-md); + background: var(--cream); + color: var(--ink); + font-size: .98rem; + transition: border-color .18s, box-shadow .18s; +} + +.field input::placeholder { color: var(--ink-muted); } + +.field input:focus { + outline: none; + border-color: var(--sea-light); + box-shadow: 0 0 0 3px rgba(26,127,196,.12); + background: var(--white); +} + +.field input.input--error { + border-color: var(--danger); + box-shadow: 0 0 0 3px rgba(224,92,92,.12); +} + +.field-error { + font-size: .82rem; + color: var(--danger); + min-height: 1.2em; + transition: opacity .18s; +} + +/* ─── Responsive ─── */ +@media (max-width: 900px) { + .lobby__inner { grid-template-columns: 1fr; gap: 40px; } +} + +@media (max-width: 680px) { + .lobby { padding: 44px 0; } + .lobby__card { padding: 28px 22px; } +} + +@media (max-width: 360px) { + .lobby__card { padding: 22px 16px; } + .lobby__title { font-size: 2.4rem; } +} diff --git a/frontend/styles/main.css b/frontend/styles/main.css new file mode 100644 index 0000000..2058270 --- /dev/null +++ b/frontend/styles/main.css @@ -0,0 +1,344 @@ +/* main.css — shared design tokens & base */ + +@import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;500;700;800&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600&display=swap'); + +:root { + --ink: #0b1f2a; + --ink-soft: #3d5563; + --ink-muted: #7a9aaa; + --sea: #1a7fc4; + --sea-light: #4faae0; + --sea-dim: rgba(26,127,196,.12); + --leaf: #41b869; + --leaf-dim: rgba(65,184,105,.13); + --gold: #f0b429; + --danger: #e05c5c; + --cream: #f4f9f6; + --white: #ffffff; + --glass: rgba(255,255,255,.72); + --line: rgba(11,31,42,.09); + --shadow: 0 24px 60px rgba(11,31,42,.10); + --shadow-sm: 0 4px 20px rgba(11,31,42,.07); + --r-xl: 32px; + --r-lg: 22px; + --r-md: 14px; + --r-sm: 8px; + --hh: 72px; +} + +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +html { scroll-behavior: smooth; } + +body { + font-family: 'DM Sans', sans-serif; + color: var(--ink); + background: var(--cream); + overflow-x: hidden; + position: relative; + min-width: 320px; +} + +body::before { + content: ''; + position: fixed; + inset: 0; + background: + radial-gradient(ellipse 60% 50% at 10% 0%, rgba(65,184,105,.10) 0%, transparent 60%), + radial-gradient(ellipse 50% 60% at 90% 100%, rgba(26,127,196,.10) 0%, transparent 60%); + pointer-events: none; + z-index: 0; +} + +body::after { + content: ''; + position: fixed; + inset: 0; + background-image: + linear-gradient(var(--line) 1px, transparent 1px), + linear-gradient(90deg, var(--line) 1px, transparent 1px); + background-size: 60px 60px; + pointer-events: none; + z-index: 0; +} + +a { color: inherit; text-decoration: none; } +img, svg { display: block; max-width: 100%; } +button, input { font: inherit; } + +.wrap { position: relative; z-index: 1; } + +.container { + width: 100%; + max-width: 1280px; + margin: 0 auto; + padding: 0 28px; +} + +.header { + position: sticky; + top: 0; + z-index: 100; + height: var(--hh); + display: flex; + align-items: center; + backdrop-filter: blur(16px) saturate(1.4); + -webkit-backdrop-filter: blur(16px) saturate(1.4); + background: rgba(244, 249, 246, 0.82); + border-bottom: 1px solid var(--line); +} + +.header__inner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; +} + +.logo { + display: inline-flex; + align-items: center; + gap: 12px; +} + +.logo__mark { + width: 44px; + height: 44px; + border-radius: 50%; + background: linear-gradient(135deg, var(--sea) 0%, var(--leaf) 100%); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + position: relative; + overflow: hidden; +} + +.logo__mark svg { + width: 22px; + height: 22px; +} + +.logo__text { + font-family: "Syne", sans-serif; + font-weight: 800; + font-size: 1.25rem; + letter-spacing: -0.02em; +} + +.nav { + display: flex; + align-items: center; + gap: 4px; +} + +.nav__link { + padding: 9px 16px; + border-radius: 999px; + font-size: 0.92rem; + font-weight: 500; + color: var(--ink-soft); + transition: + background 0.18s, + color 0.18s; +} + +.nav__link:hover { + background: var(--sea-dim); + color: var(--sea); +} + +.nav__cta { + margin-left: 8px; + padding: 9px 20px; + border-radius: 999px; + font-size: 0.92rem; + font-weight: 600; + color: var(--white); + background: var(--ink); + transition: + opacity 0.18s, + transform 0.18s; +} + +.nav__cta:hover { + opacity: 0.82; + transform: translateY(-1px); +} + + +/* ─── BUTTONS ─── */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 15px 28px; + border-radius: var(--r-lg); + font-family: 'DM Sans', sans-serif; + font-size: 1rem; + font-weight: 600; + border: none; + cursor: pointer; + transition: transform .2s, box-shadow .2s, opacity .2s; + line-height: 1; +} + +.btn:hover:not(:disabled) { transform: translateY(-2px); } +.btn:disabled { opacity: .45; cursor: not-allowed; } + +.btn--primary { + color: var(--white); + background: linear-gradient(135deg, var(--sea) 0%, #159fd4 50%, var(--leaf) 100%); + background-size: 200% 200%; + box-shadow: 0 8px 24px rgba(26,127,196,.28); + animation: gradShift 4s ease infinite; +} + +.btn--primary:hover:not(:disabled) { box-shadow: 0 12px 32px rgba(26,127,196,.4); } + +@keyframes gradShift { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +.btn--ghost { + color: var(--ink-soft); + background: transparent; + border: 1.5px solid var(--line); +} + +.btn--ghost:hover:not(:disabled) { border-color: var(--ink-muted); color: var(--ink); } + +.btn--danger { + color: var(--white); + background: var(--danger); + box-shadow: 0 6px 18px rgba(224,92,92,.25); +} + +.btn--full { width: 100%; } + +.btn--sm { padding: 10px 20px; font-size: .88rem; border-radius: var(--r-md); } + +/* ─── CARD ─── */ +.card { + background: var(--glass); + backdrop-filter: blur(12px); + border: 1px solid rgba(255,255,255,.8); + border-radius: var(--r-xl); + box-shadow: var(--shadow); +} + +/* ─── SECTION LABELS ─── */ +.section-label { + display: inline-block; + padding: 5px 13px; + border-radius: 999px; + background: var(--sea-dim); + color: var(--sea); + font-size: .78rem; + font-weight: 600; + letter-spacing: .06em; + text-transform: uppercase; + margin-bottom: 16px; +} + +.section-title { + font-family: 'Syne', sans-serif; + font-weight: 800; + font-size: clamp(2rem, 4vw, 3.6rem); + line-height: 1.04; + letter-spacing: -.03em; + margin-bottom: 16px; +} + +/* ─── FOOTER ─── */ +.footer { + border-top: 1px solid var(--line); + background: rgba(255, 255, 255, 0.5); + backdrop-filter: blur(12px); + padding: 40px 0; +} + +.footer__inner { + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + gap: 24px; +} + +.footer__brand { + font-family: "Syne", sans-serif; + font-weight: 800; + font-size: 1.5rem; + letter-spacing: -0.02em; +} + +.footer__sub { + font-size: 0.86rem; + color: var(--ink-muted); + margin-top: 4px; +} + +.footer__center { + text-align: center; +} + + +.footer__label { + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--ink-muted); + margin-bottom: 10px; +} + +.footer__right { + justify-self: end; + display: flex; + gap: 22px; + flex-wrap: wrap; + justify-content: flex-end; +} + +.footer__link { + font-size: 0.9rem; + color: var(--ink-muted); + transition: color 0.18s; +} + +.footer__link:hover { + color: var(--ink); +} + +/* ─── SCROLL REVEAL ─── */ +.reveal { + opacity: 0; + transform: translateY(24px); + transition: opacity .55s ease, transform .55s ease; +} + +.reveal.visible { opacity: 1; transform: translateY(0); } +.reveal-delay-1 { transition-delay: .1s; } +.reveal-delay-2 { transition-delay: .2s; } +.reveal-delay-3 { transition-delay: .32s; } + +/* ─── RESPONSIVE BASE ─── */ +@media (max-width: 680px) { + .container { padding: 0 18px; } + .nav__link { display: none; } + .footer__inner { grid-template-columns: 1fr; } + .footer__center, .footer__right { justify-self: start; text-align: left; } + .socials { justify-content: flex-start; } + .footer__right { justify-content: flex-start; } +} + +@media (max-width: 360px) { + :root { --r-xl: 22px; --r-lg: 16px; } + .container { padding: 0 14px; } + .logo__mark { width: 34px; height: 34px; } + .logo__text { font-size: 1.05rem; } + .nav__cta { padding: 7px 12px; font-size: .82rem; } +}