index.html
1 <html lang="{{ g.full_lang_code }}"> 2 <head> 3 <meta charset="utf-8"> 4 <title>{% if self.title() %}{% block title %}{% endblock %} - {% endif %}{{ gettext('layout.index.title') }}</title> 5 <link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}"> 6 <script defer src="{{ url_for('static', filename='js/app.js') }}"></script> 7 {% if self.meta_tags() %} 8 {% block meta_tags %}{% endblock %} 9 {% else %} 10 <meta name="description" content="{{ gettext('layout.index.meta.description') }}" /> 11 {% endif %} 12 <meta name="twitter:card" value="summary"> 13 <meta name="viewport" content="width=device-width, initial-scale=1" /> 14 <link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='apple-touch-icon.png') }}"> 15 <link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='favicon-32x32.png') }}"> 16 <link rel="icon" type="image/png" sizes="16x16" href="{{ url_for('static', filename='favicon-16x16.png') }}"> 17 <link rel="manifest" href="{{ url_for('static', filename='site.webmanifest') }}"> 18 <link rel="search" href="{{ url_for('static', filename='content-search.xml') }}" type="application/opensearchdescription+xml" title="{{ gettext('layout.index.meta.opensearch') }}" /> 19 <meta name="apple-mobile-web-app-capable" content="yes"> 20 <script> 21 window.globalUpdateAaLoggedIn = function(aa_logged_in) { 22 localStorage['aa_logged_in'] = aa_logged_in; 23 if (localStorage['aa_logged_in'] === '1') { 24 document.documentElement.classList.add("aa-logged-in"); 25 } else { 26 document.documentElement.classList.remove("aa-logged-in"); 27 } 28 } 29 window.globalUpdateAaLoggedIn(localStorage['aa_logged_in'] || 0); 30 31 // Focus search field when pressing "/". 32 document.addEventListener("keydown", e => { 33 if (e.key !== "/" || e.ctrlKey || e.metaKey || e.altKey) return; 34 if (/^(?:input|textarea|select|button)$/i.test(e.target.tagName)) return; 35 e.preventDefault(); 36 const fields = document.querySelectorAll('.js-slash-focus'); 37 const field = fields[fields.length - 1]; 38 if (field) { 39 field.select(); 40 field.scrollIntoView({ block: "center", inline: "center" }); 41 } 42 }); 43 </script> 44 </head> 45 <body> 46 <script> 47 (function() { 48 if (location.hostname.includes('localhost')) { 49 location.hostname = location.hostname.replace('localhost', 'localtest.me'); 50 return; 51 } 52 53 var langCodes = [{% for lang_code, _lang_name in g.languages %}{{ lang_code | tojson }}, {% endfor %}]; 54 55 var domainPosition = 0; 56 var potentialSubDomainLangCode = location.hostname.split(".")[0]; 57 var subDomainLangCode = 'en'; 58 if (langCodes.includes(potentialSubDomainLangCode) || potentialSubDomainLangCode === 'www') { 59 domainPosition = potentialSubDomainLangCode.length + 1; 60 if (potentialSubDomainLangCode !== 'www') { 61 subDomainLangCode = potentialSubDomainLangCode; 62 } 63 } 64 65 window.baseDomain = location.hostname.substring(domainPosition); 66 67 function setLangCookie(langCode) { 68 if (!langCodes.includes(langCode)) { 69 return; 70 } 71 document.cookie = 'selected_lang=' + langCode + ';path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;domain=' + window.baseDomain; 72 } 73 74 function redirectLang(langCode) { 75 if (!langCodes.includes(langCode)) { 76 return; 77 } 78 var prefix = ''; 79 if (langCode != 'en') { 80 prefix = langCode + '.'; 81 } 82 location.hostname = prefix + window.baseDomain; 83 } 84 85 window.handleChangeLang = function(event) { 86 const langCode = event.target.value; 87 setLangCookie(langCode); 88 redirectLang(langCode); 89 }; 90 91 // Let's also (for now) not set a cookie when getting referred. 92 // { 93 // // If our referrer was (likely) a different domain of our website (with the same lang code), 94 // // then behave as if that lang code was set as a cookie all along. 95 // if (document.referrer.includes("://" + subDomainLangCode + ".")) { 96 // setLangCookie(subDomainLangCode); 97 // } 98 // } 99 100 // Browser-based language detection is too unreliable. 101 // Disable for now. 102 // { 103 // const cookieLangMatch = document.cookie.match(/selected_lang=([^$ ;}]+)/); 104 // // If there's no cookie yet, let's try to set one. 105 // if (!cookieLangMatch) { 106 // // See if the user's browser language is one that we support directly. 107 // for (const langCode of navigator.languages) { 108 // let domainLangCode = langCode; 109 // if (langCode.toLowerCase().includes("-hant") || langCode.toLowerCase().includes("-tw")) { 110 // domainLangCode = "tw"; 111 // } 112 113 // // Take the first language that we support. 114 // if (langCodes.includes(domainLangCode)) { 115 // setLangCookie(domainLangCode); 116 // // Bail out so we don't redirect to a suboptimal language. 117 // break; 118 // } 119 // } 120 // } 121 // } 122 123 { 124 const cookieLangMatch = document.cookie.match(/selected_lang=([^$ ;}]+)/); 125 if (cookieLangMatch) { 126 // Refresh cookie with a new expiry, in case the browser has 127 // restricted it. 128 var explicitlyRequestedLangCode = cookieLangMatch[1]; 129 setLangCookie(explicitlyRequestedLangCode); 130 131 // If a cookie is set, that we want to go to the language, so let's redirect. 132 if (explicitlyRequestedLangCode != subDomainLangCode) { 133 redirectLang(explicitlyRequestedLangCode); 134 } 135 } 136 } 137 138 window.submitForm = function(event, url, handler) { 139 event.preventDefault(); 140 141 const currentTarget = event.currentTarget; 142 const fieldset = currentTarget.querySelector("fieldset"); 143 currentTarget.querySelector(".js-failure").classList.add("hidden"); 144 145 // Before disabling the fieldset. 146 fetch(url, { method: "PUT", body: new FormData(currentTarget) }) 147 .then(function(response) { 148 if (!response.ok) { throw "error"; } 149 return response.json().then(function(jsonResponse) { 150 fieldset.classList.add("hidden"); 151 currentTarget.querySelector(".js-success").classList.remove("hidden"); 152 if (handler) { 153 handler(jsonResponse); 154 } 155 }); 156 }) 157 .catch(function() { 158 fieldset.removeAttribute("disabled", "disabled"); 159 fieldset.style.opacity = 1; 160 currentTarget.querySelector(".js-failure").classList.remove("hidden"); 161 }) 162 .finally(function() { 163 currentTarget.querySelector(".js-spinner").classList.add("invisible"); 164 }); 165 166 fieldset.setAttribute("disabled", "disabled"); 167 fieldset.style.opacity = 0.5; 168 currentTarget.querySelector(".js-spinner").classList.remove("invisible"); 169 }; 170 })(); 171 </script> 172 <script> 173 (function() { 174 var pageUrl = new URL(document.location); 175 var hashMatch = pageUrl.hash.match(/r=([a-zA-Z0-9]+)/); 176 if (hashMatch && hashMatch[1].length < 20) { 177 if (!document.cookie.includes('ref_id=' + hashMatch[1])) { 178 if (window.baseDomain) { 179 document.cookie = 'ref_id=' + hashMatch[1] + ';path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;domain=' + window.baseDomain; 180 } else { 181 document.cookie = 'ref_id=' + hashMatch[1] + ';path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT'; 182 } 183 if (pageUrl.pathname == "/donate") { 184 document.location = "/donate"; 185 } 186 } 187 } 188 })(); 189 </script> 190 {% block main %} 191 <div class="header" role="navigation"> 192 <div> 193 <!-- <div class="[html:not(.aa-logged-in)_&]:hidden"> --> 194 {% if g.domain_lang_code == 'zh' and False %} <!-- NOTE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> 195 <!-- blue --> 196 <div class="bg-[#0195ff] hidden js-top-banner"> 197 <div class="max-w-[1050px] mx-auto px-4 py-2 text-[#fff] flex justify-between"> 198 <div> 199 <!-- GFW, payment processors, ads --> 200 我们正在寻找专业服务,可以帮助可靠地绕过GFW,例如通过设置定期更改的代理和域名,或其他技巧。如果您确实具有此方面的实际专业经验,请与我们联系。<strong>我们愿意为此付出代价。</strong><a class="custom-a text-[#fff] hover:text-[#ddd] underline text-xs" href="mailto:AnnaArchivist@proton.me">AnnaArchivist@proton.me</a> 201 <!-- <span class="text-xs">我们还在寻找能够让我们保持匿名的专业支付宝/微信支付处理器,使用加密货币。此外,我们正在寻找希望放置小而别致广告的公司。</span> --> 202 </div> 203 <div> 204 <a href="#" class="custom-a ml-2 text-[#fff] hover:text-[#ddd] js-top-banner-close">✕</a> 205 </div> 206 </div> 207 </div> 208 {% else %} 209 <!-- blue --> 210 <div class="bg-[#0195ff] hidden js-top-banner"> 211 <div class="max-w-[1050px] mx-auto px-4 py-2 text-[#fff] flex justify-between"> 212 <!-- <div> 213 🎄 <strong>{{ gettext('layout.index.header.banner.holiday_gift') }}</strong> ❄️ {{ gettext('layout.index.header.banner.surprise') }} <a class="custom-a text-[#fff] hover:text-[#ddd] underline" href="/donate">{{ gettext('layout.index.header.nav.donate') }}</a> 214 </div> --> 215 <!-- <div> 216 To increase the resiliency of Anna’s Archive, we’re looking for volunteers to run mirrors. <a class="custom-a text-[#fff] hover:text-[#ddd] underline text-xs" href="/mirrors">{{ gettext('layout.index.header.learn_more') }}</a> 217 </div> --> 218 <div> 219 {{ gettext('layout.index.header.banner.valentine_gift') }} {{ gettext('layout.index.header.banner.refer', percentage=50) }} <a class="custom-a text-[#fff] hover:text-[#ddd] underline text-xs" href="/refer">{{ gettext('layout.index.header.learn_more') }}</a> 220 </div> 221 <div> 222 <a href="#" class="custom-a ml-2 text-[#fff] hover:text-[#ddd] js-top-banner-close">✕</a> 223 </div> 224 </div> 225 </div> 226 {% endif %} 227 228 229 <!-- blue --> 230 <!-- <div class="bg-[#0195ff] hidden js-top-banner"> --> 231 <!-- purple --> 232 <!-- <div class="bg-[#7f01ff] hidden js-top-banner"> --> 233 <!-- <div class="hidden js-top-banner text-xs sm:text-base [html:not(.aa-logged-in)_&]:hidden"> --> 234 <!-- <div> 235 {{ gettext('layout.index.header.banner.new_donation_method', method_name=('<strong>Paypal</strong>' | safe), donate_link_open_tag=('<a href="/donate" class="custom-a text-[#fff] hover:text-[#ddd] underline">' | safe)) }} 236 </div> --> 237 <!-- <div> 238 We now have a <a class="custom-a text-[#fff] hover:text-[#ddd] underline" href="https://t.me/annasarchiveorg">Telegram</a> channel. Join us and discuss the future of Anna’s Archive.<br/>You can still also follow us on <a class="custom-a text-[#fff] hover:text-[#ddd] underline" href="https://www.reddit.com/r/Annas_Archive">Reddit</a>. 239 </div> --> 240 <!-- <div class="max-w-[1050px] mx-auto px-4 py-2"> 241 <div class="flex justify-between mb-2"> 242 <div>{{ gettext('layout.index.banners.comics_fundraiser.text') }}</div> 243 <div><a href="#" class="custom-a text-[#777] hover:text-black js-top-banner-close">✕</a></div> 244 </div> 245 <div style="background: #fff; padding: 8px; border-radius: 8px; box-shadow: 0px 2px 4px 0px #00000020"> 246 {% include 'macros/fundraiser.html' %} 247 </div> 248 </div> --> 249 <!-- <div class="max-w-[1050px] mx-auto px-4 py-2 text-[#fff] flex justify-between bg-[#0160a7]"> 250 <div> 251 Do you know experts in <strong>anonymous merchant payments</strong>? Can you help us add more convenient ways to donate? PayPal, Alipay, credit cards, gift cards. Please contact us at <a class="custom-a text-[#fff] hover:text-[#ddd] underline break-all" href="mailto:AnnaArchivist@proton.me">AnnaArchivist@proton.me</a>. 252 </div> 253 <div> 254 <a href="#" class="custom-a text-[#fff] hover:text-[#ddd] js-top-banner-close">✕</a> 255 </div> 256 </div> --> 257 <!-- <div class="max-w-[1050px] mx-auto text-[#fff] bg-[#0160a7]"> 258 <div class="flex justify-between"> 259 <div class="px-4 py-2"> 260 New technical blog post: <a class="custom-a text-[#fff] hover:text-[#ddd] underline" href="https://annas-blog.org/annas-archive-containers.html">Anna’s Archive Containers (AAC): standardizing releases from the world’s largest shadow library</a> 261 </div> 262 <div class="px-4 py-2"> 263 <a href="#" class="custom-a text-[#fff] hover:text-[#ddd] js-top-banner-close">✕</a> 264 </div> 265 </div> 266 <div class="px-4 py-2 bg-green-500"> 267 Do you know experts in <strong>anonymous merchant payments</strong>? Can you help us add more convenient ways to donate? PayPal, Alipay, credit cards, gift cards. Please contact us at <a class="custom-a text-[#fff] hover:text-[#ddd] underline break-all" href="mailto:AnnaArchivist@proton.me">AnnaArchivist@proton.me</a>. 268 </div> 269 </div> --> 270 <!-- </div> --> 271 <script> 272 (function() { 273 if (document.querySelector('.js-top-banner')) { 274 var latestTopBannerType = '10'; 275 var topBannerMatch = document.cookie.match(/top_banner_hidden=([^$ ;}]+)/); 276 var topBannerType = ''; 277 if (topBannerMatch) { 278 topBannerType = topBannerMatch[1]; 279 // Refresh cookie. 280 document.cookie = 'top_banner_hidden=' + topBannerType + ';path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT'; 281 } 282 if (topBannerType !== latestTopBannerType) { 283 document.querySelector('.js-top-banner').style.display = 'block'; 284 document.querySelector('.js-top-banner-close').addEventListener('click', function(event) { 285 document.querySelector('.js-top-banner').style.display = 'none'; 286 document.cookie = 'top_banner_hidden=' + latestTopBannerType + ';path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT'; 287 event.preventDefault(); 288 return false; 289 }); 290 } 291 } 292 })(); 293 </script> 294 </div> 295 <div class="header-inner"> 296 <div class="header-inner-top"> 297 <a href="/" class="custom-a text-black hover:text-[#444]"><h1 class="text-2xl sm:text-4xl">{{ gettext('layout.index.header.title') }}</h1></a> 298 <select class="text-lg bg-center icon-[twemoji--globe-with-meridians] py-1 rounded text-gray-500 max-w-[50px] mt-1 ml-2 appearance-none" style="width: 1.8em; height: 1.6em; background-color: white; background-size: 1em;" onchange="handleChangeLang(event)"> 299 <option></option> 300 {% for lang_code, lang_name in g.languages %} 301 <option value="{{ lang_code }}">{{ lang_name }} [{{ lang_code }}]{% if lang_code == g.domain_lang_code %} ☑️{% endif %}</option> 302 {% endfor %} 303 </select> 304 </div> 305 306 <div class="mb-1.5"> 307 <div class="max-md:hidden">{{ g.header_tagline | safe }} <a class="text-xs" href="/about">{{ gettext('layout.index.header.learn_more') }}</a></div> 308 <div class="max-sm:hidden md:hidden">{{ g.header_tagline_mid | safe }} <a class="text-xs" href="/about">{{ gettext('layout.index.header.learn_more') }}</a></div> 309 <div class="sm:hidden text-sm">{{ g.header_tagline_short | safe }} <a class="text-xs" href="/about">{{ gettext('layout.index.header.learn_more') }}</a></div> 310 </div> 311 312 <div class="hidden sm:flex text-xs mb-1" aria-hidden="true"> 313 <div class="font-bold shrink-0">{{ gettext('layout.index.header.recent_downloads') }} </div> 314 <div class="w-full overflow-hidden flex js-recent-downloads-scroll"> 315 <!-- Make sure tailwind picks up these classes --> 316 <div class="shrink-0 min-w-full"></div> 317 <div class="inline-block max-w-[50%] truncate"></div> 318 </div> 319 <script> 320 (function() { 321 function showRecentDownloads(items) { 322 // Biased towards initial positions, but whatever. 323 const shuffledItems = [...items].sort(() => Math.random() - 0.5).slice(0, 8); 324 325 const titlesLength = shuffledItems.map((item) => item.title).join(" ").length; 326 const scrollHtml = `<div class="shrink-0 min-w-full" style="animation: scroll ${Math.round(titlesLength/4)}s linear infinite">` + shuffledItems.map((item) => `<span class="inline-block truncate"> • </span><a tabindex="-1" href="${(item.path[0] == '/' ? '' : '/') + item.path}" class="inline-block max-w-[50%] truncate">${item.title}</a>`).join('') + '</div>'; 327 document.querySelector('.js-recent-downloads-scroll').innerHTML = scrollHtml + scrollHtml; 328 } 329 330 function fetchNewRecentDownloads(cb) { 331 setTimeout(() => { 332 fetch("/dyn/recent_downloads/").then((response) => response.json()).then((items) => { 333 if (localStorage) { 334 localStorage.recentDownloadsData = JSON.stringify({ items, time: Date.now() }); 335 } 336 if (cb) { 337 cb(items); 338 } 339 }); 340 }, 100); 341 } 342 343 if (localStorage && localStorage.recentDownloadsData) { 344 const recentDownloadsData = JSON.parse(localStorage.recentDownloadsData); 345 // console.log('recentDownloadsData', recentDownloadsData); 346 showRecentDownloads(recentDownloadsData.items); 347 348 const timeToRefresh = 65000 /* 65 sec */ - (Date.now() - recentDownloadsData.time); 349 // Fetch new data for the next page load. 350 if (timeToRefresh <= 0) { 351 fetchNewRecentDownloads(undefined); 352 } else { 353 setTimeout(() => { 354 fetchNewRecentDownloads(undefined); 355 }, timeToRefresh); 356 } 357 } else { 358 fetchNewRecentDownloads((items) => { 359 showRecentDownloads(items); 360 }); 361 } 362 })(); 363 </script> 364 </div> 365 366 <script> 367 function topMenuToggle(event, className) { 368 const el = document.querySelector("." + className); 369 if (el.style.display === "block") { 370 el.style.display = "none"; 371 el.setAttribute('aria-expanded', "false"); 372 } else { 373 el.style.display = "block"; 374 el.setAttribute('aria-expanded', "true"); 375 function clickOutside(innerEvent) { 376 if (!el.contains(innerEvent.target)) { 377 el.style.display = "none"; 378 el.setAttribute('aria-expanded', "false") 379 document.removeEventListener('click', clickOutside); 380 innerEvent.preventDefault(); 381 return false; 382 } 383 } 384 setTimeout(function() { 385 document.addEventListener('click', clickOutside); 386 }, 0); 387 } 388 event.preventDefault(); 389 return false; 390 } 391 </script> 392 <div class="header-bar"> 393 <div class="header-links relative z-20"> 394 <a href="#" aria-expanded="false" onclick="topMenuToggle(event, 'js-top-menu-home')" class="header-link-first {{ 'header-link-active' if header_active.startswith('home') }}" style="margin-right: 24px;"> 395 <span class="header-link-normal"> 396 {% if header_active == 'home/search' %}{{ gettext('layout.index.header.nav.search') }} 397 {% elif header_active == 'home/about' %}{{ gettext('layout.index.header.nav.about') }} 398 {% elif header_active == 'home/datasets' %}{{ gettext('layout.index.header.nav.datasets') }} 399 {% elif header_active == 'home/torrents' %}{{ gettext('layout.index.header.nav.torrents') }} 400 {% elif header_active == 'home/mirrors' %}{{ gettext('layout.index.header.nav.mirrors') }} 401 {% elif header_active == 'home/llm' %}{{ gettext('layout.index.header.nav.llm_data') }} 402 {% elif header_active == 'home/mobile' %}{{ gettext('layout.index.header.nav.mobile') }} 403 {% elif header_active == 'home/security' %}{{ gettext('layout.index.header.nav.security') }} 404 {% elif header_active == 'home/wechat' %}<!-- {{ gettext('layout.index.header.nav.wechat') }} --> 405 {% else %}{{ gettext('layout.index.header.nav.home') }}{% endif %} 406 <span class="icon-[material-symbols--arrow-drop-down] absolute text-lg mt-[3px] -ml-px"></span> 407 </span> 408 <span class="header-link-bold"> 409 {% if header_active == 'home/search' %}{{ gettext('layout.index.header.nav.search') }} 410 {% elif header_active == 'home/about' %}{{ gettext('layout.index.header.nav.about') }} 411 {% elif header_active == 'home/datasets' %}{{ gettext('layout.index.header.nav.datasets') }} 412 {% elif header_active == 'home/torrents' %}{{ gettext('layout.index.header.nav.torrents') }} 413 {% elif header_active == 'home/mirrors' %}{{ gettext('layout.index.header.nav.mirrors') }} 414 {% elif header_active == 'home/llm' %}{{ gettext('layout.index.header.nav.llm_data') }} 415 {% elif header_active == 'home/mobile' %}{{ gettext('layout.index.header.nav.mobile') }} 416 {% elif header_active == 'home/security' %}{{ gettext('layout.index.header.nav.security') }} 417 {% elif header_active == 'home/wechat' %}<!-- {{ gettext('layout.index.header.nav.wechat') }} --> 418 {% else %}{{ gettext('layout.index.header.nav.home') }}{% endif %} 419 <span class="icon-[material-symbols--arrow-drop-down] absolute text-lg mt-[3px] -ml-px"></span> 420 </span> 421 </a> 422 <div class="absolute left-0 top-full bg-[#f2f2f2] px-4 shadow js-top-menu-home hidden"> 423 <a class="custom-a block py-1 {% if header_active == 'home/home' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/">{{ gettext('layout.index.header.nav.home') }}</a> 424 <a class="custom-a block py-1 {% if header_active == 'home/search' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/search">{{ gettext('layout.index.header.nav.search') }}</a> 425 <a class="custom-a block py-1 {% if header_active == 'home/about' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/about">{{ gettext('layout.index.header.nav.about') }}</a> 426 <a class="custom-a block py-1 {% if header_active == 'home/datasets' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/datasets">{{ gettext('layout.index.header.nav.datasets') }}</a> 427 <a class="custom-a block py-1 {% if header_active == 'home/torrents' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/torrents">{{ gettext('layout.index.header.nav.torrents') }}</a> 428 <a class="custom-a block py-1 {% if header_active == 'home/mirrors' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/mirrors">{{ gettext('layout.index.header.nav.mirrors') }}</a> 429 <!-- <a class="custom-a block py-1 {% if header_active == 'home/llm' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/llm">{{ gettext('layout.index.header.nav.llm_data') }}</a> --> 430 <a class="custom-a block py-1 {% if header_active == 'home/mobile' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/mobile">{{ gettext('layout.index.header.nav.mobile') }}</a> 431 {% if g.show_wechat_in_layout %} 432 <!-- <a class="custom-a block py-1 {% if header_active == 'home/wechat' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/wechat">{{ gettext('layout.index.header.nav.wechat') }}</a> --> 433 {% endif %} 434 <a class="custom-a block py-1 text-black/64 hover:text-black" href="https://annas-blog.org" target="_blank">{{ gettext('layout.index.header.nav.annasblog') }}</a> 435 <a class="custom-a block py-1 text-black/64 hover:text-black" href="https://annas-software.org" target="_blank">{{ gettext('layout.index.header.nav.annassoftware') }}</a> 436 <a class="custom-a block py-1 text-black/64 hover:text-black" href="https://translate.annas-software.org" target="_blank">{{ gettext('layout.index.header.nav.translate') }}</a> 437 </div> 438 <a href="/donate" class="{{ 'header-link-active' if header_active == 'donate' }}"><span class="header-link-normal">{{ gettext('layout.index.header.nav.donate') }}</span><span class="header-link-bold">{{ gettext('layout.index.header.nav.donate') }}</span></a> 439 </div> 440 <form class="header-search hidden sm:flex" action="/search" method="get" role="search"> 441 <input class="js-slash-focus rounded" name="q" type="search" placeholder="{{ gettext('common.search.placeholder') }}" value="{{search_input}}" title="Focus: '/' Scroll search results: 'j', 'k'"> 442 </form> 443 <div class="header-links header-links-right relative z-10 ml-auto items-center"> 444 <!-- <div class="mr-1 bg-[#0095ff] text-white text-xs font-medium px-1 py-0.5 rounded">{{ gettext('layout.index.header.nav.beta') }}</div> --> 445 <a href="#" aria-expanded="false" onclick="topMenuToggle(event, 'js-top-menu-login')" class="header-link-first {{ 'header-link-active' if header_active.startswith('account') }} [html.aa-logged-in_&]:hidden"> 446 <span class="header-link-normal"> 447 {% if header_active == 'account/request' %}{{ gettext('layout.index.header.nav.request') }} 448 {% elif header_active == 'account/upload' %}{{ gettext('layout.index.header.nav.upload') }} 449 {% elif header_active == 'account/refer' %}{{ gettext('layout.index.header.nav.refer') }} 450 {% else %}{{ gettext('layout.index.header.nav.login_register') }}{% endif %} 451 <span class="icon-[material-symbols--arrow-drop-down] absolute text-lg mt-[3px] -ml-px"></span> 452 </span> 453 <span class="header-link-bold"> 454 {% if header_active == 'account/request' %}{{ gettext('layout.index.header.nav.request') }} 455 {% elif header_active == 'account/upload' %}{{ gettext('layout.index.header.nav.upload') }} 456 {% elif header_active == 'account/refer' %}{{ gettext('layout.index.header.nav.refer') }} 457 {% else %}{{ gettext('layout.index.header.nav.login_register') }}{% endif %} 458 <span class="icon-[material-symbols--arrow-drop-down] absolute text-lg mt-[3px] -ml-px"></span> 459 </span> 460 </a> 461 <div class="absolute right-0 top-full bg-[#f2f2f2] px-4 shadow js-top-menu-login hidden"> 462 <a class="custom-a block py-1 {% if header_active == 'account' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/login">{{ gettext('layout.index.header.nav.login_register') }}</a> 463 <a class="custom-a block py-1 {% if header_active == 'account/refer' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/account/refer">{{ gettext('layout.index.header.nav.refer') }}</a> 464 <a class="custom-a block py-1 {% if header_active == 'account/request' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/account/request">{{ gettext('layout.index.header.nav.request') }}</a> 465 <a class="custom-a block py-1 {% if header_active == 'account/upload' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/account/upload">{{ gettext('layout.index.header.nav.upload') }}</a> 466 </div> 467 <a href="#" aria-expanded="false" onclick="topMenuToggle(event, 'js-top-menu-account')" class="header-link-first {{ 'header-link-active' if header_active.startswith('account') }} [html:not(.aa-logged-in)_&]:hidden" style="margin-right: 8px;"> 468 <span class="header-link-normal"> 469 {% if header_active == 'account/profile' %}{{ gettext('layout.index.header.nav.public_profile') }} 470 {% elif header_active == 'account/downloaded' %}{{ gettext('layout.index.header.nav.downloaded_files') }} 471 {% elif header_active == 'account/donations' %}{{ gettext('layout.index.header.nav.my_donations') }} 472 {% elif header_active == 'account/request' %}{{ gettext('layout.index.header.nav.request') }} 473 {% elif header_active == 'account/upload' %}{{ gettext('layout.index.header.nav.upload') }} 474 {% elif header_active == 'account/refer' %}{{ gettext('layout.index.header.nav.refer') }} 475 {% else %}Account{% endif %} 476 <span class="icon-[material-symbols--arrow-drop-down] absolute text-lg mt-[3px] -ml-px"></span> 477 </span> 478 <span class="header-link-bold"> 479 {% if header_active == 'account/profile' %}{{ gettext('layout.index.header.nav.public_profile') }} 480 {% elif header_active == 'account/downloaded' %}{{ gettext('layout.index.header.nav.downloaded_files') }} 481 {% elif header_active == 'account/donations' %}{{ gettext('layout.index.header.nav.my_donations') }} 482 {% elif header_active == 'account/request' %}{{ gettext('layout.index.header.nav.request') }} 483 {% elif header_active == 'account/upload' %}{{ gettext('layout.index.header.nav.upload') }} 484 {% elif header_active == 'account/refer' %}{{ gettext('layout.index.header.nav.refer') }} 485 {% else %}Account{% endif %} 486 <span class="icon-[material-symbols--arrow-drop-down] absolute text-lg mt-[3px] -ml-px"></span> 487 </span> 488 </a> 489 <div class="absolute right-0 top-full bg-[#f2f2f2] px-4 shadow js-top-menu-account hidden"> 490 <a class="custom-a block py-1 {% if header_active == 'account' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/account">{{ gettext('layout.index.header.nav.account') }}</a> 491 <a class="custom-a block py-1 {% if header_active == 'account/profile' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/account/profile">{{ gettext('layout.index.header.nav.public_profile') }}</a> 492 <a class="custom-a block py-1 {% if header_active == 'account/downloaded' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/account/downloaded">{{ gettext('layout.index.header.nav.downloaded_files') }}</a> 493 <a class="custom-a block py-1 {% if header_active == 'account/donations' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/account/donations">{{ gettext('layout.index.header.nav.my_donations') }}</a> 494 <a class="custom-a block py-1 {% if header_active == 'account/refer' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/account/refer">{{ gettext('layout.index.header.nav.refer') }}</a> 495 <a class="custom-a block py-1 {% if header_active == 'account/request' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/account/request">{{ gettext('layout.index.header.nav.request') }}</a> 496 <a class="custom-a block py-1 {% if header_active == 'account/upload' %}font-bold text-black{% else %}text-black/64{% endif %} hover:text-black" href="/account/upload">{{ gettext('layout.index.header.nav.upload') }}</a> 497 </div> 498 </div> 499 </div> 500 </div> 501 </div> 502 <main class="main">{% block body %}{% endblock %}</main> 503 <footer class="bg-black/5 text-[#777]" style="box-shadow: 0px 0px 7px rgb(0 0 0 / 30%)"> 504 <div class="max-w-[1050px] mx-auto p-[12px] leading-relaxed flex flex-wrap"> 505 <div class="mr-4 mb-4 grow"> 506 <strong class="font-bold text-black">{{ gettext('layout.index.footer.list1.header') }}</strong><br> 507 <a class="custom-a hover:text-[#333]" href="/">{{ gettext('layout.index.header.nav.home') }}</a><br> 508 <a class="custom-a hover:text-[#333]" href="/search">{{ gettext('layout.index.header.nav.search') }}</a><br> 509 <a class="custom-a hover:text-[#333]" href="/about">{{ gettext('layout.index.header.nav.about') }}</a><br> 510 <a class="custom-a hover:text-[#333]" href="/donate">{{ gettext('layout.index.header.nav.donate') }}</a><br> 511 <a class="custom-a hover:text-[#333]" href="/refer">{{ gettext('layout.index.header.nav.refer') }}</a><br> 512 <a class="custom-a hover:text-[#333]" href="/account/request">{{ gettext('layout.index.header.nav.request') }}</a><br> 513 <a class="custom-a hover:text-[#333]" href="/account/upload">{{ gettext('layout.index.header.nav.upload') }}</a><br> 514 <a class="custom-a hover:text-[#333]" href="/mobile">{{ gettext('layout.index.header.nav.mobile') }}</a><br> 515 <select class="p-1 rounded text-gray-500 mt-1 max-w-[110px]" onchange="handleChangeLang(event)"> 516 {% for lang_code, lang_name in g.languages %} 517 {% if g.domain_lang_code == lang_code %} 518 <option value="{{ lang_code }}">🌐 {{ lang_name }} [{{ lang_code }}]</option> 519 {% endif %} 520 {% endfor %} 521 {% for lang_code, lang_name in g.languages %} 522 <option value="{{ lang_code }}">{{ lang_name }} [{{ lang_code }}]{% if lang_code == g.domain_lang_code %} ☑️{% endif %}</option> 523 {% endfor %} 524 </select> 525 </div> 526 527 <div class="mr-4 mb-4 grow grow"> 528 <strong class="font-bold text-black">{{ gettext('layout.index.footer.list2.header') }}</strong><br> 529 <a class="custom-a hover:text-[#333]" href="https://www.reddit.com/r/Annas_Archive">{{ gettext('layout.index.footer.list2.reddit') }}</a> / <a class="custom-a hover:text-[#333]" href="https://t.me/annasarchiveorg">{{ gettext('layout.index.footer.list2.telegram') }}</a><!-- {% if not g.show_wechat_in_layout %} / <a class="custom-a hover:text-[#333]" href="/wechat">{{ gettext('layout.index.header.nav.wechat') }}</a>{% endif %}--><br> 530 <a class="custom-a hover:text-[#333]" href="https://annas-blog.org">{{ gettext('layout.index.header.nav.annasblog') }}</a><br> 531 <a class="custom-a hover:text-[#333]" href="https://annas-software.org">{{ gettext('layout.index.header.nav.annassoftware') }}</a><br> 532 <a class="custom-a hover:text-[#333]" href="https://translate.annas-software.org">{{ gettext('layout.index.header.nav.translate') }}</a><br> 533 <a class="custom-a hover:text-[#333] break-all" href="mailto:AnnaArchivist@proton.me">AnnaArchivist@proton.me</a><br> 534 <div class="text-xs text-gray-500 mb-1 max-w-[200px]">{{ gettext('layout.index.footer.dont_email', a_request=('href="/account/request"' | safe), a_upload=('href="/account/upload"' | safe)) }} {{ gettext('page.donate.small_team') }}</div> 535 <a class="custom-a hover:text-[#333]" href="/copyright">{{ gettext('layout.index.footer.list2.dmca_copyright') }}</a><br> 536 <a class="custom-a hover:text-[#333]" href="mailto:AnnaDMCA@proton.me">AnnaDMCA@proton.me</a><br> 537 </div> 538 539 <div class="mr-4 mb-4 grow"> 540 <strong class="font-bold text-black">Advanced</strong><br> 541 <a class="custom-a hover:text-[#333]" href="/datasets">{{ gettext('layout.index.header.nav.datasets') }}</a><br> 542 <a class="custom-a hover:text-[#333]" href="/torrents">{{ gettext('layout.index.header.nav.torrents') }}</a><br> 543 <a class="custom-a hover:text-[#333]" href="/mirrors">{{ gettext('layout.index.header.nav.mirrors') }}</a><br> 544 <a class="custom-a hover:text-[#333]" href="/llm">{{ gettext('layout.index.header.nav.llm_data') }}</a><br> 545 <a class="custom-a hover:text-[#333]" href="/security">{{ gettext('layout.index.header.nav.security') }}</a><br> 546 </div> 547 548 <div class="grow"> 549 <strong class="font-bold text-black">{{ gettext('layout.index.footer.list3.header') }}</strong><br> 550 <a class="custom-a hover:text-[#333] js-annas-archive-org" href="https://annas-archive.org">annas-archive.org</a></a><br> 551 <a class="custom-a hover:text-[#333] js-annas-archive-gs" href="https://annas-archive.gs">annas-archive.gs</a><br> 552 <a class="custom-a hover:text-[#333] js-annas-archive-se" href="https://annas-archive.se">annas-archive.se</a><br> 553 {% if g.show_wechat_in_layout %} 554 <!-- <a class="custom-a hover:text-[#333]" href="/wechat">{{ gettext('layout.index.header.nav.wechat') }}</a><br> 555 <div class="bg-white p-2 max-w-[130px]"><img class="w-full" src="/images/wechat.jpg"></div> --> 556 {% endif %} 557 </div> 558 </div> 559 </footer> 560 {% endblock %} 561 <script> 562 (function() { 563 // Possible domains we can encounter: 564 const domainsToReplace = ["annas-" + "archive.org", "annas-" + "archive.gs", "annas-" + "archive.se", "localtest.me:8000", "localtest.me", window.baseDomain]; 565 const validDomains = ["annas-" + "archive.org", "annas-" + "archive.gs", "annas-" + "archive.se", "localtest.me:8000", "localtest.me"]; 566 // For checking and redirecting if our current host is down (but if Cloudflare still responds). 567 const initialCheckMs = 0; 568 const intervalCheckOtherDomains = 10000; 569 const domainsToNavigateTo = ["annas-" + "archive.org", "annas-" + "archive.gs", "annas-" + "archive.se"]; 570 // For testing: 571 // const domainsToNavigateTo = ["localtest.me:8000", "testing_redirects.localtest.me:8000"]; 572 573 // const isInvalidDomain = false; 574 // const isInvalidDomain = true; 575 const isInvalidDomain = !validDomains.includes(window.baseDomain); 576 if (isInvalidDomain) { 577 console.log("Invalid domain"); 578 // If the domain is invalid, replace window.baseDomain first, in case the domain 579 // is something weird like 'weird.annas-archive.org'. 580 domainsToReplace.unshift(window.baseDomain); 581 } 582 583 // First, set the mirror links at the bottom of the page. 584 const loc = "" + window.location; 585 let currentDomainToReplace = "localtest.me"; 586 for (const domain of domainsToReplace) { 587 if (loc.includes(domain)) { 588 currentDomainToReplace = domain; 589 break; 590 } 591 } 592 for (const el of document.querySelectorAll(".js-annas-archive-org")) { 593 el.href = loc.replace(currentDomainToReplace, "annas-" + "archive.org"); 594 } 595 for (const el of document.querySelectorAll(".js-annas-archive-gs")) { 596 el.href = loc.replace(currentDomainToReplace, "annas-" + "archive.gs"); 597 } 598 for (const el of document.querySelectorAll(".js-annas-archive-se")) { 599 el.href = loc.replace(currentDomainToReplace, "annas-" + "archive.se"); 600 } 601 602 // Use the new domain in all links and forms. 603 let areUsingOtherDomain = false; 604 function useOtherDomain(domain) { 605 if (areUsingOtherDomain) { 606 return; 607 } 608 areUsingOtherDomain = true; 609 if (isInvalidDomain) { 610 const newLoc = loc.replace(currentDomainToReplace, domain); 611 if (newLoc !== loc) { 612 window.location = newLoc; 613 return; 614 } 615 } 616 const newOrigin = window.location.origin.replace(currentDomainToReplace, domain); 617 for (const el of document.querySelectorAll("a")) { 618 el.href = el.href.replace(currentDomainToReplace, domain); 619 } 620 for (const el of document.querySelectorAll("form")) { 621 el.action = el.action.replace(currentDomainToReplace, domain); 622 } 623 } 624 // useOtherDomain('annas-archive.org'); // For testing. 625 626 function getRandomString() { 627 return Math.random() + "." + Math.random() + "." + Math.random(); 628 } 629 630 // Check if there are other domains that are still up. Use the first one that responds. 631 let foundOtherDomain = false; 632 function checkOtherDomains() { 633 console.log('checkOtherDomains'); 634 if (foundOtherDomain) { 635 return; 636 } 637 const otherFetchOptions = { mode: "cors", method: "GET", credentials: "omit", cache: "no-cache", redirect: "error" }; 638 for (const domain of domainsToNavigateTo) { 639 if (currentDomainToReplace !== domain) { 640 fetch('//' + domain + '/dyn/up/?' + getRandomString(), otherFetchOptions).then(function(response) { 641 if (foundOtherDomain) { 642 return; 643 } 644 if (!(response.status >= 500 && response.status <= 599)) { 645 foundOtherDomain = true; 646 useOtherDomain(domain); 647 } 648 }).catch(function() { 649 // Ignore. 650 }); 651 } 652 } 653 } 654 655 // If we're not on a valid domain, try to go to a valid domain. 656 if (isInvalidDomain) { 657 checkOtherDomains(); 658 // Keep checking in case one comes online. 659 setInterval(checkOtherDomains, intervalCheckOtherDomains); 660 } else { 661 // Keep checking the current domain once, to see if it's still up. 662 function checkCurrentDomain() { 663 const currentFetchOptions = { method: "GET", credentials: "same-origin", cache: "no-cache", redirect: "error" }; 664 fetch('/dyn/up/?' + getRandomString(), currentFetchOptions).then(function(response) { 665 // Only do something in the case of an actual error code from Cloudflare, not if the users network is bad. 666 if (response.status >= 500 && response.status <= 599) { 667 checkOtherDomains(); 668 // Keep checking in case one comes online. 669 setInterval(checkOtherDomains, intervalCheckOtherDomains); 670 } 671 if (response.status === 200) { 672 return response.json().then(function(jsonResponse) { 673 window.globalUpdateAaLoggedIn(jsonResponse.aa_logged_in); 674 }); 675 } 676 }).catch(function() { 677 // Ignore; see above. 678 }); 679 } 680 setTimeout(checkCurrentDomain, initialCheckMs); 681 } 682 })(); 683 </script> 684 <!-- Cloudflare Web Analytics --><script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "a60431b24db04cf2beeef0864f1df5e4"}'></script><!-- End Cloudflare Web Analytics --> 685 </body> 686 </html>