Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 233f7e809a | |||
|
|
41f6aa72ad | ||
| f2eef5be40 | |||
|
|
4780557c67 | ||
|
|
861f850401 | ||
|
|
a426bad841 | ||
|
|
abf93a0848 | ||
|
|
67935ec234 | ||
| 554be835dd | |||
| e54f99999b |
116
README.md
116
README.md
@ -1,8 +1,4 @@
|
||||
Todos:
|
||||
- für Backend und weitere Änderungen anpassen
|
||||
- evtl. kurze Self-Reflection
|
||||
|
||||
# GeoDraw - FS 2026 Frontendentwicklung
|
||||
# GeoDraw — FS 2026 Frontend-Entwicklung
|
||||
|
||||
**[Link to our presentation](https://fhgraubuenden-my.sharepoint.com/:p:/g/personal/pengniklas_fhgr_ch/IQDz4D0YTsysR7oDcVnon2lnAYEj1K0Ie75lc4-guWgusns?e=gjZJ58)**
|
||||
|
||||
@ -10,9 +6,9 @@ Todos:
|
||||
|
||||
## Project Description
|
||||
|
||||
GeoDraw is a competitive browser-based geography game. Players are shown the name of a country and must draw its border from memory on a blank canvas. City markers serve as spatial hints. After each round the drawing is scored based on accuracy. The goal is to achieve the highest total score across three rounds.
|
||||
GeoDraw is a competitive browser-based geography game. Players are shown the name of a country and must draw its border from memory on a blank canvas. City markers serve as spatial hints. After each round the drawing is scored by comparing the player's polygon against the real GeoJSON border using an Intersection over Union (IoU) algorithm. The goal is to achieve the highest total score across three rounds.
|
||||
|
||||
The game supports single-player sessions with a local leaderboard tracking top scores across multiple players on the same device.
|
||||
Players create or join a shared lobby. Scores are stored per lobby on the backend, so multiple players can compete and compare results on a shared leaderboard.
|
||||
|
||||
---
|
||||
|
||||
@ -20,33 +16,34 @@ The game supports single-player sessions with a local leaderboard tracking top s
|
||||
|
||||
From the project root, run:
|
||||
|
||||
```powershell
|
||||
```bash
|
||||
php serve.php
|
||||
```
|
||||
Then open `http://localhost:8000/` in a browser.
|
||||
You can override the port, but the default will be `8000`.
|
||||
|
||||
Then open `http://localhost:8000/` in your browser. PHP 8.0+ is required.
|
||||
|
||||
---
|
||||
|
||||
## How to play
|
||||
|
||||
1. **Create a lobby** — enter a lobby name and start the session
|
||||
1. **Create or join a lobby** — create a new lobby and share its name with friends, or enter an existing lobby name to join
|
||||
2. **Enter your username** — your name will appear on the leaderboard
|
||||
3. **Draw** — sketch the country border from memory within 60 seconds; city dots are your hints
|
||||
4. **Score** — your drawing is graded on accuracy (grades S / A / B / C / D)
|
||||
5. **Results** — after 3 rounds, see your total score and round breakdown
|
||||
6. **Leaderboard** — compare your score with previous sessions
|
||||
4. **Submit** — submit your drawing to see the real border overlaid on yours
|
||||
5. **Score** — your drawing is graded on accuracy (grades S / A / B / C / D)
|
||||
6. **Results** — after 3 rounds, see your total score and round breakdown
|
||||
7. **Leaderboard** — compare your score with other players in the same lobby
|
||||
|
||||
---
|
||||
|
||||
## Special Features
|
||||
|
||||
- **Freehand canvas drawing** using the Pointer Events API (mouse & touch support)
|
||||
- **Real polygon scoring** via rasterized IoU (96×96 grid) against GeoJSON country outlines
|
||||
- **Reference outline overlay** revealed on the canvas after each round submission
|
||||
- **City hint markers** rendered directly on the canvas with labelled pins
|
||||
- **Live countdown timer** with colour-coded urgency states (green → yellow → red)
|
||||
- **Score feedback overlay** displayed on the canvas after each round
|
||||
- **Animated results page** with per-round bar chart breakdown
|
||||
- **Persistent leaderboard** stored in `localStorage`, top 20 entries ranked by total score
|
||||
- **Per-lobby leaderboard** stored on the PHP backend, top 20 entries per lobby
|
||||
- **Scroll reveal animations** on the landing page via IntersectionObserver
|
||||
- **Fully responsive** layout across desktop, tablet and mobile
|
||||
- **WCAG Level A accessibility** — semantic HTML, `aria-label`, `aria-hidden`, `prefers-reduced-motion`
|
||||
@ -58,60 +55,38 @@ You can override the port, but the default will be `8000`.
|
||||
### Stack
|
||||
|
||||
```
|
||||
frontend
|
||||
├── HTML
|
||||
├── Vanilla CSS
|
||||
└── Vanilla JavaScript
|
||||
└── Pixi.js (TBD)
|
||||
backend
|
||||
└── PHP (TBD)
|
||||
└── Postgres (TBD)
|
||||
frontend/
|
||||
├── HTML5
|
||||
├── Vanilla CSS3
|
||||
└── Vanilla ES6+ (ES modules)
|
||||
└── Canvas 2D API + Pointer Events (drawing & scoring)
|
||||
|
||||
backend/
|
||||
└── PHP 8.5
|
||||
└── JSON file storage (backend/data/lobbies.json)
|
||||
|
||||
frontend/data/
|
||||
├── countries.json — country metadata and city coordinates
|
||||
└── outlines/*.json — GeoJSON polygon outlines (10 countries)
|
||||
|
||||
tools/
|
||||
└── convert-geojson-outlines.js — data pipeline: Natural Earth GeoJSON → game outlines
|
||||
```
|
||||
|
||||
The backend is planned to be written in PHP, with a database using PostgreSQL. Language is pending.
|
||||
|
||||
The drawing part will likely be handled by WebGL and a canvas.
|
||||
|
||||
|
||||
### Coding aspects
|
||||
|
||||
- Evaluating arbitrary shapes and determining their surface area
|
||||
- Comparing two arbitrary shapes to calculate a score
|
||||
- Storing and receiving data in the database
|
||||
- Handling user events (drawing, clicks, navigation)
|
||||
|
||||
### Technologies, Libraries & Frameworks
|
||||
|
||||
| Layer | Technology | Reason |
|
||||
|---|---|---|
|
||||
| HTML | Vanilla HTML5 | Semantic markup, no abstraction needed |
|
||||
| CSS | Vanilla CSS3 (Flexbox + Grid) | Full control over design, no framework overhead |
|
||||
| JavaScript | Vanilla ES6+ | Core learning goal of the module; no build step required |
|
||||
| Drawing | Canvas 2D API + Pointer Events | Native browser API, no library dependency |
|
||||
| Backend | PHP | Server-side logic and REST API |
|
||||
| Database | PostgreSQL | Relational data storage for scores and lobbies |
|
||||
| Linting / Formatting | Biome | Fast, unified linter and formatter replacing ESLint + Prettier |
|
||||
| Package Manager | npm | Manages dev dependencies (Biome only) |
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
### Frontend
|
||||
|
||||
Your browser must support **WebGL**. If you are uncertain, [check this website](https://get.webgl.org/).
|
||||
|
||||
Start the app from the project root:
|
||||
|
||||
```powershell
|
||||
php serve.php
|
||||
```
|
||||
|
||||
Then open `http://localhost:8000/` in a browser.
|
||||
|
||||
### Backend
|
||||
|
||||
Setup for backend is WIP.
|
||||
| CSS | Vanilla CSS3 (Flexbox + Grid) | Full control; CSS fundamentals are a learning objective |
|
||||
| JavaScript | Vanilla ES6+ (ES modules) | No build step required; core learning goal of the module |
|
||||
| Drawing & Scoring | Canvas 2D API + Pointer Events | Native browser APIs; IoU scoring via 96×96 raster grid |
|
||||
| Country data | GeoJSON outlines | Standard geospatial format, lightweight and precise |
|
||||
| Backend | PHP 8.5 | Server-side REST API |
|
||||
| Storage | JSON file (lobbies.json) | Dependency-free; no database server needed for this scope |
|
||||
| Testing | Node.js built-in test runner | Unit tests for IoU scoring algorithm (scoring.test.mjs) |
|
||||
| Linting / Formatting | Biome | Fast unified linter and formatter |
|
||||
| Package Manager | npm | Dev-only dependency (Biome) |
|
||||
|
||||
---
|
||||
|
||||
@ -122,16 +97,3 @@ Setup for backend is WIP.
|
||||
| **Ivan Szabo** | Frontend development (initial/base: pages, scripts, styles...) |
|
||||
| **Luca Jakob** | Frontend review; Backend development |
|
||||
| **Niklas Peng** | Frontend improvements (accessibility, code quality, CSS consolidation, JSDoc...); Backend review |
|
||||
|
||||
---
|
||||
|
||||
## Self-Reflection
|
||||
|
||||
**Ivan Szabo:**
|
||||
tbd
|
||||
|
||||
**Luca Jakob:**
|
||||
tbd
|
||||
|
||||
**Niklas Peng:**
|
||||
tbd
|
||||
|
||||
@ -97,7 +97,7 @@
|
||||
|
||||
<div class="sidebar-card">
|
||||
<p class="sidebar-card__label">How to play</p>
|
||||
<p style="font-size:.82rem;color:var(--ink-soft);line-height:1.55;">
|
||||
<p class="sidebar-hint">
|
||||
Draw the country's outline from memory. City dots are hints. Press <strong>Submit</strong> when done or wait for the timer.
|
||||
</p>
|
||||
</div>
|
||||
@ -147,4 +147,4 @@
|
||||
|
||||
<script type="module" src="scripts/game.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@ -12,6 +12,7 @@
|
||||
<p class="noscript-msg">GeoDraw requires JavaScript. Please enable it in your browser settings.</p>
|
||||
</noscript>
|
||||
<div class="wrap">
|
||||
|
||||
<header class="header">
|
||||
<div class="container header__inner">
|
||||
<a href="#hero" class="logo">
|
||||
@ -26,14 +27,15 @@
|
||||
</a>
|
||||
|
||||
<nav class="nav" aria-label="Main navigation">
|
||||
<a href="#hero" class="nav__link">Play</a>
|
||||
<a href="#about" class="nav__link">How to play</a>
|
||||
<a href="#register" class="nav__cta">Create lobby</a>
|
||||
<a href="#register" class="nav__cta">Play</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
<!-- ─── HERO ─── -->
|
||||
<section class="hero" id="hero">
|
||||
<div class="container hero__inner">
|
||||
|
||||
@ -56,62 +58,83 @@
|
||||
Start Playing
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M3 8h10M9 4l4 4-4 4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</a>
|
||||
<a href="#about" class="btn btn--ghost">How it works</a>
|
||||
<a href="#about" class="btn btn--ghost">How to play</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="globe-card reveal reveal-delay-1">
|
||||
<div class="globe">
|
||||
<div class="continent c1"></div>
|
||||
<div class="continent c2"></div>
|
||||
<div class="continent c3"></div>
|
||||
<div class="continent c4"></div>
|
||||
<div class="globe-line gl1"></div>
|
||||
<div class="globe-line gl2"></div>
|
||||
<div class="globe-line gl3"></div>
|
||||
<div class="globe-line gl4"></div>
|
||||
<div class="continent continent--a"></div>
|
||||
<div class="continent continent--b"></div>
|
||||
<div class="continent continent--c"></div>
|
||||
<div class="continent continent--d"></div>
|
||||
<div class="globe-line globe-line--meridian-sm"></div>
|
||||
<div class="globe-line globe-line--meridian-lg"></div>
|
||||
<div class="globe-line globe-line--parallel-sm"></div>
|
||||
<div class="globe-line globe-line--parallel-lg"></div>
|
||||
</div>
|
||||
|
||||
<div class="globe-badge gb--score">
|
||||
<span class="gb__dot gb__dot--green"></span>
|
||||
<div class="globe-badge globe-badge--score">
|
||||
<span class="globe-badge__dot globe-badge__dot--green"></span>
|
||||
<span>Score: 94%</span>
|
||||
</div>
|
||||
|
||||
<div class="globe-badge gb--country">
|
||||
<span class="gb__dot gb__dot--blue"></span>
|
||||
<div class="globe-badge globe-badge--country">
|
||||
<span class="globe-badge__dot globe-badge__dot--blue"></span>
|
||||
<span>Norway — Round 3</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ─── ABOUT ─── -->
|
||||
<section class="about" id="about">
|
||||
<div class="container about__inner">
|
||||
|
||||
<div class="about__left">
|
||||
<p class="section-label reveal">About the game</p>
|
||||
<h2 class="section-title reveal reveal-delay-1">What it is &<br>How to play</h2>
|
||||
<p class="section-label reveal">How to play</p>
|
||||
<h2 class="section-title reveal reveal-delay-1">About<br>the Game</h2>
|
||||
<p class="about__desc reveal reveal-delay-2">
|
||||
GeoDraw is a competitive browser game built on memory and precision. You get a country name, sketch its outline, and the system calculates how close you were to the real border.
|
||||
</p>
|
||||
|
||||
<div class="mini-globe-wrap reveal reveal-delay-3">
|
||||
<div class="map-outline">
|
||||
<svg viewBox="0 0 100 130" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M50 4 L78 14 L90 34 L82 56 L88 82 L62 110 L36 100 L16 76 L18 46 Z"
|
||||
fill="rgba(26,127,196,.12)"
|
||||
stroke="var(--sea)"
|
||||
stroke-width="2.5"
|
||||
stroke-linejoin="round"/>
|
||||
<path d="M50 4 L78 14 L90 34 L82 56 L88 82 L62 110 L36 100 L16 76 L18 46 Z"
|
||||
fill="none"
|
||||
stroke="var(--leaf)"
|
||||
stroke-width="1.5"
|
||||
stroke-dasharray="5 4"
|
||||
stroke-linejoin="round"
|
||||
transform="translate(4,-2) scale(0.96) translate(-2,2)"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="game-preview reveal reveal-delay-3">
|
||||
<svg class="game-preview__svg" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<!-- Grid lines -->
|
||||
<line x1="0" y1="66" x2="200" y2="66" stroke="rgba(11,31,42,.06)" stroke-width="1"/>
|
||||
<line x1="0" y1="133" x2="200" y2="133" stroke="rgba(11,31,42,.06)" stroke-width="1"/>
|
||||
<line x1="66" y1="0" x2="66" y2="200" stroke="rgba(11,31,42,.06)" stroke-width="1"/>
|
||||
<line x1="133" y1="0" x2="133" y2="200" stroke="rgba(11,31,42,.06)" stroke-width="1"/>
|
||||
<!-- Reference outline (filled) -->
|
||||
<path d="M100 22 L148 40 L162 76 L150 112 L124 148 L80 142 L52 108 L54 66 Z"
|
||||
fill="rgba(26,127,196,.10)"
|
||||
stroke="rgba(26,127,196,.5)"
|
||||
stroke-width="2"
|
||||
stroke-linejoin="round"/>
|
||||
<!-- Player's drawn path -->
|
||||
<path d="M96 26 L152 44 L164 82 L146 118 L120 152 L76 144 L48 106 L52 62 Z"
|
||||
fill="none"
|
||||
stroke="#1a7fc4"
|
||||
stroke-width="2.5"
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"/>
|
||||
<!-- City dot: Bern -->
|
||||
<circle cx="96" cy="100" r="4" fill="rgba(240,180,40,.9)"/>
|
||||
<circle cx="96" cy="100" r="4" fill="none" stroke="white" stroke-width="1.5"/>
|
||||
<!-- City dot: Zurich -->
|
||||
<circle cx="118" cy="72" r="4" fill="rgba(240,180,40,.9)"/>
|
||||
<circle cx="118" cy="72" r="4" fill="none" stroke="white" stroke-width="1.5"/>
|
||||
<!-- Score badge -->
|
||||
<rect x="118" y="22" width="62" height="26" rx="8" fill="white" filter="url(#shadow)"/>
|
||||
<defs>
|
||||
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="rgba(11,31,42,.12)"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<circle cx="131" cy="35" r="4" fill="#41b869"/>
|
||||
<text x="139" y="39" font-family="Syne, sans-serif" font-weight="700" font-size="11" fill="#0b1f2a">82%</text>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -149,29 +172,50 @@
|
||||
<div class="container register__inner">
|
||||
|
||||
<div class="register__intro reveal">
|
||||
<p class="section-label">Join the game</p>
|
||||
<h2 class="section-title">Create your<br>lobby</h2>
|
||||
<p class="section-label">Play</p>
|
||||
<h2 class="section-title">Create or join<br>a lobby</h2>
|
||||
<p class="register__desc">
|
||||
Create a private lobby to challenge friends or play solo.
|
||||
Create your own lobby and share the name with friends, or enter an existing lobby name to join their game.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="register__card reveal reveal-delay-1">
|
||||
<p class="card-title">Create a game</p>
|
||||
|
||||
<form class="form">
|
||||
<div class="field">
|
||||
<label for="username">Lobby name</label>
|
||||
<input id="username" type="text" placeholder="myLobby" />
|
||||
<div class="lobby-toggle" role="group" aria-label="Lobby action">
|
||||
<button type="button" class="lobby-toggle__btn lobby-toggle__btn--active" id="toggle-create-btn">Create</button>
|
||||
<button type="button" class="lobby-toggle__btn" id="toggle-join-btn">Join</button>
|
||||
</div>
|
||||
|
||||
<div id="form-create">
|
||||
<p class="card-title">Create a game</p>
|
||||
<div class="form">
|
||||
<div class="field">
|
||||
<label for="lobby-create">Lobby name</label>
|
||||
<input id="lobby-create" type="text" placeholder="myLobby" />
|
||||
</div>
|
||||
<button type="button" class="btn btn--primary btn--full" id="reg-btn">
|
||||
Create game
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M3 8h10M9 4l4 4-4 4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
<span class="field-error" id="lobby-error" role="alert"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn--primary btn--full" id="reg-btn">
|
||||
Create game
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M3 8h10M9 4l4 4-4 4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
<div id="form-join" hidden>
|
||||
<p class="card-title">Join a game</p>
|
||||
<div class="form">
|
||||
<div class="field">
|
||||
<label for="lobby-join">Lobby name</label>
|
||||
<input id="lobby-join" type="text" placeholder="myLobby" />
|
||||
</div>
|
||||
<button type="button" class="btn btn--primary btn--full" id="join-btn">
|
||||
Join game
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M3 8h10M9 4l4 4-4 4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
<span class="field-error" id="join-error" role="alert"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="form-footer"><a href="#" style="color:var(--sea);font-weight:600;">Join a game instead</a></p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -181,19 +225,10 @@
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container footer__inner">
|
||||
|
||||
<div class="footer__left">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="footer__center">
|
||||
<a href="#hero" class="footer__brand">GeoDraw</a>
|
||||
<p class="footer__sub">Browser geography game</p>
|
||||
</div>
|
||||
|
||||
<div class="footer__right">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@ -201,4 +236,4 @@
|
||||
|
||||
<script type="module" src="scripts/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@ -91,4 +91,4 @@
|
||||
|
||||
<script type="module" src="scripts/leaderboard.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@ -116,4 +116,4 @@
|
||||
|
||||
<script type="module" src="scripts/lobby.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@ -60,12 +60,8 @@
|
||||
</div>
|
||||
|
||||
<div class="results-actions">
|
||||
<a href="lobby.html" class="btn btn--primary">
|
||||
Play Again →
|
||||
</a>
|
||||
<a href="leaderboard.html" class="btn btn--ghost">
|
||||
🏆 Leaderboard
|
||||
</a>
|
||||
<a href="lobby.html" class="btn btn--primary">Play Again →</a>
|
||||
<a href="leaderboard.html" class="btn btn--ghost">🏆 Leaderboard</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -74,7 +70,7 @@
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container footer__inner">
|
||||
<div>
|
||||
<div class="footer__left">
|
||||
<a href="index.html" class="footer__brand">GeoDraw</a>
|
||||
<p class="footer__sub">© 2026 · Browser geography game</p>
|
||||
</div>
|
||||
@ -98,4 +94,4 @@
|
||||
|
||||
<script type="module" src="scripts/results.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@ -3,37 +3,83 @@
|
||||
import { createLobby } from "./api.js";
|
||||
import { saveLobbyName } from "./storage.js";
|
||||
|
||||
/**
|
||||
* Handle the "Create game" button click.
|
||||
* Validates the lobby name, persists it, and navigates to the lobby page.
|
||||
*/
|
||||
document.getElementById("reg-btn")?.addEventListener("click", async () => {
|
||||
const lobbyInput = /** @type {HTMLInputElement|null} */ (
|
||||
document.getElementById("username")
|
||||
);
|
||||
const button = /** @type {HTMLButtonElement|null} */ (
|
||||
document.getElementById("reg-btn")
|
||||
);
|
||||
const lobbyName = lobbyInput ? lobbyInput.value.trim() : "";
|
||||
// ── DOM refs
|
||||
const formCreate = document.getElementById("form-create");
|
||||
const formJoin = document.getElementById("form-join");
|
||||
const createInput = /** @type {HTMLInputElement|null} */ (document.getElementById("lobby-create"));
|
||||
const joinInput = /** @type {HTMLInputElement|null} */ (document.getElementById("lobby-join"));
|
||||
const createBtn = /** @type {HTMLButtonElement|null} */ (document.getElementById("reg-btn"));
|
||||
const joinBtn = /** @type {HTMLButtonElement|null} */ (document.getElementById("join-btn"));
|
||||
const createError = document.getElementById("lobby-error");
|
||||
const joinError = document.getElementById("join-error");
|
||||
const toggleCreateBtn = /** @type {HTMLButtonElement|null} */ (document.getElementById("toggle-create-btn"));
|
||||
const toggleJoinBtn = /** @type {HTMLButtonElement|null} */ (document.getElementById("toggle-join-btn"));
|
||||
|
||||
// ── Toggle switch
|
||||
function showCreate() {
|
||||
formCreate?.removeAttribute("hidden");
|
||||
formJoin?.setAttribute("hidden", "");
|
||||
toggleCreateBtn?.classList.add("lobby-toggle__btn--active");
|
||||
toggleJoinBtn?.classList.remove("lobby-toggle__btn--active");
|
||||
createInput?.focus();
|
||||
}
|
||||
|
||||
function showJoin() {
|
||||
formJoin?.removeAttribute("hidden");
|
||||
formCreate?.setAttribute("hidden", "");
|
||||
toggleJoinBtn?.classList.add("lobby-toggle__btn--active");
|
||||
toggleCreateBtn?.classList.remove("lobby-toggle__btn--active");
|
||||
joinInput?.focus();
|
||||
}
|
||||
|
||||
toggleCreateBtn?.addEventListener("click", showCreate);
|
||||
toggleJoinBtn?.addEventListener("click", showJoin);
|
||||
|
||||
// ── Create lobby
|
||||
createBtn?.addEventListener("click", async () => {
|
||||
if (!createInput || !createError) return;
|
||||
|
||||
const lobbyName = createInput.value.trim();
|
||||
createError.textContent = "";
|
||||
|
||||
if (!lobbyName) {
|
||||
lobbyInput?.focus();
|
||||
createInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (button) button.disabled = true;
|
||||
createBtn.disabled = true;
|
||||
await createLobby(lobbyName);
|
||||
saveLobbyName(lobbyName);
|
||||
window.location.href = "lobby.html";
|
||||
} catch (error) {
|
||||
alert(error instanceof Error ? error.message : "Could not create lobby.");
|
||||
if (button) button.disabled = false;
|
||||
createError.textContent =
|
||||
error instanceof Error ? error.message : "Could not create lobby.";
|
||||
createBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Scroll reveal - animate elements into view as they enter the viewport
|
||||
const reveals = document.querySelectorAll(".reveal");
|
||||
// ── Join lobby — actual joinLobby() API call happens in lobby.js
|
||||
joinBtn?.addEventListener("click", () => {
|
||||
if (!joinInput || !joinError) return;
|
||||
|
||||
const lobbyName = joinInput.value.trim();
|
||||
joinError.textContent = "";
|
||||
|
||||
if (!lobbyName) {
|
||||
joinInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
saveLobbyName(lobbyName);
|
||||
window.location.href = "lobby.html";
|
||||
});
|
||||
|
||||
// Allow Enter key on both inputs
|
||||
createInput?.addEventListener("keydown", (e) => { if (e.key === "Enter") createBtn?.click(); });
|
||||
joinInput?.addEventListener("keydown", (e) => { if (e.key === "Enter") joinBtn?.click(); });
|
||||
|
||||
// ── Scroll reveal
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
@ -46,6 +92,4 @@ const observer = new IntersectionObserver(
|
||||
{ threshold: 0.12 },
|
||||
);
|
||||
|
||||
reveals.forEach((el) => {
|
||||
observer.observe(el);
|
||||
});
|
||||
document.querySelectorAll(".reveal").forEach((el) => observer.observe(el));
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
/* game.css */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
GAME LAYOUT
|
||||
════════════════════════════════════════ */
|
||||
.game-layout {
|
||||
min-height: calc(100vh - var(--hh));
|
||||
display: grid;
|
||||
@ -8,7 +11,10 @@
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* ── Top bar */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
TOP BAR
|
||||
════════════════════════════════════════ */
|
||||
.game-topbar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
@ -16,6 +22,7 @@
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* Round pips */
|
||||
.game-round {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -30,12 +37,8 @@
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.round-pip.active {
|
||||
background: var(--sea);
|
||||
}
|
||||
.round-pip.done {
|
||||
background: var(--leaf);
|
||||
}
|
||||
.round-pip--active { background: var(--sea); }
|
||||
.round-pip--done { background: var(--leaf); }
|
||||
|
||||
.round-label {
|
||||
font-family: "Syne", sans-serif;
|
||||
@ -113,29 +116,20 @@
|
||||
width: 100%;
|
||||
background: linear-gradient(90deg, var(--sea), var(--leaf));
|
||||
border-radius: 999px;
|
||||
transition:
|
||||
width 1s linear,
|
||||
background 0.4s ease;
|
||||
transition: width 1s linear, background 0.4s ease;
|
||||
}
|
||||
|
||||
/* Timer urgency states — applied via JS classList */
|
||||
.timer--warning .timer-bar,
|
||||
.timer--warning .timer-num {
|
||||
color: var(--gold);
|
||||
}
|
||||
.timer--warning .timer-bar {
|
||||
background: var(--gold);
|
||||
}
|
||||
/* Timer urgency states — toggled via JS */
|
||||
.timer--warning .timer-num { color: var(--gold); }
|
||||
.timer--warning .timer-bar { background: var(--gold); }
|
||||
|
||||
.timer--danger .timer-bar,
|
||||
.timer--danger .timer-num {
|
||||
color: var(--danger);
|
||||
}
|
||||
.timer--danger .timer-bar {
|
||||
background: var(--danger);
|
||||
}
|
||||
.timer--danger .timer-num { color: var(--danger); }
|
||||
.timer--danger .timer-bar { background: var(--danger); }
|
||||
|
||||
/* ── Canvas area */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
CANVAS AREA
|
||||
════════════════════════════════════════ */
|
||||
.canvas-area {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 220px;
|
||||
@ -193,15 +187,16 @@
|
||||
font-size: 2rem;
|
||||
font-weight: 800;
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity 0.4s ease,
|
||||
transform 0.4s ease;
|
||||
transition: opacity 0.4s ease, transform 0.4s ease;
|
||||
pointer-events: none;
|
||||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
SIDEBAR
|
||||
════════════════════════════════════════ */
|
||||
.game-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -226,7 +221,7 @@
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Round scores */
|
||||
/* Round score rows */
|
||||
.round-scores {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
@ -256,7 +251,7 @@
|
||||
color: var(--sea);
|
||||
}
|
||||
|
||||
/* Player name in sidebar */
|
||||
/* Player info */
|
||||
.player-name-display {
|
||||
font-family: "Syne", sans-serif;
|
||||
font-weight: 700;
|
||||
@ -265,13 +260,25 @@
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* Controls */
|
||||
.sidebar-hint {
|
||||
font-size: 0.82rem;
|
||||
color: var(--ink-soft);
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
CONTROLS
|
||||
════════════════════════════════════════ */
|
||||
.game-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* ─── Responsive ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
RESPONSIVE
|
||||
════════════════════════════════════════ */
|
||||
@media (max-width: 900px) {
|
||||
.canvas-area {
|
||||
grid-template-columns: 1fr;
|
||||
@ -283,17 +290,9 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sidebar-card {
|
||||
flex: 1 1 140px;
|
||||
}
|
||||
|
||||
.canvas-wrap {
|
||||
width: min(100%, 72vh);
|
||||
}
|
||||
|
||||
.game-topbar {
|
||||
grid-template-columns: auto 1fr auto;
|
||||
}
|
||||
.sidebar-card { flex: 1 1 140px; }
|
||||
.canvas-wrap { width: min(100%, 72vh); }
|
||||
.game-topbar { grid-template-columns: auto 1fr auto; }
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@ -308,36 +307,17 @@
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.game-country {
|
||||
text-align: left;
|
||||
}
|
||||
.game-timer {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.timer-track {
|
||||
width: 100%;
|
||||
}
|
||||
.game-country { text-align: left; }
|
||||
.game-timer { align-items: flex-start; }
|
||||
.timer-track { width: 100%; }
|
||||
.canvas-wrap { width: 100%; }
|
||||
|
||||
.canvas-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.game-controls {
|
||||
flex-direction: column;
|
||||
}
|
||||
.game-controls .btn {
|
||||
width: 100%;
|
||||
}
|
||||
.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: 0;
|
||||
}
|
||||
}
|
||||
.country-name { font-size: 1.4rem; }
|
||||
.timer-num { font-size: 1.6rem; }
|
||||
.canvas-wrap { min-height: 0; }
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
/* index.css — landing page specific styles */
|
||||
|
||||
/* ─── EYEBROW ─── */
|
||||
/* ════════════════════════════════════════
|
||||
EYEBROW BADGE
|
||||
════════════════════════════════════════ */
|
||||
.eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@ -26,8 +28,7 @@
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
@ -37,7 +38,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── HERO ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
HERO SECTION
|
||||
════════════════════════════════════════ */
|
||||
.hero {
|
||||
min-height: calc(100vh - var(--hh));
|
||||
display: grid;
|
||||
@ -84,7 +88,10 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* ─── GLOBE CARD ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
GLOBE CARD
|
||||
════════════════════════════════════════ */
|
||||
.globe-card {
|
||||
background: var(--glass);
|
||||
backdrop-filter: blur(12px);
|
||||
@ -106,18 +113,11 @@
|
||||
inset: 0;
|
||||
border-radius: var(--r-xl);
|
||||
background:
|
||||
radial-gradient(
|
||||
circle at 30% 25%,
|
||||
rgba(65, 184, 105, 0.18),
|
||||
transparent 50%
|
||||
),
|
||||
radial-gradient(
|
||||
circle at 75% 75%,
|
||||
rgba(26, 127, 196, 0.15),
|
||||
transparent 50%
|
||||
);
|
||||
radial-gradient(circle at 30% 25%, rgba(65, 184, 105, 0.18), transparent 50%),
|
||||
radial-gradient(circle at 75% 75%, rgba(26, 127, 196, 0.15), transparent 50%);
|
||||
}
|
||||
|
||||
/* Globe sphere */
|
||||
.globe {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
@ -137,20 +137,22 @@
|
||||
0 30px 60px rgba(7, 47, 82, 0.28);
|
||||
}
|
||||
|
||||
/* Continents */
|
||||
.continent {
|
||||
position: absolute;
|
||||
background: #52c870;
|
||||
border-radius: 46% 54% 58% 42%;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
.continent--a {
|
||||
width: 30%;
|
||||
height: 22%;
|
||||
top: 18%;
|
||||
left: 12%;
|
||||
transform: rotate(-14deg);
|
||||
}
|
||||
.c2 {
|
||||
|
||||
.continent--b {
|
||||
width: 18%;
|
||||
height: 28%;
|
||||
top: 30%;
|
||||
@ -158,14 +160,16 @@
|
||||
transform: rotate(8deg);
|
||||
border-radius: 40% 60% 50% 50%;
|
||||
}
|
||||
.c3 {
|
||||
|
||||
.continent--c {
|
||||
width: 22%;
|
||||
height: 16%;
|
||||
top: 56%;
|
||||
right: 15%;
|
||||
transform: rotate(22deg);
|
||||
}
|
||||
.c4 {
|
||||
|
||||
.continent--d {
|
||||
width: 12%;
|
||||
height: 18%;
|
||||
top: 68%;
|
||||
@ -174,6 +178,7 @@
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Globe grid lines */
|
||||
.globe-line {
|
||||
position: absolute;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
@ -183,25 +188,12 @@
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.gl1 {
|
||||
width: 60%;
|
||||
height: 100%;
|
||||
}
|
||||
.gl2 {
|
||||
width: 88%;
|
||||
height: 100%;
|
||||
}
|
||||
.gl3 {
|
||||
width: 100%;
|
||||
height: 55%;
|
||||
top: 50%;
|
||||
}
|
||||
.gl4 {
|
||||
width: 100%;
|
||||
height: 80%;
|
||||
top: 50%;
|
||||
}
|
||||
.globe-line--meridian-sm { width: 60%; height: 100%; }
|
||||
.globe-line--meridian-lg { width: 88%; height: 100%; }
|
||||
.globe-line--parallel-sm { width: 100%; height: 55%; top: 50%; }
|
||||
.globe-line--parallel-lg { width: 100%; height: 80%; top: 50%; }
|
||||
|
||||
/* Globe floating badges */
|
||||
.globe-badge {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
@ -214,43 +206,30 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
animation: floatBadge 3s ease-in-out infinite;
|
||||
animation: float-badge 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.gb--score {
|
||||
top: 14%;
|
||||
right: 6%;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
.gb--country {
|
||||
bottom: 14%;
|
||||
left: 4%;
|
||||
animation-delay: 1.2s;
|
||||
.globe-badge--score { top: 14%; right: 6%; animation-delay: 0s; }
|
||||
.globe-badge--country { bottom: 14%; left: 4%; animation-delay: 1.2s; }
|
||||
|
||||
@keyframes float-badge {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-8px); }
|
||||
}
|
||||
|
||||
@keyframes floatBadge {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
}
|
||||
|
||||
.gb__dot {
|
||||
.globe-badge__dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.gb__dot--green {
|
||||
background: var(--leaf);
|
||||
}
|
||||
.gb__dot--blue {
|
||||
background: var(--sea);
|
||||
}
|
||||
|
||||
/* ─── ABOUT ─── */
|
||||
.globe-badge__dot--green { background: var(--leaf); }
|
||||
.globe-badge__dot--blue { background: var(--sea); }
|
||||
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
ABOUT SECTION
|
||||
════════════════════════════════════════ */
|
||||
.about {
|
||||
padding: 100px 0;
|
||||
}
|
||||
@ -269,7 +248,8 @@
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
|
||||
.mini-globe-wrap {
|
||||
/* Game preview card */
|
||||
.game-preview {
|
||||
background: var(--glass);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.8);
|
||||
@ -285,19 +265,25 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mini-globe-wrap::before {
|
||||
.game-preview::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: var(--r-xl);
|
||||
background: radial-gradient(
|
||||
circle at 60% 40%,
|
||||
rgba(26, 127, 196, 0.1),
|
||||
transparent 50%
|
||||
);
|
||||
background: radial-gradient(circle at 60% 40%, rgba(26, 127, 196, 0.1), transparent 50%);
|
||||
}
|
||||
|
||||
/* ─── STEPS ─── */
|
||||
.game-preview__svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
STEPS LIST
|
||||
════════════════════════════════════════ */
|
||||
.steps {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
@ -314,9 +300,7 @@
|
||||
border-radius: var(--r-lg);
|
||||
padding: 22px 26px;
|
||||
box-shadow: 0 4px 20px rgba(11, 31, 42, 0.06);
|
||||
transition:
|
||||
transform 0.2s,
|
||||
box-shadow 0.2s;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.step:hover {
|
||||
@ -352,7 +336,10 @@
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ─── REGISTER ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
REGISTER SECTION
|
||||
════════════════════════════════════════ */
|
||||
.register {
|
||||
padding: 100px 0;
|
||||
}
|
||||
@ -394,7 +381,40 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ─── RESPONSIVE ─── */
|
||||
/* Lobby toggle */
|
||||
.lobby-toggle {
|
||||
display: flex;
|
||||
background: var(--cream);
|
||||
border: 1.5px solid var(--line);
|
||||
border-radius: 999px;
|
||||
padding: 4px;
|
||||
gap: 4px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.lobby-toggle__btn {
|
||||
flex: 1;
|
||||
padding: 9px 16px;
|
||||
border-radius: 999px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-family: "Syne", sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 0.88rem;
|
||||
color: var(--ink-muted);
|
||||
cursor: pointer;
|
||||
transition: background 0.18s, color 0.18s;
|
||||
}
|
||||
|
||||
.lobby-toggle__btn--active {
|
||||
background: var(--ink);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
RESPONSIVE
|
||||
════════════════════════════════════════ */
|
||||
@media (max-width: 960px) {
|
||||
.hero__inner,
|
||||
.about__inner,
|
||||
@ -403,35 +423,20 @@
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.globe-card {
|
||||
min-height: 360px;
|
||||
}
|
||||
.mini-globe-wrap {
|
||||
max-width: 260px;
|
||||
}
|
||||
.globe-card { min-height: 360px; }
|
||||
.game-preview { max-width: 260px; }
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.hero {
|
||||
padding: 50px 0 40px;
|
||||
}
|
||||
.hero { padding: 50px 0 40px; }
|
||||
.about,
|
||||
.register {
|
||||
padding: 70px 0;
|
||||
}
|
||||
.register__card {
|
||||
padding: 28px 22px;
|
||||
}
|
||||
.globe-card {
|
||||
padding: 24px;
|
||||
min-height: 280px;
|
||||
}
|
||||
.register { padding: 70px 0; }
|
||||
.register__card { padding: 28px 22px; }
|
||||
.globe-card { padding: 24px; min-height: 280px; }
|
||||
}
|
||||
|
||||
@media (max-width: 420px) {
|
||||
.hero__title {
|
||||
font-size: 3rem;
|
||||
}
|
||||
.hero__title { font-size: 3rem; }
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
@ -439,49 +444,28 @@
|
||||
padding: 36px 0 32px;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.hero__title {
|
||||
font-size: 2.6rem;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.hero__desc {
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
.globe-card {
|
||||
padding: 16px;
|
||||
min-height: 240px;
|
||||
}
|
||||
.globe-badge {
|
||||
padding: 7px 10px;
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
|
||||
.globe-card { padding: 16px; min-height: 240px; }
|
||||
.globe-badge { padding: 7px 10px; font-size: 0.72rem; }
|
||||
|
||||
.about,
|
||||
.register {
|
||||
padding: 52px 0;
|
||||
}
|
||||
.about__desc {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.step {
|
||||
padding: 16px 18px;
|
||||
gap: 14px;
|
||||
}
|
||||
.step__num {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
font-size: 0.88rem;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.register__card {
|
||||
padding: 22px 16px;
|
||||
border-radius: var(--r-lg);
|
||||
}
|
||||
.card-title {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
.btn--full {
|
||||
padding: 14px 18px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
}
|
||||
.register { padding: 52px 0; }
|
||||
.about__desc { font-size: 0.95rem; }
|
||||
|
||||
.step { padding: 16px 18px; gap: 14px; }
|
||||
.step__num { width: 46px; height: 46px; font-size: 0.88rem; border-radius: 12px; }
|
||||
|
||||
.register__card { padding: 22px 16px; border-radius: var(--r-lg); }
|
||||
.card-title { font-size: 1.25rem; margin-bottom: 22px; }
|
||||
.btn--full { padding: 14px 18px; font-size: 0.95rem; }
|
||||
}
|
||||
@ -1,5 +1,8 @@
|
||||
/* leader.css — leaderboard page styles */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
PAGE LAYOUT
|
||||
════════════════════════════════════════ */
|
||||
.lb-page {
|
||||
min-height: calc(100vh - var(--hh));
|
||||
padding: 60px 0;
|
||||
@ -41,7 +44,10 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* ─── TABLE ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
TABLE
|
||||
════════════════════════════════════════ */
|
||||
.lb-table {
|
||||
background: var(--white);
|
||||
border: 1px solid var(--line);
|
||||
@ -66,9 +72,7 @@
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
.lb-th.right {
|
||||
text-align: right;
|
||||
}
|
||||
.lb-th--right { text-align: right; }
|
||||
|
||||
.lb-body {
|
||||
display: grid;
|
||||
@ -81,38 +85,23 @@
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
transition: background 0.15s;
|
||||
animation: fadeRow 0.4s ease both;
|
||||
animation: fade-row 0.4s ease both;
|
||||
}
|
||||
|
||||
.lb-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.lb-row:hover {
|
||||
background: var(--cream);
|
||||
.lb-row:last-child { border-bottom: none; }
|
||||
.lb-row:hover { background: var(--cream); }
|
||||
|
||||
@keyframes fade-row {
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@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, 0.07);
|
||||
}
|
||||
.lb-row.rank-2 {
|
||||
background: rgba(180, 180, 195, 0.06);
|
||||
}
|
||||
.lb-row.rank-3 {
|
||||
background: rgba(205, 130, 70, 0.05);
|
||||
}
|
||||
/* Top 3 row highlights */
|
||||
.lb-row--rank-1 { background: rgba(240, 180, 40, 0.07); }
|
||||
.lb-row--rank-2 { background: rgba(180, 180, 195, 0.06); }
|
||||
.lb-row--rank-3 { background: rgba(205, 130, 70, 0.05); }
|
||||
|
||||
/* Rank number */
|
||||
.lb-rank {
|
||||
font-family: "Syne", sans-serif;
|
||||
font-weight: 800;
|
||||
@ -120,20 +109,13 @@
|
||||
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-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-medal { font-size: 1.1rem; }
|
||||
|
||||
/* Player name */
|
||||
.lb-name {
|
||||
font-family: "Syne", sans-serif;
|
||||
font-weight: 700;
|
||||
@ -144,7 +126,7 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.lb-name.is-you::after {
|
||||
.lb-name--you::after {
|
||||
content: " (you)";
|
||||
font-weight: 400;
|
||||
font-family: "DM Sans", sans-serif;
|
||||
@ -164,6 +146,7 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Score value */
|
||||
.lb-score {
|
||||
font-family: "Syne", sans-serif;
|
||||
font-weight: 800;
|
||||
@ -172,17 +155,14 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.lb-score.gold {
|
||||
color: var(--gold);
|
||||
}
|
||||
.lb-score.silver {
|
||||
color: #888898;
|
||||
}
|
||||
.lb-score.bronze {
|
||||
color: #c07840;
|
||||
}
|
||||
.lb-score--gold { color: var(--gold); }
|
||||
.lb-score--silver { color: #888898; }
|
||||
.lb-score--bronze { color: #c07840; }
|
||||
|
||||
/* ─── EMPTY STATE ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
EMPTY STATE
|
||||
════════════════════════════════════════ */
|
||||
.lb-empty {
|
||||
padding: 64px 24px;
|
||||
text-align: center;
|
||||
@ -199,7 +179,10 @@
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* ─── CURRENT PLAYER BAR ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
CURRENT PLAYER BAR
|
||||
════════════════════════════════════════ */
|
||||
.your-score-bar {
|
||||
margin-top: 20px;
|
||||
padding: 16px 24px;
|
||||
@ -217,31 +200,26 @@
|
||||
font-size: 0.9rem;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.your-score-bar__text strong {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
/* ─── RESPONSIVE ─── */
|
||||
.your-score-bar__text strong { color: var(--ink); }
|
||||
.your-score-bar__text--score strong { color: var(--sea); }
|
||||
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
RESPONSIVE
|
||||
════════════════════════════════════════ */
|
||||
@media (max-width: 640px) {
|
||||
.lb-page {
|
||||
padding: 40px 0;
|
||||
}
|
||||
.lb-page { padding: 40px 0; }
|
||||
|
||||
.lb-table-head,
|
||||
.lb-row {
|
||||
grid-template-columns: 44px 1fr 72px;
|
||||
}
|
||||
.lb-row { grid-template-columns: 44px 1fr 72px; }
|
||||
|
||||
.lb-th.hide-sm,
|
||||
.lb-th--hide-sm,
|
||||
.lb-rounds,
|
||||
.lb-date {
|
||||
display: none;
|
||||
}
|
||||
.lb-date { display: none; }
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.lb-table-head,
|
||||
.lb-row {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
}
|
||||
.lb-row { padding: 12px 16px; }
|
||||
}
|
||||
@ -1,5 +1,8 @@
|
||||
/* lobby.css */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
LOBBY LAYOUT
|
||||
════════════════════════════════════════ */
|
||||
.lobby {
|
||||
min-height: calc(100vh - var(--hh));
|
||||
display: grid;
|
||||
@ -15,33 +18,12 @@
|
||||
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, 0.9);
|
||||
box-shadow: var(--shadow-sm);
|
||||
backdrop-filter: blur(10px);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.lobby__name-icon {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
/* ════════════════════════════════════════
|
||||
PROMO (LEFT COLUMN)
|
||||
════════════════════════════════════════ */
|
||||
|
||||
#lobby-name-display {
|
||||
font-family: "Syne", sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
color: var(--ink);
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
/* ─── EYEBROW ─── */
|
||||
/* Eyebrow badge — mirrors index.css for standalone page loading */
|
||||
.eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@ -67,17 +49,35 @@
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.45);
|
||||
opacity: 0.7;
|
||||
}
|
||||
0%, 100% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.45); opacity: 0.7; }
|
||||
}
|
||||
|
||||
/* 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, 0.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: -0.01em;
|
||||
}
|
||||
|
||||
/* Title */
|
||||
.lobby__title {
|
||||
font-family: "Syne", sans-serif;
|
||||
font-weight: 800;
|
||||
@ -95,6 +95,7 @@
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* Description */
|
||||
.lobby__desc {
|
||||
font-size: 1.02rem;
|
||||
line-height: 1.72;
|
||||
@ -103,6 +104,7 @@
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
/* Info badges */
|
||||
.lobby__badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@ -124,11 +126,12 @@
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.badge__icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.badge__icon { font-size: 1rem; }
|
||||
|
||||
/* Right side card */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
FORM CARD (RIGHT COLUMN)
|
||||
════════════════════════════════════════ */
|
||||
.lobby__card {
|
||||
background: var(--white);
|
||||
border: 1px solid var(--line);
|
||||
@ -151,30 +154,22 @@
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
/* Form and field styles are defined in main.css */
|
||||
/* Form and field styles live in main.css */
|
||||
|
||||
/* ─── Responsive ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
RESPONSIVE
|
||||
════════════════════════════════════════ */
|
||||
@media (max-width: 900px) {
|
||||
.lobby__inner {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 40px;
|
||||
}
|
||||
.lobby__inner { grid-template-columns: 1fr; gap: 40px; }
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.lobby {
|
||||
padding: 44px 0;
|
||||
}
|
||||
.lobby__card {
|
||||
padding: 28px 22px;
|
||||
}
|
||||
.lobby { padding: 44px 0; }
|
||||
.lobby__card { padding: 28px 22px; }
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.lobby__card {
|
||||
padding: 22px 16px;
|
||||
}
|
||||
.lobby__title {
|
||||
font-size: 2.4rem;
|
||||
}
|
||||
}
|
||||
.lobby__card { padding: 22px 16px; }
|
||||
.lobby__title { font-size: 2.4rem; }
|
||||
}
|
||||
@ -2,30 +2,49 @@
|
||||
|
||||
@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");
|
||||
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
DESIGN TOKENS
|
||||
════════════════════════════════════════ */
|
||||
:root {
|
||||
--ink: #0b1f2a;
|
||||
--ink-soft: #3d5563;
|
||||
/* Colors */
|
||||
--ink: #0b1f2a;
|
||||
--ink-soft: #3d5563;
|
||||
--ink-muted: #7a9aaa;
|
||||
--sea: #1a7fc4;
|
||||
|
||||
--sea: #1a7fc4;
|
||||
--sea-light: #4faae0;
|
||||
--sea-dim: rgba(26, 127, 196, 0.12);
|
||||
--leaf: #41b869;
|
||||
--leaf-dim: rgba(65, 184, 105, 0.13);
|
||||
--gold: #f0b429;
|
||||
--danger: #e05c5c;
|
||||
--cream: #f4f9f6;
|
||||
--white: #ffffff;
|
||||
--sea-dim: rgba(26, 127, 196, 0.12);
|
||||
|
||||
--leaf: #41b869;
|
||||
--leaf-dim: rgba(65, 184, 105, 0.13);
|
||||
|
||||
--gold: #f0b429;
|
||||
--danger: #e05c5c;
|
||||
--cream: #f4f9f6;
|
||||
--white: #ffffff;
|
||||
|
||||
--glass: rgba(255, 255, 255, 0.72);
|
||||
--line: rgba(11, 31, 42, 0.09);
|
||||
--shadow: 0 24px 60px rgba(11, 31, 42, 0.1);
|
||||
--shadow-sm: 0 4px 20px rgba(11, 31, 42, 0.07);
|
||||
--line: rgba(11, 31, 42, 0.09);
|
||||
|
||||
/* Shadows */
|
||||
--shadow: 0 24px 60px rgba(11, 31, 42, 0.10);
|
||||
--shadow-sm: 0 4px 20px rgba(11, 31, 42, 0.07);
|
||||
|
||||
/* Radii */
|
||||
--r-xl: 32px;
|
||||
--r-lg: 22px;
|
||||
--r-md: 14px;
|
||||
--r-sm: 8px;
|
||||
|
||||
/* Layout */
|
||||
--hh: 72px;
|
||||
}
|
||||
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
RESET
|
||||
════════════════════════════════════════ */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
@ -34,9 +53,7 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
html { scroll-behavior: smooth; }
|
||||
|
||||
body {
|
||||
font-family: "DM Sans", sans-serif;
|
||||
@ -47,25 +64,28 @@ body {
|
||||
min-width: 320px;
|
||||
}
|
||||
|
||||
a { color: inherit; text-decoration: none; }
|
||||
img, svg { display: block; max-width: 100%; }
|
||||
button, input { font: inherit; }
|
||||
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
BACKGROUND DECORATIONS
|
||||
════════════════════════════════════════ */
|
||||
|
||||
/* Ambient colour blobs */
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(
|
||||
ellipse 60% 50% at 10% 0%,
|
||||
rgba(65, 184, 105, 0.1) 0%,
|
||||
transparent 60%
|
||||
),
|
||||
radial-gradient(
|
||||
ellipse 50% 60% at 90% 100%,
|
||||
rgba(26, 127, 196, 0.1) 0%,
|
||||
transparent 60%
|
||||
);
|
||||
radial-gradient(ellipse 60% 50% at 10% 0%, rgba(65, 184, 105, 0.1), transparent 60%),
|
||||
radial-gradient(ellipse 50% 60% at 90% 100%, rgba(26, 127, 196, 0.1), transparent 60%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* Grid overlay */
|
||||
body::after {
|
||||
content: "";
|
||||
position: fixed;
|
||||
@ -78,25 +98,15 @@ body::after {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
LAYOUT
|
||||
════════════════════════════════════════ */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1280px;
|
||||
@ -104,6 +114,10 @@ input {
|
||||
padding: 0 28px;
|
||||
}
|
||||
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
HEADER
|
||||
════════════════════════════════════════ */
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
@ -124,6 +138,7 @@ input {
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
/* Logo */
|
||||
.logo {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@ -155,6 +170,7 @@ input {
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
/* Nav */
|
||||
.nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -167,9 +183,7 @@ input {
|
||||
font-size: 0.92rem;
|
||||
font-weight: 500;
|
||||
color: var(--ink-soft);
|
||||
transition:
|
||||
background 0.18s,
|
||||
color 0.18s;
|
||||
transition: background 0.18s, color 0.18s;
|
||||
}
|
||||
|
||||
.nav__link:hover {
|
||||
@ -185,9 +199,7 @@ input {
|
||||
font-weight: 600;
|
||||
color: var(--white);
|
||||
background: var(--ink);
|
||||
transition:
|
||||
opacity 0.18s,
|
||||
transform 0.18s;
|
||||
transition: opacity 0.18s, transform 0.18s;
|
||||
}
|
||||
|
||||
.nav__cta:hover {
|
||||
@ -195,7 +207,10 @@ input {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* ─── BUTTONS ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
BUTTONS
|
||||
════════════════════════════════════════ */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@ -208,48 +223,29 @@ input {
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform 0.2s,
|
||||
box-shadow 0.2s,
|
||||
opacity 0.2s;
|
||||
transition: transform 0.2s, box-shadow 0.2s, opacity 0.2s;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.btn:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.btn:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.btn:hover:not(:disabled) { transform: translateY(-2px); }
|
||||
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
|
||||
|
||||
.btn--primary {
|
||||
color: var(--white);
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--sea) 0%,
|
||||
#159fd4 50%,
|
||||
var(--leaf) 100%
|
||||
);
|
||||
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, 0.28);
|
||||
animation: gradShift 4s ease infinite;
|
||||
animation: grad-shift 4s ease infinite;
|
||||
}
|
||||
|
||||
.btn--primary:hover:not(:disabled) {
|
||||
box-shadow: 0 12px 32px rgba(26, 127, 196, 0.4);
|
||||
}
|
||||
|
||||
@keyframes gradShift {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
@keyframes grad-shift {
|
||||
0% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
|
||||
.btn--ghost {
|
||||
@ -269,17 +265,13 @@ input {
|
||||
box-shadow: 0 6px 18px rgba(224, 92, 92, 0.25);
|
||||
}
|
||||
|
||||
.btn--full {
|
||||
width: 100%;
|
||||
}
|
||||
.btn--full { width: 100%; }
|
||||
.btn--sm { padding: 10px 20px; font-size: 0.88rem; border-radius: var(--r-md); }
|
||||
|
||||
.btn--sm {
|
||||
padding: 10px 20px;
|
||||
font-size: 0.88rem;
|
||||
border-radius: var(--r-md);
|
||||
}
|
||||
|
||||
/* ─── CARD ─── */
|
||||
/* ════════════════════════════════════════
|
||||
CARD
|
||||
════════════════════════════════════════ */
|
||||
.card {
|
||||
background: var(--glass);
|
||||
backdrop-filter: blur(12px);
|
||||
@ -288,7 +280,10 @@ input {
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
/* ─── SECTION LABELS ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
SECTION TYPOGRAPHY
|
||||
════════════════════════════════════════ */
|
||||
.section-label {
|
||||
display: inline-block;
|
||||
padding: 5px 13px;
|
||||
@ -311,16 +306,12 @@ input {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* ─── FORM ─── */
|
||||
.form {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
/* ════════════════════════════════════════
|
||||
FORM
|
||||
════════════════════════════════════════ */
|
||||
.form { display: grid; gap: 20px; }
|
||||
.field { display: grid; gap: 8px; }
|
||||
|
||||
.field label {
|
||||
font-size: 0.84rem;
|
||||
@ -338,14 +329,10 @@ input {
|
||||
background: var(--cream);
|
||||
color: var(--ink);
|
||||
font-size: 0.98rem;
|
||||
transition:
|
||||
border-color 0.18s,
|
||||
box-shadow 0.18s;
|
||||
transition: border-color 0.18s, box-shadow 0.18s;
|
||||
}
|
||||
|
||||
.field input::placeholder {
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
.field input::placeholder { color: var(--ink-muted); }
|
||||
|
||||
.field input:focus {
|
||||
outline: none;
|
||||
@ -365,7 +352,10 @@ input {
|
||||
min-height: 1.2em;
|
||||
}
|
||||
|
||||
/* ─── FOOTER ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
FOOTER
|
||||
════════════════════════════════════════ */
|
||||
.footer {
|
||||
border-top: 1px solid var(--line);
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
@ -393,9 +383,7 @@ input {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.footer__center {
|
||||
text-align: center;
|
||||
}
|
||||
.footer__center { text-align: center; }
|
||||
|
||||
.footer__label {
|
||||
font-size: 0.7rem;
|
||||
@ -420,34 +408,27 @@ input {
|
||||
transition: color 0.18s;
|
||||
}
|
||||
|
||||
.footer__link:hover {
|
||||
color: var(--ink);
|
||||
}
|
||||
.footer__link:hover { color: var(--ink); }
|
||||
|
||||
/* ─── SCROLL REVEAL ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
SCROLL REVEAL
|
||||
════════════════════════════════════════ */
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(24px);
|
||||
transition:
|
||||
opacity 0.55s ease,
|
||||
transform 0.55s ease;
|
||||
transition: opacity 0.55s ease, transform 0.55s ease;
|
||||
}
|
||||
|
||||
.reveal.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
.reveal-delay-1 {
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
.reveal-delay-2 {
|
||||
transition-delay: 0.2s;
|
||||
}
|
||||
.reveal-delay-3 {
|
||||
transition-delay: 0.32s;
|
||||
}
|
||||
.reveal.visible { opacity: 1; transform: translateY(0); }
|
||||
.reveal-delay-1 { transition-delay: 0.10s; }
|
||||
.reveal-delay-2 { transition-delay: 0.20s; }
|
||||
.reveal-delay-3 { transition-delay: 0.32s; }
|
||||
|
||||
/* ─── NOSCRIPT ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
NOSCRIPT
|
||||
════════════════════════════════════════ */
|
||||
.noscript-msg {
|
||||
padding: 1.5rem 2rem;
|
||||
background: var(--danger);
|
||||
@ -456,39 +437,31 @@ input {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ─── REDUCED MOTION ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
REDUCED MOTION
|
||||
════════════════════════════════════════ */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── RESPONSIVE BASE ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
RESPONSIVE
|
||||
════════════════════════════════════════ */
|
||||
@media (max-width: 680px) {
|
||||
.container {
|
||||
padding: 0 18px;
|
||||
}
|
||||
.nav__link {
|
||||
display: none;
|
||||
}
|
||||
.footer__inner {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.footer__right { justify-self: start; text-align: left; }
|
||||
.socials { justify-content: flex-start; }
|
||||
.footer__right { justify-content: flex-start; }
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
@ -496,18 +469,9 @@ input {
|
||||
--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: 0.82rem;
|
||||
}
|
||||
}
|
||||
|
||||
.container { padding: 0 14px; }
|
||||
.logo__mark { width: 34px; height: 34px; }
|
||||
.logo__text { font-size: 1.05rem; }
|
||||
.nav__cta { padding: 7px 12px; font-size: 0.82rem; }
|
||||
}
|
||||
@ -1,5 +1,8 @@
|
||||
/* results.css — results page styles */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
PAGE LAYOUT
|
||||
════════════════════════════════════════ */
|
||||
.results-page {
|
||||
min-height: calc(100vh - var(--hh));
|
||||
display: grid;
|
||||
@ -14,7 +17,10 @@
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
/* ─── HEADER ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
HEADER
|
||||
════════════════════════════════════════ */
|
||||
.results-header {
|
||||
text-align: center;
|
||||
}
|
||||
@ -23,18 +29,12 @@
|
||||
font-size: 3.5rem;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
animation: popIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
||||
animation: pop-in 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
||||
}
|
||||
|
||||
@keyframes popIn {
|
||||
from {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
@keyframes pop-in {
|
||||
from { transform: scale(0); opacity: 0; }
|
||||
to { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
.results-title {
|
||||
@ -46,40 +46,26 @@
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.results-player {
|
||||
font-size: 1rem;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.results-player { font-size: 1rem; color: var(--ink-soft); }
|
||||
.results-player strong { color: var(--ink); }
|
||||
|
||||
.results-player strong {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
/* ─── TOTAL SCORE ─── */
|
||||
/* ════════════════════════════════════════
|
||||
TOTAL SCORE CARD
|
||||
════════════════════════════════════════ */
|
||||
.score-total-card {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--sea) 0%,
|
||||
#159fd4 50%,
|
||||
var(--leaf) 100%
|
||||
);
|
||||
background: linear-gradient(135deg, var(--sea) 0%, #159fd4 50%, var(--leaf) 100%);
|
||||
border-radius: var(--r-xl);
|
||||
padding: 36px 40px;
|
||||
text-align: center;
|
||||
color: var(--white);
|
||||
box-shadow: 0 16px 40px rgba(26, 127, 196, 0.3);
|
||||
animation: slideUp 0.5s 0.1s cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
||||
animation: slide-up 0.5s 0.1s cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(30px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
@keyframes slide-up {
|
||||
from { transform: translateY(30px); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
.score-total-label {
|
||||
@ -117,14 +103,17 @@
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* ─── ROUND BREAKDOWN ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
ROUND BREAKDOWN CARD
|
||||
════════════════════════════════════════ */
|
||||
.rounds-card {
|
||||
background: var(--white);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--r-xl);
|
||||
padding: 28px 32px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
animation: slideUp 0.5s 0.2s cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
||||
animation: slide-up 0.5s 0.2s cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
||||
}
|
||||
|
||||
.rounds-card-title {
|
||||
@ -136,10 +125,7 @@
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.round-rows {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
.round-rows { display: grid; gap: 14px; }
|
||||
|
||||
.round-row {
|
||||
display: grid;
|
||||
@ -178,7 +164,7 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* ─── COUNTRY TAGS ─── */
|
||||
/* Country tags */
|
||||
.countries-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@ -195,36 +181,30 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ─── ACTIONS ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
ACTIONS
|
||||
════════════════════════════════════════ */
|
||||
.results-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
animation: slideUp 0.5s 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
||||
animation: slide-up 0.5s 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
||||
}
|
||||
|
||||
.results-actions .btn {
|
||||
flex: 1 1 180px;
|
||||
}
|
||||
.results-actions .btn { flex: 1 1 180px; }
|
||||
|
||||
/* ─── RESPONSIVE ─── */
|
||||
|
||||
/* ════════════════════════════════════════
|
||||
RESPONSIVE
|
||||
════════════════════════════════════════ */
|
||||
@media (max-width: 600px) {
|
||||
.results-page {
|
||||
padding: 40px 0;
|
||||
}
|
||||
.score-total-card {
|
||||
padding: 28px 24px;
|
||||
}
|
||||
.rounds-card {
|
||||
padding: 22px 20px;
|
||||
}
|
||||
.results-actions .btn {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
.results-page { padding: 40px 0; }
|
||||
.score-total-card { padding: 28px 24px; }
|
||||
.rounds-card { padding: 22px 20px; }
|
||||
.results-actions .btn { flex-basis: 100%; }
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.score-total-num {
|
||||
font-size: 3.4rem;
|
||||
}
|
||||
}
|
||||
.score-total-num { font-size: 3.4rem; }
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user