From 87044a747b47bd83249d61a45842c7f7b2eae56d Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Sun, 11 Jan 2026 05:52:14 +0000
Subject:  Contract system

---
 .../frontend/src/components/JapaneseHoverText.tsx  | 77 ++++++++++++++++++++++
 1 file changed, 77 insertions(+)
 create mode 100644 makima/frontend/src/components/JapaneseHoverText.tsx

(limited to 'makima/frontend/src/components/JapaneseHoverText.tsx')

diff --git a/makima/frontend/src/components/JapaneseHoverText.tsx b/makima/frontend/src/components/JapaneseHoverText.tsx
new file mode 100644
index 0000000..3e60ee2
--- /dev/null
+++ b/makima/frontend/src/components/JapaneseHoverText.tsx
@@ -0,0 +1,77 @@
+import { useState, useCallback, useRef } from "react";
+
+const GLYPHS = "▒▓░█#@*+:-/[]{}<>_";
+
+interface JapaneseHoverTextProps {
+  japanese: string;
+  english: string;
+  className?: string;
+}
+
+/**
+ * Displays Japanese text, transitions to English on hover with scramble effect
+ */
+export function JapaneseHoverText({
+  japanese,
+  english,
+  className = "",
+}: JapaneseHoverTextProps) {
+  const [isHovered, setIsHovered] = useState(false);
+  const [displayText, setDisplayText] = useState(english);
+  const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
+  const iterationRef = useRef(0);
+
+  const scrambleToEnglish = useCallback(() => {
+    setIsHovered(true);
+
+    // Clear any existing animation
+    if (timerRef.current) {
+      clearInterval(timerRef.current);
+    }
+
+    iterationRef.current = 0;
+
+    timerRef.current = setInterval(() => {
+      const text = english;
+      const iteration = iterationRef.current;
+
+      const display = text
+        .split("")
+        .map((char, index) => {
+          if (index < iteration) return char;
+          return GLYPHS.charAt(Math.floor(Math.random() * GLYPHS.length));
+        })
+        .join("");
+
+      setDisplayText(display);
+      iterationRef.current += 1;
+
+      if (iteration > text.length + 2) {
+        if (timerRef.current) {
+          clearInterval(timerRef.current);
+          timerRef.current = null;
+        }
+        setDisplayText(english);
+      }
+    }, 26);
+  }, [english]);
+
+  const resetToJapanese = useCallback(() => {
+    if (timerRef.current) {
+      clearInterval(timerRef.current);
+      timerRef.current = null;
+    }
+    setIsHovered(false);
+    setDisplayText(english);
+  }, [english]);
+
+  return (
+    <span
+      className={`cursor-default ${className}`}
+      onMouseEnter={scrambleToEnglish}
+      onMouseLeave={resetToJapanese}
+    >
+      {isHovered ? displayText : japanese}
+    </span>
+  );
+}
-- 
cgit v1.2.3

