// Renders one CardSide at a given pixel size by absolutely positioning each // layer using its fractional geometry. Background / text / logo / contact layers // are supported in Phase 1 (Skia is reserved for the Pro shader/particle layers). import { Image, StyleSheet, Text, View } from "react-native"; import { BackgroundConfig, CardSide, ContactLayer, Layer, LogoLayer, ShapeLayer, TextLayer, } from "../../types/card"; import { API_BASE } from "../../api/client"; import { QRCodeView } from "./QRCodeView"; interface Props { side: CardSide; width: number; height: number; /** URL encoded by any `qr` layer on this side (the card's profile URL). */ profileUrl?: string; } function backgroundStyle(bg: BackgroundConfig): { backgroundColor: string } { if (bg.type === "solid" && bg.value) return { backgroundColor: bg.value }; if (bg.type === "gradient" && bg.stops?.length) { // Approximate a gradient's base with its first stop (a real gradient uses // a Skia/expo-linear-gradient fill in the Pro renderer). return { backgroundColor: bg.stops[0].color }; } return { backgroundColor: "#101014" }; } function assetUri(r2Key: string): string { return `${API_BASE}/assets/${r2Key}`; } function LayerView({ layer, width, height, profileUrl, }: { layer: Layer; width: number; height: number; profileUrl?: string; }) { const frame = { position: "absolute" as const, left: layer.x * width, top: layer.y * height, width: layer.width * width, height: layer.height * height, opacity: layer.opacity, }; switch (layer.type) { case "text": { const t = layer as TextLayer; return ( {t.text} ); } case "logo": { const l = layer as LogoLayer; return ( ); } case "contact": { const c = layer as ContactLayer; const lines = [c.fields.title, c.fields.company, c.fields.email, c.fields.phone].filter( Boolean, ); return ( {lines.map((line, i) => ( {line} ))} ); } case "shape": { const s = layer as ShapeLayer; const radius = s.shape === "circle" ? Math.min(frame.width, frame.height) : s.shape === "line" ? 0 : s.cornerRadius; return ( ); } case "qr": return ( ); default: return ; } } export function CardFace({ side, width, height, profileUrl }: Props) { const ordered = [...side.layers].sort((a, b) => a.zIndex - b.zIndex); return ( {ordered.map((layer) => ( ))} ); } const styles = StyleSheet.create({ face: { borderRadius: 24, overflow: "hidden", }, contactLine: { color: "#f5f5f7", fontSize: 15, marginBottom: 6, }, });