Adding: themes, predictions, match reporting, ui fixes, sse fixes, badge fixes, match flow, i18n, match edits, perf, auth fix #22

Merged
kyle merged 43 commits from development into main 2026-08-23 23:09:01 -05:00
81 changed files with 13127 additions and 286 deletions
Showing only changes of commit 5ac39ac7a2 - Show all commits
+5 -1
View File
@@ -1,7 +1,11 @@
{
"name": "tanstack-start-example-basic-react-query",
"private": true,
"sideEffects": false,
"sideEffects": [
"**/*.css",
"**/themes/styles.ts",
"**/themes/theme-styles.ts"
],
"type": "module",
"scripts": {
"dev": "vite dev --host 0.0.0.0",
+11 -1
View File
@@ -203,7 +203,17 @@ function RootDocument({ children }: { children: React.ReactNode }) {
media="(prefers-color-scheme: dark)"
content={CHROME_COLORS.dark.base}
/>
<ColorSchemeScript />
<ColorSchemeScript defaultColorScheme="auto" />
{/* Pre-hydration theme restore from ThemeProvider's localStorage
mirrors: data-theme (so exotic themes don't flash default),
forced color scheme (dark-only themes must not paint light),
and the theme's font stylesheets (avoid FOUT until hydration). */}
<script
dangerouslySetInnerHTML={{
__html:
'try{var d=document.documentElement,t=localStorage.getItem("flxn-theme");if(t&&t!=="default"){d.setAttribute("data-theme",t);}var s=localStorage.getItem("flxn-theme-scheme");if(s){d.setAttribute("data-mantine-color-scheme",s);}var f=JSON.parse(localStorage.getItem("flxn-theme-fonts")||"[]");for(var i=0;i<f.length;i++){var l=document.createElement("link");l.rel="stylesheet";l.href=f[i];l.setAttribute("data-flxn-theme-font",t||"default");document.head.appendChild(l);}}catch(e){}',
}}
/>
<link rel="stylesheet" href="/styles.css" />
</head>
<body
+2
View File
@@ -2,6 +2,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { Box, Title, Stack } from "@mantine/core";
import { ColorSchemePicker } from "@/features/settings/components/color-scheme-picker";
import AccentColorPicker from "@/features/settings/components/accent-color-picker";
import ThemePicker from "@/features/settings/components/theme-picker";
import LocalePicker from "@/features/settings/components/locale-picker";
import { NotificationsSection } from "@/features/settings/components/notifications-section";
import { SignOutIcon } from "@phosphor-icons/react";
@@ -33,6 +34,7 @@ function RouteComponent() {
>
<Title order={3}><Trans>Appearance</Trans></Title>
<Stack>
<ThemePicker />
<AccentColorPicker />
<ColorSchemePicker />
<LocalePicker />
+28 -8
View File
@@ -1,7 +1,7 @@
import {
Avatar as MantineAvatar,
AvatarProps as MantineAvatarProps,
Paper,
Box,
Modal,
Image,
Group,
@@ -22,10 +22,17 @@ interface AvatarProps
contain?: boolean;
}
// Mantine radius tokens ("sm", "xl") are valid as props but not as raw CSS
// values — resolve them before using in style/borderRadius.
const resolveRadius = (radius: string | number) =>
typeof radius === "string" && /^[a-z]+$/.test(radius)
? `var(--mantine-radius-${radius})`
: radius;
const Avatar = ({
name,
size = 35,
radius = "100%",
radius = "var(--flxn-avatar-radius, 50%)",
withBorder = true,
disableFullscreen = false,
contain = false,
@@ -34,6 +41,7 @@ const Avatar = ({
const { t } = useLingui();
const [isFullscreenOpen, setIsFullscreenOpen] = useState(false);
const hasImage = Boolean(props.src);
const frameRadius = resolveRadius(radius);
const handleAvatarClick = () => {
if (hasImage && !disableFullscreen) {
@@ -43,11 +51,20 @@ const Avatar = ({
return (
<>
<Paper
p={size / 20}
radius={radius}
withBorder={withBorder}
<Box
className="flxn-avatar-frame"
p={withBorder ? size / 20 : 0}
style={{
// Plain Box, not Paper: the frame must own the avatar's single
// border (via --flxn-avatar-border) and stay immune to generic
// theme Paper restyles; shape comes from --flxn-avatar-radius.
borderRadius: frameRadius,
border: withBorder
? 'var(--flxn-avatar-border, 1px solid var(--mantine-color-default-border))'
: undefined,
backgroundColor: withBorder
? 'var(--mantine-color-body)'
: undefined,
cursor: hasImage && !disableFullscreen ? 'pointer' : 'default',
transition: 'transform 0.15s ease',
}}
@@ -69,14 +86,17 @@ const Avatar = ({
size={size}
radius={radius}
w={size}
{...props}
styles={{
root: {
borderRadius: frameRadius,
},
image: {
objectFit: contain ? 'contain' : 'cover',
},
}}
{...props}
/>
</Paper>
</Box>
<Modal
opened={isFullscreenOpen}
+13 -5
View File
@@ -1,7 +1,13 @@
import { Box, Container, Title, useComputedColorScheme } from "@mantine/core";
import { PropsWithChildren, useEffect, useRef } from "react";
import { Drawer as VaulDrawer } from "vaul";
import { CHROME_COLORS, setThemeColorMeta } from "@/lib/mantine/theme-colors";
import {
pushDimmedThemeColor,
popDimmedThemeColor,
} from "@/lib/mantine/theme-colors";
import { getTheme } from "@/themes/registry";
import { resolveChromeColors } from "@/themes/chrome";
import { useAuth } from "@/contexts/auth-context";
import styles from "./styles.module.css";
interface DrawerProps extends PropsWithChildren {
@@ -20,22 +26,24 @@ const Drawer: React.FC<DrawerProps> = ({
onExited,
}) => {
const colorScheme = useComputedColorScheme("light");
const { metadata } = useAuth();
const contentRef = useRef<HTMLDivElement>(null);
const themeId = metadata.theme;
useEffect(() => {
if (!opened) return;
const appElement = document.querySelector<HTMLElement>(".app");
const colors = CHROME_COLORS[colorScheme];
const colors = resolveChromeColors(getTheme(themeId))[colorScheme];
appElement?.classList.add("drawer-scaling");
setThemeColorMeta(colors.dimmed);
pushDimmedThemeColor(colors.dimmed);
return () => {
appElement?.classList.remove("drawer-scaling");
setThemeColorMeta(colors.base);
popDimmedThemeColor(colors.base);
};
}, [opened, colorScheme]);
}, [opened, colorScheme, themeId]);
useEffect(() => {
if (!opened || !contentRef.current) return;
+1 -1
View File
@@ -1,7 +1,7 @@
.drawerOverlay {
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.4);
background-color: var(--flxn-overlay);
z-index: 101;
}
+25 -23
View File
@@ -48,6 +48,8 @@ function SwipeableTabs({
}, [search?.tab, tabs, defaultTab]);
const [activeTab, setActiveTab] = useState(getActiveTabFromUrl);
const [stuck, setStuck] = useState(false);
const sentinelRef = useRef<HTMLDivElement>(null);
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
const controlsRefs = useRef<Record<number, HTMLSpanElement | null>>({});
const slideRefs = useRef<Record<number, HTMLDivElement | null>>({});
@@ -132,6 +134,20 @@ function SwipeableTabs({
};
}, [activeTab, updateHeight]);
// A 1px sentinel above the sticky strip: once it scrolls out of view the
// strip is pinned — flip data-stuck so themes can swap flat → docked chrome.
useEffect(() => {
const sentinel = sentinelRef.current;
if (!sentinel) return;
const observer = new IntersectionObserver(
([entry]) => setStuck(!entry.isIntersecting),
{ threshold: 0 }
);
observer.observe(sentinel);
return () => observer.disconnect();
}, []);
const setControlRef = useCallback(
(index: number) => (node: HTMLSpanElement | null) => {
controlsRefs.current[index] = node;
@@ -148,8 +164,11 @@ function SwipeableTabs({
return (
<Box style={{ touchAction: "pan-y" }}>
<div ref={sentinelRef} aria-hidden style={{ height: 1, marginTop: -1 }} />
<Box
ref={setRootRef}
className="flxn-tabs"
data-stuck={stuck || undefined}
pos="sticky"
top={0}
style={{
@@ -157,45 +176,28 @@ function SwipeableTabs({
paddingInline: "var(--mantine-spacing-md)",
marginBottom: mb !== undefined ? mb : "var(--mantine-spacing-md)",
zIndex: 100,
backgroundColor: "var(--mantine-color-body)",
}}
>
<FloatingIndicator
target={controlsRefs.current[activeTab]}
parent={rootRef}
styles={{
root: {
borderBottom: "2px solid var(--mantine-primary-color-filled)",
paddingInline: "0.5rem",
},
}}
className="flxn-tabs-indicator"
/>
{tabs.map((tab, index) => (
<UnstyledButton
key={`${tab.label}-${index}`}
onClick={() => changeTab(index)}
style={{
flex: 1,
padding: "var(--mantine-spacing-sm) var(--mantine-spacing-xs)",
textAlign: "center",
color:
activeTab === index
? "var(--mantine-primary-color-filled)"
: "var(--mantine-color-text)",
fontWeight: activeTab === index ? 600 : 400,
transition: "color 200ms ease, font-weight 200ms ease",
backgroundColor: "transparent",
border: "none",
borderRadius: 0,
}}
className="flxn-tab"
data-active={activeTab === index || undefined}
>
<Text
size="sm"
component="span"
style={{
// the FloatingIndicator sizes to this span — keep padding symmetric
display: "inline-block",
paddingInline: "0.5rem",
paddingBottom: "0.25rem",
paddingInline: "0.875rem",
paddingBlock: "0.375rem",
}}
ref={setControlRef(index)}
>
+3 -3
View File
@@ -62,7 +62,7 @@ const TeamAvatar = ({
top: -crownSize * 1.1,
left: "45%",
transform: "translateX(-50%)",
color: "gold",
color: "var(--flxn-gold)",
rotate: "-5deg",
}}
>
@@ -83,7 +83,7 @@ const TeamAvatar = ({
top: -crownSize * 0.95,
left: "65%",
transform: "translateX(-50%)",
color: "gold",
color: "var(--flxn-gold)",
rotate: "10deg",
}}
>
@@ -116,7 +116,7 @@ const TeamAvatar = ({
top: -crownSize * 0.6,
left: -crownSize * 0.25,
transform: "rotate(-25deg)",
color: "gold",
color: "var(--flxn-gold)",
}}
>
<CrownIcon size={crownSize} weight="fill" />
+1
View File
@@ -15,6 +15,7 @@ interface AuthData {
metadata: {
accentColor: MantineColor;
colorScheme: MantineColorScheme;
theme?: string;
locale?: string;
};
roles: string[];
@@ -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;
+45
View File
@@ -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"
+9 -18
View File
@@ -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}>
+19 -6
View File
@@ -159,16 +159,29 @@ export function useServerEvents() {
logger.error("SSE connection error", error);
disconnect();
// The stream is auth-guarded, so an error is often the server rejecting
// an expired session rather than a network blip. Try to refresh; if the
// session is genuinely gone, stop retrying so we don't hammer the
// endpoint forever — a real re-login (or a wake event) will reconnect.
let sessionValid = true;
try {
const { attemptRefreshingSession } = await import('supertokens-web-js/recipe/session');
sessionValid = await attemptRefreshingSession();
} catch {
// Refresh call itself failed (likely offline) — treat as transient.
}
if (disposed) return;
if (!sessionValid) {
logger.warn("SSE stopped: session is no longer valid");
return;
}
retryCount += 1;
const delay = Math.min(1000 * Math.pow(1.5, retryCount - 1), 15000);
logger.info(`SSE reconnection attempt ${retryCount} in ${Math.round(delay)}ms`);
try {
const { attemptRefreshingSession } = await import('supertokens-web-js/recipe/session');
await attemptRefreshingSession();
} catch {
}
reconnect(delay);
};
};
+48
View File
@@ -0,0 +1,48 @@
import { createTheme } from "@mantine/core";
const commonInputStyles = {
label: {
padding: 5,
},
root: {
margin: "0",
},
};
// Shared by MantineProvider and the theme-picker preview iframes.
export const baseTheme = createTheme({
defaultRadius: "sm",
respectReducedMotion: true,
fontFamily: '"Inter", sans-serif',
headings: { fontFamily: '"League Spartan", sans-serif' },
activeClassName: "flxn-press",
components: {
TextInput: {
styles: commonInputStyles,
},
DateTimePicker: {
styles: commonInputStyles,
},
Input: {
styles: commonInputStyles,
},
Select: {
styles: commonInputStyles,
},
Autocomplete: {
styles: commonInputStyles,
},
Card: {
defaultProps: {
shadow: "xs",
},
},
Title: {
styles: {
root: {
letterSpacing: "-0.01em",
},
},
},
},
});
+29 -7
View File
@@ -1,18 +1,40 @@
import { useAuth } from "@/contexts/auth-context";
import { useMantineColorScheme } from "@mantine/core";
import { useEffect } from "react";
import { CHROME_COLORS, setThemeColorMeta } from "./theme-colors";
import { useEffect, useRef } from "react";
import { getTheme } from "@/themes/registry";
import { resolveChromeColors } from "@/themes/chrome";
import { setBaseThemeColor } from "./theme-colors";
const ColorSchemeProvider = ({ children }: { children: React.ReactNode }) => {
const {
metadata: { colorScheme },
metadata: { colorScheme, theme },
} = useAuth();
const { setColorScheme } = useMantineColorScheme();
const { colorScheme: currentScheme, setColorScheme } =
useMantineColorScheme();
// Mantine recreates setColorScheme every render; calling it redundantly
// triggers its global transition-suppression flash, so ref it and only
// call on an actual value change.
const setColorSchemeRef = useRef(setColorScheme);
setColorSchemeRef.current = setColorScheme;
const manifest = getTheme(theme);
const forcedScheme =
manifest.colorSchemes === "both" ? undefined : manifest.colorSchemes;
useEffect(() => {
const chrome = resolveChromeColors(manifest);
if (forcedScheme) {
setBaseThemeColor(chrome[forcedScheme].base);
return;
}
if (!colorScheme) return;
setColorScheme(colorScheme);
if (colorScheme !== currentScheme) {
setColorSchemeRef.current(colorScheme);
}
const media = window.matchMedia("(prefers-color-scheme: dark)");
const applyThemeColor = () => {
@@ -22,7 +44,7 @@ const ColorSchemeProvider = ({ children }: { children: React.ReactNode }) => {
? "dark"
: "light"
: colorScheme;
setThemeColorMeta(CHROME_COLORS[resolved].base);
setBaseThemeColor(chrome[resolved].base);
};
applyThemeColor();
@@ -31,7 +53,7 @@ const ColorSchemeProvider = ({ children }: { children: React.ReactNode }) => {
media.addEventListener("change", applyThemeColor);
return () => media.removeEventListener("change", applyThemeColor);
}
}, [colorScheme, setColorScheme]);
}, [colorScheme, currentScheme, manifest, forcedScheme]);
return children;
};
+18 -52
View File
@@ -1,12 +1,16 @@
import { useAuth } from "@/contexts/auth-context";
import {
createTheme,
mergeThemeOverrides,
MantineProvider as MantineProviderCore,
} from "@mantine/core";
import { baseTheme } from "./base-theme";
import { DatesProvider } from "@mantine/dates";
import { useLingui } from "@lingui/react";
import type { AppLocale } from "@/lib/i18n";
import ColorSchemeProvider from "./color-scheme-provider";
import ThemeProvider from "@/themes/theme-provider";
import { DEFAULT_THEME_ID, getTheme } from "@/themes/registry";
import "@/themes/styles";
import { useState, useEffect, useMemo } from "react";
import "dayjs/locale/es-mx";
import "dayjs/locale/de";
@@ -21,15 +25,6 @@ const DAYJS_LOCALE: Record<AppLocale, string> = {
ja: "ja",
};
const commonInputStyles = {
label: {
padding: 5,
},
root: {
margin: "0",
},
};
const pressFeedbackCss = `
.flxn-press {
transition: transform 120ms cubic-bezier(0.32, 0.72, 0, 1);
@@ -46,43 +41,6 @@ const pressFeedbackCss = `
}
`;
const theme = createTheme({
defaultRadius: "sm",
respectReducedMotion: true,
fontFamily: '"Inter", sans-serif',
headings: { fontFamily: '"League Spartan", sans-serif' },
activeClassName: "flxn-press",
components: {
TextInput: {
styles: commonInputStyles,
},
DateTimePicker: {
styles: commonInputStyles,
},
Input: {
styles: commonInputStyles,
},
Select: {
styles: commonInputStyles,
},
Autocomplete: {
styles: commonInputStyles,
},
Card: {
defaultProps: {
shadow: "xs",
},
},
Title: {
styles: {
root: {
letterSpacing: "-0.01em",
},
},
},
},
});
const MantineProvider = ({ children }: { children: React.ReactNode }) => {
const { metadata } = useAuth();
const { i18n } = useLingui();
@@ -93,17 +51,23 @@ const MantineProvider = ({ children }: { children: React.ReactNode }) => {
}, []);
const colorScheme = isHydrated ? metadata.colorScheme || "auto" : "auto";
const manifest = getTheme(isHydrated ? metadata.theme : DEFAULT_THEME_ID);
const primaryColor = isHydrated ? metadata.accentColor || "blue" : "blue";
const themeWithPrimaryColor = useMemo(
() => ({ ...theme, primaryColor }),
[primaryColor]
const theme = useMemo(
() =>
mergeThemeOverrides(baseTheme, manifest.mantine ?? {}, { primaryColor }),
[manifest, primaryColor]
);
const forceColorScheme =
manifest.colorSchemes === "both" ? undefined : manifest.colorSchemes;
return (
<MantineProviderCore
defaultColorScheme={colorScheme}
theme={themeWithPrimaryColor}
forceColorScheme={forceColorScheme}
theme={theme}
>
<style href="flxn-press-feedback" precedence="medium">
{pressFeedbackCss}
@@ -113,7 +77,9 @@ const MantineProvider = ({ children }: { children: React.ReactNode }) => {
locale: DAYJS_LOCALE[i18n.locale as AppLocale] ?? "en",
}}
>
<ColorSchemeProvider>{children}</ColorSchemeProvider>
<ThemeProvider manifest={manifest} ready={isHydrated}>
<ColorSchemeProvider>{children}</ColorSchemeProvider>
</ThemeProvider>
</DatesProvider>
</MantineProviderCore>
);
+18
View File
@@ -8,6 +8,24 @@ export const CHROME_COLORS: Record<
dark: { base: "#242424", dimmed: "#161616" },
};
// theme-color meta ownership: while a drawer holds a dimmed color, base
// writes (from scheme/theme changes) must not overwrite it.
let dimmedDepth = 0;
export function setBaseThemeColor(color: string): void {
if (dimmedDepth === 0) setThemeColorMeta(color);
}
export function pushDimmedThemeColor(color: string): void {
dimmedDepth++;
setThemeColorMeta(color);
}
export function popDimmedThemeColor(base: string): void {
dimmedDepth = Math.max(0, dimmedDepth - 1);
if (dimmedDepth === 0) setThemeColorMeta(base);
}
export function setThemeColorMeta(color: string): void {
if (typeof document === "undefined") return;
+2 -2
View File
@@ -21,7 +21,7 @@ function success(toast: Omit<ToastProps, "id"> | string) {
const config = typeof toast === "string" ? { description: toast } : toast;
return makeToast({
...config,
icon: <ShieldCheckIcon color="lightgreen" size={48} weight="fill" />,
icon: <ShieldCheckIcon color="var(--flxn-toast-success)" size={48} weight="fill" />,
});
}
@@ -29,7 +29,7 @@ function error(toast: Omit<ToastProps, "id"> | string) {
const config = typeof toast === "string" ? { description: toast } : toast;
return makeToast({
...config,
icon: <WarningCircleIcon color="lightcoral" size={48} weight="fill" />,
icon: <WarningCircleIcon color="var(--flxn-toast-error)" size={48} weight="fill" />,
});
}
+106 -33
View File
@@ -13,7 +13,7 @@ msgstr ""
"Language-Team: \n"
"Plural-Forms: \n"
#: src/features/matches/components/match-card.tsx:77
#: src/features/matches/components/match-card.tsx:78
msgid " (Losers)"
msgstr " (Verlierer)"
@@ -140,7 +140,7 @@ msgstr "{0}ms"
msgid "{count, plural, one {Players (#)} other {Players (#)}}"
msgstr "{count, plural, one {Spieler (#)} other {Spieler (#)}}"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:130
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:134
#: src/features/tournaments/components/upcoming-tournament/team-list-button.tsx:24
msgid "{count, plural, one {Teams (#)} other {Teams (#)}}"
msgstr "{count, plural, one {Teams (#)} other {Teams (#)}}"
@@ -201,7 +201,7 @@ msgstr "{size}-Team-K.-o.-Runde"
msgid "* Confidence penalty applied for players with <15 matches"
msgstr "* Vertrauensabschlag für Spieler mit <15 Spielen"
#: src/features/bracket/components/match-card.tsx:320
#: src/features/bracket/components/match-card.tsx:324
#: src/features/predictions/components/prediction-match-card.tsx:108
msgid "* If necessary"
msgstr "* Falls nötig"
@@ -327,7 +327,7 @@ msgstr "1 s"
msgid "2. Earlier submission"
msgstr "2. Frühere Abgabe"
#: src/features/tournaments/components/podium.tsx:66
#: src/features/tournaments/components/podium.tsx:59
msgid "2nd place"
msgstr "2. Platz"
@@ -335,7 +335,7 @@ msgstr "2. Platz"
msgid "30s"
msgstr "30 s"
#: src/features/tournaments/components/podium.tsx:76
#: src/features/tournaments/components/podium.tsx:68
msgid "3rd place"
msgstr "3. Platz"
@@ -406,6 +406,10 @@ msgstr "Team hinzufügen"
msgid "Admin"
msgstr "Admin"
#: src/themes/aero/manifest.ts:117
msgid "Aero"
msgstr "Aero"
#: src/app/routes/_authed/stats.tsx:59
#: src/features/activities/components/activities-table.tsx:328
#: src/features/players/components/profile/index.tsx:28
@@ -436,11 +440,11 @@ msgstr "Beim Senden eines Einmal-Codes ist ein unerwarteter Fehler aufgetreten.
msgid "An unexpected error occurred. Please try again."
msgstr "Ein unerwarteter Fehler ist aufgetreten. Bitte versuche es erneut."
#: src/features/bracket/components/match-card.tsx:336
#: src/features/bracket/components/match-card.tsx:340
msgid "Announce matchup"
msgstr "Matchup ansagen"
#: src/app/routes/_authed/settings.tsx:34
#: src/app/routes/_authed/settings.tsx:35
msgid "Appearance"
msgstr "Erscheinungsbild"
@@ -452,6 +456,10 @@ msgstr "Bist du sicher, dass du dich von diesem Turnier abmelden möchtest? Du k
msgid "Arguments"
msgstr "Argumente"
#: src/themes/deco/manifest.ts:6
msgid "Art Deco"
msgstr "Art déco"
#: src/features/tournaments/components/manage-tournament.tsx:69
msgid "Assign Partners"
msgstr "Partner zuweisen"
@@ -554,12 +562,20 @@ msgstr "Abzeichentyp"
msgid "Badges"
msgstr "Abzeichen"
#: src/themes/bauhaus/manifest.ts:6
msgid "Bauhaus"
msgstr "Bauhaus"
#: src/themes/circus/manifest.ts:141
msgid "Big Top"
msgstr "Zirkus"
#: src/features/bracket/components/match-form.tsx:43
#: src/features/bracket/components/match-form.tsx:60
msgid "Both teams cannot have 10 cups"
msgstr "Beide Teams können nicht 10 Becher haben"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:102
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:106
#: src/features/tournaments/components/upcoming-tournament/index.tsx:150
msgid "Bracket"
msgstr "Turnierbaum"
@@ -573,6 +589,10 @@ msgstr "Turnierbaum-Vorschau"
msgid "Bracket Reset"
msgstr "Bracket-Reset"
#: src/themes/brutal/manifest.ts:6
msgid "Brutal"
msgstr "Brutal"
#: src/features/players/components/player-stats-table.tsx:139
#: src/features/tournaments/components/tournament-stats.tsx:152
msgid "CA"
@@ -605,12 +625,16 @@ msgstr "Teams können nicht mit einer ungeraden Anzahl an Spielern erstellt werd
msgid "Capture current state"
msgstr "Aktuellen Status erfassen"
#: src/themes/cassette/manifest.ts:145
msgid "Cassette Futurism"
msgstr "Retro-Tech"
#: src/features/players/components/player-stats-table.tsx:138
#: src/features/tournaments/components/tournament-stats.tsx:144
msgid "CF"
msgstr "CF"
#: src/features/tournaments/components/podium.tsx:56
#: src/features/tournaments/components/podium.tsx:50
msgid "Champions"
msgstr "Champions"
@@ -623,17 +647,21 @@ msgstr "Logo ändern"
msgid "Choose a player"
msgstr "Wähle einen Spieler"
#: src/themes/clay/manifest.ts:144
msgid "Claymation"
msgstr "Knete"
#: src/features/players/components/league-head-to-head.tsx:99
msgid "Clear selected player"
msgstr "Ausgewählten Spieler entfernen"
#: src/components/sheet/modal.tsx:35
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:165
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:169
#: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:36
msgid "Close"
msgstr "Schließen"
#: src/components/avatar.tsx:107
#: src/components/avatar.tsx:127
msgid "Close image preview"
msgstr "Bildvorschau schließen"
@@ -785,10 +813,22 @@ msgstr "Benutzerdefinierter Hex-Code"
msgid "Dark"
msgstr "Dunkel"
#: src/themes/gothic/manifest.ts:6
msgid "Dark Academia"
msgstr "Dark Academia"
#: src/features/activities/components/activities-table.tsx:356
msgid "Date"
msgstr "Datum"
#: src/themes/ocean/manifest.ts:117
msgid "Deep Sea"
msgstr "Tiefsee"
#: src/themes/default/manifest.ts:6
msgid "Default"
msgstr "Standard"
#: src/features/tournaments/components/tournament-form.tsx:150
msgid "Description"
msgstr "Beschreibung"
@@ -833,16 +873,16 @@ msgstr "Angemeldete Teams bearbeiten"
msgid "Edit Enrollments"
msgstr "Anmeldungen bearbeiten"
#: src/features/bracket/components/match-card.tsx:388
#: src/features/bracket/components/match-card.tsx:392
msgid "Edit Match"
msgstr "Match bearbeiten"
#: src/features/bracket/components/match-card.tsx:376
#: src/features/bracket/components/match-card.tsx:380
#: src/features/tournaments/components/group-match-card.tsx:187
msgid "Edit match score"
msgstr "Spielstand bearbeiten"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:110
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:114
msgid "Edit Prediction"
msgstr "Tipp bearbeiten"
@@ -914,7 +954,7 @@ msgid "Enrolled Players"
msgstr "Angemeldete Spieler"
#: src/features/tournaments/components/edit-enrolled-teams.tsx:151
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:141
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:145
#: src/features/tournaments/components/upcoming-tournament/team-list-button.tsx:31
msgid "Enrolled Teams"
msgstr "Angemeldete Teams"
@@ -1104,6 +1144,10 @@ msgstr "Zurück"
msgid "Go Back"
msgstr "Zurück"
#: src/themes/seventies/manifest.ts:6
msgid "Groovy"
msgstr "Groovy"
#. placeholder {0}: group.groupName
#. placeholder {0}: group.name
#: src/features/tournaments/components/group-preview.tsx:24
@@ -1138,7 +1182,7 @@ msgstr "Gruppenphase erfolgreich erstellt!"
msgid "Group Stage Setup"
msgstr "Gruppenphase einrichten"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:94
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:98
msgid "Groups"
msgstr "Gruppen"
@@ -1154,8 +1198,8 @@ msgstr "Hast du schon mal mitgespielt?"
#: src/app/routes/_authed/stats.tsx:47
#: src/features/bracket/components/match-dock.tsx:173
#: src/features/bracket/components/match-dock.tsx:215
#: src/features/matches/components/match-card.tsx:83
#: src/features/matches/components/match-card.tsx:231
#: src/features/matches/components/match-card.tsx:84
#: src/features/matches/components/match-card.tsx:228
#: src/features/players/components/league-head-to-head.tsx:183
#: src/features/players/components/profile/header.tsx:90
msgid "Head to Head"
@@ -1339,7 +1383,7 @@ msgstr "S"
msgid "Mainline"
msgstr "Mainline"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:110
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:114
msgid "Make Prediction"
msgstr "Tipp abgeben"
@@ -1347,7 +1391,7 @@ msgstr "Tipp abgeben"
msgid "Make Your Prediction"
msgstr "Gib deinen Tipp ab"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:85
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:89
#: src/features/tournaments/components/upcoming-tournament/index.tsx:143
msgid "Manage"
msgstr "Verwalten"
@@ -1392,7 +1436,7 @@ msgstr "Match {0} · Runde {1} (Verlierer)"
#. placeholder {0}: match.order
#. placeholder {1}: match.home!.name
#. placeholder {2}: match.away!.name
#: src/features/bracket/components/match-card.tsx:288
#: src/features/bracket/components/match-card.tsx:292
msgid "Match {0}: {1} vs {2}"
msgstr "Match {0}: {1} vs {2}"
@@ -1415,7 +1459,7 @@ msgid "Match started!"
msgstr "Match gestartet!"
#: src/features/players/components/player-stats-table.tsx:366
#: src/features/players/components/profile/index.tsx:73
#: src/features/players/components/profile/index.tsx:78
#: src/features/players/components/profile/skeleton.tsx:70
#: src/features/teams/components/team-profile/index.tsx:44
#: src/features/teams/components/team-profile/skeleton.tsx:75
@@ -1489,6 +1533,10 @@ msgstr "Nerd"
msgid "Network error. Please check your connection and try again."
msgstr "Netzwerkfehler. Bitte überprüfe deine Verbindung und versuche es erneut."
#: src/themes/neumorph/manifest.ts:6
msgid "Neumorphic"
msgstr "Neumorph"
#: src/features/players/components/players-activity-table.tsx:27
msgid "Never"
msgstr "Nie"
@@ -1834,7 +1882,7 @@ msgstr "Punktevergabe für Tipps:"
#: src/app/routes/_authed/tournaments/$id.predictions.tsx:23
#: src/features/predictions/components/prediction-leaderboard.tsx:147
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:119
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:123
msgid "Predictions"
msgstr "Tipps"
@@ -1867,6 +1915,10 @@ msgstr "PKT"
msgid "Q"
msgstr "Q"
#: src/themes/quest/manifest.ts:6
msgid "Quest Log"
msgstr "Questlog"
#: src/app/routes/_authed/admin/tournaments/$id/assign-partners.tsx:167
msgid "Re-roll"
msgstr "Neu würfeln"
@@ -1923,11 +1975,11 @@ msgid "rofl"
msgstr "rofl"
#. placeholder {0}: match.round + 1
#: src/features/matches/components/match-card.tsx:76
#: src/features/matches/components/match-card.tsx:77
msgid "Round {0}"
msgstr "Runde {0}"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:138
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:142
#: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:23
msgid "Rules"
msgstr "Regeln"
@@ -2099,7 +2151,7 @@ msgstr "Serverfunktionen"
msgid "serverFn name"
msgstr "serverFn-Name"
#: src/app/routes/_authed/settings.tsx:15
#: src/app/routes/_authed/settings.tsx:16
#: src/features/core/components/settings-button.tsx:19
msgid "Settings"
msgstr "Einstellungen"
@@ -2118,10 +2170,18 @@ msgstr "{0} von {1} Spielern angezeigt"
msgid "Shuffle Groups"
msgstr "Gruppen mischen"
#: src/app/routes/_authed/settings.tsx:42
#: src/app/routes/_authed/settings.tsx:44
msgid "Sign Out"
msgstr "Abmelden"
#: src/themes/sketch/manifest.ts:6
msgid "Sketchbook"
msgstr "Skizzenbuch"
#: src/themes/skeuo/manifest.ts:6
msgid "Skeuomorphic"
msgstr "Skeuomorph"
#: src/components/DefaultCatchBoundary.tsx:89
msgid "Something went wrong"
msgstr "Etwas ist schiefgelaufen"
@@ -2188,7 +2248,7 @@ msgstr "Start"
msgid "Start Date"
msgstr "Startdatum"
#: src/features/bracket/components/match-card.tsx:354
#: src/features/bracket/components/match-card.tsx:358
#: src/features/tournaments/components/group-match-card.tsx:168
msgid "Start match"
msgstr "Match starten"
@@ -2316,11 +2376,11 @@ msgstr "Team aktualisiert, aber Logo-Upload fehlgeschlagen: {0}"
msgid "Team updated successfully!"
msgstr "Team erfolgreich aktualisiert!"
#: src/features/players/components/profile/index.tsx:78
#: src/features/players/components/profile/index.tsx:83
#: src/features/players/components/profile/skeleton.tsx:75
#: src/features/tournaments/components/profile/index.tsx:32
#: src/features/tournaments/components/profile/skeleton.tsx:89
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:133
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:137
msgid "Teams"
msgstr "Teams"
@@ -2328,6 +2388,10 @@ msgstr "Teams"
msgid "Teams created successfully!"
msgstr "Teams erfolgreich erstellt!"
#: src/themes/terminal/manifest.ts:6
msgid "Terminal"
msgstr "Terminal"
#: src/app/routes/api/push/test.ts:29
msgid "Test notification — push is working on this device."
msgstr "Test-Benachrichtigung — Push funktioniert auf diesem Gerät."
@@ -2352,6 +2416,11 @@ msgstr "Das andere Team"
msgid "The requested resource was not found"
msgstr "Die angeforderte Ressource wurde nicht gefunden"
#: src/features/settings/components/theme-picker.tsx:40
#: src/features/settings/components/theme-picker.tsx:68
msgid "Theme"
msgstr "Design"
#: src/features/players/components/player-head-to-head-sheet.tsx:107
msgid "These players have not faced each other yet."
msgstr "Diese Spieler sind noch nicht gegeneinander angetreten."
@@ -2421,7 +2490,7 @@ msgstr "Turnier erfolgreich erstellt!"
msgid "Tournament not found"
msgstr "Turnier nicht gefunden"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:148
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:152
#: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:25
msgid "Tournament Rules"
msgstr "Turnierregeln"
@@ -2512,7 +2581,7 @@ msgstr "User-Agent"
msgid "versus"
msgstr "gegen"
#: src/features/players/components/profile/index.tsx:61
#: src/features/players/components/profile/index.tsx:66
msgid "View all badges"
msgstr "Alle Abzeichen ansehen"
@@ -2529,7 +2598,7 @@ msgid "View Groups"
msgstr "Gruppen ansehen"
#: src/features/bracket/components/match-dock.tsx:178
#: src/features/matches/components/match-card.tsx:88
#: src/features/matches/components/match-card.tsx:89
msgid "View head-to-head"
msgstr "Direktvergleich ansehen"
@@ -2537,6 +2606,10 @@ msgstr "Direktvergleich ansehen"
msgid "View Predictions"
msgstr "Tipps ansehen"
#: src/themes/wireframe/manifest.ts:118
msgid "Viewport"
msgstr "Viewport"
#: src/app/routes/api/spotify/playback.ts:70
msgid "volumePercent must be a number"
msgstr "volumePercent muss eine Zahl sein"
+106 -33
View File
@@ -13,7 +13,7 @@ msgstr ""
"Language-Team: \n"
"Plural-Forms: \n"
#: src/features/matches/components/match-card.tsx:77
#: src/features/matches/components/match-card.tsx:78
msgid " (Losers)"
msgstr " (Losers)"
@@ -140,7 +140,7 @@ msgstr "{0}ms"
msgid "{count, plural, one {Players (#)} other {Players (#)}}"
msgstr "{count, plural, one {Players (#)} other {Players (#)}}"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:130
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:134
#: src/features/tournaments/components/upcoming-tournament/team-list-button.tsx:24
msgid "{count, plural, one {Teams (#)} other {Teams (#)}}"
msgstr "{count, plural, one {Teams (#)} other {Teams (#)}}"
@@ -201,7 +201,7 @@ msgstr "{size}-team knockout"
msgid "* Confidence penalty applied for players with <15 matches"
msgstr "* Confidence penalty applied for players with <15 matches"
#: src/features/bracket/components/match-card.tsx:320
#: src/features/bracket/components/match-card.tsx:324
#: src/features/predictions/components/prediction-match-card.tsx:108
msgid "* If necessary"
msgstr "* If necessary"
@@ -327,7 +327,7 @@ msgstr "1s"
msgid "2. Earlier submission"
msgstr "2. Earlier submission"
#: src/features/tournaments/components/podium.tsx:66
#: src/features/tournaments/components/podium.tsx:59
msgid "2nd place"
msgstr "2nd place"
@@ -335,7 +335,7 @@ msgstr "2nd place"
msgid "30s"
msgstr "30s"
#: src/features/tournaments/components/podium.tsx:76
#: src/features/tournaments/components/podium.tsx:68
msgid "3rd place"
msgstr "3rd place"
@@ -406,6 +406,10 @@ msgstr "Add Team"
msgid "Admin"
msgstr "Admin"
#: src/themes/aero/manifest.ts:117
msgid "Aero"
msgstr "Aero"
#: src/app/routes/_authed/stats.tsx:59
#: src/features/activities/components/activities-table.tsx:328
#: src/features/players/components/profile/index.tsx:28
@@ -436,11 +440,11 @@ msgstr "An unexpected error occurred when trying to send a one-time passcode. Pl
msgid "An unexpected error occurred. Please try again."
msgstr "An unexpected error occurred. Please try again."
#: src/features/bracket/components/match-card.tsx:336
#: src/features/bracket/components/match-card.tsx:340
msgid "Announce matchup"
msgstr "Announce matchup"
#: src/app/routes/_authed/settings.tsx:34
#: src/app/routes/_authed/settings.tsx:35
msgid "Appearance"
msgstr "Appearance"
@@ -452,6 +456,10 @@ msgstr "Are you sure you want to unenroll from this tournament? You can enroll a
msgid "Arguments"
msgstr "Arguments"
#: src/themes/deco/manifest.ts:6
msgid "Art Deco"
msgstr "Art Deco"
#: src/features/tournaments/components/manage-tournament.tsx:69
msgid "Assign Partners"
msgstr "Assign Partners"
@@ -554,12 +562,20 @@ msgstr "Badge Type"
msgid "Badges"
msgstr "Badges"
#: src/themes/bauhaus/manifest.ts:6
msgid "Bauhaus"
msgstr "Bauhaus"
#: src/themes/circus/manifest.ts:141
msgid "Big Top"
msgstr "Big Top"
#: src/features/bracket/components/match-form.tsx:43
#: src/features/bracket/components/match-form.tsx:60
msgid "Both teams cannot have 10 cups"
msgstr "Both teams cannot have 10 cups"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:102
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:106
#: src/features/tournaments/components/upcoming-tournament/index.tsx:150
msgid "Bracket"
msgstr "Bracket"
@@ -573,6 +589,10 @@ msgstr "Bracket Preview"
msgid "Bracket Reset"
msgstr "Bracket Reset"
#: src/themes/brutal/manifest.ts:6
msgid "Brutal"
msgstr "Brutal"
#: src/features/players/components/player-stats-table.tsx:139
#: src/features/tournaments/components/tournament-stats.tsx:152
msgid "CA"
@@ -605,12 +625,16 @@ msgstr "Cannot create teams with an odd number of players. Please have one playe
msgid "Capture current state"
msgstr "Capture current state"
#: src/themes/cassette/manifest.ts:145
msgid "Cassette Futurism"
msgstr "Cassette Futurism"
#: src/features/players/components/player-stats-table.tsx:138
#: src/features/tournaments/components/tournament-stats.tsx:144
msgid "CF"
msgstr "CF"
#: src/features/tournaments/components/podium.tsx:56
#: src/features/tournaments/components/podium.tsx:50
msgid "Champions"
msgstr "Champions"
@@ -623,17 +647,21 @@ msgstr "Change Logo"
msgid "Choose a player"
msgstr "Choose a player"
#: src/themes/clay/manifest.ts:144
msgid "Claymation"
msgstr "Claymation"
#: src/features/players/components/league-head-to-head.tsx:99
msgid "Clear selected player"
msgstr "Clear selected player"
#: src/components/sheet/modal.tsx:35
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:165
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:169
#: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:36
msgid "Close"
msgstr "Close"
#: src/components/avatar.tsx:107
#: src/components/avatar.tsx:127
msgid "Close image preview"
msgstr "Close image preview"
@@ -785,10 +813,22 @@ msgstr "Custom Hex Code"
msgid "Dark"
msgstr "Dark"
#: src/themes/gothic/manifest.ts:6
msgid "Dark Academia"
msgstr "Dark Academia"
#: src/features/activities/components/activities-table.tsx:356
msgid "Date"
msgstr "Date"
#: src/themes/ocean/manifest.ts:117
msgid "Deep Sea"
msgstr "Deep Sea"
#: src/themes/default/manifest.ts:6
msgid "Default"
msgstr "Default"
#: src/features/tournaments/components/tournament-form.tsx:150
msgid "Description"
msgstr "Description"
@@ -833,16 +873,16 @@ msgstr "Edit Enrolled Teams"
msgid "Edit Enrollments"
msgstr "Edit Enrollments"
#: src/features/bracket/components/match-card.tsx:388
#: src/features/bracket/components/match-card.tsx:392
msgid "Edit Match"
msgstr "Edit Match"
#: src/features/bracket/components/match-card.tsx:376
#: src/features/bracket/components/match-card.tsx:380
#: src/features/tournaments/components/group-match-card.tsx:187
msgid "Edit match score"
msgstr "Edit match score"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:110
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:114
msgid "Edit Prediction"
msgstr "Edit Prediction"
@@ -914,7 +954,7 @@ msgid "Enrolled Players"
msgstr "Enrolled Players"
#: src/features/tournaments/components/edit-enrolled-teams.tsx:151
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:141
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:145
#: src/features/tournaments/components/upcoming-tournament/team-list-button.tsx:31
msgid "Enrolled Teams"
msgstr "Enrolled Teams"
@@ -1104,6 +1144,10 @@ msgstr "Go back"
msgid "Go Back"
msgstr "Go Back"
#: src/themes/seventies/manifest.ts:6
msgid "Groovy"
msgstr "Groovy"
#. placeholder {0}: group.groupName
#. placeholder {0}: group.name
#: src/features/tournaments/components/group-preview.tsx:24
@@ -1138,7 +1182,7 @@ msgstr "Group stage generated successfully!"
msgid "Group Stage Setup"
msgstr "Group Stage Setup"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:94
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:98
msgid "Groups"
msgstr "Groups"
@@ -1154,8 +1198,8 @@ msgstr "Have you played before?"
#: src/app/routes/_authed/stats.tsx:47
#: src/features/bracket/components/match-dock.tsx:173
#: src/features/bracket/components/match-dock.tsx:215
#: src/features/matches/components/match-card.tsx:83
#: src/features/matches/components/match-card.tsx:231
#: src/features/matches/components/match-card.tsx:84
#: src/features/matches/components/match-card.tsx:228
#: src/features/players/components/league-head-to-head.tsx:183
#: src/features/players/components/profile/header.tsx:90
msgid "Head to Head"
@@ -1339,7 +1383,7 @@ msgstr "M"
msgid "Mainline"
msgstr "Mainline"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:110
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:114
msgid "Make Prediction"
msgstr "Make Prediction"
@@ -1347,7 +1391,7 @@ msgstr "Make Prediction"
msgid "Make Your Prediction"
msgstr "Make Your Prediction"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:85
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:89
#: src/features/tournaments/components/upcoming-tournament/index.tsx:143
msgid "Manage"
msgstr "Manage"
@@ -1392,7 +1436,7 @@ msgstr "Match {0} · Round {1} (Losers)"
#. placeholder {0}: match.order
#. placeholder {1}: match.home!.name
#. placeholder {2}: match.away!.name
#: src/features/bracket/components/match-card.tsx:288
#: src/features/bracket/components/match-card.tsx:292
msgid "Match {0}: {1} vs {2}"
msgstr "Match {0}: {1} vs {2}"
@@ -1415,7 +1459,7 @@ msgid "Match started!"
msgstr "Match started!"
#: src/features/players/components/player-stats-table.tsx:366
#: src/features/players/components/profile/index.tsx:73
#: src/features/players/components/profile/index.tsx:78
#: src/features/players/components/profile/skeleton.tsx:70
#: src/features/teams/components/team-profile/index.tsx:44
#: src/features/teams/components/team-profile/skeleton.tsx:75
@@ -1489,6 +1533,10 @@ msgstr "nerd"
msgid "Network error. Please check your connection and try again."
msgstr "Network error. Please check your connection and try again."
#: src/themes/neumorph/manifest.ts:6
msgid "Neumorphic"
msgstr "Neumorphic"
#: src/features/players/components/players-activity-table.tsx:27
msgid "Never"
msgstr "Never"
@@ -1834,7 +1882,7 @@ msgstr "Prediction Scoring:"
#: src/app/routes/_authed/tournaments/$id.predictions.tsx:23
#: src/features/predictions/components/prediction-leaderboard.tsx:147
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:119
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:123
msgid "Predictions"
msgstr "Predictions"
@@ -1867,6 +1915,10 @@ msgstr "PTS"
msgid "Q"
msgstr "Q"
#: src/themes/quest/manifest.ts:6
msgid "Quest Log"
msgstr "Quest Log"
#: src/app/routes/_authed/admin/tournaments/$id/assign-partners.tsx:167
msgid "Re-roll"
msgstr "Re-roll"
@@ -1923,11 +1975,11 @@ msgid "rofl"
msgstr "rofl"
#. placeholder {0}: match.round + 1
#: src/features/matches/components/match-card.tsx:76
#: src/features/matches/components/match-card.tsx:77
msgid "Round {0}"
msgstr "Round {0}"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:138
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:142
#: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:23
msgid "Rules"
msgstr "Rules"
@@ -2099,7 +2151,7 @@ msgstr "Server Functions"
msgid "serverFn name"
msgstr "serverFn name"
#: src/app/routes/_authed/settings.tsx:15
#: src/app/routes/_authed/settings.tsx:16
#: src/features/core/components/settings-button.tsx:19
msgid "Settings"
msgstr "Settings"
@@ -2118,10 +2170,18 @@ msgstr "Showing {0} of {1} players"
msgid "Shuffle Groups"
msgstr "Shuffle Groups"
#: src/app/routes/_authed/settings.tsx:42
#: src/app/routes/_authed/settings.tsx:44
msgid "Sign Out"
msgstr "Sign Out"
#: src/themes/sketch/manifest.ts:6
msgid "Sketchbook"
msgstr "Sketchbook"
#: src/themes/skeuo/manifest.ts:6
msgid "Skeuomorphic"
msgstr "Skeuomorphic"
#: src/components/DefaultCatchBoundary.tsx:89
msgid "Something went wrong"
msgstr "Something went wrong"
@@ -2188,7 +2248,7 @@ msgstr "Start"
msgid "Start Date"
msgstr "Start Date"
#: src/features/bracket/components/match-card.tsx:354
#: src/features/bracket/components/match-card.tsx:358
#: src/features/tournaments/components/group-match-card.tsx:168
msgid "Start match"
msgstr "Start match"
@@ -2316,11 +2376,11 @@ msgstr "Team updated but logo upload failed: {0}"
msgid "Team updated successfully!"
msgstr "Team updated successfully!"
#: src/features/players/components/profile/index.tsx:78
#: src/features/players/components/profile/index.tsx:83
#: src/features/players/components/profile/skeleton.tsx:75
#: src/features/tournaments/components/profile/index.tsx:32
#: src/features/tournaments/components/profile/skeleton.tsx:89
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:133
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:137
msgid "Teams"
msgstr "Teams"
@@ -2328,6 +2388,10 @@ msgstr "Teams"
msgid "Teams created successfully!"
msgstr "Teams created successfully!"
#: src/themes/terminal/manifest.ts:6
msgid "Terminal"
msgstr "Terminal"
#: src/app/routes/api/push/test.ts:29
msgid "Test notification — push is working on this device."
msgstr "Test notification — push is working on this device."
@@ -2352,6 +2416,11 @@ msgstr "The other team"
msgid "The requested resource was not found"
msgstr "The requested resource was not found"
#: src/features/settings/components/theme-picker.tsx:40
#: src/features/settings/components/theme-picker.tsx:68
msgid "Theme"
msgstr "Theme"
#: src/features/players/components/player-head-to-head-sheet.tsx:107
msgid "These players have not faced each other yet."
msgstr "These players have not faced each other yet."
@@ -2421,7 +2490,7 @@ msgstr "Tournament created successfully!"
msgid "Tournament not found"
msgstr "Tournament not found"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:148
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:152
#: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:25
msgid "Tournament Rules"
msgstr "Tournament Rules"
@@ -2512,7 +2581,7 @@ msgstr "User Agent"
msgid "versus"
msgstr "versus"
#: src/features/players/components/profile/index.tsx:61
#: src/features/players/components/profile/index.tsx:66
msgid "View all badges"
msgstr "View all badges"
@@ -2529,7 +2598,7 @@ msgid "View Groups"
msgstr "View Groups"
#: src/features/bracket/components/match-dock.tsx:178
#: src/features/matches/components/match-card.tsx:88
#: src/features/matches/components/match-card.tsx:89
msgid "View head-to-head"
msgstr "View head-to-head"
@@ -2537,6 +2606,10 @@ msgstr "View head-to-head"
msgid "View Predictions"
msgstr "View Predictions"
#: src/themes/wireframe/manifest.ts:118
msgid "Viewport"
msgstr "Viewport"
#: src/app/routes/api/spotify/playback.ts:70
msgid "volumePercent must be a number"
msgstr "volumePercent must be a number"
+106 -33
View File
@@ -13,7 +13,7 @@ msgstr ""
"Language-Team: \n"
"Plural-Forms: \n"
#: src/features/matches/components/match-card.tsx:77
#: src/features/matches/components/match-card.tsx:78
msgid " (Losers)"
msgstr " (Perdedores)"
@@ -140,7 +140,7 @@ msgstr "{0}ms"
msgid "{count, plural, one {Players (#)} other {Players (#)}}"
msgstr "{count, plural, one {Jugadores (#)} other {Jugadores (#)}}"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:130
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:134
#: src/features/tournaments/components/upcoming-tournament/team-list-button.tsx:24
msgid "{count, plural, one {Teams (#)} other {Teams (#)}}"
msgstr "{count, plural, one {Equipos (#)} other {Equipos (#)}}"
@@ -201,7 +201,7 @@ msgstr "Eliminación directa de {size} equipos"
msgid "* Confidence penalty applied for players with <15 matches"
msgstr "* Se aplica una penalización de confianza a jugadores con menos de 15 partidos"
#: src/features/bracket/components/match-card.tsx:320
#: src/features/bracket/components/match-card.tsx:324
#: src/features/predictions/components/prediction-match-card.tsx:108
msgid "* If necessary"
msgstr "* Si es necesario"
@@ -327,7 +327,7 @@ msgstr "1s"
msgid "2. Earlier submission"
msgstr "2. Envío más temprano"
#: src/features/tournaments/components/podium.tsx:66
#: src/features/tournaments/components/podium.tsx:59
msgid "2nd place"
msgstr "2do lugar"
@@ -335,7 +335,7 @@ msgstr "2do lugar"
msgid "30s"
msgstr "30s"
#: src/features/tournaments/components/podium.tsx:76
#: src/features/tournaments/components/podium.tsx:68
msgid "3rd place"
msgstr "3er lugar"
@@ -406,6 +406,10 @@ msgstr "Agregar Equipo"
msgid "Admin"
msgstr "Admin"
#: src/themes/aero/manifest.ts:117
msgid "Aero"
msgstr "Aero"
#: src/app/routes/_authed/stats.tsx:59
#: src/features/activities/components/activities-table.tsx:328
#: src/features/players/components/profile/index.tsx:28
@@ -436,11 +440,11 @@ msgstr "Ocurrió un error inesperado al intentar enviar el código de un solo us
msgid "An unexpected error occurred. Please try again."
msgstr "Ocurrió un error inesperado. Intenta de nuevo."
#: src/features/bracket/components/match-card.tsx:336
#: src/features/bracket/components/match-card.tsx:340
msgid "Announce matchup"
msgstr "Anunciar enfrentamiento"
#: src/app/routes/_authed/settings.tsx:34
#: src/app/routes/_authed/settings.tsx:35
msgid "Appearance"
msgstr "Apariencia"
@@ -452,6 +456,10 @@ msgstr "¿Seguro que quieres darte de baja de este torneo? Puedes volver a inscr
msgid "Arguments"
msgstr "Argumentos"
#: src/themes/deco/manifest.ts:6
msgid "Art Deco"
msgstr "Art Déco"
#: src/features/tournaments/components/manage-tournament.tsx:69
msgid "Assign Partners"
msgstr "Asignar Compañeros"
@@ -554,12 +562,20 @@ msgstr "Tipo de insignia"
msgid "Badges"
msgstr "Insignias"
#: src/themes/bauhaus/manifest.ts:6
msgid "Bauhaus"
msgstr "Bauhaus"
#: src/themes/circus/manifest.ts:141
msgid "Big Top"
msgstr "Circo"
#: src/features/bracket/components/match-form.tsx:43
#: src/features/bracket/components/match-form.tsx:60
msgid "Both teams cannot have 10 cups"
msgstr "Los dos equipos no pueden tener 10 vasos"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:102
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:106
#: src/features/tournaments/components/upcoming-tournament/index.tsx:150
msgid "Bracket"
msgstr "Bracket"
@@ -573,6 +589,10 @@ msgstr "Vista previa del bracket"
msgid "Bracket Reset"
msgstr "Reinicio del bracket"
#: src/themes/brutal/manifest.ts:6
msgid "Brutal"
msgstr "Brutal"
#: src/features/players/components/player-stats-table.tsx:139
#: src/features/tournaments/components/tournament-stats.tsx:152
msgid "CA"
@@ -605,12 +625,16 @@ msgstr "No se pueden crear equipos con un número impar de jugadores. Pide que u
msgid "Capture current state"
msgstr "Capturar estado actual"
#: src/themes/cassette/manifest.ts:145
msgid "Cassette Futurism"
msgstr "Retro-Tec"
#: src/features/players/components/player-stats-table.tsx:138
#: src/features/tournaments/components/tournament-stats.tsx:144
msgid "CF"
msgstr "VF"
#: src/features/tournaments/components/podium.tsx:56
#: src/features/tournaments/components/podium.tsx:50
msgid "Champions"
msgstr "Campeones"
@@ -623,17 +647,21 @@ msgstr "Cambiar logo"
msgid "Choose a player"
msgstr "Elige un jugador"
#: src/themes/clay/manifest.ts:144
msgid "Claymation"
msgstr "Plastilina"
#: src/features/players/components/league-head-to-head.tsx:99
msgid "Clear selected player"
msgstr "Quitar jugador seleccionado"
#: src/components/sheet/modal.tsx:35
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:165
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:169
#: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:36
msgid "Close"
msgstr "Cerrar"
#: src/components/avatar.tsx:107
#: src/components/avatar.tsx:127
msgid "Close image preview"
msgstr "Cerrar vista previa de la imagen"
@@ -785,10 +813,22 @@ msgstr "Código hex personalizado"
msgid "Dark"
msgstr "Oscuro"
#: src/themes/gothic/manifest.ts:6
msgid "Dark Academia"
msgstr "Academia Oscura"
#: src/features/activities/components/activities-table.tsx:356
msgid "Date"
msgstr "Fecha"
#: src/themes/ocean/manifest.ts:117
msgid "Deep Sea"
msgstr "Mar Profundo"
#: src/themes/default/manifest.ts:6
msgid "Default"
msgstr "Predeterminado"
#: src/features/tournaments/components/tournament-form.tsx:150
msgid "Description"
msgstr "Descripción"
@@ -833,16 +873,16 @@ msgstr "Editar equipos inscritos"
msgid "Edit Enrollments"
msgstr "Editar inscripciones"
#: src/features/bracket/components/match-card.tsx:388
#: src/features/bracket/components/match-card.tsx:392
msgid "Edit Match"
msgstr "Editar partido"
#: src/features/bracket/components/match-card.tsx:376
#: src/features/bracket/components/match-card.tsx:380
#: src/features/tournaments/components/group-match-card.tsx:187
msgid "Edit match score"
msgstr "Editar marcador del partido"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:110
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:114
msgid "Edit Prediction"
msgstr "Editar predicción"
@@ -914,7 +954,7 @@ msgid "Enrolled Players"
msgstr "Jugadores inscritos"
#: src/features/tournaments/components/edit-enrolled-teams.tsx:151
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:141
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:145
#: src/features/tournaments/components/upcoming-tournament/team-list-button.tsx:31
msgid "Enrolled Teams"
msgstr "Equipos inscritos"
@@ -1104,6 +1144,10 @@ msgstr "Regresar"
msgid "Go Back"
msgstr "Regresar"
#: src/themes/seventies/manifest.ts:6
msgid "Groovy"
msgstr "Setentero"
#. placeholder {0}: group.groupName
#. placeholder {0}: group.name
#: src/features/tournaments/components/group-preview.tsx:24
@@ -1138,7 +1182,7 @@ msgstr "¡Fase de grupos generada con éxito!"
msgid "Group Stage Setup"
msgstr "Configuración de la fase de grupos"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:94
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:98
msgid "Groups"
msgstr "Grupos"
@@ -1154,8 +1198,8 @@ msgstr "¿Ya has jugado antes?"
#: src/app/routes/_authed/stats.tsx:47
#: src/features/bracket/components/match-dock.tsx:173
#: src/features/bracket/components/match-dock.tsx:215
#: src/features/matches/components/match-card.tsx:83
#: src/features/matches/components/match-card.tsx:231
#: src/features/matches/components/match-card.tsx:84
#: src/features/matches/components/match-card.tsx:228
#: src/features/players/components/league-head-to-head.tsx:183
#: src/features/players/components/profile/header.tsx:90
msgid "Head to Head"
@@ -1339,7 +1383,7 @@ msgstr "J"
msgid "Mainline"
msgstr "Mainline"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:110
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:114
msgid "Make Prediction"
msgstr "Hacer predicción"
@@ -1347,7 +1391,7 @@ msgstr "Hacer predicción"
msgid "Make Your Prediction"
msgstr "Haz tu predicción"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:85
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:89
#: src/features/tournaments/components/upcoming-tournament/index.tsx:143
msgid "Manage"
msgstr "Administrar"
@@ -1392,7 +1436,7 @@ msgstr "Partido {0} · Ronda {1} (Perdedores)"
#. placeholder {0}: match.order
#. placeholder {1}: match.home!.name
#. placeholder {2}: match.away!.name
#: src/features/bracket/components/match-card.tsx:288
#: src/features/bracket/components/match-card.tsx:292
msgid "Match {0}: {1} vs {2}"
msgstr "Partido {0}: {1} vs {2}"
@@ -1415,7 +1459,7 @@ msgid "Match started!"
msgstr "¡Empezó el partido!"
#: src/features/players/components/player-stats-table.tsx:366
#: src/features/players/components/profile/index.tsx:73
#: src/features/players/components/profile/index.tsx:78
#: src/features/players/components/profile/skeleton.tsx:70
#: src/features/teams/components/team-profile/index.tsx:44
#: src/features/teams/components/team-profile/skeleton.tsx:75
@@ -1489,6 +1533,10 @@ msgstr "nerd"
msgid "Network error. Please check your connection and try again."
msgstr "Error de red. Revisa tu conexión e intenta de nuevo."
#: src/themes/neumorph/manifest.ts:6
msgid "Neumorphic"
msgstr "Neumórfico"
#: src/features/players/components/players-activity-table.tsx:27
msgid "Never"
msgstr "Nunca"
@@ -1834,7 +1882,7 @@ msgstr "Puntuación de predicciones:"
#: src/app/routes/_authed/tournaments/$id.predictions.tsx:23
#: src/features/predictions/components/prediction-leaderboard.tsx:147
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:119
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:123
msgid "Predictions"
msgstr "Predicciones"
@@ -1867,6 +1915,10 @@ msgstr "PTS"
msgid "Q"
msgstr "C"
#: src/themes/quest/manifest.ts:6
msgid "Quest Log"
msgstr "Misiones"
#: src/app/routes/_authed/admin/tournaments/$id/assign-partners.tsx:167
msgid "Re-roll"
msgstr "Volver a sortear"
@@ -1923,11 +1975,11 @@ msgid "rofl"
msgstr "carcajada"
#. placeholder {0}: match.round + 1
#: src/features/matches/components/match-card.tsx:76
#: src/features/matches/components/match-card.tsx:77
msgid "Round {0}"
msgstr "Ronda {0}"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:138
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:142
#: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:23
msgid "Rules"
msgstr "Reglas"
@@ -2099,7 +2151,7 @@ msgstr "Funciones del servidor"
msgid "serverFn name"
msgstr "nombre de serverFn"
#: src/app/routes/_authed/settings.tsx:15
#: src/app/routes/_authed/settings.tsx:16
#: src/features/core/components/settings-button.tsx:19
msgid "Settings"
msgstr "Configuración"
@@ -2118,10 +2170,18 @@ msgstr "Mostrando {0} de {1} jugadores"
msgid "Shuffle Groups"
msgstr "Mezclar grupos"
#: src/app/routes/_authed/settings.tsx:42
#: src/app/routes/_authed/settings.tsx:44
msgid "Sign Out"
msgstr "Cerrar sesión"
#: src/themes/sketch/manifest.ts:6
msgid "Sketchbook"
msgstr "Cuaderno"
#: src/themes/skeuo/manifest.ts:6
msgid "Skeuomorphic"
msgstr "Esqueuomórfico"
#: src/components/DefaultCatchBoundary.tsx:89
msgid "Something went wrong"
msgstr "Algo salió mal"
@@ -2188,7 +2248,7 @@ msgstr "Inicio"
msgid "Start Date"
msgstr "Fecha de inicio"
#: src/features/bracket/components/match-card.tsx:354
#: src/features/bracket/components/match-card.tsx:358
#: src/features/tournaments/components/group-match-card.tsx:168
msgid "Start match"
msgstr "Iniciar partido"
@@ -2316,11 +2376,11 @@ msgstr "Equipo actualizado, pero falló la carga del logo: {0}"
msgid "Team updated successfully!"
msgstr "¡Equipo actualizado exitosamente!"
#: src/features/players/components/profile/index.tsx:78
#: src/features/players/components/profile/index.tsx:83
#: src/features/players/components/profile/skeleton.tsx:75
#: src/features/tournaments/components/profile/index.tsx:32
#: src/features/tournaments/components/profile/skeleton.tsx:89
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:133
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:137
msgid "Teams"
msgstr "Equipos"
@@ -2328,6 +2388,10 @@ msgstr "Equipos"
msgid "Teams created successfully!"
msgstr "¡Equipos creados exitosamente!"
#: src/themes/terminal/manifest.ts:6
msgid "Terminal"
msgstr "Terminal"
#: src/app/routes/api/push/test.ts:29
msgid "Test notification — push is working on this device."
msgstr "Notificación de prueba: las notificaciones push funcionan en este dispositivo."
@@ -2352,6 +2416,11 @@ msgstr "El otro equipo"
msgid "The requested resource was not found"
msgstr "No se encontró el recurso solicitado"
#: src/features/settings/components/theme-picker.tsx:40
#: src/features/settings/components/theme-picker.tsx:68
msgid "Theme"
msgstr "Tema"
#: src/features/players/components/player-head-to-head-sheet.tsx:107
msgid "These players have not faced each other yet."
msgstr "Estos jugadores aún no se han enfrentado."
@@ -2421,7 +2490,7 @@ msgstr "¡Torneo creado exitosamente!"
msgid "Tournament not found"
msgstr "Torneo no encontrado"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:148
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:152
#: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:25
msgid "Tournament Rules"
msgstr "Reglas del torneo"
@@ -2512,7 +2581,7 @@ msgstr "User Agent"
msgid "versus"
msgstr "contra"
#: src/features/players/components/profile/index.tsx:61
#: src/features/players/components/profile/index.tsx:66
msgid "View all badges"
msgstr "Ver todas las insignias"
@@ -2529,7 +2598,7 @@ msgid "View Groups"
msgstr "Ver grupos"
#: src/features/bracket/components/match-dock.tsx:178
#: src/features/matches/components/match-card.tsx:88
#: src/features/matches/components/match-card.tsx:89
msgid "View head-to-head"
msgstr "Ver cara a cara"
@@ -2537,6 +2606,10 @@ msgstr "Ver cara a cara"
msgid "View Predictions"
msgstr "Ver predicciones"
#: src/themes/wireframe/manifest.ts:118
msgid "Viewport"
msgstr "Viewport"
#: src/app/routes/api/spotify/playback.ts:70
msgid "volumePercent must be a number"
msgstr "volumePercent debe ser un número"
+106 -33
View File
@@ -13,7 +13,7 @@ msgstr ""
"Language-Team: \n"
"Plural-Forms: \n"
#: src/features/matches/components/match-card.tsx:77
#: src/features/matches/components/match-card.tsx:78
msgid " (Losers)"
msgstr " (敗者側)"
@@ -140,7 +140,7 @@ msgstr "{0}ms"
msgid "{count, plural, one {Players (#)} other {Players (#)}}"
msgstr "{count, plural, one {プレイヤー(#)} other {プレイヤー(#)}}"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:130
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:134
#: src/features/tournaments/components/upcoming-tournament/team-list-button.tsx:24
msgid "{count, plural, one {Teams (#)} other {Teams (#)}}"
msgstr "{count, plural, one {チーム(#)} other {チーム(#)}}"
@@ -201,7 +201,7 @@ msgstr "{size}チームノックアウト"
msgid "* Confidence penalty applied for players with <15 matches"
msgstr "※試合数が15未満のプレイヤーには信頼度ペナルティが適用されます"
#: src/features/bracket/components/match-card.tsx:320
#: src/features/bracket/components/match-card.tsx:324
#: src/features/predictions/components/prediction-match-card.tsx:108
msgid "* If necessary"
msgstr "※必要な場合"
@@ -327,7 +327,7 @@ msgstr "1秒"
msgid "2. Earlier submission"
msgstr "2. 早い提出"
#: src/features/tournaments/components/podium.tsx:66
#: src/features/tournaments/components/podium.tsx:59
msgid "2nd place"
msgstr "2位"
@@ -335,7 +335,7 @@ msgstr "2位"
msgid "30s"
msgstr "30秒"
#: src/features/tournaments/components/podium.tsx:76
#: src/features/tournaments/components/podium.tsx:68
msgid "3rd place"
msgstr "3位"
@@ -406,6 +406,10 @@ msgstr "チームを追加"
msgid "Admin"
msgstr "管理者"
#: src/themes/aero/manifest.ts:117
msgid "Aero"
msgstr "エアロ"
#: src/app/routes/_authed/stats.tsx:59
#: src/features/activities/components/activities-table.tsx:328
#: src/features/players/components/profile/index.tsx:28
@@ -436,11 +440,11 @@ msgstr "ワンタイムパスコードの送信中に予期しないエラーが
msgid "An unexpected error occurred. Please try again."
msgstr "予期しないエラーが発生しました。もう一度お試しください。"
#: src/features/bracket/components/match-card.tsx:336
#: src/features/bracket/components/match-card.tsx:340
msgid "Announce matchup"
msgstr "対戦カードを発表"
#: src/app/routes/_authed/settings.tsx:34
#: src/app/routes/_authed/settings.tsx:35
msgid "Appearance"
msgstr "外観"
@@ -452,6 +456,10 @@ msgstr "このトーナメントから登録を解除してもよろしいです
msgid "Arguments"
msgstr "引数"
#: src/themes/deco/manifest.ts:6
msgid "Art Deco"
msgstr "アールデコ"
#: src/features/tournaments/components/manage-tournament.tsx:69
msgid "Assign Partners"
msgstr "パートナーを割り当て"
@@ -554,12 +562,20 @@ msgstr "バッジの種類"
msgid "Badges"
msgstr "バッジ"
#: src/themes/bauhaus/manifest.ts:6
msgid "Bauhaus"
msgstr "バウハウス"
#: src/themes/circus/manifest.ts:141
msgid "Big Top"
msgstr "サーカス"
#: src/features/bracket/components/match-form.tsx:43
#: src/features/bracket/components/match-form.tsx:60
msgid "Both teams cannot have 10 cups"
msgstr "両チーム同時に10カップにはなりません"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:102
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:106
#: src/features/tournaments/components/upcoming-tournament/index.tsx:150
msgid "Bracket"
msgstr "ブラケット"
@@ -573,6 +589,10 @@ msgstr "ブラケットプレビュー"
msgid "Bracket Reset"
msgstr "ブラケットリセット"
#: src/themes/brutal/manifest.ts:6
msgid "Brutal"
msgstr "ブルータル"
#: src/features/players/components/player-stats-table.tsx:139
#: src/features/tournaments/components/tournament-stats.tsx:152
msgid "CA"
@@ -605,12 +625,16 @@ msgstr "参加者が奇数のためチームを作成できません。どなた
msgid "Capture current state"
msgstr "現在の状態をキャプチャ"
#: src/themes/cassette/manifest.ts:145
msgid "Cassette Futurism"
msgstr "レトロフューチャー"
#: src/features/players/components/player-stats-table.tsx:138
#: src/features/tournaments/components/tournament-stats.tsx:144
msgid "CF"
msgstr "CF"
#: src/features/tournaments/components/podium.tsx:56
#: src/features/tournaments/components/podium.tsx:50
msgid "Champions"
msgstr "チャンピオン"
@@ -623,17 +647,21 @@ msgstr "ロゴを変更"
msgid "Choose a player"
msgstr "プレイヤーを選択"
#: src/themes/clay/manifest.ts:144
msgid "Claymation"
msgstr "クレイ"
#: src/features/players/components/league-head-to-head.tsx:99
msgid "Clear selected player"
msgstr "選択したプレイヤーを解除"
#: src/components/sheet/modal.tsx:35
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:165
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:169
#: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:36
msgid "Close"
msgstr "閉じる"
#: src/components/avatar.tsx:107
#: src/components/avatar.tsx:127
msgid "Close image preview"
msgstr "画像プレビューを閉じる"
@@ -785,10 +813,22 @@ msgstr "カスタムHEXコード"
msgid "Dark"
msgstr "ダーク"
#: src/themes/gothic/manifest.ts:6
msgid "Dark Academia"
msgstr "ダークアカデミア"
#: src/features/activities/components/activities-table.tsx:356
msgid "Date"
msgstr "日付"
#: src/themes/ocean/manifest.ts:117
msgid "Deep Sea"
msgstr "深海"
#: src/themes/default/manifest.ts:6
msgid "Default"
msgstr "デフォルト"
#: src/features/tournaments/components/tournament-form.tsx:150
msgid "Description"
msgstr "説明"
@@ -833,16 +873,16 @@ msgstr "参加チームを編集"
msgid "Edit Enrollments"
msgstr "参加登録を編集"
#: src/features/bracket/components/match-card.tsx:388
#: src/features/bracket/components/match-card.tsx:392
msgid "Edit Match"
msgstr "試合を編集"
#: src/features/bracket/components/match-card.tsx:376
#: src/features/bracket/components/match-card.tsx:380
#: src/features/tournaments/components/group-match-card.tsx:187
msgid "Edit match score"
msgstr "試合スコアを編集"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:110
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:114
msgid "Edit Prediction"
msgstr "予想を編集"
@@ -914,7 +954,7 @@ msgid "Enrolled Players"
msgstr "登録済みプレイヤー"
#: src/features/tournaments/components/edit-enrolled-teams.tsx:151
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:141
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:145
#: src/features/tournaments/components/upcoming-tournament/team-list-button.tsx:31
msgid "Enrolled Teams"
msgstr "登録済みチーム"
@@ -1104,6 +1144,10 @@ msgstr "戻る"
msgid "Go Back"
msgstr "戻る"
#: src/themes/seventies/manifest.ts:6
msgid "Groovy"
msgstr "グルーヴィー"
#. placeholder {0}: group.groupName
#. placeholder {0}: group.name
#: src/features/tournaments/components/group-preview.tsx:24
@@ -1138,7 +1182,7 @@ msgstr "グループステージを生成しました!"
msgid "Group Stage Setup"
msgstr "グループステージ設定"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:94
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:98
msgid "Groups"
msgstr "グループ"
@@ -1154,8 +1198,8 @@ msgstr "参加したことはありますか?"
#: src/app/routes/_authed/stats.tsx:47
#: src/features/bracket/components/match-dock.tsx:173
#: src/features/bracket/components/match-dock.tsx:215
#: src/features/matches/components/match-card.tsx:83
#: src/features/matches/components/match-card.tsx:231
#: src/features/matches/components/match-card.tsx:84
#: src/features/matches/components/match-card.tsx:228
#: src/features/players/components/league-head-to-head.tsx:183
#: src/features/players/components/profile/header.tsx:90
msgid "Head to Head"
@@ -1339,7 +1383,7 @@ msgstr "M"
msgid "Mainline"
msgstr "メインライン"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:110
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:114
msgid "Make Prediction"
msgstr "予想する"
@@ -1347,7 +1391,7 @@ msgstr "予想する"
msgid "Make Your Prediction"
msgstr "予想を入力"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:85
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:89
#: src/features/tournaments/components/upcoming-tournament/index.tsx:143
msgid "Manage"
msgstr "管理"
@@ -1392,7 +1436,7 @@ msgstr "試合{0}・ラウンド{1}(敗者側)"
#. placeholder {0}: match.order
#. placeholder {1}: match.home!.name
#. placeholder {2}: match.away!.name
#: src/features/bracket/components/match-card.tsx:288
#: src/features/bracket/components/match-card.tsx:292
msgid "Match {0}: {1} vs {2}"
msgstr "試合{0}: {1} vs {2}"
@@ -1415,7 +1459,7 @@ msgid "Match started!"
msgstr "試合開始!"
#: src/features/players/components/player-stats-table.tsx:366
#: src/features/players/components/profile/index.tsx:73
#: src/features/players/components/profile/index.tsx:78
#: src/features/players/components/profile/skeleton.tsx:70
#: src/features/teams/components/team-profile/index.tsx:44
#: src/features/teams/components/team-profile/skeleton.tsx:75
@@ -1489,6 +1533,10 @@ msgstr "オタク"
msgid "Network error. Please check your connection and try again."
msgstr "ネットワークエラーです。接続を確認してもう一度お試しください。"
#: src/themes/neumorph/manifest.ts:6
msgid "Neumorphic"
msgstr "ネウモーフィズム"
#: src/features/players/components/players-activity-table.tsx:27
msgid "Never"
msgstr "なし"
@@ -1834,7 +1882,7 @@ msgstr "予想の得点方式:"
#: src/app/routes/_authed/tournaments/$id.predictions.tsx:23
#: src/features/predictions/components/prediction-leaderboard.tsx:147
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:119
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:123
msgid "Predictions"
msgstr "予想"
@@ -1867,6 +1915,10 @@ msgstr "PTS"
msgid "Q"
msgstr "Q"
#: src/themes/quest/manifest.ts:6
msgid "Quest Log"
msgstr "クエスト"
#: src/app/routes/_authed/admin/tournaments/$id/assign-partners.tsx:167
msgid "Re-roll"
msgstr "再抽選"
@@ -1923,11 +1975,11 @@ msgid "rofl"
msgstr "大笑い"
#. placeholder {0}: match.round + 1
#: src/features/matches/components/match-card.tsx:76
#: src/features/matches/components/match-card.tsx:77
msgid "Round {0}"
msgstr "ラウンド{0}"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:138
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:142
#: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:23
msgid "Rules"
msgstr "ルール"
@@ -2099,7 +2151,7 @@ msgstr "サーバー関数"
msgid "serverFn name"
msgstr "serverFn名"
#: src/app/routes/_authed/settings.tsx:15
#: src/app/routes/_authed/settings.tsx:16
#: src/features/core/components/settings-button.tsx:19
msgid "Settings"
msgstr "設定"
@@ -2118,10 +2170,18 @@ msgstr "{1}人中{0}人のプレイヤーを表示"
msgid "Shuffle Groups"
msgstr "グループをシャッフル"
#: src/app/routes/_authed/settings.tsx:42
#: src/app/routes/_authed/settings.tsx:44
msgid "Sign Out"
msgstr "サインアウト"
#: src/themes/sketch/manifest.ts:6
msgid "Sketchbook"
msgstr "スケッチ"
#: src/themes/skeuo/manifest.ts:6
msgid "Skeuomorphic"
msgstr "スキューモーフィズム"
#: src/components/DefaultCatchBoundary.tsx:89
msgid "Something went wrong"
msgstr "問題が発生しました"
@@ -2188,7 +2248,7 @@ msgstr "開始"
msgid "Start Date"
msgstr "開始日"
#: src/features/bracket/components/match-card.tsx:354
#: src/features/bracket/components/match-card.tsx:358
#: src/features/tournaments/components/group-match-card.tsx:168
msgid "Start match"
msgstr "試合を開始"
@@ -2316,11 +2376,11 @@ msgstr "チームを更新しましたが、ロゴのアップロードに失敗
msgid "Team updated successfully!"
msgstr "チームを更新しました!"
#: src/features/players/components/profile/index.tsx:78
#: src/features/players/components/profile/index.tsx:83
#: src/features/players/components/profile/skeleton.tsx:75
#: src/features/tournaments/components/profile/index.tsx:32
#: src/features/tournaments/components/profile/skeleton.tsx:89
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:133
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:137
msgid "Teams"
msgstr "チーム"
@@ -2328,6 +2388,10 @@ msgstr "チーム"
msgid "Teams created successfully!"
msgstr "チームを作成しました!"
#: src/themes/terminal/manifest.ts:6
msgid "Terminal"
msgstr "ターミナル"
#: src/app/routes/api/push/test.ts:29
msgid "Test notification — push is working on this device."
msgstr "テスト通知 — このデバイスでプッシュ通知が機能しています。"
@@ -2352,6 +2416,11 @@ msgstr "相手チーム"
msgid "The requested resource was not found"
msgstr "リクエストされたリソースが見つかりませんでした"
#: src/features/settings/components/theme-picker.tsx:40
#: src/features/settings/components/theme-picker.tsx:68
msgid "Theme"
msgstr "テーマ"
#: src/features/players/components/player-head-to-head-sheet.tsx:107
msgid "These players have not faced each other yet."
msgstr "このプレイヤー同士はまだ対戦していません。"
@@ -2421,7 +2490,7 @@ msgstr "トーナメントを作成しました!"
msgid "Tournament not found"
msgstr "トーナメントが見つかりません"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:148
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:152
#: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:25
msgid "Tournament Rules"
msgstr "トーナメントルール"
@@ -2512,7 +2581,7 @@ msgstr "ユーザーエージェント"
msgid "versus"
msgstr "対"
#: src/features/players/components/profile/index.tsx:61
#: src/features/players/components/profile/index.tsx:66
msgid "View all badges"
msgstr "すべてのバッジを見る"
@@ -2529,7 +2598,7 @@ msgid "View Groups"
msgstr "グループを見る"
#: src/features/bracket/components/match-dock.tsx:178
#: src/features/matches/components/match-card.tsx:88
#: src/features/matches/components/match-card.tsx:89
msgid "View head-to-head"
msgstr "対戦成績を見る"
@@ -2537,6 +2606,10 @@ msgstr "対戦成績を見る"
msgid "View Predictions"
msgstr "予想を見る"
#: src/themes/wireframe/manifest.ts:118
msgid "Viewport"
msgstr "ビューポート"
#: src/app/routes/api/spotify/playback.ts:70
msgid "volumePercent must be a number"
msgstr "volumePercentは数値である必要があります"
+280
View File
@@ -0,0 +1,280 @@
# FLXN theme system
A theme is a self-contained folder under `src/themes/<id>/` containing exactly
two files. Themes are auto-discovered via `import.meta.glob` — adding a theme
is adding a folder, removing one is deleting it. No shared file is ever edited.
```
src/themes/
<id>/
manifest.ts # metadata + optional JS-side Mantine override
theme.css # all visual work, scoped under [data-theme="<id>"]
```
Activation: the provider sets `data-theme="<id>"` on `<html>` (mirrored to
localStorage `flxn-theme` for the pre-hydration script in `__root.tsx`).
The preference is stored per-user in SuperTokens metadata (`metadata.theme`).
## manifest.ts
```ts
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
const manifest: ThemeManifest = {
id: "comic", // MUST equal the folder name
label: msg`Comic`, // i18n — always msg``
colorSchemes: "both", // or "light" / "dark" to force a scheme
fontLinks: [ // injected only while theme is active;
"https://fonts.googleapis.com/css2?family=Bangers&display=swap",
], // browsers fetch font files lazily
chromeColors: { // browser-chrome theme-color meta
light: { base: "#fde047", dimmed: "#ca8a04" },
dark: { base: "#1c1917", dimmed: "#0c0a09" },
},
mantine: { // optional Mantine override, merged onto
defaultRadius: "xs", // the base theme via mergeThemeOverrides.
components: { /* defaultProps, vars, … */ },
},
};
export default manifest;
```
Rules:
- `label` must use `msg` from `@lingui/core/macro` (never raw strings, never
global `t`). Translations are handled centrally afterwards.
- Prefer doing visual work in `theme.css`; use `mantine` only for things CSS
cannot express (defaultProps, per-component `vars` resolvers).
## Accent color (REQUIRED)
The user picks an accent (blue, red, green, yellow, grape, orange, pink,
lime) and EVERY theme must honor it — there is no way to lock the accent.
- Primary/interactive styling must derive from the primary aliases
(`--mantine-primary-color-filled`, `-light`, `-6`, …) or from the accent
ramps generically — never from one hardcoded brand hue.
- A theme MAY retune all eight accent ramps (`--mantine-color-blue-*`,
`--mantine-color-red-*`, …) to fit its world — e.g. neon versions, pastel
versions, phosphor-tinted versions — as long as the eight choices remain
visually distinct. Retuning shades is encouraged; ignoring the choice is
forbidden.
- Theme-flavor decoration may still use fixed hues (a comic's halftone tint,
a blueprint's grid ink), but anything that reads as "the accent" —
filled buttons, active tabs/nav, focus rings, selection — follows the user.
## light-dark() trap
Never use `light-dark()` in a declaration whose selector matches `<html>`
(any `[data-theme=…]` token block): postcss-preset-mantine rewrites it into
a descendant selector that can never match the root element, so the dark
branch silently dies. Use the explicit compound scheme selectors (shape 2)
instead. Inside descendant rules (`[data-theme] .mantine-Button-root`)
light-dark() is fine.
## theme.css — selector templates (specificity is load-bearing)
Mantine emits its variables from a runtime `<style>` tag that appears AFTER
our bundled CSS, so theme overrides must WIN ON SPECIFICITY, not order. Use
these exact selector shapes:
```css
/* 1. Tokens for any scheme — (0,2,0), beats Mantine's :root block */
[data-theme="comic"][data-theme] {
--mantine-radius-default: 0;
--mantine-font-family: "Comic Neue", cursive;
}
/* 2. Per-scheme tokens — (0,3,0), beats Mantine's :root[scheme] blocks */
[data-theme="comic"][data-theme][data-mantine-color-scheme="light"] {
--mantine-color-body: #fef9c3;
}
[data-theme="comic"][data-theme][data-mantine-color-scheme="dark"] {
--mantine-color-body: #1c1917;
}
/* 3. Component restyles — descendant selectors on static classes */
[data-theme="comic"] .mantine-Button-root {
border: 3px solid #000;
box-shadow: 4px 4px 0 #000;
}
/* 4. Full-page decorations (scanlines, halftone, grain) */
[data-theme="comic"] body::before {
content: "";
position: fixed;
inset: 0;
z-index: 9998; /* above content, below --mantine-z-index-max */
pointer-events: none;
/* embed textures as data-URI SVG/gradients — no external requests */
}
```
The doubled `[data-theme]` attribute is intentional (specificity bump).
Still never use `html[data-theme]` or `:root[data-theme]` — the doubled
attribute form matches `<html>` at higher specificity and stays portable.
## Previews (no per-theme work required)
The settings picker renders each theme in its own sandboxed same-origin
srcdoc iframe (`theme-preview.tsx`): the iframe's `<html>` carries the
theme's `data-theme`/`data-mantine-color-scheme`, its head gets a snapshot
of the app's real stylesheets, the theme's `fontLinks`, and the theme's
Mantine JS-side variables computed with Mantine's own resolver from the
same `baseTheme` + `manifest.mantine` + accent merge the live provider
performs. Previews are therefore 1:1 with the active theme by construction
— fonts, textures, html/body-level rules and all — and nothing bleeds
between the active theme and a preview. There is no preview variable
contract to maintain, and `--mantine-primary-color-*` must still never be
pinned by a theme (the user's accent must show through everywhere,
previews included).
## What you can override (cheat sheet)
Mantine components style themselves from CSS variables — overriding variables
retints the whole app with zero React work.
- Palettes: `--mantine-color-<name>-<0..9>` and per-color variant vars
(`-filled`, `-filled-hover`, `-light`, `-light-hover`, `-light-color`,
`-outline`, `-outline-hover`, `-text`); primary aliases
`--mantine-primary-color-{0..9,filled,filled-hover,light,light-hover,light-color,contrast}`.
- Scheme-dependent semantics (define per scheme, selector 2):
`--mantine-color-body`, `-text`, `-dimmed`, `-bright`, `-anchor`, `-error`,
`-placeholder`, `-default`, `-default-hover`, `-default-color`,
`-default-border`.
- Shape: `--mantine-radius-{xs..xl}`, `--mantine-radius-default`.
- Elevation: `--mantine-shadow-{xs..xl}` (composite strings — neobrutalism
uses `4px 4px 0 #000`, glass uses soft large blurs).
- Type: `--mantine-font-family{,-monospace,-headings}`,
`--mantine-font-size-{xs..xl}`, `--mantine-h{1..6}-font-size/-line-height/-font-weight`,
`--mantine-heading-font-weight`, `--mantine-line-height{,-xs..xl}`.
- Spacing: `--mantine-spacing-{xs..xl}` (be conservative — layout shifts).
- App tokens (see `base.css`): `--flxn-gold/-silver/-bronze`,
`--flxn-toast-success/-error`, `--flxn-podium-{1,2,3}-{bg,border,label}`,
`--flxn-showcase-bg`, `--flxn-overlay`, `--flxn-dotgrid-color`,
`--flxn-pop-shadow`. Override these with selector 1/2 shapes (they beat
base.css's `:root` on specificity).
Static classes for restyling (most-used components in this app): Button,
ActionIcon, Paper, Card, TextInput/Input (`.mantine-Input-input`), Select,
Badge, Tabs (`-list`, `-tab`), SegmentedControl (`-root`, `-indicator`,
`-label`), Modal (`-content`, `-header`), Avatar, ThemeIcon, Skeleton,
Loader, Alert, Tooltip, Popover (`-dropdown`), Switch, Checkbox, Progress,
PinInput, NavLink, Notification, AppShell (`-header`, `-navbar`, `-main`),
Divider, Indicator — as `.mantine-<Component>-<part>`. State comes as data
attributes: `[data-variant]`, `[data-active]`, `[data-disabled]`, etc.
Custom app components (sheet drawers, nav tiles, list rows, carousels) are
built on the same `--mantine-*`/`--flxn-*` variables and follow automatically.
## App component hooks
Stable classes/tokens for custom components (defaults live in
`src/themes/hooks.css`; themes override under `[data-theme]` scopes — theme
files load after hooks.css, so equal-specificity rules win by order):
- `.flxn-tabs` / `.flxn-tab` (+`[data-active]`) / `.flxn-tabs-indicator` —
the swipeable tab strip used on profile/tournament pages. Restyle these so
tabs belong to the theme (block active states, underlines, boxed tabs…).
TWO STATES: resting (mid-page) must stay FLAT — transparent background,
no strip chrome, blending into the page; the docked treatment (background,
rules, shadows, textures) goes on `.flxn-tabs[data-stuck]` only, which the
component sets while the strip is pinned to the top. hooks.css provides
the transition between states. Tab/indicator styling must stay legible
over the bare page background in the resting state.
STACKING: the indicator is Mantine FloatingIndicator — absolutely
positioned, so it would paint OVER the static tab labels; hooks.css
lifts `.flxn-tab` (`position: relative; z-index: 1`) so labels always
render on top. Never z-index the indicator above 1 or an opaque fill
swallows the labels.
PAIRING RULE: an accent-filled indicator pill/block (or accent-filled
`.flxn-tab[data-active]`) MUST pair its label with `--flxn-on-accent`
(black/white computed from the actual cascaded fill — safe under CSS
ramp retunes, unlike `--mantine-primary-color-contrast`, which Mantine
computes from the JS theme colors and can disagree with a CSS-retuned
fill). Never accent text over an accent fill. If the indicator is an
underline/rule, accent text is fine.
NO glow/soft shadows on `.flxn-tabs-indicator` (owner preference): a
clean underline or flat plate only. Rich active-tab chrome (wells,
engraving, plates beyond the indicator) belongs under
`.flxn-tabs[data-stuck]` like the strip chrome — resting tabs stay flat.
- `.flxn-tile` / `.flxn-tile-icon` — home/tournament nav grid tiles. Treat
like Card/Paper: give them the theme's surface language.
- (retired) `.flxn-see-all` — "see all" actions are plain Mantine subtle
Buttons now, accent-driven by design. Do NOT reintroduce per-theme chip
styling for them; they inherit only your generic Button treatment.
- `.flxn-bracket-card` — the small dense match cards inside the bracket
view (they are Mantine Cards). Exclude them from heavy Card decorations
(valances, ornaments) that suit full-width cards but overwhelm bracket
density.
- `.flxn-match-card` + `.flxn-reaction-row` — a match card with a reaction
tray tucked underneath (tray = full border + full radius, hidden top edge).
Keep the pair reading as one construction in your theme: matching borders
and backgrounds, tray background contrasting subtly with the card.
- `.flxn-badge-tile` — badge grid tile motion hook; tile shape follows
`--flxn-badge-radius` (default 12px).
- `.flxn-avatar-frame` + `--flxn-avatar-radius` (default `50%`) +
`--flxn-avatar-border` (default `1px solid` default-border) — avatars.
The frame owns the avatar's SINGLE border via the token; NEVER put a
border/outline on `.mantine-Avatar-root` (hooks.css force-strips it —
doing so doubles every ring). The frame is a plain Box, so generic
Paper restyles don't reach it; square-language themes set the radius
to 0–4px and restyle via the two tokens (plus `.flxn-avatar-frame` for
anything richer, e.g. shadows/bezels).
- `.flxn-tile-icon` — keep icons FLAT: tint the glyph (color), no
background plates/borders/padding behind it. A plate is only justified
when the theme's whole language demands it (rare); default to flat.
## Hard rules
- EVERYTHING scoped under `[data-theme="<id>"]`. Zero unscoped selectors —
a theme must be inert when inactive.
- Support both schemes unless the design genuinely demands one; then set
`colorSchemes: "light" | "dark"` (the engine forces it).
- Accessibility gates:
- Wrap flicker/pulse/marquee animations in
`@media (prefers-reduced-motion: no-preference) { … }`.
- Give `backdrop-filter` surfaces a solid fallback via
`@supports not (backdrop-filter: blur(1px))` and respect
`@media (prefers-reduced-transparency: reduce)`.
- Keep real borders on interactive elements (forced-colors mode strips
shadows/gradients).
- Body text must stay ≥ 4.5:1 contrast in every supported scheme.
- Mantine hardcodes WHITE on `[data-combobox-selected]` (Select options)
and `theme.autoContrast` does not reach it. hooks.css fixes this
globally with `--flxn-on-accent`; if your theme overrides the
selected-option styling, pair the fill with an explicit legible
on-color yourself.
- Any `background-clip: text` gradient text needs a
`@media (forced-colors: active)` fallback (`background-image: none;
color: CanvasText`) or headings vanish in forced-colors mode.
- Re-flavored `.flxn-press` transforms MUST be wrapped in
`@media (prefers-reduced-motion: no-preference)` — theme selectors
outrank the global reduce kill in the provider.
- Placeholder text ≥3:1 and dimmed text ≥4.5:1 against the body color.
- Text must never share its hue with the fill directly behind it
(accent-on-accent, gold-on-gold…). Accent fills pair with
`--flxn-on-accent` (base.css token: black/white picked from the actual
cascaded `--mantine-primary-color-filled`); accent washes pair with
`--mantine-primary-color-light-color`; accent text needs a neutral
backing. Applies to tab indicators, SegmentedControl, active NavLinks,
badges, chips — anywhere a label sits on a colored plate.
- Performance gates:
- Never animate variables on `:root`/`html` — animate leaf elements.
- At most 1–2 full-viewport overlay pseudo-elements, `pointer-events: none`.
- Limit `backdrop-filter` to a handful of surfaces; never animate blur.
- Textures/assets as data-URIs inside theme.css; no network requests.
- Don't break layout: the app is a mobile-first PWA (AppShell header +
navbar). Radius/borders/shadows/fonts are fair game; avoid changing
spacing scales drastically or adding outer margins to shell parts.
- The press feedback (`.flxn-press`, scale on :active) is global; themes may
re-flavor it, e.g. `[data-theme="brutal"] .flxn-press:active { transform:
translate(4px, 4px); }`.
- i18n: manifest strings via `msg` only. Do NOT run lingui extract/compile —
handled centrally.
## Verifying a theme
`bun x tsc --noEmit` must pass. Do not run the dev server; visual QA happens
centrally after all themes land.
+137
View File
@@ -0,0 +1,137 @@
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
/* Candy-gloss Aero ramps for all eight accents. Declared in JS (not
theme.css) so Mantine regenerates every derived variant var (-light rgba,
-outline, autoContrast picks) from the retuned shades, whichever accent
the user selects. */
const aeroColors = {
// aqua
blue: [
"#e7f6fd",
"#d0edfb",
"#a6ddf6",
"#79ccf0",
"#55bdec",
"#3db1e8",
"#2a9ed7",
"#1f88bd",
"#196f9b",
"#135877",
],
// glossy grass
green: [
"#eefaea",
"#d9f2d2",
"#bce8b1",
"#9cdc8d",
"#7fd06d",
"#66c353",
"#4fb23c",
"#3f9630",
"#337b27",
"#286120",
],
// chartreuse jelly
lime: [
"#f6fbe4",
"#ebf6c6",
"#dcefa0",
"#cbe677",
"#bbdc52",
"#abd135",
"#96bd20",
"#7da019",
"#648213",
"#4e660f",
],
// candy apple
red: [
"#fdeef0",
"#fbd9dd",
"#f7b7bf",
"#f2929e",
"#ec7280",
"#e65567",
"#db3a50",
"#bd2c41",
"#9b2335",
"#791b2a",
],
// glossy sunbeam
yellow: [
"#fff9e1",
"#fff0bd",
"#ffe492",
"#fcd668",
"#f7c845",
"#f0b929",
"#e3a614",
"#c28a0e",
"#9e6f0a",
"#7c5607",
],
// jelly purple
grape: [
"#f7f0fd",
"#ecdcfa",
"#dbc0f5",
"#c79fee",
"#b581e6",
"#a468dd",
"#8f4dcd",
"#7a3cb3",
"#643093",
"#4e2573",
],
// goldfish amber
orange: [
"#fff3e4",
"#ffe4c4",
"#ffcf9b",
"#fdb56d",
"#f89c45",
"#f18827",
"#e37113",
"#c25c0d",
"#9e4a0a",
"#7b3a08",
],
// bubblegum
pink: [
"#fdeff5",
"#fbdae9",
"#f8b9d6",
"#f394bf",
"#ec72a9",
"#e55596",
"#d63c82",
"#b82e6d",
"#962558",
"#751d45",
],
} as const;
const manifest: ThemeManifest = {
id: "aero",
label: msg`Aero`,
colorSchemes: "both",
fontLinks: [
"https://fonts.googleapis.com/css2?family=Nunito+Sans:opsz,wght@6..12,400;6..12,600;6..12,700;6..12,800&display=swap",
],
chromeColors: {
light: { base: "#d3edfb", dimmed: "#8fcdee" },
dark: { base: "#0a2536", dimmed: "#051925" },
},
mantine: {
// Juicy mid-tone fills in both schemes; bright candy fills (yellow,
// lime) and mid-luminance aqua get dark text instead of low-contrast
// white (aqua blue-6 luminance ≈ 0.30 must land above the threshold).
primaryShade: { light: 6, dark: 7 },
autoContrast: true,
luminanceThreshold: 0.28,
colors: aeroColors,
},
};
export default manifest;
+607
View File
@@ -0,0 +1,607 @@
/* Frutiger Aero: glossy skeuomorphic surfaces over a fixed sky-to-white
gradient with static bubbles. The eight accent ramps are retuned in
manifest.ts (candy-gloss versions) and everything interactive derives
from --mantine-primary-color-* so the user's accent drives fills, active
states and focus rings; the sky/grass scenery stays fixed decoration.
Gloss is vertical half-highlight gradients (never animated) that layer
over any base color; backdrop-filter is limited to the AppShell header,
navbar and the docked ([data-stuck]) flxn tab strip. */
/* ---------------------------------------------------------------- */
/* 1. Tokens for any scheme */
/* ---------------------------------------------------------------- */
[data-theme="aero"][data-theme] {
--mantine-font-family: "Nunito Sans", "Segoe UI", "Helvetica Neue", sans-serif;
--mantine-font-family-headings: "Nunito Sans", "Segoe UI", "Helvetica Neue", sans-serif;
--mantine-heading-font-weight: 800;
/* bubbly radii */
--mantine-radius-xs: 0.5rem;
--mantine-radius-sm: 0.75rem;
--mantine-radius-md: 1rem;
--mantine-radius-lg: 1.25rem;
--mantine-radius-xl: 1.75rem;
--mantine-radius-default: 1rem;
/* glossy bezelled circles for avatars, rounded candy badge tiles */
--flxn-avatar-radius: 50%;
--flxn-avatar-border: 1px solid var(--aero-panel-border);
--flxn-badge-radius: 16px;
}
/* ---------------------------------------------------------------- */
/* 2. Per-scheme tokens */
/* ---------------------------------------------------------------- */
[data-theme="aero"][data-theme][data-mantine-color-scheme="light"] {
--mantine-color-body: #e9f6fe;
--mantine-color-text: #123c56;
--mantine-color-bright: #082738;
--mantine-color-dimmed: #4a758f;
--mantine-color-placeholder: #5f7f95;
--mantine-color-default: rgba(255, 255, 255, 0.75);
--mantine-color-default-hover: rgba(255, 255, 255, 0.92);
--mantine-color-default-color: #123c56;
--mantine-color-default-border: rgba(125, 178, 212, 0.55);
/* soft blue-tinted elevation */
--mantine-shadow-xs: 0 1px 3px rgba(23, 113, 159, 0.14);
--mantine-shadow-sm: 0 2px 8px rgba(23, 113, 159, 0.16);
--mantine-shadow-md: 0 6px 18px rgba(23, 113, 159, 0.18);
--mantine-shadow-lg: 0 10px 28px rgba(23, 113, 159, 0.2);
--mantine-shadow-xl: 0 16px 44px rgba(23, 113, 159, 0.24);
/* gloss ingredients */
--aero-panel-bg: rgba(255, 255, 255, 0.66);
--aero-panel-border: rgba(255, 255, 255, 0.75);
--aero-panel-sheen: linear-gradient(
rgba(255, 255, 255, 0.7),
rgba(255, 255, 255, 0.15) 50%,
rgba(255, 255, 255, 0) 50%
);
--aero-gloss: linear-gradient(
rgba(255, 255, 255, 0.55),
rgba(255, 255, 255, 0.14) 50%,
rgba(255, 255, 255, 0) 50%
);
--aero-gloss-soft: linear-gradient(
rgba(255, 255, 255, 0.35),
rgba(255, 255, 255, 0.08) 50%,
rgba(255, 255, 255, 0) 50%
);
--aero-solid: #f4fafe;
--aero-chrome-bg: rgba(255, 255, 255, 0.62);
--aero-input-bg: rgba(255, 255, 255, 0.85);
--aero-input-inset: inset 0 2px 4px rgba(23, 113, 159, 0.16),
inset 0 -1px 0 rgba(255, 255, 255, 0.8);
--aero-track: rgba(178, 216, 238, 0.45);
--aero-tube-inset: inset 0 2px 4px rgba(23, 113, 159, 0.22),
inset 0 -1px 0 rgba(255, 255, 255, 0.6);
--aero-indicator-bg: rgba(255, 255, 255, 0.95);
--aero-thumb: linear-gradient(#ffffff, #e6f2fa);
/* app tokens */
--flxn-gold: #f0ac12;
--flxn-silver: #90a9ba;
--flxn-bronze: #d0803f;
--flxn-toast-success: #5cb455;
--flxn-toast-error: #e56a62;
--flxn-showcase-bg: rgba(255, 255, 255, 0.72);
--flxn-overlay: rgba(16, 66, 102, 0.4);
--flxn-dotgrid-color: rgba(61, 177, 232, 0.35);
--flxn-pop-shadow: rgba(23, 113, 159, 0.3);
/* podium as glassy tiers */
--flxn-podium-1-bg: rgba(255, 232, 155, 0.6);
--flxn-podium-1-border: rgba(222, 178, 66, 0.65);
--flxn-podium-1-label: #8a6410;
--flxn-podium-2-bg: rgba(255, 255, 255, 0.6);
--flxn-podium-2-border: rgba(151, 183, 203, 0.6);
--flxn-podium-2-label: #4d6b7e;
--flxn-podium-3-bg: rgba(255, 214, 178, 0.55);
--flxn-podium-3-border: rgba(216, 138, 79, 0.6);
--flxn-podium-3-label: #92511f;
}
[data-theme="aero"][data-theme][data-mantine-color-scheme="dark"] {
--mantine-color-body: #071e2d;
--mantine-color-text: #d8edf8;
--mantine-color-bright: #f2fbff;
--mantine-color-dimmed: #8db3c8;
--mantine-color-placeholder: #618ba3;
--mantine-color-default: rgba(19, 58, 82, 0.72);
--mantine-color-default-hover: rgba(27, 74, 102, 0.85);
--mantine-color-default-color: #d8edf8;
--mantine-color-default-border: rgba(94, 193, 238, 0.28);
/* glow-edged elevation */
--mantine-shadow-xs: 0 0 0 1px rgba(94, 193, 238, 0.1), 0 2px 6px rgba(0, 10, 18, 0.5);
--mantine-shadow-sm: 0 0 0 1px rgba(94, 193, 238, 0.12), 0 4px 14px rgba(0, 10, 18, 0.55);
--mantine-shadow-md: 0 0 0 1px rgba(94, 193, 238, 0.14), 0 8px 24px rgba(0, 10, 18, 0.6);
--mantine-shadow-lg: 0 0 0 1px rgba(94, 193, 238, 0.16), 0 12px 34px rgba(0, 10, 18, 0.65);
--mantine-shadow-xl: 0 0 0 1px rgba(94, 193, 238, 0.18), 0 18px 48px rgba(0, 10, 18, 0.7);
/* deep-aqua glass ingredients */
--aero-panel-bg: rgba(12, 42, 62, 0.72);
--aero-panel-border: rgba(94, 193, 238, 0.3);
--aero-panel-sheen: linear-gradient(
rgba(151, 214, 246, 0.16),
rgba(151, 214, 246, 0.04) 50%,
rgba(151, 214, 246, 0) 50%
);
--aero-gloss: linear-gradient(
rgba(255, 255, 255, 0.28),
rgba(255, 255, 255, 0.07) 50%,
rgba(255, 255, 255, 0) 50%
);
--aero-gloss-soft: linear-gradient(
rgba(255, 255, 255, 0.14),
rgba(255, 255, 255, 0.04) 50%,
rgba(255, 255, 255, 0) 50%
);
--aero-solid: #0d2a3e;
--aero-chrome-bg: rgba(7, 28, 42, 0.68);
--aero-input-bg: rgba(6, 26, 40, 0.8);
--aero-input-inset: inset 0 2px 5px rgba(0, 10, 18, 0.6),
inset 0 -1px 0 rgba(94, 193, 238, 0.12);
--aero-track: rgba(9, 36, 54, 0.8);
--aero-tube-inset: inset 0 2px 5px rgba(0, 10, 18, 0.55),
inset 0 -1px 0 rgba(94, 193, 238, 0.1);
--aero-indicator-bg: rgba(31, 100, 140, 0.8);
--aero-thumb: linear-gradient(#e8f6fe, #a9d6ee);
/* app tokens */
--flxn-gold: #ffd76a;
--flxn-silver: #a5bccb;
--flxn-bronze: #e29a63;
--flxn-toast-success: #8fd98a;
--flxn-toast-error: #f2938c;
--flxn-showcase-bg: rgba(12, 42, 62, 0.75);
--flxn-overlay: rgba(2, 12, 20, 0.6);
--flxn-dotgrid-color: rgba(94, 193, 238, 0.22);
--flxn-pop-shadow: rgba(0, 0, 0, 0.55);
/* podium as glassy tiers */
--flxn-podium-1-bg: rgba(255, 205, 92, 0.14);
--flxn-podium-1-border: rgba(255, 205, 92, 0.4);
--flxn-podium-1-label: #ffd76a;
--flxn-podium-2-bg: rgba(190, 220, 238, 0.1);
--flxn-podium-2-border: rgba(190, 220, 238, 0.3);
--flxn-podium-2-label: #b8cfdd;
--flxn-podium-3-bg: rgba(226, 154, 99, 0.13);
--flxn-podium-3-border: rgba(226, 154, 99, 0.38);
--flxn-podium-3-label: #eba76f;
}
/* ---------------------------------------------------------------- */
/* 3. Fixed sky backdrop with static bubbles (below all content) */
/* ---------------------------------------------------------------- */
[data-theme="aero"] body::before {
content: "";
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
}
[data-theme="aero"][data-mantine-color-scheme="light"] body::before {
background-color: #e9f6fe;
background-image:
radial-gradient(circle 0.6rem at 13.5% 20%, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0) 70%),
radial-gradient(circle 4.5rem at 15% 22%, rgba(255, 255, 255, 0) 60%, rgba(255, 255, 255, 0.5) 66%, rgba(255, 255, 255, 0.08) 72%, rgba(255, 255, 255, 0) 74%),
radial-gradient(circle 2.5rem at 79% 12%, rgba(255, 255, 255, 0) 58%, rgba(255, 255, 255, 0.45) 66%, rgba(255, 255, 255, 0) 74%),
radial-gradient(circle 6rem at 88% 64%, rgba(255, 255, 255, 0) 62%, rgba(255, 255, 255, 0.35) 68%, rgba(255, 255, 255, 0) 75%),
radial-gradient(circle 3rem at 8% 72%, rgba(255, 255, 255, 0) 58%, rgba(255, 255, 255, 0.3) 66%, rgba(255, 255, 255, 0) 74%),
radial-gradient(56rem 20rem at 50% 106%, rgba(124, 197, 118, 0.28), rgba(124, 197, 118, 0) 70%),
linear-gradient(180deg, #a5daf6 0%, #c9e9fa 32%, #e9f6fe 60%, #ffffff 100%);
}
[data-theme="aero"][data-mantine-color-scheme="dark"] body::before {
background-color: #071e2d;
background-image:
radial-gradient(circle 4rem at 16% 24%, rgba(120, 205, 245, 0) 60%, rgba(120, 205, 245, 0.2) 67%, rgba(120, 205, 245, 0) 74%),
radial-gradient(circle 2.5rem at 80% 16%, rgba(120, 205, 245, 0) 58%, rgba(120, 205, 245, 0.16) 66%, rgba(120, 205, 245, 0) 74%),
radial-gradient(40rem 28rem at 82% -4%, rgba(31, 136, 189, 0.35), transparent 65%),
radial-gradient(36rem 26rem at 10% 86%, rgba(23, 128, 118, 0.3), transparent 65%),
linear-gradient(180deg, #0c3350 0%, #0a2a40 45%, #051520 100%);
}
/* ---------------------------------------------------------------- */
/* 4. Panels: translucent white/deep-aqua with a top-half sheen */
/* ---------------------------------------------------------------- */
[data-theme="aero"] .mantine-Paper-root,
[data-theme="aero"] .mantine-Card-root,
[data-theme="aero"] .mantine-Popover-dropdown {
background-color: var(--aero-panel-bg);
background-image: var(--aero-panel-sheen);
border: 1px solid var(--aero-panel-border);
box-shadow: var(--mantine-shadow-md);
}
[data-theme="aero"] .mantine-Modal-content {
background-color: var(--aero-panel-bg);
background-image: var(--aero-panel-sheen);
border: 1px solid var(--aero-panel-border);
box-shadow: var(--mantine-shadow-xl);
}
[data-theme="aero"] .mantine-Modal-header {
background-color: transparent;
}
[data-theme="aero"] .mantine-Popover-dropdown {
box-shadow: var(--mantine-shadow-lg);
}
/* Notifications float over arbitrary content: keep near-opaque. */
[data-theme="aero"] .mantine-Notification-root {
background-color: var(--aero-solid);
background-image: var(--aero-panel-sheen);
border: 1px solid var(--aero-panel-border);
border-radius: var(--mantine-radius-lg);
box-shadow: var(--mantine-shadow-lg);
}
/* ---------------------------------------------------------------- */
/* 5. AppShell chrome — the ONLY backdrop-filter surfaces */
/* ---------------------------------------------------------------- */
[data-theme="aero"] .mantine-AppShell-header,
[data-theme="aero"] .mantine-AppShell-navbar {
background-color: var(--aero-chrome-bg);
background-image: var(--aero-gloss-soft);
-webkit-backdrop-filter: blur(14px) saturate(150%);
backdrop-filter: blur(14px) saturate(150%);
}
[data-theme="aero"] .mantine-AppShell-header {
border-bottom: 1px solid var(--aero-panel-border);
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="aero"] .mantine-AppShell-navbar {
border-inline-end: 1px solid var(--aero-panel-border);
}
/* ---------------------------------------------------------------- */
/* 6. Glossy pill buttons with specular highlight */
/* ---------------------------------------------------------------- */
[data-theme="aero"] .mantine-Button-root {
position: relative;
overflow: hidden;
border-radius: 999px;
font-weight: 700;
}
[data-theme="aero"] .mantine-Button-root:is(
[data-variant="filled"],
[data-variant="default"],
[data-variant="light"],
[data-variant="gradient"]
) {
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.45), var(--mantine-shadow-xs);
}
[data-theme="aero"] .mantine-Button-root[data-variant="default"] {
border: 1px solid var(--mantine-color-default-border);
}
[data-theme="aero"] .mantine-Button-root:is(
[data-variant="filled"],
[data-variant="default"],
[data-variant="light"],
[data-variant="gradient"]
)::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
background: var(--aero-gloss);
pointer-events: none;
}
[data-theme="aero"] .mantine-Button-inner {
position: relative;
z-index: 1;
}
[data-theme="aero"] .mantine-ActionIcon-root {
position: relative;
overflow: hidden;
border-radius: 999px;
}
[data-theme="aero"] .mantine-ActionIcon-root:is(
[data-variant="filled"],
[data-variant="default"],
[data-variant="light"],
[data-variant="gradient"]
) {
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.45), var(--mantine-shadow-xs);
}
[data-theme="aero"] .mantine-ActionIcon-root:is(
[data-variant="filled"],
[data-variant="default"],
[data-variant="light"],
[data-variant="gradient"]
)::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
background: var(--aero-gloss);
pointer-events: none;
}
[data-theme="aero"] .mantine-ThemeIcon-root {
background-image: var(--aero-gloss);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4);
}
/* Press feedback: quick specular dim on top of the global scale. */
[data-theme="aero"] .flxn-press:active {
filter: brightness(0.95) saturate(1.05);
}
/* ---------------------------------------------------------------- */
/* 7. Glossy inset inputs */
/* ---------------------------------------------------------------- */
[data-theme="aero"] .mantine-Input-input {
background-color: var(--aero-input-bg);
border: 1px solid var(--mantine-color-default-border);
border-radius: var(--mantine-radius-lg);
box-shadow: var(--aero-input-inset);
}
[data-theme="aero"] .mantine-Input-input:focus,
[data-theme="aero"] .mantine-Input-input:focus-within {
border-color: var(--mantine-primary-color-filled);
box-shadow: var(--aero-input-inset),
0 0 0 3px color-mix(in srgb, var(--mantine-primary-color-filled) 25%, transparent);
}
[data-theme="aero"] .mantine-Checkbox-input {
border-radius: 0.55rem;
box-shadow: var(--aero-input-inset);
}
/* ---------------------------------------------------------------- */
/* 8. Glass-tube progress, tracks, toggles */
/* ---------------------------------------------------------------- */
[data-theme="aero"] .mantine-Progress-root {
background-color: var(--aero-track);
border: 1px solid var(--mantine-color-default-border);
border-radius: 999px;
box-shadow: var(--aero-tube-inset);
}
/* Shine only — background-color stays the per-instance section color. */
[data-theme="aero"] .mantine-Progress-section {
border-radius: 999px;
background-image: linear-gradient(
rgba(255, 255, 255, 0.5),
rgba(255, 255, 255, 0.14) 48%,
rgba(0, 0, 0, 0.05) 52%,
rgba(255, 255, 255, 0.08)
);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);
}
[data-theme="aero"] .mantine-SegmentedControl-root {
background-color: var(--aero-track);
border: 1px solid var(--mantine-color-default-border);
border-radius: 999px;
box-shadow: var(--aero-tube-inset);
}
[data-theme="aero"] .mantine-SegmentedControl-indicator {
background-color: var(--aero-indicator-bg);
background-image: var(--aero-gloss);
border: 1px solid var(--aero-panel-border);
border-radius: 999px;
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="aero"] .mantine-SegmentedControl-label {
font-weight: 700;
}
[data-theme="aero"] .mantine-Switch-track {
background-image: var(--aero-gloss-soft);
box-shadow: inset 0 1px 2px rgba(0, 20, 35, 0.2);
}
[data-theme="aero"] .mantine-Switch-thumb {
background: var(--aero-thumb);
border-color: transparent;
box-shadow: 0 1px 2px rgba(0, 20, 35, 0.3), inset 0 -1px 0 rgba(140, 185, 215, 0.4);
}
/* ---------------------------------------------------------------- */
/* 9. Smaller glossy details */
/* ---------------------------------------------------------------- */
[data-theme="aero"] .mantine-Badge-root {
background-image: var(--aero-gloss-soft);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35);
}
[data-theme="aero"] .mantine-Badge-root[data-variant="default"] {
background-color: var(--mantine-color-default);
border: 1px solid var(--mantine-color-default-border);
}
[data-theme="aero"] .mantine-Alert-root {
background-image: var(--aero-gloss-soft);
border-radius: var(--mantine-radius-lg);
}
[data-theme="aero"] .mantine-Tabs-list {
--tabs-list-border-color: var(--mantine-color-default-border);
}
[data-theme="aero"] .mantine-Tabs-tab {
font-weight: 700;
border-radius: var(--mantine-radius-md) var(--mantine-radius-md) 0 0;
}
[data-theme="aero"] .mantine-Tabs-tab:hover,
[data-theme="aero"] .mantine-NavLink-root:hover {
background-color: var(--mantine-color-default-hover);
}
[data-theme="aero"] .mantine-NavLink-root {
border-radius: 999px;
}
[data-theme="aero"] .mantine-Tooltip-tooltip {
background-color: rgba(10, 45, 66, 0.92);
color: #eaf7ff;
border-radius: var(--mantine-radius-md);
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="aero"] .mantine-Divider-root {
--divider-color: var(--mantine-color-default-border);
}
[data-theme="aero"] ::selection {
background: color-mix(in srgb, var(--mantine-primary-color-filled) 25%, transparent);
}
/* ---------------------------------------------------------------- */
/* 10. App component hooks in Aero language */
/* ---------------------------------------------------------------- */
/* Swipeable tab strip: flat when resting; the white-glass bar (3rd and
last blur surface, docked state only — resting pays no blur cost) */
[data-theme="aero"] .flxn-tabs[data-stuck] {
background-color: var(--aero-chrome-bg);
background-image: var(--aero-gloss-soft);
border-bottom-color: var(--aero-panel-border);
-webkit-backdrop-filter: blur(14px) saturate(150%);
backdrop-filter: blur(14px) saturate(150%);
}
[data-theme="aero"] .flxn-tab {
color: var(--mantine-color-dimmed);
font-weight: 600;
}
/* Text rides on the glossy accent pill indicator below */
[data-theme="aero"] .flxn-tab[data-active] {
color: var(--flxn-on-accent);
font-weight: 700;
}
[data-theme="aero"] .flxn-tabs-indicator {
border-bottom: none;
border-radius: 999px;
background-color: var(--mantine-primary-color-filled);
background-image: var(--aero-gloss);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.45), var(--mantine-shadow-xs);
}
/* Nav grid tiles: glass panels with the cards' top-half sheen */
[data-theme="aero"] .flxn-tile {
background-color: var(--aero-panel-bg);
background-image: var(--aero-panel-sheen);
border: 1px solid var(--aero-panel-border);
border-radius: var(--mantine-radius-lg);
box-shadow: var(--mantine-shadow-sm);
}
/* Flat accent-tinted glyph */
[data-theme="aero"] .flxn-tile-icon {
color: var(--mantine-primary-color-light-color);
}
/* "See all" link: glossy accent mini pill */
[data-theme="aero"] .flxn-see-all {
border-radius: 999px;
background-image: var(--aero-gloss-soft);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35);
}
/* Match card + reaction tray read as one glass construction: same
translucent border; tray is a subtler aqua-tinted glass, no sheen. */
[data-theme="aero"] .flxn-match-card {
background-color: var(--aero-panel-bg);
background-image: var(--aero-panel-sheen);
border: 1px solid var(--aero-panel-border);
box-shadow: var(--mantine-shadow-md);
}
[data-theme="aero"] .flxn-reaction-row {
background-color: var(--aero-track);
background-image: none;
border: 1px solid var(--aero-panel-border);
border-radius: var(--mantine-radius-md);
box-shadow: var(--mantine-shadow-xs);
}
/* Glossy bezel: soft drop under the frame; the single ring itself comes
from --flxn-avatar-border */
[data-theme="aero"] .flxn-avatar-frame {
box-shadow: var(--mantine-shadow-xs);
}
/* ---------------------------------------------------------------- */
/* 11. Fallbacks and reduced transparency */
/* ---------------------------------------------------------------- */
@supports not (backdrop-filter: blur(1px)) {
[data-theme="aero"] .mantine-AppShell-header,
[data-theme="aero"] .mantine-AppShell-navbar,
[data-theme="aero"] .flxn-tabs[data-stuck] {
background-color: var(--aero-solid);
}
}
@media (prefers-reduced-transparency: reduce) {
[data-theme="aero"][data-theme][data-mantine-color-scheme="light"] {
--aero-panel-bg: #ffffff;
--aero-panel-border: rgba(125, 178, 212, 0.55);
--aero-chrome-bg: #f4fafe;
--aero-input-bg: #ffffff;
--aero-track: #d5e8f4;
--aero-indicator-bg: #ffffff;
--mantine-color-default: #ffffff;
--mantine-color-default-hover: #e6f2fa;
--flxn-showcase-bg: #ffffff;
--flxn-podium-1-bg: #fdf3d2;
--flxn-podium-2-bg: #f2f7fb;
--flxn-podium-3-bg: #fbe8d8;
--flxn-overlay: rgba(10, 45, 70, 0.6);
}
[data-theme="aero"][data-theme][data-mantine-color-scheme="dark"] {
--aero-panel-bg: #0d2a3e;
--aero-chrome-bg: #0a2536;
--aero-input-bg: #061a28;
--aero-track: #0a2334;
--aero-indicator-bg: #1f648c;
--mantine-color-default: #133a52;
--mantine-color-default-hover: #1b4a66;
--flxn-showcase-bg: #0d2a3e;
--flxn-podium-1-bg: #23303a;
--flxn-podium-2-bg: #1a2c39;
--flxn-podium-3-bg: #222c36;
--flxn-overlay: rgba(0, 8, 14, 0.75);
}
[data-theme="aero"] .mantine-AppShell-header,
[data-theme="aero"] .mantine-AppShell-navbar,
[data-theme="aero"] .flxn-tabs[data-stuck] {
-webkit-backdrop-filter: none;
backdrop-filter: none;
background-color: var(--aero-solid);
}
}
+60
View File
@@ -0,0 +1,60 @@
/* App-level semantic tokens; themes restyle them under [data-theme] scopes.
No light-dark() in html-level declarations — postcss-preset-mantine
rewrites it into a descendant selector that never matches <html>. Use
explicit [data-mantine-color-scheme] blocks. */
:root {
/* accolades */
--flxn-gold: gold;
--flxn-silver: var(--mantine-color-gray-5);
--flxn-bronze: var(--mantine-color-orange-6);
/* toast icons */
--flxn-toast-success: lightgreen;
--flxn-toast-error: lightcoral;
/* surfaces & decoration */
--flxn-overlay: rgba(0, 0, 0, 0.4);
--flxn-dotgrid-color: var(--mantine-color-default-border);
--flxn-pop-shadow: rgba(0, 0, 0, 0.3);
--flxn-avatar-radius: 50%;
--flxn-avatar-border: 1px solid var(--mantine-color-default-border);
--flxn-badge-radius: 12px;
/* text on accent fills: black/white from the cascaded fill (oklch block
below overrides this fallback) */
--flxn-on-accent: var(--mantine-primary-color-contrast);
/* light-scheme values */
--flxn-podium-1-bg: color-mix(in srgb, var(--mantine-color-yellow-4) 10%, var(--mantine-color-white));
--flxn-podium-1-border: color-mix(in srgb, var(--mantine-color-yellow-6) 45%, var(--mantine-color-gray-3));
--flxn-podium-1-label: var(--mantine-color-yellow-8);
--flxn-podium-2-bg: color-mix(in srgb, var(--mantine-color-gray-5) 10%, var(--mantine-color-white));
--flxn-podium-2-border: var(--mantine-color-gray-4);
--flxn-podium-2-label: var(--mantine-color-gray-6);
--flxn-podium-3-bg: color-mix(in srgb, var(--mantine-color-orange-5) 8%, var(--mantine-color-white));
--flxn-podium-3-border: color-mix(in srgb, var(--mantine-color-orange-6) 35%, var(--mantine-color-gray-3));
--flxn-podium-3-label: var(--mantine-color-orange-8);
--flxn-showcase-bg: var(--mantine-color-white);
}
:root[data-mantine-color-scheme="dark"] {
--flxn-podium-1-bg: color-mix(in srgb, var(--mantine-color-yellow-8) 16%, var(--mantine-color-dark-6));
--flxn-podium-1-border: color-mix(in srgb, var(--mantine-color-yellow-6) 35%, var(--mantine-color-dark-4));
--flxn-podium-1-label: var(--mantine-color-yellow-4);
--flxn-podium-2-bg: color-mix(in srgb, var(--mantine-color-gray-5) 10%, var(--mantine-color-dark-6));
--flxn-podium-2-border: var(--mantine-color-dark-4);
--flxn-podium-2-label: var(--mantine-color-gray-4);
--flxn-podium-3-bg: color-mix(in srgb, var(--mantine-color-orange-9) 14%, var(--mantine-color-dark-6));
--flxn-podium-3-border: color-mix(in srgb, var(--mantine-color-orange-7) 30%, var(--mantine-color-dark-4));
--flxn-podium-3-label: var(--mantine-color-orange-4);
--flxn-showcase-bg: var(--mantine-color-dark-7);
}
@supports (color: oklch(from red calc(1 - 1) 0 h)) {
:root {
--flxn-on-accent: oklch(
from var(--mantine-primary-color-filled) calc((0.6 - l) * infinity) 0 h
);
}
}
+17
View File
@@ -0,0 +1,17 @@
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
const manifest: ThemeManifest = {
id: "bauhaus",
label: msg`Bauhaus`,
colorSchemes: "both",
fontLinks: [
"https://fonts.googleapis.com/css2?family=Jost:ital,wght@0,100..900;1,100..900&display=swap",
],
chromeColors: {
light: { base: "#f4f1ea", dimmed: "#93918a" },
dark: { base: "#0d0d0d", dimmed: "#000000" },
},
};
export default manifest;
+725
View File
@@ -0,0 +1,725 @@
/* Bauhaus — 1925 Dessau. Flat planes, 2px black rules, zero radius.
Fixed decoration keeps the canonical triad (red #d42220 / blue #0050b3 /
yellow #f4cc00); everything interactive follows the user's accent via the
primary aliases, with all eight ramps retuned to poster-flat print hues. */
/* ---------- 1. Scheme-independent tokens ---------- */
[data-theme="bauhaus"][data-theme] {
--bh-red: #d42220;
--bh-blue: #0050b3;
--bh-yellow: #f4cc00;
--mantine-font-family: "Jost", "Inter", sans-serif;
--mantine-font-family-headings: "Jost", "Inter", sans-serif;
--mantine-heading-font-weight: 700;
--mantine-h1-font-weight: 800;
--mantine-h2-font-weight: 800;
--mantine-h3-font-weight: 700;
--mantine-radius-default: 0;
--mantine-radius-xs: 0;
--mantine-radius-sm: 0;
--mantine-radius-md: 0;
--mantine-radius-lg: 0;
--mantine-radius-xl: 0;
--mantine-shadow-xs: none;
--mantine-shadow-sm: none;
--mantine-shadow-md: none;
--mantine-shadow-lg: none;
--mantine-shadow-xl: none;
--flxn-avatar-radius: 0;
--flxn-avatar-border: 2px solid var(--bh-line);
--flxn-badge-radius: 0;
/* poster-flat accent ramps (all eight) */
--mantine-color-red-0: #fbe9e8;
--mantine-color-red-1: #f6d0cf;
--mantine-color-red-2: #efaba9;
--mantine-color-red-3: #e77f7c;
--mantine-color-red-4: #dd534f;
--mantine-color-red-5: #d42220;
--mantine-color-red-6: #c01e1c;
--mantine-color-red-7: #a51a18;
--mantine-color-red-8: #871512;
--mantine-color-red-9: #66100e;
--mantine-color-blue-0: #e7f0fa;
--mantine-color-blue-1: #c9dcf4;
--mantine-color-blue-2: #9dc0ea;
--mantine-color-blue-3: #6b9edd;
--mantine-color-blue-4: #3675c9;
--mantine-color-blue-5: #0050b3;
--mantine-color-blue-6: #00479f;
--mantine-color-blue-7: #003d88;
--mantine-color-blue-8: #00316d;
--mantine-color-blue-9: #002550;
--mantine-color-yellow-0: #fdf9e2;
--mantine-color-yellow-1: #fbf1bb;
--mantine-color-yellow-2: #f8e587;
--mantine-color-yellow-3: #f6d94d;
--mantine-color-yellow-4: #f5d121;
--mantine-color-yellow-5: #f4cc00;
--mantine-color-yellow-6: #dcb800;
--mantine-color-yellow-7: #c0a000;
--mantine-color-yellow-8: #a08600;
--mantine-color-yellow-9: #7d6900;
--mantine-color-green-0: #e6f3ea;
--mantine-color-green-1: #c5e3cf;
--mantine-color-green-2: #97cca9;
--mantine-color-green-3: #64b17e;
--mantine-color-green-4: #349756;
--mantine-color-green-5: #0f7f36;
--mantine-color-green-6: #0d7230;
--mantine-color-green-7: #0b6229;
--mantine-color-green-8: #095021;
--mantine-color-green-9: #063d19;
--mantine-color-grape-0: #f2eaf7;
--mantine-color-grape-1: #e0cfec;
--mantine-color-grape-2: #c6a8da;
--mantine-color-grape-3: #a87cc4;
--mantine-color-grape-4: #8952aa;
--mantine-color-grape-5: #6a2c91;
--mantine-color-grape-6: #5f2782;
--mantine-color-grape-7: #522170;
--mantine-color-grape-8: #431b5c;
--mantine-color-grape-9: #331546;
--mantine-color-orange-0: #fdefe2;
--mantine-color-orange-1: #fadcbd;
--mantine-color-orange-2: #f6c28d;
--mantine-color-orange-3: #f1a355;
--mantine-color-orange-4: #ec8524;
--mantine-color-orange-5: #e86a00;
--mantine-color-orange-6: #d05f00;
--mantine-color-orange-7: #b25100;
--mantine-color-orange-8: #924300;
--mantine-color-orange-9: #703300;
--mantine-color-pink-0: #fbe9f1;
--mantine-color-pink-1: #f6cede;
--mantine-color-pink-2: #efa5c3;
--mantine-color-pink-3: #e675a2;
--mantine-color-pink-4: #dd4784;
--mantine-color-pink-5: #d6246e;
--mantine-color-pink-6: #c02063;
--mantine-color-pink-7: #a51b55;
--mantine-color-pink-8: #871646;
--mantine-color-pink-9: #661134;
--mantine-color-lime-0: #f4f7e3;
--mantine-color-lime-1: #e6edbe;
--mantine-color-lime-2: #d2df8d;
--mantine-color-lime-3: #bccd55;
--mantine-color-lime-4: #a5bd24;
--mantine-color-lime-5: #8fae00;
--mantine-color-lime-6: #819c00;
--mantine-color-lime-7: #6f8700;
--mantine-color-lime-8: #5c7000;
--mantine-color-lime-9: #475600;
}
/* ---------- 2. Per-scheme tokens ---------- */
[data-theme="bauhaus"][data-theme][data-mantine-color-scheme="light"] {
--bh-line: #000000;
--bh-plane: #f4f1ea;
--bh-ink: #000000;
--bh-ink-contrast: #f4f1ea;
--bh-focus: var(--mantine-primary-color-filled);
--bh-tray: #eae6db;
--mantine-color-body: #f4f1ea;
--mantine-color-text: #000000;
--mantine-color-bright: #000000;
--mantine-color-dimmed: #52504a;
--mantine-color-placeholder: #6f6c64;
--mantine-color-anchor: #0050b3;
--mantine-color-error: #c01e1c;
--mantine-color-default: #f4f1ea;
--mantine-color-default-hover: #eae6db;
--mantine-color-default-color: #000000;
--mantine-color-default-border: #000000;
--mantine-color-red-filled: #d42220;
--mantine-color-red-filled-hover: #b81d1b;
--mantine-color-red-contrast: #ffffff;
--mantine-color-red-light: rgba(212, 34, 32, 0.1);
--mantine-color-red-light-hover: rgba(212, 34, 32, 0.16);
--mantine-color-red-light-color: #b81d1b;
--mantine-color-red-outline: #c01e1c;
--mantine-color-red-outline-hover: rgba(212, 34, 32, 0.1);
--mantine-color-red-text: #c01e1c;
--mantine-color-blue-filled: #0050b3;
--mantine-color-blue-filled-hover: #00479f;
--mantine-color-blue-contrast: #ffffff;
--mantine-color-blue-light: rgba(0, 80, 179, 0.1);
--mantine-color-blue-light-hover: rgba(0, 80, 179, 0.16);
--mantine-color-blue-light-color: #0050b3;
--mantine-color-blue-outline: #0050b3;
--mantine-color-blue-outline-hover: rgba(0, 80, 179, 0.1);
--mantine-color-blue-text: #0050b3;
--mantine-color-yellow-filled: #f4cc00;
--mantine-color-yellow-filled-hover: #dcb800;
--mantine-color-yellow-contrast: #000000;
--mantine-color-yellow-light: rgba(244, 204, 0, 0.16);
--mantine-color-yellow-light-hover: rgba(244, 204, 0, 0.26);
--mantine-color-yellow-light-color: #8a7300;
--mantine-color-yellow-outline: #8a7300;
--mantine-color-yellow-outline-hover: rgba(244, 204, 0, 0.16);
--mantine-color-yellow-text: #8a7300;
--mantine-color-green-filled: #0f7f36;
--mantine-color-green-filled-hover: #0d7230;
--mantine-color-green-contrast: #ffffff;
--mantine-color-green-light: rgba(15, 127, 54, 0.1);
--mantine-color-green-light-hover: rgba(15, 127, 54, 0.16);
--mantine-color-green-light-color: #0b6229;
--mantine-color-green-outline: #0d7230;
--mantine-color-green-outline-hover: rgba(15, 127, 54, 0.1);
--mantine-color-green-text: #0d7230;
--mantine-color-grape-filled: #6a2c91;
--mantine-color-grape-filled-hover: #5f2782;
--mantine-color-grape-contrast: #ffffff;
--mantine-color-grape-light: rgba(106, 44, 145, 0.1);
--mantine-color-grape-light-hover: rgba(106, 44, 145, 0.16);
--mantine-color-grape-light-color: #5f2782;
--mantine-color-grape-outline: #5f2782;
--mantine-color-grape-outline-hover: rgba(106, 44, 145, 0.1);
--mantine-color-grape-text: #5f2782;
--mantine-color-orange-filled: #e86a00;
--mantine-color-orange-filled-hover: #d05f00;
--mantine-color-orange-contrast: #000000;
--mantine-color-orange-light: rgba(232, 106, 0, 0.12);
--mantine-color-orange-light-hover: rgba(232, 106, 0, 0.18);
--mantine-color-orange-light-color: #b25100;
--mantine-color-orange-outline: #b25100;
--mantine-color-orange-outline-hover: rgba(232, 106, 0, 0.12);
--mantine-color-orange-text: #b25100;
--mantine-color-pink-filled: #d6246e;
--mantine-color-pink-filled-hover: #c02063;
--mantine-color-pink-contrast: #ffffff;
--mantine-color-pink-light: rgba(214, 36, 110, 0.1);
--mantine-color-pink-light-hover: rgba(214, 36, 110, 0.16);
--mantine-color-pink-light-color: #c02063;
--mantine-color-pink-outline: #c02063;
--mantine-color-pink-outline-hover: rgba(214, 36, 110, 0.1);
--mantine-color-pink-text: #c02063;
--mantine-color-lime-filled: #8fae00;
--mantine-color-lime-filled-hover: #819c00;
--mantine-color-lime-contrast: #000000;
--mantine-color-lime-light: rgba(143, 174, 0, 0.14);
--mantine-color-lime-light-hover: rgba(143, 174, 0, 0.22);
--mantine-color-lime-light-color: #5c7000;
--mantine-color-lime-outline: #5c7000;
--mantine-color-lime-outline-hover: rgba(143, 174, 0, 0.14);
--mantine-color-lime-text: #5c7000;
/* app tokens */
--flxn-gold: #d9b500;
--flxn-silver: #8d8a80;
--flxn-bronze: #a05a2c;
--flxn-toast-success: #2b8a3e;
--flxn-toast-error: #c01e1c;
--flxn-podium-1-bg: #f4cc00;
--flxn-podium-1-border: #000000;
--flxn-podium-1-label: #000000;
--flxn-podium-2-bg: #c9dcf4;
--flxn-podium-2-border: #000000;
--flxn-podium-2-label: #003d88;
--flxn-podium-3-bg: #f6d0cf;
--flxn-podium-3-border: #000000;
--flxn-podium-3-label: #a51a18;
--flxn-showcase-bg: #faf8f2;
--flxn-overlay: rgba(0, 0, 0, 0.5);
--flxn-dotgrid-color: rgba(0, 0, 0, 0.18);
--flxn-pop-shadow: rgba(0, 0, 0, 0);
}
[data-theme="bauhaus"][data-theme][data-mantine-color-scheme="dark"] {
--bh-line: #f4f1ea;
--bh-plane: #0d0d0d;
--bh-ink: #f4f1ea;
--bh-ink-contrast: #000000;
--bh-focus: var(--mantine-primary-color-4);
--bh-tray: #1d1c19;
--mantine-color-body: #0d0d0d;
--mantine-color-text: #f4f1ea;
--mantine-color-bright: #ffffff;
--mantine-color-dimmed: #a5a196;
--mantine-color-placeholder: #8b877c;
--mantine-color-anchor: #7fb0e8;
--mantine-color-error: #ef6b64;
--mantine-color-default: #0d0d0d;
--mantine-color-default-hover: #1d1c19;
--mantine-color-default-color: #f4f1ea;
--mantine-color-default-border: #f4f1ea;
--mantine-color-red-filled: #d42220;
--mantine-color-red-filled-hover: #b81d1b;
--mantine-color-red-contrast: #ffffff;
--mantine-color-red-light: rgba(229, 83, 79, 0.16);
--mantine-color-red-light-hover: rgba(229, 83, 79, 0.24);
--mantine-color-red-light-color: #f08984;
--mantine-color-red-outline: #ef6b64;
--mantine-color-red-outline-hover: rgba(229, 83, 79, 0.16);
--mantine-color-red-text: #ef6b64;
--mantine-color-blue-filled: #0050b3;
--mantine-color-blue-filled-hover: #00479f;
--mantine-color-blue-contrast: #ffffff;
--mantine-color-blue-light: rgba(107, 158, 221, 0.16);
--mantine-color-blue-light-hover: rgba(107, 158, 221, 0.24);
--mantine-color-blue-light-color: #9dc0ea;
--mantine-color-blue-outline: #7fb0e8;
--mantine-color-blue-outline-hover: rgba(107, 158, 221, 0.16);
--mantine-color-blue-text: #7fb0e8;
--mantine-color-yellow-filled: #f4cc00;
--mantine-color-yellow-filled-hover: #dcb800;
--mantine-color-yellow-contrast: #000000;
--mantine-color-yellow-light: rgba(244, 204, 0, 0.14);
--mantine-color-yellow-light-hover: rgba(244, 204, 0, 0.22);
--mantine-color-yellow-light-color: #f4cc00;
--mantine-color-yellow-outline: #f4cc00;
--mantine-color-yellow-outline-hover: rgba(244, 204, 0, 0.14);
--mantine-color-yellow-text: #f4cc00;
--mantine-color-green-filled: #0f7f36;
--mantine-color-green-filled-hover: #0d7230;
--mantine-color-green-contrast: #ffffff;
--mantine-color-green-light: rgba(100, 177, 126, 0.16);
--mantine-color-green-light-hover: rgba(100, 177, 126, 0.24);
--mantine-color-green-light-color: #97cca9;
--mantine-color-green-outline: #64b17e;
--mantine-color-green-outline-hover: rgba(100, 177, 126, 0.16);
--mantine-color-green-text: #64b17e;
--mantine-color-grape-filled: #6a2c91;
--mantine-color-grape-filled-hover: #5f2782;
--mantine-color-grape-contrast: #ffffff;
--mantine-color-grape-light: rgba(168, 124, 196, 0.16);
--mantine-color-grape-light-hover: rgba(168, 124, 196, 0.24);
--mantine-color-grape-light-color: #c6a8da;
--mantine-color-grape-outline: #a87cc4;
--mantine-color-grape-outline-hover: rgba(168, 124, 196, 0.16);
--mantine-color-grape-text: #a87cc4;
--mantine-color-orange-filled: #e86a00;
--mantine-color-orange-filled-hover: #d05f00;
--mantine-color-orange-contrast: #000000;
--mantine-color-orange-light: rgba(241, 163, 85, 0.16);
--mantine-color-orange-light-hover: rgba(241, 163, 85, 0.24);
--mantine-color-orange-light-color: #f6c28d;
--mantine-color-orange-outline: #f1a355;
--mantine-color-orange-outline-hover: rgba(241, 163, 85, 0.16);
--mantine-color-orange-text: #f1a355;
--mantine-color-pink-filled: #d6246e;
--mantine-color-pink-filled-hover: #c02063;
--mantine-color-pink-contrast: #ffffff;
--mantine-color-pink-light: rgba(230, 117, 162, 0.16);
--mantine-color-pink-light-hover: rgba(230, 117, 162, 0.24);
--mantine-color-pink-light-color: #efa5c3;
--mantine-color-pink-outline: #e675a2;
--mantine-color-pink-outline-hover: rgba(230, 117, 162, 0.16);
--mantine-color-pink-text: #e675a2;
--mantine-color-lime-filled: #8fae00;
--mantine-color-lime-filled-hover: #819c00;
--mantine-color-lime-contrast: #000000;
--mantine-color-lime-light: rgba(165, 189, 36, 0.16);
--mantine-color-lime-light-hover: rgba(165, 189, 36, 0.24);
--mantine-color-lime-light-color: #bccd55;
--mantine-color-lime-outline: #a5bd24;
--mantine-color-lime-outline-hover: rgba(165, 189, 36, 0.16);
--mantine-color-lime-text: #a5bd24;
/* app tokens */
--flxn-gold: #f4cc00;
--flxn-silver: #a5a196;
--flxn-bronze: #d08048;
--flxn-toast-success: #51cf66;
--flxn-toast-error: #ef6b64;
--flxn-podium-1-bg: #332b00;
--flxn-podium-1-border: #f4cc00;
--flxn-podium-1-label: #f4cc00;
--flxn-podium-2-bg: #001a38;
--flxn-podium-2-border: #6b9edd;
--flxn-podium-2-label: #9dc0ea;
--flxn-podium-3-bg: #380b0a;
--flxn-podium-3-border: #e5534f;
--flxn-podium-3-label: #f08984;
--flxn-showcase-bg: #000000;
--flxn-overlay: rgba(0, 0, 0, 0.65);
--flxn-dotgrid-color: rgba(244, 241, 234, 0.18);
--flxn-pop-shadow: rgba(0, 0, 0, 0);
}
/* Without JS autoContrast, Mantine pins --mantine-primary-color-contrast to
white, which is unreadable on the light fills (yellow/orange/lime).
Recompute it from the accent fill's own lightness: below 0.62 OKLCH-L
gets white text, above gets black — matches the per-color -contrast
values declared above for all eight accents. */
@supports (color: oklch(from red calc((0.5 - l) * infinity) 0 h)) {
[data-theme="bauhaus"][data-theme][data-mantine-color-scheme] {
--mantine-primary-color-contrast: oklch(
from var(--mantine-primary-color-filled) calc((0.62 - l) * infinity) 0 h
);
}
}
/* ---------- 3. Structure: planes bounded by rules, never shadows ---------- */
[data-theme="bauhaus"] .mantine-Paper-root {
background-color: var(--bh-plane);
border: 2px solid var(--bh-line);
border-radius: 0;
box-shadow: none;
}
[data-theme="bauhaus"] .mantine-AppShell-root {
--app-shell-border-color: var(--bh-line);
}
[data-theme="bauhaus"] .mantine-AppShell-header {
background-color: var(--mantine-color-body);
border-bottom: 2px solid var(--bh-line);
}
[data-theme="bauhaus"] .mantine-AppShell-navbar {
background-color: var(--mantine-color-body);
border-inline-end: 2px solid var(--bh-line);
}
[data-theme="bauhaus"] .mantine-Divider-root {
--divider-color: var(--bh-line);
--divider-size: 3px;
}
/* ---------- 4. Type: uppercase, widely-tracked functional labels ---------- */
[data-theme="bauhaus"] .mantine-Button-root {
border-radius: 0;
box-shadow: none;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
}
[data-theme="bauhaus"]
.mantine-Button-root:where(
[data-variant="filled"],
[data-variant="default"],
[data-variant="outline"],
[data-variant="light"],
[data-variant="white"]
) {
border: 2px solid var(--bh-line);
}
[data-theme="bauhaus"] .mantine-ActionIcon-root {
border-radius: 0;
box-shadow: none;
}
[data-theme="bauhaus"]
.mantine-ActionIcon-root:where(
[data-variant="filled"],
[data-variant="default"],
[data-variant="outline"],
[data-variant="light"],
[data-variant="white"]
) {
border: 2px solid var(--bh-line);
}
[data-theme="bauhaus"] .mantine-InputWrapper-label {
font-size: var(--mantine-font-size-xs);
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
[data-theme="bauhaus"] .mantine-Badge-root {
border-radius: 0;
font-weight: 700;
letter-spacing: 0.08em;
}
/* ---------- 5. Inputs ---------- */
[data-theme="bauhaus"] .mantine-Input-input {
background-color: var(--bh-plane);
border: 2px solid var(--bh-line);
border-radius: 0;
}
[data-theme="bauhaus"] .mantine-Input-input:focus,
[data-theme="bauhaus"] .mantine-Input-input:focus-within {
border-color: var(--bh-focus);
outline: none;
}
[data-theme="bauhaus"] .mantine-Checkbox-input {
border: 2px solid var(--bh-line);
border-radius: 0;
}
[data-theme="bauhaus"] .mantine-Radio-radio {
border: 2px solid var(--bh-line);
}
[data-theme="bauhaus"] .mantine-Switch-track {
border: 2px solid var(--bh-line);
border-radius: 0;
}
[data-theme="bauhaus"] .mantine-Switch-thumb {
border: 2px solid var(--bh-line);
border-radius: 0;
}
/* ---------- 6. Tabs & SegmentedControl: black-line constructions ---------- */
[data-theme="bauhaus"] .mantine-Tabs-list::before {
border-color: var(--bh-line);
border-bottom-width: 2px;
}
[data-theme="bauhaus"] .mantine-Tabs-tab {
border-radius: 0;
border-width: 2px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
}
[data-theme="bauhaus"] .mantine-Tabs-tab[data-active] {
background-color: var(--mantine-primary-color-filled);
border-color: var(--bh-line);
color: var(--mantine-primary-color-contrast);
}
[data-theme="bauhaus"] .mantine-SegmentedControl-root {
background-color: transparent;
border: 2px solid var(--bh-line);
border-radius: 0;
padding: 0;
}
[data-theme="bauhaus"] .mantine-SegmentedControl-indicator {
background-color: var(--mantine-primary-color-filled);
border-radius: 0;
box-shadow: none;
}
[data-theme="bauhaus"] .mantine-SegmentedControl-label {
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
}
[data-theme="bauhaus"] .mantine-SegmentedControl-label[data-active],
[data-theme="bauhaus"]
.mantine-SegmentedControl-control[data-active]
.mantine-SegmentedControl-label {
color: var(--mantine-primary-color-contrast);
}
/* Accent-fill on-colors: Mantine hardcodes white text on filled variants,
selected combobox options and check glyphs; pair them with the accent's
contrast color so yellow/orange/lime accents stay legible. */
[data-theme="bauhaus"] .mantine-Button-root[data-variant="filled"],
[data-theme="bauhaus"] .mantine-Button-root:not([data-variant]),
[data-theme="bauhaus"] .mantine-ActionIcon-root[data-variant="filled"],
[data-theme="bauhaus"] .mantine-ActionIcon-root:not([data-variant]),
[data-theme="bauhaus"] .mantine-Badge-root[data-variant="filled"],
[data-theme="bauhaus"] .mantine-Badge-root:not([data-variant]),
[data-theme="bauhaus"] .mantine-ThemeIcon-root[data-variant="filled"],
[data-theme="bauhaus"] .mantine-ThemeIcon-root:not([data-variant]) {
color: var(--mantine-primary-color-contrast);
}
[data-theme="bauhaus"] [data-combobox-selected] {
color: var(--mantine-primary-color-contrast);
}
[data-theme="bauhaus"] .mantine-Checkbox-root {
--checkbox-icon-color: var(--mantine-primary-color-contrast);
}
[data-theme="bauhaus"] .mantine-Radio-root {
--radio-icon-color: var(--mantine-primary-color-contrast);
}
/* ---------- 7. Overlays & feedback surfaces ---------- */
[data-theme="bauhaus"] .mantine-Modal-content {
border: 2px solid var(--bh-line);
border-radius: 0;
box-shadow: none;
}
[data-theme="bauhaus"] .mantine-Modal-header {
border-bottom: 2px solid var(--bh-line);
}
[data-theme="bauhaus"] .mantine-Popover-dropdown {
border: 2px solid var(--bh-line);
border-radius: 0;
box-shadow: none;
}
[data-theme="bauhaus"] .mantine-Tooltip-tooltip {
background-color: var(--bh-ink);
border-radius: 0;
color: var(--bh-ink-contrast);
}
[data-theme="bauhaus"] .mantine-Notification-root {
background-color: var(--bh-plane);
border: 2px solid var(--bh-line);
border-radius: 0;
box-shadow: none;
}
[data-theme="bauhaus"] .mantine-Alert-root {
border: 2px solid var(--bh-line);
border-radius: 0;
}
[data-theme="bauhaus"] .mantine-Progress-root {
background-color: transparent;
border: 2px solid var(--bh-line);
border-radius: 0;
}
[data-theme="bauhaus"] .mantine-Progress-section {
border-radius: 0;
}
[data-theme="bauhaus"] .mantine-Indicator-indicator {
border-radius: 0;
}
[data-theme="bauhaus"] .mantine-ThemeIcon-root {
border: 2px solid var(--bh-line);
border-radius: 0;
}
/* strict square avatars: frame and content square together */
[data-theme="bauhaus"] .mantine-Avatar-root {
border-radius: 0;
}
[data-theme="bauhaus"] .mantine-Avatar-image,
[data-theme="bauhaus"] .mantine-Avatar-placeholder {
border-radius: 0;
}
/* ---------- 8. App component hooks ---------- */
/* Tab strip: flat while resting (2px transparent rule keeps height stable);
docked gets the black under-rule. Active tab is a solid accent block. */
[data-theme="bauhaus"] .flxn-tabs {
border-bottom-width: 2px;
}
[data-theme="bauhaus"] .flxn-tabs[data-stuck] {
background-color: var(--mantine-color-body);
border-bottom-color: var(--bh-line);
}
[data-theme="bauhaus"] .flxn-tab {
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
border-radius: 0;
}
[data-theme="bauhaus"] .flxn-tab[data-active] {
background-color: var(--mantine-primary-color-filled);
color: var(--flxn-on-accent);
font-weight: 700;
}
[data-theme="bauhaus"] .flxn-tabs-indicator {
border-bottom: none;
}
/* Nav grid tiles: 2px-ruled flat planes */
[data-theme="bauhaus"] .flxn-tile {
background-color: var(--bh-plane);
border: 2px solid var(--bh-line);
border-radius: 0;
box-shadow: none;
}
[data-theme="bauhaus"] .flxn-tile-icon {
color: var(--mantine-primary-color-filled);
}
/* "See all" chip: flat, bordered, uppercase tracked */
[data-theme="bauhaus"] .flxn-see-all {
background-color: var(--bh-plane);
border: 2px solid var(--bh-line);
border-radius: 0;
color: var(--bh-ink);
font-size: var(--mantine-font-size-xs);
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
@media (hover: hover) {
[data-theme="bauhaus"] .flxn-see-all:hover {
background-color: var(--mantine-primary-color-light);
color: var(--bh-ink);
}
}
/* Match card + reaction tray: one construction, same 2px rules, flat fill */
[data-theme="bauhaus"] .flxn-match-card {
background-color: var(--bh-plane);
border: 2px solid var(--bh-line);
border-radius: 0;
box-shadow: none;
}
[data-theme="bauhaus"] .flxn-reaction-row {
background-color: var(--bh-tray);
border: 2px solid var(--bh-line);
border-radius: 0;
}
/* ---------- 9. Geometric heading marks: circle / square / triangle ---------- */
[data-theme="bauhaus"] h1.mantine-Title-root::before,
[data-theme="bauhaus"] h2.mantine-Title-root::before,
[data-theme="bauhaus"] h3.mantine-Title-root::before,
[data-theme="bauhaus"] h4.mantine-Title-root::before,
[data-theme="bauhaus"] h5.mantine-Title-root::before,
[data-theme="bauhaus"] h6.mantine-Title-root::before {
content: "";
display: inline-block;
width: 0.5em;
height: 0.5em;
margin-inline-end: 0.45em;
vertical-align: 0.06em;
}
[data-theme="bauhaus"] h1.mantine-Title-root::before {
background-color: var(--bh-blue);
border-radius: 50%;
}
[data-theme="bauhaus"] h2.mantine-Title-root::before {
background-color: var(--bh-red);
}
[data-theme="bauhaus"] h3.mantine-Title-root::before {
background-color: var(--bh-yellow);
clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
[data-theme="bauhaus"] h4.mantine-Title-root::before {
background-color: var(--bh-red);
border-radius: 50%;
}
[data-theme="bauhaus"] h5.mantine-Title-root::before {
background-color: var(--bh-yellow);
}
[data-theme="bauhaus"] h6.mantine-Title-root::before {
background-color: var(--bh-blue);
clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
/* ---------- 10. Press feedback: mechanical, not springy ---------- */
[data-theme="bauhaus"]
.flxn-press:active:not(:disabled):not([data-disabled]):not([data-loading]) {
transform: translateY(2px);
}
@media (prefers-reduced-motion: reduce) {
[data-theme="bauhaus"]
.flxn-press:active:not(:disabled):not([data-disabled]):not([data-loading]) {
transform: none;
}
}
+30
View File
@@ -0,0 +1,30 @@
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
const manifest: ThemeManifest = {
id: "brutal",
label: msg`Brutal`,
colorSchemes: "both",
fontLinks: [
"https://fonts.googleapis.com/css2?family=Archivo+Black&family=Space+Grotesk:wght@400;500;600;700&display=swap",
],
chromeColors: {
light: { base: "#f5f2ea", dimmed: "#e6e1d1" },
dark: { base: "#111111", dimmed: "#000000" },
},
mantine: {
// Light acid accents (yellow/lime) must flip filled text to dark ink.
autoContrast: true,
defaultRadius: 0,
fontFamily: '"Space Grotesk", "Inter", sans-serif',
headings: {
fontFamily: '"Archivo Black", "League Spartan", sans-serif',
fontWeight: "400",
},
components: {
Loader: { defaultProps: { type: "bars" } },
},
},
};
export default manifest;
+645
View File
@@ -0,0 +1,645 @@
/* Neobrutalist theme: flat surfaces, zero radius, 3px ink borders, hard
offset shadows. Ink is #000 in light and pale off-white in dark. Acid
yellow is pure decoration (selection, hover flashes, podium gold); all
accent/active/focus styling follows the user's chosen primary color via
loud retuned ramps. */
/* ---------- 1. Scheme-independent tokens ---------- */
[data-theme="brutal"][data-theme] {
--brutal-yellow: #ffde59;
--mantine-font-family: "Space Grotesk", "Inter", sans-serif;
--mantine-font-family-headings: "Archivo Black", "League Spartan", sans-serif;
--mantine-heading-font-weight: 400;
--mantine-h1-font-weight: 400;
--mantine-h2-font-weight: 400;
--mantine-h3-font-weight: 400;
--mantine-h4-font-weight: 400;
--mantine-h5-font-weight: 400;
--mantine-h6-font-weight: 400;
--mantine-radius-default: 0;
--mantine-radius-xs: 0;
--mantine-radius-sm: 0;
--mantine-radius-md: 0;
--mantine-radius-lg: 0;
--mantine-radius-xl: 0;
--mantine-shadow-xs: 3px 3px 0 0 var(--brutal-ink, #000);
--mantine-shadow-sm: 5px 5px 0 0 var(--brutal-ink, #000);
--mantine-shadow-md: 6px 6px 0 0 var(--brutal-ink, #000);
--mantine-shadow-lg: 8px 8px 0 0 var(--brutal-ink, #000);
--mantine-shadow-xl: 10px 10px 0 0 var(--brutal-ink, #000);
--flxn-gold: var(--brutal-yellow);
--flxn-pop-shadow: var(--brutal-ink, #000);
--flxn-avatar-radius: 0;
--flxn-avatar-border: 3px solid var(--brutal-ink, #000);
--flxn-badge-radius: 0;
/* Accent ramps retuned flat and loud, borderline fluorescent. Shade 6
(light filled) and shade 8 (dark filled) keep the same light/dark
polarity as Mantine's defaults so autoContrast text stays legible. */
--mantine-color-blue-0: #e6eeff;
--mantine-color-blue-1: #c2d4ff;
--mantine-color-blue-2: #99b8ff;
--mantine-color-blue-3: #5c8aff;
--mantine-color-blue-4: #2e66ff;
--mantine-color-blue-5: #1a5eff;
--mantine-color-blue-6: #0047ff;
--mantine-color-blue-7: #003ae6;
--mantine-color-blue-8: #0033cc;
--mantine-color-blue-9: #002699;
--mantine-color-red-0: #ffe8e8;
--mantine-color-red-1: #ffc9c9;
--mantine-color-red-2: #ffa8a8;
--mantine-color-red-3: #ff7a7a;
--mantine-color-red-4: #ff5252;
--mantine-color-red-5: #ff2b2b;
--mantine-color-red-6: #f01e1e;
--mantine-color-red-7: #d61414;
--mantine-color-red-8: #c00d0d;
--mantine-color-red-9: #8f0707;
--mantine-color-green-0: #e4ffe9;
--mantine-color-green-1: #c0ffcf;
--mantine-color-green-2: #8fffab;
--mantine-color-green-3: #4dff85;
--mantine-color-green-4: #1af06a;
--mantine-color-green-5: #0ae862;
--mantine-color-green-6: #00e05c;
--mantine-color-green-7: #00b34a;
--mantine-color-green-8: #0a8a3c;
--mantine-color-green-9: #056629;
--mantine-color-yellow-0: #fff9db;
--mantine-color-yellow-1: #fff0a8;
--mantine-color-yellow-2: #ffe770;
--mantine-color-yellow-3: #ffde59;
--mantine-color-yellow-4: #ffd83d;
--mantine-color-yellow-5: #ffd61f;
--mantine-color-yellow-6: #ffd400;
--mantine-color-yellow-7: #f5c400;
--mantine-color-yellow-8: #f0b800;
--mantine-color-yellow-9: #cc9900;
--mantine-color-grape-0: #fbe7ff;
--mantine-color-grape-1: #f3c6ff;
--mantine-color-grape-2: #ea9dff;
--mantine-color-grape-3: #dd6bff;
--mantine-color-grape-4: #cf47f2;
--mantine-color-grape-5: #c437e9;
--mantine-color-grape-6: #b829e0;
--mantine-color-grape-7: #a21ec7;
--mantine-color-grape-8: #8c14ad;
--mantine-color-grape-9: #6b0c85;
--mantine-color-orange-0: #fff0e0;
--mantine-color-orange-1: #ffddb8;
--mantine-color-orange-2: #ffc78a;
--mantine-color-orange-3: #ffab52;
--mantine-color-orange-4: #ff9a2e;
--mantine-color-orange-5: #ff9114;
--mantine-color-orange-6: #ff8800;
--mantine-color-orange-7: #f07300;
--mantine-color-orange-8: #c95400;
--mantine-color-orange-9: #9a4000;
--mantine-color-pink-0: #ffe6f1;
--mantine-color-pink-1: #ffc2dd;
--mantine-color-pink-2: #ff99c6;
--mantine-color-pink-3: #ff66aa;
--mantine-color-pink-4: #ff4796;
--mantine-color-pink-5: #ff2e88;
--mantine-color-pink-6: #f21578;
--mantine-color-pink-7: #d80d67;
--mantine-color-pink-8: #bd0857;
--mantine-color-pink-9: #8f0642;
--mantine-color-lime-0: #f6ffdb;
--mantine-color-lime-1: #eaffad;
--mantine-color-lime-2: #dcff7a;
--mantine-color-lime-3: #c8ff42;
--mantine-color-lime-4: #b8fa1e;
--mantine-color-lime-5: #aff50f;
--mantine-color-lime-6: #a6f000;
--mantine-color-lime-7: #99e000;
--mantine-color-lime-8: #8ccc00;
--mantine-color-lime-9: #6fa300;
}
/* ---------- 2. Per-scheme tokens ---------- */
[data-theme="brutal"][data-theme][data-mantine-color-scheme="light"] {
--brutal-ink: #000000;
--brutal-focus: var(--mantine-primary-color-filled);
--mantine-color-body: #f5f2ea;
--mantine-color-text: #111111;
--mantine-color-bright: #000000;
--mantine-color-dimmed: #575145;
--mantine-color-placeholder: #6b6557;
/* shade 8, not 6: fluorescent accents (yellow/lime/green) are unreadable
as link text on the light body at shade 6 */
--mantine-color-anchor: var(--mantine-primary-color-8);
--mantine-color-error: #d90429;
--mantine-color-default: #f5f2ea;
--mantine-color-default-hover: var(--brutal-yellow);
--mantine-color-default-color: #111111;
--mantine-color-default-border: #000000;
--flxn-silver: #767066;
--flxn-bronze: #b45f06;
--flxn-toast-success: #087f2c;
--flxn-toast-error: #d90429;
--flxn-podium-1-bg: var(--brutal-yellow);
--flxn-podium-1-border: #000000;
--flxn-podium-1-label: #111111;
--flxn-podium-2-bg: #d9d4c6;
--flxn-podium-2-border: #000000;
--flxn-podium-2-label: #111111;
--flxn-podium-3-bg: #ff9d5c;
--flxn-podium-3-border: #000000;
--flxn-podium-3-label: #111111;
--flxn-showcase-bg: #fffdf5;
--flxn-overlay: rgba(0, 0, 0, 0.5);
--flxn-dotgrid-color: rgba(0, 0, 0, 0.22);
}
[data-theme="brutal"][data-theme][data-mantine-color-scheme="dark"] {
--brutal-ink: #ece7da;
--brutal-focus: var(--mantine-primary-color-4);
--mantine-color-body: #111111;
--mantine-color-text: #f5f2ea;
--mantine-color-bright: #ffffff;
--mantine-color-dimmed: #a39d8c;
--mantine-color-placeholder: #9a947f;
--mantine-color-anchor: var(--mantine-primary-color-4);
--mantine-color-error: #ff4d6d;
--mantine-color-default: #111111;
--mantine-color-default-hover: #26241c;
--mantine-color-default-color: #f5f2ea;
--mantine-color-default-border: #ece7da;
--flxn-silver: #b8b2a4;
--flxn-bronze: #e08e45;
--flxn-toast-success: #3ddc73;
--flxn-toast-error: #ff4d6d;
--flxn-podium-1-bg: #292110;
--flxn-podium-1-border: var(--brutal-yellow);
--flxn-podium-1-label: var(--brutal-yellow);
--flxn-podium-2-bg: #21201b;
--flxn-podium-2-border: #b8b2a4;
--flxn-podium-2-label: #cfc9bb;
--flxn-podium-3-bg: #271a10;
--flxn-podium-3-border: #e08e45;
--flxn-podium-3-label: #ffb47a;
--flxn-showcase-bg: #191813;
--flxn-overlay: rgba(0, 0, 0, 0.65);
--flxn-dotgrid-color: rgba(236, 231, 218, 0.16);
--flxn-pop-shadow: rgba(236, 231, 218, 0.55);
}
/* ---------- 3. Type ---------- */
[data-theme="brutal"] :is(h1, h2, h3, h4, h5, h6) {
text-transform: uppercase;
letter-spacing: -0.02em;
}
[data-theme="brutal"] .mantine-Title-root {
text-transform: uppercase;
}
[data-theme="brutal"] *::selection {
background: var(--brutal-yellow);
color: #111111;
}
[data-theme="brutal"] *:focus-visible:not(.mantine-Input-input) {
outline: 3px solid var(--brutal-focus);
outline-offset: 2px;
}
[data-theme="brutal"] .mantine-Anchor-root {
font-weight: 600;
text-decoration-thickness: 2px;
text-underline-offset: 2px;
}
/* ---------- 4. Press feedback: slide into the shadow ---------- */
[data-theme="brutal"] .flxn-press {
transition:
transform 100ms cubic-bezier(0.32, 0.72, 0, 1),
box-shadow 100ms cubic-bezier(0.32, 0.72, 0, 1);
}
[data-theme="brutal"] .flxn-press:active:not(:disabled):not([data-disabled]):not([data-loading]) {
transform: translate(4px, 4px);
box-shadow: 0 0 0 0 var(--brutal-ink);
}
@media (prefers-reduced-motion: reduce) {
[data-theme="brutal"] .flxn-press,
[data-theme="brutal"] .flxn-press:active:not(:disabled):not([data-disabled]):not([data-loading]) {
transition: none;
transform: none;
}
}
/* ---------- 5. Buttons & ActionIcons ---------- */
[data-theme="brutal"] .mantine-Button-root {
border: 3px solid var(--brutal-ink);
border-radius: 0;
box-shadow: 4px 4px 0 0 var(--brutal-ink);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.03em;
}
[data-theme="brutal"] .mantine-Button-root[data-variant="gradient"] {
background: var(--mantine-primary-color-filled);
}
[data-theme="brutal"] .mantine-ActionIcon-root {
border: 2px solid var(--brutal-ink);
border-radius: 0;
box-shadow: 3px 3px 0 0 var(--brutal-ink);
}
[data-theme="brutal"] .mantine-Button-root[data-variant="subtle"],
[data-theme="brutal"] .mantine-Button-root[data-variant="transparent"],
[data-theme="brutal"] .mantine-ActionIcon-root[data-variant="subtle"],
[data-theme="brutal"] .mantine-ActionIcon-root[data-variant="transparent"] {
border-color: transparent;
box-shadow: none;
}
[data-theme="brutal"] .mantine-Button-root:disabled,
[data-theme="brutal"] .mantine-Button-root[data-disabled],
[data-theme="brutal"] .mantine-ActionIcon-root:disabled,
[data-theme="brutal"] .mantine-ActionIcon-root[data-disabled] {
box-shadow: none;
}
[data-theme="brutal"] .mantine-CloseButton-root {
border-radius: 0;
}
/* ---------- 6. Surfaces: Paper, Card, Modal, Drawer, dropdowns ---------- */
[data-theme="brutal"] .mantine-Paper-root {
border: 3px solid var(--brutal-ink);
border-radius: 0;
}
[data-theme="brutal"] .mantine-Card-root {
box-shadow: var(--mantine-shadow-sm);
background: var(--flxn-showcase-bg);
}
[data-theme="brutal"] .mantine-Modal-content,
[data-theme="brutal"] .mantine-Drawer-content {
border: 3px solid var(--brutal-ink);
border-radius: 0;
box-shadow: var(--mantine-shadow-lg);
}
[data-theme="brutal"] .mantine-Modal-header {
background: var(--mantine-color-body);
border-bottom: 3px solid var(--brutal-ink);
}
[data-theme="brutal"] .mantine-Modal-title {
font-family: var(--mantine-font-family-headings);
text-transform: uppercase;
font-size: var(--mantine-font-size-lg);
}
[data-theme="brutal"] .mantine-Popover-dropdown,
[data-theme="brutal"] .mantine-Combobox-dropdown,
[data-theme="brutal"] .mantine-Menu-dropdown {
border: 3px solid var(--brutal-ink);
border-radius: 0;
box-shadow: var(--mantine-shadow-md);
}
[data-theme="brutal"] .mantine-Menu-item,
[data-theme="brutal"] .mantine-Combobox-option {
border-radius: 0;
font-weight: 600;
}
/* Hover flash stays decorative acid yellow; the SELECTED option is the
accent and follows the user's primary color. */
[data-theme="brutal"] .mantine-Menu-item:hover,
[data-theme="brutal"] .mantine-Menu-item[data-hovered],
[data-theme="brutal"] .mantine-Combobox-option:hover {
background: var(--brutal-yellow);
color: #111111;
}
[data-theme="brutal"] .mantine-Combobox-option[data-combobox-selected] {
background: var(--mantine-primary-color-filled);
color: var(--mantine-primary-color-contrast);
}
[data-theme="brutal"] .mantine-Tooltip-tooltip {
border-radius: 0;
background: var(--brutal-ink);
color: var(--mantine-color-body);
font-weight: 600;
}
/* ---------- 7. Inputs ---------- */
[data-theme="brutal"] .mantine-Input-input {
border-width: 3px;
border-style: solid;
border-color: var(--brutal-ink);
border-radius: 0;
background: var(--mantine-color-body);
font-weight: 500;
}
[data-theme="brutal"] .mantine-Input-input:focus,
[data-theme="brutal"] .mantine-Input-input:focus-within {
outline: none;
border-color: var(--brutal-ink);
box-shadow: 4px 4px 0 0 var(--brutal-ink);
}
[data-theme="brutal"] .mantine-Input-input[data-error] {
border-color: var(--mantine-color-error);
}
[data-theme="brutal"] :is(
.mantine-TextInput-label,
.mantine-Textarea-label,
.mantine-NumberInput-label,
.mantine-PasswordInput-label,
.mantine-Select-label,
.mantine-Autocomplete-label,
.mantine-MultiSelect-label,
.mantine-DateTimePicker-label,
.mantine-DatePickerInput-label,
.mantine-PinInput-label
) {
text-transform: uppercase;
font-weight: 700;
letter-spacing: 0.04em;
}
/* ---------- 8. Badges, indicators, avatars, icons ---------- */
[data-theme="brutal"] .mantine-Badge-root {
border: 2px solid var(--brutal-ink);
border-radius: 0;
box-shadow: 2px 2px 0 0 var(--brutal-ink);
font-weight: 800;
letter-spacing: 0.05em;
}
[data-theme="brutal"] .mantine-Indicator-indicator {
border-radius: 0;
border: 2px solid var(--brutal-ink);
}
[data-theme="brutal"] .mantine-ThemeIcon-root {
border-radius: 0;
border: 2px solid var(--brutal-ink);
}
[data-theme="brutal"] .mantine-Skeleton-root {
border-radius: 0;
}
/* ---------- 9. Tabs, NavLink, SegmentedControl: solid blocks ---------- */
[data-theme="brutal"] .mantine-Tabs-tab {
border: 3px solid transparent;
border-radius: 0;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.03em;
}
[data-theme="brutal"] .mantine-Tabs-tab[data-active] {
background: var(--mantine-primary-color-filled);
color: var(--mantine-primary-color-contrast);
border-color: var(--brutal-ink);
box-shadow: 3px 3px 0 0 var(--brutal-ink);
}
[data-theme="brutal"] .mantine-Tabs-list::before {
border-color: var(--brutal-ink);
}
[data-theme="brutal"] .mantine-NavLink-root {
border: 3px solid transparent;
border-radius: 0;
font-weight: 600;
}
[data-theme="brutal"] .mantine-NavLink-root[data-active] {
background: var(--mantine-primary-color-filled);
color: var(--mantine-primary-color-contrast);
border-color: var(--brutal-ink);
box-shadow: 4px 4px 0 0 var(--brutal-ink);
}
[data-theme="brutal"] .mantine-NavLink-root[data-active] :is(
.mantine-NavLink-label,
.mantine-NavLink-description,
.mantine-NavLink-section
) {
color: var(--mantine-primary-color-contrast);
}
[data-theme="brutal"] .mantine-SegmentedControl-root {
border: 3px solid var(--brutal-ink);
border-radius: 0;
background: var(--mantine-color-body);
}
[data-theme="brutal"] .mantine-SegmentedControl-control,
[data-theme="brutal"] .mantine-SegmentedControl-label {
border-radius: 0;
}
[data-theme="brutal"] .mantine-SegmentedControl-label {
text-transform: uppercase;
font-weight: 700;
}
[data-theme="brutal"] .mantine-SegmentedControl-indicator {
border-radius: 0;
background: var(--mantine-primary-color-filled);
border: 2px solid var(--brutal-ink);
box-shadow: none;
}
[data-theme="brutal"] .mantine-SegmentedControl-label[data-active],
[data-theme="brutal"] .mantine-SegmentedControl-control[data-active] .mantine-SegmentedControl-label {
color: var(--mantine-primary-color-contrast);
}
/* ---------- 10. Controls: Switch, Checkbox, Radio, Progress ---------- */
[data-theme="brutal"] .mantine-Switch-track {
border: 3px solid var(--brutal-ink);
border-radius: 0;
}
[data-theme="brutal"] .mantine-Switch-thumb {
border: 2px solid var(--brutal-ink);
border-radius: 0;
background: #ffffff;
}
[data-theme="brutal"] .mantine-Checkbox-input {
border: 3px solid var(--brutal-ink);
border-radius: 0;
}
[data-theme="brutal"] .mantine-Radio-radio {
border: 3px solid var(--brutal-ink);
}
[data-theme="brutal"] .mantine-Progress-root {
border: 2px solid var(--brutal-ink);
border-radius: 0;
background: var(--mantine-color-body);
}
[data-theme="brutal"] .mantine-Progress-section {
border-radius: 0;
}
/* ---------- 11. Feedback: Alert, Notification ---------- */
[data-theme="brutal"] .mantine-Alert-root {
border: 3px solid var(--brutal-ink);
border-radius: 0;
box-shadow: var(--mantine-shadow-xs);
}
[data-theme="brutal"] .mantine-Notification-root {
border: 3px solid var(--brutal-ink);
border-radius: 0;
box-shadow: var(--mantine-shadow-md);
background: var(--mantine-color-body);
}
[data-theme="brutal"] .mantine-Notification-title {
font-weight: 700;
text-transform: uppercase;
}
/* ---------- 12. Shell & structure ---------- */
[data-theme="brutal"] .mantine-AppShell-root {
--app-shell-border-color: var(--brutal-ink);
}
[data-theme="brutal"] .mantine-AppShell-header {
background: var(--mantine-color-body);
border-bottom: 3px solid var(--brutal-ink);
}
[data-theme="brutal"] .mantine-AppShell-navbar,
[data-theme="brutal"] .mantine-AppShell-aside {
background: var(--mantine-color-body);
}
[data-theme="brutal"] .mantine-Divider-root {
--divider-color: var(--brutal-ink);
--divider-size: 2px;
}
/* ---------- 13. App component hooks ---------- */
/* Tab strip: flat while resting; docking rules the ink baseline (width is
constant so pinning never shifts layout). Active tab is a solid accent
block with ink border + hard shadow — the floating underline indicator
is suppressed, the block IS the indicator. Constant transparent border
keeps tab boxes the same size in both active states. */
[data-theme="brutal"] .flxn-tabs {
border-bottom-width: 3px;
}
[data-theme="brutal"] .flxn-tabs[data-stuck] {
background-color: var(--mantine-color-body);
border-bottom-color: var(--brutal-ink);
}
[data-theme="brutal"] .flxn-tab.flxn-tab {
border: 3px solid transparent;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.03em;
}
[data-theme="brutal"] .flxn-tab.flxn-tab[data-active] {
background-color: var(--mantine-primary-color-filled);
color: var(--flxn-on-accent);
border-color: var(--brutal-ink);
box-shadow: 2px 2px 0 0 var(--brutal-ink);
font-weight: 700;
}
[data-theme="brutal"] .flxn-tabs-indicator.flxn-tabs-indicator {
border-bottom: none;
}
/* Nav grid tiles: same construction as cards — ink border, hard offset
shadow, flat accent glyph. Press sinks into the shadow. */
[data-theme="brutal"] .flxn-tile.flxn-tile {
border: 3px solid var(--brutal-ink);
border-radius: 0;
background-color: var(--flxn-showcase-bg);
box-shadow: var(--mantine-shadow-xs);
}
@media (prefers-reduced-motion: no-preference) {
[data-theme="brutal"] .flxn-tile.flxn-tile:active:not(:disabled) {
transform: translate(3px, 3px);
box-shadow: 0 0 0 0 var(--brutal-ink);
}
}
[data-theme="brutal"] .flxn-tile-icon.flxn-tile-icon {
color: var(--mantine-primary-color-filled);
}
/* "See all" chip: bordered block with a mini offset shadow; accent tint
comes through from hooks.css. */
[data-theme="brutal"] .flxn-see-all.flxn-see-all {
border: 2px solid var(--brutal-ink);
border-radius: 0;
box-shadow: 2px 2px 0 0 var(--brutal-ink);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.03em;
}
/* Match card + reaction tray: one construction — identical 3px ink
borders, card on the raised showcase surface, tray a body-toned step
below carrying the construction's hard shadow at its bottom edge. */
[data-theme="brutal"] .flxn-match-card.flxn-match-card {
border: 3px solid var(--brutal-ink);
border-radius: 0;
background-color: var(--flxn-showcase-bg);
}
[data-theme="brutal"] .flxn-reaction-row.flxn-reaction-row {
border: 3px solid var(--brutal-ink);
border-radius: 0;
background-color: var(--mantine-color-body);
box-shadow: var(--mantine-shadow-xs);
}
+165
View File
@@ -0,0 +1,165 @@
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
/* Instrument-panel indicator ramps — 1970s NASA/Braun control-deck lamps.
Declared in JS so Mantine regenerates every derived variant var (-filled,
-light rgba, autoContrast picks) from the retuned shades. Shade 6 is the
fill on the light beige enclosure (deep enough for white/ink autoContrast
to pick legibly); shade 5 is the fill on the powered-down dark deck
(brighter, reads as a lit lamp). */
const cassetteColors = {
// scope blue — telemetry readout
blue: [
"#eef3f7",
"#dae6ef",
"#bfd4e3",
"#9cbcd2",
"#74a0bd",
"#5586a8",
"#416d8d",
"#345a75",
"#29485e",
"#1f3748",
],
// alert red — warning lamp
red: [
"#fbecea",
"#f5d3cd",
"#edb3a8",
"#e28d7c",
"#d46c56",
"#c0503a",
"#a43f2d",
"#863323",
"#6a281b",
"#4e1e14",
],
// avocado LED — armed/ready lamp
green: [
"#eef4e6",
"#dbe8ca",
"#c2d8a5",
"#a4c47a",
"#86ac56",
"#6d9440",
"#587a34",
"#47632a",
"#384d21",
"#293818",
],
// amber — the classic panel indicator
yellow: [
"#fbf3dc",
"#f6e6b0",
"#f0d47c",
"#e9c04c",
"#e0ac2c",
"#cd9420",
"#ac7c19",
"#8c6414",
"#6d4d10",
"#4f380b",
],
// UV violet — auxiliary system lamp
grape: [
"#f2edf6",
"#e2d5ec",
"#ccb4dc",
"#b18fc9",
"#9570b3",
"#7c589c",
"#654682",
"#513869",
"#3f2b52",
"#2e1f3c",
],
// signal orange — caution lamp
orange: [
"#faede3",
"#f4d3bd",
"#ecb48c",
"#e2925c",
"#d47237",
"#bd5c22",
"#9e4b1b",
"#813d17",
"#653012",
"#4a230d",
],
// magenta signal — comms lamp
pink: [
"#f9ecf1",
"#f1d1dd",
"#e6adc2",
"#d885a2",
"#c8617f",
"#b44563",
"#9a3852",
"#7d2d42",
"#622334",
"#481a26",
],
// tracer chartreuse — tape-motion lamp
lime: [
"#f5f8de",
"#e9efad",
"#d9e378",
"#c5d24c",
"#adbb2f",
"#96a11f",
"#7c8618",
"#656e13",
"#4f560f",
"#3a3f0a",
],
// warm putty — light-scheme neutrals, seams, borders
gray: [
"#faf7f0",
"#f2ecdf",
"#e6ddc9",
"#d4c8ab",
"#bcac8b",
"#a08e6e",
"#837259",
"#665946",
"#4a4132",
"#302a20",
],
// powered-down deck — dark-scheme neutrals
dark: [
"#e9e3d8",
"#cfc6b6",
"#a89c88",
"#7c705f",
"#574c3f",
"#3a3128",
"#2a251f",
"#211e19",
"#191712",
"#100f0b",
],
} as const;
const manifest: ThemeManifest = {
id: "cassette",
label: msg`Cassette Futurism`,
colorSchemes: "both",
fontLinks: [
"https://fonts.googleapis.com/css2?family=Jost:wght@400;500;600;700&family=Space+Mono:wght@400;700&display=swap",
],
chromeColors: {
light: { base: "#ece4d4", dimmed: "#d4c8ab" },
dark: { base: "#211e19", dimmed: "#191712" },
},
mantine: {
defaultRadius: "md",
// Amber/lime/mustard fills are mid-luminance; without autoContrast they
// render white-on-amber at ~2-3:1.
autoContrast: true,
black: "#241f19",
primaryShade: { light: 6, dark: 5 },
colors: cassetteColors,
},
};
export default manifest;
+550
View File
@@ -0,0 +1,550 @@
/* Cassette Futurism — 1970s NASA/Braun control-deck instrument panel.
Light: warm beige enclosure with recessed panel seams and rounded-rect
modules in varied beige tones; buttons read as physical keys (top bevel,
1px darker base edge, press sinks 1px). Dark: powered-down deck with
glowing indicator lamps. Ramps/autoContrast live in manifest.ts; this
file carries shape, seams, and the physical-key/readout language. */
/* ---------- 1. Tokens, any scheme — (0,2,0) ---------- */
[data-theme="cassette"][data-theme] {
--mantine-font-family: "Jost", ui-sans-serif, system-ui, sans-serif;
--mantine-font-family-headings: "Jost", ui-sans-serif, system-ui, sans-serif;
--mantine-font-family-monospace: "Space Mono", ui-monospace, "SFMono-Regular",
monospace;
--mantine-heading-font-weight: 600;
--mantine-radius-xs: 6px;
--mantine-radius-sm: 8px;
--mantine-radius-md: 10px;
--mantine-radius-lg: 14px;
--mantine-radius-xl: 20px;
--mantine-radius-default: 10px;
--flxn-avatar-radius: 14px;
--flxn-avatar-border: 2px solid var(--mantine-color-default-border);
--flxn-badge-radius: 8px;
/* uppercase micro-label treatment shared by controls (buttons, badges,
tabs, toggles) — the "labeled toggle" panel aesthetic */
--cassette-label-font: "Space Mono", ui-monospace, monospace;
--cassette-label-tracking: 0.045em;
/* fine enclosure grain, tiled, baked-in low opacity */
--cassette-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.035'/%3E%3C/svg%3E");
}
/* ---------- 2. Per-scheme tokens — (0,3,0) ---------- */
[data-theme="cassette"][data-theme][data-mantine-color-scheme="light"] {
--mantine-color-body: #ece4d4;
--mantine-color-text: #2c2620;
--mantine-color-bright: #1b1712;
--mantine-color-dimmed: #6b6255;
--mantine-color-placeholder: #857b6c;
--mantine-color-anchor: var(--mantine-primary-color-filled);
--mantine-color-error: #a43f2d;
--mantine-color-default: #f1ebdd;
--mantine-color-default-hover: #e4dcc8;
--mantine-color-default-color: #2c2620;
--mantine-color-default-border: #b9ad95;
--cassette-module: #f1ebdd;
--cassette-module-alt: #e7dfc9;
--cassette-seam-hi: rgba(255, 255, 255, 0.6);
--cassette-seam-lo: rgba(44, 38, 30, 0.28);
--cassette-well: rgba(44, 38, 30, 0.14);
--mantine-shadow-xs: inset 0 1px 0 var(--cassette-seam-hi),
0 1px 2px rgba(44, 38, 30, 0.18);
--mantine-shadow-sm: inset 0 1px 0 var(--cassette-seam-hi),
0 1px 2px rgba(44, 38, 30, 0.2), 0 2px 6px rgba(44, 38, 30, 0.12);
--mantine-shadow-md: inset 0 1px 0 var(--cassette-seam-hi),
0 3px 8px rgba(44, 38, 30, 0.18);
--mantine-shadow-lg: inset 0 1px 0 var(--cassette-seam-hi),
0 6px 16px rgba(44, 38, 30, 0.2);
--mantine-shadow-xl: inset 0 1px 0 var(--cassette-seam-hi),
0 10px 28px rgba(44, 38, 30, 0.24);
--flxn-gold: #ac7c19;
--flxn-silver: #837259;
--flxn-bronze: #9e4b1b;
--flxn-toast-success: #587a34;
--flxn-toast-error: #a43f2d;
--flxn-podium-1-bg: color-mix(in srgb, var(--mantine-color-yellow-5) 16%, #f6f1e6);
--flxn-podium-1-border: var(--mantine-color-yellow-7);
--flxn-podium-1-label: var(--mantine-color-yellow-9);
--flxn-podium-2-bg: color-mix(in srgb, var(--mantine-color-gray-5) 16%, #f6f1e6);
--flxn-podium-2-border: var(--mantine-color-gray-6);
--flxn-podium-2-label: var(--mantine-color-gray-8);
--flxn-podium-3-bg: color-mix(in srgb, var(--mantine-color-orange-5) 14%, #f6f1e6);
--flxn-podium-3-border: var(--mantine-color-orange-7);
--flxn-podium-3-label: var(--mantine-color-orange-9);
--flxn-showcase-bg: #f6f1e6;
--flxn-overlay: rgba(33, 28, 22, 0.45);
--flxn-dotgrid-color: #cabf9f;
--flxn-pop-shadow: rgba(44, 38, 30, 0.3);
}
[data-theme="cassette"][data-theme][data-mantine-color-scheme="dark"] {
--mantine-color-body: #211e19;
--mantine-color-text: #d8d0c0;
--mantine-color-bright: #f2ece0;
--mantine-color-dimmed: #8f8574;
--mantine-color-placeholder: #6f6656;
--mantine-color-anchor: var(--mantine-primary-color-light-color);
--mantine-color-error: #e2685a;
--mantine-color-default: #2a251f;
--mantine-color-default-hover: #332d25;
--mantine-color-default-color: #d8d0c0;
--mantine-color-default-border: #4a4335;
--cassette-module: #2a251f;
--cassette-module-alt: #241f19;
--cassette-seam-hi: rgba(255, 255, 255, 0.07);
--cassette-seam-lo: rgba(0, 0, 0, 0.55);
--cassette-well: rgba(0, 0, 0, 0.5);
--mantine-shadow-xs: inset 0 1px 0 var(--cassette-seam-hi),
0 1px 2px rgba(0, 0, 0, 0.5);
--mantine-shadow-sm: inset 0 1px 0 var(--cassette-seam-hi),
0 2px 6px rgba(0, 0, 0, 0.55);
--mantine-shadow-md: inset 0 1px 0 var(--cassette-seam-hi),
0 4px 12px rgba(0, 0, 0, 0.55);
--mantine-shadow-lg: inset 0 1px 0 var(--cassette-seam-hi),
0 8px 20px rgba(0, 0, 0, 0.6);
--mantine-shadow-xl: inset 0 1px 0 var(--cassette-seam-hi),
0 14px 32px rgba(0, 0, 0, 0.65);
--flxn-gold: #e0ac2c;
--flxn-silver: #a89c88;
--flxn-bronze: #d47237;
--flxn-toast-success: #86ac56;
--flxn-toast-error: #e2685a;
--flxn-podium-1-bg: color-mix(in srgb, var(--mantine-color-yellow-7) 26%, #241f19);
--flxn-podium-1-border: var(--mantine-color-yellow-5);
--flxn-podium-1-label: var(--mantine-color-yellow-3);
--flxn-podium-2-bg: color-mix(in srgb, var(--mantine-color-gray-6) 20%, #241f19);
--flxn-podium-2-border: var(--mantine-color-gray-4);
--flxn-podium-2-label: var(--mantine-color-gray-2);
--flxn-podium-3-bg: color-mix(in srgb, var(--mantine-color-orange-7) 26%, #241f19);
--flxn-podium-3-border: var(--mantine-color-orange-5);
--flxn-podium-3-label: var(--mantine-color-orange-3);
--flxn-showcase-bg: #241f19;
--flxn-overlay: rgba(0, 0, 0, 0.6);
--flxn-dotgrid-color: #3a352c;
--flxn-pop-shadow: rgba(0, 0, 0, 0.55);
}
/* ---------- 3. Enclosure texture: seams + grain (1 fixed overlay) ---------- */
[data-theme="cassette"] body {
background-color: var(--mantine-color-body);
background-image: var(--cassette-grain);
}
/* ---------- 4. AppShell: top bezel + seam rules ---------- */
[data-theme="cassette"] .mantine-AppShell-header {
background-color: var(--cassette-module);
border-bottom: 1px solid var(--mantine-color-default-border);
box-shadow: inset 0 -1px 0 var(--cassette-seam-hi),
0 2px 6px rgba(0, 0, 0, 0.12);
}
[data-theme="cassette"] .mantine-AppShell-header .mantine-Title-root {
font-family: var(--cassette-label-font);
text-transform: uppercase;
letter-spacing: var(--cassette-label-tracking);
}
[data-theme="cassette"] .mantine-AppShell-navbar {
background-color: var(--cassette-module);
border-inline-end: 1px solid var(--mantine-color-default-border);
}
[data-theme="cassette"] nav[role="navigation"].mantine-Paper-root {
background-color: var(--cassette-module);
border: 1px solid var(--mantine-color-default-border);
box-shadow: inset 0 1px 0 var(--cassette-seam-hi), var(--mantine-shadow-md);
}
/* ---------- 5. Buttons & ActionIcons: physical keys ---------- */
[data-theme="cassette"] .mantine-Button-root,
[data-theme="cassette"] .mantine-ActionIcon-root {
border-radius: var(--mantine-radius-md);
border: 1px solid var(--mantine-color-default-border);
font-family: var(--cassette-label-font);
font-weight: 700;
text-transform: uppercase;
letter-spacing: var(--cassette-label-tracking);
transition: transform 100ms ease, box-shadow 100ms ease;
}
[data-theme="cassette"] .mantine-Button-root[data-variant="default"],
[data-theme="cassette"] .mantine-ActionIcon-root[data-variant="default"] {
background-color: var(--cassette-module);
border-bottom: 3px solid color-mix(in srgb, var(--mantine-color-default-border) 70%, var(--mantine-color-bright));
box-shadow: inset 0 1px 0 var(--cassette-seam-hi);
color: var(--mantine-color-text);
}
[data-theme="cassette"] .mantine-Button-root[data-variant="filled"],
[data-theme="cassette"] .mantine-ActionIcon-root[data-variant="filled"],
[data-theme="cassette"] .mantine-Button-root:not([data-variant]),
[data-theme="cassette"] .mantine-ActionIcon-root:not([data-variant]) {
background-color: var(--mantine-primary-color-filled);
border-color: color-mix(in srgb, var(--mantine-primary-color-filled) 55%, black);
border-bottom: 3px solid color-mix(in srgb, var(--mantine-primary-color-filled) 45%, black);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3);
color: var(--mantine-primary-color-contrast);
}
[data-theme="cassette"][data-mantine-color-scheme="dark"] .mantine-Button-root[data-variant="filled"],
[data-theme="cassette"][data-mantine-color-scheme="dark"] .mantine-ActionIcon-root[data-variant="filled"],
[data-theme="cassette"][data-mantine-color-scheme="dark"] .mantine-Button-root:not([data-variant]),
[data-theme="cassette"][data-mantine-color-scheme="dark"] .mantine-ActionIcon-root:not([data-variant]) {
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),
0 0 8px color-mix(in srgb, var(--mantine-primary-color-filled) 55%, transparent);
}
[data-theme="cassette"] .mantine-Button-root[data-variant="light"],
[data-theme="cassette"] .mantine-ActionIcon-root[data-variant="light"] {
border-color: color-mix(in srgb, var(--mantine-primary-color-filled) 40%, transparent);
}
[data-theme="cassette"] .mantine-Button-root[data-variant="subtle"],
[data-theme="cassette"] .mantine-ActionIcon-root[data-variant="subtle"],
[data-theme="cassette"] .mantine-Button-root[data-variant="transparent"],
[data-theme="cassette"] .mantine-ActionIcon-root[data-variant="transparent"] {
border-color: transparent;
border-bottom-width: 1px;
}
/* pressed state — static sink cue, always on (no motion required) */
[data-theme="cassette"] .mantine-Button-root:active:not(:disabled):not([data-disabled]):not([data-loading]),
[data-theme="cassette"] .mantine-ActionIcon-root:active:not(:disabled):not([data-disabled]):not([data-loading]) {
border-bottom-width: 1px;
box-shadow: inset 0 2px 4px var(--cassette-well);
}
[data-theme="cassette"] .mantine-Button-root:disabled,
[data-theme="cassette"] .mantine-Button-root[data-disabled],
[data-theme="cassette"] .mantine-ActionIcon-root:disabled,
[data-theme="cassette"] .mantine-ActionIcon-root[data-disabled] {
box-shadow: none;
border-bottom-width: 1px;
}
/* the actual 1px sink — guarded, motion-only */
@media (prefers-reduced-motion: no-preference) {
[data-theme="cassette"] .flxn-press:active:not(:disabled):not([data-disabled]):not([data-loading]) {
transform: translateY(1px);
}
}
/* ---------- 6. Inputs: recessed readout wells ---------- */
[data-theme="cassette"] .mantine-Input-input {
background-color: var(--cassette-module-alt);
border: 1px solid var(--mantine-color-default-border);
border-radius: var(--mantine-radius-sm);
font-family: var(--cassette-label-font);
box-shadow: inset 0 1px 3px var(--cassette-well);
}
[data-theme="cassette"] .mantine-Input-input:focus,
[data-theme="cassette"] .mantine-Input-input:focus-within {
border-color: var(--mantine-primary-color-filled);
outline: none;
box-shadow: inset 0 1px 3px var(--cassette-well),
0 0 0 3px color-mix(in srgb, var(--mantine-primary-color-filled) 28%, transparent);
}
[data-theme="cassette"] .mantine-PinInput-field {
font-family: var(--cassette-label-font);
}
/* ---------- 7. Panels: Paper, Card, Modal, Popover, Notification, Alert ---------- */
[data-theme="cassette"] .mantine-Paper-root,
[data-theme="cassette"] .mantine-Card-root,
[data-theme="cassette"] .mantine-Popover-dropdown,
[data-theme="cassette"] .mantine-Modal-content,
[data-theme="cassette"] .mantine-Notification-root,
[data-theme="cassette"] .mantine-Alert-root {
background-color: var(--cassette-module);
border: 1px solid var(--mantine-color-default-border);
}
[data-theme="cassette"] .mantine-Paper-root,
[data-theme="cassette"] .mantine-Card-root {
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="cassette"] .mantine-Modal-content {
box-shadow: var(--mantine-shadow-xl);
}
[data-theme="cassette"] .mantine-Modal-header {
background-color: var(--cassette-module-alt);
border-bottom: 1px solid var(--mantine-color-default-border);
box-shadow: inset 0 1px 0 var(--cassette-seam-hi);
}
[data-theme="cassette"] .mantine-Tooltip-tooltip {
background-color: var(--mantine-color-bright);
color: var(--mantine-color-body);
border: 1px solid var(--mantine-color-default-border);
font-family: var(--cassette-label-font);
font-size: 0.75rem;
}
/* ---------- 8. Tabs & SegmentedControl: labeled toggles ---------- */
[data-theme="cassette"] .mantine-Tabs-tab {
font-family: var(--cassette-label-font);
font-weight: 700;
text-transform: uppercase;
letter-spacing: var(--cassette-label-tracking);
}
[data-theme="cassette"] .mantine-Tabs-tab[data-active] {
border-color: var(--mantine-primary-color-filled);
color: var(--mantine-primary-color-filled);
}
[data-theme="cassette"] .mantine-SegmentedControl-root {
background-color: var(--cassette-module-alt);
border: 1px solid var(--mantine-color-default-border);
border-radius: var(--mantine-radius-md);
box-shadow: inset 0 1px 3px var(--cassette-well);
}
[data-theme="cassette"] .mantine-SegmentedControl-label {
font-family: var(--cassette-label-font);
text-transform: uppercase;
letter-spacing: var(--cassette-label-tracking);
font-size: 0.7rem;
}
[data-theme="cassette"] .mantine-SegmentedControl-indicator {
background-color: var(--cassette-module);
border: 1px solid var(--mantine-color-default-border);
border-radius: var(--mantine-radius-sm);
box-shadow: inset 0 1px 0 var(--cassette-seam-hi), 0 1px 2px var(--cassette-well);
}
/* ---------- 9. Badges, ThemeIcon, Checkbox/Radio, Switch ---------- */
[data-theme="cassette"] .mantine-Badge-root {
border: 1px solid var(--mantine-color-default-border);
border-radius: var(--mantine-radius-sm);
font-family: var(--cassette-label-font);
letter-spacing: var(--cassette-label-tracking);
}
[data-theme="cassette"] .mantine-ThemeIcon-root {
border: 1px solid var(--mantine-color-default-border);
}
[data-theme="cassette"] .mantine-Checkbox-input,
[data-theme="cassette"] .mantine-Radio-radio {
border: 1px solid var(--mantine-color-default-border);
box-shadow: inset 0 1px 2px var(--cassette-well);
}
/* checked-glyph on-color is hardcoded white by Mantine regardless of
autoContrast; recompute per accent so amber/lime checks stay legible */
[data-theme="cassette"] .mantine-Checkbox-root {
--checkbox-icon-color: var(--mantine-primary-color-contrast);
}
[data-theme="cassette"] .mantine-Radio-root {
--radio-icon-color: var(--mantine-primary-color-contrast);
}
[data-theme="cassette"] .mantine-Switch-track {
border: 1px solid var(--mantine-color-default-border);
box-shadow: inset 0 1px 3px var(--cassette-well);
}
[data-theme="cassette"] .mantine-Switch-thumb {
border: 1px solid var(--mantine-color-default-border);
box-shadow: inset 0 1px 0 var(--cassette-seam-hi), 0 1px 2px var(--cassette-well);
}
[data-theme="cassette"][data-mantine-color-scheme="dark"] .mantine-Switch-input:checked + .mantine-Switch-track {
box-shadow: inset 0 1px 3px var(--cassette-well),
0 0 6px color-mix(in srgb, var(--mantine-primary-color-filled) 60%, transparent);
}
/* ---------- 10. Progress: VU meter with tick marks ---------- */
[data-theme="cassette"] .mantine-Progress-root {
position: relative;
background-color: var(--cassette-module-alt);
border: 1px solid var(--mantine-color-default-border);
box-shadow: inset 0 1px 3px var(--cassette-well);
}
[data-theme="cassette"] .mantine-Progress-root::after {
content: "";
position: absolute;
inset: 0;
z-index: 1;
pointer-events: none;
background-image: repeating-linear-gradient(
90deg,
var(--mantine-color-default-border) 0 1px,
transparent 1px 12.5%
);
opacity: 0.6;
}
[data-theme="cassette"] .mantine-Progress-section {
background-color: var(--mantine-primary-color-filled);
background-image: linear-gradient(
180deg,
rgba(255, 255, 255, 0.32) 0%,
rgba(255, 255, 255, 0.05) 45%,
rgba(0, 0, 0, 0.1) 100%
);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
[data-theme="cassette"][data-mantine-color-scheme="dark"] .mantine-Progress-section {
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16),
0 0 6px color-mix(in srgb, var(--mantine-primary-color-filled) 65%, transparent);
}
/* ---------- 11. Divider: tape-window (two reel circles + line) ---------- */
[data-theme="cassette"] .mantine-Divider-root {
position: relative;
--divider-color: var(--mantine-color-default-border);
}
[data-theme="cassette"] .mantine-Divider-root:not([data-orientation="vertical"])::before,
[data-theme="cassette"] .mantine-Divider-root:not([data-orientation="vertical"])::after {
content: "";
position: absolute;
top: 50%;
width: 8px;
height: 8px;
border-radius: 50%;
transform: translateY(-50%);
background-color: var(--cassette-module);
border: 1px solid var(--mantine-color-default-border);
box-shadow: inset 0 1px 1px var(--cassette-well), inset 0 -1px 0 var(--cassette-seam-hi);
pointer-events: none;
}
[data-theme="cassette"] .mantine-Divider-root:not([data-orientation="vertical"])::before {
left: 42%;
}
[data-theme="cassette"] .mantine-Divider-root:not([data-orientation="vertical"])::after {
left: 58%;
}
/* ---------- 12. App hooks: tab strip ---------- */
[data-theme="cassette"] .flxn-tabs[data-stuck] {
background-color: var(--cassette-module);
border-bottom-color: var(--mantine-color-default-border);
box-shadow: inset 0 -1px 0 var(--cassette-seam-hi);
}
[data-theme="cassette"] .flxn-tab {
font-family: var(--cassette-label-font);
font-weight: 700;
text-transform: uppercase;
letter-spacing: var(--cassette-label-tracking);
font-size: 0.75rem;
}
/* underline indicator (not a filled pill), so accent text stays legible */
[data-theme="cassette"] .flxn-tab[data-active]::before {
content: "";
display: inline-block;
width: 6px;
height: 6px;
margin-inline-end: 6px;
border-radius: 50%;
background-color: var(--mantine-primary-color-filled);
vertical-align: middle;
}
[data-theme="cassette"][data-mantine-color-scheme="dark"] .flxn-tab[data-active]::before {
box-shadow: 0 0 5px color-mix(in srgb, var(--mantine-primary-color-filled) 80%, transparent);
}
[data-theme="cassette"] .flxn-tabs-indicator {
border-bottom: 3px solid var(--mantine-primary-color-filled);
}
[data-theme="cassette"][data-mantine-color-scheme="dark"] .flxn-tabs-indicator {
box-shadow: none;
}
/* ---------- 13. App hooks: nav tiles ---------- */
[data-theme="cassette"] .flxn-tile {
background-color: var(--cassette-module);
border: 1px solid var(--mantine-color-default-border);
border-bottom: 3px solid color-mix(in srgb, var(--mantine-color-default-border) 70%, var(--mantine-color-bright));
box-shadow: inset 0 1px 0 var(--cassette-seam-hi);
transition: transform 100ms ease, box-shadow 100ms ease;
}
[data-theme="cassette"] .flxn-tile:active {
border-bottom-width: 1px;
box-shadow: inset 0 2px 4px var(--cassette-well);
}
@media (prefers-reduced-motion: no-preference) {
[data-theme="cassette"] .flxn-tile.flxn-press:active {
transform: translateY(1px);
}
}
/* flat tint — no plate behind the glyph */
[data-theme="cassette"] .flxn-tile-icon {
color: var(--mantine-primary-color-light-color);
}
/* ---------- 14. App hooks: see-all as a labeled key chip ---------- */
[data-theme="cassette"] .flxn-see-all {
border-radius: var(--mantine-radius-sm);
border: 1px solid color-mix(in srgb, var(--mantine-primary-color-filled) 45%, transparent);
background-color: var(--mantine-primary-color-light);
color: var(--mantine-primary-color-light-color);
font-family: var(--cassette-label-font);
font-weight: 700;
text-transform: uppercase;
letter-spacing: var(--cassette-label-tracking);
font-size: 0.7rem;
box-shadow: inset 0 1px 0 var(--cassette-seam-hi);
}
/* ---------- 15. App hooks: match card + reaction tray ---------- */
[data-theme="cassette"] .flxn-match-card {
background-color: var(--cassette-module);
border: 1px solid var(--mantine-color-default-border);
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="cassette"] .flxn-reaction-row {
background-color: var(--cassette-module-alt);
border: 1px solid var(--mantine-color-default-border);
box-shadow: inset 0 2px 4px var(--cassette-well);
}
/* ---------- 16. App hooks: avatar frame (porthole bezel) ---------- */
[data-theme="cassette"] .flxn-avatar-frame {
box-shadow: inset 0 1px 0 var(--cassette-seam-hi), inset 0 -1px 2px var(--cassette-well);
}
+11
View File
@@ -0,0 +1,11 @@
import { CHROME_COLORS, ChromeColorScheme } from "@/lib/mantine/theme-colors";
import type { ThemeManifest } from "./types";
export function resolveChromeColors(
manifest: ThemeManifest
): Record<ChromeColorScheme, { base: string; dimmed: string }> {
return {
light: manifest.chromeColors?.light ?? CHROME_COLORS.light,
dark: manifest.chromeColors?.dark ?? CHROME_COLORS.dark,
};
}
+158
View File
@@ -0,0 +1,158 @@
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
/* Carnival poster inks. Declared in JS so Mantine regenerates every derived
variant var (-light rgba, -outline, filled hovers) from the retuned shades. */
const circusColors = {
// royal
blue: [
"#eef2fc",
"#d8e1f8",
"#b6c8f0",
"#8fa9e6",
"#6787d8",
"#4267c8",
"#2b4fb4",
"#21409a",
"#19317a",
"#11235c",
],
// carnival red
red: [
"#fdefed",
"#f9d8d4",
"#f2b6af",
"#e88d84",
"#d9655c",
"#c6443c",
"#b12724",
"#931c1d",
"#731316",
"#540c10",
],
// ticket-booth emerald
green: [
"#ecf8ef",
"#d2eeda",
"#a9dfba",
"#7bcb95",
"#4fb573",
"#2f9c57",
"#1d7f41",
"#156634",
"#0f4e27",
"#09381c",
],
// mustard
yellow: [
"#fbf4e2",
"#f5e5b8",
"#edd288",
"#e0ba55",
"#cf9f2a",
"#b58211",
"#97660a",
"#7b5207",
"#5f3f05",
"#452d03",
],
// sideshow plum
grape: [
"#f6effc",
"#e8d8f6",
"#d3b6ee",
"#b98ee2",
"#9d66d2",
"#8547bd",
"#6f2da0",
"#5b2284",
"#461868",
"#33104d",
],
// pumpkin
orange: [
"#fdf1e8",
"#f9dcc6",
"#f3bd97",
"#e99a63",
"#dc7736",
"#cd6016",
"#b84d09",
"#973d06",
"#752f05",
"#542103",
],
// raspberry
pink: [
"#fcedf4",
"#f8d3e4",
"#f1abcb",
"#e67dac",
"#d6528d",
"#c43574",
"#ab1f60",
"#8d154e",
"#6e0e3c",
"#50082b",
],
// fairground olive
lime: [
"#f5f8e4",
"#e7efbe",
"#d3e18d",
"#bccd5c",
"#a2b532",
"#849a15",
"#667c07",
"#536505",
"#404e04",
"#2e3902",
],
// warm parchment neutrals — light-scheme grays
gray: [
"#faf5ea",
"#f1e8d6",
"#e5d8bf",
"#d3c1a2",
"#b8a284",
"#97826a",
"#6f5d4b",
"#554638",
"#3d3128",
"#28201a",
],
// night fair — dark-scheme surfaces/borders
dark: [
"#f2e6da",
"#d8c7bb",
"#b5a096",
"#8a7268",
"#5c4547",
"#47343a",
"#382930",
"#2a1e24",
"#20161b",
"#150e12",
],
} as const;
const manifest: ThemeManifest = {
id: "circus",
label: msg`Big Top`,
colorSchemes: "both",
fontLinks: [
"https://fonts.googleapis.com/css2?family=Alfa+Slab+One&display=swap",
"https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500&display=swap",
],
chromeColors: {
light: { base: "#f9f1e0", dimmed: "#e9dcbd" },
dark: { base: "#1e151a", dimmed: "#140d11" },
},
mantine: {
// deep poster inks in both schemes keep white filled-text legible
primaryShade: { light: 6, dark: 6 },
colors: circusColors,
},
};
export default manifest;
+608
View File
@@ -0,0 +1,608 @@
/* Big Top — vintage carnival.
Light: cream ticket paper, awning-stripe bands, scalloped card edges.
Dark: night fair with warm glowing surfaces. */
/* ---------------------------------------------------------------- */
/* 1. Tokens, any scheme — (0,2,0) */
/* ---------------------------------------------------------------- */
[data-theme="circus"][data-theme] {
--mantine-font-family: "Rubik", "Trebuchet MS", sans-serif;
--mantine-font-family-headings: "Alfa Slab One", "Rockwell", "Georgia", serif;
--mantine-heading-font-weight: 400;
--mantine-h1-font-size: 2rem;
--mantine-h1-line-height: 1.15;
--mantine-h2-font-size: 1.6rem;
--mantine-h2-line-height: 1.2;
--mantine-h3-font-size: 1.3rem;
--mantine-h3-line-height: 1.25;
/* ticket-corner radii */
--mantine-radius-xs: 0.25rem;
--mantine-radius-sm: 0.375rem;
--mantine-radius-md: 0.5rem;
--mantine-radius-lg: 0.75rem;
--mantine-radius-xl: 1rem;
--mantine-radius-default: 0.5rem;
/* warm soft elevation (dark overrides below) */
--mantine-shadow-xs: 0 1px 2px rgba(64, 41, 29, 0.12);
--mantine-shadow-sm: 0 2px 6px rgba(64, 41, 29, 0.16);
--mantine-shadow-md: 0 4px 12px rgba(64, 41, 29, 0.2);
--mantine-shadow-lg: 0 8px 20px rgba(64, 41, 29, 0.22);
--mantine-shadow-xl: 0 12px 32px rgba(64, 41, 29, 0.26);
--flxn-avatar-radius: 50%;
--flxn-avatar-border: 2px solid var(--circus-ink);
--flxn-badge-radius: 0.5rem;
/* awning valance band (stripe colors set per scheme) */
--circus-awning: repeating-linear-gradient(
90deg,
var(--circus-red) 0 0.75rem,
var(--circus-stripe) 0.75rem 1.5rem
);
}
/* ---------------------------------------------------------------- */
/* 2. Light — cream ticket paper — (0,3,0) */
/* ---------------------------------------------------------------- */
[data-theme="circus"][data-theme][data-mantine-color-scheme="light"] {
--circus-ink: #40291d;
--circus-paper: #fffaef;
--circus-red: #b02e26;
--circus-stripe: #f6ead0;
--circus-scallop: #b02e26;
--circus-gold: #a8790f;
--mantine-color-body: #f9f1e0;
--mantine-color-text: #33221a;
--mantine-color-bright: #1d100a;
--mantine-color-dimmed: #7a6250;
--mantine-color-anchor: var(--mantine-primary-color-light-color);
--mantine-color-error: #b12724;
--mantine-color-placeholder: #8d7660;
--mantine-color-default: #fffaef;
--mantine-color-default-hover: #f3e7cb;
--mantine-color-default-color: #33221a;
--mantine-color-default-border: #40291d;
/* app tokens */
--flxn-gold: #c2900e;
--flxn-silver: #857c6e;
--flxn-bronze: #b05c2c;
--flxn-toast-success: #1d7f41;
--flxn-toast-error: #b12724;
--flxn-podium-1-bg: #f7e8b8;
--flxn-podium-1-border: #c2900e;
--flxn-podium-1-label: #6b4e08;
--flxn-podium-2-bg: #efe9dc;
--flxn-podium-2-border: #9a8f7c;
--flxn-podium-2-label: #5c5344;
--flxn-podium-3-bg: #f6dfc8;
--flxn-podium-3-border: #b05c2c;
--flxn-podium-3-label: #7c3c14;
--flxn-showcase-bg: #fffaef;
--flxn-overlay: rgba(38, 24, 16, 0.5);
--flxn-dotgrid-color: rgba(64, 41, 29, 0.3);
--flxn-pop-shadow: rgba(64, 41, 29, 0.35);
}
/* ---------------------------------------------------------------- */
/* 2. Dark — night fair — (0,3,0) */
/* ---------------------------------------------------------------- */
[data-theme="circus"][data-theme][data-mantine-color-scheme="dark"] {
--circus-ink: #6b4f43;
--circus-paper: #2a1e24;
--circus-red: #a03530;
--circus-stripe: #d8b073;
--circus-scallop: #a03530;
--circus-gold: #e3b45c;
--mantine-color-body: #1e151a;
--mantine-color-text: #f4e6d4;
--mantine-color-bright: #fff6e8;
--mantine-color-dimmed: #b39d8c;
--mantine-color-anchor: var(--mantine-primary-color-light-color);
--mantine-color-error: #f2857e;
--mantine-color-placeholder: #8a7466;
--mantine-color-default: #2a1e24;
--mantine-color-default-hover: #362730;
--mantine-color-default-color: #f4e6d4;
--mantine-color-default-border: #5c443c;
/* black depth plus a faint fairground-lamp glow */
--mantine-shadow-xs: 0 1px 3px rgba(0, 0, 0, 0.4);
--mantine-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.45),
0 0 14px rgba(232, 178, 102, 0.06);
--mantine-shadow-md: 0 4px 14px rgba(0, 0, 0, 0.5),
0 0 18px rgba(232, 178, 102, 0.08);
--mantine-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.55),
0 0 24px rgba(232, 178, 102, 0.09);
--mantine-shadow-xl: 0 14px 36px rgba(0, 0, 0, 0.6),
0 0 32px rgba(232, 178, 102, 0.1);
/* app tokens */
--flxn-gold: #e8b84f;
--flxn-silver: #c9bfae;
--flxn-bronze: #e09060;
--flxn-toast-success: #6fce8f;
--flxn-toast-error: #f2857e;
--flxn-podium-1-bg: #322818;
--flxn-podium-1-border: #9c7c2c;
--flxn-podium-1-label: #ecc86a;
--flxn-podium-2-bg: #2c2522;
--flxn-podium-2-border: #6e6357;
--flxn-podium-2-label: #d6cabb;
--flxn-podium-3-bg: #34211a;
--flxn-podium-3-border: #9c5b34;
--flxn-podium-3-label: #eda579;
--flxn-showcase-bg: #2a1e24;
--flxn-overlay: rgba(8, 4, 6, 0.65);
--flxn-dotgrid-color: rgba(240, 221, 196, 0.22);
--flxn-pop-shadow: rgba(0, 0, 0, 0.6);
}
/* ---------------------------------------------------------------- */
/* 3. Type — slab display headings, sparingly */
/* ---------------------------------------------------------------- */
[data-theme="circus"] :is(h1, h2, h3) {
letter-spacing: 0.035em;
}
/* small headings stay in the body face so the slab reads as a poster
headline, not a wall of it */
[data-theme="circus"] :is(h4, h5, h6) {
font-family: var(--mantine-font-family);
font-weight: 700;
letter-spacing: 0.01em;
}
/* ---------------------------------------------------------------- */
/* 4. Surfaces — warm-ink borders, scalloped card valances */
/* ---------------------------------------------------------------- */
[data-theme="circus"] .mantine-Paper-root {
background-color: var(--circus-paper);
border: 2px solid var(--circus-ink);
box-shadow: var(--mantine-shadow-sm);
}
/* scallop strip painted along the top edge only (repeat-x half-discs);
bracket cards are dense and small — no valance there */
[data-theme="circus"] .mantine-Card-root:not(.flxn-bracket-card),
[data-theme="circus"] .flxn-match-card.flxn-match-card {
background-image: radial-gradient(
circle at 0.375rem 0,
var(--circus-scallop) 0.3rem,
transparent calc(0.3rem + 1px)
);
background-repeat: repeat-x;
background-size: 0.75rem 0.45rem;
background-position: top left;
}
/* ---------------------------------------------------------------- */
/* 5. Buttons & action icons — poster lettering on inked plates */
/* ---------------------------------------------------------------- */
[data-theme="circus"] .mantine-Button-root {
border: 2px solid var(--circus-ink);
border-radius: var(--mantine-radius-sm);
box-shadow: 0 2px 0 color-mix(in srgb, var(--circus-ink) 45%, transparent);
background-clip: padding-box;
}
[data-theme="circus"] .mantine-Button-label {
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
}
[data-theme="circus"]
.mantine-Button-root:is(
[data-variant="subtle"],
[data-variant="transparent"],
[data-variant="white"]
) {
border: none;
box-shadow: none;
}
[data-theme="circus"]
.mantine-ActionIcon-root:is(
[data-variant="filled"],
[data-variant="default"],
[data-variant="outline"],
[data-variant="light"]
) {
border: 2px solid var(--circus-ink);
box-shadow: var(--mantine-shadow-xs);
}
@media (prefers-reduced-motion: no-preference) {
[data-theme="circus"] .mantine-Button-root {
transition:
transform 140ms cubic-bezier(0.32, 0.72, 0, 1),
box-shadow 140ms cubic-bezier(0.32, 0.72, 0, 1);
}
[data-theme="circus"]
.mantine-Button-root:not(
[data-variant="subtle"],
[data-variant="transparent"],
[data-variant="white"]
):active:not([data-disabled]):not(:disabled) {
transform: translateY(1px);
box-shadow: 0 1px 0 color-mix(in srgb, var(--circus-ink) 45%, transparent);
}
}
/* ---------------------------------------------------------------- */
/* 6. Inputs — ticket-window frames */
/* ---------------------------------------------------------------- */
[data-theme="circus"] .mantine-Input-input {
background-color: var(--circus-paper);
border: 2px solid var(--circus-ink);
border-radius: var(--mantine-radius-sm);
}
[data-theme="circus"] .mantine-Input-input:focus,
[data-theme="circus"] .mantine-Input-input:focus-within {
border-color: var(--mantine-primary-color-filled);
box-shadow: 0 0 0 3px
color-mix(in srgb, var(--mantine-primary-color-filled) 25%, transparent);
}
[data-theme="circus"] .mantine-InputWrapper-label {
font-weight: 600;
letter-spacing: 0.01em;
}
[data-theme="circus"] .mantine-PinInput-field {
border: 2px solid var(--circus-ink);
border-radius: var(--mantine-radius-sm);
font-weight: 700;
}
/* ---------------------------------------------------------------- */
/* 7. Badges — prize ribbons (notched banner ends) */
/* ---------------------------------------------------------------- */
[data-theme="circus"] .mantine-Badge-root {
border-radius: 0;
clip-path: polygon(
0 0,
100% 0,
calc(100% - 0.45em) 50%,
100% 100%,
0 100%,
0.45em 50%
);
padding-inline: 0.95em;
font-weight: 700;
letter-spacing: 0.05em;
}
/* ---------------------------------------------------------------- */
/* 8. Tabs & SegmentedControl */
/* ---------------------------------------------------------------- */
[data-theme="circus"] .mantine-Tabs-list::before {
border-color: color-mix(in srgb, var(--circus-ink) 40%, transparent);
}
[data-theme="circus"] .mantine-Tabs-tab {
font-weight: 600;
}
[data-theme="circus"] .mantine-Tabs-tab[data-active] {
border-color: var(--mantine-primary-color-filled);
color: var(--mantine-primary-color-light-color);
}
[data-theme="circus"] .mantine-SegmentedControl-root {
background-color: var(--circus-paper);
border: 2px solid var(--circus-ink);
border-radius: var(--mantine-radius-sm);
}
[data-theme="circus"] .mantine-SegmentedControl-indicator {
background-color: var(--mantine-primary-color-filled);
border-radius: var(--mantine-radius-xs);
box-shadow: none;
}
[data-theme="circus"] .mantine-SegmentedControl-label {
font-weight: 600;
}
[data-theme="circus"] .mantine-SegmentedControl-label[data-active] {
color: var(--mantine-primary-color-contrast);
}
/* ---------------------------------------------------------------- */
/* 9. Overlays — marquee-framed modal, poster dropdowns */
/* ---------------------------------------------------------------- */
[data-theme="circus"] .mantine-Modal-content {
background-color: var(--circus-paper);
border: 2px solid var(--circus-ink);
border-radius: var(--mantine-radius-md);
box-shadow: var(--mantine-shadow-xl);
/* marquee-bulb ring just inside the frame */
outline: 2px dotted var(--circus-gold);
outline-offset: -0.45rem;
}
[data-theme="circus"] .mantine-Modal-header {
background-color: var(--circus-paper);
border-bottom: 2px solid var(--circus-ink);
}
[data-theme="circus"] .mantine-Modal-title {
font-family: var(--mantine-font-family-headings);
font-weight: 400;
font-size: 1.25rem;
letter-spacing: 0.035em;
}
[data-theme="circus"] .mantine-Popover-dropdown,
[data-theme="circus"] .mantine-Menu-dropdown {
background-color: var(--circus-paper);
border: 2px solid var(--circus-ink);
border-radius: var(--mantine-radius-sm);
box-shadow: var(--mantine-shadow-md);
}
/* sign plate: inverts per scheme, always warm-on-warm neutral */
[data-theme="circus"] .mantine-Tooltip-tooltip {
border-radius: var(--mantine-radius-sm);
font-weight: 600;
}
[data-theme="circus"][data-mantine-color-scheme="light"] .mantine-Tooltip-tooltip {
background-color: #33221a;
color: #f9f1e0;
}
[data-theme="circus"][data-mantine-color-scheme="dark"] .mantine-Tooltip-tooltip {
background-color: #f4e6d4;
color: #33221a;
}
[data-theme="circus"] .mantine-Notification-root {
background-color: var(--circus-paper);
border: 2px solid var(--circus-ink);
border-radius: var(--mantine-radius-md);
box-shadow: var(--mantine-shadow-lg);
}
[data-theme="circus"] .mantine-Notification-title {
font-weight: 700;
letter-spacing: 0.02em;
}
[data-theme="circus"] .mantine-Alert-root {
border: 2px solid var(--circus-ink);
border-radius: var(--mantine-radius-md);
}
[data-theme="circus"] .mantine-Alert-title {
font-weight: 700;
letter-spacing: 0.02em;
}
/* ---------------------------------------------------------------- */
/* 10. App shell — awning band under the header */
/* ---------------------------------------------------------------- */
[data-theme="circus"] .mantine-AppShell-root {
--app-shell-border-color: var(--circus-ink);
}
[data-theme="circus"] .mantine-AppShell-header {
background-color: var(--circus-paper);
background-image: var(--circus-awning);
background-repeat: no-repeat;
background-size: 100% 0.5rem;
background-position: bottom left;
border-bottom: 2px solid var(--circus-ink);
}
[data-theme="circus"] .mantine-AppShell-navbar {
background-color: var(--circus-paper);
border-inline-end: 2px solid var(--circus-ink);
}
[data-theme="circus"] .mantine-AppShell-aside {
border-inline-start: 2px solid var(--circus-ink);
}
/* ---------------------------------------------------------------- */
/* 11. Small controls & details */
/* ---------------------------------------------------------------- */
[data-theme="circus"] .mantine-Checkbox-input {
background-color: var(--circus-paper);
border: 2px solid var(--circus-ink);
border-radius: var(--mantine-radius-xs);
}
[data-theme="circus"] .mantine-Checkbox-input:checked {
background-color: var(--mantine-primary-color-filled);
border-color: var(--circus-ink);
}
[data-theme="circus"] .mantine-Switch-track {
background-color: var(--circus-paper);
border: 2px solid var(--circus-ink);
}
[data-theme="circus"] .mantine-Switch-input:checked + .mantine-Switch-track {
background-color: var(--mantine-primary-color-filled);
border-color: var(--circus-ink);
}
[data-theme="circus"] .mantine-Switch-thumb {
background-color: #fffaef;
border: 2px solid var(--circus-ink);
}
/* striped midway banner bar */
[data-theme="circus"] .mantine-Progress-root {
background-color: var(--circus-paper);
border: 2px solid var(--circus-ink);
border-radius: var(--mantine-radius-xl);
}
[data-theme="circus"] .mantine-Progress-section {
border-radius: var(--mantine-radius-xl);
background-image: repeating-linear-gradient(
45deg,
rgba(255, 250, 239, 0.28) 0 0.5rem,
transparent 0.5rem 1rem
);
}
[data-theme="circus"] .mantine-ThemeIcon-root {
border: 2px solid var(--circus-ink);
}
[data-theme="circus"] .mantine-Indicator-indicator {
border: 2px solid var(--circus-paper);
font-weight: 700;
}
[data-theme="circus"] .mantine-NavLink-root {
font-weight: 600;
border-radius: var(--mantine-radius-sm);
}
[data-theme="circus"] .mantine-Divider-root {
--divider-color: color-mix(in srgb, var(--circus-ink) 45%, transparent);
}
[data-theme="circus"] .mantine-Skeleton-root {
border-radius: var(--mantine-radius-sm);
}
/* ---------------------------------------------------------------- */
/* 12. App component hooks */
/* ---------------------------------------------------------------- */
/* Tab strip: flat resting; docking drops the awning valance. Rule width
stays 2px in both states so pinning never shifts layout. */
[data-theme="circus"] .flxn-tabs {
border-bottom-width: 2px;
}
[data-theme="circus"] .flxn-tabs[data-stuck] {
background-color: var(--circus-paper);
background-image: var(--circus-awning);
background-repeat: no-repeat;
background-size: 100% 0.375rem;
background-position: bottom left;
border-bottom-color: var(--circus-ink);
}
[data-theme="circus"] .flxn-tab.flxn-tab {
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
}
[data-theme="circus"] .flxn-tab.flxn-tab[data-active] {
color: var(--mantine-primary-color-light-color);
font-weight: 700;
}
/* marquee-dot indicator: a dotted rule, so accent tab text is fine */
[data-theme="circus"] .flxn-tabs-indicator.flxn-tabs-indicator {
border-bottom: 0.25rem dotted var(--mantine-primary-color-filled);
padding-inline: 0.4rem;
}
/* Nav grid tiles: sideshow plates with flat accent-tinted icons */
[data-theme="circus"] .flxn-tile {
background-color: var(--circus-paper);
border: 2px solid var(--circus-ink);
border-radius: var(--mantine-radius-md);
box-shadow: var(--mantine-shadow-xs);
}
@media (hover: hover) {
[data-theme="circus"] .flxn-tile:hover:not(:disabled) {
background-color: color-mix(
in srgb,
var(--mantine-primary-color-filled) 8%,
var(--circus-paper)
);
}
}
[data-theme="circus"] .flxn-tile:active:not(:disabled) {
background-color: color-mix(
in srgb,
var(--mantine-primary-color-filled) 8%,
var(--circus-paper)
);
}
[data-theme="circus"] .flxn-tile-icon {
color: var(--mantine-primary-color-light-color);
}
/* "See all" as a ticket stub: accent wash + dashed perforation edge */
[data-theme="circus"] .flxn-see-all.flxn-see-all {
font-weight: 700;
letter-spacing: 0.02em;
background-color: var(--mantine-primary-color-light);
color: var(--mantine-primary-color-light-color);
border: 1px dashed
color-mix(in srgb, var(--mantine-primary-color-light-color) 70%, transparent);
border-radius: var(--mantine-radius-sm);
}
@media (hover: hover) {
[data-theme="circus"] .flxn-see-all.flxn-see-all:hover {
background-color: var(--mantine-primary-color-light-hover);
text-decoration: none;
}
}
/* Match card + tray: same 2px warm-ink frame; tray one shade deeper */
[data-theme="circus"] .flxn-match-card.flxn-match-card {
background-color: var(--circus-paper);
border: 2px solid var(--circus-ink);
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="circus"] .flxn-reaction-row.flxn-reaction-row {
background-color: color-mix(in srgb, var(--circus-ink) 8%, var(--circus-paper));
border: 2px solid var(--circus-ink);
border-radius: var(--mantine-radius-md);
box-shadow: var(--mantine-shadow-xs);
}
/* ---------------------------------------------------------------- */
/* 13. Accent-driven selection & focus */
/* ---------------------------------------------------------------- */
[data-theme="circus"] ::selection {
background-color: color-mix(
in srgb,
var(--mantine-primary-color-filled) 30%,
transparent
);
}
[data-theme="circus"] :focus-visible {
outline: 2px solid var(--mantine-primary-color-filled);
outline-offset: 2px;
}
+165
View File
@@ -0,0 +1,165 @@
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
/* Plasticine ramps — chalky, high-pigment clay mixes. Declared in JS so
Mantine regenerates every derived variant var (-light rgba, -outline,
autoContrast picks) from the retuned shades. Shade 4 (the fill) keeps
>=4.5:1 with the clay ink #33291f; shade 8 keeps >=4.5:1 on the light
backdrop; shades 3-4 keep >=4.5:1 on the dark backdrop. */
const clayColors = {
// denim clay
blue: [
"#eef3f7",
"#dbe7f0",
"#c2d7e8",
"#9cc0dc",
"#6fa5cd",
"#5590bd",
"#4478a3",
"#396286",
"#2d4d69",
"#22394e",
],
// terracotta
red: [
"#f9eeea",
"#f2dcd5",
"#ecc5b9",
"#e8a893",
"#e08466",
"#cd6a4c",
"#b0543a",
"#8f4430",
"#713627",
"#54291e",
],
// moss clay
green: [
"#edf4ea",
"#dcead5",
"#c4dcb9",
"#a3c996",
"#7fb371",
"#689e5b",
"#538148",
"#44693c",
"#365330",
"#283d24",
],
// mustard clay
yellow: [
"#faf4e2",
"#f5e8c2",
"#efd99b",
"#e9c96f",
"#e0b545",
"#c99b2e",
"#a67d24",
"#86641e",
"#684d18",
"#4b3812",
],
// plum clay
grape: [
"#f4eef6",
"#e8dcec",
"#d9c4e0",
"#c5a4d2",
"#b28ac3",
"#9c70ae",
"#7c548d",
"#654573",
"#4f365a",
"#3a2842",
],
// apricot clay
orange: [
"#faf0e6",
"#f4dfc9",
"#eecaa6",
"#e8b17e",
"#de9355",
"#c97a3d",
"#a86230",
"#885028",
"#6a3f20",
"#4d2e18",
],
// rose clay
pink: [
"#f9eef1",
"#f2dbe2",
"#ecc2cf",
"#e6a1b6",
"#dd7f9c",
"#c96585",
"#ab4f6d",
"#8b4059",
"#6d3346",
"#502634",
],
// pea clay
lime: [
"#f3f6e4",
"#e7edc6",
"#d6e19f",
"#c1d174",
"#a8bd4c",
"#90a636",
"#75882b",
"#5f6e24",
"#4a561d",
"#363f16",
],
// warm putty — light-scheme neutrals, borders, dimmed text
gray: [
"#f7f3ec",
"#ede7dd",
"#e0d8cb",
"#cec3b2",
"#b5a795",
"#97887a",
"#786a5d",
"#5d5248",
"#453d36",
"#2e2925",
],
// night studio — dark-scheme body/surfaces/borders, warm-tinted
dark: [
"#e8e0da",
"#cfc4bd",
"#b1a49e",
"#857a78",
"#56494e",
"#453a3f",
"#332c30",
"#262023",
"#1e191c",
"#141013",
],
} as const;
const manifest: ThemeManifest = {
id: "clay",
label: msg`Claymation`,
colorSchemes: "both",
fontLinks: [
"https://fonts.googleapis.com/css2?family=Baloo+2:wght@500;600;700;800&display=swap",
"https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700;800&display=swap",
],
chromeColors: {
light: { base: "#efe9e2", dimmed: "#d8cfc2" },
dark: { base: "#262023", dimmed: "#141013" },
},
mantine: {
defaultRadius: "md",
// Chalky mid-tone fills pair with dark clay ink, not white
autoContrast: true,
luminanceThreshold: 0.28,
black: "#33291f",
primaryShade: { light: 4, dark: 4 },
colors: clayColors,
},
};
export default manifest;
+620
View File
@@ -0,0 +1,620 @@
/* Claymation — hand-pinched plasticine world.
Light: soft studio backdrop; surfaces are matte clay blobs with irregular
organic radii, "pinched" depth from an inner top highlight plus a soft
bottom shadow. No hard edges anywhere.
Dark: night studio with a warm key light — highlights warmer, shadows
deeper. Filled controls carry dark clay ink, never white-on-mid-tone. */
/* ---------- 1. Tokens, any scheme ---------- */
[data-theme="clay"][data-theme] {
--mantine-font-family: "Nunito", "Inter", system-ui, sans-serif;
--mantine-font-family-headings: "Baloo 2", "Nunito", system-ui, sans-serif;
--mantine-heading-font-weight: 700;
--mantine-radius-xs: 10px;
--mantine-radius-sm: 14px;
--mantine-radius-md: 18px;
--mantine-radius-lg: 24px;
--mantine-radius-xl: 30px;
--mantine-radius-default: 18px;
/* hand-pinched blob radii, varied per component */
--clay-blob-card: 26px 20px 28px 22px / 22px 28px 20px 26px;
--clay-blob-card-alt: 20px 28px 22px 26px / 28px 20px 26px 22px;
--clay-blob-md: 20px 16px 22px 18px / 18px 22px 16px 20px;
--clay-blob-btn: 16px 20px 14px 22px / 20px 14px 22px 16px;
--clay-blob-round: 42% 58% 55% 45% / 48% 45% 55% 52%;
--clay-blob-pellet: 14px 18px 15px 19px / 18px 14px 19px 15px;
--clay-ink: #33291f;
--flxn-avatar-radius: 45%;
--flxn-avatar-border: 3px solid var(--clay-ring);
--flxn-badge-radius: 16px 20px 18px 22px;
}
/* ---------- 2. Light — soft studio ---------- */
[data-theme="clay"][data-theme][data-mantine-color-scheme="light"] {
--clay-surface: #f6f0e7;
--clay-surface-hover: #faf5ee;
--clay-well: #e5dccd;
--clay-border: #d3c8b8;
--clay-ring: #fffdf7;
--clay-inset: inset 0 3px 6px rgba(122, 104, 86, 0.28),
inset 0 -1px 0 rgba(255, 255, 255, 0.5);
--clay-inset-sm: inset 0 2px 4px rgba(122, 104, 86, 0.24),
inset 0 -1px 0 rgba(255, 255, 255, 0.45);
--mantine-color-body: #efe9e2;
--mantine-color-text: #3a3128;
--mantine-color-bright: #241f19;
--mantine-color-dimmed: #6d6156;
--mantine-color-placeholder: #837568;
--mantine-color-anchor: var(--mantine-primary-color-light-color);
--mantine-color-error: var(--mantine-color-red-7);
--mantine-color-default: #f6f0e7;
--mantine-color-default-hover: #faf5ee;
--mantine-color-default-color: #3a3128;
--mantine-color-default-border: #d3c8b8;
/* primaryShade 4 gives chalky plasticine fills, but Mantine then derives
chalky light-variant/text/outline colors that fail contrast on the light
backdrop. Re-point every ramp's aliases at deeper shades so
--mantine-primary-color-* keeps following the user's accent. */
--mantine-color-blue-light: color-mix(in srgb, var(--mantine-color-blue-4) 18%, transparent);
--mantine-color-blue-light-hover: color-mix(in srgb, var(--mantine-color-blue-4) 26%, transparent);
--mantine-color-blue-light-color: var(--mantine-color-blue-8);
--mantine-color-blue-text: var(--mantine-color-blue-8);
--mantine-color-blue-outline: var(--mantine-color-blue-7);
--mantine-color-red-light: color-mix(in srgb, var(--mantine-color-red-4) 18%, transparent);
--mantine-color-red-light-hover: color-mix(in srgb, var(--mantine-color-red-4) 26%, transparent);
--mantine-color-red-light-color: var(--mantine-color-red-8);
--mantine-color-red-text: var(--mantine-color-red-8);
--mantine-color-red-outline: var(--mantine-color-red-7);
--mantine-color-green-light: color-mix(in srgb, var(--mantine-color-green-4) 18%, transparent);
--mantine-color-green-light-hover: color-mix(in srgb, var(--mantine-color-green-4) 26%, transparent);
--mantine-color-green-light-color: var(--mantine-color-green-8);
--mantine-color-green-text: var(--mantine-color-green-8);
--mantine-color-green-outline: var(--mantine-color-green-7);
--mantine-color-yellow-light: color-mix(in srgb, var(--mantine-color-yellow-4) 18%, transparent);
--mantine-color-yellow-light-hover: color-mix(in srgb, var(--mantine-color-yellow-4) 26%, transparent);
--mantine-color-yellow-light-color: var(--mantine-color-yellow-8);
--mantine-color-yellow-text: var(--mantine-color-yellow-8);
--mantine-color-yellow-outline: var(--mantine-color-yellow-7);
--mantine-color-grape-light: color-mix(in srgb, var(--mantine-color-grape-4) 18%, transparent);
--mantine-color-grape-light-hover: color-mix(in srgb, var(--mantine-color-grape-4) 26%, transparent);
--mantine-color-grape-light-color: var(--mantine-color-grape-8);
--mantine-color-grape-text: var(--mantine-color-grape-8);
--mantine-color-grape-outline: var(--mantine-color-grape-7);
--mantine-color-orange-light: color-mix(in srgb, var(--mantine-color-orange-4) 18%, transparent);
--mantine-color-orange-light-hover: color-mix(in srgb, var(--mantine-color-orange-4) 26%, transparent);
--mantine-color-orange-light-color: var(--mantine-color-orange-8);
--mantine-color-orange-text: var(--mantine-color-orange-8);
--mantine-color-orange-outline: var(--mantine-color-orange-7);
--mantine-color-pink-light: color-mix(in srgb, var(--mantine-color-pink-4) 18%, transparent);
--mantine-color-pink-light-hover: color-mix(in srgb, var(--mantine-color-pink-4) 26%, transparent);
--mantine-color-pink-light-color: var(--mantine-color-pink-8);
--mantine-color-pink-text: var(--mantine-color-pink-8);
--mantine-color-pink-outline: var(--mantine-color-pink-7);
--mantine-color-lime-light: color-mix(in srgb, var(--mantine-color-lime-4) 18%, transparent);
--mantine-color-lime-light-hover: color-mix(in srgb, var(--mantine-color-lime-4) 26%, transparent);
--mantine-color-lime-light-color: var(--mantine-color-lime-8);
--mantine-color-lime-text: var(--mantine-color-lime-8);
--mantine-color-lime-outline: var(--mantine-color-lime-7);
/* pinched elevation: inner top highlight + soft warm drop */
--mantine-shadow-xs: inset 0 1px 0 rgba(255, 255, 255, 0.55),
0 3px 8px rgba(122, 104, 86, 0.18);
--mantine-shadow-sm: inset 0 2px 3px rgba(255, 255, 255, 0.6),
0 6px 14px rgba(122, 104, 86, 0.22);
--mantine-shadow-md: inset 0 2px 4px rgba(255, 255, 255, 0.6),
0 10px 22px rgba(122, 104, 86, 0.26);
--mantine-shadow-lg: inset 0 2px 4px rgba(255, 255, 255, 0.62),
0 14px 30px rgba(122, 104, 86, 0.3);
--mantine-shadow-xl: inset 0 3px 5px rgba(255, 255, 255, 0.65),
0 20px 40px rgba(122, 104, 86, 0.34);
/* app tokens */
--flxn-gold: #8a6716;
--flxn-silver: #6f665c;
--flxn-bronze: #8a5a34;
--flxn-toast-success: #538148;
--flxn-toast-error: #b0543a;
--flxn-podium-1-bg: color-mix(in srgb, #e0b545 12%, #f6f0e7);
--flxn-podium-1-border: color-mix(in srgb, #a67d24 40%, #d3c8b8);
--flxn-podium-1-label: #7c5d1b;
--flxn-podium-2-bg: color-mix(in srgb, #97887a 12%, #f6f0e7);
--flxn-podium-2-border: color-mix(in srgb, #786a5d 35%, #d3c8b8);
--flxn-podium-2-label: #5d5248;
--flxn-podium-3-bg: color-mix(in srgb, #c97a3d 12%, #f6f0e7);
--flxn-podium-3-border: color-mix(in srgb, #a86230 38%, #d3c8b8);
--flxn-podium-3-label: #7d4a22;
--flxn-showcase-bg: #f6f0e7;
--flxn-overlay: rgba(58, 49, 40, 0.45);
--flxn-dotgrid-color: #d9cfc1;
--flxn-pop-shadow: rgba(122, 104, 86, 0.4);
}
/* ---------- 2. Dark — night studio, warm key light ---------- */
[data-theme="clay"][data-theme][data-mantine-color-scheme="dark"] {
--clay-surface: #332c30;
--clay-surface-hover: #3b3336;
--clay-well: #1f1a1d;
--clay-border: #4a3f42;
--clay-ring: #4a4046;
--clay-inset: inset 0 3px 6px rgba(0, 0, 0, 0.5),
inset 0 -1px 0 rgba(255, 225, 185, 0.08);
--clay-inset-sm: inset 0 2px 4px rgba(0, 0, 0, 0.45),
inset 0 -1px 0 rgba(255, 225, 185, 0.07);
--mantine-color-body: #262023;
--mantine-color-text: #efe6dc;
--mantine-color-bright: #fbf6ef;
--mantine-color-dimmed: #b3a79d;
--mantine-color-placeholder: #8c7f76;
--mantine-color-anchor: var(--mantine-primary-color-3);
--mantine-color-error: var(--mantine-color-red-3);
--mantine-color-default: #332c30;
--mantine-color-default-hover: #3b3336;
--mantine-color-default-color: #efe6dc;
--mantine-color-default-border: #4a3f42;
/* warm inner highlight, deep studio shadows */
--mantine-shadow-xs: inset 0 1px 0 rgba(255, 225, 185, 0.1),
0 3px 8px rgba(0, 0, 0, 0.4);
--mantine-shadow-sm: inset 0 2px 3px rgba(255, 225, 185, 0.12),
0 8px 16px rgba(0, 0, 0, 0.45);
--mantine-shadow-md: inset 0 2px 4px rgba(255, 225, 185, 0.13),
0 12px 24px rgba(0, 0, 0, 0.5);
--mantine-shadow-lg: inset 0 2px 4px rgba(255, 225, 185, 0.14),
0 16px 32px rgba(0, 0, 0, 0.55);
--mantine-shadow-xl: inset 0 3px 5px rgba(255, 225, 185, 0.15),
0 22px 44px rgba(0, 0, 0, 0.6);
/* app tokens */
--flxn-gold: #e0bd6a;
--flxn-silver: #b8ada2;
--flxn-bronze: #d69a6e;
--flxn-toast-success: #9cc98e;
--flxn-toast-error: #eda28c;
--flxn-podium-1-bg: color-mix(in srgb, #e0bd6a 13%, #332c30);
--flxn-podium-1-border: color-mix(in srgb, #e0bd6a 32%, #4a3f42);
--flxn-podium-1-label: #e0bd6a;
--flxn-podium-2-bg: color-mix(in srgb, #b8ada2 11%, #332c30);
--flxn-podium-2-border: color-mix(in srgb, #b8ada2 28%, #4a3f42);
--flxn-podium-2-label: #b8ada2;
--flxn-podium-3-bg: color-mix(in srgb, #d69a6e 12%, #332c30);
--flxn-podium-3-border: color-mix(in srgb, #d69a6e 28%, #4a3f42);
--flxn-podium-3-label: #d69a6e;
--flxn-showcase-bg: #332c30;
--flxn-overlay: rgba(0, 0, 0, 0.55);
--flxn-dotgrid-color: rgba(255, 235, 215, 0.12);
--flxn-pop-shadow: rgba(0, 0, 0, 0.6);
}
/* ---------- 3. Type flavor ---------- */
[data-theme="clay"] body {
font-weight: 500;
}
/* ---------- 4. Backdrop — studio key light + clay grain ---------- */
[data-theme="clay"] body::before {
content: "";
position: fixed;
inset: 0;
z-index: -1; /* behind content — never tints text or surfaces */
pointer-events: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
}
[data-theme="clay"][data-mantine-color-scheme="light"] body::before {
background-image: radial-gradient(
120% 70% at 50% 0%,
rgba(255, 252, 244, 0.65),
transparent 65%
),
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
}
[data-theme="clay"][data-mantine-color-scheme="dark"] body::before {
background-image: radial-gradient(
110% 65% at 50% 0%,
rgba(255, 208, 158, 0.09),
transparent 62%
),
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E");
}
/* ---------- 5. Clay blob surfaces ---------- */
[data-theme="clay"] .mantine-Paper-root {
background-color: var(--clay-surface);
border: 1px solid var(--clay-border);
border-radius: var(--clay-blob-md);
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="clay"] .mantine-Card-root {
border-radius: var(--clay-blob-card);
}
[data-theme="clay"] .mantine-Popover-dropdown,
[data-theme="clay"] .mantine-Menu-dropdown,
[data-theme="clay"] .mantine-Modal-content,
[data-theme="clay"] .mantine-Notification-root {
background-color: var(--clay-surface);
border: 1px solid var(--clay-border);
box-shadow: var(--mantine-shadow-lg);
}
[data-theme="clay"] .mantine-Modal-content {
border-radius: var(--clay-blob-card);
}
[data-theme="clay"] .mantine-Modal-header {
background-color: var(--clay-surface);
}
[data-theme="clay"] .mantine-Notification-root {
border-radius: var(--clay-blob-md);
}
[data-theme="clay"] .mantine-Alert-root {
border-radius: var(--clay-blob-md);
box-shadow: var(--mantine-shadow-xs);
}
[data-theme="clay"] .mantine-Tooltip-tooltip {
border-radius: var(--clay-blob-pellet);
font-weight: 600;
box-shadow: var(--mantine-shadow-xs);
}
[data-theme="clay"][data-mantine-color-scheme="light"] .mantine-Tooltip-tooltip {
background-color: #3a3128;
color: #f3ece2;
}
[data-theme="clay"][data-mantine-color-scheme="dark"] .mantine-Tooltip-tooltip {
background-color: #efe6dc;
color: #33291f;
}
/* App shell: rolled clay slabs, soft edges */
[data-theme="clay"] .mantine-AppShell-header {
background-color: var(--clay-surface);
border-bottom: 1px solid var(--clay-border);
box-shadow: var(--mantine-shadow-xs);
}
[data-theme="clay"] .mantine-AppShell-navbar {
background-color: var(--clay-surface);
border-top: 1px solid var(--clay-border);
border-inline-end: 1px solid var(--clay-border);
box-shadow: var(--mantine-shadow-xs);
}
/* ---------- 6. Thumbed buttons ---------- */
[data-theme="clay"] .mantine-Button-root {
border: 1px solid var(--clay-border);
border-radius: var(--clay-blob-btn);
font-family: "Baloo 2", "Nunito", system-ui, sans-serif;
font-weight: 700;
box-shadow: var(--mantine-shadow-sm);
transition: box-shadow 160ms ease, background-color 120ms ease-out;
}
[data-theme="clay"] .mantine-ActionIcon-root {
border: 1px solid var(--clay-border);
border-radius: var(--clay-blob-round);
box-shadow: var(--mantine-shadow-xs);
}
[data-theme="clay"] .mantine-Button-root:where([data-variant="filled"], :not([data-variant])),
[data-theme="clay"] .mantine-ActionIcon-root:where([data-variant="filled"], :not([data-variant])) {
border-color: color-mix(in srgb, var(--mantine-primary-color-filled) 78%, var(--clay-ink));
}
[data-theme="clay"] .mantine-Button-root:where([data-variant="default"], [data-variant="outline"]),
[data-theme="clay"] .mantine-ActionIcon-root:where([data-variant="default"], [data-variant="outline"]) {
background-color: var(--clay-surface);
}
[data-theme="clay"] .mantine-Button-root:where([data-variant="default"], [data-variant="outline"]):hover,
[data-theme="clay"] .mantine-ActionIcon-root:where([data-variant="default"], [data-variant="outline"]):hover {
background-color: var(--clay-surface-hover);
}
/* Ghost variants stay flat — no floating clay plates */
[data-theme="clay"] .mantine-Button-root:where([data-variant="subtle"], [data-variant="transparent"], [data-variant="white"]),
[data-theme="clay"] .mantine-ActionIcon-root:where([data-variant="subtle"], [data-variant="transparent"], [data-variant="white"]) {
border-color: transparent;
box-shadow: none;
}
[data-theme="clay"] .mantine-Button-root:active:not([data-disabled], :disabled, [data-loading]),
[data-theme="clay"] .mantine-ActionIcon-root:active:not([data-disabled], :disabled, [data-loading]) {
box-shadow: var(--clay-inset-sm);
}
[data-theme="clay"] .mantine-Button-root:where([data-disabled], :disabled),
[data-theme="clay"] .mantine-ActionIcon-root:where([data-disabled], :disabled) {
box-shadow: none;
}
/* Every chalky fill pairs with the dark clay ink, never white */
[data-theme="clay"] .mantine-Button-root:not([data-variant]):not(:disabled):not([data-disabled]),
[data-theme="clay"] .mantine-Button-root[data-variant="filled"]:not(:disabled):not([data-disabled]),
[data-theme="clay"] .mantine-ActionIcon-root:not([data-variant]):not(:disabled):not([data-disabled]),
[data-theme="clay"] .mantine-ActionIcon-root[data-variant="filled"]:not(:disabled):not([data-disabled]),
[data-theme="clay"] .mantine-Badge-root:not([data-variant]),
[data-theme="clay"] .mantine-Badge-root[data-variant="filled"],
[data-theme="clay"] .mantine-ThemeIcon-root:not([data-variant]),
[data-theme="clay"] .mantine-ThemeIcon-root[data-variant="filled"] {
color: var(--clay-ink);
}
/* Extra squish: pressed clay squashes wide and short */
@media (prefers-reduced-motion: no-preference) {
[data-theme="clay"] .flxn-press {
transition: transform 160ms cubic-bezier(0.34, 1.3, 0.5, 1);
}
[data-theme="clay"]
.flxn-press:active:not(:disabled):not([data-disabled]):not([data-loading]) {
transform: scale(1.02, 0.92);
}
}
/* ---------- 7. Inputs — thumb-pressed wells ---------- */
[data-theme="clay"] .mantine-Input-input {
background-color: var(--clay-surface);
border: 1px solid var(--clay-border);
border-radius: var(--clay-blob-md);
box-shadow: var(--clay-inset-sm);
transition: border-color 160ms ease, box-shadow 160ms ease;
}
[data-theme="clay"] .mantine-Input-input:focus,
[data-theme="clay"] .mantine-Input-input:focus-within {
border-color: var(--mantine-primary-color-filled);
box-shadow: var(--clay-inset);
}
[data-theme="clay"] .mantine-Input-input[data-error] {
border-color: var(--mantine-color-error);
}
[data-theme="clay"] .mantine-PinInput-field {
border-radius: var(--clay-blob-pellet);
font-family: "Baloo 2", "Nunito", system-ui, sans-serif;
}
/* ---------- 8. Clay pellet badges ---------- */
[data-theme="clay"] .mantine-Badge-root {
border: 1px solid var(--clay-border);
border-radius: var(--clay-blob-pellet);
font-family: "Baloo 2", "Nunito", system-ui, sans-serif;
font-weight: 700;
text-transform: none;
letter-spacing: 0.01em;
box-shadow: var(--mantine-shadow-xs);
}
[data-theme="clay"] .mantine-Badge-root:where([data-variant="filled"], :not([data-variant])) {
border-color: color-mix(in srgb, var(--mantine-primary-color-filled) 78%, var(--clay-ink));
}
[data-theme="clay"] .mantine-Badge-root[data-variant="default"] {
background-color: var(--clay-surface);
color: var(--mantine-color-text);
}
[data-theme="clay"] .mantine-ThemeIcon-root {
border-radius: var(--clay-blob-round);
}
[data-theme="clay"] .mantine-ThemeIcon-root[data-variant="default"] {
background-color: var(--clay-surface);
border: 1px solid var(--clay-border);
box-shadow: var(--mantine-shadow-xs);
}
/* ---------- 9. Selection controls ---------- */
/* SegmentedControl: pressed trough, raised clay pellet indicator */
[data-theme="clay"] .mantine-SegmentedControl-root {
background-color: var(--clay-well);
border: 1px solid var(--clay-border);
border-radius: var(--clay-blob-md);
box-shadow: var(--clay-inset-sm);
}
[data-theme="clay"] .mantine-SegmentedControl-indicator {
background-color: var(--clay-surface);
border: 1px solid var(--clay-border);
border-radius: var(--clay-blob-pellet);
box-shadow: var(--mantine-shadow-xs);
}
[data-theme="clay"] .mantine-SegmentedControl-label {
font-weight: 700;
}
[data-theme="clay"] .mantine-SegmentedControl-label[data-active] {
color: var(--mantine-color-bright);
}
/* Switch: trough + rolled clay ball thumb */
[data-theme="clay"] .mantine-Switch-track {
border: 1px solid var(--clay-border);
border-radius: 999px;
box-shadow: var(--clay-inset-sm);
}
[data-theme="clay"] .mantine-Switch-thumb {
border: 1px solid var(--clay-border);
border-radius: var(--clay-blob-round);
box-shadow: var(--mantine-shadow-xs);
}
[data-theme="clay"] .mantine-Checkbox-input {
border: 2px solid var(--clay-border);
border-radius: 9px 11px 10px 12px / 11px 9px 12px 10px;
}
[data-theme="clay"] .mantine-Checkbox-input:not(:checked) {
background-color: var(--clay-surface);
box-shadow: var(--clay-inset-sm);
}
/* Progress: finger-groove trough */
[data-theme="clay"] .mantine-Progress-root {
background-color: var(--clay-well);
border: 1px solid var(--clay-border);
border-radius: 999px;
box-shadow: var(--clay-inset-sm);
}
[data-theme="clay"] .mantine-Progress-section {
border-radius: 999px;
}
/* ---------- 10. Navigation & misc ---------- */
[data-theme="clay"] .mantine-Tabs-list {
border-bottom-color: transparent;
gap: 0.25rem;
}
[data-theme="clay"] .mantine-Tabs-tab {
border: 0;
border-radius: var(--clay-blob-pellet);
font-weight: 700;
}
[data-theme="clay"] .mantine-Tabs-tab[data-active] {
background-color: var(--mantine-primary-color-light);
color: var(--mantine-primary-color-light-color);
}
[data-theme="clay"] .mantine-NavLink-root {
border: 1px solid transparent;
border-radius: var(--clay-blob-md);
font-weight: 600;
}
[data-theme="clay"] .mantine-NavLink-root[data-active] {
background-color: var(--mantine-primary-color-light);
border-color: var(--clay-border);
color: var(--mantine-primary-color-light-color);
}
[data-theme="clay"] .mantine-Skeleton-root {
border-radius: var(--clay-blob-md);
}
[data-theme="clay"] .mantine-Divider-root {
--divider-color: var(--clay-border);
}
[data-theme="clay"] .mantine-Loader-root {
--loader-color: var(--mantine-primary-color-filled);
}
/* ---------- 11. App component hooks ---------- */
[data-theme="clay"] .flxn-tabs[data-stuck] {
background-color: var(--mantine-color-body);
border-bottom-color: var(--clay-border);
box-shadow: 0 8px 16px -8px var(--flxn-pop-shadow);
}
[data-theme="clay"] .flxn-tab.flxn-tab {
border-radius: var(--clay-blob-pellet);
font-weight: 600;
}
[data-theme="clay"] .flxn-tab.flxn-tab[data-active] {
color: var(--flxn-on-accent);
font-weight: 700;
}
[data-theme="clay"] .flxn-tabs-indicator.flxn-tabs-indicator {
border-bottom: none;
border-radius: var(--clay-blob-pellet);
background-color: var(--mantine-primary-color-filled);
box-shadow: var(--clay-inset-sm);
}
/* Nav grid tiles: pinched clay pats, radii varied like hand-rolled pieces */
[data-theme="clay"] .flxn-tile.flxn-tile {
background-color: var(--clay-surface);
border: 1px solid var(--clay-border);
border-radius: var(--clay-blob-card);
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="clay"] .flxn-tile.flxn-tile:nth-child(even) {
border-radius: var(--clay-blob-card-alt);
}
@media (hover: hover) {
[data-theme="clay"] .flxn-tile.flxn-tile:hover:not(:disabled) {
background-color: var(--clay-surface-hover);
}
}
[data-theme="clay"] .flxn-tile.flxn-tile:active:not(:disabled) {
box-shadow: var(--clay-inset-sm);
}
/* Flat glyph tint — no plates behind icons */
[data-theme="clay"] .flxn-tile-icon.flxn-tile-icon {
color: var(--mantine-primary-color-light-color);
}
/* "See all" as a small clay chip: accent wash + wash on-color */
[data-theme="clay"] .flxn-see-all.flxn-see-all {
font-family: "Baloo 2", "Nunito", system-ui, sans-serif;
font-weight: 700;
background-color: var(--mantine-primary-color-light);
border: 1px solid color-mix(in srgb, var(--mantine-primary-color-light-color) 30%, transparent);
border-radius: var(--clay-blob-pellet);
color: var(--mantine-primary-color-light-color);
}
@media (hover: hover) {
[data-theme="clay"] .flxn-see-all.flxn-see-all:hover {
background-color: var(--mantine-primary-color-light-hover);
text-decoration: none;
}
}
/* Match card + reaction tray: one pressed-together construction — the card
is a raised pat, the tray a thumb-groove in the same slab */
[data-theme="clay"] .flxn-match-card.flxn-match-card {
background-color: var(--clay-surface);
border: 1px solid var(--clay-border);
border-radius: var(--clay-blob-card);
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="clay"] .flxn-reaction-row.flxn-reaction-row {
background-color: var(--clay-well);
border: 1px solid var(--clay-border);
border-radius: var(--clay-blob-md);
box-shadow: var(--clay-inset-sm);
}
/* Blobby avatar: the frame owns the single 3px clay ring */
[data-theme="clay"] .flxn-avatar-frame {
box-shadow: var(--mantine-shadow-xs);
}
+20
View File
@@ -0,0 +1,20 @@
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
const manifest: ThemeManifest = {
id: "deco",
label: msg`Art Deco`,
colorSchemes: "both",
fontLinks: [
"https://fonts.googleapis.com/css2?family=Cinzel:wght@400..700&family=Josefin+Sans:ital,wght@0,300..700;1,300..700&display=swap",
],
chromeColors: {
light: { base: "#f6f1e5", dimmed: "#e7dfc9" },
dark: { base: "#16151a", dimmed: "#0e0d11" },
},
mantine: {
defaultRadius: 0,
},
};
export default manifest;
+619
View File
@@ -0,0 +1,619 @@
/* Art Deco — 1928 Gatsby luxury. Champagne gold on midnight ink (dark) and
ivory (light). Sharp geometry, double gold rules, gold-foil ornament.
Gold is STRUCTURAL decoration only; the interactive accent follows the
user's choice via the primary aliases over eight jewel-tone ramps. */
/* ------------------------------------------------------------------ */
/* 1. Tokens, any scheme */
/* ------------------------------------------------------------------ */
[data-theme="deco"][data-theme] {
--mantine-font-family: "Josefin Sans", "Avenir Next", "Century Gothic", sans-serif;
--mantine-font-family-headings: "Cinzel", "Trajan Pro", "Times New Roman", serif;
--mantine-heading-font-weight: 600;
--mantine-radius-default: 0;
--mantine-radius-xs: 0;
--mantine-radius-sm: 0;
--mantine-radius-md: 0;
--mantine-radius-lg: 0;
--mantine-radius-xl: 0;
--flxn-avatar-radius: 0;
--flxn-avatar-border: 1px solid var(--deco-rule);
--flxn-badge-radius: 0;
--mantine-color-white: #fcf9f0;
--mantine-color-black: #16151a;
/* Jewel-tone accent ramps. Shade 6 (light filled/text) holds >= 4.5:1
against ivory; shades 3-4 (dark text/anchor) read on midnight ink. */
/* blue -> sapphire */
--mantine-color-blue-0: #eaf1fb;
--mantine-color-blue-1: #cfdff4;
--mantine-color-blue-2: #a9c6e8;
--mantine-color-blue-3: #7fa8d8;
--mantine-color-blue-4: #5a8ac6;
--mantine-color-blue-5: #3d6eb0;
--mantine-color-blue-6: #2c5697;
--mantine-color-blue-7: #224477;
--mantine-color-blue-8: #19345c;
--mantine-color-blue-9: #112544;
/* red -> ruby */
--mantine-color-red-0: #fdecee;
--mantine-color-red-1: #f7cdd2;
--mantine-color-red-2: #eea6af;
--mantine-color-red-3: #e17d8a;
--mantine-color-red-4: #cf5766;
--mantine-color-red-5: #b93a4c;
--mantine-color-red-6: #9e2b3f;
--mantine-color-red-7: #822235;
--mantine-color-red-8: #651a2a;
--mantine-color-red-9: #4a121f;
/* green -> emerald */
--mantine-color-green-0: #e6f5ee;
--mantine-color-green-1: #c6e8d8;
--mantine-color-green-2: #9cd6bc;
--mantine-color-green-3: #6fc19e;
--mantine-color-green-4: #46aa82;
--mantine-color-green-5: #21936a;
--mantine-color-green-6: #147a57;
--mantine-color-green-7: #0f6247;
--mantine-color-green-8: #0b4c37;
--mantine-color-green-9: #073828;
/* yellow -> champagne gold */
--mantine-color-yellow-0: #fbf6e3;
--mantine-color-yellow-1: #f7ecc4;
--mantine-color-yellow-2: #f5e08c;
--mantine-color-yellow-3: #eacf6d;
--mantine-color-yellow-4: #dfbd52;
--mantine-color-yellow-5: #d4af37;
--mantine-color-yellow-6: #8a6508;
--mantine-color-yellow-7: #6f5106;
--mantine-color-yellow-8: #563e05;
--mantine-color-yellow-9: #3d2c03;
/* grape -> amethyst */
--mantine-color-grape-0: #f4edfa;
--mantine-color-grape-1: #e2d3f2;
--mantine-color-grape-2: #cdb1e8;
--mantine-color-grape-3: #b58ade;
--mantine-color-grape-4: #9c6ecb;
--mantine-color-grape-5: #7f4cb3;
--mantine-color-grape-6: #663693;
--mantine-color-grape-7: #532b79;
--mantine-color-grape-8: #40215e;
--mantine-color-grape-9: #2e1844;
/* orange -> amber */
--mantine-color-orange-0: #fdf1e3;
--mantine-color-orange-1: #f9dcbb;
--mantine-color-orange-2: #f2c184;
--mantine-color-orange-3: #e8a355;
--mantine-color-orange-4: #da8730;
--mantine-color-orange-5: #c66f15;
--mantine-color-orange-6: #a85a0c;
--mantine-color-orange-7: #8a4a09;
--mantine-color-orange-8: #6b3a07;
--mantine-color-orange-9: #4d2a05;
/* pink -> rose */
--mantine-color-pink-0: #fbedf3;
--mantine-color-pink-1: #f5cfe0;
--mantine-color-pink-2: #eba7c6;
--mantine-color-pink-3: #dd7aa8;
--mantine-color-pink-4: #c9538b;
--mantine-color-pink-5: #b23a72;
--mantine-color-pink-6: #a12a5e;
--mantine-color-pink-7: #862150;
--mantine-color-pink-8: #681940;
--mantine-color-pink-9: #4b122e;
/* lime -> peridot */
--mantine-color-lime-0: #f5f7e1;
--mantine-color-lime-1: #e8edbd;
--mantine-color-lime-2: #d5df90;
--mantine-color-lime-3: #c0cd64;
--mantine-color-lime-4: #a8b83f;
--mantine-color-lime-5: #8c9c26;
--mantine-color-lime-6: #5f6e12;
--mantine-color-lime-7: #4c580e;
--mantine-color-lime-8: #39430b;
--mantine-color-lime-9: #283007;
/* minimal shadows with a whisper of gold edge-glow */
--mantine-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.16);
--mantine-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.18), 0 0 0 1px rgba(212, 175, 55, 0.08);
--mantine-shadow-md: 0 2px 6px rgba(0, 0, 0, 0.2), 0 0 10px rgba(212, 175, 55, 0.07);
--mantine-shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.24), 0 0 16px rgba(212, 175, 55, 0.09);
--mantine-shadow-xl: 0 8px 24px rgba(0, 0, 0, 0.28), 0 0 22px rgba(212, 175, 55, 0.1);
}
/* ------------------------------------------------------------------ */
/* 2. Per-scheme tokens */
/* ------------------------------------------------------------------ */
[data-theme="deco"][data-theme][data-mantine-color-scheme="dark"] {
--mantine-color-body: #16151a;
--mantine-color-text: #eae3d1;
--mantine-color-bright: #f6f1e5;
--mantine-color-dimmed: #a79d85;
--mantine-color-placeholder: #837a66;
--mantine-color-error: #d06a6d;
--mantine-color-default: #201e25;
--mantine-color-default-hover: #272430;
--mantine-color-default-color: #eae3d1;
--mantine-color-default-border: #4a4230;
/* ink-charcoal surface ramp */
--mantine-color-dark-0: #d6d0c2;
--mantine-color-dark-1: #aaa294;
--mantine-color-dark-2: #8a8274;
--mantine-color-dark-3: #5c564a;
--mantine-color-dark-4: #444038;
--mantine-color-dark-5: #302d31;
--mantine-color-dark-6: #232127;
--mantine-color-dark-7: #1b1a1f;
--mantine-color-dark-8: #141317;
--mantine-color-dark-9: #101014;
/* deep jewel fills in dark too (Mantine defaults to shade 8) */
--mantine-color-blue-filled: var(--mantine-color-blue-6);
--mantine-color-blue-filled-hover: var(--mantine-color-blue-5);
--mantine-color-red-filled: var(--mantine-color-red-6);
--mantine-color-red-filled-hover: var(--mantine-color-red-5);
--mantine-color-green-filled: var(--mantine-color-green-6);
--mantine-color-green-filled-hover: var(--mantine-color-green-5);
--mantine-color-yellow-filled: var(--mantine-color-yellow-6);
--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-7);
--mantine-color-grape-filled: var(--mantine-color-grape-6);
--mantine-color-grape-filled-hover: var(--mantine-color-grape-5);
--mantine-color-orange-filled: var(--mantine-color-orange-6);
--mantine-color-orange-filled-hover: var(--mantine-color-orange-5);
--mantine-color-pink-filled: var(--mantine-color-pink-6);
--mantine-color-pink-filled-hover: var(--mantine-color-pink-5);
--mantine-color-lime-filled: var(--mantine-color-lime-6);
--mantine-color-lime-filled-hover: var(--mantine-color-lime-7);
/* soft-variant tints from the jewel mid-shades */
--mantine-color-blue-light: rgba(90, 138, 198, 0.14);
--mantine-color-blue-light-hover: rgba(90, 138, 198, 0.2);
--mantine-color-blue-outline-hover: rgba(90, 138, 198, 0.08);
--mantine-color-red-light: rgba(207, 87, 102, 0.14);
--mantine-color-red-light-hover: rgba(207, 87, 102, 0.2);
--mantine-color-red-outline-hover: rgba(207, 87, 102, 0.08);
--mantine-color-green-light: rgba(70, 170, 130, 0.14);
--mantine-color-green-light-hover: rgba(70, 170, 130, 0.2);
--mantine-color-green-outline-hover: rgba(70, 170, 130, 0.08);
--mantine-color-yellow-light: rgba(212, 175, 55, 0.14);
--mantine-color-yellow-light-hover: rgba(212, 175, 55, 0.2);
--mantine-color-yellow-outline-hover: rgba(212, 175, 55, 0.08);
--mantine-color-grape-light: rgba(156, 110, 203, 0.14);
--mantine-color-grape-light-hover: rgba(156, 110, 203, 0.2);
--mantine-color-grape-outline-hover: rgba(156, 110, 203, 0.08);
--mantine-color-orange-light: rgba(218, 135, 48, 0.14);
--mantine-color-orange-light-hover: rgba(218, 135, 48, 0.2);
--mantine-color-orange-outline-hover: rgba(218, 135, 48, 0.08);
--mantine-color-pink-light: rgba(201, 83, 139, 0.14);
--mantine-color-pink-light-hover: rgba(201, 83, 139, 0.2);
--mantine-color-pink-outline-hover: rgba(201, 83, 139, 0.08);
--mantine-color-lime-light: rgba(168, 184, 63, 0.14);
--mantine-color-lime-light-hover: rgba(168, 184, 63, 0.2);
--mantine-color-lime-outline-hover: rgba(168, 184, 63, 0.08);
--mantine-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(212, 175, 55, 0.1);
--mantine-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.45), 0 0 12px rgba(212, 175, 55, 0.08);
/* deco internals */
--deco-rule: rgba(212, 175, 55, 0.55);
--deco-rule-faint: rgba(212, 175, 55, 0.28);
--deco-chrome: #1b1a20;
--deco-gold-text: #eacf6d;
--deco-ray: rgba(212, 175, 55, 0.045);
--deco-tray: #121116;
/* app tokens */
--flxn-gold: #d4af37;
--flxn-silver: #c9ccd6;
--flxn-bronze: #c08447;
--flxn-toast-success: #46aa82;
--flxn-toast-error: #d06a6d;
--flxn-podium-1-bg: linear-gradient(150deg, rgba(245, 224, 140, 0.16), rgba(184, 134, 11, 0.1) 55%, rgba(122, 86, 7, 0.16));
--flxn-podium-1-border: #b8860b;
--flxn-podium-1-label: #eacf6d;
--flxn-podium-2-bg: linear-gradient(150deg, rgba(232, 234, 240, 0.13), rgba(148, 153, 166, 0.08) 55%, rgba(96, 101, 114, 0.12));
--flxn-podium-2-border: #9aa0ad;
--flxn-podium-2-label: #cfd4de;
--flxn-podium-3-bg: linear-gradient(150deg, rgba(222, 152, 82, 0.14), rgba(160, 106, 53, 0.09) 55%, rgba(110, 70, 32, 0.14));
--flxn-podium-3-border: #a06a35;
--flxn-podium-3-label: #d9a066;
--flxn-showcase-bg: #1b1a20;
--flxn-overlay: rgba(8, 7, 10, 0.62);
--flxn-dotgrid-color: rgba(212, 175, 55, 0.16);
--flxn-pop-shadow: rgba(0, 0, 0, 0.5);
}
[data-theme="deco"][data-theme][data-mantine-color-scheme="light"] {
--mantine-color-body: #f6f1e5;
--mantine-color-text: #26200f;
--mantine-color-bright: #131008;
--mantine-color-dimmed: #6d6450;
--mantine-color-placeholder: #948a72;
--mantine-color-error: #9e2b3f;
--mantine-color-default: #fbf7ec;
--mantine-color-default-hover: #f0e9d6;
--mantine-color-default-color: #26200f;
--mantine-color-default-border: #d3c69f;
/* warm champagne gray ramp */
--mantine-color-gray-0: #f3edda;
--mantine-color-gray-1: #eae2ca;
--mantine-color-gray-2: #ddd2b4;
--mantine-color-gray-3: #cfc29e;
--mantine-color-gray-4: #b5a988;
--mantine-color-gray-5: #988d70;
--mantine-color-gray-6: #7a7059;
--mantine-color-gray-7: #5c5443;
--mantine-color-gray-8: #3f3a2e;
--mantine-color-gray-9: #26221a;
/* soft-variant tints from the jewel shade-6 fills */
--mantine-color-blue-light: rgba(44, 86, 151, 0.1);
--mantine-color-blue-light-hover: rgba(44, 86, 151, 0.16);
--mantine-color-blue-outline-hover: rgba(44, 86, 151, 0.06);
--mantine-color-red-light: rgba(158, 43, 63, 0.1);
--mantine-color-red-light-hover: rgba(158, 43, 63, 0.16);
--mantine-color-red-outline-hover: rgba(158, 43, 63, 0.06);
--mantine-color-green-light: rgba(20, 122, 87, 0.1);
--mantine-color-green-light-hover: rgba(20, 122, 87, 0.16);
--mantine-color-green-outline-hover: rgba(20, 122, 87, 0.06);
--mantine-color-yellow-light: rgba(138, 101, 8, 0.1);
--mantine-color-yellow-light-hover: rgba(138, 101, 8, 0.16);
--mantine-color-yellow-outline-hover: rgba(138, 101, 8, 0.06);
--mantine-color-grape-light: rgba(102, 54, 147, 0.1);
--mantine-color-grape-light-hover: rgba(102, 54, 147, 0.16);
--mantine-color-grape-outline-hover: rgba(102, 54, 147, 0.06);
--mantine-color-orange-light: rgba(168, 90, 12, 0.1);
--mantine-color-orange-light-hover: rgba(168, 90, 12, 0.16);
--mantine-color-orange-outline-hover: rgba(168, 90, 12, 0.06);
--mantine-color-pink-light: rgba(161, 42, 94, 0.1);
--mantine-color-pink-light-hover: rgba(161, 42, 94, 0.16);
--mantine-color-pink-outline-hover: rgba(161, 42, 94, 0.06);
--mantine-color-lime-light: rgba(95, 110, 18, 0.1);
--mantine-color-lime-light-hover: rgba(95, 110, 18, 0.16);
--mantine-color-lime-outline-hover: rgba(95, 110, 18, 0.06);
--mantine-shadow-sm: 0 1px 2px rgba(60, 45, 10, 0.12), 0 0 0 1px rgba(150, 109, 9, 0.08);
--mantine-shadow-md: 0 2px 6px rgba(60, 45, 10, 0.14), 0 0 10px rgba(150, 109, 9, 0.07);
/* deco internals — darker golds for on-ivory contrast */
--deco-rule: rgba(150, 109, 9, 0.6);
--deco-rule-faint: rgba(150, 109, 9, 0.32);
--deco-chrome: #f2ecda;
--deco-gold-text: #7c5a06;
--deco-ray: rgba(150, 109, 9, 0.05);
--deco-tray: #ede4cc;
/* app tokens */
--flxn-gold: #b8860b;
--flxn-silver: #7b8494;
--flxn-bronze: #9c5f28;
--flxn-toast-success: #147a57;
--flxn-toast-error: #9e2b3f;
--flxn-podium-1-bg: linear-gradient(150deg, #fdf6dd, #f5e6b2 55%, #ecd68b);
--flxn-podium-1-border: #c49a1e;
--flxn-podium-1-label: #563e05;
--flxn-podium-2-bg: linear-gradient(150deg, #f6f5f2, #e6e6e8 55%, #d7d8dc);
--flxn-podium-2-border: #a9adb8;
--flxn-podium-2-label: #565d6b;
--flxn-podium-3-bg: linear-gradient(150deg, #f9ecdb, #f0d8b6 55%, #e5c294);
--flxn-podium-3-border: #b07a3e;
--flxn-podium-3-label: #6b3a07;
--flxn-showcase-bg: #fdfaf1;
--flxn-overlay: rgba(24, 20, 8, 0.42);
--flxn-dotgrid-color: rgba(138, 101, 8, 0.28);
--flxn-pop-shadow: rgba(60, 45, 10, 0.28);
}
/* ------------------------------------------------------------------ */
/* 3. Typography */
/* ------------------------------------------------------------------ */
[data-theme="deco"] :is(h1, h2, h3, h4, h5, h6),
[data-theme="deco"] .mantine-Title-root {
letter-spacing: 0.06em;
font-weight: 600;
}
/* gold-foil h1 (decoration): solid gold fallback, gradient foil where supported */
[data-theme="deco"] h1 {
color: var(--deco-gold-text);
}
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
[data-theme="deco"] h1 {
background-image: linear-gradient(170deg, #f5e08c 0%, #d4af37 45%, #b8860b 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
[data-theme="deco"][data-mantine-color-scheme="light"] h1 {
background-image: linear-gradient(170deg, #c49a1e 0%, #966d09 55%, #6e4f05 100%);
}
}
@media (forced-colors: active) {
[data-theme="deco"] h1 {
background-image: none;
color: CanvasText;
-webkit-text-fill-color: currentColor;
}
}
[data-theme="deco"] .mantine-Anchor-root {
text-underline-offset: 3px;
text-decoration-color: var(--deco-rule-faint);
}
/* ------------------------------------------------------------------ */
/* 4. Sunburst fan — faint gold rays from top center */
/* ------------------------------------------------------------------ */
[data-theme="deco"] body::before {
content: "";
position: fixed;
inset: 0;
z-index: 9998;
pointer-events: none;
background: repeating-conic-gradient(
from 240deg at 50% -12%,
var(--deco-ray) 0deg 3deg,
transparent 3deg 8deg
);
-webkit-mask-image: linear-gradient(to bottom, #000, transparent 42%);
mask-image: linear-gradient(to bottom, #000, transparent 42%);
}
/* ------------------------------------------------------------------ */
/* 5. Surfaces — double gold rules */
/* ------------------------------------------------------------------ */
[data-theme="deco"] .mantine-Paper-root {
border-radius: 0;
}
[data-theme="deco"] .mantine-Card-root,
[data-theme="deco"] .mantine-Modal-content,
[data-theme="deco"] .mantine-Notification-root {
border: 1px solid var(--deco-rule);
outline: 1px solid var(--deco-rule-faint);
outline-offset: -5px;
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="deco"] .mantine-Popover-dropdown,
[data-theme="deco"] .mantine-Alert-root {
border: 1px solid var(--deco-rule-faint);
}
[data-theme="deco"] .mantine-Modal-header {
border-bottom: 1px solid var(--deco-rule-faint);
}
[data-theme="deco"] .mantine-Tooltip-tooltip {
border: 1px solid var(--deco-rule-faint);
border-radius: 0;
}
/* ------------------------------------------------------------------ */
/* 6. App shell — gold double rules on header/navbar */
/* ------------------------------------------------------------------ */
[data-theme="deco"] .mantine-AppShell-header {
background-color: var(--deco-chrome);
border-bottom: 1px solid var(--deco-rule);
box-shadow: 0 4px 0 -3px var(--deco-rule-faint);
}
[data-theme="deco"] .mantine-AppShell-navbar {
background-color: var(--deco-chrome);
border-top: 1px solid var(--deco-rule);
box-shadow: 0 -4px 0 -3px var(--deco-rule-faint);
}
/* ------------------------------------------------------------------ */
/* 7. Buttons — jewel accent fill inside a thin gold rim */
/* ------------------------------------------------------------------ */
[data-theme="deco"] .mantine-Button-root {
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: 600;
}
[data-theme="deco"] .mantine-Button-root[data-variant="filled"],
[data-theme="deco"] .mantine-ActionIcon-root[data-variant="filled"] {
border: 1px solid var(--deco-rule);
}
[data-theme="deco"] .mantine-Button-root[data-variant="default"],
[data-theme="deco"] .mantine-Button-root[data-variant="outline"] {
border: 1px solid var(--deco-rule);
}
[data-theme="deco"] .mantine-Button-root:disabled,
[data-theme="deco"] .mantine-Button-root[data-disabled],
[data-theme="deco"] .mantine-ActionIcon-root:disabled,
[data-theme="deco"] .mantine-ActionIcon-root[data-disabled] {
background: var(--mantine-color-default);
color: var(--mantine-color-dimmed);
border: 1px solid var(--mantine-color-default-border);
}
/* ------------------------------------------------------------------ */
/* 8. Inputs */
/* ------------------------------------------------------------------ */
[data-theme="deco"] .mantine-Input-input,
[data-theme="deco"] .mantine-PinInput-input {
border: 1px solid var(--deco-rule-faint);
border-radius: 0;
}
[data-theme="deco"] .mantine-Input-input:focus,
[data-theme="deco"] .mantine-Input-input:focus-within,
[data-theme="deco"] .mantine-PinInput-input:focus {
border-color: var(--mantine-primary-color-filled);
box-shadow: 0 0 0 1px var(--deco-rule-faint);
}
/* ------------------------------------------------------------------ */
/* 9. Tabs / SegmentedControl / Badge / NavLink */
/* ------------------------------------------------------------------ */
[data-theme="deco"] .mantine-Tabs-tab {
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: var(--mantine-font-size-xs);
}
[data-theme="deco"] .mantine-Tabs-tab[data-active] {
color: var(--mantine-primary-color-light-color);
border-color: var(--flxn-gold);
}
[data-theme="deco"] .mantine-SegmentedControl-root {
border: 1px solid var(--deco-rule-faint);
background-color: var(--mantine-color-default);
}
[data-theme="deco"] .mantine-SegmentedControl-indicator {
background-color: var(--mantine-primary-color-filled);
border: 1px solid var(--deco-rule);
}
[data-theme="deco"] .mantine-SegmentedControl-label {
text-transform: uppercase;
letter-spacing: 0.06em;
}
[data-theme="deco"] .mantine-SegmentedControl-label[data-active] {
color: var(--mantine-color-white);
}
[data-theme="deco"] .mantine-Badge-root {
letter-spacing: 0.09em;
border: 1px solid color-mix(in srgb, currentColor 45%, transparent);
}
[data-theme="deco"] .mantine-NavLink-root[data-active] {
background-color: var(--mantine-primary-color-light);
color: var(--mantine-primary-color-light-color);
box-shadow: inset 2px 0 0 var(--flxn-gold);
}
/* ------------------------------------------------------------------ */
/* 10. App hooks — tab strip, tiles, see-all, match card */
/* ------------------------------------------------------------------ */
[data-theme="deco"] .flxn-tabs[data-stuck] {
background-color: var(--deco-chrome);
border-bottom-color: var(--deco-rule);
box-shadow: 0 4px 0 -3px var(--deco-rule-faint);
}
[data-theme="deco"] .flxn-tab.flxn-tab {
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: var(--mantine-font-size-xs);
}
[data-theme="deco"] .flxn-tab.flxn-tab[data-active] {
color: var(--mantine-primary-color-light-color);
font-weight: 600;
}
[data-theme="deco"] .flxn-tabs-indicator.flxn-tabs-indicator {
border-bottom: 2px solid var(--flxn-gold);
}
[data-theme="deco"] .flxn-tile {
background-color: var(--mantine-color-default);
border: 1px solid var(--deco-rule);
outline: 1px solid var(--deco-rule-faint);
outline-offset: -4px;
border-radius: 0;
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="deco"] .flxn-tile-icon {
color: var(--mantine-primary-color-light-color);
}
[data-theme="deco"] .flxn-see-all.flxn-see-all {
border-radius: 0;
border: 1px solid var(--deco-rule);
background-color: transparent;
color: var(--mantine-primary-color-light-color);
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: var(--mantine-font-size-xs);
}
@media (hover: hover) {
[data-theme="deco"] .flxn-see-all.flxn-see-all:hover {
background-color: var(--mantine-primary-color-light);
}
}
[data-theme="deco"] .flxn-match-card {
border: 1px solid var(--deco-rule);
border-radius: 0;
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="deco"] .flxn-reaction-row.flxn-reaction-row {
border: 1px solid var(--deco-rule);
border-radius: 0;
background-color: var(--deco-tray);
}
/* ------------------------------------------------------------------ */
/* 11. Divider — centered diamond ornament */
/* ------------------------------------------------------------------ */
[data-theme="deco"] .mantine-Divider-root {
--divider-color: var(--deco-rule-faint);
}
[data-theme="deco"] .mantine-Divider-root[data-orientation="horizontal"]:not(:has(.mantine-Divider-label)) {
position: relative;
overflow: visible;
}
[data-theme="deco"] .mantine-Divider-root[data-orientation="horizontal"]:not(:has(.mantine-Divider-label))::after {
content: "\25C6";
position: absolute;
left: 50%;
top: 0;
transform: translate(-50%, -50%);
font-size: 0.5rem;
line-height: 1;
color: var(--flxn-gold);
background-color: var(--mantine-color-body);
padding: 0 0.5rem;
pointer-events: none;
}
[data-theme="deco"] .mantine-Divider-label {
color: var(--deco-gold-text);
text-transform: uppercase;
letter-spacing: 0.14em;
font-size: 0.6875rem;
}
/* ------------------------------------------------------------------ */
/* 12. Press feedback re-flavor */
/* ------------------------------------------------------------------ */
@media (prefers-reduced-motion: no-preference) {
[data-theme="deco"] .flxn-press:active {
transform: scale(0.975);
}
}
+11
View File
@@ -0,0 +1,11 @@
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
const manifest: ThemeManifest = {
id: "default",
label: msg`Default`,
colorSchemes: "both",
order: 0,
};
export default manifest;
+24
View File
@@ -0,0 +1,24 @@
/* Preview-contract values for the default theme so its picker card renders
correctly while another theme is active. Values mirror Mantine defaults +
the base theme object; the live app gets these from Mantine itself. */
[data-theme="default"][data-theme] {
--mantine-font-family: "Inter", sans-serif;
--mantine-font-family-headings: "League Spartan", sans-serif;
--mantine-radius-default: 0.25rem;
--mantine-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.05) 0 10px 15px -5px, rgba(0, 0, 0, 0.04) 0 7px 7px -5px;
}
[data-theme="default"][data-theme][data-mantine-color-scheme="light"] {
--mantine-color-body: #ffffff;
--mantine-color-text: #000000;
--mantine-color-dimmed: #868e96;
--mantine-color-default-border: #ced4da;
}
[data-theme="default"][data-theme][data-mantine-color-scheme="dark"] {
--mantine-color-body: #242424;
--mantine-color-text: #c9c9c9;
--mantine-color-dimmed: #828282;
--mantine-color-default-border: #424242;
}
+18
View File
@@ -0,0 +1,18 @@
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
const manifest: ThemeManifest = {
id: "gothic",
label: msg`Dark Academia`,
colorSchemes: "both",
fontLinks: [
"https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,500;0,600;0,700;1,500;1,600&display=swap",
"https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;0,600;1,400;1,500&display=swap",
],
chromeColors: {
light: { base: "#efe6d5", dimmed: "#e2d4bc" },
dark: { base: "#0e0b10", dimmed: "#070509" },
},
};
export default manifest;
+641
View File
@@ -0,0 +1,641 @@
/* Dark Academia — candlelit library gothic.
Antique brass #b08d57 is the structural decoration (double borders, rules,
fleurons); the interactive accent follows the user via the primary aliases.
All eight accent ramps are retuned as heraldic manuscript pigments. */
/* ---------------------------------------------------------------- *
* 1. Scheme-independent tokens — (0,2,0)
* ---------------------------------------------------------------- */
[data-theme="gothic"][data-theme] {
/* Type: Garamond has a small x-height — sizes are bumped to compensate. */
--mantine-font-family: "EB Garamond", "Iowan Old Style", Georgia, serif;
--mantine-font-family-headings: "Cormorant Garamond", "EB Garamond", Georgia, serif;
--mantine-font-size-xs: 0.8125rem;
--mantine-font-size-sm: 0.9375rem;
--mantine-font-size-md: 1.0625rem;
--mantine-font-size-lg: 1.1875rem;
--mantine-font-size-xl: 1.375rem;
--mantine-heading-font-weight: 600;
--mantine-h1-font-size: 2.6rem;
--mantine-h1-line-height: 1.15;
--mantine-h2-font-size: 2.1rem;
--mantine-h2-line-height: 1.2;
--mantine-h3-font-size: 1.65rem;
--mantine-h3-line-height: 1.25;
--mantine-h4-font-size: 1.35rem;
--mantine-h4-line-height: 1.3;
--mantine-h5-font-size: 1.175rem;
--mantine-h5-line-height: 1.35;
--mantine-h6-font-size: 1.05rem;
--mantine-h6-line-height: 1.4;
/* Shape: near-square, engraved rather than rounded. */
--mantine-radius-xs: 0;
--mantine-radius-sm: 0.0625rem;
--mantine-radius-md: 0.125rem;
--mantine-radius-lg: 0.125rem;
--mantine-radius-xl: 0.125rem;
--mantine-radius-default: 0.125rem;
/* Elevation: deep, soft, black — never bright. */
--mantine-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.45);
--mantine-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5), 0 6px 16px rgba(0, 0, 0, 0.35);
--mantine-shadow-md: 0 2px 4px rgba(0, 0, 0, 0.5), 0 12px 28px rgba(0, 0, 0, 0.4);
--mantine-shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.5), 0 20px 44px rgba(0, 0, 0, 0.45);
--mantine-shadow-xl: 0 6px 12px rgba(0, 0, 0, 0.55), 0 28px 64px rgba(0, 0, 0, 0.5);
/* Accent ramps retuned as heraldic manuscript pigments — muted and
ink-saturated; every shade 6 carries parchment contrast text. */
/* blue → lapis lazuli */
--mantine-color-blue-0: #edeff7;
--mantine-color-blue-1: #d7dcee;
--mantine-color-blue-2: #b6bede;
--mantine-color-blue-3: #8e9ac9;
--mantine-color-blue-4: #6879b1;
--mantine-color-blue-5: #4a5b97;
--mantine-color-blue-6: #374a80;
--mantine-color-blue-7: #2c3b69;
--mantine-color-blue-8: #222d52;
--mantine-color-blue-9: #18213c;
/* red → oxblood */
--mantine-color-red-0: #f9ecea;
--mantine-color-red-1: #f0d5d3;
--mantine-color-red-2: #e2b0ad;
--mantine-color-red-3: #d18a86;
--mantine-color-red-4: #bd5f5c;
--mantine-color-red-5: #a53a3f;
--mantine-color-red-6: #8a1c2b;
--mantine-color-red-7: #741522;
--mantine-color-red-8: #5e101b;
--mantine-color-red-9: #470b14;
/* green → forest */
--mantine-color-green-0: #ecf1ea;
--mantine-color-green-1: #d4e1d1;
--mantine-color-green-2: #b2c9af;
--mantine-color-green-3: #8aac88;
--mantine-color-green-4: #628e62;
--mantine-color-green-5: #427245;
--mantine-color-green-6: #2f5b35;
--mantine-color-green-7: #25492b;
--mantine-color-green-8: #1c3821;
--mantine-color-green-9: #132818;
/* yellow → gilded gold leaf */
--mantine-color-yellow-0: #faf3df;
--mantine-color-yellow-1: #f1e3ba;
--mantine-color-yellow-2: #e5cd8c;
--mantine-color-yellow-3: #d5b05c;
--mantine-color-yellow-4: #c1963a;
--mantine-color-yellow-5: #a87a1a;
--mantine-color-yellow-6: #8a600f;
--mantine-color-yellow-7: #6f4d0c;
--mantine-color-yellow-8: #573c0a;
--mantine-color-yellow-9: #402c07;
/* grape → royal purple */
--mantine-color-grape-0: #f2edf5;
--mantine-color-grape-1: #e1d6e9;
--mantine-color-grape-2: #c8b4d6;
--mantine-color-grape-3: #a98bbd;
--mantine-color-grape-4: #8965a3;
--mantine-color-grape-5: #6e4789;
--mantine-color-grape-6: #593272;
--mantine-color-grape-7: #48285d;
--mantine-color-grape-8: #381e49;
--mantine-color-grape-9: #291636;
/* orange → burnt amber */
--mantine-color-orange-0: #f9efe3;
--mantine-color-orange-1: #f1dcc0;
--mantine-color-orange-2: #e6c192;
--mantine-color-orange-3: #dc9d5c;
--mantine-color-orange-4: #cd7c33;
--mantine-color-orange-5: #b95f18;
--mantine-color-orange-6: #9c4a10;
--mantine-color-orange-7: #7e3c0d;
--mantine-color-orange-8: #62300b;
--mantine-color-orange-9: #482407;
/* pink → rose madder */
--mantine-color-pink-0: #f8edf0;
--mantine-color-pink-1: #efd6dc;
--mantine-color-pink-2: #e2b3c0;
--mantine-color-pink-3: #d18b9e;
--mantine-color-pink-4: #bc637e;
--mantine-color-pink-5: #a34463;
--mantine-color-pink-6: #882f4e;
--mantine-color-pink-7: #712540;
--mantine-color-pink-8: #591c32;
--mantine-color-pink-9: #421425;
/* lime → sage */
--mantine-color-lime-0: #f0f2ea;
--mantine-color-lime-1: #dfe3d0;
--mantine-color-lime-2: #c8cdae;
--mantine-color-lime-3: #aab187;
--mantine-color-lime-4: #8c9465;
--mantine-color-lime-5: #707a48;
--mantine-color-lime-6: #5a6337;
--mantine-color-lime-7: #48502c;
--mantine-color-lime-8: #373d22;
--mantine-color-lime-9: #292d19;
/* Gray ramp as parchment/ink so grey-derived UI stays in the world. */
--mantine-color-gray-0: #f6efe0;
--mantine-color-gray-1: #efe6d5;
--mantine-color-gray-2: #e2d5bc;
--mantine-color-gray-3: #cfc0a4;
--mantine-color-gray-4: #b3a487;
--mantine-color-gray-5: #8f8069;
--mantine-color-gray-6: #6b5d4b;
--mantine-color-gray-7: #4a3f33;
--mantine-color-gray-8: #352c23;
--mantine-color-gray-9: #2b2118;
/* Dark ramp as ink-black with a violet-umber cast. */
--mantine-color-dark-0: #e8e0d0;
--mantine-color-dark-1: #a89a8a;
--mantine-color-dark-2: #6e5f68;
--mantine-color-dark-3: #4a3e4a;
--mantine-color-dark-4: #322833;
--mantine-color-dark-5: #241c26;
--mantine-color-dark-6: #1a141c;
--mantine-color-dark-7: #141016;
--mantine-color-dark-8: #0e0b10;
--mantine-color-dark-9: #0a0709;
--flxn-pop-shadow: rgba(0, 0, 0, 0.55);
--flxn-avatar-radius: 50%; /* portrait miniatures */
--flxn-avatar-border: 1px solid var(--gothic-brass-faint);
--flxn-badge-radius: 2px;
}
/* ---------------------------------------------------------------- *
* 2. Per-scheme tokens — (0,3,0)
* ---------------------------------------------------------------- */
[data-theme="gothic"][data-theme][data-mantine-color-scheme="dark"] {
--mantine-color-body: #0e0b10;
--mantine-color-text: #e8e0d0;
--mantine-color-bright: #f6efe0;
--mantine-color-dimmed: #a89a82;
--mantine-color-placeholder: #7d7264;
--mantine-color-anchor: var(--mantine-primary-color-2);
--mantine-color-error: #d4736a;
--mantine-color-default: #171219;
--mantine-color-default-hover: #1f1822;
--mantine-color-default-color: #e8e0d0;
--mantine-color-default-border: #453729;
/* Accent variant vars re-derived from the retuned ramps (Mantine's
defaults are literal rgba of its stock hues). Filled stays mid-ramp:
shade 8 pigment vanishes on ink black. */
--mantine-color-blue-filled: var(--mantine-color-blue-6);
--mantine-color-blue-filled-hover: var(--mantine-color-blue-5);
--mantine-color-blue-light: color-mix(in srgb, var(--mantine-color-blue-4) 15%, transparent);
--mantine-color-blue-light-hover: color-mix(in srgb, var(--mantine-color-blue-4) 22%, transparent);
--mantine-color-blue-light-color: var(--mantine-color-blue-2);
--mantine-color-blue-outline: var(--mantine-color-blue-3);
--mantine-color-blue-outline-hover: color-mix(in srgb, var(--mantine-color-blue-3) 12%, transparent);
--mantine-color-blue-text: var(--mantine-color-blue-3);
--mantine-color-blue-contrast: #f3ead8;
--mantine-color-red-filled: var(--mantine-color-red-6);
--mantine-color-red-filled-hover: var(--mantine-color-red-5);
--mantine-color-red-light: color-mix(in srgb, var(--mantine-color-red-4) 15%, transparent);
--mantine-color-red-light-hover: color-mix(in srgb, var(--mantine-color-red-4) 22%, transparent);
--mantine-color-red-light-color: var(--mantine-color-red-2);
--mantine-color-red-outline: var(--mantine-color-red-3);
--mantine-color-red-outline-hover: color-mix(in srgb, var(--mantine-color-red-3) 12%, transparent);
--mantine-color-red-text: var(--mantine-color-red-3);
--mantine-color-red-contrast: #f3ead8;
--mantine-color-green-filled: var(--mantine-color-green-6);
--mantine-color-green-filled-hover: var(--mantine-color-green-5);
--mantine-color-green-light: color-mix(in srgb, var(--mantine-color-green-4) 15%, transparent);
--mantine-color-green-light-hover: color-mix(in srgb, var(--mantine-color-green-4) 22%, transparent);
--mantine-color-green-light-color: var(--mantine-color-green-2);
--mantine-color-green-outline: var(--mantine-color-green-3);
--mantine-color-green-outline-hover: color-mix(in srgb, var(--mantine-color-green-3) 12%, transparent);
--mantine-color-green-text: var(--mantine-color-green-3);
--mantine-color-green-contrast: #f3ead8;
--mantine-color-yellow-filled: var(--mantine-color-yellow-6);
--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-5);
--mantine-color-yellow-light: color-mix(in srgb, var(--mantine-color-yellow-4) 15%, transparent);
--mantine-color-yellow-light-hover: color-mix(in srgb, var(--mantine-color-yellow-4) 22%, transparent);
--mantine-color-yellow-light-color: var(--mantine-color-yellow-2);
--mantine-color-yellow-outline: var(--mantine-color-yellow-3);
--mantine-color-yellow-outline-hover: color-mix(in srgb, var(--mantine-color-yellow-3) 12%, transparent);
--mantine-color-yellow-text: var(--mantine-color-yellow-3);
--mantine-color-yellow-contrast: #f3ead8;
--mantine-color-grape-filled: var(--mantine-color-grape-6);
--mantine-color-grape-filled-hover: var(--mantine-color-grape-5);
--mantine-color-grape-light: color-mix(in srgb, var(--mantine-color-grape-4) 15%, transparent);
--mantine-color-grape-light-hover: color-mix(in srgb, var(--mantine-color-grape-4) 22%, transparent);
--mantine-color-grape-light-color: var(--mantine-color-grape-2);
--mantine-color-grape-outline: var(--mantine-color-grape-3);
--mantine-color-grape-outline-hover: color-mix(in srgb, var(--mantine-color-grape-3) 12%, transparent);
--mantine-color-grape-text: var(--mantine-color-grape-3);
--mantine-color-grape-contrast: #f3ead8;
--mantine-color-orange-filled: var(--mantine-color-orange-6);
--mantine-color-orange-filled-hover: var(--mantine-color-orange-5);
--mantine-color-orange-light: color-mix(in srgb, var(--mantine-color-orange-4) 15%, transparent);
--mantine-color-orange-light-hover: color-mix(in srgb, var(--mantine-color-orange-4) 22%, transparent);
--mantine-color-orange-light-color: var(--mantine-color-orange-2);
--mantine-color-orange-outline: var(--mantine-color-orange-3);
--mantine-color-orange-outline-hover: color-mix(in srgb, var(--mantine-color-orange-3) 12%, transparent);
--mantine-color-orange-text: var(--mantine-color-orange-3);
--mantine-color-orange-contrast: #f3ead8;
--mantine-color-pink-filled: var(--mantine-color-pink-6);
--mantine-color-pink-filled-hover: var(--mantine-color-pink-5);
--mantine-color-pink-light: color-mix(in srgb, var(--mantine-color-pink-4) 15%, transparent);
--mantine-color-pink-light-hover: color-mix(in srgb, var(--mantine-color-pink-4) 22%, transparent);
--mantine-color-pink-light-color: var(--mantine-color-pink-2);
--mantine-color-pink-outline: var(--mantine-color-pink-3);
--mantine-color-pink-outline-hover: color-mix(in srgb, var(--mantine-color-pink-3) 12%, transparent);
--mantine-color-pink-text: var(--mantine-color-pink-3);
--mantine-color-pink-contrast: #f3ead8;
--mantine-color-lime-filled: var(--mantine-color-lime-6);
--mantine-color-lime-filled-hover: var(--mantine-color-lime-5);
--mantine-color-lime-light: color-mix(in srgb, var(--mantine-color-lime-4) 15%, transparent);
--mantine-color-lime-light-hover: color-mix(in srgb, var(--mantine-color-lime-4) 22%, transparent);
--mantine-color-lime-light-color: var(--mantine-color-lime-2);
--mantine-color-lime-outline: var(--mantine-color-lime-3);
--mantine-color-lime-outline-hover: color-mix(in srgb, var(--mantine-color-lime-3) 12%, transparent);
--mantine-color-lime-text: var(--mantine-color-lime-3);
--mantine-color-lime-contrast: #f3ead8;
--mantine-color-gray-light: color-mix(in srgb, var(--mantine-color-gray-4) 12%, transparent);
--mantine-color-gray-light-hover: color-mix(in srgb, var(--mantine-color-gray-4) 18%, transparent);
--mantine-color-gray-light-color: var(--mantine-color-gray-2);
/* Theme-local palette used by component rules below. */
--gothic-brass: #b08d57;
--gothic-brass-bright: #c9a86b;
--gothic-brass-faint: rgba(176, 141, 87, 0.38);
--gothic-brass-ghost: rgba(176, 141, 87, 0.14);
--gothic-surface: #17121a;
--gothic-recessed: #0a0709;
--gothic-shell: #120e14;
--gothic-line: #060409;
--gothic-line-highlight: rgba(232, 224, 208, 0.08);
/* App tokens: gold -> antique brass; manuscript podium tiers. */
--flxn-gold: #c9a86b;
--flxn-silver: #a8a196;
--flxn-bronze: #b57945;
--flxn-toast-success: #a3b98a;
--flxn-toast-error: #dc9a90;
--flxn-podium-1-bg: color-mix(in srgb, #b08d57 14%, #141016);
--flxn-podium-1-border: color-mix(in srgb, #b08d57 55%, #322833);
--flxn-podium-1-label: #d6b878;
--flxn-podium-2-bg: color-mix(in srgb, #a8a196 8%, #141016);
--flxn-podium-2-border: color-mix(in srgb, #a8a196 32%, #322833);
--flxn-podium-2-label: #b5aea1;
--flxn-podium-3-bg: color-mix(in srgb, #b57945 12%, #141016);
--flxn-podium-3-border: color-mix(in srgb, #b57945 45%, #322833);
--flxn-podium-3-label: #c08757;
--flxn-showcase-bg: #141016;
--flxn-overlay: rgba(6, 3, 8, 0.62);
--flxn-dotgrid-color: rgba(176, 141, 87, 0.18);
}
[data-theme="gothic"][data-theme][data-mantine-color-scheme="light"] {
--mantine-color-body: #efe6d5;
--mantine-color-text: #2b2118;
--mantine-color-bright: #1a140e;
--mantine-color-dimmed: #6b5d4b;
--mantine-color-placeholder: #857661;
--mantine-color-anchor: var(--mantine-primary-color-7);
--mantine-color-error: #9e2b20;
--mantine-color-default: #f4ecdc;
--mantine-color-default-hover: #ece1cb;
--mantine-color-default-color: #2b2118;
--mantine-color-default-border: #b9a67f;
/* Accent variant vars re-derived from the retuned ramps; ink-saturated
text shades on aged parchment. */
--mantine-color-blue-light: color-mix(in srgb, var(--mantine-color-blue-6) 10%, transparent);
--mantine-color-blue-light-hover: color-mix(in srgb, var(--mantine-color-blue-6) 16%, transparent);
--mantine-color-blue-light-color: var(--mantine-color-blue-7);
--mantine-color-blue-outline: var(--mantine-color-blue-6);
--mantine-color-blue-outline-hover: color-mix(in srgb, var(--mantine-color-blue-6) 8%, transparent);
--mantine-color-blue-text: var(--mantine-color-blue-7);
--mantine-color-blue-contrast: #f3ead8;
--mantine-color-red-light: color-mix(in srgb, var(--mantine-color-red-6) 10%, transparent);
--mantine-color-red-light-hover: color-mix(in srgb, var(--mantine-color-red-6) 16%, transparent);
--mantine-color-red-light-color: var(--mantine-color-red-7);
--mantine-color-red-outline: var(--mantine-color-red-6);
--mantine-color-red-outline-hover: color-mix(in srgb, var(--mantine-color-red-6) 8%, transparent);
--mantine-color-red-text: var(--mantine-color-red-7);
--mantine-color-red-contrast: #f3ead8;
--mantine-color-green-light: color-mix(in srgb, var(--mantine-color-green-6) 10%, transparent);
--mantine-color-green-light-hover: color-mix(in srgb, var(--mantine-color-green-6) 16%, transparent);
--mantine-color-green-light-color: var(--mantine-color-green-7);
--mantine-color-green-outline: var(--mantine-color-green-6);
--mantine-color-green-outline-hover: color-mix(in srgb, var(--mantine-color-green-6) 8%, transparent);
--mantine-color-green-text: var(--mantine-color-green-7);
--mantine-color-green-contrast: #f3ead8;
--mantine-color-yellow-light: color-mix(in srgb, var(--mantine-color-yellow-6) 10%, transparent);
--mantine-color-yellow-light-hover: color-mix(in srgb, var(--mantine-color-yellow-6) 16%, transparent);
--mantine-color-yellow-light-color: var(--mantine-color-yellow-7);
--mantine-color-yellow-outline: var(--mantine-color-yellow-6);
--mantine-color-yellow-outline-hover: color-mix(in srgb, var(--mantine-color-yellow-6) 8%, transparent);
--mantine-color-yellow-text: var(--mantine-color-yellow-7);
--mantine-color-yellow-contrast: #f3ead8;
--mantine-color-grape-light: color-mix(in srgb, var(--mantine-color-grape-6) 10%, transparent);
--mantine-color-grape-light-hover: color-mix(in srgb, var(--mantine-color-grape-6) 16%, transparent);
--mantine-color-grape-light-color: var(--mantine-color-grape-7);
--mantine-color-grape-outline: var(--mantine-color-grape-6);
--mantine-color-grape-outline-hover: color-mix(in srgb, var(--mantine-color-grape-6) 8%, transparent);
--mantine-color-grape-text: var(--mantine-color-grape-7);
--mantine-color-grape-contrast: #f3ead8;
--mantine-color-orange-light: color-mix(in srgb, var(--mantine-color-orange-6) 10%, transparent);
--mantine-color-orange-light-hover: color-mix(in srgb, var(--mantine-color-orange-6) 16%, transparent);
--mantine-color-orange-light-color: var(--mantine-color-orange-7);
--mantine-color-orange-outline: var(--mantine-color-orange-6);
--mantine-color-orange-outline-hover: color-mix(in srgb, var(--mantine-color-orange-6) 8%, transparent);
--mantine-color-orange-text: var(--mantine-color-orange-7);
--mantine-color-orange-contrast: #f3ead8;
--mantine-color-pink-light: color-mix(in srgb, var(--mantine-color-pink-6) 10%, transparent);
--mantine-color-pink-light-hover: color-mix(in srgb, var(--mantine-color-pink-6) 16%, transparent);
--mantine-color-pink-light-color: var(--mantine-color-pink-7);
--mantine-color-pink-outline: var(--mantine-color-pink-6);
--mantine-color-pink-outline-hover: color-mix(in srgb, var(--mantine-color-pink-6) 8%, transparent);
--mantine-color-pink-text: var(--mantine-color-pink-7);
--mantine-color-pink-contrast: #f3ead8;
--mantine-color-lime-light: color-mix(in srgb, var(--mantine-color-lime-6) 10%, transparent);
--mantine-color-lime-light-hover: color-mix(in srgb, var(--mantine-color-lime-6) 16%, transparent);
--mantine-color-lime-light-color: var(--mantine-color-lime-7);
--mantine-color-lime-outline: var(--mantine-color-lime-6);
--mantine-color-lime-outline-hover: color-mix(in srgb, var(--mantine-color-lime-6) 8%, transparent);
--mantine-color-lime-text: var(--mantine-color-lime-7);
--mantine-color-lime-contrast: #f3ead8;
--mantine-color-gray-light: color-mix(in srgb, var(--mantine-color-gray-6) 10%, transparent);
--mantine-color-gray-light-hover: color-mix(in srgb, var(--mantine-color-gray-6) 15%, transparent);
--mantine-color-gray-light-color: var(--mantine-color-gray-7);
--gothic-brass: #8a6a34;
--gothic-brass-bright: #7a5a28;
--gothic-brass-faint: rgba(138, 106, 52, 0.45);
--gothic-brass-ghost: rgba(138, 106, 52, 0.12);
--gothic-surface: #f6efe0;
--gothic-recessed: #e4d6bd;
--gothic-shell: #e9dec9;
--gothic-line: rgba(74, 58, 38, 0.45);
--gothic-line-highlight: rgba(255, 252, 240, 0.85);
--flxn-gold: #7a5a16;
--flxn-silver: #7b7264;
--flxn-bronze: #8c4f22;
--flxn-toast-success: #4d6b34;
--flxn-toast-error: #9e2b20;
--flxn-podium-1-bg: color-mix(in srgb, #b08d57 16%, #f6efe0);
--flxn-podium-1-border: color-mix(in srgb, #8a6a34 55%, #cfc0a4);
--flxn-podium-1-label: #6f5116;
--flxn-podium-2-bg: color-mix(in srgb, #8f8069 10%, #f6efe0);
--flxn-podium-2-border: #b3a487;
--flxn-podium-2-label: #6b5d4b;
--flxn-podium-3-bg: color-mix(in srgb, #8c4f22 12%, #f6efe0);
--flxn-podium-3-border: color-mix(in srgb, #8c4f22 45%, #cfc0a4);
--flxn-podium-3-label: #74431c;
--flxn-showcase-bg: #f6efe0;
--flxn-overlay: rgba(26, 18, 10, 0.45);
--flxn-dotgrid-color: rgba(138, 104, 58, 0.22);
}
/* ---------------------------------------------------------------- *
* 3. Full-page decoration: candlelit vignette (dark scheme only)
* ---------------------------------------------------------------- */
[data-theme="gothic"][data-mantine-color-scheme="dark"] body::before {
content: "";
position: fixed;
inset: 0;
z-index: 9998;
pointer-events: none;
background:
radial-gradient(80% 50% at 50% 0%, rgba(176, 141, 87, 0.04), transparent 70%),
radial-gradient(120% 90% at 50% 42%, transparent 55%, rgba(4, 2, 6, 0.45) 100%);
}
/* ---------------------------------------------------------------- *
* 4. Component restyles
* ---------------------------------------------------------------- */
/* Headings: Cormorant, slight italic from h3 down for the manuscript voice. */
[data-theme="gothic"] .mantine-Title-root {
letter-spacing: 0.01em;
}
[data-theme="gothic"] h3.mantine-Title-root,
[data-theme="gothic"] h4.mantine-Title-root {
font-style: italic;
font-weight: 600;
}
/* Cards & panels: thin double border in brass (1px border + inset outline). */
[data-theme="gothic"] .mantine-Paper-root[data-with-border] {
border: 1px solid var(--gothic-brass-faint);
outline: 1px solid var(--gothic-brass-ghost);
outline-offset: -4px;
background-color: var(--gothic-surface);
}
[data-theme="gothic"] .mantine-Card-root {
background-color: var(--gothic-surface);
}
/* Buttons: wax-seal solids in the user's accent, or brass-ruled outlines. */
[data-theme="gothic"] .mantine-Button-root {
letter-spacing: 0.03em;
}
[data-theme="gothic"] .mantine-Button-root[data-variant="filled"],
[data-theme="gothic"] .mantine-ActionIcon-root[data-variant="filled"] {
border: 1px solid var(--mantine-primary-color-9);
box-shadow: inset 0 1px 0 rgba(246, 239, 224, 0.12), 0 2px 8px rgba(0, 0, 0, 0.45);
}
[data-theme="gothic"] .mantine-Button-root[data-variant="default"],
[data-theme="gothic"] .mantine-Button-root[data-variant="outline"],
[data-theme="gothic"] .mantine-ActionIcon-root[data-variant="default"],
[data-theme="gothic"] .mantine-ActionIcon-root[data-variant="outline"] {
border-color: var(--gothic-brass-faint);
}
/* Inputs: recessed ledger lines with brass rules; focus follows the accent. */
[data-theme="gothic"] .mantine-Input-input {
background-color: var(--gothic-recessed);
border-color: var(--gothic-brass-faint);
}
[data-theme="gothic"] .mantine-PinInput-input {
background-color: var(--gothic-recessed);
}
/* Tabs: rubricated active label in the accent under a brass rule. */
[data-theme="gothic"] .mantine-Tabs-list {
--tabs-list-border-size: 1px;
}
[data-theme="gothic"] .mantine-Tabs-tab {
letter-spacing: 0.02em;
}
[data-theme="gothic"] .mantine-Tabs-tab[data-active] {
border-color: var(--gothic-brass);
color: var(--mantine-primary-color-light-color);
}
[data-theme="gothic"] .mantine-Tabs-tab[data-active]:hover {
border-color: var(--gothic-brass-bright);
}
/* Nav: active entries follow the accent — tinted wash plus a marginal rule. */
[data-theme="gothic"] .mantine-NavLink-root[data-active] {
background-color: var(--mantine-primary-color-light);
color: var(--mantine-primary-color-light-color);
box-shadow: inset 2px 0 0 var(--mantine-primary-color-filled);
}
[data-theme="gothic"] .mantine-NavLink-root[data-active]:hover {
background-color: var(--mantine-primary-color-light-hover);
}
/* Shell: bound pages — header and navbar as darker leather edges. */
[data-theme="gothic"] .mantine-AppShell-header {
background-color: var(--gothic-shell);
border-bottom: 1px solid var(--gothic-brass-faint);
}
[data-theme="gothic"] .mantine-AppShell-navbar {
background-color: var(--gothic-shell);
border-color: var(--gothic-brass-faint);
}
/* Segmented control: engraved channel with a brass-edged slider. */
[data-theme="gothic"] .mantine-SegmentedControl-root {
background-color: var(--gothic-recessed);
}
[data-theme="gothic"] .mantine-SegmentedControl-indicator {
background-color: var(--gothic-surface);
border: 1px solid var(--gothic-brass-faint);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
}
[data-theme="gothic"] .mantine-SegmentedControl-label {
letter-spacing: 0.02em;
}
/* Dividers: engraved rule with a centered fleuron ornament. */
[data-theme="gothic"] .mantine-Divider-root {
--divider-color: var(--gothic-line);
}
[data-theme="gothic"] .mantine-Divider-root[data-orientation="horizontal"] {
box-shadow: 0 1px 0 var(--gothic-line-highlight);
}
[data-theme="gothic"] .mantine-Divider-root[data-orientation="horizontal"]:not([data-with-label]) {
position: relative;
overflow: visible;
}
[data-theme="gothic"] .mantine-Divider-root[data-orientation="horizontal"]:not([data-with-label])::before {
content: "\2766"; /* ❦ fleuron */
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
padding: 0 0.5em;
background-color: var(--mantine-color-body);
color: var(--gothic-brass);
font-size: 0.8rem;
line-height: 1;
}
[data-theme="gothic"] .mantine-Divider-label {
color: var(--gothic-brass);
letter-spacing: 0.08em;
text-transform: uppercase;
font-size: var(--mantine-font-size-xs);
}
/* Overlays: sealed-note dropdowns and modals with double brass borders. */
[data-theme="gothic"] .mantine-Modal-content,
[data-theme="gothic"] .mantine-Popover-dropdown {
background-color: var(--gothic-surface);
border: 1px solid var(--gothic-brass-faint);
outline: 1px solid var(--gothic-brass-ghost);
outline-offset: -4px;
}
[data-theme="gothic"] .mantine-Modal-header {
background-color: var(--gothic-surface);
}
[data-theme="gothic"] .mantine-Tooltip-tooltip {
background-color: #060409;
color: #e8e0d0;
border: 1px solid var(--gothic-brass-faint);
}
/* Notifications: sealed notes with a brass frame. */
[data-theme="gothic"] .mantine-Notification-root {
background-color: var(--gothic-surface);
border: 1px solid var(--gothic-brass-faint);
box-shadow: var(--mantine-shadow-md);
}
/* Badges: engraved plaques. */
[data-theme="gothic"] .mantine-Badge-root {
letter-spacing: 0.06em;
font-weight: 600;
}
/* Alerts keep a real border for forced-colors resilience. */
[data-theme="gothic"] .mantine-Alert-root {
border: 1px solid var(--gothic-brass-faint);
}
/* ---------------------------------------------------------------- *
* 5. App component hooks
* ---------------------------------------------------------------- */
/* Tab strip: docks as a shell strip with a brass rule; active label
rubricated in the accent over a thin double brass rule (gilded underline). */
[data-theme="gothic"] .flxn-tabs[data-stuck] {
background-color: var(--gothic-shell);
box-shadow: inset 0 -1px 0 var(--gothic-brass-faint);
}
[data-theme="gothic"] .flxn-tab {
color: var(--mantine-color-dimmed);
letter-spacing: 0.02em;
}
[data-theme="gothic"] .flxn-tab[data-active] {
color: var(--mantine-primary-color-light-color);
font-weight: 600;
}
[data-theme="gothic"] .flxn-tabs-indicator {
border-bottom: 3px double var(--gothic-brass);
padding-inline: 0.5rem;
}
/* Nav grid tiles: brass double-ruled panels, flat gilded glyph. */
[data-theme="gothic"] .flxn-tile {
background-color: var(--gothic-surface);
border: 1px solid var(--gothic-brass-faint);
outline: 1px solid var(--gothic-brass-ghost);
outline-offset: -4px;
}
[data-theme="gothic"] .flxn-tile-icon {
color: var(--gothic-brass-bright);
}
/* "See all" link: brass-rimmed chip, accent-inked label. */
[data-theme="gothic"] .flxn-see-all {
background-color: var(--gothic-brass-ghost);
border: 1px solid var(--gothic-brass-faint);
color: var(--mantine-primary-color-light-color);
}
@media (hover: hover) {
[data-theme="gothic"] .flxn-see-all:hover {
background-color: var(--mantine-primary-color-light);
}
}
/* Match card + reaction tray: one construction — matching brass rules,
tray recessed as a deeper ink panel. */
[data-theme="gothic"] .flxn-match-card {
background-color: var(--gothic-surface);
border: 1px solid var(--gothic-brass-faint);
}
[data-theme="gothic"] .flxn-reaction-row {
background-color: var(--gothic-recessed);
border: 1px solid var(--gothic-brass-faint);
}
+69
View File
@@ -0,0 +1,69 @@
/* Default look for custom app-component hooks; themes override under
[data-theme] scopes (see README "App component hooks"). Loads before all
theme.css files. */
/* Mantine hardcodes white on selected combobox options; autoContrast never
reaches this rule. */
[data-combobox-selected] {
color: var(--flxn-on-accent);
}
/* Swipeable tab strip: flat while resting, docked chrome on [data-stuck]. */
.flxn-tabs {
background-color: transparent;
border-bottom: 1px solid transparent;
transition: background-color 200ms ease, border-color 200ms ease,
box-shadow 200ms ease;
}
.flxn-tabs[data-stuck] {
background-color: var(--mantine-color-body);
border-bottom-color: var(--mantine-color-default-border);
}
@media (prefers-reduced-motion: reduce) {
.flxn-tabs {
transition: none;
}
}
.flxn-tab.flxn-tab {
/* lifted above the absolutely-positioned FloatingIndicator, or an opaque
indicator fill covers the labels */
position: relative;
z-index: 1;
flex: 1;
padding: var(--mantine-spacing-sm) var(--mantine-spacing-xs);
text-align: center;
color: var(--mantine-color-text);
font-weight: 400;
background-color: transparent;
border: none;
border-radius: 0;
transition: color 200ms ease;
}
.flxn-tab.flxn-tab[data-active] {
color: var(--mantine-primary-color-filled);
font-weight: 600;
}
.flxn-tabs-indicator.flxn-tabs-indicator {
border-bottom: 2px solid var(--mantine-primary-color-filled);
padding-inline: 0.5rem;
}
@media (prefers-reduced-motion: reduce) {
.flxn-tab.flxn-tab {
transition: none;
}
}
/* The frame owns the avatar's single border (--flxn-avatar-border); an inner
Avatar border would double every ring. */
.flxn-avatar-frame .mantine-Avatar-root.mantine-Avatar-root {
border: none;
outline: none;
}
/* Reaction tray tucked under a match card; full border + radius so rounded
card corners never expose square tray corners. */
.flxn-reaction-row.flxn-reaction-row {
background-color: var(--mantine-color-gray-light);
border: 1px solid var(--mantine-color-default-border);
border-radius: var(--mantine-radius-md);
}
+17
View File
@@ -0,0 +1,17 @@
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
const manifest: ThemeManifest = {
id: "neumorph",
label: msg`Neumorphic`,
colorSchemes: "both",
fontLinks: [
"https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800&display=swap",
],
chromeColors: {
light: { base: "#e0e5ec", dimmed: "#c8d0dc" },
dark: { base: "#2a2d34", dimmed: "#1e2127" },
},
};
export default manifest;
+632
View File
@@ -0,0 +1,632 @@
/* Neumorphic — soft-UI extruded plastic. One surface color for body AND
cards; elevation comes only from paired light/dark shadows. Raised =
drop pair, pressed = inset pair. Faint real 1px borders stay on every
interactive element so forced-colors / high-contrast modes survive. */
/* ---------------------------------------------------------------- */
/* 1. Scheme-independent tokens */
/* ---------------------------------------------------------------- */
[data-theme="neumorph"][data-theme] {
--mantine-font-family: "Nunito", "Inter", system-ui, sans-serif;
--mantine-font-family-headings: "Nunito", "Inter", system-ui, sans-serif;
--mantine-heading-font-weight: 800;
--mantine-radius-xs: 8px;
--mantine-radius-sm: 12px;
--mantine-radius-md: 16px;
--mantine-radius-lg: 20px;
--mantine-radius-xl: 28px;
--mantine-radius-default: 16px;
--flxn-avatar-radius: 30%;
--flxn-avatar-border: 1px solid var(--neu-border);
--flxn-badge-radius: 16px;
/* muted, low-saturation slate grays (light-scheme neutrals) */
--mantine-color-gray-0: #f2f5fa;
--mantine-color-gray-1: #e9edf4;
--mantine-color-gray-2: #dfe4ed;
--mantine-color-gray-3: #d0d7e2;
--mantine-color-gray-4: #bac4d3;
--mantine-color-gray-5: #9daabd;
--mantine-color-gray-6: #7d8ba0;
--mantine-color-gray-7: #606d81;
--mantine-color-gray-8: #495466;
--mantine-color-gray-9: #333c4b;
/* dark-scheme neutrals tuned to the #2a2d34 shell */
--mantine-color-dark-0: #d5dae3;
--mantine-color-dark-1: #b8bfcb;
--mantine-color-dark-2: #99a1b0;
--mantine-color-dark-3: #6f7787;
--mantine-color-dark-4: #4a505c;
--mantine-color-dark-5: #383d47;
--mantine-color-dark-6: #2a2d34;
--mantine-color-dark-7: #2a2d34;
--mantine-color-dark-8: #222429;
--mantine-color-dark-9: #191b1f;
/* accent ramps retuned to muted pastel-plastic; shade 6 keeps >=4.5:1 on
the light surface, shades 3-4 keep >=4.5:1 on the dark surface */
--mantine-color-blue-0: #e9eff6;
--mantine-color-blue-1: #d3dfee;
--mantine-color-blue-2: #b9cce4;
--mantine-color-blue-3: #98b4d7;
--mantine-color-blue-4: #779cca;
--mantine-color-blue-5: #5181bb;
--mantine-color-blue-6: #3c669a;
--mantine-color-blue-7: #31537d;
--mantine-color-blue-8: #274263;
--mantine-color-blue-9: #1d3149;
--mantine-color-red-0: #f6ebe9;
--mantine-color-red-1: #edd6d4;
--mantine-color-red-2: #e3beba;
--mantine-color-red-3: #d6a09a;
--mantine-color-red-4: #c88179;
--mantine-color-red-5: #b95d53;
--mantine-color-red-6: #98473e;
--mantine-color-red-7: #7b3a32;
--mantine-color-red-8: #622e28;
--mantine-color-red-9: #48221e;
--mantine-color-green-0: #eaf5f0;
--mantine-color-green-1: #d5ebe0;
--mantine-color-green-2: #bde0cf;
--mantine-color-green-3: #9ed1b8;
--mantine-color-green-4: #7fc3a1;
--mantine-color-green-5: #4ea67a;
--mantine-color-green-6: #357153;
--mantine-color-green-7: #2b5c44;
--mantine-color-green-8: #214734;
--mantine-color-green-9: #183225;
--mantine-color-yellow-0: #f9f4e7;
--mantine-color-yellow-1: #f2e9cf;
--mantine-color-yellow-2: #ebdcb2;
--mantine-color-yellow-3: #e1cb8e;
--mantine-color-yellow-4: #d7ba6a;
--mantine-color-yellow-5: #bc9732;
--mantine-color-yellow-6: #7b6321;
--mantine-color-yellow-7: #634f1a;
--mantine-color-yellow-8: #4b3c14;
--mantine-color-yellow-9: #32290d;
--mantine-color-grape-0: #f3eaf5;
--mantine-color-grape-1: #e7d5eb;
--mantine-color-grape-2: #d9bde0;
--mantine-color-grape-3: #c79ed1;
--mantine-color-grape-4: #b680c3;
--mantine-color-grape-5: #a15bb2;
--mantine-color-grape-6: #824592;
--mantine-color-grape-7: #693776;
--mantine-color-grape-8: #542c5e;
--mantine-color-grape-9: #3e2145;
--mantine-color-orange-0: #f9efe7;
--mantine-color-orange-1: #f2decf;
--mantine-color-orange-2: #ebcbb2;
--mantine-color-orange-3: #e1b28e;
--mantine-color-orange-4: #d7996a;
--mantine-color-orange-5: #c97636;
--mantine-color-orange-6: #955728;
--mantine-color-orange-7: #7d4921;
--mantine-color-orange-8: #653b1b;
--mantine-color-orange-9: #4d2d14;
--mantine-color-pink-0: #f6e9ee;
--mantine-color-pink-1: #edd4dc;
--mantine-color-pink-2: #e3bac8;
--mantine-color-pink-3: #d69aae;
--mantine-color-pink-4: #ca7d96;
--mantine-color-pink-5: #b95576;
--mantine-color-pink-6: #983e5c;
--mantine-color-pink-7: #7b324b;
--mantine-color-pink-8: #62283b;
--mantine-color-pink-9: #481e2c;
--mantine-color-lime-0: #eff6e9;
--mantine-color-lime-1: #e0eed3;
--mantine-color-lime-2: #cde4b9;
--mantine-color-lime-3: #b6d798;
--mantine-color-lime-4: #9eca77;
--mantine-color-lime-5: #74ac43;
--mantine-color-lime-6: #4b702c;
--mantine-color-lime-7: #3d5a23;
--mantine-color-lime-8: #2e441a;
--mantine-color-lime-9: #1f2e12;
}
/* ---------------------------------------------------------------- */
/* 2. Per-scheme tokens */
/* ---------------------------------------------------------------- */
[data-theme="neumorph"][data-theme][data-mantine-color-scheme="light"] {
--neu-surface: #e0e5ec;
--neu-surface-high: #e8ecf2; /* hover */
--neu-well: #d3dae4; /* pressed-in wells */
--neu-border: rgba(148, 163, 184, 0.4);
--neu-shadow-dark: rgba(163, 177, 198, 0.6);
--neu-shadow-light: rgba(255, 255, 255, 0.9);
--neu-inset: inset 6px 6px 12px rgba(163, 177, 198, 0.6),
inset -6px -6px 12px rgba(255, 255, 255, 0.9);
--neu-inset-sm: inset 3px 3px 6px rgba(163, 177, 198, 0.55),
inset -3px -3px 6px rgba(255, 255, 255, 0.85);
--neu-edge: 0 8px 16px rgba(163, 177, 198, 0.35);
--mantine-color-body: #e0e5ec;
--mantine-color-text: #2f3542;
--mantine-color-bright: #171b23;
--mantine-color-dimmed: #5b6472;
--mantine-color-placeholder: #5f6a80;
--mantine-color-error: var(--mantine-color-red-6);
--mantine-color-default: #e0e5ec;
--mantine-color-default-hover: #e8ecf2;
--mantine-color-default-color: #2f3542;
--mantine-color-default-border: rgba(148, 163, 184, 0.4);
/* Mantine's -light variants are alpha literals of its stock hues;
re-derive them from the retuned ramps */
--mantine-color-blue-light: color-mix(in srgb, var(--mantine-color-blue-6) 10%, transparent);
--mantine-color-blue-light-hover: color-mix(in srgb, var(--mantine-color-blue-6) 14%, transparent);
--mantine-color-red-light: color-mix(in srgb, var(--mantine-color-red-6) 10%, transparent);
--mantine-color-red-light-hover: color-mix(in srgb, var(--mantine-color-red-6) 14%, transparent);
--mantine-color-green-light: color-mix(in srgb, var(--mantine-color-green-6) 10%, transparent);
--mantine-color-green-light-hover: color-mix(in srgb, var(--mantine-color-green-6) 14%, transparent);
--mantine-color-yellow-light: color-mix(in srgb, var(--mantine-color-yellow-6) 10%, transparent);
--mantine-color-yellow-light-hover: color-mix(in srgb, var(--mantine-color-yellow-6) 14%, transparent);
--mantine-color-grape-light: color-mix(in srgb, var(--mantine-color-grape-6) 10%, transparent);
--mantine-color-grape-light-hover: color-mix(in srgb, var(--mantine-color-grape-6) 14%, transparent);
--mantine-color-orange-light: color-mix(in srgb, var(--mantine-color-orange-6) 10%, transparent);
--mantine-color-orange-light-hover: color-mix(in srgb, var(--mantine-color-orange-6) 14%, transparent);
--mantine-color-pink-light: color-mix(in srgb, var(--mantine-color-pink-6) 10%, transparent);
--mantine-color-pink-light-hover: color-mix(in srgb, var(--mantine-color-pink-6) 14%, transparent);
--mantine-color-lime-light: color-mix(in srgb, var(--mantine-color-lime-6) 10%, transparent);
--mantine-color-lime-light-hover: color-mix(in srgb, var(--mantine-color-lime-6) 14%, transparent);
--mantine-color-gray-light: color-mix(in srgb, var(--mantine-color-gray-6) 10%, transparent);
--mantine-color-gray-light-hover: color-mix(in srgb, var(--mantine-color-gray-6) 14%, transparent);
/* Compact spreads: app containers clip overflow, and big soft shadows
truncate visibly at those edges. Keep the extrusion read tight. */
--mantine-shadow-xs: 2px 2px 5px rgba(163, 177, 198, 0.45),
-2px -2px 5px rgba(255, 255, 255, 0.8);
--mantine-shadow-sm: 3px 3px 7px rgba(163, 177, 198, 0.5),
-3px -3px 7px rgba(255, 255, 255, 0.85);
--mantine-shadow-md: 4px 4px 9px rgba(163, 177, 198, 0.55),
-4px -4px 9px rgba(255, 255, 255, 0.85);
--mantine-shadow-lg: 5px 5px 12px rgba(163, 177, 198, 0.55),
-5px -5px 12px rgba(255, 255, 255, 0.85);
--mantine-shadow-xl: 7px 7px 16px rgba(163, 177, 198, 0.6),
-7px -7px 16px rgba(255, 255, 255, 0.9);
/* app tokens — soft embossed metals, muted toasts, extruded podium */
--flxn-gold: #8f6f1e;
--flxn-silver: #66727f;
--flxn-bronze: #96603a;
--flxn-toast-success: #2f8f5b;
--flxn-toast-error: #b04a44;
--flxn-podium-1-bg: color-mix(in srgb, #d9b25a 10%, #e0e5ec);
--flxn-podium-1-border: color-mix(in srgb, #b08d2c 35%, rgba(148, 163, 184, 0.5));
--flxn-podium-1-label: #7d651f;
--flxn-podium-2-bg: color-mix(in srgb, #9daabd 10%, #e0e5ec);
--flxn-podium-2-border: color-mix(in srgb, #7d8ba0 35%, rgba(148, 163, 184, 0.5));
--flxn-podium-2-label: #5b6472;
--flxn-podium-3-bg: color-mix(in srgb, #c08552 10%, #e0e5ec);
--flxn-podium-3-border: color-mix(in srgb, #a56a3a 35%, rgba(148, 163, 184, 0.5));
--flxn-podium-3-label: #7d5230;
--flxn-showcase-bg: #e0e5ec;
--flxn-overlay: rgba(99, 112, 133, 0.45);
--flxn-dotgrid-color: rgba(148, 163, 184, 0.5);
--flxn-pop-shadow: rgba(163, 177, 198, 0.65);
}
[data-theme="neumorph"][data-theme][data-mantine-color-scheme="dark"] {
--neu-surface: #2a2d34;
--neu-surface-high: #31353d;
--neu-well: #23262c;
--neu-border: rgba(255, 255, 255, 0.08);
--neu-shadow-dark: rgba(13, 15, 19, 0.65);
--neu-shadow-light: rgba(68, 74, 86, 0.5);
--neu-inset: inset 6px 6px 12px rgba(13, 15, 19, 0.65),
inset -6px -6px 12px rgba(68, 74, 86, 0.5);
--neu-inset-sm: inset 3px 3px 6px rgba(13, 15, 19, 0.6),
inset -3px -3px 6px rgba(68, 74, 86, 0.45);
--neu-edge: 0 8px 16px rgba(0, 0, 0, 0.4);
--mantine-color-body: #2a2d34;
--mantine-color-text: #dbdfe7;
--mantine-color-bright: #f4f6fa;
--mantine-color-dimmed: #9ba3b2;
--mantine-color-placeholder: #8a92a3;
--mantine-color-error: var(--mantine-color-red-3);
--mantine-color-default: #2a2d34;
--mantine-color-default-hover: #31353d;
--mantine-color-default-color: #dbdfe7;
--mantine-color-default-border: rgba(255, 255, 255, 0.08);
/* re-derive alpha-literal -light variants from the retuned ramps */
--mantine-color-blue-light: color-mix(in srgb, var(--mantine-color-blue-6) 15%, transparent);
--mantine-color-blue-light-hover: color-mix(in srgb, var(--mantine-color-blue-6) 20%, transparent);
--mantine-color-red-light: color-mix(in srgb, var(--mantine-color-red-6) 15%, transparent);
--mantine-color-red-light-hover: color-mix(in srgb, var(--mantine-color-red-6) 20%, transparent);
--mantine-color-green-light: color-mix(in srgb, var(--mantine-color-green-6) 15%, transparent);
--mantine-color-green-light-hover: color-mix(in srgb, var(--mantine-color-green-6) 20%, transparent);
--mantine-color-yellow-light: color-mix(in srgb, var(--mantine-color-yellow-6) 15%, transparent);
--mantine-color-yellow-light-hover: color-mix(in srgb, var(--mantine-color-yellow-6) 20%, transparent);
--mantine-color-grape-light: color-mix(in srgb, var(--mantine-color-grape-6) 15%, transparent);
--mantine-color-grape-light-hover: color-mix(in srgb, var(--mantine-color-grape-6) 20%, transparent);
--mantine-color-orange-light: color-mix(in srgb, var(--mantine-color-orange-6) 15%, transparent);
--mantine-color-orange-light-hover: color-mix(in srgb, var(--mantine-color-orange-6) 20%, transparent);
--mantine-color-pink-light: color-mix(in srgb, var(--mantine-color-pink-6) 15%, transparent);
--mantine-color-pink-light-hover: color-mix(in srgb, var(--mantine-color-pink-6) 20%, transparent);
--mantine-color-lime-light: color-mix(in srgb, var(--mantine-color-lime-6) 15%, transparent);
--mantine-color-lime-light-hover: color-mix(in srgb, var(--mantine-color-lime-6) 20%, transparent);
--mantine-color-gray-light: color-mix(in srgb, var(--mantine-color-gray-5) 15%, transparent);
--mantine-color-gray-light-hover: color-mix(in srgb, var(--mantine-color-gray-5) 20%, transparent);
--mantine-shadow-xs: 2px 2px 5px rgba(13, 15, 19, 0.5),
-2px -2px 5px rgba(68, 74, 86, 0.4);
--mantine-shadow-sm: 3px 3px 7px rgba(13, 15, 19, 0.55),
-3px -3px 7px rgba(68, 74, 86, 0.45);
--mantine-shadow-md: 4px 4px 9px rgba(13, 15, 19, 0.6),
-4px -4px 9px rgba(68, 74, 86, 0.45);
--mantine-shadow-lg: 5px 5px 12px rgba(13, 15, 19, 0.6),
-5px -5px 12px rgba(68, 74, 86, 0.45);
--mantine-shadow-xl: 7px 7px 16px rgba(13, 15, 19, 0.65),
-7px -7px 16px rgba(68, 74, 86, 0.5);
--flxn-gold: #dcbb6a;
--flxn-silver: #b3bcc9;
--flxn-bronze: #cf9a72;
--flxn-toast-success: #86d3a8;
--flxn-toast-error: #ef9d9d;
--flxn-podium-1-bg: color-mix(in srgb, #dcbb6a 12%, #2a2d34);
--flxn-podium-1-border: color-mix(in srgb, #dcbb6a 30%, rgba(255, 255, 255, 0.12));
--flxn-podium-1-label: #dcbb6a;
--flxn-podium-2-bg: color-mix(in srgb, #b3bcc9 10%, #2a2d34);
--flxn-podium-2-border: color-mix(in srgb, #b3bcc9 28%, rgba(255, 255, 255, 0.12));
--flxn-podium-2-label: #b3bcc9;
--flxn-podium-3-bg: color-mix(in srgb, #cf9a72 11%, #2a2d34);
--flxn-podium-3-border: color-mix(in srgb, #cf9a72 28%, rgba(255, 255, 255, 0.12));
--flxn-podium-3-label: #cf9a72;
--flxn-showcase-bg: #2a2d34;
--flxn-overlay: rgba(0, 0, 0, 0.55);
--flxn-dotgrid-color: rgba(255, 255, 255, 0.09);
--flxn-pop-shadow: rgba(0, 0, 0, 0.6);
}
/* ---------------------------------------------------------------- */
/* 3. Surfaces — same color as body, elevation via shadow only */
/* ---------------------------------------------------------------- */
[data-theme="neumorph"] .mantine-Paper-root {
background-color: var(--neu-surface);
border: 1px solid var(--neu-border);
border-radius: var(--mantine-radius-md);
box-shadow: var(--mantine-shadow-md);
}
[data-theme="neumorph"] .mantine-Card-root {
border-radius: var(--mantine-radius-lg);
}
/* Floating surfaces sit higher */
[data-theme="neumorph"] .mantine-Modal-content,
[data-theme="neumorph"] .mantine-Popover-dropdown,
[data-theme="neumorph"] .mantine-Notification-root {
background-color: var(--neu-surface);
border: 1px solid var(--neu-border);
box-shadow: var(--mantine-shadow-lg);
}
[data-theme="neumorph"] .mantine-Modal-content {
border-radius: var(--mantine-radius-lg);
}
[data-theme="neumorph"] .mantine-Modal-header {
background-color: var(--neu-surface);
}
[data-theme="neumorph"] .mantine-Notification-root {
border-radius: var(--mantine-radius-md);
}
[data-theme="neumorph"] .mantine-Tooltip-tooltip {
border-radius: var(--mantine-radius-sm);
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="neumorph"] .mantine-Alert-root {
border-radius: var(--mantine-radius-md);
box-shadow: var(--mantine-shadow-xs);
}
/* App shell: soft edges instead of hard hairlines */
[data-theme="neumorph"] .mantine-AppShell-header {
background-color: var(--neu-surface);
border-bottom: 1px solid var(--neu-border);
box-shadow: var(--neu-edge);
}
[data-theme="neumorph"] .mantine-AppShell-navbar {
background-color: var(--neu-surface);
border-top: 1px solid var(--neu-border);
border-inline-end: 1px solid var(--neu-border);
box-shadow: var(--neu-edge);
}
/* ---------------------------------------------------------------- */
/* 4. Raised controls — extruded plastic; press swaps to inset */
/* ---------------------------------------------------------------- */
[data-theme="neumorph"] .mantine-Button-root,
[data-theme="neumorph"] .mantine-ActionIcon-root {
border: 1px solid var(--neu-border);
border-radius: var(--mantine-radius-md);
box-shadow: var(--mantine-shadow-sm);
transition:
box-shadow 160ms ease,
transform 120ms cubic-bezier(0.32, 0.72, 0, 1);
}
[data-theme="neumorph"] .mantine-Button-root:where([data-variant="default"], [data-variant="outline"]),
[data-theme="neumorph"] .mantine-ActionIcon-root:where([data-variant="default"], [data-variant="outline"]) {
background-color: var(--neu-surface);
color: var(--mantine-color-default-color);
}
[data-theme="neumorph"] .mantine-Button-root:where([data-variant="default"], [data-variant="outline"]):hover,
[data-theme="neumorph"] .mantine-ActionIcon-root:where([data-variant="default"], [data-variant="outline"]):hover {
background-color: var(--neu-surface-high);
}
/* Ghost variants stay flat — no floating shadow plates */
[data-theme="neumorph"] .mantine-Button-root:where([data-variant="subtle"], [data-variant="transparent"], [data-variant="white"]),
[data-theme="neumorph"] .mantine-ActionIcon-root:where([data-variant="subtle"], [data-variant="transparent"], [data-variant="white"]) {
border-color: transparent;
box-shadow: none;
}
[data-theme="neumorph"] .mantine-Button-root:active:not([data-disabled], :disabled, [data-loading]),
[data-theme="neumorph"] .mantine-ActionIcon-root:active:not([data-disabled], :disabled, [data-loading]) {
box-shadow: var(--neu-inset-sm);
}
[data-theme="neumorph"] .mantine-Button-root:where([data-disabled], :disabled),
[data-theme="neumorph"] .mantine-ActionIcon-root:where([data-disabled], :disabled) {
box-shadow: none;
}
/* Re-flavor global press feedback: pressed-in, not just scaled */
@media (prefers-reduced-motion: no-preference) {
[data-theme="neumorph"] .flxn-press:active:not(:disabled):not([data-disabled]):not([data-loading]) {
box-shadow: var(--neu-inset-sm);
transform: scale(0.985);
}
}
/* ---------------------------------------------------------------- */
/* 5. Inputs — pressed-in wells */
/* ---------------------------------------------------------------- */
[data-theme="neumorph"] .mantine-Input-input {
background-color: var(--neu-surface);
border: 1px solid var(--neu-border);
border-radius: var(--mantine-radius-md);
box-shadow: var(--neu-inset-sm);
transition: border-color 160ms ease, box-shadow 160ms ease;
}
[data-theme="neumorph"] .mantine-Input-input:focus,
[data-theme="neumorph"] .mantine-Input-input:focus-within {
border-color: var(--mantine-primary-color-filled);
box-shadow: var(--neu-inset);
}
[data-theme="neumorph"] .mantine-Input-input[data-error] {
border-color: var(--mantine-color-error);
}
/* ---------------------------------------------------------------- */
/* 6. Selection controls */
/* ---------------------------------------------------------------- */
/* SegmentedControl: raised track, indicator is a pressed-in well */
[data-theme="neumorph"] .mantine-SegmentedControl-root {
background-color: var(--neu-surface);
border: 1px solid var(--neu-border);
border-radius: var(--mantine-radius-md);
box-shadow: var(--mantine-shadow-xs);
}
[data-theme="neumorph"] .mantine-SegmentedControl-indicator {
background-color: var(--neu-well);
border: 1px solid var(--neu-border);
border-radius: var(--mantine-radius-sm);
box-shadow: var(--neu-inset-sm);
}
[data-theme="neumorph"] .mantine-SegmentedControl-label[data-active] {
color: var(--mantine-color-bright);
}
/* Switch: inset track, raised thumb */
[data-theme="neumorph"] .mantine-Switch-track {
border: 1px solid var(--neu-border);
box-shadow: var(--neu-inset-sm);
}
[data-theme="neumorph"] .mantine-Switch-thumb {
border: 1px solid var(--neu-border);
box-shadow: 2px 2px 5px var(--neu-shadow-dark), -2px -2px 5px var(--neu-shadow-light);
}
/* Checkbox / Radio: unchecked = soft well, checked keeps accent */
[data-theme="neumorph"] .mantine-Checkbox-input,
[data-theme="neumorph"] .mantine-Radio-radio {
border: 1px solid var(--neu-border);
}
[data-theme="neumorph"] .mantine-Checkbox-input:not(:checked),
[data-theme="neumorph"] .mantine-Radio-radio:not(:checked) {
background-color: var(--neu-surface);
box-shadow: var(--neu-inset-sm);
}
[data-theme="neumorph"] .mantine-Checkbox-input {
border-radius: 0.375rem;
}
/* Progress: inset trough, soft bar */
[data-theme="neumorph"] .mantine-Progress-root {
background-color: var(--neu-well);
border: 1px solid var(--neu-border);
box-shadow: var(--neu-inset-sm);
}
[data-theme="neumorph"] .mantine-Progress-section {
border-radius: var(--mantine-radius-xl);
}
/* ---------------------------------------------------------------- */
/* 7. Navigation & misc */
/* ---------------------------------------------------------------- */
[data-theme="neumorph"] .mantine-Tabs-tab {
border-radius: var(--mantine-radius-sm) var(--mantine-radius-sm) 0 0;
}
[data-theme="neumorph"] .mantine-Tabs-tab:hover {
background-color: var(--neu-surface-high);
}
[data-theme="neumorph"] .mantine-NavLink-root {
border: 1px solid transparent;
border-radius: var(--mantine-radius-md);
}
[data-theme="neumorph"] .mantine-NavLink-root[data-active] {
background-color: var(--neu-well);
border-color: var(--neu-border);
box-shadow: var(--neu-inset-sm);
color: var(--mantine-color-text);
}
[data-theme="neumorph"] .mantine-Badge-root {
border: 1px solid var(--neu-border);
}
[data-theme="neumorph"] .mantine-Badge-root[data-variant="default"] {
background-color: var(--neu-surface);
box-shadow: var(--mantine-shadow-xs);
color: var(--mantine-color-text);
}
[data-theme="neumorph"] .flxn-avatar-frame {
box-shadow: var(--mantine-shadow-xs);
}
[data-theme="neumorph"] .mantine-ThemeIcon-root[data-variant="default"] {
background-color: var(--neu-surface);
border: 1px solid var(--neu-border);
box-shadow: var(--mantine-shadow-xs);
}
[data-theme="neumorph"] .mantine-Skeleton-root {
border-radius: var(--mantine-radius-md);
}
[data-theme="neumorph"] .mantine-Divider-root {
--divider-color: var(--neu-border);
}
/* ---------------------------------------------------------------- */
/* 8. App hooks — tab strip, nav tiles, chips, match tray */
/* ---------------------------------------------------------------- */
/* Tab strip: flat while resting; docking lands it back on the shared
surface. The active state is a soft pressed-in pill instead of a hard
underline, legible on the bare page (body IS --neu-surface). */
[data-theme="neumorph"] .flxn-tabs[data-stuck] {
background-color: var(--neu-surface);
}
[data-theme="neumorph"] .flxn-tab.flxn-tab {
border-radius: var(--mantine-radius-xl);
}
[data-theme="neumorph"] .flxn-tab.flxn-tab[data-active] {
color: color-mix(
in srgb,
var(--mantine-primary-color-filled) 45%,
var(--mantine-color-text)
);
font-weight: 700;
}
[data-theme="neumorph"] .flxn-tabs-indicator.flxn-tabs-indicator {
border-bottom: none;
border-radius: var(--mantine-radius-xl);
background-color: color-mix(in srgb, var(--mantine-primary-color-filled) 8%, var(--neu-well));
box-shadow: var(--neu-inset-sm);
}
/* Nav tiles: extruded plastic, pressed-in on tap; icon sits on a
debossed coin */
[data-theme="neumorph"] .flxn-tile.flxn-tile {
background-color: var(--neu-surface);
border: 1px solid var(--neu-border);
border-radius: var(--mantine-radius-lg);
box-shadow: var(--mantine-shadow-sm);
}
@media (hover: hover) {
[data-theme="neumorph"] .flxn-tile.flxn-tile:hover:not(:disabled) {
background-color: var(--neu-surface-high);
}
}
[data-theme="neumorph"] .flxn-tile.flxn-tile:active:not(:disabled) {
background-color: var(--neu-surface);
box-shadow: var(--neu-inset-sm);
}
@media (prefers-reduced-motion: no-preference) {
[data-theme="neumorph"] .flxn-tile.flxn-tile {
transition:
background-color 120ms ease-out,
box-shadow 160ms ease,
transform 120ms cubic-bezier(0.32, 0.72, 0, 1);
}
}
[data-theme="neumorph"] .flxn-tile-icon.flxn-tile-icon {
color: light-dark(var(--mantine-primary-color-6), var(--mantine-primary-color-3));
}
/* "See all" as a soft extruded chip */
[data-theme="neumorph"] .flxn-see-all.flxn-see-all {
background-color: var(--neu-surface);
border: 1px solid var(--neu-border);
box-shadow: var(--mantine-shadow-xs);
color: var(--mantine-primary-color-light-color);
}
@media (hover: hover) {
[data-theme="neumorph"] .flxn-see-all.flxn-see-all:hover {
background-color: var(--neu-surface-high);
}
}
[data-theme="neumorph"] .flxn-see-all.flxn-see-all:active {
box-shadow: var(--neu-inset-sm);
}
/* Match card stays extruded; the reaction tray tucked beneath it is a
pressed-in well */
[data-theme="neumorph"] .flxn-match-card.flxn-match-card {
background-color: var(--neu-surface);
border: 1px solid var(--neu-border);
box-shadow: var(--mantine-shadow-md);
}
[data-theme="neumorph"] .flxn-reaction-row.flxn-reaction-row {
background-color: var(--neu-well);
border: 1px solid var(--neu-border);
border-radius: var(--mantine-radius-md);
box-shadow: var(--neu-inset-sm);
}
+137
View File
@@ -0,0 +1,137 @@
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
/* Bioluminescent sea ramps for all eight accents. Declared in JS (not
theme.css) so Mantine regenerates every derived variant var (-light rgba,
-outline, autoContrast picks) from the retuned shades, whichever accent
the user selects. */
const oceanColors = {
// plankton blue
blue: [
"#e3f7fd",
"#c6eefb",
"#9ce2f8",
"#6dd2f2",
"#45c3ec",
"#27b3e2",
"#1897c7",
"#147ba6",
"#106284",
"#0c4c67",
],
// kelp green
green: [
"#e8f8ee",
"#cdefdb",
"#a8e3bf",
"#80d5a1",
"#5cc686",
"#3eb56e",
"#2f9c5c",
"#27814d",
"#1f683e",
"#185231",
],
// sea-sparkle phosphor
lime: [
"#f2fbe3",
"#e2f6c4",
"#ccee9d",
"#b3e474",
"#9cda50",
"#86ce32",
"#6fb51f",
"#5c9718",
"#4a7a12",
"#3a600e",
],
// siphonophore crimson
red: [
"#fdeef0",
"#fad8dc",
"#f6b6be",
"#f18f9c",
"#ec6c7e",
"#e54d64",
"#d1344e",
"#b02840",
"#8f2034",
"#701929",
],
// anglerfish gold
yellow: [
"#fdf8e2",
"#fbefbe",
"#f8e392",
"#f4d363",
"#efc43c",
"#e8b31d",
"#ce9a10",
"#ad800c",
"#8c6709",
"#6d5007",
],
// amber lure
orange: [
"#fef2e5",
"#fce2c6",
"#f9cb9c",
"#f5b06e",
"#f09746",
"#ea8127",
"#d16a15",
"#af5810",
"#8d460c",
"#6e3709",
],
// medusa violet
grape: [
"#f5f0fd",
"#e8dcfa",
"#d4c0f5",
"#bd9fee",
"#a781e6",
"#9268dc",
"#7e4fca",
"#6a3fae",
"#56328e",
"#43276f",
],
// jelly pink
pink: [
"#fdeef6",
"#fbd8ea",
"#f7b5d8",
"#f28dc2",
"#ec69ae",
"#e54c9c",
"#d13487",
"#b02871",
"#8f215c",
"#701a48",
],
} as const;
const manifest: ThemeManifest = {
id: "ocean",
label: msg`Deep Sea`,
colorSchemes: "both",
fontLinks: [
"https://fonts.googleapis.com/css2?family=Comfortaa:wght@500;600;700&family=Figtree:wght@400;500;600;700&display=swap",
],
chromeColors: {
light: { base: "#eaf6f6", dimmed: "#c3e2df" },
dark: { base: "#052430", dimmed: "#03141d" },
},
mantine: {
// Bright bioluminescent fills in the dark (shade 5), deeper sea tones in
// the shallows (shade 6); bright fills (gold, phosphor, plankton) take
// dark text via autoContrast.
primaryShade: { light: 6, dark: 5 },
autoContrast: true,
luminanceThreshold: 0.3,
colors: oceanColors,
},
};
export default manifest;
+423
View File
@@ -0,0 +1,423 @@
/* Deep Sea: a submarine descent. Dark is the primary mood — a fixed
abyssal deep-teal→ink-navy backdrop with sparse static rising bubbles;
surfaces are opaque pressure-hull panels with 1px bioluminescent rims,
focus states are sonar rings (double accent box-shadow), avatars are
portholes. Light is the sunlit shallows: sand-and-seafoam with a very
subtle refracted-caustic hint. The fixed teal rim/scenery is decoration;
everything interactive derives from --mantine-primary-color-* (the eight
ramps are retuned in manifest.ts as bioluminescent sea colors). No
backdrop-filter anywhere — hulls are solid. */
/* ---------------------------------------------------------------- */
/* 1. Tokens for any scheme */
/* ---------------------------------------------------------------- */
[data-theme="ocean"][data-theme] {
--mantine-font-family: "Figtree", "Avenir Next", "Helvetica Neue", sans-serif;
--mantine-font-family-headings: "Comfortaa", "Figtree", sans-serif;
--mantine-heading-font-weight: 700;
/* rounded, water-worn radii */
--mantine-radius-xs: 0.375rem;
--mantine-radius-sm: 0.5625rem;
--mantine-radius-md: 0.75rem;
--mantine-radius-lg: 1rem;
--mantine-radius-xl: 1.5rem;
--mantine-radius-default: 0.75rem;
/* porthole avatars: circle + bold hull ring (the frame owns the border) */
--flxn-avatar-radius: 50%;
--flxn-avatar-border: 3px solid var(--ocean-porthole);
--flxn-badge-radius: 14px;
/* sonar focus ring: two accent rings, inner solid + outer echo */
--ocean-sonar: 0 0 0 2px
color-mix(in srgb, var(--mantine-primary-color-filled) 90%, transparent),
0 0 0 6px
color-mix(in srgb, var(--mantine-primary-color-filled) 28%, transparent);
}
/* ---------------------------------------------------------------- */
/* 2. Per-scheme tokens */
/* ---------------------------------------------------------------- */
[data-theme="ocean"][data-theme][data-mantine-color-scheme="dark"] {
--mantine-color-body: #062632;
--mantine-color-text: #d9f2f0;
--mantine-color-bright: #f2fffd;
--mantine-color-dimmed: #93bcc0;
--mantine-color-placeholder: #628e96;
--mantine-color-default: #0c3745;
--mantine-color-default-hover: #11424f;
--mantine-color-default-color: #d9f2f0;
--mantine-color-default-border: rgba(103, 232, 214, 0.24);
/* deep-water elevation with a faint bioluminescent edge */
--mantine-shadow-xs: 0 0 0 1px rgba(103, 232, 214, 0.06), 0 2px 6px rgba(1, 10, 16, 0.5);
--mantine-shadow-sm: 0 0 0 1px rgba(103, 232, 214, 0.08), 0 4px 12px rgba(1, 10, 16, 0.55);
--mantine-shadow-md: 0 0 0 1px rgba(103, 232, 214, 0.1), 0 8px 24px rgba(1, 10, 16, 0.6);
--mantine-shadow-lg: 0 0 0 1px rgba(103, 232, 214, 0.12), 0 12px 34px rgba(1, 10, 16, 0.65);
--mantine-shadow-xl: 0 0 0 1px rgba(103, 232, 214, 0.14), 0 18px 48px rgba(1, 10, 16, 0.7);
/* pressure-hull ingredients */
--ocean-hull: #0a3240;
--ocean-hull-raised: #0e3d4d;
--ocean-deep: #041d27;
--ocean-rim: rgba(103, 232, 214, 0.22);
--ocean-rim-strong: rgba(103, 232, 214, 0.38);
--ocean-chrome: #052430;
--ocean-porthole: #2c7482;
--ocean-porthole-glow: 0 0 0 1px rgba(103, 232, 214, 0.25),
0 0 12px rgba(103, 232, 214, 0.2);
--ocean-tooltip-bg: #0d3947;
--ocean-tooltip-color: #dcf5f2;
/* app tokens */
--flxn-gold: #f2c14b;
--flxn-silver: #a9c3cb;
--flxn-bronze: #dd9364;
--flxn-toast-success: #6fe3a5;
--flxn-toast-error: #ff8f9f;
--flxn-showcase-bg: #0a3240;
--flxn-overlay: rgba(2, 10, 16, 0.62);
--flxn-dotgrid-color: rgba(103, 232, 214, 0.16);
--flxn-pop-shadow: rgba(0, 0, 0, 0.55);
/* podium as glowing depth tiers */
--flxn-podium-1-bg: rgba(242, 193, 75, 0.1);
--flxn-podium-1-border: rgba(242, 193, 75, 0.38);
--flxn-podium-1-label: #f2c14b;
--flxn-podium-2-bg: rgba(169, 195, 203, 0.08);
--flxn-podium-2-border: rgba(169, 195, 203, 0.3);
--flxn-podium-2-label: #b8ccd2;
--flxn-podium-3-bg: rgba(221, 147, 100, 0.1);
--flxn-podium-3-border: rgba(221, 147, 100, 0.36);
--flxn-podium-3-label: #e5a273;
}
[data-theme="ocean"][data-theme][data-mantine-color-scheme="light"] {
--mantine-color-body: #eaf6f6;
--mantine-color-text: #11373f;
--mantine-color-bright: #072229;
--mantine-color-dimmed: #4a6e72;
--mantine-color-placeholder: #5f858a;
--mantine-color-default: #ffffff;
--mantine-color-default-hover: #f0f7f4;
--mantine-color-default-color: #11373f;
--mantine-color-default-border: rgba(23, 115, 110, 0.3);
/* soft sea-tinted elevation */
--mantine-shadow-xs: 0 1px 3px rgba(15, 80, 90, 0.1);
--mantine-shadow-sm: 0 2px 8px rgba(15, 80, 90, 0.12);
--mantine-shadow-md: 0 6px 18px rgba(15, 80, 90, 0.14);
--mantine-shadow-lg: 0 10px 28px rgba(15, 80, 90, 0.16);
--mantine-shadow-xl: 0 16px 44px rgba(15, 80, 90, 0.2);
/* sand-and-seafoam ingredients */
--ocean-hull: #fbfaf3;
--ocean-hull-raised: #ffffff;
--ocean-deep: #ddeeea;
--ocean-rim: rgba(23, 115, 110, 0.25);
--ocean-rim-strong: rgba(23, 115, 110, 0.42);
--ocean-chrome: #f3f9f6;
--ocean-porthole: #7fb5ae;
--ocean-porthole-glow: 0 0 0 1px rgba(23, 115, 110, 0.18),
0 2px 8px rgba(15, 80, 90, 0.16);
--ocean-tooltip-bg: #14424d;
--ocean-tooltip-color: #e4f6f3;
/* app tokens */
--flxn-gold: #d8a419;
--flxn-silver: #8ba7ad;
--flxn-bronze: #c76e34;
--flxn-toast-success: #2e9e6b;
--flxn-toast-error: #d64d64;
--flxn-showcase-bg: #ffffff;
--flxn-overlay: rgba(10, 50, 60, 0.4);
--flxn-dotgrid-color: rgba(23, 115, 110, 0.2);
--flxn-pop-shadow: rgba(15, 80, 90, 0.28);
/* podium as sunlit sand tiers */
--flxn-podium-1-bg: rgba(216, 164, 25, 0.14);
--flxn-podium-1-border: rgba(179, 131, 16, 0.45);
--flxn-podium-1-label: #7c5a0b;
--flxn-podium-2-bg: rgba(90, 120, 125, 0.1);
--flxn-podium-2-border: rgba(90, 120, 125, 0.35);
--flxn-podium-2-label: #47666a;
--flxn-podium-3-bg: rgba(199, 110, 52, 0.12);
--flxn-podium-3-border: rgba(199, 110, 52, 0.42);
--flxn-podium-3-label: #8a4c1d;
}
/* ---------------------------------------------------------------- */
/* 3. Fixed backdrop (below all content) */
/* ---------------------------------------------------------------- */
[data-theme="ocean"] body::before {
content: "";
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
}
/* Abyssal descent: deep teal fading to ink navy, sparse static bubbles
rising (bigger and brighter near the sea floor, fading toward the top). */
[data-theme="ocean"][data-mantine-color-scheme="dark"] body::before {
background-color: #062632;
background-image:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='640' viewBox='0 0 260 640'%3E%3Cg fill='none' stroke='%239beee2'%3E%3Ccircle cx='36' cy='584' r='5' stroke-opacity='.13'/%3E%3Ccircle cx='44' cy='512' r='3' stroke-opacity='.1'/%3E%3Ccircle cx='210' cy='612' r='4' stroke-opacity='.12'/%3E%3Ccircle cx='202' cy='540' r='2.5' stroke-opacity='.09'/%3E%3Ccircle cx='128' cy='430' r='3.5' stroke-opacity='.1'/%3E%3Ccircle cx='120' cy='360' r='2' stroke-opacity='.08'/%3E%3Ccircle cx='232' cy='300' r='3' stroke-opacity='.09'/%3E%3Ccircle cx='60' cy='250' r='2.5' stroke-opacity='.08'/%3E%3Ccircle cx='150' cy='160' r='2' stroke-opacity='.07'/%3E%3Ccircle cx='90' cy='80' r='1.5' stroke-opacity='.06'/%3E%3C/g%3E%3Cg fill='%239beee2'%3E%3Ccircle cx='70' cy='470' r='1.2' fill-opacity='.1'/%3E%3Ccircle cx='180' cy='390' r='1' fill-opacity='.09'/%3E%3Ccircle cx='40' cy='330' r='1' fill-opacity='.08'/%3E%3Ccircle cx='222' cy='190' r='1' fill-opacity='.07'/%3E%3Ccircle cx='140' cy='40' r='1' fill-opacity='.05'/%3E%3C/g%3E%3C/svg%3E"),
radial-gradient(50rem 30rem at 50% -10%, rgba(45, 160, 175, 0.18), transparent 65%),
linear-gradient(180deg, #0b3b46 0%, #072b38 45%, #03121e 100%);
background-repeat: repeat, no-repeat, no-repeat;
background-size: 260px 640px, auto, auto;
}
/* Sunlit shallows: seafoam fading to a sandy floor, with a faint white
caustic ripple tile — refracted light, kept very subtle. */
[data-theme="ocean"][data-mantine-color-scheme="light"] body::before {
background-color: #eaf6f6;
background-image:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='320' height='220' viewBox='0 0 320 220'%3E%3Cg fill='none' stroke='%23ffffff' stroke-width='1.4'%3E%3Cpath d='M0 40 Q 40 10 80 42 T 160 44 T 240 38 T 320 42' stroke-opacity='.4'/%3E%3Cpath d='M0 110 Q 45 82 90 112 T 180 110 T 268 114 T 320 108' stroke-opacity='.3'/%3E%3Cpath d='M0 180 Q 50 152 100 182 T 200 178 T 300 184 T 320 180' stroke-opacity='.35'/%3E%3C/g%3E%3C/svg%3E"),
radial-gradient(46rem 26rem at 30% -8%, rgba(255, 255, 255, 0.75), transparent 60%),
radial-gradient(60rem 22rem at 50% 108%, rgba(226, 214, 178, 0.5), transparent 70%),
linear-gradient(180deg, #d8efee 0%, #eaf6f6 55%, #f2f3e9 100%);
background-repeat: repeat, no-repeat, no-repeat, no-repeat;
background-size: 320px 220px, auto, auto, auto;
}
/* ---------------------------------------------------------------- */
/* 4. Pressure-hull surfaces: solid panels, bioluminescent rims */
/* ---------------------------------------------------------------- */
[data-theme="ocean"] .mantine-Paper-root,
[data-theme="ocean"] .mantine-Card-root {
background-color: var(--ocean-hull);
border: 1px solid var(--ocean-rim);
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="ocean"] .mantine-Modal-content {
background-color: var(--ocean-hull);
border: 1px solid var(--ocean-rim-strong);
box-shadow: var(--mantine-shadow-xl);
}
[data-theme="ocean"] .mantine-Modal-header {
background-color: transparent;
}
[data-theme="ocean"] .mantine-Popover-dropdown {
background-color: var(--ocean-hull);
border: 1px solid var(--ocean-rim-strong);
box-shadow: var(--mantine-shadow-lg);
}
[data-theme="ocean"] .mantine-Notification-root {
background-color: var(--ocean-hull);
border: 1px solid var(--ocean-rim-strong);
box-shadow: var(--mantine-shadow-lg);
}
/* AppShell chrome: the conning tower and lower deck — solid hull strips
sealed with a stronger rim seam. */
[data-theme="ocean"] .mantine-AppShell-header {
background-color: var(--ocean-chrome);
border-bottom: 1px solid var(--ocean-rim-strong);
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="ocean"] .mantine-AppShell-navbar {
background-color: var(--ocean-chrome);
border-inline-end: 1px solid var(--ocean-rim-strong);
}
/* ---------------------------------------------------------------- */
/* 5. Sonar focus rings */
/* ---------------------------------------------------------------- */
/* The transparent outline stays invisible here but becomes a real ring in
forced-colors mode, where the box-shadow sonar is stripped. */
[data-theme="ocean"] :focus-visible {
outline: 2px solid transparent;
outline-offset: 2px;
box-shadow: var(--ocean-sonar);
}
[data-theme="ocean"] .mantine-Input-input:focus,
[data-theme="ocean"] .mantine-Input-input:focus-within {
border-color: var(--mantine-primary-color-filled);
box-shadow: var(--ocean-sonar);
}
/* ---------------------------------------------------------------- */
/* 6. Controls */
/* ---------------------------------------------------------------- */
/* Filled controls give off a faint accent glow; the transparent border
keeps a real edge for forced-colors mode. */
[data-theme="ocean"] .mantine-Button-root[data-variant="filled"],
[data-theme="ocean"] .mantine-ActionIcon-root[data-variant="filled"] {
border: 1px solid transparent;
box-shadow:
0 0 14px -4px color-mix(in srgb, var(--mantine-primary-color-filled) 55%, transparent),
var(--mantine-shadow-xs);
}
[data-theme="ocean"] .mantine-Button-root {
font-weight: 600;
}
[data-theme="ocean"] .mantine-Input-input {
background-color: var(--ocean-deep);
border: 1px solid var(--ocean-rim);
}
[data-theme="ocean"] .mantine-Checkbox-input {
background-color: var(--ocean-deep);
border-color: var(--ocean-rim-strong);
}
[data-theme="ocean"] .mantine-SegmentedControl-root {
background-color: var(--ocean-deep);
border: 1px solid var(--ocean-rim);
}
[data-theme="ocean"] .mantine-SegmentedControl-indicator {
background-color: var(--ocean-hull-raised);
border: 1px solid var(--ocean-rim-strong);
box-shadow: var(--mantine-shadow-xs);
}
[data-theme="ocean"] .mantine-Progress-root {
background-color: var(--ocean-deep);
border: 1px solid var(--ocean-rim);
}
[data-theme="ocean"] .mantine-Badge-root[data-variant="default"] {
background-color: var(--mantine-color-default);
border: 1px solid var(--ocean-rim);
}
[data-theme="ocean"] .mantine-Tabs-list {
--tabs-list-border-color: var(--mantine-color-default-border);
}
[data-theme="ocean"] .mantine-Tabs-tab:hover,
[data-theme="ocean"] .mantine-NavLink-root:hover {
background-color: var(--mantine-color-default-hover);
}
[data-theme="ocean"] .mantine-Tooltip-tooltip {
background-color: var(--ocean-tooltip-bg);
color: var(--ocean-tooltip-color);
border-radius: var(--mantine-radius-md);
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="ocean"] ::selection {
background: color-mix(in srgb, var(--mantine-primary-color-filled) 30%, transparent);
}
/* ---------------------------------------------------------------- */
/* 7. Wave divider: repeating radial scallop line */
/* ---------------------------------------------------------------- */
[data-theme="ocean"] .mantine-Divider-root {
--divider-color: var(--ocean-rim-strong);
}
[data-theme="ocean"] .mantine-Divider-root[data-orientation="horizontal"]:not([data-with-label]) {
border-top: none;
height: 5px;
background-image: radial-gradient(
circle at 6px -2px,
transparent 4px,
var(--ocean-rim-strong) 4px,
var(--ocean-rim-strong) 5.4px,
transparent 5.4px
);
background-size: 12px 5px;
background-repeat: repeat-x;
}
@media (forced-colors: active) {
[data-theme="ocean"] .mantine-Divider-root[data-orientation="horizontal"]:not([data-with-label]) {
border-top: 1px solid CanvasText;
height: auto;
background-image: none;
}
}
/* ---------------------------------------------------------------- */
/* 8. App component hooks */
/* ---------------------------------------------------------------- */
/* Tab strip: flat while resting; docked it becomes a hull deck strip. */
[data-theme="ocean"] .flxn-tabs[data-stuck] {
background-color: var(--ocean-chrome);
border-bottom-color: var(--ocean-rim-strong);
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="ocean"] .flxn-tab {
color: var(--mantine-color-dimmed);
font-weight: 500;
}
/* Underline indicator, so accent text on the active tab is allowed. */
[data-theme="ocean"] .flxn-tab[data-active] {
color: var(--mantine-primary-color-light-color);
font-weight: 700;
}
[data-theme="ocean"] .flxn-tabs-indicator {
border-bottom: 2px solid var(--mantine-primary-color-filled);
padding-inline: 0.5rem;
}
/* Nav grid tiles: instrument panels — hull plate with a lit top seam. */
[data-theme="ocean"] .flxn-tile {
background-color: var(--ocean-hull);
border: 1px solid var(--ocean-rim);
border-radius: var(--mantine-radius-lg);
box-shadow: var(--mantine-shadow-xs), inset 0 1px 0 var(--ocean-rim);
}
/* Flat accent-tinted glyph — no plate. */
[data-theme="ocean"] .flxn-tile-icon {
color: var(--mantine-primary-color-light-color);
}
/* "See all" as a depth-tag chip: accent wash + its dedicated on-color. */
[data-theme="ocean"] .flxn-see-all {
background-color: var(--mantine-primary-color-light);
color: var(--mantine-primary-color-light-color);
border: 1px solid
color-mix(in srgb, var(--mantine-primary-color-light-color) 35%, transparent);
}
/* Match card + reaction tray as one hull construction: same rim, tray one
deck lower (deeper water). */
[data-theme="ocean"] .flxn-match-card {
background-color: var(--ocean-hull);
border: 1px solid var(--ocean-rim);
}
[data-theme="ocean"] .flxn-reaction-row {
background-color: var(--ocean-deep);
border: 1px solid var(--ocean-rim);
}
/* Porthole: the bold ring comes from --flxn-avatar-border; the frame adds
the bioluminescent halo around it. */
[data-theme="ocean"] .flxn-avatar-frame {
border-radius: 50%;
box-shadow: var(--ocean-porthole-glow);
}
/* Press feedback: a brief bioluminescent flare (filter only — the global
scale transform stays untouched). */
[data-theme="ocean"] .flxn-press:active {
filter: brightness(1.06) saturate(1.08);
}
+18
View File
@@ -0,0 +1,18 @@
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
const manifest: ThemeManifest = {
id: "quest",
label: msg`Quest Log`,
colorSchemes: "both",
fontLinks: [
"https://fonts.googleapis.com/css2?family=Grenze:wght@500;600;700&display=swap",
"https://fonts.googleapis.com/css2?family=Alegreya:ital,wght@0,400;0,500;0,600;0,700;1,400;1,600&display=swap",
],
chromeColors: {
light: { base: "#f0e6cf", dimmed: "#ddcca6" },
dark: { base: "#211a12", dimmed: "#150f08" },
},
};
export default manifest;
+762
View File
@@ -0,0 +1,762 @@
/* Quest Log — fantasy-RPG journal.
Aged bronze is the structural decoration (2px entry frames, corner
brackets, ribbon banners); the interactive accent follows the user via
the primary aliases. All eight accent ramps are retuned as heraldic
banner dyes. Light = aged parchment journal, dark = torchlit hall. */
/* ---------------------------------------------------------------- *
* 1. Scheme-independent tokens — (0,2,0)
* ---------------------------------------------------------------- */
[data-theme="quest"][data-theme] {
/* Type: Grenze for medieval-but-readable headings, Alegreya body. */
--mantine-font-family: "Alegreya", "Iowan Old Style", Georgia, serif;
--mantine-font-family-headings: "Grenze", "Alegreya", Georgia, serif;
--mantine-font-size-xs: 0.8125rem;
--mantine-font-size-sm: 0.9375rem;
--mantine-font-size-md: 1.0625rem;
--mantine-font-size-lg: 1.1875rem;
--mantine-font-size-xl: 1.375rem;
--mantine-heading-font-weight: 600;
--mantine-h1-font-size: 2.4rem;
--mantine-h1-line-height: 1.15;
--mantine-h2-font-size: 1.95rem;
--mantine-h2-line-height: 1.2;
--mantine-h3-font-size: 1.55rem;
--mantine-h3-line-height: 1.25;
--mantine-h4-font-size: 1.3rem;
--mantine-h4-line-height: 1.3;
--mantine-h5-font-size: 1.15rem;
--mantine-h5-line-height: 1.35;
--mantine-h6-font-size: 1.05rem;
--mantine-h6-line-height: 1.4;
/* Shape: gently squared journal entries. */
--mantine-radius-xs: 0.125rem;
--mantine-radius-sm: 0.25rem;
--mantine-radius-md: 0.375rem;
--mantine-radius-lg: 0.5rem;
--mantine-radius-xl: 0.75rem;
--mantine-radius-default: 0.375rem;
/* Elevation: soft warm sepia, never harsh. */
--mantine-shadow-xs: 0 1px 2px rgba(38, 26, 10, 0.2);
--mantine-shadow-sm: 0 1px 2px rgba(38, 26, 10, 0.22), 0 5px 14px rgba(38, 26, 10, 0.16);
--mantine-shadow-md: 0 2px 4px rgba(38, 26, 10, 0.24), 0 10px 24px rgba(38, 26, 10, 0.2);
--mantine-shadow-lg: 0 3px 7px rgba(38, 26, 10, 0.26), 0 18px 40px rgba(38, 26, 10, 0.24);
--mantine-shadow-xl: 0 5px 10px rgba(38, 26, 10, 0.28), 0 26px 56px rgba(38, 26, 10, 0.28);
/* Accent ramps retuned as heraldic banner dyes — saturated but cloth-worn;
every shade 6 carries ≥4.5:1 parchment contrast text. */
/* blue → azure banner */
--mantine-color-blue-0: #eef1fa;
--mantine-color-blue-1: #d8dff2;
--mantine-color-blue-2: #b4c2e6;
--mantine-color-blue-3: #8aa0d6;
--mantine-color-blue-4: #6280c4;
--mantine-color-blue-5: #4263ae;
--mantine-color-blue-6: #2f4d96;
--mantine-color-blue-7: #263e7b;
--mantine-color-blue-8: #1d2f60;
--mantine-color-blue-9: #152246;
/* red → gules crimson */
--mantine-color-red-0: #faeceb;
--mantine-color-red-1: #f2d3d1;
--mantine-color-red-2: #e6aba7;
--mantine-color-red-3: #d87d78;
--mantine-color-red-4: #c65450;
--mantine-color-red-5: #ad322f;
--mantine-color-red-6: #92201f;
--mantine-color-red-7: #7a1719;
--mantine-color-red-8: #601114;
--mantine-color-red-9: #470c0f;
/* green → vert */
--mantine-color-green-0: #ebf3ec;
--mantine-color-green-1: #d2e5d4;
--mantine-color-green-2: #aacfae;
--mantine-color-green-3: #7db383;
--mantine-color-green-4: #55975d;
--mantine-color-green-5: #35793f;
--mantine-color-green-6: #24632e;
--mantine-color-green-7: #1c5026;
--mantine-color-green-8: #153e1e;
--mantine-color-green-9: #0e2c16;
/* yellow → or (gold leaf) */
--mantine-color-yellow-0: #fbf3dc;
--mantine-color-yellow-1: #f4e3b0;
--mantine-color-yellow-2: #e9cd7c;
--mantine-color-yellow-3: #dbb14b;
--mantine-color-yellow-4: #c99527;
--mantine-color-yellow-5: #ab7812;
--mantine-color-yellow-6: #8c5f0b;
--mantine-color-yellow-7: #714c09;
--mantine-color-yellow-8: #583b07;
--mantine-color-yellow-9: #402b05;
/* grape → purpure */
--mantine-color-grape-0: #f4edf7;
--mantine-color-grape-1: #e5d5ec;
--mantine-color-grape-2: #cfb2dc;
--mantine-color-grape-3: #b389c8;
--mantine-color-grape-4: #9663b2;
--mantine-color-grape-5: #7a4399;
--mantine-color-grape-6: #642e82;
--mantine-color-grape-7: #52246b;
--mantine-color-grape-8: #401b54;
--mantine-color-grape-9: #2f133e;
/* orange → tenné */
--mantine-color-orange-0: #faeee2;
--mantine-color-orange-1: #f3d9bd;
--mantine-color-orange-2: #e9bc8d;
--mantine-color-orange-3: #dd9a58;
--mantine-color-orange-4: #cf7a2c;
--mantine-color-orange-5: #b45e14;
--mantine-color-orange-6: #96490d;
--mantine-color-orange-7: #7a3a0b;
--mantine-color-orange-8: #602d08;
--mantine-color-orange-9: #472106;
/* pink → rose madder */
--mantine-color-pink-0: #f9ecf1;
--mantine-color-pink-1: #f0d3de;
--mantine-color-pink-2: #e3aac1;
--mantine-color-pink-3: #d27da0;
--mantine-color-pink-4: #bf5280;
--mantine-color-pink-5: #a53064;
--mantine-color-pink-6: #8a1f51;
--mantine-color-pink-7: #711842;
--mantine-color-pink-8: #581233;
--mantine-color-pink-9: #400c25;
/* lime → spring leaf */
--mantine-color-lime-0: #f2f5e6;
--mantine-color-lime-1: #e2e9c6;
--mantine-color-lime-2: #cdd89e;
--mantine-color-lime-3: #b2c26f;
--mantine-color-lime-4: #96a944;
--mantine-color-lime-5: #7a8d26;
--mantine-color-lime-6: #59690f;
--mantine-color-lime-7: #485510;
--mantine-color-lime-8: #38420d;
--mantine-color-lime-9: #293009;
/* Gray ramp as parchment/sepia so gray-derived UI stays in the world. */
--mantine-color-gray-0: #f7efdd;
--mantine-color-gray-1: #f0e6cf;
--mantine-color-gray-2: #e3d4b4;
--mantine-color-gray-3: #d0bd95;
--mantine-color-gray-4: #b5a17a;
--mantine-color-gray-5: #92805e;
--mantine-color-gray-6: #6a5a41;
--mantine-color-gray-7: #4a3d2b;
--mantine-color-gray-8: #362c1e;
--mantine-color-gray-9: #291f14;
/* Dark ramp as torch-lit umber. */
--mantine-color-dark-0: #ecdfc6;
--mantine-color-dark-1: #b3a488;
--mantine-color-dark-2: #7a6a52;
--mantine-color-dark-3: #55462f;
--mantine-color-dark-4: #3b2f1f;
--mantine-color-dark-5: #2b2318;
--mantine-color-dark-6: #241d13;
--mantine-color-dark-7: #1c160e;
--mantine-color-dark-8: #15100a;
--mantine-color-dark-9: #0e0a06;
--flxn-avatar-radius: 50%; /* party-member portraits */
--flxn-avatar-border: 2px solid var(--quest-frame);
--flxn-badge-radius: 8px;
}
/* ---------------------------------------------------------------- *
* 2. Per-scheme tokens — (0,3,0)
* ---------------------------------------------------------------- */
[data-theme="quest"][data-theme][data-mantine-color-scheme="light"] {
--mantine-color-body: #f0e6cf;
--mantine-color-text: #2f2414;
--mantine-color-bright: #1d1509;
--mantine-color-dimmed: #6a5a41;
--mantine-color-placeholder: #83704f;
--mantine-color-anchor: var(--mantine-primary-color-7);
--mantine-color-error: #9e2b20;
--mantine-color-default: #f7efdd;
--mantine-color-default-hover: #efe3c8;
--mantine-color-default-color: #2f2414;
--mantine-color-default-border: #b39a6b;
/* Accent variant vars re-derived from the retuned ramps (Mantine's
defaults are literal rgba of its stock hues). */
--mantine-color-blue-light: color-mix(in srgb, var(--mantine-color-blue-6) 10%, transparent);
--mantine-color-blue-light-hover: color-mix(in srgb, var(--mantine-color-blue-6) 16%, transparent);
--mantine-color-blue-light-color: var(--mantine-color-blue-7);
--mantine-color-blue-outline: var(--mantine-color-blue-6);
--mantine-color-blue-outline-hover: color-mix(in srgb, var(--mantine-color-blue-6) 8%, transparent);
--mantine-color-blue-text: var(--mantine-color-blue-7);
--mantine-color-blue-contrast: #f6ecd8;
--mantine-color-red-light: color-mix(in srgb, var(--mantine-color-red-6) 10%, transparent);
--mantine-color-red-light-hover: color-mix(in srgb, var(--mantine-color-red-6) 16%, transparent);
--mantine-color-red-light-color: var(--mantine-color-red-7);
--mantine-color-red-outline: var(--mantine-color-red-6);
--mantine-color-red-outline-hover: color-mix(in srgb, var(--mantine-color-red-6) 8%, transparent);
--mantine-color-red-text: var(--mantine-color-red-7);
--mantine-color-red-contrast: #f6ecd8;
--mantine-color-green-light: color-mix(in srgb, var(--mantine-color-green-6) 10%, transparent);
--mantine-color-green-light-hover: color-mix(in srgb, var(--mantine-color-green-6) 16%, transparent);
--mantine-color-green-light-color: var(--mantine-color-green-7);
--mantine-color-green-outline: var(--mantine-color-green-6);
--mantine-color-green-outline-hover: color-mix(in srgb, var(--mantine-color-green-6) 8%, transparent);
--mantine-color-green-text: var(--mantine-color-green-7);
--mantine-color-green-contrast: #f6ecd8;
--mantine-color-yellow-light: color-mix(in srgb, var(--mantine-color-yellow-6) 10%, transparent);
--mantine-color-yellow-light-hover: color-mix(in srgb, var(--mantine-color-yellow-6) 16%, transparent);
--mantine-color-yellow-light-color: var(--mantine-color-yellow-7);
--mantine-color-yellow-outline: var(--mantine-color-yellow-6);
--mantine-color-yellow-outline-hover: color-mix(in srgb, var(--mantine-color-yellow-6) 8%, transparent);
--mantine-color-yellow-text: var(--mantine-color-yellow-7);
--mantine-color-yellow-contrast: #f6ecd8;
--mantine-color-grape-light: color-mix(in srgb, var(--mantine-color-grape-6) 10%, transparent);
--mantine-color-grape-light-hover: color-mix(in srgb, var(--mantine-color-grape-6) 16%, transparent);
--mantine-color-grape-light-color: var(--mantine-color-grape-7);
--mantine-color-grape-outline: var(--mantine-color-grape-6);
--mantine-color-grape-outline-hover: color-mix(in srgb, var(--mantine-color-grape-6) 8%, transparent);
--mantine-color-grape-text: var(--mantine-color-grape-7);
--mantine-color-grape-contrast: #f6ecd8;
--mantine-color-orange-light: color-mix(in srgb, var(--mantine-color-orange-6) 10%, transparent);
--mantine-color-orange-light-hover: color-mix(in srgb, var(--mantine-color-orange-6) 16%, transparent);
--mantine-color-orange-light-color: var(--mantine-color-orange-7);
--mantine-color-orange-outline: var(--mantine-color-orange-6);
--mantine-color-orange-outline-hover: color-mix(in srgb, var(--mantine-color-orange-6) 8%, transparent);
--mantine-color-orange-text: var(--mantine-color-orange-7);
--mantine-color-orange-contrast: #f6ecd8;
--mantine-color-pink-light: color-mix(in srgb, var(--mantine-color-pink-6) 10%, transparent);
--mantine-color-pink-light-hover: color-mix(in srgb, var(--mantine-color-pink-6) 16%, transparent);
--mantine-color-pink-light-color: var(--mantine-color-pink-7);
--mantine-color-pink-outline: var(--mantine-color-pink-6);
--mantine-color-pink-outline-hover: color-mix(in srgb, var(--mantine-color-pink-6) 8%, transparent);
--mantine-color-pink-text: var(--mantine-color-pink-7);
--mantine-color-pink-contrast: #f6ecd8;
--mantine-color-lime-light: color-mix(in srgb, var(--mantine-color-lime-6) 10%, transparent);
--mantine-color-lime-light-hover: color-mix(in srgb, var(--mantine-color-lime-6) 16%, transparent);
--mantine-color-lime-light-color: var(--mantine-color-lime-7);
--mantine-color-lime-outline: var(--mantine-color-lime-6);
--mantine-color-lime-outline-hover: color-mix(in srgb, var(--mantine-color-lime-6) 8%, transparent);
--mantine-color-lime-text: var(--mantine-color-lime-7);
--mantine-color-lime-contrast: #f6ecd8;
--mantine-color-gray-light: color-mix(in srgb, var(--mantine-color-gray-6) 10%, transparent);
--mantine-color-gray-light-hover: color-mix(in srgb, var(--mantine-color-gray-6) 15%, transparent);
--mantine-color-gray-light-color: var(--mantine-color-gray-7);
/* Theme-local palette. */
--quest-frame: #a3854f;
--quest-corner: #8a6a3a;
--quest-bronze: #7c5f2c;
--quest-surface: #f7efdd;
--quest-recessed: #e7d8b6;
--quest-shell: #ece0c3;
--quest-tick: rgba(90, 66, 30, 0.22);
--quest-ribbon-hi: #8d6c38;
--quest-ribbon: #7a5c2e;
--quest-ribbon-lo: #5f4620;
--quest-ribbon-ink: #f7eedb;
/* App tokens: guild-board accolades on parchment. */
--flxn-gold: #7d5c12;
--flxn-silver: #776e5c;
--flxn-bronze: #8a4f22;
--flxn-toast-success: #45702c;
--flxn-toast-error: #9e2b20;
--flxn-podium-1-bg: color-mix(in srgb, #c99527 15%, #f7efdd);
--flxn-podium-1-border: color-mix(in srgb, #7d5c12 55%, #d0bd95);
--flxn-podium-1-label: #6f5116;
--flxn-podium-2-bg: color-mix(in srgb, #92805e 10%, #f7efdd);
--flxn-podium-2-border: #b5a17a;
--flxn-podium-2-label: #6a5a41;
--flxn-podium-3-bg: color-mix(in srgb, #8a4f22 12%, #f7efdd);
--flxn-podium-3-border: color-mix(in srgb, #8a4f22 45%, #d0bd95);
--flxn-podium-3-label: #74431c;
--flxn-showcase-bg: #f7efdd;
--flxn-overlay: rgba(35, 24, 10, 0.45);
--flxn-dotgrid-color: rgba(138, 106, 58, 0.25);
--flxn-pop-shadow: rgba(46, 32, 12, 0.3);
}
[data-theme="quest"][data-theme][data-mantine-color-scheme="dark"] {
--mantine-color-body: #211a12;
--mantine-color-text: #ecdfc6;
--mantine-color-bright: #f7efdd;
--mantine-color-dimmed: #a5937a;
--mantine-color-placeholder: #837259;
--mantine-color-anchor: var(--mantine-primary-color-2);
--mantine-color-error: #d4736a;
--mantine-color-default: #2b2216;
--mantine-color-default-hover: #342a1b;
--mantine-color-default-color: #ecdfc6;
--mantine-color-default-border: #5c4a2c;
/* Filled stays mid-ramp: shade-8 dye vanishes in the torchlit hall. */
--mantine-color-blue-filled: var(--mantine-color-blue-6);
--mantine-color-blue-filled-hover: var(--mantine-color-blue-5);
--mantine-color-blue-light: color-mix(in srgb, var(--mantine-color-blue-4) 15%, transparent);
--mantine-color-blue-light-hover: color-mix(in srgb, var(--mantine-color-blue-4) 22%, transparent);
--mantine-color-blue-light-color: var(--mantine-color-blue-2);
--mantine-color-blue-outline: var(--mantine-color-blue-3);
--mantine-color-blue-outline-hover: color-mix(in srgb, var(--mantine-color-blue-3) 12%, transparent);
--mantine-color-blue-text: var(--mantine-color-blue-3);
--mantine-color-blue-contrast: #f6ecd8;
--mantine-color-red-filled: var(--mantine-color-red-6);
--mantine-color-red-filled-hover: var(--mantine-color-red-5);
--mantine-color-red-light: color-mix(in srgb, var(--mantine-color-red-4) 15%, transparent);
--mantine-color-red-light-hover: color-mix(in srgb, var(--mantine-color-red-4) 22%, transparent);
--mantine-color-red-light-color: var(--mantine-color-red-2);
--mantine-color-red-outline: var(--mantine-color-red-3);
--mantine-color-red-outline-hover: color-mix(in srgb, var(--mantine-color-red-3) 12%, transparent);
--mantine-color-red-text: var(--mantine-color-red-3);
--mantine-color-red-contrast: #f6ecd8;
--mantine-color-green-filled: var(--mantine-color-green-6);
--mantine-color-green-filled-hover: var(--mantine-color-green-5);
--mantine-color-green-light: color-mix(in srgb, var(--mantine-color-green-4) 15%, transparent);
--mantine-color-green-light-hover: color-mix(in srgb, var(--mantine-color-green-4) 22%, transparent);
--mantine-color-green-light-color: var(--mantine-color-green-2);
--mantine-color-green-outline: var(--mantine-color-green-3);
--mantine-color-green-outline-hover: color-mix(in srgb, var(--mantine-color-green-3) 12%, transparent);
--mantine-color-green-text: var(--mantine-color-green-3);
--mantine-color-green-contrast: #f6ecd8;
--mantine-color-yellow-filled: var(--mantine-color-yellow-6);
--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-5);
--mantine-color-yellow-light: color-mix(in srgb, var(--mantine-color-yellow-4) 15%, transparent);
--mantine-color-yellow-light-hover: color-mix(in srgb, var(--mantine-color-yellow-4) 22%, transparent);
--mantine-color-yellow-light-color: var(--mantine-color-yellow-2);
--mantine-color-yellow-outline: var(--mantine-color-yellow-3);
--mantine-color-yellow-outline-hover: color-mix(in srgb, var(--mantine-color-yellow-3) 12%, transparent);
--mantine-color-yellow-text: var(--mantine-color-yellow-3);
--mantine-color-yellow-contrast: #f6ecd8;
--mantine-color-grape-filled: var(--mantine-color-grape-6);
--mantine-color-grape-filled-hover: var(--mantine-color-grape-5);
--mantine-color-grape-light: color-mix(in srgb, var(--mantine-color-grape-4) 15%, transparent);
--mantine-color-grape-light-hover: color-mix(in srgb, var(--mantine-color-grape-4) 22%, transparent);
--mantine-color-grape-light-color: var(--mantine-color-grape-2);
--mantine-color-grape-outline: var(--mantine-color-grape-3);
--mantine-color-grape-outline-hover: color-mix(in srgb, var(--mantine-color-grape-3) 12%, transparent);
--mantine-color-grape-text: var(--mantine-color-grape-3);
--mantine-color-grape-contrast: #f6ecd8;
--mantine-color-orange-filled: var(--mantine-color-orange-6);
--mantine-color-orange-filled-hover: var(--mantine-color-orange-5);
--mantine-color-orange-light: color-mix(in srgb, var(--mantine-color-orange-4) 15%, transparent);
--mantine-color-orange-light-hover: color-mix(in srgb, var(--mantine-color-orange-4) 22%, transparent);
--mantine-color-orange-light-color: var(--mantine-color-orange-2);
--mantine-color-orange-outline: var(--mantine-color-orange-3);
--mantine-color-orange-outline-hover: color-mix(in srgb, var(--mantine-color-orange-3) 12%, transparent);
--mantine-color-orange-text: var(--mantine-color-orange-3);
--mantine-color-orange-contrast: #f6ecd8;
--mantine-color-pink-filled: var(--mantine-color-pink-6);
--mantine-color-pink-filled-hover: var(--mantine-color-pink-5);
--mantine-color-pink-light: color-mix(in srgb, var(--mantine-color-pink-4) 15%, transparent);
--mantine-color-pink-light-hover: color-mix(in srgb, var(--mantine-color-pink-4) 22%, transparent);
--mantine-color-pink-light-color: var(--mantine-color-pink-2);
--mantine-color-pink-outline: var(--mantine-color-pink-3);
--mantine-color-pink-outline-hover: color-mix(in srgb, var(--mantine-color-pink-3) 12%, transparent);
--mantine-color-pink-text: var(--mantine-color-pink-3);
--mantine-color-pink-contrast: #f6ecd8;
--mantine-color-lime-filled: var(--mantine-color-lime-6);
--mantine-color-lime-filled-hover: var(--mantine-color-lime-5);
--mantine-color-lime-light: color-mix(in srgb, var(--mantine-color-lime-4) 15%, transparent);
--mantine-color-lime-light-hover: color-mix(in srgb, var(--mantine-color-lime-4) 22%, transparent);
--mantine-color-lime-light-color: var(--mantine-color-lime-2);
--mantine-color-lime-outline: var(--mantine-color-lime-3);
--mantine-color-lime-outline-hover: color-mix(in srgb, var(--mantine-color-lime-3) 12%, transparent);
--mantine-color-lime-text: var(--mantine-color-lime-3);
--mantine-color-lime-contrast: #f6ecd8;
--mantine-color-gray-light: color-mix(in srgb, var(--mantine-color-gray-5) 12%, transparent);
--mantine-color-gray-light-hover: color-mix(in srgb, var(--mantine-color-gray-5) 18%, transparent);
--mantine-color-gray-light-color: var(--mantine-color-gray-2);
/* Theme-local palette: ember-warm parchment panels in a torchlit hall. */
--quest-frame: #7a6136;
--quest-corner: #c49a5e;
--quest-bronze: #cfa768;
--quest-surface: #2b2216;
--quest-recessed: #171208;
--quest-shell: #1a140c;
--quest-tick: rgba(0, 0, 0, 0.4);
--quest-ribbon-hi: #d8b077;
--quest-ribbon: #c49a5e;
--quest-ribbon-lo: #a97f45;
--quest-ribbon-ink: #241a0c;
--flxn-gold: #d9b36b;
--flxn-silver: #a89f90;
--flxn-bronze: #c08050;
--flxn-toast-success: #a8bd85;
--flxn-toast-error: #dc9a90;
--flxn-podium-1-bg: color-mix(in srgb, #c99527 14%, #2b2216);
--flxn-podium-1-border: color-mix(in srgb, #d9b36b 45%, #3b2f1f);
--flxn-podium-1-label: #d9b36b;
--flxn-podium-2-bg: color-mix(in srgb, #a89f90 9%, #2b2216);
--flxn-podium-2-border: color-mix(in srgb, #a89f90 32%, #3b2f1f);
--flxn-podium-2-label: #b5aea1;
--flxn-podium-3-bg: color-mix(in srgb, #c08050 12%, #2b2216);
--flxn-podium-3-border: color-mix(in srgb, #c08050 42%, #3b2f1f);
--flxn-podium-3-label: #cd8f61;
--flxn-showcase-bg: #2b2216;
--flxn-overlay: rgba(8, 5, 2, 0.6);
--flxn-dotgrid-color: rgba(196, 154, 94, 0.18);
--flxn-pop-shadow: rgba(0, 0, 0, 0.55);
}
/* ---------------------------------------------------------------- *
* 3. Full-page decoration: worn parchment edges / torchlight
* ---------------------------------------------------------------- */
[data-theme="quest"][data-mantine-color-scheme="light"] body::before {
content: "";
position: fixed;
inset: 0;
z-index: 9998;
pointer-events: none;
background:
radial-gradient(140% 100% at 50% 32%, transparent 58%, rgba(122, 91, 42, 0.1) 88%, rgba(96, 69, 28, 0.2) 100%),
radial-gradient(55% 38% at 6% 2%, rgba(122, 91, 42, 0.07), transparent 70%),
radial-gradient(55% 38% at 95% 98%, rgba(122, 91, 42, 0.07), transparent 70%);
}
[data-theme="quest"][data-mantine-color-scheme="dark"] body::before {
content: "";
position: fixed;
inset: 0;
z-index: 9998;
pointer-events: none;
background:
radial-gradient(90% 55% at 50% 0%, rgba(214, 158, 84, 0.07), transparent 70%),
radial-gradient(130% 95% at 50% 45%, transparent 55%, rgba(5, 3, 1, 0.5) 100%);
}
/* ---------------------------------------------------------------- *
* 4. Quest entries: 2px bronze frames + ornate corner brackets
* ---------------------------------------------------------------- */
[data-theme="quest"] .mantine-Paper-root[data-with-border],
[data-theme="quest"] .mantine-Card-root,
[data-theme="quest"] .mantine-Modal-content,
[data-theme="quest"] .flxn-tile,
[data-theme="quest"] .flxn-match-card {
position: relative;
background-color: var(--quest-surface);
border: 2px solid var(--quest-frame);
}
/* One pseudo paints all four L-shaped brackets (2 strips per corner). */
[data-theme="quest"] .mantine-Paper-root[data-with-border]::before,
[data-theme="quest"] .mantine-Card-root::before,
[data-theme="quest"] .mantine-Modal-content::before,
[data-theme="quest"] .flxn-tile::before,
[data-theme="quest"] .flxn-match-card::before {
content: "";
position: absolute;
inset: 3px;
pointer-events: none;
background-image:
linear-gradient(var(--quest-corner), var(--quest-corner)),
linear-gradient(var(--quest-corner), var(--quest-corner)),
linear-gradient(var(--quest-corner), var(--quest-corner)),
linear-gradient(var(--quest-corner), var(--quest-corner)),
linear-gradient(var(--quest-corner), var(--quest-corner)),
linear-gradient(var(--quest-corner), var(--quest-corner)),
linear-gradient(var(--quest-corner), var(--quest-corner)),
linear-gradient(var(--quest-corner), var(--quest-corner));
background-repeat: no-repeat;
background-size:
12px 2px, 2px 12px,
12px 2px, 2px 12px,
12px 2px, 2px 12px,
12px 2px, 2px 12px;
background-position:
left top, left top,
right top, right top,
left bottom, left bottom,
right bottom, right bottom;
}
/* ---------------------------------------------------------------- *
* 5. Component restyles
* ---------------------------------------------------------------- */
/* Headings: Grenze; section headers (h3/h4) ride ribbon banners. */
[data-theme="quest"] .mantine-Title-root {
letter-spacing: 0.01em;
}
[data-theme="quest"] h3.mantine-Title-root,
[data-theme="quest"] h4.mantine-Title-root {
width: fit-content;
max-width: 100%;
padding: 0.12em 1.1em 0.18em;
background-image: linear-gradient(
180deg,
var(--quest-ribbon-hi) 0%,
var(--quest-ribbon) 55%,
var(--quest-ribbon-lo) 100%
);
color: var(--quest-ribbon-ink);
clip-path: polygon(
0 0,
100% 0,
calc(100% - 0.55em) 50%,
100% 100%,
0 100%,
0.55em 50%
);
}
/* Buttons: banner-dye solids in the user's accent; bronze-ruled defaults. */
[data-theme="quest"] .mantine-Button-root {
font-weight: 600;
letter-spacing: 0.02em;
}
[data-theme="quest"] .mantine-Button-root[data-variant="filled"],
[data-theme="quest"] .mantine-ActionIcon-root[data-variant="filled"] {
border: 1px solid color-mix(in srgb, var(--mantine-primary-color-filled) 55%, black);
box-shadow: inset 0 1px 0 rgba(255, 250, 235, 0.2), 0 1px 3px var(--flxn-pop-shadow);
}
[data-theme="quest"] .mantine-Button-root[data-variant="default"],
[data-theme="quest"] .mantine-ActionIcon-root[data-variant="default"] {
border-color: var(--quest-frame);
background-color: var(--quest-surface);
}
/* Inputs: recessed ledger lines; focus follows the accent. */
[data-theme="quest"] .mantine-Input-input {
background-color: var(--quest-recessed);
border-color: var(--quest-frame);
}
[data-theme="quest"] .mantine-PinInput-input {
background-color: var(--quest-recessed);
}
/* Mantine Tabs: illuminated active label under a bronze rule. */
[data-theme="quest"] .mantine-Tabs-tab {
letter-spacing: 0.02em;
}
[data-theme="quest"] .mantine-Tabs-tab[data-active] {
border-color: var(--quest-corner);
color: var(--mantine-primary-color-light-color);
}
/* Nav: active entries follow the accent — wash plus a marginal rule. */
[data-theme="quest"] .mantine-NavLink-root[data-active] {
background-color: var(--mantine-primary-color-light);
color: var(--mantine-primary-color-light-color);
box-shadow: inset 3px 0 0 var(--mantine-primary-color-filled);
}
[data-theme="quest"] .mantine-NavLink-root[data-active]:hover {
background-color: var(--mantine-primary-color-light-hover);
}
/* Shell: the journal's leather binding. */
[data-theme="quest"] .mantine-AppShell-header {
background-color: var(--quest-shell);
border-bottom: 1px solid var(--quest-frame);
}
[data-theme="quest"] .mantine-AppShell-navbar {
background-color: var(--quest-shell);
border-color: var(--quest-frame);
}
/* Segmented control: carved channel with a parchment slider. */
[data-theme="quest"] .mantine-SegmentedControl-root {
background-color: var(--quest-recessed);
border: 1px solid var(--quest-frame);
}
[data-theme="quest"] .mantine-SegmentedControl-indicator {
background-color: var(--quest-surface);
border: 1px solid var(--quest-frame);
box-shadow: 0 1px 3px var(--flxn-pop-shadow);
}
/* Progress: XP bars — framed groove, segment ticks, glossed accent fill. */
[data-theme="quest"] .mantine-Progress-root {
position: relative;
background-color: var(--quest-recessed);
border: 1px solid var(--quest-frame);
box-shadow: inset 0 1px 2px rgba(20, 12, 4, 0.3);
}
[data-theme="quest"] .mantine-Progress-root::after {
content: "";
position: absolute;
inset: 0;
z-index: 1;
pointer-events: none;
background-image: repeating-linear-gradient(
90deg,
transparent 0,
transparent 11px,
var(--quest-tick) 11px,
var(--quest-tick) 12px
);
}
[data-theme="quest"] .mantine-Progress-section {
background-image: linear-gradient(
180deg,
rgba(255, 246, 224, 0.32) 0%,
rgba(255, 246, 224, 0.06) 45%,
rgba(24, 14, 4, 0.14) 100%
);
}
/* Badges: embossed wax/metal seals; filled keeps a legible on-color. */
[data-theme="quest"] .mantine-Badge-root {
font-weight: 700;
letter-spacing: 0.05em;
border: 1px solid rgba(24, 14, 4, 0.25);
box-shadow: inset 0 1px 0 rgba(255, 250, 235, 0.28),
inset 0 -1px 0 rgba(24, 14, 4, 0.18), 0 1px 2px var(--flxn-pop-shadow);
}
[data-theme="quest"] .mantine-Badge-root[data-variant="filled"],
[data-theme="quest"] .mantine-Badge-root:not([data-variant]),
[data-theme="quest"] .mantine-ThemeIcon-root[data-variant="filled"],
[data-theme="quest"] .mantine-ThemeIcon-root:not([data-variant]) {
color: var(--mantine-primary-color-contrast);
}
[data-theme="quest"] .mantine-Checkbox-root {
--checkbox-icon-color: var(--mantine-primary-color-contrast);
}
/* Dividers: bronze rule with a compass-star waypoint. */
[data-theme="quest"] .mantine-Divider-root {
--divider-color: color-mix(in srgb, var(--quest-frame) 60%, transparent);
}
[data-theme="quest"] .mantine-Divider-root[data-orientation="horizontal"]:not([data-with-label]) {
position: relative;
overflow: visible;
}
[data-theme="quest"] .mantine-Divider-root[data-orientation="horizontal"]:not([data-with-label])::before {
content: "\2726"; /* ✦ */
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
padding: 0 0.5em;
background-color: var(--mantine-color-body);
color: var(--quest-bronze);
font-size: 0.7rem;
line-height: 1;
}
[data-theme="quest"] .mantine-Divider-label {
color: var(--quest-bronze);
letter-spacing: 0.08em;
text-transform: uppercase;
font-size: var(--mantine-font-size-xs);
}
/* Overlays: sealed notes. Popovers get a single bronze rule (the corner
brackets are reserved for full entries; Modal-content has them above). */
[data-theme="quest"] .mantine-Popover-dropdown {
background-color: var(--quest-surface);
border: 1px solid var(--quest-frame);
}
[data-theme="quest"] .mantine-Modal-header {
background-color: var(--quest-surface);
}
[data-theme="quest"] .mantine-Tooltip-tooltip {
background-color: #1c160e;
color: #ecdfc6;
border: 1px solid var(--quest-frame);
}
/* Notifications: dispatched scrolls. */
[data-theme="quest"] .mantine-Notification-root {
background-color: var(--quest-surface);
border: 1px solid var(--quest-frame);
box-shadow: var(--mantine-shadow-md);
}
/* Alerts keep a real border for forced-colors resilience. */
[data-theme="quest"] .mantine-Alert-root {
border: 1px solid var(--quest-frame);
}
[data-theme="quest"][data-mantine-color-scheme="light"] .mantine-Skeleton-root::after {
background-color: #e3d3ae;
}
/* ---------------------------------------------------------------- *
* 6. App component hooks
* ---------------------------------------------------------------- */
/* Tab strip: flat chapter tabs resting; docked = binding strip with a
bronze rule. Underline indicator, so accent text on the tab is fine. */
[data-theme="quest"] .flxn-tabs[data-stuck] {
background-color: var(--quest-shell);
border-bottom-color: var(--quest-frame);
}
[data-theme="quest"] .flxn-tab {
color: var(--mantine-color-dimmed);
letter-spacing: 0.02em;
}
[data-theme="quest"] .flxn-tab[data-active] {
color: var(--mantine-primary-color-light-color);
font-weight: 700;
}
[data-theme="quest"] .flxn-tabs-indicator {
border-bottom: 3px solid var(--mantine-primary-color-filled);
padding-inline: 0.5rem;
}
/* Nav grid tiles: quest entries (frame + brackets set in section 4);
glyphs stay FLAT — bronze-inked tint only. */
[data-theme="quest"] .flxn-tile-icon {
color: var(--quest-bronze);
}
/* "See all": wax-seal-tabbed chip — parchment chip, accent seal dot,
accent ink on a neutral backing. */
[data-theme="quest"] .flxn-see-all {
background-color: var(--quest-surface);
border: 1px solid var(--quest-frame);
color: var(--mantine-primary-color-light-color);
font-weight: 600;
}
[data-theme="quest"] .flxn-see-all::before {
content: "";
display: inline-block;
width: 0.6em;
height: 0.6em;
margin-right: 0.45em;
border-radius: 50%;
vertical-align: baseline;
background-image: radial-gradient(
circle at 35% 30%,
color-mix(in srgb, var(--mantine-primary-color-filled) 55%, white) 0%,
var(--mantine-primary-color-filled) 45%,
color-mix(in srgb, var(--mantine-primary-color-filled) 55%, black) 100%
);
box-shadow: 0 1px 1px var(--flxn-pop-shadow);
}
@media (hover: hover) {
[data-theme="quest"] .flxn-see-all:hover {
background-color: var(--mantine-primary-color-light);
text-decoration: none;
}
}
/* Match card + reaction tray: the entry and its reward line share one
frame — same 2px bronze border, tray on deeper parchment. */
[data-theme="quest"] .flxn-reaction-row {
background-color: var(--quest-recessed);
border: 2px solid var(--quest-frame);
}
/* Badge tiles: seal-stamped keepsakes. */
[data-theme="quest"] .flxn-badge-tile {
box-shadow: var(--mantine-shadow-xs);
}
/* Avatars: party-member portrait medallions (ring via --flxn-avatar-*). */
[data-theme="quest"] .flxn-avatar-frame {
box-shadow: 0 1px 2px var(--flxn-pop-shadow);
}
+37
View File
@@ -0,0 +1,37 @@
import type { ThemeManifest } from "./types";
// Themes are auto-discovered: each src/themes/<id>/manifest.ts default-export
// is registered here. Keep this module CSS-free so server fns can import it;
// theme stylesheets are aggregated separately in ./styles.ts.
const modules = import.meta.glob<{ default: ThemeManifest }>("./*/manifest.ts", {
eager: true,
});
export const THEMES: Record<string, ThemeManifest> = {};
for (const [path, mod] of Object.entries(modules)) {
const folder = path.split("/")[1];
const manifest = mod.default;
if (!manifest || manifest.id !== folder) {
console.warn(`[themes] skipping ${path}: manifest id must equal folder name`);
continue;
}
THEMES[manifest.id] = manifest;
}
export const DEFAULT_THEME_ID = "default";
if (!THEMES[DEFAULT_THEME_ID]) {
throw new Error("[themes] default theme manifest is missing or invalid");
}
export const THEME_IDS = Object.keys(THEMES);
// Base order: default first, rest by id. Pickers re-sort by LOCALIZED title
// at render time (labels are i18n descriptors, unresolvable here).
export const THEME_LIST: ThemeManifest[] = Object.values(THEMES).sort(
(a, b) => (a.order ?? 100) - (b.order ?? 100) || a.id.localeCompare(b.id)
);
export const getTheme = (id?: string | null): ThemeManifest =>
(id ? THEMES[id] : undefined) ?? THEMES[DEFAULT_THEME_ID];
+24
View File
@@ -0,0 +1,24 @@
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
const manifest: ThemeManifest = {
id: "seventies",
label: msg`Groovy`,
colorSchemes: "both",
fontLinks: [
"https://fonts.googleapis.com/css2?family=Righteous&display=swap",
"https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap",
],
chromeColors: {
light: { base: "#f6ecd9", dimmed: "#e4d5b8" },
dark: { base: "#241a12", dimmed: "#171009" },
},
mantine: {
// Mustard/avocado fills are mid-luminance; without autoContrast the
// yellow/lime accents render white-on-mustard at ~2-3.5:1.
autoContrast: true,
black: "#3f2d20",
},
};
export default manifest;
+728
View File
@@ -0,0 +1,728 @@
/* ============================================================
seventies — "Groovy": 1974 warm earth-tone lounge.
Cream + chocolate, the classic quad (mustard / burnt orange /
avocado / harvest brown), chunky radii, 2px chocolate borders,
sunburst stripe bands, contour-line arcs on cards.
============================================================ */
/* ---------- 1. Scheme-independent tokens — (0,2,0) ---------- */
[data-theme="seventies"][data-theme] {
/* type */
--mantine-font-family: "Poppins", "Trebuchet MS", "Verdana", ui-sans-serif,
sans-serif;
--mantine-font-family-headings: "Righteous", "Poppins", "Trebuchet MS",
ui-sans-serif, sans-serif;
--mantine-heading-font-weight: 400;
/* chunky rounded shape */
--mantine-radius-xs: 0.375rem;
--mantine-radius-sm: 0.5rem;
--mantine-radius-md: 0.75rem;
--mantine-radius-lg: 1rem;
--mantine-radius-xl: 1.5rem;
--mantine-radius-default: var(--mantine-radius-lg);
/* flat / soft warm shadows */
--mantine-shadow-xs: 0 1px 0 rgba(63, 45, 32, 0.08),
0 2px 6px rgba(63, 45, 32, 0.06);
--mantine-shadow-sm: 0 2px 0 rgba(63, 45, 32, 0.1),
0 4px 12px -2px rgba(63, 45, 32, 0.1);
--mantine-shadow-md: 0 3px 0 rgba(63, 45, 32, 0.1),
0 8px 20px -6px rgba(63, 45, 32, 0.16);
--mantine-shadow-lg: 0 4px 0 rgba(63, 45, 32, 0.1),
0 14px 32px -10px rgba(63, 45, 32, 0.2);
--mantine-shadow-xl: 0 5px 0 rgba(63, 45, 32, 0.12),
0 22px 44px -12px rgba(63, 45, 32, 0.24);
/* sunburst stripe band: mustard / burnt orange / harvest brown / avocado */
--seventies-band: linear-gradient(
to bottom,
#e3a92e 0 25%,
#c9552e 25% 50%,
#7a4a21 50% 75%,
#7a8450 75% 100%
);
/* app hook shape tokens */
--flxn-avatar-radius: 30%;
--flxn-avatar-border: 2px solid var(--mantine-color-default-border);
--flxn-badge-radius: 14px;
/* --- retuned ramps (warm-shifted 1974 palette) --- */
/* yellow → mustard (#e3a92e) */
--mantine-color-yellow-0: #fbf3dc;
--mantine-color-yellow-1: #f5e7bf;
--mantine-color-yellow-2: #efd899;
--mantine-color-yellow-3: #e9c46b;
--mantine-color-yellow-4: #e6b544;
--mantine-color-yellow-5: #e3a92e;
--mantine-color-yellow-6: #cd9322;
--mantine-color-yellow-7: #ad7a1c;
--mantine-color-yellow-8: #8c6216;
--mantine-color-yellow-9: #6b4a11;
/* orange → burnt orange (#c9552e) */
--mantine-color-orange-0: #fbeee6;
--mantine-color-orange-1: #f6dcca;
--mantine-color-orange-2: #efc2a4;
--mantine-color-orange-3: #e5a179;
--mantine-color-orange-4: #d87b50;
--mantine-color-orange-5: #c9552e;
--mantine-color-orange-6: #b64826;
--mantine-color-orange-7: #973b20;
--mantine-color-orange-8: #782f1a;
--mantine-color-orange-9: #5c2414;
/* red → cherry brick (crimson, kept off burnt orange's hue) */
--mantine-color-red-0: #fae9e9;
--mantine-color-red-1: #f2cfcf;
--mantine-color-red-2: #e8abab;
--mantine-color-red-3: #db8282;
--mantine-color-red-4: #ca5b5b;
--mantine-color-red-5: #b83a3a;
--mantine-color-red-6: #a32f2f;
--mantine-color-red-7: #872626;
--mantine-color-red-8: #6b1e1e;
--mantine-color-red-9: #511717;
/* green → avocado (#7a8450) */
--mantine-color-green-0: #f3f4e7;
--mantine-color-green-1: #e5e7cb;
--mantine-color-green-2: #d1d5a9;
--mantine-color-green-3: #b9bf85;
--mantine-color-green-4: #9aa268;
--mantine-color-green-5: #7a8450;
--mantine-color-green-6: #6a7343;
--mantine-color-green-7: #575e36;
--mantine-color-green-8: #444a2a;
--mantine-color-green-9: #32361f;
/* lime → olive chartreuse */
--mantine-color-lime-0: #f6f6e2;
--mantine-color-lime-1: #ecedc2;
--mantine-color-lime-2: #dee09b;
--mantine-color-lime-3: #cbcf72;
--mantine-color-lime-4: #b5bb50;
--mantine-color-lime-5: #9da437;
--mantine-color-lime-6: #888f2c;
--mantine-color-lime-7: #6f7423;
--mantine-color-lime-8: #575b1b;
--mantine-color-lime-9: #404313;
/* teal → lounge teal */
--mantine-color-teal-0: #e9f2ef;
--mantine-color-teal-1: #cfe3dd;
--mantine-color-teal-2: #adcec5;
--mantine-color-teal-3: #86b3a7;
--mantine-color-teal-4: #619789;
--mantine-color-teal-5: #467d6f;
--mantine-color-teal-6: #3a6b5f;
--mantine-color-teal-7: #2f574d;
--mantine-color-teal-8: #24443c;
--mantine-color-teal-9: #1a322c;
/* cyan → dusty aqua */
--mantine-color-cyan-0: #e9f2f2;
--mantine-color-cyan-1: #cfe3e2;
--mantine-color-cyan-2: #accccb;
--mantine-color-cyan-3: #85b0af;
--mantine-color-cyan-4: #629392;
--mantine-color-cyan-5: #4a7c7a;
--mantine-color-cyan-6: #3e6a69;
--mantine-color-cyan-7: #325655;
--mantine-color-cyan-8: #274343;
--mantine-color-cyan-9: #1c3131;
/* blue → petrol denim */
--mantine-color-blue-0: #eaf0f3;
--mantine-color-blue-1: #d1dee4;
--mantine-color-blue-2: #b0c5cf;
--mantine-color-blue-3: #89a7b5;
--mantine-color-blue-4: #648b9c;
--mantine-color-blue-5: #4b7386;
--mantine-color-blue-6: #3f6274;
--mantine-color-blue-7: #33505f;
--mantine-color-blue-8: #283e4a;
--mantine-color-blue-9: #1d2d36;
/* indigo → deep denim */
--mantine-color-indigo-0: #eaeef4;
--mantine-color-indigo-1: #d3dbe6;
--mantine-color-indigo-2: #b4c1d3;
--mantine-color-indigo-3: #8fa2bb;
--mantine-color-indigo-4: #6d85a3;
--mantine-color-indigo-5: #546d8c;
--mantine-color-indigo-6: #475d79;
--mantine-color-indigo-7: #3a4c63;
--mantine-color-indigo-8: #2d3b4d;
--mantine-color-indigo-9: #212b38;
/* violet → plum */
--mantine-color-violet-0: #f1edf2;
--mantine-color-violet-1: #e0d7e2;
--mantine-color-violet-2: #c9bacc;
--mantine-color-violet-3: #ad97b1;
--mantine-color-violet-4: #917797;
--mantine-color-violet-5: #7a5f80;
--mantine-color-violet-6: #695070;
--mantine-color-violet-7: #56415c;
--mantine-color-violet-8: #443348;
--mantine-color-violet-9: #322535;
/* grape → aubergine */
--mantine-color-grape-0: #f4ecf1;
--mantine-color-grape-1: #e6d4df;
--mantine-color-grape-2: #d2b3c6;
--mantine-color-grape-3: #b98da7;
--mantine-color-grape-4: #a06a8b;
--mantine-color-grape-5: #8b5175;
--mantine-color-grape-6: #794464;
--mantine-color-grape-7: #633751;
--mantine-color-grape-8: #4e2b40;
--mantine-color-grape-9: #3a202f;
/* pink → dusty rose (rosier than red so the pair stays distinct) */
--mantine-color-pink-0: #f9edf0;
--mantine-color-pink-1: #f1d5dc;
--mantine-color-pink-2: #e6b3c0;
--mantine-color-pink-3: #d78da0;
--mantine-color-pink-4: #c66781;
--mantine-color-pink-5: #b24a66;
--mantine-color-pink-6: #9e3e59;
--mantine-color-pink-7: #83334a;
--mantine-color-pink-8: #682839;
--mantine-color-pink-9: #4e1e2b;
/* gray → warm taupe */
--mantine-color-gray-0: #faf4e6;
--mantine-color-gray-1: #f1e6cf;
--mantine-color-gray-2: #e4d5b8;
--mantine-color-gray-3: #d0bd9c;
--mantine-color-gray-4: #b5a083;
--mantine-color-gray-5: #97826a;
--mantine-color-gray-6: #7a6753;
--mantine-color-gray-7: #5f4e3e;
--mantine-color-gray-8: #47392c;
--mantine-color-gray-9: #30251b;
/* dark → espresso */
--mantine-color-dark-0: #efe3d3;
--mantine-color-dark-1: #d9c6ad;
--mantine-color-dark-2: #b39a7d;
--mantine-color-dark-3: #8a7258;
--mantine-color-dark-4: #5c4732;
--mantine-color-dark-5: #4a3826;
--mantine-color-dark-6: #33261a;
--mantine-color-dark-7: #241a12;
--mantine-color-dark-8: #1b130d;
--mantine-color-dark-9: #120c08;
}
/* ---------- 2. Per-scheme tokens — (0,3,0) ---------- */
[data-theme="seventies"][data-theme][data-mantine-color-scheme="light"] {
--mantine-color-body: #f6ecd9;
--mantine-color-text: #3f2d20;
--mantine-color-bright: #241a12;
--mantine-color-dimmed: #7a6753;
--mantine-color-placeholder: #97826a;
--mantine-color-anchor: var(--mantine-primary-color-filled);
--mantine-color-error: var(--mantine-color-red-6);
--mantine-color-default: #fdf7ea;
--mantine-color-default-hover: #f3e6c9;
--mantine-color-default-color: #3f2d20;
--mantine-color-default-border: #3f2d20;
--seventies-ink: #3f2d20;
--seventies-surface: #fdf7ea;
--seventies-tray: #f0e2c3;
--seventies-contour: rgba(63, 45, 32, 0.07);
/* alpha tints re-derived from the retuned ramps */
--mantine-color-yellow-light: color-mix(in srgb, var(--mantine-color-yellow-6) 12%, transparent);
--mantine-color-yellow-light-hover: color-mix(in srgb, var(--mantine-color-yellow-6) 16%, transparent);
--mantine-color-orange-light: color-mix(in srgb, var(--mantine-color-orange-6) 10%, transparent);
--mantine-color-orange-light-hover: color-mix(in srgb, var(--mantine-color-orange-6) 14%, transparent);
--mantine-color-red-light: color-mix(in srgb, var(--mantine-color-red-6) 10%, transparent);
--mantine-color-red-light-hover: color-mix(in srgb, var(--mantine-color-red-6) 14%, transparent);
--mantine-color-green-light: color-mix(in srgb, var(--mantine-color-green-6) 11%, transparent);
--mantine-color-green-light-hover: color-mix(in srgb, var(--mantine-color-green-6) 15%, transparent);
--mantine-color-lime-light: color-mix(in srgb, var(--mantine-color-lime-6) 11%, transparent);
--mantine-color-lime-light-hover: color-mix(in srgb, var(--mantine-color-lime-6) 15%, transparent);
--mantine-color-teal-light: color-mix(in srgb, var(--mantine-color-teal-6) 10%, transparent);
--mantine-color-teal-light-hover: color-mix(in srgb, var(--mantine-color-teal-6) 14%, transparent);
--mantine-color-cyan-light: color-mix(in srgb, var(--mantine-color-cyan-6) 10%, transparent);
--mantine-color-cyan-light-hover: color-mix(in srgb, var(--mantine-color-cyan-6) 14%, transparent);
--mantine-color-blue-light: color-mix(in srgb, var(--mantine-color-blue-6) 10%, transparent);
--mantine-color-blue-light-hover: color-mix(in srgb, var(--mantine-color-blue-6) 14%, transparent);
--mantine-color-indigo-light: color-mix(in srgb, var(--mantine-color-indigo-6) 10%, transparent);
--mantine-color-indigo-light-hover: color-mix(in srgb, var(--mantine-color-indigo-6) 14%, transparent);
--mantine-color-violet-light: color-mix(in srgb, var(--mantine-color-violet-6) 10%, transparent);
--mantine-color-violet-light-hover: color-mix(in srgb, var(--mantine-color-violet-6) 14%, transparent);
--mantine-color-grape-light: color-mix(in srgb, var(--mantine-color-grape-6) 10%, transparent);
--mantine-color-grape-light-hover: color-mix(in srgb, var(--mantine-color-grape-6) 14%, transparent);
--mantine-color-pink-light: color-mix(in srgb, var(--mantine-color-pink-6) 10%, transparent);
--mantine-color-pink-light-hover: color-mix(in srgb, var(--mantine-color-pink-6) 14%, transparent);
--mantine-color-gray-light: color-mix(in srgb, var(--mantine-color-gray-6) 10%, transparent);
--mantine-color-gray-light-hover: color-mix(in srgb, var(--mantine-color-gray-6) 14%, transparent);
/* outline-hover tints for the pickable accents (Mantine's are stock-hue rgba) */
--mantine-color-blue-outline-hover: color-mix(in srgb, var(--mantine-color-blue-6) 6%, transparent);
--mantine-color-red-outline-hover: color-mix(in srgb, var(--mantine-color-red-6) 6%, transparent);
--mantine-color-green-outline-hover: color-mix(in srgb, var(--mantine-color-green-6) 6%, transparent);
--mantine-color-yellow-outline-hover: color-mix(in srgb, var(--mantine-color-yellow-6) 6%, transparent);
--mantine-color-grape-outline-hover: color-mix(in srgb, var(--mantine-color-grape-6) 6%, transparent);
--mantine-color-orange-outline-hover: color-mix(in srgb, var(--mantine-color-orange-6) 6%, transparent);
--mantine-color-pink-outline-hover: color-mix(in srgb, var(--mantine-color-pink-6) 6%, transparent);
--mantine-color-lime-outline-hover: color-mix(in srgb, var(--mantine-color-lime-6) 6%, transparent);
/* app tokens */
--flxn-gold: #d3931b;
--flxn-silver: #a08a70;
--flxn-bronze: #b05c33;
--flxn-toast-success: var(--mantine-color-green-6);
--flxn-toast-error: var(--mantine-color-red-6);
--flxn-showcase-bg: #fdf7ea;
--flxn-overlay: rgba(36, 26, 18, 0.45);
--flxn-dotgrid-color: rgba(63, 45, 32, 0.22);
--flxn-pop-shadow: rgba(63, 45, 32, 0.35);
/* wood-paneled podium tiers */
--flxn-podium-1-bg: repeating-linear-gradient(
90deg,
rgba(63, 45, 32, 0.06) 0 2px,
transparent 2px 12px
),
linear-gradient(180deg, #f2dca6, #e9c878);
--flxn-podium-1-border: #b07c1c;
--flxn-podium-1-label: #6b4a11;
--flxn-podium-2-bg: repeating-linear-gradient(
90deg,
rgba(63, 45, 32, 0.06) 0 2px,
transparent 2px 12px
),
linear-gradient(180deg, #eee0c8, #e0cba6);
--flxn-podium-2-border: #97826a;
--flxn-podium-2-label: #5f4e3e;
--flxn-podium-3-bg: repeating-linear-gradient(
90deg,
rgba(63, 45, 32, 0.06) 0 2px,
transparent 2px 12px
),
linear-gradient(180deg, #f2cdb4, #e6b18c);
--flxn-podium-3-border: #a2542f;
--flxn-podium-3-label: #782f1a;
}
[data-theme="seventies"][data-theme][data-mantine-color-scheme="dark"] {
--mantine-color-body: #241a12;
--mantine-color-text: #f2e6d0;
--mantine-color-bright: #fdf6e4;
--mantine-color-dimmed: #b39a7d;
--mantine-color-placeholder: #8a7258;
--mantine-color-anchor: var(--mantine-primary-color-light-color);
--mantine-color-error: var(--mantine-color-red-3);
--mantine-color-default: #33261a;
--mantine-color-default-hover: #40301f;
--mantine-color-default-color: #f2e6d0;
--mantine-color-default-border: #5f4832;
--seventies-ink: #f2e6d0;
--seventies-surface: #2c2015;
--seventies-tray: #362818;
--seventies-contour: rgba(242, 230, 208, 0.06);
/* filled variants stay mid-ramp (default shade 8 vanishes on espresso);
primary aliases follow via Mantine's var indirection, so the user's
accent picks these up automatically */
--mantine-color-yellow-filled: var(--mantine-color-yellow-5);
--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-4);
--mantine-color-orange-filled: var(--mantine-color-orange-6);
--mantine-color-orange-filled-hover: var(--mantine-color-orange-5);
--mantine-color-red-filled: var(--mantine-color-red-6);
--mantine-color-red-filled-hover: var(--mantine-color-red-5);
--mantine-color-green-filled: var(--mantine-color-green-6);
--mantine-color-green-filled-hover: var(--mantine-color-green-5);
--mantine-color-lime-filled: var(--mantine-color-lime-6);
--mantine-color-lime-filled-hover: var(--mantine-color-lime-5);
--mantine-color-teal-filled: var(--mantine-color-teal-6);
--mantine-color-teal-filled-hover: var(--mantine-color-teal-5);
--mantine-color-cyan-filled: var(--mantine-color-cyan-6);
--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-5);
--mantine-color-blue-filled: var(--mantine-color-blue-6);
--mantine-color-blue-filled-hover: var(--mantine-color-blue-5);
--mantine-color-indigo-filled: var(--mantine-color-indigo-6);
--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-5);
--mantine-color-violet-filled: var(--mantine-color-violet-6);
--mantine-color-violet-filled-hover: var(--mantine-color-violet-5);
--mantine-color-grape-filled: var(--mantine-color-grape-6);
--mantine-color-grape-filled-hover: var(--mantine-color-grape-5);
--mantine-color-pink-filled: var(--mantine-color-pink-6);
--mantine-color-pink-filled-hover: var(--mantine-color-pink-5);
/* alpha tints + tint text colors */
--mantine-color-yellow-light: color-mix(in srgb, var(--mantine-color-yellow-4) 16%, transparent);
--mantine-color-yellow-light-hover: color-mix(in srgb, var(--mantine-color-yellow-4) 22%, transparent);
--mantine-color-yellow-light-color: var(--mantine-color-yellow-3);
--mantine-color-orange-light: color-mix(in srgb, var(--mantine-color-orange-4) 16%, transparent);
--mantine-color-orange-light-hover: color-mix(in srgb, var(--mantine-color-orange-4) 22%, transparent);
--mantine-color-orange-light-color: var(--mantine-color-orange-3);
--mantine-color-red-light: color-mix(in srgb, var(--mantine-color-red-4) 16%, transparent);
--mantine-color-red-light-hover: color-mix(in srgb, var(--mantine-color-red-4) 22%, transparent);
--mantine-color-red-light-color: var(--mantine-color-red-3);
--mantine-color-green-light: color-mix(in srgb, var(--mantine-color-green-4) 16%, transparent);
--mantine-color-green-light-hover: color-mix(in srgb, var(--mantine-color-green-4) 22%, transparent);
--mantine-color-green-light-color: var(--mantine-color-green-3);
--mantine-color-lime-light: color-mix(in srgb, var(--mantine-color-lime-4) 16%, transparent);
--mantine-color-lime-light-hover: color-mix(in srgb, var(--mantine-color-lime-4) 22%, transparent);
--mantine-color-lime-light-color: var(--mantine-color-lime-3);
--mantine-color-teal-light: color-mix(in srgb, var(--mantine-color-teal-4) 16%, transparent);
--mantine-color-teal-light-hover: color-mix(in srgb, var(--mantine-color-teal-4) 22%, transparent);
--mantine-color-teal-light-color: var(--mantine-color-teal-3);
--mantine-color-cyan-light: color-mix(in srgb, var(--mantine-color-cyan-4) 16%, transparent);
--mantine-color-cyan-light-hover: color-mix(in srgb, var(--mantine-color-cyan-4) 22%, transparent);
--mantine-color-cyan-light-color: var(--mantine-color-cyan-3);
--mantine-color-blue-light: color-mix(in srgb, var(--mantine-color-blue-4) 16%, transparent);
--mantine-color-blue-light-hover: color-mix(in srgb, var(--mantine-color-blue-4) 22%, transparent);
--mantine-color-blue-light-color: var(--mantine-color-blue-3);
--mantine-color-indigo-light: color-mix(in srgb, var(--mantine-color-indigo-4) 16%, transparent);
--mantine-color-indigo-light-hover: color-mix(in srgb, var(--mantine-color-indigo-4) 22%, transparent);
--mantine-color-indigo-light-color: var(--mantine-color-indigo-3);
--mantine-color-violet-light: color-mix(in srgb, var(--mantine-color-violet-4) 16%, transparent);
--mantine-color-violet-light-hover: color-mix(in srgb, var(--mantine-color-violet-4) 22%, transparent);
--mantine-color-violet-light-color: var(--mantine-color-violet-3);
--mantine-color-grape-light: color-mix(in srgb, var(--mantine-color-grape-4) 16%, transparent);
--mantine-color-grape-light-hover: color-mix(in srgb, var(--mantine-color-grape-4) 22%, transparent);
--mantine-color-grape-light-color: var(--mantine-color-grape-3);
--mantine-color-pink-light: color-mix(in srgb, var(--mantine-color-pink-4) 16%, transparent);
--mantine-color-pink-light-hover: color-mix(in srgb, var(--mantine-color-pink-4) 22%, transparent);
--mantine-color-pink-light-color: var(--mantine-color-pink-3);
--mantine-color-gray-light: color-mix(in srgb, var(--mantine-color-gray-4) 14%, transparent);
--mantine-color-gray-light-hover: color-mix(in srgb, var(--mantine-color-gray-4) 20%, transparent);
--mantine-color-gray-light-color: var(--mantine-color-gray-2);
/* outline-hover tints for the pickable accents (Mantine's are stock-hue rgba) */
--mantine-color-blue-outline-hover: color-mix(in srgb, var(--mantine-color-blue-4) 10%, transparent);
--mantine-color-red-outline-hover: color-mix(in srgb, var(--mantine-color-red-4) 10%, transparent);
--mantine-color-green-outline-hover: color-mix(in srgb, var(--mantine-color-green-4) 10%, transparent);
--mantine-color-yellow-outline-hover: color-mix(in srgb, var(--mantine-color-yellow-4) 10%, transparent);
--mantine-color-grape-outline-hover: color-mix(in srgb, var(--mantine-color-grape-4) 10%, transparent);
--mantine-color-orange-outline-hover: color-mix(in srgb, var(--mantine-color-orange-4) 10%, transparent);
--mantine-color-pink-outline-hover: color-mix(in srgb, var(--mantine-color-pink-4) 10%, transparent);
--mantine-color-lime-outline-hover: color-mix(in srgb, var(--mantine-color-lime-4) 10%, transparent);
/* app tokens */
--flxn-gold: #e6b544;
--flxn-silver: #b5a083;
--flxn-bronze: #d87b50;
--flxn-toast-success: var(--mantine-color-green-3);
--flxn-toast-error: var(--mantine-color-red-3);
--flxn-showcase-bg: #2c2015;
--flxn-overlay: rgba(18, 12, 8, 0.6);
--flxn-dotgrid-color: rgba(242, 230, 208, 0.14);
--flxn-pop-shadow: rgba(0, 0, 0, 0.5);
--flxn-podium-1-bg: repeating-linear-gradient(
90deg,
rgba(0, 0, 0, 0.18) 0 2px,
transparent 2px 12px
),
linear-gradient(180deg, #4a3a1c, #3a2d15);
--flxn-podium-1-border: #a37a20;
--flxn-podium-1-label: #e6b544;
--flxn-podium-2-bg: repeating-linear-gradient(
90deg,
rgba(0, 0, 0, 0.18) 0 2px,
transparent 2px 12px
),
linear-gradient(180deg, #3d3123, #322718);
--flxn-podium-2-border: #6b5138;
--flxn-podium-2-label: #cbb391;
--flxn-podium-3-bg: repeating-linear-gradient(
90deg,
rgba(0, 0, 0, 0.18) 0 2px,
transparent 2px 12px
),
linear-gradient(180deg, #4a2c1c, #3a2115);
--flxn-podium-3-border: #8a4a28;
--flxn-podium-3-label: #e09a72;
}
/* ---------- 3. Component restyles ---------- */
/* app shell: warm surfaces, sunburst band under the header */
[data-theme="seventies"] .mantine-AppShell-header {
background-color: var(--seventies-surface);
border-bottom: none;
}
[data-theme="seventies"] .mantine-AppShell-header::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 5px;
background: var(--seventies-band);
pointer-events: none;
}
[data-theme="seventies"] .mantine-AppShell-navbar {
background-color: var(--seventies-surface);
border-inline-end: 2px solid var(--mantine-color-default-border);
}
/* surfaces: chunky 2px chocolate borders (covers cards, sheets, the
floating mobile nav pill, modal content — all Paper-based) */
[data-theme="seventies"] .mantine-Paper-root {
background-color: var(--seventies-surface);
border: 2px solid var(--mantine-color-default-border);
}
/* retro contour-line arcs in card corners */
[data-theme="seventies"] .mantine-Card-root {
position: relative;
}
[data-theme="seventies"] .mantine-Card-root::after {
content: "";
position: absolute;
top: 0;
right: 0;
width: 7rem;
height: 7rem;
pointer-events: none;
border-top-right-radius: calc(var(--mantine-radius-default) - 2px);
background: repeating-radial-gradient(
circle at 100% 0%,
var(--seventies-contour) 0 2px,
transparent 2px 14px
);
-webkit-mask-image: radial-gradient(circle at 100% 0%, #000 30%, transparent 72%);
mask-image: radial-gradient(circle at 100% 0%, #000 30%, transparent 72%);
}
/* buttons: solid warm fills with a darker chunky bottom edge */
[data-theme="seventies"] .mantine-Button-root {
font-weight: 600;
letter-spacing: 0.02em;
transition: box-shadow 120ms cubic-bezier(0.32, 0.72, 0, 1);
}
[data-theme="seventies"] .mantine-Button-root[data-variant="filled"],
[data-theme="seventies"] .mantine-Button-root[data-variant="light"],
[data-theme="seventies"] .mantine-Button-root[data-variant="default"] {
box-shadow: 0 3px 0 color-mix(in srgb, var(--button-bg) 60%, #120c08);
}
[data-theme="seventies"] .mantine-Button-root[data-variant="default"],
[data-theme="seventies"] .mantine-Button-root[data-variant="outline"] {
border-width: 2px;
}
[data-theme="seventies"] .mantine-Button-root[data-variant="filled"]:active:not(:disabled):not([data-disabled]):not([data-loading]),
[data-theme="seventies"] .mantine-Button-root[data-variant="light"]:active:not(:disabled):not([data-disabled]):not([data-loading]),
[data-theme="seventies"] .mantine-Button-root[data-variant="default"]:active:not(:disabled):not([data-disabled]):not([data-loading]) {
box-shadow: 0 1px 0 color-mix(in srgb, var(--button-bg) 60%, #120c08);
}
[data-theme="seventies"] .mantine-ActionIcon-root[data-variant="filled"],
[data-theme="seventies"] .mantine-ActionIcon-root[data-variant="light"],
[data-theme="seventies"] .mantine-ActionIcon-root[data-variant="default"] {
box-shadow: 0 2px 0 color-mix(in srgb, var(--ai-bg) 60%, #120c08);
}
[data-theme="seventies"] .mantine-ActionIcon-root[data-variant="filled"]:active:not(:disabled):not([data-disabled]):not([data-loading]),
[data-theme="seventies"] .mantine-ActionIcon-root[data-variant="light"]:active:not(:disabled):not([data-disabled]):not([data-loading]),
[data-theme="seventies"] .mantine-ActionIcon-root[data-variant="default"]:active:not(:disabled):not([data-disabled]):not([data-loading]) {
box-shadow: 0 0 0 transparent;
}
/* press feedback: sink instead of scale */
@media (prefers-reduced-motion: no-preference) {
[data-theme="seventies"] .flxn-press:active:not(:disabled):not([data-disabled]):not([data-loading]) {
transform: translateY(2px);
}
}
/* inputs */
[data-theme="seventies"] .mantine-Input-input {
border-width: 2px;
background-color: var(--seventies-surface);
font-weight: 500;
}
/* badges as retro patches: bold border + inner stitch line */
[data-theme="seventies"] .mantine-Badge-root {
position: relative;
border: 2px solid var(--mantine-color-default-border);
border-radius: var(--mantine-radius-sm);
font-weight: 700;
letter-spacing: 0.05em;
}
[data-theme="seventies"] .mantine-Badge-root::after {
content: "";
position: absolute;
inset: 2px;
border: 1px dashed color-mix(in srgb, currentColor 55%, transparent);
border-radius: calc(var(--mantine-radius-sm) - 3px);
pointer-events: none;
}
/* tabs + segmented control */
[data-theme="seventies"] .mantine-Tabs-tab {
font-weight: 600;
}
[data-theme="seventies"] .mantine-Tabs-tab[data-active] {
border-color: var(--mantine-primary-color-filled);
}
[data-theme="seventies"] .mantine-SegmentedControl-root {
background-color: color-mix(in srgb, var(--seventies-ink) 8%, var(--mantine-color-body));
border: 2px solid var(--mantine-color-default-border);
}
[data-theme="seventies"] .mantine-SegmentedControl-indicator {
background-color: light-dark(#e9c46b, #8c6216);
box-shadow: 0 2px 0 rgba(18, 12, 8, 0.25);
}
/* overlays & floating surfaces */
[data-theme="seventies"] .mantine-Modal-header {
background-color: var(--seventies-surface);
position: relative;
}
[data-theme="seventies"] .mantine-Modal-header::after {
content: "";
position: absolute;
left: var(--mantine-spacing-md);
right: var(--mantine-spacing-md);
bottom: 0;
height: 4px;
border-radius: 2px;
background: var(--seventies-band);
pointer-events: none;
}
[data-theme="seventies"] .mantine-Popover-dropdown {
background-color: var(--seventies-surface);
border: 2px solid var(--mantine-color-default-border);
box-shadow: var(--mantine-shadow-md);
}
[data-theme="seventies"] .mantine-Tooltip-tooltip {
background-color: #33261a;
color: #f2e6d0;
border-radius: var(--mantine-radius-sm);
}
[data-theme="seventies"] .mantine-Notification-root {
background-color: var(--seventies-surface);
border: 2px solid var(--mantine-color-default-border);
box-shadow: var(--mantine-shadow-md);
}
[data-theme="seventies"] .mantine-Alert-root {
border: 2px solid var(--mantine-color-default-border);
}
/* controls */
[data-theme="seventies"] .mantine-Switch-track {
border: 2px solid var(--mantine-color-default-border);
}
[data-theme="seventies"] .mantine-Checkbox-input {
border: 2px solid var(--mantine-color-default-border);
}
[data-theme="seventies"] .mantine-Progress-root {
background-color: color-mix(in srgb, var(--seventies-ink) 12%, transparent);
}
/* ---------- 4. App component hooks ---------- */
/* tab strip: docks as a cream strip closed off by a chocolate rule */
[data-theme="seventies"] .flxn-tabs[data-stuck] {
background-color: var(--seventies-surface);
border-bottom: 2px solid var(--mantine-color-default-border);
}
[data-theme="seventies"] .flxn-tab {
font-weight: 600;
color: var(--mantine-color-dimmed);
}
[data-theme="seventies"] .flxn-tab[data-active] {
color: light-dark(
var(--mantine-primary-color-filled),
var(--mantine-primary-color-light-color)
);
font-weight: 700;
}
[data-theme="seventies"] .flxn-tabs-indicator {
border-bottom: 3px solid var(--mantine-primary-color-filled);
border-radius: 3px;
}
/* nav tiles: chunky bordered cards with a chocolate ledge; sink on press */
[data-theme="seventies"] .flxn-tile {
background-color: var(--seventies-surface);
border: 2px solid var(--mantine-color-default-border);
box-shadow: 0 3px 0 var(--mantine-color-default-border);
transition: box-shadow 120ms cubic-bezier(0.32, 0.72, 0, 1);
}
[data-theme="seventies"] .flxn-tile:active {
box-shadow: 0 1px 0 var(--mantine-color-default-border);
}
[data-theme="seventies"] .flxn-tile-icon {
color: light-dark(
var(--mantine-primary-color-filled),
var(--mantine-primary-color-light-color)
);
}
/* "see all" as a sew-on patch chip: light accent wash + wash text */
[data-theme="seventies"] .flxn-see-all {
position: relative;
border: 2px solid var(--mantine-color-default-border);
border-radius: var(--mantine-radius-sm);
background-color: var(--mantine-primary-color-light);
color: var(--mantine-primary-color-light-color);
font-weight: 700;
letter-spacing: 0.04em;
}
[data-theme="seventies"] .flxn-see-all::after {
content: "";
position: absolute;
inset: 2px;
border: 1px dashed color-mix(in srgb, currentColor 55%, transparent);
border-radius: calc(var(--mantine-radius-sm) - 3px);
pointer-events: none;
}
/* match card + reaction tray: one construction — matching chocolate
borders, tray reads as a wood-panel shelf under the card */
[data-theme="seventies"] .flxn-match-card {
background-color: var(--seventies-surface);
border: 2px solid var(--mantine-color-default-border);
}
[data-theme="seventies"] .flxn-reaction-row {
background: repeating-linear-gradient(
90deg,
color-mix(in srgb, var(--seventies-ink) 7%, transparent) 0 2px,
transparent 2px 12px
),
var(--seventies-tray);
border: 2px solid var(--mantine-color-default-border);
border-radius: var(--mantine-radius-md);
}
+17
View File
@@ -0,0 +1,17 @@
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
const manifest: ThemeManifest = {
id: "sketch",
label: msg`Sketchbook`,
colorSchemes: "both",
fontLinks: [
"https://fonts.googleapis.com/css2?family=Caveat:wght@500;600;700&family=Patrick+Hand&display=swap",
],
chromeColors: {
light: { base: "#f9f4e8", dimmed: "#e9e1cf" },
dark: { base: "#232a26", dimmed: "#1a201d" },
},
};
export default manifest;
+749
View File
@@ -0,0 +1,749 @@
/* Sketchbook — hand-drawn ink on warm paper (light) / chalk on slate (dark).
The look is carried by 2px "pen" borders with asymmetric wobbly radii
(varied per component so each element reads as individually drawn), a faint
feTurbulence grain overlay, and watercolor washes derived from the user's
accent via color-mix. No SVG displacement filters (perf). */
/* ---------------------------------------------------------------- */
/* 1. Scheme-independent tokens */
/* ---------------------------------------------------------------- */
[data-theme="sketch"][data-theme] {
--mantine-font-family: "Patrick Hand", "Segoe Print", "Comic Sans MS", cursive;
--mantine-font-family-headings: "Caveat", "Patrick Hand", cursive;
--mantine-heading-font-weight: 600;
/* Caveat runs small for its em box — headings get larger sizes */
--mantine-h1-font-size: 2.75rem;
--mantine-h1-line-height: 1.15;
--mantine-h2-font-size: 2.25rem;
--mantine-h2-line-height: 1.18;
--mantine-h3-font-size: 1.85rem;
--mantine-h3-line-height: 1.2;
--mantine-h4-font-size: 1.55rem;
--mantine-h4-line-height: 1.25;
--mantine-h5-font-size: 1.3rem;
--mantine-h5-line-height: 1.3;
--mantine-h6-font-size: 1.15rem;
--mantine-h6-line-height: 1.35;
--mantine-radius-xs: 6px;
--mantine-radius-sm: 8px;
--mantine-radius-md: 10px;
--mantine-radius-lg: 14px;
--mantine-radius-xl: 20px;
--mantine-radius-default: 10px;
--flxn-avatar-radius: 42%; /* hand-drawn "circle" */
--flxn-avatar-border: 2px solid var(--sk-ink); /* single pen ring on the frame */
--flxn-badge-radius: 14px;
/* pencil-smudge shadows: soft, gray, slightly offset */
--mantine-shadow-xs: 0 1px 2px var(--sk-smudge);
--mantine-shadow-sm: 1px 2px 5px var(--sk-smudge);
--mantine-shadow-md: 2px 3px 9px var(--sk-smudge);
--mantine-shadow-lg: 3px 5px 14px var(--sk-smudge);
--mantine-shadow-xl: 4px 7px 20px var(--sk-smudge);
}
/* ---------------------------------------------------------------- */
/* 2a. Light — ink on warm paper */
/* ---------------------------------------------------------------- */
[data-theme="sketch"][data-theme][data-mantine-color-scheme="light"] {
--sk-ink: #333333;
--sk-ink-soft: rgba(51, 51, 51, 0.55);
--sk-on-fill: #2f2c26;
--sk-paper: #f9f4e8;
--sk-paper-raised: #fffdf6;
--sk-wash: color-mix(in srgb, var(--mantine-primary-color-6) 18%, #fffdf6);
--sk-accent-ink: color-mix(in srgb, var(--mantine-primary-color-6) 65%, #333333);
--sk-smudge: rgba(88, 80, 62, 0.2);
--sk-hatch: rgba(51, 51, 51, 0.22);
--sk-skeleton: #ebe3d0;
--sk-squiggle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='5' viewBox='0 0 22 5'%3E%3Cpath d='M0 3 Q 5.5 0.8 11 3 T 22 3' fill='none' stroke='%23333333' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
--mantine-color-body: #f9f4e8;
--mantine-color-text: #333333;
--mantine-color-bright: #1f1d18;
--mantine-color-dimmed: #6b655a;
--mantine-color-placeholder: #8a8375;
--mantine-color-anchor: var(--sk-accent-ink);
--mantine-color-error: #a44e46;
--mantine-color-default: #fffdf6;
--mantine-color-default-hover: #f1ead9;
--mantine-color-default-color: #333333;
--mantine-color-default-border: #333333;
--mantine-primary-color-contrast: #2f2c26;
/* app tokens */
--flxn-gold: #dca815; /* crayon yellow */
--flxn-silver: #8f8a80; /* pencil gray */
--flxn-bronze: #b0744a; /* crayon sienna */
--flxn-toast-success: #5f8f5f;
--flxn-toast-error: #c26e63;
--flxn-podium-1-bg: #f8eecb;
--flxn-podium-1-border: #8c7a35;
--flxn-podium-1-label: #7c6614;
--flxn-podium-2-bg: #f0ede4;
--flxn-podium-2-border: #77746c;
--flxn-podium-2-label: #5f5c54;
--flxn-podium-3-bg: #f4e5d3;
--flxn-podium-3-border: #8f6a45;
--flxn-podium-3-label: #82552e;
--flxn-showcase-bg: #fffdf6;
--flxn-overlay: rgba(56, 50, 38, 0.45);
--flxn-dotgrid-color: rgba(51, 51, 51, 0.3); /* pencil dots */
--flxn-pop-shadow: rgba(88, 80, 62, 0.3);
}
/* ---------------------------------------------------------------- */
/* 2b. Dark — chalk on slate */
/* ---------------------------------------------------------------- */
[data-theme="sketch"][data-theme][data-mantine-color-scheme="dark"] {
--sk-ink: #dfdcd0; /* chalk */
--sk-ink-soft: rgba(223, 220, 208, 0.5);
--sk-on-fill: #232a26;
--sk-paper: #232a26;
--sk-paper-raised: #2a322d;
--sk-wash: color-mix(in srgb, var(--mantine-primary-color-4) 22%, #2a322d);
--sk-accent-ink: color-mix(in srgb, var(--mantine-primary-color-3) 70%, #dfdcd0);
--sk-smudge: rgba(0, 0, 0, 0.45);
--sk-hatch: rgba(35, 42, 38, 0.4);
--sk-skeleton: #38423b;
--sk-squiggle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='5' viewBox='0 0 22 5'%3E%3Cpath d='M0 3 Q 5.5 0.8 11 3 T 22 3' fill='none' stroke='%23dfdcd0' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
--mantine-color-body: #232a26;
--mantine-color-text: #e8e5da;
--mantine-color-bright: #f6f4ec;
--mantine-color-dimmed: #a3ac9f;
--mantine-color-placeholder: #8b948a;
--mantine-color-anchor: var(--sk-accent-ink);
--mantine-color-error: #e8a198;
--mantine-color-default: #2a322d;
--mantine-color-default-hover: #333c36;
--mantine-color-default-color: #e8e5da;
--mantine-color-default-border: #d7d4c6;
--mantine-primary-color-contrast: #232a26;
/* app tokens — dusty chalk pastels */
--flxn-gold: #ecd27a;
--flxn-silver: #b9beb3;
--flxn-bronze: #d9a06b;
--flxn-toast-success: #b3d7ae;
--flxn-toast-error: #eaa79b;
--flxn-podium-1-bg: color-mix(in srgb, #ecd27a 12%, #2a322d);
--flxn-podium-1-border: #cdb96b;
--flxn-podium-1-label: #e9d78f;
--flxn-podium-2-bg: color-mix(in srgb, #b9beb3 10%, #2a322d);
--flxn-podium-2-border: #a4a99e;
--flxn-podium-2-label: #c9cec2;
--flxn-podium-3-bg: color-mix(in srgb, #d9a06b 12%, #2a322d);
--flxn-podium-3-border: #c08f5c;
--flxn-podium-3-label: #e0b384;
--flxn-showcase-bg: #2a322d;
--flxn-overlay: rgba(6, 9, 8, 0.6);
--flxn-dotgrid-color: rgba(223, 220, 208, 0.22); /* chalk dots */
--flxn-pop-shadow: rgba(0, 0, 0, 0.55);
}
/* ---------------------------------------------------------------- */
/* 3. Paper grain / chalk dust — one fixed overlay, feTurbulence */
/* ---------------------------------------------------------------- */
[data-theme="sketch"] body::before {
content: "";
position: fixed;
inset: 0;
z-index: 9998;
pointer-events: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23g)'/%3E%3C/svg%3E");
background-repeat: repeat;
mix-blend-mode: multiply;
opacity: 0.05;
}
[data-theme="sketch"][data-mantine-color-scheme="dark"] body::before {
mix-blend-mode: screen;
opacity: 0.07;
}
/* ---------------------------------------------------------------- */
/* 4. Surfaces — every card its own doodle */
/* ---------------------------------------------------------------- */
[data-theme="sketch"] .mantine-Paper-root {
background-color: var(--sk-paper-raised);
border: 2px solid var(--sk-ink);
border-radius: 255px 15px 225px 15px / 15px 225px 15px 255px;
box-shadow: none;
}
[data-theme="sketch"] .mantine-Card-root {
border-radius: 15px 225px 15px 255px / 225px 15px 255px 15px;
}
[data-theme="sketch"] .mantine-Modal-content {
border-radius: 18px 225px 22px 255px / 255px 22px 225px 18px;
box-shadow: var(--mantine-shadow-xl);
}
[data-theme="sketch"] .mantine-Modal-header {
background-color: var(--sk-paper-raised);
}
[data-theme="sketch"] .mantine-Drawer-content {
border-radius: 20px 235px 16px 245px / 245px 16px 235px 20px;
box-shadow: var(--mantine-shadow-xl);
}
[data-theme="sketch"] .mantine-Popover-dropdown,
[data-theme="sketch"] .mantine-Menu-dropdown {
background-color: var(--sk-paper-raised);
border: 2px solid var(--sk-ink);
border-radius: 255px 25px 225px 22px / 22px 225px 25px 255px;
box-shadow: var(--mantine-shadow-md);
}
[data-theme="sketch"] .mantine-Menu-item {
border-radius: 215px 14px 195px 16px / 16px 195px 14px 215px;
}
[data-theme="sketch"] .mantine-Notification-root {
background-color: var(--sk-paper-raised);
border: 2px solid var(--sk-ink);
border-radius: 15px 245px 18px 235px / 235px 18px 245px 15px;
box-shadow: var(--mantine-shadow-md);
}
[data-theme="sketch"] .mantine-Alert-root {
border: 2px solid var(--sk-ink-soft);
border-radius: 20px 240px 16px 250px / 250px 16px 240px 20px;
}
[data-theme="sketch"] .mantine-Tooltip-tooltip {
background-color: var(--sk-ink);
color: var(--sk-paper);
border-radius: 205px 12px 225px 14px / 14px 225px 12px 205px;
}
/* ---------------------------------------------------------------- */
/* 5. Shell */
/* ---------------------------------------------------------------- */
[data-theme="sketch"] .mantine-AppShell-header {
border-bottom: 2px solid var(--sk-ink);
}
[data-theme="sketch"] .mantine-AppShell-navbar {
border-inline-end: 2px solid var(--sk-ink);
}
[data-theme="sketch"] .mantine-AppShell-aside {
border-inline-start: 2px solid var(--sk-ink);
}
[data-theme="sketch"] .mantine-Divider-root {
--divider-color: var(--sk-ink-soft);
--divider-size: 2px;
}
[data-theme="sketch"] .mantine-Divider-root[data-orientation="horizontal"] {
border-top-style: dashed;
}
[data-theme="sketch"] .mantine-Divider-root[data-orientation="vertical"] {
border-inline-start-style: dashed;
}
/* ---------------------------------------------------------------- */
/* 6. Buttons — sketched boxes */
/* ---------------------------------------------------------------- */
[data-theme="sketch"] .mantine-Button-root {
border-radius: 225px 20px 255px 15px / 15px 255px 20px 225px;
box-shadow: none;
font-weight: 400; /* Patrick Hand ships 400 only; avoid faux bold */
}
[data-theme="sketch"] .mantine-Button-root:where([data-variant="filled"], [data-variant="default"], [data-variant="outline"], [data-variant="light"], [data-variant="white"]) {
border: 2px solid var(--sk-ink);
}
[data-theme="sketch"] .mantine-ActionIcon-root {
border-radius: 160px 25px 185px 28px / 28px 185px 25px 160px;
box-shadow: none;
}
[data-theme="sketch"] .mantine-ActionIcon-root:where([data-variant="filled"], [data-variant="default"], [data-variant="outline"], [data-variant="light"], [data-variant="white"]) {
border: 2px solid var(--sk-ink);
}
[data-theme="sketch"] .mantine-Button-root[data-variant="filled"],
[data-theme="sketch"] .mantine-ActionIcon-root[data-variant="filled"],
[data-theme="sketch"] .mantine-Badge-root[data-variant="filled"],
[data-theme="sketch"] .mantine-ThemeIcon-root[data-variant="filled"] {
color: var(--sk-on-fill); /* ink on watercolor wash, chalkboard on pastel */
}
/* press feedback: a slight crooked stamp */
@media (prefers-reduced-motion: no-preference) {
[data-theme="sketch"] .flxn-press:active:not(:disabled):not([data-disabled]):not([data-loading]) {
transform: scale(0.96) rotate(-0.7deg);
}
}
/* ---------------------------------------------------------------- */
/* 7. Inputs */
/* ---------------------------------------------------------------- */
[data-theme="sketch"] .mantine-Input-input {
background-color: var(--sk-paper-raised);
border: 2px solid var(--sk-ink);
border-radius: 12px 255px 18px 225px / 225px 18px 255px 12px;
}
[data-theme="sketch"] .mantine-Input-input:focus,
[data-theme="sketch"] .mantine-Input-input:focus-within {
border-color: var(--sk-accent-ink);
}
[data-theme="sketch"] .mantine-InputWrapper-label {
font-family: var(--mantine-font-family-headings);
font-size: 1.15rem;
}
[data-theme="sketch"] .mantine-PinInput-input {
border-radius: 190px 16px 210px 14px / 14px 210px 16px 190px;
}
[data-theme="sketch"] .mantine-Checkbox-input {
background-color: var(--sk-paper-raised);
border: 2px solid var(--sk-ink);
border-radius: 155px 8px 145px 10px / 10px 145px 8px 155px;
}
[data-theme="sketch"] .mantine-Checkbox-icon {
color: var(--sk-on-fill);
}
[data-theme="sketch"] .mantine-Radio-radio {
background-color: var(--sk-paper-raised);
border: 2px solid var(--sk-ink);
border-radius: 48% 52% 51% 49% / 52% 48% 52% 48%;
}
[data-theme="sketch"] .mantine-Radio-icon {
color: var(--sk-on-fill);
}
[data-theme="sketch"] .mantine-Switch-track {
background-color: var(--sk-paper-raised);
border: 2px solid var(--sk-ink);
border-radius: 120px 40px 110px 45px / 45px 110px 40px 120px;
}
[data-theme="sketch"] .mantine-Switch-thumb {
background-color: var(--sk-paper-raised);
border: 2px solid var(--sk-ink);
border-radius: 50% 46% 54% 48% / 48% 54% 46% 52%;
}
/* ---------------------------------------------------------------- */
/* 8. Tabs — scribbled underline on the active tab */
/* ---------------------------------------------------------------- */
[data-theme="sketch"] .mantine-Tabs-tab {
border-radius: 200px 12px 190px 14px / 14px 190px 12px 200px;
font-weight: 400;
}
[data-theme="sketch"] .mantine-Tabs-tab[data-active] {
border-color: transparent;
background-image: var(--sk-squiggle);
background-repeat: repeat-x;
background-position: left bottom 1px;
background-size: 22px 5px;
color: var(--mantine-color-text);
}
[data-theme="sketch"] .mantine-SegmentedControl-root {
background-color: transparent;
border: 2px solid var(--sk-ink);
border-radius: 225px 18px 255px 15px / 15px 255px 18px 225px;
}
[data-theme="sketch"] .mantine-SegmentedControl-indicator {
background-color: var(--sk-wash);
border: 2px solid var(--sk-ink-soft);
border-radius: 205px 15px 235px 12px / 12px 235px 15px 205px;
box-shadow: none;
}
[data-theme="sketch"] .mantine-NavLink-root {
border: 2px solid transparent;
border-radius: 235px 18px 215px 20px / 20px 215px 18px 235px;
}
[data-theme="sketch"] .mantine-NavLink-root[data-active] {
border-color: var(--sk-ink-soft);
}
/* ---------------------------------------------------------------- */
/* 9. Small pieces */
/* ---------------------------------------------------------------- */
[data-theme="sketch"] .mantine-Badge-root {
border: 2px solid var(--sk-ink);
border-radius: 255px 10px 225px 12px / 12px 225px 10px 255px;
font-weight: 400;
letter-spacing: 0.02em;
}
[data-theme="sketch"] .mantine-ThemeIcon-root {
border: 2px solid var(--sk-ink);
border-radius: 170px 30px 150px 35px / 35px 150px 30px 170px;
}
[data-theme="sketch"] .mantine-Indicator-indicator {
border: 1.5px solid var(--sk-ink);
border-radius: 50% 45% 55% 48% / 52% 50% 46% 54%;
}
[data-theme="sketch"] .mantine-Skeleton-root {
border-radius: 225px 20px 245px 18px / 18px 245px 20px 225px;
}
[data-theme="sketch"] .mantine-Skeleton-root::after {
background-color: var(--sk-skeleton);
}
[data-theme="sketch"] .mantine-Loader-root {
--loader-color: var(--sk-accent-ink);
}
/* progress bar: wobbly pill with pencil cross-hatch fill */
[data-theme="sketch"] .mantine-Progress-root {
background-color: var(--sk-paper-raised);
border: 2px solid var(--sk-ink);
border-radius: 100px 30px 90px 35px / 35px 90px 30px 100px;
}
[data-theme="sketch"] .mantine-Progress-section {
background-image: repeating-linear-gradient(45deg, transparent 0 4px, var(--sk-hatch) 4px 6px);
}
[data-theme="sketch"] .mantine-Progress-label {
color: var(--sk-on-fill);
}
/* ---------------------------------------------------------------- */
/* 9b. App component hooks */
/* ---------------------------------------------------------------- */
/* Tab strip: flat on the page when resting; the raised paper strip with
its pencil rule appears only while docked. Resting keeps the 2px dashed
border transparent so docking doesn't shift layout. */
[data-theme="sketch"] .flxn-tabs {
border-bottom: 2px dashed transparent;
}
[data-theme="sketch"] .flxn-tabs[data-stuck] {
background-color: var(--sk-paper-raised);
border-bottom-color: var(--sk-ink-soft);
}
[data-theme="sketch"] .flxn-tab.flxn-tab {
position: relative;
border-radius: 200px 12px 190px 14px / 14px 190px 12px 200px;
}
/* active tab: accent watercolor wash + accent scribble underline */
[data-theme="sketch"] .flxn-tab.flxn-tab[data-active] {
background-color: var(--sk-wash);
color: var(--sk-accent-ink);
font-weight: 400; /* Patrick Hand has no bold */
}
[data-theme="sketch"] .flxn-tab.flxn-tab[data-active]::after {
content: "";
position: absolute;
inset-inline: 0.6rem;
bottom: 2px;
height: 5px;
background-color: var(--sk-accent-ink);
-webkit-mask-image: var(--sk-squiggle);
mask-image: var(--sk-squiggle);
-webkit-mask-repeat: repeat-x;
mask-repeat: repeat-x;
-webkit-mask-size: 22px 5px;
mask-size: 22px 5px;
}
/* the tab treatment carries the state — drop the straight underline */
[data-theme="sketch"] .flxn-tabs-indicator.flxn-tabs-indicator {
border-bottom: none;
}
/* Nav grid tiles: sketched boxes, each drawn slightly differently */
[data-theme="sketch"] .flxn-tile {
background-color: var(--sk-paper-raised);
border: 2px solid var(--sk-ink);
border-radius: 255px 15px 225px 18px / 18px 225px 15px 255px;
box-shadow: none;
}
[data-theme="sketch"] .flxn-tile:nth-child(even) {
border-radius: 15px 235px 18px 245px / 235px 15px 245px 18px;
}
[data-theme="sketch"] .flxn-tile:nth-child(3n) {
border-radius: 225px 22px 245px 15px / 15px 245px 22px 225px;
}
[data-theme="sketch"] .flxn-tile-icon {
color: var(--sk-accent-ink);
}
/* "See all" chip: hand-drawn pill; wash bg/hover come from the retuned
primary variant vars in hooks.css */
[data-theme="sketch"] .flxn-see-all.flxn-see-all {
border: 2px solid var(--sk-ink);
border-radius: 255px 12px 225px 14px / 14px 225px 12px 255px;
font-weight: 400;
}
/* Match card + reaction tray: one construction, matching 2px pen border */
[data-theme="sketch"] .flxn-match-card.flxn-match-card {
background-color: var(--sk-paper-raised);
border: 2px solid var(--sk-ink);
border-radius: 18px 235px 16px 245px / 235px 18px 245px 16px;
box-shadow: none;
}
[data-theme="sketch"] .flxn-reaction-row.flxn-reaction-row {
background-color: var(--sk-paper);
border: 2px solid var(--sk-ink);
border-radius: 15px 225px 18px 235px / 225px 15px 235px 18px;
}
/* ---------------------------------------------------------------- */
/* 10. Watercolor washes (light) / dusty chalk pastels (dark) */
/* Every palette color's variant vars are re-derived from its own */
/* numbered shades, so the user's accent (and any colored variant) */
/* shows through as a low-saturation wash instead of a solid fill. */
/* ---------------------------------------------------------------- */
[data-theme="sketch"][data-theme][data-mantine-color-scheme="light"] {
--mantine-color-red-filled: color-mix(in srgb, var(--mantine-color-red-6) 45%, #fffdf6);
--mantine-color-red-filled-hover: color-mix(in srgb, var(--mantine-color-red-6) 58%, #fffdf6);
--mantine-color-red-light: color-mix(in srgb, var(--mantine-color-red-6) 15%, #fffdf6);
--mantine-color-red-light-hover: color-mix(in srgb, var(--mantine-color-red-6) 23%, #fffdf6);
--mantine-color-red-light-color: color-mix(in srgb, var(--mantine-color-red-9) 72%, #333333);
--mantine-color-red-outline: color-mix(in srgb, var(--mantine-color-red-7) 65%, #333333);
--mantine-color-red-outline-hover: color-mix(in srgb, var(--mantine-color-red-6) 15%, #fffdf6);
--mantine-color-red-text: color-mix(in srgb, var(--mantine-color-red-8) 75%, #333333);
--mantine-color-pink-filled: color-mix(in srgb, var(--mantine-color-pink-6) 45%, #fffdf6);
--mantine-color-pink-filled-hover: color-mix(in srgb, var(--mantine-color-pink-6) 58%, #fffdf6);
--mantine-color-pink-light: color-mix(in srgb, var(--mantine-color-pink-6) 15%, #fffdf6);
--mantine-color-pink-light-hover: color-mix(in srgb, var(--mantine-color-pink-6) 23%, #fffdf6);
--mantine-color-pink-light-color: color-mix(in srgb, var(--mantine-color-pink-9) 72%, #333333);
--mantine-color-pink-outline: color-mix(in srgb, var(--mantine-color-pink-7) 65%, #333333);
--mantine-color-pink-outline-hover: color-mix(in srgb, var(--mantine-color-pink-6) 15%, #fffdf6);
--mantine-color-pink-text: color-mix(in srgb, var(--mantine-color-pink-8) 75%, #333333);
--mantine-color-grape-filled: color-mix(in srgb, var(--mantine-color-grape-6) 45%, #fffdf6);
--mantine-color-grape-filled-hover: color-mix(in srgb, var(--mantine-color-grape-6) 58%, #fffdf6);
--mantine-color-grape-light: color-mix(in srgb, var(--mantine-color-grape-6) 15%, #fffdf6);
--mantine-color-grape-light-hover: color-mix(in srgb, var(--mantine-color-grape-6) 23%, #fffdf6);
--mantine-color-grape-light-color: color-mix(in srgb, var(--mantine-color-grape-9) 72%, #333333);
--mantine-color-grape-outline: color-mix(in srgb, var(--mantine-color-grape-7) 65%, #333333);
--mantine-color-grape-outline-hover: color-mix(in srgb, var(--mantine-color-grape-6) 15%, #fffdf6);
--mantine-color-grape-text: color-mix(in srgb, var(--mantine-color-grape-8) 75%, #333333);
--mantine-color-violet-filled: color-mix(in srgb, var(--mantine-color-violet-6) 45%, #fffdf6);
--mantine-color-violet-filled-hover: color-mix(in srgb, var(--mantine-color-violet-6) 58%, #fffdf6);
--mantine-color-violet-light: color-mix(in srgb, var(--mantine-color-violet-6) 15%, #fffdf6);
--mantine-color-violet-light-hover: color-mix(in srgb, var(--mantine-color-violet-6) 23%, #fffdf6);
--mantine-color-violet-light-color: color-mix(in srgb, var(--mantine-color-violet-9) 72%, #333333);
--mantine-color-violet-outline: color-mix(in srgb, var(--mantine-color-violet-7) 65%, #333333);
--mantine-color-violet-outline-hover: color-mix(in srgb, var(--mantine-color-violet-6) 15%, #fffdf6);
--mantine-color-violet-text: color-mix(in srgb, var(--mantine-color-violet-8) 75%, #333333);
--mantine-color-indigo-filled: color-mix(in srgb, var(--mantine-color-indigo-6) 45%, #fffdf6);
--mantine-color-indigo-filled-hover: color-mix(in srgb, var(--mantine-color-indigo-6) 58%, #fffdf6);
--mantine-color-indigo-light: color-mix(in srgb, var(--mantine-color-indigo-6) 15%, #fffdf6);
--mantine-color-indigo-light-hover: color-mix(in srgb, var(--mantine-color-indigo-6) 23%, #fffdf6);
--mantine-color-indigo-light-color: color-mix(in srgb, var(--mantine-color-indigo-9) 72%, #333333);
--mantine-color-indigo-outline: color-mix(in srgb, var(--mantine-color-indigo-7) 65%, #333333);
--mantine-color-indigo-outline-hover: color-mix(in srgb, var(--mantine-color-indigo-6) 15%, #fffdf6);
--mantine-color-indigo-text: color-mix(in srgb, var(--mantine-color-indigo-8) 75%, #333333);
--mantine-color-blue-filled: color-mix(in srgb, var(--mantine-color-blue-6) 45%, #fffdf6);
--mantine-color-blue-filled-hover: color-mix(in srgb, var(--mantine-color-blue-6) 58%, #fffdf6);
--mantine-color-blue-light: color-mix(in srgb, var(--mantine-color-blue-6) 15%, #fffdf6);
--mantine-color-blue-light-hover: color-mix(in srgb, var(--mantine-color-blue-6) 23%, #fffdf6);
--mantine-color-blue-light-color: color-mix(in srgb, var(--mantine-color-blue-9) 72%, #333333);
--mantine-color-blue-outline: color-mix(in srgb, var(--mantine-color-blue-7) 65%, #333333);
--mantine-color-blue-outline-hover: color-mix(in srgb, var(--mantine-color-blue-6) 15%, #fffdf6);
--mantine-color-blue-text: color-mix(in srgb, var(--mantine-color-blue-8) 75%, #333333);
--mantine-color-cyan-filled: color-mix(in srgb, var(--mantine-color-cyan-6) 45%, #fffdf6);
--mantine-color-cyan-filled-hover: color-mix(in srgb, var(--mantine-color-cyan-6) 58%, #fffdf6);
--mantine-color-cyan-light: color-mix(in srgb, var(--mantine-color-cyan-6) 15%, #fffdf6);
--mantine-color-cyan-light-hover: color-mix(in srgb, var(--mantine-color-cyan-6) 23%, #fffdf6);
--mantine-color-cyan-light-color: color-mix(in srgb, var(--mantine-color-cyan-9) 72%, #333333);
--mantine-color-cyan-outline: color-mix(in srgb, var(--mantine-color-cyan-7) 65%, #333333);
--mantine-color-cyan-outline-hover: color-mix(in srgb, var(--mantine-color-cyan-6) 15%, #fffdf6);
--mantine-color-cyan-text: color-mix(in srgb, var(--mantine-color-cyan-8) 75%, #333333);
--mantine-color-teal-filled: color-mix(in srgb, var(--mantine-color-teal-6) 45%, #fffdf6);
--mantine-color-teal-filled-hover: color-mix(in srgb, var(--mantine-color-teal-6) 58%, #fffdf6);
--mantine-color-teal-light: color-mix(in srgb, var(--mantine-color-teal-6) 15%, #fffdf6);
--mantine-color-teal-light-hover: color-mix(in srgb, var(--mantine-color-teal-6) 23%, #fffdf6);
--mantine-color-teal-light-color: color-mix(in srgb, var(--mantine-color-teal-9) 72%, #333333);
--mantine-color-teal-outline: color-mix(in srgb, var(--mantine-color-teal-7) 65%, #333333);
--mantine-color-teal-outline-hover: color-mix(in srgb, var(--mantine-color-teal-6) 15%, #fffdf6);
--mantine-color-teal-text: color-mix(in srgb, var(--mantine-color-teal-8) 75%, #333333);
--mantine-color-green-filled: color-mix(in srgb, var(--mantine-color-green-6) 45%, #fffdf6);
--mantine-color-green-filled-hover: color-mix(in srgb, var(--mantine-color-green-6) 58%, #fffdf6);
--mantine-color-green-light: color-mix(in srgb, var(--mantine-color-green-6) 15%, #fffdf6);
--mantine-color-green-light-hover: color-mix(in srgb, var(--mantine-color-green-6) 23%, #fffdf6);
--mantine-color-green-light-color: color-mix(in srgb, var(--mantine-color-green-9) 72%, #333333);
--mantine-color-green-outline: color-mix(in srgb, var(--mantine-color-green-7) 65%, #333333);
--mantine-color-green-outline-hover: color-mix(in srgb, var(--mantine-color-green-6) 15%, #fffdf6);
--mantine-color-green-text: color-mix(in srgb, var(--mantine-color-green-8) 75%, #333333);
--mantine-color-lime-filled: color-mix(in srgb, var(--mantine-color-lime-6) 45%, #fffdf6);
--mantine-color-lime-filled-hover: color-mix(in srgb, var(--mantine-color-lime-6) 58%, #fffdf6);
--mantine-color-lime-light: color-mix(in srgb, var(--mantine-color-lime-6) 15%, #fffdf6);
--mantine-color-lime-light-hover: color-mix(in srgb, var(--mantine-color-lime-6) 23%, #fffdf6);
--mantine-color-lime-light-color: color-mix(in srgb, var(--mantine-color-lime-9) 72%, #333333);
--mantine-color-lime-outline: color-mix(in srgb, var(--mantine-color-lime-7) 65%, #333333);
--mantine-color-lime-outline-hover: color-mix(in srgb, var(--mantine-color-lime-6) 15%, #fffdf6);
--mantine-color-lime-text: color-mix(in srgb, var(--mantine-color-lime-8) 75%, #333333);
--mantine-color-yellow-filled: color-mix(in srgb, var(--mantine-color-yellow-6) 45%, #fffdf6);
--mantine-color-yellow-filled-hover: color-mix(in srgb, var(--mantine-color-yellow-6) 58%, #fffdf6);
--mantine-color-yellow-light: color-mix(in srgb, var(--mantine-color-yellow-6) 15%, #fffdf6);
--mantine-color-yellow-light-hover: color-mix(in srgb, var(--mantine-color-yellow-6) 23%, #fffdf6);
--mantine-color-yellow-light-color: color-mix(in srgb, var(--mantine-color-yellow-9) 72%, #333333);
--mantine-color-yellow-outline: color-mix(in srgb, var(--mantine-color-yellow-7) 65%, #333333);
--mantine-color-yellow-outline-hover: color-mix(in srgb, var(--mantine-color-yellow-6) 15%, #fffdf6);
--mantine-color-yellow-text: color-mix(in srgb, var(--mantine-color-yellow-8) 75%, #333333);
--mantine-color-orange-filled: color-mix(in srgb, var(--mantine-color-orange-6) 45%, #fffdf6);
--mantine-color-orange-filled-hover: color-mix(in srgb, var(--mantine-color-orange-6) 58%, #fffdf6);
--mantine-color-orange-light: color-mix(in srgb, var(--mantine-color-orange-6) 15%, #fffdf6);
--mantine-color-orange-light-hover: color-mix(in srgb, var(--mantine-color-orange-6) 23%, #fffdf6);
--mantine-color-orange-light-color: color-mix(in srgb, var(--mantine-color-orange-9) 72%, #333333);
--mantine-color-orange-outline: color-mix(in srgb, var(--mantine-color-orange-7) 65%, #333333);
--mantine-color-orange-outline-hover: color-mix(in srgb, var(--mantine-color-orange-6) 15%, #fffdf6);
--mantine-color-orange-text: color-mix(in srgb, var(--mantine-color-orange-8) 75%, #333333);
--mantine-color-gray-filled: color-mix(in srgb, var(--mantine-color-gray-6) 45%, #fffdf6);
--mantine-color-gray-filled-hover: color-mix(in srgb, var(--mantine-color-gray-6) 58%, #fffdf6);
--mantine-color-gray-light: color-mix(in srgb, var(--mantine-color-gray-6) 15%, #fffdf6);
--mantine-color-gray-light-hover: color-mix(in srgb, var(--mantine-color-gray-6) 23%, #fffdf6);
--mantine-color-gray-light-color: color-mix(in srgb, var(--mantine-color-gray-9) 72%, #333333);
--mantine-color-gray-outline: color-mix(in srgb, var(--mantine-color-gray-7) 65%, #333333);
--mantine-color-gray-outline-hover: color-mix(in srgb, var(--mantine-color-gray-6) 15%, #fffdf6);
--mantine-color-gray-text: color-mix(in srgb, var(--mantine-color-gray-8) 75%, #333333);
}
[data-theme="sketch"][data-theme][data-mantine-color-scheme="dark"] {
--mantine-color-red-filled: color-mix(in srgb, var(--mantine-color-red-3) 62%, #d9d5c7);
--mantine-color-red-filled-hover: color-mix(in srgb, var(--mantine-color-red-3) 76%, #d9d5c7);
--mantine-color-red-light: color-mix(in srgb, var(--mantine-color-red-4) 20%, #2a322d);
--mantine-color-red-light-hover: color-mix(in srgb, var(--mantine-color-red-4) 30%, #2a322d);
--mantine-color-red-light-color: color-mix(in srgb, var(--mantine-color-red-2) 78%, #e8e5da);
--mantine-color-red-outline: color-mix(in srgb, var(--mantine-color-red-2) 80%, #dfdcd0);
--mantine-color-red-outline-hover: color-mix(in srgb, var(--mantine-color-red-4) 22%, #2a322d);
--mantine-color-red-text: color-mix(in srgb, var(--mantine-color-red-2) 80%, #e8e5da);
--mantine-color-pink-filled: color-mix(in srgb, var(--mantine-color-pink-3) 62%, #d9d5c7);
--mantine-color-pink-filled-hover: color-mix(in srgb, var(--mantine-color-pink-3) 76%, #d9d5c7);
--mantine-color-pink-light: color-mix(in srgb, var(--mantine-color-pink-4) 20%, #2a322d);
--mantine-color-pink-light-hover: color-mix(in srgb, var(--mantine-color-pink-4) 30%, #2a322d);
--mantine-color-pink-light-color: color-mix(in srgb, var(--mantine-color-pink-2) 78%, #e8e5da);
--mantine-color-pink-outline: color-mix(in srgb, var(--mantine-color-pink-2) 80%, #dfdcd0);
--mantine-color-pink-outline-hover: color-mix(in srgb, var(--mantine-color-pink-4) 22%, #2a322d);
--mantine-color-pink-text: color-mix(in srgb, var(--mantine-color-pink-2) 80%, #e8e5da);
--mantine-color-grape-filled: color-mix(in srgb, var(--mantine-color-grape-3) 62%, #d9d5c7);
--mantine-color-grape-filled-hover: color-mix(in srgb, var(--mantine-color-grape-3) 76%, #d9d5c7);
--mantine-color-grape-light: color-mix(in srgb, var(--mantine-color-grape-4) 20%, #2a322d);
--mantine-color-grape-light-hover: color-mix(in srgb, var(--mantine-color-grape-4) 30%, #2a322d);
--mantine-color-grape-light-color: color-mix(in srgb, var(--mantine-color-grape-2) 78%, #e8e5da);
--mantine-color-grape-outline: color-mix(in srgb, var(--mantine-color-grape-2) 80%, #dfdcd0);
--mantine-color-grape-outline-hover: color-mix(in srgb, var(--mantine-color-grape-4) 22%, #2a322d);
--mantine-color-grape-text: color-mix(in srgb, var(--mantine-color-grape-2) 80%, #e8e5da);
--mantine-color-violet-filled: color-mix(in srgb, var(--mantine-color-violet-3) 62%, #d9d5c7);
--mantine-color-violet-filled-hover: color-mix(in srgb, var(--mantine-color-violet-3) 76%, #d9d5c7);
--mantine-color-violet-light: color-mix(in srgb, var(--mantine-color-violet-4) 20%, #2a322d);
--mantine-color-violet-light-hover: color-mix(in srgb, var(--mantine-color-violet-4) 30%, #2a322d);
--mantine-color-violet-light-color: color-mix(in srgb, var(--mantine-color-violet-2) 78%, #e8e5da);
--mantine-color-violet-outline: color-mix(in srgb, var(--mantine-color-violet-2) 80%, #dfdcd0);
--mantine-color-violet-outline-hover: color-mix(in srgb, var(--mantine-color-violet-4) 22%, #2a322d);
--mantine-color-violet-text: color-mix(in srgb, var(--mantine-color-violet-2) 80%, #e8e5da);
--mantine-color-indigo-filled: color-mix(in srgb, var(--mantine-color-indigo-3) 62%, #d9d5c7);
--mantine-color-indigo-filled-hover: color-mix(in srgb, var(--mantine-color-indigo-3) 76%, #d9d5c7);
--mantine-color-indigo-light: color-mix(in srgb, var(--mantine-color-indigo-4) 20%, #2a322d);
--mantine-color-indigo-light-hover: color-mix(in srgb, var(--mantine-color-indigo-4) 30%, #2a322d);
--mantine-color-indigo-light-color: color-mix(in srgb, var(--mantine-color-indigo-2) 78%, #e8e5da);
--mantine-color-indigo-outline: color-mix(in srgb, var(--mantine-color-indigo-2) 80%, #dfdcd0);
--mantine-color-indigo-outline-hover: color-mix(in srgb, var(--mantine-color-indigo-4) 22%, #2a322d);
--mantine-color-indigo-text: color-mix(in srgb, var(--mantine-color-indigo-2) 80%, #e8e5da);
--mantine-color-blue-filled: color-mix(in srgb, var(--mantine-color-blue-3) 62%, #d9d5c7);
--mantine-color-blue-filled-hover: color-mix(in srgb, var(--mantine-color-blue-3) 76%, #d9d5c7);
--mantine-color-blue-light: color-mix(in srgb, var(--mantine-color-blue-4) 20%, #2a322d);
--mantine-color-blue-light-hover: color-mix(in srgb, var(--mantine-color-blue-4) 30%, #2a322d);
--mantine-color-blue-light-color: color-mix(in srgb, var(--mantine-color-blue-2) 78%, #e8e5da);
--mantine-color-blue-outline: color-mix(in srgb, var(--mantine-color-blue-2) 80%, #dfdcd0);
--mantine-color-blue-outline-hover: color-mix(in srgb, var(--mantine-color-blue-4) 22%, #2a322d);
--mantine-color-blue-text: color-mix(in srgb, var(--mantine-color-blue-2) 80%, #e8e5da);
--mantine-color-cyan-filled: color-mix(in srgb, var(--mantine-color-cyan-3) 62%, #d9d5c7);
--mantine-color-cyan-filled-hover: color-mix(in srgb, var(--mantine-color-cyan-3) 76%, #d9d5c7);
--mantine-color-cyan-light: color-mix(in srgb, var(--mantine-color-cyan-4) 20%, #2a322d);
--mantine-color-cyan-light-hover: color-mix(in srgb, var(--mantine-color-cyan-4) 30%, #2a322d);
--mantine-color-cyan-light-color: color-mix(in srgb, var(--mantine-color-cyan-2) 78%, #e8e5da);
--mantine-color-cyan-outline: color-mix(in srgb, var(--mantine-color-cyan-2) 80%, #dfdcd0);
--mantine-color-cyan-outline-hover: color-mix(in srgb, var(--mantine-color-cyan-4) 22%, #2a322d);
--mantine-color-cyan-text: color-mix(in srgb, var(--mantine-color-cyan-2) 80%, #e8e5da);
--mantine-color-teal-filled: color-mix(in srgb, var(--mantine-color-teal-3) 62%, #d9d5c7);
--mantine-color-teal-filled-hover: color-mix(in srgb, var(--mantine-color-teal-3) 76%, #d9d5c7);
--mantine-color-teal-light: color-mix(in srgb, var(--mantine-color-teal-4) 20%, #2a322d);
--mantine-color-teal-light-hover: color-mix(in srgb, var(--mantine-color-teal-4) 30%, #2a322d);
--mantine-color-teal-light-color: color-mix(in srgb, var(--mantine-color-teal-2) 78%, #e8e5da);
--mantine-color-teal-outline: color-mix(in srgb, var(--mantine-color-teal-2) 80%, #dfdcd0);
--mantine-color-teal-outline-hover: color-mix(in srgb, var(--mantine-color-teal-4) 22%, #2a322d);
--mantine-color-teal-text: color-mix(in srgb, var(--mantine-color-teal-2) 80%, #e8e5da);
--mantine-color-green-filled: color-mix(in srgb, var(--mantine-color-green-3) 62%, #d9d5c7);
--mantine-color-green-filled-hover: color-mix(in srgb, var(--mantine-color-green-3) 76%, #d9d5c7);
--mantine-color-green-light: color-mix(in srgb, var(--mantine-color-green-4) 20%, #2a322d);
--mantine-color-green-light-hover: color-mix(in srgb, var(--mantine-color-green-4) 30%, #2a322d);
--mantine-color-green-light-color: color-mix(in srgb, var(--mantine-color-green-2) 78%, #e8e5da);
--mantine-color-green-outline: color-mix(in srgb, var(--mantine-color-green-2) 80%, #dfdcd0);
--mantine-color-green-outline-hover: color-mix(in srgb, var(--mantine-color-green-4) 22%, #2a322d);
--mantine-color-green-text: color-mix(in srgb, var(--mantine-color-green-2) 80%, #e8e5da);
--mantine-color-lime-filled: color-mix(in srgb, var(--mantine-color-lime-3) 62%, #d9d5c7);
--mantine-color-lime-filled-hover: color-mix(in srgb, var(--mantine-color-lime-3) 76%, #d9d5c7);
--mantine-color-lime-light: color-mix(in srgb, var(--mantine-color-lime-4) 20%, #2a322d);
--mantine-color-lime-light-hover: color-mix(in srgb, var(--mantine-color-lime-4) 30%, #2a322d);
--mantine-color-lime-light-color: color-mix(in srgb, var(--mantine-color-lime-2) 78%, #e8e5da);
--mantine-color-lime-outline: color-mix(in srgb, var(--mantine-color-lime-2) 80%, #dfdcd0);
--mantine-color-lime-outline-hover: color-mix(in srgb, var(--mantine-color-lime-4) 22%, #2a322d);
--mantine-color-lime-text: color-mix(in srgb, var(--mantine-color-lime-2) 80%, #e8e5da);
--mantine-color-yellow-filled: color-mix(in srgb, var(--mantine-color-yellow-3) 62%, #d9d5c7);
--mantine-color-yellow-filled-hover: color-mix(in srgb, var(--mantine-color-yellow-3) 76%, #d9d5c7);
--mantine-color-yellow-light: color-mix(in srgb, var(--mantine-color-yellow-4) 20%, #2a322d);
--mantine-color-yellow-light-hover: color-mix(in srgb, var(--mantine-color-yellow-4) 30%, #2a322d);
--mantine-color-yellow-light-color: color-mix(in srgb, var(--mantine-color-yellow-2) 78%, #e8e5da);
--mantine-color-yellow-outline: color-mix(in srgb, var(--mantine-color-yellow-2) 80%, #dfdcd0);
--mantine-color-yellow-outline-hover: color-mix(in srgb, var(--mantine-color-yellow-4) 22%, #2a322d);
--mantine-color-yellow-text: color-mix(in srgb, var(--mantine-color-yellow-2) 80%, #e8e5da);
--mantine-color-orange-filled: color-mix(in srgb, var(--mantine-color-orange-3) 62%, #d9d5c7);
--mantine-color-orange-filled-hover: color-mix(in srgb, var(--mantine-color-orange-3) 76%, #d9d5c7);
--mantine-color-orange-light: color-mix(in srgb, var(--mantine-color-orange-4) 20%, #2a322d);
--mantine-color-orange-light-hover: color-mix(in srgb, var(--mantine-color-orange-4) 30%, #2a322d);
--mantine-color-orange-light-color: color-mix(in srgb, var(--mantine-color-orange-2) 78%, #e8e5da);
--mantine-color-orange-outline: color-mix(in srgb, var(--mantine-color-orange-2) 80%, #dfdcd0);
--mantine-color-orange-outline-hover: color-mix(in srgb, var(--mantine-color-orange-4) 22%, #2a322d);
--mantine-color-orange-text: color-mix(in srgb, var(--mantine-color-orange-2) 80%, #e8e5da);
--mantine-color-gray-filled: color-mix(in srgb, var(--mantine-color-gray-3) 62%, #d9d5c7);
--mantine-color-gray-filled-hover: color-mix(in srgb, var(--mantine-color-gray-3) 76%, #d9d5c7);
--mantine-color-gray-light: color-mix(in srgb, var(--mantine-color-gray-4) 20%, #2a322d);
--mantine-color-gray-light-hover: color-mix(in srgb, var(--mantine-color-gray-4) 30%, #2a322d);
--mantine-color-gray-light-color: color-mix(in srgb, var(--mantine-color-gray-2) 78%, #e8e5da);
--mantine-color-gray-outline: color-mix(in srgb, var(--mantine-color-gray-2) 80%, #dfdcd0);
--mantine-color-gray-outline-hover: color-mix(in srgb, var(--mantine-color-gray-4) 22%, #2a322d);
--mantine-color-gray-text: color-mix(in srgb, var(--mantine-color-gray-2) 80%, #e8e5da);
}
+17
View File
@@ -0,0 +1,17 @@
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
const manifest: ThemeManifest = {
id: "skeuo",
label: msg`Skeuomorphic`,
colorSchemes: "both",
fontLinks: [
"https://fonts.googleapis.com/css2?family=Lato:wght@400;700;900&display=swap",
],
chromeColors: {
light: { base: "#b7bdc5", dimmed: "#8b929b" },
dark: { base: "#2a2d31", dimmed: "#141619" },
},
};
export default manifest;
+947
View File
@@ -0,0 +1,947 @@
/* Skeuomorphic — 2010 machined realism (iOS 4 / brushed aluminum era).
Light: warm linen body, machined-pill controls, deeply inset inputs.
Dark: graphite/leather with subtle white inset highlights.
All textures are inline data-URIs; real 1px borders everywhere so
forced-colors mode keeps usable outlines. */
/* ---------- 1. Tokens, any scheme ---------- */
[data-theme="skeuo"][data-theme] {
--mantine-font-family: "Lato", "Inter", "Helvetica Neue", Helvetica, sans-serif;
--mantine-font-family-headings: "Lato", "Inter", "Helvetica Neue", Helvetica, sans-serif;
--mantine-heading-font-weight: 900;
--mantine-radius-xs: 0.25rem;
--mantine-radius-sm: 0.375rem;
--mantine-radius-md: 0.5rem;
--mantine-radius-lg: 0.75rem;
--mantine-radius-xl: 1rem;
--mantine-radius-default: 0.5rem;
--flxn-avatar-radius: 50%;
--flxn-avatar-border: 1px solid rgba(0, 0, 0, 0.3);
--flxn-badge-radius: 10px;
/* anodized accent ramps — interactive shades 5-9 deepened and slightly
desaturated so filled surfaces read as lacquered metal in any hue */
--mantine-color-blue-5: #3a8ad3;
--mantine-color-blue-6: #1f78c8;
--mantine-color-blue-7: #1a67ad;
--mantine-color-blue-8: #165890;
--mantine-color-blue-9: #124874;
--mantine-color-red-5: #e05c56;
--mantine-color-red-6: #d64545;
--mantine-color-red-7: #bb3a3a;
--mantine-color-red-8: #9e3131;
--mantine-color-red-9: #822928;
--mantine-color-green-5: #4bb35c;
--mantine-color-green-6: #379f4d;
--mantine-color-green-7: #2e8841;
--mantine-color-green-8: #267136;
--mantine-color-green-9: #1e5c2c;
--mantine-color-yellow-5: #ecad14;
--mantine-color-yellow-6: #d99a06;
--mantine-color-yellow-7: #bb8405;
--mantine-color-yellow-8: #9c6e04;
--mantine-color-yellow-9: #7f5a04;
--mantine-color-grape-5: #b355cb;
--mantine-color-grape-6: #a341bd;
--mantine-color-grape-7: #8d37a4;
--mantine-color-grape-8: #762e8a;
--mantine-color-grape-9: #612671;
--mantine-color-orange-5: #ef8221;
--mantine-color-orange-6: #dd6f13;
--mantine-color-orange-7: #c05f10;
--mantine-color-orange-8: #a2500d;
--mantine-color-orange-9: #86420b;
--mantine-color-pink-5: #d75a86;
--mantine-color-pink-6: #c8406f;
--mantine-color-pink-7: #ad365f;
--mantine-color-pink-8: #922d50;
--mantine-color-pink-9: #782542;
--mantine-color-lime-5: #83bb2f;
--mantine-color-lime-6: #71ab1d;
--mantine-color-lime-7: #619219;
--mantine-color-lime-8: #517a15;
--mantine-color-lime-9: #426311;
/* fractal-noise grain, tiled; ~4% opacity baked into the rect */
--skeuo-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.045'/%3E%3C/svg%3E");
/* fine horizontal brushing for metal surfaces */
--skeuo-brush: repeating-linear-gradient(
180deg,
rgba(255, 255, 255, 0.05) 0 1px,
rgba(0, 0, 0, 0.03) 1px 2px,
transparent 2px 3px
);
}
/* ---------- 2. Per-scheme tokens ---------- */
[data-theme="skeuo"][data-theme][data-mantine-color-scheme="light"] {
--mantine-color-body: #e8e4da;
--mantine-color-text: #3b362e;
--mantine-color-bright: #1f1b15;
--mantine-color-dimmed: #665e4e;
--mantine-color-placeholder: #7d745f;
--mantine-color-anchor: #3a6ea5;
--mantine-color-error: #b0382e;
--mantine-color-default: #eeebe2;
--mantine-color-default-hover: #e3dfd2;
--mantine-color-default-color: #3b362e;
--mantine-color-default-border: #a99f8c;
/* warm machined grays */
--mantine-color-gray-0: #faf8f3;
--mantine-color-gray-1: #f1eee6;
--mantine-color-gray-2: #e6e2d6;
--mantine-color-gray-3: #d7d2c3;
--mantine-color-gray-4: #c3bcaa;
--mantine-color-gray-5: #a99f8c;
--mantine-color-gray-6: #857c6c;
--mantine-color-gray-7: #5f584b;
--mantine-color-gray-8: #403a30;
--mantine-color-gray-9: #29241d;
/* raised-panel shadow stacks: inset top highlight + soft drop */
--mantine-shadow-xs: inset 0 1px 0 rgba(255, 255, 255, 0.65),
0 1px 2px rgba(46, 41, 33, 0.16);
--mantine-shadow-sm: inset 0 1px 0 rgba(255, 255, 255, 0.7),
0 1px 2px rgba(46, 41, 33, 0.18), 0 3px 8px rgba(46, 41, 33, 0.1);
--mantine-shadow-md: inset 0 1px 0 rgba(255, 255, 255, 0.75),
0 2px 4px rgba(46, 41, 33, 0.16), 0 7px 16px rgba(46, 41, 33, 0.13);
--mantine-shadow-lg: inset 0 1px 0 rgba(255, 255, 255, 0.75),
0 4px 8px rgba(46, 41, 33, 0.18), 0 14px 28px rgba(46, 41, 33, 0.16);
--mantine-shadow-xl: inset 0 1px 0 rgba(255, 255, 255, 0.8),
0 6px 12px rgba(46, 41, 33, 0.2), 0 22px 44px rgba(46, 41, 33, 0.2);
/* app tokens */
--flxn-gold: #cf9312;
--flxn-silver: #8f97a1;
--flxn-bronze: #b06f36;
--flxn-toast-success: #3d8f43;
--flxn-toast-error: #bf4136;
/* labels: engraved neutral inks — never the plate's own metal hue */
--flxn-podium-1-bg: linear-gradient(180deg, #fbecbb 0%, #efd489 48%, #e0b955 52%, #d8ad42 100%);
--flxn-podium-1-border: #b18a2b;
--flxn-podium-1-label: #46402e;
--flxn-podium-2-bg: linear-gradient(180deg, #f7f8fa 0%, #e2e5e9 48%, #ccd0d6 52%, #c2c7ce 100%);
--flxn-podium-2-border: #969da6;
--flxn-podium-2-label: #3f444b;
--flxn-podium-3-bg: linear-gradient(180deg, #f0d3b4 0%, #ddad7d 48%, #ca9257 52%, #c08a4e 100%);
--flxn-podium-3-border: #99672f;
--flxn-podium-3-label: #2f2822;
--flxn-showcase-bg: linear-gradient(180deg, #fbf9f4 0%, #efece2 100%);
--flxn-overlay: rgba(30, 26, 20, 0.45);
--flxn-dotgrid-color: #c9c2b1;
--flxn-pop-shadow: rgba(46, 41, 33, 0.35);
}
[data-theme="skeuo"][data-theme][data-mantine-color-scheme="dark"] {
--mantine-color-body: #1a1b1e;
--mantine-color-text: #d5d2ca;
--mantine-color-bright: #f2efe8;
--mantine-color-dimmed: #8f8c84;
--mantine-color-placeholder: #6f6c64;
--mantine-color-anchor: #82abdd;
--mantine-color-error: #e05e52;
--mantine-color-default: #2b2d31;
--mantine-color-default-hover: #34373c;
--mantine-color-default-color: #d5d2ca;
--mantine-color-default-border: #46484d;
/* graphite darks */
--mantine-color-dark-0: #e0ddd6;
--mantine-color-dark-1: #c7c4bd;
--mantine-color-dark-2: #94918a;
--mantine-color-dark-3: #4c4f55;
--mantine-color-dark-4: #3a3d42;
--mantine-color-dark-5: #2b2d31;
--mantine-color-dark-6: #222428;
--mantine-color-dark-7: #1a1b1e;
--mantine-color-dark-8: #141518;
--mantine-color-dark-9: #0e0f11;
--mantine-shadow-xs: inset 0 1px 0 rgba(255, 255, 255, 0.06),
0 1px 2px rgba(0, 0, 0, 0.5);
--mantine-shadow-sm: inset 0 1px 0 rgba(255, 255, 255, 0.07),
0 1px 3px rgba(0, 0, 0, 0.55), 0 3px 8px rgba(0, 0, 0, 0.35);
--mantine-shadow-md: inset 0 1px 0 rgba(255, 255, 255, 0.08),
0 2px 5px rgba(0, 0, 0, 0.55), 0 8px 18px rgba(0, 0, 0, 0.4);
--mantine-shadow-lg: inset 0 1px 0 rgba(255, 255, 255, 0.08),
0 4px 10px rgba(0, 0, 0, 0.6), 0 16px 32px rgba(0, 0, 0, 0.45);
--mantine-shadow-xl: inset 0 1px 0 rgba(255, 255, 255, 0.09),
0 6px 14px rgba(0, 0, 0, 0.65), 0 24px 48px rgba(0, 0, 0, 0.5);
--flxn-gold: #e7b93c;
--flxn-silver: #a9b0ba;
--flxn-bronze: #cd8a4c;
--flxn-toast-success: #6fca75;
--flxn-toast-error: #ef8078;
--flxn-podium-1-bg: linear-gradient(180deg, #4d3f1c 0%, #3a3016 48%, #2c2510 52%, #272009 100%);
--flxn-podium-1-border: #7c6425;
--flxn-podium-1-label: #ecc65a;
--flxn-podium-2-bg: linear-gradient(180deg, #3c3f45 0%, #2f3237 48%, #26282c 52%, #222428 100%);
--flxn-podium-2-border: #5c6069;
--flxn-podium-2-label: #bcc2cb;
--flxn-podium-3-bg: linear-gradient(180deg, #46311c 0%, #372613 48%, #2a1d0d 52%, #251a09 100%);
--flxn-podium-3-border: #7a5528;
--flxn-podium-3-label: #dc9c5c;
--flxn-showcase-bg: linear-gradient(180deg, #26282c 0%, #1d1f22 100%);
--flxn-overlay: rgba(0, 0, 0, 0.55);
--flxn-dotgrid-color: #33363b;
--flxn-pop-shadow: rgba(0, 0, 0, 0.6);
}
/* Light scheme only: Mantine pins --mantine-primary-color-contrast to white
(no JS autoContrast), unreadable on the light shade-6 fills (yellow,
orange, lime, green). Recompute from the fill's lightness — dark-scheme
shade-8 fills all carry white fine and keep the default. */
@supports (color: oklch(from red calc((0.5 - l) * infinity) 0 h)) {
[data-theme="skeuo"][data-theme][data-mantine-color-scheme="light"] {
--mantine-primary-color-contrast: oklch(
from var(--mantine-primary-color-filled) calc((0.59 - l) * infinity) 0 h
);
}
}
/* ---------- Body texture (linen / leather grain) ---------- */
[data-theme="skeuo"][data-mantine-color-scheme="light"] body {
background-color: var(--mantine-color-body);
background-image: var(--skeuo-noise),
repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.04) 0 1px, transparent 1px 3px),
repeating-linear-gradient(90deg, rgba(66, 56, 40, 0.025) 0 1px, transparent 1px 3px);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] body {
background-color: var(--mantine-color-body);
background-image: var(--skeuo-noise),
radial-gradient(130% 80% at 50% 0%, rgba(255, 255, 255, 0.035), transparent 60%);
}
/* ---------- Typography: embossed / debossed labels ---------- */
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-Title-root {
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-Title-root {
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.7);
}
/* ---------- Buttons & ActionIcons: machined pills ---------- */
[data-theme="skeuo"] .mantine-Button-root,
[data-theme="skeuo"] .mantine-ActionIcon-root {
border: 1px solid var(--mantine-color-default-border);
border-radius: 100px;
}
/* default variant — bare aluminum */
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-Button-root[data-variant="default"],
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-ActionIcon-root[data-variant="default"] {
background-color: #e3dfd2;
background-image: linear-gradient(180deg, #fdfcf8 0%, #ece8dc 48%, #dcd7c8 52%, #d0cabb 100%);
border-color: #9d947f;
color: #3b362e;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9),
inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 2px rgba(46, 41, 33, 0.3);
}
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-Button-root[data-variant="default"]:hover,
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-ActionIcon-root[data-variant="default"]:hover {
background-image: linear-gradient(180deg, #fffefb 0%, #f2eee3 48%, #e3decf 52%, #d8d2c3 100%);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-Button-root[data-variant="default"],
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-ActionIcon-root[data-variant="default"] {
background-color: #2b2d31;
background-image: linear-gradient(180deg, #40434a 0%, #303338 48%, #26282c 52%, #202226 100%);
border-color: #0d0e10;
color: #d5d2ca;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.6);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12),
inset 0 -1px 0 rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.55);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-Button-root[data-variant="default"]:hover,
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-ActionIcon-root[data-variant="default"]:hover {
background-image: linear-gradient(180deg, #474a52 0%, #363940 48%, #2b2d32 52%, #24262b 100%);
}
/* filled variant — glossy accent metal (keeps the user's accent) */
[data-theme="skeuo"] .mantine-Button-root[data-variant="filled"],
[data-theme="skeuo"] .mantine-ActionIcon-root[data-variant="filled"] {
background-color: var(--mantine-primary-color-filled);
background-image: linear-gradient(
180deg,
color-mix(in srgb, var(--mantine-primary-color-filled) 62%, white) 0%,
color-mix(in srgb, var(--mantine-primary-color-filled) 92%, white) 48%,
var(--mantine-primary-color-filled) 52%,
color-mix(in srgb, var(--mantine-primary-color-filled) 82%, black) 100%
);
border-color: color-mix(in srgb, var(--mantine-primary-color-filled) 55%, black);
color: var(--mantine-primary-color-contrast);
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.35);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.45),
inset 0 -1px 0 rgba(0, 0, 0, 0.25), 0 1px 3px rgba(0, 0, 0, 0.35);
}
/* prop-less Button/ActionIcon default to the filled variant without a
data-variant attribute — give them the same on-color */
[data-theme="skeuo"] .mantine-Button-root:not([data-variant]),
[data-theme="skeuo"] .mantine-ActionIcon-root:not([data-variant]) {
color: var(--mantine-primary-color-contrast);
}
[data-theme="skeuo"] .mantine-Button-root[data-variant="filled"]:hover,
[data-theme="skeuo"] .mantine-ActionIcon-root[data-variant="filled"]:hover {
background-image: linear-gradient(
180deg,
color-mix(in srgb, var(--mantine-primary-color-filled) 52%, white) 0%,
color-mix(in srgb, var(--mantine-primary-color-filled) 84%, white) 48%,
var(--mantine-primary-color-filled) 52%,
color-mix(in srgb, var(--mantine-primary-color-filled) 78%, black) 100%
);
}
/* light / subtle / outline — etched into the surface */
[data-theme="skeuo"] .mantine-Button-root[data-variant="light"],
[data-theme="skeuo"] .mantine-ActionIcon-root[data-variant="light"] {
border-color: color-mix(in srgb, var(--mantine-primary-color-filled) 35%, transparent);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.08);
}
[data-theme="skeuo"] .mantine-Button-root[data-variant="outline"],
[data-theme="skeuo"] .mantine-ActionIcon-root[data-variant="outline"] {
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1),
inset 0 -1px 0 rgba(255, 255, 255, 0.15);
}
[data-theme="skeuo"] .mantine-Button-root[data-variant="subtle"],
[data-theme="skeuo"] .mantine-ActionIcon-root[data-variant="subtle"],
[data-theme="skeuo"] .mantine-Button-root[data-variant="transparent"],
[data-theme="skeuo"] .mantine-ActionIcon-root[data-variant="transparent"] {
border-color: transparent;
}
/* pressed — inverted gradient + sunk-in inset */
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-Button-root[data-variant="default"]:active,
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-ActionIcon-root[data-variant="default"]:active {
background-image: linear-gradient(180deg, #c8c2b1 0%, #d5d0c0 48%, #e4e0d3 52%, #efece2 100%);
box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.28),
inset 0 1px 2px rgba(0, 0, 0, 0.22), 0 1px 0 rgba(255, 255, 255, 0.6);
text-shadow: none;
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-Button-root[data-variant="default"]:active,
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-ActionIcon-root[data-variant="default"]:active {
background-image: linear-gradient(180deg, #17181b 0%, #202226 48%, #2b2d32 52%, #34363c 100%);
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.65),
0 1px 0 rgba(255, 255, 255, 0.06);
text-shadow: none;
}
[data-theme="skeuo"] .mantine-Button-root[data-variant="filled"]:active,
[data-theme="skeuo"] .mantine-ActionIcon-root[data-variant="filled"]:active {
background-image: linear-gradient(
180deg,
color-mix(in srgb, var(--mantine-primary-color-filled) 75%, black) 0%,
var(--mantine-primary-color-filled) 52%,
color-mix(in srgb, var(--mantine-primary-color-filled) 85%, white) 100%
);
box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.4);
text-shadow: none;
}
[data-theme="skeuo"] .mantine-Button-root:disabled,
[data-theme="skeuo"] .mantine-Button-root[data-disabled],
[data-theme="skeuo"] .mantine-ActionIcon-root:disabled,
[data-theme="skeuo"] .mantine-ActionIcon-root[data-disabled] {
background-image: none;
box-shadow: none;
text-shadow: none;
border: 1px solid var(--mantine-color-default-border);
}
/* press feedback stays subtle — machined buttons sink, not shrink */
@media (prefers-reduced-motion: no-preference) {
[data-theme="skeuo"] .flxn-press:active {
transform: scale(0.98) translateY(1px);
}
}
/* ---------- Inputs: deeply inset wells ---------- */
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-Input-input {
background-color: #fbfaf5;
background-image: linear-gradient(180deg, #efece2 0%, #f8f6ef 18%, #fdfcf8 100%);
border: 1px solid #a1977f;
border-radius: var(--mantine-radius-md);
color: var(--mantine-color-text);
box-shadow: inset 0 1px 3px rgba(46, 41, 33, 0.24),
inset 0 2px 6px rgba(46, 41, 33, 0.08), 0 1px 0 rgba(255, 255, 255, 0.7);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-Input-input {
background-color: #121316;
background-image: linear-gradient(180deg, #0c0d0f 0%, #121316 25%, #17181c 100%);
border: 1px solid #0a0b0c;
border-radius: var(--mantine-radius-md);
color: var(--mantine-color-text);
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.65),
inset 0 2px 8px rgba(0, 0, 0, 0.3), 0 1px 0 rgba(255, 255, 255, 0.07);
}
[data-theme="skeuo"] .mantine-Input-input:focus,
[data-theme="skeuo"] .mantine-Input-input:focus-within {
border-color: var(--mantine-primary-color-filled);
outline: none;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.25),
0 0 0 3px color-mix(in srgb, var(--mantine-primary-color-filled) 30%, transparent);
}
/* ---------- Panels: Paper, Card, dropdowns, Modal ---------- */
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-Paper-root,
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-Card-root,
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-Popover-dropdown,
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-Modal-content {
background-color: #f6f3ec;
background-image: linear-gradient(180deg, #fcfbf7 0%, #f1eee5 100%);
border: 1px solid #c2bbaa;
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-Paper-root,
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-Card-root,
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-Popover-dropdown,
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-Modal-content {
background-color: #222428;
background-image: linear-gradient(180deg, #27292e 0%, #1e2023 100%);
border: 1px solid #0d0e10;
}
[data-theme="skeuo"] .mantine-Paper-root,
[data-theme="skeuo"] .mantine-Card-root {
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="skeuo"] .mantine-Modal-content {
box-shadow: var(--mantine-shadow-xl);
}
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-Modal-header {
background-color: #d5dae0;
background-image: var(--skeuo-brush),
linear-gradient(180deg, #f2f4f6 0%, #d9dde2 48%, #c2c8cf 52%, #ced3da 100%);
border-bottom: 1px solid #8f97a1;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-Modal-header {
background-color: #2b2d31;
background-image: var(--skeuo-brush),
linear-gradient(180deg, #3c3f45 0%, #2c2e33 48%, #222428 52%, #26282c 100%);
border-bottom: 1px solid #0a0b0c;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
/* ---------- AppShell: brushed-metal header, machined dock ---------- */
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-AppShell-header {
background-color: #b7bdc5;
background-image: var(--skeuo-noise), var(--skeuo-brush),
linear-gradient(180deg, #f4f6f8 0%, #d3d8de 45%, #b3bac3 50%, #a9b0ba 96%, #99a1ab 100%);
border-bottom: 1px solid #6f7681;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.75),
0 1px 0 rgba(255, 255, 255, 0.25), 0 2px 8px rgba(46, 41, 33, 0.3);
}
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-AppShell-header .mantine-Title-root {
color: #2e333a;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-AppShell-header {
background-color: #26282c;
background-image: var(--skeuo-noise), var(--skeuo-brush),
linear-gradient(180deg, #3e4147 0%, #2c2e33 45%, #212327 50%, #1d1f22 100%);
border-bottom: 1px solid #0a0b0c;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14),
0 2px 10px rgba(0, 0, 0, 0.6);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-AppShell-header .mantine-Title-root {
color: #e9e6df;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.8);
}
[data-theme="skeuo"] .mantine-AppShell-navbar {
background-color: transparent;
}
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-AppShell-navbar {
background-image: linear-gradient(90deg, #e3dfd3 0%, #ece8dd 100%);
border-right: 1px solid #b3ab99;
box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.6);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-AppShell-navbar {
background-image: linear-gradient(90deg, #1d1f22 0%, #222428 100%);
border-right: 1px solid #0d0e10;
box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.05);
}
/* mobile bottom dock (Paper nav) — polished metal slab */
[data-theme="skeuo"][data-mantine-color-scheme="light"] nav[role="navigation"].mantine-Paper-root {
background-color: #d9dde2;
background-image: var(--skeuo-brush),
linear-gradient(180deg, #f3f5f7 0%, #d8dce1 48%, #c3c9d0 52%, #cdd2d9 100%);
border: 1px solid #868e98;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8),
0 4px 10px rgba(46, 41, 33, 0.28), 0 10px 24px rgba(46, 41, 33, 0.18);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] nav[role="navigation"].mantine-Paper-root {
background-color: #26282c;
background-image: var(--skeuo-brush),
linear-gradient(180deg, #393c42 0%, #2a2c30 48%, #202226 52%, #24262a 100%);
border: 1px solid #0a0b0c;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12),
0 4px 12px rgba(0, 0, 0, 0.55), 0 12px 28px rgba(0, 0, 0, 0.4);
}
/* ---------- Tabs & SegmentedControl: machined switches ---------- */
[data-theme="skeuo"] .mantine-Tabs-list {
--tabs-list-border-size: 1px;
}
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-Tabs-tab:where([data-active]) {
box-shadow: inset 0 1px 3px rgba(46, 41, 33, 0.12);
}
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-SegmentedControl-root {
background-color: #d3cec0;
background-image: linear-gradient(180deg, #c8c2b2 0%, #d6d1c3 30%, #dedacd 100%);
border: 1px solid #a1977f;
border-radius: 100px;
box-shadow: inset 0 1px 3px rgba(46, 41, 33, 0.25),
0 1px 0 rgba(255, 255, 255, 0.65);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-SegmentedControl-root {
background-color: #141518;
background-image: linear-gradient(180deg, #0e0f11 0%, #16171a 40%, #1b1c20 100%);
border: 1px solid #0a0b0c;
border-radius: 100px;
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6),
0 1px 0 rgba(255, 255, 255, 0.06);
}
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-SegmentedControl-indicator {
background-color: #f4f1e9;
background-image: linear-gradient(180deg, #fdfcf8 0%, #efebe0 48%, #e0dbcc 52%, #d8d2c3 100%);
border: 1px solid #998f7a;
border-radius: 100px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9),
0 1px 3px rgba(46, 41, 33, 0.35);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-SegmentedControl-indicator {
background-color: #34363c;
background-image: linear-gradient(180deg, #45484f 0%, #34373c 48%, #2a2c31 52%, #26282c 100%);
border: 1px solid #0d0e10;
border-radius: 100px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14),
0 1px 3px rgba(0, 0, 0, 0.6);
}
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-SegmentedControl-label {
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-SegmentedControl-label {
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.55);
}
/* ---------- App hooks: tab strip, nav tiles, see-all, match card ---------- */
/* tab strip — flat while resting; docked = brushed band, same alloy as
the header */
[data-theme="skeuo"][data-mantine-color-scheme="light"] .flxn-tabs[data-stuck] {
background-color: #ccd1d8;
background-image: var(--skeuo-brush),
linear-gradient(180deg, #eef0f3 0%, #d3d8de 48%, #bfc5cd 52%, #c8cdd4 100%);
border-bottom-color: #868e98;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .flxn-tabs[data-stuck] {
background-color: #26282c;
background-image: var(--skeuo-brush),
linear-gradient(180deg, #35383e 0%, #292b2f 48%, #202226 52%, #24262a 100%);
border-bottom-color: #0a0b0c;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
[data-theme="skeuo"] .flxn-tab {
font-weight: 700;
}
[data-theme="skeuo"][data-mantine-color-scheme="light"] .flxn-tab {
color: #4d5560;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .flxn-tab {
color: #94918a;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.6);
}
/* active tab — accent-tinted engraving ink; the pressed-well chrome only
appears when the strip is DOCKED (resting strip stays flat paper) */
[data-theme="skeuo"][data-mantine-color-scheme="light"] .flxn-tab[data-active] {
/* ≥4.5:1 over both the page and the brushed strip for all eight accents */
color: color-mix(in srgb, var(--mantine-primary-color-filled) 45%, black);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .flxn-tab[data-active] {
color: color-mix(in srgb, var(--mantine-primary-color-filled) 50%, white);
}
[data-theme="skeuo"][data-mantine-color-scheme="light"]
.flxn-tabs[data-stuck]
.flxn-tab[data-active] {
background-image: linear-gradient(
180deg,
rgba(46, 41, 33, 0.12) 0%,
rgba(46, 41, 33, 0.03) 40%,
rgba(255, 255, 255, 0.14) 100%
);
box-shadow: inset 0 2px 4px rgba(46, 41, 33, 0.22);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"]
.flxn-tabs[data-stuck]
.flxn-tab[data-active] {
background-image: linear-gradient(
180deg,
rgba(0, 0, 0, 0.35) 0%,
rgba(0, 0, 0, 0.1) 40%,
rgba(255, 255, 255, 0.04) 100%
);
box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.55);
}
/* indicator — plain accent underline, no glow */
[data-theme="skeuo"] .flxn-tabs-indicator {
border-bottom: 3px solid var(--mantine-primary-color-filled);
box-shadow: none;
}
/* nav grid tiles — raised machined slabs, pressed state sinks */
[data-theme="skeuo"][data-mantine-color-scheme="light"] .flxn-tile {
background-color: #eeebe0;
background-image: linear-gradient(180deg, #fdfcf8 0%, #f0ede2 48%, #e2ddce 52%, #d8d2c3 100%);
border: 1px solid #9d947f;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.85),
inset 0 -1px 0 rgba(0, 0, 0, 0.08), 0 1px 2px rgba(46, 41, 33, 0.22),
0 4px 10px rgba(46, 41, 33, 0.12);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .flxn-tile {
background-color: #2b2d31;
background-image: linear-gradient(180deg, #3d4046 0%, #2e3035 48%, #25272b 52%, #202226 100%);
border: 1px solid #0d0e10;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12),
inset 0 -1px 0 rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.5),
0 4px 12px rgba(0, 0, 0, 0.35);
}
[data-theme="skeuo"][data-mantine-color-scheme="light"] .flxn-tile:active {
background-image: linear-gradient(180deg, #c8c2b1 0%, #d5d0c0 48%, #e4e0d3 52%, #efece2 100%);
box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.26),
0 1px 0 rgba(255, 255, 255, 0.6);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .flxn-tile:active {
background-image: linear-gradient(180deg, #17181b 0%, #202226 48%, #2b2d32 52%, #34363c 100%);
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.65),
0 1px 0 rgba(255, 255, 255, 0.06);
}
/* tile icon — flat anodized tint */
[data-theme="skeuo"] .flxn-tile-icon {
color: var(--mantine-primary-color-light-color);
}
/* see-all — small glossy accent pill. Base fill stays primary-filled under
contrast text; the gloss is a translucent overlay so it never shifts the
fill toward the text color. */
[data-theme="skeuo"] .flxn-see-all {
border-radius: 100px;
border: 1px solid color-mix(in srgb, var(--mantine-primary-color-filled) 50%, black);
background-color: var(--mantine-primary-color-filled);
background-image: linear-gradient(
180deg,
rgba(255, 255, 255, 0.28) 0%,
rgba(255, 255, 255, 0.08) 48%,
rgba(255, 255, 255, 0) 52%,
rgba(0, 0, 0, 0.16) 100%
);
color: var(--mantine-primary-color-contrast);
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.45),
0 1px 2px rgba(0, 0, 0, 0.3);
}
@media (hover: hover) {
[data-theme="skeuo"] .flxn-see-all:hover {
background-image: linear-gradient(
180deg,
rgba(255, 255, 255, 0.36) 0%,
rgba(255, 255, 255, 0.14) 48%,
rgba(255, 255, 255, 0.05) 52%,
rgba(0, 0, 0, 0.12) 100%
);
}
}
/* match card raised, reaction tray recessed as an inset well beneath it */
[data-theme="skeuo"][data-mantine-color-scheme="light"] .flxn-match-card {
background-color: #f6f3ec;
background-image: linear-gradient(180deg, #fcfbf7 0%, #f1eee5 100%);
border: 1px solid #c2bbaa;
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .flxn-match-card {
background-color: #222428;
background-image: linear-gradient(180deg, #27292e 0%, #1e2023 100%);
border: 1px solid #0d0e10;
box-shadow: var(--mantine-shadow-sm);
}
[data-theme="skeuo"][data-mantine-color-scheme="light"] .flxn-reaction-row {
background-color: #ddd7c8;
background-image: linear-gradient(
180deg,
rgba(46, 41, 33, 0.14) 0%,
rgba(46, 41, 33, 0.03) 45%,
rgba(255, 255, 255, 0.25) 100%
);
border: 1px solid #c2bbaa;
box-shadow: inset 0 2px 4px rgba(46, 41, 33, 0.18),
0 1px 0 rgba(255, 255, 255, 0.55);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .flxn-reaction-row {
background-color: #17181b;
background-image: linear-gradient(
180deg,
rgba(0, 0, 0, 0.45) 0%,
rgba(0, 0, 0, 0.1) 45%,
rgba(255, 255, 255, 0.04) 100%
);
border: 1px solid #0d0e10;
box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.55),
0 1px 0 rgba(255, 255, 255, 0.05);
}
/* ---------- Badges: glossy enamel pills ---------- */
[data-theme="skeuo"] .mantine-Badge-root {
border: 1px solid rgba(0, 0, 0, 0.28);
background-image: linear-gradient(
180deg,
rgba(255, 255, 255, 0.4) 0%,
rgba(255, 255, 255, 0.12) 48%,
rgba(0, 0, 0, 0.06) 52%,
rgba(0, 0, 0, 0.16) 100%
);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35),
0 1px 2px rgba(0, 0, 0, 0.25);
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
}
/* accent-fill on-colors (Mantine hardcodes white on filled variants,
selected combobox options and check glyphs) */
[data-theme="skeuo"] .mantine-Badge-root[data-variant="filled"],
[data-theme="skeuo"] .mantine-Badge-root:not([data-variant]),
[data-theme="skeuo"] .mantine-ThemeIcon-root[data-variant="filled"],
[data-theme="skeuo"] .mantine-ThemeIcon-root:not([data-variant]) {
color: var(--mantine-primary-color-contrast);
}
[data-theme="skeuo"] [data-combobox-selected] {
color: var(--mantine-primary-color-contrast);
}
[data-theme="skeuo"] .mantine-Checkbox-root {
--checkbox-icon-color: var(--mantine-primary-color-contrast);
}
[data-theme="skeuo"] .mantine-Radio-root {
--radio-icon-color: var(--mantine-primary-color-contrast);
}
/* ---------- Toasts: glossy pills ---------- */
[data-theme="skeuo"] .mantine-Notification-root {
border-radius: 100px;
padding-block: 0.5rem;
}
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-Notification-root {
background-color: #f8f6f0;
background-image: linear-gradient(180deg, #fefdfb 0%, #f4f1e9 48%, #e9e5d8 52%, #efece2 100%);
border: 1px solid #a99f8c;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9),
0 3px 8px rgba(46, 41, 33, 0.28), 0 10px 24px rgba(46, 41, 33, 0.18);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-Notification-root {
background-color: #2b2d31;
background-image: linear-gradient(180deg, #3b3e44 0%, #2c2e33 48%, #232529 52%, #27292d 100%);
border: 1px solid #0a0b0c;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12),
0 3px 10px rgba(0, 0, 0, 0.55), 0 12px 28px rgba(0, 0, 0, 0.4);
}
/* ---------- Switch: chrome knob in a well ---------- */
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-Switch-track {
border: 1px solid #968c77;
background-image: linear-gradient(180deg, rgba(0, 0, 0, 0.18) 0%, rgba(0, 0, 0, 0.04) 40%, rgba(255, 255, 255, 0.1) 100%);
box-shadow: inset 0 1px 3px rgba(46, 41, 33, 0.3);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-Switch-track {
border: 1px solid #0a0b0c;
background-image: linear-gradient(180deg, rgba(0, 0, 0, 0.45) 0%, rgba(0, 0, 0, 0.1) 40%, rgba(255, 255, 255, 0.05) 100%);
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6);
}
[data-theme="skeuo"] .mantine-Switch-thumb {
border: 1px solid rgba(0, 0, 0, 0.4);
background-color: #f2f2f0;
background-image: linear-gradient(180deg, #ffffff 0%, #e8e8e4 48%, #d4d4ce 52%, #dededa 100%);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9),
0 1px 3px rgba(0, 0, 0, 0.4);
}
/* ---------- Checkbox / Radio: inset wells, glossy when checked ---------- */
[data-theme="skeuo"] .mantine-Checkbox-input,
[data-theme="skeuo"] .mantine-Radio-radio {
border: 1px solid var(--mantine-color-default-border);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2);
}
[data-theme="skeuo"] .mantine-Checkbox-input:checked,
[data-theme="skeuo"] .mantine-Radio-radio:checked {
background-image: linear-gradient(
180deg,
rgba(255, 255, 255, 0.35) 0%,
rgba(255, 255, 255, 0.08) 48%,
rgba(0, 0, 0, 0.1) 52%,
rgba(0, 0, 0, 0.2) 100%
);
border-color: color-mix(in srgb, var(--mantine-primary-color-filled) 60%, black);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35),
0 1px 2px rgba(0, 0, 0, 0.25);
}
/* ---------- Progress: candy bar in a machined groove ---------- */
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-Progress-root {
background-color: #cfc9ba;
background-image: linear-gradient(180deg, rgba(0, 0, 0, 0.14), transparent 60%);
border: 1px solid #a1977f;
box-shadow: inset 0 1px 3px rgba(46, 41, 33, 0.28);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-Progress-root {
background-color: #121316;
background-image: linear-gradient(180deg, rgba(0, 0, 0, 0.5), transparent 60%);
border: 1px solid #0a0b0c;
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6);
}
[data-theme="skeuo"] .mantine-Progress-section {
background-image: linear-gradient(
180deg,
rgba(255, 255, 255, 0.45) 0%,
rgba(255, 255, 255, 0.12) 48%,
rgba(0, 0, 0, 0.08) 52%,
rgba(0, 0, 0, 0.18) 100%
);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4);
}
/* ---------- Misc: dividers, tooltips, avatars, alerts, skeleton ---------- */
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-Divider-root {
--divider-color: #c4bcab;
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.65);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-Divider-root {
--divider-color: #0f1012;
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05);
}
[data-theme="skeuo"] .mantine-Tooltip-tooltip {
background-color: #26282c;
background-image: linear-gradient(180deg, #3a3d43 0%, #26282c 55%, #1e2023 100%);
color: #ece9e2;
border: 1px solid #0d0e10;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.6);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12),
0 2px 6px rgba(0, 0, 0, 0.4);
}
/* polished bezel: dark rim + inner highlight ring */
[data-theme="skeuo"] .mantine-ThemeIcon-root {
border: 1px solid rgba(0, 0, 0, 0.3);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35),
inset 0 0 0 1px rgba(255, 255, 255, 0.18),
0 1px 2px rgba(0, 0, 0, 0.25);
}
/* avatar bezel lives on the frame; the metal rim is --flxn-avatar-border */
[data-theme="skeuo"] .flxn-avatar-frame {
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35),
inset 0 0 0 1px rgba(255, 255, 255, 0.18),
0 1px 2px rgba(0, 0, 0, 0.25);
}
[data-theme="skeuo"] .mantine-ThemeIcon-root[data-variant="filled"] {
background-image: linear-gradient(
180deg,
rgba(255, 255, 255, 0.35) 0%,
rgba(255, 255, 255, 0.08) 48%,
rgba(0, 0, 0, 0.08) 52%,
rgba(0, 0, 0, 0.18) 100%
);
}
[data-theme="skeuo"] .mantine-Alert-root {
border: 1px solid rgba(0, 0, 0, 0.2);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 3px rgba(0, 0, 0, 0.15);
}
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-Skeleton-root::after {
background-color: #ddd8ca;
}
/* ---------- PinInput inherits Input wells; keep digits engraved ---------- */
[data-theme="skeuo"][data-mantine-color-scheme="light"] .mantine-PinInput-field {
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
}
[data-theme="skeuo"][data-mantine-color-scheme="dark"] .mantine-PinInput-field {
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.6);
}
+7
View File
@@ -0,0 +1,7 @@
// Aggregates all theme CSS. Import once from the client provider tree only —
// never from server-fn modules.
// The glob stays in theme-styles.ts: Vite hoists eager-glob imports above a
// module's own static imports, which would load theme CSS before base/hooks.
import "./base.css";
import "./hooks.css";
import "./theme-styles";
+19
View File
@@ -0,0 +1,19 @@
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
const manifest: ThemeManifest = {
id: "terminal",
label: msg`Terminal`,
colorSchemes: "dark",
fontLinks: [
"https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap",
],
chromeColors: {
dark: { base: "#050805", dimmed: "#020402" },
},
mantine: {
defaultRadius: 0,
},
};
export default manifest;
+599
View File
@@ -0,0 +1,599 @@
/* CRT Terminal — phosphor monochrome in the user's accent hue, dark only.
The accent picks the tube: green = classic CRT, orange = amber terminal,
blue = blue phosphor, etc. Error stays red-phosphor. */
/* 1. Tokens, any scheme */
[data-theme="terminal"][data-theme] {
--mantine-font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
--mantine-font-family-headings: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
--mantine-font-family-monospace: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
--mantine-heading-font-weight: 600;
--mantine-radius-default: 0;
--mantine-radius-xs: 0;
--mantine-radius-sm: 0;
--mantine-radius-md: 0;
--mantine-radius-lg: 0;
--mantine-radius-xl: 0;
--flxn-avatar-radius: 0;
--flxn-avatar-border: 1px solid var(--mantine-primary-color-6);
--flxn-badge-radius: 0;
/* flat / glow elevation — no soft shadows (sm is preview-contract: neutral) */
--mantine-shadow-xs: 0 0 0 1px color-mix(in srgb, var(--mantine-primary-color-5) 12%, transparent);
--mantine-shadow-sm: 0 0 0 1px rgba(200, 212, 200, 0.15);
--mantine-shadow-md: 0 0 0 1px color-mix(in srgb, var(--mantine-primary-color-5) 20%, transparent),
0 0 12px color-mix(in srgb, var(--mantine-primary-color-5) 12%, transparent);
--mantine-shadow-lg: 0 0 0 1px color-mix(in srgb, var(--mantine-primary-color-5) 25%, transparent),
0 0 18px color-mix(in srgb, var(--mantine-primary-color-5) 15%, transparent);
--mantine-shadow-xl: 0 0 0 1px color-mix(in srgb, var(--mantine-primary-color-5) 30%, transparent),
0 0 26px color-mix(in srgb, var(--mantine-primary-color-5) 18%, transparent);
/* eight phosphor ramps — bright tube core at 4-5, dim glass below */
--mantine-color-green-0: #eaffea;
--mantine-color-green-1: #ccffcc;
--mantine-color-green-2: #a8ffa8;
--mantine-color-green-3: #7dff7d;
--mantine-color-green-4: #55ff55;
--mantine-color-green-5: #33ff33;
--mantine-color-green-6: #21d621;
--mantine-color-green-7: #17a317;
--mantine-color-green-8: #0e700e;
--mantine-color-green-9: #084008;
--mantine-color-red-0: #ffeaea;
--mantine-color-red-1: #ffcccc;
--mantine-color-red-2: #ffa8a8;
--mantine-color-red-3: #ff7d7d;
--mantine-color-red-4: #ff5555;
--mantine-color-red-5: #ff3333;
--mantine-color-red-6: #d62121;
--mantine-color-red-7: #a31717;
--mantine-color-red-8: #700e0e;
--mantine-color-red-9: #400808;
--mantine-color-blue-0: #eaf4ff;
--mantine-color-blue-1: #cce6ff;
--mantine-color-blue-2: #a8d5ff;
--mantine-color-blue-3: #7dc1ff;
--mantine-color-blue-4: #55aeff;
--mantine-color-blue-5: #339cff;
--mantine-color-blue-6: #217fd6;
--mantine-color-blue-7: #1760a3;
--mantine-color-blue-8: #0e4170;
--mantine-color-blue-9: #082440;
--mantine-color-yellow-0: #fffbea;
--mantine-color-yellow-1: #fff6cc;
--mantine-color-yellow-2: #fff0a8;
--mantine-color-yellow-3: #ffe97d;
--mantine-color-yellow-4: #ffe255;
--mantine-color-yellow-5: #ffdb33;
--mantine-color-yellow-6: #d6b421;
--mantine-color-yellow-7: #a38817;
--mantine-color-yellow-8: #705d0e;
--mantine-color-yellow-9: #403408;
--mantine-color-orange-0: #fff5e0;
--mantine-color-orange-1: #ffeab8;
--mantine-color-orange-2: #ffdd85;
--mantine-color-orange-3: #ffcf5c;
--mantine-color-orange-4: #ffc133;
--mantine-color-orange-5: #ffb000;
--mantine-color-orange-6: #d69200;
--mantine-color-orange-7: #a37000;
--mantine-color-orange-8: #704d00;
--mantine-color-orange-9: #402c00;
--mantine-color-grape-0: #f6eaff;
--mantine-color-grape-1: #eaccff;
--mantine-color-grape-2: #dba8ff;
--mantine-color-grape-3: #c97dff;
--mantine-color-grape-4: #b855ff;
--mantine-color-grape-5: #a833ff;
--mantine-color-grape-6: #8c21d6;
--mantine-color-grape-7: #6a17a3;
--mantine-color-grape-8: #490e70;
--mantine-color-grape-9: #290840;
--mantine-color-pink-0: #ffeaf6;
--mantine-color-pink-1: #ffccea;
--mantine-color-pink-2: #ffa8db;
--mantine-color-pink-3: #ff7dc9;
--mantine-color-pink-4: #ff55b8;
--mantine-color-pink-5: #ff33a8;
--mantine-color-pink-6: #d6218c;
--mantine-color-pink-7: #a3176a;
--mantine-color-pink-8: #700e49;
--mantine-color-pink-9: #400829;
--mantine-color-lime-0: #f6ffea;
--mantine-color-lime-1: #eaffcc;
--mantine-color-lime-2: #dbffa8;
--mantine-color-lime-3: #c9ff7d;
--mantine-color-lime-4: #b8ff55;
--mantine-color-lime-5: #a8ff33;
--mantine-color-lime-6: #8cd621;
--mantine-color-lime-7: #6aa317;
--mantine-color-lime-8: #49700e;
--mantine-color-lime-9: #294008;
/* preview contract (neutral-phosphor; mirrored in scheme block below) */
--mantine-color-body: #050505;
--mantine-color-text: #c8d4c8;
--mantine-color-dimmed: #7c887c;
--mantine-color-default-border: #333a33;
}
/* 2. Scheme tokens (engine forces dark) */
[data-theme="terminal"][data-theme][data-mantine-color-scheme="dark"] {
--mantine-color-body: #050505;
--mantine-color-text: #c8d4c8;
--mantine-color-bright: #e8efe8;
--mantine-color-dimmed: #7c887c;
--mantine-color-anchor: var(--mantine-primary-color-3);
--mantine-color-placeholder: #5a655a;
--mantine-color-error: #ff5c5c;
--mantine-color-default: #0b0d0b;
--mantine-color-default-hover: #141714;
--mantine-color-default-color: #c8d4c8;
--mantine-color-default-border: #333a33;
/* per-color variant vars, phosphor-tuned (beat Mantine's :root[scheme]) */
--mantine-color-green-filled: var(--mantine-color-green-5);
--mantine-color-green-filled-hover: var(--mantine-color-green-4);
--mantine-color-green-light: color-mix(in srgb, var(--mantine-color-green-5) 12%, transparent);
--mantine-color-green-light-hover: color-mix(in srgb, var(--mantine-color-green-5) 20%, transparent);
--mantine-color-green-light-color: var(--mantine-color-green-4);
--mantine-color-green-outline: var(--mantine-color-green-5);
--mantine-color-green-outline-hover: color-mix(in srgb, var(--mantine-color-green-5) 12%, transparent);
--mantine-color-green-text: var(--mantine-color-green-4);
--mantine-color-red-filled: var(--mantine-color-red-5);
--mantine-color-red-filled-hover: var(--mantine-color-red-4);
--mantine-color-red-light: color-mix(in srgb, var(--mantine-color-red-5) 12%, transparent);
--mantine-color-red-light-hover: color-mix(in srgb, var(--mantine-color-red-5) 20%, transparent);
--mantine-color-red-light-color: var(--mantine-color-red-4);
--mantine-color-red-outline: var(--mantine-color-red-5);
--mantine-color-red-outline-hover: color-mix(in srgb, var(--mantine-color-red-5) 12%, transparent);
--mantine-color-red-text: var(--mantine-color-red-4);
--mantine-color-blue-filled: var(--mantine-color-blue-5);
--mantine-color-blue-filled-hover: var(--mantine-color-blue-4);
--mantine-color-blue-light: color-mix(in srgb, var(--mantine-color-blue-5) 12%, transparent);
--mantine-color-blue-light-hover: color-mix(in srgb, var(--mantine-color-blue-5) 20%, transparent);
--mantine-color-blue-light-color: var(--mantine-color-blue-4);
--mantine-color-blue-outline: var(--mantine-color-blue-5);
--mantine-color-blue-outline-hover: color-mix(in srgb, var(--mantine-color-blue-5) 12%, transparent);
--mantine-color-blue-text: var(--mantine-color-blue-4);
--mantine-color-yellow-filled: var(--mantine-color-yellow-5);
--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-4);
--mantine-color-yellow-light: color-mix(in srgb, var(--mantine-color-yellow-5) 12%, transparent);
--mantine-color-yellow-light-hover: color-mix(in srgb, var(--mantine-color-yellow-5) 20%, transparent);
--mantine-color-yellow-light-color: var(--mantine-color-yellow-4);
--mantine-color-yellow-outline: var(--mantine-color-yellow-5);
--mantine-color-yellow-outline-hover: color-mix(in srgb, var(--mantine-color-yellow-5) 12%, transparent);
--mantine-color-yellow-text: var(--mantine-color-yellow-4);
--mantine-color-orange-filled: var(--mantine-color-orange-5);
--mantine-color-orange-filled-hover: var(--mantine-color-orange-4);
--mantine-color-orange-light: color-mix(in srgb, var(--mantine-color-orange-5) 12%, transparent);
--mantine-color-orange-light-hover: color-mix(in srgb, var(--mantine-color-orange-5) 20%, transparent);
--mantine-color-orange-light-color: var(--mantine-color-orange-4);
--mantine-color-orange-outline: var(--mantine-color-orange-5);
--mantine-color-orange-outline-hover: color-mix(in srgb, var(--mantine-color-orange-5) 12%, transparent);
--mantine-color-orange-text: var(--mantine-color-orange-4);
--mantine-color-grape-filled: var(--mantine-color-grape-5);
--mantine-color-grape-filled-hover: var(--mantine-color-grape-4);
--mantine-color-grape-light: color-mix(in srgb, var(--mantine-color-grape-5) 12%, transparent);
--mantine-color-grape-light-hover: color-mix(in srgb, var(--mantine-color-grape-5) 20%, transparent);
--mantine-color-grape-light-color: var(--mantine-color-grape-4);
--mantine-color-grape-outline: var(--mantine-color-grape-5);
--mantine-color-grape-outline-hover: color-mix(in srgb, var(--mantine-color-grape-5) 12%, transparent);
--mantine-color-grape-text: var(--mantine-color-grape-4);
--mantine-color-pink-filled: var(--mantine-color-pink-5);
--mantine-color-pink-filled-hover: var(--mantine-color-pink-4);
--mantine-color-pink-light: color-mix(in srgb, var(--mantine-color-pink-5) 12%, transparent);
--mantine-color-pink-light-hover: color-mix(in srgb, var(--mantine-color-pink-5) 20%, transparent);
--mantine-color-pink-light-color: var(--mantine-color-pink-4);
--mantine-color-pink-outline: var(--mantine-color-pink-5);
--mantine-color-pink-outline-hover: color-mix(in srgb, var(--mantine-color-pink-5) 12%, transparent);
--mantine-color-pink-text: var(--mantine-color-pink-4);
--mantine-color-lime-filled: var(--mantine-color-lime-5);
--mantine-color-lime-filled-hover: var(--mantine-color-lime-4);
--mantine-color-lime-light: color-mix(in srgb, var(--mantine-color-lime-5) 12%, transparent);
--mantine-color-lime-light-hover: color-mix(in srgb, var(--mantine-color-lime-5) 20%, transparent);
--mantine-color-lime-light-color: var(--mantine-color-lime-4);
--mantine-color-lime-outline: var(--mantine-color-lime-5);
--mantine-color-lime-outline-hover: color-mix(in srgb, var(--mantine-color-lime-5) 12%, transparent);
--mantine-color-lime-text: var(--mantine-color-lime-4);
/* app tokens — monochrome ranks in the tube's hue */
--flxn-gold: var(--mantine-primary-color-4);
--flxn-silver: var(--mantine-primary-color-6);
--flxn-bronze: var(--mantine-primary-color-7);
--flxn-toast-success: var(--mantine-primary-color-4);
--flxn-toast-error: #ff7a7a;
--flxn-podium-1-bg: color-mix(in srgb, var(--mantine-primary-color-5) 10%, #050505);
--flxn-podium-1-border: var(--mantine-primary-color-5);
--flxn-podium-1-label: var(--mantine-primary-color-3);
--flxn-podium-2-bg: color-mix(in srgb, var(--mantine-primary-color-5) 6%, #050505);
--flxn-podium-2-border: var(--mantine-primary-color-6);
--flxn-podium-2-label: var(--mantine-primary-color-5);
--flxn-podium-3-bg: color-mix(in srgb, var(--mantine-primary-color-5) 3%, #050505);
--flxn-podium-3-border: var(--mantine-primary-color-7);
--flxn-podium-3-label: var(--mantine-primary-color-6);
--flxn-showcase-bg: color-mix(in srgb, var(--mantine-primary-color-5) 5%, #050505);
--flxn-overlay: rgba(0, 0, 0, 0.74);
--flxn-dotgrid-color: color-mix(in srgb, var(--mantine-primary-color-5) 15%, transparent);
--flxn-pop-shadow: color-mix(in srgb, var(--mantine-primary-color-5) 20%, transparent);
}
/* App-side phosphor text/borders: re-driven from the accent on body so the
preview contract above stays neutral (previews render in divs, no body). */
[data-theme="terminal"] body {
color: var(--mantine-primary-color-4);
--mantine-color-text: var(--mantine-primary-color-4);
--mantine-color-bright: var(--mantine-primary-color-2);
--mantine-color-dimmed: color-mix(in srgb, var(--mantine-primary-color-4) 62%, #050505);
--mantine-color-placeholder: color-mix(in srgb, var(--mantine-primary-color-4) 45%, #050505);
--mantine-color-default-color: var(--mantine-primary-color-4);
--mantine-color-default-border: color-mix(in srgb, var(--mantine-primary-color-5) 55%, #050505);
--mantine-primary-color-contrast: #050505;
--mantine-shadow-sm: 0 0 0 1px color-mix(in srgb, var(--mantine-primary-color-5) 15%, transparent);
}
/* 3. Component restyles */
/* selection & focus: inverse video */
[data-theme="terminal"] ::selection {
background: var(--mantine-primary-color-filled);
color: #050505;
text-shadow: none;
}
[data-theme="terminal"] :focus-visible {
outline: 1px solid var(--mantine-primary-color-4);
outline-offset: 1px;
}
/* phosphor glow on headings */
[data-theme="terminal"] :is(h1, h2, h3, h4, h5, h6),
[data-theme="terminal"] .mantine-Title-root {
color: var(--mantine-primary-color-4);
text-shadow: 0 0 6px color-mix(in srgb, var(--mantine-primary-color-5) 50%, transparent),
0 0 18px color-mix(in srgb, var(--mantine-primary-color-5) 20%, transparent);
}
[data-theme="terminal"] a {
text-underline-offset: 3px;
}
/* surfaces: flat panels with 1px phosphor rule */
[data-theme="terminal"] .mantine-Paper-root,
[data-theme="terminal"] .mantine-Card-root {
background-color: color-mix(in srgb, var(--mantine-primary-color-5) 4%, #050505);
border: 1px solid var(--mantine-color-default-border);
box-shadow: none;
}
[data-theme="terminal"] .mantine-AppShell-header,
[data-theme="terminal"] .mantine-AppShell-navbar {
background-color: rgba(5, 5, 5, 0.96);
border-color: var(--mantine-color-default-border);
}
/* buttons: bracketed bordered blocks */
[data-theme="terminal"] .mantine-Button-root {
border: 1px solid var(--mantine-primary-color-filled);
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 600;
}
[data-theme="terminal"] .mantine-Button-root .mantine-Button-label::before {
content: "[ ";
opacity: 0.55;
}
[data-theme="terminal"] .mantine-Button-root .mantine-Button-label::after {
content: " ]";
opacity: 0.55;
}
[data-theme="terminal"] .mantine-Button-root[data-variant="default"],
[data-theme="terminal"] .mantine-Button-root[data-variant="outline"],
[data-theme="terminal"] .mantine-Button-root[data-variant="light"],
[data-theme="terminal"] .mantine-Button-root[data-variant="subtle"] {
background-color: transparent;
color: var(--mantine-primary-color-4);
}
[data-theme="terminal"] .mantine-Button-root[data-variant="subtle"] {
border-color: transparent;
}
[data-theme="terminal"] .mantine-Button-root[data-variant="filled"] {
background-color: var(--mantine-primary-color-filled);
color: #050505;
box-shadow: 0 0 10px color-mix(in srgb, var(--mantine-primary-color-5) 35%, transparent);
}
@media (hover: hover) {
[data-theme="terminal"] .mantine-Button-root:not([data-disabled]):hover {
background-color: var(--mantine-primary-color-filled);
color: #050505;
text-shadow: none;
}
}
[data-theme="terminal"] .mantine-Button-root[data-disabled] {
border-color: color-mix(in srgb, var(--mantine-primary-color-5) 45%, #050505);
color: color-mix(in srgb, var(--mantine-primary-color-5) 35%, #050505);
background-color: transparent;
}
[data-theme="terminal"] .mantine-ActionIcon-root {
border: 1px solid var(--mantine-color-default-border);
}
[data-theme="terminal"] .mantine-ActionIcon-root[data-variant="filled"] {
border-color: var(--mantine-primary-color-filled);
background-color: var(--mantine-primary-color-filled);
color: #050505;
}
[data-theme="terminal"] .mantine-ActionIcon-root[data-variant="subtle"],
[data-theme="terminal"] .mantine-ActionIcon-root[data-variant="transparent"] {
border-color: transparent;
}
/* inputs: block-cursor terminal fields */
[data-theme="terminal"] .mantine-Input-input {
background-color: #030303;
border: 1px solid var(--mantine-color-default-border);
color: var(--mantine-primary-color-4);
caret-color: var(--mantine-primary-color-4);
caret-shape: block;
font-family: var(--mantine-font-family-monospace);
}
[data-theme="terminal"] .mantine-Input-input:focus,
[data-theme="terminal"] .mantine-Input-input:focus-within {
border-color: var(--mantine-primary-color-filled);
box-shadow: 0 0 8px color-mix(in srgb, var(--mantine-primary-color-5) 35%, transparent);
}
[data-theme="terminal"] .mantine-InputWrapper-label {
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 0.72em;
color: var(--mantine-color-dimmed);
}
[data-theme="terminal"] .mantine-PinInput-input {
background-color: #030303;
border-color: var(--mantine-color-default-border);
color: var(--mantine-primary-color-4);
}
/* tabs & segmented control: active = inverse video */
[data-theme="terminal"] .mantine-Tabs-list {
border-color: var(--mantine-color-default-border);
}
[data-theme="terminal"] .mantine-Tabs-tab {
text-transform: uppercase;
letter-spacing: 0.05em;
}
[data-theme="terminal"] .mantine-Tabs-tab[data-active] {
background-color: var(--mantine-primary-color-filled);
color: #050505;
border-color: var(--mantine-primary-color-filled);
}
[data-theme="terminal"] .mantine-SegmentedControl-root {
background-color: #030303;
border: 1px solid var(--mantine-color-default-border);
}
[data-theme="terminal"] .mantine-SegmentedControl-indicator {
background-color: var(--mantine-primary-color-filled);
box-shadow: none;
}
[data-theme="terminal"] .mantine-SegmentedControl-label[data-active] {
color: #050505;
}
/* badges, chips, indicators */
[data-theme="terminal"] .mantine-Badge-root {
background-color: transparent;
border: 1px solid var(--mantine-primary-color-6);
color: var(--mantine-primary-color-4);
text-transform: uppercase;
letter-spacing: 0.06em;
}
[data-theme="terminal"] .mantine-Badge-root[data-variant="filled"] {
background-color: var(--mantine-primary-color-filled);
border-color: var(--mantine-primary-color-filled);
color: #050505;
}
[data-theme="terminal"] .mantine-Indicator-indicator {
border: 1px solid #050505;
}
/* overlays */
[data-theme="terminal"] .mantine-Modal-content,
[data-theme="terminal"] .mantine-Popover-dropdown {
background-color: #050505;
border: 1px solid var(--mantine-primary-color-filled);
box-shadow: 0 0 24px color-mix(in srgb, var(--mantine-primary-color-5) 18%, transparent);
}
[data-theme="terminal"] .mantine-Modal-header {
background-color: #050505;
border-bottom: 1px solid var(--mantine-color-default-border);
}
[data-theme="terminal"] .mantine-Tooltip-tooltip {
background-color: var(--mantine-primary-color-filled);
color: #050505;
border-radius: 0;
}
/* selected option = inverse video (Mantine hardcodes white on the fill) */
[data-theme="terminal"] [data-combobox-selected] {
color: #050505;
}
[data-theme="terminal"] .mantine-Notification-root {
background-color: color-mix(in srgb, var(--mantine-primary-color-5) 4%, #050505);
border: 1px solid var(--mantine-primary-color-6);
box-shadow: 0 0 14px color-mix(in srgb, var(--mantine-primary-color-5) 18%, transparent);
}
[data-theme="terminal"] .mantine-Alert-root {
background-color: color-mix(in srgb, var(--mantine-primary-color-5) 8%, transparent);
border: 1px solid var(--mantine-primary-color-6);
}
/* controls */
[data-theme="terminal"] .mantine-Switch-track {
background-color: #030303;
border: 1px solid var(--mantine-color-default-border);
}
[data-theme="terminal"] .mantine-Switch-input:checked + .mantine-Switch-track {
background-color: var(--mantine-primary-color-filled);
border-color: var(--mantine-primary-color-filled);
}
[data-theme="terminal"] .mantine-Switch-thumb {
background-color: var(--mantine-primary-color-6);
border: none;
border-radius: 0;
}
[data-theme="terminal"] .mantine-Switch-input:checked + .mantine-Switch-track .mantine-Switch-thumb {
background-color: #050505;
}
[data-theme="terminal"] .mantine-Checkbox-input {
background-color: #030303;
border: 1px solid var(--mantine-color-default-border);
}
[data-theme="terminal"] .mantine-Checkbox-input:checked {
background-color: var(--mantine-primary-color-filled);
border-color: var(--mantine-primary-color-filled);
}
[data-theme="terminal"] .mantine-Checkbox-icon {
color: #050505;
}
[data-theme="terminal"] .mantine-Progress-root {
background-color: #0b0d0b;
border: 1px solid var(--mantine-color-default-border);
}
[data-theme="terminal"] .mantine-Progress-section {
background-color: var(--mantine-primary-color-filled);
}
[data-theme="terminal"] .mantine-Skeleton-root::before {
background-color: #0f110f;
}
[data-theme="terminal"] .mantine-Skeleton-root::after {
background-color: #191c19;
}
[data-theme="terminal"] .mantine-Divider-root {
--divider-color: var(--mantine-color-default-border);
}
/* nav */
[data-theme="terminal"] .mantine-NavLink-root[data-active] {
background-color: var(--mantine-primary-color-filled);
color: #050505;
}
[data-theme="terminal"] .mantine-NavLink-root[data-active] .mantine-NavLink-label,
[data-theme="terminal"] .mantine-NavLink-root[data-active] .mantine-NavLink-section {
color: #050505;
}
/* theme icons stay in the phosphor family */
[data-theme="terminal"] .mantine-ThemeIcon-root {
border: 1px solid var(--mantine-primary-color-5);
}
/* app hooks: swipeable tab strip — flat while resting; docked = ruled
readout band. Transparent resting top border keeps the strip height
stable across the state change. Active = inverse video. */
[data-theme="terminal"] .flxn-tabs {
border-top: 1px solid transparent;
}
[data-theme="terminal"] .flxn-tabs[data-stuck] {
background-color: var(--mantine-color-body);
border-top-color: var(--mantine-color-default-border);
border-bottom-color: var(--mantine-color-default-border);
}
[data-theme="terminal"] .flxn-tab.flxn-tab {
color: var(--mantine-color-dimmed);
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 500;
}
[data-theme="terminal"] .flxn-tab.flxn-tab[data-active] {
background-color: var(--mantine-primary-color-filled);
color: #050505;
font-weight: 600;
text-shadow: none;
}
[data-theme="terminal"] .flxn-tabs-indicator.flxn-tabs-indicator {
border-bottom: 2px solid var(--mantine-primary-color-filled);
padding-inline: 0;
}
/* app hooks: nav grid tiles as bordered readout boxes */
[data-theme="terminal"] .flxn-tile {
background-color: color-mix(in srgb, var(--mantine-primary-color-5) 4%, #050505);
border: 1px solid var(--mantine-color-default-border);
border-radius: 0;
box-shadow: none;
}
[data-theme="terminal"] .flxn-tile-icon {
color: var(--mantine-primary-color-4);
}
/* app hooks: "see all" as a bracketed [link] */
[data-theme="terminal"] .flxn-see-all.flxn-see-all {
background-color: transparent;
border-radius: 0;
color: var(--mantine-primary-color-4);
text-transform: uppercase;
letter-spacing: 0.06em;
}
[data-theme="terminal"] .flxn-see-all.flxn-see-all::before {
content: "[ ";
opacity: 0.55;
}
[data-theme="terminal"] .flxn-see-all.flxn-see-all::after {
content: " ]";
opacity: 0.55;
}
@media (hover: hover) {
[data-theme="terminal"] .flxn-see-all.flxn-see-all:hover {
background-color: var(--mantine-primary-color-filled);
color: #050505;
}
}
/* app hooks: match card + reaction tray read as one bordered construction */
[data-theme="terminal"] .flxn-match-card {
background-color: color-mix(in srgb, var(--mantine-primary-color-5) 4%, #050505);
border: 1px solid var(--mantine-color-default-border);
box-shadow: none;
}
[data-theme="terminal"] .flxn-reaction-row.flxn-reaction-row {
background-color: color-mix(in srgb, var(--mantine-primary-color-5) 2%, #030303);
border: 1px solid var(--mantine-color-default-border);
border-radius: 0;
}
/* 4. Decoration: fixed scanline overlay + faint RGB aperture mask */
[data-theme="terminal"] body::before {
content: "";
position: fixed;
inset: 0;
z-index: 9998;
pointer-events: none;
background:
linear-gradient(rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0.22) 50%),
linear-gradient(90deg, rgba(255, 0, 0, 0.05), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.05));
background-size: 100% 2px, 3px 100%;
opacity: 0.35;
}
@media (prefers-reduced-motion: no-preference) {
[data-theme="terminal"] body::before {
animation: terminal-phosphor-drift 9s ease-in-out infinite;
}
@keyframes terminal-phosphor-drift {
0%, 100% { opacity: 0.35; }
50% { opacity: 0.3; }
}
}
+72
View File
@@ -0,0 +1,72 @@
import { useEffect } from "react";
import type { ThemeManifest } from "./types";
export const THEME_STORAGE_KEY = "flxn-theme";
export const THEME_SCHEME_STORAGE_KEY = "flxn-theme-scheme";
export const THEME_FONTS_STORAGE_KEY = "flxn-theme-fonts";
/** Applies html[data-theme] and mirrors theme id / forced scheme / font links
* to localStorage for the pre-hydration script in __root; injects the active
* theme's font stylesheets and removes stale ones from other themes.
* Writes nothing until `ready` (post-hydration with real metadata) so the
* pre-hydration default render can't clobber the mirror. */
const ThemeProvider = ({
manifest,
ready,
children,
}: {
manifest: ThemeManifest;
ready: boolean;
children: React.ReactNode;
}) => {
useEffect(() => {
if (!ready) return;
const root = document.documentElement;
if (root.getAttribute("data-theme") !== manifest.id) {
root.setAttribute("data-theme", manifest.id);
}
try {
localStorage.setItem(THEME_STORAGE_KEY, manifest.id);
if (manifest.colorSchemes === "both") {
localStorage.removeItem(THEME_SCHEME_STORAGE_KEY);
} else {
localStorage.setItem(THEME_SCHEME_STORAGE_KEY, manifest.colorSchemes);
}
localStorage.setItem(
THEME_FONTS_STORAGE_KEY,
JSON.stringify(manifest.fontLinks ?? [])
);
} catch {
// storage unavailable (private mode) — theme still applies for session
}
}, [manifest, ready]);
useEffect(() => {
if (!ready) return;
const links = Array.from(
document.querySelectorAll<HTMLLinkElement>("link[data-flxn-theme-font]")
);
const wanted = new Set(manifest.fontLinks ?? []);
const present = new Set<string>();
for (const link of links) {
const href = link.getAttribute("href");
if (!href || !wanted.has(href)) {
link.remove();
} else {
present.add(href);
}
}
for (const href of wanted) {
if (present.has(href)) continue;
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = href;
link.dataset.flxnThemeFont = manifest.id;
document.head.appendChild(link);
}
}, [manifest, ready]);
return children;
};
export default ThemeProvider;
+6
View File
@@ -0,0 +1,6 @@
const themeCss = import.meta.glob("./*/theme.css", { eager: true });
// The glob result must stay used, or Rollup tree-shakes every theme.css out
// of the production bundle.
(globalThis as Record<string, unknown>).__flxnThemeCssCount =
Object.keys(themeCss).length;
+26
View File
@@ -0,0 +1,26 @@
import type { MessageDescriptor } from "@lingui/core";
import type { MantineThemeOverride } from "@mantine/core";
export type ThemeColorSchemes = "both" | "light" | "dark";
export interface ThemeChromeColors {
light?: { base: string; dimmed: string };
dark?: { base: string; dimmed: string };
}
export interface ThemeManifest {
/** Must match the theme's folder name; becomes the html[data-theme] value. */
id: string;
label: MessageDescriptor;
/** "light"/"dark" force that scheme while the theme is active. */
colorSchemes: ThemeColorSchemes;
/** Sort position in the picker; default theme is 0, unset sorts last. */
order?: number;
/** Stylesheet URLs (e.g. Google Fonts) injected while the theme is active. */
fontLinks?: string[];
/** Browser chrome theme-color meta overrides; falls back to defaults. */
chromeColors?: ThemeChromeColors;
/** JS-side Mantine override (defaultProps, component vars, variants…).
* Must never set primaryColor — the user's accent choice always applies. */
mantine?: MantineThemeOverride;
}
+136
View File
@@ -0,0 +1,136 @@
import { msg } from "@lingui/core/macro";
import type { ThemeManifest } from "../types";
/* Viewport gizmo ramps, declared in JS (not theme.css) so Mantine regenerates
every derived variant var (-filled, -light rgba, autoContrast picks) from
the retuned shades. Shade 6 is the fill in BOTH schemes (primaryShade
below) — each shade-6 hand-checked against its autoContrast on-color
(white or the custom ink black) for 4.5:1+. */
const gizmoColors = {
// Z-axis blue
blue: [
"#eaf2ff",
"#d2e4fe",
"#a8cbfd",
"#7aaffa",
"#4f92f5",
"#2f7cf0",
"#1c64d6",
"#154fb0",
"#103e8a",
"#0a2c62",
],
// X-axis red
red: [
"#ffecec",
"#ffd2d2",
"#ffa8a8",
"#fd7c7c",
"#f85454",
"#ef3535",
"#d0231f",
"#a91b19",
"#821513",
"#5c0f0e",
],
// Y-axis green
green: [
"#e7fbf0",
"#c9f5da",
"#98e8b8",
"#64d692",
"#38c072",
"#1a9e5a",
"#0e7d3f",
"#0a6432",
"#074b26",
"#05331a",
],
// measurement/annotation yellow
yellow: [
"#fff8dc",
"#ffedad",
"#ffdf76",
"#ffce46",
"#f9bc24",
"#daa00e",
"#bd870a",
"#946909",
"#6c4c07",
"#463005",
],
// rotate-gizmo violet
grape: [
"#f3edff",
"#e2d3fd",
"#c8adfa",
"#ac85f5",
"#9163ec",
"#7a47df",
"#6535c0",
"#522a9c",
"#3f2077",
"#2c1653",
],
// classic viewport selection orange
orange: [
"#fff1e3",
"#ffddb8",
"#ffc182",
"#ffa350",
"#ee8a2c",
"#cc6a0f",
"#b04f06",
"#8f4005",
"#6e3104",
"#4d2203",
],
// magenta gizmo tag
pink: [
"#ffeef7",
"#ffd3ea",
"#ffa8d6",
"#fd7cc0",
"#f552a9",
"#db308f",
"#c22174",
"#9a1a5b",
"#731442",
"#4d0d2c",
],
// chartreuse highlight
lime: [
"#f6ffe0",
"#e8feb0",
"#d3f47a",
"#bce34a",
"#a3ce27",
"#8db518",
"#7a9c12",
"#607b0d",
"#475c09",
"#2f3d06",
],
} as const;
const manifest: ThemeManifest = {
id: "wireframe",
label: msg`Viewport`,
colorSchemes: "both",
fontLinks: [
"https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap",
],
chromeColors: {
light: { base: "#f2f3f5", dimmed: "#dcdfe3" },
dark: { base: "#17191c", dimmed: "#0d0e10" },
},
mantine: {
autoContrast: true,
luminanceThreshold: 0.25,
black: "#15181c",
primaryShade: { light: 6, dark: 6 },
colors: gizmoColors,
},
};
export default manifest;
+545
View File
@@ -0,0 +1,545 @@
/* Wireframe "Viewport" — an untextured 3D-modeling viewport. Surfaces are
unshaded mesh objects: flat fills, thin vector edges, vertex dots at the
four corners. Selection follows the classic viewport convention: the
user's accent as a 1px outline + accent vertex dots + a light accent
wash. Isometric-cube divider motif; mono annotation labels (JetBrains
Mono) against clean, heavier Inter headings. */
/* ---------- 1. Tokens, any scheme ---------- */
[data-theme="wireframe"][data-theme] {
--mantine-font-family: "Inter", sans-serif;
--mantine-font-family-headings: "Inter", sans-serif;
--mantine-font-family-monospace: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
--mantine-heading-font-weight: 800;
--mantine-radius-default: 3px;
--mantine-radius-xs: 2px;
--mantine-radius-sm: 3px;
--mantine-radius-md: 4px;
--mantine-radius-lg: 4px;
--mantine-radius-xl: 6px;
--mantine-shadow-xs: none;
--mantine-shadow-sm: none;
--mantine-shadow-md: none;
--mantine-shadow-lg: none;
--mantine-shadow-xl: none;
/* square viewport avatar, vertex-dotted corners applied via .flxn-avatar-frame */
--flxn-avatar-radius: 3px;
--flxn-badge-radius: 3px;
}
/* ---------- 2. Per-scheme tokens ---------- */
[data-theme="wireframe"][data-theme][data-mantine-color-scheme="light"] {
/* thin vector edge ink + vertex-dot color */
--wf-edge: #445;
--wf-edge-dim: rgba(68, 68, 85, 0.32);
--wf-vertex: #445;
--wf-surface: #ffffff;
--wf-panel: #f8f9fa;
/* the user's accent, kept legible as ink on the mesh */
--wf-accent-ink: var(--mantine-primary-color-6);
--wf-cube-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpolygon points='12,2 20.7,7 20.7,17 12,22 3.3,17 3.3,7' fill='none' stroke='%23445' stroke-width='1.5'/%3E%3Cpath d='M12 12 L12 2 M12 12 L3.3 17 M12 12 L20.7 17' stroke='%23445' stroke-width='1.5' fill='none'/%3E%3C/svg%3E");
--flxn-avatar-border: 1px solid var(--wf-edge);
--mantine-color-body: #f2f3f5;
--mantine-color-text: #20242a;
--mantine-color-bright: #0e1013;
--mantine-color-dimmed: #55606b;
--mantine-color-anchor: var(--wf-accent-ink);
--mantine-color-error: #d0231f;
--mantine-color-placeholder: #7d8590;
--mantine-color-default: #ffffff;
--mantine-color-default-hover: #eaecef;
--mantine-color-default-color: #20242a;
--mantine-color-default-border: rgba(68, 68, 85, 0.45);
/* cool viewport gray ramp */
--mantine-color-gray-0: #f8f9fa;
--mantine-color-gray-1: #eef0f2;
--mantine-color-gray-2: #e2e5e9;
--mantine-color-gray-3: #d1d5db;
--mantine-color-gray-4: #b7bcc4;
--mantine-color-gray-5: #9aa1a8;
--mantine-color-gray-6: #7d8590;
--mantine-color-gray-7: #5f666f;
--mantine-color-gray-8: #454b52;
--mantine-color-gray-9: #2c3136;
/* app tokens */
--flxn-gold: #9a6700;
--flxn-silver: #5f666f;
--flxn-bronze: #a05a1e;
--flxn-toast-success: #0e7d3f;
--flxn-toast-error: #d0231f;
--flxn-podium-1-bg: rgba(154, 103, 0, 0.06);
--flxn-podium-1-border: rgba(154, 103, 0, 0.5);
--flxn-podium-1-label: #855800;
--flxn-podium-2-bg: rgba(95, 102, 111, 0.07);
--flxn-podium-2-border: rgba(95, 102, 111, 0.5);
--flxn-podium-2-label: #4d545c;
--flxn-podium-3-bg: rgba(160, 90, 30, 0.06);
--flxn-podium-3-border: rgba(160, 90, 30, 0.45);
--flxn-podium-3-label: #a05a1e;
--flxn-showcase-bg: #ffffff;
--flxn-overlay: rgba(15, 17, 20, 0.4);
--flxn-dotgrid-color: rgba(68, 68, 85, 0.28);
--flxn-pop-shadow: rgba(0, 0, 0, 0);
}
[data-theme="wireframe"][data-theme][data-mantine-color-scheme="dark"] {
/* pale vector edge on the night viewport */
--wf-edge: #aab2bc;
--wf-edge-dim: rgba(170, 178, 188, 0.3);
--wf-vertex: #aab2bc;
--wf-surface: #1d2024;
--wf-panel: #131518;
--wf-accent-ink: var(--mantine-primary-color-4);
--wf-cube-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpolygon points='12,2 20.7,7 20.7,17 12,22 3.3,17 3.3,7' fill='none' stroke='%23aab2bc' stroke-width='1.5'/%3E%3Cpath d='M12 12 L12 2 M12 12 L3.3 17 M12 12 L20.7 17' stroke='%23aab2bc' stroke-width='1.5' fill='none'/%3E%3C/svg%3E");
--flxn-avatar-border: 1px solid var(--wf-edge);
--mantine-color-body: #17191c;
--mantine-color-text: #e4e6e9;
--mantine-color-bright: #ffffff;
--mantine-color-dimmed: #9aa1a8;
--mantine-color-anchor: var(--wf-accent-ink);
--mantine-color-error: #ff6b6b;
--mantine-color-placeholder: #6e747b;
--mantine-color-default: #1d2024;
--mantine-color-default-hover: #232629;
--mantine-color-default-color: #e4e6e9;
--mantine-color-default-border: rgba(170, 178, 188, 0.32);
/* steel-cast dark ramp, tuned to the night viewport body */
--mantine-color-dark-0: #eef0f2;
--mantine-color-dark-1: #d6dadd;
--mantine-color-dark-2: #b0b7bd;
--mantine-color-dark-3: #868f97;
--mantine-color-dark-4: #5b636b;
--mantine-color-dark-5: #383d43;
--mantine-color-dark-6: #232629;
--mantine-color-dark-7: #17191c;
--mantine-color-dark-8: #131518;
--mantine-color-dark-9: #0d0f11;
/* app tokens */
--flxn-gold: #ffc861;
--flxn-silver: #c3cad1;
--flxn-bronze: #e0a06a;
--flxn-toast-success: #4bd08a;
--flxn-toast-error: #ff6b6b;
--flxn-podium-1-bg: rgba(255, 200, 97, 0.08);
--flxn-podium-1-border: rgba(255, 200, 97, 0.5);
--flxn-podium-1-label: #ffc861;
--flxn-podium-2-bg: rgba(195, 202, 209, 0.08);
--flxn-podium-2-border: rgba(195, 202, 209, 0.45);
--flxn-podium-2-label: #c3cad1;
--flxn-podium-3-bg: rgba(224, 160, 106, 0.08);
--flxn-podium-3-border: rgba(224, 160, 106, 0.45);
--flxn-podium-3-label: #e0a06a;
--flxn-showcase-bg: #1d2024;
--flxn-overlay: rgba(0, 0, 0, 0.6);
--flxn-dotgrid-color: rgba(170, 178, 188, 0.16);
--flxn-pop-shadow: rgba(0, 0, 0, 0);
}
/* ---------- 3. Decoration: static perspective floor-grid hint ---------- */
[data-theme="wireframe"] body::before {
content: "";
position: fixed;
left: -30vw;
right: -30vw;
bottom: -4vh;
height: 26vh;
z-index: 9998;
pointer-events: none;
opacity: 0.22;
background-image:
repeating-linear-gradient(90deg, var(--wf-edge) 0 1px, transparent 1px 64px),
repeating-linear-gradient(0deg, var(--wf-edge) 0 1px, transparent 1px 32px);
transform: perspective(280px) rotateX(62deg);
transform-origin: 50% 100%;
-webkit-mask-image: linear-gradient(to top, #000 45%, transparent 100%);
mask-image: linear-gradient(to top, #000 45%, transparent 100%);
}
/* ---------- 4. Mesh surfaces: flat fill, thin edge, vertex dots ---------- */
[data-theme="wireframe"] .mantine-Paper-root,
[data-theme="wireframe"] .mantine-Card-root,
[data-theme="wireframe"] .flxn-tile,
[data-theme="wireframe"] .flxn-match-card {
position: relative;
background-color: var(--wf-surface);
border: 1px solid var(--wf-edge-dim);
box-shadow: none;
}
[data-theme="wireframe"] .mantine-Card-root,
[data-theme="wireframe"] .flxn-tile,
[data-theme="wireframe"] .flxn-match-card {
border-color: var(--wf-edge);
}
[data-theme="wireframe"] .mantine-Card-root::after,
[data-theme="wireframe"] .flxn-tile::after,
[data-theme="wireframe"] .flxn-match-card::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
background-repeat: no-repeat;
background-image:
radial-gradient(var(--wf-vertex) 0%, var(--wf-vertex) 100%),
radial-gradient(var(--wf-vertex) 0%, var(--wf-vertex) 100%),
radial-gradient(var(--wf-vertex) 0%, var(--wf-vertex) 100%),
radial-gradient(var(--wf-vertex) 0%, var(--wf-vertex) 100%);
background-size: 4px 4px;
background-position:
-2px -2px,
calc(100% + 2px) -2px,
-2px calc(100% + 2px),
calc(100% + 2px) calc(100% + 2px);
}
[data-theme="wireframe"] .mantine-Divider-root,
[data-theme="wireframe"] .mantine-Divider-root::before,
[data-theme="wireframe"] .mantine-Divider-root::after {
--divider-color: var(--wf-edge-dim);
}
/* isometric-cube divider motif on labeled dividers */
[data-theme="wireframe"] .mantine-Divider-label {
display: inline-flex;
align-items: center;
gap: 0.4em;
font-family: var(--mantine-font-family-monospace);
text-transform: uppercase;
letter-spacing: 0.07em;
font-size: 0.72rem;
color: var(--mantine-color-dimmed);
}
[data-theme="wireframe"] .mantine-Divider-label::before {
content: "";
display: inline-block;
width: 11px;
height: 11px;
flex: none;
background-image: var(--wf-cube-icon);
background-repeat: no-repeat;
background-size: contain;
}
/* ---------- 5. Annotation type: mono, uppercase, tracked ---------- */
[data-theme="wireframe"] .mantine-InputWrapper-label,
[data-theme="wireframe"] .mantine-Badge-label,
[data-theme="wireframe"] .mantine-Tabs-tab,
[data-theme="wireframe"] .mantine-SegmentedControl-label,
[data-theme="wireframe"] .mantine-Notification-title,
[data-theme="wireframe"] .mantine-Modal-title,
[data-theme="wireframe"] .mantine-Menu-label,
[data-theme="wireframe"] .mantine-Alert-title,
[data-theme="wireframe"] .mantine-Tooltip-tooltip {
font-family: var(--mantine-font-family-monospace);
text-transform: uppercase;
letter-spacing: 0.06em;
}
[data-theme="wireframe"] .mantine-InputWrapper-label {
font-size: 0.72rem;
font-weight: 600;
color: var(--mantine-color-dimmed);
}
/* ---------- 6. Controls: outlined technical boxes ---------- */
[data-theme="wireframe"] .mantine-Button-root {
font-family: var(--mantine-font-family-monospace);
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 600;
box-shadow: none;
}
[data-theme="wireframe"] .mantine-Button-root[data-variant="default"],
[data-theme="wireframe"] .mantine-ActionIcon-root[data-variant="default"] {
background-color: var(--wf-surface);
border: 1px solid var(--wf-edge);
}
[data-theme="wireframe"] .mantine-Button-root[data-variant="filled"],
[data-theme="wireframe"] .mantine-ActionIcon-root[data-variant="filled"] {
border: 1px solid var(--mantine-primary-color-filled);
}
[data-theme="wireframe"] .mantine-Input-input {
background-color: var(--wf-surface);
border-color: var(--wf-edge-dim);
}
[data-theme="wireframe"] .mantine-Input-input:focus,
[data-theme="wireframe"] .mantine-Input-input:focus-within {
border-color: var(--mantine-primary-color-filled);
outline: 1px solid var(--mantine-primary-color-filled);
outline-offset: -1px;
}
[data-theme="wireframe"] .mantine-Checkbox-input {
border-color: var(--wf-edge);
background-color: var(--wf-surface);
}
[data-theme="wireframe"] .mantine-Checkbox-input:checked {
background-color: var(--mantine-primary-color-filled);
border-color: var(--mantine-primary-color-filled);
}
[data-theme="wireframe"] .mantine-Switch-track {
border-radius: 2px;
border: 1px solid var(--wf-edge);
background-color: var(--wf-surface);
}
[data-theme="wireframe"] .mantine-Switch-thumb {
border-radius: 1px;
}
[data-theme="wireframe"] .mantine-SegmentedControl-root {
background-color: var(--wf-panel);
border: 1px solid var(--wf-edge-dim);
}
[data-theme="wireframe"] .mantine-SegmentedControl-indicator {
background-color: var(--mantine-primary-color-filled);
box-shadow: none;
}
[data-theme="wireframe"] .mantine-SegmentedControl-label[data-active] {
color: var(--mantine-primary-color-contrast);
}
[data-theme="wireframe"] .mantine-Badge-root {
box-shadow: none;
}
[data-theme="wireframe"] .mantine-Badge-root[data-variant="light"],
[data-theme="wireframe"] .mantine-Badge-root[data-variant="default"],
[data-theme="wireframe"] .mantine-Badge-root[data-variant="outline"] {
border: 1px solid color-mix(in srgb, currentColor 55%, transparent);
}
[data-theme="wireframe"] .mantine-Progress-root {
border: 1px solid var(--wf-edge-dim);
background-color: var(--wf-panel);
}
[data-theme="wireframe"] .mantine-Avatar-root,
[data-theme="wireframe"] .mantine-Avatar-root .mantine-Avatar-image,
[data-theme="wireframe"] .mantine-ThemeIcon-root {
border-radius: 3px;
}
/* selection: accent 1px ring on the highlighted combobox option */
[data-theme="wireframe"] [data-combobox-selected] {
box-shadow: inset 0 0 0 1px var(--mantine-primary-color-filled);
}
/* ---------- 7. Overlays: opaque panels, no shadow ---------- */
[data-theme="wireframe"] .mantine-Modal-content {
background-color: var(--wf-surface);
border: 1px solid var(--wf-edge);
box-shadow: none;
}
[data-theme="wireframe"] .mantine-Modal-header {
background-color: var(--wf-surface);
border-bottom: 1px solid var(--wf-edge-dim);
}
[data-theme="wireframe"] .mantine-Popover-dropdown,
[data-theme="wireframe"] .mantine-Menu-dropdown,
[data-theme="wireframe"] .mantine-Combobox-dropdown {
background-color: var(--wf-surface);
border: 1px solid var(--wf-edge);
box-shadow: none;
}
[data-theme="wireframe"] .mantine-Tooltip-tooltip {
font-size: 0.68rem;
background-color: var(--wf-panel);
color: var(--mantine-color-text);
border: 1px solid var(--wf-edge);
box-shadow: none;
}
[data-theme="wireframe"] .mantine-Notification-root {
background-color: var(--wf-surface);
border: 1px solid var(--wf-edge);
box-shadow: none;
}
[data-theme="wireframe"] .mantine-Alert-root {
border: 1px solid color-mix(in srgb, currentColor 40%, transparent);
box-shadow: none;
}
/* ---------- 8. Shell ---------- */
[data-theme="wireframe"] .mantine-AppShell-root {
--app-shell-border-color: var(--wf-edge-dim);
}
[data-theme="wireframe"] .mantine-AppShell-header,
[data-theme="wireframe"] .mantine-AppShell-navbar {
background-color: var(--mantine-color-body);
border-color: var(--wf-edge-dim);
}
/* ---------- 9. Selection convention: accent outline + wash + vertex ---------- */
[data-theme="wireframe"] .mantine-NavLink-root[data-active] {
background-color: var(--mantine-primary-color-light);
color: var(--mantine-primary-color-light-color);
box-shadow: inset 0 0 0 1px var(--mantine-primary-color-filled);
}
[data-theme="wireframe"] .mantine-NavLink-root[data-active] .mantine-NavLink-label,
[data-theme="wireframe"] .mantine-NavLink-root[data-active] .mantine-NavLink-section {
color: var(--mantine-primary-color-light-color);
}
/* ---------- 10. App hooks: viewport language ---------- */
/* tab strip: resting stays flat; docked = title strip with heavy edge rule */
[data-theme="wireframe"] .flxn-tabs[data-stuck] {
border-bottom: 2px solid var(--wf-edge);
}
[data-theme="wireframe"] .flxn-tab.flxn-tab {
font-family: var(--mantine-font-family-monospace);
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 0.75rem;
color: var(--mantine-color-dimmed);
}
[data-theme="wireframe"] .flxn-tab.flxn-tab[data-active] {
color: var(--wf-accent-ink);
font-weight: 700;
}
[data-theme="wireframe"] .flxn-tabs-indicator.flxn-tabs-indicator {
border-bottom: 2px solid var(--mantine-primary-color-filled);
padding-inline: 0.5rem;
}
/* nav tiles: mesh surface (border + vertex dots from rule 4); selecting them
(hover/focus) takes on the accent selection convention */
[data-theme="wireframe"] .flxn-tile-icon {
color: var(--wf-accent-ink);
}
@media (hover: hover) {
[data-theme="wireframe"] .flxn-tile:hover {
border-color: var(--mantine-primary-color-filled);
background-color: var(--mantine-primary-color-light);
}
[data-theme="wireframe"] .flxn-tile:hover::after {
background-image:
radial-gradient(var(--mantine-primary-color-filled) 0%, var(--mantine-primary-color-filled) 100%),
radial-gradient(var(--mantine-primary-color-filled) 0%, var(--mantine-primary-color-filled) 100%),
radial-gradient(var(--mantine-primary-color-filled) 0%, var(--mantine-primary-color-filled) 100%),
radial-gradient(var(--mantine-primary-color-filled) 0%, var(--mantine-primary-color-filled) 100%);
}
/* accent icon over the light accent wash needs the wash's own on-color */
[data-theme="wireframe"] .flxn-tile:hover .flxn-tile-icon {
color: var(--mantine-primary-color-light-color);
}
}
[data-theme="wireframe"] .flxn-tile:focus-visible {
border-color: var(--mantine-primary-color-filled);
background-color: var(--mantine-primary-color-light);
outline: none;
}
[data-theme="wireframe"] .flxn-tile:focus-visible .flxn-tile-icon {
color: var(--mantine-primary-color-light-color);
}
/* "see all": annotation chip */
[data-theme="wireframe"] .flxn-see-all.flxn-see-all {
font-family: var(--mantine-font-family-monospace);
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 0.7rem;
font-weight: 600;
border: 1px solid var(--wf-edge-dim);
background-color: transparent;
color: var(--wf-accent-ink);
}
@media (hover: hover) {
[data-theme="wireframe"] .flxn-see-all.flxn-see-all:hover {
background-color: var(--mantine-primary-color-light);
border-color: var(--mantine-primary-color-filled);
color: var(--mantine-primary-color-light-color);
text-decoration: none;
}
}
/* reaction tray: properties-panel strip under the match-card mesh object */
[data-theme="wireframe"] .flxn-reaction-row.flxn-reaction-row {
background-color: var(--wf-panel);
border: 1px solid var(--wf-edge-dim);
font-family: var(--mantine-font-family-monospace);
}
/* square avatar, vertex-dotted corners just outside the frame */
[data-theme="wireframe"] .flxn-avatar-frame {
position: relative;
}
[data-theme="wireframe"] .flxn-avatar-frame::after {
content: "";
position: absolute;
inset: -2px;
pointer-events: none;
background-repeat: no-repeat;
background-image:
radial-gradient(var(--wf-vertex) 0%, var(--wf-vertex) 100%),
radial-gradient(var(--wf-vertex) 0%, var(--wf-vertex) 100%),
radial-gradient(var(--wf-vertex) 0%, var(--wf-vertex) 100%),
radial-gradient(var(--wf-vertex) 0%, var(--wf-vertex) 100%);
background-size: 4px 4px;
background-position: 0 0, 100% 0, 0 100%, 100% 100%;
}
/* press feedback: a light scale-down + accent selection-ring flash */
[data-theme="wireframe"] .flxn-press {
outline: 1px solid transparent;
outline-offset: -1px;
transition: transform 150ms cubic-bezier(0.32, 0.72, 0, 1), outline-color 150ms ease;
}
[data-theme="wireframe"] .flxn-press:active:not(:disabled):not([data-disabled]):not([data-loading]) {
transform: scale(0.97);
outline-color: var(--mantine-primary-color-filled);
}
@media (prefers-reduced-motion: reduce) {
[data-theme="wireframe"] .flxn-press,
[data-theme="wireframe"] .flxn-press:active:not(:disabled):not([data-disabled]):not([data-loading]) {
transition: none;
transform: none;
}
}
-2
View File
@@ -60,7 +60,6 @@ const verifySuperTokensSession = async (request: Request) => {
const userAuthId = session?.userId;
if (!userAuthId || !session) {
logger.info("No authenticated user");
return { context: { userAuthId: null, roles: [] } };
}
@@ -109,7 +108,6 @@ export const getSessionContextCore = async (
}
if (!session.context.userAuthId) {
logger.error("Unauthenticated user", session.context);
throw new Error("Unauthenticated");
}