← back to CelebritySignatures
apps/mobile/ui/glass.tsx
170 lines
import React from 'react';
import {
ActivityIndicator, Platform, Pressable, StyleSheet, Text, View, ViewStyle,
} from 'react-native';
import { BlurView } from 'expo-blur';
import { LinearGradient } from 'expo-linear-gradient';
// Liquid Glass primitives (reused from the Charge & Explore app, recolored to a
// white gallery-wall palette for the signatures gallery — Steve 2026-08-05:
// "make all white not black"). Every surface is a real BlurView refracting the
// paper-white scene behind it, with a specular top edge so panels read as glass.
// Android's BlurView needs the experimental method or it silently renders a slab.
// NOTE: `ink` stays the dark museum charcoal — it is the TEXT color and the
// text-on-gold color; `accentSoft` (once parchment used as light-on-dark text)
// is now the dark warm body-text color so every screen inherits readable text.
export const GLASS = {
ink: '#141210',
inkDeep: '#faf8f2', // scene mid-tone (was near-black)
emerald: '#f1ebdd', // warm paper tone closing the scene gradient
accent: '#9a7b3f', // museum gold, deepened to read on white
accentSoft: '#2b241a', // primary text — warm near-black on white
textDim: '#7a715f', // muted text on white
edge: 'rgba(20,18,16,0.16)',
edgeFaint: 'rgba(20,18,16,0.09)',
sheen: 'rgba(255,255,255,0.65)',
} as const;
const blurProps = Platform.select({
android: { experimentalBlurMethod: 'dimezisBlurView' as const },
default: {},
});
/** Full-screen layered gradient the glass refracts. Place once at scene root. */
export function GlassScene({ children, style }: { children: React.ReactNode; style?: ViewStyle }) {
return (
<LinearGradient
colors={['#ffffff', GLASS.inkDeep, GLASS.emerald]}
locations={[0, 0.55, 1]}
start={{ x: 0.1, y: 0 }}
end={{ x: 0.9, y: 1 }}
style={[styles.scene, style]}
>
{/* off-center warm-gold glow orb gives the blur a light source to bend */}
<LinearGradient
colors={['rgba(201,168,106,0.18)', 'rgba(201,168,106,0)']}
style={styles.orb}
pointerEvents="none"
/>
{children}
</LinearGradient>
);
}
/** Frosted panel — the base glass slab (cards, hero, sections). */
export function GlassPanel({
children, style, intensity = 28, tint = 'light',
}: {
children: React.ReactNode; style?: ViewStyle; intensity?: number;
tint?: 'dark' | 'light';
}) {
return (
<View style={[styles.panelClip, style]}>
<BlurView intensity={intensity} tint={tint} style={StyleSheet.absoluteFill} {...blurProps} />
<View style={styles.sheenTop} pointerEvents="none" />
<View style={styles.panelBody}>{children}</View>
</View>
);
}
/** Capsule glass button. solid=accent-filled call to action; else clear glass. */
export function GlassButton({
label, onPress, solid = false, busy = false, disabled = false,
}: {
label: string; onPress: () => void; solid?: boolean; busy?: boolean; disabled?: boolean;
}) {
return (
<Pressable onPress={onPress} disabled={disabled || busy}
style={({ pressed }) => [styles.btnClip, pressed && styles.btnPressed]}>
<BlurView intensity={solid ? 50 : 26} tint="light" style={StyleSheet.absoluteFill} {...blurProps} />
{solid && (
<LinearGradient
colors={['rgba(201,168,106,0.95)', 'rgba(176,142,84,0.92)']}
style={StyleSheet.absoluteFill}
/>
)}
<View style={styles.sheenTop} pointerEvents="none" />
{busy
? <ActivityIndicator color={solid ? GLASS.ink : GLASS.accent} />
: <Text style={[styles.btnText, solid ? styles.btnTextSolid : styles.btnTextGlass]}>{label}</Text>}
</Pressable>
);
}
/** Small glass pill (score chips, status tags). tintColor washes the glass. */
export function GlassPill({
children, tintColor, style,
}: { children: React.ReactNode; tintColor?: string; style?: ViewStyle }) {
return (
<View style={[styles.pillClip, style]}>
<BlurView intensity={30} tint="light" style={StyleSheet.absoluteFill} {...blurProps} />
{tintColor && <View style={[StyleSheet.absoluteFill, { backgroundColor: tintColor, opacity: 0.55 }]} />}
<View style={styles.sheenTop} pointerEvents="none" />
<View style={styles.pillBody}>{children}</View>
</View>
);
}
/** Modal top chrome — floating glass bar with Done + centered title. */
export function GlassBar({
title, onDone, light = false,
}: { title: string; onDone: () => void; light?: boolean }) {
return (
<View style={styles.barClip}>
<BlurView intensity={40} tint="light" style={StyleSheet.absoluteFill} {...blurProps} />
<View style={styles.sheenTop} pointerEvents="none" />
<View style={styles.barRow}>
<Pressable onPress={onDone} hitSlop={12}>
<Text style={[styles.barDone, light && styles.barDoneLight]}>Done</Text>
</Pressable>
<Text style={[styles.barTitle, light && styles.barTitleLight]}>{title}</Text>
<View style={{ width: 44 }} />
</View>
</View>
);
}
const styles = StyleSheet.create({
scene: { flex: 1 },
orb: {
position: 'absolute', top: -120, right: -100,
width: 340, height: 340, borderRadius: 170,
},
panelClip: {
borderRadius: 22, overflow: 'hidden',
borderWidth: StyleSheet.hairlineWidth, borderColor: GLASS.edgeFaint,
},
panelBody: { padding: 18 },
sheenTop: {
position: 'absolute', top: 0, left: 0, right: 0, height: 1.5,
backgroundColor: GLASS.edge,
},
btnClip: {
borderRadius: 999, overflow: 'hidden',
borderWidth: StyleSheet.hairlineWidth, borderColor: GLASS.edge,
paddingVertical: 16, alignItems: 'center', justifyContent: 'center',
},
btnPressed: { transform: [{ scale: 0.98 }], opacity: 0.9 },
btnText: { fontSize: 17, fontWeight: '700', letterSpacing: 0.2 },
btnTextSolid: { color: GLASS.ink },
btnTextGlass: { color: GLASS.accentSoft },
pillClip: {
borderRadius: 999, overflow: 'hidden',
borderWidth: StyleSheet.hairlineWidth, borderColor: GLASS.edgeFaint,
},
pillBody: { paddingHorizontal: 10, paddingVertical: 5, alignItems: 'center' },
barClip: {
marginHorizontal: 12, marginTop: 6, borderRadius: 999, overflow: 'hidden',
borderWidth: StyleSheet.hairlineWidth, borderColor: GLASS.edgeFaint,
},
barRow: {
flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between',
paddingHorizontal: 18, paddingVertical: 12,
},
barDone: { color: GLASS.accent, fontSize: 16, fontWeight: '600', width: 44 },
barDoneLight: { color: '#141210' },
barTitle: { fontSize: 16, fontWeight: '700', color: GLASS.ink },
barTitleLight: { color: '#141210' },
});