/* ─── CSS CUSTOM PROPERTIES — LIGHT MODE (default) ──────── */
:root {
  /* Backgrounds.
     --color-bg is the datum and every other surface sits one step off it, in
     the direction that reads as "not the page" for that scheme: darker in
     light, lighter in dark. Which way is the rule; how far is taste, and the
     rule says nothing about the rank of the non-page surfaces among
     themselves — don't read one into it.
     The four panels that carry a box-shadow — the header dropdown, the open
     version switcher, the drawer and #search-results — all paint with
     --color-bg-secondary, so a value on the wrong side of --color-bg paints
     them as recessed while their shadow claims they are raised. That is what
     the dark value had done. */
  --color-bg:              #f6f6f4;
  --color-bg-secondary:    #f0f0ee;
  --color-bg-code:         #f3f3f1;
  --color-bg-code-header:  var(--color-bg-secondary);
  --color-bg-aside:        #efefed;

  /* Borders */
  --color-border-subtle:   #e0e0dc;
  --color-border:          #ccc;

  /* Text — muted and dim both carry body-size copy (blockquotes, TOC links,
     table headers), so both clear 4.5:1 on --color-bg for WCAG AA. */
  --color-text:            #1a1a1a;
  --color-text-muted:      #6a6a6a;  /* 4.9:1 on --color-bg, 4.7:1 on --color-bg-secondary */
  --color-text-dim:        #4f4f4f;  /* 7.6:1 — keeps a visible step below muted */
  --color-text-heading:    #0a0a0a;
  --color-text-code:       #1a1a1a;

  /* Brand */
  --color-accent:          #0f7a4a;
  --color-accent-hover:    rgba(15, 122, 74, 0);
  /* The accent at the same alpha --color-accent-hover derives to, but shipped
     as a real tint rather than as zero. The mega panel's rows are two lines
     each in a grid, so the pill is what says "this title and this description
     are one target" — it cannot be a hover that only appears once the buyer
     changes the accent. */
  --color-mega-row-hover:  rgba(15, 122, 74, .08);
  --color-link:            #0f7a4a;
  /* The ink that reads on --color-accent, for the skip link, the button block
     and the comment submit. Derived per-buyer in doku_accent_css(): white
     clears AA on this accent and lands at 1.9:1 on the dark scheme's, so one
     hardcoded value is always wrong for one of the two. */
  --color-on-accent:       #fff;

  /* Tags / badges */
  /* 8% of the accent mixed onto white, solid, so badge text clears 4.5:1 on
     every surface: a translucent 10% over the page grays left it at 4.13:1.
     doku_hex_tint() derives a custom accent's the same way. */
  --color-tag-bg:          #ecf4f1;
  --color-tag-text:        #0f7a4a;

  /* Sidebar */
  --color-sidebar-active:       rgba(15, 122, 74, .13);
  --color-sidebar-active-text:  #0f7a4a;

  /* Search */
  --color-search-bg:       #fff;

  /* Breadcrumb */
  --color-breadcrumb:      #6a6a6a;

  /* Keyboard shortcut */
  --color-kbd-bg:          #eee;
  --color-kbd-border:      #ddd;

  /* A bare <mark>, as raw HTML imports carry it */
  --color-mark-bg:         #fcefc2;

  /* Scrollbar */
  --color-scrollbar:       #e0e0dc;

  /* Helpfulness rating — idle thumb, 3.2:1 as a UI component */
  --color-vote-idle:       #8a8a8a;

  /* Diff markers in a code block. Deliberately NOT accent-derived: green-adds
     and red-dels are a convention the reader arrives with, and following a
     buyer's brand hue into orange would cost more than it gained. Both clear
     4.5:1 on --color-bg-code, so the marker reads without the fill. */
  --color-diff-add:        #1a7f37;
  --color-diff-add-bg:     rgba(26, 127, 55, .10);
  --color-diff-del:        #cf222e;
  --color-diff-del-bg:     rgba(207, 34, 46, .10);

  /* The API param table's type column */
  --color-param-type:      #b45309;

  /* Callouts */
  --color-note:            rgba(15, 122, 74, .06);
  --color-note-border:     rgba(15, 122, 74, .25);
  --color-tip:             rgba(16, 185, 129, .06);
  --color-tip-border:      rgba(16, 185, 129, .25);
  --color-warning:         rgba(180, 100, 0, .06);
  --color-warning-border:  rgba(180, 100, 0, .25);
  --color-danger:          rgba(239, 68, 68, .06);
  --color-danger-border:   rgba(239, 68, 68, .25);
  --color-info:            rgba(99, 102, 241, .06);
  --color-info-border:     rgba(99, 102, 241, .25);

  /* Callout title colors. The background and border tints above are translucent
     and can stay vivid, but the title is 11px text and has to clear 4.5:1 against
     the composited callout background — hence darker shades of the same hues here,
     and the vivid ones in dark.css. The note callout uses --color-accent. */
  --color-tip-text:        #047857;
  --color-warning-text:    #8f5a00;
  --color-danger-text:     #cc2929;
  --color-info-text:       #4f46e5;

  /* Typography */
  --font-mono: 'DM Mono', 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
  --font-sans: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  /* UI type scale, in px like the bar geometry it sits in. The theme ran to 23
     sizes across px, rem and em before these existed, because a size had no
     name to reach for the way the radii and --nav-row-pad do. */
  --text-2xs:  10px;   /* uppercase micro labels — rail headings, pager, mega column */
  --text-xs:   11px;   /* chips and dense controls — badges, theme toggle, version */
  --text-sm:   12px;   /* secondary rows — TOC links, descriptions, metadata */
  --text-base: 13px;   /* the chrome default — nav links, sidebar rows, form labels */
  --text-md:   14px;   /* comfortable reading chrome — accordions, comments, results */
  --text-lg:   15px;   /* the wordmark */
  /* Mono renders optically smaller than sans at the same nominal size, so code
     sits half a pixel above --text-base rather than on it. */
  --text-code: 13.5px;

  /* Article type, in rem so it tracks the root rather than the chrome. The
     heading ramp is the one CLAUDE.md documents; keep the two in step. */
  --text-body: .9375rem;  /* 15px */
  --text-h1:   2rem;      /* 32px */
  --text-h2:   1.375rem;  /* 22px */
  --text-h3:   1.125rem;  /* 18px */
  --text-h4:   1rem;      /* 16px */
  --text-h5:   .9375rem;  /* 15px */
  --text-h6:   .875rem;   /* 14px */

  /* Layout widths */
  /* The documentation reading column, and what the Layout section's
     "Content width" control writes. Primary rather than derived: the
     frame below is built from it, so #content fills the 1fr track exactly at
     every value and can never stop short of #aside. */
  --width-article: 760px;
  /* Derived, not chosen — the article column plus the two rails. */
  --width-content: calc(var(--width-article) + var(--width-sidebar) + var(--width-rail));
  /* The header bar's cap, switched by the Header section's "Header width"
     control, which emits `none` here for a full-bleed bar. The documentation
     frame is built the same way — --width-content inside a --gutter-bar each
     side — so .nav-inner's content box meets the outer edges of the two rails at
     every width: the logo sits on #doku-sidebar's left border and
     #theme-toggle's trailing edge on #aside's right border, which is what makes
     the four vertical rules read as one frame. */
  --width-nav:     calc(var(--width-content) + 2 * var(--gutter-bar));
  /* The left column: the docs tree, the blog navigation or a page's Left
     Column widgets. doku_page_width() in customizer.php repeats it. */
  --width-sidebar: 280px;
  /* The right column, whatever it holds: the table of contents, a listing
     page's browse sections or the Right Column widgets. doku_page_width()
     repeats it. */
  --width-rail:    220px;
  /* The desktop rails are fixed rather than sticky, so each carries the offset
     the centered #doku-app would otherwise have given it. 50% resolves against
     the viewport for a fixed box, which excludes the scrollbar — 100vw includes
     it and would land both rails half a scrollbar off their own columns. Never
     under --gutter-bar, the padding #doku-app keeps beside the rails, so on a
     window narrower than the frame they stay off the edge. */
  --rail-gutter: max(var(--gutter-bar), calc(50% - var(--width-content) / 2));
  /* How much of the footer is on screen, written by initRailFooterClearance()
     in main.js so each rail's foot meets the footer's top edge. 0 without JS,
     where the footer's own z-index rides it over them instead. */
  --rail-footer-overlap: 0px;
  /* Cap, not a width: the header switcher sizes to its own label, and growth
     past this point is absorbed by #search-bar shrinking. Theme-invariant, so
     :root only, like the radii and the icon tokens below. */
  --width-version-max: 160px;

  /* The horizontal gutter shared by the header bar and the drawer. It is one
     token because the drawer's × replaces the header's ☰ in place, so the two
     must land under the same finger. Narrows to 16px below 560px. */
  --gutter-bar: 24px;

  /* The space between two content blocks — code, callouts, quotes, tables,
     steps, tabs, cards, diagrams, and every styled core block. One deliberate
     exception, which says something the token cannot: hr / wp-block-separator
     take 32px because a section break is meant to be louder. */
  --space-block: 20px;

  /* Corner radii, by the size of the thing being rounded:
       sm — inline chips inside a line of text: kbd, inline code, badges
       md — interactive controls and small panels: buttons, fields, callouts
       lg — surfaces that own their own block: code blocks, cards, panels
     Composite values (the tab bar against the code block beneath it) are
     written corner by corner and must stay in step. */
  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 8px;

  /* Panel shadows. Unlike the radii these are NOT theme-invariant and need a
     dark.css counterpart: a black shadow does almost nothing on a near-black
     page, so dark UI needs more of it than light does, not less. These four
     values were one set shared by both schemes, and at 12% black on #0d0d0d
     the header dropdown and the version panel cast nothing at all — which is
     where they need it most, since the panel sits only .00116 of luminance
     above the page with a #2a2a2a hairline doing the rest of the work.
     The two drawer shadows are separate because their offset is horizontal:
     they are the panels that arrive from an edge rather than dropping, one
     from each side. */
  --shadow-panel:   0 4px 16px rgba(0, 0, 0, .12);
  --shadow-overlay: 0 8px 32px rgba(0, 0, 0, .2);
  --shadow-drawer:  8px 0 32px rgba(0, 0, 0, .3);
  --shadow-drawer-end: -8px 0 32px rgba(0, 0, 0, .3);

  /* Transition durations. Two, because the theme only does two kinds of move:
       quick — a control answering the pointer: hover, focus, a glyph fading in
       panel — something arriving or leaving: the drawer, a disclosure, a fade
     These ran to six values (.1 .12 .15 .2 .22 .25) across 43 declarations, and
     the .12/.15 split alone was 36 of them — a difference no reader can see and
     nothing to learn from. Reach for one of these rather than typing a number;
     that is the whole reason they have names.

     Easing is NOT tokenized: only the drawer and .sidebar-sub carry a curve,
     they carry the same one, and a third consumer would be the time to name it.

     Every consumer must also be switched off in the MOTION section at the foot
     of this file — see the note there. */
  --duration-quick: .12s;
  --duration-panel: .25s;

  /* Icons — verbatim path data from lucide 1.31.0, 24x24 on a 2px stroke.
     ISC License, Copyright (c) 2026 Lucide Icons and Contributors.
     See ICONS below; take new ones from the same release.
     Geometry only: each is painted as a mask, which reads alpha alone, so the
     literal stroke color never shows — it stands in for currentColor, which a
     data URI has nothing to resolve against. Theme-invariant, so like the radii
     they live in :root alone. */
  --icon-chevron-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  --icon-chevron-right: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E");
  --icon-menu: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 5h16'/%3E%3Cpath d='M4 12h16'/%3E%3Cpath d='M4 19h16'/%3E%3C/svg%3E");
  --icon-table-of-contents: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 5H3'/%3E%3Cpath d='M16 12H3'/%3E%3Cpath d='M16 19H3'/%3E%3Cpath d='M21 5h.01'/%3E%3Cpath d='M21 12h.01'/%3E%3Cpath d='M21 19h.01'/%3E%3C/svg%3E");
  --icon-panel-right-open: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect width='18' height='18' x='3' y='3' rx='2'/%3E%3Cpath d='M15 3v18'/%3E%3Cpath d='m10 15-3-3 3-3'/%3E%3C/svg%3E");
  --icon-x: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6 6 18'/%3E%3Cpath d='m6 6 12 12'/%3E%3C/svg%3E");
  --icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m21 21-4.34-4.34'/%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3C/svg%3E");
  --icon-sun: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='4'/%3E%3Cpath d='M12 2v2'/%3E%3Cpath d='M12 20v2'/%3E%3Cpath d='m4.93 4.93 1.41 1.41'/%3E%3Cpath d='m17.66 17.66 1.41 1.41'/%3E%3Cpath d='M2 12h2'/%3E%3Cpath d='M20 12h2'/%3E%3Cpath d='m6.34 17.66-1.41 1.41'/%3E%3Cpath d='m19.07 4.93-1.41 1.41'/%3E%3C/svg%3E");
  --icon-moon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401'/%3E%3C/svg%3E");
  --icon-monitor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect width='20' height='14' x='2' y='3' rx='2'/%3E%3Cline x1='8' x2='16' y1='21' y2='21'/%3E%3Cline x1='12' x2='12' y1='17' y2='21'/%3E%3C/svg%3E");
  --icon-link: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71'/%3E%3Cpath d='M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71'/%3E%3C/svg%3E");
  --icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  --icon-thumbs-up: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z'/%3E%3Cpath d='M7 10v12'/%3E%3C/svg%3E");
  --icon-thumbs-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z'/%3E%3Cpath d='M17 14V2'/%3E%3C/svg%3E");
  /* The same paths filled, so the reader's vote differs from the other button
     in shape and not only in color. */
  --icon-thumbs-up-filled: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z'/%3E%3Cpath d='M7 10v12'/%3E%3C/svg%3E");
  --icon-thumbs-down-filled: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z'/%3E%3Cpath d='M17 14V2'/%3E%3C/svg%3E");
  --icon-folder: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z'/%3E%3C/svg%3E");
  --icon-file-text: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z'/%3E%3Cpath d='M14 2v5a1 1 0 0 0 1 1h5'/%3E%3Cpath d='M10 9H8'/%3E%3Cpath d='M16 13H8'/%3E%3Cpath d='M16 17H8'/%3E%3C/svg%3E");
  --icon-arrow-left: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m12 19-7-7 7-7'/%3E%3Cpath d='M19 12H5'/%3E%3C/svg%3E");
  --icon-arrow-right: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='m12 5 7 7-7 7'/%3E%3C/svg%3E");
  --icon-loader: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 12a9 9 0 1 1-6.219-8.56'/%3E%3C/svg%3E");
  --icon-alert: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3'/%3E%3Cpath d='M12 9v4'/%3E%3Cpath d='M12 17h.01'/%3E%3C/svg%3E");

  /* Accessibility */
  --tap-target-min: 32px;

  --nav-row-leading: 1.4;

  --nav-row-pad: 8px;

  scrollbar-width: thin;
  scrollbar-color: var(--color-scrollbar) transparent;
}

/* ─── RESET ──────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* scroll-padding-top is the only anchor offset in the theme: #topnav's 56px plus
   16px of clearance, restated with the admin bar's own height in the ADMIN BAR
   OFFSET section. A scroll-margin-top on a heading does not replace it, it adds
   to it — h2 carried 64px and landed 80px below the bar while h3 and h4, which
   are anchor targets too, landed at 16. */
html { font-size: 16px; scroll-padding-top: 72px; scroll-behavior: smooth; }
/* A <button> does not inherit font-family — Chrome's UA sheet pins it to Arial.
   Matters for any button showing text: the theme toggle's label, the code-copy
   buttons. Buttons wanting a different face still set it below. */
button { font-family: inherit; }
input:not([type="checkbox"]):not([type="radio"]),
select,
textarea { min-height: var(--tap-target-min); }
input[type="checkbox"],
input[type="radio"] { min-width: var(--tap-target-min); min-height: var(--tap-target-min); }
body {
  min-height: 100vh;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-sans);
  text-wrap: pretty;
}

/* ─── SKIP LINK ──────────────────────────────────────────── */
/* Doubled class: core prints .screen-reader-text:focus inline at (0,2,0), which
   painted the link gray with its own padding and a -1px margin. Inset from the
   corner so the focus ring is not cut off by the top of the window. */
.skip-link.screen-reader-text {
  position: absolute;
  top: -48px;
  left: 8px;
  margin: 0;
  background: var(--color-accent);
  color: var(--color-on-accent);
  padding: 8px 16px;
  z-index: 9999;
  font-size: var(--text-md);
  line-height: 1.4;
  text-decoration: none;
  border-radius: var(--radius-md);
  transition: top var(--duration-quick);
}
.skip-link.screen-reader-text:focus {
  top: 8px;
  left: 8px;
  padding: 8px 16px;
  background: var(--color-accent);
  color: var(--color-on-accent);
  font-size: var(--text-md);
  line-height: 1.4;
}

/* ─── ICONS ──────────────────────────────────────────────── */
/* One paint model for every glyph in the theme: an --icon-* token from :root
   drawn as a mask over background-color: currentColor, so the icon takes the
   color its context already sets and follows the light/dark toggle. Never a
   background-image — that freezes the stroke and goes invisible in one theme.

   currentColor also means no color rule needs rewriting for an icon: it
   resolves the element's own `color`, so a state that already sets `color`
   (a chosen vote, an incomplete index) keeps tinting the glyph unchanged.

   A real element takes .doku-icon plus a --icon-* modifier. A pseudo-element
   cannot carry a class, so it joins the selector lists here and sets --icon in
   its own section. Both lists below have to be edited together: without the
   `content` rule the pseudo-element is never generated, which no measurement
   catches — getComputedStyle resolves width, mask and color for a
   pseudo-element that does not exist. Sizing is 1em; consumers needing a fixed
   size override width/height. */
.doku-icon,
.wp-block-details > summary::after,
.nav-version > summary::after,
.doc-actions > summary::after,
.doku-mega-foot::after,
.sidebar-arrow,
.nav-submenu-arrow,
.breadcrumb li + li::before,
.doku-search-status::before,
:is(#widget-sidebar, #widget-sidebar-left, .footer-widgets, .entry-content) :is(.wp-block-archives-dropdown, .wp-block-categories-dropdown, .widget_categories form)::after {
  /* inline-block keeps a pseudo-element in its text flow; the flex containers
     blockify it anyway. */
  display: inline-block;
  width: 1em;
  height: 1em;
  flex-shrink: 0;
  vertical-align: middle;
  background-color: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
  mask: var(--icon) center / contain no-repeat;
}

/* `content` belongs to the pseudo-elements alone — on a real element
   (.doku-icon, .sidebar-arrow, .nav-submenu-arrow) it is an
   element replacement and would blank the span. */
.wp-block-details > summary::after,
.nav-version > summary::after,
.doc-actions > summary::after,
.doku-mega-foot::after,
.breadcrumb li + li::before,
.doku-search-status::before,
:is(#widget-sidebar, #widget-sidebar-left, .footer-widgets, .entry-content) :is(.wp-block-archives-dropdown, .wp-block-categories-dropdown, .widget_categories form)::after { content: ''; }

.doku-icon--menu        { --icon: var(--icon-menu); }
.doku-icon--x           { --icon: var(--icon-x); }
.doku-icon--table-of-contents { --icon: var(--icon-table-of-contents); }
.doku-icon--panel-right-open { --icon: var(--icon-panel-right-open); }
.doku-icon--search      { --icon: var(--icon-search); }
.doku-icon--sun         { --icon: var(--icon-sun); }
.doku-icon--moon        { --icon: var(--icon-moon); }
.doku-icon--monitor     { --icon: var(--icon-monitor); }
.doku-icon--link        { --icon: var(--icon-link); }
.doku-icon--check       { --icon: var(--icon-check); }
.doku-icon--thumbs-up   { --icon: var(--icon-thumbs-up); }
.doku-icon--thumbs-down { --icon: var(--icon-thumbs-down); }
.doku-icon--folder      { --icon: var(--icon-folder); }
.doku-icon--file-text   { --icon: var(--icon-file-text); }
.doku-icon--arrow-left  { --icon: var(--icon-arrow-left); }
.doku-icon--arrow-right { --icon: var(--icon-arrow-right); }

/* ─── DISCLOSURE CHEVRON ─────────────────────────────────── */
/* One arrow vocabulary: chevron-down at 15px, used by the header menu, the docs
   sidebar toggle and its drawer mirror, and the accordion block.
   The glyph points DOWN at rest, so open is 0deg and collapsed is
   rotate(-90deg).

   Panel openers are the exception, and they follow a rule of their own: the
   arrow points at where the panel will appear, and points back at the trigger
   once it is open. .nav-version and a top-level header item drop a panel
   downwards, so both rest at 0deg and flip to 180deg. A header item inside a
   panel flies its own out to the right, so it rests at -90deg and flips to
   90deg. Both are set in .nav-submenu-arrow's own rules.

   The breadcrumb separator is deliberately not part of this: it is a static
   chevron-right that marks nothing expandable and never rotates.

   Shape and rotation only — each consumer keeps its own placement and color. */
.wp-block-details > summary::after,
.nav-version > summary::after,
.doc-actions > summary::after,
.sidebar-arrow,
.nav-submenu-arrow {
  --icon: var(--icon-chevron-down);
  width: 15px;
  height: 15px;
  transition: transform var(--duration-panel);
}

/* ─── APP WRAPPER ────────────────────────────────────────── */
#doku-app {
  min-height: calc(100vh - 56px);
  display: grid;
  grid-template-columns: 1fr;
  max-width: var(--width-content);
  margin: 0 auto;
  width: 100%;
  background: var(--color-bg);
  color: var(--color-text);
}

/* ─── TOP NAV ────────────────────────────────────────────── */
#topnav {
  height: 56px;
  border-bottom: 1px solid var(--color-border-subtle);
  background: var(--color-bg-secondary);
  position: sticky;
  top: 0;
  z-index: 100;
}

/* This gap is the bar's only inter-control spacing: a control added to the
   header sets no margin of its own. .nav-logo's margin-right is the one
   exception, and it marks the logo-block/menu boundary rather than a control
   gap. A display:none child contributes no gap, which is what lets .nav-links
   and #topnav .nav-version-wrap drop out below 1101px without leaving a hole. */
.nav-inner {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 var(--gutter-bar);
  max-width: var(--width-nav);
  margin: 0 auto;
  height: 100%;
}

.nav-logo {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--color-text-heading);
  text-decoration: none;
  /* Adds to .nav-inner's gap for 32px of separation between the logo block and
     the menu — a section boundary, wider than the gap between peer controls. */
  margin-right: 20px;
  letter-spacing: -.01em;
  flex-shrink: 0;
  min-height: var(--tap-target-min);
}

/* Tracks .nav-logo's height rather than the tap target itself — the row is
   sized by the same token, and the mark should fill it rather than sit shorter
   than the text beside it. add_theme_support( 'custom-logo' ) in
   inc/theme-setup.php suggests twice this, for retina; the two move together. */
.custom-logo {
  max-height: var(--tap-target-min);
  width: auto;
  display: block;
}

/* The dark-mode logo. Both images are in the page and CSS picks one, because
   the toggle changes data-theme without a reload — and because PHP never knows
   the resolved scheme: with the 'auto' setting the server renders dark and the
   head script may correct it to light before paint. <picture> with
   prefers-color-scheme would key on the OS instead and ignore the toggle.

   header.php emits these modifiers only when a dark logo is set, so a lone
   .custom-logo carries neither and no rule here can reach it. */
.custom-logo--dark { display: none; }
[data-theme="dark"] .custom-logo--light { display: none; }
[data-theme="dark"] .custom-logo--dark { display: block; }

/* The accent-tinted logo — the icons' paint model applied to an uploaded SVG,
   so it follows the light/dark toggle and a customized accent with no rule of
   its own. Not .doku-icon: that rule pins 1em, and its two selector lists are
   for lucide tokens.

   --logo-mask and --logo-aspect come from doku_customizer_inline_css(). A mask
   has no intrinsic size, so without the ratio the box collapses to zero width;
   the fallback is the 192 × 64 add_theme_support suggestion.

   height rather than the max-height .custom-logo caps at, because this element
   has no content to size it. Both resolve --tap-target-min, which is what keeps
   the two renderings of the mark the same height.

   max-width is what stops a wide wordmark reaching the search field, and it is
   the only declaration here that does. .nav-logo takes min-width: 0 at 782px
   and flex-shrink: 1 at 560px, so below 560px it shrinks below its content —
   but the ratio sizes this box from its own height, so it does not shrink with
   the parent and overflows it instead. Measured at 375px: a 512 × 64 mark ran
   85px into #search-bar. flex-shrink on THIS element changes nothing either way
   for the same reason; it is set only so a masked box, whose content width is
   zero, never becomes the item the row chooses to collapse. */
.doku-logo-mask {
  display: block;
  height: var(--tap-target-min);
  width: auto;
  max-width: 100%;
  aspect-ratio: var(--logo-aspect, 3);
  flex-shrink: 0;
  background-color: var(--color-accent);
  -webkit-mask: var(--logo-mask) center / contain no-repeat;
  mask: var(--logo-mask) center / contain no-repeat;
}

/* Site name over tagline, as one block beside the logo image. Stacking is what
   removes the alignment problem: side by side, a 15px wordmark and a 12px
   tagline in equal line boxes are box-centered but ~1px apart on the baseline,
   which reads as crooked. min-width:0 lets the column shrink below its content
   width so the ellipsis rules below can bite.

   Deliberately no height: the tagline is hidden below 782px and absent when the
   site description is empty, so any pinned value would center the site name in a
   half-empty box. .nav-logo's align-items:center already centers this against
   the mark at whatever height it happens to be. */
.logo-lockup {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
}

.logo-text {
  line-height: 17px;
}

/* Sans, not the mono face .nav-logo sets — a tagline is a sentence, and in the
   code face it reads as a third nav item. The max-width caps a long
   buyer-authored tagline before it starts pushing the primary menu right. */
.logo-tagline {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: 400;
  line-height: 13px;
  color: var(--color-text-muted);
  max-width: 180px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}


.nav-links {
  display: flex;
  align-items: center;
  gap: 0;
  list-style: none;
  margin: 0;
  padding: 0;
}
.nav-links li { list-style: none; }
/* Every row rule names `li > span` beside `a`: an item pointing at '#' renders
   as a <span> label (doku_nav_menu_static_label()). The twin sits one element
   above its `a`, so each pair keeps the same order against the others. */
.nav-links a,
.nav-links li > span {
  display: block;
  padding: 0 12px;
  height: 56px;
  line-height: 56px;
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--color-text-dim);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  transition: color var(--duration-quick), border-color var(--duration-quick);
  white-space: nowrap;
}
.nav-links a:hover,
.nav-links li > span:hover { color: var(--color-text-heading); }
.nav-links .current-menu-item > a,
.nav-links .current-menu-ancestor > a,
.nav-links a.active,
.nav-links li.current-menu-ancestor > span {
  color: var(--color-accent);
  border-bottom-color: var(--color-accent);
}

/* ── Header dropdowns (menu depth 3) ───────────────────────
   #topnav is position:sticky with a z-index, which makes it a stacking context
   — so the panel's z-index is relative to the nav's own children, and sits
   below #search-results (200) which can overlap it. #topnav sets no overflow,
   so the panel escapes the 56px bar. */
.nav-links li { position: relative; }

/* The header's floating dropdown panel. --radius-md is its tier: "interactive
   controls and small panels". #search-results is deliberately not styled from
   here — it is a 360px results list in the --radius-lg tier with a heavier
   shadow, a different border token and its own z-index of 200.

   max-content, not 100%: the rows are nowrap, so a label longer than the
   trigger would otherwise spill outside this background. min-width keeps the
   panel from ever being narrower than the item it hangs off. */
.nav-links .sub-menu {
  position: absolute;
  top: 100%;
  z-index: 190;
  min-width: 100%;
  width: max-content;
  margin: 0;
  padding: 8px 0;
  list-style: none;
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-panel);
  left: 0;
  /* Revealed by the trio below, so it fades in. */
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity var(--duration-quick), transform var(--duration-quick), visibility var(--duration-quick);
}

/* Both set by initHeaderMenus(), which measures the panels against the viewport
   and moves whichever would run off its right edge. */
.nav-links > li > .sub-menu.align-right { left: auto; right: 0; }
.nav-links .sub-menu .sub-menu.flip-left { left: auto; right: calc(100% + 1px); }

/* Level two flies out beside its row rather than dropping below it. The 9px is
   this panel's own 8px padding plus its 1px border, so its first row lands
   level with the row that opened it rather than 7px below. The 1px on the left
   puts the two borders on the same pixel, so the boxes touch and a pointer
   moving diagonally between them never crosses a gap.

   min-width is dropped because the inherited 100% would be the parent panel's
   width, which has nothing to do with this one's contents. */
.nav-links .sub-menu .sub-menu {
  top: -9px;
  left: calc(100% + 1px);
  min-width: 0;
}

/* Three ways to open a panel, and the chevron's rotation is bound to exactly
   the same three or the arrow and the panel disagree:

   - aria-expanded, set by initHeaderMenus(). This is the tap path, and it pins:
     the panel survives the pointer leaving, and a second click closes it.
   - :hover, on fine pointers only. A touch screen leaves li:hover stuck after a
     tap, which would strand a panel open under an arrow that says closed.
   - :focus-within, only without JS. With JS the button's aria-expanded is the
     single source of truth, and focus-within would reveal a panel the ARIA
     state calls closed; without it, it is the only keyboard route in. .doku-js
     is set pre-paint in header.php, so the swap between the two never flashes.

   .no-hover is added for as long as the pointer stays on an item the reader
   just clicked closed — otherwise hover would immediately reopen it and the
   click would look ignored. */
.nav-links .nav-submenu-toggle[aria-expanded="true"] ~ .sub-menu,
html:not(.doku-js) .nav-links li:focus-within > .sub-menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
@media (hover: hover) and (pointer: fine) {
  .nav-links li:hover:not(.no-hover) > .sub-menu {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
  }
}

/* The parent's 56px line-height would make each child row 56px tall. */
.nav-links .sub-menu a,
.nav-links .sub-menu li > span {
  height: auto;
  line-height: 1.5;
  padding: 8px 16px;
  border-bottom: none;
  white-space: nowrap;
}
/* Color alone, no background pill — the same way the bar itself marks hover.
   These rows are single-line and nowrap, so the step from --color-text-dim is
   the whole affordance; the mega panel's two-line rows are the case that needs
   a pill, and they have their own always-visible token for it. */
.nav-links .sub-menu a:hover,
.nav-links .sub-menu a:focus-visible {
  color: var(--color-text-heading);
}
.nav-links .sub-menu .current-menu-item > a { color: var(--color-accent); }

/* ── Header submenu toggle ─────────────────────────────────
   A parent item is a row: label, then a 32px disclosure button. The <a> hands
   over its trailing padding, so the item is no wider than it was when the
   chevron was an ::after inside the link.

   Keyed on the button rather than on .menu-item-has-children: WordPress sets
   that class from the database, before the depth cap, so an item at the deepest
   rendered level has it while having no panel and no button. Both rules tie
   with `.nav-links .sub-menu a` above and win on source order. */
.nav-links li:has(> .nav-submenu-toggle) { display: flex; align-items: center; }
.nav-links a:has(+ .nav-submenu-toggle),
.nav-links li > span:has(+ .nav-submenu-toggle) { padding-right: 0; }

/* Placement and color only; shape and rotation come from the DISCLOSURE
   CHEVRON section. Color alone on hover, no background pill: that is how the
   bar marks hover, and .sidebar-toggle's pill belongs to the drawer's rows. */
.nav-submenu-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tap-target-min);
  min-height: var(--tap-target-min);
  padding: 0;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--color-text-dim);
  transition: color var(--duration-quick), border-color var(--duration-quick);
  flex-shrink: 0;
}
/* Hovering the label lights the chevron too, so the item reads as one control.
   Deliberately not the reverse: an `:hover > a` rule would outrank
   .current-menu-item > a and drain the accent off the current item, which
   .nav-links a:hover is careful not to do. */
.nav-links li:hover > .nav-submenu-toggle { color: var(--color-text-heading); }
/* Inset, because the button stretches to the full height of a 56px bar that has
   no room above or below it for a ring. */
.nav-submenu-toggle:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

/* Top level only. Stretching to the full 56px carries the active underline
   across the chevron as well — left centered, the accent rule below would draw
   2px of it under the label and stop. */
.nav-links > li > .nav-submenu-toggle {
  align-self: stretch;
  border-top: 2px solid transparent;
  border-bottom: 2px solid transparent;
}
.nav-links > .current-menu-item > .nav-submenu-toggle,
.nav-links > .current-menu-ancestor > .nav-submenu-toggle {
  color: var(--color-accent);
  border-bottom-color: var(--color-accent);
}

/* The arrow points at where the panel will appear and back at the trigger once
   it is open, so the pair differs by level: down/up at the top, right/left
   inside a panel. Set on the button and read by the span it inherits to. */
.nav-submenu-arrow { transform: rotate(var(--chevron-rest, 0deg)); }
.nav-links .sub-menu .nav-submenu-toggle {
  --chevron-rest: -90deg;
  --chevron-open: 90deg;
}
.nav-links .nav-submenu-toggle[aria-expanded="true"] .nav-submenu-arrow,
html:not(.doku-js) .nav-links li:focus-within > .nav-submenu-toggle .nav-submenu-arrow {
  transform: rotate(var(--chevron-open, 180deg));
}
@media (hover: hover) and (pointer: fine) {
  .nav-links li:hover:not(.no-hover) > .nav-submenu-toggle .nav-submenu-arrow {
    transform: rotate(var(--chevron-open, 180deg));
  }
}

/* ── Mega panel ────────────────────────────────────────────
   The same dropdown, laid out as columns. Nothing marks a branch by hand: PHP
   tags the items of any top-level branch whose children have children (see
   inc/nav-mega.php), so a two-level branch keeps the narrow dropdown above and
   this block never matches it.

   One 8px frame and one `8px 12px` child padding throughout, so every hover
   pill sits 8px from the panel's edges and two columns' rows line up.

   Everything that lands on a row is scoped through `.sub-menu`, because
   `.nav-links .sub-menu a` is (0,2,1) and would otherwise win on specificity
   against a bare class — restoring that rule's 16px padding and its `nowrap` on
   rows that must wrap. */
.nav-links > li.doku-mega > .sub-menu {
  display: grid;
  column-gap: 24px;
  /* Not the column gap: this is the space above the footer rule, and 24px there
     would detach the link from the panel. Columns only share a row-gap when a
     fifth one wraps. */
  row-gap: 8px;
  padding: 8px;
  /* Backstop only — placePanels() caps this to .nav-inner's content box, which
     100vw cannot express because it counts the scrollbar. */
  max-width: calc(100vw - 2 * var(--gutter-bar));
}

/* One track per column, capped at the widest panel that still clears the
   gutters at the 1101px this menu appears at. A fifth column wraps. */
.nav-links .doku-mega-cols-1 > .sub-menu { grid-template-columns: 236px; }
.nav-links .doku-mega-cols-2 > .sub-menu { grid-template-columns: repeat(2, 236px); }
.nav-links .doku-mega-cols-3 > .sub-menu { grid-template-columns: repeat(3, 236px); }
.nav-links .doku-mega-cols-4 > .sub-menu { grid-template-columns: repeat(4, 236px); }

/* A two-level branch told to open as a panel: its rows are grid items rather
   than list items inside a column, so the row-gap that spaces the footer rule
   would space them apart too. There is no footer here — every second-level item
   is a row. Both classes are stated to reach the panel rule's (0,3,1); one
   alone loses to it and the gap stays. */
.nav-links > li.doku-mega.doku-mega-flat > .sub-menu { row-gap: 0; }

/* A column is a grid item — neither a positioned dropdown host nor the flex row
   `li:has(> .nav-submenu-toggle)` builds for a label and its chevron. */
.nav-links .doku-mega > .sub-menu > li {
  position: static;
  display: block;
}

/* The column's own list is in flow. Without this the three reveal states would
   treat it as a flyout and keep it hidden until its heading was hovered.

   `inherit`, never `visible`: visibility is what makes a closed panel
   untouchable, and a visible descendant of a hidden ancestor stays hit-testable
   — so a pinned value leaves every row live and the pointer opens the panel
   from the band below the bar, without ever reaching the menu item. */
.nav-links .doku-mega > .sub-menu > li > .sub-menu {
  position: static;
  opacity: 1;
  visibility: inherit;
  transform: none;
  transition: none;
  min-width: 0;
  width: auto;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

/* .sidebar-label's type ramp, this panel's padding. One rule covers both
   renderings: a <span> when the item points at '#', an <a> when it has a real
   URL of its own. */
.nav-links .sub-menu .doku-mega-col-title {
  display: block;
  height: auto;
  padding: 8px 12px;
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: .1em;
  line-height: 1.4;
  text-transform: uppercase;
  color: var(--color-text-muted);
  border-bottom: none;
  white-space: normal;
}
.nav-links .sub-menu a.doku-mega-col-title:hover,
.nav-links .sub-menu a.doku-mega-col-title:focus-visible {
  color: var(--color-text-heading);
}

/* Two lines in one target. white-space is reset because the panel's width is
   its own: a long title wraps inside its column instead of widening the grid. */
.nav-links .sub-menu .doku-mega-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  height: auto;
  padding: 8px 12px;
  border-radius: var(--radius-md);
  white-space: normal;
  transition: background var(--duration-quick), color var(--duration-quick);
}
.doku-mega-row-title {
  font-size: var(--text-base);
  font-weight: 500;
  line-height: 1.4;
  color: var(--color-text);
}
.doku-mega-row-desc {
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-text-muted);
}

/* --color-mega-row-hover, not --color-accent-hover: see the note beside the two
   of them in the tokens at the top of this file. The title deliberately does
   not go accent either — accent is what marks .current-menu-item in this panel,
   and a hover borrowing it would be indistinguishable from the page the reader
   is already on. */
.nav-links .sub-menu .doku-mega-row:hover,
.nav-links .sub-menu .doku-mega-row:focus-visible {
  background: var(--color-mega-row-hover);
}
.doku-mega-row:hover .doku-mega-row-title,
.doku-mega-row:focus-visible .doku-mega-row-title {
  color: var(--color-text-heading);
}
/* The current-item accent is set on the <a>, and the title sets its own color,
   so it has to be restated here or the row the reader is on is the one row in
   the panel with no marking at all. */
.nav-links .sub-menu .current-menu-item > .doku-mega-row .doku-mega-row-title {
  color: var(--color-accent);
}

/* A second-level item with no children of its own. The placement goes on the
   <li>, because that is the grid item — on the <a> inside it both properties
   are inert, and the rule above the link then draws across one column instead
   of the panel. `order` rather than source position, so the row lands under the
   columns wherever the buyer put it in the menu. */
.nav-links .doku-mega > .sub-menu > .doku-mega-foot-item {
  grid-column: 1 / -1;
  order: 1;
}

/* No pill: this link belongs to the panel rather than to any one column, and a
   pill would read as one more row. */
.nav-links .sub-menu .doku-mega-foot {
  display: flex;
  align-items: center;
  gap: 6px;
  height: auto;
  /* 8px above the rule is the grid's row-gap. 16px below it, because the
     label's own padding meets the panel's 8px frame underneath: an even value
     sits the label twice as far from the panel's foot as from the rule. */
  padding: 16px 12px 8px;
  border-top: 1px solid var(--color-border-subtle);
  border-bottom: none;
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1.4;
  color: var(--color-accent);
  white-space: normal;
}
.nav-links .sub-menu .doku-mega-foot:hover,
.nav-links .sub-menu .doku-mega-foot:focus-visible {
  color: var(--color-accent);
  text-decoration: underline;
}
.doku-mega-foot::after {
  --icon: var(--icon-arrow-right);
  width: 14px;
  height: 14px;
}

/* Stands in for #search-bar's auto margin when the search bar is hidden. */
.nav-spacer { margin-left: auto; }

#search-bar {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--color-search-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 0 12px;
  min-height: var(--tap-target-min);
  width: 220px;
  position: relative;
  transition: border-color var(--duration-quick), width var(--duration-panel);
}
#search-bar:focus-within { border-color: var(--color-accent); width: 260px; }
#search-bar .doku-icon { width: 14px; height: 14px; opacity: .4; }
/* min-height:0 undoes the reset's tap-target floor: the bar already meets
   --tap-target-min itself, so the input's own 32px would stack with the bar's
   borders and put the pill at 34px beside a 32px #theme-toggle.
   align-self:stretch overrides the bar's align-items:center so the input still
   fills it, and no click area is lost. */
#search-input {
  background: none;
  border: none;
  outline: none;
  min-height: 0;
  align-self: stretch;
  font-family: var(--font-sans);
  font-size: var(--text-base);
  color: var(--color-text);
  width: 100%;
  text-overflow: ellipsis;
}
#search-input::placeholder { color: var(--color-text-muted); }
#search-bar .search-kbd {
  display: none;
  font-size: var(--text-2xs);
  padding: 1px 5px;
  line-height: 1.4;
  flex-shrink: 0;
}
/* The label is written at runtime, so without this the kbd chrome paints an empty
   pill before main.js runs — and permanently with JS off. Deliberately (1,2,0), which
   outranks every display rule around it wherever it sits. */
#search-bar .search-kbd:empty { display: none; }
/* Closest standard proxy for a physical keyboard, and unlike a one-shot matchMedia read
   it follows a device that gains or loses one. Must stay at (1,1,0) to match the hide
   above and the 560px rule below, which win on order — adding :not(:empty) here would
   outrank the 560px rule and show the chip on a narrow header row. */
@media (hover: hover) and (pointer: fine) {
  #search-bar .search-kbd { display: inline-block; }
}
/* In the field the shortcut has nothing left to do, and Escape only acts on an open
   panel, where search.js relabels the chip Esc. A hide, so outranking the 560px rule
   is harmless. */
#search-bar:focus-within:not(:has([aria-expanded="true"])) .search-kbd { display: none; }

/* Height pinned, width auto: held at one tap target the slot stays stable
   however tall the open panel gets, while the trigger sizes to its own label,
   capped before a long term name crowds #search-bar.

   Stays scoped to #topnav because the drawer renders the same markup and must
   not pick up flex-shrink from a row it does not sit in — see .sidebar-version,
   which pins the same height and cap for the same reason. */
#topnav .nav-version-wrap {
  max-width: var(--width-version-max);
  height: var(--tap-target-min);
  flex-shrink: 0;
}
/* One box: border, radius and background live on the <details>, so the
   summary/list contact edge is square for free and [open]'s shadow wraps both
   halves. Only works while the list stays in flow inside the border-box — and
   an out-of-flow child sizes nothing, so the wrapper could not shrink-wrap the
   trigger either.

   position: relative is here for the z-index only. Both renderings need the
   open panel to paint over what follows it: .sub-menu and #search-results (200)
   inside #topnav's own stacking context, .sidebar-inner inside the drawer's.

   NO overflow: hidden — it clips summary:focus-visible's outline-offset, and is
   not needed: .nav-version-list's padding keeps a row's hover background off
   the rounded bottom corners. */
.nav-version {
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  position: relative;
  z-index: 190;
}
.nav-version[open] { box-shadow: var(--shadow-panel); }

/* The trigger. Carries no border of its own — that lives on .nav-version — but
   matches #theme-toggle's radius, tap-target floor, font-size and horizontal
   padding so the two read as one control vocabulary side by side.

   That leading padding and font-size are also what .nav-version-list's rows
   must use: this is a select, so the value shown at rest has to sit at the same
   x and the same size as the same value in the open list, or it jumps when the
   panel opens. Three rules, one pair of numbers — see the list below. The
   trailing edge is free of that, and gives its 32px to the chevron instead. */
.nav-version > summary {
  display: flex;
  align-items: center;
  gap: 6px;
  /* Minus the parent's two 1px borders, which this must track. --tap-target-min
     is a floor, not a height, and it is the <details> that has to meet it; left
     at the full 32px the closed control lands at 34px beside a 32px
     #theme-toggle. */
  min-height: calc(var(--tap-target-min) - 2px);
  padding: 4px 0 4px 12px;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  cursor: pointer;
  /* Both are needed to drop the UA's triangle: Firefox honors list-style,
     Chrome needs the pseudo-element below. */
  list-style: none;
}
.nav-version > summary::-webkit-details-marker { display: none; }
.nav-version > summary:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}
/* Shape comes from the DISCLOSURE CHEVRON section. This follows the header
   menu's convention, not the collapsed-section one, and the flip stays bound to
   [open] — the same state that reveals the panel. */
.nav-version[open] > summary::after { transform: rotate(180deg); }
/* margin-left pins it to the trailing edge for whenever the trigger is wider
   than its label: always in the drawer, and in the header once a term name hits
   the cap.

   The glyph then sits centered in a 32px slot flush with that edge, which is
   exactly where a .sidebar-toggle would put it — the drawer stacks the version
   mirror above the docs tree, so the two chevrons share a column. The mask is
   centered in the box, so the extra width is padding; the negative margin pulls
   the slot over .nav-version's border, which .sidebar-toggle does not have. */
.nav-version > summary::after {
  margin-left: auto;
  margin-right: -1px;
  width: var(--tap-target-min);
}

/* The only thing that may shrink, so a long version name ellipsizes in the
   trigger rather than widening it. Still readable in full in the list below. */
.nav-version-current {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* In flow inside the <details>, which is what lets the parent's box grow to
   contain it. Surface comes from .nav-version; only the divider is local. */
.nav-version-list {
  margin: 0;
  padding: 8px 0;
  list-style: none;
  border-top: 1px solid var(--color-border-subtle);
  /* Not a no-op: keeps the list out of the parent's INTRINSIC width while still
     filling it at layout time. Both renderings shrink-wrap their label, so
     without this the list's max-content width joins in the moment it opens and
     the control resizes as you use it. Only the summary may drive the box. */
  width: 0;
  min-width: 100%;
}
/* Horizontal padding and font-size must equal the summary's above — that is what
   makes this read as a select rather than a card under a chip: the current
   version stays put when the panel opens instead of shifting right and growing.
   Vertical padding is free to differ, and does; it is what gives the rows their
   height. Left-aligned for the same reason: right-aligning would slide the value
   across the box on open. */
.nav-version-list a {
  display: block;
  padding: 8px 12px;
  font-size: var(--text-xs);
  line-height: 1.5;
  color: var(--color-text);
  text-decoration: none;
  /* The panel is exactly the trigger's width, so a long name wraps inside it
     instead of overflowing. */
  white-space: normal;
  text-align: left;
}
.nav-version-list a:hover,
.nav-version-list a:focus-visible {
  background: var(--color-accent-hover);
  color: var(--color-text-heading);
}
/* Functional state, so it gets its own color rather than sharing the hover
   declaration above: --color-accent-hover ships fully transparent until a buyer
   picks a non-default accent, which would erase the marker. */
.nav-version-list [aria-current] { color: var(--color-accent); }

/* ─── DOC PAGE ACTIONS ───────────────────────────────────── */
/* Copy / view as Markdown and the AI hand-off links, on a documentation page.
   Built exactly like .nav-version above: one box, with the border, radius and
   background on the <details>, so the summary/list contact edge is square for
   free and [open]'s shadow wraps both halves. Everything that control's notes
   call load-bearing holds here unchanged — no overflow: hidden, the list in
   flow so the parent's box contains it, and a wrapper whose height is pinned
   so the open panel overflows rather than pushing the article down.

   It parts company in one place. .nav-version shows its value at rest, so its
   widest row is a label it is already displaying, and its list is pinned out
   of the intrinsic width with `width: 0` so that opening it can never resize
   the control. This is a menu — its label names the control and its rows are
   longer than it — so both halves size the box instead: `max-content` on the
   <details>, and a list that keeps only the `min-width: 100%` half of that
   pair. See the ::details-content rule below for what makes the closed control
   that width too. */
.doc-actions-wrap {
  /* Pinned like .nav-version-wrap, and a block rather than a flex container for
     the same reason: a stretched flex item would take the wrapper's height, and
     the panel would then hang outside the border box that is supposed to
     contain it. The control carries its own width, so nothing here has to
     shrink-wrap it.

     No margin: the wrapper is a child of .doc-topline, which owns the row's
     spacing. */
  height: var(--tap-target-min);
}

.doc-actions {
  width: max-content;
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  /* For the z-index alone, which paints the overflowing panel over the article
     that follows it. */
  position: relative;
  z-index: 20;
}
.doc-actions[open] { box-shadow: var(--shadow-panel); }

/* What makes the closed control already as wide as its widest row.

   A closed <details> lays its body out nowhere, so `max-content` on the
   element cannot see the list and the control grew 7px the moment it opened.
   ::details-content is the only lever: the UA hides that body by giving the
   pseudo-element content-visibility: hidden, which skips the subtree for
   layout as well as for paint. Turned back on, the rows are laid out while
   closed — so the width reaches the trigger — and the hiding is done here
   instead. All three declarations are load-bearing: block-size takes the
   height, overflow clips what is left, and visibility is what keeps the rows
   out of the tab order and the accessibility tree. Clipped but visible, four
   focusable rows sit in a zero-height box above the title.

   @supports, because an engine without the pseudo-element must keep the
   native hiding rather than a panel it cannot close; those fall back to the
   7px growth, which is what shipped for a round. */
@supports selector(::details-content) {
  .doc-actions::details-content {
    content-visibility: visible;
    visibility: hidden;
    block-size: 0;
    overflow: hidden;
  }
  .doc-actions[open]::details-content {
    content-visibility: visible;
    visibility: visible;
    block-size: auto;
    overflow: visible;
  }
}

/* Carries no border of its own — that lives on .doc-actions — so its floor is
   the parent's tap target minus those two 1px borders, the same arithmetic
   .nav-version > summary does. Leading padding and font-size are shared with
   the rows below, so a row's label sits at the same x as the trigger's. */
.doc-actions > summary {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: calc(var(--tap-target-min) - 2px);
  padding: 4px 0 4px 12px;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  cursor: pointer;
  /* Both marker killers: Firefox honors list-style, Chrome needs the
     pseudo-element. */
  list-style: none;
}
.doc-actions > summary::-webkit-details-marker { display: none; }
.doc-actions > summary:hover { color: var(--color-text-heading); }
.doc-actions > summary:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* The chevron owns the trailing edge — a --tap-target-min slot with the mask
   centered in it — which is why the summary's trailing padding is zero. The
   negative margin pulls that slot over the parent's border, as .nav-version's
   does. */
.doc-actions > summary::after {
  margin-left: auto;
  margin-right: -1px;
  width: var(--tap-target-min);
}
/* A downward panel, so it rests at 0deg and points back at the trigger when
   open, per the panel-opener rule in the chevron section. */
.doc-actions[open] > summary::after { transform: rotate(180deg); }

.doc-actions-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* In flow inside the <details>, which is what lets the parent's box grow to
   contain it. Surface comes from .doc-actions; only the divider is local.

   min-width: 100% without .nav-version-list's companion `width: 0`, and that
   omission is the whole difference: the list is allowed to drive the parent's
   max-content width, so a row never wraps, while the min-width still fills a
   trigger that happens to be wider. */
.doc-actions-list {
  margin: 0;
  padding: 8px 0;
  list-style: none;
  border-top: 1px solid var(--color-border-subtle);
  min-width: 100%;
}

/* Leading padding and font-size match the summary's, the same way the version
   control's rows match its trigger. */
.doc-action {
  display: block;
  width: 100%;
  padding: 8px 12px;
  font-family: inherit;
  font-size: var(--text-xs);
  line-height: 1.5;
  text-align: left;
  color: var(--color-text);
  text-decoration: none;
  /* Explicit: an unset button background resolves to the UA's ButtonFace,
     which follows the OS and ignores data-theme. */
  background: transparent;
  border: 0;
  cursor: pointer;
  white-space: normal;
}
.doc-action:hover,
.doc-action:focus-visible {
  background: var(--color-accent-hover);
  color: var(--color-text-heading);
}
/* Confirmation is a color change on a row whose whole content is its label —
   the .code-copy pattern, not .heading-anchor's glyph swap. */
.doc-action-copy.copied { color: var(--color-accent); }

#theme-toggle {
  padding: 4px 12px;
  min-height: var(--tap-target-min);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  /* Explicit, not inherited: an unset button background resolves to the UA's
     ButtonFace, which follows the OS and ignores data-theme. */
  background: transparent;
  color: var(--color-text-dim);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  /* :hover moves these two and nothing else. `all` additionally animated the
     padding the 560px block sets, which is a breakpoint, not a state. */
  transition: color var(--duration-quick), border-color var(--duration-quick);
  flex-shrink: 0;
  white-space: nowrap;
  /* Flex so the glyph can be sized independently of the 11px label. */
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
#theme-toggle:hover { border-color: var(--color-accent); color: var(--color-accent); }

/* Sized on its own so the label keeps its 11px: the glyph has to match the
   14px search icon and 16px hamburger, not the text beside it. It names the
   current mode, matching the label beside it, and swaps off data-theme-mode —
   not data-theme, which cannot tell light-because-chosen from
   light-because-the-OS-says-so. The inline <head> script sets that attribute
   before first paint, so the button is never briefly the wrong glyph. */
.theme-toggle-icon {
  --icon: var(--icon-sun);
  width: 16px;
  height: 16px;
}
[data-theme-mode="dark"] .theme-toggle-icon   { --icon: var(--icon-moon); }
[data-theme-mode="system"] .theme-toggle-icon { --icon: var(--icon-monitor); }

/* Floors the label at the longest of the three words — 6ch is exact in the
   button's mono face — so cycling does not resize the header row, and so the
   span occupies its final width before JS fills it. */
.theme-toggle-label { min-width: 6ch; }

/* ─── HAMBURGER BUTTON ───────────────────────────────────── */
#menu-btn {
  display: none;
  align-items: center;
  justify-content: center;
  min-width: var(--tap-target-min);
  min-height: var(--tap-target-min);
  border-radius: var(--radius-md);
  background: none;
  border: 1px solid var(--color-border);
  color: var(--color-text-dim);
  cursor: pointer;
  flex-shrink: 0;
  transition: background var(--duration-quick), color var(--duration-quick);
}
#menu-btn:hover { background: var(--color-accent-hover); color: var(--color-accent); }
#menu-btn .doku-icon { width: 16px; height: 16px; }

/* #menu-btn's counterpart at the bar's trailing edge: it opens #aside as an
   off-canvas panel from the right, as #menu-btn opens the drawer from the left.
   Shown only at the widths where #aside is one (see RESPONSIVE), and only with
   JS, which builds the list. */
.toc-open {
  display: none;
  align-items: center;
  justify-content: center;
  min-width: var(--tap-target-min);
  min-height: var(--tap-target-min);
  border-radius: var(--radius-md);
  background: none;
  border: 1px solid var(--color-border);
  color: var(--color-text-dim);
  cursor: pointer;
  flex-shrink: 0;
  transition: background var(--duration-quick), color var(--duration-quick);
}
.toc-open:hover { background: var(--color-accent-hover); color: var(--color-accent); }
.toc-open .doku-icon { width: 16px; height: 16px; }

/* ─── SIDEBAR ────────────────────────────────────────────── */
/* A flex column, NOT a scroll container: .sidebar-top is a non-shrinking header
   and .sidebar-inner below owns the scroll. Overflow must stay visible — it is
   what lets the version mirror's open panel paint outside .sidebar-top instead
   of growing the row. */
#doku-sidebar {
  background: var(--color-bg-secondary);
  border-right: 1px solid var(--color-border-subtle);
  display: flex;
  flex-direction: column;
  /* drawer by default — repositioned as the desktop rail at >= 1101px */
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: var(--width-sidebar);
  z-index: 150;
  height: 100%;
  transform: translateX(-100%);
  transition: transform var(--duration-panel) cubic-bezier(.4, 0, .2, 1);
}
#doku-sidebar.open {
  transform: translateX(0);
  box-shadow: var(--shadow-drawer);
}

/* A flow item at the left end of .sidebar-top, opposite the version mirror.
   The tap-target floor below lands it at exactly the closed height of
   .nav-version beside it, so the two line up without either being pinned. */
#sidebar-close {
  display: flex !important;
  background: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-text-dim);
  cursor: pointer;
  min-width: var(--tap-target-min);
  min-height: var(--tap-target-min);
  align-items: center;
  justify-content: center;
  line-height: 1;
}
#sidebar-close .doku-icon { width: 16px; height: 16px; }

/* The drawer's scroller, so .sidebar-top stays put while the menus move.
   min-height: 0 is required — a flex item's `auto` minimum refuses to shrink
   below its content, and the box would grow past the sidebar instead.

   No horizontal padding: the gutter belongs to the menus below, so a section
   rule inside one spans the full drawer width the way .sidebar-top's does.
   Putting it back here silently insets every hairline. */
.sidebar-inner {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--color-scrollbar) transparent;
  padding: 24px 0 24px;
}

.sidebar-primary-nav,
.doku-sidebar-nav {
  padding-left: var(--gutter-bar);
  padding-right: var(--gutter-bar);
}

/* A heading over its own group of links, built two ways: doku_sidebar_fallback()'s
   per-category grouping when no docs menu is assigned, and a top-level '#' item
   in one that is. The negative margin takes the li out to the menu's border box
   so its rule reaches the drawer edges like .sidebar-top's and
   .sidebar-primary-nav's; the matching padding puts the label and its group back
   on the menu's column. The two are one pair and move together. 12px around that
   rule matches the one between the two menus.

   The display override needs the descendant selector to outrank
   `.doku-sidebar-nav li`'s flex row: this li holds a label above a list, not a
   link beside its toggle, and as a flex container it lays the two side by
   side. */
.sidebar-section {
  margin-left: calc(-1 * var(--gutter-bar));
  margin-right: calc(-1 * var(--gutter-bar));
  padding: 0 var(--gutter-bar) 12px;
}
.doku-sidebar-nav .sidebar-section { display: block; }
/* Any preceding row, not just another section: an assigned menu may mix plain
   top-level links with headings, and a section opening under one still has to
   announce itself. */
.doku-sidebar-nav > li + .sidebar-section {
  padding-top: 12px;
  border-top: 1px solid var(--color-border-subtle);
}

/* A grandchild of .doku-sidebar-nav, so its `> ul` reset misses it and the UA's
   40px list indent survives. */
.sidebar-group {
  list-style: none;
  margin: 0;
  padding: 0;
}

.sidebar-label {
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--color-text-dim);
  padding: var(--nav-row-pad) 8px;
  display: block;
}

/* wp_nav_menu outputs <ul> as the root. No `padding` shorthand here or in
   .sidebar-primary-nav: both would land after the shared gutter rule above and
   silently flatten it. */
.doku-sidebar-nav {
  list-style: none;
  margin: 0;
}
.doku-sidebar-nav ul { list-style: none; }
.doku-sidebar-nav > ul { margin: 0; padding: 0; }
/* Both drawer menus are built by Doku_Nav_Walker, so both need the row layout:
   the link and its toggle button share line one, and .sidebar-sub wraps onto
   line two via flex-basis: 100%. */
.doku-sidebar-nav li,
.sidebar-primary-nav li { position: relative; display: flex; flex-wrap: wrap; align-items: stretch; }

.sidebar-link {
  flex: 1;
  min-width: 0;
  /* A floor, never a height: --nav-row-pad already fills it, so a wrapped row
     grows instead of spilling its second line into the row below. */
  min-height: var(--tap-target-min);
  display: flex;
  align-items: center;
  padding: var(--nav-row-pad) 8px;
  line-height: var(--nav-row-leading);
  /* Doc titles carry identifiers with no break opportunity. `anywhere` rather
     than `break-word`: the label is an anonymous flex item, and only `anywhere`
     lowers its min-content width, so `break-word` leaves the row overflowing. */
  overflow-wrap: anywhere;
  border-radius: var(--radius-md);
  font-size: var(--text-base);
  font-weight: 400;
  color: var(--color-text-muted);
  text-decoration: none;
  transition: background var(--duration-quick), color var(--duration-quick);
  cursor: pointer;
}
a.sidebar-link:hover {
  background: var(--color-accent-hover);
  color: var(--color-text-heading);
}
/* A menu item pointing at '#' — a column heading in the header's mega panel,
   and here the label of a branch whose chevron is the control. It keeps its
   row's color and geometry, because it is not disabled — the three hover rules
   for these rows are scoped to `a.sidebar-link`, so a <span> simply never picks
   one up. */
.sidebar-link-static { cursor: default; }
/* Color and weight, no fill: a row's background belongs to hover alone, so the
   pointer never lands on something that already looks pressed. aria-current
   marks the row for assistive tech either way. */
.sidebar-link.active,
.sidebar-link.current-menu-item {
  color: var(--color-sidebar-active-text);
  font-weight: 600;
}

.sidebar-sub {
  flex-basis: 100%;
  list-style: none;
  padding: 0;
  margin: 0 0 0 16px;
  border-left: 1px solid var(--color-border-subtle);
  display: block;
  overflow: hidden;
  /* Open by default, collapsed only under .doku-js. Collapsing in plain CSS
     would make every child of both drawer menus unreachable without JS — the
     primary menu especially, since below 1101px the drawer is the only place it
     exists. */
  height: max-content;
  interpolate-size: allow-keywords;
  transition: height var(--duration-panel) cubic-bezier(.4, 0, .2, 1);
}
/* .doku-js is set on <html> by the inline script in header.php, before first
   paint. It cannot move to main.js: the menus would paint expanded and collapse
   only when the deferred script runs, which looks like the menu opening itself
   on every load. */
.doku-js .sidebar-sub { height: 0; }
.doku-js .sidebar-sub.open { height: max-content; }

/* Applied while main.js opens the current page's branch, so it appears
   already-open instead of animating open on every page load. Removed on the
   second frame, once that state has painted. */
#doku-sidebar.no-transition .sidebar-sub,
#doku-sidebar.no-transition .sidebar-arrow { transition: none; }

/* ─── PRIMARY NAV MIRROR (drawer only) ───────────────────── */
/* The header's primary menu is hidden below 1101px, so it is repeated inside
   the drawer to stay reachable. Hidden by default and revealed only in the
   drawer breakpoint, so it never duplicates the visible header menu. */
.sidebar-primary { display: none; }

/* The gutter comes from the rule shared with the docs tree, so the border-bottom
   spans the full drawer width like .sidebar-top's. The 12px above and below it
   match .sidebar-inner's padding-top under that hairline. */
.sidebar-primary-nav {
  list-style: none;
  margin: 0 0 12px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--color-border-subtle);
}
.sidebar-primary-nav li { list-style: none; }

/* Structure comes from the rules shared with the docs tree — the walker emits
   the same markup for both. Only what makes this read as its own section lives
   here. Scoped to `> li >` so it never competes with .sidebar-sub
   .sidebar-link, which has identical specificity and would win on source order;
   level two takes the shared muted treatment. */
.sidebar-primary-nav > li > .sidebar-link {
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--color-text);
}
.sidebar-primary-nav > li > a.sidebar-link:hover { color: var(--color-accent); }
/* The mirror marks its current item with the accent rather than the docs tree's
   own active color, matching the header menu it repeats. */
.sidebar-primary-nav > li > .sidebar-link.active { color: var(--color-accent); }

/* ─── DRAWER TOP ROW (drawer only) ───────────────────────── */
/* Close button at the left end, version mirror at the right. Hidden by default
   and revealed only at the drawer breakpoint, like the mirrors above — left
   switched on, its height and border survive on desktop as a 56px band and a
   stray rule above the docs tree.

   flex-shrink: 0 keeps it out of the scroll: it is the drawer's fixed header,
   and .sidebar-inner scrolls beneath the hairline.

   height matches #topnav's, so the drawer's hairline lands on the header's and
   the two bars read as one. It is a fixed height rather than padding around the
   content: the version mirror's open panel then overflows the row without
   moving anything, and nothing has to be kept in step with the admin bar.

   --gutter-bar is what puts the × exactly where the ☰ that opened the drawer
   was; the same token gives the two menus below their gutter, so #sidebar-close
   and every menu label start on one column, and .nav-version's trailing edge
   lands on every .sidebar-toggle's. */
.sidebar-top {
  display: none;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--gutter-bar);
  flex-shrink: 0;
  border-bottom: 1px solid var(--color-border-subtle);
  height: 56px;
}

/* Same story as .sidebar-primary: the header hides its version dropdown below
   1101px, so the drawer carries a copy. Hidden by default so the two never
   appear at once. */
.sidebar-version { display: none; }
/* Capped and shrink-to-fit like #topnav .nav-version-wrap, so a long term name
   ellipsizes via .nav-version-current rather than shoving the close button.
   min-width: 0 is what allows that ellipsis inside a flex item.

   height pins the slot for the same reason the header pins its own: the in-flow
   panel overflows a fixed-height wrapper, so opening the switcher never reflows
   the docs tree below it. It works here only because #doku-sidebar is not a
   scroll container.

   .nav-version itself takes NO width override: letting the trigger size to its
   own label is what keeps .nav-version-list's `width: 0; min-width: 100%` in
   play. The panel stays in flow so the <details> border-box still contains it —
   one box rather than a chip with a card under it. */
.sidebar-version .nav-version-wrap {
  max-width: var(--width-version-max);
  min-width: 0;
  height: var(--tap-target-min);
}
/* The drawer's own height is the only thing bounding the panel now, and nothing
   clips it, so cap and scroll rather than spilling past the drawer's foot. */
.sidebar-version .nav-version-list {
  max-height: 50vh;
  overflow-y: auto;
}

/* No padding of its own: .sidebar-sub's margin-left is the whole indent, so
   every level steps by the same 16px and a third level cannot compound a
   margin and a padding into a wider step than the second. Matches .toc-sub. */
.sidebar-sub .sidebar-link {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.sidebar-sub a.sidebar-link:hover {
  background: var(--color-accent-hover);
  color: var(--color-text-heading);
}
.sidebar-sub .sidebar-link.active {
  color: var(--color-accent);
  font-weight: 600;
}

.sidebar-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Pinned to a 32px box at the top of the row, which is the vertical center of
     line one. Stretching would drift the chevron down a wrapped label. */
  align-self: flex-start;
  min-width: var(--tap-target-min);
  min-height: var(--tap-target-min);
  padding: 0;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--color-text-muted);
  border-radius: var(--radius-md);
  transition: background var(--duration-quick), color var(--duration-quick);
  flex-shrink: 0;
}
.sidebar-toggle:hover { background: var(--color-accent-hover); color: var(--color-text-heading); }

.sidebar-arrow {
  color: inherit;
  transform: rotate(-90deg);
}
.sidebar-toggle[aria-expanded="true"] .sidebar-arrow { transform: rotate(0deg); }

/* ─── MOBILE SIDEBAR OVERLAY ─────────────────────────────── */
#sidebar-overlay {
  position: fixed;
  inset: 0;
  z-index: 149;
  background: rgba(0, 0, 0, .55);
  backdrop-filter: blur(2px);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-panel);
}
#sidebar-overlay.visible {
  opacity: 1;
  pointer-events: all;
}

/* ─── CONTENT WRAP ───────────────────────────────────────── */
#content-wrap {
  display: grid;
  grid-template-columns: 1fr var(--width-rail);
  min-width: 0;
}

#content {
  padding: 40px 56px 80px;
  min-width: 0;
  max-width: var(--width-article);
  scroll-behavior: smooth;
}

/* ─── BREADCRUMBS ────────────────────────────────────────── */
/* On a documentation page the trail shares its line with the Page actions
   menu, both emitted by doku_doc_topline(). The 24px that .breadcrumb carries
   on every other template moves onto the row, so the two halves cannot
   disagree about it.

   The controls are right-aligned with margin-left: auto rather than with
   justify-content, because the trail is not always there and is not always
   ours: doku_breadcrumbs() hands off to Yoast or Rank Math when either is
   active, and Rank Math prints markup of its own with a class this theme has
   never seen. An auto margin pushes the group to the trailing edge whatever
   precedes it, including nothing.

   The row never wraps: when space runs out the trail wraps inside its own box
   and the controls keep their place and size on the first line. Nothing here
   may clip — the open panel overflows the row. */
.doc-topline {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  margin-bottom: 24px;
}
/* Everything that is not the controls: the trail, whichever plugin drew it.
   The padding centers its first line on the 32px controls; 1lh keeps that true
   for a plugin's trail at its own line height. */
.doc-topline > :not(.doc-topline-tools) {
  flex: 1 1 0;
  min-width: 0;
  margin-bottom: 0;
  padding-block: calc((var(--tap-target-min) - 1lh) / 2);
}
/* A crumb longer than the space left breaks rather than pushing the row wide. */
.breadcrumb li {
  min-width: 0;
  overflow-wrap: anywhere;
}
/* The Page actions menu's trailing group, placed by one auto margin. */
.doc-topline > .doc-topline-tools {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  gap: 8px;
  margin-left: auto;
}

.breadcrumb {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-sm);
  color: var(--color-breadcrumb);
  margin-bottom: 24px;
  list-style: none;
  flex-wrap: wrap;
}
.breadcrumb li + li::before {
  --icon: var(--icon-chevron-right);
  margin-right: 6px;
  opacity: .5;
}
.breadcrumb a { color: var(--color-breadcrumb); text-decoration: none; }
.breadcrumb a:hover { color: var(--color-accent); }

/* ─── HEADINGS ───────────────────────────────────────────── */
.doc-h1,
.entry-content h1 {
  font-family: var(--font-mono);
  font-size: var(--text-h1);
  font-weight: 700;
  line-height: 1.2;
  color: var(--color-text-heading);
  letter-spacing: -.02em;
  margin-bottom: 16px;
  text-wrap: balance;
  /* A title is often one long identifier; without this it widens the page. */
  overflow-wrap: anywhere;
}
/* An H1 inside the article opens a section at least as loudly as an H2. */
.entry-content h1 { margin-top: 56px; }
.doc-h2,
.entry-content h2,
.doc-h3,
.entry-content h3,
.doc-h4,
.entry-content h4 { position: relative; overflow-wrap: anywhere; }

.heading-anchor {
  position: absolute;
  top: 0;
  bottom: 0;
  left: -1.1rem;
  font-size: 1rem;
  font-weight: normal;
  color: var(--color-text-muted);
  text-decoration: none;
  opacity: 0;
  transition: opacity var(--duration-quick);
  display: flex;
  align-items: center;
}
.doc-h2:hover .heading-anchor,
.entry-content h2:hover .heading-anchor,
.doc-h3:hover .heading-anchor,
.entry-content h3:hover .heading-anchor,
.doc-h4:hover .heading-anchor,
.entry-content h4:hover .heading-anchor,
.heading-anchor:hover,
/* Without this the anchor is reachable by keyboard but invisible while focused,
   since it is hover-revealed. */
.heading-anchor:focus-visible { opacity: 1; }

.heading-anchor.copied {
  opacity: 1;
  color: var(--color-accent);
}
/* Applied by main.js for the frame in which .copied comes off. --icon reverts
   instantly while opacity would fade, so without this the link paints at full
   opacity for the length of the fade. */
.heading-anchor.no-transition { transition: none; }
/* Confirmation swaps the glyph alone: the aria-label is what announces the
   action, and rewriting the link's text would fight it. */
.heading-anchor.copied .doku-icon { --icon: var(--icon-check); }

.doc-h2,
.entry-content h2 {
  font-family: var(--font-mono);
  font-size: var(--text-h2);
  font-weight: 600;
  line-height: 1.3;
  color: var(--color-text-heading);
  letter-spacing: -.015em;
  margin: 56px 0 12px;
  text-wrap: balance;
}
.doc-h3,
.entry-content h3 {
  font-family: var(--font-mono);
  font-size: var(--text-h3);
  font-weight: 600;
  color: var(--color-text-heading);
  margin: 32px 0 8px;
  text-wrap: balance;
}
.doc-h4,
.entry-content h4 {
  font-family: var(--font-mono);
  font-size: var(--text-h4);
  font-weight: 600;
  color: var(--color-text-heading);
  margin: 20px 0 8px;
  text-wrap: balance;
}
.entry-content h5,
.entry-content h6 {
  font-family: var(--font-mono);
  font-size: var(--text-h5);
  font-weight: 600;
  color: var(--color-text-heading);
  margin: 20px 0 8px;
  text-wrap: balance;
  overflow-wrap: anywhere;
}
.entry-content h6 {
  font-size: var(--text-h6);
  color: var(--color-text-dim);
}

/* ─── BODY TEXT ──────────────────────────────────────────── */
.doc-p,
.entry-content p {
  font-size: var(--text-body);
  line-height: 1.75;
  color: var(--color-text);
  margin-bottom: 16px;
}
.doc-p a,
.entry-content p a {
  color: var(--color-link);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.doc-p a:hover,
.entry-content p a:hover { opacity: .8; }
/* A step is an <li> that can hold a whole code block, so the list and cell
   selectors leave a <pre>'s code alone; they outrank .shiki-wrap code. */
.doc-p code,
.entry-content p code,
.entry-content li code:not(pre code),
.entry-content td code:not(pre code) {
  font-family: var(--font-mono);
  font-size: .82em;
  background: var(--color-accent-hover);
  color: var(--color-accent);
  padding: 1px 5px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-note-border);
}

.entry-content ul,
.entry-content ol {
  padding-left: 24px;
  margin-bottom: 16px;
}
.entry-content li {
  font-size: var(--text-body);
  line-height: 1.75;
  color: var(--color-text);
  margin-bottom: 4px;
}
/* The 4px separates items; after the last it would leak out of the list and
   past the edge of a container the list ends. */
.entry-content li:last-child { margin-bottom: 0; }

/* A block with its own text color, and a Cover, whose text core colors by the
   image's brightness, pass that color down by inheritance. The ink restated on
   every paragraph, heading and list item above outranks core's (0,0,0)
   inherit, and printed dark text on a dark Cover. */
.entry-content :is(.has-text-color, .wp-block-cover) :is(p, h1, h2, h3, h4, h5, h6, li) {
  color: inherit;
}

/* ─── ARTICLE FLOW ───────────────────────────────────────── */
/* Vertical space between two blocks in the article, decided by the pair:
     - before a heading, its top margin;
     - after a heading, its bottom margin, whatever follows;
     - between two text blocks (paragraph, list), 16px;
     - between any other pair, --space-block, and 32px around a rule.
   Margins collapse, so each block carries its own on both sides and the larger
   wins: text has 16px below and none above, every other block --space-block
   both ways. A block styled elsewhere in this file sets that pair itself; this
   (0,0,0) rule gives it to every block nothing names (Image, Cover, File,
   Audio, Video, Embed, Media & Text, Details…). */
:where(.entry-content, .entry-content .wp-block-group, .entry-content .wp-block-group__inner-container, .entry-content .wp-block-column)
  > :where(:not(p, ul, ol, h1, h2, h3, h4, h5, h6, hr, .wp-block-spacer)) {
  margin-block: var(--space-block);
}

/* A heading binds to what it introduces: a box's --space-block would otherwise
   beat its 8–12px and hold a code block further off than a paragraph. A second
   heading keeps its own top margin, since it opens a section of its own. */
:is(.entry-content, .entry-content :is(.wp-block-group, .wp-block-column, .wp-block-media-text__content, .wp-block-cover__inner-container))
  > :is(h1, h2, h3, h4, h5, h6) + :not(h1, h2, h3, h4, h5, h6) { margin-top: 0; }

/* A container's padding, or its edge, frames what it holds, so the first
   child's top margin and the last one's bottom margin would unbalance it. The
   article is one too: its title sits the same distance above whatever block
   opens it, and what follows the article the same distance below whatever
   ends it. */
:is(.entry-content, .entry-content :is(.wp-block-group, .wp-block-column, .wp-block-media-text__content, .wp-block-cover__inner-container)) > :first-child { margin-top: 0; }
:is(.entry-content, .entry-content :is(.wp-block-group, .wp-block-column, .wp-block-media-text__content, .wp-block-cover__inner-container)) > :last-child { margin-bottom: 0; }
/* KaTeX gives its display 1em above and below, which would collapse through
   the math block's wrapper and replace the block's own spacing. */
.entry-content .math-block .katex-display { margin-block: 0; }
/* Row, Stack, Grid and Columns space their children with gap alone; a
   paragraph's margin would add to it. */
.entry-content :is(.is-layout-flex, .is-layout-grid) > * { margin-block: 0; }

/* Deliberately not --space-block: a rule is a section break, and it is supposed
   to read louder than the gap between two ordinary blocks. */
.entry-content hr {
  border: none;
  border-top: 1px solid var(--color-border-subtle);
  margin: 32px 0;
}

/* ─── CLASSIC CONTENT ────────────────────────────────────── */
/* Markup from the classic editor, shortcodes and imported posts. Blocks get
   their media and alignment rules from core's block library; this markup gets
   none, and html5 gallery support also withholds core's gallery CSS. */
.entry-content img,
.entry-content video,
.entry-content iframe,
.entry-content embed,
.entry-content object { max-width: 100%; }
.entry-content img,
.entry-content video { height: auto; }
/* An embed whose provider no longer answers prints its bare URL. */
.entry-content .wp-block-embed__wrapper { overflow-wrap: anywhere; }

/* Floats stay inside the article instead of wrapping the comments. */
.entry-content::after {
  content: '';
  display: table;
  clear: both;
}
.entry-content .alignleft {
  float: left;
  margin: 4px 24px 16px 0;
}
.entry-content .alignright {
  float: right;
  margin: 4px 0 16px 24px;
}
.entry-content .aligncenter {
  display: block;
  margin-left: auto;
  margin-right: auto;
}

/* core writes the image's width inline on the figure. */
.entry-content .wp-caption {
  max-width: 100%;
  margin-bottom: var(--space-block);
}
.entry-content .wp-caption img { display: block; }
.wp-caption-text,
.gallery-caption {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-text-muted);
  margin-top: 8px;
}

.gallery {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
  margin: var(--space-block) 0;
}
.gallery-columns-1 { grid-template-columns: minmax(0, 1fr); }
.gallery-columns-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.gallery-columns-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.gallery-columns-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.gallery-columns-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.gallery-columns-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); }
.gallery-columns-8 { grid-template-columns: repeat(8, minmax(0, 1fr)); }
.gallery-columns-9 { grid-template-columns: repeat(9, minmax(0, 1fr)); }
.gallery-item { margin: 0; }
.gallery-icon img {
  display: block;
  border-radius: var(--radius-md);
}

/* ─── BADGES ─────────────────────────────────────────────── */
.badge-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 24px;
  flex-wrap: wrap;
}
.badge {
  font-size: var(--text-xs);
  font-weight: 500;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  background: var(--color-tag-bg);
  color: var(--color-tag-text);
  letter-spacing: .03em;
}
.badge.stable {
  background: var(--color-note);
  color: var(--color-accent);
  border: 1px solid var(--color-note-border);
}
.badge.since {
  background: var(--color-bg-aside);
  color: var(--color-text-dim);
  border: 1px solid var(--color-border-subtle);
}

/* ─── DOC TAGS ───────────────────────────────────────────── */
/* After the article, before the rating. The chips are the tag cloud's (see
   the widget blocks), so a tag looks the same wherever it is listed. */
.doc-tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 32px;
}
.doc-tags-label {
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--color-text-dim);
}
.doc-tags-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}
/* A flex item will not shrink below its content, which would hold a long
   chip at full width and keep its ellipsis from showing. */
.doc-tags-list > li { min-width: 0; max-width: 100%; }
.doc-tag { display: block; text-decoration: none; }

/* ─── CODE BLOCKS ────────────────────────────────────────── */
.code-block {
  margin: var(--space-block) 0;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--color-border);
  font-family: var(--font-mono);
}

pre {
  background: var(--color-bg-code) !important;
  padding: 20px;
  overflow-x: auto;
  font-family: var(--font-mono);
  font-size: var(--text-code);
  line-height: 1.7;
  scrollbar-width: thin;
  color: var(--color-text-code);
}

code {
  font-family: var(--font-mono);
}

.code-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  background: var(--color-bg-code-header);
  border-bottom: 1px solid var(--color-border);
}
.code-lang {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  letter-spacing: .06em;
  text-transform: uppercase;
}
.code-filename {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-text-dim);
}
.code-copy {
  margin-left: auto;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: var(--radius-md);
  transition: color var(--duration-quick), background var(--duration-quick);
  white-space: nowrap;
}
.code-copy:hover { background: var(--color-accent-hover); color: var(--color-accent); }
.code-copy.copied { color: var(--color-accent); }

.code-block.collapsible pre { max-height: 280px; overflow-y: hidden; }
.code-block.collapsible.expanded pre { max-height: none; }
.code-expand {
  display: none;
  width: 100%;
  padding: 8px;
  text-align: center;
  background: var(--color-bg-code-header);
  border: none;
  border-top: 1px solid var(--color-border);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  cursor: pointer;
  transition: color var(--duration-quick), background var(--duration-quick);
}
.code-block.collapsible .code-expand { display: block; }
.code-expand:hover { color: var(--color-accent); background: var(--color-accent-hover); }

/* ─── SHIKI HIGHLIGHTED OUTPUT ───────────────────────────── */
.shiki-wrap {
  overflow-x: auto;
}
.shiki-wrap pre {
  margin: 0;
  padding: 20px;
  background: transparent !important;
  font-family: var(--font-mono);
  font-size: var(--text-code);
  line-height: 1.7;
}
.shiki-wrap code {
  background: none;
  padding: 0;
  font-size: inherit;
}

/* github-light's keyword red is 4.2:1 on --color-bg, which a code block shows
   through, and 4.0:1 on --color-bg-secondary inside a quote; GitHub's current
   keyword red is 4.7:1 on the darker. Stored highlights keep the original, so
   it is remapped here, as dark.css remaps github-dark's comment color. */
:root:not([data-theme="dark"]) .shiki span[style*="color:#D73A49" i] {
  color: #cf222e !important;
}

.shiki-wrap.wrap { overflow-x: hidden; }
.shiki-wrap.wrap pre,
.shiki-wrap.wrap code { white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere; }
.code-block pre.wrap { white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere; }

.shiki-wrap.line-numbers pre {
  padding-left: 3.5rem;
}
.shiki-wrap.line-numbers code {
  counter-reset: line;
}
.shiki-wrap.line-numbers .line::before {
  counter-increment: line;
  content: counter(line);
  display: inline-block;
  width: 1.5rem;
  margin-right: 1.5rem;
  margin-left: -3rem;
  text-align: right;
  color: var(--color-text-muted);
  opacity: 0.45;
  user-select: none;
  font-variant-numeric: tabular-nums;
}

.line-add {
  display: block;
  margin: 0 -20px;
  padding: 0 20px;
  background: var(--color-diff-add-bg);
  border-left: 3px solid var(--color-diff-add);
}
.line-del {
  display: block;
  margin: 0 -20px;
  padding: 0 20px;
  background: var(--color-diff-del-bg);
  border-left: 3px solid var(--color-diff-del);
}
.line-add .diff-sym { color: var(--color-diff-add); user-select: none; }
.line-del .diff-sym { color: var(--color-diff-del); user-select: none; }
.diff-sym { display: inline-block; width: 14px; user-select: none; opacity: .7; }

/* ─── TABBED CODE ────────────────────────────────────────── */
.doku-tabbed-code { margin: var(--space-block) 0; }
/* The tab bar above supplies the top edge, and its bottom corners must stay in
   step with the radii here. */
.doku-tabbed-code .code-block { margin: 0; border-radius: 0 0 var(--radius-lg) var(--radius-lg); border-top: none; }
/* With a tab bar in place the panel header only repeats the sample's name.
   Scoped to .has-tabs, which tabs.js sets, so a reader without JS still gets
   the per-panel headers and their copy buttons. */
.doku-tabbed-code.has-tabs .code-header { display: none; }

.doku-tab-bar {
  display: flex;
  align-items: stretch;
  gap: 8px;
  padding: 0 8px;
  background: var(--color-bg-code-header);
  border: 1px solid var(--color-border);
  border-bottom: none;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.doku-tab-list {
  display: flex;
  align-items: stretch;
  gap: 0;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.doku-tab-list::-webkit-scrollbar { display: none; }

.doku-tab {
  display: flex;
  align-items: center;
  padding: 0 12px;
  /* Explicit: a <button> defaults to the UA's ButtonFace, which follows the OS
     and ignores data-theme. */
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  white-space: nowrap;
  transition: color var(--duration-quick), border-color var(--duration-quick), background var(--duration-quick);
  user-select: none;
  min-height: 38px;
}
.doku-tab:hover { color: var(--color-text); background: var(--color-accent-hover); }
.doku-tab.active {
  color: var(--color-text-heading);
  border-bottom-color: var(--color-accent);
}
/* Sits outside the tablist so the tabs can scroll under it without carrying it
   off-screen; .code-copy already supplies the styling and the margin-left auto. */
.doku-tab-copy { align-self: center; flex-shrink: 0; }

/* ─── TABS ───────────────────────────────────────────────── */
/* Shares the tab bar above. Without JS the panels stack, each under its own
   label, the same way Tabbed Code keeps its per-panel headers. They sit 8px
   apart, not --space-block, so the stack reads as one set. */
.doku-tabs { margin: var(--space-block) 0; }
.doku-tab-panel {
  padding: 16px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}
.doku-tab-panel + .doku-tab-panel { margin-top: 8px; }
.doku-tab-panel-label {
  margin: -16px -16px 16px;
  padding: 8px 16px;
  background: var(--color-bg-code-header);
  border-bottom: 1px solid var(--color-border);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-text-heading);
}
.doku-tab-panel > :first-child:not(.doku-tab-panel-label),
.doku-tab-panel > .doku-tab-panel-label + * { margin-top: 0; }
.doku-tab-panel > :last-child { margin-bottom: 0; }
.doku-tabs.has-tabs > .doku-tab-panel {
  margin-top: 0;
  border-top: none;
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
.doku-tabs.has-tabs > .doku-tab-panel > .doku-tab-panel-label { display: none; }

/* ─── CALLOUTS ───────────────────────────────────────────── */
.callout {
  padding: 16px;
  border-radius: var(--radius-md);
  border-left: 3px solid;
  margin: var(--space-block) 0;
  font-size: var(--text-md);
}
.callout.note {
  background: var(--color-note);
  border-color: var(--color-note-border);
  color: var(--color-text);
}
.callout.tip {
  background: var(--color-tip);
  border-color: var(--color-tip-border);
  color: var(--color-text);
}
.callout.warning {
  background: var(--color-warning);
  border-color: var(--color-warning-border);
  color: var(--color-text);
}
.callout.danger {
  background: var(--color-danger);
  border-color: var(--color-danger-border);
  color: var(--color-text);
}
.callout.info {
  background: var(--color-info);
  border-color: var(--color-info-border);
  color: var(--color-text);
}
.callout-title {
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .07em;
  margin-bottom: 8px;
  display: block;
  color: var(--color-text-muted);
}
.callout.note .callout-title    { color: var(--color-accent); }
.callout.tip .callout-title     { color: var(--color-tip-text); }
.callout.warning .callout-title { color: var(--color-warning-text); }
.callout.danger .callout-title  { color: var(--color-danger-text); }
.callout.info .callout-title    { color: var(--color-info-text); }

/* ─── BLOCKQUOTE ─────────────────────────────────────────── */
.entry-content blockquote {
  padding: 16px;
  border-radius: var(--radius-md);
  border-left: 3px solid var(--color-note-border);
  margin: var(--space-block) 0;
  background: var(--color-bg-secondary);
  color: var(--color-text-muted);
  font-size: var(--text-body);
}
.entry-content blockquote p { margin: 0; }
.entry-content blockquote p + p { margin-top: 12px; }

/* ─── API PARAMETER TABLE ────────────────────────────────── */
.param-table {
  width: 100%;
  border-collapse: collapse;
  margin: var(--space-block) 0;
  font-size: var(--text-md);
}
.param-table th {
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--color-text-muted);
  padding: 8px 12px;
  text-align: left;
  border-bottom: 2px solid var(--color-border);
}
.param-table td {
  padding: 12px;
  border-bottom: 1px solid var(--color-border-subtle);
  vertical-align: top;
}
.param-table tr:last-child td { border-bottom: none; }
.param-name {
  font-family: var(--font-mono);
  font-size: var(--text-base);
  color: var(--color-accent);
}
.param-type {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-param-type);
}
.param-table tbody tr:hover td { background: var(--color-accent-hover); }
/* block.json sets className:false, so save() emits a bare <div>: the
   wrapper is only reachable through the table it holds. */
.entry-content div:has(> .param-table) { overflow-x: auto; }

/* ─── STEP LIST ──────────────────────────────────────────── */
.steps { list-style: none; counter-reset: steps; }
/* ol.steps, to outrank .entry-content ol's 16px list margin. */
ol.steps { margin: var(--space-block) 0; }
/* A step holds arbitrary blocks, so the badge is positioned absolutely and the
   content indented by its 32px plus a 20px gap; flex would lay a step's
   heading, text and code side by side instead of stacking them. */
.steps > li {
  counter-increment: steps;
  position: relative;
  padding: 0 0 24px 52px;
  margin: 0;
}
.steps > li:not(:last-child)::after {
  content: '';
  position: absolute;
  left: 15px;
  /* The badge is 32px tall, so the line starts 2px behind its bottom edge —
     the badge is opaque and sits on z-index 1, which hides the overlap and
     avoids a hairline seam where the line meets the circle. */
  top: 30px;
  bottom: 0;
  width: 2px;
  background: var(--color-border);
}
.steps > li::before {
  content: counter(steps);
  position: absolute;
  left: 0;
  top: 0;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--color-bg-aside);
  border: 2px solid var(--color-border);
  color: var(--color-accent);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 28px;
  text-align: center;
  z-index: 1;
}
.steps > li > :first-child { margin-top: 0; }
.steps > li > :last-child { margin-bottom: 0; }

/* ─── ACCORDION (core/details) ───────────────────────────── */
/* Spaced like any other block, so a run of them sits --space-block apart. */
.wp-block-details {
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  margin: var(--space-block) 0;
  padding: 0 16px;
  overflow: hidden;
}
/* The body has no wrapper, so the <details> carries its inset and the summary
   bleeds back out across it. */
.wp-block-details[open] { padding-bottom: 16px; }
/* Block, not flex: the summary is rich text, and flex would split a <code> or
   link out of its sentence. The chevron is placed absolutely instead. */
.wp-block-details > summary {
  position: relative;
  margin: 0 -16px;
  padding: 12px 48px 12px 16px;
  min-height: var(--tap-target-min);
  cursor: pointer;
  font-size: var(--text-md);
  font-weight: 500;
  color: var(--color-text-heading);
  background: var(--color-bg-aside);
  list-style: none;
}
.wp-block-details > summary::-webkit-details-marker { display: none; }
.wp-block-details > summary::after {
  position: absolute;
  inset-block: 0;
  right: 16px;
  margin-block: auto;
  color: var(--color-text-muted);
  transform: rotate(-90deg);
}
.wp-block-details[open] > summary {
  margin-bottom: 16px;
  border-bottom: 1px solid var(--color-border-subtle);
}
.wp-block-details[open] > summary::after { transform: rotate(0deg); }

/* Where the body has its own box, the inset and the divider move onto it, so
   they open and close with the body instead of jumping at the start of either.
   Same geometry as above. */
@supports selector(::details-content) {
  .wp-block-details,
  .wp-block-details[open] { padding: 0; }
  .wp-block-details > summary,
  .wp-block-details[open] > summary {
    margin: 0;
    border-bottom: 0;
  }
  .wp-block-details::details-content {
    padding: 0 16px;
    border-top: 0 solid var(--color-border-subtle);
  }
  .wp-block-details[open]::details-content {
    padding-block: 16px;
    border-top-width: 1px;
  }
}
/* The body slides open only where a height can transition to auto (Chromium);
   elsewhere it opens at once. content-visibility is what the UA hides a closed
   body with, and allow-discrete holds it visible until the close has run. */
@supports selector(::details-content) and (interpolate-size: allow-keywords) {
  .wp-block-details { interpolate-size: allow-keywords; }
  .wp-block-details::details-content {
    block-size: 0;
    overflow: clip;
    transition:
      block-size var(--duration-panel) cubic-bezier(.4, 0, .2, 1),
      padding-block var(--duration-panel) cubic-bezier(.4, 0, .2, 1),
      border-top-width var(--duration-panel) cubic-bezier(.4, 0, .2, 1),
      content-visibility var(--duration-panel) allow-discrete;
  }
  .wp-block-details[open]::details-content { block-size: auto; }
}

/* ─── MERMAID DIAGRAM ────────────────────────────────────── */
.doku-mermaid { margin: var(--space-block) 0; }
/* Mermaid renders its SVG inside the <pre>, so the diagram would otherwise sit
   on a code-block background. !important is needed to beat `pre` above, which
   sets its background with !important. */
.doku-mermaid .mermaid {
  background: transparent !important;
  font-family: inherit;
  padding: 0;
}
/* Mermaid sizes its SVG as width="100%" of the <pre>, so the <pre> must not
   shrink to fit its content: the percentage would be cyclic and fall back to
   300px. Centering goes on the SVG instead. */
.doku-mermaid svg {
  display: block;
  max-width: 100%;
  height: auto;
  margin-inline: auto;
}
/* Node boxes are sized from the label width measured at render time and frozen
   into the SVG, so article typography must not reach the labels afterwards or
   the text outgrows its box and clips. The bake step applies the same reset
   (see blocks/render-mermaid/index.js). */
.doku-mermaid svg foreignObject p {
  font-size: inherit;
  line-height: 1.5;
  margin: 0;
}

/* Pre-rendered diagrams carry mermaid's palette baked into the SVG, so these
   rules remap the default chrome to theme tokens for one SVG to read in both
   modes. !important is required: mermaid scopes its own styles by the SVG's id.
   The guards match on the declaration, not the attribute — mermaid stamps an
   empty style="" on every shape, so :not([style]) would exclude nearly
   everything — and they preserve colors a diagram sets via classDef/style. */
.doku-mermaid svg .nodeLabel:not([style*="fill:"]):not([style*="color:"]),
.doku-mermaid svg .edgeLabel:not([style*="fill:"]):not([style*="color:"]),
.doku-mermaid svg .cluster-label text,
.doku-mermaid svg .cluster-label span,
.doku-mermaid svg .messageText:not([style*="fill:"]):not([style*="color:"]),
.doku-mermaid svg .titleText,
/* Sequence-diagram labels put the color on the tspan, not the text element. */
.doku-mermaid svg .loopText, .doku-mermaid svg .loopText > tspan,
.doku-mermaid svg .noteText, .doku-mermaid svg .noteText > tspan,
.doku-mermaid svg .labelText, .doku-mermaid svg .labelText > tspan,
.doku-mermaid svg .sectionTitle, .doku-mermaid svg .sectionTitle > tspan,
.doku-mermaid svg .actor > tspan {
  color: var(--color-text) !important;
  fill: var(--color-text) !important;
}
.doku-mermaid svg .node rect:not([style*="fill:"]):not([style*="color:"]),
.doku-mermaid svg .node circle:not([style*="fill:"]):not([style*="color:"]),
.doku-mermaid svg .node ellipse:not([style*="fill:"]):not([style*="color:"]),
.doku-mermaid svg .node polygon:not([style*="fill:"]):not([style*="color:"]),
.doku-mermaid svg .node path:not([style*="fill:"]):not([style*="color:"]),
/* rect. prefixes matter: text.actor is an actor *label*, not the box. */
.doku-mermaid svg rect.actor:not([style*="fill:"]):not([style*="color:"]),
.doku-mermaid svg rect.note:not([style*="fill:"]):not([style*="color:"]) {
  fill: var(--color-bg-secondary) !important;
  stroke: var(--color-border) !important;
}
.doku-mermaid svg .cluster rect:not([style*="fill:"]):not([style*="color:"]) {
  fill: var(--color-bg-code) !important;
  stroke: var(--color-border) !important;
}
.doku-mermaid svg .flowchart-link,
.doku-mermaid svg .edgePath .path,
.doku-mermaid svg .messageLine0,
.doku-mermaid svg .messageLine1,
.doku-mermaid svg .loopLine,
.doku-mermaid svg .actor-line {
  stroke: var(--color-text-muted) !important;
}
.doku-mermaid svg .marker,
.doku-mermaid svg marker path {
  fill: var(--color-text-muted) !important;
  stroke: var(--color-text-muted) !important;
}
/* Edge labels sit on an opaque plate so the link line does not run through the
   text; mermaid paints that plate in its own background color. */
.doku-mermaid svg .edgeLabel .labelBkg,
.doku-mermaid svg span.edgeLabel,
.doku-mermaid svg .edgeLabel p,
.doku-mermaid svg .edgeLabel foreignObject > div {
  background-color: var(--color-bg) !important;
}
.doku-mermaid svg .edgeLabel rect {
  fill: var(--color-bg) !important;
  opacity: 1 !important;
}

/* ─── CARD GRID ──────────────────────────────────────────── */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 12px;
  margin: var(--space-block) 0;
}
.doc-card {
  padding: 20px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-border);
  background: var(--color-bg-aside);
  text-decoration: none;
  transition: border-color var(--duration-quick), background var(--duration-quick);
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
  overflow-wrap: anywhere;
}
/* The block saves its column count as an inline repeat(N, 1fr), which outranks
   the auto-fill above at every width: three cards overflowed a phone. */
@media (max-width: 560px) {
  .card-grid { grid-template-columns: 1fr !important; }
}
a.doc-card:hover { border-color: var(--color-accent); background: var(--color-accent-hover); }
.doc-card-icon {
  width: 36px;
  height: 36px;
  background: var(--color-bg-code);
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-border);
  flex-shrink: 0;
}
.doc-card-icon .doku-icon { width: 18px; height: 18px; color: var(--color-text-muted); }
.doc-card-title {
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--color-text-heading);
}
.doc-card-desc {
  font-size: var(--text-sm);
  color: var(--color-text-dim);
  line-height: 1.5;
}

/* ─── POST LIST ──────────────────────────────────────────── */
/* Blog listings. Distinct from the .doc-card grid above on purpose: a post
   carries a featured image, a date and an author, and reads as an article
   rather than as an entry in a documentation tree. */
.post-list {
  list-style: none;
  margin: 24px 0 0;
  padding: 0;
}
.post-list-item {
  display: flex;
  gap: 20px;
  padding-bottom: 24px;
  margin-bottom: 24px;
  border-bottom: 1px solid var(--color-border-subtle);
}
.post-list-item:last-child {
  padding-bottom: 0;
  margin-bottom: 0;
  border-bottom: 0;
}
.post-list-thumb {
  display: block;
  flex: 0 0 200px;
  /* flex-start so the anchor hugs the image: stretched, its own background
     shows as an empty band under a 16:9 thumbnail beside a taller row. */
  align-self: flex-start;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-bg-code);
}
.post-list-thumb img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
/* min-width: 0 so a long unbroken title shrinks the body instead of the image. */
.post-list-body {
  flex: 1 1 auto;
  min-width: 0;
}
.post-list-title {
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.35;
  color: var(--color-text-heading);
  margin: 0 0 8px;
  overflow-wrap: anywhere;
}
.post-list-title a { color: inherit; text-decoration: none; }
.post-list-title a:hover { color: var(--color-accent); }
/* The category badge, and on a featured row its label. */
.post-list-kicker {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}
.post-list-sticky {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

/* The blog index's sticky posts (index.php), then its category chips: each
   opens on a rule with 24px either side, the gap the rows below keep around
   theirs, so the page reads the same with no featured post, one or several.
   The list margin collapses into the first row's. */
.post-list-featured .post-list-item {
  flex-direction: column;
  gap: 16px;
  margin: 24px 0 0;
  padding: 24px 0 0;
  border-top: 1px solid var(--color-border-subtle);
  border-bottom: 0;
}
.post-list-featured .post-list-thumb {
  flex: 0 0 auto;
  align-self: stretch;
}
.post-list-featured .post-list-title { font-size: var(--text-h2); }
.post-chips,
.post-list-featured + .post-list {
  margin-top: 24px;
  padding-top: 24px;
  border-top: 1px solid var(--color-border-subtle);
}
.post-chips-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.post-chips-list > li { min-width: 0; max-width: 100%; }
/* The tag chip's outline and type, with room for a filter row: 4px 12px with
   the border lands the box at 25px, clear of WCAG 2.5.8's 24px. */
.post-chip {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 4px 12px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-bg-aside);
  color: var(--color-text-dim);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.4;
  text-decoration: none;
  transition: color var(--duration-quick), border-color var(--duration-quick);
}
.post-chip:hover {
  border-color: var(--color-accent);
  color: var(--color-link);
}
.post-chip.active {
  border-color: var(--color-note-border);
  background: var(--color-note);
  color: var(--color-accent);
}
.post-list .entry-meta { margin-bottom: 8px; }
.post-list-excerpt {
  font-size: var(--text-md);
  line-height: 1.6;
  color: var(--color-text-dim);
  margin: 0;
}
@media (max-width: 560px) {
  .post-list-item {
    flex-direction: column;
    gap: 12px;
  }
  .post-list-thumb {
    flex: 0 0 auto;
    align-self: stretch;
  }
}

/* ─── ASIDE / TOC ────────────────────────────────────────── */
/* Fixed, not sticky: the footer is a sibling of #doku-app, so a sticky rail
   releases at the end of its grid area and the last stretch of scroll drags it
   up out of this slot. Out of flow it occupies no grid cell, which leaves
   #content in the 1fr track it already had, but a fixed rail has to state the
   width the track used to give it. */
#aside {
  /* Horizontal on the token, vertical its own — the same split .sidebar-inner
     takes, and what puts a TOC row's text on the column a sidebar row's lands
     on. --gutter-bar is a horizontal gutter, so the 24px top and bottom are not
     it and must not be folded in. */
  padding: 24px var(--gutter-bar);
  position: fixed;
  top: 56px;
  right: var(--rail-gutter);
  width: var(--width-rail);
  height: calc(100vh - 56px - var(--rail-footer-overlap));
  overflow-y: auto;
  /* Both sides, so the rail is a framed column rather than a panel with one
     edge. The outer one is what the header's trailing control lands on — see
     --width-nav. Border-box, so the pair costs the interior 2px, not the
     track. */
  border-left: 1px solid var(--color-border-subtle);
  border-right: 1px solid var(--color-border-subtle);
  scrollbar-width: thin;
  scrollbar-color: var(--color-scrollbar) transparent;
  background: var(--color-bg-secondary);
}
.toc-label {
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--color-text-dim);
  padding: var(--nav-row-pad) 8px;
  display: block;
}
/* The off-canvas panel's close control, styled as #sidebar-close. Hidden while
   #aside is a rail; see RESPONSIVE. */
.toc-close {
  display: none;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  min-width: var(--tap-target-min);
  min-height: var(--tap-target-min);
  background: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-text-dim);
  cursor: pointer;
}
.toc-close .doku-icon { width: 16px; height: 16px; }
.toc-list { list-style: none; }
.toc-list a,
.browse-link {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  text-decoration: none;
  padding: var(--nav-row-pad) 8px;
  min-height: var(--tap-target-min);
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  border-left: 2px solid transparent;
  transition: color var(--duration-quick), border-color var(--duration-quick);
  line-height: var(--nav-row-leading);
  /* Headings carry identifiers with no break opportunity. See .sidebar-link for
     why this is `anywhere` and not `break-word`. */
  overflow-wrap: anywhere;
}
.toc-list a:hover,
.browse-link:hover { color: var(--color-text-heading); }
.toc-list a.active,
.browse-link.is-current { color: var(--color-accent); border-left-color: var(--color-accent); }
.toc-sub { list-style: none; padding-left: 16px; }
.toc-sub a { font-size: var(--text-xs); }
.toc-sub2 { list-style: none; padding-left: 16px; }
.toc-sub2 a { font-size: var(--text-xs); }

/* A docs listing page has no headings to list, so the column shows the doc
   categories, versions, tags and products instead (sidebar-toc.php). Each
   section is .sidebar-section's shape: full bleed with the gutter paid back,
   and a hairline between two. */
.browse-section {
  margin-left: calc(-1 * var(--gutter-bar));
  margin-right: calc(-1 * var(--gutter-bar));
  padding: 0 var(--gutter-bar) 12px;
}
.browse-section + .browse-section {
  padding-top: 12px;
  border-top: 1px solid var(--color-border-subtle);
}
.browse-section .toc-label { margin: 0; font-family: inherit; line-height: inherit; }
.browse-list { list-style: none; margin: 0; padding: 0; }
.browse-link { gap: 8px; }
.browse-name { min-width: 0; }
.browse-count {
  margin-left: auto;
  color: var(--color-text-dim);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}
/* Tag chips start on the column the labels and row text share. */
.browse-section .doc-tags-list { padding: 0 8px; }
.doc-tag[aria-current] { border-color: var(--color-accent); color: var(--color-link); }
/* The head exists for the off-canvas panel's close button; as a rail each
   section carries its own label. */
@media (min-width: 901px) {
  .aside-browse .toc-head,
  .aside-widgets .toc-head { display: none; }
}
/* ─── PREV / NEXT ────────────────────────────────────────── */
.doc-pagination {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-top: 64px;
  padding-top: 32px;
  border-top: 1px solid var(--color-border-subtle);
}
.pager {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 16px 20px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-border);
  text-decoration: none;
  min-width: 0;
  transition: border-color var(--duration-quick), background var(--duration-quick);
  background: var(--color-bg-aside);
}
.pager:hover { border-color: var(--color-accent); background: var(--color-accent-hover); }
/* Flex rather than an inline glyph in the string: the arrow is an icon, and the
   direction word beside it is what translators get. */
.pager-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-2xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--color-text-muted);
}
.pager-label .doku-icon { width: 12px; height: 12px; }
.pager-title {
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--color-text-heading);
  overflow-wrap: anywhere;
}
/* Column two by name, so a lone Next keeps the half it occupies on every
   other page instead of sliding under Previous. */
.pager.next { grid-column: 2; text-align: right; }
.pager.next .pager-label { justify-content: flex-end; }

/* ─── INLINE KBD ─────────────────────────────────────────── */
kbd {
  font-family: var(--font-mono);
  font-size: .78em;
  background: var(--color-kbd-bg);
  color: var(--color-text-dim);
  border: 1px solid var(--color-kbd-border);
  border-radius: var(--radius-sm);
  padding: 1px 5px;
  box-shadow: 0 1px 0 var(--color-kbd-border);
}

/* The UA's yellow on black ignores dark mode. Core's Highlight format writes its
   own inline colors and keeps them. */
mark:not(.has-inline-color) {
  background: var(--color-mark-bg);
  color: inherit;
}

/* ─── FUNCTION SIGNATURE ─────────────────────────────────── */
.fn-sig {
  padding: 16px;
  background: var(--color-bg-aside);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  margin: var(--space-block) 0;
  font-family: var(--font-mono);
  font-size: var(--text-base);
  color: var(--color-text);
  line-height: 1.6;
}

/* ─── SEARCH DROPDOWN ────────────────────────────────────── */
#search-results {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  width: 360px;
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-overlay);
  max-height: 400px;
  overflow-y: auto;
  z-index: 200;
}
#search-results.open { display: block; }
.doku-search-result {
  display: block;
  padding: 12px 16px;
  border-bottom: 1px solid var(--color-border-subtle);
  text-decoration: none;
  transition: background var(--duration-quick);
}
.doku-search-result:last-child { border-bottom: none; }
/* Hover and the keyboard cursor are one state, so they paint identically and
   initSearchPointerNav() keeps only one row marked at a time. Neither may use
   --color-accent-hover, which ships fully transparent: the cursor is the only
   thing saying which result Enter opens. The inset bar keeps it legible whatever
   the accent resolves to, without the 3px text shift a real border-left causes. */
.doku-search-result:hover,
.doku-search-result.selected {
  background: var(--color-sidebar-active);
  box-shadow: inset 3px 0 0 var(--color-accent);
}
.doku-search-result-title {
  font-size: var(--text-md);
  font-weight: 500;
  color: var(--color-text-heading);
  margin-bottom: 4px;
}
.doku-search-result-section {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin-bottom: 4px;
}
.doku-search-result-excerpt {
  font-size: var(--text-sm);
  color: var(--color-text-dim);
  line-height: 1.5;
}
.doku-search-empty {
  padding: 32px;
  text-align: center;
  font-size: var(--text-md);
  color: var(--color-text-muted);
}
/* Chunk-progress line, shown while the index is still arriving so a partial
   result set does not read as a complete one. --color-text-muted is the
   AA-pinned token; at 12px on this background it must not become --color-text-dim. */
.doku-search-status {
  padding: 8px 16px;
  border-top: 1px solid var(--color-border-subtle);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.doku-search-status::before {
  --icon: var(--icon-loader);
  margin-right: 6px;
  animation: doku-search-spin 1.4s linear infinite;
}
@keyframes doku-search-spin {
  to { transform: rotate(360deg); }
}
/* A chunk failed: the index is usable but incomplete, and says so. Static
   marker, not the spinner — nothing is still in flight. */
.doku-search-status.is-incomplete::before {
  --icon: var(--icon-alert);
  animation: none;
  color: var(--color-warning-text);
}

/* ─── HELPFULNESS WIDGET ─────────────────────────────────── */
.doku-helpfulness {
  margin-top: 48px;
  padding-top: 24px;
  border-top: 1px solid var(--color-border-subtle);
  text-align: center;
}
.doku-helpfulness p {
  font-size: var(--text-base);
  color: var(--color-text-muted);
  margin-bottom: 12px;
}
.doku-helpfulness-votes { display: flex; gap: 8px; justify-content: center; }
.doku-helpfulness-vote {
  background: none;
  border: none;
  /* Not --color-border: an idle button is the control's only visible state and
     has to clear 3:1 as a UI component (WCAG 1.4.11), which border gray does not.
     The glyph inherits it through currentColor. */
  color: var(--color-vote-idle);
  cursor: pointer;
  padding: 0;
  line-height: 1;
  min-width: var(--tap-target-min);
  min-height: var(--tap-target-min);
  display: flex;
  align-items: center;
  justify-content: center;
}
.doku-helpfulness-vote .doku-icon {
  width: 24px;
  height: 24px;
  transition: background-color var(--duration-quick);
}
@media (hover: hover) and (pointer: fine) {
  .doku-helpfulness-vote:hover:not(:disabled) { color: var(--color-accent); }
}
.doku-helpfulness-vote.selected { color: var(--color-accent); }
/* The reader's vote is solid, so it still reads as chosen with color removed. */
.doku-helpfulness-vote.selected .doku-icon--thumbs-up   { --icon: var(--icon-thumbs-up-filled); }
.doku-helpfulness-vote.selected .doku-icon--thumbs-down { --icon: var(--icon-thumbs-down-filled); }
.doku-helpfulness-vote:disabled { cursor: default; }
.doku-helpfulness-result {
  font-size: var(--text-base);
  color: var(--color-text-muted);
  margin-top: 8px;
}

/* ─── RESPONSIVE ─────────────────────────────────────────── */
/* A --gutter-bar between each rail and the window, as .nav-inner keeps one
   around the header's contents: the rails' tracks end where --rail-gutter
   places the fixed rails, so the two agree on a window narrower than the frame
   too. Both sides from 901px, where #aside becomes a rail, so the frame stays
   centered while #doku-sidebar is still the drawer; the left gutter then meets
   #menu-btn's. Every template keeps this frame, with or without its columns,
   so the header lines up on every page. */
@media (min-width: 901px) {
  #doku-app {
    max-width: calc(var(--width-content) + 2 * var(--gutter-bar));
    padding-inline: var(--gutter-bar);
  }
}
/* No left track yet, so #content's track runs wider than the article: fill it,
   or the cap leaves a gap before #aside. */
@media (min-width: 901px) and (max-width: 1100px) {
  #doku-app:not(.doku-app-no-right) #content { max-width: none; }
}
@media (min-width: 1101px) {
  #doku-app {
    grid-template-columns: var(--width-sidebar) 1fr;
  }
  /* The sidebar is out of flow here and so occupies no grid cell — without this
     #content-wrap auto-places into the first column and the whole content area
     is laid out inside the sidebar's track. Scoped away from a frame with no
     left column, which is one track and would gain an implicit second one. */
  #doku-app:not(.doku-app-no-left) > #content-wrap { grid-column: 2; }
  #doku-app.doku-app-no-left { grid-template-columns: 1fr; }
  /* Fixed rather than sticky, for the reason given on #aside. The drawer's own
     left, bottom and transform still cascade in here, so each is restated. */
  #doku-sidebar {
    position: fixed !important;
    top: 56px !important;
    bottom: auto !important;
    left: var(--rail-gutter) !important;
    height: calc(100vh - 56px - var(--rail-footer-overlap)) !important;
    transform: none !important;
    transition: none !important;
    /* Both sides at rail widths only. As the drawer it slides in from x=0, so a
       left border there would ride the screen edge; here it is the line the
       header's logo starts on. */
    border-left: 1px solid var(--color-border-subtle) !important;
    border-right: 1px solid var(--color-border-subtle) !important;
    box-shadow: none !important;
    width: var(--width-sidebar) !important;
    /* Must stay below #topnav (100), and separate from the drawer's 150.
       Outranking the bar paints the rail over header dropdowns wherever the two
       overlap, since their 190 only orders them inside #topnav's own stacking
       context. */
    z-index: 1;
  }
  #sidebar-close { display: none !important; }
  #sidebar-overlay { pointer-events: none !important; opacity: 0 !important; }

  /* A template with no left column still renders the drawer, so the hamburger
     has something to open on mobile. Here it drops out: the grid reserves no
     track for it, and a fixed rail would cover the content's left edge. */
  #doku-app.doku-app-no-left > #doku-sidebar { display: none !important; }
}

@media (max-width: 1100px) {
  #menu-btn { display: flex; }
  .nav-links { display: none; }
  /* Scoped to the header: the drawer mirrors this same .nav-version-wrap markup
     via doku_version_dropdown(), and an unscoped rule would hide the mirror at
     exactly the widths it exists to serve. */
  #topnav .nav-version-wrap { display: none; }
  #search-bar { width: 170px; }
  /* Both header controls are hidden above, so surface each in the drawer. */
  .sidebar-primary { display: block; }
  .sidebar-top { display: flex; }
  .sidebar-version { display: block; }

  /* The first row's clearance under .sidebar-top's hairline, matching the 12px
     .sidebar-primary-nav gives its own. Drawer-only: above 1101px there is no
     hairline and #doku-sidebar's own padding-top spaces the column. */
  .sidebar-inner { padding-top: 12px; }
}

/* Narrow nav: hamburger + logo + search + theme toggle must fit on one row, or
   the last flex item is pushed off the right edge. The tagline goes first, then
   the site name is allowed to ellipsize. */
@media (max-width: 782px) {
  .logo-tagline { display: none; }
  .nav-logo { margin-right: 0; min-width: 0; }
  .logo-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

@media (max-width: 560px) {
  /* Carries the header bar, the drawer's top row and both drawer menus at once.
     Custom-property resolution is not source-order dependent, so consumers
     earlier in the file pick this up. */
  :root { --gutter-bar: 16px; }
  .nav-inner { gap: 8px; }
  .nav-logo { flex-shrink: 1; }
  /* Flexible rather than a fixed width, so the field absorbs leftover space
     instead of overflowing. min-width makes a long site name ellipsize rather
     than collapsing the field to its icon. */
  #search-bar,
  #search-bar:focus-within { width: auto; min-width: 90px; flex: 1 1 0; }
  #search-bar .search-kbd { display: none; }
  /* The 360px panel is wider than the field's distance from the left edge here,
     and anchored to the field's right edge it hangs off-screen. Handing the
     containing block to .nav-inner spans it gutter to gutter instead, and keeps
     top: 100% meaning "under the bar" with no header height restated. Above this
     width the panel fits with room to spare and stays under its own field. */
  .nav-inner { position: relative; }
  #search-bar { position: static; }
  #search-results { left: var(--gutter-bar); right: var(--gutter-bar); width: auto; }
  .theme-toggle-label { display: none; }
  #theme-toggle { padding: 4px 8px; }
}

/* Below 901px #aside leaves the grid's frame and becomes an off-canvas panel
   from the right, the #doku-sidebar drawer's mirror: same width, layer, overlay
   and easing. visibility, not the transform alone, takes the closed panel's
   links out of the tab order; its transition waits out the slide on close and
   the open state zeroes the delay, so the panel is visible before it moves in.
   The admin-bar offsets for it are in WP ADMIN BAR OFFSET. */
@media (max-width: 900px) {
  #aside {
    top: 0;
    right: 0;
    width: var(--width-sidebar);
    max-width: 100%;
    height: 100%;
    /* .toc-head supplies the top edge; see below. */
    padding-top: 0;
    z-index: 150;
    border-right: 0;
    visibility: hidden;
    transform: translateX(100%);
    transition:
      transform var(--duration-panel) cubic-bezier(.4, 0, .2, 1),
      visibility 0s linear var(--duration-panel);
  }
  #aside.open {
    visibility: visible;
    transform: translateX(0);
    box-shadow: var(--shadow-drawer-end);
    transition-delay: 0s;
  }
  /* .sidebar-top's twin: a row as tall as #topnav with the same --gutter-bar,
     which lands #toc-close on the pixel #toc-open occupies in the bar, as
     #sidebar-close replaces #menu-btn. Sticky because #aside is its own
     scroller; the negative margins run the hairline to both panel edges, and
     the 12px under it is .sidebar-inner's drawer clearance. */
  .toc-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    position: sticky;
    top: 0;
    z-index: 1;
    height: 56px;
    margin: 0 calc(-1 * var(--gutter-bar)) 12px;
    padding: 0 var(--gutter-bar);
    background: var(--color-bg-secondary);
    border-bottom: 1px solid var(--color-border-subtle);
  }
  .toc-close { display: flex; }
  /* :not([hidden]) because main.js hides the button when the list comes out
     empty, and a display value would override the attribute. */
  .doku-js .toc-open:not([hidden]) { display: flex; }

  #content-wrap { grid-template-columns: 1fr; }
  #content { padding: 32px 24px 64px; max-width: 100%; }
}

@media (max-width: 480px) {
  #content { padding: 20px 16px 56px; }
  .doc-h1,
  .entry-content h1 { font-size: 1.5rem; }
  /* Inside #content's 16px padding, so it cannot hang past the screen edge. */
  .heading-anchor { left: -1rem; }
  /* A float beside a 300px image leaves a column a few words wide. */
  .entry-content .alignleft,
  .entry-content .alignright {
    float: none;
    margin: 0 0 16px;
  }
  .gallery-columns-4,
  .gallery-columns-5,
  .gallery-columns-6,
  .gallery-columns-7,
  .gallery-columns-8,
  .gallery-columns-9 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .doc-pagination { grid-template-columns: 1fr; }
  .pager.next { grid-column: auto; text-align: left; }
  .pager.next .pager-label { justify-content: flex-start; }
  pre { font-size: var(--text-sm); }
}



/* ─── FOCUS VISIBLE ──────────────────────────────────────── */
:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}
/* The scrolling boxes main.js makes focusable. Inset, because a code block's
   overflow: hidden would clip a ring drawn outside the pre. */
.entry-content :is(pre, .shiki-wrap, .wp-block-table, div:has(> .param-table)):focus-visible {
  outline-offset: -2px;
}

/* ─── WP ADMIN BAR OFFSET ────────────────────────────────── */
.admin-bar #topnav { top: 32px; }
/* `top` is the ONLY thing here that may react to the admin bar — never padding:
   .sidebar-top's own height spaces the drawer's contents, and any padding
   restated here double-counts the bar height that `top` has absorbed.

   height must be restated alongside top: the base rule sets both bottom: 0 and
   height: 100%, which agree only at top: 0 — height wins and the box hangs
   exactly `top` below the fold. */
.admin-bar #doku-sidebar { top: 32px; height: calc(100% - 32px); }
html:has(body.admin-bar) { scroll-padding-top: 104px; }
@media screen and (min-width: 783px) {
  .admin-bar #aside {
    top: 88px;
    height: calc(100vh - 88px - var(--rail-footer-overlap));
  }
}
/* The off-canvas #aside, which clears the bar the way the drawer does. After the
   rail rule above, which also matches 783–900px. */
@media screen and (max-width: 900px) {
  .admin-bar #aside { top: 32px; height: calc(100% - 32px); }
}
/* !important to beat the layout media query's own !important. */
@media screen and (min-width: 1101px) {
  .admin-bar #doku-sidebar {
    top: 88px !important;
    height: calc(100vh - 88px - var(--rail-footer-overlap)) !important;
  }
}
@media screen and (max-width: 782px) {
  .admin-bar #topnav { top: 46px; }
  /* Taller bar at this width — top and height only, never padding. */
  .admin-bar #doku-sidebar { top: 46px; height: calc(100% - 46px); }
  .admin-bar #aside { top: 46px; height: calc(100% - 46px); }
  html:has(body.admin-bar) { scroll-padding-top: 118px; }
}

/* ─── SCREEN READER TEXT ─────────────────────────────────── */
/* Scoped away from .skip-link, which does its own off-screen positioning
   and must stay visible when focused. */
.screen-reader-text:not(.skip-link) {
  border: 0;
  clip-path: inset(50%);
  height: 1px;
  width: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  position: absolute;
  word-wrap: normal;
}
.screen-reader-text:not(.skip-link):focus {
  clip-path: none;
  height: auto;
  width: auto;
  margin: 0;
  padding: 8px 16px;
  overflow: visible;
  z-index: 9999;
  background: var(--color-bg-secondary);
  color: var(--color-text-heading);
  font-size: var(--text-md);
  border-radius: var(--radius-sm);
}

/* ─── COLUMN VARIANTS ────────────────────────────────────── */
/* doku_app_class() takes away what doku_rail_layout() leaves out; the frame
   above stays. Without a right column #content-wrap is one track. A page fills
   whatever its track holds, so a column switched off widens the article, and
   drops #content's padding on that side so the text meets the frame's edge,
   where the header's own contents end. From 901px, where #doku-app takes its
   gutters; below that the padding is the only gutter. */
#doku-app.doku-app-no-right #content-wrap { grid-template-columns: 1fr; }
#doku-app.doku-app-fill #content { max-width: none; }
@media (min-width: 901px) {
  #doku-app.doku-app-fill.doku-app-no-right #content { padding-right: 0; }
  #doku-app.doku-app-fill.doku-app-no-left #content { padding-left: 0; }
}

/* ─── WIDGET COLUMNS ─────────────────────────────────────── */
/* The Right Column area inside #aside and the Left Column area inside
   #doku-sidebar. The rail around each supplies the box, the background, the
   borders and the vertical padding; #doku-sidebar's menus own their gutter
   rather than .sidebar-inner, so the left area takes it here. */
#widget-sidebar,
#widget-sidebar-left {
  min-width: 0;
  font-size: var(--text-base);
  line-height: 1.5;
  color: var(--color-text-muted);
}
#widget-sidebar-left { padding: 0 var(--gutter-bar) 24px; }

/* The rail's text column is the gutter plus 8px, the 24 + 8 all three rails
   share. Every widget's own content starts there, so a paragraph, a calendar,
   a field and a row of pills line up with the title above them — the nav rows
   below pay this 8px back to put their box on the gutter instead. */
:is(#widget-sidebar, #widget-sidebar-left) .widget { padding: 0 8px; }

/* The hairline spans the rail, the negative gutter and the padding back being
   one pair — the same construction .sidebar-section uses, so all three rails
   draw a rule the same way, with its 12px each side. The horizontal padding
   carries the column's 8px too, this shorthand being the later rule of the
   two. */
:is(#widget-sidebar, #widget-sidebar-left) .widget + .widget {
  margin: 12px calc(-1 * var(--gutter-bar)) 0;
  padding: 12px calc(var(--gutter-bar) + 8px) 0;
  border-top: 1px solid var(--color-border-subtle);
}

/* A widget owns its edges, as an article container does: a block's own
   --space-block would otherwise widen the gap to the hairline or the title by
   whatever the block happens to be. A Widget Group puts its blocks one level
   down. */
:is(#widget-sidebar, #widget-sidebar-left, .footer-widgets) :is(.widget, .wp-widget-group__inner-blocks) > :first-child { margin-top: 0; }
:is(#widget-sidebar, #widget-sidebar-left, .footer-widgets) :is(.widget, .wp-widget-group__inner-blocks) > :last-child { margin-bottom: 0; }

/* The third rendering of the label .sidebar-label and .toc-label share: a
   widget heading is the same kind of thing as "On this page". The rows beneath
   stay --color-text-muted, which keeps the two-tone ramp the same way round in
   this rail as in the other two. A Heading block dropped into the area reads as
   a title for the same reason, and so do the blocks that name themselves
   with markup of their own. */
:is(#widget-sidebar, #widget-sidebar-left) .widget-title,
:is(#widget-sidebar, #widget-sidebar-left) .widget > h1,
:is(#widget-sidebar, #widget-sidebar-left) .widget > h2,
:is(#widget-sidebar, #widget-sidebar-left) .widget > h3,
:is(#widget-sidebar, #widget-sidebar-left) .widget > h4,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-search__label,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-archives__label,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-categories__label,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-calendar caption {
  display: block;
  margin: 0 0 4px;
  padding: var(--nav-row-pad) 0;
  /* A Heading block would keep the article headings' mono face and leading. */
  font-family: inherit;
  line-height: inherit;
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  text-align: left;
  color: var(--color-text-dim);
}

:is(#widget-sidebar, #widget-sidebar-left) ul,
:is(#widget-sidebar, #widget-sidebar-left) ol { list-style: none; margin: 0; padding: 0; }
:is(#widget-sidebar, #widget-sidebar-left) li + li { margin-top: 8px; }

/* A nav-shaped row takes its vertical space from padding alone, the model the
   nav row rhythm section describes and both other rails already follow. An
   8px li + li margin left a 17px row, under the 24px target minimum on a phone,
   and made this the one rail built a different way. The negative margin gives
   back the column's own 8px, so the row's box starts on the gutter while its
   text stays on the column with every other widget's content. */
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-archives a,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-categories a,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-page-list a,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-latest-posts a,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-rss__item-title a,
:is(#widget-sidebar, #widget-sidebar-left) .widget_archive li > a,
:is(#widget-sidebar, #widget-sidebar-left) .widget_categories li > a,
:is(#widget-sidebar, #widget-sidebar-left) .widget_pages li > a,
:is(#widget-sidebar, #widget-sidebar-left) .widget_recent_entries li > a,
:is(#widget-sidebar, #widget-sidebar-left) .widget_rss li > .rsswidget,
:is(#widget-sidebar, #widget-sidebar-left) .widget_nav_menu a,
:is(#widget-sidebar, #widget-sidebar-left) .widget_meta a,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-loginout a {
  display: block;
  margin-left: -8px;
  margin-right: -8px;
  padding: var(--nav-row-pad) 8px;
  border-radius: var(--radius-md);
}
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-archives li + li,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-categories li + li,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-page-list li + li,
:is(#widget-sidebar, #widget-sidebar-left) .widget_archive li + li,
:is(#widget-sidebar, #widget-sidebar-left) .widget_categories li + li,
:is(#widget-sidebar, #widget-sidebar-left) .widget_pages li + li,
:is(#widget-sidebar, #widget-sidebar-left) .widget_nav_menu li + li,
:is(#widget-sidebar, #widget-sidebar-left) .widget_meta li + li { margin-top: 0; }
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-latest-posts li + li,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-rss li + li { margin-top: 4px; }

/* The footer holds the same navigation-shaped widgets and so has the same 17px
   rows the rail was rebuilt to get rid of — measured on a phone, where the
   footer is the whole of it. Same fix, same rhythm: padding on a display: block
   anchor, and the row margin that used to space them removed, because row
   spacing gets exactly one source. No negative margins here — those pay back a
   column inset that only the rail has. */
.footer-widgets .wp-block-archives a,
.footer-widgets .wp-block-categories a,
.footer-widgets .wp-block-page-list a,
.footer-widgets .wp-block-latest-posts a,
.footer-widgets .wp-block-rss__item-title a,
.footer-widgets .widget_archive li > a,
.footer-widgets .widget_categories li > a,
.footer-widgets .widget_pages li > a,
.footer-widgets .widget_recent_entries li > a,
.footer-widgets .widget_rss li > .rsswidget,
.footer-widgets .widget_nav_menu a,
.footer-widgets .widget_meta a,
.footer-widgets .wp-block-loginout a {
  display: block;
  padding: var(--nav-row-pad) 0;
  border-radius: var(--radius-md);
}
.footer-widgets .wp-block-archives li + li,
.footer-widgets .wp-block-categories li + li,
.footer-widgets .wp-block-page-list li + li,
.footer-widgets .widget_archive li + li,
.footer-widgets .widget_categories li + li,
.footer-widgets .widget_pages li + li,
.footer-widgets .widget_nav_menu li + li,
.footer-widgets .widget_meta li + li { margin-top: 0; }
/* Core prints the post count as a bare text node after the link, so a display:
   block row would drop it onto a line of its own — the rail's fix applies here
   for the same reason. */
.footer-widgets .wp-block-archives-list li > a,
.footer-widgets .wp-block-categories-list li > a,
.footer-widgets .widget_archive li > a,
.footer-widgets .widget_categories li > a {
  display: inline-block;
  overflow-wrap: anywhere;
}

/* Core prints the post count as a bare text node after the link, so a display:
   block row drops it onto a line of its own. An inline-block row keeps it on
   the line and lets the space already in the markup do the spacing; a flex line
   also works but strips that space, and the gap replacing it is a value the
   scale has nothing to say about. The row's negative margin then absorbs its
   own trailing padding, so the count follows one word space after the name. The
   wrap rule is anywhere rather than break-word because only anywhere lowers the
   min-content width a shrink-to-fit box is sized from. */
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-archives-list li > a,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-categories-list li > a,
:is(#widget-sidebar, #widget-sidebar-left) .widget_archive li > a,
:is(#widget-sidebar, #widget-sidebar-left) .widget_categories li > a {
  display: inline-block;
  overflow-wrap: anywhere;
}

:is(#widget-sidebar, #widget-sidebar-left) a { color: var(--color-text-muted); text-decoration: none; }
:is(#widget-sidebar, #widget-sidebar-left) a:hover { color: var(--color-accent); }
/* A link inside prose needs a second signal: the rule above paints it the same
   color as the text around it, which is right for a nav-shaped widget — where
   the whole row is the link — and invisible inside a sentence.

   The tag cloud is excluded because it is a <p> that holds no prose: core's
   render callback is a sprintf into one, so every chip matched this rule and
   wore an underline through its own tint. The chip rule wins on color but
   declares no text-decoration, which is why only half of it showed. */
:is(#widget-sidebar, #widget-sidebar-left) p:not(.wp-block-tag-cloud) a,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-list a,
:is(#widget-sidebar, #widget-sidebar-left) blockquote a,
.footer-widgets p:not(.wp-block-tag-cloud) a,
.footer-widgets .wp-block-list a,
.footer-widgets blockquote a {
  color: var(--color-link);
  text-decoration: underline;
}
/* Blocks take the paragraph gap, not the article's 20px: a widget's spacing is
   the rail's 12px throughout. */
:is(#widget-sidebar, #widget-sidebar-left, .footer-widgets) { --space-block: 12px; }
:is(#widget-sidebar, #widget-sidebar-left) p,
.footer-widgets p { margin: 0 0 12px; }
:is(#widget-sidebar, #widget-sidebar-left) p:last-child,
.footer-widgets p:last-child { margin-bottom: 0; }
:is(#widget-sidebar, #widget-sidebar-left) img,
.footer-widgets img { max-width: 100%; height: auto; }
/* A bare hr only. A Separator block keeps blocks.css's own 32px — a section
   break is meant to be louder, and neither widget area is a reason to
   re-decide that. */
:is(#widget-sidebar, #widget-sidebar-left) hr:not(.wp-block-separator),
.footer-widgets hr:not(.wp-block-separator) {
  border: 0;
  border-top: 1px solid var(--color-border-subtle);
  margin: 16px 0;
}
:is(#widget-sidebar, #widget-sidebar-left) .current-menu-item > a,
:is(#widget-sidebar, #widget-sidebar-left) .current_page_item > a,
.footer-widgets .current-menu-item > a,
.footer-widgets .current_page_item > a {
  color: var(--color-sidebar-active-text);
  font-weight: 600;
}

/* The ul reset above is for the navigation-shaped widgets, which are most of
   them. A List block is content and keeps its markers. */
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-list,
.footer-widgets .wp-block-list { list-style: disc; padding-left: 20px; }
:is(#widget-sidebar, #widget-sidebar-left) ol.wp-block-list,
.footer-widgets ol.wp-block-list { list-style: decimal; }

/* Blocks that bring a width of their own into a narrow column. */
:is(#widget-sidebar, #widget-sidebar-left) figure,
.footer-widgets figure { margin: 0; }
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-table,
.footer-widgets .wp-block-table { overflow-x: auto; }
:is(#widget-sidebar, #widget-sidebar-left) iframe,
:is(#widget-sidebar, #widget-sidebar-left) video,
:is(#widget-sidebar, #widget-sidebar-left) embed,
:is(#widget-sidebar, #widget-sidebar-left) object,
.footer-widgets iframe,
.footer-widgets video,
.footer-widgets embed,
.footer-widgets object { max-width: 100%; }
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-embed__wrapper iframe,
.footer-widgets .wp-block-embed__wrapper iframe { width: 100%; }

/* Rows that wrap need more between them than single-line ones, or a three-line
   post title runs into the next row's first line. These three carry an author,
   a date or an excerpt under the title in every area that styles them, so the
   rule follows them out of the rail rather than staying with it. */
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-latest-posts li + li,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-rss li + li,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-latest-comments li + li,
:is(#widget-sidebar, #widget-sidebar-left) .widget_recent_entries li + li,
:is(#widget-sidebar, #widget-sidebar-left) .widget_rss li + li,
:is(#widget-sidebar, #widget-sidebar-left) .widget_recent_comments li + li,
.footer-widgets .wp-block-latest-posts li + li,
.footer-widgets .wp-block-rss li + li,
.footer-widgets .wp-block-latest-comments li + li,
.footer-widgets .widget_recent_entries li + li,
.footer-widgets .widget_rss li + li,
.footer-widgets .widget_recent_comments li + li,
.entry-content .wp-block-latest-posts li + li,
.entry-content .wp-block-rss li + li,
.entry-content .wp-block-latest-comments li + li { margin-top: 16px; }

/* The ul reset above zeroes padding at every depth, so a submenu has to take
   its indent back — 16px per level, the step both other rails use. */
:is(#widget-sidebar, #widget-sidebar-left) .sub-menu,
:is(#widget-sidebar, #widget-sidebar-left) .children,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-page-list ul,
.footer-widgets .sub-menu,
.footer-widgets .children,
.footer-widgets .wp-block-page-list ul { margin-top: 0; padding-left: 16px; }

/* Core paints the calendar with three literals — #40464d text, #ddd borders and
   a #ddd header band — so it is the one widget that is wrong in dark mode (and
   off the theme's color scheme entirely) before anything is configured, in
   whichever widget area it lands. The grid is drawn with a hairline instead,
   and the weekday row takes each area's own title/label treatment. */
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-calendar,
:is(#widget-sidebar, #widget-sidebar-left) .widget_calendar,
.footer-widgets .wp-block-calendar,
.footer-widgets .widget_calendar,
.entry-content .wp-block-calendar { text-align: center; }
/* The rail's label rule sets display: block, which stops this being a caption
   at all — the table then wraps it in an anonymous cell in the first column,
   which is what made one day column 122px wide against 18px for the other six.
   Only the rail needs the restore: the footer and the article style the caption
   in a rule of its own and never take that display away. */
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-calendar caption { display: table-caption; }
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-calendar table,
:is(#widget-sidebar, #widget-sidebar-left) .widget_calendar table,
.footer-widgets .wp-block-calendar table,
.footer-widgets .widget_calendar table,
.entry-content .wp-block-calendar table {
  width: 100%;
  /* Seven equal day columns, whatever the colspan pad cells ask for. */
  table-layout: fixed;
  border-collapse: collapse;
  color: var(--color-text-muted);
}
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-calendar th,
:is(#widget-sidebar, #widget-sidebar-left) .widget_calendar th,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-calendar td,
:is(#widget-sidebar, #widget-sidebar-left) .widget_calendar td,
.footer-widgets .wp-block-calendar th,
.footer-widgets .widget_calendar th,
.footer-widgets .wp-block-calendar td,
.footer-widgets .widget_calendar td,
.entry-content .wp-block-calendar th,
.entry-content .wp-block-calendar td {
  border: 0;
  padding: 4px 0;
  font-weight: 400;
}
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-calendar th,
:is(#widget-sidebar, #widget-sidebar-left) .widget_calendar th,
.footer-widgets .wp-block-calendar th,
.footer-widgets .widget_calendar th,
.entry-content .wp-block-calendar th {
  padding-bottom: 8px;
  border-bottom: 1px solid var(--color-border-subtle);
  background: none;
  font-weight: 600;
  letter-spacing: .1em;
  color: var(--color-text-dim);
}
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-calendar tbody tr:first-child td,
:is(#widget-sidebar, #widget-sidebar-left) .widget_calendar tbody tr:first-child td,
.footer-widgets .wp-block-calendar tbody tr:first-child td,
.footer-widgets .widget_calendar tbody tr:first-child td,
.entry-content .wp-block-calendar tbody tr:first-child td { padding-top: 8px; }
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-calendar td a,
:is(#widget-sidebar, #widget-sidebar-left) .widget_calendar td a,
.footer-widgets .wp-block-calendar td a,
.footer-widgets .widget_calendar td a,
.entry-content .wp-block-calendar td a {
  display: block;
  padding: 4px 0;
  font-weight: 600;
  color: var(--color-accent);
}
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-calendar #today,
:is(#widget-sidebar, #widget-sidebar-left) .widget_calendar #today,
.footer-widgets .wp-block-calendar #today,
.footer-widgets .widget_calendar #today,
.entry-content .wp-block-calendar #today { font-weight: 600; color: var(--color-text-heading); }
:is(#widget-sidebar, #widget-sidebar-left) .wp-calendar-nav,
.footer-widgets .wp-calendar-nav,
.entry-content .wp-calendar-nav {
  display: flex;
  justify-content: space-between;
  margin-top: 12px;
}
:is(#widget-sidebar, #widget-sidebar-left) .wp-calendar-nav .pad,
.footer-widgets .wp-calendar-nav .pad,
.entry-content .wp-calendar-nav .pad { display: none; }
/* The month links navigate, like the day links above them, so they take the
   same accent rather than each area's own link color — left to that they came
   out muted in the rail, dim in the footer and underlined accent in an
   article, three renderings of one control. Restating color costs them the
   area's shared a:hover, which is why the underline below is here: this rule
   outranks it in all three. Their size stays under 24px deliberately — an
   inline nav is a 2.5.8 exception, and the per-area font-size above is the
   only thing that may differ. */
:is(#widget-sidebar, #widget-sidebar-left) .wp-calendar-nav a,
.footer-widgets .wp-calendar-nav a,
.entry-content .wp-calendar-nav a {
  color: var(--color-accent);
  text-decoration: none;
}
:is(#widget-sidebar, #widget-sidebar-left) .wp-calendar-nav a:hover,
.footer-widgets .wp-calendar-nav a:hover,
.entry-content .wp-calendar-nav a:hover { text-decoration: underline; }

/* The two rails hold a 280px column, so the calendar is sized for one. These
   three rules and their counterparts in the article block below are the whole
   difference between the two renderings — everything above is shared. */
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-calendar table,
:is(#widget-sidebar, #widget-sidebar-left) .widget_calendar table,
.footer-widgets .wp-block-calendar table,
.footer-widgets .widget_calendar table { font-size: var(--text-sm); }
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-calendar th,
:is(#widget-sidebar, #widget-sidebar-left) .widget_calendar th,
.footer-widgets .wp-block-calendar th,
.footer-widgets .widget_calendar th { font-size: var(--text-2xs); }
:is(#widget-sidebar, #widget-sidebar-left) .wp-calendar-nav,
.footer-widgets .wp-calendar-nav { font-size: var(--text-xs); }

/* The same field and submit vocabulary the comment form uses, so a buyer who
   puts a Search block here gets the control they already know from the article.
   The button must state its own background: left unset it resolves to the UA's
   ButtonFace, which tracks the OS and ignores data-theme. Core's nowrap and its
   off-grid 10px margin both go, so the pair can drop to two rows when the rail
   is narrower than the two of them.

   Two markups share these declarations rather than a third copy of them: the
   Search *block*, and the classic `.search-form`, core's markup reproduced by
   searchform.php for search.php and the classic Search widget. Unstyled, it
   renders as raw UA controls — a square gray ButtonFace submit that stays light
   in dark mode, which is the trap above wearing core's markup. The classic selectors
   are deliberately unscoped: the form is not the rail's, and a buyer who drops
   a classic Search widget anywhere should get the same control. */
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-search__inside-wrapper,
.footer-widgets .wp-block-search__inside-wrapper,
.entry-content .wp-block-search__inside-wrapper,
.search-form {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
/* Only the classic form needs a layout of its own — the block ships a flex
   wrapper around its two controls, while this one is a bare <label> around the
   field. The label is a flex container as well as a flex item, so the field
   below is a flex item in both markups and the shared `flex: 1 1 120px` does
   the same work in each; as a plain block it would be inert there. Capped at
   #search-results' own 360px, the theme's measure for a search control, so the
   field does not sprawl the full narrow column under one line of text. */
.search-form { max-width: 360px; }
.search-form label { display: flex; flex: 1 1 120px; min-width: 0; }
/* search.php renders the form under its h1 on every result state, and nothing
   below it carries a top margin of its own — .doc-p sets margin-bottom only, so
   the no-results message butted against the submit. 24px rather than 16 so it
   equals .post-list's own top margin and collapses into it: the results branch
   measures exactly as it did and only the message moves. Scoped to #content's
   child, so a classic Search widget in the rail keeps the .widget rhythm. */
#content > .search-form { margin-bottom: 24px; }
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-search__input,
.footer-widgets .wp-block-search__input,
.entry-content .wp-block-search__input,
.search-form .search-field {
  flex: 1 1 120px;
  min-width: 0;
  padding: 8px 12px;
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  background: var(--color-search-bg);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--text-md);
  line-height: 1.4;
}
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-search__input:focus,
.footer-widgets .wp-block-search__input:focus,
.entry-content .wp-block-search__input:focus,
.search-form .search-field:focus {
  border-color: var(--color-accent);
  outline: none;
}
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-search__button,
.footer-widgets .wp-block-search__button,
.entry-content .wp-block-search__button,
.search-form .search-submit {
  flex: 0 0 auto;
  margin-left: 0;
  min-height: var(--tap-target-min);
  padding: 8px 16px;
  /* Not border: none. The button sits beside the field rather than under it, so
     the two boxes must be built the same way or it lands 2px shorter — the same
     arithmetic that put #search-bar 2px above #theme-toggle. */
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-md);
  background: var(--color-accent);
  color: var(--color-on-accent);
  font-family: var(--font-sans);
  font-size: var(--text-md);
  font-weight: 600;
  line-height: 1.4;
  cursor: pointer;
  transition: opacity var(--duration-quick);
}
/* --text-md sizes a field against an article's 15px text. A widget area sets
   its own 13px, which every row and paragraph there takes, so its fields and
   the button beside them take it too. */
:is(#widget-sidebar, #widget-sidebar-left, .footer-widgets) :is(.wp-block-search__input, .wp-block-search__button, .search-field, .search-submit, select) { font-size: inherit; }
/* One hover for every accent-painted button in the theme: the block button in
   blocks.css, this pair and the comment submit below. */
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-search__button:hover,
.footer-widgets .wp-block-search__button:hover,
.entry-content .wp-block-search__button:hover,
.search-form .search-submit:hover { opacity: .85; }

/* Every term at one size. Core writes the frequency into an inline font-size
   per link, which here spanned 8pt to 22pt — five values off the type scale,
   two of them below --text-2xs. The count is still on each link's aria-label,
   and !important is the only thing that reaches an inline style, which is what
   core's own outline variant does for the same reason. */
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-tag-cloud,
.footer-widgets .wp-block-tag-cloud,
.entry-content .wp-block-tag-cloud {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
}
/* An outlined mono chip, quieter than the accent controls beside it: a tag is
   a label you may follow, not an action. 4px 8px with the border lands the box
   at 25px, clear of WCAG 2.5.8's 24px. One line, cut with an ellipsis where
   the column is narrower than the name; the full name stays the link's
   accessible name. */
:is(#widget-sidebar, #widget-sidebar-left) .tag-cloud-link,
.footer-widgets .tag-cloud-link,
.entry-content .tag-cloud-link,
.doc-tag {
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-right: 0;
  padding: 4px 8px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-bg-aside);
  color: var(--color-text-muted);
  font-family: var(--font-mono);
  font-size: var(--text-xs) !important;
  line-height: 1.4;
  transition: color var(--duration-quick), border-color var(--duration-quick);
}
/* Restated for every area: each one's a:hover would otherwise set the color
   alone and leave the border grey. */
:is(#widget-sidebar, #widget-sidebar-left) .tag-cloud-link:hover,
.footer-widgets .tag-cloud-link:hover,
.entry-content .tag-cloud-link:hover,
.doc-tag:hover {
  border-color: var(--color-accent);
  color: var(--color-link);
}

/* Core's filled brand colors are deliberately left alone — a buyer who reaches
   for Social Icons is asking for them, and the logos-only style is core's own
   way of opting out. Only that variant is retoken-ed. */
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-social-links,
.footer-widgets .wp-block-social-links,
.entry-content .wp-block-social-links { gap: 8px; margin: 0; font-size: 20px; }
/* align-items: center centers each item's MARGIN box, so any row margin the
   surrounding area sets drops every icon by half of it — the rails' li + li
   margin-top, and the article's own margin-bottom on a list item. Both axes,
   rather than the one the rails needed. */
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-social-links li,
.footer-widgets .wp-block-social-links li,
.entry-content .wp-block-social-links li { margin: 0; }
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-social-links .wp-social-link a,
.footer-widgets .wp-block-social-links .wp-social-link a,
.entry-content .wp-block-social-links .wp-social-link a {
  padding: 8px;
  /* The brand color sits on the <li>. The area's own link-color rule would
     otherwise repaint the glyph in that color, on top of the brand fill. */
  color: inherit;
}
:is(#widget-sidebar, #widget-sidebar-left) .is-style-logos-only .wp-social-link,
.footer-widgets .is-style-logos-only .wp-social-link,
.entry-content .is-style-logos-only .wp-social-link { color: var(--color-text-muted); }
:is(#widget-sidebar, #widget-sidebar-left) .is-style-logos-only .wp-social-link:hover,
.footer-widgets .is-style-logos-only .wp-social-link:hover,
.entry-content .is-style-logos-only .wp-social-link:hover { color: var(--color-accent); }

:is(#widget-sidebar, #widget-sidebar-left) .wp-block-latest-comments,
.footer-widgets .wp-block-latest-comments,
.entry-content .wp-block-latest-comments { padding: 0; }
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-latest-comments__comment,
.footer-widgets .wp-block-latest-comments__comment,
.entry-content .wp-block-latest-comments__comment { margin-bottom: 0; }
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-latest-comments__comment-avatar,
.footer-widgets .wp-block-latest-comments__comment-avatar,
.entry-content .wp-block-latest-comments__comment-avatar {
  width: 32px;
  height: 32px;
  margin-right: 12px;
  border-radius: var(--radius-md);
}
:is(#widget-sidebar, #widget-sidebar-left) .has-avatars .wp-block-latest-comments__comment-meta,
:is(#widget-sidebar, #widget-sidebar-left) .has-avatars .wp-block-latest-comments__comment-excerpt,
.footer-widgets .has-avatars .wp-block-latest-comments__comment-meta,
.footer-widgets .has-avatars .wp-block-latest-comments__comment-excerpt,
.entry-content .has-avatars .wp-block-latest-comments__comment-meta,
.entry-content .has-avatars .wp-block-latest-comments__comment-excerpt { margin-left: 44px; }
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-latest-comments__comment-author,
.footer-widgets .wp-block-latest-comments__comment-author,
.entry-content .wp-block-latest-comments__comment-author { font-weight: 600; color: var(--color-text-dim); }
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-latest-comments__comment-date,
.footer-widgets .wp-block-latest-comments__comment-date,
.entry-content .wp-block-latest-comments__comment-date {
  margin-top: 4px;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-latest-comments__comment-excerpt p,
.footer-widgets .wp-block-latest-comments__comment-excerpt p,
.entry-content .wp-block-latest-comments__comment-excerpt p {
  margin: 4px 0 0;
  font-size: var(--text-sm);
}

:is(#widget-sidebar, #widget-sidebar-left) .wp-block-rss,
.footer-widgets .wp-block-rss,
.entry-content .wp-block-rss { padding: 0; }
/* An item title is the stronger half of the pair it heads — its author and date
   below are muted — so it takes dim rather than the row color. That ties with
   the area's own a:hover rule at equal specificity and sits later in the file,
   which silently took the hover away from the one widget whose rows are
   headlines: the shared rule can never reach a row that restates color. Any
   row given a color of its own needs its own hover beside it. */
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-rss__item-title a,
.footer-widgets .wp-block-rss__item-title a,
.entry-content .wp-block-rss__item-title a { font-weight: 500; color: var(--color-text-dim); }
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-rss__item-title a:hover,
.footer-widgets .wp-block-rss__item-title a:hover,
.entry-content .wp-block-rss__item-title a:hover { color: var(--color-accent); }
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-rss__item-author,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-rss__item-publish-date,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-latest-posts__post-author,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-latest-posts__post-date,
.footer-widgets .wp-block-rss__item-author,
.footer-widgets .wp-block-rss__item-publish-date,
.footer-widgets .wp-block-latest-posts__post-author,
.footer-widgets .wp-block-latest-posts__post-date,
.entry-content .wp-block-rss__item-author,
.entry-content .wp-block-rss__item-publish-date,
.entry-content .wp-block-latest-posts__post-author,
.entry-content .wp-block-latest-posts__post-date {
  margin-top: 4px;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
/* Core's .5em / 1em land Latest Posts' excerpt off the 4px grid; the gap to
   the next row belongs to li + li. */
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-rss__item-excerpt,
:is(#widget-sidebar, #widget-sidebar-left) .wp-block-latest-posts__post-excerpt,
.footer-widgets .wp-block-rss__item-excerpt,
.footer-widgets .wp-block-latest-posts__post-excerpt,
.entry-content .wp-block-rss__item-excerpt,
.entry-content .wp-block-latest-posts__post-excerpt {
  margin-top: 4px;
  margin-bottom: 0;
  font-size: var(--text-sm);
}
/* An unreachable feed renders wp-admin's own placeholder markup on the front
   end, where none of its classes are styled at all. */
:is(#widget-sidebar, #widget-sidebar-left) .components-placeholder .notice,
.footer-widgets .components-placeholder .notice,
.entry-content .components-placeholder .notice {
  padding: 12px;
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* Classic widgets share the block rules above wherever their markup matches.
   These are the parts with no block counterpart. */

/* A classic calendar has its widget title above the table, so the month is a
   secondary line rather than a second title. */
:is(#widget-sidebar, #widget-sidebar-left) .widget_calendar caption,
.footer-widgets .widget_calendar caption {
  padding-bottom: 8px;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text-dim);
}
/* Date, author and summary under a row, in the block widgets' own sizes. */
:is(#widget-sidebar, #widget-sidebar-left) .widget_recent_entries .post-date,
:is(#widget-sidebar, #widget-sidebar-left) .widget_rss .rss-date,
:is(#widget-sidebar, #widget-sidebar-left) .widget_rss cite,
.footer-widgets .widget_recent_entries .post-date,
.footer-widgets .widget_rss .rss-date,
.footer-widgets .widget_rss cite {
  display: block;
  margin-top: 4px;
  font-size: var(--text-xs);
  font-style: normal;
  color: var(--color-text-muted);
}
:is(#widget-sidebar, #widget-sidebar-left) .widget_rss .rssSummary,
.footer-widgets .widget_rss .rssSummary {
  margin-top: 4px;
  font-size: var(--text-sm);
}
:is(#widget-sidebar, #widget-sidebar-left) .widget_rss li > .rsswidget,
.footer-widgets .widget_rss li > .rsswidget { font-weight: 500; color: var(--color-text-dim); }
:is(#widget-sidebar, #widget-sidebar-left) .widget_rss li > .rsswidget:hover,
.footer-widgets .widget_rss li > .rsswidget:hover { color: var(--color-accent); }
/* The feed icon is a raster PNG at a size the title's small caps dwarf. */
:is(#widget-sidebar, #widget-sidebar-left) .rss-widget-feed,
.footer-widgets .rss-widget-feed { display: none; }
:is(#widget-sidebar, #widget-sidebar-left) .widget_recent_comments .comment-author-link,
.footer-widgets .widget_recent_comments .comment-author-link { font-weight: 600; color: var(--color-text-dim); }

/* In an article these name a field, so they read as the comment form's labels
   do rather than at core's inherited 16px. */
.entry-content :is(.wp-block-search__label, .wp-block-archives__label, .wp-block-categories__label):not(.screen-reader-text) {
  display: block;
  margin-bottom: 4px;
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--color-text-dim);
}

/* Archives and Categories dropdowns, block and classic: the field vocabulary of
   the Search widget, full width. dark.css gives it the dark color-scheme, which
   is what repaints the native arrow and the open list. */
:is(#widget-sidebar, #widget-sidebar-left) select,
.footer-widgets select,
.entry-content .wp-block-archives-dropdown select,
.entry-content .wp-block-categories-dropdown select {
  width: 100%;
  max-width: 100%;
  padding: 8px 12px;
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  background: var(--color-search-bg);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--text-md);
  line-height: 1.4;
  transition: border-color var(--duration-quick);
}
:is(#widget-sidebar, #widget-sidebar-left) select:focus,
.footer-widgets select:focus,
.entry-content .wp-block-archives-dropdown select:focus,
.entry-content .wp-block-categories-dropdown select:focus {
  border-color: var(--color-accent);
  outline: none;
}
@media (hover: hover) and (pointer: fine) {
  :is(#widget-sidebar, #widget-sidebar-left) select:hover:not(:focus),
  .footer-widgets select:hover:not(:focus),
  .entry-content .wp-block-archives-dropdown select:hover:not(:focus),
  .entry-content .wp-block-categories-dropdown select:hover:not(:focus) { border-color: var(--color-border); }
}
/* The UA's arrow is drawn in the select's own font and ignores the icon set,
   so a select with a wrapper of its own draws the theme's chevron instead, in
   a 32px slot on the trailing edge as .nav-version's. A select cannot hold a
   pseudo-element, so the glyph is the wrapper's ::after, sharing the select's
   grid cell: the visible label, when there is one, takes the row above. The
   classic Archives dropdown has no wrapper and keeps the UA's arrow. */
:is(#widget-sidebar, #widget-sidebar-left, .footer-widgets, .entry-content) :is(.wp-block-archives-dropdown, .wp-block-categories-dropdown, .widget_categories form) {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}
:is(#widget-sidebar, #widget-sidebar-left, .footer-widgets, .entry-content) :is(.wp-block-archives-dropdown, .wp-block-categories-dropdown, .widget_categories form) > select {
  grid-area: 2 / 1;
  appearance: none;
  padding-right: var(--tap-target-min);
  cursor: pointer;
}
:is(#widget-sidebar, #widget-sidebar-left, .footer-widgets, .entry-content) :is(.wp-block-archives-dropdown, .wp-block-categories-dropdown, .widget_categories form)::after {
  --icon: var(--icon-chevron-down);
  grid-area: 2 / 1;
  justify-self: end;
  align-self: center;
  width: var(--tap-target-min);
  height: 15px;
  color: var(--color-text-muted);
  pointer-events: none;
}

/* A widget can hold an unbroken string — pasted text, or the bare file URL
   the Video widget prints when the browser cannot play the format. anywhere,
   not break-word, because only it lowers the min-content width the footer's
   grid tracks are sized from. Core also writes an image caption's width
   inline on its figure. */
:is(#widget-sidebar, #widget-sidebar-left) .widget,
.footer-widgets .widget { overflow-wrap: anywhere; }
:is(#widget-sidebar, #widget-sidebar-left) .wp-caption,
.footer-widgets .wp-caption { max-width: 100%; }
:is(#widget-sidebar, #widget-sidebar-left) .wp-caption img,
.footer-widgets .wp-caption img { display: block; }

/* ─── WIDGET BLOCKS IN AN ARTICLE ────────────────────────── */
/* Every rule above is shared with the two widget areas. What is left here is
   the article's own doing: a widget block dropped into post or page content
   lands in prose styling that a rail does not have, and at a measure a rail
   does not have either. These rules sit after the shared ones so the ties go
   to the more specific of the two contexts. */

/* The rails size the calendar for a 280px column. In a 760px one that reads as
   a thumbnail of a calendar, so the article steps it up to body copy and gives
   the day links a real target rather than a 4px one. */
.entry-content .wp-block-calendar table { font-size: var(--text-body); }
.entry-content .wp-block-calendar th { font-size: var(--text-sm); }
.entry-content .wp-block-calendar td a { padding: 8px 0; }
.entry-content .wp-calendar-nav { font-size: var(--text-base); }
/* Not the rail's uppercase label: in an article the month is a caption over a
   table, and the article already has a voice for that. */
.entry-content .wp-block-calendar caption {
  padding-bottom: 8px;
  font-size: var(--text-body);
  font-weight: 600;
  color: var(--color-text-heading);
}

/* The blanket ul/ol reset the rails use would strip the markers off every list
   in the article, so the navigation-shaped blocks are named instead. Without
   it .entry-content ul hands each one disc markers and a 24px indent, and a
   feed reads as a bulleted list of headlines. */
.entry-content .wp-block-rss,
.entry-content .wp-block-latest-comments,
.entry-content .wp-block-latest-posts__list,
.entry-content .wp-block-social-links,
.entry-content .wp-block-archives-list,
.entry-content .wp-block-categories-list,
.entry-content .wp-block-page-list { list-style: none; padding-left: 0; }
/* Their own nesting still steps, at the 16px both rails use, unmarked and on
   the rows' 4px rhythm rather than a list's 16px margin. */
.entry-content .wp-block-page-list ul,
.entry-content .wp-block-categories-list ul,
.entry-content .wp-block-archives-list ul {
  list-style: none;
  margin: 4px 0 0;
  padding-left: 16px;
}

/* The shared rules zero these margins for a rail, where a widget's spacing is
   the rail's to set. In an article each is a block like any other (see ARTICLE
   FLOW); the lists and the tag cloud's <p> are named because the flow rule
   leaves text elements to their own 16px. */
.entry-content .wp-block-calendar,
.entry-content .wp-block-tag-cloud,
.entry-content .wp-block-search,
.entry-content .wp-block-social-links,
.entry-content .wp-block-rss,
.entry-content .wp-block-latest-comments,
.entry-content .wp-block-latest-posts,
.entry-content .wp-block-archives-list,
.entry-content .wp-block-categories-list,
.entry-content .wp-block-page-list,
.entry-content .wp-block-footnotes { margin-block: var(--space-block); }

/* The tag cloud is a <p>, so .entry-content p a reaches every chip: its
   underline and p a:hover's .8 fade, neither of which the chip rules declare.
   (0,3,0) outranks both. */
.entry-content .tag-cloud-link { text-decoration: none; }
.entry-content .tag-cloud-link:hover { opacity: 1; }

/* This block sits HERE, after the rules it overrides, and not up with the
   other breakpoints. A media query adds no specificity, so a restated selector
   only wins if it also comes later in the file. */
@media (max-width: 1100px) {
  /* Stacked, the footer area is as wide as the page, and the calendar is the
     one widget sized at 100% of it. */
  .footer-widgets .wp-block-calendar table,
  .footer-widgets .widget_calendar table { max-width: 360px; margin: 0 auto; }
}

/* Wraps, with the 8px gap as the row gap: a post's line adds its comment
   count, and a search row a kind label and for a doc the doc_category name,
   which the importer builds by flattening a folder path ("Guides Getting
   Started"). */
.entry-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin-bottom: 24px;
}
/* Chrome, as the breadcrumb above it and a comment's date: muted until
   hovered. Only .entry-footer's sentence ("Posted in …") underlines its links. */
.entry-meta a {
  color: var(--color-text-muted);
  text-decoration: none;
}
.entry-meta a:hover { color: var(--color-accent); }

/* A post's written excerpt, between its title and its meta line: a summary of
   the article, set apart from the prose it introduces. */
.entry-lead {
  font-size: var(--text-h3);
  line-height: 1.6;
  color: var(--color-text-dim);
  margin: 0 0 16px;
  overflow-wrap: anywhere;
}

.entry-thumbnail {
  margin: 0 0 32px;
}
.entry-thumbnail img {
  max-width: 100%;
  height: auto;
  border-radius: var(--radius-md);
}

.entry-footer {
  margin-top: 40px;
  padding-top: 20px;
  border-top: 1px solid var(--color-border-subtle);
  font-size: var(--text-base);
  color: var(--color-text-muted);
}
.entry-footer .entry-terms + .entry-terms { margin-top: 8px; }
/* Underlined, as in the article: beside muted text, dark mode's accent is
   1.9:1 from it, too close for color alone to mark a link. */
.entry-footer a {
  color: var(--color-link);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.entry-footer a:hover { opacity: .8; }

/* A split post's page list is navigation, not content, so it is set off like
   a rule rather than spaced as a block. */
.page-links {
  margin: 32px 0;
  font-size: var(--text-base);
  color: var(--color-text-muted);
}
.page-links a {
  color: var(--color-link);
  padding: 0 4px;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.page-links a:hover { opacity: .8; }

.doc-card-meta {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin-top: 4px;
}

/* ─── POSTS / COMMENTS PAGINATION ────────────────────────── */
/* .nav-links is also the header menu class, so every rule here is scoped. */
.doku-posts-pagination {
  margin: 48px 0 0;
}
.doku-posts-pagination .nav-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  font-size: var(--text-base);
}
.doku-posts-pagination .nav-links .page-numbers {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Spaces out the direction arrow on the prev/next links; the numbered ones
     hold a single child and are unaffected. */
  gap: 6px;
  min-width: var(--tap-target-min);
  height: var(--tap-target-min);
  padding: 0 12px;
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  color: var(--color-text-dim);
  text-decoration: none;
  line-height: 1;
  transition: color var(--duration-quick), border-color var(--duration-quick), background var(--duration-quick);
}
.doku-posts-pagination .nav-links a.page-numbers:hover {
  color: var(--color-accent);
  border-color: var(--color-accent);
  background: var(--color-accent-hover);
}
.doku-posts-pagination .nav-links .page-numbers.current {
  color: var(--color-accent);
  border-color: var(--color-accent);
  background: var(--color-accent-hover);
  font-weight: 600;
}
.doku-posts-pagination .nav-links .page-numbers.dots {
  border-color: transparent;
}

/* ─── COMMENTS ───────────────────────────────────────────── */
.doku-comments {
  margin-top: 64px;
  padding-top: 32px;
  border-top: 1px solid var(--color-border-subtle);
}
.doku-comments .comments-title,
.doku-comments .comment-reply-title {
  margin-top: 0;
}
.doku-comments .comment-list,
.doku-comments .comment-list .children {
  list-style: none;
  margin: 0;
  padding: 0;
}
.doku-comments .comment-list .children {
  margin-left: 24px;
  padding-left: 16px;
  border-left: 1px solid var(--color-border-subtle);
}
.doku-comments .comment-body {
  padding: 20px 0;
  border-bottom: 1px solid var(--color-border-subtle);
  font-size: var(--text-md);
  line-height: 1.7;
  color: var(--color-text);
}
.doku-comments .comment-author {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  color: var(--color-text-heading);
}
.doku-comments .comment-author img {
  border-radius: 50%;
}
/* The post's own author, marked by a ring and the accent on the name. */
.doku-comments .bypostauthor > .comment-body .comment-author { color: var(--color-accent); }
.doku-comments .bypostauthor > .comment-body .comment-author img {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}
.doku-comments .comment-metadata,
.doku-comments .reply {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.doku-comments .comment-metadata a,
.doku-comments .reply a {
  color: var(--color-text-muted);
  text-decoration: none;
}
.doku-comments .comment-metadata a:hover,
.doku-comments .reply a:hover { color: var(--color-accent); }
.doku-comments .comment-awaiting-moderation {
  font-style: italic;
  color: var(--color-text-muted);
}

.doku-comments .comment-form {
  margin-top: 32px;
  display: grid;
  gap: 12px;
}
.doku-comments .comment-form label {
  display: block;
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--color-text-dim);
  margin-bottom: 4px;
}
.doku-comments .comment-form input[type="text"],
.doku-comments .comment-form input[type="email"],
.doku-comments .comment-form input[type="url"],
.doku-comments .comment-form textarea {
  width: 100%;
  padding: 8px 12px;
  background: var(--color-search-bg);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--text-md);
}
.doku-comments .comment-form textarea {
  min-height: 140px;
  resize: vertical;
}
/* The text fields only: the submit and the consent checkbox have no border to
   recolor, so they keep the global focus ring. */
.doku-comments .comment-form input:is([type="text"], [type="email"], [type="url"]):focus,
.doku-comments .comment-form textarea:focus {
  border-color: var(--color-accent);
  outline: none;
}
.doku-comments .comment-form .form-submit input[type="submit"] {
  min-height: var(--tap-target-min);
  padding: 8px 20px;
  background: var(--color-accent);
  color: var(--color-on-accent);
  border: none;
  border-radius: var(--radius-md);
  font-family: var(--font-sans);
  font-size: var(--text-md);
  font-weight: 600;
  cursor: pointer;
  transition: opacity var(--duration-quick);
}
.doku-comments .comment-form .form-submit input[type="submit"]:hover { opacity: .85; }
.doku-comments .comment-form .comment-notes,
.doku-comments .comment-form .form-submit { margin: 0; }

/* ─── FOOTER ─────────────────────────────────────────────── */
/* The footer keeps its full width, so it meets both fixed rails. Each rail
   shrinks to clear it — see --rail-footer-overlap — and this pair is the
   backstop for the two cases where that has not happened yet: the frame between
   a scroll event and the measured height landing, and no JS at all. #colophon's
   own background is what makes it opaque; lifted above the rails without one it
   would let them show through while its text sits on top. #topnav's 100 still
   outranks both, and so does the drawer's 150, which is what keeps an open
   drawer in front of the footer. */
.footer-widgets,
#colophon {
  position: relative;
  z-index: 2;
}

.footer-widgets {
  border-top: 1px solid var(--color-border-subtle);
  background: var(--color-bg-secondary);
  padding: 40px 24px;
}
.footer-widgets-inner {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 32px;
  max-width: var(--width-content);
  margin: 0 auto;
  font-size: var(--text-base);
  color: var(--color-text-dim);
}
/* A column heading, not a rail label — see the widget rail note above for why
   footer-widgets keeps this typography instead of the uppercase small-caps
   treatment the two rails share. A bare Heading block dropped into the area,
   the Search and dropdown blocks' own labels and the calendar's caption all name
   themselves the same way a widget's title does, so they take the same rule. */
.footer-widgets .widget-title,
.footer-widgets .widget > h1,
.footer-widgets .widget > h2,
.footer-widgets .widget > h3,
.footer-widgets .widget > h4,
.footer-widgets .wp-block-search__label,
.footer-widgets .wp-block-archives__label,
.footer-widgets .wp-block-categories__label {
  display: block;
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--color-text-heading);
  margin: 0 0 12px;
}
/* Deliberately not in the selector list above, though it repeats it: that rule
   sets display: block, which stops a caption being one at all, and the restore
   the rail uses would have to come after this rule rather than where it sits.
   Keep the two in step by hand — the caption is a widget's own heading here as
   much as a title is. */
.footer-widgets .wp-block-calendar caption {
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--color-text-heading);
  margin: 0 0 12px;
}
.footer-widgets ul { list-style: none; margin: 0; padding: 0; }
.footer-widgets li + li { margin-top: 8px; }
.footer-widgets a { color: var(--color-text-dim); text-decoration: none; }
.footer-widgets a:hover { color: var(--color-accent); }

/* The copyright line and the Footer Menu share one centered row, the two
   parted by a dot as .entry-meta parts a post's date and author. */
#colophon {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 8px;
  border-top: 1px solid var(--color-border-subtle);
  /* Matches body. Load-bearing, not decoration — see the section comment. */
  background: var(--color-bg);
  padding: 24px;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  text-align: center;
}
#colophon p { margin: 0; }
#colophon a { color: var(--color-text-muted); }
#colophon a:hover { color: var(--color-accent); }
.colophon-nav {
  display: flex;
  align-items: center;
  gap: 8px;
}
/* The empty alternative text keeps the dot out of the accessibility tree, as
   aria-hidden does for .entry-meta-sep; an engine without that syntax keeps
   the first declaration. */
.colophon-nav::before {
  content: "·";
  content: "·" / "";
}
.colophon-menu {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0 16px;
  margin: 0;
  padding: 0;
  list-style: none;
}
/* Stacked rather than wrapped on a phone, where the two rarely fit, so no line
   opens on the dot. */
@media (max-width: 560px) {
  #colophon { flex-direction: column; }
  .colophon-nav::before { display: none; }
}
/* 4px above and below lifts a 12px link's box past WCAG 2.5.8's 24px. */
.colophon-menu a {
  display: block;
  padding: 4px 0;
  text-decoration: none;
}

/* ─── MOTION ────────────────────────────────────── */
/* Everything that moves, switched off in one place.

   What belongs here is **geometry** — a box that travels, resizes or scrolls —
   plus any looping animation. The theme has exactly eight geometric transitions
   and one animation, and all nine are below. Color and background crossfades
   are deliberately NOT here: twenty-odd controls carry one, none of them moves
   anything, and prefers-reduced-motion is about motion rather than about change.
   The overlay is the one opacity fade in the list, because it is one state with
   the drawer's transform and the pair has to arrive together.

   It has to sit LAST in the file: a media query adds no specificity, so these
   rules beat the ones above them on source order alone. Anything declared after
   this block wins and is silently unguarded — which is how the drawer, the
   disclosure and the overlay went three rounds without a guard while three
   smaller blocks scattered through the file suggested the subject was covered.
   To audit it, walk document.styleSheets and check that no selector redeclares
   a transition after its guarded copy; measuring one element cannot see this.

   Keep the scroll rule first: smooth scrolling is the one that moves the whole
   viewport, on every anchor link and every TOC click. */
@media (prefers-reduced-motion: reduce) {
  html,
  #content { scroll-behavior: auto; }

  /* Panels arriving and leaving. */
  #doku-sidebar,
  #aside,
  #sidebar-overlay,
  .sidebar-sub,
  .nav-links .sub-menu,
  .wp-block-details::details-content { transition: none; }

  /* Chevrons, which turn as their panel opens. */
  .wp-block-details > summary::after,
  .nav-version > summary::after,
  .doc-actions > summary::after,
  .sidebar-arrow,
  .nav-submenu-arrow { transition: none; }

  /* The two controls that move rather than recolor: the field widens on focus,
     the skip link drops in from above the viewport. */
  #search-bar,
  .skip-link.screen-reader-text { transition: none; }

  .doku-search-status::before { animation: none; }
}
