← 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' },
});