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>