import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' type AnchorRect = { top: number; left: number; width: number; height: number } type Props = { isOpen: boolean onClose: () => void anchorRect: AnchorRect | null } export const MissionDrawer: React.FC = ({ isOpen, onClose, anchorRect }) => { const [box, setBox] = useState(null) const [expanded, setExpanded] = useState(false) const closingRef = useRef(false) const finalBox = useMemo(() => { if (!isOpen) return null const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0) const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0) const margin = 24 const maxW = Math.min(640, vw - margin * 2) const maxH = Math.min(Math.round(vh * 0.86), 560) const left = Math.round((vw - maxW) / 2) const top = Math.round((vh - maxH) / 2) return { top, left, width: maxW, height: maxH } }, [isOpen]) useLayoutEffect(() => { if (!isOpen || !anchorRect) return closingRef.current = false setBox(anchorRect) // Defer to next frame to allow transition from anchor -> final const id = requestAnimationFrame(() => { setExpanded(true) if (finalBox) setBox(finalBox) }) return () => cancelAnimationFrame(id) // eslint-disable-next-line react-hooks/exhaustive-deps }, [isOpen, anchorRect]) useEffect(() => { if (!isOpen) return const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') handleClose() } window.addEventListener('keydown', handler) return () => window.removeEventListener('keydown', handler) // eslint-disable-next-line react-hooks/exhaustive-deps }, [isOpen]) if (!isOpen || !anchorRect || !box) return null const handleClose = () => { if (!anchorRect) return onClose() closingRef.current = true setExpanded(false) setBox(anchorRect) } const onTransitionEnd = () => { if (closingRef.current) { closingRef.current = false onClose() } } const style: React.CSSProperties = { position: 'fixed', top: box.top, left: box.left, width: box.width, height: box.height } return (
e.stopPropagation()} onTransitionEnd={onTransitionEnd} role="dialog" aria-modal="true" aria-labelledby="mission-title" >
そりゅう SORYU

Our Mission

At Soryu, our mission is to make real‑time conversation understanding feel instant, reliable, and human. We build low‑latency infrastructure for streaming transcription and interaction so products can turn live dialogue into actionable, privacy‑respecting insight.

We obsess over end‑to‑end performance — from the first byte on the wire to the words on the screen — so teams can deliver conversational experiences that are responsive, accessible, and trustworthy.

By combining efficient streaming (SSE/WS), robust clients, and thoughtful design, we help developers ship experiences where every millisecond matters and every conversation counts.

) }