+
{lineType === "curve-down" ? (
) : null}
diff --git a/app/features/tournament-bracket/components/Bracket/PlacementsTable.module.css b/app/features/tournament-bracket/components/Bracket/PlacementsTable.module.css
new file mode 100644
index 000000000..2330e7aff
--- /dev/null
+++ b/app/features/tournament-bracket/components/Bracket/PlacementsTable.module.css
@@ -0,0 +1,97 @@
+.rrPlacementsTable {
+ font-size: var(--font-xs);
+ font-weight: var(--weight-semi);
+ min-width: max-content;
+ overflow-x: auto;
+ max-width: 600px;
+
+ & thead {
+ color: var(--color-text-high);
+ }
+
+ & th {
+ text-align: left;
+
+ & abbr {
+ padding-inline: var(--s-2);
+ }
+ }
+
+ & td span {
+ padding-inline: var(--s-2);
+ }
+
+ & td a.destinationLink {
+ color: inherit;
+ font-style: inherit;
+ padding-inline: var(--s-2);
+ }
+
+ & td button {
+ height: auto;
+ }
+
+ & td a.teamNameLink {
+ display: inline-block;
+ max-width: 240px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ vertical-align: middle;
+ }
+
+ & tbody tr:nth-child(odd) {
+ background-color: var(--color-bg-high);
+ }
+
+ & tbody tr:nth-child(even) {
+ background-color: var(--color-bg-higher);
+ }
+}
+
+.standingsDividerRow {
+ background-color: transparent !important;
+}
+
+.standingsDivider {
+ padding: 0;
+}
+
+.standingsDividerContent {
+ display: flex;
+ align-items: center;
+ gap: var(--s-2);
+ padding-block: var(--s-2);
+}
+
+.standingsDividerLine {
+ flex: 1;
+ height: 2px;
+ background-color: var(--color-border);
+}
+
+.standingsDividerText {
+ font-size: var(--font-2xs);
+ font-weight: var(--weight-bold);
+ white-space: nowrap;
+}
+
+.standingsDividerQualified {
+ & .standingsDividerLine {
+ background-color: var(--color-success);
+ }
+
+ & .standingsDividerText {
+ color: var(--color-success);
+ }
+}
+
+.standingsDividerEliminated {
+ & .standingsDividerLine {
+ background-color: var(--color-error);
+ }
+
+ & .standingsDividerText {
+ color: var(--color-error);
+ }
+}
diff --git a/app/features/tournament-bracket/components/Bracket/PlacementsTable.tsx b/app/features/tournament-bracket/components/Bracket/PlacementsTable.tsx
index ad0920ab4..ce6817219 100644
--- a/app/features/tournament-bracket/components/Bracket/PlacementsTable.tsx
+++ b/app/features/tournament-bracket/components/Bracket/PlacementsTable.tsx
@@ -14,7 +14,7 @@ import * as Swiss from "../../core/engine/swiss/team-status";
import * as Progression from "../../core/Progression";
import type { BracketMeta } from "../../core/Tournament";
import { bracketSchema } from "../../tournament-bracket-schemas";
-import styles from "./bracket.module.css";
+import styles from "./PlacementsTable.module.css";
export function PlacementsTable({
groupId,
diff --git a/app/features/tournament-bracket/components/Bracket/RoundHeader.module.css b/app/features/tournament-bracket/components/Bracket/RoundHeader.module.css
new file mode 100644
index 000000000..43ef22974
--- /dev/null
+++ b/app/features/tournament-bracket/components/Bracket/RoundHeader.module.css
@@ -0,0 +1,18 @@
+.elimRoundHeader {
+ text-align: center;
+ background-color: var(--color-bg-higher);
+ font-size: var(--font-xs);
+ font-weight: var(--weight-semi);
+ padding-block: var(--s-2);
+ width: var(--match-width);
+ border-radius: var(--radius-box);
+}
+
+.elimRoundHeaderInfos {
+ width: var(--match-width);
+ display: flex;
+ justify-content: space-between;
+ font-size: var(--font-2xs);
+ color: var(--color-text-high);
+ font-weight: var(--weight-semi);
+}
diff --git a/app/features/tournament-bracket/components/Bracket/RoundHeader.tsx b/app/features/tournament-bracket/components/Bracket/RoundHeader.tsx
index 445feafe7..a14a071f5 100644
--- a/app/features/tournament-bracket/components/Bracket/RoundHeader.tsx
+++ b/app/features/tournament-bracket/components/Bracket/RoundHeader.tsx
@@ -9,7 +9,7 @@ import { databaseTimestampToDate } from "~/utils/dates";
import type { Unpacked } from "~/utils/types";
import * as Deadline from "../../core/Deadline";
import type { TournamentData } from "../../core/Tournament.server";
-import styles from "./bracket.module.css";
+import styles from "./RoundHeader.module.css";
export function RoundHeader({
roundId,
diff --git a/app/features/tournament-bracket/components/Bracket/RoundRobin.tsx b/app/features/tournament-bracket/components/Bracket/RoundRobin.tsx
index 0d16754fb..903fcaad9 100644
--- a/app/features/tournament-bracket/components/Bracket/RoundRobin.tsx
+++ b/app/features/tournament-bracket/components/Bracket/RoundRobin.tsx
@@ -1,7 +1,11 @@
import type { MatchData as MatchType } from "~/features/tournament-bracket/core/engine/types";
import type { Bracket as BracketType } from "../../core/Bracket";
import { groupNumberToLetters } from "../../tournament-bracket-utils";
-import styles from "./bracket.module.css";
+import {
+ BracketColumn,
+ BracketColumnMatches,
+ BracketColumns,
+} from "./BracketColumns";
import { Match } from "./Match";
import { PlacementsTable } from "./PlacementsTable";
import { RoundHeader } from "./RoundHeader";
@@ -29,10 +33,7 @@ export function RoundRobinBracket({ bracket }: { bracket: BracketType }) {
return (
{groupName}
-
+
{rounds.flatMap((round) => {
const bestOf = round.maps?.count;
@@ -46,7 +47,7 @@ export function RoundRobinBracket({ bracket }: { bracket: BracketType }) {
);
return (
-
+
-
+
{matches.map((match) => {
if (!match.opponent1 || !match.opponent2) {
return null;
@@ -80,11 +81,11 @@ export function RoundRobinBracket({ bracket }: { bracket: BracketType }) {
/>
);
})}
-
-
+
+
);
})}
-
+
{censored ? null : (
, which spans the content area in breakout
- mode (its own padding keeps the bracket off the edges). */
-.breakoutWrapper {
- :global([data-main-breakout]) & {
- width: 100cqw;
- margin-inline: calc(50% - 50cqw);
- }
-}
-
-.matchHeader {
- position: absolute;
- display: flex;
- justify-content: space-between;
- width: var(--match-width);
- margin-block-start: calc(var(--selector-size-xs) * -1);
-}
-
-.matchHeaderBox {
- background-color: var(--color-bg-higher);
- padding: 0 var(--s-1);
- height: var(--selector-size-xs);
- display: flex;
- align-items: center;
- border-radius: var(--radius-selector);
- font-size: var(--font-2xs) !important;
- font-weight: var(--weight-semi);
- color: var(--color-text);
- border: 0;
-}
-
-.matchHeaderBoxButton {
- height: 18.86px;
-}
-
-.matchTimer {
- position: absolute;
- top: 50%;
- left: 0;
- transform: translate(-100%, -50%);
- margin-inline-start: 8px;
-}
-
-.matchTimerWarning {
- background-color: var(--color-warning);
-}
-
-.matchTimerError {
- background-color: var(--color-error);
-}
-
-.match {
- width: var(--match-width);
- min-height: var(--match-height);
- max-height: var(--match-height);
- border-radius: var(--radius-field);
- background-color: var(--color-bg-high);
- font-size: var(--font-2xs);
- font-weight: var(--weight-semi);
- padding: 0 var(--s-2);
- display: flex;
- flex-direction: column;
- gap: var(--s-1);
- color: var(--text-main);
- justify-content: center;
- transition: background-color 0.2s;
-}
-
-a.match:hover {
- background-color: var(--color-bg-high);
- border-radius: var(--radius-field);
-}
-
-.matchSeparator {
- min-height: 2px;
- max-height: 2px;
- width: 100%;
- background-color: var(--color-bg-higher);
-}
-
-.matchBye {
- visibility: hidden;
- min-height: var(--match-height);
- max-height: var(--match-height);
-}
-
-.matchSeed {
- color: var(--color-text-accent);
- margin-inline-end: var(--s-0-5);
- min-width: 15px;
- max-width: 15px;
-}
-
-.matchSeedWide {
- min-width: 22px;
- max-width: 22px;
-}
-
-.matchTeamName {
- max-width: 95px;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
-}
-
-.matchTeamNameNarrow {
- max-width: 75px;
-}
-
-.matchTeamNameNarrowest {
- max-width: 70px;
-}
-
-.matchScore {
- margin-inline-start: auto;
-}
-
-.elimContainer {
- --line-width: 30px;
- display: grid;
- grid-template-columns: repeat(
- var(--round-count),
- calc(var(--match-width) + var(--line-width))
- );
- overflow: visible;
-}
-
-.elimRoundMatchesContainer {
- display: flex;
- flex-direction: column;
- justify-content: space-around;
- gap: var(--s-8);
- margin-top: var(--s-6);
- flex: 1;
- overflow: visible;
-}
-
-.elimRoundMatchesContainerTopBye {
- margin-top: -18px;
-}
-
-.elimRoundHeader {
- text-align: center;
- background-color: var(--color-bg-higher);
- font-size: var(--font-xs);
- font-weight: var(--weight-semi);
- padding-block: var(--s-2);
- width: var(--match-width);
- border-radius: var(--radius-box);
-}
-
-.elimRoundHeaderInfos {
- width: var(--match-width);
- display: flex;
- justify-content: space-between;
- font-size: var(--font-2xs);
- color: var(--color-text-high);
- font-weight: var(--weight-semi);
-}
-
-.elimRoundColumn {
- display: flex;
- flex-direction: column;
-}
-
-.matchWrapper {
- position: relative;
- width: var(--match-width);
- height: var(--match-height);
-}
-
-.matchLineContainer {
- position: absolute;
- top: 0;
- left: 100%;
- width: var(--line-width);
- height: 100%;
- pointer-events: none;
-}
-
-.matchLineStraight::before {
- content: "";
- position: absolute;
- top: 50%;
- left: 0;
- width: 100%;
- height: 2px;
- background: var(--color-border);
- transform: translateY(-50%);
-}
-
-.matchLineCurveDown::before,
-.matchLineCurveUp::before {
- content: "";
- position: absolute;
- top: 50%;
- left: 0;
- width: 50%;
- height: 2px;
- background: var(--color-border);
- transform: translateY(-50%);
-}
-
-.matchLineCurveDown::after {
- content: "";
- position: absolute;
- top: 50%;
- left: calc(50% - 1px);
- width: 2px;
- height: calc(50% + var(--bracket-vertical-extend, 50px));
- background: var(--color-border);
-}
-
-.matchLineCurveUp::after {
- content: "";
- position: absolute;
- bottom: 50%;
- left: calc(50% - 1px);
- width: 2px;
- height: calc(50% + var(--bracket-vertical-extend, 50px));
- background: var(--color-border);
-}
-
-.matchLineConnectorDown {
- position: absolute;
- top: calc(100% + var(--bracket-vertical-extend, 50px) - 1px);
- left: calc(50% - 1px);
- width: calc(50% + 1px);
- height: 2px;
- background: var(--color-border);
-}
-
-.matchLineConnectorUp {
- position: absolute;
- bottom: calc(100% + var(--bracket-vertical-extend, 50px) - 1px);
- left: calc(50% - 1px);
- width: calc(50% + 1px);
- height: 2px;
- background: var(--color-border);
-}
-
-.rrPlacementsTable {
- font-size: var(--font-xs);
- font-weight: var(--weight-semi);
- min-width: max-content;
- overflow-x: auto;
- max-width: 600px;
-
- & thead {
- color: var(--color-text-high);
- }
-
- & th {
- text-align: left;
-
- & abbr {
- padding-inline: var(--s-2);
- }
- }
-
- & td span {
- padding-inline: var(--s-2);
- }
-
- & td a.destinationLink {
- color: inherit;
- font-style: inherit;
- padding-inline: var(--s-2);
- }
-
- & td button {
- height: auto;
- }
-
- & td a.teamNameLink {
- display: inline-block;
- max-width: 240px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- vertical-align: middle;
- }
-
- & tbody tr:nth-child(odd) {
- background-color: var(--color-bg-high);
- }
-
- & tbody tr:nth-child(even) {
- background-color: var(--color-bg-higher);
- }
-}
-
-.standingsDividerRow {
- background-color: transparent !important;
-}
-
-.standingsDivider {
- padding: 0;
-}
-
-.standingsDividerContent {
- display: flex;
- align-items: center;
- gap: var(--s-2);
- padding-block: var(--s-2);
-}
-
-.standingsDividerLine {
- flex: 1;
- height: 2px;
- background-color: var(--color-border);
-}
-
-.standingsDividerText {
- font-size: var(--font-2xs);
- font-weight: var(--weight-bold);
- white-space: nowrap;
-}
-
-.standingsDividerQualified {
- & .standingsDividerLine {
- background-color: var(--color-success);
- }
-
- & .standingsDividerText {
- color: var(--color-success);
- }
-}
-
-.standingsDividerEliminated {
- & .standingsDividerLine {
- background-color: var(--color-error);
- }
-
- & .standingsDividerText {
- color: var(--color-error);
- }
-}
diff --git a/app/features/tournament-bracket/components/Bracket/index.module.css b/app/features/tournament-bracket/components/Bracket/index.module.css
new file mode 100644
index 000000000..413d69607
--- /dev/null
+++ b/app/features/tournament-bracket/components/Bracket/index.module.css
@@ -0,0 +1,60 @@
+/* --match-height and gap must match constants in Elimination.tsx */
+.bracket {
+ --match-width: 140px;
+ --match-height: 55px;
+ overflow-x: auto;
+ display: flex;
+ flex-direction: column;
+ gap: var(--s-8);
+ padding-block-end: var(--s-6);
+}
+
+.scrollingBracket {
+ padding: var(--s-4) var(--s-6);
+ max-width: 100%;
+ max-height: min(1200px, 82dvh);
+ scrollbar-width: none;
+ border: var(--border-style);
+ border-radius: var(--radius-box);
+ overflow: scroll;
+ user-select: none;
+
+ /* Inside a breakout wrapper (see `mainBreakout`) grow only as wide as the
+ bracket actually needs: never narrower than the normal page width, never
+ wider than the content area (then it scrolls), centered either way.
+ Height fills down to the bottom of the viewport using the top offset
+ published by JS (`--bracket-fill-top`), floored so it never collapses. */
+ :global([data-main-breakout]) & {
+ width: fit-content;
+ min-width: min(72rem, 100%);
+ margin-inline: auto;
+ max-height: max(
+ 300px,
+ calc(100dvh - var(--bracket-fill-top, 30dvh) - var(--s-6))
+ );
+
+ /* The mobile bottom nav is fixed below 600px; keep clear of it. */
+ @media screen and (max-width: 599.98px) {
+ max-height: max(
+ 300px,
+ calc(
+ 100dvh -
+ var(--bracket-fill-top, 30dvh) -
+ var(--layout-nav-height) -
+ env(safe-area-inset-bottom) -
+ var(--s-3)
+ )
+ );
+ }
+ }
+}
+
+/* Lets the bracket size against the full content area instead of the page
+ width. cqw resolves to , which spans the content area in breakout
+ mode (its own padding keeps the bracket off the edges). */
+.breakoutWrapper {
+ :global([data-main-breakout]) & {
+ width: 100cqw;
+ margin-inline: calc(50% - 50cqw);
+ }
+}
diff --git a/app/features/tournament-bracket/components/Bracket/index.tsx b/app/features/tournament-bracket/components/Bracket/index.tsx
index 8464c2181..23de4c1f1 100644
--- a/app/features/tournament-bracket/components/Bracket/index.tsx
+++ b/app/features/tournament-bracket/components/Bracket/index.tsx
@@ -4,8 +4,8 @@ import { useBracketExpanded } from "~/features/tournament/routes/to.$id";
import { useDragToScroll } from "~/hooks/useDragToScroll";
import { useIsomorphicLayoutEffect } from "~/hooks/useIsomorphicLayoutEffect";
import type { Bracket as BracketType } from "../../core/Bracket";
-import styles from "./bracket.module.css";
import { EliminationBracketSide } from "./Elimination";
+import styles from "./index.module.css";
import { RoundRobinBracket } from "./RoundRobin";
import { SwissBracket } from "./Swiss";
diff --git a/app/features/tournament-bracket/components/TournamentTeamActions.module.css b/app/features/tournament-bracket/components/TournamentTeamActions.module.css
new file mode 100644
index 000000000..14108dc94
--- /dev/null
+++ b/app/features/tournament-bracket/components/TournamentTeamActions.module.css
@@ -0,0 +1,25 @@
+.quickAction {
+ font-size: var(--font-xs);
+ color: var(--color-text);
+ background-color: var(--color-bg);
+ border-radius: var(--radius-box);
+ border: 2px solid var(--color-bg-higher);
+ padding-block: var(--s-1-5);
+ padding-inline: var(--s-3);
+ font-weight: var(--weight-bold);
+ align-items: center;
+}
+
+.quickActionSpaced {
+ display: flex;
+ gap: var(--s-1-5);
+}
+
+.quickActionVerySpaced {
+ display: flex;
+ gap: var(--s-3);
+}
+
+.quickActionCheckmark {
+ color: var(--color-success);
+}
diff --git a/app/features/tournament-bracket/components/TournamentTeamActions.tsx b/app/features/tournament-bracket/components/TournamentTeamActions.tsx
index 3d628e419..cb7488567 100644
--- a/app/features/tournament-bracket/components/TournamentTeamActions.tsx
+++ b/app/features/tournament-bracket/components/TournamentTeamActions.tsx
@@ -18,7 +18,7 @@ import {
tournamentMatchPage,
tournamentRegisterPage,
} from "~/utils/urls";
-import styles from "../tournament-bracket.module.css";
+import styles from "./TournamentTeamActions.module.css";
export function TournamentTeamActions({
status,
diff --git a/app/features/tournament-bracket/routes/to.$id.brackets.module.css b/app/features/tournament-bracket/routes/to.$id.brackets.module.css
new file mode 100644
index 000000000..f58b7a0bb
--- /dev/null
+++ b/app/features/tournament-bracket/routes/to.$id.brackets.module.css
@@ -0,0 +1,14 @@
+.miniAlert {
+ background-color: var(--color-info-low);
+ font-size: var(--font-2xs);
+ border-radius: var(--radius-box);
+ padding: var(--s-1) var(--s-2);
+}
+
+.compactifyButton {
+ font-size: var(--font-2xs);
+ color: var(--color-text-high);
+ border-color: var(--color-bg-higher);
+ background-color: var(--color-bg);
+ border-radius: var(--radius-box);
+}
diff --git a/app/features/tournament-bracket/routes/to.$id.brackets.tsx b/app/features/tournament-bracket/routes/to.$id.brackets.tsx
index a6c80bbbc..257e2b2dd 100644
--- a/app/features/tournament-bracket/routes/to.$id.brackets.tsx
+++ b/app/features/tournament-bracket/routes/to.$id.brackets.tsx
@@ -74,7 +74,7 @@ export const handle: SendouRouteHandle = {
};
import { tournamentJoinPage } from "~/features/tournament/tournament-urls";
-import styles from "../tournament-bracket.module.css";
+import styles from "./to.$id.brackets.module.css";
export default function TournamentBracketsPage() {
const data = useLoaderData();
@@ -709,7 +709,6 @@ function StartBracketAlert({
{bracket.participantTournamentTeamIds.length}/
diff --git a/app/features/tournament-bracket/tournament-bracket.module.css b/app/features/tournament-bracket/tournament-bracket.module.css
deleted file mode 100644
index 2e0b4c32c..000000000
--- a/app/features/tournament-bracket/tournament-bracket.module.css
+++ /dev/null
@@ -1,589 +0,0 @@
-.miniAlert {
- background-color: var(--color-info-low);
- font-size: var(--font-2xs);
- border-radius: var(--radius-box);
- padding: var(--s-1) var(--s-2);
-}
-
-.infos {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- margin-bottom: var(--s-3);
- background: var(--color-bg-high);
- border-end-end-radius: var(--radius-box);
- border-end-start-radius: var(--radius-box);
- color: var(--tournaments-text);
- column-gap: var(--s-8);
- font-size: var(--font-sm);
- letter-spacing: 0.3px;
-}
-
-.infosValue {
- & > button {
- font-size: var(--font-2xs);
- }
-}
-
-.lockedBanner {
- width: 100%;
- height: 10rem;
- background-color: var(--color-bg-high);
- border-start-end-radius: var(--radius-box);
- border-start-start-radius: var(--radius-box);
- grid-area: img;
- display: grid;
- place-items: center;
- position: relative;
- padding-inline: var(--s-2);
-}
-
-.lockedBannerLonely {
- border-radius: var(--radius-box);
-}
-
-.stageBanner {
- display: flex;
- width: 100%;
- height: 10rem;
- flex-direction: column;
- justify-content: space-between;
- background-image: var(--_tournament-bg-url);
- background-position: center;
- border-start-end-radius: var(--radius-box);
- border-start-start-radius: var(--radius-box);
- grid-area: img;
- background-repeat: no-repeat;
- background-size: cover;
- position: relative;
-}
-
-.stageBannerTopBar {
- display: flex;
- justify-content: space-between;
- padding: var(--s-2);
- backdrop-filter: blur(10px) brightness(95%);
- background: rgb(0 0 0 / 40%);
- border-start-end-radius: var(--radius-box);
- border-start-start-radius: var(--radius-box);
- font-size: var(--font-xs);
- color: #fff;
-}
-
-.stageBannerBottomBar {
- display: flex;
- justify-content: flex-end;
- padding: var(--s-2);
-}
-
-.stageBannerUndoButton {
- position: absolute;
- right: 8px;
- bottom: 8px;
-}
-
-.stageBannerEndSetButton {
- right: 8px;
- bottom: 8px;
-}
-
-.stageBanner {
- &:has(.stageBannerEndSetButton)
- .stageBannerUndoButton:not(.stageBannerEndSetButton) {
- right: 78px;
- }
-}
-
-.deadlinePopover {
- position: absolute;
- left: 8px;
- bottom: 8px;
- display: flex;
- align-items: center;
- gap: var(--s-1);
-}
-
-.deadlinePopoverTrigger {
- margin-block-start: 0;
- background-color: var(--color-bg-higher) !important;
-}
-
-.deadlineIndicator {
- display: flex;
- align-items: center;
- justify-content: center;
- width: var(--selector-size-sm);
- height: var(--selector-size-sm);
- border-radius: 50%;
- font-weight: var(--weight-extra);
- font-size: var(--font-xs);
-}
-
-.deadlineIndicatorWarning {
- background-color: var(--color-warning);
- color: var(--color-text-inverse);
-}
-
-.deadlineIndicatorError {
- background-color: var(--color-error);
- color: var(--color-text-inverse);
-}
-
-.stageBannerTopBarHeader {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: var(--s-2);
-}
-
-.stageBannerTopBarMapTextBig {
- display: none;
-}
-
-@container (width >= 480px) {
- .stageBannerTopBar {
- font-size: initial;
- }
-
- .stageBannerTopBarMapTextSmall {
- display: none;
- }
-
- .stageBannerTopBarMapTextBig {
- display: initial;
- }
-}
-
-.noScreen {
- display: flex;
- gap: var(--s-0-5);
-
- & > svg {
- width: 24px;
- color: var(--color-success);
- }
-}
-
-.noScreenBanned {
- & > svg {
- color: var(--color-error);
- }
-}
-
-.duringMatchActions {
- display: grid;
- grid-template-areas:
- "img"
- "infos"
- "rosters";
- grid-template-columns: 1fr;
-}
-
-.duringMatchActionsActions {
- display: flex;
- justify-content: center;
- color: var(--color-warning);
- margin-block-start: var(--s-6);
-}
-
-.duringMatchActionsAmountWarningParagraph {
- display: flex;
- align-items: center;
- text-align: center;
- font-size: var(--font-xs);
-}
-
-.duringMatchActionsRosters {
- display: flex;
- width: 100%;
- flex-wrap: wrap;
- justify-content: space-evenly;
- row-gap: var(--s-4);
- text-align: center;
-}
-
-.duringMatchActionsRadioContainer {
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.rosterButtonsContainer {
- display: flex;
- gap: var(--s-4);
- align-items: center;
- justify-content: center;
- margin-block-start: var(--s-6);
- height: 30px;
-}
-
-.editRosterButton {
- font-style: italic;
-}
-
-.duringMatchActionsTeamPlayers {
- display: flex;
- width: 15rem;
- flex-direction: column;
- margin-top: var(--s-3);
- margin-inline: auto;
- gap: var(--s-2);
-}
-
-.duringMatchActionsCheckboxName {
- display: flex;
- align-items: center;
- flex: 1;
-
- &:not(:global(.disabled-opaque)):not(:global(.presentational)):hover {
- border-radius: var(--radius-box);
- cursor: pointer;
- outline: 2px solid var(--color-accent-high);
- outline-offset: 2px;
- }
-}
-
-.duringMatchActionsCheckbox:not(#_) {
- background: none;
- all: unset;
- width: 40% !important;
- height: 1.5rem !important;
- appearance: none;
- background-color: var(--color-bg-higher);
- border-end-start-radius: var(--radius-box);
- border-start-start-radius: var(--radius-box);
- cursor: pointer;
- font-size: var(--font-2xs);
- font-weight: var(--weight-bold);
- letter-spacing: 0.4px;
- z-index: 1;
-
- &::after {
- all: unset;
- display: flex;
- width: 100%;
- height: 100%;
- align-items: center;
- justify-content: center;
- color: var(--color-text-high);
- content: "Inactive";
- padding-block-end: 1px;
- }
-
- &:checked {
- background-color: var(--color-text-accent);
-
- &::after {
- display: flex;
- color: var(--color-text-inverse);
- content: "Playing";
- }
- }
-
- &:disabled {
- pointer-events: none;
- cursor: not-allowed;
- opacity: 0.5;
- outline: none;
- }
-
- &:focus-within {
- outline: none;
- }
-
- &:focus-visible {
- outline: var(--focus-ring);
- outline-offset: 1px;
- }
-}
-
-.duringMatchActionsPlayerName {
- display: flex;
- width: 60%;
- height: 1.5rem;
- align-items: center;
- justify-content: center;
- margin: 0;
- background-color: var(--color-bg);
- border-end-end-radius: var(--radius-box);
- border-start-end-radius: var(--radius-box);
- overflow-x: hidden;
- text-overflow: ellipsis;
- user-select: none;
- cursor: pointer;
- flex: 1;
-}
-
-.duringMatchActionsPlayerNameInner {
- white-space: nowrap;
- overflow-x: hidden;
- text-overflow: ellipsis;
- padding-inline: var(--s-2);
- max-width: 150px;
-}
-
-.duringMatchActionsSeed {
- font-size: var(--font-2xs);
- color: var(--color-text-accent);
-}
-
-.duringMatchActionsTeamName {
- color: var(--color-text);
- font-weight: var(--weight-bold);
-}
-
-.rosters {
- display: flex;
- flex-wrap: wrap;
- gap: var(--s-8);
- font-size: var(--font-xs);
- font-weight: var(--weight-semi);
- flex-direction: column;
-
- & ul {
- padding: 0;
- list-style: none;
- }
-}
-
-.rostersSpacedHeader {
- min-height: 45px;
- display: flex;
- align-items: center;
-}
-
-.inactivePlayer {
- color: var(--color-text-high);
- text-decoration: line-through;
-}
-
-@container (width >= 640px) {
- .rosters {
- justify-content: space-evenly;
- flex-direction: row;
- }
-}
-
-.modeProgress {
- display: block;
- text-align: center;
- overflow-x: auto;
- padding: 2px;
- margin-bottom: var(--s-1-5);
-}
-
-.modeProgressInner {
- display: inline-flex;
- align-items: center;
- gap: var(--s-4);
- justify-content: flex-start;
-}
-
-.modeProgressImage {
- background-color: var(--color-bg-high);
- border-radius: 100%;
- padding: var(--s-3);
- outline: 2px solid var(--color-bg-higher);
- min-width: 40px;
-
- & > svg {
- width: 20px;
- height: 20px;
- }
-}
-
-.modeProgressImageNotable {
- background-color: var(--color-bg-higher);
- border: none;
- outline: 2px solid transparent;
-}
-
-.modeProgressImageTeamOneWin {
- outline: 2px solid var(--color-accent);
-}
-
-.modeProgressImageTeamTwoWin {
- outline: 2px solid var(--color-second);
-}
-
-.modeProgressImageSelected {
- background-color: var(--color-bg-high);
-}
-
-.modeProgressImageBanned {
- outline: 2px solid var(--color-error);
- background-color: var(--color-bg-high);
-
- & > img {
- filter: grayscale(100%);
- }
-}
-
-.modeProgressImageBannedPopoverTrigger:focus-visible {
- outline: none !important;
-}
-
-.teamOneDot {
- border-radius: 100%;
- background-color: var(--color-accent);
- width: 8px;
- height: 8px;
-}
-
-.teamTwoDot {
- border-radius: 100%;
- background-color: var(--color-second);
- width: 8px;
- height: 8px;
-}
-
-.pointsInput {
- padding: var(--s-4) var(--s-2) !important;
- font-size: var(--font-sm);
-}
-
-@container (width >= 480px) {
- .infos {
- flex-direction: row;
- }
-}
-
-.quickAction {
- font-size: var(--font-xs);
- color: var(--color-text);
- background-color: var(--color-bg);
- border-radius: var(--radius-box);
- border: 2px solid var(--color-bg-higher);
- padding-block: var(--s-1-5);
- padding-inline: var(--s-3);
- font-weight: var(--weight-bold);
- align-items: center;
-}
-
-.quickActionSpaced {
- display: flex;
- gap: var(--s-1-5);
-}
-
-.quickActionVerySpaced {
- display: flex;
- gap: var(--s-3);
-}
-
-.quickActionCheckmark {
- color: var(--color-success);
-}
-
-.compactifyButton {
- font-size: var(--font-2xs);
- color: var(--color-text-high);
- border-color: var(--color-bg-higher);
- background-color: var(--color-bg);
- border-radius: var(--radius-box);
-}
-
-.castInfoContainer {
- display: flex;
- gap: var(--s-2);
- border-radius: var(--radius-box);
- background-color: var(--color-bg-high);
- width: max-content;
-}
-
-.castInfoContainerLabel {
- padding: var(--s-2) var(--s-4);
- text-transform: uppercase;
- background-color: var(--color-bg-higher);
- border-radius: var(--radius-box) 0 0 var(--radius-box);
- font-weight: var(--weight-bold);
- color: var(--color-text-high);
- display: grid;
- place-items: center;
- justify-content: center;
-}
-
-.castInfoContainerContent {
- padding-block: var(--s-2);
- display: flex;
- align-items: center;
-}
-
-.streamPopover {
- width: 280px;
-}
-
-.vodGrid {
- display: grid;
- grid-template-columns: auto 1fr auto;
- gap: var(--s-1) var(--s-2);
- align-items: center;
- font-size: var(--font-xs);
- white-space: nowrap;
-}
-
-.vodGrid a {
- display: grid;
- grid-template-columns: subgrid;
- grid-column: 1 / -1;
- align-items: center;
-}
-
-.vodUser {
- display: flex;
- align-items: center;
- gap: var(--s-2);
-}
-
-.vodTeamName {
- overflow: hidden;
- text-overflow: ellipsis;
- min-width: 0;
-}
-
-.actionSectionWrapper {
- & [class*="tabPanel"] {
- background-color: var(--color-bg-high);
- border-radius: 0 0 var(--radius-box) var(--radius-box);
- padding: var(--s-4);
-
- &:has([class*="chatContainer"]) {
- padding: var(--s-1) var(--s-4);
- }
- }
-}
-
-.bracketNavLink {
- font-size: var(--font-2xs);
- color: var(--color-text-high);
- border-color: var(--color-bg-higher);
- background-color: var(--color-bg);
- border-radius: 0;
-
- &:active {
- transform: translateY(0px);
- }
-
- &:first-of-type {
- border-start-start-radius: var(--radius-box);
- border-end-start-radius: var(--radius-box);
- }
-
- &:not(&:first-of-type) {
- margin-left: -2px;
- }
-
- &:last-of-type {
- border-start-end-radius: var(--radius-box);
- border-end-end-radius: var(--radius-box);
- }
-}
-
-.bracketNavLinkBig {
- font-size: var(--font-lg);
-}
-
-.bracketNavLinkSelected {
- color: var(--color-text);
- background-color: var(--color-bg-high);
-}
diff --git a/app/features/tournament-match/components/TournamentMatchBanner.tsx b/app/features/tournament-match/components/TournamentMatchBanner.tsx
index 0cffed821..449394c6a 100644
--- a/app/features/tournament-match/components/TournamentMatchBanner.tsx
+++ b/app/features/tournament-match/components/TournamentMatchBanner.tsx
@@ -10,15 +10,14 @@ import {
} from "lucide-react";
import { useTranslation } from "react-i18next";
import { Avatar } from "~/components/Avatar";
-import { SendouButton } from "~/components/elements/Button";
import { SendouPopover } from "~/components/elements/Popover";
import {
IconBanner,
MatchBanner,
MatchBannerContainer,
+ MatchBannerInfoBadge,
MultiMatchBanner,
} from "~/components/match-page/MatchBanner";
-import bannerStyles from "~/components/match-page/MatchBanner.module.css";
import { MatchBannerBottomRow } from "~/components/match-page/MatchBannerBottomRow";
import { MatchBannerStartedAt } from "~/components/match-page/MatchBannerStartedAt";
import { MatchBannerTimer } from "~/components/match-page/MatchBannerTimer";
@@ -318,7 +317,7 @@ function CurrentMapPickInfo({
return (
+
{teams.map((team) => (
))}
-
+
}
>
{text}
diff --git a/app/features/tournament-organization/components/EventCalendar.module.css b/app/features/tournament-organization/components/EventCalendar.module.css
new file mode 100644
index 000000000..ccdf9cd1f
--- /dev/null
+++ b/app/features/tournament-organization/components/EventCalendar.module.css
@@ -0,0 +1,101 @@
+.calendar {
+ display: grid;
+ grid-template-columns: repeat(7, minmax(0, 55px));
+ gap: var(--s-2);
+ width: max-content;
+ max-width: 100%;
+}
+
+.calendarContainer {
+ display: flex;
+ flex-direction: column;
+ gap: var(--s-2);
+ height: max-content;
+ overflow-x: visible;
+ max-width: 100%;
+}
+
+.calendarDayHeader {
+ font-size: var(--font-md);
+ font-weight: var(--weight-semi);
+}
+
+.calendarDay {
+ aspect-ratio: 1;
+ font-size: var(--font-xs);
+ font-weight: var(--weight-semi);
+ background-color: var(--color-bg-high);
+ padding: var(--s-1);
+ position: relative;
+ display: grid;
+ place-items: center;
+}
+
+.calendarDayDate {
+ position: absolute;
+ top: 1px;
+ left: 6px;
+}
+
+.calendarDayLogo {
+ position: absolute;
+ border-radius: var(--radius-box);
+ top: 50%;
+ left: 50%;
+ transform: translate(-15%, -20%);
+ width: 48%;
+ height: auto;
+ max-width: 28px;
+ max-height: 28px;
+}
+
+.calendarDayManyEvents {
+ position: absolute;
+ border-radius: var(--radius-box);
+ top: 50%;
+ left: 50%;
+ transform: translate(-15%, -20%);
+ width: 48%;
+ height: auto;
+ aspect-ratio: 1;
+ max-width: 28px;
+ max-height: 28px;
+ border: var(--border-style);
+ display: grid;
+ place-items: center;
+ font-size: var(--font-md);
+ background-color: var(--color-bg-higher);
+}
+
+.calendarDayToday {
+ color: var(--color-text-accent);
+ font-weight: var(--weight-bold);
+}
+
+.calendarDayPrevious {
+ background-color: var(--color-bg);
+}
+
+.calendarMonthSelector {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--s-2);
+ font-weight: var(--weight-bold);
+
+ & a {
+ font-size: var(--font-xl);
+ }
+
+ & > div {
+ font-size: var(--font-lg);
+ margin-block-start: var(--s-1);
+ }
+}
+
+@container (width >= 720px) {
+ .calendarContainer {
+ position: sticky;
+ top: 47px;
+ }
+}
diff --git a/app/features/tournament-organization/components/EventCalendar.tsx b/app/features/tournament-organization/components/EventCalendar.tsx
index ea64f70f8..830621579 100644
--- a/app/features/tournament-organization/components/EventCalendar.tsx
+++ b/app/features/tournament-organization/components/EventCalendar.tsx
@@ -6,8 +6,8 @@ import { useHydrated } from "~/hooks/useHydrated";
import { databaseTimestampToDate, nullPaddedDatesOfMonth } from "~/utils/dates";
import type { SerializeFrom } from "~/utils/remix";
import type { loader } from "../loaders/org.$slug.server";
-import styles from "../tournament-organization.module.css";
import { tournamentOrganizationSearchParams } from "../tournament-organization-search-params";
+import styles from "./EventCalendar.module.css";
interface EventCalendarProps {
month: number;
diff --git a/app/features/tournament-organization/components/SocialLinksList.module.css b/app/features/tournament-organization/components/SocialLinksList.module.css
new file mode 100644
index 000000000..afca26a79
--- /dev/null
+++ b/app/features/tournament-organization/components/SocialLinksList.module.css
@@ -0,0 +1,38 @@
+.socialLink {
+ font-size: var(--font-sm);
+ color: var(--text-main);
+ display: flex;
+ gap: var(--s-2);
+ align-items: center;
+ max-width: max-content;
+
+ & svg {
+ width: 18px;
+ }
+}
+
+.socialLinkIconContainer {
+ background-color: var(--color-bg-higher);
+ display: grid;
+ place-items: center;
+ border-radius: var(--radius-box);
+ padding: var(--s-2);
+}
+
+.socialLinkTwitch {
+ & svg {
+ fill: #9146ff;
+ }
+}
+
+.socialLinkYoutube {
+ & svg {
+ fill: #f00;
+ }
+}
+
+.socialLinkBsky {
+ & path {
+ fill: #1285fe;
+ }
+}
diff --git a/app/features/tournament-organization/components/SocialLinksList.tsx b/app/features/tournament-organization/components/SocialLinksList.tsx
index c1c97c1f7..a320be6d2 100644
--- a/app/features/tournament-organization/components/SocialLinksList.tsx
+++ b/app/features/tournament-organization/components/SocialLinksList.tsx
@@ -3,7 +3,7 @@ import { Link } from "lucide-react";
import { BskyIcon } from "~/components/icons/Bsky";
import { TwitchIcon } from "~/components/icons/Twitch";
import { YouTubeIcon } from "~/components/icons/YouTube";
-import styles from "../tournament-organization.module.css";
+import styles from "./SocialLinksList.module.css";
export function SocialLinksList({ links }: { links: string[] }) {
return (
diff --git a/app/features/tournament-organization/routes/org.$slug.module.css b/app/features/tournament-organization/routes/org.$slug.module.css
new file mode 100644
index 000000000..f9a97738e
--- /dev/null
+++ b/app/features/tournament-organization/routes/org.$slug.module.css
@@ -0,0 +1,83 @@
+.sectionDivider {
+ font-size: var(--font-md);
+ font-weight: var(--weight-bold);
+ color: var(--color-text-high);
+ border-bottom: var(--border-style);
+ width: 100%;
+ padding-block: var(--s-1);
+}
+
+.eventsContainer {
+ display: flex;
+ flex-direction: column;
+ gap: var(--s-16);
+ align-items: center;
+}
+
+@container (width >= 720px) {
+ .eventsContainer {
+ flex-direction: row;
+ gap: var(--s-8);
+ overflow-x: initial;
+ align-items: flex-start;
+ }
+}
+
+.eventInfo {
+ display: flex;
+ align-items: center;
+ gap: var(--s-2);
+ font-weight: var(--weight-semi);
+ color: var(--color-text);
+ width: max-content;
+
+ & img {
+ border-radius: var(--radius-avatar);
+ }
+}
+
+.eventInfoTime {
+ display: block;
+ color: var(--color-text-high);
+ font-size: var(--font-sm);
+}
+
+.leaderboardList {
+ display: flex;
+ gap: var(--s-4);
+ flex-direction: column;
+
+ & li::marker {
+ font-size: var(--font-lg);
+ font-weight: var(--weight-bold);
+ color: var(--color-accent);
+ padding-inline-end: var(--s-2);
+ }
+}
+
+.leaderboardListRow {
+ display: flex;
+ flex-direction: column;
+ gap: var(--s-2);
+ padding-inline-start: var(--s-3);
+}
+
+.trophyGridButton {
+ all: unset;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.trophyModalTournaments {
+ display: flex;
+ flex-direction: column;
+ gap: var(--s-2);
+ opacity: 1;
+ transition: opacity 0.2s ease;
+
+ @starting-style {
+ opacity: 0;
+ }
+}
diff --git a/app/features/tournament-organization/routes/org.$slug.tsx b/app/features/tournament-organization/routes/org.$slug.tsx
index 056a848af..f6acf32cf 100644
--- a/app/features/tournament-organization/routes/org.$slug.tsx
+++ b/app/features/tournament-organization/routes/org.$slug.tsx
@@ -63,10 +63,10 @@ import { action } from "../actions/org.$slug.server";
import { EventCalendar } from "../components/EventCalendar";
import { SocialLinksList } from "../components/SocialLinksList";
import { loader } from "../loaders/org.$slug.server";
-import styles from "../tournament-organization.module.css";
import { TOURNAMENT_SERIES_EVENTS_PER_PAGE } from "../tournament-organization-constants";
import { updateIsEstablishedSchema } from "../tournament-organization-schemas";
import { tournamentOrganizationSearchParams } from "../tournament-organization-search-params";
+import styles from "./org.$slug.module.css";
export { action, loader };
diff --git a/app/features/tournament-organization/tournament-organization.module.css b/app/features/tournament-organization/tournament-organization.module.css
deleted file mode 100644
index 845bd7e0c..000000000
--- a/app/features/tournament-organization/tournament-organization.module.css
+++ /dev/null
@@ -1,222 +0,0 @@
-.calendar {
- display: grid;
- grid-template-columns: repeat(7, minmax(0, 55px));
- gap: var(--s-2);
- width: max-content;
- max-width: 100%;
-}
-
-.calendarContainer {
- display: flex;
- flex-direction: column;
- gap: var(--s-2);
- height: max-content;
- overflow-x: visible;
- max-width: 100%;
-}
-
-.calendarDayHeader {
- font-size: var(--font-md);
- font-weight: var(--weight-semi);
-}
-
-.calendarDay {
- aspect-ratio: 1;
- font-size: var(--font-xs);
- font-weight: var(--weight-semi);
- background-color: var(--color-bg-high);
- padding: var(--s-1);
- position: relative;
- display: grid;
- place-items: center;
-}
-
-.calendarDayDate {
- position: absolute;
- top: 1px;
- left: 6px;
-}
-
-.calendarDayLogo {
- position: absolute;
- border-radius: var(--radius-box);
- top: 50%;
- left: 50%;
- transform: translate(-15%, -20%);
- width: 48%;
- height: auto;
- max-width: 28px;
- max-height: 28px;
-}
-
-.calendarDayManyEvents {
- position: absolute;
- border-radius: var(--radius-box);
- top: 50%;
- left: 50%;
- transform: translate(-15%, -20%);
- width: 48%;
- height: auto;
- aspect-ratio: 1;
- max-width: 28px;
- max-height: 28px;
- border: var(--border-style);
- display: grid;
- place-items: center;
- font-size: var(--font-md);
- background-color: var(--color-bg-higher);
-}
-
-.calendarDayToday {
- color: var(--color-text-accent);
- font-weight: var(--weight-bold);
-}
-
-.calendarDayPrevious {
- background-color: var(--color-bg);
-}
-
-.calendarMonthSelector {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: var(--s-2);
- font-weight: var(--weight-bold);
-
- & a {
- font-size: var(--font-xl);
- }
-
- & > div {
- font-size: var(--font-lg);
- margin-block-start: var(--s-1);
- }
-}
-
-.sectionDivider {
- font-size: var(--font-md);
- font-weight: var(--weight-bold);
- color: var(--color-text-high);
- border-bottom: var(--border-style);
- width: 100%;
- padding-block: var(--s-1);
-}
-
-.eventsContainer {
- display: flex;
- flex-direction: column;
- gap: var(--s-16);
- align-items: center;
-}
-
-@container (width >= 720px) {
- .eventsContainer {
- flex-direction: row;
- gap: var(--s-8);
- overflow-x: initial;
- align-items: flex-start;
- }
-
- .calendarContainer {
- position: sticky;
- top: 47px;
- }
-}
-
-.eventInfo {
- display: flex;
- align-items: center;
- gap: var(--s-2);
- font-weight: var(--weight-semi);
- color: var(--color-text);
- width: max-content;
-
- & img {
- border-radius: var(--radius-avatar);
- }
-}
-
-.eventInfoTime {
- display: block;
- color: var(--color-text-high);
- font-size: var(--font-sm);
-}
-
-.leaderboardList {
- display: flex;
- gap: var(--s-4);
- flex-direction: column;
-
- & li::marker {
- font-size: var(--font-lg);
- font-weight: var(--weight-bold);
- color: var(--color-accent);
- padding-inline-end: var(--s-2);
- }
-}
-
-.leaderboardListRow {
- display: flex;
- flex-direction: column;
- gap: var(--s-2);
- padding-inline-start: var(--s-3);
-}
-
-.socialLink {
- font-size: var(--font-sm);
- color: var(--text-main);
- display: flex;
- gap: var(--s-2);
- align-items: center;
- max-width: max-content;
-
- & svg {
- width: 18px;
- }
-}
-
-.socialLinkIconContainer {
- background-color: var(--color-bg-higher);
- display: grid;
- place-items: center;
- border-radius: var(--radius-box);
- padding: var(--s-2);
-}
-
-.socialLinkTwitch {
- & svg {
- fill: #9146ff;
- }
-}
-
-.socialLinkYoutube {
- & svg {
- fill: #f00;
- }
-}
-
-.socialLinkBsky {
- & path {
- fill: #1285fe;
- }
-}
-
-.trophyGridButton {
- all: unset;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.trophyModalTournaments {
- display: flex;
- flex-direction: column;
- gap: var(--s-2);
- opacity: 1;
- transition: opacity 0.2s ease;
-
- @starting-style {
- opacity: 0;
- }
-}
diff --git a/app/features/tournament/components/MarkdownSection.module.css b/app/features/tournament/components/MarkdownSection.module.css
new file mode 100644
index 000000000..9a8aab3e3
--- /dev/null
+++ b/app/features/tournament/components/MarkdownSection.module.css
@@ -0,0 +1,27 @@
+.section {
+ white-space: pre-wrap;
+
+ & > :is(h1, h2, h3, h4, h5, h6) {
+ margin-block-end: var(--s-4);
+ }
+
+ & > :is(h2, h3, h4, h5, h6) {
+ margin-block-start: var(--s-6);
+ }
+
+ & > :first-child {
+ margin-block-start: 0;
+ }
+
+ & > h1 {
+ font-size: var(--font-xl);
+ }
+
+ & > :is(h2, h3, h4, h5, h6) {
+ font-size: var(--font-lg);
+ }
+
+ & > :is(h3, h4, h5, h6) {
+ font-size: var(--font-md);
+ }
+}
diff --git a/app/features/tournament/components/MarkdownSection.tsx b/app/features/tournament/components/MarkdownSection.tsx
new file mode 100644
index 000000000..a955fe8e2
--- /dev/null
+++ b/app/features/tournament/components/MarkdownSection.tsx
@@ -0,0 +1,11 @@
+import { Markdown } from "~/components/Markdown";
+import styles from "./MarkdownSection.module.css";
+
+/** Organizer-authored markdown (tournament description, rules) as prose. */
+export function MarkdownSection({ children }: { children: string }) {
+ return (
+
+ );
+}
diff --git a/app/features/tournament/components/TeamWithRoster.module.css b/app/features/tournament/components/TeamWithRoster.module.css
new file mode 100644
index 000000000..78ec8ffb1
--- /dev/null
+++ b/app/features/tournament/components/TeamWithRoster.module.css
@@ -0,0 +1,117 @@
+.teamWithRoster {
+ display: flex;
+ width: 100%;
+ align-items: center;
+}
+
+.teamWithRosterName {
+ flex: 1;
+ min-width: 0;
+ font-weight: var(--weight-bold);
+ padding-inline-end: var(--s-4);
+ text-align: right;
+}
+
+.teamWithRosterMembers {
+ display: flex;
+ flex: 1;
+ min-width: 0;
+ flex-direction: column;
+ border-inline-start: var(--border-style-accent);
+ font-size: var(--font-xs);
+ font-weight: var(--weight-semi);
+ gap: var(--s-2);
+ padding-inline-start: var(--s-4);
+ padding-block: var(--s-3);
+}
+
+.teamWithRosterMember {
+ display: grid;
+ gap: var(--s-1-5);
+ grid-template-columns: max-content minmax(0, max-content);
+ align-items: center;
+}
+
+.teamWithRosterMemberInactive {
+ text-decoration: line-through;
+ color: var(--color-text-high);
+ text-decoration-thickness: 2px;
+}
+
+.teamWithRosterMemberAvatarInactive {
+ opacity: 0.4;
+}
+
+.teamWithRosterMapPool {
+ display: grid;
+ grid-template-columns: max-content max-content max-content max-content;
+ border-radius: var(--radius-box);
+ border: var(--border-style-accent);
+ width: max-content;
+ margin: 0 auto;
+ overflow: hidden;
+}
+
+.teamWithRosterMapPool3Columns {
+ grid-template-columns: max-content max-content max-content;
+}
+
+.teamWithRosterMapPoolModeInfo {
+ background-color: var(--color-bg-high);
+ display: flex;
+ font-size: var(--font-2xs);
+ justify-content: center;
+ align-items: center;
+ gap: var(--s-1);
+ font-weight: var(--weight-semi);
+ padding-block: var(--s-0-5);
+}
+
+.teamWithRosterSeed {
+ font-size: var(--font-2xs);
+ font-weight: var(--weight-semi);
+ color: var(--color-text-high);
+}
+
+.teamWithRosterTeamName {
+ word-break: break-word;
+ text-wrap: balance;
+ max-width: 250px;
+ display: block;
+ margin-inline-start: auto;
+}
+
+.teamMemberRow {
+ list-style: none;
+ position: relative;
+}
+
+.teamMemberName {
+ overflow: hidden;
+ color: var(--color-text);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ display: block;
+ padding-block: 1px;
+}
+
+.teamMemberNameRole {
+ position: absolute;
+ background-color: var(--color-text-accent);
+ color: var(--color-text-inverse);
+ width: 12px;
+ height: 12px;
+ border-radius: var(--radius-full);
+ font-size: var(--font-3xs);
+ font-weight: var(--weight-bold);
+ line-height: 1;
+ display: grid;
+ place-items: center;
+ top: 14px;
+ left: 14px;
+ z-index: 1;
+}
+
+.teamMemberNameRoleSub {
+ background-color: var(--color-warning-high);
+}
diff --git a/app/features/tournament/components/TeamWithRoster.tsx b/app/features/tournament/components/TeamWithRoster.tsx
index 720951faf..9bda39cd0 100644
--- a/app/features/tournament/components/TeamWithRoster.tsx
+++ b/app/features/tournament/components/TeamWithRoster.tsx
@@ -9,7 +9,7 @@ import type { TournamentTeamFull } from "~/features/tournament-bracket/core/Tour
import { userPage } from "~/utils/urls";
import { accountCreatedInTheLastSixMonths } from "~/utils/users";
import { useTournamentFriendCodes } from "../routes/to.$id";
-import styles from "../tournament.module.css";
+import styles from "./TeamWithRoster.module.css";
export function TeamWithRoster({
team,
diff --git a/app/features/tournament/components/TournamentStream.module.css b/app/features/tournament/components/TournamentStream.module.css
new file mode 100644
index 000000000..419fe78be
--- /dev/null
+++ b/app/features/tournament/components/TournamentStream.module.css
@@ -0,0 +1,33 @@
+.streamUserContainer {
+ font-size: var(--font-xs);
+ display: flex;
+ gap: var(--s-2);
+ align-items: center;
+ font-weight: var(--weight-semi);
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ min-width: 0;
+}
+
+.streamTeamName {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ min-width: 0;
+ max-width: 200px;
+}
+
+.streamViewerCount {
+ font-size: var(--font-xs);
+ display: flex;
+ gap: var(--s-2);
+ align-items: center;
+ margin-block-start: -5px;
+ color: var(--color-text-high);
+
+ & > svg {
+ width: 1rem;
+ min-width: 1rem;
+ }
+}
diff --git a/app/features/tournament/components/TournamentStream.tsx b/app/features/tournament/components/TournamentStream.tsx
index c178269da..eb4a8ebaf 100644
--- a/app/features/tournament/components/TournamentStream.tsx
+++ b/app/features/tournament/components/TournamentStream.tsx
@@ -5,7 +5,7 @@ import { useTournament } from "~/features/tournament/tournament-context";
import type { Tournament } from "~/features/tournament-bracket/core/Tournament";
import { twitchThumbnailUrlToSrc } from "~/modules/twitch/utils";
import { twitchUrl } from "~/utils/urls";
-import styles from "../tournament.module.css";
+import styles from "./TournamentStream.module.css";
export function TournamentStream({
stream,
diff --git a/app/features/tournament/routes/to.$id.info.module.css b/app/features/tournament/routes/to.$id.info.module.css
index 207c0df83..ee5d808a4 100644
--- a/app/features/tournament/routes/to.$id.info.module.css
+++ b/app/features/tournament/routes/to.$id.info.module.css
@@ -1,31 +1,3 @@
-.description {
- white-space: pre-wrap;
-
- & > :is(h1, h2, h3, h4, h5, h6) {
- margin-block-end: var(--s-4);
- }
-
- & > :is(h2, h3, h4, h5, h6) {
- margin-block-start: var(--s-6);
- }
-
- & > :first-child {
- margin-block-start: 0;
- }
-
- & > h1 {
- font-size: var(--font-xl);
- }
-
- & > :is(h2, h3, h4, h5, h6) {
- font-size: var(--font-lg);
- }
-
- & > :is(h3, h4, h5, h6) {
- font-size: var(--font-md);
- }
-}
-
.underground {
color: var(--color-text-high);
}
diff --git a/app/features/tournament/routes/to.$id.info.tsx b/app/features/tournament/routes/to.$id.info.tsx
index 118b7b5e8..8012892e9 100644
--- a/app/features/tournament/routes/to.$id.info.tsx
+++ b/app/features/tournament/routes/to.$id.info.tsx
@@ -4,7 +4,6 @@ import type { MetaFunction } from "react-router";
import { useLoaderData } from "react-router";
import { ModeImage } from "~/components/Image";
import { containerClassName } from "~/components/Main";
-import { Markdown } from "~/components/Markdown";
import { TierPill } from "~/components/TierPill";
import * as Seasons from "~/features/mmr/core/Seasons";
import { useTournament } from "~/features/tournament/tournament-context";
@@ -14,6 +13,7 @@ import { removeMarkdown } from "~/utils/strings";
import { tournamentPage } from "~/utils/urls";
import { action } from "../actions/to.$id.info.server";
import { FactCardGrid, type FactCardItem } from "../components/FactCard";
+import { MarkdownSection } from "../components/MarkdownSection";
import { RegistrationActions } from "../components/RegistrationActions";
import {
TournamentHeader,
@@ -68,9 +68,7 @@ export default function TournamentInfoPage() {
{data.description ? (
-
+ {data.description}
) : null}
);
diff --git a/app/features/tournament/routes/to.$id.join.module.css b/app/features/tournament/routes/to.$id.join.module.css
new file mode 100644
index 000000000..e4343e7a0
--- /dev/null
+++ b/app/features/tournament/routes/to.$id.join.module.css
@@ -0,0 +1,10 @@
+.inviteContainer {
+ display: flex;
+ flex-direction: column;
+ gap: var(--s-12);
+ align-items: center;
+}
+
+.joinAlert {
+ max-width: 22rem;
+}
diff --git a/app/features/tournament/routes/to.$id.join.tsx b/app/features/tournament/routes/to.$id.join.tsx
index d05730250..b0e177cc0 100644
--- a/app/features/tournament/routes/to.$id.join.tsx
+++ b/app/features/tournament/routes/to.$id.join.tsx
@@ -16,8 +16,8 @@ import {
} from "~/utils/urls";
import { action } from "../actions/to.$id.join.server";
import { loader } from "../loaders/to.$id.join.server";
-import styles from "../tournament.module.css";
import { validateCanJoinTeam } from "../tournament-utils";
+import styles from "./to.$id.join.module.css";
export { action, loader };
diff --git a/app/features/tournament/routes/to.$id.register.module.css b/app/features/tournament/routes/to.$id.register.module.css
new file mode 100644
index 000000000..04c9aef9d
--- /dev/null
+++ b/app/features/tournament/routes/to.$id.register.module.css
@@ -0,0 +1,60 @@
+.sectionHeader {
+ font-size: var(--font-sm);
+}
+
+.section {
+ background-color: var(--color-bg-high);
+ margin-inline: -12px;
+ padding: var(--s-4) var(--s-3);
+}
+
+.sectionInputContainer {
+ width: 16rem;
+}
+
+.sectionWarning {
+ font-size: var(--font-xs);
+ font-weight: var(--weight-semi);
+ text-align: center;
+ color: var(--color-text-high);
+}
+
+.rosterGrid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, 110px);
+ gap: var(--s-4);
+ margin-block-start: var(--s-2);
+ width: 100%;
+ justify-content: center;
+}
+
+.rosterGridMemberName {
+ max-width: 110px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.missingPlayer {
+ width: 62px;
+ height: 62px;
+ font-size: 32px;
+ border-radius: 100%;
+ border: var(--border-style-accent);
+ color: var(--color-text-accent);
+ display: grid;
+ place-items: center;
+ margin: 0 auto;
+}
+
+.missingPlayerOptional {
+ border: 2px dashed var(--color-text-accent);
+ color: var(--color-text-accent);
+}
+
+@container (width >= 640px) {
+ .section {
+ margin: 0;
+ border-radius: var(--radius-box);
+ }
+}
diff --git a/app/features/tournament/routes/to.$id.register.tsx b/app/features/tournament/routes/to.$id.register.tsx
index 61bb428ef..0f5984f0d 100644
--- a/app/features/tournament/routes/to.$id.register.tsx
+++ b/app/features/tournament/routes/to.$id.register.tsx
@@ -40,7 +40,6 @@ import {
import { action } from "../actions/to.$id.register.server";
import type { TournamentRegisterPageLoader } from "../loaders/to.$id.register.server";
import { loader } from "../loaders/to.$id.register.server";
-import styles from "../tournament.module.css";
import {
type RegisterTeamFormValues,
registerTeamFormSchema,
@@ -51,6 +50,7 @@ import {
deleteTeamMemberSchema,
updateMapPoolSchema,
} from "../tournament-schemas";
+import styles from "./to.$id.register.module.css";
export { action, loader };
@@ -345,15 +345,13 @@ function RegistrationProgress({
{step.name}
{step.status === "completed" ? (
) : step.status === "notice" ? (
-
+
) : (
-
+
)}
);
diff --git a/app/features/tournament/routes/to.$id.results.module.css b/app/features/tournament/routes/to.$id.results.module.css
new file mode 100644
index 000000000..d7150c4a6
--- /dev/null
+++ b/app/features/tournament/routes/to.$id.results.module.css
@@ -0,0 +1,54 @@
+.standingsMatchResultSquare {
+ width: 28px;
+ height: 28px;
+ display: grid;
+ place-items: center;
+ font-size: var(--font-xs);
+ font-weight: var(--weight-semi);
+ border: 3px solid var(--color-accent);
+ border-radius: var(--radius-field);
+ color: var(--color-text);
+}
+
+.standingsMatchResultSquareWin {
+ border-color: var(--color-success);
+}
+
+.standingsMatchResultSquareLoss {
+ border-color: var(--color-error);
+}
+
+.standingsRowAlt > td {
+ background-color: var(--color-bg-high);
+}
+
+.standingsTeamName {
+ min-width: 125px;
+ display: flex;
+ gap: var(--s-1-5);
+ align-items: center;
+ color: var(--color-text);
+}
+
+.standingsTeamNameText {
+ max-width: 16ch;
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ line-clamp: 2;
+ -webkit-box-orient: vertical;
+ word-break: break-word;
+}
+
+.standingsDivider {
+ width: 5px;
+ background-color: var(--color-border-high);
+ border-radius: var(--radius-box);
+}
+
+.spoilerRevealContainer {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: var(--s-12) 0;
+}
diff --git a/app/features/tournament/routes/to.$id.results.tsx b/app/features/tournament/routes/to.$id.results.tsx
index c7f89f82f..3006aaf03 100644
--- a/app/features/tournament/routes/to.$id.results.tsx
+++ b/app/features/tournament/routes/to.$id.results.tsx
@@ -24,8 +24,8 @@ import {
tournamentTeamPage,
} from "~/utils/urls";
import type { TournamentResultsLoaderData } from "../loaders/to.$id.results.server";
-import styles from "../tournament.module.css";
import { TOURNAMENT } from "../tournament-constants";
+import styles from "./to.$id.results.module.css";
export { loader } from "../loaders/to.$id.results.server";
diff --git a/app/features/tournament/routes/to.$id.rules.tsx b/app/features/tournament/routes/to.$id.rules.tsx
index ab14439d8..68121c6bd 100644
--- a/app/features/tournament/routes/to.$id.rules.tsx
+++ b/app/features/tournament/routes/to.$id.rules.tsx
@@ -5,7 +5,6 @@ import { LinkButton } from "~/components/elements/Button";
import { Image } from "~/components/Image";
import { containerClassName } from "~/components/Main";
import { MapPoolStages } from "~/components/MapPoolSelector";
-import { Markdown } from "~/components/Markdown";
import { Section } from "~/components/Section";
import { MapPool } from "~/features/map-list-generator/core/map-pool";
import { mapsPageWithMapPool } from "~/features/map-list-generator/map-list-generator-urls";
@@ -13,8 +12,8 @@ import { useTournament } from "~/features/tournament/tournament-context";
import { modesShort } from "~/modules/in-game-lists/modes";
import type { SendouRouteHandle } from "~/utils/remix.server";
import { navIconUrl } from "~/utils/urls";
+import { MarkdownSection } from "../components/MarkdownSection";
import { loader } from "../loaders/to.$id.rules.server";
-import styles from "./to.$id.info.module.css";
export { loader };
@@ -27,11 +26,7 @@ export default function TournamentRulesPage() {
return (
- {rules ? (
-
- ) : null}
+ {rules ? {rules} : null}
diff --git a/app/features/tournament/routes/to.$id.streams.module.css b/app/features/tournament/routes/to.$id.streams.module.css
new file mode 100644
index 000000000..a9fbe84fc
--- /dev/null
+++ b/app/features/tournament/routes/to.$id.streams.module.css
@@ -0,0 +1,6 @@
+.streamsGrid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, 320px);
+ gap: var(--s-6);
+ justify-content: center;
+}
diff --git a/app/features/tournament/routes/to.$id.streams.tsx b/app/features/tournament/routes/to.$id.streams.tsx
index 4f857265a..594ea3d02 100644
--- a/app/features/tournament/routes/to.$id.streams.tsx
+++ b/app/features/tournament/routes/to.$id.streams.tsx
@@ -5,7 +5,7 @@ import { useTournament } from "~/features/tournament/tournament-context";
import { tournamentRegisterPage } from "~/utils/urls";
import { TournamentStream } from "../components/TournamentStream";
import type { TournamentStreamsLoaderData } from "../loaders/to.$id.streams.server";
-import styles from "../tournament.module.css";
+import styles from "./to.$id.streams.module.css";
export { loader } from "../loaders/to.$id.streams.server";
diff --git a/app/features/tournament/routes/to.$id.teams.$tid.module.css b/app/features/tournament/routes/to.$id.teams.$tid.module.css
new file mode 100644
index 000000000..94c957b72
--- /dev/null
+++ b/app/features/tournament/routes/to.$id.teams.$tid.module.css
@@ -0,0 +1,159 @@
+.teamStats {
+ border-radius: var(--radius-box);
+ padding: var(--s-4);
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: var(--s-4);
+ margin: 0 auto;
+}
+
+.teamStat {
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-template-rows: 1fr 2fr 1fr;
+ place-items: center;
+ font-weight: var(--weight-bold);
+ text-align: center;
+}
+
+.teamStatTitle {
+ font-size: var(--font-sm);
+ color: var(--color-text-high);
+}
+
+.teamStatMain {
+ font-size: var(--font-xl);
+ font-weight: var(--weight-semi);
+
+ & sup {
+ font-size: var(--font-sm);
+ font-weight: var(--weight-semi);
+ }
+}
+
+.teamStatSub {
+ color: var(--color-text-high);
+ font-size: var(--font-sm);
+}
+
+@container (width >= 640px) {
+ .teamStats {
+ grid-template-columns: 1fr 1fr 1fr 1fr;
+ }
+}
+
+.teamSets {
+ display: flex;
+ flex-direction: column;
+ max-width: 32rem;
+ margin: 0 auto;
+ gap: var(--s-8);
+}
+
+.teamSet {
+ display: flex;
+ flex-direction: column;
+ gap: var(--s-3);
+ background-color: var(--color-bg-high);
+ border-radius: var(--radius-box);
+ padding: var(--s-3) var(--s-6);
+}
+
+.teamSetTopContainer {
+ display: flex;
+ justify-content: center;
+ gap: var(--s-2);
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+}
+
+@container (width >= 480px) {
+ .teamSetTopContainer {
+ flex-direction: row;
+ align-items: flex-end;
+ }
+}
+
+.teamSetScore {
+ font-size: var(--font-xl);
+ font-weight: var(--weight-bold);
+ white-space: nowrap;
+}
+
+.teamSetRoundName {
+ font-size: var(--font-lg);
+ font-weight: var(--weight-semi);
+ color: var(--color-text-high);
+ margin-block-end: 2px;
+}
+
+.teamSetMode {
+ background-color: var(--color-bg-high);
+ border-radius: 100%;
+ padding: var(--s-2);
+ border: 2px solid var(--color-success);
+}
+
+.teamSetModeLoss {
+ border-color: var(--color-error);
+}
+
+.teamSetStageContainer {
+ display: flex;
+ flex-direction: column;
+ gap: var(--s-2);
+ align-items: center;
+ font-size: var(--font-xs);
+ font-weight: var(--weight-semi);
+ color: var(--color-text);
+}
+
+.teamSetOpponent {
+ display: grid;
+ grid-template-areas: "vs team" "vs members";
+ grid-template-columns: max-content 1fr;
+ column-gap: var(--s-4);
+ row-gap: var(--s-2);
+}
+
+.teamSetOpponentVs {
+ grid-area: vs;
+ font-size: var(--font-xl);
+ font-weight: var(--weight-bold);
+ color: var(--color-text-high);
+ align-self: center;
+}
+
+.teamSetOpponentTeam {
+ grid-area: team;
+ font-size: var(--font-lg);
+ font-weight: var(--weight-semi);
+ color: var(--color-text);
+}
+
+.teamSetOpponentMembers {
+ grid-area: members;
+ display: flex;
+ gap: var(--s-2);
+ flex-wrap: wrap;
+}
+
+.teamSetOpponentMember {
+ font-size: var(--font-xs);
+}
+
+.overlapDivider {
+ display: flex;
+ width: 100%;
+ align-items: center;
+ color: var(--color-accent);
+ font-size: var(--font-lg);
+
+ &::before,
+ &::after {
+ flex: 1;
+ border-bottom: 2px solid var(--color-bg-higher);
+ content: "";
+ }
+}
diff --git a/app/features/tournament/routes/to.$id.teams.$tid.tsx b/app/features/tournament/routes/to.$id.teams.$tid.tsx
index 2891f9c16..1598a0955 100644
--- a/app/features/tournament/routes/to.$id.teams.$tid.tsx
+++ b/app/features/tournament/routes/to.$id.teams.$tid.tsx
@@ -27,7 +27,7 @@ import {
loader,
type TournamentTeamLoaderData,
} from "../loaders/to.$id.teams.$tid.server";
-import styles from "../tournament.module.css";
+import styles from "./to.$id.teams.$tid.module.css";
export { loader };
diff --git a/app/features/tournament/tournament.module.css b/app/features/tournament/tournament.module.css
deleted file mode 100644
index 9f0abf606..000000000
--- a/app/features/tournament/tournament.module.css
+++ /dev/null
@@ -1,540 +0,0 @@
-.selectContainer > label {
- margin-left: var(--s-3);
-}
-
-.teamWithRoster {
- display: flex;
- width: 100%;
- align-items: center;
-}
-
-.teamWithRosterName {
- flex: 1;
- min-width: 0;
- font-weight: var(--weight-bold);
- padding-inline-end: var(--s-4);
- text-align: right;
-}
-
-.teamWithRosterMembers {
- display: flex;
- flex: 1;
- min-width: 0;
- flex-direction: column;
- border-inline-start: var(--border-style-accent);
- font-size: var(--font-xs);
- font-weight: var(--weight-semi);
- gap: var(--s-2);
- padding-inline-start: var(--s-4);
- padding-block: var(--s-3);
-}
-
-.teamWithRosterMember {
- display: grid;
- gap: var(--s-1-5);
- grid-template-columns: max-content minmax(0, max-content);
- align-items: center;
-}
-
-.teamWithRosterMemberInactive {
- text-decoration: line-through;
- color: var(--color-text-high);
- text-decoration-thickness: 2px;
-}
-
-.teamWithRosterMemberAvatarInactive {
- opacity: 0.4;
-}
-
-.teamWithRosterMapPool {
- display: grid;
- grid-template-columns: max-content max-content max-content max-content;
- border-radius: var(--radius-box);
- border: var(--border-style-accent);
- width: max-content;
- margin: 0 auto;
- overflow: hidden;
-}
-
-.teamWithRosterMapPool3Columns {
- grid-template-columns: max-content max-content max-content;
-}
-
-.teamWithRosterMapPoolModeInfo {
- background-color: var(--color-bg-high);
- display: flex;
- font-size: var(--font-2xs);
- justify-content: center;
- align-items: center;
- gap: var(--s-1);
- font-weight: var(--weight-semi);
- padding-block: var(--s-0-5);
-}
-
-.teamWithRosterSeed {
- font-size: var(--font-2xs);
- font-weight: var(--weight-semi);
- color: var(--color-text-high);
-}
-
-.teamWithRosterTeamName {
- word-break: break-word;
- text-wrap: balance;
- max-width: 250px;
- display: block;
- margin-inline-start: auto;
-}
-
-.teamMemberRow {
- list-style: none;
- position: relative;
-}
-
-.teamMemberName {
- overflow: hidden;
- color: var(--color-text);
- text-overflow: ellipsis;
- white-space: nowrap;
- display: block;
- padding-block: 1px;
-}
-
-.teamMemberNameRole {
- position: absolute;
- background-color: var(--color-text-accent);
- color: var(--color-text-inverse);
- width: 12px;
- height: 12px;
- border-radius: var(--radius-full);
- font-size: var(--font-3xs);
- font-weight: var(--weight-bold);
- line-height: 1;
- display: grid;
- place-items: center;
- top: 14px;
- left: 14px;
- z-index: 1;
-}
-
-.teamMemberNameRoleSub {
- background-color: var(--color-warning-high);
-}
-
-.logoContainer {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: var(--s-4);
-}
-
-@container (width >= 480px) {
- .logoContainer {
- flex-direction: row;
- }
-}
-
-.logo {
- border-radius: var(--radius-avatar);
- min-width: 124px;
-}
-
-.title {
- font-size: var(--font-xl);
- font-weight: var(--weight-bold);
-}
-
-.badge {
- text-transform: uppercase;
- font-size: var(--font-2xs);
- font-weight: var(--weight-bold);
- padding: 0 var(--s-2);
- border-radius: var(--radius-selector);
- height: var(--selector-size-sm);
- place-items: center;
- width: max-content;
- display: flex;
- gap: var(--s-2);
-}
-
-.badgeRanked {
- background-color: var(--color-info-low);
- color: var(--color-info-high);
-}
-
-.badgeUnranked {
- background-color: var(--color-success-low);
- color: var(--color-success-high);
-}
-
-.badgeModes {
- background-color: var(--color-bg-high);
-}
-
-.infoIcon {
- width: 18px;
- padding: var(--s-1) 0;
-}
-
-.infoDescription {
- white-space: pre-wrap;
-
- & > :is(h1, h2, h3, h4, h5, h6) {
- margin-block-end: var(--s-6);
- }
-
- & > :is(h2, h3, h4, h5, h6) {
- margin-block-start: var(--s-6);
- }
-
- & > :first-child {
- margin-block-start: 0;
- }
-
- & > h1 {
- font-size: var(--font-xl);
- }
-
- & > :is(h2, h3, h4, h5, h6) {
- font-size: var(--font-lg);
- }
-
- & > :is(h3, h4, h5, h6) {
- font-size: var(--font-md);
- }
-
- & > ul:has(+ p) {
- margin-block-end: var(--s-6);
- }
-}
-
-.by {
- color: var(--color-text-high);
- font-size: var(--font-sm);
- font-weight: var(--weight-semi);
-}
-
-.sectionHeader {
- font-size: var(--font-sm);
-}
-
-.section {
- background-color: var(--color-bg-high);
- margin-inline: -12px;
- padding: var(--s-4) var(--s-3);
-}
-
-.sectionInputContainer {
- width: 16rem;
-}
-
-.sectionWarning {
- font-size: var(--font-xs);
- font-weight: var(--weight-semi);
- text-align: center;
- color: var(--color-text-high);
-}
-
-.rosterGrid {
- display: grid;
- grid-template-columns: repeat(auto-fill, 110px);
- gap: var(--s-4);
- margin-block-start: var(--s-2);
- width: 100%;
- justify-content: center;
-}
-
-.rosterGridMemberName {
- max-width: 110px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.missingPlayer {
- width: 62px;
- height: 62px;
- font-size: 32px;
- border-radius: 100%;
- border: var(--border-style-accent);
- color: var(--color-text-accent);
- display: grid;
- place-items: center;
- margin: 0 auto;
-}
-
-.missingPlayerOptional {
- border: 2px dashed var(--color-text-accent);
- color: var(--color-text-accent);
-}
-
-.inviteContainer {
- display: flex;
- flex-direction: column;
- gap: var(--s-12);
- align-items: center;
-}
-
-.joinAlert {
- max-width: 22rem;
-}
-
-.streamUserContainer {
- font-size: var(--font-xs);
- display: flex;
- gap: var(--s-2);
- align-items: center;
- font-weight: var(--weight-semi);
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- min-width: 0;
-}
-
-.streamTeamName {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- min-width: 0;
- max-width: 200px;
-}
-
-.streamsGrid {
- display: grid;
- grid-template-columns: repeat(auto-fill, 320px);
- gap: var(--s-6);
- justify-content: center;
-}
-
-.streamViewerCount {
- font-size: var(--font-xs);
- display: flex;
- gap: var(--s-2);
- align-items: center;
- margin-block-start: -5px;
- color: var(--color-text-high);
-
- & > svg {
- width: 1rem;
- min-width: 1rem;
- }
-}
-
-.teamStats {
- border-radius: var(--radius-box);
- padding: var(--s-4);
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: var(--s-4);
- margin: 0 auto;
-}
-
-.teamStat {
- display: grid;
- grid-template-columns: 1fr;
- grid-template-rows: 1fr 2fr 1fr;
- place-items: center;
- font-weight: var(--weight-bold);
- text-align: center;
-}
-
-.teamStatTitle {
- font-size: var(--font-sm);
- color: var(--color-text-high);
-}
-
-.teamStatMain {
- font-size: var(--font-xl);
- font-weight: var(--weight-semi);
-
- & sup {
- font-size: var(--font-sm);
- font-weight: var(--weight-semi);
- }
-}
-
-.teamStatSub {
- color: var(--color-text-high);
- font-size: var(--font-sm);
-}
-
-@container (width >= 640px) {
- .section {
- margin: 0;
- border-radius: var(--radius-box);
- }
-
- .teamStats {
- grid-template-columns: 1fr 1fr 1fr 1fr;
- }
-}
-
-.teamSets {
- display: flex;
- flex-direction: column;
- max-width: 32rem;
- margin: 0 auto;
- gap: var(--s-8);
-}
-
-.teamSet {
- display: flex;
- flex-direction: column;
- gap: var(--s-3);
- background-color: var(--color-bg-high);
- border-radius: var(--radius-box);
- padding: var(--s-3) var(--s-6);
-}
-
-.teamSetTopContainer {
- display: flex;
- justify-content: center;
- gap: var(--s-2);
- flex-direction: column;
- align-items: center;
- text-align: center;
-}
-
-@container (width >= 480px) {
- .teamSetTopContainer {
- flex-direction: row;
- align-items: flex-end;
- }
-}
-
-.teamSetScore {
- font-size: var(--font-xl);
- font-weight: var(--weight-bold);
- white-space: nowrap;
-}
-
-.teamSetRoundName {
- font-size: var(--font-lg);
- font-weight: var(--weight-semi);
- color: var(--color-text-high);
- margin-block-end: 2px;
-}
-
-.teamSetMode {
- background-color: var(--color-bg-high);
- border-radius: 100%;
- padding: var(--s-2);
- border: 2px solid var(--color-success);
-}
-
-.teamSetModeLoss {
- border-color: var(--color-error);
-}
-
-.teamSetStageContainer {
- display: flex;
- flex-direction: column;
- gap: var(--s-2);
- align-items: center;
- font-size: var(--font-xs);
- font-weight: var(--weight-semi);
- color: var(--color-text);
-}
-
-.teamSetOpponent {
- display: grid;
- grid-template-areas: "vs team" "vs members";
- grid-template-columns: max-content 1fr;
- column-gap: var(--s-4);
- row-gap: var(--s-2);
-}
-
-.teamSetOpponentVs {
- grid-area: vs;
- font-size: var(--font-xl);
- font-weight: var(--weight-bold);
- color: var(--color-text-high);
- align-self: center;
-}
-
-.teamSetOpponentTeam {
- grid-area: team;
- font-size: var(--font-lg);
- font-weight: var(--weight-semi);
- color: var(--color-text);
-}
-
-.teamSetOpponentMembers {
- grid-area: members;
- display: flex;
- gap: var(--s-2);
- flex-wrap: wrap;
-}
-
-.teamSetOpponentMember {
- font-size: var(--font-xs);
-}
-
-.overlapDivider {
- display: flex;
- width: 100%;
- align-items: center;
- color: var(--color-accent);
- font-size: var(--font-lg);
-
- &::before,
- &::after {
- flex: 1;
- border-bottom: 2px solid var(--color-bg-higher);
- content: "";
- }
-}
-
-.standingsMatchResultSquare {
- width: 28px;
- height: 28px;
- display: grid;
- place-items: center;
- font-size: var(--font-xs);
- font-weight: var(--weight-semi);
- border: 3px solid var(--color-accent);
- border-radius: var(--radius-field);
- color: var(--color-text);
-}
-
-.standingsMatchResultSquareWin {
- border-color: var(--color-success);
-}
-
-.standingsMatchResultSquareLoss {
- border-color: var(--color-error);
-}
-
-.standingsRowAlt > td {
- background-color: var(--color-bg-high);
-}
-
-.standingsTeamName {
- min-width: 125px;
- display: flex;
- gap: var(--s-1-5);
- align-items: center;
- color: var(--color-text);
-}
-
-.standingsTeamNameText {
- max-width: 16ch;
- overflow: hidden;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- line-clamp: 2;
- -webkit-box-orient: vertical;
- word-break: break-word;
-}
-
-.standingsDivider {
- width: 5px;
- background-color: var(--color-border-high);
- border-radius: var(--radius-box);
-}
-
-.spoilerRevealContainer {
- display: flex;
- justify-content: center;
- align-items: center;
- padding: var(--s-12) 0;
-}
diff --git a/app/features/trophies/routes/trophies.$id.module.css b/app/features/trophies/routes/trophies.$id.module.css
new file mode 100644
index 000000000..5563cbf61
--- /dev/null
+++ b/app/features/trophies/routes/trophies.$id.module.css
@@ -0,0 +1,47 @@
+.trophyDetailsContainer {
+ border: var(--border-style);
+ border-radius: var(--radius-box);
+ overflow: clip;
+}
+
+@container (width < 560px) {
+ .trophyDetails {
+ height: 512px;
+ }
+}
+
+.divider {
+ margin-top: var(--s-1);
+}
+
+.trophyName {
+ font-weight: var(--weight-bold);
+ font-size: var(--font-lg);
+}
+
+.trophyMeta {
+ color: var(--color-text-high);
+ font-size: var(--font-2xs);
+}
+
+.owners {
+ display: flex;
+ flex-wrap: wrap;
+ padding: 0;
+ font-size: var(--font-xs);
+ gap: var(--s-2);
+
+ & > li {
+ display: flex;
+ align-items: center;
+ gap: var(--s-1);
+ list-style: none;
+ border-right: var(--border-style);
+ padding-right: var(--s-2);
+ }
+}
+
+.ownerCount {
+ color: var(--color-text-high);
+ font-size: var(--font-2xs);
+}
diff --git a/app/features/trophies/routes/trophies.$id.tsx b/app/features/trophies/routes/trophies.$id.tsx
index 6277f0a4b..31873cb37 100644
--- a/app/features/trophies/routes/trophies.$id.tsx
+++ b/app/features/trophies/routes/trophies.$id.tsx
@@ -9,7 +9,7 @@ import { TrophyShowcase } from "../components/TrophyShowcase";
import { TrophyTournamentHistory } from "../components/TrophyTournamentHistory";
import { loader } from "../loaders/trophies.$id.server";
import { parseSpecialTrophyCode } from "../trophies-utils";
-import styles from "./trophies.module.css";
+import styles from "./trophies.$id.module.css";
export { loader };
diff --git a/app/features/trophies/routes/trophies.module.css b/app/features/trophies/routes/trophies.module.css
index 4bcdb1417..bc2964e85 100644
--- a/app/features/trophies/routes/trophies.module.css
+++ b/app/features/trophies/routes/trophies.module.css
@@ -14,51 +14,3 @@
flex-direction: column;
gap: var(--s-4);
}
-
-.trophyDetailsContainer {
- border: var(--border-style);
- border-radius: var(--radius-box);
- overflow: clip;
-}
-
-@container (width < 560px) {
- .trophyDetails {
- height: 512px;
- }
-}
-
-.divider {
- margin-top: var(--s-1);
-}
-
-.trophyName {
- font-weight: var(--weight-bold);
- font-size: var(--font-lg);
-}
-
-.trophyMeta {
- color: var(--color-text-high);
- font-size: var(--font-2xs);
-}
-
-.owners {
- display: flex;
- flex-wrap: wrap;
- padding: 0;
- font-size: var(--font-xs);
- gap: var(--s-2);
-
- & > li {
- display: flex;
- align-items: center;
- gap: var(--s-1);
- list-style: none;
- border-right: var(--border-style);
- padding-right: var(--s-2);
- }
-}
-
-.ownerCount {
- color: var(--color-text-high);
- font-size: var(--font-2xs);
-}
diff --git a/app/features/user-page/components/UserResultsTable.module.css b/app/features/user-page/components/UserResultsTable.module.css
new file mode 100644
index 000000000..1c7542f27
--- /dev/null
+++ b/app/features/user-page/components/UserResultsTable.module.css
@@ -0,0 +1,7 @@
+.resultsPlayers {
+ display: flex;
+ flex-direction: column;
+ padding: 0;
+ gap: var(--s-3);
+ list-style: none;
+}
diff --git a/app/features/user-page/components/UserResultsTable.tsx b/app/features/user-page/components/UserResultsTable.tsx
index 5b4901b20..bddc42c77 100644
--- a/app/features/user-page/components/UserResultsTable.tsx
+++ b/app/features/user-page/components/UserResultsTable.tsx
@@ -11,12 +11,12 @@ import { UserLink } from "~/components/UserLink";
import { tournamentBracketsPage } from "~/features/tournament-bracket/tournament-bracket-urls";
import { calendarEventPage, tournamentTeamPage } from "~/utils/urls";
import type { UserResultsLoaderData } from "../loaders/u.$identifier.results.server";
-import styles from "../user-page.module.css";
import {
HIGHLIGHT_CHECKBOX_NAME,
HIGHLIGHT_TOURNAMENT_CHECKBOX_NAME,
} from "../user-page-constants";
import { ParticipationPill } from "./ParticipationPill";
+import styles from "./UserResultsTable.module.css";
export type UserResultsTableProps = {
results: UserResultsLoaderData["results"]["value"];
diff --git a/app/features/user-page/components/Widget.tsx b/app/features/user-page/components/Widget.tsx
index 9b68ae6bc..71b806f91 100644
--- a/app/features/user-page/components/Widget.tsx
+++ b/app/features/user-page/components/Widget.tsx
@@ -802,7 +802,7 @@ function SocialLinksWidget({
variant="minimal"
className={clsx(
styles.socialLinkIconContainer,
- styles[link.platform],
+ styles.discord,
)}
>
{link.platform === "discord" ?
: null}
diff --git a/app/features/user-page/components/WidgetSettingsForm.tsx b/app/features/user-page/components/WidgetSettingsForm.tsx
index 34f52b415..073d5f2b2 100644
--- a/app/features/user-page/components/WidgetSettingsForm.tsx
+++ b/app/features/user-page/components/WidgetSettingsForm.tsx
@@ -1,4 +1,3 @@
-import clsx from "clsx";
import { useTranslation } from "react-i18next";
import type { Tables } from "~/db/tables";
import { type CustomFieldRenderProps, FormField } from "~/form/FormField";
@@ -7,7 +6,6 @@ import {
getWidgetFormSchema,
TIMEZONE_OPTIONS,
} from "../core/widgets/widget-form-schemas";
-import styles from "../routes/u.$identifier.module.css";
import { USER } from "../user-page-constants";
import { GameBadgeSelectField } from "./GameBadgeSelectField";
@@ -158,7 +156,6 @@ function SensFields() {
e.target.value === "" ? null : Number(e.target.value),
)
}
- className={clsx(styles.sensSelect)}
>
{"-"}
{SENS_OPTIONS.map((sens) => (
@@ -179,7 +176,6 @@ function SensFields() {
e.target.value === "" ? null : Number(e.target.value),
)
}
- className={clsx(styles.sensSelect)}
>
{"-"}
{SENS_OPTIONS.map((sens) => (
diff --git a/app/features/user-page/routes/u.$identifier.builds.module.css b/app/features/user-page/routes/u.$identifier.builds.module.css
index bdb55f920..4e153f56c 100644
--- a/app/features/user-page/routes/u.$identifier.builds.module.css
+++ b/app/features/user-page/routes/u.$identifier.builds.module.css
@@ -4,3 +4,9 @@
gap: var(--s-4) var(--s-3);
grid-template-columns: repeat(auto-fit, 240px);
}
+
+.buildFilterButton {
+ padding: 0 var(--s-1-5) !important;
+ gap: var(--s-1);
+ min-height: 32px;
+}
diff --git a/app/features/user-page/routes/u.$identifier.builds.tsx b/app/features/user-page/routes/u.$identifier.builds.tsx
index 673c57527..2fdab891d 100644
--- a/app/features/user-page/routes/u.$identifier.builds.tsx
+++ b/app/features/user-page/routes/u.$identifier.builds.tsx
@@ -33,7 +33,6 @@ import { userBuildsSearchParams } from "../user-page-search-params";
export { action, loader };
-import userStyles from "../user-page.module.css";
import styles from "./u.$identifier.builds.module.css";
export const handle: SendouRouteHandle = {
@@ -151,7 +150,7 @@ function BuildsFilters({
onPress={() => setWeaponFilter("ALL")}
variant={weaponFilter === "ALL" ? undefined : "outlined"}
size="small"
- className={userStyles.buildFilterButton}
+ className={styles.buildFilterButton}
>
{t("builds:stats.all")} ({data.builds.length})
@@ -161,7 +160,7 @@ function BuildsFilters({
onPress={() => setWeaponFilter("PUBLIC")}
variant={weaponFilter === "PUBLIC" ? undefined : "outlined"}
size="small"
- className={userStyles.buildFilterButton}
+ className={styles.buildFilterButton}
icon={
}
>
{t("builds:stats.public")} ({publicBuildsCount})
@@ -170,7 +169,7 @@ function BuildsFilters({
onPress={() => setWeaponFilter("PRIVATE")}
variant={weaponFilter === "PRIVATE" ? undefined : "outlined"}
size="small"
- className={userStyles.buildFilterButton}
+ className={styles.buildFilterButton}
icon={
}
>
{t("builds:stats.private")} ({privateBuildsCount})
@@ -350,7 +349,7 @@ function WeaponFilterMenu({
* {
+ flex: 0 0 280px;
+ scroll-snap-align: start;
+ }
+}
+
+.mainStack {
+ display: flex;
+ flex-direction: column;
+ gap: var(--s-6);
+}
+
+.grid {
+ display: none;
+}
+
+@container (width >= 720px) {
+ .header {
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: flex-start;
+ align-items: center;
+ padding-block-start: var(--s-6);
+ }
+
+ .nameGroup {
+ align-items: flex-start;
+ }
+
+ .desktopIconNav {
+ display: block;
+ margin-left: auto;
+ }
+
+ .mobileIconNav {
+ display: none;
+ }
+
+ .mainStack {
+ display: none;
+ }
+
+ .sideCarousel {
+ display: none;
+ }
+
+ .editButtons {
+ margin: initial;
+ }
+
+ .grid {
+ display: grid;
+ grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
+ gap: var(--s-8);
+ align-items: start;
+ }
+
+ .main {
+ display: flex;
+ flex-direction: column;
+ gap: var(--s-6);
+ }
+
+ .side {
+ position: sticky;
+ top: 50px;
+ display: flex;
+ flex-direction: column;
+ gap: var(--s-6);
+ align-self: start;
+ }
+}
+
+.oldPageContainer {
+ display: flex;
+ flex-direction: column;
+ gap: var(--s-6);
+}
+
+.avatarContainer {
+ display: grid;
+ justify-content: center;
+ column-gap: var(--s-3);
+ grid-template-areas: "avatar name" "avatar team" "avatar ." "avatar ." "socials .";
+}
+
+.avatar {
+ min-width: 125px;
+ grid-area: avatar;
+}
+
+.team {
+ display: flex;
+ font-weight: var(--weight-bold);
+ color: var(--color-text);
+ gap: var(--s-1-5);
+ grid-area: team;
+ align-items: center;
+}
+
+.name {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ grid-area: name;
+ overflow-wrap: anywhere;
+ gap: var(--s-3);
+}
+
+.socials {
+ display: flex;
+ justify-content: center;
+ gap: var(--s-1-5);
+ grid-area: socials;
+ padding-block-start: var(--s-3);
+}
+
+.socialLink {
+ padding: var(--s-1);
+ border: 1px solid;
+ border-radius: 50%;
+}
+
+.extraInfoHeading {
+ & > svg {
+ width: 0.8rem;
+ }
+}
+
+.socialLink {
+ & > svg {
+ width: 0.9rem;
+ }
+}
+
+.socialLinkYoutube {
+ border-color: #f00;
+ background-color: #ff00002f;
+
+ & > svg {
+ fill: #f00;
+ }
+}
+
+.socialLinkTwitch {
+ border-color: #9146ff;
+ background-color: #9146ff2f;
+
+ & > svg {
+ fill: #9146ff;
+ }
+}
+
+.socialLinkBattlefy {
+ border-color: #de4c5e;
+ background-color: #de4c5e2f;
+
+ & > svg {
+ fill: #de4c5e;
+ }
+}
+
+.socialLinkBsky {
+ border-color: #1285fe;
+ background-color: #1285fe2f;
+ display: grid;
+ place-items: center;
+
+ & path {
+ fill: #1285fe;
+ }
+}
+
+.extraInfos {
+ display: flex;
+ max-width: 24rem;
+ flex-wrap: wrap;
+ gap: var(--s-3);
+ margin-inline: auto;
+}
+
+.extraInfo {
+ padding: var(--s-1) var(--s-1-5);
+ border-radius: var(--radius-box);
+ background-color: var(--color-bg-high);
+ font-size: var(--font-2xs);
+ display: flex;
+ align-items: center;
+ gap: var(--s-1);
+}
+
+.extraInfoHeading {
+ color: var(--color-text-accent);
+ font-weight: var(--weight-bold);
+}
+
+.weapon {
+ padding: var(--s-2);
+ border-radius: 100%;
+ background-color: var(--color-bg-high);
+}
+
+.placements {
+ display: flex;
+ flex-wrap: wrap;
+ padding: var(--s-4);
+ border-radius: var(--radius-box);
+ margin: 0 auto;
+ background-color: var(--color-bg-high);
+ color: var(--color-text);
+ gap: var(--s-6);
+ transition: 0.1s ease-in-out background-color;
+
+ &:hover {
+ background-color: var(--color-bg-higher);
+ }
+}
+
+.placementsMode {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ font-size: var(--font-xs);
+ font-weight: var(--weight-semi);
+ gap: var(--s-1-5);
+}
+
+@container (width >= 480px) {
+ .placements {
+ gap: var(--s-10);
+ }
+
+ .placementsMode {
+ font-size: var(--font-sm);
+ }
+}
diff --git a/app/features/user-page/routes/u.$identifier.index.tsx b/app/features/user-page/routes/u.$identifier.index.tsx
index 8291fb81f..71bcd523a 100644
--- a/app/features/user-page/routes/u.$identifier.index.tsx
+++ b/app/features/user-page/routes/u.$identifier.index.tsx
@@ -36,8 +36,7 @@ import { UserPageIconNav } from "../components/UserPageIconNav";
import { Widget } from "../components/Widget";
import { loader } from "../loaders/u.$identifier.index.server";
import type { UserPageLoaderData } from "../loaders/u.$identifier.server";
-import styles from "../user-page.module.css";
-import newStyles from "./u.$identifier.module.css";
+import styles from "./u.$identifier.index.module.css";
export { loader };
@@ -85,15 +84,15 @@ function NewUserInfoPage() {
const isOwnPage = layoutData.user.id === user?.id;
return (
-
+
-
+
-
-
-
+
+
+
{layoutData.user.username}
@@ -107,14 +106,14 @@ function NewUserInfoPage() {
/>
-
{isOwnPage ? (
-
+
) : null}
-
+
-
+
{sideWidgets.map((widget) => (
))}
-
+
{mainWidgets.map((widget) => (
))}
-
-
+
+
{mainWidgets.map((widget) => (
))}
-
+
{sideWidgets.map((widget) => (
))}
@@ -183,7 +182,7 @@ export function OldUserInfoPage() {
}
return (
-
+
@@ -545,7 +544,7 @@ function ProfileSubtitle({
if (parts.length === 0) return null;
return (
-
+
{parts.map((part, i) => (
{i > 0 ? · : null}
diff --git a/app/features/user-page/routes/u.$identifier.module.css b/app/features/user-page/routes/u.$identifier.module.css
deleted file mode 100644
index 4148721c2..000000000
--- a/app/features/user-page/routes/u.$identifier.module.css
+++ /dev/null
@@ -1,137 +0,0 @@
-.container {
- display: flex;
- flex-direction: column;
- gap: var(--s-6);
- width: 100%;
- max-width: 1200px;
- margin: 0 auto;
-}
-
-.header {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: var(--s-4);
-}
-
-.userInfo {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: var(--s-2);
-}
-
-.username {
- font-size: var(--font-xl);
- font-weight: var(--weight-bold);
- margin: 0;
-}
-
-.subtitle {
- font-size: var(--font-2xs);
- color: var(--color-text-high);
- display: flex;
- align-items: center;
- gap: var(--s-1);
-}
-
-.nameGroup {
- display: flex;
- flex-direction: column;
- line-height: 1.25;
- align-items: center;
-}
-
-.editButtons {
- display: flex;
- gap: var(--s-2);
- margin: 0 auto;
-}
-
-.mobileIconNav {
- display: block;
-}
-
-.desktopIconNav {
- display: none;
-}
-
-.sideCarousel {
- display: flex;
- overflow-x: auto;
- gap: var(--s-4);
- padding-block: var(--s-2);
- scroll-snap-type: x mandatory;
-
- & > * {
- flex: 0 0 280px;
- scroll-snap-align: start;
- }
-}
-
-.mainStack {
- display: flex;
- flex-direction: column;
- gap: var(--s-6);
-}
-
-.grid {
- display: none;
-}
-
-@container (width >= 720px) {
- .header {
- flex-direction: row;
- flex-wrap: wrap;
- justify-content: flex-start;
- align-items: center;
- padding-block-start: var(--s-6);
- }
-
- .nameGroup {
- align-items: flex-start;
- }
-
- .desktopIconNav {
- display: block;
- margin-left: auto;
- }
-
- .mobileIconNav {
- display: none;
- }
-
- .mainStack {
- display: none;
- }
-
- .sideCarousel {
- display: none;
- }
-
- .editButtons {
- margin: initial;
- }
-
- .grid {
- display: grid;
- grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
- gap: var(--s-8);
- align-items: start;
- }
-
- .main {
- display: flex;
- flex-direction: column;
- gap: var(--s-6);
- }
-
- .side {
- position: sticky;
- top: 50px;
- display: flex;
- flex-direction: column;
- gap: var(--s-6);
- align-self: start;
- }
-}
diff --git a/app/features/user-page/routes/u.$identifier.results.highlights.module.css b/app/features/user-page/routes/u.$identifier.results.highlights.module.css
new file mode 100644
index 000000000..8e1634653
--- /dev/null
+++ b/app/features/user-page/routes/u.$identifier.results.highlights.module.css
@@ -0,0 +1,32 @@
+.resultsTableWrapper {
+ width: 100%;
+ overflow-x: auto;
+}
+
+.resultsTableHighlights {
+ border: var(--s-2) solid var(--color-bg-high);
+ padding-inline: 0 !important;
+
+ & > legend {
+ margin-inline-start: var(--s-2);
+ padding-inline: var(--s-1);
+ }
+}
+
+.highlightsContainer {
+ position: relative;
+ padding-bottom: 80px; /* Leave space for sticky button */
+}
+
+.highlightsStickyButton {
+ position: sticky;
+ bottom: 0;
+ background-color: var(--color-bg);
+ padding: var(--s-2) 0;
+ z-index: 10;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: var(--s-2);
+ margin-top: var(--s-4);
+}
diff --git a/app/features/user-page/routes/u.$identifier.results.highlights.tsx b/app/features/user-page/routes/u.$identifier.results.highlights.tsx
index bae93b5b0..34916fd3e 100644
--- a/app/features/user-page/routes/u.$identifier.results.highlights.tsx
+++ b/app/features/user-page/routes/u.$identifier.results.highlights.tsx
@@ -5,7 +5,7 @@ import { SubmitButton } from "~/components/SubmitButton";
import { UserResultsTable } from "~/features/user-page/components/UserResultsTable";
import { action } from "../actions/u.$identifier.results.highlights.server";
import { loader } from "../loaders/u.$identifier.results.server";
-import styles from "../user-page.module.css";
+import styles from "./u.$identifier.results.highlights.module.css";
export { action, loader };
diff --git a/app/features/user-page/routes/u.$identifier.results.module.css b/app/features/user-page/routes/u.$identifier.results.module.css
new file mode 100644
index 000000000..e3014201c
--- /dev/null
+++ b/app/features/user-page/routes/u.$identifier.results.module.css
@@ -0,0 +1,6 @@
+.resultsHeaderActions {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: var(--s-2);
+}
diff --git a/app/features/user-page/routes/u.$identifier.results.tsx b/app/features/user-page/routes/u.$identifier.results.tsx
index 010ef5c44..e92b74f80 100644
--- a/app/features/user-page/routes/u.$identifier.results.tsx
+++ b/app/features/user-page/routes/u.$identifier.results.tsx
@@ -11,8 +11,8 @@ import { ResultsFiltersBar } from "../components/ResultsFiltersBar";
import { SubPageHeader } from "../components/SubPageHeader";
import { loader } from "../loaders/u.$identifier.results.server";
import type { UserPageLoaderData } from "../loaders/u.$identifier.server";
-import styles from "../user-page.module.css";
import { userResultsSearchParams } from "../user-page-search-params";
+import styles from "./u.$identifier.results.module.css";
export { loader };
diff --git a/app/features/user-page/routes/u.$identifier.seasons.index.module.css b/app/features/user-page/routes/u.$identifier.seasons.index.module.css
new file mode 100644
index 000000000..f57c1c662
--- /dev/null
+++ b/app/features/user-page/routes/u.$identifier.seasons.index.module.css
@@ -0,0 +1,79 @@
+.seasonMatch {
+ background-color: var(--color-bg-high);
+ border-radius: var(--radius-box);
+ padding: var(--s-2) var(--s-3);
+ color: var(--color-text);
+ transition: ease-in 0.2s background-color;
+ display: flex;
+ flex-direction: column;
+ gap: var(--s-4);
+
+ &:hover {
+ background-color: var(--color-bg-higher);
+ }
+}
+
+.seasonMatchWithSubSection {
+ border-radius: var(--radius-box) var(--radius-box) 0 0;
+}
+
+.seasonMatchSubSection {
+ border-radius: 0 0 var(--radius-box) var(--radius-box);
+ background-color: var(--color-bg-high);
+ font-size: var(--font-xs);
+ text-align: center;
+ font-weight: var(--weight-bold);
+ display: flex;
+ gap: var(--s-3);
+ justify-content: center;
+ padding-block: var(--s-0-5);
+}
+
+.seasonMatchUser {
+ display: flex;
+ flex-direction: column;
+ font-size: var(--font-xs);
+ width: 65px;
+ align-items: center;
+}
+
+.seasonMatchUserName {
+ max-width: 60px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.seasonMatchScore {
+ font-size: var(--font-xl);
+ font-weight: var(--weight-bold);
+ margin-inline: auto;
+}
+
+.seasonMatchSetResults {
+ list-style: none;
+ display: flex;
+ gap: var(--s-2);
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: center;
+ margin: 0 auto;
+ padding: 0;
+
+ & li {
+ width: 28px;
+ height: 28px;
+ display: grid;
+ place-items: center;
+ font-size: var(--font-xs);
+ font-weight: var(--weight-semi);
+ border: 3px solid var(--color-accent);
+ border-radius: var(--radius-field);
+ color: var(--color-text);
+ border-color: var(--color-error);
+
+ &[data-is-win="true"] {
+ border-color: var(--color-success);
+ }
+ }
+}
diff --git a/app/features/user-page/routes/u.$identifier.seasons.index.tsx b/app/features/user-page/routes/u.$identifier.seasons.index.tsx
index 130ae6f69..651f0482d 100644
--- a/app/features/user-page/routes/u.$identifier.seasons.index.tsx
+++ b/app/features/user-page/routes/u.$identifier.seasons.index.tsx
@@ -22,8 +22,8 @@ import {
type UserSeasonsSetsLoaderData,
} from "../loaders/u.$identifier.seasons.index.server";
import type { UserPageLoaderData } from "../loaders/u.$identifier.server";
-import styles from "../user-page.module.css";
import { userSeasonsSearchParams } from "../user-page-search-params";
+import styles from "./u.$identifier.seasons.index.module.css";
export { loader };
diff --git a/app/features/user-page/routes/u.$identifier.seasons.module.css b/app/features/user-page/routes/u.$identifier.seasons.module.css
new file mode 100644
index 000000000..71785dca2
--- /dev/null
+++ b/app/features/user-page/routes/u.$identifier.seasons.module.css
@@ -0,0 +1,23 @@
+.seasonWinrate {
+ background-color: var(--color-bg-high);
+ padding: var(--s-0-5) var(--s-2);
+ border-radius: var(--radius-box);
+ font-size: var(--font-xs);
+ font-weight: var(--weight-semi);
+}
+
+.seasonTentative {
+ text-transform: uppercase;
+ font-weight: var(--weight-semi);
+ color: var(--color-text-high);
+ font-size: var(--font-2xs);
+ margin-top: -8px;
+ display: flex;
+ gap: var(--s-1);
+ align-items: center;
+ line-height: 1.3;
+}
+
+.seasonTentativeExplanation {
+ text-transform: initial;
+}
diff --git a/app/features/user-page/routes/u.$identifier.seasons.stats.module.css b/app/features/user-page/routes/u.$identifier.seasons.stats.module.css
new file mode 100644
index 000000000..fcac860b0
--- /dev/null
+++ b/app/features/user-page/routes/u.$identifier.seasons.stats.module.css
@@ -0,0 +1,89 @@
+.seasonWeaponContainer {
+ display: flex;
+ align-items: center;
+ font-size: var(--font-sm);
+ flex-direction: column;
+ font-weight: var(--weight-bold);
+ position: relative;
+ width: 60px;
+}
+
+.seasonWeaponBorderOuter {
+ --degree: 80deg;
+ --smoothing: 0.5deg;
+ --color: var(--color-text-accent);
+
+ display: block;
+ content: "";
+ height: 60px;
+ width: 60px;
+ border-radius: 50%;
+ background: conic-gradient(
+ var(--color) var(--degree),
+ transparent calc(var(--degree) + var(--smoothing)) 100%
+ );
+ position: absolute;
+}
+
+.seasonWeaponBorderOuterStatic {
+ height: 60px;
+ width: 60px;
+ background-color: var(--color-bg-high);
+ position: absolute;
+ border-radius: 50%;
+}
+
+.seasonWeaponBorderInner {
+ height: 84%;
+ width: 84%;
+ top: 8%;
+ left: 8%;
+ position: relative;
+ border-radius: 50%;
+ background: var(--color-bg);
+ display: grid;
+ place-items: center;
+}
+
+.seasonWeaponCount {
+ margin-top: 62px;
+}
+
+.seasonPlayerName {
+ width: 64px;
+ font-size: var(--font-xs);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ text-align: center;
+ font-weight: var(--weight-semi);
+}
+
+.seasonInfoContainer {
+ background-color: var(--color-bg-high);
+ padding: var(--s-3) var(--s-2);
+ border-radius: var(--radius-box);
+}
+
+.seasonWeaponUsageContainer {
+ display: flex;
+ flex-direction: column;
+ gap: var(--s-2);
+ width: 300px;
+ min-height: 175px;
+}
+
+.seasonWeaponUsageWeaponsContainer {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--s-3);
+ justify-content: center;
+ max-height: 300px;
+ overflow-y: auto;
+}
+
+.seasonWeaponUsageWeapon {
+ border-radius: 100%;
+ background-color: var(--color-bg-high);
+ padding: var(--s-1-5);
+}
diff --git a/app/features/user-page/routes/u.$identifier.seasons.stats.tsx b/app/features/user-page/routes/u.$identifier.seasons.stats.tsx
index 6d85d2637..eb4e327c3 100644
--- a/app/features/user-page/routes/u.$identifier.seasons.stats.tsx
+++ b/app/features/user-page/routes/u.$identifier.seasons.stats.tsx
@@ -25,8 +25,8 @@ import {
type UserSeasonsStatsLoaderData,
} from "../loaders/u.$identifier.seasons.stats.server";
import type { UserPageLoaderData } from "../loaders/u.$identifier.server";
-import styles from "../user-page.module.css";
import { userSeasonsSearchParams } from "../user-page-search-params";
+import styles from "./u.$identifier.seasons.stats.module.css";
export { loader };
diff --git a/app/features/user-page/routes/u.$identifier.seasons.tsx b/app/features/user-page/routes/u.$identifier.seasons.tsx
index ed1ae8717..6d2d2405e 100644
--- a/app/features/user-page/routes/u.$identifier.seasons.tsx
+++ b/app/features/user-page/routes/u.$identifier.seasons.tsx
@@ -53,8 +53,8 @@ import {
} from "../loaders/u.$identifier.seasons.server";
import type { UserSeasonSummaryGraphicLoaderData } from "../loaders/u.$identifier.seasons.summary-graphic.server";
import type { UserPageLoaderData } from "../loaders/u.$identifier.server";
-import styles from "../user-page.module.css";
import { userSeasonsSearchParams } from "../user-page-search-params";
+import styles from "./u.$identifier.seasons.module.css";
export { loader };
diff --git a/app/features/user-page/routes/u.$identifier.tsx b/app/features/user-page/routes/u.$identifier.tsx
index c800c6b6f..67cc8afc5 100644
--- a/app/features/user-page/routes/u.$identifier.tsx
+++ b/app/features/user-page/routes/u.$identifier.tsx
@@ -25,7 +25,6 @@ import {
export { loader };
-import "~/features/user-page/user-page.module.css";
import { userArtPage } from "~/features/art/art-urls";
import { userSeasonsPage } from "~/features/user-page/user-page-urls";
diff --git a/app/features/user-page/routes/u.$identifier.vods.tsx b/app/features/user-page/routes/u.$identifier.vods.tsx
index 9a9d02c61..18a5ecbcc 100644
--- a/app/features/user-page/routes/u.$identifier.vods.tsx
+++ b/app/features/user-page/routes/u.$identifier.vods.tsx
@@ -1,7 +1,9 @@
import { useLoaderData, useMatches } from "react-router";
import { Pagination } from "~/components/Pagination";
-import { VodListing } from "~/features/vods/components/VodListing";
-import styles from "~/features/vods/routes/vods.module.css";
+import {
+ VodListing,
+ VodListingList,
+} from "~/features/vods/components/VodListing";
import { userVodsSearchParams } from "~/features/vods/vods-search-params";
import { useSearchParamPagination } from "~/hooks/useSearchParamPagination";
import invariant from "~/utils/invariant";
@@ -35,11 +37,11 @@ export default function UserVodsPage() {
user={layoutData.user}
backTo={userPage(layoutData.user)}
/>
-
+
{data.vods.map((vod) => (
))}
-
+
{data.pagesCount > 1 ? : null}
);
diff --git a/app/features/user-page/user-page.module.css b/app/features/user-page/user-page.module.css
deleted file mode 100644
index bc97a4a78..000000000
--- a/app/features/user-page/user-page.module.css
+++ /dev/null
@@ -1,411 +0,0 @@
-.container {
- display: flex;
- flex-direction: column;
- gap: var(--s-6);
-}
-
-.avatarContainer {
- display: grid;
- justify-content: center;
- column-gap: var(--s-3);
- grid-template-areas: "avatar name" "avatar team" "avatar ." "avatar ." "socials .";
-}
-
-.avatar {
- min-width: 125px;
- grid-area: avatar;
-}
-
-.team {
- display: flex;
- font-weight: var(--weight-bold);
- color: var(--color-text);
- gap: var(--s-1-5);
- grid-area: team;
- align-items: center;
-}
-
-.name {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- grid-area: name;
- overflow-wrap: anywhere;
- gap: var(--s-3);
-}
-
-.socials {
- display: flex;
- justify-content: center;
- gap: var(--s-1-5);
- grid-area: socials;
- padding-block-start: var(--s-3);
-}
-
-.socialLink {
- padding: var(--s-1);
- border: 1px solid;
- border-radius: 50%;
-}
-
-.extraInfoHeading {
- & > svg {
- width: 0.8rem;
- }
-}
-
-.socialLink {
- & > svg {
- width: 0.9rem;
- }
-}
-
-.socialLinkYoutube {
- border-color: #f00;
- background-color: #ff00002f;
-
- & > svg {
- fill: #f00;
- }
-}
-
-.socialLinkTwitch {
- border-color: #9146ff;
- background-color: #9146ff2f;
-
- & > svg {
- fill: #9146ff;
- }
-}
-
-.socialLinkBattlefy {
- border-color: #de4c5e;
- background-color: #de4c5e2f;
-
- & > svg {
- fill: #de4c5e;
- }
-}
-
-.socialLinkBsky {
- border-color: #1285fe;
- background-color: #1285fe2f;
- display: grid;
- place-items: center;
-
- & path {
- fill: #1285fe;
- }
-}
-
-.extraInfos {
- display: flex;
- max-width: 24rem;
- flex-wrap: wrap;
- gap: var(--s-3);
- margin-inline: auto;
-}
-
-.extraInfo {
- padding: var(--s-1) var(--s-1-5);
- border-radius: var(--radius-box);
- background-color: var(--color-bg-high);
- font-size: var(--font-2xs);
- display: flex;
- align-items: center;
- gap: var(--s-1);
-}
-
-.extraInfoHeading {
- color: var(--color-text-accent);
- font-weight: var(--weight-bold);
-}
-
-.resultsTableWrapper {
- width: 100%;
- overflow-x: auto;
-}
-
-.resultsHeaderActions {
- display: flex;
- align-items: center;
- justify-content: flex-end;
- gap: var(--s-2);
-}
-
-.resultsTableHighlights {
- border: var(--s-2) solid var(--color-bg-high);
- padding-inline: 0 !important;
-
- & > legend {
- margin-inline-start: var(--s-2);
- padding-inline: var(--s-1);
- }
-}
-
-.resultsPlayers {
- display: flex;
- flex-direction: column;
- padding: 0;
- gap: var(--s-3);
- list-style: none;
-}
-
-.weapon {
- padding: var(--s-2);
- border-radius: 100%;
- background-color: var(--color-bg-high);
-}
-
-.buildFilterButton {
- padding: 0 var(--s-1-5) !important;
- gap: var(--s-1);
- min-height: 32px;
-}
-
-.placements {
- display: flex;
- flex-wrap: wrap;
- padding: var(--s-4);
- border-radius: var(--radius-box);
- margin: 0 auto;
- background-color: var(--color-bg-high);
- color: var(--color-text);
- gap: var(--s-6);
- transition: 0.1s ease-in-out background-color;
-
- &:hover {
- background-color: var(--color-bg-higher);
- }
-}
-
-.placementsMode {
- display: flex;
- flex-direction: column;
- align-items: center;
- font-size: var(--font-xs);
- font-weight: var(--weight-semi);
- gap: var(--s-1-5);
-}
-
-@container (width >= 480px) {
- .placements {
- gap: var(--s-10);
- }
-
- .placementsMode {
- font-size: var(--font-sm);
- }
-}
-
-.seasonWeaponContainer {
- display: flex;
- align-items: center;
- font-size: var(--font-sm);
- flex-direction: column;
- font-weight: var(--weight-bold);
- position: relative;
- width: 60px;
-}
-
-.seasonWeaponBorderOuter {
- --degree: 80deg;
- --smoothing: 0.5deg;
- --color: var(--color-text-accent);
-
- display: block;
- content: "";
- height: 60px;
- width: 60px;
- border-radius: 50%;
- background: conic-gradient(
- var(--color) var(--degree),
- transparent calc(var(--degree) + var(--smoothing)) 100%
- );
- position: absolute;
-}
-
-.seasonWeaponBorderOuterStatic {
- height: 60px;
- width: 60px;
- background-color: var(--color-bg-high);
- position: absolute;
- border-radius: 50%;
-}
-
-.seasonWeaponBorderInner {
- height: 84%;
- width: 84%;
- top: 8%;
- left: 8%;
- position: relative;
- border-radius: 50%;
- background: var(--color-bg);
- display: grid;
- place-items: center;
-}
-
-.seasonWeaponCount {
- margin-top: 62px;
-}
-
-.seasonPlayerName {
- width: 64px;
- font-size: var(--font-xs);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- text-align: center;
- font-weight: var(--weight-semi);
-}
-
-.seasonMatch {
- background-color: var(--color-bg-high);
- border-radius: var(--radius-box);
- padding: var(--s-2) var(--s-3);
- color: var(--color-text);
- transition: ease-in 0.2s background-color;
- display: flex;
- flex-direction: column;
- gap: var(--s-4);
-
- &:hover {
- background-color: var(--color-bg-higher);
- }
-}
-
-.seasonMatchWithSubSection {
- border-radius: var(--radius-box) var(--radius-box) 0 0;
-}
-
-.seasonMatchSubSection {
- border-radius: 0 0 var(--radius-box) var(--radius-box);
- background-color: var(--color-bg-high);
- font-size: var(--font-xs);
- text-align: center;
- font-weight: var(--weight-bold);
- display: flex;
- gap: var(--s-3);
- justify-content: center;
- padding-block: var(--s-0-5);
-}
-
-.seasonMatchUser {
- display: flex;
- flex-direction: column;
- font-size: var(--font-xs);
- width: 65px;
- align-items: center;
-}
-
-.seasonMatchUserName {
- max-width: 60px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.seasonMatchScore {
- font-size: var(--font-xl);
- font-weight: var(--weight-bold);
- margin-inline: auto;
-}
-
-.seasonMatchSetResults {
- list-style: none;
- display: flex;
- gap: var(--s-2);
- flex-direction: row;
- flex-wrap: wrap;
- justify-content: center;
- margin: 0 auto;
- padding: 0;
-
- & li {
- width: 28px;
- height: 28px;
- display: grid;
- place-items: center;
- font-size: var(--font-xs);
- font-weight: var(--weight-semi);
- border: 3px solid var(--color-accent);
- border-radius: var(--radius-field);
- color: var(--color-text);
- border-color: var(--color-error);
-
- &[data-is-win="true"] {
- border-color: var(--color-success);
- }
- }
-}
-
-.seasonInfoContainer {
- background-color: var(--color-bg-high);
- padding: var(--s-3) var(--s-2);
- border-radius: var(--radius-box);
-}
-
-.seasonWinrate {
- background-color: var(--color-bg-high);
- padding: var(--s-0-5) var(--s-2);
- border-radius: var(--radius-box);
- font-size: var(--font-xs);
- font-weight: var(--weight-semi);
-}
-
-.seasonWeaponUsageContainer {
- display: flex;
- flex-direction: column;
- gap: var(--s-2);
- width: 300px;
- min-height: 175px;
-}
-
-.seasonWeaponUsageWeaponsContainer {
- display: flex;
- flex-wrap: wrap;
- gap: var(--s-3);
- justify-content: center;
- max-height: 300px;
- overflow-y: auto;
-}
-
-.seasonWeaponUsageWeapon {
- border-radius: 100%;
- background-color: var(--color-bg-high);
- padding: var(--s-1-5);
-}
-
-.seasonTentative {
- text-transform: uppercase;
- font-weight: var(--weight-semi);
- color: var(--color-text-high);
- font-size: var(--font-2xs);
- margin-top: -8px;
- display: flex;
- gap: var(--s-1);
- align-items: center;
- line-height: 1.3;
-}
-
-.seasonTentativeExplanation {
- text-transform: initial;
-}
-
-.highlightsContainer {
- position: relative;
- padding-bottom: 80px; /* Leave space for sticky button */
-}
-
-.highlightsStickyButton {
- position: sticky;
- bottom: 0;
- background-color: var(--color-bg);
- padding: var(--s-2) 0;
- z-index: 10;
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: var(--s-2);
- margin-top: var(--s-4);
-}
diff --git a/app/features/vods/components/VodListing.module.css b/app/features/vods/components/VodListing.module.css
index 326bdfd39..2dd2b62e1 100644
--- a/app/features/vods/components/VodListing.module.css
+++ b/app/features/vods/components/VodListing.module.css
@@ -1,3 +1,10 @@
+.list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--s-6);
+ justify-content: center;
+}
+
.listing {
max-width: 355px;
}
diff --git a/app/features/vods/components/VodListing.tsx b/app/features/vods/components/VodListing.tsx
index f14c43612..ce9d7c379 100644
--- a/app/features/vods/components/VodListing.tsx
+++ b/app/features/vods/components/VodListing.tsx
@@ -66,3 +66,7 @@ function WeaponsPeek({ weapons }: { weapons: ListVod["weapons"] }) {
function youtubeIdToThumbnailUrl(youtubeId: string) {
return `http://img.youtube.com/vi/${youtubeId}/hqdefault.jpg`;
}
+
+export function VodListingList({ children }: { children: React.ReactNode }) {
+ return
{children}
;
+}
diff --git a/app/features/vods/routes/vods.module.css b/app/features/vods/routes/vods.module.css
deleted file mode 100644
index 792282ce5..000000000
--- a/app/features/vods/routes/vods.module.css
+++ /dev/null
@@ -1,6 +0,0 @@
-.listingList {
- display: flex;
- flex-wrap: wrap;
- gap: var(--s-6);
- justify-content: center;
-}
diff --git a/app/features/vods/routes/vods.tsx b/app/features/vods/routes/vods.tsx
index 133d4c3f4..22e0e11a5 100644
--- a/app/features/vods/routes/vods.tsx
+++ b/app/features/vods/routes/vods.tsx
@@ -18,11 +18,10 @@ import { useSearchParamsTyped } from "~/modules/search-params/hooks";
import { metaTags } from "~/utils/remix";
import type { SendouRouteHandle } from "~/utils/remix.server";
import { navIconUrl, VODS_PAGE } from "~/utils/urls";
-import { VodListing } from "../components/VodListing";
+import { VodListing, VodListingList } from "../components/VodListing";
import { loader } from "../loaders/vods.server";
import { videoMatchTypes } from "../vods-constants";
import { vodsSearchParams } from "../vods-search-params";
-import styles from "./vods.module.css";
export { loader };
@@ -60,11 +59,11 @@ export default function VodsSearchPage() {
{data.vods.length > 0 ? (
<>
-
+
{data.vods.map((vod) => (
))}
-
+
{data.pagesCount > 1 ?
: null}
>
) : (
diff --git a/app/form/SendouForm.browser.test.tsx b/app/form/SendouForm.browser.test.tsx
index 1812dbe13..e2eb079b3 100644
--- a/app/form/SendouForm.browser.test.tsx
+++ b/app/form/SendouForm.browser.test.tsx
@@ -4,7 +4,6 @@ import * as v from "valibot";
import { beforeEach, describe, expect, test, vi } from "vitest";
import { userEvent } from "vitest/browser";
import { render } from "vitest-browser-react";
-import labelStyles from "~/components/Label.module.css";
import { FormField } from "./FormField";
import {
array,
@@ -298,8 +297,10 @@ describe("SendouForm", () => {
defaultValues: { bio: "123456789" },
});
- const counter = screen.container.querySelector(`.${labelStyles.value}`);
- expect(counter?.classList.contains(labelStyles.valueWarning)).toBe(true);
+ const counter = screen.container.querySelector(
+ '[data-testid="label-value-counter"]',
+ );
+ expect(counter?.getAttribute("data-length-state")).toBe("warning");
});
test("value counter shows error style when over max length", async () => {
@@ -311,8 +312,10 @@ describe("SendouForm", () => {
defaultValues: { bio: "123456" },
});
- const counter = screen.container.querySelector(`.${labelStyles.value}`);
- expect(counter?.classList.contains(labelStyles.valueError)).toBe(true);
+ const counter = screen.container.querySelector(
+ '[data-testid="label-value-counter"]',
+ );
+ expect(counter?.getAttribute("data-length-state")).toBe("error");
});
test("typing updates value", async () => {
diff --git a/app/form/fields/FormFieldWrapper.module.css b/app/form/fields/FormFieldWrapper.module.css
index e29139999..182e4e6cd 100644
--- a/app/form/fields/FormFieldWrapper.module.css
+++ b/app/form/fields/FormFieldWrapper.module.css
@@ -10,3 +10,12 @@
width: 100%;
}
}
+
+/* the entity-search fields: their trigger button has to fill the field's width */
+.searchRoot {
+ width: 100%;
+
+ & button[class] {
+ width: 100%;
+ }
+}
diff --git a/app/form/fields/FormFieldWrapper.tsx b/app/form/fields/FormFieldWrapper.tsx
index e6ac89551..bd464ebe1 100644
--- a/app/form/fields/FormFieldWrapper.tsx
+++ b/app/form/fields/FormFieldWrapper.tsx
@@ -117,3 +117,19 @@ export function FormFieldWrapper({
);
}
+
+/**
+ * Wrapper for the entity-search fields (user, team, tournament), which render
+ * their own label through the search component instead of taking one here.
+ */
+export function SearchFormFieldWrapper({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+ );
+}
diff --git a/app/form/fields/TeamSearchFormField.tsx b/app/form/fields/TeamSearchFormField.tsx
index 5e650b6ac..cbb12795f 100644
--- a/app/form/fields/TeamSearchFormField.tsx
+++ b/app/form/fields/TeamSearchFormField.tsx
@@ -1,7 +1,10 @@
import { TeamSearch } from "~/components/elements/TeamSearch";
import type { FormFieldProps, TeamSearchFieldOptions } from "../types";
-import { FormFieldMessages, useTranslatedTexts } from "./FormFieldWrapper";
-import styles from "./UserSearchFormField.module.css";
+import {
+ FormFieldMessages,
+ SearchFormFieldWrapper,
+ useTranslatedTexts,
+} from "./FormFieldWrapper";
type TeamSearchFormFieldProps = FormFieldProps<"team-search"> &
TeamSearchFieldOptions & {
@@ -26,21 +29,19 @@ export function TeamSearchFormField({
});
return (
-
-
- {
- onChange(team?.id ?? null);
- onTeamSelected?.(team);
- }}
- onBlur={() => onBlur?.()}
- label={translatedLabel}
- isRequired={required}
- isDisabled={disabled}
- />
-
-
-
+
+ {
+ onChange(team?.id ?? null);
+ onTeamSelected?.(team);
+ }}
+ onBlur={() => onBlur?.()}
+ label={translatedLabel}
+ isRequired={required}
+ isDisabled={disabled}
+ />
+
+
);
}
diff --git a/app/form/fields/TournamentSearchFormField.tsx b/app/form/fields/TournamentSearchFormField.tsx
index 89f7aaf28..669fb6224 100644
--- a/app/form/fields/TournamentSearchFormField.tsx
+++ b/app/form/fields/TournamentSearchFormField.tsx
@@ -3,8 +3,11 @@ import {
type TournamentSearchItem,
} from "~/components/elements/TournamentSearch";
import type { FormFieldProps } from "../types";
-import { FormFieldMessages, useTranslatedTexts } from "./FormFieldWrapper";
-import styles from "./UserSearchFormField.module.css";
+import {
+ FormFieldMessages,
+ SearchFormFieldWrapper,
+ useTranslatedTexts,
+} from "./FormFieldWrapper";
type TournamentSearchFormFieldProps = FormFieldProps<"tournament-search"> & {
value: number | null;
@@ -32,22 +35,20 @@ export function TournamentSearchFormField({
});
return (
-
-
- {
- onChange(tournament?.id ?? null);
- onTournamentSelected?.(tournament);
- }}
- onBlur={() => onBlur?.()}
- label={translatedLabel}
- isRequired={required}
- isDisabled={disabled}
- />
-
-
-
+
+ {
+ onChange(tournament?.id ?? null);
+ onTournamentSelected?.(tournament);
+ }}
+ onBlur={() => onBlur?.()}
+ label={translatedLabel}
+ isRequired={required}
+ isDisabled={disabled}
+ />
+
+
);
}
diff --git a/app/form/fields/UserSearchFormField.module.css b/app/form/fields/UserSearchFormField.module.css
deleted file mode 100644
index 9d3f6984f..000000000
--- a/app/form/fields/UserSearchFormField.module.css
+++ /dev/null
@@ -1,7 +0,0 @@
-.root {
- width: 100%;
-
- & button[class] {
- width: 100%;
- }
-}
diff --git a/app/form/fields/UserSearchFormField.tsx b/app/form/fields/UserSearchFormField.tsx
index a9bf1fb18..671893d3f 100644
--- a/app/form/fields/UserSearchFormField.tsx
+++ b/app/form/fields/UserSearchFormField.tsx
@@ -1,7 +1,10 @@
import { UserSearch } from "~/components/elements/UserSearch";
import type { FormFieldProps, UserSearchFieldOptions } from "../types";
-import { FormFieldMessages, useTranslatedTexts } from "./FormFieldWrapper";
-import styles from "./UserSearchFormField.module.css";
+import {
+ FormFieldMessages,
+ SearchFormFieldWrapper,
+ useTranslatedTexts,
+} from "./FormFieldWrapper";
type UserSearchFormFieldProps = FormFieldProps<"user-search"> &
UserSearchFieldOptions & {
@@ -27,21 +30,19 @@ export function UserSearchFormField({
});
return (
-
-
- {
- onChange(user?.id ?? null);
- onUserSelected?.(user);
- }}
- onBlur={() => onBlur?.()}
- label={translatedLabel}
- isRequired={required}
- isDisabled={disabled}
- />
-
-
-
+
+ {
+ onChange(user?.id ?? null);
+ onUserSelected?.(user);
+ }}
+ onBlur={() => onBlur?.()}
+ label={translatedLabel}
+ isRequired={required}
+ isDisabled={disabled}
+ />
+
+
);
}