diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx
index 39e2089..697734e 100644
--- a/src/components/Header/Header.tsx
+++ b/src/components/Header/Header.tsx
@@ -123,9 +123,14 @@ const Header: FC = () => {
{
- if (router.pathname !== '/') {
- !isSmallScreen && handleClick(e, '/');
+ const goingToLanding = router.pathname !== '/';
+ if (isSmallScreen) {
+ e.preventDefault();
+ if (isOpenedSidebar) toggleSidebar();
+ if (goingToLanding) router.push('/');
+ return;
}
+ if (goingToLanding) handleClick(e, '/');
}}
src={
isDarkTheme
diff --git a/src/pages/ai-atlas.tsx b/src/pages/ai-atlas.tsx
index 78d8e8d..451ce2d 100644
--- a/src/pages/ai-atlas.tsx
+++ b/src/pages/ai-atlas.tsx
@@ -20,6 +20,21 @@ const POL = (r: number, theta: number) => ({
y: Math.sin(RAD(theta)) * r * HALF,
});
+/* On touch devices Mouse* events fire synthetically on tap but never
+ get a leave — without this, hover state would lock on Android. */
+function useHasHover() {
+ const [hasHover, setHasHover] = useState(false);
+ useEffect(() => {
+ if (typeof window === 'undefined' || !window.matchMedia) return;
+ const mq = window.matchMedia('(hover: hover) and (pointer: fine)');
+ const update = () => setHasHover(mq.matches);
+ update();
+ mq.addEventListener?.('change', update);
+ return () => mq.removeEventListener?.('change', update);
+ }, []);
+ return hasHover;
+}
+
type Lang = 'en' | 'ru';
const pickLang = (locale: string | undefined): Lang =>
locale === 'ru' ? 'ru' : 'en';
@@ -1328,8 +1343,11 @@ function SecurityCallout({
);
}
-function SecurityView({ t }: { t: T }) {
- const [hoveredLayer, setHoveredLayer] = useState(null);
+function SecurityView({ t, hasHover }: { t: T; hasHover: boolean }) {
+ const [hoveredLayer, setHoveredLayerRaw] = useState(null);
+ const setHoveredLayer = (n: number | null) => {
+ if (hasHover) setHoveredLayerRaw(n);
+ };
const leftLayers = t.securityLayers.filter(l => l.side === 'left');
const rightLayers = t.securityLayers.filter(l => l.side === 'right');
const calloutTops = ['4%', '38%', '72%'];
@@ -1448,6 +1466,7 @@ function AiAtlasApp() {
const [hoverNode, setHoverNode] = useState(null);
const [linkHoverNode, setLinkHoverNode] = useState(null);
const [viewMode, setViewMode] = useState('environment');
+ const hasHover = useHasHover();
/* mark while AI Atlas is mounted so the global navbar can
match the page's paper background (light mode only for now).
@@ -1651,9 +1670,11 @@ function AiAtlasApp() {
const pinnedSolo = !!focusedNode && !hoverNode && !linkHoverNode;
const onSelect = (id: string | null, mode?: string) => {
- if (mode === 'hover') setHoverNode(id);
- else if (mode === 'link-hover') setLinkHoverNode(id);
- else {
+ if (mode === 'hover') {
+ if (hasHover) setHoverNode(id);
+ } else if (mode === 'link-hover') {
+ if (hasHover) setLinkHoverNode(id);
+ } else {
setLinkHoverNode(null);
setFocusedNode(id === focusedNode ? null : id);
}
@@ -2126,7 +2147,9 @@ function AiAtlasApp() {
)}
- {viewMode === 'security' && }
+ {viewMode === 'security' && (
+
+ )}