import React, { useEffect, useRef } from "react"; import { Animated, Easing, Platform, StyleSheet, View } from "react-native"; import { useColorScheme } from "nativewind"; import Svg, { Path } from "react-native-svg"; import { ENTROPY_MARK_ASPECT, ENTROPY_MARK_PATH, ENTROPY_MARK_VIEWBOX } from "./entropy-mark"; import { usePalette } from "../utils/palette"; import { DISABLE_BRANDING } from "../flags"; // Web has no native animated module; requesting the native driver there just // warns and falls back to JS. Gate it so the console stays clean. const USE_NATIVE_DRIVER = Platform.OS !== "web"; export interface EntropyBootSplashProps { /** Called once the intro + hold + fade-out finishes. */ onFinish?: () => void; /** Mark height in px. */ size?: number; /** "chroma" (default) shows the faint cyan/red fringe; "mono" is plain. */ variant?: "mono" | "chroma"; /** Override background; defaults to themed near-black / white. */ background?: string; /** ms to hold fully-drifted before fading out. Default 550. */ holdMs?: number; } /** * Company boot splash — the website's logo motion as a launch animation: the * mark fades in flat, the three echoes drift apart + breathe (driven by * EntropyMarkAnimated's `active` state — the same motion source as the * website hover), hold, then the whole thing fades out and calls `onFinish`. * Subtle corporate recognition at launch; each product keeps its own * icon/splash for the OS-level launch screen — this is an in-app overlay. */ export function EntropyBootSplash({ onFinish, size = 96, // `variant` is retained on the props interface for API compatibility but the // current flat-mark splash renders the same regardless, so it isn't read. background, holdMs = 550, }: EntropyBootSplashProps) { const { colorScheme } = useColorScheme(); const isDark = colorScheme === "dark"; const { palette } = usePalette(); // Match the app's themed background exactly (bg-background) so there's no // color seam when the splash dismisses into the screen behind it. const bg = background ?? palette.background; const fade = useRef(new Animated.Value(0)).current; useEffect(() => { // Self-host branding opt-out: skip the splash entirely, let the app proceed. if (DISABLE_BRANDING) { onFinish?.(); return; } const anim = Animated.sequence([ Animated.timing(fade, { toValue: 1, duration: 320, easing: Easing.out(Easing.quad), useNativeDriver: USE_NATIVE_DRIVER, }), // mark's own open-ease (~450ms) + breathing run during the hold Animated.delay(900 + holdMs), Animated.timing(fade, { toValue: 0, duration: 420, easing: Easing.in(Easing.quad), useNativeDriver: USE_NATIVE_DRIVER, }), ]); anim.start(({ finished }) => { if (finished) { onFinish?.(); } }); return () => anim.stop(); // run once on mount // eslint-disable-next-line react-hooks/exhaustive-deps }, []); if (DISABLE_BRANDING) { return null; } return ( {/* Static flat source mark — the topmost layer only, monochromatic to the theme (black in light, white in dark). No echoes, no motion. */} ); } export default EntropyBootSplash;