Add player leaderboard and homepage preview
This commit is contained in:
69
app/home-map.css
Normal file
69
app/home-map.css
Normal file
@@ -0,0 +1,69 @@
|
||||
/* Vanilla 1.21.11 map_background.png, rendered with its original alpha edges. */
|
||||
.card-map-content {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
.home-page .minecraft-paper {
|
||||
--map-paper-inset: 9.375%;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
clip-path: none;
|
||||
background: url("/images/minecraft-map-background.png") center / 100% 100%
|
||||
no-repeat;
|
||||
box-shadow: none;
|
||||
image-rendering: pixelated;
|
||||
filter: drop-shadow(4px 6px 0 #04080c99);
|
||||
}
|
||||
.home-page .map-art-frame {
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
transform: none;
|
||||
padding: 0;
|
||||
}
|
||||
.home-page .map-art-frame img {
|
||||
position: absolute;
|
||||
inset: var(--map-paper-inset);
|
||||
width: 81.25%;
|
||||
height: 81.25%;
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
.home-page .card-image .card-map-content {
|
||||
inset: var(--map-paper-inset);
|
||||
}
|
||||
.home-page .minecraft-paper.card-image .card-map-content img {
|
||||
object-fit: contain;
|
||||
}
|
||||
.home-page .map-card:hover .minecraft-paper img {
|
||||
transform: none;
|
||||
}
|
||||
.home-page .minecraft-paper .card-type {
|
||||
top: -16px;
|
||||
left: 0;
|
||||
}
|
||||
.home-page .empty-map-art {
|
||||
aspect-ratio: 1;
|
||||
padding: 0;
|
||||
transform: none;
|
||||
}
|
||||
.home-page .empty-map-art img {
|
||||
position: absolute;
|
||||
inset: var(--map-paper-inset);
|
||||
width: 81.25%;
|
||||
height: 81.25%;
|
||||
object-fit: contain;
|
||||
filter: none;
|
||||
}
|
||||
@media (max-width: 1000px) {
|
||||
.home-page .map-art-frame {
|
||||
width: 288px;
|
||||
height: 288px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
.home-page .map-art-frame {
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,9 @@ import "./block-theme.css";
|
||||
import "./account.css";
|
||||
import "./map-theme.css";
|
||||
import "./workshop.css";
|
||||
import "./leaderboard.css";
|
||||
import "./home-map.css";
|
||||
import "./rounded-theme.css";
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
default: "MineGeo — Get lost in a familiar world",
|
||||
|
||||
1080
app/leaderboard.css
Normal file
1080
app/leaderboard.css
Normal file
File diff suppressed because it is too large
Load Diff
24
app/leaderboard/loading.tsx
Normal file
24
app/leaderboard/loading.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
export default function LoadingLeaderboard() {
|
||||
return (
|
||||
<main
|
||||
className="section page-content leaderboard-page"
|
||||
aria-busy="true"
|
||||
aria-label="Loading leaderboard"
|
||||
>
|
||||
<span className="eyebrow">THE EXPLORERS’ BOARD</span>
|
||||
<h1>Every block counts.</h1>
|
||||
<p className="lead" role="status">
|
||||
Fetching the explorers’ scores…
|
||||
</p>
|
||||
<div className="leaderboard-skeleton" aria-hidden="true">
|
||||
{Array.from({ length: 6 }, (_, index) => (
|
||||
<div key={index}>
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
217
app/leaderboard/page.tsx
Normal file
217
app/leaderboard/page.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { ArrowLeft, ArrowRight, Search } from "lucide-react";
|
||||
import { Header, Footer } from "@/components/Shell";
|
||||
import {
|
||||
LeaderboardEmpty,
|
||||
LeaderboardTable,
|
||||
WinnersPodium,
|
||||
} from "@/components/Leaderboard";
|
||||
import { PixelTrophy } from "@/components/PixelArt";
|
||||
import { leaderboard } from "@/lib/leaderboard";
|
||||
import { user } from "@/lib/supabase";
|
||||
import {
|
||||
formatScore,
|
||||
leaderboardDifficulties,
|
||||
leaderboardFilters,
|
||||
leaderboardHref,
|
||||
} from "@/shared/leaderboard";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const metadata: Metadata = {
|
||||
title: "Leaderboard",
|
||||
description:
|
||||
"Meet MineGeo’s top explorers. Compare the best five-round scores across easy, normal, and hard Minecraft expeditions.",
|
||||
};
|
||||
|
||||
export default async function LeaderboardPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
||||
}) {
|
||||
const { difficulty, search, page } = leaderboardFilters(await searchParams);
|
||||
const [data, account] = await Promise.all([
|
||||
leaderboard({ difficulty, search, page }),
|
||||
user(),
|
||||
]);
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="section page-content leaderboard-page" id="main-content">
|
||||
<Link href="/" className="text-link leaderboard-back">
|
||||
<ArrowLeft size={14} /> Back to home
|
||||
</Link>
|
||||
<header className="leaderboard-intro">
|
||||
<div>
|
||||
<span className="eyebrow">THE EXPLORERS’ BOARD</span>
|
||||
<h1>Every block counts.</h1>
|
||||
<p className="lead">
|
||||
The sharpest eyes in the Overworld.
|
||||
<br />
|
||||
Five rounds. 25,000 possible points. Where do you stand?
|
||||
</p>
|
||||
</div>
|
||||
<PixelTrophy className="intro-trophy" />
|
||||
</header>
|
||||
<div className="leaderboard-toolbar">
|
||||
<nav
|
||||
className="leaderboard-filters"
|
||||
aria-label="Filter leaderboard by difficulty"
|
||||
>
|
||||
{leaderboardDifficulties.map((value) => (
|
||||
<Link
|
||||
key={value}
|
||||
href={leaderboardHref(value, search)}
|
||||
aria-current={difficulty === value ? "page" : undefined}
|
||||
>
|
||||
{value === "all"
|
||||
? "All difficulties"
|
||||
: value[0].toUpperCase() + value.slice(1)}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
<span className="leaderboard-period">All time</span>
|
||||
</div>
|
||||
{data ? (
|
||||
<>
|
||||
{data.leaders.length > 0 && !search && data.page === 1 && (
|
||||
<WinnersPodium leaders={data.leaders} />
|
||||
)}
|
||||
<section
|
||||
className="rankings-board"
|
||||
aria-labelledby="rankings-heading"
|
||||
>
|
||||
<div className="rankings-heading">
|
||||
<div>
|
||||
<h2 id="rankings-heading">The rankings</h2>
|
||||
<p>
|
||||
{formatScore(data.total_players)}{" "}
|
||||
{data.total_players === 1 ? "explorer" : "explorers"} ·{" "}
|
||||
{formatScore(data.total_games)} completed{" "}
|
||||
{data.total_games === 1 ? "expedition" : "expeditions"}
|
||||
</p>
|
||||
</div>
|
||||
<form
|
||||
action="/leaderboard"
|
||||
className="leaderboard-search"
|
||||
role="search"
|
||||
>
|
||||
{difficulty !== "all" && (
|
||||
<input type="hidden" name="difficulty" value={difficulty} />
|
||||
)}
|
||||
<label className="sr-only" htmlFor="player-search">
|
||||
Search players
|
||||
</label>
|
||||
<input
|
||||
key={`${difficulty}-${search}`}
|
||||
id="player-search"
|
||||
type="search"
|
||||
name="q"
|
||||
defaultValue={search}
|
||||
placeholder="Find an explorer…"
|
||||
maxLength={64}
|
||||
/>
|
||||
<button type="submit" aria-label="Search players">
|
||||
<Search size={17} />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{search && (
|
||||
<div className="search-summary">
|
||||
<span>
|
||||
{formatScore(data.matching_players)}{" "}
|
||||
{data.matching_players === 1 ? "result" : "results"} for “
|
||||
{search}”
|
||||
</span>
|
||||
<Link href={leaderboardHref(difficulty)}>Clear search ×</Link>
|
||||
</div>
|
||||
)}
|
||||
{data.entries.length ? (
|
||||
<LeaderboardTable
|
||||
entries={data.entries}
|
||||
playerId={account?.id}
|
||||
/>
|
||||
) : (
|
||||
<LeaderboardEmpty search={!!search} />
|
||||
)}
|
||||
{data.matching_players > 0 && (
|
||||
<div className="leaderboard-pagination">
|
||||
<span>
|
||||
Showing {formatScore((data.page - 1) * 25 + 1)}–
|
||||
{formatScore(
|
||||
Math.min(data.page * 25, data.matching_players),
|
||||
)}{" "}
|
||||
of {formatScore(data.matching_players)}
|
||||
</span>
|
||||
<nav aria-label="Leaderboard pages">
|
||||
{data.page > 1 ? (
|
||||
<Link
|
||||
aria-label="Previous page"
|
||||
href={leaderboardHref(
|
||||
difficulty,
|
||||
search,
|
||||
data.page - 1,
|
||||
)}
|
||||
>
|
||||
<ArrowLeft size={15} /> Previous
|
||||
</Link>
|
||||
) : (
|
||||
<span aria-disabled="true">
|
||||
<ArrowLeft size={15} /> Previous
|
||||
</span>
|
||||
)}
|
||||
<span className="page-number">
|
||||
{data.page} / {data.pages}
|
||||
</span>
|
||||
{data.page < data.pages ? (
|
||||
<Link
|
||||
aria-label="Next page"
|
||||
href={leaderboardHref(
|
||||
difficulty,
|
||||
search,
|
||||
data.page + 1,
|
||||
)}
|
||||
>
|
||||
Next <ArrowRight size={15} />
|
||||
</Link>
|
||||
) : (
|
||||
<span aria-disabled="true">
|
||||
Next <ArrowRight size={15} />
|
||||
</span>
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</>
|
||||
) : (
|
||||
<LeaderboardEmpty unavailable />
|
||||
)}
|
||||
<aside className="leaderboard-rules">
|
||||
<div>
|
||||
<span className="eyebrow">HOW THE BOARD WORKS</span>
|
||||
<h2>A place earned, one guess at a time.</h2>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
Your best completed expedition sets your rank. Ties are broken by
|
||||
average score, then completed games. Difficulty filters rank only
|
||||
games in that mode.
|
||||
</p>
|
||||
<p>
|
||||
Log in before you play. All five rounds must be finished to count;
|
||||
guest games stay off the board.
|
||||
</p>
|
||||
<Link href={account ? "/servers" : "/log-in"} className="text-link">
|
||||
{account
|
||||
? "Start your next expedition"
|
||||
: "Log in to join the board"}{" "}
|
||||
<ArrowRight size={15} />
|
||||
</Link>
|
||||
</div>
|
||||
</aside>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
29
app/page.tsx
29
app/page.tsx
@@ -11,20 +11,23 @@ import {
|
||||
import { Header, Footer } from "@/components/Shell";
|
||||
import { MapCard } from "@/components/MapCard";
|
||||
import { catalog } from "@/lib/catalog";
|
||||
import { leaderboard } from "@/lib/leaderboard";
|
||||
import { LeaderboardPreview } from "@/components/Leaderboard";
|
||||
export const dynamic = "force-dynamic";
|
||||
export default async function Home() {
|
||||
const maps = await catalog();
|
||||
const [maps, rankings] = await Promise.all([
|
||||
catalog(),
|
||||
leaderboard({ limit: 5 }),
|
||||
]);
|
||||
const servers = new Set(maps.map((map) => map.server_id)).size;
|
||||
const locations = maps.reduce((total, map) => total + map.panorama_count, 0);
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="home-page">
|
||||
<main className="home-page" id="main-content">
|
||||
<section className="hero">
|
||||
<div className="hero-copy">
|
||||
<div className="status-label">
|
||||
<span className="live-dot" /> MINECRAFT · EXPLORE · GUESS
|
||||
</div>
|
||||
<div className="status-label">A MINECRAFT GUESSING GAME</div>
|
||||
<h1>
|
||||
Guess where you are
|
||||
<br />
|
||||
@@ -40,10 +43,13 @@ export default async function Home() {
|
||||
<Link href="/servers" className="button primary">
|
||||
Start exploring <ArrowRight size={18} />
|
||||
</Link>
|
||||
<span className="hero-mode-note">3 difficulty levels</span>
|
||||
<Link href="/leaderboard" className="text-link">
|
||||
Meet the top explorers <ArrowRight size={14} />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="hero-notes">
|
||||
<span className="mode-dots" aria-hidden="true">
|
||||
<span className="round-blocks" aria-hidden="true">
|
||||
<i />
|
||||
<i />
|
||||
<i />
|
||||
<i />
|
||||
@@ -53,10 +59,10 @@ export default async function Home() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="hero-art" aria-label="Minecraft surface map">
|
||||
<div className="map-art-frame">
|
||||
<div className="map-art-frame minecraft-paper">
|
||||
<Image
|
||||
src={maps[0]?.mapImage || "/images/example-map.webp"}
|
||||
alt="A Minecraft surface map framed in parchment"
|
||||
alt="A Minecraft surface map on the original Minecraft parchment texture"
|
||||
width={1024}
|
||||
height={1024}
|
||||
loading="eager"
|
||||
@@ -123,12 +129,12 @@ export default async function Home() {
|
||||
{maps.length ? (
|
||||
<div className="cards-grid">
|
||||
{maps.slice(0, 4).map((map, index) => (
|
||||
<MapCard map={map} index={index} key={map.id} />
|
||||
<MapCard map={map} index={index} key={map.id} minecraftPaper />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="first-map-panel">
|
||||
<div className="empty-map-art" aria-hidden="true">
|
||||
<div className="empty-map-art minecraft-paper" aria-hidden="true">
|
||||
<Image
|
||||
src="/images/example-map.webp"
|
||||
alt=""
|
||||
@@ -155,6 +161,7 @@ export default async function Home() {
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
<LeaderboardPreview data={rankings} />
|
||||
<section className="section home-panels">
|
||||
<article className="home-panel">
|
||||
<span className="eyebrow">HOW TO PLAY</span>
|
||||
|
||||
272
app/rounded-theme.css
Normal file
272
app/rounded-theme.css
Normal file
@@ -0,0 +1,272 @@
|
||||
/* Soft charcoal panels around the original pixel artwork and Minecraft maps. */
|
||||
:root {
|
||||
--bg: #080b10;
|
||||
--panel: #1a222c;
|
||||
--panel2: #222c37;
|
||||
--line: #35404c;
|
||||
--green: #8dce66;
|
||||
--muted: #a1acb8;
|
||||
--text: #eef1f4;
|
||||
--board-stone: #1a222c;
|
||||
--board-edge: #35404c;
|
||||
--radius-panel: 16px;
|
||||
--radius-control: 9px;
|
||||
}
|
||||
body {
|
||||
background-image: radial-gradient(
|
||||
ellipse at 80% 12%,
|
||||
#25303d55,
|
||||
transparent 55%
|
||||
);
|
||||
}
|
||||
.site-header {
|
||||
background: #0c1017;
|
||||
border-bottom: 1px solid #2c3540;
|
||||
box-shadow: none;
|
||||
}
|
||||
.hero {
|
||||
grid-template-columns: 1.35fr 1fr;
|
||||
padding-top: 52px;
|
||||
padding-bottom: 52px;
|
||||
}
|
||||
.hero h1 {
|
||||
font-size: clamp(48px, 4.4vw, 64px);
|
||||
line-height: 1.05;
|
||||
}
|
||||
.hero h1 span {
|
||||
color: var(--green);
|
||||
}
|
||||
.button {
|
||||
border-radius: var(--radius-control);
|
||||
background: #293542;
|
||||
border: 1px solid #526071;
|
||||
box-shadow:
|
||||
inset 0 1px #ffffff14,
|
||||
0 3px 8px #04070b55;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
.button.primary {
|
||||
background: linear-gradient(#9ada72, #77b852);
|
||||
border: 1px solid #a6df80;
|
||||
color: #14200e;
|
||||
box-shadow:
|
||||
inset 0 1px #ddffc080,
|
||||
0 3px 8px #04070b55;
|
||||
}
|
||||
.button.primary:hover {
|
||||
background: linear-gradient(#ace783, #88c962);
|
||||
}
|
||||
.button:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.button:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
/* .header-account deliberately retains its existing square, beveled styling. */
|
||||
.compass-art {
|
||||
border-radius: 50%;
|
||||
border: 1px solid #536170;
|
||||
background: #202b36;
|
||||
width: 62px;
|
||||
height: 62px;
|
||||
box-shadow:
|
||||
inset 0 1px #ffffff15,
|
||||
0 5px 14px #04070b66;
|
||||
}
|
||||
.floating-tag {
|
||||
border-radius: var(--radius-control);
|
||||
background: #171f29;
|
||||
border-color: #35404c;
|
||||
box-shadow: 0 4px 12px #04070b55;
|
||||
}
|
||||
.world-stats {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: calc(100% - 56px);
|
||||
max-width: 1104px;
|
||||
margin: -9px auto 0;
|
||||
padding: 23px 24px;
|
||||
border: 1px solid #35404c;
|
||||
border-radius: var(--radius-panel);
|
||||
background: #1a222c;
|
||||
box-shadow: 0 8px 24px #03060a55;
|
||||
}
|
||||
.world-stats > div {
|
||||
border-color: #35404c;
|
||||
}
|
||||
.world-stats .stat-cube.gold-cube {
|
||||
background: #cba950;
|
||||
border-color: #edca74;
|
||||
}
|
||||
.world-stats .stat-cube.cyan-cube {
|
||||
background: #60b8bb;
|
||||
border-color: #94dcde;
|
||||
}
|
||||
.world-stats .stat-cube.red-cube {
|
||||
background: #c16067;
|
||||
border-color: #e98a91;
|
||||
}
|
||||
.map-card,
|
||||
.home-page .map-card {
|
||||
border: 1px solid #35404c;
|
||||
border-radius: var(--radius-panel);
|
||||
background: #1a222c;
|
||||
box-shadow: 0 4px 16px #03060a40;
|
||||
overflow: hidden;
|
||||
}
|
||||
.map-card:hover {
|
||||
border-color: #6c8b59;
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
.card-body,
|
||||
.home-page .card-body {
|
||||
background: #1a222c;
|
||||
}
|
||||
.card-type,
|
||||
.card-number,
|
||||
.badge {
|
||||
border-radius: 4px;
|
||||
}
|
||||
.leaderboard-preview-board,
|
||||
.rankings-board,
|
||||
.home-panel,
|
||||
.tool-card,
|
||||
.start-panel,
|
||||
.auth-card,
|
||||
.settings-card,
|
||||
.editor-panel,
|
||||
.report-dialog,
|
||||
.result-summary,
|
||||
.round-result,
|
||||
.empty-state,
|
||||
.first-map-panel {
|
||||
border-radius: var(--radius-panel);
|
||||
border: 1px solid var(--line);
|
||||
background: var(--panel);
|
||||
box-shadow: 0 5px 18px #03060a40;
|
||||
}
|
||||
.leaderboard-preview-board,
|
||||
.rankings-board {
|
||||
overflow: hidden;
|
||||
}
|
||||
.leaderboard-table thead {
|
||||
background: #131b24;
|
||||
}
|
||||
.home-panels {
|
||||
gap: 24px;
|
||||
}
|
||||
.home-panel,
|
||||
.home-panel + .home-panel {
|
||||
padding: 28px;
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
.mode-row {
|
||||
border-radius: var(--radius-control);
|
||||
background: #202b36;
|
||||
border-color: #3b4856;
|
||||
box-shadow: none;
|
||||
}
|
||||
.mode-row:hover {
|
||||
background: #293642;
|
||||
border-color: #6c8b59;
|
||||
}
|
||||
.contribute-banner {
|
||||
border-radius: var(--radius-panel);
|
||||
background: #19251f;
|
||||
border: 1px solid #435942;
|
||||
box-shadow: none;
|
||||
}
|
||||
.leaderboard-filters a,
|
||||
.leaderboard-pagination a,
|
||||
.difficulty-options button,
|
||||
.filter-tabs button,
|
||||
.icon-button,
|
||||
.auth-tabs a {
|
||||
border-radius: var(--radius-control);
|
||||
}
|
||||
.field input,
|
||||
.field textarea,
|
||||
.field select,
|
||||
.search-field,
|
||||
.leaderboard-search,
|
||||
.preview-toolbar select {
|
||||
border-radius: var(--radius-control);
|
||||
}
|
||||
.draft-row,
|
||||
.token-box,
|
||||
.preview-tile,
|
||||
.leaderboard-rules article {
|
||||
border-radius: var(--radius-control);
|
||||
}
|
||||
.player-avatar {
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* Native vector cursors track directly, with normal text/drag cursors retained. */
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
body {
|
||||
cursor:
|
||||
url("/cursors/orbit.svg") 14 14,
|
||||
auto;
|
||||
}
|
||||
a,
|
||||
button,
|
||||
select,
|
||||
summary,
|
||||
label[for],
|
||||
input[type="checkbox"],
|
||||
input[type="radio"],
|
||||
input[type="file"],
|
||||
[role="button"] {
|
||||
cursor:
|
||||
url("/cursors/orbit-link.svg") 18 18,
|
||||
pointer;
|
||||
}
|
||||
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
|
||||
textarea,
|
||||
[contenteditable="true"] {
|
||||
cursor: text;
|
||||
}
|
||||
button:disabled,
|
||||
[aria-disabled="true"] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
@media (max-width: 1000px) {
|
||||
.hero {
|
||||
grid-template-columns: 1.15fr 1fr;
|
||||
}
|
||||
.hero h1 {
|
||||
font-size: 54px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
.hero {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 36px 22px 32px;
|
||||
}
|
||||
.hero h1 {
|
||||
font-size: clamp(43px, 10.8vw, 58px);
|
||||
}
|
||||
.world-stats {
|
||||
width: calc(100% - 40px);
|
||||
padding: 22px 18px;
|
||||
gap: 24px 12px;
|
||||
}
|
||||
.world-stats strong {
|
||||
font-size: 28px;
|
||||
}
|
||||
.home-panel,
|
||||
.home-panel + .home-panel {
|
||||
padding: 23px 20px;
|
||||
}
|
||||
.home-panels {
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
a,
|
||||
button {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user