docs/templates/base.html (10092B)
1 {% from "partials/sidebar.html" import nav %}
2 {% from "partials/toc.html" import toc_list %}
3 {%- set ko = page.language == "ko" %}
4 {#- 서브경로에 올라갈 수도 있으므로 홈 링크에 site.base_path를 붙인다.
5 페이지 URL들은 이미 이 접두사를 품고 있어서 손댈 필요가 없다. #}
6 {%- set home = site.base_path ~ ("/ko/" if ko else "/") %}
7 <!doctype html>
8 {#- The default is written here rather than left to `prefers-color-scheme`, so
9 a reader with a light OS still gets the dark page the design assumes — and,
10 more importantly, so the generated highlight stylesheet's `[data-theme]`
11 rules match without JavaScript. The script below only overrides it. #}
12 <html lang="{{ page.language }}" data-theme="dark">
13 <head>
14 <meta charset="utf-8">
15 <meta name="viewport" content="width=device-width, initial-scale=1">
16 <title>{% if page.url == home %}{{ site.title }} — {{ t("tagline") }}{% else %}{{ page.title }} · {{ site.title }}{% endif %}</title>
17 {% if page.description %}<meta name="description" content="{{ page.description }}">{% endif %}
18 <link rel="canonical" href="{{ page.permalink }}">
19 {#- 피드 자동 발견. 이 언어에 날짜 있는 페이지가 하나도 없으면 site.feed가 없고,
20 그때는 링크도 걸지 않는다 — 빈 피드를 가리키는 링크가 제일 나쁘다. #}
21 {%- if site.feed %}
22 <link rel="alternate" type="application/atom+xml" title="{{ site.title }}" href="{{ site.feed }}">
23 {%- endif %}
24 {#- 파비콘은 CSS 필터가 닿지 않는다. 탭 배경이 어두울 때 검은 선화는 보이지
25 않으므로 흰 선 버전을 따로 준다. media를 이해 못 하는 브라우저는 마지막
26 것을 쓰므로, 밝은 탭이 기본인 쪽을 뒤에 둔다. #}
27 <link rel="icon" type="image/png" media="(prefers-color-scheme: dark)" href="{{ asset("images/favicon-dark.png") }}">
28 <link rel="icon" type="image/png" href="{{ asset("images/favicon.png") }}">
29 <link rel="apple-touch-icon" href="{{ asset("images/apple-touch-icon.png") }}">
30 {#- `lang`이지 `t`가 아니다. `t`는 번역 함수이고, 루프 변수가 그 이름을 가리면
31 이 블록 안에서 라벨을 하나 넣는 순간 조용히 깨진다. #}
32 {%- for lang in page.translations %}
33 <link rel="alternate" hreflang="{{ lang.code }}" href="{{ site.origin }}{{ lang.url }}">
34 {%- endfor %}
35 {#- 소셜 카드. sqzass는 <head>에 아무것도 주입하지 않으므로 이건 우리 마크업이다.
36 입력은 전부 이미 컨텍스트에 있다 — 새로 만들 데이터가 없다. #}
37 <meta property="og:type" content="{{ "website" if page.is_section else "article" }}">
38 <meta property="og:site_name" content="{{ site.title }}">
39 <meta property="og:title" content="{{ page.title }}">
40 <meta property="og:url" content="{{ page.permalink }}">
41 <meta property="og:locale" content="{{ "ko_KR" if ko else "en_US" }}">
42 {%- if page.description %}
43 <meta property="og:description" content="{{ page.description }}">
44 {%- endif %}
45 {#- summary_large_image는 이미지가 있을 때만 뜻이 있다. 이미지가 없으면 summary가
46 맞고, 이미지 없이 large를 선언하면 카드가 빈 상자로 나온다. #}
47 <meta name="twitter:card" content="summary">
48 <link rel="stylesheet" href="{{ asset("css/main.css") }}">
49 {%- if site.highlight_css %}
50 <link rel="stylesheet" href="{{ site.highlight_css }}">
51 {%- endif %}
52 <script>
53 /* Runs before first paint so a stored preference does not flash the other
54 theme. Inline on purpose: an external file would arrive too late. */
55 (function(){try{var t=localStorage.getItem("theme");if(t)document.documentElement.dataset.theme=t}catch(e){}})();
56
57 </script>
58 </head>
59 <body>
60 <a class="skip" href="#content">{{ t("skip_to_content") }}</a>
61
62 <header class="masthead">
63 {#- alt는 비운다. 바로 옆 텍스트가 이미 같은 이름을 말하므로, 여기에 이름을
64 또 넣으면 스크린리더가 "sqzass sqzass"라고 읽는다. #}
65 <a class="brand" href="{{ home }}">
66 <img src="{{ asset("images/logo.png") }}" alt="" width="121" height="120">
67 <span>{{ site.title }}</span>
68 </a>
69
70 {#- 문서 갈래. 사이드바와 같은 데이터를 한 단계 위에서 보여 주므로, 좁은 화면에서는
71 사이드바 서랍이 같은 일을 해서 감춘다. #}
72 <nav class="sections" aria-label="{{ t("sections") }}">
73 {%- for s in site.sections %}
74 <a href="{{ s.url }}"{% if page.section and page.section.url == s.url or page.url == s.url %} aria-current="true"{% endif %}>{{ s.title }}</a>
75 {%- endfor %}
76 </nav>
77
78 {#- 크롬은 전부 같은 컨트롤이다. 하나만 텍스트 링크로 두면 그 하나가 고장난
79 것처럼 보인다. #}
80 <div class="tools">
81 {%- if site.search %}
82 <button id="search-trigger" class="chip search-trigger" type="button"
83 data-index="{{ site.search }}">
84 <span>{{ t("search") }}</span>
85 {#- 라벨은 search.js가 읽는 사람의 수정자 키로 바꿔 쓴다. 이건 JS가 없을 때 값. #}
86 <kbd>Ctrl /</kbd>
87 </button>
88 {%- endif %}
89 {%- for lang in page.translations %}
90 <a class="chip" href="{{ lang.url }}" hreflang="{{ lang.code }}">{{ lang.name }}</a>
91 {%- endfor %}
92 {#- 아이콘은 인라인 SVG다. 이 레포에 "글리프 말고 텍스트"라는 결정이 있지만,
93 그 근거는 ◐/☾ 같은 문자가 폰트 폴백 스택에 없어 네모로 나온다는 것이었다.
94 SVG는 폰트를 타지 않으므로 그 이유가 적용되지 않는다. 대신 아이콘만 있는
95 컨트롤은 이름이 없으므로 aria-label을 반드시 준다. #}
96 <a class="chip chip-icon chip-wide" href="https://github.com/debuman/sqzass"
97 aria-label="GitHub" title="GitHub">
98 <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>
99 </a>
100 {#- 지금 테마가 아니라 **누르면 갈 곳**을 보여 준다. 다크에서는 해가 뜨고,
101 라이트에서는 달이 뜬다 — 버튼은 상태가 아니라 동작을 말해야 한다. #}
102 <button class="chip chip-icon theme-toggle" type="button"
103 aria-label="{{ t("theme") }}" title="{{ t("theme") }}">
104 <svg class="i-sun" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false"
105 fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
106 <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"/>
107 </svg>
108 <svg class="i-moon" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false"
109 fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
110 <path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/>
111 </svg>
112 </button>
113 </div>
114 </header>
115
116 <div class="layout">
117 {#- <details>는 열린 채로 나간다. 좁은 화면에서만 ui.js가 닫는다 — JS가 없으면
118 펼쳐진 채로 남는데, 그건 못 쓰는 상태가 아니라 그냥 긴 상태다. #}
119 <details class="sidebar" id="nav-drawer" open>
120 <summary>{{ t("documentation") }}</summary>
121 <nav aria-label="{{ t("documentation") }}">
122 {{ nav(site.sections, page.url, page.section.url if page.section else "") }}
123 </nav>
124 </details>
125
126 <main id="content">
127 <div class="title-block">
128 {%- if page.section %}
129 <nav class="crumbs" aria-label="{{ t("breadcrumb") }}">
130 <a href="{{ home }}">{{ t("home") }}</a>
131 <a href="{{ page.section.url }}">{{ page.section.title }}</a>
132 </nav>
133 {%- endif %}
134 {#- 페이지가 아닌 템플릿(404)은 자기 제목을 갖는다. #}
135 <h1>{% block heading %}{{ page.title }}{% endblock %}</h1>
136 {%- if page.description %}
137 <p class="lede">{{ page.description }}</p>
138 {%- endif %}
139 </div>
140
141 {#- The front matter fence: metadata ends, content begins. Same boundary
142 it marks in the source file. #}
143 <div class="fence" aria-hidden="true">+++</div>
144
145 {% block content %}{% endblock %}
146 </main>
147
148 {%- if page.toc and page.toc_entries %}
149 <aside class="toc" data-label="{{ t("on_this_page") }}" aria-label="{{ t("on_this_page") }}">
150 {{ toc_list(page.toc_entries) }}
151 </aside>
152 {%- endif %}
153 </div>
154
155 {%- include "partials/colophon.html" %}
156
157 {%- if site.search %}
158 <dialog id="search-dialog" class="search-dialog" aria-label="{{ t("search_the_docs") }}">
159 <div class="search-panel">
160 <input type="search" autocomplete="off" spellcheck="false" aria-label="{{ t("search_the_docs") }}">
161 <ul class="search-results"></ul>
162 <div class="search-foot">
163 <span class="search-status"></span>
164 <span class="search-hint"></span>
165 </div>
166 </div>
167 </dialog>
168 {%- endif %}
169
170 <script>
171 document.querySelector(".theme-toggle").addEventListener("click", function () {
172 var root = document.documentElement;
173 var next = root.dataset.theme === "light" ? "dark" : "light";
174 root.dataset.theme = next;
175 try { localStorage.setItem("theme", next); } catch (e) {}
176 });
177 </script>
178 {%- if site.search %}
179 <script src="{{ asset("js/search.js") }}" defer></script>
180 {%- endif %}
181 <script src="{{ asset("js/ui.js") }}" defer
182 data-copy="{{ t("copy") }}" data-copied="{{ t("copied") }}"></script>
183 </body>
184 </html>