/ allthethings / templates / layouts / index.html
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') }}&nbsp;&nbsp;</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">&nbsp;•&nbsp;</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>