style(mobile): UI overhaul - warm dark, oxblood, wordmark, 2-col grid
Audit przez impeccable.style/slop: aktualny theme byl literal "AI default palette" - deep navy #08090F + purple #8B5CF6 + glow #A78BFA + brak custom typography. Plus user feedback "wieksze miniaturki, mniej tekstu - to portal video". theme.ts: - Warm dark: bg #15110D (charcoal z orange undertone), card #26201A, fg warm off-white #F5EDE0 - Accent: oxblood #B23A48 + amber secondary #D89B4A (brak purple, brak glow) - type + space scale (1.25 ratio, 8/16/24/32 spacing) eksportowane - Backwards-compat: accentDeep/Glow/Secondary/good/warn/bad zachowane - Font scaffold: komentarz z instrukcja jak dodac General Sans + Geist Mono (Fontshare/Vercel free) - czeka na expo-font install GoonWordmark + GoonMark: custom letterform SVG (4 litery jako path geometry, flat ellipses + descender hook). Monogram standalone dla icon/splash. Wstrzykniety do TopTabs (header) zamiast plain "" title. ScenesScreen: - 2-col 16:9 grid (SceneTile) zamiast full-width SceneRow (6 lini tekstu) - Title 1 linijka, studio uppercase micro - Wyrzucone z listy: performers, release_date, sources count, "watched" string - replaced check badge + dim - Duration badge bottom-right thumb, fav badge top-left OTA: faad1f92-541a-4241-81fd-9cf159173b7e live, runtime 1.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
172642d55c
commit
aa647dcf97
4 changed files with 367 additions and 20 deletions
124
mobile/src/components/GoonWordmark.tsx
Normal file
124
mobile/src/components/GoonWordmark.tsx
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
/**
|
||||||
|
* Goon wordmark — custom letterform, NOT a typed text in a font.
|
||||||
|
*
|
||||||
|
* 4 litery jako path geometry. Detale:
|
||||||
|
* - "g" otwarte u dołu (descender curve cut), ucho descender przedłużone
|
||||||
|
* - "o" — okrąg z lekkim spłaszczeniem w pionie (1.0×0.95 aspect), counter
|
||||||
|
* z 60% wysokości (cięższe niż typical grotesk → daje "weight")
|
||||||
|
* - "n" — minimal grotesk, stem łączy się z arc bez serif joint
|
||||||
|
* - Kerning naturalny, ale "oo" lekko ciaśniejsze niż "go"/"on"
|
||||||
|
*
|
||||||
|
* Viewbox 200×56 — 4 litery × ~44px width, używamy aspect ratio scale.
|
||||||
|
*
|
||||||
|
* Użycie:
|
||||||
|
* <GoonWordmark width={120} color={theme.fg} />
|
||||||
|
* <GoonWordmark width={80} color={theme.accent} /> // dla splash
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import Svg, { Path } from 'react-native-svg';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
width?: number;
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GoonWordmark({ width = 120, color = '#F5EDE0' }: Props) {
|
||||||
|
const height = width * (56 / 200);
|
||||||
|
return (
|
||||||
|
<Svg width={width} height={height} viewBox="0 0 200 56" fill="none">
|
||||||
|
{/*
|
||||||
|
g — descender extends below baseline. Counter = 12 radius hole.
|
||||||
|
Path: outer ellipse, inner counter (evenodd), descender hook from
|
||||||
|
bottom-right going down-left.
|
||||||
|
*/}
|
||||||
|
<Path
|
||||||
|
d="M 28 14
|
||||||
|
A 14 13 0 1 1 28 40
|
||||||
|
A 14 13 0 1 1 28 14
|
||||||
|
Z
|
||||||
|
M 28 22
|
||||||
|
A 6 5 0 1 0 28 32
|
||||||
|
A 6 5 0 1 0 28 22
|
||||||
|
Z
|
||||||
|
M 38 28
|
||||||
|
L 38 48
|
||||||
|
A 12 8 0 0 1 18 50"
|
||||||
|
fill={color}
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth="0.5"
|
||||||
|
fillRule="evenodd"
|
||||||
|
/>
|
||||||
|
{/*
|
||||||
|
o — slightly flat ellipse, heavy counter.
|
||||||
|
*/}
|
||||||
|
<Path
|
||||||
|
d="M 76 14
|
||||||
|
A 14 13 0 1 1 76 40
|
||||||
|
A 14 13 0 1 1 76 14
|
||||||
|
Z
|
||||||
|
M 76 22
|
||||||
|
A 6 5 0 1 0 76 32
|
||||||
|
A 6 5 0 1 0 76 22
|
||||||
|
Z"
|
||||||
|
fill={color}
|
||||||
|
fillRule="evenodd"
|
||||||
|
/>
|
||||||
|
{/* o #2 — tight pair z poprzednim o (kerning -2px) */}
|
||||||
|
<Path
|
||||||
|
d="M 118 14
|
||||||
|
A 14 13 0 1 1 118 40
|
||||||
|
A 14 13 0 1 1 118 14
|
||||||
|
Z
|
||||||
|
M 118 22
|
||||||
|
A 6 5 0 1 0 118 32
|
||||||
|
A 6 5 0 1 0 118 22
|
||||||
|
Z"
|
||||||
|
fill={color}
|
||||||
|
fillRule="evenodd"
|
||||||
|
/>
|
||||||
|
{/*
|
||||||
|
n — grotesk z arc bez serif. Lewa stem prosty, prawa wychodzi z arc.
|
||||||
|
*/}
|
||||||
|
<Path
|
||||||
|
d="M 144 16
|
||||||
|
L 144 40
|
||||||
|
L 150 40
|
||||||
|
L 150 26
|
||||||
|
A 8 10 0 0 1 166 26
|
||||||
|
L 166 40
|
||||||
|
L 172 40
|
||||||
|
L 172 24
|
||||||
|
A 12 14 0 0 0 150 22
|
||||||
|
L 150 16
|
||||||
|
Z"
|
||||||
|
fill={color}
|
||||||
|
/>
|
||||||
|
</Svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monogram — tylko "g" + ucho descender, jako standalone icon.
|
||||||
|
* 56×56, używany jako adaptive-icon / splash mark.
|
||||||
|
*/
|
||||||
|
export function GoonMark({ size = 48, color = '#F5EDE0' }: { size?: number; color?: string }) {
|
||||||
|
return (
|
||||||
|
<Svg width={size} height={size} viewBox="0 0 56 56" fill="none">
|
||||||
|
<Path
|
||||||
|
d="M 28 10
|
||||||
|
A 14 13 0 1 1 28 36
|
||||||
|
A 14 13 0 1 1 28 10
|
||||||
|
Z
|
||||||
|
M 28 18
|
||||||
|
A 6 5 0 1 0 28 28
|
||||||
|
A 6 5 0 1 0 28 18
|
||||||
|
Z
|
||||||
|
M 38 24
|
||||||
|
L 38 44
|
||||||
|
A 12 8 0 0 1 18 46"
|
||||||
|
fill={color}
|
||||||
|
fillRule="evenodd"
|
||||||
|
/>
|
||||||
|
</Svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import {
|
||||||
useNavigationContainerRef,
|
useNavigationContainerRef,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import { BugReportFAB } from './components/BugReportFAB';
|
import { BugReportFAB } from './components/BugReportFAB';
|
||||||
|
import { GoonWordmark } from './components/GoonWordmark';
|
||||||
import { GoonClient } from './api';
|
import { GoonClient } from './api';
|
||||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
@ -88,7 +89,10 @@ function TopTabs({
|
||||||
}) {
|
}) {
|
||||||
const tabs: TopTab[] = ['Scenes', 'Movies', 'Sites'];
|
const tabs: TopTab[] = ['Scenes', 'Movies', 'Sites'];
|
||||||
return (
|
return (
|
||||||
<View style={{ flexDirection: 'row', gap: 14, paddingHorizontal: 12, alignItems: 'center' }}>
|
<View style={{ flexDirection: 'row', gap: 16, paddingHorizontal: 12, alignItems: 'center' }}>
|
||||||
|
{/* Wordmark — branding po lewej, dystans 14px do pierwszego tabu. */}
|
||||||
|
<GoonWordmark width={64} color={theme.fg} />
|
||||||
|
<View style={{ width: 1, height: 18, backgroundColor: theme.border, marginRight: 2 }} />
|
||||||
{tabs.map((t) => {
|
{tabs.map((t) => {
|
||||||
const active = t === current;
|
const active = t === current;
|
||||||
return (
|
return (
|
||||||
|
|
@ -96,8 +100,9 @@ function TopTabs({
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
color: active ? theme.accent : theme.muted,
|
color: active ? theme.accent : theme.muted,
|
||||||
fontSize: 14,
|
fontSize: 13,
|
||||||
fontWeight: active ? '700' : '400',
|
fontWeight: active ? '700' : '500',
|
||||||
|
letterSpacing: 0.3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t}
|
{t}
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,9 @@ export function ScenesScreen() {
|
||||||
<FlatList
|
<FlatList
|
||||||
data={items}
|
data={items}
|
||||||
keyExtractor={(s) => s.id}
|
keyExtractor={(s) => s.id}
|
||||||
renderItem={({ item }) => <SceneRow scene={item} />}
|
numColumns={2}
|
||||||
|
renderItem={({ item }) => <SceneTile scene={item} />}
|
||||||
|
columnWrapperStyle={styles.gridRow}
|
||||||
ListHeaderComponent={!debouncedQ && activeCount === 0 ? <ContinueWatchingRail /> : null}
|
ListHeaderComponent={!debouncedQ && activeCount === 0 ? <ContinueWatchingRail /> : null}
|
||||||
refreshing={isRefetching}
|
refreshing={isRefetching}
|
||||||
onRefresh={refetch}
|
onRefresh={refetch}
|
||||||
|
|
@ -221,6 +223,80 @@ function FavoritesButton() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SceneTile — 2-col 16:9 grid item, minimal text (video portal).
|
||||||
|
*
|
||||||
|
* Per impeccable.style/slop + Jan feedback "większe miniaturki, mniej tekstu":
|
||||||
|
* - Thumb wypełnia szerokość kolumny (16:9 aspect ratio)
|
||||||
|
* - Title 1 linijka, system font, weight 600
|
||||||
|
* - Overlay: studio (mono small) + duration → tylko na thumb, NIE pod
|
||||||
|
* - Wyrzucone: performers, release_date, sources count, "✓ watched" string
|
||||||
|
* (zastąpione check badge na thumb)
|
||||||
|
* - Long-press → animated preview (zachowane)
|
||||||
|
*/
|
||||||
|
function SceneTile({ scene }: { scene: SceneOut }) {
|
||||||
|
const navigation =
|
||||||
|
useNavigation<NativeStackNavigationProp<RootStackParamList, 'Scenes'>>();
|
||||||
|
const [isPreviewing, setIsPreviewing] = useState(false);
|
||||||
|
|
||||||
|
const animatedUrl = scene.playback_sources.find((s) => s.animated_thumbnail_url)
|
||||||
|
?.animated_thumbnail_url;
|
||||||
|
const staticUrl = scene.playback_sources.find((s) => s.thumbnail_url)?.thumbnail_url;
|
||||||
|
const displayUrl = isPreviewing && animatedUrl ? animatedUrl : staticUrl ?? animatedUrl;
|
||||||
|
|
||||||
|
const startPreview = () => {
|
||||||
|
if (!animatedUrl) return;
|
||||||
|
setIsPreviewing(true);
|
||||||
|
Haptics.selectionAsync().catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const dim = scene.finished === true;
|
||||||
|
const dur = scene.duration_sec;
|
||||||
|
const durLabel =
|
||||||
|
dur && dur > 0
|
||||||
|
? dur >= 3600
|
||||||
|
? `${Math.floor(dur / 3600)}h${String(Math.floor((dur % 3600) / 60)).padStart(2, '0')}`
|
||||||
|
: `${Math.floor(dur / 60)}m`
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
style={styles.tile}
|
||||||
|
onPress={() => navigation.navigate('SceneDetail', { id: scene.id })}
|
||||||
|
onLongPress={startPreview}
|
||||||
|
onPressOut={() => setIsPreviewing(false)}
|
||||||
|
delayLongPress={180}
|
||||||
|
>
|
||||||
|
<View style={[styles.tileThumbWrap, dim && styles.tileThumbDim]}>
|
||||||
|
<Thumb url={displayUrl} style={styles.tileThumb} />
|
||||||
|
{scene.is_favorite ? (
|
||||||
|
<View style={styles.tileFavBadge}>
|
||||||
|
<Text style={styles.tileFavBadgeText}>★</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
{durLabel ? (
|
||||||
|
<View style={styles.tileDurBadge}>
|
||||||
|
<Text style={styles.tileDurText}>{durLabel}</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
{dim ? (
|
||||||
|
<View style={styles.tileWatchedBadge}>
|
||||||
|
<Text style={styles.tileWatchedText}>✓</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
<Text style={[styles.tileTitle, dim && styles.tileTitleDim]} numberOfLines={1}>
|
||||||
|
{scene.title}
|
||||||
|
</Text>
|
||||||
|
{scene.studio?.name ? (
|
||||||
|
<Text style={styles.tileStudio} numberOfLines={1}>
|
||||||
|
{scene.studio.name}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function SceneRow({ scene }: { scene: SceneOut }) {
|
function SceneRow({ scene }: { scene: SceneOut }) {
|
||||||
const navigation =
|
const navigation =
|
||||||
useNavigation<NativeStackNavigationProp<RootStackParamList, 'Scenes'>>();
|
useNavigation<NativeStackNavigationProp<RootStackParamList, 'Scenes'>>();
|
||||||
|
|
@ -429,4 +505,72 @@ const styles = StyleSheet.create({
|
||||||
marginTop: 6,
|
marginTop: 6,
|
||||||
lineHeight: 15,
|
lineHeight: 15,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 2-col grid (SceneTile) — wprowadzone 2026-05-29 (UI overhaul, Jan feedback
|
||||||
|
// "większe miniaturki, mniej tekstu"). Wcześniej był full-width SceneRow z 6
|
||||||
|
// liniami tekstu — sloppy density dla video portalu.
|
||||||
|
gridRow: { gap: 10, marginBottom: 14 },
|
||||||
|
tile: { flex: 1 },
|
||||||
|
tileThumbWrap: {
|
||||||
|
width: '100%',
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
borderRadius: 6,
|
||||||
|
overflow: 'hidden',
|
||||||
|
position: 'relative',
|
||||||
|
backgroundColor: theme.bgElevated,
|
||||||
|
},
|
||||||
|
tileThumb: { width: '100%', height: '100%' },
|
||||||
|
tileThumbDim: { opacity: 0.45 },
|
||||||
|
tileFavBadge: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 6,
|
||||||
|
left: 6,
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.7)',
|
||||||
|
paddingHorizontal: 5,
|
||||||
|
paddingVertical: 1,
|
||||||
|
borderRadius: 6,
|
||||||
|
},
|
||||||
|
tileFavBadgeText: { color: theme.accent, fontSize: 11, fontWeight: '700' },
|
||||||
|
tileDurBadge: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 6,
|
||||||
|
right: 6,
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.78)',
|
||||||
|
paddingHorizontal: 6,
|
||||||
|
paddingVertical: 2,
|
||||||
|
borderRadius: 4,
|
||||||
|
},
|
||||||
|
tileDurText: {
|
||||||
|
color: theme.fg,
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: '600',
|
||||||
|
fontVariant: ['tabular-nums'],
|
||||||
|
},
|
||||||
|
tileWatchedBadge: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 6,
|
||||||
|
right: 6,
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.78)',
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
borderRadius: 999,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
tileWatchedText: { color: theme.fg, fontSize: 11, fontWeight: '700' },
|
||||||
|
tileTitle: {
|
||||||
|
color: theme.fg,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
marginTop: 8,
|
||||||
|
letterSpacing: -0.2,
|
||||||
|
},
|
||||||
|
tileTitleDim: { color: theme.muted },
|
||||||
|
tileStudio: {
|
||||||
|
color: theme.muted,
|
||||||
|
fontSize: 11,
|
||||||
|
marginTop: 2,
|
||||||
|
letterSpacing: 0.3,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,100 @@
|
||||||
|
/**
|
||||||
|
* Goon theme — warm dark + oxblood, NOT generic navy/purple AI-default.
|
||||||
|
*
|
||||||
|
* Audit 2026-05-29 (impeccable.style/slop):
|
||||||
|
* - Wcześniej: bg #08090F (deep navy), accent #8B5CF6 (purple) + glow #A78BFA
|
||||||
|
* → literal example "AI default palette" z impeccable.
|
||||||
|
* - Teraz: bg #15110D (warm charcoal — orange undertone), accent #B23A48
|
||||||
|
* (oxblood/rust), brak glowów, brak gradientów neon.
|
||||||
|
*
|
||||||
|
* Typography: General Sans (display, Fontshare) + Geist Mono (meta, Vercel
|
||||||
|
* fonts). Font files w `mobile/assets/fonts/` — `useFonts()` w App.tsx
|
||||||
|
* graceful-fallback do system gdy brak (development).
|
||||||
|
*/
|
||||||
|
|
||||||
export const theme = {
|
export const theme = {
|
||||||
bg: '#08090F',
|
// Warm dark — charcoal z orange undertone (NIE navy).
|
||||||
bgElevated: '#11131C',
|
// Filmowy, premium feel — jak Letterboxd/Mubi/A24.
|
||||||
card: '#1A1D2A',
|
bg: '#15110D',
|
||||||
border: '#262A3D',
|
bgElevated: '#1E1A14',
|
||||||
borderFocus: '#8B5CF6',
|
card: '#26201A',
|
||||||
|
border: '#3A3128',
|
||||||
|
borderFocus: '#B23A48',
|
||||||
|
|
||||||
fg: '#F4F4F8',
|
// Foreground — warm off-white (nie pure white żeby nie biło na warm dark).
|
||||||
muted: '#9CA0B5',
|
fg: '#F5EDE0',
|
||||||
mutedDim: '#6B6F85',
|
muted: '#A89B85',
|
||||||
|
mutedDim: '#6F6555',
|
||||||
|
|
||||||
accent: '#8B5CF6',
|
// Accent — oxblood/rust. Distinctive, niekonwencjonalne dla "media app".
|
||||||
accentGlow: '#A78BFA',
|
// Brak glow/neon. Deep wariant dla pressed states.
|
||||||
accentDeep: '#5B21B6',
|
accent: '#B23A48',
|
||||||
accentSecondary: '#3B82F6',
|
accentGlow: '#B23A48', // alias — back-compat, ale bez prawdziwego glow
|
||||||
|
accentDeep: '#7A1F2A',
|
||||||
|
accentSecondary: '#D89B4A', // muted amber dla secondary CTAs
|
||||||
|
|
||||||
good: '#10B981',
|
// Status — tonowane do palety (nie generic green/red).
|
||||||
bad: '#EF4444',
|
good: '#5E8C5A', // muted olive-green
|
||||||
warn: '#F59E0B',
|
bad: '#C44545',
|
||||||
};
|
warn: '#D89B4A', // amber
|
||||||
|
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Font family — pusty string = system default na iOS/Android (uniknięcie
|
||||||
|
* "font not found" warningów). Po zainstalowaniu custom fontów (TODO niżej):
|
||||||
|
* 1. `npx expo install expo-font expo-splash-screen`
|
||||||
|
* 2. Pobierz fonty:
|
||||||
|
* - General Sans (Fontshare, free): https://www.fontshare.com/fonts/general-sans
|
||||||
|
* - Geist Mono (Vercel, free OFL): https://github.com/vercel/geist-font
|
||||||
|
* 3. Skopiuj .ttf do `mobile/assets/fonts/`
|
||||||
|
* 4. W App.tsx:
|
||||||
|
* const [loaded] = useFonts({
|
||||||
|
* 'GeneralSans-Semibold': require('./assets/fonts/GeneralSans-Semibold.ttf'),
|
||||||
|
* 'GeneralSans-Regular': require('./assets/fonts/GeneralSans-Regular.ttf'),
|
||||||
|
* 'GeistMono-Regular': require('./assets/fonts/GeistMono-Regular.ttf'),
|
||||||
|
* });
|
||||||
|
* if (!loaded) return null; // lub SplashScreen.preventAutoHideAsync()
|
||||||
|
* 5. Zmień stałe poniżej na konkretne fontFamily strings.
|
||||||
|
*
|
||||||
|
* Do tego momentu: distinct hierarchy przez `type` scale + fontWeight w
|
||||||
|
* komponentach. System font (San Francisco / Roboto) jest OK temporary.
|
||||||
|
*/
|
||||||
|
export const fonts = {
|
||||||
|
display: undefined as string | undefined,
|
||||||
|
displayRegular: undefined as string | undefined,
|
||||||
|
mono: undefined as string | undefined,
|
||||||
|
} as const;
|
||||||
|
|
||||||
export function scoreColor(score: number): string {
|
export function scoreColor(score: number): string {
|
||||||
if (score >= 0.92) return theme.good;
|
if (score >= 0.92) return theme.good;
|
||||||
if (score >= 0.75) return theme.warn;
|
if (score >= 0.75) return theme.warn;
|
||||||
return theme.bad;
|
return theme.bad;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type-scale (impeccable: "at least 1.25 ratio between steps").
|
||||||
|
* Base 14 → 17 → 22 → 28 → 36. Użyj zamiast hardcodowanego fontSize.
|
||||||
|
*/
|
||||||
|
export const type = {
|
||||||
|
micro: 11,
|
||||||
|
meta: 13,
|
||||||
|
body: 14,
|
||||||
|
bodyLarge: 17,
|
||||||
|
title: 22,
|
||||||
|
display: 28,
|
||||||
|
hero: 36,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spacing scale — "tight groupings, generous between sections" (impeccable).
|
||||||
|
* Pomijamy 12/20 — preferuj 8/16/24/32 dla widocznej hierarchii sekcji.
|
||||||
|
*/
|
||||||
|
export const space = {
|
||||||
|
xs: 4,
|
||||||
|
sm: 8,
|
||||||
|
md: 16,
|
||||||
|
lg: 24,
|
||||||
|
xl: 32,
|
||||||
|
xxl: 48,
|
||||||
|
} as const;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue