diff --git a/README.md b/README.md index d3e757da5..d46c337ac 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ Another key objective is to bridge the gap between casual and competitive player - [Git](https://git-scm.com/) - [Node.js v22](https://nodejs.org/en) +- [pnpm](https://pnpm.io/installation) Optionally [nvm](https://github.com/nvm-sh/nvm) can be convenient for managing multiple Node.js installs @@ -66,6 +67,7 @@ First verify you have Node.js and git installed: ```bash node --version git --version +pnpm --version ``` You should see something like: @@ -73,6 +75,7 @@ You should see something like: ``` v22.13.0 git version 2.39.5 (Apple Git-154) +10.33.0 ``` (if not then go back to "Prerequisites" and install what is missing) diff --git a/app/components/layout/TopNavMenus.tsx b/app/components/layout/TopNavMenus.tsx index 18b40a493..efcb3b078 100644 --- a/app/components/layout/TopNavMenus.tsx +++ b/app/components/layout/TopNavMenus.tsx @@ -73,9 +73,10 @@ function CategoryMenu({ const [isOpen, setIsOpen] = useState(false); const user = useUser(); const isStaff = user?.roles.includes("STAFF") ?? false; + const showStaffOnly = isStaff || process.env.NODE_ENV === "development"; const visibleItems = category.items.filter( - (item) => !("staffOnly" in item) || isStaff, + (item) => !("staffOnly" in item) || showStaffOnly, ); return ( diff --git a/app/features/art/routes/art.tsx b/app/features/art/routes/art.tsx index 8372d066a..c0daf4c80 100644 --- a/app/features/art/routes/art.tsx +++ b/app/features/art/routes/art.tsx @@ -68,7 +68,7 @@ export const meta: MetaFunction = (args) => { }; export default function ArtPage() { - const { t } = useTranslation(["art", "common"]); + const { t } = useTranslation(["art", "common", "forms"]); const data = useLoaderData(); const [searchParams, setSearchParams] = useSearchParams(); const switchId = React.useId(); @@ -100,7 +100,7 @@ export default function ArtPage() { id={switchId} />
>; + +export const buildsLimitSearchParam = z.coerce + .number() + .int() + .positive() + .catch(BUILDS_PAGE_BATCH_SIZE) + .transform((value) => Math.min(value, BUILDS_PAGE_MAX_BUILDS)); diff --git a/app/features/builds/loaders/builds.$slug.server.ts b/app/features/builds/loaders/builds.$slug.server.ts index 6e88bd4e6..6a62f1687 100644 --- a/app/features/builds/loaders/builds.$slug.server.ts +++ b/app/features/builds/loaders/builds.$slug.server.ts @@ -6,11 +6,13 @@ import { weaponNameSlugToId } from "~/utils/unslugify.server"; import { mySlugify } from "~/utils/urls"; import * as BuildRepository from "../BuildRepository.server"; import { - BUILDS_PAGE_BATCH_SIZE, BUILDS_PAGE_MAX_BUILDS, FILTER_SEARCH_PARAM_KEY, } from "../builds-constants"; -import { buildFiltersSearchParams } from "../builds-schemas.server"; +import { + buildFiltersSearchParams, + buildsLimitSearchParam, +} from "../builds-schemas.server"; import { filterBuilds } from "../core/filter.server"; export const loader = async ({ request, params }: LoaderFunctionArgs) => { @@ -25,10 +27,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { } const url = new URL(request.url); - const limit = Math.min( - Number(url.searchParams.get("limit") ?? BUILDS_PAGE_BATCH_SIZE), - BUILDS_PAGE_MAX_BUILDS, - ); + const limit = buildsLimitSearchParam.parse(url.searchParams.get("limit")); const weaponName = t(`weapons:MAIN_${weaponId}`); diff --git a/app/features/calendar/calendar-types.ts b/app/features/calendar/calendar-types.ts index 263e67b87..5782ec19b 100644 --- a/app/features/calendar/calendar-types.ts +++ b/app/features/calendar/calendar-types.ts @@ -46,13 +46,13 @@ export interface ShowcaseCalendarEvent extends CommonEvent { hidden: boolean; isFinalized: boolean; minMembersPerTeam: number; - firstPlacer: { + firstPlacers: Array<{ teamName: string; logoUrl: string | null; members: (CommonUser & { country: Tables["User"]["country"] })[]; notShownMembersCount: number; div: string | null; - } | null; + }>; hasVods?: boolean; } diff --git a/app/features/calendar/components/TournamentCard.tsx b/app/features/calendar/components/TournamentCard.tsx index a90b5cd81..6682fa624 100644 --- a/app/features/calendar/components/TournamentCard.tsx +++ b/app/features/calendar/components/TournamentCard.tsx @@ -57,7 +57,8 @@ export function TournamentCard({ return (
0, })} data-testid="tournament-card" > @@ -117,15 +118,17 @@ export function TournamentCard({
) : null} - {isShowcase && tournament.firstPlacer ? ( + {isShowcase && tournament.firstPlacers.length > 0 ? ( ) : null}
- {isShowcase && tournament.firstPlacer && isCensored(tournament.id) ? ( + {isShowcase && + tournament.firstPlacers.length > 0 && + isCensored(tournament.id) ? ( reveal(tournament.id)} /> ) : null} {isShowcase && "hasVods" in tournament && tournament.hasVods ? ( @@ -157,20 +160,52 @@ export function TournamentCard({ } function TournamentFirstPlacers({ - firstPlacer, + firstPlacers, censored, }: { - firstPlacer: NonNullable; + firstPlacers: ShowcaseCalendarEvent["firstPlacers"]; + censored: boolean; +}) { + if (firstPlacers.length > 1) { + return ( +
+
+ {firstPlacers.map((placer) => ( + + ))} +
+
+ ); + } + + const placer = firstPlacers[0]; + + return ( +
+ +
+ ); +} + +function TournamentFirstPlacerWithMembers({ + placer, + censored, +}: { + placer: ShowcaseCalendarEvent["firstPlacers"][number]; censored: boolean; }) { const { t } = useTranslation(["front"]); return ( -
+ <>
- {!censored && firstPlacer.logoUrl ? ( + {!censored && placer.logoUrl ? ( - {censored ? "???" : firstPlacer.teamName} + {censored ? "???" : placer.teamName}
{t("front:showcase.card.winner")} - {firstPlacer.div ? ` (${firstPlacer.div})` : null} + {placer.div ? ` (${placer.div})` : null}
- {firstPlacer.members.map((member) => ( + {placer.members.map((member) => (
{!censored && member.country ? ( @@ -195,12 +230,34 @@ function TournamentFirstPlacers({ {censored ? "???" : member.username}{" "}
))} - {!censored && firstPlacer.notShownMembersCount > 0 ? ( + {!censored && placer.notShownMembersCount > 0 ? (
- +{firstPlacer.notShownMembersCount} + +{placer.notShownMembersCount}
) : null}
+ + ); +} + +function TournamentFirstPlacerTeamNameOnly({ + placer, + censored, +}: { + placer: ShowcaseCalendarEvent["firstPlacers"][number]; + censored: boolean; +}) { + const { t } = useTranslation(["front"]); + + return ( +
+ + {censored ? "???" : placer.teamName} + +
+ {t("front:showcase.card.winner")} + {placer.div ? ` (${placer.div})` : null} +
); } diff --git a/app/features/chat/ChatSystemMessage.server.ts b/app/features/chat/ChatSystemMessage.server.ts index 18cd53bf1..69da91c06 100644 --- a/app/features/chat/ChatSystemMessage.server.ts +++ b/app/features/chat/ChatSystemMessage.server.ts @@ -141,6 +141,10 @@ export async function setMetadata(args: SetMetadataArgs) { args.participantUserIds, ); + logger.debug( + `Setting chat room metadata for ${args.chatCode} (participants: ${participantsKey})`, + ); + return void fetch(process.env.SKALOP_SYSTEM_MESSAGE_URL, { method: "POST", body: JSON.stringify({ diff --git a/app/features/front-page/core/ShowcaseTournaments.server.ts b/app/features/front-page/core/ShowcaseTournaments.server.ts index 2064eef28..792652114 100644 --- a/app/features/front-page/core/ShowcaseTournaments.server.ts +++ b/app/features/front-page/core/ShowcaseTournaments.server.ts @@ -1,10 +1,12 @@ import cachified from "@epic-web/cachified"; +import * as R from "remeda"; import type { ShowcaseCalendarEvent } from "~/features/calendar/calendar-types"; import * as TournamentRepository from "~/features/tournament/TournamentRepository.server"; import { getBracketProgressionLabel, tournamentIsRanked, } from "~/features/tournament/tournament-utils"; +import * as Progression from "~/features/tournament-bracket/core/Progression"; import { getTentativeTier } from "~/features/tournament-organization/core/tentativeTiers.server"; import { cache, IN_MILLISECONDS, ttl } from "~/utils/cache.server"; import { @@ -186,16 +188,21 @@ function deleteExtraResults(tournaments: ShowcaseCalendarEvent[]) { const threeDaysAgo = databaseTimestampThreeDaysAgo(); const nonResults = tournaments.filter( (tournament) => - !tournament.firstPlacer && + tournament.firstPlacers.length === 0 && !tournament.isFinalized && tournament.startTime > threeDaysAgo, ); const rankedResults = tournaments - .filter((tournament) => tournament.firstPlacer && tournament.isRanked) + .filter( + (tournament) => tournament.firstPlacers.length > 0 && tournament.isRanked, + ) .sort((a, b) => showcaseScore(b) - showcaseScore(a)); const nonRankedResults = tournaments - .filter((tournament) => tournament.firstPlacer && !tournament.isRanked) + .filter( + (tournament) => + tournament.firstPlacers.length > 0 && !tournament.isRanked, + ) .sort((a, b) => showcaseScore(b) - showcaseScore(a)); const rankedResultsToKeep = rankedResults.slice(0, 4); @@ -283,7 +290,7 @@ const MEMBERS_TO_SHOW = 5; function mapTournamentFromDB( tournament: TournamentRepository.ForShowcase, ): ShowcaseCalendarEvent { - const highestDivWinners = resolveHighestDivisionWinners(tournament); + const firstPlacers = resolveFirstPlacers(tournament); const tentativeTier = tournament.tier === null && @@ -320,41 +327,35 @@ function mapTournamentFromDB( minMembersPerTeam: tournament.settings.minMembersPerTeam ?? 4, modes: null, hasVods: (tournament.vodCount ?? 0) > 0, - firstPlacer: - highestDivWinners.length > 0 - ? { - teamName: highestDivWinners[0].teamName, - logoUrl: - highestDivWinners[0].teamLogoUrl ?? - highestDivWinners[0].pickupAvatarUrl, - div: highestDivWinners[0].div, - members: highestDivWinners - .slice(0, MEMBERS_TO_SHOW) - .map((firstPlacer) => ({ - customUrl: firstPlacer.customUrl, - discordAvatar: firstPlacer.discordAvatar, - discordId: firstPlacer.discordId, - id: firstPlacer.id, - username: firstPlacer.username, - country: firstPlacer.country, - })), - notShownMembersCount: - highestDivWinners.length > MEMBERS_TO_SHOW - ? highestDivWinners.length - MEMBERS_TO_SHOW - : 0, - } - : null, + firstPlacers, }; } -function resolveHighestDivisionWinners( +type FirstPlacerRow = TournamentRepository.ForShowcase["firstPlacers"][number]; + +function resolveFirstPlacers( tournament: TournamentRepository.ForShowcase, -) { +): ShowcaseCalendarEvent["firstPlacers"] { if (tournament.firstPlacers.length === 0) { return []; } - // not a "many starting brackets" tournament + if ( + Progression.hasAbDivisionsFinals(tournament.settings.bracketProgression) + ) { + const byDiv = R.groupBy(tournament.firstPlacers, (p) => p.div ?? ""); + return Object.values(byDiv) + .map((rows) => buildFirstPlacerEntry(rows, { withMembers: false })) + .sort((a, b) => (a.div ?? "").localeCompare(b.div ?? "")); + } + + const winnerRows = winnersOfHighestDivision(tournament); + return [buildFirstPlacerEntry(winnerRows, { withMembers: true })]; +} + +function winnersOfHighestDivision( + tournament: TournamentRepository.ForShowcase, +): FirstPlacerRow[] { if (tournament.firstPlacers.every((p) => p.div === null)) { return tournament.firstPlacers; } @@ -363,8 +364,6 @@ function resolveHighestDivisionWinners( 0, tournament.settings.bracketProgression, ); - - // Filter to only include winners from the highest division const highestDivWinners = tournament.firstPlacers.filter( (p) => p.div === highestDivName, ); @@ -374,6 +373,34 @@ function resolveHighestDivisionWinners( : tournament.firstPlacers; } +function buildFirstPlacerEntry( + rows: FirstPlacerRow[], + { withMembers }: { withMembers: boolean }, +): ShowcaseCalendarEvent["firstPlacers"][number] { + const first = rows[0]; + const members = withMembers + ? rows.slice(0, MEMBERS_TO_SHOW).map((row) => ({ + customUrl: row.customUrl, + discordAvatar: row.discordAvatar, + discordId: row.discordId, + id: row.id, + username: row.username, + country: row.country, + })) + : []; + + return { + teamName: first.teamName, + logoUrl: first.teamLogoUrl ?? first.pickupAvatarUrl, + div: first.div, + members, + notShownMembersCount: + withMembers && rows.length > MEMBERS_TO_SHOW + ? rows.length - MEMBERS_TO_SHOW + : 0, + }; +} + function databaseTimestampWeekFromNow() { const now = new Date(); diff --git a/app/features/info/routes/contributions.tsx b/app/features/info/routes/contributions.tsx index 106fb2b21..dfcf6e1ad 100644 --- a/app/features/info/routes/contributions.tsx +++ b/app/features/info/routes/contributions.tsx @@ -23,6 +23,7 @@ export const handle: SendouRouteHandle = { }; const PROGRAMMERS = [ + "hfcRed", "DoubleCookies", "ElementUser", "remmycat", diff --git a/app/features/mmr/mmr-utils.ts b/app/features/mmr/mmr-utils.ts index 1678cd07d..504af45be 100644 --- a/app/features/mmr/mmr-utils.ts +++ b/app/features/mmr/mmr-utils.ts @@ -1,5 +1,10 @@ -import type { Rating, Team } from "node_modules/openskill/dist/types"; -import { rate as openskillRate, ordinal, rating } from "openskill"; +import { + rate as openskillRate, + ordinal, + type Rating, + rating, + type Team, +} from "openskill"; import invariant from "~/utils/invariant"; import type { TierName } from "./mmr-constants"; import { TIERS } from "./mmr-constants"; diff --git a/app/features/notifications/components/NotificationList.tsx b/app/features/notifications/components/NotificationList.tsx index 3d2b29d99..2ec373477 100644 --- a/app/features/notifications/components/NotificationList.tsx +++ b/app/features/notifications/components/NotificationList.tsx @@ -4,7 +4,6 @@ import { Link } from "react-router"; import { Image } from "~/components/Image"; import type { LoaderNotification } from "~/components/layout/NotificationPopover"; import { - mapMetaForTranslation, notificationLink, notificationNavIcon, } from "~/features/notifications/notifications-utils"; @@ -23,7 +22,7 @@ export function NotificationItem({ notification: LoaderNotification; onClose?: () => void; }) { - const { t, i18n } = useTranslation(["common"]); + const { t } = useTranslation(["common"]); return ( : null}
- {t( - `common:notifications.text.${notification.type}`, - mapMetaForTranslation(notification, i18n.language), - )} + {t(`common:notifications.text.${notification.type}`, notification.meta)}
{formatDistance( diff --git a/app/features/notifications/core/notify.server.test.ts b/app/features/notifications/core/notify.server.test.ts index 3cb31ac2a..3e0126f52 100644 --- a/app/features/notifications/core/notify.server.test.ts +++ b/app/features/notifications/core/notify.server.test.ts @@ -139,7 +139,7 @@ describe("notify()", () => { userIds: [10, 11], notification: { type: "SCRIM_SCHEDULED", - meta: { id: 1, at: 123 }, + meta: { id: 1, opponentTeamName: "Alpha" }, }, }); @@ -147,7 +147,7 @@ describe("notify()", () => { userIds: [10, 11], notification: { type: "SCRIM_CANCELED", - meta: { id: 1, at: 123 }, + meta: { id: 1, opponentTeamName: "Alpha" }, }, }); @@ -346,7 +346,7 @@ describe("notify() - web push notifications", () => { expect(mockSendNotification).not.toHaveBeenCalled(); }); - test("formats timestamp for scrim notifications", async () => { + test("includes opponent team name for scrim notifications", async () => { const mockSubscription = { endpoint: "https://fcm.googleapis.com/fcm/send/test", keys: { @@ -367,13 +367,11 @@ describe("notify() - web push notifications", () => { mockWebPushEnabled.value = true; - const testTimestamp = new Date("2024-01-15T15:30:00Z").getTime(); - await notify({ userIds: [1], notification: { type: "SCRIM_SCHEDULED", - meta: { id: 1, at: testTimestamp }, + meta: { id: 1, opponentTeamName: "Sendou's pickup" }, }, }); @@ -383,8 +381,6 @@ describe("notify() - web push notifications", () => { const payload = JSON.parse(callArgs); expect(payload.title).toBe("Scrim Scheduled"); - expect(payload.body).toMatch( - /New scrim scheduled at \d+\/\d+, \d+:\d+ (AM|PM)/, - ); + expect(payload.body).toBe("New scrim scheduled vs. Sendou's pickup"); }); }); diff --git a/app/features/notifications/core/notify.server.ts b/app/features/notifications/core/notify.server.ts index 1f97022d7..b1e7b13e5 100644 --- a/app/features/notifications/core/notify.server.ts +++ b/app/features/notifications/core/notify.server.ts @@ -7,10 +7,7 @@ import { i18next } from "../../../modules/i18n/i18next.server"; import { logger } from "../../../utils/logger"; import * as NotificationRepository from "../NotificationRepository.server"; import type { Notification } from "../notifications-types"; -import { - mapMetaForTranslation, - notificationLink, -} from "../notifications-utils"; +import { notificationLink } from "../notifications-utils"; import webPush, { webPushEnabled } from "./webPush.server"; const NOTIFICATION_URGENCY: Record = { @@ -168,10 +165,12 @@ function pushNotificationOptions( ): Parameters[1] & { title: string; } { - const meta = mapMetaForTranslation(notification, "en-US"); return { title: t(`common:notifications.title.${notification.type}`), - body: t(`common:notifications.text.${notification.type}`, meta), + body: t( + `common:notifications.text.${notification.type}`, + notification.meta, + ), icon: notification.pictureUrl ?? "/static-assets/img/app-icon.png", data: { url: notificationLink(notification) }, }; diff --git a/app/features/notifications/notifications-types.ts b/app/features/notifications/notifications-types.ts index 4519d9e1e..f213d2a1e 100644 --- a/app/features/notifications/notifications-types.ts +++ b/app/features/notifications/notifications-types.ts @@ -62,9 +62,15 @@ export type Notification = > | NotificationItem<"SEASON_STARTED", { seasonNth: number }> | NotificationItem<"SCRIM_NEW_REQUEST", { fromUsername: string }> - | NotificationItem<"SCRIM_SCHEDULED", { id: number; at: number }> - | NotificationItem<"SCRIM_CANCELED", { id: number; at: number }> - | NotificationItem<"SCRIM_STARTING_SOON", { id: number; at: number }> + | NotificationItem< + "SCRIM_SCHEDULED", + { id: number; opponentTeamName: string } + > + | NotificationItem<"SCRIM_CANCELED", { id: number; opponentTeamName: string }> + | NotificationItem< + "SCRIM_STARTING_SOON", + { id: number; opponentTeamName: string } + > | NotificationItem<"COMMISSIONS_CLOSED", { discordId: string }> | NotificationItem<"FRIEND_REQUEST_RECEIVED", { senderUsername: string }> | NotificationItem< diff --git a/app/features/notifications/notifications-utils.ts b/app/features/notifications/notifications-utils.ts index be53272be..642cb87a6 100644 --- a/app/features/notifications/notifications-utils.ts +++ b/app/features/notifications/notifications-utils.ts @@ -107,27 +107,3 @@ export const notificationLink = (notification: Notification) => { assertUnreachable(notification); } }; - -/** Takes the `meta` object of a notification and transforms it (if needed) to show the translated string to user */ -export const mapMetaForTranslation = ( - notification: Notification, - language: string, -) => { - if ( - notification.type === "SCRIM_SCHEDULED" || - notification.type === "SCRIM_CANCELED" || - notification.type === "SCRIM_STARTING_SOON" - ) { - return { - ...notification.meta, - timeString: new Date(notification.meta.at).toLocaleString(language, { - day: "numeric", - month: "numeric", - hour: "numeric", - minute: "numeric", - }), - }; - } - - return notification.meta; -}; diff --git a/app/features/scrims/actions/scrims.$id.server.ts b/app/features/scrims/actions/scrims.$id.server.ts index aa45f25c7..d78e2c1c2 100644 --- a/app/features/scrims/actions/scrims.$id.server.ts +++ b/app/features/scrims/actions/scrims.$id.server.ts @@ -8,10 +8,7 @@ import { parseRequestPayload, } from "~/utils/remix.server"; import { idObject } from "~/utils/zod"; -import { - databaseTimestampToDate, - databaseTimestampToJavascriptTimestamp, -} from "../../../utils/dates"; +import { databaseTimestampToDate } from "../../../utils/dates"; import { errorToast } from "../../../utils/remix.server"; import { requireUser } from "../../auth/core/user.server"; import * as Scrim from "../core/Scrim"; @@ -42,17 +39,29 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { reason: data.reason, }); - notify({ - userIds: Scrim.participantIdsListFromAccepted(post), - defaultSeenUserIds: [user.id], - notification: { - type: "SCRIM_CANCELED", - meta: { - id: post.id, - at: databaseTimestampToJavascriptTimestamp(Scrim.getStartTime(post)), + const acceptedRequest = post.requests.find((r) => r.isAccepted); + if (acceptedRequest) { + const postTeamName = Scrim.sideDisplayName(post); + const requestTeamName = Scrim.sideDisplayName(acceptedRequest); + + notify({ + userIds: post.users.map((m) => m.id), + defaultSeenUserIds: [user.id], + notification: { + type: "SCRIM_CANCELED", + meta: { id: post.id, opponentTeamName: requestTeamName }, }, - }, - }); + }); + + notify({ + userIds: acceptedRequest.users.map((m) => m.id), + defaultSeenUserIds: [user.id], + notification: { + type: "SCRIM_CANCELED", + meta: { id: post.id, opponentTeamName: postTeamName }, + }, + }); + } return null; }; diff --git a/app/features/scrims/actions/scrims.server.ts b/app/features/scrims/actions/scrims.server.ts index 260bf369b..e1a29dff1 100644 --- a/app/features/scrims/actions/scrims.server.ts +++ b/app/features/scrims/actions/scrims.server.ts @@ -11,7 +11,6 @@ import * as UserRepository from "~/features/user-page/UserRepository.server"; import { requirePermission } from "~/modules/permissions/guards.server"; import { databaseTimestampToDate, - databaseTimestampToJavascriptTimestamp, dateToDatabaseTimestamp, } from "~/utils/dates"; import { ConcurrentModificationError } from "~/utils/errors"; @@ -157,18 +156,24 @@ export const action = async ({ request }: ActionFunctionArgs) => { }); } + const postTeamName = Scrim.sideDisplayName(post); + const requestTeamName = Scrim.sideDisplayName(request); + notify({ - userIds: [ - ...post.users.map((m) => m.id), - ...request.users.map((m) => m.id), - ], + userIds: post.users.map((m) => m.id), defaultSeenUserIds: [user.id], notification: { type: "SCRIM_SCHEDULED", - meta: { - id: post.id, - at: databaseTimestampToJavascriptTimestamp(request.at ?? post.at), - }, + meta: { id: post.id, opponentTeamName: requestTeamName }, + }, + }); + + notify({ + userIds: request.users.map((m) => m.id), + defaultSeenUserIds: [user.id], + notification: { + type: "SCRIM_SCHEDULED", + meta: { id: post.id, opponentTeamName: postTeamName }, }, }); diff --git a/app/features/scrims/core/Scrim.test.ts b/app/features/scrims/core/Scrim.test.ts index afdf51f9f..602a96b48 100644 --- a/app/features/scrims/core/Scrim.test.ts +++ b/app/features/scrims/core/Scrim.test.ts @@ -1,7 +1,11 @@ import { describe, expect, it } from "vitest"; import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates"; import type { ScrimFilters, ScrimPost } from "../scrims-types"; -import { applyFilters, participantIdsListFromAccepted } from "./Scrim"; +import { + applyFilters, + participantIdsListFromAccepted, + sideDisplayName, +} from "./Scrim"; type MockUser = { id: number }; type MockRequest = { isAccepted: boolean; users: MockUser[] }; @@ -78,6 +82,27 @@ describe("participantIdsListFromAccepted", () => { }); }); +describe("sideDisplayName", () => { + it("returns the team name when team is set", () => { + const result = sideDisplayName({ + team: { name: "Team Olive" }, + users: [{ username: "sendou", isOwner: true }], + }); + expect(result).toBe("Team Olive"); + }); + + it("falls back to {owner}'s pickup when team is null", () => { + const result = sideDisplayName({ + team: null, + users: [ + { username: "alice", isOwner: false }, + { username: "sendou", isOwner: true }, + ], + }); + expect(result).toBe("sendou's pickup"); + }); +}); + describe("applyFilters", () => { function createPostForFilters( at: Date, diff --git a/app/features/scrims/core/Scrim.ts b/app/features/scrims/core/Scrim.ts index e5ca8f842..1a45a425d 100644 --- a/app/features/scrims/core/Scrim.ts +++ b/app/features/scrims/core/Scrim.ts @@ -50,6 +50,19 @@ export function getStartTime(post: ScrimPost): number { return acceptedRequest?.at ?? post.at; } +/** + * Returns a display name for a scrim side: the team name when set, + * otherwise "{ownerUsername}'s pickup". + */ +export function sideDisplayName(side: { + team: { name: string } | null; + users: Array<{ username: string; isOwner: boolean }>; +}): string { + if (side.team) return side.team.name; + const owner = side.users.find((u) => u.isOwner) ?? side.users[0]; + return `${owner.username}'s pickup`; +} + export function applyFilters(post: ScrimPost, filters: ScrimFilters): boolean { const hasMinFilter = filters.divs?.min !== null; const hasMaxFilter = filters.divs?.max !== null; diff --git a/app/features/sendouq-match/SQMatchRepository.server.test.ts b/app/features/sendouq-match/SQMatchRepository.server.test.ts index 98f5993ba..6ad0f90f2 100644 --- a/app/features/sendouq-match/SQMatchRepository.server.test.ts +++ b/app/features/sendouq-match/SQMatchRepository.server.test.ts @@ -447,4 +447,36 @@ describe("cancelMatch", () => { expect(result.status).toBe("CANT_CANCEL"); expect(result.shouldRefreshCaches).toBe(false); }); + + test("admin cancel locks match without applying SP changes", async () => { + const alphaGroupId = await createGroup([1, 2, 3, 4]); + const bravoGroupId = await createGroup([5, 6, 7, 8]); + const match = await createMatch(alphaGroupId, bravoGroupId); + + const adminUserId = 1; + const result = await SQMatchRepository.cancelMatch({ + matchId: match.id, + reportedByUserId: adminUserId, + isAdminReport: true, + }); + + expect(result.status).toBe("CANCEL_CONFIRMED"); + expect(result.shouldRefreshCaches).toBe(true); + + const alphaGroup = await fetchGroup(alphaGroupId); + const bravoGroup = await fetchGroup(bravoGroupId); + expect(alphaGroup?.status).toBe("INACTIVE"); + expect(bravoGroup?.status).toBe("INACTIVE"); + + const skills = await fetchSkills(match.id); + const realSkills = skills.filter((s) => s.season !== -1); + expect(realSkills).toHaveLength(0); + expect(skills).toHaveLength(1); + expect(skills[0].season).toBe(-1); + + const maps = await fetchMapResults(match.id); + for (const map of maps) { + expect(map.winnerGroupId).toBeNull(); + } + }); }); diff --git a/app/features/sendouq-match/SQMatchRepository.server.ts b/app/features/sendouq-match/SQMatchRepository.server.ts index f2bbffece..a970c518c 100644 --- a/app/features/sendouq-match/SQMatchRepository.server.ts +++ b/app/features/sendouq-match/SQMatchRepository.server.ts @@ -16,8 +16,10 @@ import { concatUserSubmittedImagePrefix, tournamentLogoWithDefault, } from "~/utils/kysely.server"; +import { errorIsSqliteUniqueConstraintFailure } from "~/utils/sql"; import type { Unpacked } from "~/utils/types"; import { FULL_GROUP_SIZE } from "../sendouq/q-constants"; +import { SendouQError } from "../sendouq/q-utils.server"; import * as SQGroupRepository from "../sendouq/SQGroupRepository.server"; import { MATCHES_PER_SEASONS_PAGE } from "../user-page/user-page-constants"; import { compareMatchToReportedScores } from "./core/match.server"; @@ -438,7 +440,15 @@ export function create({ memento: JSON.stringify(memento), }) .returningAll() - .executeTakeFirstOrThrow(); + .executeTakeFirstOrThrow() + .catch((error) => { + // race: another manager matched one of the two groups first, tripping the + // unique constraint on GroupMatch.alphaGroupId / bravoGroupId + if (errorIsSqliteUniqueConstraintFailure(error)) { + throw new SendouQError("Group is already in a match"); + } + throw error; + }); await trx .insertInto("GroupMatchMap") @@ -790,13 +800,25 @@ export async function reportScore({ export async function cancelMatch({ matchId, reportedByUserId, + isAdminReport, }: { matchId: number; reportedByUserId: number; + isAdminReport?: boolean; }): Promise { const match = await findById(matchId); invariant(match, "Match not found"); + if (isAdminReport) { + await db.transaction().execute(async (trx) => { + await updateScore({ matchId, reportedByUserId, winners: [] }, trx); + await SQGroupRepository.setAsInactive(match.groupAlpha.id, trx); + await SQGroupRepository.setAsInactive(match.groupBravo.id, trx); + await lockMatchWithoutSkillChange(match.id, trx); + }); + return { status: "CANCEL_CONFIRMED", shouldRefreshCaches: true }; + } + const members = buildMembers(match); const reporterGroupId = members.find( (m) => m.id === reportedByUserId, diff --git a/app/features/sendouq-match/core/skills.server.ts b/app/features/sendouq-match/core/skills.server.ts index c4018631d..3304d6573 100644 --- a/app/features/sendouq-match/core/skills.server.ts +++ b/app/features/sendouq-match/core/skills.server.ts @@ -1,5 +1,4 @@ -import type { Rating } from "node_modules/openskill/dist/types"; -import { ordinal } from "openskill"; +import { ordinal, type Rating } from "openskill"; import type { GroupSkillDifference, Tables, diff --git a/app/features/tier-list-maker/components/TierRow.module.css b/app/features/tier-list-maker/components/TierRow.module.css index c413db4bd..ab2e091a0 100644 --- a/app/features/tier-list-maker/components/TierRow.module.css +++ b/app/features/tier-list-maker/components/TierRow.module.css @@ -28,7 +28,9 @@ .tierName { color: var(--color-text-inverse); line-height: 1.2; - white-space: nowrap; + max-width: 90px; + overflow-wrap: break-word; + word-wrap: break-word; } .popupContent { diff --git a/app/features/tier-list-maker/routes/tier-list-maker.module.css b/app/features/tier-list-maker/routes/tier-list-maker.module.css index 1d160f435..d12c1c2dd 100644 --- a/app/features/tier-list-maker/routes/tier-list-maker.module.css +++ b/app/features/tier-list-maker/routes/tier-list-maker.module.css @@ -70,7 +70,6 @@ font-weight: var(--weight-bold); color: var(--color-text-high); text-transform: lowercase; - white-space: nowrap; } .authorInfo { @@ -83,5 +82,4 @@ font-weight: var(--weight-bold); font-size: var(--font-xs); color: var(--color-text); - white-space: nowrap; } diff --git a/app/features/tournament-bracket/core/Bracket/RoundRobinBracket.ts b/app/features/tournament-bracket/core/Bracket/RoundRobinBracket.ts index 5b13a66cb..332517d0f 100644 --- a/app/features/tournament-bracket/core/Bracket/RoundRobinBracket.ts +++ b/app/features/tournament-bracket/core/Bracket/RoundRobinBracket.ts @@ -1,5 +1,6 @@ import * as R from "remeda"; import type { Tables } from "~/db/tables"; +import * as Standings from "~/features/tournament/core/Standings"; import type { TournamentManagerDataSet } from "~/modules/brackets-manager/types"; import invariant from "~/utils/invariant"; import { logger } from "~/utils/logger"; @@ -278,22 +279,8 @@ export class RoundRobinBracket extends Bracket { return 0; }); - let lastPlacement = 0; - let currentPlacement = 1; - let teamsEncountered = 0; return this.standingsWithoutNonParticipants( - sorted.map((team) => { - if (team.placement !== lastPlacement) { - lastPlacement = team.placement; - currentPlacement = teamsEncountered + 1; - } - teamsEncountered++; - return { - ...team, - placement: currentPlacement, - stats: team.stats, - }; - }), + Standings.reNumberPlacements(sorted), ); } diff --git a/app/features/tournament-bracket/core/Bracket/SwissBracket.ts b/app/features/tournament-bracket/core/Bracket/SwissBracket.ts index f17006a6b..b1a6294eb 100644 --- a/app/features/tournament-bracket/core/Bracket/SwissBracket.ts +++ b/app/features/tournament-bracket/core/Bracket/SwissBracket.ts @@ -1,5 +1,6 @@ import * as R from "remeda"; import type { Tables } from "~/db/tables"; +import * as Standings from "~/features/tournament/core/Standings"; import { TOURNAMENT } from "~/features/tournament/tournament-constants"; import type { TournamentManagerDataSet } from "~/modules/brackets-manager/types"; import invariant from "~/utils/invariant"; @@ -443,22 +444,8 @@ export class SwissBracket extends Bracket { return 0; }); - let lastPlacement = 0; - let currentPlacement = 1; - let teamsEncountered = 0; return this.standingsWithoutNonParticipants( - sorted.map((team) => { - if (team.placement !== lastPlacement) { - lastPlacement = team.placement; - currentPlacement = teamsEncountered + 1; - } - teamsEncountered++; - return { - ...team, - placement: currentPlacement, - stats: team.stats, - }; - }), + Standings.reNumberPlacements(sorted), ); } diff --git a/app/features/tournament-bracket/core/Progression.test.ts b/app/features/tournament-bracket/core/Progression.test.ts index 26051fdc1..85b5638bb 100644 --- a/app/features/tournament-bracket/core/Progression.test.ts +++ b/app/features/tournament-bracket/core/Progression.test.ts @@ -274,22 +274,36 @@ const getValidatedBrackets = ( ); describe("validatedSources - other rules", () => { - it("handles NOT_RESOLVING_WINNER (only round robin)", () => { - const error = getValidatedBrackets([ + it("accepts a single round robin with no follow-ups", () => { + const result = getValidatedBrackets([ { settings: {}, type: "round_robin", }, - ]) as Progression.ValidationError; + ]); - expect(error.type).toBe("NOT_RESOLVING_WINNER"); + expect(Array.isArray(result)).toBe(true); }); - it("handles NOT_RESOLVING_WINNER (ends in round robin)", () => { - const error = getValidatedBrackets([ + it("accepts a single A/B round robin with no follow-ups", () => { + const result = getValidatedBrackets([ + { + settings: { + hasAbDivisions: true, + teamsPerGroup: 6, + }, + type: "round_robin", + }, + ]); + + expect(Array.isArray(result)).toBe(true); + }); + + it("accepts a swiss to round robin progression", () => { + const result = getValidatedBrackets([ { settings: {}, - type: "single_elimination", + type: "swiss", }, { settings: {}, @@ -301,9 +315,30 @@ describe("validatedSources - other rules", () => { }, ], }, - ]) as Progression.ValidationError; + ]); - expect(error.type).toBe("NOT_RESOLVING_WINNER"); + expect(Array.isArray(result)).toBe(true); + }); + + it("accepts a round robin to round robin progression", () => { + const result = getValidatedBrackets([ + { + settings: {}, + type: "round_robin", + }, + { + settings: {}, + type: "round_robin", + sources: [ + { + bracketId: "0", + placements: "1,2", + }, + ], + }, + ]); + + expect(Array.isArray(result)).toBe(true); }); it("handles NOT_RESOLVING_WINNER (swiss with many groups)", () => { diff --git a/app/features/tournament-bracket/core/Progression.ts b/app/features/tournament-bracket/core/Progression.ts index 3f6302bdd..746d21ce1 100644 --- a/app/features/tournament-bracket/core/Progression.ts +++ b/app/features/tournament-bracket/core/Progression.ts @@ -405,7 +405,6 @@ function resolvesWinner(brackets: ParsedBracket[]) { const finals = brackets.find((_, idx) => isFinals(idx, brackets)); if (!finals) return false; - if (finals?.type === "round_robin") return false; if ( finals.type === "swiss" && (finals.settings.groupCount ?? TOURNAMENT.SWISS_DEFAULT_GROUP_COUNT) > 1 @@ -668,6 +667,16 @@ export function isFinals(idx: number, brackets: ParsedBracket[]) { return resolveMainBracketProgression(brackets).at(-1) === idx; } +/** Returns true if the finals bracket of the tournament is an A/B divisions round robin. */ +export function hasAbDivisionsFinals(brackets: ParsedBracket[]): boolean { + const finals = brackets.find((_, idx) => isFinals(idx, brackets)); + if (!finals) return false; + + return ( + finals.type === "round_robin" && finals.settings?.hasAbDivisions === true + ); +} + /** Given bracketIdx and bracketProgression will resolve if this an "underground bracket". * Underground bracket is defined as a bracket that is not part of the main tournament progression e.g. optional bracket for early losers */ diff --git a/app/features/tournament-bracket/core/brackets-manager/crud-db.server.ts b/app/features/tournament-bracket/core/brackets-manager/crud-db.server.ts index 179e6dcc9..88a1c7a33 100644 --- a/app/features/tournament-bracket/core/brackets-manager/crud-db.server.ts +++ b/app/features/tournament-bracket/core/brackets-manager/crud-db.server.ts @@ -1,7 +1,7 @@ // this file offers database functions specifically for the crud.server.ts file import { sql } from "~/db/sql"; -import type { Tables, TournamentRoundMaps } from "~/db/tables"; +import type { Tables } from "~/db/tables"; import type { Group as GroupType, Match as MatchType, @@ -243,20 +243,17 @@ export class Round { stageId: Tables["TournamentRound"]["stageId"]; groupId: Tables["TournamentRound"]["groupId"]; number: Tables["TournamentRound"]["number"]; - maps: Pick; constructor( id: Tables["TournamentRound"]["id"] | undefined, stageId: Tables["TournamentRound"]["stageId"], groupId: Tables["TournamentRound"]["groupId"], number: Tables["TournamentRound"]["number"], - maps: Pick, ) { this.id = id; this.stageId = stageId; this.groupId = groupId; this.number = number; - this.maps = maps; } insert() { @@ -389,9 +386,6 @@ export class Match { groupId: Tables["TournamentMatch"]["groupId"], roundId: Tables["TournamentMatch"]["roundId"], number: Tables["TournamentMatch"]["number"], - _unknown1: null, - _unknown2: null, - _unknown3: null, opponentOne: string, opponentTwo: string, ) { diff --git a/app/features/tournament-bracket/core/brackets-manager/crud.server.ts b/app/features/tournament-bracket/core/brackets-manager/crud.server.ts index 45a11a960..31dad6068 100644 --- a/app/features/tournament-bracket/core/brackets-manager/crud.server.ts +++ b/app/features/tournament-bracket/core/brackets-manager/crud.server.ts @@ -1,210 +1,195 @@ -// @ts-nocheck TODO - +import type { + CrudInterface, + DataTypes, + OmitId, + Table, +} from "~/modules/brackets-manager/types"; import { Group, Match, Round, Stage } from "./crud-db.server"; -export class SqlDatabase { - insert(table, arg) { +export class SqlDatabase implements CrudInterface { + insert(table: T, value: OmitId): number; + insert(table: T, values: OmitId[]): boolean; + insert( + table: T, + arg: OmitId | OmitId[], + ): number | boolean { switch (table) { - case "participant": - throw new Error("not implemented"); - // return Team.insertMissing(arg); - case "stage": { + const value = arg as OmitId; const stage = new Stage( undefined, - arg.tournament_id, - arg.number, - arg.name, - arg.type, - JSON.stringify(arg.settings), + value.tournament_id, + value.number, + value.name, + value.type, + JSON.stringify(value.settings), ); - return stage.insert() && stage.id; + stage.insert(); + return stage.id!; } case "group": { - const group = new Group(undefined, arg.stage_id, arg.number); - return group.insert() && group.id; + const value = arg as OmitId; + const group = new Group(undefined, value.stage_id, value.number); + group.insert(); + return group.id!; } case "round": { + const value = arg as OmitId; const round = new Round( undefined, - arg.stage_id, - arg.group_id, - arg.number, + value.stage_id, + value.group_id, + value.number, ); - return round.insert() && round.id; + round.insert(); + return round.id!; } case "match": { + const value = arg as OmitId; const match = new Match( undefined, - arg.status, - arg.stage_id, - arg.group_id, - arg.round_id, - arg.number, - null, - null, - null, - JSON.stringify(arg.opponent1), - JSON.stringify(arg.opponent2), + value.status, + value.stage_id, + value.group_id, + value.round_id, + value.number, + JSON.stringify(value.opponent1), + JSON.stringify(value.opponent2), ); - return match.insert() && match.id; + match.insert(); + return match.id!; } } } - select(table, arg) { + select(table: T): Array | null; + select(table: T, id: number): DataTypes[T] | null; + select( + table: T, + filter: Partial, + ): Array | null; + select( + table: T, + arg?: number | Partial, + ): DataTypes[T] | Array | null { switch (table) { - case "participant": + case "stage": { if (typeof arg === "number") { - throw new Error("not implemented"); - // const team = Team.getById(arg); - // return team && convertTeam(team); + return Stage.getById(arg) as DataTypes[T]; } - if (arg.tournament_id) { - return Team.getByTournamentId(arg.tournament_id); + const filter = arg as Partial | undefined; + if (filter?.tournament_id) { + return Stage.getByTournamentId(filter.tournament_id) as Array< + DataTypes[T] + >; } break; + } - case "stage": + case "group": { if (typeof arg === "number") { - return Stage.getById(arg); + return Group.getById(arg) as DataTypes[T]; } - if (arg.tournament_id && arg.number) { - throw new Error("not implemented"); - // const stage = Stage.getByTournamentAndNumber( - // arg.tournament_id, - // arg.number, - // ); - // return stage && [convertStage(stage)]; + const filter = arg as Partial | undefined; + if (filter?.stage_id && filter.number) { + const group = Group.getByStageAndNumber( + filter.stage_id, + filter.number, + ); + return group ? ([group] as Array) : null; } - if (arg.tournament_id) { - return Stage.getByTournamentId(arg.tournament_id); + if (filter?.stage_id) { + return Group.getByStageId(filter.stage_id) as Array; } break; + } - case "group": - if (!arg) { - throw new Error("not implemented"); - // const groups = Group.getAll(); - // return groups?.map(convertGroup); - } - + case "round": { if (typeof arg === "number") { - return Group.getById(arg); + return Round.getById(arg) as DataTypes[T]; } - if (arg.stage_id && arg.number) { - const group = Group.getByStageAndNumber(arg.stage_id, arg.number); - return group && [group]; + const filter = arg as Partial | undefined; + if (filter?.group_id && filter.number) { + const round = Round.getByGroupAndNumber( + filter.group_id, + filter.number, + ); + return round ? ([round] as Array) : null; } - if (arg.stage_id) { - return Group.getByStageId(arg.stage_id); + if (filter?.group_id) { + return Round.getByGroupId(filter.group_id) as Array; + } + + if (filter?.stage_id) { + return Round.getByStageId(filter.stage_id) as Array; } break; + } - case "round": - if (!arg) { - throw new Error("not implemented"); - // const rounds = Round.getAll(); - // return rounds?.map(convertRound); - } - + case "match": { if (typeof arg === "number") { - return Round.getById(arg); + return Match.getById(arg) as DataTypes[T]; } - if (arg.group_id && arg.number) { - const round = Round.getByGroupAndNumber(arg.group_id, arg.number); - return round && [round]; + const filter = arg as Partial | undefined; + if (filter?.round_id && filter.number) { + const match = Match.getByRoundAndNumber( + filter.round_id, + filter.number, + ); + return match ? ([match] as Array) : null; } - if (arg.group_id) { - return Round.getByGroupId(arg.group_id); + if (filter?.stage_id) { + return Match.getByStageId(filter.stage_id) as Array; } - if (arg.stage_id) { - return Round.getByStageId(arg.stage_id); + if (filter?.round_id) { + return Match.getByRoundId(filter.round_id) as Array; } break; - - case "match": - if (!arg) { - throw new Error("not implemented"); - // const matches = Match.getAll(); - // return matches?.map(convertMatch); - } - - if (typeof arg === "number") { - return Match.getById(arg); - } - - if (arg.round_id && arg.number) { - const match = Match.getByRoundAndNumber(arg.round_id, arg.number); - return match && [match]; - } - - if (arg.stage_id) { - return Match.getByStageId(arg.stage_id); - } - - if (arg.group_id) { - throw new Error("not implemented"); - // const matches = Match.getByGroupId(arg.group_id); - // return matches?.map(convertMatch); - } - - if (arg.round_id) { - return Match.getByRoundId(arg.round_id); - } - - break; - // throw new Error("not implemented"); - // if (typeof arg === "number") { - // const game = MatchGame.getById(arg); - // return game && convertMatchGame(game); - // } - - // if (arg.parent_id && arg.number) { - // const game = MatchGame.getByParentAndNumber( - // arg.parent_id, - // arg.number, - // ); - // return game && [convertMatchGame(game)]; - // } - - // if (arg.parent_id) { - // const games = MatchGame.getByParentId(arg.parent_id); - // return games?.map(convertMatchGame); - // } - - // break; + } } return null; } - update(table, query, update) { + update(table: T, id: number, value: DataTypes[T]): boolean; + update( + table: T, + filter: Partial, + value: Partial, + ): boolean; + update( + table: T, + query: number | Partial, + value: DataTypes[T] | Partial, + ): boolean { switch (table) { - case "stage": + case "stage": { if (typeof query === "number") { + const update = value as Partial; return Stage.updateSettings(query, JSON.stringify(update.settings)); } break; + } - case "match": + case "match": { if (typeof query === "number") { + const update = value as DataTypes["match"]; const match = new Match( query, update.status, @@ -212,95 +197,22 @@ export class SqlDatabase { update.group_id, update.round_id, update.number, - null, - null, - null, JSON.stringify(update.opponent1), JSON.stringify(update.opponent2), ); - return match.update(); } break; - // throw new Error("not implemented"); - // if (typeof query === "number") { - // const game = new MatchGame( - // query, - // update.stage_id, - // update.parent_id, - // update.status, - // update.number, - // null, - // null, - // null, - // JSON.stringify(update.opponent1), - // JSON.stringify(update.opponent2), - // ); - - // return game.update(); - // } - - // if (query.parent_id) { - // const game = new MatchGame( - // undefined, - // update.stage_id, - // query.parent_id, - // update.status, - // update.number, - // null, - // null, - // null, - // JSON.stringify(update.opponent1), - // JSON.stringify(update.opponent2), - // ); - - // return game.updateByParentId(); - // } - - // break; + } } return false; } - delete(_table, _filter) { + delete(table: T): boolean; + delete(table: T, filter: Partial): boolean; + delete(): boolean { throw new Error("not implemented"); - // switch (table) { - // case "stage": - // return Number.isInteger(filter.id) && Stage.deleteById(filter.id); - - // case "group": - // return ( - // Number.isInteger(filter.stage_id) && - // Group.deleteByStageId(filter.stage_id) - // ); - - // case "round": - // return ( - // Number.isInteger(filter.stage_id) && - // Round.deleteByStageId(filter.stage_id) - // ); - - // case "match": - // return ( - // Number.isInteger(filter.stage_id) && - // Match.deleteByStageId(filter.stage_id) - // ); - // if (Number.isInteger(filter.stage_id)) - // return MatchGame.deleteByStageId(filter.stage_id); - // if ( - // Number.isInteger(filter.parent_id) && - // Number.isInteger(filter.number) - // ) - // return MatchGame.deleteByParentAndNumber( - // filter.parent_id, - // filter.number, - // ); - // return false; - - // default: - // return false; - // } } } diff --git a/app/features/tournament-bracket/core/brackets-manager/manager.server.ts b/app/features/tournament-bracket/core/brackets-manager/manager.server.ts index 2199f2465..ce23cce3d 100644 --- a/app/features/tournament-bracket/core/brackets-manager/manager.server.ts +++ b/app/features/tournament-bracket/core/brackets-manager/manager.server.ts @@ -3,8 +3,6 @@ import { SqlDatabase } from "./crud.server"; export function getServerTournamentManager() { const storage = new SqlDatabase(); - // TODO: fix this ts-expect-error comment - // @ts-expect-error interface mismatch const manager = new BracketsManager(storage); return manager; diff --git a/app/features/tournament-bracket/core/summarizer.server.ts b/app/features/tournament-bracket/core/summarizer.server.ts index 7fbdb794c..6f31d9ff8 100644 --- a/app/features/tournament-bracket/core/summarizer.server.ts +++ b/app/features/tournament-bracket/core/summarizer.server.ts @@ -16,6 +16,7 @@ import type { Tables, WinLossParticipationArray } from "../../../db/tables"; import { ensureOneStandingPerUser } from "../tournament-bracket-utils"; import type { Standing } from "./Bracket"; import type { ParsedBracket } from "./Progression"; +import * as Progression from "./Progression"; export interface TournamentSummary { skills: Omit< @@ -39,6 +40,7 @@ type TeamsArg = Array<{ id: number; members: Array<{ userId: number }>; startingBracketIdx?: number | null; + abDivision?: number | null; }>; type Rating = Pick; @@ -572,25 +574,31 @@ function tournamentResults({ }) { const result: TournamentSummary["tournamentResults"] = []; - const firstPlaceFinishesCount = finalStandings.filter( - (s) => s.placement === 1, - ).length; - const isMultiStartingBracket = firstPlaceFinishesCount > 1; + const isMultiStartingBracket = + Progression.startingBrackets(progression).length > 1; + const isAbDivisionsFinals = Progression.hasAbDivisionsFinals(progression); for (const standing of finalStandings) { const team = teams.find((t) => t.id === standing.team.id); invariant(team); - const div = - // second check should be redundant, but just here in case - typeof team.startingBracketIdx === "number" && isMultiStartingBracket - ? getBracketProgressionLabel(team.startingBracketIdx, progression) - : null; - const divisionParticipantCount = - div !== null - ? teams.filter((t) => t.startingBracketIdx === team.startingBracketIdx) - .length - : participantCount; + let div: string | null = null; + let divisionParticipantCount = participantCount; + + if (isAbDivisionsFinals && typeof team.abDivision === "number") { + div = team.abDivision === 0 ? "A" : "B"; + divisionParticipantCount = teams.filter( + (t) => t.abDivision === team.abDivision, + ).length; + } else if ( + isMultiStartingBracket && + typeof team.startingBracketIdx === "number" + ) { + div = getBracketProgressionLabel(team.startingBracketIdx, progression); + divisionParticipantCount = teams.filter( + (t) => t.startingBracketIdx === team.startingBracketIdx, + ).length; + } for (const player of standing.team.members) { result.push({ diff --git a/app/features/tournament-bracket/core/summarizer.test.ts b/app/features/tournament-bracket/core/summarizer.test.ts index e14fa21c3..277db3aa9 100644 --- a/app/features/tournament-bracket/core/summarizer.test.ts +++ b/app/features/tournament-bracket/core/summarizer.test.ts @@ -3,6 +3,7 @@ import { describe, expect, test } from "vitest"; import type { AllMatchResult } from "~/features/tournament-match/queries/allMatchResultsByTournamentId.server"; import invariant from "~/utils/invariant"; import type { Tables } from "../../../db/tables"; +import type * as Progression from "./Progression"; import { tournamentSummary } from "./summarizer.server"; import type { TournamentDataTeam } from "./Tournament.server"; @@ -65,6 +66,7 @@ describe("tournamentSummary()", () => { seedingSkillCountsFor, withMemberInTwoTeams = false, teamsWithStartingBrackets, + teamsWithAbDivisions, progression, finalStandings, }: { @@ -75,13 +77,11 @@ describe("tournamentSummary()", () => { id: number; startingBracketIdx: number | null; }>; - progression?: Array<{ - name: string; - type: "single_elimination"; - settings: Record; - requiresCheckIn: boolean; - sources?: Array<{ bracketIdx: number; placements: number[] }>; + teamsWithAbDivisions?: Array<{ + id: number; + abDivision: 0 | 1; }>; + progression?: Progression.ParsedBracket[]; finalStandings?: Array<{ placement: number; team: TournamentDataTeam; @@ -122,17 +122,19 @@ describe("tournamentSummary()", () => { }, ]; - const teams = teamsWithStartingBrackets - ? defaultTeams.map((team) => { - const startingBracket = teamsWithStartingBrackets.find( - (t) => t.id === team.id, - ); - return { - ...team, - startingBracketIdx: startingBracket?.startingBracketIdx ?? null, - }; - }) - : defaultTeams; + const teams = defaultTeams.map((team) => { + const startingBracket = teamsWithStartingBrackets?.find( + (t) => t.id === team.id, + ); + const abDivisionEntry = teamsWithAbDivisions?.find( + (t) => t.id === team.id, + ); + return { + ...team, + startingBracketIdx: startingBracket?.startingBracketIdx ?? null, + abDivision: abDivisionEntry?.abDivision ?? null, + }; + }); return tournamentSummary({ finalStandings: finalStandings ?? [ @@ -779,6 +781,102 @@ describe("tournamentSummary()", () => { expect(team4Results.every((r) => r.participantCount === 2)).toBeTruthy(); }); + test("div is set from abDivision when finals is an A/B divisions round robin", () => { + const summary = summarize({ + teamsWithAbDivisions: [ + { id: 1, abDivision: 0 }, + { id: 2, abDivision: 1 }, + { id: 3, abDivision: 0 }, + { id: 4, abDivision: 1 }, + ], + progression: [ + { + name: "Groups stage", + type: "round_robin", + settings: { hasAbDivisions: true, teamsPerGroup: 4 }, + requiresCheckIn: false, + }, + ], + finalStandings: [ + { + placement: 1, + team: createTeam(1, [1, 2, 3, 4]), + }, + { + placement: 1, + team: createTeam(2, [5, 6, 7, 8]), + }, + { + placement: 2, + team: createTeam(3, [9, 10, 11, 12]), + }, + { + placement: 2, + team: createTeam(4, [13, 14, 15, 16]), + }, + ], + }); + + const team1Results = summary.tournamentResults.filter( + (r) => r.tournamentTeamId === 1, + ); + const team2Results = summary.tournamentResults.filter( + (r) => r.tournamentTeamId === 2, + ); + const team3Results = summary.tournamentResults.filter( + (r) => r.tournamentTeamId === 3, + ); + const team4Results = summary.tournamentResults.filter( + (r) => r.tournamentTeamId === 4, + ); + + expect(team1Results.every((r) => r.div === "A")).toBeTruthy(); + expect(team2Results.every((r) => r.div === "B")).toBeTruthy(); + expect(team3Results.every((r) => r.div === "A")).toBeTruthy(); + expect(team4Results.every((r) => r.div === "B")).toBeTruthy(); + }); + + test("participantCount counts teams per abDivision for A/B finals", () => { + const summary = summarize({ + teamsWithAbDivisions: [ + { id: 1, abDivision: 0 }, + { id: 2, abDivision: 1 }, + { id: 3, abDivision: 0 }, + { id: 4, abDivision: 1 }, + ], + progression: [ + { + name: "Groups stage", + type: "round_robin", + settings: { hasAbDivisions: true, teamsPerGroup: 4 }, + requiresCheckIn: false, + }, + ], + finalStandings: [ + { + placement: 1, + team: createTeam(1, [1, 2, 3, 4]), + }, + { + placement: 1, + team: createTeam(2, [5, 6, 7, 8]), + }, + { + placement: 2, + team: createTeam(3, [9, 10, 11, 12]), + }, + { + placement: 2, + team: createTeam(4, [13, 14, 15, 16]), + }, + ], + }); + + for (const result of summary.tournamentResults) { + expect(result.participantCount).toBe(2); + } + }); + test("excludes matches ended early by organizer from calculations", () => { const summary = summarize({ results: [ diff --git a/app/features/tournament-match/loaders/to.$id.matches.$mid.server.ts b/app/features/tournament-match/loaders/to.$id.matches.$mid.server.ts index 09f9cd9bf..14acf8d86 100644 --- a/app/features/tournament-match/loaders/to.$id.matches.$mid.server.ts +++ b/app/features/tournament-match/loaders/to.$id.matches.$mid.server.ts @@ -11,6 +11,7 @@ import { tournamentFromDBCached } from "~/features/tournament-bracket/core/Tourn import { matchPageParamsSchema } from "~/features/tournament-bracket/tournament-bracket-schemas.server"; import { tournamentTeamToActiveRosterUserIds } from "~/features/tournament-bracket/tournament-bracket-utils"; import * as UserRepository from "~/features/user-page/UserRepository.server"; +import { Status } from "~/modules/brackets-model"; import { cache, IN_MILLISECONDS, ttl } from "~/utils/cache.server"; import { IS_E2E_TEST_RUN } from "~/utils/e2e"; import { logger } from "~/utils/logger"; @@ -148,7 +149,8 @@ export const loader = async ({ params }: LoaderFunctionArgs) => { match.chatCode && !matchIsOver && match.opponentOne?.id && - match.opponentTwo?.id + match.opponentTwo?.id && + match.status > Status.Locked ) { // only add global chat for active roster (or all if not yet set i.e. first match) // if roster changed mid-set the subs can still see the chat on the match page @@ -180,7 +182,7 @@ export const loader = async ({ params }: LoaderFunctionArgs) => { }); } - const shouldSeeChat = + const hasPermsToSeeChat = tournament.isOrganizerOrStreamer(user) || match.players.some((p) => p.id === user?.id); @@ -195,7 +197,7 @@ export const loader = async ({ params }: LoaderFunctionArgs) => { }); const visibleChatCode = - shouldSeeChat && !chatCodeExpired ? match.chatCode : undefined; + hasPermsToSeeChat && !chatCodeExpired ? match.chatCode : undefined; // xxx: optimization, can be skipped if user can't join anyway const [roomLinks, anyUserPrefersNoSplatnet] = matchIsOver @@ -209,7 +211,7 @@ export const loader = async ({ params }: LoaderFunctionArgs) => { ]); return { - match: shouldSeeChat ? match : { ...match, chatCode: undefined }, + match: hasPermsToSeeChat ? match : { ...match, chatCode: undefined }, results, mapList, matchIsOver, diff --git a/app/features/tournament/core/Standings.test.ts b/app/features/tournament/core/Standings.test.ts new file mode 100644 index 000000000..4a6193390 --- /dev/null +++ b/app/features/tournament/core/Standings.test.ts @@ -0,0 +1,253 @@ +import { describe, expect, it } from "vitest"; +import { + progressions, + testTournament, + tournamentCtxTeam, +} from "~/features/tournament-bracket/core/tests/test-utils"; +import { BracketsManager } from "~/modules/brackets-manager"; +import { InMemoryDatabase } from "~/modules/brackets-memory-db"; +import invariant from "~/utils/invariant"; +import { reNumberPlacements, tournamentStandings } from "./Standings"; + +describe("tournamentStandings", () => { + it("returns single-division standings for a tournament with one starting bracket", () => { + const tournament = singleEliminationTournament(); + + const result = tournamentStandings(tournament); + + expect(result.type).toBe("single"); + invariant(result.type === "single"); + expect(result.standings.length).toBeGreaterThan(0); + expect(result.standings[0].placement).toBe(1); + expect(result.standings[0].team.id).toBe(1); + }); + + it("returns one div per starting bracket for a tournament with multiple starting brackets", () => { + const tournament = testTournament({ + ctx: { + settings: { bracketProgression: progressions.manyStartBrackets }, + teams: [ + tournamentCtxTeam(1, { startingBracketIdx: 0, seed: 1 }), + tournamentCtxTeam(2, { startingBracketIdx: 0, seed: 2 }), + tournamentCtxTeam(3, { startingBracketIdx: 1, seed: 3 }), + tournamentCtxTeam(4, { startingBracketIdx: 1, seed: 4 }), + ], + }, + }); + + const result = tournamentStandings(tournament); + + expect(result.type).toBe("multi"); + invariant(result.type === "multi"); + expect(result.standings).toHaveLength(2); + for (const { div } of result.standings) { + expect(typeof div).toBe("string"); + expect(div.length).toBeGreaterThan(0); + } + const divs = result.standings.map((s) => s.div); + expect(new Set(divs).size).toBe(2); + }); + + it("splits A/B divisions finals into 'A' and 'B' divs with teams partitioned by abDivision", () => { + const tournament = abDivisionsTournament(); + + const result = tournamentStandings(tournament); + + expect(result.type).toBe("multi"); + invariant(result.type === "multi"); + expect(result.standings.map((s) => s.div)).toEqual(["A", "B"]); + + const [a, b] = result.standings; + expect(a.standings.map((s) => s.team.id)).toEqual([1, 3]); + expect(b.standings.map((s) => s.team.id)).toEqual([2, 4]); + expect(a.standings.every((s) => s.team.abDivision === 0)).toBe(true); + expect(b.standings.every((s) => s.team.abDivision === 1)).toBe(true); + }); + + it("re-numbers placements within each A/B division starting from 1", () => { + const tournament = abDivisionsTournament(); + + const result = tournamentStandings(tournament); + + invariant(result.type === "multi"); + const [a, b] = result.standings; + expect(a.standings.map((s) => s.placement)).toEqual([1, 2]); + expect(b.standings.map((s) => s.placement)).toEqual([1, 2]); + }); +}); + +describe("reNumberPlacements", () => { + it("keeps already contiguous placements unchanged", () => { + const result = reNumberPlacements([ + { placement: 1 }, + { placement: 2 }, + { placement: 3 }, + ]); + + expect(result.map((s) => s.placement)).toEqual([1, 2, 3]); + }); + + it("groups tied placements and skips numbers to match team count", () => { + const result = reNumberPlacements([ + { placement: 1 }, + { placement: 1 }, + { placement: 3 }, + { placement: 3 }, + { placement: 5 }, + ]); + + expect(result.map((s) => s.placement)).toEqual([1, 1, 3, 3, 5]); + }); + + it("re-numbers from 1 when the input has been filtered (e.g. top finishers removed)", () => { + const result = reNumberPlacements([ + { placement: 3 }, + { placement: 3 }, + { placement: 5 }, + { placement: 7 }, + ]); + + expect(result.map((s) => s.placement)).toEqual([1, 1, 3, 4]); + }); + + it("adds the offset to every placement", () => { + const result = reNumberPlacements( + [{ placement: 1 }, { placement: 1 }, { placement: 3 }], + 10, + ); + + expect(result.map((s) => s.placement)).toEqual([11, 11, 13]); + }); + + it("preserves non-placement fields on each standing", () => { + const result = reNumberPlacements([ + { placement: 1, team: { id: 7 }, note: "a" }, + { placement: 2, team: { id: 8 }, note: "b" }, + ]); + + expect(result).toEqual([ + { placement: 1, team: { id: 7 }, note: "a" }, + { placement: 2, team: { id: 8 }, note: "b" }, + ]); + }); + + it("returns an empty array when given an empty array", () => { + expect(reNumberPlacements([])).toEqual([]); + expect(reNumberPlacements([], 5)).toEqual([]); + }); +}); + +function singleEliminationTournament() { + const storage = new InMemoryDatabase(); + const manager = new BracketsManager(storage); + + manager.create({ + name: "Main Bracket", + tournamentId: 1, + type: "single_elimination", + seeding: [1, 2, 3, 4], + settings: { seedOrdering: ["natural"] }, + }); + + while (true) { + const pending = storage + .select("match")! + .find( + (m) => + typeof m.opponent1?.id === "number" && + typeof m.opponent2?.id === "number" && + m.opponent1.result !== "win" && + m.opponent2.result !== "win", + ); + if (!pending) break; + + const winnerIsOpp1 = pending.opponent1.id < pending.opponent2.id; + manager.update.match({ + id: pending.id, + opponent1: winnerIsOpp1 ? { score: 2, result: "win" } : { score: 0 }, + opponent2: winnerIsOpp1 ? { score: 0 } : { score: 2, result: "win" }, + }); + } + + return testTournament({ + ctx: { + settings: { + bracketProgression: progressions.singleElimination, + }, + teams: [ + tournamentCtxTeam(1, { seed: 1 }), + tournamentCtxTeam(2, { seed: 2 }), + tournamentCtxTeam(3, { seed: 3 }), + tournamentCtxTeam(4, { seed: 4 }), + ], + }, + data: manager.get.tournamentData(1), + }); +} + +function abDivisionsTournament() { + const storage = new InMemoryDatabase(); + const manager = new BracketsManager(storage); + + manager.create({ + name: "AB RR", + tournamentId: 1, + type: "round_robin", + seeding: [1, 2, 3, 4], + abDivisions: [0, 1, 0, 1], + settings: { + groupCount: 1, + hasAbDivisions: true, + seedOrdering: ["groups.seed_optimized"], + }, + }); + + const winnerByMatchup: Record = { + "1-2": 1, + "1-4": 1, + "2-3": 2, + "3-4": 3, + }; + for (const match of storage.select("match")!) { + const a = match.opponent1.id as number; + const b = match.opponent2.id as number; + const key = a < b ? `${a}-${b}` : `${b}-${a}`; + const winnerId = winnerByMatchup[key]; + invariant(winnerId, `unexpected matchup ${key}`); + const loserScore = key === "2-3" || key === "3-4" ? 1 : 0; + const winnerIsOpp1 = match.opponent1.id === winnerId; + manager.update.match({ + id: match.id, + opponent1: winnerIsOpp1 + ? { score: 2, result: "win" } + : { score: loserScore }, + opponent2: winnerIsOpp1 + ? { score: loserScore } + : { score: 2, result: "win" }, + }); + } + + const data = manager.get.tournamentData(1); + + return testTournament({ + ctx: { + settings: { + bracketProgression: [ + { + type: "round_robin", + name: "AB RR", + requiresCheckIn: false, + settings: { hasAbDivisions: true }, + }, + ], + }, + teams: [ + tournamentCtxTeam(1, { abDivision: 0, seed: 1 }), + tournamentCtxTeam(2, { abDivision: 1, seed: 2 }), + tournamentCtxTeam(3, { abDivision: 0, seed: 3 }), + tournamentCtxTeam(4, { abDivision: 1, seed: 4 }), + ], + }, + data, + }); +} diff --git a/app/features/tournament/core/Standings.ts b/app/features/tournament/core/Standings.ts index a2fa012ce..d6894df4a 100644 --- a/app/features/tournament/core/Standings.ts +++ b/app/features/tournament/core/Standings.ts @@ -24,6 +24,34 @@ export function flattenStandings( : standingsResult.standings.flatMap((div) => div.standings); } +/** + * Re-numbers placements in a sorted standings array so that tied placements stay + * grouped (e.g. `[1, 1, 3, 3, 5]`) while non-tied positions reflect the true + * number of teams above them. Useful after filtering or merging standings where + * the original placement numbers no longer match the team count. + * + * Pass `offset` to shift every placement downwards — used when the returned + * standings will be appended below standings from another bracket. + */ +export function reNumberPlacements( + standings: T[], + offset = 0, +): T[] { + let lastOriginalPlacement = 0; + let currentPlacement = 0; + + return standings.map((standing, index) => { + if (standing.placement !== lastOriginalPlacement) { + lastOriginalPlacement = standing.placement; + currentPlacement = index + 1; + } + return { + ...standing, + placement: currentPlacement + offset, + }; + }); +} + /** Calculates SPR (Seed Performance Rating) - see https://web.archive.org/web/20250513034545/https://www.pgstats.com/articles/introducing-spr-and-uf */ export function calculateSPR({ standings, @@ -143,24 +171,42 @@ export function matchesPlayed({ export function tournamentStandings( tournament: Tournament, ): TournamentStandingsResult { - const startingBracketIdxs = Progression.startingBrackets( - tournament.ctx.settings.bracketProgression, - ); + const progression = tournament.ctx.settings.bracketProgression; + const startingBracketIdxs = Progression.startingBrackets(progression); if (startingBracketIdxs.length <= 1) { + const standings = tournamentStandingsForBracket(tournament, undefined); + + if (Progression.hasAbDivisionsFinals(progression)) { + return { + type: "multi", + standings: [ + { + div: "A", + standings: reNumberPlacements( + standings.filter((s) => s.team.abDivision === 0), + ), + }, + { + div: "B", + standings: reNumberPlacements( + standings.filter((s) => s.team.abDivision === 1), + ), + }, + ], + }; + } + return { type: "single", - standings: tournamentStandingsForBracket(tournament, undefined), + standings, }; } return { type: "multi", standings: startingBracketIdxs.map((bracketIdx) => ({ - div: getBracketProgressionLabel( - bracketIdx, - tournament.ctx.settings.bracketProgression, - ), + div: getBracketProgressionLabel(bracketIdx, progression), standings: tournamentStandingsForBracket(tournament, bracketIdx), })), }; @@ -241,8 +287,6 @@ function standingsToMergeable< standings: T[]; teamsAboveFromAnotherBracketsCount: number; }) { - const result: T[] = []; - const filtered = standings.filter( (standing) => !alreadyIncludedTeamIds.has(standing.team.id), ); @@ -250,22 +294,8 @@ function standingsToMergeable< // e.g. if standings start at 3rd place, this must mean there is 2 teams left to finish _this_ bracket const unfinishedTeamsCount = (standings.at(0)?.placement ?? 1) - 1; - let placement = 1; - - for (const [i, standing] of filtered.entries()) { - const placementChanged = - i !== 0 && standing.placement !== filtered[i - 1].placement; - - if (placementChanged) { - placement = i + 1; - } - - result.push({ - ...standing, - placement: - placement + teamsAboveFromAnotherBracketsCount + unfinishedTeamsCount, - }); - } - - return result; + return reNumberPlacements( + filtered, + teamsAboveFromAnotherBracketsCount + unfinishedTeamsCount, + ); } diff --git a/app/features/tournament/routes/to.$id.admin.tsx b/app/features/tournament/routes/to.$id.admin.tsx index a6ab4b8a6..297d015c8 100644 --- a/app/features/tournament/routes/to.$id.admin.tsx +++ b/app/features/tournament/routes/to.$id.admin.tsx @@ -589,6 +589,10 @@ function DownloadParticipants() { } function simpleListInSeededOrder() { + const hasCheckedInTeams = tournament.ctx.teams.some( + (team) => team.checkIns.length > 0, + ); + return tournament.ctx.teams .slice() .sort( @@ -596,7 +600,7 @@ function DownloadParticipants() { (a.seed ?? Number.POSITIVE_INFINITY) - (b.seed ?? Number.POSITIVE_INFINITY), ) - .filter((team) => team.checkIns.length > 0) + .filter((team) => !hasCheckedInTeams || team.checkIns.length > 0) .map((team) => team.name) .join("\n"); } diff --git a/app/features/user-page/components/UserResultsTable.tsx b/app/features/user-page/components/UserResultsTable.tsx index db7e889b4..dadea4a9a 100644 --- a/app/features/user-page/components/UserResultsTable.tsx +++ b/app/features/user-page/components/UserResultsTable.tsx @@ -89,11 +89,11 @@ export function UserResultsTable({ {formatDate(databaseTimestampToDate(result.startTime), { day: "numeric", - month: "short", - year: "numeric", + month: "numeric", + year: "2-digit", })} - +
{result.eventId ? ( @@ -106,11 +106,12 @@ export function UserResultsTable({ ) : null} + {result.tier ? : null} {result.eventName} - {result.tier ? : null} {result.div ? ( ({result.div}) ) : null} @@ -130,7 +130,7 @@ export function UserResultsTable({ - +
r.isAccepted); + if (!acceptedRequest) continue; + + const postTeamName = Scrim.sideDisplayName(scrim); + const requestTeamName = Scrim.sideDisplayName(acceptedRequest); logger.info( - `Notifying scrim starting soon for scrim ${scrim.id} with ${participantIds.length} participants`, + `Notifying scrim starting soon for scrim ${scrim.id} with ${scrim.users.length + acceptedRequest.users.length} participants`, ); await notify({ notification: { type: "SCRIM_STARTING_SOON", - meta: { - id: scrim.id, - at: databaseTimestampToJavascriptTimestamp( - Scrim.getStartTime(scrim), - ), - }, + meta: { id: scrim.id, opponentTeamName: requestTeamName }, }, - userIds: participantIds, + userIds: scrim.users.map((u) => u.id), + }); + + await notify({ + notification: { + type: "SCRIM_STARTING_SOON", + meta: { id: scrim.id, opponentTeamName: postTeamName }, + }, + userIds: acceptedRequest.users.map((u) => u.id), }); } }, diff --git a/app/utils/string.test.ts b/app/utils/string.test.ts index 34c57ce94..61c97ac2c 100644 --- a/app/utils/string.test.ts +++ b/app/utils/string.test.ts @@ -1,5 +1,9 @@ import { describe, expect, test } from "vitest"; -import { pathnameFromPotentialURL, truncateBySentence } from "./strings"; +import { + pathnameFromPotentialURL, + removeMarkdown, + truncateBySentence, +} from "./strings"; describe("pathnameFromPotentialURL()", () => { test("Resolves path name from valid URL", () => { @@ -44,3 +48,30 @@ describe("truncateBySentence()", () => { expect(truncateBySentence(text, 20)).toBe("First line"); }); }); + +describe("removeMarkdown()", () => { + test("Decodes   entities and collapses runs", () => { + const text = "     Global Gauntlet is an event"; + expect(removeMarkdown(text)).toBe("Global Gauntlet is an event"); + }); + + test("Decodes common named HTML entities", () => { + expect(removeMarkdown("Tom & Jerry <3 "hi"")).toBe( + 'Tom & Jerry <3 "hi"', + ); + }); + + test("Decodes numeric HTML entities", () => { + expect(removeMarkdown("café & tea")).toBe("café & tea"); + }); + + test("Leaves unknown named entities untouched", () => { + expect(removeMarkdown("AT&T &fakeentity; rules")).toBe( + "AT&T &fakeentity; rules", + ); + }); + + test("Strips HTML tags and markdown emphasis", () => { + expect(removeMarkdown("

Hello **world**!

")).toBe("Hello world!"); + }); +}); diff --git a/app/utils/strings.ts b/app/utils/strings.ts index 60aac7e53..c38d5e409 100644 --- a/app/utils/strings.ts +++ b/app/utils/strings.ts @@ -76,12 +76,35 @@ export function truncateBySentence(value: string, max: number) { } // based on https://github.com/zuchka/remove-markdown +const NAMED_HTML_ENTITIES: Record = { + nbsp: " ", + amp: "&", + lt: "<", + gt: ">", + quot: '"', + apos: "'", +}; + export function removeMarkdown(value: string) { const htmlReplaceRegex = /<[^>]*>/g; return ( value // Remove HTML tags .replace(htmlReplaceRegex, "") + // Decode named HTML entities (e.g.  , &) + .replace(/&([a-zA-Z]+);/g, (match, name: string) => { + const replacement = NAMED_HTML_ENTITIES[name.toLowerCase()]; + return replacement ?? match; + }) + // Decode numeric HTML entities (e.g.   or  ) + .replace(/&#(x?[0-9a-fA-F]+);/g, (_, code: string) => { + const codePoint = code.startsWith("x") + ? Number.parseInt(code.slice(1), 16) + : Number.parseInt(code, 10); + return Number.isFinite(codePoint) + ? String.fromCodePoint(codePoint) + : ""; + }) // Remove setext-style headers .replace(/^[=-]{2,}\s*$/g, "") // Remove footnotes? @@ -113,5 +136,8 @@ export function removeMarkdown(value: string) { // .replace(/(\S+)\n\s*(\S+)/g, '$1 $2') // Replace strike through .replace(/~(.*?)~/g, "$1") + // Collapse runs of whitespace (e.g. from decoded   or stripped tags) + .replace(/[ \t ]{2,}/g, " ") + .trim() ); } diff --git a/content/articles/na-league-2026-r1e3.md b/content/articles/na-league-2026-r1e3.md new file mode 100644 index 000000000..2b8e4a034 --- /dev/null +++ b/content/articles/na-league-2026-r1e3.md @@ -0,0 +1,150 @@ +--- +title: "NA League 2026 Event #3 (Regular Season Week 1) " +date: 2026-04-21 +author: + - name: YELLOW + link: https://sendou.ink/u/great-hero-yellow +--- + +#### *The first week of the Regular Season was so intense that by the end of it, you forget that it’s not the Playoffs yet.* + +NAL2026-r1e3 + +On Saturday, April 18, 2026, the Splatoon 3 North American League began its Regular Season. The training wheels were gone, and now wins started counting, earning points to qualify for the Playoffs in June. Teams only need to play in four weeks to be eligible for the Playoffs, but needed to ensure a high amount of points to make the top eight. + +Nine, Falco, and Nox led the commentator panel, and Power was operating the spectator camera. + +Despite the day being filled with competing events like the Global Gauntlet Qualifier 2, LUTI Playoffs, and two LANs in Sunset Surge and Don’t Flounder at the Function, big names in the North American scene showed up to throw down: + +1. Milky Way +2. SIZA +3. Content Cat +4. usagi fanclub +5. NME +6. Moonlight +7. Hypernova +8. lfn + +The Splattercolor Screen was used in Loser’s Finals, and its effects were briefly on screen. As we dive into the details, if you plan on rewatching, take caution if you are sensitive to its effects. + +## **Winner’s Quarterfinals: Content Cat vs. Moonlight (1–3)** + +While Content Cat is a new team to the NA League, its players aren’t, with most having appeared in the Top Cut stream in last year’s Regular Season. And Moonlight needs no introduction by now; you know them, you love them\! + +Game one, Tower Control at MakoMart; it took nearly half of the game before anyone cleared a single checkpoint. Once Moonlight started rolling, they didn’t stop, giving Content Cat no points and taking the knockout. + +Game two, Clam Blitz at Urchin Underpass, notorious for its difficulty to score, saw Moonlight opening the basket within 30 seconds. Held up in mid, Content Cat had to backtrack once Moonlight snuck in and scored again. Content Cat got points on the board, but after overtime, lost 52–86. + +Turf War at Scorch Gorge was next, and it only saw three splats in the first 60 seconds. It took until the latter half for things to pick up, with Content Cat losing players and seeing the Danger\! warning. At the one minute pan out, the map looked favorable to Content Cat, and true to appearances, they won, 53.8% – 44.2%. + +WQF_TWscorch + +*An even 98.0% total ink coverage—can you spot the 2% left uninked?* + +Already, the 2026 NA League was setting new records, as the total ink coverage percent from this game, 98%, beat the 2025 NA League’s record of 97.3%, set by Gourmet Race vs. BEt in last year’s Event \#10\! + +Splat Zones at Mahi-Mahi Resort saw Content Cat take the first lead when both teams went down two players. Moonlight had the lead when the first minute ended, never giving it up and stopping Content Cat when they were one point from taking it. Moonlight knocked out at the halfway point, sending Content Cat to the Loser’s Bracket. + +## **Winner’s Semifinals: Milky Way vs. NME (3–1)** + +Advancing to Winner’s Semifinals were Moonlight, Hypernova, Milky Way, and NME, which is a team three parts fofofo (last NA League’s \#3 team). Content Cat, SIZA, usagi fanclub, and lfn were in the Loser’s Bracket. + +The set began on Undertow Spillway for Splat Zones. NME struggled to keep players up, almost always down at least one. They still gave Milky Way a tough fight, keeping the zone neutral to stall the points gain. In overtime, NME was ticking close to Milky Way’s lead, but was wiped out, losing 94–97. + +Tower Control at Robo ROM-en was second, another difficult game for NME. Within the first two minutes, Milky Way’s push ended at just 4 points to knockout. NME was able to get some points to their name, but Milky Way’s lead persevered, 96–7. + +Rainmaker at Mahi-Mahi Resort would become NME’s bread and butter—Milky Way cleared their first checkpoint in 30 seconds, but NME retaliated by taking them down three players, dunking the checkpoint, taking Milky Way down in a delayed wipe, and knocking out just after a minute into the game\! + +WSF_RMmahi-mahi + +*What could have been a sub-minute knockout, if only the pedestal had been inked\!* + +Game four went to Turf War at Crableg Capital. At the start, Milky Way didn’t paint their base much, but it was NME who had the Danger\! indicator sooner. Milky Way had their turn with it as Now or Never started to play, and were even down three players in the last 20 seconds, but still walked out 55.3% – 40.8%, headed to Winner’s Finals. + +## **Winner’s Finals: Milky Way vs. Moonlight (3–2)** + +Those who paid attention to last year’s NA League Playoffs will recognize Milky Way vs. Moonlight as the Grand Finals set. The two teams have historically clashed multiple times, and the hosts were stoked to commentate it so early in the Regular Season. + +Splat Zones at Mahi-Mahi Resort saw Moonlight take a strong offense, getting the zone in 12 seconds and Milky Way down two players. They were stopped at 37, and Milky Way inched ahead, but Moonlight stopped them at 26\. Painting through Triple Inkstrikes and a Booyah Bomb, Moonlight took the lead and knocked out at 2:24. + +Turf War at MakoMart didn’t bring out anything unexpected from these teams; both had a REEF-LUX 450, both had a Snipewriter 5H. Danger\! popped up in Moonlight’s overhead a few times, critically in the last minute, and they lost 39.4% – 56.6%. + +Game three was Tower Control at Inkblot Art Academy. Moonlight moved the tower one point, then Milky Way, and they were pushed back to 100\. Moonlight cleared the first checkpoint before Milky Way, but 20 seconds later, Milky Way had the lead; soon after, more checkpoints, and before three minutes had passed, the knockout. + +Clam Blitz at Crableg Capital saw no points gained until the last 40 seconds, where Milky Way scored just one Power Clam. Moonlight ran in and scored in the final six seconds, took the lead, and in overtime, a jump in sealed the basket and the win, 44–20. + +WF_CBcrableg + +*SSNolan with the game-winning play, getting the wipeout, providing jump-ins, and grabbing the last clam needed to close the basket.* + +Game five went to Rainmaker at Museum d’Alfonsino. Moonlight entered with a double Ink Vac comp, and it truly felt like there was one online at all times. At the halfway point, with both teams down two, Moonlight took the lead, but needed another 30 seconds to clear the first checkpoint. + +Milky Way only needed 25 seconds to get their first checkpoint from there, and shortly after, took the lead to 18\. Approaching overtime, things looked good for Moonlight with Milky Way down 3, but trapped between an enemy and a Suction Bomb, they fell short of the lead, 62–82, and were sent to the Loser’s Bracket. + +## **Loser’s Semifinals: Hypernova vs. NME (2–3)** + +After both Hypernova and NME went 3–1 over their opponents in Loser’s Quarterfinals, they squared up to decide who advanced to fight Moonlight in Loser’s Finals. + +Game one was Tower Control at Museum d’Alfonsino. NME went all the way to 16 in a minute and a half. It took Hypernova two minutes, but they got the lead and third checkpoint, then jumped out to play defense for the last minute. In the final 40 seconds, they ended up wiped out twice, and NME beat the clock for a knockout. + +For Rainmaker at Crableg Capital, NME brought the uncommon Ballpoint Splatling Nouveau for Ink Vac coverage. NME was at a numbers disadvantage frequently, but Hypernova played cautiously and hesitated to move forward. They held the lead at 9, and NME crept closer, to 14, but two wipeouts in the last 20 seconds kept them at bay, and Hypernova kept the win, 91–86. + +LSF_RMcrableg + +*In the background, Synapse trades splats with DATKID to get a second wipeout over NME, six seconds after the previous one.* + +Game three was Clam Blitz at Scorch Gorge. Across two pushes in the first half of the game, NME brought their score down to 27\. Hypernova’s first push got them to 54, but they were caught in a delayed wipeout. At the one minute mark, NME scored again, wiped out Hypernova, and knocked out. + +Shipshape Cargo Co. was chosen for Turf War—the Splattercolor Screen was used in this game, so be careful watching replays. The game was solidly in Hypernova’s hands for a majority of the three minutes, taking NME down two, three players multiple times. By the final minute, they had set up a lockout, keeping NME contained in 1/3rd of the map. + +Hypernova won 63.6% – 33.7%, tying with the 2025 NA League’s total ink coverage percentage record, 97.3%\! + +A game five Splat Zones is rare, and unexpected at Undertow Spillway. Early in the game, NME set the goal to reach at 34\. The teams traded wipeouts as Hypernova fought to keep up with NME’s score. In the last five seconds, Hypernova wiped out NME, and in overtime, kept them down three. Just one tick to tie, the zone was neutralized. Hypernova was wiped out, and NME took the win, 88–87. + +## **Loser’s Finals: Moonlight vs. NME (3–1)** + +A brief respite from game five sets, Moonlight and NME would face off to determine who got their runback against Milky Way at Grand Finals. + +The set started with Tower Control at MakoMart, and already 40 seconds in, Moonlight suffered a delayed wipeout and NME was down two players. It took until the halfway point for a checkpoint to be cleared, which was Moonlight’s, but they didn’t get much further after half of their team was splatted. NME took the game into overtime, but lost with a wipeout, and their score was 7–55. + +The most popular Turf War stage from 2025’s NA League, Urchin Underpass, was back, and one minute in, Moonlight was the team with Danger\! in their overhead. NME went down two, but Moonlight was still behind in score. In the last ten seconds, NME was again down two, then three, and Moonlight earned their comeback, 53.8% – 39.8%. + +Next, NME brought Moonlight to their turf, Rainmaker at Mahi-Mahi Resort. Running a double-Ultra Stamp comp, NME replayed the same story as before: their opponent got the first checkpoint, but it was NME going directly from their first checkpoint right into the knockout, leaving two minutes and 19 seconds to spare. + +LF_RMmahi-mahi + +*Pedestal painted this time, 3z is able to squid roll to the win, protected by an Ultra Stamp.* + +Game four was Splat Zones at Crableg Capital. The opening favored NME, overtaking Moonlight’s score of 34, and Moonlight had to use an Ink Vac and Big Bubbler to stop NME at 4, before the knockout. Moonlight rebounded, wiping out NME a minute later, and in the last seconds, Omega got a triple, securing their knockout and set win. + +## **Grand Finals: Milky Way vs. Moonlight (3–2)** + +Back to knocking on Milky Way’s door, Moonlight was ready to take on their biggest opponent and deepen the storyline between the \#1 and \#2 teams from the 2025 NA League. + +Repeating game one from Winner’s Finals, Splat Zones at Mahi-Mahi Resort, the game could be neatly divided into one-minute segments: at 4:11, Milky Way took the lead. At 3:11, Moonlight took the lead. At 2:11, Milky Way was back in the lead. Fast forward to 0:39, Moonlight was ahead once again, and the game went into overtime. Just as Milky Way was about to tie, Moonlight’s Ink Vac protected the zone, ending the game 92–91. + +Repeating game two, Turf War at MakoMart, the first minute was advantageous for Moonlight, with only one player being splatted on their side. Overall, splats were few in the game until the end. Milky Way threw out Triple Inkstrikes at the last second, which was the most crucial play, giving them just enough ink to eke ahead, 49.4% – 47.4%. + +Game three was Tower Control again, but at Crableg Capital rather than Inkblot Art Academy. The result was still the same, as Milky Way rode the tower through checkpoints one, two, and three with speed, knocking out by the three minute mark. + +Interestingly, game four was Clam Blitz… at Shipshape Cargo Co. But it worked out for Moonlight, scoring in the first 40 seconds. At the halfway point, as Milky Way was throwing clams to open Moonlight’s basket, Moonlight beat them to it, forcing Milky Way to turn around. Moonlight was wiped out for this, but they had the lead at 58\. + +GF_CBshipshape + +*Milky Way nowhere to be seen near their basket, instead set up by Moonlight’s basket in anticipation of scoring first.* + +Once the basket closed, Milky Way was already set up and immediately scored. However, they only scored to 60\. Moonlight scored again as overtime began, ending it as soon as it started, and won the game 42–40, and tied the set again. + +Game five again. Rainmaker at Museum d’Alfonsino again. Moonlight had a double Ink Vac comp again. Moonlight cleared their checkpoint, and went to extend their lead, but the Rainmaker reset faster. Milky Way baited out an Ink Vac; taking Moonlight down two, they claimed the lead at 47\. Moonlight lost the Rainmaker in the final seconds and couldn’t pop the shield in time, and without overtime, their run ended short, 42–57. + +Moonlight sitting at second place to Milky Way after two down-to-the-wire game five sets is building a strong storyline so early in the Regular Season, and as more teams end up in Top Cut, who else will enrich the narrative? When will the newly-revitalized FTWin make their entrance? + +With no 3–0 sets on stream, in a strangely Hagglefish Market-less event, the first week of the Regular Season has set a high standard for successive weeks to live up to. + +Make sure you’re there to see it\! + +R1E3_finalbracket + +Original Posting Date: April 21, 2026 at [Splatoon Stronghold](https://www.splatoonstronghold.com/news/na-league-2026-e3-rs1). + +Written and formatted for publication by [YELLOW](https://bsky.app/profile/great-hero-yellow.bsky.social). diff --git a/content/articles/sws26recap-gg1-q2.md b/content/articles/sws26recap-gg1-q2.md new file mode 100644 index 000000000..0742841b2 --- /dev/null +++ b/content/articles/sws26recap-gg1-q2.md @@ -0,0 +1,187 @@ +--- +title: "SWS26 Recap: Global Gauntlet 1, Qualifier 2" +date: 2026-04-23 +author: + - name: YELLOW + link: https://sendou.ink/u/great-hero-yellow +--- + +#### *To no surprise, PxG qualifies for the Global Gauntlet Finals—more interestingly, who’s going with them?* + +SWS26Recap_GG1Q2 + +On Saturday, April 18, 2026, the second qualifier for IPL and AREA CUP’s global tournament series, the Global Gauntlet, took place to decide the final three Western teams who will compete at the First Edition Global Gauntlet Finals. + +Last week, the first three teams—FTWin, ezmd, and Azure—earned their seats, and all six teams from Japan have been revealed by this point. You’ll find all of the teams participating at the end of the article\! + +The double-elimination, Splat Zones-only tournament saw 15 teams step into the ring, including heavy-hitters like PxG, Sun-Eater, healthy diet food groups, and nm. Popgun and Sasu commentated the action, and Ely was on the spectator camera. + +After nearly three and a half hours of action, what went down? + +## **Winner’s Round 1: healthy diet food groups vs. Sooo nights at a lan 😂 (1–2)** + +Kicking off Qualifier 2 was healthy diet food groups, returning from Qualifier 1, and Sooo nights at a lan, a pickup with players who were on team hi last qualifier. The first two rounds of the event were Best of 3 sets. + +Game one, the random map selection, went to Hammerhead Bridge, after healthy diet food groups banned Flounder Heights and Crableg Capital, and Sooo nights at a lan banned Humpback Pump Track and Manta Maria. + +Healthy diet food groups was the first team to take the zone, after 35 seconds of fighting over it in neutral. Midway through the game, Sooo nights at a lan got close to taking the lead, but needed a second push to get it, and began to lock out once there. Healthy diet food groups prevented the knockout, but still lost the game, 76–88. + +Game two went to Museum d’Alfonsino, after Sooo nights at a lan banned Sturgeon Shipyard and Shipshape Cargo Co. + +Once more, healthy diet good groups started with the zone. They didn’t get too far before their opponent took the zone, then the lead. After going down two players each, healthy diet food groups ended up back in the lead, and this would flip-flop a few times before a tense clash in the final seconds where healthy diet food groups stopped Sooo nights at a lan and took the win, 85–81. + +WR1_museum + +*Just five ticks from losing the lead, healthy diet food groups neutralized the zone and would quickly cap it, just before the game ended, securing their win.* + +Game three went to Brinewater Springs; Mincemeat Metalworks and Eeltail Alley were healthy diet food groups’ map strikes. + +Sooo nights at a lan, who selected this map, chose well; they kept the game in their hands for pretty much the whole, short match. After wiping healthy diet food groups out after two minutes had passed, the game ended in a knockout victory for them, and they advanced to the next round. + +## **Winner’s Round 2 \- Black Bull ♣️🐂 vs. NEVER BACK DOWN NEVER WHAT (2–0)** + +The second round in Winner’s Bracket was between Black Bull, a pickup of Black Lotus players, and NEVER BACK DOWN NEVER WHAT, another returning face from Qualifier 1\. This set was still a Best of 3, but the next would be a Best of 5\. + +Game one, Brinewater Springs, was randomly chosen after Black Bull banned Eeltail Alley and Crableg Capital, and NEVER BACK DOWN NEVER WHAT banned Urchin Underpass and Humpback Pump Track. + +The game began with NEVER BACK DOWN NEVER WHAT taking the zone, but within the first 30 seconds, Black Bull took possession and the lead. Black Bull’s run was stopped at 49, but was just a small bump in the road, as they ended up knocking out just past the three-minute mark. + +Game two, Mahi-Mahi Resort, was NEVER BACK DOWN NEVER WHAT’s selection after Black Bull banned Museum d’Alfonsino and Mincemeat Metalworks. + +Once more, NEVER BACK DOWN NEVER WHAT led the game from the start, taking Black Bull down three players, getting all the way to 11 before Black Bull set up a Big Bubbler in the corner to safely break their way into the zone. Just when 11 sounded like a safe lead, Black Bull got the 100-to-0, taking the game just one second slower than the previous one. + +WR2_mahi-mahi + +*SOUL WORLD rallies their Black Bull teammates to the Big Bubbler, initiating the play that would lead the team to a 100-to-0 victory.* + +## **Winner’s Semifinals Part 1 \- PxG vs. French \+ Kenshin (3–1)** + +The bracket skipped over Winner’s Quarterfinals and went right to Winner’s Semifinals, transitioning from a Best of 3 to Best of 5\. PxG was set up to face French \+ Kenshin, for the first of Qualifier 2’s three tickets to the First Edition Global Gauntlet Finals. + +Game one was at Hagglefish Market, chosen after PxG banned Urchin Underpass and Barnacle & Dime, and French \+ Kenshin banned Crableg Capital and Hammerhead Bridge. + +French \+ Kenshin claimed the zone first and was able to get to a respectable score of 61 before PxG locked in. Two minutes into the game, PxG took the lead, being delayed at 34 before knocking out with just over two minutes to spare. + +Game two was at Mahi-Mahi Resort; PxG banned Um’ami Ruins and Undertow Spillway. + +A disconnect (which would be some foreshadowing for later) caused the game to reset, and once it started in full, PxG’s opening saw them hold the zone to 34, where French \+ Kenshin finally capped the zone after several attempts of only neutralizing it. French \+ Kenshin was wiped out by the two-minute mark, and seconds later, PxG had another knockout. + +French \+ Kenshin’s game three selection was Inkblot Art Academy, after PxG banned Shipshape Cargo Co. and Wahoo World. + +French \+ Kenshin’s opening push got them incredibly far, to 25, before PxG could take the zone. It took PxG about a minute to take the lead, and they were so close to knocking out, but were stopped at 4\. With one minute remaining, French \+ Kenshin at 9 and PxG at 4, Gos disconnected, and PxG was not able to maintain their lead, losing it in the final 20 seconds, and French \+ Kenshin had their own knockout in the books. + +WSF_inkblot + +*PxG held the lead in the 3v4 for an impressive amount of time, but one E-liter 4K can’t keep the zone against three opponents.* + +PxG counterpicked to MakoMart after French \+ Kenshin banned Museum d’Alfonsino and Mincemeat Metalworks. + +PxG came back with a vengeance in this game, wiping out their opponent twice in the first minute. PxG made it to 21 before French \+ Kenshin took the zone from them, but only for four points before they cycled being down two players, and ultimately, PxG took the final knockout, securing their \#4 spot at the Global Gauntlet Finals\! + +## **Winner’s Semifinals Part 2: Sooo nights at a lan 😂 vs. Black Bull ♣️🐂 (2–3)** + +On the other side of the bracket, for the second ticket to the GG Finals, was Sooo nights at a lan and Black Bull, for their second time each on stream. Sooo nights at a lan (seed 10\) has upset seeds 7 (healthy diet food groups) and 2 (nm) to get this far, and was looking to upset another in Black Bull, who was seed 3\. + +Urchin Underpass was game one’s map; Black Bull banned Inkblot Art Academy and Museum d’Alfonsino, and Sooo nights at a lan banned Wahoo World and Manta Maria. + +Sooo nights at a lan started the match with the zone, but by the end of the first minute, Black Bull had the zone and lead, ticking down points, and almost knocked out, finally losing the zone at 2\. With 70 ticks and 24 penalty points, Sooo nights at a lan went the distance in one sweep, knocking out as the clock read 2:10. + +WSF2_urchin + +Game two went to Um’ami Ruins by Black Bull’s choice, seeing strikes on Hammerhead Bridge and Mincemeat Metalworks. + +The first major lead in the game occurred after Sooo nights at a lan, who only had a lead of 1 point, won a 20-second firefight over the neutral zones, and was able to get to 55 before Black Bull had their turn. Black Bull locked out their opponent, taking them down three players to get the lead, and knocked out for the win. + +Game three went to Scorch Gorge as Sooo nights at a lan’s counterpick after Black Bull banned Humpback Pump Track and Shipshape Cargo Co. + +It took 40 seconds for the zone to be capped; Sooo nights at a lan had it up until they reached 42\. After having the zone for about 30 seconds, Black Bull was in the lead and extended it to 19\. Both teams started to trade possession, and Sooo nights at a lan tied with Black Bull, unfortunately ending up one point behind after they lost the zone. However, they came back and wiped out Black Bull, getting the win with time to spare. + +Game four, Black Bull’s counterpick, went to Robo ROM-en. Sooo nights at a lan banned Bluefin Depot and Crableg Capital. + +Sooo nights at a lan had a strong lead, keeping ahead of Black Bull even after they took the zone. Black Bull eventually got their lead after wiping out Sooo nights at a lan, and their opposition couldn’t regroup and retake, resulting in another knockout for Black Bull. + +After trading wins, the set was tied 2–2, going to a game five, where Black Bull banned MakoMart and Undertow Spillway. Sooo nights at a lan selected Hagglefish Market. + +First Black Bull had the lead, then Sooo nights at a lan had the lead, both in the same minute. The zone flipped back and forth, finally settling with Black Bull, who sealed the game with a Triple Splashdown for the knockout, set win, and \#5 spot at the GG Finals\! + +## **Loser’s Round 4: Sun-Eater vs. Sooo nights at a lan 😂 (1–2)** + +Once the Winner’s Bracket concluded, the stream would see two sets in the Loser’s Bracket to determine the final Western team for the GG Finals. Since this was not Quarterfinals, it was another Best of 3 set. + +As some fun trivia provided by Ely, we learned that Sooo nights at a lan was named that because one of their players, Nightmare, couldn’t play in the tournament due to playing at the Sunset Surge LAN. + +Game one, Mahi-Mahi Resort, was selected from the map pool after Sun-Eater banned Shipshape Cargo Co. and Marlin Airport and Sooo nights at a lan banned Crableg Capital and Hammerhead Bridge. + +Although Sun-Eater began with the zone, they lost it quickly. The water on the map was already dropping before the first minute was up, as Sooo nights at a lan extended their lead. One minute and a half into the game, Sooo nights at a lan knocked Sun-Eater out. + +Much to the dismay of the commentators, Sun-Eater selected Wahoo World for game two. Bluefin Depot and Museum d’Alfonsino were banned. + +Sooo nights at a lan’s recurring tactic was to use their Crab Tank to paint the zone offensively, and again and again it worked against Sun-Eater. Each time, Sun-Eater bounced back stronger, ending up back in the lead. In the final seconds, they secured their win by taking the zone back, and the final score was 77–63. + +LR4_wahooworld + +*The Crab Tank that caused Sun-Eater so much trouble, just one of the many instances where it both protected and painted the zone.* + +Game three, determining who went to Loser’s Semifinals, went to MakoMart after Sun-Eater banned Humpback Pump Track and Flounder Heights. + +Sun-Eater started strong, holding the zone for most of the first minute. While their respawns kept staggering, Sooo nights at a lan capitalized to take the lead. Both teams kept one-upping the other with the lead. The only game to go into overtime, and Sun-Eater was just one point away from winning before Sooo nights at a lan overpowered them, winning 91–90 and advancing to face nm in Loser’s Semifinals. + +## **Loser’s Semifinals: Sooo nights at a lan 😂 vs. nm (0–3)** + +The final ticket to the GG Finals on April 24th\! This was not the first time Sooo nights at a lan met nm in this tournament—in fact, Sooo nights at a lan was the team who sent nm to the Loser’s Bracket 2-1 in Winner’s Round 2\. While Sooo nights at a lan had taken every set thus far to the last possible game, nm meanwhile had torn through the Loser’s Bracket 2–0 in every set. + +While waiting for the set to start, nm was in the Twitch chat talking with Popgun and Sasu, and revealed that the name “nm” stands for “No Mercy”, which was exactly what they were about to put on display. + +Game one went to Sturgeon Shipyard. Nm banned Humpback Pump Track and Undertow Spillway. Sooo nights at a lan banned Hagglefish Market and Manta Maria. + +Early on, nm claimed the zone, taking down two players on the opposing team. Sooo nights at a lan tried to paint with their Crab Tank, but it was shot down before it could even neutralize the zone. Going 100-to-0, nm ended the first game in a knockout. This was the first game one in the entire tournament that Sooo nights at a lan didn’t win. + +MakoMart was selected for game two after nm banned Flounder Heights and Um’ami Ruins. + +Nm guarded the zone so well that Sooo nights at a lan could only take shots at the objective from afar. Sooo nights at a lan was able to take the zone a few times this game, but never held it for long. After a wipeout, nm earned another knockout victory. + +LSF_makomart + +*Sooo nights at a lan, using their Crab Tank to try breaking into the zone, only for it to be taken out momentarily by an Ultra Stamp.* + +Game three, match point for nm, went to Brinewater Springs after Urchin Underpass and Inkblot Art Academy were banned. + +Leading the charge with an Ink Vac, nm took the zone first. After going down two players, Sooo nights at a lan had a chance to take the zone and keep their opponent at 40 points. They took their turn with the lead, defended it against nm a few times, but nm had the last say, wresting control and getting one final, decisive knockout to end the set 3–0 and take the \#6 spot on the West’s GG Finals roster\! + +### **The First Edition Global Gauntlet Finals: Who’s Going?** + +The First Edition Global Gauntlet Finals takes place during the International Timeslot, which is 8 PM PT / 11 PM ET on Friday, April 24th for North America and 5 AM CET / 12 PM JT on Saturday, April 25th for the EU and Japan. + +From the West, the six teams have been decided: + +1. FTWin +2. ezmd +3. Azure +4. PxG +5. Black Lotus (Black Bull) +6. No Mercy (nm) + +From Japan, AREA CUP has revealed all six teams attending: + +1. Zest +2. Utopia +3. 07 Quartet +4. EmpEror +5. swing +6. ISM rhythm + +EmpEror, a newly-announced addition to Japan’s GG Finals team, is composed of Lobster and Naegora, two champions from Splat World Series 2025’s \#1 team The Invincible Fleet Rei Maru, along with Neespa and Chiaki. + +Speaking of The Invincible Fleet Rei Maru, their player Reimaru is competing as part of ‘swing’, a group made up of Momo and Norishio from last SWS’s team DragonReX, Reimaru, and Streamer Sena. Momo recently played in the SendouQ Season 10 Finale last month on Flow Dragons, with Black Lotus’s captain, Noctis, and took the second place silver, next to PxG’s first place gold. + +The last-announced team, revealed one day before the Finals take place, ISM rhythm, has the final player from The Invincible Fleet Rei Maru, Grandroll, playing with new teammates Livia, Yocchan Ika, and Goyame. + +Following the Splat World Series last year, the West has made a strong showing of taking part in more Japanese events, such as AREA CUP, or just meeting together for scrims. The Global Gauntlets, created as a warm up to prepare both sides for the Splat World Series, will give players much to strategize over after the premiere Finals event. + +Last time teams of this caliber met, only PxG walked away with a win over a Japanese team. How has the West used their time to close the gap? + +Be there on April 24th to find out\! + +Original Posting Date: April 23, 2026 at [Splatoon Stronghold](https://www.splatoonstronghold.com/news/sws26-recap-gg1-q2). + +Written and formatted for publication by [YELLOW](https://bsky.app/profile/great-hero-yellow.bsky.social). diff --git a/locales/da/art.json b/locales/da/art.json index cc15c1e11..92c5a3710 100644 --- a/locales/da/art.json +++ b/locales/da/art.json @@ -9,7 +9,6 @@ "filteringByTag": "Viser resultater filtreret efter #{{tag}}", "commissionsOpen": "Åben for bestillinger", "commissionsClosed": "Lukket for bestillinger", - "openCommissionsOnly": "Vis kunstnere med åbne bestillinger", "gainPerms": "Lav venligt et opslad til vores helpdesk på vores Discord-server for at få tilladelse til at uploade kunst. Bemærk venligt, at du skal være kunstneren af det kunst, som du uploader, og kun Splatoon-relateret kunst tillades.", "tabs.recentlyUploaded": "", "tabs.showcase": "", diff --git a/locales/da/contributions.json b/locales/da/contributions.json index d3b6e918f..6d60cd1da 100644 --- a/locales/da/contributions.json +++ b/locales/da/contributions.json @@ -1,5 +1,5 @@ { - "project": "Sendou.ink er et projekt af <2>Sendou med hjælp fra bidragsydere:", + "project": "Sendou.ink er et projekt af Sendou med hjælp fra bidragsydere:", "code": "Se alle der har hjulpet med kodningen", "lean": "Hjalp med at fremvise Splatoons 'indre' og skabte Lanista-botten", "borzoic": "Lavede mærker, ikoner og kunst til forsiden", diff --git a/locales/de/art.json b/locales/de/art.json index d75450428..26d9f7108 100644 --- a/locales/de/art.json +++ b/locales/de/art.json @@ -9,7 +9,6 @@ "filteringByTag": "", "commissionsOpen": "", "commissionsClosed": "", - "openCommissionsOnly": "", "gainPerms": "", "tabs.recentlyUploaded": "", "tabs.showcase": "", diff --git a/locales/de/contributions.json b/locales/de/contributions.json index 0341ac459..d45f323e1 100644 --- a/locales/de/contributions.json +++ b/locales/de/contributions.json @@ -1,5 +1,5 @@ { - "project": "Sendou.ink ist ein Projekt von <2>Sendou - mit der Hilfe von Mitwirkenden:", + "project": "Sendou.ink ist ein Projekt von Sendou - mit der Hilfe von Mitwirkenden:", "code": "Alle am Code Mitwirkenden ansehen", "lean": "Hilft mit der Analyse von Splatoon-Internals und hat den Lanista-Bot erstellt", "borzoic": "Erstellte Abzeichen, Icons und Grafiken auf der Homepage", diff --git a/locales/en/art.json b/locales/en/art.json index 785c750b9..9afa33411 100644 --- a/locales/en/art.json +++ b/locales/en/art.json @@ -9,7 +9,6 @@ "filteringByTag": "Showing results filtered by #{{tag}}", "commissionsOpen": "Commissions are open", "commissionsClosed": "Commissions are closed", - "openCommissionsOnly": "Show artists with open commissions", "gainPerms": "Please post on the helpdesk of our Discord to gain permissions to upload art. Note that you must be the artist of the art you are uploading and only Splatoon related art is allowed.", "tabs.recentlyUploaded": "Recently Uploaded", "tabs.showcase": "Showcase", diff --git a/locales/en/common.json b/locales/en/common.json index cf4f5f84f..a45381b3b 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -79,11 +79,11 @@ "notifications.title.SCRIM_NEW_REQUEST": "New Scrim Request", "notifications.text.SCRIM_NEW_REQUEST": "{{fromUsername}} requested a scrim", "notifications.title.SCRIM_SCHEDULED": "Scrim Scheduled", - "notifications.text.SCRIM_SCHEDULED": "New scrim scheduled at {{timeString}}", + "notifications.text.SCRIM_SCHEDULED": "New scrim scheduled vs. {{opponentTeamName}}", "notifications.title.SCRIM_CANCELED": "Scrim Canceled", - "notifications.text.SCRIM_CANCELED": "The scrim at {{timeString}} was canceled", + "notifications.text.SCRIM_CANCELED": "The scrim vs. {{opponentTeamName}} was canceled", "notifications.title.SCRIM_STARTING_SOON": "Scrim Starting Soon", - "notifications.text.SCRIM_STARTING_SOON": "Your scrim at {{timeString}} is starting soon", + "notifications.text.SCRIM_STARTING_SOON": "Your scrim vs. {{opponentTeamName}} is starting soon", "notifications.title.COMMISSIONS_CLOSED": "Commissions Closed", "notifications.text.COMMISSIONS_CLOSED": "If your commissions are still open, please re-enable them", "notifications.title.FRIEND_REQUEST_RECEIVED": "Friend Request", diff --git a/locales/en/contributions.json b/locales/en/contributions.json index 867b71e25..4316728b9 100644 --- a/locales/en/contributions.json +++ b/locales/en/contributions.json @@ -1,5 +1,5 @@ { - "project": "Sendou.ink is a project by <2>Sendou with help from contributors:", + "project": "Sendou.ink is a project by Sendou with help from contributors:", "code": "See all code contributors", "lean": "Helped with uncovering Splatoon internals and created the Lanista bot", "borzoic": "Made badges, icons and front page art", diff --git a/locales/es-ES/art.json b/locales/es-ES/art.json index d009261c2..86c290159 100644 --- a/locales/es-ES/art.json +++ b/locales/es-ES/art.json @@ -10,7 +10,6 @@ "filteringByTag": "Mostrando resultados filtrados por #{{tag}}", "commissionsOpen": "Comisiones abiertas", "commissionsClosed": "Comisiones cerradas", - "openCommissionsOnly": "Mostrar artistas con comisiones abiertas", "gainPerms": "Por favor manda mensaje en el 'helpdesk' de nuestro Discord para obtener permiso para subir arte. Debes ser el artista que creó el arte que subas, y solo se permite arte relacionado con Splatoon.", "tabs.recentlyUploaded": "Subidas recientemente", "tabs.showcase": "Destacadas", diff --git a/locales/es-ES/common.json b/locales/es-ES/common.json index ccf1daeb7..7ced46ab8 100644 --- a/locales/es-ES/common.json +++ b/locales/es-ES/common.json @@ -79,11 +79,11 @@ "notifications.title.SCRIM_NEW_REQUEST": "Nueva Solicitud de Scrim", "notifications.text.SCRIM_NEW_REQUEST": "{{fromUsername}} ha solicitado un scrim", "notifications.title.SCRIM_SCHEDULED": "Scrim Programado", - "notifications.text.SCRIM_SCHEDULED": "Nuevo scrim programado para las {{timeString}}", + "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "Scrim Cancelado", - "notifications.text.SCRIM_CANCELED": "El scrim de las {{timeString}} fue cancelado", + "notifications.text.SCRIM_CANCELED": "", "notifications.title.SCRIM_STARTING_SOON": "El scrim empieza pronto", - "notifications.text.SCRIM_STARTING_SOON": "Tu scrim de las {{timeString}} empieza pronto", + "notifications.text.SCRIM_STARTING_SOON": "", "notifications.title.COMMISSIONS_CLOSED": "Comisiones Cerradas", "notifications.text.COMMISSIONS_CLOSED": "Si tus comisiones siguen abiertas, por favor vuelve a activarlas", "notifications.title.FRIEND_REQUEST_RECEIVED": "", diff --git a/locales/es-ES/contributions.json b/locales/es-ES/contributions.json index f37dabf09..8c1908be1 100644 --- a/locales/es-ES/contributions.json +++ b/locales/es-ES/contributions.json @@ -1,5 +1,5 @@ { - "project": "Sendou.ink es un proyecto hecho por <2>Sendou con ayuda de contribuidores:", + "project": "Sendou.ink es un proyecto hecho por Sendou con ayuda de contribuidores:", "code": "Ver todos los contribuidores", "lean": "Ayudó a descubrir partes internas de Splatoon y creó el bot Lanista", "borzoic": "Creó insignias, íconos y el arte de la página principal", diff --git a/locales/es-US/art.json b/locales/es-US/art.json index 0064019cd..4c2ac7c37 100644 --- a/locales/es-US/art.json +++ b/locales/es-US/art.json @@ -10,7 +10,6 @@ "filteringByTag": "Mostrando resultados filtrados por #{{tag}}", "commissionsOpen": "Comisiones abiertas", "commissionsClosed": "Comisiones cerradas", - "openCommissionsOnly": "Mostrar artistas con comisiones abiertas", "gainPerms": "Por favor manda mensaje en el 'helpdesk' de nuestro Discord para obtener permiso para subir arte. Debes ser el artista que creó el arte que subas, y solo se permite arte relacionada con Splatoon.", "tabs.recentlyUploaded": "", "tabs.showcase": "", diff --git a/locales/es-US/contributions.json b/locales/es-US/contributions.json index 50daba4c7..ecaae3b26 100644 --- a/locales/es-US/contributions.json +++ b/locales/es-US/contributions.json @@ -1,5 +1,5 @@ { - "project": "Sendou.ink es un proyecto hecho por <2>Sendou con ayuda de contribuidores:", + "project": "Sendou.ink es un proyecto hecho por Sendou con ayuda de contribuidores:", "code": "Ver todos los contribuidores", "lean": "Ayudó a descubrir partes internas de Splatoon y creó el bot Lanista", "borzoic": "Creó insignias, íconos y el arte de la página principal", diff --git a/locales/fr-CA/art.json b/locales/fr-CA/art.json index 8bff08a1d..438c93ae5 100644 --- a/locales/fr-CA/art.json +++ b/locales/fr-CA/art.json @@ -10,7 +10,6 @@ "filteringByTag": "Affichage des résultats filtrés par #{{tag}}", "commissionsOpen": "Accepte les commissions", "commissionsClosed": "N'accepte pas les commissions", - "openCommissionsOnly": "Ne montrer que les artistes qui acceptent les commissions", "gainPerms": "", "tabs.recentlyUploaded": "", "tabs.showcase": "", diff --git a/locales/fr-CA/contributions.json b/locales/fr-CA/contributions.json index cafea0a07..c12a13366 100644 --- a/locales/fr-CA/contributions.json +++ b/locales/fr-CA/contributions.json @@ -1,5 +1,5 @@ { - "project": "Sendou.ink est un projet créé par <2>Sendou avec l'aide de contributeurs :", + "project": "Sendou.ink est un projet créé par Sendou avec l'aide de contributeurs :", "code": "Voir tous les contributeurs au code", "lean": "A aidé à découvrir les fonctionnements internes de Splatoon et a créé le bot Lanista", "borzoic": "A créé les badges, les icônes et l'illustration de la page d'accueil", diff --git a/locales/fr-EU/art.json b/locales/fr-EU/art.json index f9da97048..1094b9fe0 100644 --- a/locales/fr-EU/art.json +++ b/locales/fr-EU/art.json @@ -10,7 +10,6 @@ "filteringByTag": "Affichage des résultats filtrés par #{{tag}}", "commissionsOpen": "Accepte les commissions", "commissionsClosed": "N'accepte pas les commissions", - "openCommissionsOnly": "Ne montrer que les artistes qui acceptent les commissions", "gainPerms": "Vous pouvez demander dans le salon ''helpdesk'' sur notre discord pour avoir cette permission. Note: vous devez êtres l'artist pour publier votre création, celle-ci doit être seulement en rapport avec Splatoon.", "tabs.recentlyUploaded": "", "tabs.showcase": "", diff --git a/locales/fr-EU/common.json b/locales/fr-EU/common.json index 1e3f3a6f6..32c1afb3b 100644 --- a/locales/fr-EU/common.json +++ b/locales/fr-EU/common.json @@ -79,7 +79,7 @@ "notifications.title.SCRIM_NEW_REQUEST": "Nouvelle Demande De Scrim", "notifications.text.SCRIM_NEW_REQUEST": "{{fromUsername}} vous demande de scrim", "notifications.title.SCRIM_SCHEDULED": "Scrim Programmé", - "notifications.text.SCRIM_SCHEDULED": "Nouveau scrim programmé à {{timeString}}", + "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", "notifications.title.SCRIM_STARTING_SOON": "", diff --git a/locales/fr-EU/contributions.json b/locales/fr-EU/contributions.json index 5a47c1d57..fccf5576b 100644 --- a/locales/fr-EU/contributions.json +++ b/locales/fr-EU/contributions.json @@ -1,5 +1,5 @@ { - "project": "Sendou.ink est un projet créé par <2>Sendou avec l'aide de contributeurs :", + "project": "Sendou.ink est un projet créé par Sendou avec l'aide de contributeurs :", "code": "Voir tous les contributeurs au code", "lean": "A aidé à découvrir les fonctionnements internes de Splatoon et a créé le bot Lanista", "borzoic": "A créé les badges, les icônes et l'illustration de la page d'accueil", diff --git a/locales/he/art.json b/locales/he/art.json index 6488f515c..f64df4eb3 100644 --- a/locales/he/art.json +++ b/locales/he/art.json @@ -10,7 +10,6 @@ "filteringByTag": "מראה תוצאות לפי סינון של #{{tag}}", "commissionsOpen": "בקשות פתוחות", "commissionsClosed": "בקשות סגורות", - "openCommissionsOnly": "הראה אומנים עם בקשות פתוחות", "gainPerms": "נא לכתוב בערוץ helpdesk בדיסקורד כדי לקבל הרשאות להעלות ציורים. שימו לב שאתם חייבים להיות יוצר הציור ורק אמנות הקשורה ל-Splatoon מותרת..", "tabs.recentlyUploaded": "הועלה לאחרונה", "tabs.showcase": "תצוגה", diff --git a/locales/he/contributions.json b/locales/he/contributions.json index 7a7ceffd3..874e110d0 100644 --- a/locales/he/contributions.json +++ b/locales/he/contributions.json @@ -1,5 +1,5 @@ { - "project": "Sendou.ink הוא פרויקט של <2>Sendou עם עזרה מהתורמים:", + "project": "Sendou.ink הוא פרויקט של Sendou עם עזרה מהתורמים:", "code": "ראה את כל תורמי הקוד", "lean": "עזר בחשיפת מידע פנימי של Splatoon ויצירת הבוט Lanista", "borzoic": "הכין תגים, אייקונים וציור לעמוד הראשון", diff --git a/locales/it/art.json b/locales/it/art.json index a14696355..e09ea1a4d 100644 --- a/locales/it/art.json +++ b/locales/it/art.json @@ -10,7 +10,6 @@ "filteringByTag": "Mostrando risultati filtrati da #{{tag}}", "commissionsOpen": "Commissioni aperte", "commissionsClosed": "Commissioni chiuse", - "openCommissionsOnly": "Mostra artisti con commissioni aperte", "gainPerms": "Si prega di postare sull'helpdesk del nostro Discord per ottenere i permessi per caricare art. Nota che devi essere tu l'artista dell'art che stai caricando, e solo art relative a Splatoon sono ammesse.", "tabs.recentlyUploaded": "", "tabs.showcase": "", diff --git a/locales/it/contributions.json b/locales/it/contributions.json index fe2b4e0f6..b23dd94d1 100644 --- a/locales/it/contributions.json +++ b/locales/it/contributions.json @@ -1,5 +1,5 @@ { - "project": "Sendou.ink è un proggeto creato da <2>Sendou con aiuto dai contributori:", + "project": "Sendou.ink è un proggeto creato da Sendou con aiuto dai contributori:", "code": "Vedi tutti i contributori al codice sorgente", "lean": "Ha aiutato scoprendo le mecchaniche interne di Splatoon e creando il bot Lanista", "borzoic": "Ha creato le medaglie, le icone e l'arte sulla pagina principale", diff --git a/locales/ja/art.json b/locales/ja/art.json index d36fd4bba..8cf737e9b 100644 --- a/locales/ja/art.json +++ b/locales/ja/art.json @@ -7,7 +7,6 @@ "filteringByTag": "#{{tag}} で絞り込まれた結果を表示中", "commissionsOpen": "依頼を受付中", "commissionsClosed": "依頼の受付なし", - "openCommissionsOnly": "依頼を受付中のアーティストを表示", "gainPerms": "作品をアップロードしたい場合は私たちのディスコードサーバーのヘルプデスクで許可を得てください。アップロードするには作品の作者でないといけません。また、スプラトゥーン関連の作品のみアップロードできます。", "tabs.recentlyUploaded": "", "tabs.showcase": "", diff --git a/locales/ja/contributions.json b/locales/ja/contributions.json index 320b40a11..cdcce4b19 100644 --- a/locales/ja/contributions.json +++ b/locales/ja/contributions.json @@ -1,5 +1,5 @@ { - "project": "Sendou.ink は <2>Sendou によるプロジェクトで、以下のコントリビューターに支えられています:", + "project": "Sendou.ink は Sendou によるプロジェクトで、以下のコントリビューターに支えられています:", "code": "すべてのコードコントリビューターを見る", "lean": "Splatoon 内部理解のサポート、Lanista bot の作成", "borzoic": "バッジ、アイコン、トップページアートの作成", diff --git a/locales/ko/art.json b/locales/ko/art.json index 8e8b0deda..6b365fbd1 100644 --- a/locales/ko/art.json +++ b/locales/ko/art.json @@ -7,7 +7,6 @@ "filteringByTag": "", "commissionsOpen": "", "commissionsClosed": "", - "openCommissionsOnly": "", "gainPerms": "", "tabs.recentlyUploaded": "", "tabs.showcase": "", diff --git a/locales/ko/contributions.json b/locales/ko/contributions.json index d734e2e3b..b2b7ae553 100644 --- a/locales/ko/contributions.json +++ b/locales/ko/contributions.json @@ -1,5 +1,5 @@ { - "project": "Sendou.ink는 <2>Sendou가 기여자들의 도움을 받아 만든 프로젝트입니다:", + "project": "Sendou.ink는 Sendou가 기여자들의 도움을 받아 만든 프로젝트입니다:", "code": "전체 코드 기여자 확인", "lean": "스플래툰의 내부를 파헤치는 데에 도움을 주고 Lanista bot을 만들었습니다", "borzoic": "배지, 아이콘과 표지 그림을 만들었습니다", diff --git a/locales/nl/art.json b/locales/nl/art.json index d75450428..26d9f7108 100644 --- a/locales/nl/art.json +++ b/locales/nl/art.json @@ -9,7 +9,6 @@ "filteringByTag": "", "commissionsOpen": "", "commissionsClosed": "", - "openCommissionsOnly": "", "gainPerms": "", "tabs.recentlyUploaded": "", "tabs.showcase": "", diff --git a/locales/nl/contributions.json b/locales/nl/contributions.json index 6ff436d37..79df59745 100644 --- a/locales/nl/contributions.json +++ b/locales/nl/contributions.json @@ -1,5 +1,5 @@ { - "project": "Sendou.ink is een project door <2>Sendou met behulp van de volgende bijdragers:", + "project": "Sendou.ink is een project door Sendou met behulp van de volgende bijdragers:", "code": "", "lean": "Heeft geholpen met het onthullingen van de interne werkingen van Splatoon en heeft ook de Lanista bot ontwikkeld.", "borzoic": "Heeft badges, iconen en de voorpagina art gemaakt.", diff --git a/locales/pl/art.json b/locales/pl/art.json index 8c1aece3c..4d96340cd 100644 --- a/locales/pl/art.json +++ b/locales/pl/art.json @@ -11,7 +11,6 @@ "filteringByTag": "", "commissionsOpen": "", "commissionsClosed": "", - "openCommissionsOnly": "", "gainPerms": "", "tabs.recentlyUploaded": "", "tabs.showcase": "", diff --git a/locales/pl/contributions.json b/locales/pl/contributions.json index ddff3e83a..0b55d5e32 100644 --- a/locales/pl/contributions.json +++ b/locales/pl/contributions.json @@ -1,5 +1,5 @@ { - "project": "Sendou.ink jest projektem zrobionym przez <2>Sendou wraz z pomocą innych współtwórców:", + "project": "Sendou.ink jest projektem zrobionym przez Sendou wraz z pomocą innych współtwórców:", "code": "Zobacz wszystkich współtwórców kodu", "lean": "Pomógł z odkrywaniem wnętrz Splatoona oraz stworzył Lanista bot", "borzoic": "Stworzyła odznaki, ikony i ilustracje na głównej stronie", diff --git a/locales/pt-BR/art.json b/locales/pt-BR/art.json index 60d87e22e..9fd086fdc 100644 --- a/locales/pt-BR/art.json +++ b/locales/pt-BR/art.json @@ -10,7 +10,6 @@ "filteringByTag": "Mostrando resultados filtrados por/pela #{{tag}}", "commissionsOpen": "Comissões estão abertas", "commissionsClosed": "Comissões estão fechadas", - "openCommissionsOnly": "Mostrar somente artistas com comissões abertas", "gainPerms": "Por favor, poste na central de ajuda (helpdesk em Inglês) do nosso Discord para ganhar permissões para fazer o upload de arte. Lembre-se que você precisa ser o artista da arte da qual você está fazendo o upload e que apenas arte relacionada com Splatoon é permitida.", "tabs.recentlyUploaded": "", "tabs.showcase": "", diff --git a/locales/pt-BR/contributions.json b/locales/pt-BR/contributions.json index 15292073d..8bf05e8ad 100644 --- a/locales/pt-BR/contributions.json +++ b/locales/pt-BR/contributions.json @@ -1,5 +1,5 @@ { - "project": "Sendou.ink é um projeto por <2>Sendou com ajuda dos contribuidores(as):", + "project": "Sendou.ink é um projeto por Sendou com ajuda dos contribuidores(as):", "code": "Ver todos os contribuidores(as) de código", "lean": "Ajudou com as descobertas do sistema interno do Splatoon e criou o bot Lanista", "borzoic": "Fez as insígnias, ícones e arte da página inicial", diff --git a/locales/ru/art.json b/locales/ru/art.json index 398ceb4ba..14dd7f67f 100644 --- a/locales/ru/art.json +++ b/locales/ru/art.json @@ -11,7 +11,6 @@ "filteringByTag": "Результаты по фильтру #{{tag}}", "commissionsOpen": "Заказы открыты", "commissionsClosed": "Заказы закрыты", - "openCommissionsOnly": "Показать художников с открытыми заказами", "gainPerms": "Пожалуйста, напишите в helpdesk на нашем Discord сервере, чтобы получить доступ к загрузке артов. Учтите, что вы должны быть автором артов, которые вы загружаете. Арты должны быть строго по тематике Splatoon.", "tabs.recentlyUploaded": "", "tabs.showcase": "", diff --git a/locales/ru/common.json b/locales/ru/common.json index f044486d2..b7595947f 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -79,7 +79,7 @@ "notifications.title.SCRIM_NEW_REQUEST": "Новый Скрим Запрос", "notifications.text.SCRIM_NEW_REQUEST": "{{fromUsername}} запросил скрим", "notifications.title.SCRIM_SCHEDULED": "Скрим Запланирован", - "notifications.text.SCRIM_SCHEDULED": "Новый скрим запланирован на {{timeString}}", + "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", "notifications.title.SCRIM_STARTING_SOON": "", diff --git a/locales/ru/contributions.json b/locales/ru/contributions.json index 549db390a..d1cbf2064 100644 --- a/locales/ru/contributions.json +++ b/locales/ru/contributions.json @@ -1,5 +1,5 @@ { - "project": "Sendou.ink это проект, созданный <2>Sendou с поддержкой помощников:", + "project": "Sendou.ink это проект, созданный Sendou с поддержкой помощников:", "code": "Посмотреть всех, кто внёс вклад в код", "lean": "Помощь с исследованием внутренностей Splatoon и создатель бота Lanista", "borzoic": "Создатель рисунка на главной странице, а также значков и иконок", diff --git a/locales/zh/art.json b/locales/zh/art.json index fa63e8ead..843f81861 100644 --- a/locales/zh/art.json +++ b/locales/zh/art.json @@ -7,7 +7,6 @@ "filteringByTag": "正在显示包含 #{{tag}} 的结果", "commissionsOpen": "委托开放中", "commissionsClosed": "委托未开放", - "openCommissionsOnly": "显示开放委托的创作者", "gainPerms": "请在我们Discord中的helpdesk申请上传作品的权限。请注意您必须是作品的原作者并且只能上传斯普拉遁相关作品。", "tabs.recentlyUploaded": "", "tabs.showcase": "", diff --git a/locales/zh/contributions.json b/locales/zh/contributions.json index e271b322d..9c53240da 100644 --- a/locales/zh/contributions.json +++ b/locales/zh/contributions.json @@ -1,5 +1,5 @@ { - "project": "Sendou.ink是<2>Sendou在以下贡献者的帮助下创建的:", + "project": "Sendou.ink是Sendou在以下贡献者的帮助下创建的:", "code": "查看所有代码贡献者", "lean": "帮助破解斯普拉遁并创建了Lanista bot", "borzoic": "制作徽章、图标,设计首页", diff --git a/package.json b/package.json index 6a57a2c2f..39f7740fc 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "biome:fix": "biome check --error-on-warnings --write .", "biome:fix:unsafe": "biome check --error-on-warnings --write --unsafe .", "typecheck": "react-router typegen && tsc --noEmit", + "typecheck:scripts": "tsc --noEmit -p scripts", "test:unit:browser": "cross-env VITE_SITE_DOMAIN=http://localhost:5173 BROWSER_HEADLESS=true vitest --silent=passed-only run", "test:browser:ui": "cross-env VITE_SITE_DOMAIN=http://localhost:5173 vitest --silent=passed-only --project browser", "test:unit:browser:ui": "cross-env VITE_SITE_DOMAIN=http://localhost:5173 vitest --silent=passed-only", @@ -46,12 +47,12 @@ "@epic-web/cachified": "5.6.2", "@faker-js/faker": "10.4.0", "@formatjs/intl-durationformat": "0.10.4", - "@internationalized/date": "3.12.0", + "@internationalized/date": "3.12.1", "@react-router/node": "7.14.1", "@react-router/serve": "7.14.1", "@remix-run/form-data-parser": "0.16.0", "@tldraw/tldraw": "3.12.1", - "@zumer/snapdom": "2.8.0", + "@zumer/snapdom": "2.9.0", "aws-sdk": "2.1693.0", "better-sqlite3": "12.9.0", "clsx": "2.1.1", @@ -61,7 +62,7 @@ "gray-matter": "4.0.3", "i18next": "25.10.10", "i18next-browser-languagedetector": "8.2.1", - "i18next-http-backend": "3.0.4", + "i18next-http-backend": "3.0.5", "ics": "3.11.0", "isbot": "5.1.38", "jsoncrush": "1.1.8", @@ -69,7 +70,7 @@ "lru-cache": "11.3.5", "lucide-react": "1.8.0", "markdown-to-jsx": "9.7.15", - "nanoid": "5.1.7", + "nanoid": "5.1.9", "neverthrow": "8.2.0", "node-cron": "4.2.1", "nprogress": "0.2.0", @@ -78,7 +79,7 @@ "partysocket": "1.1.16", "qrcode.react": "4.2.0", "react": "19.2.5", - "react-aria-components": "1.16.0", + "react-aria-components": "1.17.0", "react-charts": "3.0.0-beta.57", "react-dom": "19.2.5", "react-error-boundary": "6.1.1", @@ -98,7 +99,7 @@ "zod": "4.3.6" }, "devDependencies": { - "@biomejs/biome": "2.4.11", + "@biomejs/biome": "2.4.13", "@playwright/test": "1.59.1", "@react-router/dev": "7.14.1", "@types/better-sqlite3": "7.6.13", @@ -118,7 +119,7 @@ "ley": "0.8.1", "sql-formatter": "15.7.3", "tsx": "4.21.0", - "typescript": "5.9.3", + "typescript": "6.0.3", "vite": "8.0.8", "vite-node": "6.0.0", "vite-plugin-babel": "1.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4aca7e400..2fb8b9ca2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,14 +39,14 @@ importers: specifier: 0.10.4 version: 0.10.4 '@internationalized/date': - specifier: 3.12.0 - version: 3.12.0 + specifier: 3.12.1 + version: 3.12.1 '@react-router/node': specifier: 7.14.1 - version: 7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3) + version: 7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3) '@react-router/serve': specifier: 7.14.1 - version: 7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3) + version: 7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3) '@remix-run/form-data-parser': specifier: 0.16.0 version: 0.16.0 @@ -54,8 +54,8 @@ importers: specifier: 3.12.1 version: 3.12.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@zumer/snapdom': - specifier: 2.8.0 - version: 2.8.0 + specifier: 2.9.0 + version: 2.9.0 aws-sdk: specifier: 2.1693.0 version: 2.1693.0 @@ -79,13 +79,13 @@ importers: version: 4.0.3 i18next: specifier: 25.10.10 - version: 25.10.10(typescript@5.9.3) + version: 25.10.10(typescript@6.0.3) i18next-browser-languagedetector: specifier: 8.2.1 version: 8.2.1 i18next-http-backend: - specifier: 3.0.4 - version: 3.0.4 + specifier: 3.0.5 + version: 3.0.5 ics: specifier: 3.11.0 version: 3.11.0 @@ -108,8 +108,8 @@ importers: specifier: 9.7.15 version: 9.7.15(react@19.2.5) nanoid: - specifier: 5.1.7 - version: 5.1.7 + specifier: 5.1.9 + version: 5.1.9 neverthrow: specifier: 8.2.0 version: 8.2.0 @@ -135,8 +135,8 @@ importers: specifier: 19.2.5 version: 19.2.5 react-aria-components: - specifier: 1.16.0 - version: 1.16.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + specifier: 1.17.0 + version: 1.17.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) react-charts: specifier: 3.0.0-beta.57 version: 3.0.0-beta.57(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -151,7 +151,7 @@ importers: version: 7.2.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5) react-i18next: specifier: 16.6.6 - version: 16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3) + version: 16.6.6(i18next@25.10.10(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) react-router: specifier: 7.14.1 version: 7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -172,7 +172,7 @@ importers: version: 3.4.1(remix-auth@4.2.0) remix-i18next: specifier: 7.4.2 - version: 7.4.2(i18next@25.10.10(typescript@5.9.3))(react-i18next@16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3))(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) + version: 7.4.2(i18next@25.10.10(typescript@6.0.3))(react-i18next@16.6.6(i18next@25.10.10(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) slugify: specifier: 1.6.9 version: 1.6.9 @@ -190,14 +190,14 @@ importers: version: 4.3.6 devDependencies: '@biomejs/biome': - specifier: 2.4.11 - version: 2.4.11 + specifier: 2.4.13 + version: 2.4.13 '@playwright/test': specifier: 1.59.1 version: 1.59.1 '@react-router/dev': specifier: 7.14.1 - version: 7.14.1(@react-router/serve@7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3))(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3) + version: 7.14.1(@react-router/serve@7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3))(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(tsx@4.21.0)(typescript@6.0.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3) '@types/better-sqlite3': specifier: 7.6.13 version: 7.6.13 @@ -250,8 +250,8 @@ importers: specifier: 4.21.0 version: 4.21.0 typescript: - specifier: 5.9.3 - version: 5.9.3 + specifier: 6.0.3 + version: 6.0.3 vite: specifier: 8.0.8 version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) @@ -568,59 +568,59 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} - '@biomejs/biome@2.4.11': - resolution: {integrity: sha512-nWxHX8tf3Opb/qRgZpBbsTOqOodkbrkJ7S+JxJAruxOReaDPPmPuLBAGQ8vigyUgo0QBB+oQltNEAvalLcjggA==} + '@biomejs/biome@2.4.13': + resolution: {integrity: sha512-gLXOwkOBBg0tr7bDsqlkIh4uFeKuMjxvqsrb1Tukww1iDmHcfr4Uu8MoQxp0Rcte+69+osRNWXwHsu/zxT6XqA==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.4.11': - resolution: {integrity: sha512-wOt+ed+L2dgZanWyL6i29qlXMc088N11optzpo10peayObBaAshbTcxKUchzEMp9QSY8rh5h6VfAFE3WTS1rqg==} + '@biomejs/cli-darwin-arm64@2.4.13': + resolution: {integrity: sha512-2KImO1jhNFBa2oWConyr0x6flxbQpGKv6902uGXpYM62Xyem8U80j441SyUJ8KyngsmKbQjeIv1q2CQfDkNnYg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.4.11': - resolution: {integrity: sha512-gZ6zR8XmZlExfi/Pz/PffmdpWOQ8Qhy7oBztgkR8/ylSRyLwfRPSadmiVCV8WQ8PoJ2MWUy2fgID9zmtgUUJmw==} + '@biomejs/cli-darwin-x64@2.4.13': + resolution: {integrity: sha512-BKrJklbaFN4p1Ts4kPBczo+PkbsHQg57kmJ+vON9u2t6uN5okYHaSr7h/MutPCWQgg2lglaWoSmm+zhYW+oOkg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.4.11': - resolution: {integrity: sha512-+Sbo1OAmlegtdwqFE8iOxFIWLh1B3OEgsuZfBpyyN/kWuqZ8dx9ZEes6zVnDMo+zRHF2wLynRVhoQmV7ohxl2Q==} + '@biomejs/cli-linux-arm64-musl@2.4.13': + resolution: {integrity: sha512-U5MsuBQW25dXaYtqWWSPM3P96H6Y+fHuja3TQpMNnylocHW0tEbtFTDlUj6oM+YJLntvEkQy4grBvQNUD4+RCg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] libc: [musl] - '@biomejs/cli-linux-arm64@2.4.11': - resolution: {integrity: sha512-avdJaEElXrKceK0va9FkJ4P5ci3N01TGkc6ni3P8l3BElqbOz42Wg2IyX3gbh0ZLEd4HVKEIrmuVu/AMuSeFFA==} + '@biomejs/cli-linux-arm64@2.4.13': + resolution: {integrity: sha512-NzkUDSqfvMBrPplKgVr3aXLHZ2NEELvvF4vZxXulEylKWIGqlvNEcwUcj9OLrn75TD3lJ/GIqCVlBwd1MZCuYQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] libc: [glibc] - '@biomejs/cli-linux-x64-musl@2.4.11': - resolution: {integrity: sha512-bexd2IklK7ZgPhrz6jXzpIL6dEAH9MlJU1xGTrypx+FICxrXUp4CqtwfiuoDKse+UlgAlWtzML3jrMqeEAHEhA==} + '@biomejs/cli-linux-x64-musl@2.4.13': + resolution: {integrity: sha512-Z601MienRgTBDza/+u2CH3RSrWoXo9rtr8NK6A4KJzqGgfxx+H3VlyLgTJ4sRo40T3pIsqpTmiOQEvYzQvBRvQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] libc: [musl] - '@biomejs/cli-linux-x64@2.4.11': - resolution: {integrity: sha512-TagWV0iomp5LnEnxWFg4nQO+e52Fow349vaX0Q/PIcX6Zhk4GGBgp3qqZ8PVkpC+cuehRctMf3+6+FgQ8jCEFQ==} + '@biomejs/cli-linux-x64@2.4.13': + resolution: {integrity: sha512-Az3ZZedYRBo9EQzNnD9SxFcR1G5QsGo6VEc2hIyVPZ1rdKwee/7E9oeBBZFpE8Z44ekxsDQBqbiWGW5ShOhUSQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] libc: [glibc] - '@biomejs/cli-win32-arm64@2.4.11': - resolution: {integrity: sha512-RJhaTnY8byzxDt4bDVb7AFPHkPcjOPK3xBip4ZRTrN3TEfyhjLRm3r3mqknqydgVTB74XG8l4jMLwEACEeihVg==} + '@biomejs/cli-win32-arm64@2.4.13': + resolution: {integrity: sha512-Px9PS2B5/Q183bUwy/5VHqp3J2lzdOCeVGzMpphYfl8oSa7VDCqenBdqWpy6DCy/en4Rbf/Y1RieZF6dJPcc9A==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.4.11': - resolution: {integrity: sha512-A8D3JM/00C2KQgUV3oj8Ba15EHEYwebAGCy5Sf9GAjr5Y3+kJIYOiESoqRDeuRZueuMdCsbLZIUqmPhpYXJE9A==} + '@biomejs/cli-win32-x64@2.4.13': + resolution: {integrity: sha512-tTcMkXyBrmHi9BfrD2VNHs/5rYIUKETqsBlYOvSAABwBkJhSDVb5e7wPukftsQbO3WzQkXe6kaztC6WtUOXSoQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -853,41 +853,23 @@ packages: '@floating-ui/utils@0.2.11': resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} - '@formatjs/ecma402-abstract@2.3.6': - resolution: {integrity: sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==} - - '@formatjs/fast-memoize@2.2.7': - resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==} - '@formatjs/fast-memoize@3.1.2': resolution: {integrity: sha512-vPnriihkfK0lzoQGaXq+qXH23VsYyansRTkTgo2aTG0k1NjLFyZimFVdfj4C9JkSE5dm7CEngcQ5TTc1yAyBfQ==} - '@formatjs/icu-messageformat-parser@2.11.4': - resolution: {integrity: sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==} - - '@formatjs/icu-skeleton-parser@1.8.16': - resolution: {integrity: sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==} - '@formatjs/intl-durationformat@0.10.4': resolution: {integrity: sha512-SUS4xT3GX43/thXTZojOXMNMB1zUIlLC+3mdyJga7evrgV4jvmgycX9bXfmMNDu7c2TC9gnXySUpUcxMghZrKg==} - '@formatjs/intl-localematcher@0.6.2': - resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==} - '@formatjs/intl-localematcher@0.8.3': resolution: {integrity: sha512-pHUjWb9NuhnMs8+PxQdzBtZRFJHlGhrURGAbm6Ltwl82BFajeuiIR3jblSa7ia3r62rXe/0YtVpUG3xWr5bFCA==} - '@internationalized/date@3.12.0': - resolution: {integrity: sha512-/PyIMzK29jtXaGU23qTvNZxvBXRtKbNnGDFD+PY6CZw/Y8Ex8pFUzkuCJCG9aOqmShjqhS9mPqP6Dk5onQY8rQ==} + '@internationalized/date@3.12.1': + resolution: {integrity: sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ==} - '@internationalized/message@3.1.8': - resolution: {integrity: sha512-Rwk3j/TlYZhn3HQ6PyXUV0XP9Uv42jqZGNegt0BXlxjE6G3+LwHjbQZAGHhCnCPdaA6Tvd3ma/7QzLlLkJxAWA==} + '@internationalized/number@3.6.6': + resolution: {integrity: sha512-iFgmQaXHE0vytNfpLZWOC2mEJCBRzcUxt53Xf/yCXG93lRvqas237i3r7X4RKMwO3txiyZD4mQjKAByFv6UGSQ==} - '@internationalized/number@3.6.5': - resolution: {integrity: sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==} - - '@internationalized/string@3.2.7': - resolution: {integrity: sha512-D4OHBjrinH+PFZPvfCXvG28n2LSykWcJ7GIioQL+ok0LON15SdfoUssoHzzOUmVZLbRoREsQXVzA6r8JKsbP6A==} + '@internationalized/string@3.2.8': + resolution: {integrity: sha512-NdbMQUSfXLYIQol5VyMtinm9pZDciiMfN7RtmSuSB78io1hqwJ0naYfxyW6vgxWBkzWymQa/3uLDlbfmshtCaA==} '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1568,297 +1550,6 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@react-aria/autocomplete@3.0.0-rc.6': - resolution: {integrity: sha512-uymUNJ8NW+dX7lmgkHE+SklAbxwktycAJcI5lBBw6KPZyc0EdMHC+/Fc5CUz3enIAhNwd2oxxogcSHknquMzQA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/breadcrumbs@3.5.32': - resolution: {integrity: sha512-S61vh5DJ2PXiXUwD7gk+pvS/b4VPrc3ZJOUZ0yVRLHkVESr5LhIZH+SAVgZkm1lzKyMRG+BH+fiRH/DZRSs7SA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/button@3.14.5': - resolution: {integrity: sha512-ZuLx+wQj9VQhH9BYe7t0JowmKnns2XrFHFNvIVBb5RwxL+CIycIOL7brhWKg2rGdxvlOom7jhVbcjSmtAaSyaQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/calendar@3.9.5': - resolution: {integrity: sha512-k0kvceYdZZu+DoeqephtlmIvh1CxqdFyoN52iqVzTz9O0pe5Xfhq7zxPGbeCp4pC61xzp8Lu/6uFA/YNfQQNag==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/checkbox@3.16.5': - resolution: {integrity: sha512-ZhUT7ELuD52hb+Zpzw0ElLQiVOd5sKYahrh+PK3vq13Wk5TedBscALpjuXetI4pwFfdmAM1Lhgcsrd8+6AmyvA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/collections@3.0.3': - resolution: {integrity: sha512-lbC5DEbHeVFvVr4ke9y8D9Nynnr8G8UjVEBoFGRylpAaScU7SX1TN84QI+EjMbsdZ0/5P2H7gUTS+MYd+6U3Rg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/color@3.1.5': - resolution: {integrity: sha512-eysWdBRzE8WDhBzh1nfjyUgzseMokXGHjIoJo880T7IPJ8tTavfQni49pU1B2qWrNOWPyrwx4Bd9pzHyboxJSA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/combobox@3.15.0': - resolution: {integrity: sha512-qSjQTFwKl3x1jCP2NRSJ6doZqAp6c2GTfoiFwWjaWg1IewwLsglaW6NnzqRDFiqFbDGgXPn4MqtC1VYEJ3NEjA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/datepicker@3.16.1': - resolution: {integrity: sha512-6BltCVWt09yefTkGjb2gViGCwoddx9HKJiZbY9u6Es/Q+VhwNJQRtczbnZ3K32p262hIknukNf/5nZaCOI1AKA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/dialog@3.5.34': - resolution: {integrity: sha512-/x53Q5ynpW5Kv9637WYu7SrDfj3woSp6jJRj8l6teGnWW/iNZWYJETgzHfbxx+HPKYATCZesRoIeO2LnYIXyEA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/disclosure@3.1.3': - resolution: {integrity: sha512-S3k7Wqrj+x0sWcP88Z1stSr5TIZmKEmx2rU7RB1O1/jPpbw5mgKnjtiriOlTh+kwdK11FkeqgxyHzAcBAR+FMQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/dnd@3.11.6': - resolution: {integrity: sha512-4YLHUeYJleF+moAYaYt8UZqujudPvpoaHR+QMkWIFzhfridVUhCr6ZjGWrzpSZY3r68k46TG7YCsi4IEiNnysw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/focus@3.21.5': - resolution: {integrity: sha512-V18fwCyf8zqgJdpLQeDU5ZRNd9TeOfBbhLgmX77Zr5ae9XwaoJ1R3SFJG1wCJX60t34AW+aLZSEEK+saQElf3Q==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/form@3.1.5': - resolution: {integrity: sha512-BWlONgHn8hmaMkcS6AgMSLQeNqVBwqPNLhdqjDO/PCfzvV7O8NZw/dFeIzJwfG4aBfSpbHHRdXGdfrk3d8dylQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/grid@3.14.8': - resolution: {integrity: sha512-X6rRFKDu/Kh6Sv8FBap3vjcb+z4jXkSOwkYnexIJp5kMTo5/Dqo55cCBio5B70Tanfv32Ev/6SpzYG7ryxnM9w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/gridlist@3.14.4': - resolution: {integrity: sha512-C/SbwC0qagZatoBrCjx8iZUex9apaJ8o8iRJ9eVHz0cpj7mXg6HuuotYGmDy9q67A2hve4I693RM1Cuwqwm+PQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/i18n@3.12.16': - resolution: {integrity: sha512-Km2CAz6MFQOUEaattaW+2jBdWOHUF8WX7VQoNbjlqElCP58nSaqi9yxTWUDRhAcn8/xFUnkFh4MFweNgtrHuEA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/interactions@3.27.1': - resolution: {integrity: sha512-M3wLpTTmDflI0QGNK0PJNUaBXXfeBXue8ZxLMngfc1piHNiH4G5lUvWd9W14XVbqrSCVY8i8DfGrNYpyyZu0tw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/label@3.7.25': - resolution: {integrity: sha512-oNK3Pqj4LDPwEbQaoM/uCip4QvQmmwGOh08VeW+vzSi6TAwf+KoWTyH/tiAeB0CHWNDK0k3e1iTygTAt4wzBmg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/landmark@3.0.10': - resolution: {integrity: sha512-GpNjJaI8/a6WxYDZgzTCLYSzPM6xp2pxCIQ4udiGbTCtxx13Trmm0cPABvPtzELidgolCf05em9Phr+3G0eE8A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/link@3.8.9': - resolution: {integrity: sha512-UaAFBfs84/Qq6TxlMWkREqqNY6SFLukot+z2Aa1kC+VyStv1kWG6sE5QLjm4SBn1Q3CGRsefhB/5+taaIbB4Pw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/listbox@3.15.3': - resolution: {integrity: sha512-C6YgiyrHS5sbS5UBdxGMhEs+EKJYotJgGVtl9l0ySXpBUXERiHJWLOyV7a8PwkUOmepbB4FaLD7Y9EUzGkrGlw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/live-announcer@3.4.4': - resolution: {integrity: sha512-PTTBIjNRnrdJOIRTDGNifY2d//kA7GUAwRFJNOEwSNG4FW+Bq9awqLiflw0JkpyB0VNIwou6lqKPHZVLsGWOXA==} - - '@react-aria/menu@3.21.0': - resolution: {integrity: sha512-CKTVZ4izSE1eKIti6TbTtzJAUo+WT8O4JC0XZCYDBpa0f++lD19Kz9aY+iY1buv5xGI20gAfpO474E9oEd4aQA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/meter@3.4.30': - resolution: {integrity: sha512-ZmANKW7s/Z4QGylHi46nhwtQ47T1bfMsU9MysBu7ViXXNJ03F4b6JXCJlKL5o2goQ3NbfZ68GeWamIT0BWSgtw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/numberfield@3.12.5': - resolution: {integrity: sha512-Fi41IUWXEHLFIeJ/LHuZ9Azs8J/P563fZi37GSBkIq5P1pNt1rPgJJng5CNn4KsHxwqadTRUlbbZwbZraWDtRg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/overlays@3.31.2': - resolution: {integrity: sha512-78HYI08r6LvcfD34gyv19ArRIjy1qxOKuXl/jYnjLDyQzD4pVb634IQWcm0zt10RdKgyuH6HTqvuDOgZTLet7Q==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/progress@3.4.30': - resolution: {integrity: sha512-S6OWVGgluSWYSd/A6O8CVjz83eeMUfkuWSra0ewAV9bmxZ7TP9pUmD3bGdqHZEl97nt5vHGjZ3eq/x8eCmzKhA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/radio@3.12.5': - resolution: {integrity: sha512-8CCJKJzfozEiWBPO9QAATG1rBGJEJ+xoqvHf9LKU2sPFGsA2/SRnLs6LB9fCG5R3spvaK1xz0any1fjWPl7x8A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/searchfield@3.8.12': - resolution: {integrity: sha512-kYlUHD/+mWzNroHoR8ojUxYBoMviRZn134WaKPFjfNUGZDOEuh4XzOoj+cjdJfe6N3mwTaYu6rJQtunSHIAfhA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/select@3.17.3': - resolution: {integrity: sha512-u0UFWw0S7q9oiSbjetDpRoLLIcC+L89uYlm+YfCrdT8ntbQgABNiJRxdVvxnhR0fR6MC9ASTTvuQnNHNn52+1A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/selection@3.27.2': - resolution: {integrity: sha512-GbUSSLX/ciXix95KW1g+SLM9np7iXpIZrFDSXkC6oNx1uhy18eAcuTkeZE25+SY5USVUmEzjI3m/3JoSUcebbg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/separator@3.4.16': - resolution: {integrity: sha512-RCUtQhDGnPxKzyG8KM79yOB0fSiEf8r/rxShidOVnGLiBW2KFmBa22/Gfc4jnqg/keN3dxvkSGoqmeXgctyp6g==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/slider@3.8.5': - resolution: {integrity: sha512-gqkJxznk141mE0JamXF5CXml9PDbPkBz8dyKlihtWHWX4yhEbVYdC9J0otE7iCR3zx69Bm7WHoTGL0BsdpKzVA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/spinbutton@3.7.2': - resolution: {integrity: sha512-adjE1wNCWlugvAtVXlXWPtIG9JWurEgYVn1Eeyh19x038+oXGvOsOAoKCXM+SnGleTWQ9J7pEZITFoEI3cVfAw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/ssr@3.9.10': - resolution: {integrity: sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==} - engines: {node: '>= 12'} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/switch@3.7.11': - resolution: {integrity: sha512-dYVX71HiepBsKyeMaQgHbhqI+MQ3MVoTd5EnTbUjefIBnmQZavYj1/e4NUiUI4Ix+/C0HxL8ibDAv4NlSW3eLQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/table@3.17.11': - resolution: {integrity: sha512-GkYmWPiW3OM+FUZxdS33teHXHXde7TjHuYgDDaG9phvg6cQTQjGilJozrzA3OfftTOq5VB8XcKTIQW3c0tpYsQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/tabs@3.11.1': - resolution: {integrity: sha512-3Ppz7yaEDW9L7p9PE9yNOl5caLwNnnLQqI+MX/dwbWlw9HluHS7uIjb21oswNl6UbSxAWyENOka45+KN4Fkh7A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/tag@3.8.1': - resolution: {integrity: sha512-VonpO++F8afXGDWc9VUxAc2wefyJpp1n9OGpbnB7zmqWiuPwO/RixjUdcH7iJkiC4vADwx9uLnhyD6kcwGV2ig==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/textfield@3.18.5': - resolution: {integrity: sha512-ttwVSuwoV3RPaG2k2QzEXKeQNQ3mbdl/2yy6I4Tjrn1ZNkYHfVyJJ26AjenfSmj1kkTQoSAfZ8p+7rZp4n0xoQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/toast@3.0.11': - resolution: {integrity: sha512-2DjZjBAvm8/CWbnZ6s7LjkYCkULKtjMve6GvhPTq98AthuEDLEiBvM1wa3xdecCRhZyRT1g6DXqVca0EfZ9fJA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/toggle@3.12.5': - resolution: {integrity: sha512-XXVFLzcV8fr9mz7y/wfxEAhWvaBZ9jSfhCMuxH2bsivO7nTcMJ1jb4g2xJNwZgne17bMWNc7mKvW5dbsdlI6BA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/toolbar@3.0.0-beta.24': - resolution: {integrity: sha512-B2Rmpko7Ghi2RbNfsGdbR7I+RQBDhPGVE4bU3/EwHz+P/vNe5LyGPTeSwqaOMsQTF9lKNCkY8424dVTCr6RUMg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/tooltip@3.9.2': - resolution: {integrity: sha512-VrgkPwHiEnAnBhoQ4W7kfry/RfVuRWrUPaJSp0+wKM6u0gg2tmn7OFRDXTxBAm/omQUguIdIjRWg7sf3zHH82A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/tree@3.1.7': - resolution: {integrity: sha512-C54yH5NmsOFa2Q+cg6B1BPr5KUlU9vLIoBnVrgrH237FRSXQPIbcM4VpmITAHq1VR7w6ayyS1hgTwFxo67ykWQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/utils@3.33.1': - resolution: {integrity: sha512-kIx1Sj6bbAT0pdqCegHuPanR9zrLn5zMRiM7LN12rgRf55S19ptd9g3ncahArifYTRkfEU9VIn+q0HjfMqS9/w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/virtualizer@4.1.13': - resolution: {integrity: sha512-d5KS+p8GXGNRbGPRE/N6jtth3et3KssQIz52h2+CAoAh7C3vvR64kkTaGdeywClvM+fSo8FxJuBrdfQvqC2ktQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/visually-hidden@3.8.31': - resolution: {integrity: sha512-RTOHHa4n56a9A3criThqFHBifvZoV71+MCkSuNP2cKO662SUWjqKkd0tJt/mBRMEJPkys8K7Eirp6T8Wt5FFRA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-router/dev@7.14.1': resolution: {integrity: sha512-ZBEwods1TxqPVY2SrXDuDCfoaE5VoTMBYrfa/+3MesprY3foSo1jhin9mh4FwmXPXhhmDYKXi2z5UR+oMj8Qjg==} engines: {node: '>=20.0.0'} @@ -1911,298 +1602,8 @@ packages: peerDependencies: react-router: 7.14.1 - '@react-stately/autocomplete@3.0.0-beta.4': - resolution: {integrity: sha512-K2Uy7XEdseFvgwRQ8CyrYEHMupjVKEszddOapP8deNz4hntYvT1aRm0m+sKa5Kl/4kvg9c/3NZpQcrky/vRZIg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/calendar@3.9.3': - resolution: {integrity: sha512-uw7fCZXoypSBBUsVkbNvJMQWTihZReRbyLIGG3o/ZM630N3OCZhb/h4Uxke4pNu7n527H0V1bAnZgAldIzOYqg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/checkbox@3.7.5': - resolution: {integrity: sha512-K5R5ted7AxLB3sDkuVAazUdyRMraFT1imVqij2GuAiOUFvsZvbuocnDuFkBVKojyV3GpqLBvViV8IaCMc4hNIw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/collections@3.12.10': - resolution: {integrity: sha512-wmF9VxJDyBujBuQ76vXj2g/+bnnj8fx5DdXgRmyfkkYhPB46+g2qnjbVGEvipo7bJuGxDftCUC4SN7l7xqUWfg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/color@3.9.5': - resolution: {integrity: sha512-8pZxzXWDRuglzDwyTG7mLw2LQMCHIVNbVc9YmbsxbOjAL+lOqszo60KzyaFKVxeDQczSvrNTHcQZqlbNIC0eyQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/combobox@3.13.0': - resolution: {integrity: sha512-dX9g/cK1hjLRjcbWVF6keHxTQDGhKGB2QAgPhWcBmOK3qJv+2dQqsJ6YCGWn/Y2N2acoEseLrAA7+Qe4HWV9cg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/data@3.15.2': - resolution: {integrity: sha512-BsmeeGgFwOGwo0g9Waprdyt+846n3KhKggZfpEnp5+sC4dE4uW1VIYpdyupMfr3bQcmX123q6TegfNP3eszrUA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/datepicker@3.16.1': - resolution: {integrity: sha512-BtAMDvxd1OZxkxjqq5tN5TYmp6Hm8+o3+IDA4qmem2/pfQfVbOZeWS2WitcPBImj4n4T+W1A5+PI7mT/6DUBVg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/disclosure@3.0.11': - resolution: {integrity: sha512-/KjB/0HkxGWbhFAPztCP411LUKZCx9k8cKukrlGqrUWyvrcXlmza90j0g/CuxACBoV+DJP9V+4q+8ide0x750A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/dnd@3.7.4': - resolution: {integrity: sha512-YD0TVR5JkvTqskc1ouBpVKs6t/QS4RYCIyu8Ug8RgO122iIizuf2pfKnRLjYMdu5lXzBXGaIgd49dvnLzEXHIw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/flags@3.1.2': - resolution: {integrity: sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==} - - '@react-stately/form@3.2.4': - resolution: {integrity: sha512-qNBzun8SbLdgahryhKLqL1eqP+MXY6as82sVXYOOvUYLzgU5uuN8mObxYlxJgMI5akSdQJQV3RzyfVobPRE7Kw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/grid@3.11.9': - resolution: {integrity: sha512-qQY6F+27iZRn30dt0ZOrSetUmbmNJ0pLe9Weuqw3+XDVSuWT+2O/rO1UUYeK+mO0Acjzdv+IWiYbu9RKf2wS9w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/layout@4.6.0': - resolution: {integrity: sha512-kBenEsP03nh5rKgfqlVMPcoKTJv0v92CTvrAb5gYY8t9g8LOwzdL89Yannq7f5xv8LFck/MmRQlotpMt2InETg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/list@3.13.4': - resolution: {integrity: sha512-HHYSjA9VG7FPSAtpXAjQyM/V7qFHWGg88WmMrDt5QDlTBexwPuH0oFLnW0qaVZpAIxuWIsutZfxRAnme/NhhAA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/menu@3.9.11': - resolution: {integrity: sha512-vYkpO9uV2OUecsIkrOc+Urdl/s1xw/ibNH/UXsp4PtjMnS6mK9q2kXZTM3WvMAKoh12iveUO+YkYCZQshmFLHQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/numberfield@3.11.0': - resolution: {integrity: sha512-rxfC047vL0LP4tanjinfjKAriAvdVL57Um5RUL5nHML8IOWCB3TBxegQkJ6to6goScC/oZhd0/Y2LSaiRuKbNw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/overlays@3.6.23': - resolution: {integrity: sha512-RzWxots9A6gAzQMP4s8hOAHV7SbJRTFSlQbb6ly1nkWQXacOSZSFNGsKOaS0eIatfNPlNnW4NIkgtGws5UYzfw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/radio@3.11.5': - resolution: {integrity: sha512-QxA779S4ea5icQ0ja7CeiNzY1cj7c9G9TN0m7maAIGiTSinZl2Ia8naZJ0XcbRRp+LBll7RFEdekne15TjvS/w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/searchfield@3.5.19': - resolution: {integrity: sha512-URllgjbtTQEaOCfddbHpJSPKOzG3pE3ajQHJ7Df8qCoHTjKfL6hnm/vp7X5sxPaZaN7VLZ5kAQxTE8hpo6s0+A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/select@3.9.2': - resolution: {integrity: sha512-oWn0bijuusp8YI7FRM/wgtPVqiIrgU/ZUfLKe/qJUmT8D+JFaMAJnyrAzKpx98TrgamgtXynF78ccpopPhgrKQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/selection@3.20.9': - resolution: {integrity: sha512-RhxRR5Wovg9EVi3pq7gBPK2BoKmP59tOXDMh2r1PbnGevg/7TNdR67DCEblcmXwHuBNS46ELfKdd0XGHqmS8nQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/slider@3.7.5': - resolution: {integrity: sha512-OrQMNR5xamLYH52TXtvTgyw3EMwv+JI+1istQgEj1CHBjC9eZZqn5iNCN20tzm+uDPTH0EIGULFjjPIumqYUQg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/table@3.15.4': - resolution: {integrity: sha512-fGaNyw3wv7JgRCNzgyDzpaaTFuSy5f4Qekch4UheMXDJX7dOeaMhUXeOfvnXCVg+BGM4ey/D82RvDOGvPy1Nww==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/tabs@3.8.9': - resolution: {integrity: sha512-AQ4Xrn6YzIolaVShCV9cnwOjBKPAOGP/PTp7wpSEtQbQ0HZzUDG2RG/M4baMeUB2jZ33b7ifXyPcK78o0uOftg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/toast@3.1.3': - resolution: {integrity: sha512-mT9QJKmD523lqFpOp0VWZ6QHZENFK7HrodnNJDVc7g616s5GNmemdlkITV43fSY3tHeThCVvPu+Uzh7RvQ9mpQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/toggle@3.9.5': - resolution: {integrity: sha512-PVzXc788q3jH98Kvw1LYDL+wpVC14dCEKjOku8cSaqhEof6AJGaLR9yq+EF1yYSL2dxI6z8ghc0OozY8WrcFcA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/tooltip@3.5.11': - resolution: {integrity: sha512-o8PnFXbvDCuVZ4Ht9ahfS6KHwIZjXopvoQ2vUPxv920irdgWEeC+4omgDOnJ/xFvcpmmJAmSsrQsTQrTguDUQA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/tree@3.9.6': - resolution: {integrity: sha512-JCuhGyX2A+PAMsx2pRSwArfqNFZJ9JSPkDaOQJS8MFPAsBe5HemvXsdmv9aBIMzlbCYcVq6EsrFnzbVVTBt/6w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/utils@3.11.0': - resolution: {integrity: sha512-8LZpYowJ9eZmmYLpudbo/eclIRnbhWIJZ994ncmlKlouNzKohtM8qTC6B1w1pwUbiwGdUoyzLuQbeaIor5Dvcw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/virtualizer@4.4.6': - resolution: {integrity: sha512-9SfXgLFB61/8SXNLfg5ARx9jAK4m03Aw6/Cg8mdZN24SYarL4TKNRpfw8K/HHVU/bi6WHSJypk6Z/z19o/ztrg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/autocomplete@3.0.0-alpha.38': - resolution: {integrity: sha512-0XrlVC8drzcrCNzybbkZdLcTofXEzBsHuaFevt5awW1J0xBJ+SMLIQMDeUYrvKjjwXUBlCtjJJpOvitGt4Z+KA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/breadcrumbs@3.7.19': - resolution: {integrity: sha512-AnkyYYmzaM2QFi/N0P/kQLM8tHOyFi7p397B/jEMucXDfwMw5Ny1ObCXeIEqbh8KrIa2Xp8SxmQlCV+8FPs4LA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/button@3.15.1': - resolution: {integrity: sha512-M1HtsKreJkigCnqceuIT22hDJBSStbPimnpmQmsl7SNyqCFY3+DHS7y/Sl3GvqCkzxF7j9UTL0dG38lGQ3K4xQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/calendar@3.8.3': - resolution: {integrity: sha512-fpH6WNXotzH0TlKHXXxtjeLZ7ko0sbyHmwDAwmDFyP7T0Iwn1YQZ+lhceLifvynlxuOgX6oBItyUKmkHQ0FouQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/checkbox@3.10.4': - resolution: {integrity: sha512-tYCG0Pd1usEz5hjvBEYcqcA0youx930Rss1QBIse9TgMekA1c2WmPDNupYV8phpO8Zuej3DL1WfBeXcgavK8aw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/color@3.1.4': - resolution: {integrity: sha512-s+Xj4pvNBlJPpQ1Gr7bO1j4/tuwMUfdS9xIVFuiW5RvDsSybKTUJ/gqPzTxms94VDCRhLFocVn2STNdD2Erf6A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/combobox@3.14.0': - resolution: {integrity: sha512-zmSSS7BcCOD8rGT8eGbVy7UlL5qq1vm88fFn4WgFe+lfK33ne+E7yTzTxcPY2TCGSo5fY6xMj3OG79FfVNGbSg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/datepicker@3.13.5': - resolution: {integrity: sha512-j28Vz+xvbb4bj7+9Xbpc4WTvSitlBvt7YEaEGM/8ZQ5g4Jr85H2KwkmDwjzmMN2r6VMQMMYq9JEcemq5wWpfUQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/dialog@3.5.24': - resolution: {integrity: sha512-NFurEP/zV0dA/41422lV1t+0oh6f/13n+VmLHZG8R13m1J3ql/kAXZ49zBSqkqANBO1ojyugWebk99IiR4pYOw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/form@3.7.18': - resolution: {integrity: sha512-0sBJW0+I9nJcF4SmKrYFEWAlehiebSTy7xqriqAXtqfTEdvzAYLGaAK2/7gx+wlNZeDTdW43CDRJ4XAhyhBqnw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/grid@3.3.8': - resolution: {integrity: sha512-zJvXH8gc1e1VH2H3LRnHH/W2HIkLkZMH3Cu5pLcj0vDuLBSWpcr3Ikh3jZ+VUOZF0G1Jt1lO8pKIaqFzDLNmLQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/link@3.6.7': - resolution: {integrity: sha512-1apXCFJgMC1uydc2KNENrps1qR642FqDpwlNWe254UTpRZn/hEZhA6ImVr8WhomfLJu672WyWA0rUOv4HT+/pQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/listbox@3.7.6': - resolution: {integrity: sha512-335NYElKEByXMalAmeRPyulKIDd2cjOCQhLwvv2BtxO5zaJfZnBbhZs+XPd9zwU6YomyOxODKSHrwbNDx+Jf3w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/menu@3.10.7': - resolution: {integrity: sha512-+p7ixZdvPDJZhisqdtWiiuJ9pteNfK5i19NB6wzAw5XkljbEzodNhwLv6rI96DY5XpbFso2kcjw7IWi+rAAGGQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/meter@3.4.15': - resolution: {integrity: sha512-9WjNphhLLM+TA4Ev1y2MkpugJ5JjTXseHh7ZWWx2veq5DrXMZYclkRpfUrUdLVKvaBIPQCgpQIj0TcQi+quR9A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/numberfield@3.8.18': - resolution: {integrity: sha512-nLzk7YAG9yAUtSv+9R8LgCHsu8hJq8/A+m1KsKxvc8WmNJjIujSFgWvT21MWBiUgPBzJKGzAqpMDDa087mltJQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/overlays@3.9.4': - resolution: {integrity: sha512-7Z9HaebMFyYBqtv3XVNHEmVkm7AiYviV7gv0c98elEN2Co+eQcKFGvwBM9Gy/lV57zlTqFX1EX/SAqkMEbCLOA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/progress@3.5.18': - resolution: {integrity: sha512-mKeQn+KrHr1y0/k7KtrbeDGDaERH6i4f6yBwj/ZtYDCTNKMO3tPHJY6nzF0w/KKZLplIO+BjUbHXc2RVm8ovwQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/radio@3.9.4': - resolution: {integrity: sha512-TkMRY3sA1PcFZhhclu4IUzUTIir6MzNJj8h6WT8vO6Nug2kXJ72qigugVFBWJSE472mltduOErEAo0rtAYWbQA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/searchfield@3.6.8': - resolution: {integrity: sha512-M2p7OVdMTMDmlBcHd4N2uCBwg3uJSNM4lmEyf09YD44N5wDAI0yogk52QBwsnhpe+i2s65UwCYgunB+QltRX8A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/select@3.12.2': - resolution: {integrity: sha512-AseOjfr3qM1W1qIWcbAe6NFpwZluVeQX/dmu9BYxjcnVvtoBLPMbE5zX/BPbv+N5eFYjoMyj7Ug9dqnI+LrlGw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/shared@3.33.1': - resolution: {integrity: sha512-oJHtjvLG43VjwemQDadlR5g/8VepK56B/xKO2XORPHt9zlW6IZs3tZrYlvH29BMvoqC7RtE7E5UjgbnbFtDGag==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/slider@3.8.4': - resolution: {integrity: sha512-C+xFVvfKREai9S/ekBDCVaGPOQYkNUAsQhjQnNsUAATaox4I6IYLmcIgLmljpMQWqAe+gZiWsIwacRYMez2Tew==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/switch@3.5.17': - resolution: {integrity: sha512-2GTPJvBCYI8YZ3oerHtXg+qikabIXCMJ6C2wcIJ5Xn0k9XOovowghfJi10OPB2GGyOiLBU74CczP5nx8adG90Q==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/table@3.13.6': - resolution: {integrity: sha512-eluL+iFfnVmFm7OSZrrFG9AUjw+tcv898zbv+NsZACa8oXG1v9AimhZfd+Mo8q/5+sX/9hguWNXFkSvmTjuVPQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/tabs@3.3.22': - resolution: {integrity: sha512-HGwLD9dA3k3AGfRKGFBhNgxU9/LyRmxN0kxVj1ghA4L9S/qTOzS6GhrGNkGzsGxyVLV4JN8MLxjWN2o9QHnLEg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/textfield@3.12.8': - resolution: {integrity: sha512-wt6FcuE5AyntxsnPika/h3nf/DPmeAVbI018L9o6h+B/IL4sMWWdx663wx2KOOeHH8ejKGZQNPLhUKs4s1mVQA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/tooltip@3.5.2': - resolution: {integrity: sha512-FvSuZ2WP08NEWefrpCdBYpEEZh/5TvqvGjq0wqGzWg2OPwpc14HjD8aE7I3MOuylXkD4MSlMjl7J4DlvlcCs3Q==} + '@react-types/shared@3.34.0': + resolution: {integrity: sha512-gp6xo/s2lX54AlTjOiqwDnxA7UW79BNvI9dB9pr3LZTzRKCd1ZA+ZbgKw/ReIiWuvvVw/8QFJpnqeeFyLocMcQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 @@ -2672,8 +2073,8 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@swc/helpers@0.5.20': - resolution: {integrity: sha512-2egEBHUMasdypIzrprsu8g+OEVd7Vp2MM3a2eVlM/cyFYto0nGz5BX5BTgh/ShZZI9ed+ozEq+Ngt+rgmUs8tw==} + '@swc/helpers@0.5.21': + resolution: {integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==} '@tiptap/core@2.27.2': resolution: {integrity: sha512-ABL1N6eoxzDzC1bYvkMbvyexHacszsKdVPYqhl5GwHLOvpZcv9VE9QaKwDILTyz5voCA0lGcAAXZp+qnXOk5lQ==} @@ -3002,8 +2403,8 @@ packages: '@xobotyi/scrollbar-width@1.9.5': resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==} - '@zumer/snapdom@2.8.0': - resolution: {integrity: sha512-NhztgFDNfOkFt8Ox9PIJ1IwggyMui5UDazysOgZD7FSGL0G7H8U+J3ft0iecxAS8daj5aC62i3blaTk7s2GcpA==} + '@zumer/snapdom@2.9.0': + resolution: {integrity: sha512-ksDM5gXQmu1isBof/YumD2HeIHf4ImTMfba9xE4JyDbARhcZCWHFdjW8oZV3hSKdWpQILEspNq5MrAfwEfzpIw==} accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} @@ -3308,9 +2709,6 @@ packages: supports-color: optional: true - decimal.js@10.6.0: - resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -3668,8 +3066,8 @@ packages: i18next-browser-languagedetector@8.2.1: resolution: {integrity: sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==} - i18next-http-backend@3.0.4: - resolution: {integrity: sha512-udwrBIE6cNpqn1gRAqRULq3+7MzIIuaiKRWrz++dVz5SqWW2VwXmPJtAgkI0JtMLFaADC9qNmnZAxWAhsxXx2g==} + i18next-http-backend@3.0.5: + resolution: {integrity: sha512-QaWHnsxieEDcqKe+vo/RFqpiIFRi/KBqlOSPcUlvinBaISCeiTRCbtrazHAjtHtsLC66oDsROAH8frWkQzfMMQ==} i18next-locales-sync@2.1.1: resolution: {integrity: sha512-NC/Zw0kKT+VULSh6zzDSxvqDUS9BHY6tk2cj93qQ/yaNuKc3hg4VlJFDeuaxdovY1/LobcdQCAc6g1U03qe2SQ==} @@ -3712,9 +3110,6 @@ packages: internmap@1.0.1: resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} - intl-messageformat@10.7.18: - resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==} - ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -4077,8 +3472,8 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.1.7: - resolution: {integrity: sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ==} + nanoid@5.1.9: + resolution: {integrity: sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw==} engines: {node: ^18 || >=20} hasBin: true @@ -4365,14 +3760,14 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react-aria-components@1.16.0: - resolution: {integrity: sha512-MjHbTLpMFzzD2Tv5KbeXoZwPczuUWZcRavVvQQlNHRtXHH38D+sToMEYpNeir7Wh3K/XWtzeX3EujfJW6QNkrw==} + react-aria-components@1.17.0: + resolution: {integrity: sha512-0EyisMgvsFJ2aML3crDYv2tW5vT2Ryf8PGzY/g63JjDdCbLshlwazhS8JNtPF1vkTkungJJ6sVJbKyX+YKSoFA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-aria@3.47.0: - resolution: {integrity: sha512-nvahimIqdByl/PXk/xPkG30LPRzcin+/Uk0uFfwbbKRRFC9aa22a6BRULZLqVHwa9GaNyKe6CDUxO1Dde4v0kA==} + react-aria@3.48.0: + resolution: {integrity: sha512-jQjd4rBEIMqecBaAKYJbVGK6EqIHLa5znVQ7jwFyK5vCyljoj6KhgtiahmcIPsG5vG5vEDLw+ba+bEWn6A2P4w==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 @@ -4453,8 +3848,8 @@ packages: react-dom: optional: true - react-stately@3.45.0: - resolution: {integrity: sha512-G3bYr0BIiookpt4H05VeZUuVS/FslQAj2TeT8vDfCiL314Y+LtPXIPe/a3eamCA0wljy7z1EDYKV50Qbz7pcJg==} + react-stately@3.46.0: + resolution: {integrity: sha512-OdxhWvHgs2L4OJGIs7hnuTr5WjjMM6enhNEAMRqiekhF8+ITvA2LRwNftOZwcogaoCslGYq5S2VQTQwnm0GbCA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 @@ -4850,8 +4245,8 @@ packages: types-ramda@0.31.0: resolution: {integrity: sha512-vaoC35CRC3xvL8Z6HkshDbi6KWM1ezK0LHN0YyxXWUn9HKzBNg/T3xSGlJZjCYspnOD3jE7bcizsp0bUXZDxnQ==} - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true @@ -5843,39 +5238,39 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@biomejs/biome@2.4.11': + '@biomejs/biome@2.4.13': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.4.11 - '@biomejs/cli-darwin-x64': 2.4.11 - '@biomejs/cli-linux-arm64': 2.4.11 - '@biomejs/cli-linux-arm64-musl': 2.4.11 - '@biomejs/cli-linux-x64': 2.4.11 - '@biomejs/cli-linux-x64-musl': 2.4.11 - '@biomejs/cli-win32-arm64': 2.4.11 - '@biomejs/cli-win32-x64': 2.4.11 + '@biomejs/cli-darwin-arm64': 2.4.13 + '@biomejs/cli-darwin-x64': 2.4.13 + '@biomejs/cli-linux-arm64': 2.4.13 + '@biomejs/cli-linux-arm64-musl': 2.4.13 + '@biomejs/cli-linux-x64': 2.4.13 + '@biomejs/cli-linux-x64-musl': 2.4.13 + '@biomejs/cli-win32-arm64': 2.4.13 + '@biomejs/cli-win32-x64': 2.4.13 - '@biomejs/cli-darwin-arm64@2.4.11': + '@biomejs/cli-darwin-arm64@2.4.13': optional: true - '@biomejs/cli-darwin-x64@2.4.11': + '@biomejs/cli-darwin-x64@2.4.13': optional: true - '@biomejs/cli-linux-arm64-musl@2.4.11': + '@biomejs/cli-linux-arm64-musl@2.4.13': optional: true - '@biomejs/cli-linux-arm64@2.4.11': + '@biomejs/cli-linux-arm64@2.4.13': optional: true - '@biomejs/cli-linux-x64-musl@2.4.11': + '@biomejs/cli-linux-x64-musl@2.4.13': optional: true - '@biomejs/cli-linux-x64@2.4.11': + '@biomejs/cli-linux-x64@2.4.13': optional: true - '@biomejs/cli-win32-arm64@2.4.11': + '@biomejs/cli-win32-arm64@2.4.13': optional: true - '@biomejs/cli-win32-x64@2.4.11': + '@biomejs/cli-win32-x64@2.4.13': optional: true '@blazediff/core@1.9.1': {} @@ -6033,58 +5428,27 @@ snapshots: '@floating-ui/utils@0.2.11': {} - '@formatjs/ecma402-abstract@2.3.6': - dependencies: - '@formatjs/fast-memoize': 2.2.7 - '@formatjs/intl-localematcher': 0.6.2 - decimal.js: 10.6.0 - tslib: 2.8.1 - - '@formatjs/fast-memoize@2.2.7': - dependencies: - tslib: 2.8.1 - '@formatjs/fast-memoize@3.1.2': {} - '@formatjs/icu-messageformat-parser@2.11.4': - dependencies: - '@formatjs/ecma402-abstract': 2.3.6 - '@formatjs/icu-skeleton-parser': 1.8.16 - tslib: 2.8.1 - - '@formatjs/icu-skeleton-parser@1.8.16': - dependencies: - '@formatjs/ecma402-abstract': 2.3.6 - tslib: 2.8.1 - '@formatjs/intl-durationformat@0.10.4': dependencies: '@formatjs/intl-localematcher': 0.8.3 - '@formatjs/intl-localematcher@0.6.2': - dependencies: - tslib: 2.8.1 - '@formatjs/intl-localematcher@0.8.3': dependencies: '@formatjs/fast-memoize': 3.1.2 - '@internationalized/date@3.12.0': + '@internationalized/date@3.12.1': dependencies: - '@swc/helpers': 0.5.20 + '@swc/helpers': 0.5.21 - '@internationalized/message@3.1.8': + '@internationalized/number@3.6.6': dependencies: - '@swc/helpers': 0.5.20 - intl-messageformat: 10.7.18 + '@swc/helpers': 0.5.21 - '@internationalized/number@3.6.5': + '@internationalized/string@3.2.8': dependencies: - '@swc/helpers': 0.5.20 - - '@internationalized/string@3.2.7': - dependencies: - '@swc/helpers': 0.5.20 + '@swc/helpers': 0.5.21 '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -6658,642 +6022,7 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@react-aria/autocomplete@3.0.0-rc.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/combobox': 3.15.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/focus': 3.21.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/listbox': 3.15.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/searchfield': 3.8.12(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/textfield': 3.18.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/autocomplete': 3.0.0-beta.4(react@19.2.5) - '@react-stately/combobox': 3.13.0(react@19.2.5) - '@react-types/autocomplete': 3.0.0-alpha.38(react@19.2.5) - '@react-types/button': 3.15.1(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/breadcrumbs@3.5.32(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/link': 3.8.9(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/breadcrumbs': 3.7.19(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/button@3.14.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/toolbar': 3.0.0-beta.24(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/toggle': 3.9.5(react@19.2.5) - '@react-types/button': 3.15.1(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/calendar@3.9.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@internationalized/date': 3.12.0 - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/live-announcer': 3.4.4 - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/calendar': 3.9.3(react@19.2.5) - '@react-types/button': 3.15.1(react@19.2.5) - '@react-types/calendar': 3.8.3(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/checkbox@3.16.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/form': 3.1.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/label': 3.7.25(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/toggle': 3.12.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/checkbox': 3.7.5(react@19.2.5) - '@react-stately/form': 3.2.4(react@19.2.5) - '@react-stately/toggle': 3.9.5(react@19.2.5) - '@react-types/checkbox': 3.10.4(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/collections@3.0.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/ssr': 3.9.10(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - use-sync-external-store: 1.6.0(react@19.2.5) - - '@react-aria/color@3.1.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/numberfield': 3.12.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/slider': 3.8.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/spinbutton': 3.7.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/textfield': 3.18.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/visually-hidden': 3.8.31(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/color': 3.9.5(react@19.2.5) - '@react-stately/form': 3.2.4(react@19.2.5) - '@react-types/color': 3.1.4(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/combobox@3.15.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/focus': 3.21.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/listbox': 3.15.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/live-announcer': 3.4.4 - '@react-aria/menu': 3.21.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/overlays': 3.31.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/selection': 3.27.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/textfield': 3.18.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/collections': 3.12.10(react@19.2.5) - '@react-stately/combobox': 3.13.0(react@19.2.5) - '@react-stately/form': 3.2.4(react@19.2.5) - '@react-types/button': 3.15.1(react@19.2.5) - '@react-types/combobox': 3.14.0(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/datepicker@3.16.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@internationalized/date': 3.12.0 - '@internationalized/number': 3.6.5 - '@internationalized/string': 3.2.7 - '@react-aria/focus': 3.21.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/form': 3.1.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/label': 3.7.25(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/spinbutton': 3.7.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/datepicker': 3.16.1(react@19.2.5) - '@react-stately/form': 3.2.4(react@19.2.5) - '@react-types/button': 3.15.1(react@19.2.5) - '@react-types/calendar': 3.8.3(react@19.2.5) - '@react-types/datepicker': 3.13.5(react@19.2.5) - '@react-types/dialog': 3.5.24(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/dialog@3.5.34(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/overlays': 3.31.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/dialog': 3.5.24(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/disclosure@3.1.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/ssr': 3.9.10(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/disclosure': 3.0.11(react@19.2.5) - '@react-types/button': 3.15.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/dnd@3.11.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@internationalized/string': 3.2.7 - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/live-announcer': 3.4.4 - '@react-aria/overlays': 3.31.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/collections': 3.12.10(react@19.2.5) - '@react-stately/dnd': 3.7.4(react@19.2.5) - '@react-types/button': 3.15.1(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/focus@3.21.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - clsx: 2.1.1 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/form@3.1.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/form': 3.2.4(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/grid@3.14.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/focus': 3.21.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/live-announcer': 3.4.4 - '@react-aria/selection': 3.27.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/collections': 3.12.10(react@19.2.5) - '@react-stately/grid': 3.11.9(react@19.2.5) - '@react-stately/selection': 3.20.9(react@19.2.5) - '@react-types/checkbox': 3.10.4(react@19.2.5) - '@react-types/grid': 3.3.8(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/gridlist@3.14.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/focus': 3.21.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/grid': 3.14.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/selection': 3.27.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/list': 3.13.4(react@19.2.5) - '@react-stately/tree': 3.9.6(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/i18n@3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@internationalized/date': 3.12.0 - '@internationalized/message': 3.1.8 - '@internationalized/number': 3.6.5 - '@internationalized/string': 3.2.7 - '@react-aria/ssr': 3.9.10(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/interactions@3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/ssr': 3.9.10(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/flags': 3.1.2 - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/label@3.7.25(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/landmark@3.0.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - use-sync-external-store: 1.6.0(react@19.2.5) - - '@react-aria/link@3.8.9(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/link': 3.6.7(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/listbox@3.15.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/label': 3.7.25(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/selection': 3.27.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/collections': 3.12.10(react@19.2.5) - '@react-stately/list': 3.13.4(react@19.2.5) - '@react-types/listbox': 3.7.6(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/live-announcer@3.4.4': - dependencies: - '@swc/helpers': 0.5.20 - - '@react-aria/menu@3.21.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/focus': 3.21.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/overlays': 3.31.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/selection': 3.27.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/collections': 3.12.10(react@19.2.5) - '@react-stately/menu': 3.9.11(react@19.2.5) - '@react-stately/selection': 3.20.9(react@19.2.5) - '@react-stately/tree': 3.9.6(react@19.2.5) - '@react-types/button': 3.15.1(react@19.2.5) - '@react-types/menu': 3.10.7(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/meter@3.4.30(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/progress': 3.4.30(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/meter': 3.4.15(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/numberfield@3.12.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/live-announcer': 3.4.4 - '@react-aria/spinbutton': 3.7.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/textfield': 3.18.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/form': 3.2.4(react@19.2.5) - '@react-stately/numberfield': 3.11.0(react@19.2.5) - '@react-types/button': 3.15.1(react@19.2.5) - '@react-types/numberfield': 3.8.18(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/overlays@3.31.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/focus': 3.21.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/ssr': 3.9.10(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/visually-hidden': 3.8.31(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/flags': 3.1.2 - '@react-stately/overlays': 3.6.23(react@19.2.5) - '@react-types/button': 3.15.1(react@19.2.5) - '@react-types/overlays': 3.9.4(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/progress@3.4.30(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/label': 3.7.25(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/progress': 3.5.18(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/radio@3.12.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/focus': 3.21.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/form': 3.1.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/label': 3.7.25(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/radio': 3.11.5(react@19.2.5) - '@react-types/radio': 3.9.4(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/searchfield@3.8.12(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/textfield': 3.18.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/searchfield': 3.5.19(react@19.2.5) - '@react-types/button': 3.15.1(react@19.2.5) - '@react-types/searchfield': 3.6.8(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/select@3.17.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/form': 3.1.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/label': 3.7.25(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/listbox': 3.15.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/menu': 3.21.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/selection': 3.27.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/visually-hidden': 3.8.31(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/select': 3.9.2(react@19.2.5) - '@react-types/button': 3.15.1(react@19.2.5) - '@react-types/select': 3.12.2(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/selection@3.27.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/focus': 3.21.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/selection': 3.20.9(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/separator@3.4.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/slider@3.8.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/label': 3.7.25(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/slider': 3.7.5(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@react-types/slider': 3.8.4(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/spinbutton@3.7.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/live-announcer': 3.4.4 - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/button': 3.15.1(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/ssr@3.9.10(react@19.2.5)': - dependencies: - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-aria/switch@3.7.11(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/toggle': 3.12.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/toggle': 3.9.5(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@react-types/switch': 3.5.17(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/table@3.17.11(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/focus': 3.21.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/grid': 3.14.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/live-announcer': 3.4.4 - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/visually-hidden': 3.8.31(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/collections': 3.12.10(react@19.2.5) - '@react-stately/flags': 3.1.2 - '@react-stately/table': 3.15.4(react@19.2.5) - '@react-types/checkbox': 3.10.4(react@19.2.5) - '@react-types/grid': 3.3.8(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@react-types/table': 3.13.6(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/tabs@3.11.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/focus': 3.21.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/selection': 3.27.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/tabs': 3.8.9(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@react-types/tabs': 3.3.22(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/tag@3.8.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/gridlist': 3.14.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/label': 3.7.25(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/selection': 3.27.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/list': 3.13.4(react@19.2.5) - '@react-types/button': 3.15.1(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/textfield@3.18.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/form': 3.1.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/label': 3.7.25(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/form': 3.2.4(react@19.2.5) - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@react-types/textfield': 3.12.8(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/toast@3.0.11(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/landmark': 3.0.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/toast': 3.1.3(react@19.2.5) - '@react-types/button': 3.15.1(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/toggle@3.12.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/toggle': 3.9.5(react@19.2.5) - '@react-types/checkbox': 3.10.4(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/toolbar@3.0.0-beta.24(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/focus': 3.21.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/tooltip@3.9.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/tooltip': 3.5.11(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@react-types/tooltip': 3.5.2(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/tree@3.1.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/gridlist': 3.14.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/selection': 3.27.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/tree': 3.9.6(react@19.2.5) - '@react-types/button': 3.15.1(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/utils@3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/ssr': 3.9.10(react@19.2.5) - '@react-stately/flags': 3.1.2 - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - clsx: 2.1.1 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/virtualizer@4.1.13(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/virtualizer': 4.4.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-aria/visually-hidden@3.8.31(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-router/dev@7.14.1(@react-router/serve@7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3))(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3)': + '@react-router/dev@7.14.1(@react-router/serve@7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3))(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(tsx@4.21.0)(typescript@6.0.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3)': dependencies: '@babel/core': 7.29.0 '@babel/generator': 7.29.1 @@ -7302,7 +6031,7 @@ snapshots: '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 - '@react-router/node': 7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3) + '@react-router/node': 7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3) '@remix-run/node-fetch-server': 0.13.0 arg: 5.0.2 babel-dead-code-elimination: 1.0.12 @@ -7322,12 +6051,12 @@ snapshots: react-router: 7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) semver: 7.7.4 tinyglobby: 0.2.16 - valibot: 1.3.1(typescript@5.9.3) + valibot: 1.3.1(typescript@6.0.3) vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) vite-node: 3.2.4(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.3) optionalDependencies: - '@react-router/serve': 7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3) - typescript: 5.9.3 + '@react-router/serve': 7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -7343,26 +6072,26 @@ snapshots: - tsx - yaml - '@react-router/express@7.14.1(express@4.22.1)(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3)': + '@react-router/express@7.14.1(express@4.22.1)(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3)': dependencies: - '@react-router/node': 7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3) + '@react-router/node': 7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3) express: 4.22.1 react-router: 7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.3 - '@react-router/node@7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3)': + '@react-router/node@7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3)': dependencies: '@mjackson/node-fetch-server': 0.2.0 react-router: 7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.3 - '@react-router/serve@7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3)': + '@react-router/serve@7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3)': dependencies: '@mjackson/node-fetch-server': 0.2.0 - '@react-router/express': 7.14.1(express@4.22.1)(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3) - '@react-router/node': 7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3) + '@react-router/express': 7.14.1(express@4.22.1)(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3) + '@react-router/node': 7.14.1(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3) compression: 1.8.1 express: 4.22.1 get-port: 5.1.1 @@ -7373,414 +6102,8 @@ snapshots: - supports-color - typescript - '@react-stately/autocomplete@3.0.0-beta.4(react@19.2.5)': + '@react-types/shared@3.34.0(react@19.2.5)': dependencies: - '@react-stately/utils': 3.11.0(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/calendar@3.9.3(react@19.2.5)': - dependencies: - '@internationalized/date': 3.12.0 - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/calendar': 3.8.3(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/checkbox@3.7.5(react@19.2.5)': - dependencies: - '@react-stately/form': 3.2.4(react@19.2.5) - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/checkbox': 3.10.4(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/collections@3.12.10(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/color@3.9.5(react@19.2.5)': - dependencies: - '@internationalized/number': 3.6.5 - '@internationalized/string': 3.2.7 - '@react-stately/form': 3.2.4(react@19.2.5) - '@react-stately/numberfield': 3.11.0(react@19.2.5) - '@react-stately/slider': 3.7.5(react@19.2.5) - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/color': 3.1.4(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/combobox@3.13.0(react@19.2.5)': - dependencies: - '@react-stately/collections': 3.12.10(react@19.2.5) - '@react-stately/form': 3.2.4(react@19.2.5) - '@react-stately/list': 3.13.4(react@19.2.5) - '@react-stately/overlays': 3.6.23(react@19.2.5) - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/combobox': 3.14.0(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/data@3.15.2(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/datepicker@3.16.1(react@19.2.5)': - dependencies: - '@internationalized/date': 3.12.0 - '@internationalized/number': 3.6.5 - '@internationalized/string': 3.2.7 - '@react-stately/form': 3.2.4(react@19.2.5) - '@react-stately/overlays': 3.6.23(react@19.2.5) - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/datepicker': 3.13.5(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/disclosure@3.0.11(react@19.2.5)': - dependencies: - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/dnd@3.7.4(react@19.2.5)': - dependencies: - '@react-stately/selection': 3.20.9(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/flags@3.1.2': - dependencies: - '@swc/helpers': 0.5.20 - - '@react-stately/form@3.2.4(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/grid@3.11.9(react@19.2.5)': - dependencies: - '@react-stately/collections': 3.12.10(react@19.2.5) - '@react-stately/selection': 3.20.9(react@19.2.5) - '@react-types/grid': 3.3.8(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/layout@4.6.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-stately/collections': 3.12.10(react@19.2.5) - '@react-stately/table': 3.15.4(react@19.2.5) - '@react-stately/virtualizer': 4.4.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/grid': 3.3.8(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@react-types/table': 3.13.6(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-stately/list@3.13.4(react@19.2.5)': - dependencies: - '@react-stately/collections': 3.12.10(react@19.2.5) - '@react-stately/selection': 3.20.9(react@19.2.5) - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/menu@3.9.11(react@19.2.5)': - dependencies: - '@react-stately/overlays': 3.6.23(react@19.2.5) - '@react-types/menu': 3.10.7(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/numberfield@3.11.0(react@19.2.5)': - dependencies: - '@internationalized/number': 3.6.5 - '@react-stately/form': 3.2.4(react@19.2.5) - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/numberfield': 3.8.18(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/overlays@3.6.23(react@19.2.5)': - dependencies: - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/overlays': 3.9.4(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/radio@3.11.5(react@19.2.5)': - dependencies: - '@react-stately/form': 3.2.4(react@19.2.5) - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/radio': 3.9.4(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/searchfield@3.5.19(react@19.2.5)': - dependencies: - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/searchfield': 3.6.8(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/select@3.9.2(react@19.2.5)': - dependencies: - '@react-stately/form': 3.2.4(react@19.2.5) - '@react-stately/list': 3.13.4(react@19.2.5) - '@react-stately/overlays': 3.6.23(react@19.2.5) - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/select': 3.12.2(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/selection@3.20.9(react@19.2.5)': - dependencies: - '@react-stately/collections': 3.12.10(react@19.2.5) - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/slider@3.7.5(react@19.2.5)': - dependencies: - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@react-types/slider': 3.8.4(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/table@3.15.4(react@19.2.5)': - dependencies: - '@react-stately/collections': 3.12.10(react@19.2.5) - '@react-stately/flags': 3.1.2 - '@react-stately/grid': 3.11.9(react@19.2.5) - '@react-stately/selection': 3.20.9(react@19.2.5) - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/grid': 3.3.8(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@react-types/table': 3.13.6(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/tabs@3.8.9(react@19.2.5)': - dependencies: - '@react-stately/list': 3.13.4(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@react-types/tabs': 3.3.22(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/toast@3.1.3(react@19.2.5)': - dependencies: - '@swc/helpers': 0.5.20 - react: 19.2.5 - use-sync-external-store: 1.6.0(react@19.2.5) - - '@react-stately/toggle@3.9.5(react@19.2.5)': - dependencies: - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/checkbox': 3.10.4(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/tooltip@3.5.11(react@19.2.5)': - dependencies: - '@react-stately/overlays': 3.6.23(react@19.2.5) - '@react-types/tooltip': 3.5.2(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/tree@3.9.6(react@19.2.5)': - dependencies: - '@react-stately/collections': 3.12.10(react@19.2.5) - '@react-stately/selection': 3.20.9(react@19.2.5) - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/utils@3.11.0(react@19.2.5)': - dependencies: - '@swc/helpers': 0.5.20 - react: 19.2.5 - - '@react-stately/virtualizer@4.4.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - '@swc/helpers': 0.5.20 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@react-types/autocomplete@3.0.0-alpha.38(react@19.2.5)': - dependencies: - '@react-types/combobox': 3.14.0(react@19.2.5) - '@react-types/searchfield': 3.6.8(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/breadcrumbs@3.7.19(react@19.2.5)': - dependencies: - '@react-types/link': 3.6.7(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/button@3.15.1(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/calendar@3.8.3(react@19.2.5)': - dependencies: - '@internationalized/date': 3.12.0 - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/checkbox@3.10.4(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/color@3.1.4(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - '@react-types/slider': 3.8.4(react@19.2.5) - react: 19.2.5 - - '@react-types/combobox@3.14.0(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/datepicker@3.13.5(react@19.2.5)': - dependencies: - '@internationalized/date': 3.12.0 - '@react-types/calendar': 3.8.3(react@19.2.5) - '@react-types/overlays': 3.9.4(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/dialog@3.5.24(react@19.2.5)': - dependencies: - '@react-types/overlays': 3.9.4(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/form@3.7.18(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/grid@3.3.8(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/link@3.6.7(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/listbox@3.7.6(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/menu@3.10.7(react@19.2.5)': - dependencies: - '@react-types/overlays': 3.9.4(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/meter@3.4.15(react@19.2.5)': - dependencies: - '@react-types/progress': 3.5.18(react@19.2.5) - react: 19.2.5 - - '@react-types/numberfield@3.8.18(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/overlays@3.9.4(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/progress@3.5.18(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/radio@3.9.4(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/searchfield@3.6.8(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - '@react-types/textfield': 3.12.8(react@19.2.5) - react: 19.2.5 - - '@react-types/select@3.12.2(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/shared@3.33.1(react@19.2.5)': - dependencies: - react: 19.2.5 - - '@react-types/slider@3.8.4(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/switch@3.5.17(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/table@3.13.6(react@19.2.5)': - dependencies: - '@react-types/grid': 3.3.8(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/tabs@3.3.22(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/textfield@3.12.8(react@19.2.5)': - dependencies: - '@react-types/shared': 3.33.1(react@19.2.5) - react: 19.2.5 - - '@react-types/tooltip@3.5.2(react@19.2.5)': - dependencies: - '@react-types/overlays': 3.9.4(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) react: 19.2.5 '@remirror/core-constants@3.0.0': {} @@ -8258,7 +6581,7 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@swc/helpers@0.5.20': + '@swc/helpers@0.5.21': dependencies: tslib: 2.8.1 @@ -8674,7 +6997,7 @@ snapshots: '@xobotyi/scrollbar-width@1.9.5': {} - '@zumer/snapdom@2.8.0': {} + '@zumer/snapdom@2.9.0': {} accepts@1.3.8: dependencies: @@ -9009,8 +7332,6 @@ snapshots: dependencies: ms: 2.1.3 - decimal.js@10.6.0: {} - decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 @@ -9370,7 +7691,7 @@ snapshots: dependencies: '@babel/runtime': 7.29.2 - i18next-http-backend@3.0.4: + i18next-http-backend@3.0.5: dependencies: cross-fetch: 4.1.0 transitivePeerDependencies: @@ -9384,11 +7705,11 @@ snapshots: picomatch: 4.0.4 yargs: 17.7.2 - i18next@25.10.10(typescript@5.9.3): + i18next@25.10.10(typescript@6.0.3): dependencies: '@babel/runtime': 7.29.2 optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.3 iconv-lite@0.4.24: dependencies: @@ -9416,13 +7737,6 @@ snapshots: internmap@1.0.1: {} - intl-messageformat@10.7.18: - dependencies: - '@formatjs/ecma402-abstract': 2.3.6 - '@formatjs/fast-memoize': 2.2.7 - '@formatjs/icu-messageformat-parser': 2.11.4 - tslib: 2.8.1 - ipaddr.js@1.9.1: {} is-arguments@1.2.0: @@ -9715,7 +8029,7 @@ snapshots: nanoid@3.3.11: {} - nanoid@5.1.7: {} + nanoid@5.1.9: {} napi-build-utils@2.0.0: {} @@ -10065,86 +8379,30 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-aria-components@1.16.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + react-aria-components@1.17.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: - '@internationalized/date': 3.12.0 - '@internationalized/string': 3.2.7 - '@react-aria/autocomplete': 3.0.0-rc.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/collections': 3.0.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/dnd': 3.11.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/focus': 3.21.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/live-announcer': 3.4.4 - '@react-aria/overlays': 3.31.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/ssr': 3.9.10(react@19.2.5) - '@react-aria/textfield': 3.18.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/toolbar': 3.0.0-beta.24(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/virtualizer': 4.1.13(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/autocomplete': 3.0.0-beta.4(react@19.2.5) - '@react-stately/layout': 4.6.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-stately/selection': 3.20.9(react@19.2.5) - '@react-stately/table': 3.15.4(react@19.2.5) - '@react-stately/utils': 3.11.0(react@19.2.5) - '@react-stately/virtualizer': 4.4.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/form': 3.7.18(react@19.2.5) - '@react-types/grid': 3.3.8(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) - '@react-types/table': 3.13.6(react@19.2.5) - '@swc/helpers': 0.5.20 + '@internationalized/date': 3.12.1 + '@react-types/shared': 3.34.0(react@19.2.5) + '@swc/helpers': 0.5.21 client-only: 0.0.1 react: 19.2.5 - react-aria: 3.47.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react-aria: 3.48.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) react-dom: 19.2.5(react@19.2.5) - react-stately: 3.45.0(react@19.2.5) - use-sync-external-store: 1.6.0(react@19.2.5) + react-stately: 3.46.0(react@19.2.5) - react-aria@3.47.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + react-aria@3.48.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: - '@internationalized/string': 3.2.7 - '@react-aria/breadcrumbs': 3.5.32(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/button': 3.14.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/calendar': 3.9.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/checkbox': 3.16.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/color': 3.1.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/combobox': 3.15.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/datepicker': 3.16.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/dialog': 3.5.34(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/disclosure': 3.1.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/dnd': 3.11.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/focus': 3.21.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/gridlist': 3.14.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/i18n': 3.12.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/label': 3.7.25(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/landmark': 3.0.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/link': 3.8.9(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/listbox': 3.15.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/menu': 3.21.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/meter': 3.4.30(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/numberfield': 3.12.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/overlays': 3.31.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/progress': 3.4.30(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/radio': 3.12.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/searchfield': 3.8.12(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/select': 3.17.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/selection': 3.27.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/separator': 3.4.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/slider': 3.8.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/ssr': 3.9.10(react@19.2.5) - '@react-aria/switch': 3.7.11(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/table': 3.17.11(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/tabs': 3.11.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/tag': 3.8.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/textfield': 3.18.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/toast': 3.0.11(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/tooltip': 3.9.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/tree': 3.1.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/utils': 3.33.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-aria/visually-hidden': 3.8.31(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) + '@internationalized/date': 3.12.1 + '@internationalized/number': 3.6.6 + '@internationalized/string': 3.2.8 + '@react-types/shared': 3.34.0(react@19.2.5) + '@swc/helpers': 0.5.21 + aria-hidden: 1.2.6 + clsx: 2.1.1 react: 19.2.5 react-dom: 19.2.5(react@19.2.5) + react-stately: 3.46.0(react@19.2.5) + use-sync-external-store: 1.6.0(react@19.2.5) react-charts@3.0.0-beta.57(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: @@ -10181,16 +8439,16 @@ snapshots: react: 19.2.5 react-dom: 19.2.5(react@19.2.5) - react-i18next@16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3): + react-i18next@16.6.6(i18next@25.10.10(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3): dependencies: '@babel/runtime': 7.29.2 html-parse-stringify: 3.0.1 - i18next: 25.10.10(typescript@5.9.3) + i18next: 25.10.10(typescript@6.0.3) react: 19.2.5 use-sync-external-store: 1.6.0(react@19.2.5) optionalDependencies: react-dom: 19.2.5(react@19.2.5) - typescript: 5.9.3 + typescript: 6.0.3 react-is@16.13.1: {} @@ -10223,35 +8481,15 @@ snapshots: optionalDependencies: react-dom: 19.2.5(react@19.2.5) - react-stately@3.45.0(react@19.2.5): + react-stately@3.46.0(react@19.2.5): dependencies: - '@react-stately/calendar': 3.9.3(react@19.2.5) - '@react-stately/checkbox': 3.7.5(react@19.2.5) - '@react-stately/collections': 3.12.10(react@19.2.5) - '@react-stately/color': 3.9.5(react@19.2.5) - '@react-stately/combobox': 3.13.0(react@19.2.5) - '@react-stately/data': 3.15.2(react@19.2.5) - '@react-stately/datepicker': 3.16.1(react@19.2.5) - '@react-stately/disclosure': 3.0.11(react@19.2.5) - '@react-stately/dnd': 3.7.4(react@19.2.5) - '@react-stately/form': 3.2.4(react@19.2.5) - '@react-stately/list': 3.13.4(react@19.2.5) - '@react-stately/menu': 3.9.11(react@19.2.5) - '@react-stately/numberfield': 3.11.0(react@19.2.5) - '@react-stately/overlays': 3.6.23(react@19.2.5) - '@react-stately/radio': 3.11.5(react@19.2.5) - '@react-stately/searchfield': 3.5.19(react@19.2.5) - '@react-stately/select': 3.9.2(react@19.2.5) - '@react-stately/selection': 3.20.9(react@19.2.5) - '@react-stately/slider': 3.7.5(react@19.2.5) - '@react-stately/table': 3.15.4(react@19.2.5) - '@react-stately/tabs': 3.8.9(react@19.2.5) - '@react-stately/toast': 3.1.3(react@19.2.5) - '@react-stately/toggle': 3.9.5(react@19.2.5) - '@react-stately/tooltip': 3.5.11(react@19.2.5) - '@react-stately/tree': 3.9.6(react@19.2.5) - '@react-types/shared': 3.33.1(react@19.2.5) + '@internationalized/date': 3.12.1 + '@internationalized/number': 3.6.6 + '@internationalized/string': 3.2.8 + '@react-types/shared': 3.34.0(react@19.2.5) + '@swc/helpers': 0.5.21 react: 19.2.5 + use-sync-external-store: 1.6.0(react@19.2.5) react-style-singleton@2.2.3(@types/react@19.2.14)(react@19.2.5): dependencies: @@ -10312,11 +8550,11 @@ snapshots: remix-auth@4.2.0: {} - remix-i18next@7.4.2(i18next@25.10.10(typescript@5.9.3))(react-i18next@16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3))(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5): + remix-i18next@7.4.2(i18next@25.10.10(typescript@6.0.3))(react-i18next@16.6.6(i18next@25.10.10(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3))(react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5): dependencies: - i18next: 25.10.10(typescript@5.9.3) + i18next: 25.10.10(typescript@6.0.3) react: 19.2.5 - react-i18next: 16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3) + react-i18next: 16.6.6(i18next@25.10.10(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) react-router: 7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) require-directory@2.1.1: {} @@ -10709,7 +8947,7 @@ snapshots: dependencies: ts-toolbelt: 9.6.0 - typescript@5.9.3: {} + typescript@6.0.3: {} uc.micro@2.1.0: {} @@ -10765,9 +9003,9 @@ snapshots: uuid@8.0.0: {} - valibot@1.3.1(typescript@5.9.3): + valibot@1.3.1(typescript@6.0.3): optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.3 vary@1.1.2: {} diff --git a/public/static-assets/badges/bubblyb.avif b/public/static-assets/badges/bubblyb.avif new file mode 100644 index 000000000..ef18df3ad Binary files /dev/null and b/public/static-assets/badges/bubblyb.avif differ diff --git a/public/static-assets/badges/bubblyb.gif b/public/static-assets/badges/bubblyb.gif new file mode 100644 index 000000000..de9be0967 Binary files /dev/null and b/public/static-assets/badges/bubblyb.gif differ diff --git a/public/static-assets/badges/oocee.avif b/public/static-assets/badges/oocee.avif new file mode 100644 index 000000000..2e1c5da9f Binary files /dev/null and b/public/static-assets/badges/oocee.avif differ diff --git a/public/static-assets/badges/oocee.gif b/public/static-assets/badges/oocee.gif new file mode 100644 index 000000000..7687b7d3c Binary files /dev/null and b/public/static-assets/badges/oocee.gif differ diff --git a/public/static-assets/badges/wellstringcustom.avif b/public/static-assets/badges/wellstringcustom.avif new file mode 100644 index 000000000..c70eb96d0 Binary files /dev/null and b/public/static-assets/badges/wellstringcustom.avif differ diff --git a/public/static-assets/badges/wellstringcustom.gif b/public/static-assets/badges/wellstringcustom.gif new file mode 100644 index 000000000..2dcae265c Binary files /dev/null and b/public/static-assets/badges/wellstringcustom.gif differ diff --git a/public/static-assets/badges/wellstringregular.avif b/public/static-assets/badges/wellstringregular.avif new file mode 100644 index 000000000..b1199487d Binary files /dev/null and b/public/static-assets/badges/wellstringregular.avif differ diff --git a/public/static-assets/badges/wellstringregular.gif b/public/static-assets/badges/wellstringregular.gif new file mode 100644 index 000000000..c67c5ef4f Binary files /dev/null and b/public/static-assets/badges/wellstringregular.gif differ diff --git a/public/static-assets/badges/wings.avif b/public/static-assets/badges/wings.avif new file mode 100644 index 000000000..7f6536dbe Binary files /dev/null and b/public/static-assets/badges/wings.avif differ diff --git a/public/static-assets/badges/wings.gif b/public/static-assets/badges/wings.gif new file mode 100644 index 000000000..0deaa02a8 Binary files /dev/null and b/public/static-assets/badges/wings.gif differ diff --git a/scripts/create-analyzer-json.ts b/scripts/create-analyzer-json.ts index 03b030877..0d3ee4c16 100644 --- a/scripts/create-analyzer-json.ts +++ b/scripts/create-analyzer-json.ts @@ -1,5 +1,3 @@ -// @ts-nocheck - // To run this script you need from https://github.com/Leanny/leanny.github.io // 1) WeaponInfoMain.json inside dicts // 2) WeaponInfoSub.json inside dicts @@ -18,10 +16,13 @@ import type { SubWeaponParams, WeaponKit, } from "~/features/build-analyzer/analyzer-types"; +import type { + MainWeaponId, + SpecialWeaponId, + SubWeaponId, +} from "~/modules/in-game-lists/types"; import { - type SpecialWeaponId, SQUID_BEAKON_ID, - type SubWeaponId, subWeaponIds, weaponIdToBaseWeaponId, } from "~/modules/in-game-lists/weapon-ids"; @@ -109,7 +110,8 @@ async function main() { if (specialWeaponShouldBeSkipped(specialWeapon)) continue; const rawParams = loadWeaponParamsObject(specialWeapon); - const params = parametersToSpecialWeaponResult(rawParams); + const params: Record = + parametersToSpecialWeaponResult(rawParams); // Super Chumps has two distinct splash damage values (near/far) // that should be labeled separately in the analyzer @@ -179,7 +181,7 @@ function splitIntoBaseStatsAndKits( > = {}; for (const [idStr, params] of Object.entries(allParams)) { - const id = Number(idStr); + const id = Number(idStr) as MainWeaponId; const baseId = weaponIdToBaseWeaponId(id); if (!weaponGroups[baseId]) weaponGroups[baseId] = []; weaponGroups[baseId].push({ id, params }); @@ -497,11 +499,11 @@ function parametersToMainWeaponResult( const resolveMin = ( valueOne: number | null | undefined, valueTwo: number | null | undefined, - ) => { + ): number | undefined => { if (typeof valueOne !== "number" && typeof valueTwo !== "number") return undefined; - if (typeof valueOne !== "number") return valueTwo; + if (typeof valueOne !== "number") return valueTwo as number; if (typeof valueTwo !== "number") return valueOne; return Math.min(valueOne, valueTwo); @@ -510,11 +512,11 @@ function parametersToMainWeaponResult( const resolveMax = ( valueOne: number | null | undefined, valueTwo: number | null | undefined, - ) => { + ): number | undefined => { if (typeof valueOne !== "number" && typeof valueTwo !== "number") return undefined; - if (typeof valueOne !== "number") return valueTwo; + if (typeof valueOne !== "number") return valueTwo as number; if (typeof valueTwo !== "number") return valueOne; return Math.max(valueOne, valueTwo); diff --git a/scripts/create-gear-json.ts b/scripts/create-gear-json.ts index bac47f907..96f7780dd 100644 --- a/scripts/create-gear-json.ts +++ b/scripts/create-gear-json.ts @@ -1,5 +1,3 @@ -// @ts-nocheck - import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -57,7 +55,13 @@ async function main() { internalName, brand: gear.Brand, translations: langDicts.map(([langCode, translations]) => { - const name = translations[categoryKey]?.[internalName]; + const category = ( + translations as unknown as Record< + string, + Record | undefined + > + )[categoryKey]; + const name = category?.[internalName]; invariant(name, `Missing translation for ${internalName}`); return { diff --git a/scripts/create-misc-json.ts b/scripts/create-misc-json.ts index 2e88473d9..f181852d9 100644 --- a/scripts/create-misc-json.ts +++ b/scripts/create-misc-json.ts @@ -1,5 +1,3 @@ -// @ts-nocheck - import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -12,6 +10,9 @@ import { translationJsonFolderName, } from "./utils"; +type LangDicts = Awaited>; +type LangDict = LangDicts[number][1]; + const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -176,7 +177,9 @@ async function main() { invariant(internalName, `Missing internal name for ${ability}`); const translation = decodeURIComponent( - langDict["CommonMsg/Gear/GearPowerName"][internalName], + (langDict["CommonMsg/Gear/GearPowerName"] as Record)[ + internalName + ], ); translationsMap[`ABILITY_${ability}`] = translation; @@ -216,7 +219,7 @@ async function main() { generateBadgeData(langDicts); } -function generateBadgeData(langDicts) { +function generateBadgeData(langDicts: LangDicts) { const badgeFiles = fs .readdirSync(BADGE_DIR) .filter((f) => f.endsWith(".png")); @@ -271,9 +274,12 @@ function generateBadgeData(langDicts) { fs.writeFileSync(path.join(OUTPUT_DIR, "game-badge-ids.ts"), tsContent); } -function buildBadgeTranslations(badgeIds, langDict) { - const badgeMsg = langDict["CommonMsg/Badge/BadgeMsg"]; - const translationsMap = {}; +function buildBadgeTranslations(badgeIds: string[], langDict: LangDict) { + const badgeMsg = langDict["CommonMsg/Badge/BadgeMsg"] as Record< + string, + string + >; + const translationsMap: Record = {}; for (const id of badgeIds) { if (badgeMsg[id] && !badgeMsg[id].includes("[group=")) { @@ -306,7 +312,9 @@ function buildBadgeTranslations(badgeIds, langDict) { continue; } - const lookupValue = langDict[rule.lookupDict]?.[variantName]; + const lookupValue = ( + langDict as unknown as Record | undefined> + )[rule.lookupDict]?.[variantName]; if (!lookupValue) { translationsMap[id] = id; continue; @@ -321,7 +329,10 @@ function buildBadgeTranslations(badgeIds, langDict) { return translationsMap; } -function writeBadgeJson(folder, translationsMap) { +function writeBadgeJson( + folder: string, + translationsMap: Record, +) { fs.writeFileSync( path.join(__dirname, "..", "locales", folder, "game-badges.json"), `${JSON.stringify(translationsMap, null, 2)}\n`, diff --git a/scripts/create-object-dmg-json.ts b/scripts/create-object-dmg-json.ts index 7dcc3bc7b..eaec40c38 100644 --- a/scripts/create-object-dmg-json.ts +++ b/scripts/create-object-dmg-json.ts @@ -1,9 +1,12 @@ -// @ts-nocheck - import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { DAMAGE_RECEIVERS } from "~/features/object-damage-calculator/calculator-constants"; +import type { + MainWeaponId, + SpecialWeaponId, + SubWeaponId, +} from "~/modules/in-game-lists/types"; import { mainWeaponIds, specialWeaponIds, @@ -23,6 +26,21 @@ const __dirname = path.dirname(__filename); const OUTPUT_DIR_PATH = path.join(__dirname, "output"); +type DamageReceiver = (typeof DAMAGE_RECEIVERS)[number]; + +type ResultEntry = { + mainWeaponIds: MainWeaponId[]; + subWeaponIds: SubWeaponId[]; + specialWeaponIds: SpecialWeaponId[]; + rates: { target: string; rate: number }[]; +}; + +type DamageRateCell = { + ColumnKey: string; + RowKey: string; + DamageRate?: number; +}; + const weaponParamsToWeaponIds = ( params: typeof weapons | typeof subWeapons | typeof specialWeapons, key: string, @@ -39,39 +57,48 @@ const weaponParamsToWeaponIds = ( .map((weapon) => weapon.Id); }; -const result = {}; -for (const cell of Object.values(params.CellList)) { - if (!DAMAGE_RECEIVERS.includes(cell.ColumnKey)) continue; +const isDamageReceiver = (key: string): key is DamageReceiver => + (DAMAGE_RECEIVERS as readonly string[]).includes(key); + +const result: Record = {}; +for (const cell of Object.values(params.CellList) as DamageRateCell[]) { + if (!isDamageReceiver(cell.ColumnKey)) continue; if (!cell.DamageRate) continue; if (!result[cell.RowKey]) { result[cell.RowKey] = { mainWeaponIds: weaponParamsToWeaponIds(weapons, cell.RowKey).filter( - (id) => mainWeaponIds.includes(id), + (id): id is MainWeaponId => + (mainWeaponIds as readonly number[]).includes(id), ), subWeaponIds: weaponParamsToWeaponIds(subWeapons, cell.RowKey).filter( - (id) => subWeaponIds.includes(id), + (id): id is SubWeaponId => + (subWeaponIds as readonly number[]).includes(id), ), specialWeaponIds: weaponParamsToWeaponIds( specialWeapons, cell.RowKey, - ).filter((id) => specialWeaponIds.includes(id)), + ).filter((id): id is SpecialWeaponId => + (specialWeaponIds as readonly number[]).includes(id), + ), rates: [], }; } + const entry = result[cell.RowKey]!; + // if it applies to no PvP weapons, we don't care about it if ( - result[cell.RowKey].mainWeaponIds.length === 0 && - result[cell.RowKey].subWeaponIds.length === 0 && - result[cell.RowKey].specialWeaponIds.length === 0 && + entry.mainWeaponIds.length === 0 && + entry.subWeaponIds.length === 0 && + entry.specialWeaponIds.length === 0 && cell.RowKey !== "ObjectEffect_Up" ) { result[cell.RowKey] = undefined; continue; } - result[cell.RowKey].rates.push({ + entry.rates.push({ target: cell.ColumnKey, rate: cell.DamageRate, }); @@ -81,7 +108,7 @@ for (const cell of Object.values(params.CellList)) { cell.ColumnKey.includes("BulletUmbrellaCanopyNormal") || cell.ColumnKey.includes("BulletUmbrellaCanopyWide") ) { - result[cell.RowKey].rates.push({ + entry.rates.push({ target: `${cell.ColumnKey}_Launched`, rate: cell.DamageRate, }); @@ -89,12 +116,12 @@ for (const cell of Object.values(params.CellList)) { // if it has special damage rates for Splat Brella, add the same value for Recycled Brella if (cell.ColumnKey === "BulletUmbrellaCanopyNormal") { - result[cell.RowKey].rates.push({ + entry.rates.push({ target: "BulletShelterCanopyFocus", rate: cell.DamageRate, }); - result[cell.RowKey].rates.push({ + entry.rates.push({ target: "BulletShelterCanopyFocus_Launched", rate: cell.DamageRate, }); diff --git a/scripts/replace-weapon-names.ts b/scripts/replace-weapon-names.ts index 9acecec54..d141a5a09 100644 --- a/scripts/replace-weapon-names.ts +++ b/scripts/replace-weapon-names.ts @@ -1,5 +1,3 @@ -// @ts-nocheck - import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -63,8 +61,8 @@ async function main() { continue; } - const weapon: any = weapons.find( - (weapon: any) => + const weapon = (weapons as Array<{ __RowId: string; Id: number }>).find( + (weapon) => file.includes(`${weapon.__RowId}.`) || file.includes(`${weapon.__RowId}_`), ); diff --git a/scripts/sync-weapon-params.ts b/scripts/sync-weapon-params.ts index 1d884991e..52f3ee88b 100644 --- a/scripts/sync-weapon-params.ts +++ b/scripts/sync-weapon-params.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 000000000..4719c9c7b --- /dev/null +++ b/scripts/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../tsconfig.json", + "include": ["./**/*.ts", "../types/**/*.d.ts"], + "exclude": [] +} diff --git a/scripts/utils.ts b/scripts/utils.ts index 6ef82e4f4..f4689d89c 100644 --- a/scripts/utils.ts +++ b/scripts/utils.ts @@ -1,5 +1,3 @@ -// @ts-nocheck - import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; diff --git a/tsconfig.json b/tsconfig.json index 1310f6fdd..cf043241b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "**/*.tsx", ".react-router/types/**/*" ], + "exclude": ["scripts/**/*"], "compilerOptions": { "types": ["@react-router/node", "vite/client"], "rootDirs": [".", "./.react-router/types"], @@ -18,7 +19,6 @@ "resolveJsonModule": true, "target": "ES2022", "strict": true, - "baseUrl": ".", "paths": { "~/*": ["./app/*"] },