/* =====================================================================
   botanical garden — core theme
   Aesthetic: Y2K / Frutiger Aero / Winamp (visual only, no audio).

   ARCHITECTURE NOTE
   -----------------
   The DEFAULT SKIN is the variable block in :root below. Every color,
   gradient, and bevel in the structural CSS references a var(--…) — no
   hard-coded colors past this block. To add a skin later (M5 switcher),
   define another scoped block, e.g.

       [data-skin="dusk"] { --page-bg-top: #2a2150; … }

   and the entire site recolors. Do not bake colors into the structure.
   ===================================================================== */

/* ---------- self-hosted webfont --------------------------------- */
/* Iosevka: open-source (SIL OFL) monospace. Self-hosted woff2. Paths are
   relative to this stylesheet (/theme/css/), so they survive any SITEURL. */
@font-face {
  font-family: "Iosevka";
  src: url("../fonts/iosevka-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Iosevka";
  src: url("../fonts/iosevka-bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* ---------- DEFAULT SKIN: "aero" --------------------------------- */
:root {
  /* page backdrop (sky → aqua) */
  --page-bg-top:    #bfe6ff;
  --page-bg-mid:    #8fd3f0;
  --page-bg-bot:    #cdeecb;

  /* Per-page background image. Default `none` → the gradient above shows.
     base.html sets this inline on <body> to url('/images/backgrounds/<file>')
     when a template/page declares one (see the `bg_image` block). The image
     is a full-viewport, fixed backdrop layered OVER the gradient, so it wins
     when present and the gradient is the graceful fallback when it isn't. */
  --page-bg-image: none;

  /* the application window */
  --window-bg:      #eef4fb;
  --window-border:  #5a83b0;
  --content-bg:     #ffffff;
  --content-ink:    #16263a;
  --selected-bg:    #cfe3f7;   /* playlist selected row (pledit SelectedBG) */
  --muted-ink:      #5d7186;

  /* glossy chrome (title bar / nav / buttons) */
  --chrome-top:     #6db8f4;   /* light shine, upper half  */
  --chrome-mid:     #2f7fd6;   /* the fold                 */
  --chrome-bot:     #1f5fa8;   /* deeper, lower half       */
  --chrome-ink:     #ffffff;

  /* bevels — light catches top/left, shadow falls bottom/right */
  --bevel-light:    rgba(255, 255, 255, 0.85);
  --bevel-dark:     rgba(12, 40, 72, 0.45);

  /* accents */
  --accent:         #36a3e0;
  --accent-glow:    rgba(80, 190, 255, 0.55);
  --link:           #1466b0;
  --link-hover:     #0a3f7a;

  /* shape & type */
  --radius:         9px;
  --radius-sm:      5px;
  --font-ui:        "Iosevka", ui-monospace, "Consolas", "DejaVu Sans Mono", monospace;
  --font-body:      "Iosevka", ui-monospace, "Consolas", "DejaVu Sans Mono", monospace;

  /* fenced code blocks (Pygments output, see "code" section below). A glossy
     deep-navy panel that reads against the aqua chrome; tokens use an aero
     blue/green/amber palette. Inline `code` keeps the light pill look. */
  --code-bg:        #0e2233;   /* panel backdrop          */
  --code-bg-top:    #163149;   /* top sheen for the gloss */
  --code-ink:       #dce8f5;   /* default code text       */
  --code-border:    #0a1825;
  --code-comment:   #7c93a8;
  --code-keyword:   #5fc8ff;
  --code-type:      #8fe6d0;
  --code-string:    #b6e88a;
  --code-number:    #ffcf70;
  --code-func:      #ffe08a;
  --code-op:        #ff8fb3;
}

/* ---------- reset-ish ------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  padding: 2.5rem 1rem 3.5rem;
  min-height: 100%;
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  color: var(--content-ink);
  /* Two layers: the per-page image (top, when --page-bg-image is a url())
     and the skin gradient beneath it as fallback. A `cover`, fixed image
     fully hides the gradient; an unset/transparent image lets it show. */
  background-color: var(--page-bg-bot);
  background-image:
    var(--page-bg-image),
    linear-gradient(
      180deg,
      var(--page-bg-top) 0%,
      var(--page-bg-mid) 55%,
      var(--page-bg-bot) 100%
    );
  background-repeat: no-repeat, no-repeat;
  background-position: center center, center center;
  background-size: cover, auto;
  background-attachment: fixed, fixed;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* ---------- the main window: a 1:1 Winamp clone ----------------- *
   A pixel-exact reproduction of the Winamp main window. main.png is
   the focal point (set as the background, inlined in base.html so the
   active-skin path resolves); every overlay below is absolutely placed
   at its true Winamp coordinate. A skin is REQUIRED — there is no
   non-sprite fallback. Tune --wa-scale to resize the whole window;
   integer factors keep the pixel art crisp.
   ---------------------------------------------------------------- */
:root {
  --wa-scale: 2;   /* whole-window zoom; 1 = native 275x116px */
}

/* the playlist rows use a small sans-serif (Webamp-style); --pl-font is shared. */
:root {
  --pl-font: "Arial", "Helvetica Neue", Helvetica, sans-serif;
  --wa-title-scale: 4;   /* integer zoom of the bitmap title; keep it whole */
}

/* site title in the skin's bitmap lettering, directly on the dark page —
   no frame or backing box. The page background is dark (from the skin
   palette) so the light glyphs read against it. */
.site-title { width: 100%; margin: 0 0 1.5rem; text-align: center; line-height: 0; }
.site-title .skintext { zoom: var(--wa-title-scale); }

.winamp-stage {        /* the page hero: centers the player */
  width: 100%;
  display: flex;
  justify-content: center;
  margin-bottom: 1.5rem;
}

/* main window + playlist scale together and stay docked */
.winamp-stack {
  zoom: var(--wa-scale);   /* whole-player zoom; 1 = native px */
  box-shadow: 0 10px 30px rgba(20, 50, 90, 0.45);
}

.winamp-main {
  position: relative;
  width: 275px;
  height: 116px;
  background-repeat: no-repeat;
  background-size: 275px 116px;
  image-rendering: pixelated;
}

/* every direct child is an overlay pinned to a true Winamp coordinate */
.winamp-main > * { position: absolute; }
.winamp-main a   { line-height: 0; cursor: pointer; text-decoration: none; }
.winamp-main a:focus-visible { outline: 1px solid var(--accent); outline-offset: 1px; }
/* inert transport buttons: clickable + they depress, but do nothing */
.winamp-main button { cursor: pointer; }

/* --- title bar: active sprite strip + window-control buttons --- */
.wa-titlebar { top: 0;   left: 0;    }
.wa-opt      { top: 3px; left: 6px;   }
.wa-min      { top: 3px; left: 244px; }
.wa-shade    { top: 3px; left: 254px; }
.wa-close    { top: 3px; left: 264px; }

/* --- display readouts (player.js) --- *
   Title scrolls in the long thin bar on the right of the display; the time
   digits sit top-left in the big screen box. */
.wa-marquee { top: 25px; left: 115px; width: 145px; height: 9px;
  overflow: hidden; white-space: nowrap; line-height: 0;
  display: flex; align-items: center;   /* vertically center the text in the bar */ }
.wa-marquee-strip { flex: none; }        /* keep full width; may overflow + scroll */
.wa-time { top: 26px; left: 39px; display: flex; align-items: flex-start; }
.wa-d-sec { margin-left: 5px; }   /* gap where the ":" sits on main.bmp */
.wa-marquee-strip { display: inline-block; }
.wa-marquee-strip.wa-scrolling { animation: wa-marquee-scroll linear infinite; }
@keyframes wa-marquee-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-1 * var(--wa-scroll-w, 0px))); }
}

/* --- transport buttons (player controls), cbuttons row y≈88 -------------- */
.wa-prev  { left: 16px;  top: 88px; }
.wa-play  { left: 39px;  top: 88px; }
.wa-pause { left: 62px;  top: 88px; }
.wa-stop  { left: 85px;  top: 88px; }
.wa-next  { left: 108px; top: 88px; }
.wa-eject { left: 136px; top: 89px; }

/* ---------- the equalizer panel (eqmain.png sprites) ------------ *
   A 1:1 clone of the classic Winamp EQ window, docked flush between the
   main window and the playlist panel (same 275x116 native size, same
   --wa-scale zoom via .winamp-stack). ON/AUTO are real (if audio-inert)
   toggle buttons — the skin ships a genuine off/on sprite pair for them
   (see write_sprites_css's SELECTED handling in import_skin.py), so
   clicking persists a visual toggle state via eq.js. Close/presets are
   decorative, matching the main window's own window-chrome buttons (no
   real window management or preset menu exists here either). Nothing in
   this panel processes real audio — it's the classic skin's EQ chrome,
   kept as a fully-slideable visual toy rather than a Web Audio DSP graph.

   Unlike main.png (which IS just the 275x116 window), eqmain.png is the
   whole combined EQMAIN sheet — window + titlebar + buttons + graph +
   sliders all stacked in one bitmap (275x315-ish; varies per skin). No
   background-size here: that would scale the WHOLE sheet down to fit
   275x116, distorting it. Left at its natural size with no-repeat, the
   image is simply clipped to the box, showing exactly the top-left
   EQ_WINDOW_BACKGROUND region — the same crop the sprite table already
   uses for every other piece of this panel.
   ---------------------------------------------------------------- */
.winamp-eq {
  position: relative;
  width: 275px;
  height: 116px;
  background-repeat: no-repeat;
  image-rendering: pixelated;
}
.winamp-eq > * { position: absolute; }
.winamp-eq button { cursor: pointer; border: 0; padding: 0; background-color: transparent; }

.wa-eq-titlebar { top: 0;    left: 0; }
.wa-eq-close    { top: 3px;  left: 264px; }
.wa-eq-on       { top: 18px; left: 14px; }
.wa-eq-auto     { top: 18px; left: 39px; }
.wa-eq-graph    { top: 17px; left: 86px; }
.wa-eq-presets  { top: 18px; left: 217px; }

/* Slider thumbs: 1 preamp + 10 bands, each positioned by `left` inline
   (index.html, computed from the classic 18px band pitch) and dragged
   vertically by eq.js between top:38px (+12dB) and top:90px (-12dB) — 64px
   (this default) is the midpoint, 0dB. Those three numbers are shared with
   eq.js's TOP_MIN/TOP_MAX constants; change one, change the other. */
.wa-eq-thumb { top: 64px; }
.wa-eq-thumb:focus-visible { outline: 1px solid var(--accent); outline-offset: 1px; }

/* ---------- the playlist panel (pledit sprites) ---------------- *
   Docks flush under the main window (275px wide). Assembled the way
   Webamp does it: fixed corner/title sprite pieces with flex-grow fill
   strips that tile the edge-tile PNGs (cropped by import_skin.py) via
   background-repeat — so the frame is seamless and the title stays
   centered at any width. The tile background-image is set inline per
   element in index.html (a relative url() in a CSS var would resolve
   against this stylesheet and 404); here we only set the repeat/size.
   The rows ARE the nav.
   ---------------------------------------------------------------- */
.winamp-playlist { width: 275px; image-rendering: pixelated; }

/* fixed sprite pieces never shrink; fills absorb the slack */
.pl-fixed { flex: none; }

/* --- title bar: corners + centered title + tiled fills --- */
.pl-top { display: flex; height: 20px; background: var(--content-bg); }
.pl-fill { flex: 1 1 0; height: 20px; background-repeat: repeat-x; }

/* The title shows the skin's UNSELECTED graphic by default ("BLAME!" in
   blame-wired) and flips to the SELECTED row ("wired") while the panel is
   hovered — mimicking Winamp's window-focus swap. Only the title piece
   differs between the two states (corners/tiles are identical), and the
   states differ only in Y, so flipping background-position-y is enough and
   stays skin-agnostic. */
.winamp-playlist:hover .pl-title { background-position-y: 0; }

/* --- middle: tiled left/right edges + the (scrollable) row list --- */
.pl-mid { display: flex; }
.pl-edge-left  { flex: none; width: 12px; background-repeat: repeat-y; }
.pl-edge-right { flex: none; width: 20px; background-repeat: repeat-y;
                 background-position: top right; position: relative; }
/* the Winamp scroll handle, parked in the right-edge groove; moved by
   playlist-scroll.js to reflect/drive the row list's scroll position */
.pl-handle { position: absolute; top: 0; left: 6px; cursor: pointer; }
.pl-body {
  flex: 1 1 0;
  list-style: none;
  margin: 0;
  padding: 3px 4px;
  background: var(--content-bg);
  max-height: var(--pl-max-h, 66px);  /* fits ~4 rows; beyond that it scrolls */
  overflow-y: auto;
  scrollbar-width: none;              /* hide native bar; the sprite handle is the UI */
}
.pl-body::-webkit-scrollbar { display: none; }
.pl-body a {
  display: flex;
  justify-content: space-between;
  gap: 1.5em;
  padding: 1px 6px;
  font-family: var(--pl-font);
  font-size: 7px;             /* small Winamp track text; ×--wa-scale via the stack */
  line-height: 12px;
  letter-spacing: 0.5px;
  color: var(--content-ink); /* pledit Normal (track) text color */
  text-decoration: none;
}
/* "N. Artist - Title" left (truncates like Winamp); track length right */
.pl-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pl-time { flex: none; }
/* selected/hovered row uses the pledit SelectedBG + Current colors, like
   selecting a track in Winamp */
.pl-body a:hover,
.pl-body a:focus-visible { background: var(--selected-bg); color: var(--link); outline: none; }

/* --- bottom: two corner pieces meet flush at 275px --- */
.pl-bottom { display: flex; height: 38px; position: relative; }

/* Skin-cycle hit-targets, laid over the "+FILE"/"-FILE" buttons already
   painted into the left corner's artwork (fixed Winamp pledit.png layout —
   see the comment in index.html). Transparent so any skin's corner art
   shows through unmodified; a translucent overlay stands in for a pressed
   state instead of a skin-specific sprite swap. */
.pl-skin-btn {
  position: absolute;
  top: 8px;
  width: 22px;
  height: 18px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}
.pl-skin-prev { left: 14px; }
.pl-skin-next { left: 43px; }
.pl-skin-btn:active { background: rgba(0, 0, 0, 0.25); }

/* screen-reader-only labels for the icon-only transport links */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip: rect(0 0 0 0); clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}

/* ---------- section-page content: framed like the playlist panel -----
   .content-surface is just the centered wrapper now; .wa-frame draws the
   pledit-sprite border (matching the playlist) around the page content.
   Pieces are scaled by --wf-scale (2x, to match the 2x player) via
   background-size, kept crisp with image-rendering. --content-bg backing
   hides sub-pixel seams between pieces.
   --------------------------------------------------------------------- */
.content-surface { width: 100%; max-width: 860px; margin-top: 1rem; }
/* opt-in wider surface (gallery.html) — photos want the extra room. */
.content-surface.is-wide { max-width: 1180px; }

:root { --wf-scale: 2; }
.wa-frame { image-rendering: pixelated; background: var(--content-bg); }
/* image-rendering inherits, so content <img>s would pick up the sprite's
   pixelated scaling. Restore smooth resampling for real photos in content. */
.wf-body img { image-rendering: auto; }
.wf-row  { display: flex; }
.wf-corner {
  flex: none;
  width:  calc(25px * var(--wf-scale));
  height: calc(20px * var(--wf-scale));
  background-repeat: no-repeat;
  background-size: calc(25px * var(--wf-scale)) calc(20px * var(--wf-scale));
}
.wf-fill {
  flex: 1 1 0;
  height: calc(20px * var(--wf-scale));
  background-repeat: repeat-x;
  background-size: calc(25px * var(--wf-scale)) calc(20px * var(--wf-scale));
}
.wf-mid  { display: flex; }
.wf-edge {
  flex: none;
  width: calc(12px * var(--wf-scale));
  background-repeat: repeat-y;
  background-size: calc(12px * var(--wf-scale)) calc(29px * var(--wf-scale));
}
.wf-body {
  flex: 1 1 auto;
  min-width: 0;
  background: var(--content-bg);
  padding: 1.5rem 1.75rem;
}
/* top-right corner doubles as a close button -> home */
.wf-close { display: block; cursor: pointer; }
.wf-close:hover { filter: brightness(1.2); }
/* mirror the single corner/edge sprite into the other positions */
.wf-fx  { transform: scaleX(-1); }
.wf-fy  { transform: scaleY(-1); }
.wf-fxy { transform: scale(-1, -1); }

/* ---------- typography & content -------------------------------- */
h1, h2, h3 {
  font-family: var(--font-ui);
  line-height: 1.25;
  color: var(--content-ink);
}
h1 { font-size: 1.7rem; margin: 0 0 0.75rem; }
h2 { font-size: 1.3rem; }

/* section-page headers rendered in the skin's bitmap lettering (skintext
   filter). The glyphs are 5x6 .skinchar cells; zoom them like the homepage
   title. line-height:0 strips the inline line box so spacing stays tight.
   If no skin is active, skintext falls back to plain text and this still
   styles the <h1> sanely (font props from the h1 rule above apply). */
.section-title { line-height: 0; margin: 0 0 1.25rem; }
.section-title .skintext { zoom: var(--wa-title-scale); }

a { color: var(--link); }
a:hover { color: var(--link-hover); }

time { color: var(--muted-ink); font-size: 0.85rem; }

/* inline code — a light pill. Scoped with :not(pre code) so it never touches
   the <code> that codehilite wraps around a fenced block (that one is the
   glossy panel below). */
code:not(pre code) {
  font-family: var(--font-body);
  background: #eef3f9;
  border: 1px solid #d3e0ee;
  border-radius: 3px;
  padding: 0.05em 0.35em;
  font-size: 0.9em;
  color: var(--content-ink);
}

/* ---------- fenced code blocks (Pygments / codehilite) ---------- */
/* Pelican's codehilite emits <div class="highlight"><pre><code>…</code></pre>.
   We style the panel here and the syntax tokens just below. Colors come from
   the --code-* vars in the skin block so a skin can recolor the panel too. */
.highlight {
  background:
    linear-gradient(180deg, var(--code-bg-top) 0%, var(--code-bg) 38%);
  color: var(--code-ink);
  border: 1px solid var(--code-border);
  border-radius: var(--radius-sm);
  /* inset top highlight + soft drop = the glossy "panel" read */
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.10),
    0 2px 6px rgba(10, 24, 37, 0.35);
  margin: 1.25rem 0;
  overflow: hidden;
}
.highlight pre {
  margin: 0;
  padding: 0.85rem 1rem;
  overflow-x: auto;            /* long lines scroll, never break the layout */
  font-family: var(--font-body);
  font-size: 0.85rem;
  line-height: 1.5;
  tab-size: 4;
}
.highlight pre code {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
}

/* Pygments token colors, mapped onto the aero --code-* palette. */
.highlight .c, .highlight .ch, .highlight .cm, .highlight .cp, .highlight .cpf,
.highlight .c1, .highlight .cs { color: var(--code-comment); font-style: italic; }
.highlight .k, .highlight .kc, .highlight .kd, .highlight .kn, .highlight .kp,
.highlight .kr, .highlight .ow { color: var(--code-keyword); }
.highlight .kt, .highlight .nc, .highlight .nn, .highlight .no, .highlight .nb,
.highlight .bp { color: var(--code-type); }
.highlight .s, .highlight .sa, .highlight .sb, .highlight .sc, .highlight .dl,
.highlight .sd, .highlight .s2, .highlight .sh, .highlight .si, .highlight .sx,
.highlight .sr, .highlight .s1, .highlight .ss, .highlight .se, .highlight .ld
  { color: var(--code-string); }
.highlight .m, .highlight .mb, .highlight .mf, .highlight .mh, .highlight .mi,
.highlight .mo, .highlight .il, .highlight .l { color: var(--code-number); }
.highlight .nf, .highlight .fm, .highlight .nd, .highlight .na, .highlight .ne
  { color: var(--code-func); }
.highlight .o, .highlight .nt, .highlight .nv, .highlight .vc, .highlight .vg,
.highlight .vi { color: var(--code-op); }
.highlight .err { color: var(--code-ink); background: none; }

/* ---------- post list (index) ----------------------------------- */
/* Each entry is a card: optional thumbnail (left) + title/date/description.
   Posts with no Image:/Description: header simply omit those pieces. */
.post-list ul { list-style: none; margin: 0; padding: 0; }

.post-list li {
  position: relative;            /* positioning context for the stretched link */
  display: flex;
  align-items: flex-start;
  gap: 0.9rem;
  padding: 0.9rem 0.5rem;
  border-bottom: 1px dotted #c4d6ea;
  border-radius: var(--radius-sm);
  transition: background-color 0.12s ease;
}
.post-list li:last-child { border-bottom: none; }
/* whole-card click target + hover feedback (skin-neutral translucent wash) */
.post-card:hover { background: rgba(255, 255, 255, 0.07); }
.post-link::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* thumbnail — fixed box, image cropped to fill (object-fit) for a tidy grid */
.post-thumb {
  flex: 0 0 auto;
  display: block;
  width: 116px;
  height: 84px;
  overflow: hidden;
  border: 1px solid var(--bevel-dark);
  border-radius: var(--radius-sm);
  background: rgba(0, 0, 0, 0.2);
}
.post-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* text column */
.post-info {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.post-link {
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 1.05rem;
  text-decoration: none;
}
.post-link:hover { text-decoration: underline; }
.post-desc {
  margin: 0.2rem 0 0;
  color: var(--content-ink);
  opacity: 0.82;
  font-size: 0.9rem;
  line-height: 1.4;
}

/* stack on narrow screens so the thumbnail doesn't crowd the text */
@media (max-width: 480px) {
  .post-list li { flex-direction: column; gap: 0.5rem; }
  .post-thumb { width: 100%; height: 140px; }
}

/* ---------- article header -------------------------------------- */
article header {
  margin-bottom: 1.25rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid #d3e0ee;
}
article header h1 { margin-bottom: 0.25rem; }

/* ---------- status bar (window footer, Win9x-style) ------------- */
.statusbar {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.35rem 0.75rem;
  font-family: var(--font-ui);
  font-size: 0.78rem;
  color: var(--muted-ink);
  background: linear-gradient(180deg, #eef4fb, #d8e4f1);
  border-top: 1px solid var(--window-border);
  box-shadow: inset 0 1px 0 var(--bevel-light);
}
.statusbar .status-cell {
  padding: 0.05rem 0.5rem;
  border: 1px solid #c4d6ea;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.5);
  box-shadow: inset 0 1px 2px rgba(20, 50, 90, 0.12);
}

/* ---------- gallery: collage ------------------------------------------
   Two masonry columns (CSS multi-column, so mixed portrait/landscape shots
   pack without gaps), then each photo is nudged off the column grid: widths,
   side offsets and gaps all cycle on *different* periods (3n / 4n / 5n / 7n)
   so the pattern never reads as a repeat. Photos stay square to the page —
   no rotation. Every 6th photo spans
   both columns as a big single-per-row shot, alternating which edge it hugs.
   No JS and no per-image markup: it works for any number of files dropped
   into content/images/gallery/.
   --------------------------------------------------------------------- */
.gallery-collage {
  column-count: 2;
  column-gap: 2.5rem;
  margin-top: 1.5rem;
  /* breathing room so the offset photos aren't flush with the frame edges */
  padding: 0.5rem 0.5rem 0;
}

.gallery-item {
  break-inside: avoid;
  margin: 0 0 2.25rem;
  padding: 9px;
  background: var(--content-bg);
  border: 1px solid var(--window-border);
  border-radius: var(--radius-sm);
  box-shadow:
    inset 1px 1px 0 var(--bevel-light),
    0 3px 10px rgba(20, 50, 90, 0.28);
}
.gallery-item img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 3px;
}

/* --- width variation (period 3, 4, 7) --- */
.gallery-item                 { width: 100%; }
.gallery-item:nth-child(3n+1) { width: 93%; }
.gallery-item:nth-child(4n)   { width: 84%; }
.gallery-item:nth-child(7n+2) { width: 78%; }

/* --- side offsets: `auto` on one side pushes a narrow photo to that edge,
       so nothing can overflow the column no matter the width above --- */
.gallery-item:nth-child(2n)   { margin-left: auto; }
.gallery-item:nth-child(5n+3) { margin-left: auto; margin-right: 0; }
.gallery-item:nth-child(7n+4) { margin-left: 0; margin-right: auto; }

/* --- vertical rhythm (period 3, 5) --- */
.gallery-item:nth-child(3n+2) { margin-bottom: 3.5rem; }
.gallery-item:nth-child(5n)   { margin-bottom: 1.25rem; }

/* --- every 6th photo: full-width hero across both columns --- */
.gallery-item:nth-child(6n+1) {
  column-span: all;
  width: 74%;
  margin: 0.5rem 0 3rem auto; /* hugs the right edge */
}
.gallery-item:nth-child(12n+1) {
  width: 80%;
  margin: 0.5rem auto 3rem 0; /* ...and every other one hugs the left */
}

/* One column on narrow screens; drop the offsets so photos stay as wide
   as possible. */
@media (max-width: 700px) {
  .gallery-collage { column-count: 1; column-gap: 0; padding: 0.5rem 0 0; }
  .gallery-item,
  .gallery-item:nth-child(3n+1),
  .gallery-item:nth-child(4n),
  .gallery-item:nth-child(7n+2),
  .gallery-item:nth-child(6n+1),
  .gallery-item:nth-child(12n+1) {
    width: 96%;
    margin: 0 auto 1.75rem;
  }
  .gallery-item:nth-child(2n) { margin-left: auto; margin-right: 0; }
}

/* opt-in: center a block image in article/page content */
.centered {
  display: block;
  margin: 1rem auto;
  max-width: 100%;
  height: auto;
}

/* ---------- small screens --------------------------------------- */
@media (max-width: 560px) {
  body { padding: 1rem 0.5rem 2rem; }
  .wf-body { padding: 1rem; }
}
