Skip to content

Commit 3b547a2

Browse files
Copexitclaude
andcommitted
fix: responsive header for mobile viewports, remove skip-to-search
- Hide Clearnet/Tor/Local labels on mobile (icon-only, visible on sm+) - Tighten header gap on small screens (gap-2 mobile, gap-3 desktop) - Remove skip-to-search link that was rendering visibly - Add local API (Umbrel) connection badge and network priority Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 214db59 commit 3b547a2

File tree

4 files changed

+52
-43
lines changed

4 files changed

+52
-43
lines changed

src/app/layout.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,6 @@ export default function RootLayout({
101101
<I18nProvider>
102102
<LangAttributeSync />
103103
<NetworkProvider>
104-
<a
105-
href="#main-input"
106-
className="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-[100] focus:bg-bitcoin focus:text-black focus:px-4 focus:py-2 focus:rounded-lg focus:text-sm focus:font-semibold"
107-
>
108-
Skip to search
109-
</a>
110104
<Header />
111105
<PrivacyNotice />
112106
<main className="flex-1 flex flex-col">{children}</main>

src/components/ConnectionBadge.tsx

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { useState, useRef, useEffect } from "react";
4-
import { Shield, ShieldAlert, ShieldQuestion } from "lucide-react";
4+
import { Shield, ShieldAlert, ShieldCheck, ShieldQuestion } from "lucide-react";
55
import { useNetwork } from "@/context/NetworkContext";
66
import { useTranslation } from "react-i18next";
77

@@ -11,7 +11,7 @@ import { useTranslation } from "react-i18next";
1111
*/
1212
export function ConnectionBadge() {
1313
const { t } = useTranslation();
14-
const { torStatus: status } = useNetwork();
14+
const { torStatus, localApiStatus } = useNetwork();
1515
const [showTip, setShowTip] = useState(false);
1616
const ref = useRef<HTMLDivElement>(null);
1717

@@ -27,28 +27,37 @@ export function ConnectionBadge() {
2727
return () => document.removeEventListener("click", handleClick, true);
2828
}, [showTip]);
2929

30-
const config = {
31-
checking: {
32-
icon: <Shield size={16} className="text-muted animate-pulse" />,
33-
label: null,
34-
tip: t("common.connectionChecking", { defaultValue: "Checking connection type..." }),
35-
},
36-
tor: {
37-
icon: <Shield size={16} className="text-success" />,
38-
label: <span className="text-success text-xs hidden sm:inline">{t("common.tor", { defaultValue: "Tor" })}</span>,
39-
tip: t("common.connectionTor", { defaultValue: "Connected via Tor - your IP is hidden from API providers" }),
40-
},
41-
unknown: {
42-
icon: <ShieldQuestion size={16} className="text-muted" />,
43-
label: null,
44-
tip: t("common.connectionUnknown", { defaultValue: "Connection privacy status could not be determined" }),
45-
},
46-
clearnet: {
47-
icon: <ShieldAlert size={16} className="text-warning" />,
48-
label: <span className="text-warning text-xs hidden sm:inline">{t("common.clearnet", { defaultValue: "Clearnet" })}</span>,
49-
tip: t("common.connectionClearnet", { defaultValue: "Not using Tor - mempool.space can see your IP address" }),
50-
},
51-
}[status];
30+
// Local API (Umbrel) takes display priority - it's the most private option
31+
const isLocal = localApiStatus === "available";
32+
33+
const config = isLocal
34+
? {
35+
icon: <ShieldCheck size={16} className="text-success" />,
36+
label: <span className="text-success text-xs hidden sm:inline">{t("common.local", { defaultValue: "Local" })}</span>,
37+
tip: t("common.connectionLocal", { defaultValue: "Connected to local mempool instance - all queries stay on your network" }),
38+
}
39+
: {
40+
checking: {
41+
icon: <Shield size={16} className="text-muted animate-pulse" />,
42+
label: null,
43+
tip: t("common.connectionChecking", { defaultValue: "Checking connection type..." }),
44+
},
45+
tor: {
46+
icon: <Shield size={16} className="text-success" />,
47+
label: <span className="text-success text-xs hidden sm:inline">{t("common.tor", { defaultValue: "Tor" })}</span>,
48+
tip: t("common.connectionTor", { defaultValue: "Connected via Tor - your IP is hidden from API providers" }),
49+
},
50+
unknown: {
51+
icon: <ShieldQuestion size={16} className="text-muted" />,
52+
label: null,
53+
tip: t("common.connectionUnknown", { defaultValue: "Connection privacy status could not be determined" }),
54+
},
55+
clearnet: {
56+
icon: <ShieldAlert size={16} className="text-warning" />,
57+
label: <span className="text-warning text-xs hidden sm:inline">{t("common.clearnet", { defaultValue: "Clearnet" })}</span>,
58+
tip: t("common.connectionClearnet", { defaultValue: "Not using Tor - mempool.space can see your IP address" }),
59+
},
60+
}[torStatus];
5261

5362
return (
5463
<div ref={ref} className="relative">

src/components/Header.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"use client";
22

3-
import Image from "next/image";
43
import { NetworkSelector } from "./NetworkSelector";
54
import { ConnectionBadge } from "./ConnectionBadge";
65
import { ApiSettings } from "./ApiSettings";
@@ -19,14 +18,7 @@ export function Header() {
1918
aria-label="am-i.exposed home"
2019
className="flex items-center gap-2 group hover:opacity-80 transition-opacity cursor-pointer"
2120
>
22-
<Image
23-
src="/logo-256.svg"
24-
alt=""
25-
width={28}
26-
height={28}
27-
className="rounded-md"
28-
/>
29-
<span className="hidden sm:inline text-xl sm:text-2xl font-bold tracking-tight text-foreground select-none whitespace-nowrap">
21+
<span className="text-xl sm:text-2xl font-bold tracking-tight text-foreground select-none whitespace-nowrap">
3022
am-i.<span className="text-danger">exposed</span>
3123
</span>
3224
</button>

src/context/NetworkContext.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import { useUrlState } from "@/hooks/useUrlState";
1616
import { useCustomApi } from "@/hooks/useCustomApi";
1717
import { useTorDetection, type TorStatus } from "@/hooks/useTorDetection";
18+
import { useLocalApi, type LocalApiStatus } from "@/hooks/useLocalApi";
1819

1920
interface NetworkContextValue {
2021
network: BitcoinNetwork;
@@ -23,6 +24,7 @@ interface NetworkContextValue {
2324
customApiUrl: string | null;
2425
setCustomApiUrl: (url: string | null) => void;
2526
torStatus: TorStatus;
27+
localApiStatus: LocalApiStatus;
2628
}
2729

2830
const NetworkContext = createContext<NetworkContextValue>({
@@ -32,16 +34,18 @@ const NetworkContext = createContext<NetworkContextValue>({
3234
customApiUrl: null,
3335
setCustomApiUrl: () => {},
3436
torStatus: "checking",
37+
localApiStatus: "checking",
3538
});
3639

3740
export function NetworkProvider({ children }: { children: ReactNode }) {
3841
const { network, setNetwork } = useUrlState();
3942
const { customUrl, setCustomUrl } = useCustomApi();
4043
const torStatus = useTorDetection();
44+
const localApiStatus = useLocalApi();
4145
const baseConfig = NETWORK_CONFIG[network];
4246

4347
const config = useMemo(() => {
44-
// Custom API URL takes priority over everything
48+
// Priority 1: Custom API URL takes priority over everything
4549
if (customUrl) {
4650
return {
4751
...baseConfig,
@@ -50,7 +54,15 @@ export function NetworkProvider({ children }: { children: ReactNode }) {
5054
explorerUrl: customUrl.replace(/\/api\/?$/, ""),
5155
};
5256
}
53-
// When Tor detected and onion URL available, use it as primary
57+
// Priority 2: Same-origin API proxy detected (Umbrel mode)
58+
if (localApiStatus === "available") {
59+
return {
60+
...baseConfig,
61+
mempoolBaseUrl: "/api",
62+
esploraBaseUrl: "/api", // Disable external fallback
63+
};
64+
}
65+
// Priority 3: Tor detected and onion URL available - use it as primary
5466
// with clearnet mempool as fallback (still routed through Tor exit nodes)
5567
if (torStatus === "tor" && baseConfig.mempoolOnionUrl) {
5668
return {
@@ -60,8 +72,9 @@ export function NetworkProvider({ children }: { children: ReactNode }) {
6072
explorerUrl: baseConfig.mempoolOnionUrl.replace(/\/api\/?$/, ""),
6173
};
6274
}
75+
// Priority 4: Hardcoded defaults
6376
return baseConfig;
64-
}, [baseConfig, customUrl, torStatus]);
77+
}, [baseConfig, customUrl, localApiStatus, torStatus]);
6578

6679
const value = useMemo(
6780
() => ({
@@ -71,8 +84,9 @@ export function NetworkProvider({ children }: { children: ReactNode }) {
7184
customApiUrl: customUrl,
7285
setCustomApiUrl: setCustomUrl,
7386
torStatus,
87+
localApiStatus,
7488
}),
75-
[network, setNetwork, config, customUrl, setCustomUrl, torStatus],
89+
[network, setNetwork, config, customUrl, setCustomUrl, torStatus, localApiStatus],
7690
);
7791

7892
return (

0 commit comments

Comments
 (0)