Front page styling tweaks (#2906)

This commit is contained in:
Kalle
2026-03-22 20:56:14 +02:00
committed by GitHub
parent d4b866528c
commit b7318c8070
4 changed files with 193 additions and 56 deletions

View File

@@ -150,6 +150,7 @@
.firstPlacers {
margin-inline: auto;
margin-top: var(--s-5);
margin-block-end: var(--s-4);
width: max-content;
}

View File

@@ -49,39 +49,49 @@ export default function FrontPage() {
);
}
function SeasonBanner() {
const { t } = useTranslation(["front"]);
const season = Seasons.next(new Date()) ?? Seasons.currentOrPrevious()!;
const _previousSeason = Seasons.previous();
function useSeasonData() {
const season = Seasons.next() ?? Seasons.currentOrPrevious()!;
const previousSeason = Seasons.previous();
const isInFuture = new Date() < season.starts;
const isShowingPreviousSeason = previousSeason?.nth === season.nth;
return { season, isInFuture, isShowingPreviousSeason };
}
function SeasonDates({
season,
className,
}: {
season: ReturnType<typeof useSeasonData>["season"];
className: string;
}) {
const isMounted = useIsMounted();
const { formatDate } = useTimeFormat();
const isInFuture = new Date() < season.starts;
const isShowingPreviousSeason = _previousSeason?.nth === season.nth;
return isMounted ? (
<div className={className}>
{formatDate(season.starts, { month: "long", day: "numeric" })} -{" "}
{formatDate(season.ends, { month: "long", day: "numeric" })}
</div>
) : (
<div className={clsx(className, "invisible")}>X</div>
);
}
function SeasonBanner() {
const { t } = useTranslation(["front"]);
const { season, isInFuture, isShowingPreviousSeason } = useSeasonData();
if (isShowingPreviousSeason) return null;
return (
<div className="stack xs">
<div className={styles.seasonBannerMobileOnly}>
<Link to={SENDOUQ_PAGE} className={styles.seasonBanner}>
<div className={styles.seasonBannerHeader}>
{t("front:sq.season", { nth: season.nth })}
</div>
{isMounted ? (
<div className={styles.seasonBannerDates}>
{formatDate(season.starts, {
month: "long",
day: "numeric",
})}{" "}
-{" "}
{formatDate(season.ends, {
month: "long",
day: "numeric",
})}
</div>
) : (
<div className={clsx(styles.seasonBannerDates, "invisible")}>X</div>
)}
<SeasonDates season={season} className={styles.seasonBannerDates} />
<Image
className={styles.seasonBannerImg}
path={sqHeaderGuyImageUrl(season.nth)}
@@ -99,6 +109,33 @@ function SeasonBanner() {
);
}
function SeasonCard() {
const { t } = useTranslation(["front"]);
const { season, isInFuture, isShowingPreviousSeason } = useSeasonData();
if (isShowingPreviousSeason) return null;
return (
<>
<Link to={SENDOUQ_PAGE} className={styles.seasonCard}>
<div className={styles.seasonCardHeader}>
{t("front:sq.season", { nth: season.nth })}
</div>
<SeasonDates season={season} className={styles.seasonCardDates} />
<Image
className={styles.seasonCardImg}
path={sqHeaderGuyImageUrl(season.nth)}
alt=""
/>
</Link>
<Link to={SENDOUQ_PAGE} className={styles.seasonCardButton}>
<Image path={navIconUrl("sendouq")} size={16} alt="" />
{isInFuture ? t("front:sq.prepare") : t("front:sq.participate")}
</Link>
</>
);
}
function LeagueBanner() {
const showBannerFor = import.meta.env.VITE_SHOW_BANNER_FOR_SEASON;
if (!showBannerFor) return null;
@@ -113,7 +150,7 @@ function LeagueBanner() {
}
function ResultHighlights() {
const { t } = useTranslation(["front"]);
const { t } = useTranslation(["front", "common"]);
const data = useLoaderData<typeof loader>();
// should not happen
@@ -127,33 +164,21 @@ function ResultHighlights() {
const season = Seasons.currentOrPrevious()!;
const recentResults = (
<>
<h2
className={clsx(
styles.resultHighlightsTitle,
styles.resultHighlightsTitleTournaments,
)}
>
{t("front:showcase.results")}
</h2>
<div className={styles.tournamentCardsSpacer}>
{data.tournaments.results.map((tournament) => (
<TournamentCard
key={tournament.id}
tournament={tournament}
withRelativeTime
/>
))}
</div>
</>
);
return (
<>
<div
className={clsx(styles.resultHighlights, "overflow-x-auto scrollbar")}
className={clsx(
styles.resultHighlights,
styles.resultHighlightsTop,
"overflow-x-auto scrollbar",
)}
>
<div className={styles.seasonCardDesktopOnly}>
<h2 className={styles.resultHighlightsTitle}>
{t("common:pages.sendouq")}
</h2>
<SeasonCard />
</div>
<div className="stack sm text-center">
<h2 className={styles.resultHighlightsTitle}>
{t("front:leaderboards.topPlayers")}
@@ -178,15 +203,26 @@ function ResultHighlights() {
})}
/>
</div>
<div className="stack sm text-center mobile-hidden">
{recentResults}
</div>
</div>
<div
className={clsx(styles.resultHighlights, "overflow-x-auto scrollbar")}
>
<div className="stack sm text-center desktop-hidden">
{recentResults}
<div className={clsx(styles.resultHighlights, "scrollbar")}>
<div className="stack sm text-center">
<h2
className={clsx(
styles.resultHighlightsTitle,
styles.resultHighlightsTitleTournaments,
)}
>
{t("front:showcase.results")}
</h2>
<div className={styles.tournamentCardsSpacer}>
{data.tournaments.results.map((tournament) => (
<TournamentCard
key={tournament.id}
tournament={tournament}
withRelativeTime
/>
))}
</div>
</div>
</div>
</>

View File

@@ -97,6 +97,30 @@
transition: all 0.2s ease-out;
}
.seasonBannerMobileOnly {
display: flex;
flex-direction: column;
gap: var(--s-1);
}
.seasonCardDesktopOnly {
display: none;
flex-direction: column;
gap: var(--s-2);
text-align: center;
align-items: center;
}
@media screen and (min-width: 750px) {
.seasonBannerMobileOnly {
display: none;
}
.seasonCardDesktopOnly {
display: flex;
}
}
.seasonBannerLink:hover svg {
translate: 2px;
}
@@ -105,7 +129,8 @@
display: flex;
gap: var(--s-2);
height: 100%;
padding: var(--s-2);
padding: 0 var(--s-2) var(--s-2) var(--s-2);
overflow-x: auto;
}
.resultHighlights {
@@ -114,6 +139,20 @@
padding: var(--s-2);
}
.resultHighlights > div {
flex: 3 1 0;
width: 100%;
}
.resultHighlights > .seasonCardDesktopOnly {
flex: 2 1 0;
}
.resultHighlightsTop {
--card-width: 100%;
overflow-x: auto;
}
.resultHighlightsTitle {
font-size: var(--font-xs);
}
@@ -123,9 +162,68 @@
margin-inline-start: 65px;
}
.leaderboard {
.seasonCard {
--l: 50%;
min-width: var(--card-width);
max-width: var(--card-width);
flex: 1;
background: linear-gradient(
to bottom,
var(--color-bg-higher),
var(--l),
var(--color-bg)
);
border-radius: var(--radius-box);
border: var(--border-style-high);
display: flex;
flex-direction: column;
padding: var(--s-2);
position: relative;
color: var(--color-text);
overflow: hidden;
transition: --l 0.2s ease-out;
}
.seasonCard:hover {
--l: 75%;
}
.seasonCardHeader {
font-size: var(--font-lg);
font-weight: var(--weight-bold);
padding-block-start: var(--s-2);
}
.seasonCardDates {
font-size: var(--font-xs);
font-weight: var(--weight-semi);
color: var(--color-text-lighter);
}
.seasonCardImg {
position: absolute;
width: 140px;
bottom: 0;
right: 16px;
}
.seasonCardButton {
font-size: var(--font-2xs);
color: var(--color-text);
background-color: var(--color-bg-higher);
border-radius: var(--radius-selector);
width: max-content;
height: var(--selector-size);
display: flex;
gap: var(--s-1);
padding: var(--s-0-5) var(--s-1-5);
}
.leaderboard {
width: 100%;
min-width: 14rem;
max-width: var(--card-width);
background-color: var(--color-bg-higher);
border-radius: var(--radius-box);
display: flex;

View File

@@ -98,6 +98,8 @@ test.describe("SendouQ", () => {
test("Challenge flow - send challenge, report match, seasons page, quick rejoin with replay", async ({
page,
}) => {
test.slow();
await seed(page); // DEFAULT seed includes full groups for ADMIN and NZAP
await impersonate(page, ADMIN_ID);