diff --git a/src/components/messageStream/overlays/ImageLightbox.tsx b/src/components/messageStream/overlays/ImageLightbox.tsx
index c3237af..62d9db6 100644
--- a/src/components/messageStream/overlays/ImageLightbox.tsx
+++ b/src/components/messageStream/overlays/ImageLightbox.tsx
@@ -121,7 +121,7 @@ function LightboxView({
return createPortal(
-
+
{index + 1} / {images.length}
>
) : null}
e.stopPropagation()}
- onTouchStart={(e) => {
- touchStartX.current = e.touches[0].clientX;
- }}
- onTouchEnd={(e) => {
- if (touchStartX.current == null) return;
- const dx = e.changedTouches[0].clientX - touchStartX.current;
- if (Math.abs(dx) > 40) {
- if (dx > 0) goPrev();
- else goNext();
- }
- touchStartX.current = null;
- }}
+ className={`flex min-h-0 w-full flex-1 items-center justify-center px-4 pt-16 ${
+ caption ? "pb-3" : "pb-16"
+ }`}
>
-

- {caption ? (
-
-
- {autolink(caption)}
-
-
- ) : null}
+
e.stopPropagation()}
+ onTouchStart={(e) => {
+ touchStartX.current = e.touches[0].clientX;
+ }}
+ onTouchEnd={(e) => {
+ if (touchStartX.current == null) return;
+ const dx = e.changedTouches[0].clientX - touchStartX.current;
+ if (Math.abs(dx) > 40) {
+ if (dx > 0) goPrev();
+ else goNext();
+ }
+ touchStartX.current = null;
+ }}
+ >
+

+
+
+ {caption ? (
+
e.stopPropagation()}
+ >
+
+ {autolink(caption)}
+
+
+ ) : null}
,
document.body,
);
diff --git a/src/layouts/PublicLayout.tsx b/src/layouts/PublicLayout.tsx
index ddf3d1d..6ba44eb 100644
--- a/src/layouts/PublicLayout.tsx
+++ b/src/layouts/PublicLayout.tsx
@@ -1,5 +1,12 @@
-import { Globe, Menu, Search as SearchIcon, X } from "lucide-react";
-import { useState } from "react";
+import {
+ Check,
+ ChevronDown,
+ Globe,
+ Menu,
+ Search as SearchIcon,
+ X,
+} from "lucide-react";
+import { useEffect, useRef, useState } from "react";
import { Link, Outlet, useLocation, useNavigate } from "react-router-dom";
import { ArkLogoMark } from "../components/ArkLogoMark";
import { useI18n, type Lang } from "../i18n";
@@ -55,6 +62,105 @@ function navClassName(active: boolean) {
].join(" ");
}
+type LanguageDropdownProps = {
+ lang: Lang;
+ setLang: (lang: Lang) => void;
+ ariaLabel: string;
+ className?: string;
+};
+
+function LanguageDropdown({
+ lang,
+ setLang,
+ ariaLabel,
+ className = "",
+}: LanguageDropdownProps) {
+ const [open, setOpen] = useState(false);
+ const rootRef = useRef
(null);
+ const selected = LANG_OPTIONS.find((option) => option.code === lang);
+
+ useEffect(() => {
+ if (!open) return;
+
+ const closeOnOutside = (event: MouseEvent | TouchEvent) => {
+ if (!rootRef.current?.contains(event.target as Node)) setOpen(false);
+ };
+ const closeOnEscape = (event: KeyboardEvent) => {
+ if (event.key === "Escape") setOpen(false);
+ };
+
+ document.addEventListener("mousedown", closeOnOutside);
+ document.addEventListener("touchstart", closeOnOutside);
+ window.addEventListener("keydown", closeOnEscape);
+ return () => {
+ document.removeEventListener("mousedown", closeOnOutside);
+ document.removeEventListener("touchstart", closeOnOutside);
+ window.removeEventListener("keydown", closeOnEscape);
+ };
+ }, [open]);
+
+ return (
+
+
+
+ {open ? (
+
+ {LANG_OPTIONS.map((option) => {
+ const active = option.code === lang;
+ return (
+
+ );
+ })}
+
+ ) : null}
+
+ );
+}
+
export function PublicLayout() {
const { t, lang, setLang } = useI18n();
const { pathname, search, hash } = useLocation();
@@ -154,25 +260,12 @@ export function PublicLayout() {
className="min-w-0 flex-1 rounded-md bg-transparent text-sm text-neutral-200 outline-none placeholder:text-[#777985] focus-visible:ring-2 focus-visible:ring-ark-gold/60 focus-visible:ring-offset-2 focus-visible:ring-offset-[#1a1b20]"
/>
-