/* ==========================================================================
   syntax.css — Rouge 4.7.0 syntax highlighting
   ==========================================================================

   Interdotensional "leaf" role vocabulary. Every colour in this file is a
   var(--id-*) reference carrying the generated LIGHT value as its literal
   fallback, so a vanished interdot-theme.css degrades to a readable light
   palette rather than to `inherit`. Those fallbacks were generated FROM
   interdot-theme.css rather than typed, for the reason main.css states at the
   top of itself: a fallback that has drifted from the generated value still
   parses, so it voids the property it claims to protect without ever failing
   loudly. There are no per-theme blocks and no prefers-color-scheme queries
   here — the --id-* properties already flip between gruvbox-material light
   and dark on their own, so this one stylesheet serves both themes.

   The token classes below were not guessed. They were extracted by lexing
   the 45 fenced blocks of _posts/2022-07-15-Google-Capture-The-Flag-2022-
   madcore-pwn.md (22 cpp / 15 bash / 8 python) through Rouge and collecting
   every class Rouge actually emitted: 25 distinct classes. The rest of the
   file covers the fallback classes a future post in ruby / go / js / diff /
   make / nasm / console / json / yaml / html would hit — also verified by
   lexing real samples of each, not from memory.

   Contrast (WCAG 2.1, computed against --id-bg-deep in BOTH themes):
   every role used for a code token clears 4.5:1 in both. Worst case is
   --id-muted at 4.64:1 light / 6.55:1 dark. --id-dim (2.99:1 light) and
   --id-faint (2.04:1 light) are below AA and are therefore used only for
   chrome — a hairline divider, an inset rule — never for a token that

   MAP
     1  container: div.highlighter-rouge / div.highlight / pre.highlight
     2  the Rouge table (dormant: block.line_numbers is false in _config.yml)
     3  Rouge's own line-number gutter, and the clipboard bug it caused
     4  tokens, grouped by --id-* role
     5  the SECOND container: figure.highlight, which {% highlight %} emits
     6  marked lines — mark_lines / .hll
     7  the language label, drawn from the wrapper class
     8  the three local lexers in _plugins/ (hexdump, telescope, objdump)
   ========================================================================== */


/* --------------------------------------------------------------------------
   1. CONTAINER
   Kramdown + Rouge with block.line_numbers: true emits exactly this:

     div.language-cpp.highlighter-rouge
       > div.highlight
           > pre.highlight
               > code
                   > table.rouge-table > tbody > tr
                       > td.rouge-gutter.gl > pre.lineno   <- literal digits
                       > td.rouge-code            > pre    <- the code

   Note .highlight lands on BOTH the outer div and the inner pre, so the
   selectors below are qualified (div.highlight / pre.highlight) to keep
   padding and background from being applied twice.
   -------------------------------------------------------------------------- */

div.highlighter-rouge {
  margin: 1.25rem 0;
  border: 1px solid var(--id-border, #d5c4a1);
  border-radius: 6px;
  overflow: hidden; /* clips the code background to the rounded corners */
  background: var(--id-bg-deep, #f4e8be);
}

div.highlight {
  background: var(--id-bg-deep, #f4e8be);
  color: var(--id-fg, #654735); /* plaintext blocks emit no spans at all — this is them */
  overflow-x: auto;    /* long exploit lines scroll here, not on the page body */
  tab-size: 4;
  -moz-tab-size: 4;
  /* With block.line_numbers OFF (the default here) Rouge emits no
     table.rouge-table, so nothing downstream carries padding and the code
     would sit flush against the border. This is that padding. */
  padding: 0.85rem 1rem;
}

/* ...and when line numbers ARE enabled, the table cells carry it instead —
   otherwise the two would stack. */
div.highlight:has(table.rouge-table) {
  padding: 0;
}

/* Rouge nests a second .highlight on the <pre>. Neutralise it completely so
   it contributes no box of its own and does not become a second scroll
   container (which would break the sticky gutter below). */
pre.highlight {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  overflow: visible;
  color: inherit;
}

/* `font: inherit` is here to undo the UA stylesheet on <code> (monospace at
   font-size: smaller). The two restatements after it are not decoration:
   `font` is a shorthand whose reset-only sub-properties include
   font-feature-settings and every font-variant-* longhand, so main.css's two
   global type decisions — `font-variant-ligatures: none` and
   font-feature-settings: "zero" 1 on <body> — were being reset to normal
   INSIDE every code block. That is the whole of it: ligatures came back on in
   the one place they must never fire (`===>`, `->`, `!=`, `─➜` and the `x` of
   `0x00` all ligate in Maple Mono with calt on) and the slashed zero came off
   in the one place a page full of 0x-prefixed hex actually needs it. Same
   defence, same reason, as .themebtn and .picker__row in main.css.
   Also width/min-width: the code element is the row box that the copy-mode
   the marked-row ground (section 6) and any full-row fill
   are drawn on, so it has to be as wide as the widest line AND at least as
   wide as the scroll container — otherwise a row's ground stops at the
   visible edge and a scrolled line runs out of its own highlight. */
div.highlight > pre.highlight > code {
  display: block;
  width: max-content;
  min-width: 100%;
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  font-variant-ligatures: inherit;
  font-feature-settings: inherit;
  color: inherit;
}

/* --------------------------------------------------------------------------
   2. THE ROUGE TABLE
   border-spacing/border-collapse are pinned so the gutter and the code sit
   on the same baseline grid and so the inset divider on the sticky gutter
   cell renders predictably.
   -------------------------------------------------------------------------- */

.rouge-table {
  width: 100%;
  margin: 0;
  border: 0;
  border-collapse: separate;
  border-spacing: 0;
  table-layout: auto;
}

.rouge-table,
.rouge-table tbody,
.rouge-table tr,
.rouge-table td {
  background: none;
  border: 0;
}

.rouge-table td {
  padding: 0;
  vertical-align: top;
  line-height: inherit;
}

.rouge-table td.rouge-code {
  width: 100%; /* code cell eats the remaining width; gutter shrink-wraps */
  padding: 0.85rem 1rem 0.85rem 0.85rem;
}

.rouge-table td.rouge-code pre {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  overflow: visible;
  white-space: pre;
  color: inherit;
}

/* --------------------------------------------------------------------------
   3. LINE-NUMBER GUTTER  —  the clipboard bug
   --------------------------------------------------------------------------

   FAILURE MODE (this is why the rules below exist):

   _config.yml sets kramdown.syntax_highlighter_opts.block.line_numbers: true.
   Rouge does not draw those numbers with CSS counters or with generated
   content — it emits them as REAL TEXT NODES:

       <td class="rouge-gutter gl"><pre class="lineno">1
       2
       3
       </pre></td>

   That text is part of the document, so it is part of any selection that
   crosses it, and it lands in the clipboard. Two separate paths produced the
   bug:

     (a) Ctrl+A / triple-click / a drag that starts above or ends below the
         block sweeps the gutter cell in with the code cell.
     (b) TABLE LAYOUT specifically: a selection that spans more than one
         table cell is serialised by every engine as cell-by-cell text with
         tab or newline separators, and Gecko additionally switches into a
         whole-cell selection mode as soon as a drag crosses a cell boundary.
         So even a careful drag over lines 2..5 of the code column could
         snap the entire gutter column into the selection.

   Either way the paste came out as

       1 from pwn import *
       2 io = process("./madcore")

   or the tab-separated variant. On a pwn blog that is not a cosmetic
   problem: the reader's exploit is a SyntaxError before it ever runs, and
   the failure looks like the writeup is wrong rather than like the copy was.

   THE FIX, in three layers:

     1. user-select: none on every element in the gutter subtree — the td
        (both .rouge-gutter and Rouge's own .gl class, belt and braces
        against markup changes) and the pre.lineno inside it. All engines
        exclude a user-select:none subtree from the copy buffer, which kills
        path (a) and path (b) at the source: the digits can no longer enter
        a selection at all, however the selection was made.
     2. user-select: text is then re-asserted on the code cell and its pre,
        so the code stays copyable even if an ancestor stylesheet (or a
        future layout wrapper) sets user-select: none higher up and the
        gutter rule would otherwise have been inherited past its cell.
     3. The table itself is pinned so its layout cannot reintroduce the
        digits: fixed cell padding/vertical-align keeps the two columns as
        two independent blocks, and ::selection on the gutter is forced
        transparent so a drag cannot even paint as though it had grabbed the
        numbers. Layer 3 is cosmetic reinforcement — layer 1 is what makes
        the copy correct.
   -------------------------------------------------------------------------- */

.rouge-table td.rouge-gutter,
.rouge-table td.gl,
.rouge-gutter,
pre.lineno,
.lineno {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

.rouge-table td.rouge-gutter {
  position: sticky; /* numbers stay put while a long line scrolls under them */
  left: 0;
  z-index: 1;
  width: 1px;       /* shrink-wrap to the widest line number */
  padding: 0.85rem 0.7rem 0.85rem 0.85rem;
  text-align: right;
  white-space: nowrap;
  background: var(--id-bg-deep, #f4e8be); /* opaque, so scrolled code passes behind it */
  /* inset shadow instead of border-right: survives position:sticky cleanly */
  box-shadow: inset -1px 0 0 var(--id-faint, #a9a39a);
}

/* --id-dim / --id-faint are the two roles that do NOT clear 4.5:1
   (--id-dim is 2.99:1 in light, --id-faint 2.04:1). That is deliberate and
   safe here and only here: the gutter is decorative, it is not selectable,
   it is not copyable, and it carries no information that is not already
   visible in the code beside it. If you ever want the numbers to clear AA
   anyway, swap --id-dim for --id-muted (4.64:1 light / 6.55:1 dark) — it is
   a one-token change and nothing else in this file depends on it. */
.rouge-table td.rouge-gutter,
.gl,
pre.lineno,
.lineno {
  color: var(--id-dim, #928374);
}

pre.lineno,
.lineno {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  overflow: visible;
  font: inherit;
  font-variant-ligatures: inherit;  /* see the note on the code element above */
  font-feature-settings: inherit;
}

/* Cosmetic reinforcement — see layer 3 above. */
.rouge-gutter::selection,
pre.lineno::selection,
.lineno::selection {
  background: transparent;
  color: var(--id-dim, #928374);
}
.rouge-gutter::-moz-selection,
pre.lineno::-moz-selection,
.lineno::-moz-selection {
  background: transparent;
  color: var(--id-dim, #928374);
}

/* Re-assert selectability on the code side. Deliberately applied to the cell
   and its <pre> only, NOT to a descendant `*` — user-select is an inherited
   property so the spans pick it up anyway, and a `.rouge-code *` rule would
   out-specify the .gp prompt rule further down (0,2,1 vs 0,2,0) and quietly
   make shell prompts copyable again. */
.rouge-table td.rouge-code,
.rouge-table td.rouge-code pre {
  -webkit-user-select: text;
  -moz-user-select: text;
  -ms-user-select: text;
  user-select: text;
}


/* ==========================================================================
   4. TOKENS
   Grouped by --id-* role. Every class present in the real post is marked
   [post]; the rest are fallbacks for languages this blog will plausibly
   use next, each verified against the actual lexer output.
   ========================================================================== */

/* --- plain text: identifiers and untagged content -------------------------
   .n is the single most frequent class in the post (646 hits — plain C++ and
   Python identifiers, incl. calls like malloc/memset), so it must read as
   body text, not as an accent. */
.highlight .n,   /* Name                    [post] */
.highlight .nx,  /* Name.Other       (js)          */
.highlight .py,  /* Name.Property                  */
.highlight .w,   /* Text.Whitespace                */
.highlight .x {  /* Other                          */
  color: var(--id-fg, #654735);
}

/* --- keywords -------------------------------------------------------------
   .ow (Operator.Word: python `and`/`is`/`not`, ruby `and`/`or`) is grouped
   here rather than with the operators below. It is a WORD, and muting it to
   the same grey as `(` would read as noise; Rouge only splits it out of
   Keyword for lexer-internal reasons. */
.highlight .k,   /* Keyword               [post] */
.highlight .kd,  /* Keyword.Declaration   (js: const/class/static) */
.highlight .kn,  /* Keyword.Namespace              */
.highlight .kr,  /* Keyword.Reserved               */
.highlight .kv,  /* Keyword.Variable               */
.highlight .ow,  /* Operator.Word         [post] */
.highlight .ne { /* Name.Exception                 */
  color: var(--id-danger, #b43e3e);
}

/* --- callables and class names -------------------------------------------- */
.highlight .nf,  /* Name.Function  [post] (python calls; nasm mnemonics; make targets) */
.highlight .fm,  /* Name.Function.Magic            */
.highlight .nc { /* Name.Class     [post] (C++ Binary/CallFrame/Backtrace)  */
  color: var(--id-success, #626d2a);
}

/* --- types and namespaces ------------------------------------------------- */
.highlight .kt,  /* Keyword.Type   [post] (int/long/void/char/bool/uint64_t) */
.highlight .nn,  /* Name.Namespace                 */
.highlight .ld { /* Literal.Date                   */
  color: var(--id-accent, #925c07);
}

/* --- strings --------------------------------------------------------------
   .sh, .sa and .dl are load-bearing, not padding. Rouge's Python lexer tags
   the QUOTE CHARACTERS themselves as Literal.String.Heredoc (.sh) and the
   b/f prefix as Literal.String.Affix (.sa) — verified in the post, e.g.
   open(args.CORE, "rb") lexes as .sh " / .s rb / .sh ". The JS lexer does
   the same thing with Literal.String.Delimiter (.dl). Leave any of those
   three out and every Python/JS string on the blog renders with unstyled
   quotes wrapped around a coloured body. */
.highlight .s,   /* Literal.String            [post] */
.highlight .s1,  /* Literal.String.Single            */
.highlight .s2,  /* Literal.String.Double     [post] */
.highlight .sa,  /* Literal.String.Affix      [post] */
.highlight .sb,  /* Literal.String.Backtick          */
.highlight .sc,  /* Literal.String.Char              */
.highlight .sd,  /* Literal.String.Doc               */
.highlight .sh,  /* Literal.String.Heredoc    [post] */
.highlight .sr,  /* Literal.String.Regex             */
.highlight .ss,  /* Literal.String.Symbol            */
.highlight .sx,  /* Literal.String.Other             */
.highlight .dl,  /* Literal.String.Delimiter  (js)   */
.highlight .cpf {/* Comment.PreprocFile       [post] (<vector>, <stdio.h>) */
  color: var(--id-accent, #925c07);
}

/* --- numbers -------------------------------------------------------------- */
.highlight .m,   /* Literal.Number                    */
.highlight .mb,  /* Literal.Number.Bin                */
.highlight .mf,  /* Literal.Number.Float              */
.highlight .mh,  /* Literal.Number.Hex        [post] (225 hits — it's a pwn blog) */
.highlight .mi,  /* Literal.Number.Integer    [post] */
.highlight .mo,  /* Literal.Number.Oct                */
.highlight .il,  /* Literal.Number.Integer.Long       */
.highlight .l {  /* Literal                           */
  color: var(--id-special, #8b5878);
}

/* --- builtins, constants, and the "active" bits inside strings ------------
   Grouped deliberately: C++ `true` lexes as .nb, Python `True`/`None`/`...`
   lex as .bp, Ruby `nil` as .kp, Go `true`/`false` as .no, JS/JSON `null` as
   .kc — five different classes for one concept. Putting .kc and .kp on
   --id-danger with the other keywords (the suggested mapping) would make
   `nil` red in a Ruby post while `None` stays orange in the Python post
   above it, so all five sit together on --id-accent2 instead.
   .se/.si/.ni are here too: an escape or an interpolation is the one part
   of a string that is *not* literal text, and orange-inside-yellow is the
   conventional way to say so. */
.highlight .nb,  /* Name.Builtin         [post] (bash: ls/cat/sudo; C++: true) */
.highlight .bp,  /* Name.Builtin.Pseudo  [post] (python True / ...)  */
.highlight .no,  /* Name.Constant                                    */
.highlight .kc,  /* Keyword.Constant     (js/json null, go true)     */
.highlight .kp,  /* Keyword.Pseudo       (ruby nil)                  */
.highlight .se,  /* Literal.String.Escape [post] (\n, \x00)          */
.highlight .si,  /* Literal.String.Interpol [post] ({} in f-strings) */
.highlight .ni,  /* Name.Entity          (html &amp;)                */
.highlight .nd,  /* Name.Decorator                                   */
.highlight .cp { /* Comment.Preproc      [post] (#include, #define)  */
  color: var(--id-accent2, #a65009);
}

/* .cp and .cpf are Comment.* in Rouge's token tree but they are NOT comments
   and are deliberately excluded from the comment rule below. Grouping them
   with .c would render every `#include <vector>` in the 22 C++ blocks in
   comment grey, i.e. as if the includes had been commented out. */

/* --- variables, attributes, tags, labels ---------------------------------- */
.highlight .nv,  /* Name.Variable  [post] (bash $rdi, $1, and the `$ ` prompt) */
.highlight .vc,  /* Name.Variable.Class                     */
.highlight .vg,  /* Name.Variable.Global                    */
.highlight .vi,  /* Name.Variable.Instance                  */
.highlight .vm,  /* Name.Variable.Magic                     */
.highlight .nt,  /* Name.Tag       [post] (bash flags -o/--obj/-std; html tags) */
.highlight .na,  /* Name.Attribute (html attrs; yaml keys)  */
.highlight .nl { /* Name.Label     (json keys; nasm labels) */
  color: var(--id-info, #436d77);
}

/* --- comments -------------------------------------------------------------
   Comments would conventionally sit on --id-dim, but --id-dim is 2.99:1
   against --id-bg-deep in the light theme, i.e. below AA, and unlike the
   line numbers a comment is content. They stay on --id-muted (4.64:1 light /
   6.55:1 dark) and are separated from the punctuation below — which shares
   that colour — by italics instead of by hue. */
.highlight .c,   /* Comment          [post] */
.highlight .c1,  /* Comment.Single   [post] */
.highlight .cm,  /* Comment.Multiline[post] */
.highlight .ch,  /* Comment.Hashbang        */
.highlight .cd,  /* Comment.Doc             */
.highlight .cs { /* Comment.Special         */
  color: var(--id-muted, #6b665f);
  font-style: italic;
}

/* --- operators, punctuation, program output -------------------------------
   .p (662 hits) and .o (531) are the two most frequent classes in the post,
   almost all of it C++ structural noise: ( ) ; , { } :: . Recessing them
   lets the identifiers and literals carry the eye. Note that Rouge's shell
   lexer classes brackets as .o rather than .p, so both must be here or bash
   and C++ blocks would not match each other. */
.highlight .o,   /* Operator              [post] */
.highlight .p,   /* Punctuation           [post] */
.highlight .pi,  /* Punctuation.Indicator (yaml) */
.highlight .go { /* Generic.Output — console transcripts: what the program
                    printed, as opposed to what the reader typed */
  color: var(--id-muted, #6b665f);
  font-style: normal;
}

/* Generic.Prompt: the `$` / `>>>` in console and pycon blocks. Same
   clipboard reasoning as the line-number gutter — copying `$ ls -la` must
   yield `ls -la`, not a shell prompt. */
.highlight .gp {
  color: var(--id-muted, #6b665f);
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

/* ...except in a hexdump, where the offset column is NOT a prompt.
   `.gp` carries the `00000d60:` offsets, and the same post tells the reader to
   run `:%!xxd -r` to write the file back — which needs those offsets. Copying a
   dump and silently losing its addresses gives them something xxd cannot
   reverse. The unselectable-prompt reasoning is right for `$ ls -la` and wrong
   here, so it is re-asserted for the three hexdump aliases only. */
div:is(.language-hexdump, .language-xxd, .language-hexyl) .highlight .gp {
  -webkit-user-select: text;
  -moz-user-select: text;
  -ms-user-select: text;
  user-select: text;
}

/* --- diffs ---------------------------------------------------------------- */
.highlight .gd { /* Generic.Deleted */
  color: var(--id-danger, #b43e3e);
  background: color-mix(in srgb, var(--id-danger, #b43e3e) 12%, transparent);
}

.highlight .gi { /* Generic.Inserted */
  color: var(--id-success, #626d2a);
  background: color-mix(in srgb, var(--id-success, #626d2a) 12%, transparent);
}

/* Rouge's diff lexer tags the `@@ -1,3 +1,3 @@` hunk header as Punctuation,
   not as Generic.Subheading, so it would otherwise be muted along with all
   other punctuation. Scoped to the diff wrapper class kramdown emits. */
.language-diff .highlight .p,
.language-diff .p {
  color: var(--id-info, #436d77);
}

/* --- generic / prose tokens ----------------------------------------------- */
.highlight .gh { /* Generic.Heading */
  color: var(--id-fg-strong, #4f3829);
  font-weight: 700;
}

.highlight .gu { /* Generic.Subheading */
  color: var(--id-info, #436d77);
  font-weight: 700;
}

.highlight .gs { /* Generic.Strong */
  color: var(--id-fg-strong, #4f3829);
  font-weight: 700;
}

.highlight .ge { /* Generic.Emph */
  font-style: italic;
}

.highlight .gr,  /* Generic.Error     */
.highlight .gt { /* Generic.Traceback */
  color: var(--id-danger, #b43e3e);
}

/* --- lexer errors ---------------------------------------------------------
   .err is Token.Error, but it does NOT reliably mean "broken code". Rouge's
   Makefile lexer emits .err for perfectly valid text — recipe lines, $(CC),
   $(CFLAGS), -o — so the usual loud treatment (red text on a red background,
   or a wavy underline) would make every Makefile on this blog look like it
   failed to parse. It gets --id-warning and nothing else: visible, tinted,
   no background, no underline. --id-warning resolves to the same value as
   --id-accent in both themes, so an .err run reads as string-ish rather
   than as an alarm, which is the correct level of alarm for a lexer that
   simply gave up on a tab character. */
.highlight .err {
  color: var(--id-warning, #925c07);
  background: none;
  text-decoration: none;
}


/* ==========================================================================
   5. THE SECOND CONTAINER  —  figure.highlight
   --------------------------------------------------------------------------
   Sections 1-4 describe what kramdown + Rouge emit for a fenced block.
   Jekyll's own {% highlight %} tag emits something else entirely:

     <figure class="highlight">
       > <pre>
           > <code class="language-cpp" data-lang="cpp">

   No div.highlighter-rouge, no div.highlight, and no .highlight on the <pre>
   (jekyll-4.4.1/lib/jekyll/tags/highlight.rb, add_code_tag — read, not
   guessed). The token rules in section 4 do still fire, because the FIGURE
   carries the .highlight class those descendant selectors key on; every
   container rule in section 1 misses it, so until this block a
   {% highlight %} block rendered as unframed, unpadded, page-size text on the
   page ground, with the syntax colours floating on nothing.

   It has to be dealt with because {% highlight %} is the ONLY way to pass
   mark_lines (section 7). kramdown's fences take LEXER options after a `?`
   (```console?prompt=$) and no formatter options at all, and mark_lines is a
   formatter option.

   Two things this cannot fix from here, both of them main.css's to own:
   `.prose > .highlighter-rouge` is what puts fenced code on the wide grid
   track, and `.prose div.highlight` is what sets its font-size — neither
   selector matches a figure, so a marked block sits at the reading measure
   until main.css names it. The font-size below is a floor for that gap, not a
   claim on it: it is the one property section 1 deliberately leaves to the
   page, and any `.prose > figure.highlight` rule out-specifies it.
   -------------------------------------------------------------------------- */

figure.highlight {
  position: relative;   /* the language label in section 8 anchors on this */
  margin: 1.25rem 0;
  border: 1px solid var(--id-border, #d5c4a1);
  border-radius: 6px;
  background: var(--id-bg-deep, #f4e8be);
  color: var(--id-fg, #654735);
  overflow: hidden;     /* clips the code ground to the rounded corners */
  font-size: var(--fs-sm, 0.8125rem);
  line-height: 1.6;
}

figure.highlight > pre {
  margin: 0;
  padding: 0.85rem 1rem;
  border: 0;
  background: none;
  color: inherit;
  overflow-x: auto;     /* the sideways scroller, as div.highlight is for fences */
  tab-size: 4;
  -moz-tab-size: 4;
}

figure.highlight > pre > code {
  display: block;
  width: max-content;
  min-width: 100%;
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  font-variant-ligatures: inherit;  /* see the note on the fenced code element */
  font-feature-settings: inherit;
  color: inherit;
}


/* ==========================================================================
   6. MARKED LINES  —  mark_lines / .hll                         [CODE-05]
   --------------------------------------------------------------------------
   Rouge has shipped the line highlighter all along and nothing drew it.
   Authoring is Jekyll's tag, and only Jekyll's tag:

     {% highlight cpp mark_lines="3" %}
     ...
     {% endhighlight %}

   which wraps Rouge::Formatters::HTMLLineHighlighter around the HTML
   formatter and puts the marked line — the newline included — in
   <span class="hll"> (rouge-4.7.0/lib/rouge/formatters/html_line_highlighter.rb).
   Do NOT add `linenos` to that tag: it switches on HTMLTable and brings back
   the real-digit gutter that section 3 exists to have removed. Line numbers
   are the block's own job.

   THE GROUND, measured. The specimen this comes from tinted the row with
   color-mix(in srgb, var(--id-danger) 11%, transparent) over --id-bg-deep,
   and a reviewer measured the row's own punctuation at 4.00:1 in the light
   theme. That is structural rather than a tuning error: section 4's palette
   is deliberately at 4.5-4.6:1 on --id-bg-deep, so it has about 0.15 of
   headroom and any mid-luminance wash spends more than that. Over
   --id-bg-deep in light: 11% -> 4.00:1, 8% -> 4.16:1, 6% -> 4.29:1. No tint
   percentage keeps AA, so a lighter tint alone was not the fix.

   Tinting the PAGE ground instead makes the row a lift and a wash at once.
   color-mix(in srgb, var(--id-danger) 3%, var(--id-bg)) is #f9ecc3 light /
   #2e2a2a dark, and every token clears 4.5:1 on it: worst case 4.68:1 light
   (--id-accent2 / --id-special) and 4.52:1 dark (--id-danger — the keyword
   colour standing on its own hue). Both are better than the same token on the
   untinted code ground. The plain --id-bg declaration in front of it is the
   fallback for an engine without color-mix: same lift, no hue.

   NON-COLOUR CHANNELS, because WCAG 1.4.1 forbids hue as the only one:
     1. the row has a left rule and no other row has one — presence;
     2. its ground is lighter than the block's — luminance;
     3. a ✗ trails the line — shape. Generated content deliberately: an
        inline marker span would ride along on copy, and a reserved marker
        COLUMN would have to be reserved on all 466 code lines in the post to
        keep every block's columns aligned. Trailing, it can collide with
        nothing and shifts not one character of code.
   -------------------------------------------------------------------------- */

.hll {
  display: block;
  background: var(--id-bg, #fbf1c7);
  background: color-mix(in srgb, var(--id-danger, #b43e3e) 3%, var(--id-bg, #fbf1c7));
  box-shadow: inset 2px 0 0 var(--id-danger, #b43e3e);
}

.hll::after {
  content: "  ✗" / "";
  color: var(--id-danger, #b43e3e);   /* 4.67:1 light / 4.52:1 dark on the row */
}



/* ==========================================================================
   7. THE LANGUAGE LABEL                                         [CODE-08]
   --------------------------------------------------------------------------
   leaf specifies `code_label = dim`. --id-dim is 2.99:1 on the code ground in
   the light theme and a language name is information, so this is --id-muted
   (4.64:1 / 6.55:1) — the same substitution section 4 makes for comments.

   NO NEW MARKUP. kramdown already writes the language into the wrapper class
   (div.language-cpp.highlighter-rouge) and {% highlight %} already writes it
   into a data-lang attribute on the <code>, so the label is drawn from what
   is there rather than added. attr() can only read the element's own
   attributes, which is why there are two halves: an enumerated list for the
   class, because a class name cannot be read out as a string, and a single
   attr() rule for the figure.

   WHERE IT SITS. Absolutely positioned in the wrapper's top-right corner,
   which is OUTSIDE the scroll container — div.highlight is the scroller and
   the wrapper is not, and for the figure the containing block is the figure
   while the scroller is the <pre> inside it. Scrolled code therefore cannot
   reach it, which is the failure the rejected half of the specimen showed:
   with the label positioned on the block itself, the 91-character first line
   of Symbolizer::Symbolicate() ran underneath it before the reader had
   scrolled at all. It also lands inside the block's own top padding, so it
   overlaps no line even at scroll 0, and pointer-events: none means it can
   never eat a click meant for the code.

   ON HOVER, NOT ALWAYS. 45 blocks would be 45 more standing things on a page
   that is already 25,000px tall, and a fenced block's language is almost
   always obvious from the code in it. Revealed on :hover and on
   :focus-within — the second half is not decoration, a keyboard reader who
   tabs into a focusable region inside a block gets the same label a pointer
   does. Print shows it unconditionally, because print has no hover.
   -------------------------------------------------------------------------- */

div.highlighter-rouge { position: relative; }

div.highlighter-rouge::before,
figure.highlight > pre > code[data-lang]::before {
  position: absolute;
  inset-block-start: 0.15rem;
  inset-inline-end: 0.5rem;
  z-index: 2;
  padding-inline: 0.5ch;
  background: var(--id-bg-deep, #f4e8be);
  color: var(--id-muted, #6b665f);
  font-size: 0.7rem;
  line-height: 1.5;
  pointer-events: none;
  opacity: 0;
  transition: opacity 120ms ease;
}

/* One line per language, and only for languages this blog has or will
   plausibly have. A fence in anything else simply gets no content, and with
   no content there is no box — the same mechanism the copy-mode gutter uses
   to be absent rather than hidden. */
div.language-cpp::before        { content: "cpp" / ""; }
div.language-c::before          { content: "c" / ""; }
div.language-python::before     { content: "python" / ""; }
div.language-bash::before       { content: "bash" / ""; }
div.language-shell::before      { content: "shell" / ""; }
div.language-console::before    { content: "console" / ""; }
div.language-diff::before       { content: "diff" / ""; }
div.language-make::before       { content: "make" / ""; }
div.language-nasm::before       { content: "nasm" / ""; }
div.language-ruby::before       { content: "ruby" / ""; }
div.language-javascript::before { content: "javascript" / ""; }
div.language-json::before       { content: "json" / ""; }
div.language-yaml::before       { content: "yaml" / ""; }
div.language-html::before       { content: "html" / ""; }
div.language-rust::before       { content: "rust" / ""; }
div.language-go::before         { content: "go" / ""; }
/* the three lexers in _plugins/, each under every alias it registers */
div.language-hexdump::before    { content: "hexdump" / ""; }
div.language-xxd::before        { content: "xxd" / ""; }
div.language-hexyl::before      { content: "hexyl" / ""; }
div.language-telescope::before  { content: "telescope" / ""; }
div.language-pwndbg::before     { content: "pwndbg" / ""; }
div.language-gef::before        { content: "gef" / ""; }
div.language-objdump::before    { content: "objdump" / ""; }
div.language-gas::before        { content: "gas" / ""; }
div.language-asm::before        { content: "asm" / ""; }
div.language-disas::before      { content: "disas" / ""; }
div.language-gdb::before        { content: "gdb" / ""; }

figure.highlight > pre > code[data-lang]::before {
  content: attr(data-lang);
  content: attr(data-lang) / "";
}

div.highlighter-rouge:hover::before,
div.highlighter-rouge:focus-within::before,
figure.highlight:hover > pre > code[data-lang]::before,
figure.highlight:focus-within > pre > code[data-lang]::before {
  opacity: 1;
}

@media print {
  div.highlighter-rouge::before,
  figure.highlight > pre > code[data-lang]::before { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  div.highlighter-rouge::before,
  figure.highlight > pre > code[data-lang]::before { transition: none; }
}


/* ==========================================================================
   8. THE THREE LOCAL LEXERS      [PWN-01 hexdump, PWN-03 telescope, objdump]
   --------------------------------------------------------------------------
   Rouge 4.7.0 has no lexer for a hex dump, for pwndbg output, or for
   disassembly — Rouge::Lexer.find returns nil for all of hexdump, gdb,
   objdump, gas and asm — which is why the madcore post fences its dumps as
   ```python today. _plugins/hexdump_lexer.rb, telescope_lexer.rb and
   objdump_lexer.rb add them, and each one documents the token it emits per
   role. Those tokens are Rouge's standard classes, on purpose: a block still
   reads correctly with none of the rules below, because section 4 already
   colours every class the three lexers use. What follows is only the handful
   of places where the role a class resolves to in code is the wrong role for
   the same class in a dump.

   If a lexer's token choice changes, the class names in these comments are
   the contract to reconcile against.
   -------------------------------------------------------------------------- */

/* --- hexdump / xxd / hexyl  [PWN-01] --------------------------------------
   hexdump_lexer.rb parses each line into offset / hex column / ASCII pane and
   colours BOTH columns from one decision per byte, so these classes are byte
   CATEGORIES, not syntax:

     .gp   offset column, and the `*` repeat marker   Generic::Prompt
     .go   null byte, 00                              Generic::Output
     .s    printable ASCII, 21..7e                    Literal::String
     .w    whitespace byte, 09-0d and 20              Text::Whitespace
     .mh   non-ASCII and C0 control                   Literal::Number::Hex
     .p    the `|` pane guards of `hexdump -C`        Punctuation
     —     anything that is not a dump line           Text

   The offset column needs nothing from this section: .gp is already
   --id-muted and already user-select: none in section 4, for exactly the
   reason it is wanted here — copying a dump has to yield bytes, not
   addresses, which is the line-number argument again with a different column.

   Null bytes are pinned to --id-muted here even though section 4 already
   puts .go there. They are the majority of every dump in the post and they
   are CONTENT — "this region is zero" is the observation a heap writeup is
   making — so they may not drift onto --id-dim (2.99:1 light) the way a
   receding role invites. --id-muted is 4.64:1 light / 6.55:1 dark.

   Printable / whitespace / non-ASCII take hexyl's three hues, which in this
   palette are: --id-success 4.58:1 light / 7.47:1 dark, --id-info 4.64:1 /
   6.60:1, --id-accent2 4.54:1 / 6.36:1. .w has to be named because section 4
   groups Text.Whitespace with plain identifiers on --id-fg, which would make
   a 20 byte indistinguishable from a filename character.

   Hue is not the only channel: the lexer substitutes 0 / _ / · for xxd's
   ambiguous `.` in the pane (PANE_GLYPHS), so each category reads as a
   different GLYPH there as well as a different colour. */
div:is(.language-hexdump, .language-xxd, .language-hexyl) .highlight .go {
  color: var(--id-muted, #6b665f);
}
div:is(.language-hexdump, .language-xxd, .language-hexyl) .highlight .s {
  color: var(--id-success, #626d2a);
}
div:is(.language-hexdump, .language-xxd, .language-hexyl) .highlight .w {
  color: var(--id-info, #436d77);
}
div:is(.language-hexdump, .language-xxd, .language-hexyl) .highlight .mh {
  color: var(--id-accent2, #a65009);
}

/* --- telescope / pwndbg / gef  [PWN-03] -----------------------------------
   telescope_lexer.rb's contract:

     .gp   `00:0000│` index gutter, `pwndbg>`   Generic::Prompt
     .p    banner rules, chain arrows, `│`      Punctuation
     .gu   banner label, `[ REGISTERS ]`        Generic::Subheading
     .nv   register name, RSI / rax             Name::Variable
     .gs   frame marker, rsp / rbp, and `*`     Generic::Strong
     .mh   hex value, 0x5647a04a5730            Literal::Number::Hex
     .mi   decimal value (info registers)       Literal::Number::Integer
     .s    the string at the end of a chain     Literal::String
     .nf   symbol, (ParseNtFile+0x11a)          Name::Function
     .nl   x/10xg row address                   Name::Label
     .no   flag set, [ CF SF IF RF ]            Name::Constant
     .c1   `#` notes, and pwndbg's C-comment    Comment::Single / Multiline
           char annotation (written here as a
           description, because the literal
           closes this comment)

   Nine of those twelve want exactly what section 4 gives them. The index
   gutter is .gp again, so it is muted and unselectable for free.

   .mh drops to body colour: a telescope block is ALL addresses, and 225 hex
   literals in --id-special would make the purple the page rather than the
   pointer chain. The values are what the reader is reading, so they take the
   reading colour and the annotations around them carry the hue.

   .gu goes to --id-accent2, matching what pwndbg's own banner does — it is a
   section marker inside the output, not a heading in a document.

   And the banner RULES are dimmed, which needs the two sibling selectors
   below rather than a rule on .p, because the lexer emits the rule, the
   brackets AND the pointer-chain arrow as Punctuation and Rouge consolidates
   consecutive same-token runs into one span. So the rule glyphs are exactly
   the .p that touches the .gu label, and the arrows are every other .p.
   --id-dim is allowed on that one: `─────` is chrome by any reading, while
   an arrow means "points at" and stays on --id-muted at 4.64:1. If :has() is
   missing the whole rule drops and the rules stay muted, which is quieter
   than intended and no less legible. */
div:is(.language-telescope, .language-pwndbg, .language-gef) .highlight .mh {
  color: var(--id-fg, #654735);
}
div:is(.language-telescope, .language-pwndbg, .language-gef) .highlight .gu {
  color: var(--id-accent2, #a65009);
}
div:is(.language-telescope, .language-pwndbg, .language-gef) .highlight .p:has(+ .gu),
div:is(.language-telescope, .language-pwndbg, .language-gef) .highlight .gu + .p {
  color: var(--id-dim, #928374);
}

/* --- objdump / gas / asm / disas / gdb ------------------------------------
   TAB-8, and this is the reason it is scoped. objdump lays its columns out
   with hard tabs and assumes an 8-column stop; section 1 sets tab-size: 4 on
   div.highlight, which is right for C++ and Python and puts the opcode byte
   column of a disassembly half a stop out of line with the mnemonic column
   under it. objdump_lexer.rb emits tabs verbatim rather than expanding them,
   precisely so a copied dump is still the bytes objdump printed — so the
   alignment has to be fixed here, in the only place it can be fixed without
   touching what the clipboard gets.

   Specificity: (0,3,1) against section 1's (0,1,1), so the inner div wins.

   .nl is the one colour the objdump lexer's contract asks for — an
   instruction address is a coordinate, not a label, and 200 of them in
   --id-info would out-shout the mnemonics. --id-muted, 4.64:1 / 6.55:1. */
div:is(.language-objdump, .language-gas, .language-asm, .language-disas, .language-gdb) div.highlight {
  tab-size: 8;
  -moz-tab-size: 8;
}
div:is(.language-objdump, .language-gas, .language-asm, .language-disas, .language-gdb) .highlight .nl {
  color: var(--id-muted, #6b665f);
}
