www.git

www

~ Personal site — projects, posts, about. Built with sqzass.
git clone https://github.com/debuman/www.git

templates/base.html (8996B)

      1 {#- 모든 페이지가 상속하는 골격. 여기는 크롬만 안다 — 본문은 각 템플릿이
      2     content 블록에 채우고, 카피는 content/ 와 i18n/ 이 갖는다.
      3 
      4     내비는 site.sections 를 그대로 돈다. 현재 언어의 트리만 담기므로 번역이
      5     없는 갈래는 애초에 그려지지 않고, 죽은 링크가 생길 수 없다. 그래서 About도
      6     페이지가 아니라 섹션이다 — 세 갈래가 같은 자료에서 나와야 하나만 다르게
      7     동작하는 일이 없다. #}
      8 {%- set ko = page.language == "ko" %}
      9 {#- 서브경로에 올라갈 수도 있으므로 손으로 쓰는 링크에는 base_path를 붙인다.
     10     페이지 URL들은 이미 이 접두사를 품고 있어서 손댈 필요가 없다.
     11 
     12     `home` 은 링크가 아니라 **갈래 URL 을 알아보는 접두사**다(nav_label 이 이걸로
     13     /posts/ 인지 /ko/posts/ 인지 가른다). 실제로 가는 첫 화면은 `root` 이고 둘은
     14     한국어에서 갈라진다 — 한국어 홈은 없다. 첫 화면은 영어 한 장뿐이고, 번역되는
     15     것은 읽으러 온 문서다. 브랜드가 /ko/ 로 가면 그건 없는 페이지다. #}
     16 {%- set home = site.base_path ~ ("/ko/" if ko else "/") %}
     17 {%- set root = site.base_path ~ "/" %}
     18 {% from "partials/nav.html" import nav_label %}
     19 <!doctype html>
     20 {#- 기본값을 여기 적는다. prefers-color-scheme 에 맡기지 않는 이유는 생성된
     21     하이라이트 스타일시트가 같은 [data-theme] 을 보기 때문이다. 둘이 서로 다른
     22     근거로 결정하면 한쪽이 어두운 페이지에 밝은 코드를 낸다. #}
     23 <html lang="{{ page.language }}" data-theme="light">
     24 <head>
     25 <meta charset="utf-8">
     26 <meta name="viewport" content="width=device-width, initial-scale=1">
     27 <title>{% if page.url == home %}{{ site.title }} — {{ site.description }}{% else %}{{ page.title }} · {{ site.title }}{% endif %}</title>
     28 {%- if page.description %}
     29 <meta name="description" content="{{ page.description }}">
     30 {%- endif %}
     31 <link rel="canonical" href="{{ page.permalink }}">
     32 {%- if site.feed %}
     33 <link rel="alternate" type="application/atom+xml" title="{{ site.title }}" href="{{ site.feed }}">
     34 {%- endif %}
     35 {%- for lang in page.translations %}
     36 <link rel="alternate" hreflang="{{ lang.code }}" href="{{ site.origin }}{{ lang.url }}">
     37 {%- endfor %}
     38 
     39 {#- 파비콘에는 CSS 가 닿지 않는다. 검은 선화는 어두운 탭에서 안 보이므로 흰 선
     40     버전을 따로 준다. media 를 모르는 브라우저는 마지막 것을 쓰므로, 밝은 탭이
     41     기본인 쪽을 뒤에 둔다. #}
     42 <link rel="icon" type="image/png" media="(prefers-color-scheme: dark)" href="{{ asset("images/favicon-dark.png") }}">
     43 <link rel="icon" type="image/png" href="{{ asset("images/favicon.png") }}">
     44 <link rel="apple-touch-icon" href="{{ asset("images/apple-touch-icon.png") }}">
     45 
     46 {#- 소셜 카드. sqzass 는 <head> 에 아무것도 넣지 않으므로 전부 우리 마크업이다.
     47     summary_large_image 는 이미지가 있을 때만 뜻이 있다 — 없이 선언하면 빈 상자가 나온다. #}
     48 <meta property="og:type" content="{{ "website" if page.is_section else "article" }}">
     49 <meta property="og:site_name" content="{{ site.title }}">
     50 <meta property="og:title" content="{{ page.title }}">
     51 <meta property="og:url" content="{{ page.permalink }}">
     52 <meta property="og:locale" content="{{ "ko_KR" if ko else "en_US" }}">
     53 {%- if page.description %}
     54 <meta property="og:description" content="{{ page.description }}">
     55 {%- endif %}
     56 <meta name="twitter:card" content="summary">
     57 
     58 {#- 라틴 서브셋만 미리 받는다. 한글 파일은 unicode-range 뒤에 있어서 한글이
     59     한 글자도 없는 페이지는 애초에 받지 않는데, preload 는 그 판단을 건너뛰므로
     60     여기 넣으면 영문 페이지가 쓰지도 않을 116KB 를 받는다. #}
     61 <link rel="preload" href="{{ asset("fonts/display-latin.woff2") }}" as="font" type="font/woff2" crossorigin>
     62 <link rel="stylesheet" href="{{ asset("css/main.css") }}">
     63 {%- if site.highlight_css %}
     64 <link rel="stylesheet" href="{{ site.highlight_css }}">
     65 {%- endif %}
     66 <script>
     67 /* 첫 페인트 전에 저장된 테마를 얹는다. 외부 파일이면 늦게 도착해서 반대 테마가
     68    한 번 번쩍인다. 그래서 인라인이다.
     69 
     70    `js` 클래스도 여기서 붙인다. 스크롤 리빌이 이 클래스 뒤에 숨어 있어서,
     71    defer 된 파일이 붙이면 내용이 한 번 보였다가 사라졌다가 다시 나타난다. */
     72 (function(){var d=document.documentElement;d.className+=" js";try{var t=localStorage.getItem("theme");if(t)d.dataset.theme=t}catch(e){}})();
     73 </script>
     74 </head>
     75 <body>
     76 <a class="skip" href="#content">{{ t("skip_to_content") }}</a>
     77 
     78 <header class="masthead">
     79   <div class="masthead-in">
     80     {#- 마크는 <img> 가 아니라 CSS 마스크다. 원본이 투명 배경의 검은 선화라
     81         그대로 넣으면 다크 배경에서 사라진다. 마스크로 두면 currentColor 를
     82         타서 테마와 호버를 글자와 똑같이 따라간다.
     83         aria-hidden 인 이유는 바로 옆 텍스트가 이미 같은 이름을 말하기 때문이다 —
     84         여기 이름을 또 넣으면 스크린리더가 두 번 읽는다. #}
     85     <a class="brand" href="{{ root }}">
     86       <span class="brand-mark" aria-hidden="true"></span>
     87       <span class="brand-name">{{ site.title }}</span>
     88     </a>
     89 
     90     <nav class="nav" aria-label="{{ t("menu") }}">
     91       {%- for s in site.sections %}
     92       <a href="{{ s.url }}"{% if page.url == s.url or (page.section and page.section.url == s.url) %} aria-current="true"{% endif %}>{{ nav_label(s, home) }}</a>
     93       {%- endfor %}
     94     </nav>
     95 
     96     <div class="tools">
     97       {%- if site.search %}
     98       <button class="chip chip-search search-trigger" type="button" data-index="{{ site.search }}"
     99               aria-label="{{ t("search_the_site") }}">
    100         {#- 아이콘 없이 키캡만. 무엇을 누르면 되는지가 돋보기보다 정확한 안내이고,
    101             검색이라는 이름은 aria-label 이 이미 지고 있다.
    102             라벨은 search.js 가 읽는 사람의 수정자 키로 바꿔 쓴다 — 이건 JS 가
    103             없을 때 남는 값.
    104 
    105             좁은 화면에서는 키캡이 빠지고 낱말이 대신 선다. 손가락으로 미는
    106             화면에 `Ctrl /` 는 아무 안내도 아니고, 키캡만 두면 그 자리가 이름
    107             없는 빈 버튼이 된다. #}
    108         <kbd>Ctrl /</kbd>
    109         <span class="chip-word">{{ t("search") }}</span>
    110       </button>
    111       {%- endif %}
    112       <a class="chip chip-icon chip-github" href="https://github.com/sqzer-x" aria-label="GitHub" title="GitHub">
    113         <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false"><path fill="currentColor" d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
    114       </a>
    115       {#- 지금 테마가 아니라 **누르면 갈 곳**을 보여 준다. 버튼은 상태가 아니라 동작을 말해야 한다. #}
    116       <button class="chip chip-icon theme-toggle" type="button" aria-label="{{ t("theme") }}" title="{{ t("theme") }}">
    117         <svg class="i-sun" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
    118           <circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/>
    119         </svg>
    120         <svg class="i-moon" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
    121           <path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/>
    122         </svg>
    123       </button>
    124     </div>
    125   </div>
    126 </header>
    127 
    128 <main id="content">
    129 {% block content %}{% endblock %}
    130 </main>
    131 
    132 {%- include "partials/footer.html" %}
    133 
    134 {%- if site.search %}
    135 {%- include "partials/search.html" %}
    136 {%- endif %}
    137 
    138 <script src="{{ asset("js/ui.js") }}" defer
    139         data-copy="{{ t("copy") }}" data-copied="{{ t("copied") }}"></script>
    140 {%- if site.search %}
    141 <script src="{{ asset("js/search.js") }}" defer
    142         data-empty="{{ t("search_empty") }}" data-hint="{{ t("search_hint") }}"></script>
    143 {%- endif %}
    144 </body>
    145 </html>