Merge branch 'development' into player-picker-single-sele
# Conflicts: # src/locales/de/messages.po # src/locales/en/messages.po # src/locales/es/messages.po # src/locales/ja/messages.po
This commit is contained in:
@@ -208,8 +208,8 @@ const BadgeShowcase = ({ playerId }: BadgeShowcaseProps) => {
|
||||
style={{
|
||||
width: '100px',
|
||||
height: '100px',
|
||||
borderRadius: '12px',
|
||||
background: 'light-dark(var(--mantine-color-white), var(--mantine-color-dark-7))',
|
||||
borderRadius: 'var(--flxn-badge-radius)',
|
||||
background: 'var(--flxn-showcase-bg)',
|
||||
border: '2px solid var(--mantine-primary-color-5)',
|
||||
position: 'absolute',
|
||||
top: `${(i + 1) * 4}px`,
|
||||
@@ -226,8 +226,8 @@ const BadgeShowcase = ({ playerId }: BadgeShowcaseProps) => {
|
||||
style={{
|
||||
width: '100px',
|
||||
height: '100px',
|
||||
borderRadius: '12px',
|
||||
background: 'light-dark(var(--mantine-color-white), var(--mantine-color-dark-7))',
|
||||
borderRadius: 'var(--flxn-badge-radius)',
|
||||
background: 'var(--flxn-showcase-bg)',
|
||||
border: display.earned
|
||||
? '2px solid var(--mantine-primary-color-6)'
|
||||
: '2px dashed var(--mantine-primary-color-4)',
|
||||
|
||||
@@ -9,7 +9,7 @@ export function BracketPending() {
|
||||
p={0}
|
||||
style={{
|
||||
overflow: "hidden",
|
||||
backgroundImage: `radial-gradient(circle, var(--mantine-color-default-border) 1px, transparent 1px)`,
|
||||
backgroundImage: `radial-gradient(circle, var(--flxn-dotgrid-color) 1px, transparent 1px)`,
|
||||
backgroundSize: "16px 16px",
|
||||
backgroundPosition: "0 0, 8px 8px",
|
||||
minHeight: "70dvh",
|
||||
|
||||
@@ -89,7 +89,7 @@ const BracketView: React.FC<BracketViewProps> = ({ bracket, showControls, groupC
|
||||
h={`calc(${height})`}
|
||||
className={styles["bracket-container"]}
|
||||
style={{
|
||||
backgroundImage: `radial-gradient(circle, var(--mantine-color-default-border) 1px, transparent 1px)`,
|
||||
backgroundImage: `radial-gradient(circle, var(--flxn-dotgrid-color) 1px, transparent 1px)`,
|
||||
backgroundSize: "16px 16px",
|
||||
backgroundPosition: "0 0, 8px 8px",
|
||||
}}
|
||||
|
||||
@@ -269,7 +269,11 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
w={showToolbar || showEditButton ? 200 : 220}
|
||||
withBorder
|
||||
pos="relative"
|
||||
className={canTap ? styles["tappable-card"] : undefined}
|
||||
className={
|
||||
canTap
|
||||
? `flxn-bracket-card ${styles["tappable-card"]}`
|
||||
: "flxn-bracket-card"
|
||||
}
|
||||
onClick={canTap ? () => onTap!(match) : undefined}
|
||||
role={canTap ? "button" : undefined}
|
||||
tabIndex={canTap ? 0 : undefined}
|
||||
|
||||
@@ -83,10 +83,10 @@ export const MatchSlot: React.FC<MatchSlotProps> = ({
|
||||
size={14}
|
||||
weight="fill"
|
||||
style={{
|
||||
color: 'gold',
|
||||
color: 'var(--flxn-gold)',
|
||||
marginLeft: '2px',
|
||||
marginTop: '-1px',
|
||||
filter: 'drop-shadow(0 1px 1px rgba(0,0,0,0.3))',
|
||||
filter: 'drop-shadow(0 1px 1px var(--flxn-pop-shadow))',
|
||||
flexShrink: 0
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -52,6 +52,7 @@ const MatchCard = ({ match, hideH2H = false }: MatchCardProps) => {
|
||||
py="md"
|
||||
withBorder
|
||||
radius="md"
|
||||
className="flxn-match-card"
|
||||
style={{ position: "relative", zIndex: 2 }}
|
||||
>
|
||||
<Stack gap="sm">
|
||||
@@ -208,16 +209,12 @@ const MatchCard = ({ match, hideH2H = false }: MatchCardProps) => {
|
||||
<Paper
|
||||
px="md"
|
||||
pb={4}
|
||||
className="flxn-reaction-row"
|
||||
style={{
|
||||
position: "relative",
|
||||
zIndex: 1,
|
||||
marginTop: -6,
|
||||
paddingTop: 8,
|
||||
backgroundColor: "var(--mantine-color-gray-light)",
|
||||
borderTop: "none",
|
||||
borderRadius:
|
||||
"0 0 var(--mantine-radius-md) var(--mantine-radius-md)",
|
||||
border: "1px solid var(--mantine-color-default-border)",
|
||||
}}
|
||||
>
|
||||
<Suspense>
|
||||
|
||||
@@ -130,7 +130,7 @@ const TeamHeadToHeadContent = ({ team1, team2, isOpen = true }: TeamHeadToHeadSh
|
||||
|
||||
{leader && (
|
||||
<Group justify="center" gap="xs">
|
||||
<CrownIcon size={16} weight="fill" color="gold" />
|
||||
<CrownIcon size={16} weight="fill" color="var(--flxn-gold)" />
|
||||
<Text size="xs" c="dimmed">
|
||||
<Trans>{leader.name} leads the series</Trans>
|
||||
</Text>
|
||||
|
||||
@@ -158,7 +158,7 @@ const PlayerHeadToHeadContent = ({
|
||||
|
||||
{leader && (
|
||||
<Group justify="center" gap="xs">
|
||||
<CrownIcon size={16} weight="fill" color="gold" />
|
||||
<CrownIcon size={16} weight="fill" color="var(--flxn-gold)" />
|
||||
<Text size="xs" c="dimmed">
|
||||
<Trans>{leader} leads the series</Trans>
|
||||
</Text>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, Stack, Text, Divider, Group, Button, Anchor } from "@mantine/core";
|
||||
import { Box, Stack, Text, Divider, Group, Button } from "@mantine/core";
|
||||
import { Suspense, useState, useDeferredValue } from "react";
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
@@ -57,9 +57,14 @@ const Profile = ({ id }: ProfileProps) => {
|
||||
<Stack px="md">
|
||||
<Group justify="space-between" align="center">
|
||||
<Text size="md" fw={700}><Trans>Badges</Trans></Text>
|
||||
<Anchor component={Link} to="/badges" size="sm" fw={500}>
|
||||
<Button
|
||||
component={Link}
|
||||
to="/badges"
|
||||
variant="subtle"
|
||||
size="compact-sm"
|
||||
>
|
||||
<Trans>View all badges</Trans>
|
||||
</Anchor>
|
||||
</Button>
|
||||
</Group>
|
||||
<Suspense fallback={<BadgeShowcaseSkeleton />}>
|
||||
<BadgeShowcase playerId={id} />
|
||||
|
||||
@@ -244,7 +244,7 @@ export const PredictionLeaderboard: React.FC<PredictionLeaderboardProps> = ({
|
||||
weight="fill"
|
||||
color={
|
||||
entry.championCorrect && isComplete
|
||||
? "gold"
|
||||
? "var(--flxn-gold)"
|
||||
: "var(--mantine-color-dimmed)"
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -79,8 +79,8 @@ const TeamChip = ({
|
||||
size={14}
|
||||
weight="fill"
|
||||
style={{
|
||||
color: "gold",
|
||||
filter: "drop-shadow(0 1px 1px rgba(0,0,0,0.3))",
|
||||
color: "var(--flxn-gold)",
|
||||
filter: "drop-shadow(0 1px 1px var(--flxn-pop-shadow))",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
Box,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Text,
|
||||
UnstyledButton,
|
||||
useComputedColorScheme,
|
||||
} from "@mantine/core";
|
||||
import { CaretRightIcon } from "@phosphor-icons/react";
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
import Sheet from "@/components/sheet/sheet";
|
||||
import { useSheet } from "@/hooks/use-sheet";
|
||||
import { updateUserTheme } from "@/features/settings/server";
|
||||
import { useAuth } from "@/contexts/auth-context";
|
||||
import { DEFAULT_THEME_ID, THEME_LIST, getTheme } from "@/themes/registry";
|
||||
import ThemePreview from "./theme-preview";
|
||||
|
||||
const ThemePicker = () => {
|
||||
const { metadata, user, set } = useAuth();
|
||||
const { t } = useLingui();
|
||||
const sheet = useSheet();
|
||||
const scheme = useComputedColorScheme("dark");
|
||||
const currentId = metadata.theme || DEFAULT_THEME_ID;
|
||||
const current = getTheme(currentId);
|
||||
|
||||
const handleSelect = async (id: string) => {
|
||||
if (!user || id === currentId) return;
|
||||
const previous = metadata;
|
||||
set({ metadata: { ...metadata, theme: id } });
|
||||
try {
|
||||
await updateUserTheme({ data: id });
|
||||
} catch {
|
||||
set({ metadata: previous });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Text fw={500} size="sm" mb="xs">
|
||||
<Trans>Theme</Trans>
|
||||
</Text>
|
||||
<UnstyledButton
|
||||
onClick={sheet.open}
|
||||
w="100%"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "var(--mantine-spacing-sm)",
|
||||
padding: "var(--mantine-spacing-xs)",
|
||||
border: "1px solid var(--mantine-color-default-border)",
|
||||
borderRadius: "var(--mantine-radius-md)",
|
||||
}}
|
||||
>
|
||||
<Box w="3.25rem" style={{ flexShrink: 0 }}>
|
||||
<ThemePreview
|
||||
manifest={current}
|
||||
scheme={scheme}
|
||||
height="2.25rem"
|
||||
scale={0.5}
|
||||
/>
|
||||
</Box>
|
||||
<Text size="sm" fw={500} style={{ flex: 1, textAlign: "left" }}>
|
||||
{t(current.label)}
|
||||
</Text>
|
||||
<CaretRightIcon size={16} color="var(--mantine-color-dimmed)" />
|
||||
</UnstyledButton>
|
||||
|
||||
<Sheet title={t`Theme`} {...sheet.props}>
|
||||
<SimpleGrid cols={3} spacing="sm" py="sm">
|
||||
{[...THEME_LIST]
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(a.order ?? 100) - (b.order ?? 100) ||
|
||||
t(a.label).localeCompare(t(b.label))
|
||||
)
|
||||
.map((manifest) => {
|
||||
const isSelected = manifest.id === currentId;
|
||||
return (
|
||||
<UnstyledButton
|
||||
key={manifest.id}
|
||||
onClick={() => handleSelect(manifest.id)}
|
||||
>
|
||||
<Stack gap={6} align="center">
|
||||
<Box
|
||||
w="100%"
|
||||
p="0.125rem"
|
||||
bd={
|
||||
isSelected
|
||||
? "0.125rem solid var(--mantine-color-bright)"
|
||||
: "0.125rem solid transparent"
|
||||
}
|
||||
style={{
|
||||
borderRadius: "calc(var(--mantine-radius-md) + 0.25rem)",
|
||||
}}
|
||||
>
|
||||
<ThemePreview manifest={manifest} scheme={scheme} />
|
||||
</Box>
|
||||
<Text size="xs" fw={isSelected ? 700 : 400} ta="center">
|
||||
{t(manifest.label)}
|
||||
</Text>
|
||||
</Stack>
|
||||
</UnstyledButton>
|
||||
);
|
||||
})}
|
||||
</SimpleGrid>
|
||||
</Sheet>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemePicker;
|
||||
@@ -0,0 +1,179 @@
|
||||
import {
|
||||
DEFAULT_THEME,
|
||||
convertCssVariables,
|
||||
defaultCssVariablesResolver,
|
||||
mergeMantineTheme,
|
||||
mergeThemeOverrides,
|
||||
} from "@mantine/core";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { useAuth } from "@/contexts/auth-context";
|
||||
import { baseTheme } from "@/lib/mantine/base-theme";
|
||||
import type { ThemeManifest } from "@/themes/types";
|
||||
|
||||
/* Each preview is a same-origin srcdoc iframe carrying the theme's own
|
||||
data-theme/-scheme, the app's stylesheets, the theme's font links, and its
|
||||
Mantine variables — 1:1 with the active theme, with nothing bleeding in. */
|
||||
|
||||
const buildMantineVarsCss = (manifest: ThemeManifest, accent: string) => {
|
||||
const merged = mergeMantineTheme(
|
||||
DEFAULT_THEME,
|
||||
mergeThemeOverrides(baseTheme, manifest.mantine ?? {}, {
|
||||
primaryColor: accent,
|
||||
})
|
||||
);
|
||||
return convertCssVariables(defaultCssVariablesResolver(merged), ":root");
|
||||
};
|
||||
|
||||
const collectAppCss = () =>
|
||||
Array.from(document.querySelectorAll('style, link[rel="stylesheet"]'))
|
||||
.filter(
|
||||
(el) =>
|
||||
!el.hasAttribute("data-mantine-styles") &&
|
||||
!el.hasAttribute("data-flxn-theme-font")
|
||||
)
|
||||
.map((el) =>
|
||||
el.tagName === "LINK"
|
||||
? el.outerHTML
|
||||
: `<style>${el.textContent ?? ""}</style>`
|
||||
)
|
||||
.join("\n");
|
||||
|
||||
const PREVIEW_LOCAL_CSS = `
|
||||
html, body { margin: 0; height: 100%; overflow: hidden; }
|
||||
body { display: flex; }
|
||||
.pv-card {
|
||||
flex: 1;
|
||||
margin: 0.375rem;
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
background-color: var(--mantine-color-body);
|
||||
border: 1px solid var(--mantine-color-default-border);
|
||||
border-radius: var(--mantine-radius-default);
|
||||
box-shadow: var(--mantine-shadow-sm);
|
||||
}
|
||||
.pv-heading {
|
||||
font-family: var(--mantine-font-family-headings);
|
||||
color: var(--mantine-color-text);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
.pv-row { display: flex; align-items: center; gap: 4px; }
|
||||
.pv-chip {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
flex-shrink: 0;
|
||||
border-radius: var(--mantine-radius-default);
|
||||
background-color: var(--mantine-primary-color-filled);
|
||||
}
|
||||
.pv-bars { display: flex; flex-direction: column; gap: 3px; flex: 1; }
|
||||
.pv-bar {
|
||||
display: block;
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
background-color: var(--mantine-color-dimmed);
|
||||
}
|
||||
`;
|
||||
|
||||
const buildSrcdoc = (
|
||||
manifest: ThemeManifest,
|
||||
scheme: "light" | "dark",
|
||||
accent: string,
|
||||
appCss: string
|
||||
) => {
|
||||
const fontLinks = (manifest.fontLinks ?? [])
|
||||
.map((href) => `<link rel="stylesheet" href="${href}">`)
|
||||
.join("");
|
||||
return `<!doctype html>
|
||||
<html data-theme="${manifest.id}" data-mantine-color-scheme="${scheme}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>html { color-scheme: ${scheme}; }</style>
|
||||
<style>${buildMantineVarsCss(manifest, accent)}</style>
|
||||
${appCss}
|
||||
${fontLinks}
|
||||
<style>${PREVIEW_LOCAL_CSS}</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="pv-card">
|
||||
<span class="pv-heading">Ag</span>
|
||||
<div class="pv-row">
|
||||
<span class="pv-chip"></span>
|
||||
<span class="pv-bars">
|
||||
<span class="pv-bar" style="opacity: 0.7"></span>
|
||||
<span class="pv-bar" style="opacity: 0.4; width: 60%"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
};
|
||||
|
||||
interface ThemePreviewProps {
|
||||
manifest: ThemeManifest;
|
||||
scheme: "light" | "dark";
|
||||
height?: string;
|
||||
/** Renders the document at height/scale and scales it down visually. */
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
const ThemePreview = ({
|
||||
manifest,
|
||||
scheme,
|
||||
height = "4.5rem",
|
||||
scale = 1,
|
||||
}: ThemePreviewProps) => {
|
||||
const { metadata } = useAuth();
|
||||
const { t } = useLingui();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
useEffect(() => setMounted(true), []);
|
||||
|
||||
const previewScheme =
|
||||
manifest.colorSchemes === "both" ? scheme : manifest.colorSchemes;
|
||||
const accent = metadata.accentColor || "blue";
|
||||
|
||||
const srcdoc = useMemo(
|
||||
() =>
|
||||
mounted
|
||||
? buildSrcdoc(manifest, previewScheme, accent, collectAppCss())
|
||||
: null,
|
||||
[mounted, manifest, previewScheme, accent]
|
||||
);
|
||||
|
||||
const frameStyle: React.CSSProperties = {
|
||||
width: "100%",
|
||||
height,
|
||||
border: "1px solid var(--mantine-color-default-border)",
|
||||
borderRadius: "var(--mantine-radius-md)",
|
||||
overflow: "hidden",
|
||||
};
|
||||
|
||||
if (!srcdoc) return <div style={frameStyle} />;
|
||||
return (
|
||||
<div style={frameStyle}>
|
||||
<iframe
|
||||
srcDoc={srcdoc}
|
||||
title={t(manifest.label)}
|
||||
sandbox=""
|
||||
scrolling="no"
|
||||
aria-hidden
|
||||
tabIndex={-1}
|
||||
style={{
|
||||
display: "block",
|
||||
border: 0,
|
||||
width: `${100 / scale}%`,
|
||||
height: `calc(${height} / ${scale})`,
|
||||
transform: `scale(${scale})`,
|
||||
transformOrigin: "top left",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemePreview;
|
||||
@@ -2,12 +2,28 @@ import { createServerFn } from "@tanstack/react-start";
|
||||
import { superTokensFunctionMiddleware } from "@/utils/supertokens";
|
||||
import { serverFnLoggingMiddleware } from "@/utils/activities";
|
||||
|
||||
// Whitelists: these values flow into Mantine's primaryColor / the
|
||||
// data-mantine-color-scheme attribute — an unknown value crash-loops or
|
||||
// unstyles the account's UI on every load.
|
||||
const ACCENT_COLORS = [
|
||||
"blue",
|
||||
"red",
|
||||
"green",
|
||||
"yellow",
|
||||
"grape",
|
||||
"orange",
|
||||
"pink",
|
||||
"lime",
|
||||
];
|
||||
const COLOR_SCHEMES = ["light", "dark", "auto"];
|
||||
|
||||
export const updateUserColorScheme = createServerFn({ method: "POST" })
|
||||
.validator((data: string) => data)
|
||||
.middleware([superTokensFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ context, data }) => {
|
||||
const { userAuthId, metadata } = context;
|
||||
if (!userAuthId) return;
|
||||
if (!COLOR_SCHEMES.includes(data)) return;
|
||||
|
||||
const { updateUserMetadataFields } = await import(
|
||||
"@/utils/supertokens-core.server"
|
||||
@@ -51,12 +67,41 @@ export const updateUserLocale = createServerFn({ method: "POST" })
|
||||
};
|
||||
});
|
||||
|
||||
export const updateUserTheme = createServerFn({ method: "POST" })
|
||||
.validator((data: string) => data)
|
||||
.middleware([superTokensFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ context, data }) => {
|
||||
const { userAuthId, metadata } = context;
|
||||
if (!userAuthId) return;
|
||||
|
||||
const { THEME_IDS } = await import("@/themes/registry");
|
||||
if (!THEME_IDS.includes(data)) {
|
||||
throw new Error("Unknown theme");
|
||||
}
|
||||
|
||||
const { updateUserMetadataFields } = await import(
|
||||
"@/utils/supertokens-core.server"
|
||||
);
|
||||
|
||||
await updateUserMetadataFields(userAuthId, {
|
||||
theme: data,
|
||||
});
|
||||
|
||||
return {
|
||||
metadata: {
|
||||
...metadata,
|
||||
theme: data,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const updateUserAccentColor = createServerFn({ method: "POST" })
|
||||
.validator((data: string) => data)
|
||||
.middleware([superTokensFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ context, data }) => {
|
||||
const { userAuthId, metadata } = context;
|
||||
if (!userAuthId) return;
|
||||
if (!ACCENT_COLORS.includes(data)) return;
|
||||
|
||||
const { updateUserMetadataFields } = await import(
|
||||
"@/utils/supertokens-core.server"
|
||||
|
||||
@@ -14,26 +14,20 @@ type PodiumTier = "first" | "second" | "third";
|
||||
|
||||
const tierStyles: Record<PodiumTier, React.CSSProperties> = {
|
||||
first: {
|
||||
background:
|
||||
"light-dark(color-mix(in srgb, var(--mantine-color-yellow-4) 10%, var(--mantine-color-white)), color-mix(in srgb, var(--mantine-color-yellow-8) 16%, var(--mantine-color-dark-6)))",
|
||||
border:
|
||||
"1px solid light-dark(color-mix(in srgb, var(--mantine-color-yellow-6) 45%, var(--mantine-color-gray-3)), color-mix(in srgb, var(--mantine-color-yellow-6) 35%, var(--mantine-color-dark-4)))",
|
||||
background: "var(--flxn-podium-1-bg)",
|
||||
border: "1px solid var(--flxn-podium-1-border)",
|
||||
borderRadius: "var(--mantine-radius-md)",
|
||||
boxShadow: "var(--mantine-shadow-md)",
|
||||
},
|
||||
second: {
|
||||
background:
|
||||
"light-dark(color-mix(in srgb, var(--mantine-color-gray-5) 10%, var(--mantine-color-white)), color-mix(in srgb, var(--mantine-color-gray-5) 10%, var(--mantine-color-dark-6)))",
|
||||
border:
|
||||
"1px solid light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-4))",
|
||||
background: "var(--flxn-podium-2-bg)",
|
||||
border: "1px solid var(--flxn-podium-2-border)",
|
||||
borderRadius: "var(--mantine-radius-md)",
|
||||
boxShadow: "var(--mantine-shadow-xs)",
|
||||
},
|
||||
third: {
|
||||
background:
|
||||
"light-dark(color-mix(in srgb, var(--mantine-color-orange-5) 8%, var(--mantine-color-white)), color-mix(in srgb, var(--mantine-color-orange-9) 14%, var(--mantine-color-dark-6)))",
|
||||
border:
|
||||
"1px solid light-dark(color-mix(in srgb, var(--mantine-color-orange-6) 35%, var(--mantine-color-gray-3)), color-mix(in srgb, var(--mantine-color-orange-7) 30%, var(--mantine-color-dark-4)))",
|
||||
background: "var(--flxn-podium-3-bg)",
|
||||
border: "1px solid var(--flxn-podium-3-border)",
|
||||
borderRadius: "var(--mantine-radius-md)",
|
||||
boxShadow: "var(--mantine-shadow-xs)",
|
||||
},
|
||||
@@ -54,8 +48,7 @@ const tierConfig: Record<
|
||||
first: {
|
||||
color: "yellow",
|
||||
label: msg`Champions`,
|
||||
labelColor:
|
||||
"light-dark(var(--mantine-color-yellow-8), var(--mantine-color-yellow-4))",
|
||||
labelColor: "var(--flxn-podium-1-label)",
|
||||
icon: <CrownIcon size={24} />,
|
||||
iconSize: "xl",
|
||||
padding: "md",
|
||||
@@ -64,8 +57,7 @@ const tierConfig: Record<
|
||||
second: {
|
||||
color: "gray",
|
||||
label: msg`2nd place`,
|
||||
labelColor:
|
||||
"light-dark(var(--mantine-color-gray-6), var(--mantine-color-gray-4))",
|
||||
labelColor: "var(--flxn-podium-2-label)",
|
||||
icon: <MedalIcon size={20} />,
|
||||
iconSize: "lg",
|
||||
padding: "xs",
|
||||
@@ -74,8 +66,7 @@ const tierConfig: Record<
|
||||
third: {
|
||||
color: "orange",
|
||||
label: msg`3rd place`,
|
||||
labelColor:
|
||||
"light-dark(var(--mantine-color-orange-8), var(--mantine-color-orange-4))",
|
||||
labelColor: "var(--flxn-podium-3-label)",
|
||||
icon: <MedalIcon size={18} />,
|
||||
iconSize: "lg",
|
||||
padding: "xs",
|
||||
|
||||
@@ -320,7 +320,7 @@ const SetupGroupStage: React.FC<SetupGroupStageProps> = ({
|
||||
</Text>
|
||||
<Box
|
||||
style={{
|
||||
backgroundImage: `radial-gradient(circle, var(--mantine-color-default-border) 1px, transparent 1px)`,
|
||||
backgroundImage: `radial-gradient(circle, var(--flxn-dotgrid-color) 1px, transparent 1px)`,
|
||||
backgroundSize: "16px 16px",
|
||||
backgroundPosition: "0 0, 8px 8px",
|
||||
padding: "1rem",
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background-color: var(--mantine-color-default-border);
|
||||
/* Mantine ships box-shadow: var(--mantine-shadow-sm) on indicators;
|
||||
themes with hard offset shadow tokens render it as a doubled dot. */
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.indicator.indicator[data-active] {
|
||||
|
||||
@@ -39,8 +39,12 @@ interface NavTileProps {
|
||||
|
||||
/** A single square-ish nav tile with icon-over-label and press feedback. */
|
||||
export const NavTile = ({ label, Icon, onClick, disabled }: NavTileProps) => (
|
||||
<UnstyledButton className={classes.tile} onClick={onClick} disabled={disabled}>
|
||||
<span className={classes.icon}>
|
||||
<UnstyledButton
|
||||
className={`${classes.tile} flxn-tile`}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<span className={`${classes.icon} flxn-tile-icon`}>
|
||||
<Icon size={22} weight="bold" />
|
||||
</span>
|
||||
<Text size="xs" fw={600} lineClamp={2} lh={1.2}>
|
||||
|
||||
Reference in New Issue
Block a user