sqzass.git

sqzass

~ A static site generator written in Rust
git clone https://github.com/debuman/sqzass.git

docs/static/css/main.css (31755B)

      1 /* sqzass documentation
      2  *
      3  * Dark first, and one accent. The page is otherwise achromatic, because the
      4  * colour that matters is in the code: highlighting is the feature this tool
      5  * does properly, and a second palette competing with it would be noise. The
      6  * accent is warm on purpose — it sits away from the blues and greens of the
      7  * syntax theme instead of arguing with them.
      8  *
      9  * The accent carries meaning, never decoration: links, the focus ring, the
     10  * current item, and the rule under the page title. If it is orange, it is
     11  * something you can go to or something you are on.
     12  *
     13  * The +++ rule appears once per page, between the title block and the body.
     14  * That is the same boundary it marks in a source file: front matter ends,
     15  * content begins. It is the one decorative-looking thing here and it is not
     16  * decoration.
     17  *
     18  * The theme is chosen by [data-theme], written into the document by the
     19  * template and overridden from localStorage — not by prefers-color-scheme.
     20  * The generated highlight stylesheet keys off the same attribute, so the two
     21  * cannot disagree, including with JavaScript off.
     22  */
     23 
     24 :root {
     25   color-scheme: dark;
     26 
     27   --paper:   #0e1013;
     28   --raised:  #171a1e;
     29   --ink:     #e8eaec;
     30   --ink-2:   #a7adb4;
     31   --ink-3:   #6f767e;
     32   --rule:    #242930;
     33   --accent:  #f0a15e;
     34 
     35   --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans KR",
     36           "Apple SD Gothic Neo", "Malgun Gothic", Roboto, sans-serif;
     37   --mono: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, Consolas,
     38           "Liberation Mono", monospace;
     39 
     40   --measure: 68ch;
     41   --gutter: clamp(1.25rem, 4vw, 3rem);
     42 
     43   /* 크롬 컨트롤의 치수. 검색·언어·GitHub·테마가 전부 이 값을 쓴다.
     44      하나만 다른 모양이면 그 하나가 고장난 것처럼 보인다.
     45      터미널에서 모는 도구라 키캡처럼 다룬다 — 얇은 테두리, 작은 반경. */
     46   --control-h: 2rem;
     47   --radius: 3px;
     48   --masthead-h: 3.5rem;
     49   /* 한 박자. 값을 여기 한 번만 두면 모든 상호작용이 같은 속도로 반응하고,
     50      그게 "같은 물건"이라는 느낌을 만든다. */
     51   --tr: 0.14s ease;
     52 
     53   --s-1: 0.8125rem;
     54   --s0:  1.0625rem;
     55   --s1:  1.25rem;
     56   --s2:  1.5rem;
     57   --s3:  1.9rem;
     58   --s4:  2.5rem;
     59 }
     60 
     61 :root[data-theme="light"] {
     62   color-scheme: light;
     63 
     64   --paper:   #fbfbfc;
     65   --raised:  #f1f3f5;
     66   --ink:     #14171a;
     67   --ink-2:   #4a5058;
     68   --ink-3:   #767d86;
     69   --rule:    #e2e5e9;
     70   /* Darker than the dark theme's accent: the same orange that reads clearly on
     71      near-black fails contrast on near-white. */
     72   --accent:  #9a4e05;
     73 }
     74 
     75 *, *::before, *::after { box-sizing: border-box; }
     76 
     77 html {
     78   -webkit-text-size-adjust: 100%;
     79   /* 목차나 heading anchor를 눌렀을 때 뛰지 않고 미끄러진다. 어디로 갔는지가
     80      보여야 방금 누른 것과 지금 보는 것을 연결할 수 있다. */
     81   scroll-behavior: smooth;
     82 }
     83 
     84 /* 스크롤바를 사이트의 일부로 다룬다. 코드 블록 하나만 손보고 페이지·사이드바·
     85    목차·검색 결과를 기본값으로 두면, 스크롤이 생기는 자리마다 다른 물건이 나온다.
     86    Firefox는 표준 속성을, 나머지는 -webkit- 의사요소를 본다. */
     87 * {
     88   scrollbar-width: thin;
     89   scrollbar-color: var(--rule) transparent;
     90 }
     91 ::-webkit-scrollbar { width: 8px; height: 8px; }
     92 ::-webkit-scrollbar-track { background: transparent; }
     93 ::-webkit-scrollbar-thumb {
     94   background: var(--rule);
     95   border-radius: 999px;
     96 }
     97 ::-webkit-scrollbar-thumb:hover { background: var(--ink-3); }
     98 ::-webkit-scrollbar-corner { background: transparent; }
     99 
    100 /* 선택 영역. 기본 파랑은 이 사이트의 어느 색과도 관계가 없다. */
    101 ::selection {
    102   background: color-mix(in srgb, var(--accent) 30%, transparent);
    103   color: var(--ink);
    104 }
    105 
    106 body {
    107   margin: 0;
    108   background: var(--paper);
    109   color: var(--ink);
    110   font: var(--s0)/1.7 var(--sans);
    111   /* Korean does not break inside a word. Without this, browsers split 어절
    112      mid-word at narrow widths and the text becomes hard to read. */
    113   word-break: keep-all;
    114   overflow-wrap: anywhere;
    115 }
    116 
    117 a { color: var(--accent); text-underline-offset: 0.2em; transition: color var(--tr); }
    118 a:hover { text-decoration-thickness: 2px; }
    119 
    120 :focus-visible {
    121   outline: 2px solid var(--accent);
    122   outline-offset: 2px;
    123   border-radius: 2px;
    124 }
    125 
    126 .skip {
    127   position: absolute;
    128   left: -9999px;
    129   top: 0;
    130   background: var(--paper);
    131   color: var(--ink);
    132   padding: 0.6rem 1rem;
    133   z-index: 10;
    134 }
    135 .skip:focus { left: 0; }
    136 
    137 /* ---------- header ---------- */
    138 
    139 /* 높이를 고정한다. 내용에 따라 늘어나면 페이지마다 상단바 높이가 달라지고,
    140    그게 "비율이 이상하다"로 읽힌다.
    141    align-items는 center다. baseline이면 로고를 품은 브랜드와 옆 내비가 서로
    142    다른 기준선에 앉아 글자들이 어긋난 것처럼 보인다. */
    143 .masthead {
    144   position: sticky;
    145   top: 0;
    146   z-index: 20;
    147   display: flex;
    148   align-items: center;
    149   gap: clamp(0.75rem, 2.5vw, 2rem);
    150   height: var(--masthead-h);
    151   padding: 0 var(--gutter);
    152   background: var(--paper);
    153   border-bottom: 1px solid var(--rule);
    154 }
    155 
    156 .brand {
    157   display: inline-flex;
    158   align-items: center;
    159   gap: 0.5rem;
    160   flex: none;
    161   font-family: var(--mono);
    162   font-size: var(--s1);
    163   font-weight: 600;
    164   letter-spacing: -0.03em;
    165   color: var(--ink);
    166   text-decoration: none;
    167   /* 좁은 화면에서 "sqzass"가 한 글자씩 세로로 쪼개지던 자리. */
    168   white-space: nowrap;
    169 }
    170 .brand:hover { color: var(--accent); }
    171 
    172 /* 검은 선화라 다크 배경에서는 그냥 안 보인다. 파일을 두 벌 두는 대신 반전한다 —
    173    순수 흑백 선화에서는 반전이 정확히 흰 선을 만든다. */
    174 .brand img {
    175   width: 1.9em;
    176   height: 1.9em;
    177   filter: invert(1);
    178 }
    179 :root[data-theme="light"] .brand img { filter: none; }
    180 
    181 /* 문서 갈래. 사이드바와 같은 데이터를 한 단계 위에서. */
    182 .sections {
    183   display: flex;
    184   align-items: center;
    185   gap: clamp(0.6rem, 1.6vw, 1.4rem);
    186   min-width: 0;
    187   overflow: hidden;
    188   font-size: var(--s-1);
    189   white-space: nowrap;
    190 }
    191 .sections a {
    192   color: var(--ink-2);
    193   text-decoration: none;
    194   line-height: var(--masthead-h);
    195   border-bottom: 2px solid transparent;
    196 }
    197 .sections a { transition: color var(--tr), border-color var(--tr); }
    198 .sections a:hover { color: var(--ink); }
    199 .sections a[aria-current] { color: var(--ink); border-bottom-color: var(--accent); }
    200 
    201 .tools {
    202   margin-left: auto;
    203   display: flex;
    204   align-items: center;
    205   gap: 0.4rem;
    206   flex: none;
    207 }
    208 
    209 /* ---------- chip: 크롬 컨트롤 하나의 모양 ----------
    210    검색, 언어, GitHub, 테마가 전부 이것이다. 링크든 버튼이든 겉모습은 같아야
    211    한다 — 사용자에게는 둘 다 "누르는 것"이고, 하나만 맨 텍스트면 그 하나가
    212    빠뜨린 것처럼 보인다. */
    213 .chip {
    214   display: inline-flex;
    215   align-items: center;
    216   gap: 0.5rem;
    217   height: var(--control-h);
    218   padding: 0 0.6rem;
    219   font: inherit;
    220   font-family: var(--mono);
    221   font-size: var(--s-1);
    222   line-height: 1;
    223   color: var(--ink-2);
    224   background: none;
    225   border: 1px solid var(--rule);
    226   border-radius: var(--radius);
    227   text-decoration: none;
    228   white-space: nowrap;
    229   cursor: pointer;
    230 }
    231 .chip { transition: color var(--tr), background var(--tr), border-color var(--tr); }
    232 .chip:hover {
    233   color: var(--ink);
    234   background: var(--raised);
    235   border-color: var(--ink-3);
    236 }
    237 
    238 /* 아이콘만 있는 컨트롤은 정사각이어야 한다. 글자 폭에 따라 너비가 달라지면
    239    같은 줄의 컨트롤들이 서로 다른 리듬을 갖는다. */
    240 .chip-icon {
    241   width: var(--control-h);
    242   padding: 0;
    243   justify-content: center;
    244 }
    245 .chip-icon svg { display: block; }
    246 
    247 /* 버튼은 상태가 아니라 동작을 말한다. 다크에서는 해(→ 라이트로), 라이트에서는 달. */
    248 .theme-toggle .i-moon { display: none; }
    249 :root[data-theme="light"] .theme-toggle .i-sun { display: none; }
    250 :root[data-theme="light"] .theme-toggle .i-moon { display: block; }
    251 
    252 .search-trigger { min-width: 11rem; justify-content: space-between; }
    253 .search-trigger kbd {
    254   font: inherit;
    255   font-size: 0.7rem;
    256   color: var(--ink-3);
    257   border: 1px solid var(--rule);
    258   border-radius: var(--radius);
    259   padding: 0.1rem 0.3rem;
    260 }
    261 
    262 /* ---------- breadcrumb ---------- */
    263 
    264 .crumbs {
    265   display: flex;
    266   flex-wrap: wrap;
    267   gap: 0.5rem;
    268   font-family: var(--mono);
    269   font-size: 0.75rem;
    270   letter-spacing: 0.06em;
    271   text-transform: uppercase;
    272   margin-bottom: 0.9rem;
    273 }
    274 .crumbs a { color: var(--ink-2); text-decoration: none; }
    275 .crumbs a:hover { color: var(--accent); text-decoration: underline; }
    276 /* 마지막 조각은 현재 위치라 링크가 아니다. 다른 조각과 같은 밝기면 어디까지가
    277    갈 수 있는 곳인지 읽히지 않는다. */
    278 .crumbs a:last-child { color: var(--ink); }
    279 .crumbs a + a::before { content: "/"; margin-right: 0.5rem; color: var(--rule); }
    280 
    281 /* ---------- layout ---------- */
    282 
    283 .layout {
    284   display: grid;
    285   grid-template-columns: 15rem minmax(0, 1fr) 13rem;
    286   gap: clamp(1.5rem, 4vw, 3rem);
    287   align-items: start;
    288   max-width: 84rem;
    289   margin: 0 auto;
    290   padding: 2.5rem var(--gutter) 5rem;
    291 }
    292 
    293 main { min-width: 0; max-width: var(--measure); }
    294 
    295 .sidebar, .toc {
    296   position: sticky;
    297   /* 상단바가 sticky이므로 그 아래에 붙는다. */
    298   top: calc(var(--masthead-h) + 1.5rem);
    299   font-size: var(--s-1);
    300   line-height: 1.6;
    301   max-height: calc(100vh - var(--masthead-h) - 3rem);
    302   overflow-y: auto;
    303 }
    304 
    305 /* 사이드바는 높이를 채운다. 내용 높이에 맞추면 경계선이 목록 끝에서 뚝 끊겨
    306    구역이 아니라 미완성으로 읽힌다. */
    307 .sidebar { height: calc(100vh - var(--masthead-h) - 3rem); }
    308 
    309 /* ---------- sidebar ---------- */
    310 
    311 /* <details>다. 넓은 화면에서는 열린 채 고정이라 summary를 감추고, 좁은 화면에서만
    312    서랍이 된다. 구역을 나누는 건 오른쪽 경계선 하나 — 배경을 따로 깔지 않아도
    313    사이드바가 본문과 다른 층이라는 게 읽힌다. */
    314 /* 구역을 나누는 건 경계선 하나다. 여기에 면과 라운드를 더 얹어 봤더니 사이드바가
    315    구역이 아니라 떠 있는 카드로 읽혔다. 층은 항목의 hover와 현재 표시가 만든다 —
    316    판 전체에 색을 깔면 그 층이 묻힌다. */
    317 .sidebar {
    318   border-right: 1px solid var(--rule);
    319   padding-right: 1rem;
    320 }
    321 .sidebar > summary { display: none; }
    322 
    323 .nav-tree, .nav-tree ul { list-style: none; margin: 0; padding: 0; }
    324 .nav-tree > li + li { margin-top: 0.15rem; }
    325 
    326 /* 갈래 제목. 누르면 접힌다는 걸 셰브런이 말한다 — 텍스트로 쓸 수 없는 자리라
    327    여기서는 도형을 쓰되, 폰트가 아니라 CSS로 그려서 폴백 문제를 피한다. */
    328 .nav-group > summary {
    329   display: flex;
    330   align-items: center;
    331   gap: 0.5rem;
    332   padding: 0.4rem 0.6rem;
    333   border-radius: var(--radius);
    334   font-family: var(--mono);
    335   font-size: 0.75rem;
    336   letter-spacing: 0.08em;
    337   text-transform: uppercase;
    338   color: var(--ink-3);
    339   cursor: pointer;
    340   list-style: none;
    341 }
    342 .nav-group > summary::-webkit-details-marker { display: none; }
    343 .nav-group > summary:hover { color: var(--ink); background: var(--raised); }
    344 
    345 .nav-group > summary::before {
    346   content: "";
    347   width: 0.35rem;
    348   height: 0.35rem;
    349   border-right: 1.5px solid currentColor;
    350   border-bottom: 1.5px solid currentColor;
    351   transform: rotate(-45deg);
    352   transition: transform var(--tr);
    353 }
    354 .nav-group[open] > summary::before { transform: rotate(45deg); }
    355 
    356 .nav-tree a {
    357   display: block;
    358   padding: 0.35rem 0.6rem;
    359   border-radius: var(--radius);
    360   color: var(--ink-2);
    361   text-decoration: none;
    362 }
    363 .nav-tree a { transition: color var(--tr), background var(--tr); }
    364 .nav-tree a:hover { color: var(--ink); background: var(--raised); }
    365 
    366 /* 현재 항목은 판 전체 폭을 쓰는 알약이다. 왼쪽 막대 하나보다 훨씬 빨리 눈에 띈다. */
    367 .nav-tree a[aria-current="page"] {
    368   color: var(--ink);
    369   font-weight: 600;
    370   background: var(--raised);
    371   box-shadow: inset 2px 0 0 var(--accent);
    372 }
    373 
    374 .nav-group > ul { margin: 0.1rem 0 0.6rem; padding-left: 0.6rem; }
    375 
    376 /* ---------- table of contents ---------- */
    377 
    378 .toc ul { list-style: none; margin: 0; padding: 0; }
    379 .toc > ul > li + li { margin-top: 0.15rem; }
    380 .toc ul ul { padding-left: 0.85rem; }
    381 .toc a {
    382   display: block;
    383   padding: 0.2rem 0 0.2rem 0.7rem;
    384   border-left: 2px solid transparent;
    385   color: var(--ink-3);
    386   text-decoration: none;
    387 }
    388 .toc a { transition: color var(--tr), border-color var(--tr); }
    389 .toc a:hover { color: var(--ink); }
    390 /* 지금 읽고 있는 자리. ui.js가 스크롤을 보고 표시한다 — 없으면 그냥 목록이다. */
    391 .toc a[aria-current] { color: var(--ink); border-left-color: var(--accent); }
    392 
    393 .toc::before {
    394   content: attr(data-label);
    395   display: block;
    396   font-family: var(--mono);
    397   font-size: 0.75rem;
    398   letter-spacing: 0.08em;
    399   text-transform: uppercase;
    400   color: var(--ink-3);
    401   margin-bottom: 0.6rem;
    402 }
    403 
    404 /* ---------- title block and the +++ rule ---------- */
    405 
    406 /* Mono carries structure -- wordmark, headings, labels, code. The sans face is
    407    reserved for prose. For a tool you drive from a terminal that split says
    408    something; it also keeps headings visually adjacent to the commands they
    409    introduce. Mono runs wide, so the scale is pulled in a step. */
    410 .title-block h1 {
    411   font-family: var(--mono);
    412   font-size: var(--s3);
    413   font-weight: 600;
    414   letter-spacing: -0.04em;
    415   line-height: 1.15;
    416   margin: 0;
    417 }
    418 
    419 /* A short accent rule under the title. It is the one place the page states
    420    "this is where you are" in colour rather than in words. */
    421 .title-block h1::after {
    422   content: "";
    423   display: block;
    424   width: 2.5rem;
    425   height: 2px;
    426   margin-top: 0.7rem;
    427   background: var(--accent);
    428 }
    429 
    430 .title-block .lede {
    431   color: var(--ink-2);
    432   font-size: var(--s1);
    433   line-height: 1.55;
    434   margin: 0.6rem 0 0;
    435 }
    436 
    437 /* The front matter fence. Ends the metadata, opens the content -- exactly
    438    what +++ does in the source file this page was written in. */
    439 .fence {
    440   display: flex;
    441   align-items: center;
    442   gap: 0.85rem;
    443   font-family: var(--mono);
    444   font-size: var(--s0);
    445   font-weight: 500;
    446   letter-spacing: 0.1em;
    447   color: var(--ink-3);
    448   margin: 1.6rem 0 2.25rem;
    449   user-select: none;
    450 }
    451 .fence::after {
    452   content: "";
    453   flex: 1;
    454   border-top: 1px solid var(--rule);
    455 }
    456 
    457 /* ---------- prose ---------- */
    458 
    459 .prose > :first-child { margin-top: 0; }
    460 
    461 .prose h2, .prose h3, .prose h4 {
    462   font-family: var(--mono);
    463   font-weight: 600;
    464   letter-spacing: -0.03em;
    465   line-height: 1.3;
    466   /* 상단바가 sticky이므로 그 높이만큼 비켜야 한다. 이 값이 작으면 목차를
    467      눌렀을 때 제목이 상단바 아래로 들어가 보이지 않는다 — 실제로 24px 가렸다. */
    468   scroll-margin-top: calc(var(--masthead-h) + 1.5rem);
    469 }
    470 .prose h2 { font-size: var(--s2); margin: 2.9rem 0 0.9rem; }
    471 .prose h3 { font-size: var(--s1); margin: 2.25rem 0 0.7rem; }
    472 .prose h4 { font-size: var(--s0); margin: 1.75rem 0 0.5rem; }
    473 
    474 /* h2 opens a section; a hairline above it makes that legible at a glance
    475    without adding another visual device. */
    476 .prose h2 {
    477   border-top: 1px solid var(--rule);
    478   padding-top: 2.9rem;
    479 }
    480 .prose h2:first-child { border-top: 0; padding-top: 0; margin-top: 0; }
    481 
    482 .prose p, .prose ul, .prose ol { margin: 0 0 1.1rem; }
    483 .prose li + li { margin-top: 0.3rem; }
    484 
    485 /* The anchor is there for people who want to link to a section, not for
    486    everyone else to look at. */
    487 .anchor {
    488   margin-left: 0.4rem;
    489   color: var(--ink-3);
    490   text-decoration: none;
    491   opacity: 0;
    492   font-weight: 400;
    493   transition: opacity var(--tr), color var(--tr);
    494 }
    495 .anchor:hover { color: var(--accent); }
    496 h1:hover > .anchor, h2:hover > .anchor, h3:hover > .anchor,
    497 h4:hover > .anchor, h5:hover > .anchor, h6:hover > .anchor,
    498 .anchor:focus-visible { opacity: 1; }
    499 @media (hover: none) { .anchor { opacity: 1; } }
    500 
    501 /* ---------- code ---------- */
    502 
    503 .prose code {
    504   font-family: var(--mono);
    505   font-size: 0.875em;
    506   background: var(--raised);
    507   padding: 0.15em 0.35em;
    508   border-radius: 3px;
    509 }
    510 
    511 .prose pre.highlight {
    512   position: relative;
    513   font-family: var(--mono);
    514   font-size: 0.875rem;
    515   line-height: 1.6;
    516   background: var(--raised);
    517   border: 1px solid var(--rule);
    518   border-radius: var(--radius);
    519   padding: 0.9rem 1.1rem;
    520   margin: 0 0 1.3rem;
    521   overflow-x: auto;
    522 
    523   /* 넘치는 코드에는 스크롤이 있다는 표시가 필요하다. 잘린 줄만 보이고 아무
    524      힌트가 없으면 사람들은 그게 전부인 줄 안다. 스크롤바 모양은 위쪽 전역
    525      규칙이 정하고, 여기서는 대비만 한 단계 올린다 — 코드 블록의 면이 본문보다
    526      밝아서 같은 색이면 묻힌다. */
    527   scrollbar-color: var(--ink-3) transparent;
    528 }
    529 .prose pre.highlight::-webkit-scrollbar-thumb { background: var(--ink-3); }
    530 
    531 /* 복사 버튼. 도구 설정 키가 아니라 이 사이트의 CSS와 JS다 — 문서에도 그렇게
    532    적어 두었고, 여기가 그 말을 지키는 자리다.
    533    포커스로도 닿아야 하므로 hover에만 기대지 않는다. */
    534 .copy {
    535   position: absolute;
    536   top: 0.45rem;
    537   right: 0.45rem;
    538   height: 1.5rem;
    539   padding: 0 0.45rem;
    540   font: inherit;
    541   font-size: 0.7rem;
    542   color: var(--ink-3);
    543   background: var(--paper);
    544   border: 1px solid var(--rule);
    545   border-radius: var(--radius);
    546   cursor: pointer;
    547   opacity: 0;
    548   transition: opacity var(--tr), color var(--tr), border-color var(--tr);
    549   /* 버튼은 pre 안에 있어서, 블록을 통째로 긁어 복사하면 이 라벨까지 따라온다.
    550      선택에서 빼면 복사문이 코드만 남는다 — 줄 번호 레시피와 같은 이유다. */
    551   user-select: none;
    552 }
    553 .prose pre.highlight:hover .copy,
    554 .copy:focus-visible { opacity: 1; }
    555 .copy:hover { color: var(--ink); border-color: var(--ink-3); }
    556 .copy[data-done] { color: var(--accent); border-color: var(--accent); opacity: 1; }
    557 
    558 /* 터치 화면에는 hover가 없다. 항상 보이게 둔다. */
    559 @media (hover: none) {
    560   .prose pre.highlight .copy { opacity: 1; }
    561 }
    562 .prose pre.highlight code {
    563   /* 블록으로 넓혀 두는 이유: hl-line 표시와 data-name 라벨이 이 박스의 폭을
    564      물려받는다. auto 폭이면 스크롤되는 블록에서 보이는 폭까지만 칠해져서,
    565      옆으로 넘긴 순간 강조가 사라진 채로 줄의 나머지가 보인다. */
    566   display: block;
    567   width: max-content;
    568   min-width: 100%;
    569   background: none;
    570   padding: 0;
    571   border-radius: 0;
    572   font-size: inherit;
    573 }
    574 
    575 /* 펜스 옵션 `hl_lines=`가 감싼 줄. <mark>의 브라우저 기본(노란 배경, 검은 글자)은
    576    두 테마 어느 쪽과도 안 맞으므로 악센트 기운의 면으로 바꾼다. 음수 마진으로
    577    pre의 좌우 패딩까지 칠해 "줄 전체"가 표시된 것으로 읽히게 한다. */
    578 .prose pre.highlight mark.hl-line {
    579   display: block;
    580   margin: 0 -1.1rem;
    581   padding: 0 1.1rem;
    582   background: color-mix(in srgb, var(--accent) 12%, transparent);
    583   box-shadow: inset 2px 0 0 var(--accent);
    584   color: inherit;
    585 }
    586 
    587 /* 펜스 옵션 `name=`. 빌드는 data-name 속성만 내보내고 보여주는 건 사이트 몫이다 —
    588    여기가 그 몫을 하는 자리. */
    589 .prose pre.highlight code[data-name]::before {
    590   content: attr(data-name);
    591   display: block;
    592   margin: -0.9rem -1.1rem 0.9rem;
    593   padding: 0.4rem 1.1rem;
    594   font-size: 0.75rem;
    595   color: var(--ink-3);
    596   background: var(--paper);
    597   border-bottom: 1px solid var(--rule);
    598   border-radius: var(--radius) var(--radius) 0 0;
    599 }
    600 
    601 /* ---------- benchmark bars ---------- */
    602 
    603 /* 도구 식별은 행 라벨이, 수치는 막대 옆 텍스트가 운반한다. 색은 우리(악센트)와
    604    비교군(중립) 둘뿐이라 색이 단독으로 정보를 나르지 않는다. 막대 폭이 style
    605    속성이 아니라 SVG rect의 표현 속성인 이유: 인라인 style은 이 사이트가
    606    지켜 온 strict style-src CSP 가능성을 깨기 때문이다. */
    607 .bench {
    608   display: grid;
    609   grid-template-columns: auto 1fr;
    610   gap: 0.45rem 0.8rem;
    611   align-items: center;
    612   margin: 0.8rem 0 1.8rem;
    613 }
    614 .bench .b-name {
    615   font-family: var(--mono);
    616   font-size: 0.8rem;
    617   color: var(--ink);
    618   text-align: right;
    619 }
    620 .bench .b-row { display: flex; align-items: center; gap: 0.6rem; min-width: 0; }
    621 .bench svg { flex: 1; min-width: 0; height: 0.75rem; display: block; }
    622 .bench rect { fill: var(--ink-3); opacity: 0.55; }
    623 .bench rect.b-us { fill: var(--accent); opacity: 1; }
    624 .bench .b-val {
    625   font-family: var(--mono);
    626   font-size: 0.78rem;
    627   color: var(--ink-2);
    628   white-space: nowrap;
    629 }
    630 
    631 /* ---------- tables ---------- */
    632 
    633 /* `display: block`이었다. 넓은 표를 가로로 굴리려던 것인데, 그러면 표가 내부
    634    레이아웃을 잃고 내용 폭으로 쭈그러들어 본문의 40%만 쓰는 모양이 된다.
    635    우리 표는 짧은 텍스트 두세 열이라 그냥 감싸면 되고, 진짜 넓은 표가 생기면
    636    그때 감싸는 상자를 두는 게 맞다. */
    637 .prose table {
    638   border-collapse: collapse;
    639   width: 100%;
    640   max-width: 100%;
    641   font-size: var(--s-1);
    642   margin: 0 0 1.3rem;
    643 }
    644 .prose th, .prose td {
    645   text-align: left;
    646   padding: 0.55rem 0.9rem 0.55rem 0;
    647   border-bottom: 1px solid var(--rule);
    648   vertical-align: top;
    649 }
    650 /* 마지막 열의 오른쪽 여백은 필요 없다 — 표의 오른쪽 끝이 이미 여백이다. */
    651 .prose th:last-child, .prose td:last-child { padding-right: 0; }
    652 /* 머리글과 본문 사이는 더 굵게. 어디까지가 라벨인지가 한눈에 보여야 한다. */
    653 .prose thead th { border-bottom-color: var(--ink-3); }
    654 .prose th {
    655   font-family: var(--mono);
    656   font-size: 0.75rem;
    657   letter-spacing: 0.06em;
    658   text-transform: uppercase;
    659   color: var(--ink-3);
    660   font-weight: 500;
    661 }
    662 
    663 /* ---------- admonitions (comrak alerts) ---------- */
    664 
    665 .prose .markdown-alert {
    666   border-left: 2px solid var(--ink-3);
    667   padding: 0.1rem 0 0.1rem 1rem;
    668   margin: 0 0 1.3rem;
    669   color: var(--ink-2);
    670 }
    671 .prose .markdown-alert-title {
    672   font-family: var(--mono);
    673   font-size: 0.75rem;
    674   letter-spacing: 0.08em;
    675   text-transform: uppercase;
    676   color: var(--ink-3);
    677   margin: 0 0 0.35rem;
    678 }
    679 .prose .markdown-alert > p:last-child { margin-bottom: 0; }
    680 
    681 .prose blockquote {
    682   margin: 0 0 1.3rem;
    683   padding-left: 1rem;
    684   border-left: 2px solid var(--rule);
    685   color: var(--ink-2);
    686 }
    687 
    688 .prose hr { border: 0; border-top: 1px solid var(--rule); margin: 2.5rem 0; }
    689 
    690 /* ---------- section index ---------- */
    691 
    692 .children { list-style: none; margin: 0; padding: 0; }
    693 .children li { border-top: 1px solid var(--rule); }
    694 .children li:last-child { border-bottom: 1px solid var(--rule); }
    695 .children a {
    696   display: block;
    697   padding: 0.9rem 0;
    698   text-decoration: none;
    699   color: var(--ink);
    700 }
    701 .children a:hover { color: var(--accent); }
    702 .children .desc { display: block; color: var(--ink-3); font-size: var(--s-1); margin-top: 0.15rem; }
    703 
    704 /* ---------- footer ---------- */
    705 
    706 .colophon {
    707   border-top: 1px solid var(--rule);
    708   max-width: 84rem;
    709   margin: 0 auto;
    710   padding: 2.5rem var(--gutter) 3.5rem;
    711   font-size: var(--s-1);
    712 }
    713 
    714 /* 세 덩이. 브랜드 열이 넓고 링크 열이 좁은 건 왼쪽이 읽는 것이고 오른쪽이
    715    찾는 것이기 때문이다. */
    716 /* 열 폭을 비율로 두면 넓은 화면에서 링크 열이 오른쪽 끝까지 밀려 서로 상관없는
    717    덩이처럼 보인다. 왼쪽부터 채우고 남는 공간은 오른쪽에 둔다. */
    718 .colophon-grid {
    719   display: grid;
    720   grid-template-columns: minmax(0, 20rem) repeat(2, minmax(0, 9rem));
    721   justify-content: start;
    722   gap: clamp(1.5rem, 5vw, 4rem);
    723 }
    724 
    725 .colophon-mark {
    726   display: inline-flex;
    727   align-items: center;
    728   gap: 0.5rem;
    729   font-family: var(--mono);
    730   font-size: var(--s0);
    731   font-weight: 600;
    732   letter-spacing: -0.03em;
    733   color: var(--ink-2);
    734   text-decoration: none;
    735 }
    736 .colophon-mark:hover { color: var(--ink); text-decoration: none; }
    737 
    738 .colophon-brand img {
    739   width: 1.7rem;
    740   height: 1.7rem;
    741   filter: invert(1);
    742   opacity: 0.6;
    743 }
    744 :root[data-theme="light"] .colophon-brand img { filter: none; }
    745 
    746 .colophon-brand p {
    747   margin: 0.6rem 0 0;
    748   max-width: 24rem;
    749   color: var(--ink-3);
    750   line-height: 1.6;
    751 }
    752 
    753 /* 열 제목은 사이드바 갈래 제목과 같은 처리다. 라벨은 사이트 어디서나 같은
    754    물건으로 보여야 한다. */
    755 .colophon h2 {
    756   margin: 0 0 0.7rem;
    757   font-family: var(--mono);
    758   font-size: 0.72rem;
    759   font-weight: 500;
    760   letter-spacing: 0.08em;
    761   text-transform: uppercase;
    762   color: var(--ink-3);
    763 }
    764 
    765 .colophon ul { list-style: none; margin: 0; padding: 0; }
    766 .colophon li + li { margin-top: 0.4rem; }
    767 .colophon a {
    768   color: var(--ink-2);
    769   text-decoration: none;
    770   transition: color var(--tr);
    771 }
    772 .colophon a:hover { color: var(--accent); text-decoration: underline; }
    773 
    774 /* ---------- Korean ----------
    775    Three Latin devices do not survive contact with Hangul:
    776 
    777    letter-spacing pushes syllable blocks apart until a word stops reading as one
    778    word; text-transform does nothing at all; and a monospace family is the worst
    779    of them, because Hangul falls back to a proportional font while the *space*
    780    still comes from the mono face, so every gap between 어절 renders double
    781    width and the heading looks broken.
    782 
    783    Structure still reads through size, weight and colour, which need no help. */
    784 html[lang="ko"] .title-block h1,
    785 html[lang="ko"] .prose h2,
    786 html[lang="ko"] .prose h3,
    787 html[lang="ko"] .prose h4,
    788 html[lang="ko"] .nav-group > summary,
    789 html[lang="ko"] .colophon h2,
    790 html[lang="ko"] .toc::before,
    791 html[lang="ko"] .prose th,
    792 html[lang="ko"] .crumbs,
    793 html[lang="ko"] .search-crumb,
    794 html[lang="ko"] .prose .markdown-alert-title {
    795   font-family: var(--sans);
    796   letter-spacing: -0.01em;
    797   text-transform: none;
    798 }
    799 html[lang="ko"] .title-block h1,
    800 html[lang="ko"] .prose h2,
    801 html[lang="ko"] .prose h3,
    802 html[lang="ko"] .prose h4 {
    803   font-weight: 700;
    804 }
    805 
    806 /* ---------- pager ---------- */
    807 
    808 .pager {
    809   display: flex;
    810   gap: 1rem;
    811   margin-top: 4rem;
    812   padding-top: 1.5rem;
    813   border-top: 1px solid var(--rule);
    814 }
    815 
    816 .pager a {
    817   flex: 1;
    818   display: block;
    819   padding: 0.85rem 1rem;
    820   border: 1px solid var(--rule);
    821   border-radius: 4px;
    822   color: inherit;
    823   text-decoration: none;
    824 }
    825 .pager a:hover { border-color: var(--accent); }
    826 
    827 /* A single "next" sits on the right, where the reader is heading. */
    828 .pager-next { text-align: right; }
    829 .pager-next:only-child { margin-left: auto; flex: 0 1 auto; }
    830 
    831 .pager span {
    832   display: block;
    833   font-family: var(--mono);
    834   font-size: 0.7rem;
    835   letter-spacing: 0.08em;
    836   text-transform: uppercase;
    837   color: var(--ink-3);
    838   margin-bottom: 0.2rem;
    839 }
    840 .pager strong { font-weight: 600; }
    841 
    842 html[lang="ko"] .pager span {
    843   font-family: var(--sans);
    844   letter-spacing: -0.01em;
    845   text-transform: none;
    846 }
    847 
    848 @media (max-width: 40rem) {
    849   .pager { flex-direction: column; }
    850   .pager-next { text-align: left; }
    851 }
    852 
    853 /* ---------- search palette ----------
    854    A <dialog>. Escape, the backdrop, the focus trap and returning focus to the
    855    trigger are all the element's own behaviour; none of it is written here. */
    856 
    857 .search-dialog {
    858   border: none;
    859   background: none;
    860   padding: 0;
    861   margin: 0 auto;
    862   width: min(40rem, calc(100vw - 2rem));
    863   max-width: none;
    864   max-height: 100dvh;
    865   margin-top: 12vh;
    866 }
    867 .search-dialog::backdrop { background: rgb(0 0 0 / 0.55); }
    868 
    869 .search-panel {
    870   background: var(--paper);
    871   border: 1px solid var(--rule);
    872   border-radius: 8px;
    873   box-shadow: 0 1.5rem 3rem rgb(0 0 0 / 0.35);
    874   overflow: hidden;
    875 }
    876 
    877 .search-panel input {
    878   width: 100%;
    879   font: var(--s1)/1.4 var(--sans);
    880   color: var(--ink);
    881   background: none;
    882   border: none;
    883   border-bottom: 1px solid var(--rule);
    884   padding: 1rem 1.15rem;
    885 }
    886 .search-panel input:focus { outline: none; }
    887 .search-panel input::placeholder { color: var(--ink-3); }
    888 /* The UA's clear button for type=search is a heavy glyph that sits at a
    889    different weight from everything else here. Escape already clears. */
    890 .search-panel input::-webkit-search-cancel-button { display: none; }
    891 
    892 .search-results {
    893   list-style: none;
    894   margin: 0;
    895   padding: 0.35rem;
    896   max-height: min(26rem, 55dvh);
    897   overflow-y: auto;
    898 }
    899 .search-results:empty { display: none; }
    900 
    901 .search-results a {
    902   display: block;
    903   padding: 0.6rem 0.8rem;
    904   border-radius: 5px;
    905   color: inherit;
    906   text-decoration: none;
    907 }
    908 .search-results a[aria-selected] { background: var(--raised); }
    909 .search-results a[aria-selected] .search-title { color: var(--accent); }
    910 
    911 .search-crumb {
    912   display: block;
    913   font-family: var(--mono);
    914   font-size: 0.7rem;
    915   letter-spacing: 0.08em;
    916   text-transform: uppercase;
    917   color: var(--ink-3);
    918 }
    919 .search-title {
    920   display: block;
    921   font-weight: 600;
    922   line-height: 1.35;
    923 }
    924 .search-snippet {
    925   display: block;
    926   font-size: var(--s-1);
    927   line-height: 1.5;
    928   color: var(--ink-2);
    929   /* Two lines is enough to show why a page matched. More turns the list into
    930      a wall and hides the results below it. */
    931   display: -webkit-box;
    932   -webkit-line-clamp: 2;
    933   -webkit-box-orient: vertical;
    934   overflow: hidden;
    935 }
    936 .search-results mark {
    937   background: none;
    938   color: var(--accent);
    939   font-weight: 600;
    940 }
    941 
    942 .search-foot {
    943   display: flex;
    944   justify-content: space-between;
    945   gap: 1rem;
    946   border-top: 1px solid var(--rule);
    947   padding: 0.5rem 1.15rem;
    948   font-family: var(--mono);
    949   font-size: 0.72rem;
    950   color: var(--ink-3);
    951 }
    952 
    953 /* ---------- narrow screens ----------
    954    여기 규칙이 없어서 420px에서 마스트헤드가 무너졌다. 브랜드가 한 글자씩 세로로
    955    쪼개지고 GitHub도 마찬가지였다. 좁아지면 줄이는 게 아니라 **뺀다** — 갈래 내비는
    956    사이드바 서랍이 같은 일을 하고, GitHub는 콜로폰에 이미 있다. */
    957 
    958 @media (max-width: 60rem) {
    959   .layout {
    960     grid-template-columns: minmax(0, 1fr);
    961     padding-top: 1.5rem;
    962   }
    963 
    964   /* 서랍이 된다. 열고 닫는 건 <details>가, 처음 닫는 건 ui.js가 한다. */
    965   .sidebar {
    966     position: static;
    967     /* 넓은 화면에서 경계선을 채우려고 준 높이를 되돌린다. 안 그러면 서랍이
    968        접혀 있어도 100vh를 차지해 본문이 화면 밖으로 밀린다. */
    969     height: auto;
    970     max-height: none;
    971     overflow: visible;
    972     border-right: none;
    973     border-bottom: 1px solid var(--rule);
    974     padding: 0 0 1rem;
    975     margin-bottom: 1.5rem;
    976   }
    977   .sidebar > summary {
    978     display: inline-flex;
    979     align-items: center;
    980     gap: 0.5rem;
    981     height: var(--control-h);
    982     padding: 0 0.6rem;
    983     border: 1px solid var(--rule);
    984     border-radius: var(--radius);
    985     font-family: var(--mono);
    986     font-size: var(--s-1);
    987     color: var(--ink-2);
    988     cursor: pointer;
    989     list-style: none;
    990   }
    991   .sidebar > summary::-webkit-details-marker { display: none; }
    992   .sidebar > summary::before {
    993     content: "";
    994     width: 0.35rem;
    995     height: 0.35rem;
    996     border-right: 1.5px solid currentColor;
    997     border-bottom: 1.5px solid currentColor;
    998     transform: rotate(-45deg);
    999   }
   1000   .sidebar[open] > summary::before { transform: rotate(45deg); }
   1001   .sidebar[open] > nav { margin-top: 0.75rem; }
   1002 
   1003   /* 목차는 사이드바 서랍과 같은 말을 한 번 더 하는 셈이라 뺀다. */
   1004   .toc { display: none; }
   1005 
   1006   .colophon-grid { grid-template-columns: minmax(0, 1fr); gap: 2rem; }
   1007 
   1008   /* 갈래 내비도 서랍에 있다. */
   1009   .sections { display: none; }
   1010 
   1011   /* GitHub는 콜로폰에 있다. 검색은 라벨만 남기고 단축키 힌트를 뺀다 —
   1012      터치 화면에는 누를 키가 없다. */
   1013   .chip-wide { display: none; }
   1014   .search-trigger { min-width: 0; }
   1015   .search-trigger kbd { display: none; }
   1016 }
   1017 
   1018 @media (max-width: 26rem) {
   1019   /* 이보다 좁으면 워드마크까지 자리를 다툰다. 마크만 남긴다 — 로고가 곧 이름이다. */
   1020   .brand span { display: none; }
   1021 }
   1022 
   1023 @media (prefers-reduced-motion: reduce) {
   1024   html { scroll-behavior: auto; }
   1025   *, *::before, *::after {
   1026     animation-duration: 0.01ms !important;
   1027     transition-duration: 0.01ms !important;
   1028   }
   1029 }