Unify nav into single NAV_HTML source of truth, complete i18n for 20 langs

This commit is contained in:
Russell Ballestrini 2025-12-31 19:29:21 -05:00
parent 54975667f3
commit cd2d786865

142
serp.py
View file

@ -44,6 +44,7 @@ logger = logging.getLogger(__name__)
TRANSLATIONS = {
"en": {
"search": "Search", "crawl": "Crawl", "live": "Live Feed", "about": "About",
"random": "Random", "phantom": "Phantom", "loading": "Loading...",
"start_crawl": "Start Crawl", "pause": "Pause", "resume": "Resume", "delete": "Delete",
"url": "URL", "mode": "Mode", "depth": "Depth", "keywords": "Keywords",
"screenshots": "Screenshots", "fast_mode": "Fast Mode", "fresh_start": "Fresh Start",
@ -54,10 +55,11 @@ TRANSLATIONS = {
"everything": "Everything (text + media)", "all_media": "All media (images + videos + audio)",
"images_only": "Images only", "videos_only": "Videos only", "text_only": "Text only",
"recent_jobs": "Recent Jobs", "search_placeholder": "Search media...",
"media": "Media", "pages_label": "Pages",
"media": "Media", "pages_label": "Pages", "download": "Download",
},
"zh": {
"search": "搜索", "crawl": "爬取", "live": "实时动态", "about": "关于",
"random": "随机", "phantom": "幻影", "loading": "加载中...",
"start_crawl": "开始爬取", "pause": "暂停", "resume": "继续", "delete": "删除",
"url": "网址", "mode": "模式", "depth": "深度", "keywords": "关键词",
"screenshots": "截图", "fast_mode": "快速模式", "fresh_start": "全新开始",
@ -68,10 +70,11 @@ TRANSLATIONS = {
"everything": "全部(文本+媒体)", "all_media": "所有媒体(图片+视频+音频)",
"images_only": "仅图片", "videos_only": "仅视频", "text_only": "仅文本",
"recent_jobs": "最近任务", "search_placeholder": "搜索媒体...",
"media": "媒体", "pages_label": "页面",
"media": "媒体", "pages_label": "页面", "download": "下载",
},
"es": {
"search": "Buscar", "crawl": "Rastrear", "live": "En Vivo", "about": "Acerca de",
"random": "Aleatorio", "phantom": "Fantasma", "loading": "Cargando...",
"start_crawl": "Iniciar Rastreo", "pause": "Pausar", "resume": "Reanudar", "delete": "Eliminar",
"url": "URL", "mode": "Modo", "depth": "Profundidad", "keywords": "Palabras clave",
"screenshots": "Capturas", "fast_mode": "Modo Rápido", "fresh_start": "Inicio Limpio",
@ -82,10 +85,11 @@ TRANSLATIONS = {
"everything": "Todo (texto + medios)", "all_media": "Todos los medios",
"images_only": "Solo imágenes", "videos_only": "Solo videos", "text_only": "Solo texto",
"recent_jobs": "Tareas Recientes", "search_placeholder": "Buscar medios...",
"media": "Medios", "pages_label": "Páginas",
"media": "Medios", "pages_label": "Páginas", "download": "Descargar",
},
"hi": {
"search": "खोजें", "crawl": "क्रॉल", "live": "लाइव फ़ीड", "about": "के बारे में",
"random": "यादृच्छिक", "phantom": "प्रेत", "loading": "लोड हो रहा है...",
"start_crawl": "क्रॉल शुरू करें", "pause": "रोकें", "resume": "जारी रखें", "delete": "हटाएं",
"url": "यूआरएल", "mode": "मोड", "depth": "गहराई", "keywords": "कीवर्ड",
"screenshots": "स्क्रीनशॉट", "fast_mode": "फास्ट मोड", "fresh_start": "नई शुरुआत",
@ -96,10 +100,11 @@ TRANSLATIONS = {
"everything": "सब कुछ", "all_media": "सभी मीडिया", "images_only": "केवल चित्र",
"videos_only": "केवल वीडियो", "text_only": "केवल टेक्स्ट",
"recent_jobs": "हाल के कार्य", "search_placeholder": "मीडिया खोजें...",
"media": "मीडिया", "pages_label": "पेज",
"media": "मीडिया", "pages_label": "पेज", "download": "डाउनलोड",
},
"ar": {
"search": "بحث", "crawl": "زحف", "live": "مباشر", "about": "حول",
"random": "عشوائي", "phantom": "شبح", "loading": "جاري التحميل...",
"start_crawl": "بدء الزحف", "pause": "إيقاف", "resume": "استئناف", "delete": "حذف",
"url": "الرابط", "mode": "الوضع", "depth": "العمق", "keywords": "كلمات مفتاحية",
"screenshots": "لقطات", "fast_mode": "وضع سريع", "fresh_start": "بداية جديدة",
@ -110,10 +115,11 @@ TRANSLATIONS = {
"everything": "الكل", "all_media": "جميع الوسائط", "images_only": "صور فقط",
"videos_only": "فيديو فقط", "text_only": "نص فقط",
"recent_jobs": "المهام الأخيرة", "search_placeholder": "بحث في الوسائط...",
"media": "وسائط", "pages_label": "صفحات",
"media": "وسائط", "pages_label": "صفحات", "download": "تحميل",
},
"pt": {
"search": "Pesquisar", "crawl": "Rastrear", "live": "Ao Vivo", "about": "Sobre",
"random": "Aleatório", "phantom": "Fantasma", "loading": "Carregando...",
"start_crawl": "Iniciar Rastreio", "pause": "Pausar", "resume": "Retomar", "delete": "Excluir",
"url": "URL", "mode": "Modo", "depth": "Profundidade", "keywords": "Palavras-chave",
"screenshots": "Capturas", "fast_mode": "Modo Rápido", "fresh_start": "Novo Início",
@ -124,10 +130,11 @@ TRANSLATIONS = {
"everything": "Tudo (texto + mídia)", "all_media": "Toda mídia",
"images_only": "Apenas imagens", "videos_only": "Apenas vídeos", "text_only": "Apenas texto",
"recent_jobs": "Tarefas Recentes", "search_placeholder": "Pesquisar mídia...",
"media": "Mídia", "pages_label": "Páginas",
"media": "Mídia", "pages_label": "Páginas", "download": "Baixar",
},
"ru": {
"search": "Поиск", "crawl": "Сканировать", "live": "Прямой эфир", "about": "О нас",
"random": "Случайное", "phantom": "Призрак", "loading": "Загрузка...",
"start_crawl": "Начать сканирование", "pause": "Пауза", "resume": "Продолжить", "delete": "Удалить",
"url": "URL", "mode": "Режим", "depth": "Глубина", "keywords": "Ключевые слова",
"screenshots": "Скриншоты", "fast_mode": "Быстрый режим", "fresh_start": "Начать заново",
@ -138,10 +145,11 @@ TRANSLATIONS = {
"everything": "Всё (текст + медиа)", "all_media": "Все медиа",
"images_only": "Только изображения", "videos_only": "Только видео", "text_only": "Только текст",
"recent_jobs": "Недавние задачи", "search_placeholder": "Поиск медиа...",
"media": "Медиа", "pages_label": "Страницы",
"media": "Медиа", "pages_label": "Страницы", "download": "Скачать",
},
"ja": {
"search": "検索", "crawl": "クロール", "live": "ライブ", "about": "について",
"random": "ランダム", "phantom": "ファントム", "loading": "読み込み中...",
"start_crawl": "クロール開始", "pause": "一時停止", "resume": "再開", "delete": "削除",
"url": "URL", "mode": "モード", "depth": "深さ", "keywords": "キーワード",
"screenshots": "スクリーンショット", "fast_mode": "高速モード", "fresh_start": "新規開始",
@ -152,10 +160,11 @@ TRANSLATIONS = {
"everything": "すべて(テキスト+メディア)", "all_media": "すべてのメディア",
"images_only": "画像のみ", "videos_only": "動画のみ", "text_only": "テキストのみ",
"recent_jobs": "最近のジョブ", "search_placeholder": "メディアを検索...",
"media": "メディア", "pages_label": "ページ",
"media": "メディア", "pages_label": "ページ", "download": "ダウンロード",
},
"fr": {
"search": "Rechercher", "crawl": "Explorer", "live": "En Direct", "about": "À propos",
"random": "Aléatoire", "phantom": "Fantôme", "loading": "Chargement...",
"start_crawl": "Démarrer", "pause": "Pause", "resume": "Reprendre", "delete": "Supprimer",
"url": "URL", "mode": "Mode", "depth": "Profondeur", "keywords": "Mots-clés",
"screenshots": "Captures", "fast_mode": "Mode Rapide", "fresh_start": "Nouveau Départ",
@ -166,10 +175,11 @@ TRANSLATIONS = {
"everything": "Tout (texte + médias)", "all_media": "Tous les médias",
"images_only": "Images uniquement", "videos_only": "Vidéos uniquement", "text_only": "Texte uniquement",
"recent_jobs": "Tâches Récentes", "search_placeholder": "Rechercher des médias...",
"media": "Médias", "pages_label": "Pages",
"media": "Médias", "pages_label": "Pages", "download": "Télécharger",
},
"de": {
"search": "Suchen", "crawl": "Crawlen", "live": "Live-Feed", "about": "Über",
"random": "Zufällig", "phantom": "Phantom", "loading": "Laden...",
"start_crawl": "Crawl starten", "pause": "Pause", "resume": "Fortsetzen", "delete": "Löschen",
"url": "URL", "mode": "Modus", "depth": "Tiefe", "keywords": "Stichwörter",
"screenshots": "Screenshots", "fast_mode": "Schnellmodus", "fresh_start": "Neustart",
@ -180,10 +190,11 @@ TRANSLATIONS = {
"everything": "Alles (Text + Medien)", "all_media": "Alle Medien",
"images_only": "Nur Bilder", "videos_only": "Nur Videos", "text_only": "Nur Text",
"recent_jobs": "Letzte Aufgaben", "search_placeholder": "Medien suchen...",
"media": "Medien", "pages_label": "Seiten",
"media": "Medien", "pages_label": "Seiten", "download": "Herunterladen",
},
"ko": {
"search": "검색", "crawl": "크롤", "live": "라이브", "about": "정보",
"random": "랜덤", "phantom": "팬텀", "loading": "로딩 중...",
"start_crawl": "크롤 시작", "pause": "일시정지", "resume": "재개", "delete": "삭제",
"url": "URL", "mode": "모드", "depth": "깊이", "keywords": "키워드",
"screenshots": "스크린샷", "fast_mode": "빠른 모드", "fresh_start": "새로 시작",
@ -194,10 +205,11 @@ TRANSLATIONS = {
"everything": "모두", "all_media": "모든 미디어",
"images_only": "이미지만", "videos_only": "동영상만", "text_only": "텍스트만",
"recent_jobs": "최근 작업", "search_placeholder": "미디어 검색...",
"media": "미디어", "pages_label": "페이지",
"media": "미디어", "pages_label": "페이지", "download": "다운로드",
},
"it": {
"search": "Cerca", "crawl": "Scansiona", "live": "In Diretta", "about": "Info",
"random": "Casuale", "phantom": "Fantasma", "loading": "Caricamento...",
"start_crawl": "Avvia Scansione", "pause": "Pausa", "resume": "Riprendi", "delete": "Elimina",
"url": "URL", "mode": "Modalità", "depth": "Profondità", "keywords": "Parole chiave",
"screenshots": "Screenshot", "fast_mode": "Modalità Veloce", "fresh_start": "Nuovo Inizio",
@ -208,10 +220,11 @@ TRANSLATIONS = {
"everything": "Tutto", "all_media": "Tutti i media",
"images_only": "Solo immagini", "videos_only": "Solo video", "text_only": "Solo testo",
"recent_jobs": "Attività Recenti", "search_placeholder": "Cerca media...",
"media": "Media", "pages_label": "Pagine",
"media": "Media", "pages_label": "Pagine", "download": "Scarica",
},
"nl": {
"search": "Zoeken", "crawl": "Crawlen", "live": "Live", "about": "Over",
"random": "Willekeurig", "phantom": "Fantoom", "loading": "Laden...",
"start_crawl": "Start Crawl", "pause": "Pauzeer", "resume": "Hervat", "delete": "Verwijder",
"url": "URL", "mode": "Modus", "depth": "Diepte", "keywords": "Trefwoorden",
"screenshots": "Schermafbeeldingen", "fast_mode": "Snelle Modus", "fresh_start": "Nieuwe Start",
@ -222,10 +235,11 @@ TRANSLATIONS = {
"everything": "Alles", "all_media": "Alle media",
"images_only": "Alleen afbeeldingen", "videos_only": "Alleen video's", "text_only": "Alleen tekst",
"recent_jobs": "Recente Taken", "search_placeholder": "Zoek media...",
"media": "Media", "pages_label": "Pagina's",
"media": "Media", "pages_label": "Pagina's", "download": "Downloaden",
},
"pl": {
"search": "Szukaj", "crawl": "Indeksuj", "live": "Na żywo", "about": "O nas",
"random": "Losowo", "phantom": "Fantom", "loading": "Ładowanie...",
"start_crawl": "Rozpocznij", "pause": "Pauza", "resume": "Wznów", "delete": "Usuń",
"url": "URL", "mode": "Tryb", "depth": "Głębokość", "keywords": "Słowa kluczowe",
"screenshots": "Zrzuty ekranu", "fast_mode": "Tryb szybki", "fresh_start": "Nowy start",
@ -236,10 +250,11 @@ TRANSLATIONS = {
"everything": "Wszystko", "all_media": "Wszystkie media",
"images_only": "Tylko obrazy", "videos_only": "Tylko filmy", "text_only": "Tylko tekst",
"recent_jobs": "Ostatnie Zadania", "search_placeholder": "Szukaj mediów...",
"media": "Media", "pages_label": "Strony",
"media": "Media", "pages_label": "Strony", "download": "Pobierz",
},
"tr": {
"search": "Ara", "crawl": "Tara", "live": "Canlı", "about": "Hakkında",
"random": "Rastgele", "phantom": "Hayalet", "loading": "Yükleniyor...",
"start_crawl": "Taramayı Başlat", "pause": "Duraklat", "resume": "Devam", "delete": "Sil",
"url": "URL", "mode": "Mod", "depth": "Derinlik", "keywords": "Anahtar kelimeler",
"screenshots": "Ekran görüntüleri", "fast_mode": "Hızlı Mod", "fresh_start": "Yeni Başlangıç",
@ -250,10 +265,11 @@ TRANSLATIONS = {
"everything": "Her şey", "all_media": "Tüm medya",
"images_only": "Sadece resimler", "videos_only": "Sadece videolar", "text_only": "Sadece metin",
"recent_jobs": "Son Görevler", "search_placeholder": "Medya ara...",
"media": "Medya", "pages_label": "Sayfalar",
"media": "Medya", "pages_label": "Sayfalar", "download": "İndir",
},
"vi": {
"search": "Tìm kiếm", "crawl": "Thu thập", "live": "Trực tiếp", "about": "Giới thiệu",
"random": "Ngẫu nhiên", "phantom": "Bóng ma", "loading": "Đang tải...",
"start_crawl": "Bắt đầu", "pause": "Tạm dừng", "resume": "Tiếp tục", "delete": "Xóa",
"url": "URL", "mode": "Chế độ", "depth": "Độ sâu", "keywords": "Từ khóa",
"screenshots": "Ảnh chụp", "fast_mode": "Chế độ nhanh", "fresh_start": "Bắt đầu mới",
@ -264,10 +280,11 @@ TRANSLATIONS = {
"everything": "Tất cả", "all_media": "Tất cả media",
"images_only": "Chỉ hình ảnh", "videos_only": "Chỉ video", "text_only": "Chỉ văn bản",
"recent_jobs": "Tác vụ gần đây", "search_placeholder": "Tìm media...",
"media": "Media", "pages_label": "Trang",
"media": "Media", "pages_label": "Trang", "download": "Tải xuống",
},
"th": {
"search": "ค้นหา", "crawl": "รวบรวม", "live": "สด", "about": "เกี่ยวกับ",
"random": "สุ่ม", "phantom": "แฟนทอม", "loading": "กำลังโหลด...",
"start_crawl": "เริ่มรวบรวม", "pause": "หยุดชั่วคราว", "resume": "ดำเนินต่อ", "delete": "ลบ",
"url": "URL", "mode": "โหมด", "depth": "ความลึก", "keywords": "คำสำคัญ",
"screenshots": "ภาพหน้าจอ", "fast_mode": "โหมดเร็ว", "fresh_start": "เริ่มใหม่",
@ -278,10 +295,11 @@ TRANSLATIONS = {
"everything": "ทั้งหมด", "all_media": "สื่อทั้งหมด",
"images_only": "เฉพาะรูปภาพ", "videos_only": "เฉพาะวิดีโอ", "text_only": "เฉพาะข้อความ",
"recent_jobs": "งานล่าสุด", "search_placeholder": "ค้นหาสื่อ...",
"media": "สื่อ", "pages_label": "หน้า",
"media": "สื่อ", "pages_label": "หน้า", "download": "ดาวน์โหลด",
},
"id": {
"search": "Cari", "crawl": "Jelajahi", "live": "Langsung", "about": "Tentang",
"random": "Acak", "phantom": "Hantu", "loading": "Memuat...",
"start_crawl": "Mulai Jelajah", "pause": "Jeda", "resume": "Lanjutkan", "delete": "Hapus",
"url": "URL", "mode": "Mode", "depth": "Kedalaman", "keywords": "Kata kunci",
"screenshots": "Tangkapan layar", "fast_mode": "Mode Cepat", "fresh_start": "Mulai Baru",
@ -292,10 +310,11 @@ TRANSLATIONS = {
"everything": "Semua", "all_media": "Semua media",
"images_only": "Hanya gambar", "videos_only": "Hanya video", "text_only": "Hanya teks",
"recent_jobs": "Tugas Terbaru", "search_placeholder": "Cari media...",
"media": "Media", "pages_label": "Halaman",
"media": "Media", "pages_label": "Halaman", "download": "Unduh",
},
"uk": {
"search": "Пошук", "crawl": "Сканувати", "live": "Наживо", "about": "Про нас",
"random": "Випадково", "phantom": "Привид", "loading": "Завантаження...",
"start_crawl": "Почати сканування", "pause": "Пауза", "resume": "Продовжити", "delete": "Видалити",
"url": "URL", "mode": "Режим", "depth": "Глибина", "keywords": "Ключові слова",
"screenshots": "Знімки екрану", "fast_mode": "Швидкий режим", "fresh_start": "Новий старт",
@ -306,10 +325,11 @@ TRANSLATIONS = {
"everything": "Все", "all_media": "Усі медіа",
"images_only": "Лише зображення", "videos_only": "Лише відео", "text_only": "Лише текст",
"recent_jobs": "Останні завдання", "search_placeholder": "Пошук медіа...",
"media": "Медіа", "pages_label": "Сторінки",
"media": "Медіа", "pages_label": "Сторінки", "download": "Завантажити",
},
"sv": {
"search": "Sök", "crawl": "Genomsök", "live": "Live", "about": "Om",
"random": "Slumpmässig", "phantom": "Fantom", "loading": "Laddar...",
"start_crawl": "Starta genomsökning", "pause": "Pausa", "resume": "Återuppta", "delete": "Ta bort",
"url": "URL", "mode": "Läge", "depth": "Djup", "keywords": "Nyckelord",
"screenshots": "Skärmbilder", "fast_mode": "Snabbläge", "fresh_start": "Ny start",
@ -320,7 +340,7 @@ TRANSLATIONS = {
"everything": "Allt", "all_media": "Alla media",
"images_only": "Endast bilder", "videos_only": "Endast videor", "text_only": "Endast text",
"recent_jobs": "Senaste Jobb", "search_placeholder": "Sök media...",
"media": "Media", "pages_label": "Sidor",
"media": "Media", "pages_label": "Sidor", "download": "Ladda ner",
},
}
@ -351,11 +371,24 @@ def t(key: str, lang: str = "en") -> str:
"""Get translation for key in language."""
return TRANSLATIONS.get(lang, TRANSLATIONS["en"]).get(key, TRANSLATIONS["en"].get(key, key))
# Single source of truth for navigation - uses {{key}} placeholders
NAV_HTML = '''<div class="nav">
<a href="/" class="brand">🐷 neopig</a>
<a href="/">{{search}}</a>
<a href="/live">{{live}}</a>
<a href="/random">{{random}}</a>
<a href="/crawl">{{crawl}}</a>
<a href="/phantom">{{phantom}}</a>
<a href="/about">{{about}}</a>
</div>'''
def inject_i18n(html: str, lang: str) -> str:
"""Replace {key} placeholders and inject JS translations + language selector."""
trans = TRANSLATIONS.get(lang, TRANSLATIONS["en"])
# Add lang attribute and inject JS translations
html = html.replace('<html>', f'<html lang="{lang}">')
# Replace nav placeholder with actual nav
html = html.replace('<!-- NAV -->', NAV_HTML)
# Build language selector options
lang_options = ''.join(f'<option value="{code}"{" selected" if code == lang else ""}>{name}</option>'
for code, name in LANG_NAMES.items())
@ -368,13 +401,8 @@ function setLang(code) {
}
'''
html = html.replace('<script>', f'<script>\nconst T={json.dumps(trans)};\n{lang_js}', 1)
# Insert language selector in nav (handle both {{about}} and {about} from f-strings)
for pattern in ['{{about}}</a>\n </div>', '{about}</a>\n </div>',
'{{about}}</a>\n </div>', '{about}</a>\n </div>']:
if pattern in html:
html = html.replace(f'<a href="/about">{pattern}',
f'<a href="/about">{pattern.split("</a>")[0]}</a>\n {lang_selector}\n </div>')
break
# Insert language selector after {{about}} link in nav
html = html.replace('{{about}}</a>\n</div>', f'{{{{about}}}}</a>\n {lang_selector}\n</div>')
# Replace all {key} and {{key}} placeholders
for key, value in trans.items():
html = html.replace('{{' + key + '}}', value) # Double braces (static templates)
@ -585,15 +613,7 @@ def render_detail_page(
</style>
</head>
<body>
<div class="nav">
<a href="/" class="brand">🐷 neopig</a>
<a href="/">Search</a>
<a href="/live">{{live}}</a>
<a href="/random">Random</a>
<a href="/crawl">{{crawl}}</a>
<a href="/phantom">Phantom</a>
<a href="/about">{{about}}</a>
</div>
<!-- NAV -->
<div class="container">
<form class="search-box" action="/" method="get" style="display:flex;gap:10px;margin-bottom:20px;">
<input type="text" name="q" placeholder="{{search_placeholder}}" style="flex:1;padding:12px 16px;font-size:16px;border:2px solid #333;border-radius:8px;background:#1a1a1a;color:#fff;">
@ -1115,15 +1135,7 @@ SEARCH_HTML = """
</style>
</head>
<body>
<div class="nav">
<a href="/" class="brand">🐷 neopig</a>
<a href="/">{{search}}</a>
<a href="/live">{{live}}</a>
<a href="/random">Random</a>
<a href="/crawl">{{crawl}}</a>
<a href="/phantom">Phantom</a>
<a href="/about">{{about}}</a>
</div>
<!-- NAV -->
<div class="container">
<div class="search-box">
@ -1494,15 +1506,7 @@ CRAWL_HTML = """
</style>
</head>
<body>
<div class="nav">
<a href="/" class="brand">🐷 neopig</a>
<a href="/">{{search}}</a>
<a href="/live">{{live}}</a>
<a href="/random">Random</a>
<a href="/crawl">{{crawl}}</a>
<a href="/phantom">Phantom</a>
<a href="/about">{{about}}</a>
</div>
<!-- NAV -->
<div class="container">
<form class="search-box" action="/" method="get" style="display:flex;gap:10px;margin-bottom:20px;">
@ -1980,15 +1984,7 @@ LIVE_HTML = """
</style>
</head>
<body>
<div class="nav">
<a href="/" class="brand">🐷 neopig</a>
<a href="/">Search</a>
<a href="/live">{{live}}</a>
<a href="/random">Random</a>
<a href="/crawl">{{crawl}}</a>
<a href="/phantom">Phantom</a>
<a href="/about">{{about}}</a>
</div>
<!-- NAV -->
<div class="container">
<form class="search-box" action="/" method="get" style="display:flex;gap:10px;margin-bottom:20px;">
@ -2690,15 +2686,7 @@ async def phantom_page(lang: str = Cookie(None), accept_language: str = Header(N
</style>
</head>
<body>
<div class="nav">
<a href="/" class="brand">🐷 neopig</a>
<a href="/">Search</a>
<a href="/live">{{live}}</a>
<a href="/random">Random</a>
<a href="/crawl">{{crawl}}</a>
<a href="/phantom">Phantom</a>
<a href="/about">{{about}}</a>
</div>
<!-- NAV -->
<div class="container">
<form class="search-box" action="/" method="get" style="display:flex;gap:10px;margin-bottom:20px;">
<input type="text" name="q" placeholder="{{search_placeholder}}" style="flex:1;padding:12px 16px;font-size:16px;border:2px solid #333;border-radius:8px;background:#1a1a1a;color:#fff;width:auto;">
@ -2850,15 +2838,7 @@ async def about_page(lang: str = Cookie(None), accept_language: str = Header(Non
</style>
</head>
<body>
<div class="nav">
<a href="/" class="brand">🐷 neopig</a>
<a href="/">{{search}}</a>
<a href="/live">{{live}}</a>
<a href="/random">Random</a>
<a href="/crawl">{{crawl}}</a>
<a href="/phantom">Phantom</a>
<a href="/about">{{about}}</a>
</div>
<!-- NAV -->
<div class="about-content">
<h1>🐷 neopig</h1>
<p><strong>Neo Python Image Grabber</strong> &mdash; A full-domain async media crawler with content-addressable storage, full-text search, and page screenshot capture.</p>