/* yiffy.cz — the legacy look, rebuilt.
 *
 * Values are traced to the original stylesheets in import/www/css/ rather than
 * invented. The design is two regions with opposite polarity, which is the single
 * easiest thing to get wrong when porting it:
 *
 *   chrome  (page ground, header, footer)  dark   — text #eee
 *   panel   (content, gallery band)        light  — text #606283
 *
 * The accent is yellow-green #c2e756, not gold. Sprites live in public/img/ui/.
 */

:root {
  /* page ground and chrome — globals.css:1-9, layout.css:160,594 */
  --ground: #3c3c4c;
  --chrome: #111;
  --chrome-text: #eee;
  --chrome-link: #e7e6ee;

  /* content panel — layout.css:558 */
  --panel: #f4f2fb;
  --panel-text: #606283;
  --panel-muted: #8082a3;
  --heading: #707393;

  /* links — globals.css:16,22 */
  --link: #3b3c7c;
  --link-hover: #8b8ccc;

  /* accent — layout.css:221,642 */
  --accent: #c2e756;

  /* gallery band and tiles — gallery.css:68-205 */
  --band: #d3d3e3;
  --band-border: #aeb2c1;
  --tile: #bfc0d1;
  --tile-border: #b4b5c5;
  --tile-img-border: #c5c6d7;
  --tile-hover: #d7eb8d;
  --tile-hover-border: #c4d781;
  --tile-hover-img: #d8e8a0;

  /* geometry: legacy pitch was 133px (125px cell + 8px gutter) */
  --cell-min: 125px;
  --gutter: 8px;
  --panel-width: 1020px;
  /* .content's inset from the panel edge, PER SIDE, counting its margin and padding:
     36px (30 margin + 6 padding) at the full 1020px panel, easing to 0 at 960px —
     the same floor the 960px bands use — so below that the column IS .body-mid's
     width with no doubly-inset column beside full-bleed furniture. Must stay in
     lockstep with .content's own margin/padding clamps below. */
  --content-inset: clamp(0px, calc((100vw - 960px) * 36px / 60px), 36px);
  /* Desired inset of the 960px bands (.file-header, .file-same) from the panel edge,
     per side. The bands hold the legacy 30px while the viewport has room for it; the
     inset tapers to 0 as the viewport reaches the elements' own 960px width, so below
     that the bands go full-bleed and .body-mid's children no longer lurch at the
     1020 crossing. The 960px floor is what keeps the bands at full width down to
     viewport 1020 (30+30+960) and no further. */
  --band-inset: clamp(0px, calc((100vw - 960px) / 2), 30px);
  /* Scale factor for the header's fixed left furniture: 1 at the full 1020px panel,
     easing to 0 at 960px — the same floor as --content-inset and --band-inset, so
     the banner's LEFT air collapses in lockstep with the rest of the panel
     furniture instead of lingering to 700px. The wordmark hit area (left 55px),
     the nav strip's left pin and its 35px paddings multiply by this. */
  --header-collapse: clamp(0, calc((100vw - 960px) / 60px), 1);
  /* Side air for the trimmed panel sprites (header.png, panel-body.png): 20px at
     the full 1020px panel, replacing the transparent edge columns they used to
     carry, easing to 0 at 960px like every other panel inset so the art reaches
     the banner/panel edges as the viewport narrows. */
  --panel-side-air: clamp(0px, calc(20px * var(--header-collapse)), 20px);
}

* { box-sizing: border-box; }

html {
  background: var(--ground) url("/img/ui/noise.png") top center;
  /* Fill the viewport even when the content is shorter, so the ground never stops
     short of the bottom edge. */
  min-height: 100%;
}

body {
  margin: 0;
  /* globals.css:9 — 12px base, not 15px */
  font: 12px Helvetica, Arial, sans-serif;
  color: var(--chrome-text);
  background: url("/img/ui/body-bg.png") repeat-x top center;

  /* Column flex so the footer can be pushed to the viewport bottom on pages whose
     content is shorter than the screen (see .site-footer's margin-top:auto).

     This was tried before and reverted: as a flex column the children shrank to the
     widest item, which narrowed the footer's background to the panel width. The
     cause was the footer being a flex item with no width of its own -- its .inner is
     a fixed 960px, so the box hugged it. .site-footer now sets width:100%, which
     keeps the background spanning the viewport. */
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

a { color: var(--link); text-decoration: none; }
a:hover { color: var(--link-hover); text-decoration: underline; }

/* ---- header -------------------------------------------------------------
   layout.css:160-172. The legacy header is TWO stacked bars, not one:

     #masthead   77px, masthead.png as a no-repeat background. The logo art sits
                 on the left of that image; the links row floats right over it.
     #mainMenu   30px strip below, holding the nav as fixed 120px cells.

   Putting the nav inside the 77px band made the live text collide with the logo
   artwork baked into the sprite. The banner is therefore its own element, with
   the nav beneath it. */

/* layout.css:1-10 — the header is 281px tall, not 77px.
 *
 *   #head-background  height 80px, padding-top 201px, head-bg.png no-repeat
 *
 * The 269px artwork occupies the padded area and masthead.png (80px) is layered
 * on top of it. Treating the header as a single 77px bar clipped both sprites;
 * the tall gradient is what gives the original its presence. */
.site-header {
  /* No fill: the header is the 1020px artwork over the page ground. A solid
     --chrome background darkened everything outside the sprite's width and read
     as an overlay across the top of the page. */
  background: transparent;
}

.site-header > .inner {
  max-width: var(--panel-width);
  margin: 0 auto;
  position: relative;
  /* 201px of air above the banner, then the banner itself. */
  padding-top: 201px;
  /* The tall artwork sits in that padding. 1020x269, no-repeat, anchored RIGHT:
     the character art is in the sprite's right half, and the left is open sky, so
     as the header narrows below 1020px the crop takes sky first and the art stays
     visible until the header runs out of width entirely. */
  background: url("/img/ui/header-bg.png") right top no-repeat;
}

/* Banner: 1020x80 sprite containing the wordmark on the left and the menu strip
   along its bottom edge. The nav OVERLAYS the artwork — layout.css:160-165 gives
   #masthead height 77px + padding-top 3px, and #mainMenu (30px) floats inside that
   box. Flowing the nav after the sprite instead of over it pushed it below the
   artwork, which is what made it read as a separate bar.

   The sprite is fixed art: below the 1020px panel it is cropped from the RIGHT, so
   the wordmark survives and the menu band loses its trailing decoration first.
   The box itself tracks .inner (which follows the viewport like .body-mid), so the
   only thing that changes with width is how much of the art is visible. */
.site-header .banner {
  position: relative;
  /* The sprite is 1020x80. Global box-sizing is border-box, so height must include
     the 3px top padding: 80px total, not 77 + 3. Getting this wrong left the nav
     floating a few pixels above the artwork's bottom edge. */
  height: 80px;
  padding-top: 3px;
  /* Three-layer composite (header-left/-mid/-right.png, cut from header.png):
     the edges are DIFFERENT art than the middle (per user), so a single sprite
     can only crop from one side. LEFT (270px) carries the wordmark AND the
     subtitle — user-verified visually: 310px worked but left plenty of room,
     the cut tightened to 270. MID is the tonal ramp (270..930), stretched;
     RIGHT (50px) is minimal edge art, pinned to the banner's right edge. */
  background-image:
    url("/img/ui/header-left.png"),
    url("/img/ui/header-mid.png"),
    url("/img/ui/header-right.png");
  background-repeat: no-repeat, no-repeat, no-repeat;
  background-position:
    var(--panel-side-air) top,
    calc(var(--panel-side-air) + 270px) top,
    right var(--panel-side-air) top;
  background-size:
    270px 80px,
    calc(100% - 320px - 2 * var(--panel-side-air)) 80px,
    50px 80px;
}

.brand {
  font: 600 20px "Trebuchet MS", Helvetica, Arial, sans-serif;
  color: var(--chrome-text);
  letter-spacing: 0.5px;
  text-shadow: 0 1px 2px #111;
  padding: 0 10px;
  /* The wordmark is part of the sprite, so the text link is for accessibility
     and mobile only; hide it on wide screens where the artwork shows. */
  position: absolute;
  left: -9999px;
}
.brand:hover { color: #fff; text-decoration: none; }

/* The wordmark itself is painted into header.png, so the link has to be an
   invisible hit area over it. Without this the logo looks clickable and is not,
   which is worse than either extreme. Sized to the wordmark's footprint in the
   sprite and placed above the nav's z-order so the two never fight for the same
   pixels. */
.site-header .banner::after {
  content: "";
  position: absolute;
  /* 55px on the full panel, scaled by --header-collapse so the hit area eases left
     with the narrowing banner instead of holding a fixed offset. */
  left: calc(55px * var(--header-collapse));
  top: 0;
  width: 242px;
  height: 80px;
  z-index: 1;
  cursor: pointer;
}

/* The ::after above cannot be a link itself, so the anchor stretches to cover it.
   .brand keeps its off-screen text for screen readers and the mobile layout. */
.site-header .banner .brand {
  position: absolute;
  /* Moves with the ::after hit area above: same scaled offset, same footprint. */
  left: calc(55px * var(--header-collapse));
  top: 0;
  width: 242px;
  height: 80px;
  z-index: 2;
  padding: 0;
  /* Hide the text visually without removing it from the accessibility tree. */
  font-size: 0;
  color: transparent;
  text-shadow: none;
  overflow: hidden;
}

.site-header nav {
  /* Absolutely positioned inside the banner, pinned to its bottom edge, so the
     menu sits ON the artwork rather than after it.

     layout.css:489 — #mainMenu is height:30px with padding:2px 35px 3px, and the
     legacy has NO box-sizing:border-box, so it computes to 35px total. Under our
     global border-box the same declarations give 30px, which is 5px short and
     leaves the menu sitting low against the sprite. */
  position: absolute;
  right: 0;
  /* Lifts the whole strip, hover sprite included, off the banner's bottom edge.
     bottom:0 put the sprite flush with the artwork's lowest row, which reads as
     too low; a small positive offset seats it in the menu band instead. */
  bottom: 7px;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  box-sizing: content-box;
  height: 30px;
  /* Bottom padding positions the whole strip, including the hover sprite. The
     text is centred inside the anchor below, so this value moves the background
     without moving the text within its cell. The 35px side paddings scale with
     --header-collapse so the strip's left air collapses with the banner instead
     of staying open; the media block below additionally pins the strip past the
     wordmark when the two would collide.
     CRITICAL: the RIGHT padding stays CONSTANT (35px) even as the left collapses,
     otherwise the entire flex row shifts left and the active sprite overflows
     the banner's right edge (reported: "between 1020 and 960, nav's right padding
     decreases which pushes the menu items more to the right edge, and when
     collapsed, the active/hover sprite leaks out"). */
  padding: 2px calc(35px * var(--header-collapse)) 5px 35px;
}

.site-header nav a {
  display: block;
  /* layout.css:505-517 — 120x29 cell, 600/16px uppercase, #ccc */
  width: 120px;
  min-width: 0;
  flex: 0 0 120px;
  /* Centre the label in the cell rather than pushing it to the top with
     padding-top. The hover sprite is anchored to the cell's box, so text
     alignment and background position are independent concerns. */
  box-sizing: border-box;
  padding: 0;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  color: #ccc;
  /* Source Sans Pro is not loaded (no web fonts), so the Helvetica fallback is
     wider than the original. 14px approximates the rendered width of 16px SSP. */
  font: 600 14px "Source Sans Pro", Helvetica, Arial, sans-serif;
  text-transform: uppercase;
  text-shadow: 0 1px 2px #111;
  background-repeat: no-repeat;
  background-position: 0 0;
}

.site-header nav a:hover {
  color: #fff;
  text-shadow: 0 -1px 1px #668666;
  text-decoration: none;
  /* Sliced and cleaned nav-hover cell (120x29 at 322,36). The protruding
     half-circle at the right edge has been trimmed (4px strip x=116–119,
     y=8–12 erased), so the sprite can fill the cell width fully — no edge
     clipping at any shrink level. Height locked to 29px
     prevents vertical stretch on phones. Cache-buster ?v=2 forces reload. */
  background: url("/img/ui/nav-active.png?v=2") center / 100% 29px no-repeat;
}

/* Active page marker: the SAME sprite the pointer shows, so the current entry is
   marked without any extra artwork. Grouped with :hover deliberately — the
   active cell must look identical whether or not it is being hovered. */
.site-header nav a.current,
.site-header nav a.current:hover {
  color: #fff;
  text-shadow: 0 -1px 1px #668666;
  background: url("/img/ui/nav-active.png?v=2") center / 100% 29px no-repeat;
}

/* ---- panel --------------------------------------------------------------
   layout.css:542-592. The panel is THREE elements, not one:

     .body-top     1020x14   bodyBorders.png at 0 0     (rounded cap)
     .body-mid     1020 wide  panel-body.png repeat-y   (side rails)
       .content    960 wide   #f4f2fb, margin 0 30px   (the lavender inset)
     .body-bottom  1020x20   bodyBorders.png at 0 -14px (rounded foot)

   bodyBorders.png is 1020x34: the top cap is its first 14 rows, the bottom cap the
   remaining 20. A single element with a ::before pseudo-element could not reproduce
   both caps at the correct heights, which is why the content overflowed the sprite. */

/* No overlap: the cap keeps its full 14px and the panel starts below it. */
.body-top,
.body-bottom {
  width: 100%;
  max-width: var(--panel-width);
  margin: 0 auto;
  /* Sprite only, no fill: a background colour here spans the full 1020px and
     squares off the rounded corners. */
  background: url("/img/ui/body-borders.png") center top no-repeat;
  /* No z-index: it would create a stacking context and lift the cap above the
     relatively-positioned .file-same band, which must sit over it. */
}
.body-top { height: 14px; }

/* Panel background pieces. The sprites ship TRIMMED: the 20px fully-transparent
   edge columns that header.png and panel-body.png used to carry are cut away
   (980px wide now), and CSS supplies what spacing remains via --panel-side-air,
   so no baked-in gap fights the responsive rules. body-borders.png (the caps)
   keeps its 1020px width: its edges are drawn art, not padding. */

/* No overlap: the cap keeps its full 14px and the panel starts below it. */
.body-mid {
  width: 100%;
  max-width: var(--panel-width);
  margin: 0 auto;
  /* SUBPIXEL SEAM FIX (reported: a hairline horizontal gap between .body-top
     and .body-mid across all viewports on the homepage): at fractional widths
     the centered sprite's raster offset rounds independently of the element's
     device-pixel box, so two ABUTTING edges can land a device pixel apart.
     Overlapping the seam by a subpixel amount (pulling .body-mid up 0.5px)
     turns the junction into a covered overlap; the cap's bottom rows are
     opaque, so nothing shifts visually. */
  margin-top: -0.5px;
  /* Trimmed sprite (980px) with the 20px of former baked padding supplied here as
     side air: the rails sit 20px inside the panel edge, exactly where the old
     transparent columns put them. Being CSS, the air can now respond to the
     viewport — below the panel width it tapers like every other inset so the
     rails reach the panel edge rather than holding a fixed gap. */
  background: url("/img/ui/panel-body.png") repeat-y center top;
  /* Natural size, centered, CROPPED by the element edges — the same treatment the
     caps get. The previous calc(100% - 2*air) scaling squeezed the 980px sprite
     (and its rail art) continuously from 1020 down to 960, visibly compressing
     and crushing the rails before they even reached the panel edge (reported:
     "border sprites are getting squeezed and disappear even before reaching the
     full width at 960"). Centered-crop keeps the rails pixel-identical at every
     width; below the panel width the element simply shows less of the middle. */
}
.body-bottom {
  height: 20px;
  /* the lower 20 rows of the same sprite */
  background-position: center -14px;
  margin-bottom: 5px;
}

.content {
  /* 1020 - 30 - 30 = 960, matching #mastbody .content. Margin and padding each clamp
     on their own so the LEGACY SPLIT stays exact at the full panel: margin 30px and
     padding 6px. Both taper to ZERO at 960px — the same floor the 960px bands use —
     so below that the column is exactly as wide as .body-mid, no doubly-inset column
     beside full-bleed furniture. */
  margin: 0 clamp(0px, calc(30px * (100vw - 960px) / 60px), 30px);
  background: var(--panel);
  /* No top padding: the sections bar is meant to sit flush against the panel's
     top edge, and any padding here shows as a lavender strip above it. */
  padding: 0 clamp(0px, calc(6px * (100vw - 960px) / 60px), 6px);
  color: var(--panel-text);
  /* No overflow:hidden here. It would clip the sections bar's 9px upward lift,
     cutting its top off. The legacy's #mastbody .content has no overflow either.

     display:flow-root instead. A block container establishes a new formatting
     context, so a child's bottom margin cannot collapse through this box and
     escape into .body-mid -- which is what left an 8px gap above .body-bottom on
     any page whose last element carried a margin (the artist listing was the
     visible case). Unlike overflow:hidden it clips nothing, so the sections bar's
     negative top offset still shows. */
  display: flow-root;
}

main { display: block; }

h1 {
  font: normal 26px Arial, Helvetica, sans-serif;
  color: var(--heading);
  margin: 0 0 16px;
}
h1 small {
  font-size: 15px;
  color: var(--panel-muted);
  font-weight: normal;
}

/* ---- sections bar --------------------------------------------------------
   layout.css:850-984. A 940px bar in #dedeea with rounded top corners, sitting
   flush against the panel edges (left:-6px, top:-9px in the original). Pill tabs
   are 28px high with a 12px radius; the current one is filled #8d91a7.

   The filter select is a <details>: summary is the closed pill, the list is the
   open menu. Same 2px translucent border and 12px radius as the original. */

.sections {
  /* Same panel-spanning geometry as the 960px bands: capped at the legacy width,
     width tracked against the viewport, margins cancelling .content's inset down to
     --band-inset from the panel edge. The old fixed 940px + margin:-6px held width
     at every viewport and overflowed below 960 (measured: 820px bar in an 800px
     viewport dragging the page sideways). */
  width: calc(100vw - 2 * var(--band-inset));
  /* 960 gross, not 940: border-box now includes the 10px side padding in the
     width, so the legacy bar's 940px CONTENT + 20px padding total is 960. Keeping
     940 under border-box shaved 20px off the bar and left it inset 50px on the
     right against the left's 30px (measured at the full panel). */
  max-width: 960px;
  height: 42px;
  line-height: 42px;
  background-color: #dedeea;
  border-bottom: 1px solid #cdceda;
  border-top-left-radius: 9px;
  border-top-right-radius: 9px;
  padding: 1px 10px 0;
  /* border-box, unlike the legacy: under the global border-box the content-box
     declaration added this rule's 20px of side padding OUTSIDE the computed width,
     which is what made the bar 820px wide in an 800px viewport. */
  box-sizing: border-box;
  /* layout.css:861-863 — #sections is lifted 9px inside #mastbody, whose rail
     background starts at its own top edge, so the bar rises into a region the
     rails already cover. Same here: .body-mid's panel-body.png rails begin at its
     top, so the bar can rise into them without exposing the page ground. */
  position: relative;
  top: -9px;
  margin-left: calc(var(--band-inset) - var(--content-inset));
  margin-right: calc(var(--band-inset) - var(--content-inset));
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 0;
}

.sections .item {
  height: 28px;
  color: #707090;
  font: normal 14px Helvetica, Arial, "Lucida Grande", sans-serif;
  line-height: 28px;
  padding: 0 12px;
  margin: 0 5px;
  display: inline-block;
  white-space: nowrap;
}

/* Active marker: gray rounded pill for the current entry (whether it is a link
   or a plain label). The first entry (content type) must also carry this when
   it is the current listing, and "All tags" must carry it when we are on /t. */
.sections .item.current {
  color: #fff;
  background-color: #8d91a7;
  border-radius: 12px;
}
.sections .item:hover { text-decoration: none; }

.sections label {
  height: 28px;
  color: #9999ba;
  font: normal 14px Helvetica, Arial, "Lucida Grande", sans-serif;
  line-height: 28px;
  border-left: 1px solid #c9c9ea;
  padding: 0 8px 0 19px;
  /* Follows the tabs directly; the legacy has no auto margin pushing it right. */
  margin: 0;
  display: inline-block;
  white-space: nowrap;
}

/* ---- filter select ------------------------------------------------------ */

.menu-box {
  /* No fixed height or vertical padding: the pill is centred by the bar's
     align-items, so any padding here offsets it. */
  margin-right: 0;
  position: relative;
  /* Shrinkable like the search input, but shrink-ONLY: flex:1 1 auto grew the
     pill to 222px at ~1000px, where the free space the input gave up became the
     pill's to claim (measured). flex:0 1 auto shares the squeeze without ever
     taking the input's surrendered width as growth. */
  flex: 0 1 auto;
  min-width: 76px;
  max-width: 168px;
  display: inline-block;
  align-self: center;
  /* The SUMMARY clips: the span keeps its own 148px width (a width:100% span
     makes the flex basis circular — fit-content collapses to the 76px floor,
     measured) and the box shrinking simply cuts the span's tail with the
     ellipsis the span already carries. NOT overflow:hidden on the box itself:
     the box hosts the absolutely-positioned dropdown, and a hidden-overflow box
     clips the popup behind the bar's other furniture (reported: popup hidden
     behind .pager/.grid). */
}

/* Label + pill travel together immediately AFTER the search form, per the
   legacy layout (#secondMenu followed the search input, it was not right-
   pinned). No margin-left:auto here: that pushed the pair to the bar's right
   end (reported regression). Inside the group the label keeps the separator
   styling and the pill shrinks against it. */
.filter-group {
  /* Immediately after the search input, no extra margin: the legacy spacing
     came from the label's own padding (19px left), not a group margin. */
  margin-left: 0;
  display: flex;
  align-items: center;
  flex: 0 1 auto;
  min-width: 0;
}
/* Labels do NOT ellipsize on the way down: the input and the pill shrink to
   their floors first, and the labels only disappear whole at the ≤540px
   breakpoint below. The earlier ellipsis treatment made "Filter:" collapse
   into three dots far too early (~670px, reported) while the pill still had
   70px of shrink left in it. */
.filter-group label {
  flex: 0 0 auto;
  overflow: hidden;
}

.menu-box > summary {
   list-style: none;
   height: 28px;
   color: #9999ba;
   font: normal 14px Helvetica, Arial, "Lucida Grande", sans-serif;
   line-height: 28px;
   background-color: #eeeefa;
   border: 2px solid rgba(0, 0, 60, 0.14);
   border-radius: 12px;
   padding: 0 6px;
   display: block;
   cursor: pointer;
   box-sizing: content-box;
   overflow: hidden;
   /* Arrow lives HERE, anchored to the SUMMARY's right edge (reported: as the
      pill shrank, the span's 100%-positioned arrow slid under the clip and
      disappeared). The span clips its own text independently below. */
   background-image: url("/img/ui/select-arrow.png");
   background-repeat: no-repeat;
   background-position: right 6px center;
}
.menu-box > summary::-webkit-details-marker { display: none; }
.menu-box > summary span {
   padding-right: 13px;
   padding-left: 2px;
   display: block;
   /* Own width (148px), NOT width:100%: a percentage span makes the flex basis
      circular and collapses the box; a fixed span gives the basis and the
      summary's overflow:hidden clips it with the span's own ellipsis as the box
      shrinks. No background here — the arrow is on the summary now, so the
      span's fixed width cannot push it out of view. */
   width: 148px;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
}

.menu-box .menu-list {
  background-color: #eeeefa;
  border: 2px solid rgba(0, 0, 60, 0.14);
  border-radius: 12px;
  padding: 5px 0;
  position: absolute;
  /* Drawn OVER the pill rather than below it: the panel starts at the box's own top
     edge and is at least as wide, so the pill's outline and corners disappear under
     it and the control reads as unfolding in place.

     The legacy achieved the same look differently -- gallery.js hid the anchor when
     the menu opened -- but the geometry is the same idea, and this needs no script.
     Anchoring on the left rather than the right is what makes the panel cover the
     pill: right:0 plus a wider panel pushed it 12px out to the LEFT, leaving the
     pill's right edge exposed. */
  top: 0;
  left: 0;
  z-index: 100;
  min-width: 100%;
  /* Cap on phones: the nowrap filter labels made the panel wider than the
     viewport (measured 623px in a 390px one), dragging the page sideways.
     The cap lets the list clip within .content's column instead. */
  max-width: calc(100vw - 44px);
  box-sizing: border-box;
}

.menu-box .menu-list a {
  height: 28px;
  color: #9999ba;
  font: normal 14px Helvetica, Arial, "Lucida Grande", sans-serif;
  line-height: 28px;
  padding: 0 12px;
  display: block;
  white-space: nowrap;
}
.menu-box .menu-list a:hover {
  color: #79799a;
  text-decoration: none;
}

/* Author and video cards carry a single-line sub ("239 works", "Muzzl"), unlike
   the gallery's two-line author/orientation block, so the fixed 30px leaves dead
   space under every tile and makes the row gaps look larger than elsewhere. */
.card--artist .card-sub,
.card--video .card-sub {
  height: 15px;
}

/* ---- listing search ------------------------------------------------------
   Lives INSIDE the sections bar, in the filter's slot, with the same left
   separator. Used by /authors and /t, where orientation does not apply. */

.sections-search {
  display: flex;
  align-items: center;
  /* No fixed height: it holds a 32px input, and a 28px box left the input
     overflowing so align-items had nothing to centre against. */
  height: auto;
  /* Same separator and inset as .sections label. */
  border-left: 1px solid #c9c9ea;
  padding: 0 8px 0 19px;
  margin: 0;
}

/* When the search box is present it already carries a left separator, and the
   Filter: label that follows it would draw a second rule a few pixels away. Only
   the first element after the tabs gets one. The padding stays at 19px so the
   label's text begins at the same x it would if the filter stood alone. */
.sections.has-search label {
  border-left: none;
}

.sections-search label {
  /* Reset what `.sections label` imposes on every label in the bar: it would add a
     SECOND separator here, and its 28px height/line-height fights the 32px input.
     The bar's own line-height: 42px is inherited too, so it is cleared as well. */
  border-left: none;
  padding: 0 8px 0 0;
  margin: 0;
  height: auto;
  line-height: normal;
  color: #9999ba;
  font: normal 14px Helvetica, Arial, "Lucida Grande", sans-serif;
  white-space: nowrap;
}

/* Matches the filter select (.menu-box > summary) box-for-box: 32px overall from a
   28px content box plus 2px borders, same 12px radius and translucent border. */
.sections-search input {
  /* 220px is the flex BASIS, not a fixed width: the form is a flex row (below),
     so the input shrinks CONTINUOUSLY as the bar narrows — the old width:220px
     held until the 1019px media block flipped it to width:auto in one step,
     which is what made the search collapse abruptly at 1020 while the filter
     beside it did not move at all. min-width:0 lets it shrink past its content
     and keeps the "Search:" label from being overrun. */
  flex: 1 1 220px;
  /* 110px floor: below ~750px the input would otherwise collapse to a sliver
     (24px measured at 740) while the tabs and pill keep their sizes. */
  min-width: 110px;
  height: 32px;
  padding: 0 10px;
  font: normal 14px Helvetica, Arial, "Lucida Grande", sans-serif;
  color: #707090;
  background-color: #eeeefa;
  border: 2px solid rgba(0, 0, 60, 0.14);
  border-radius: 12px;
  box-sizing: border-box;
}
/* Flex row so label and input negotiate space: the label keeps its nowrap text
   and its 8px gap, the input absorbs ALL the narrowing (it is the only flexible
   child), and neither can push the other aside. min-width matches the input's
   own floor: the form itself must not shrink below its content or the input
   paints UNDER the "Filter:" group at phone widths (measured -66px overlap) —
   the strip scrolls whatever does not fit, exactly as it does for the tabs. */
.sections-search {
  display: flex;
  align-items: center;
  flex: 0 1 auto;
  min-width: 190px;
}
.sections-search input::placeholder { color: #9999ba; }
.sections-search input:focus {
  outline: none;
  border-color: #8d91a7;
}

/* Announced by screen readers, invisible on screen. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Shown when a query matches more than the card cap. */
.list-search-more {
  grid-column: 1 / -1;
  margin: 4px 0 0;
  font-size: 12px;
  color: #707090;
}

/* Hidden tags: the attribute alone loses to `.tag-index .tag-list li { display:flex }`. */
.tag-index .tag-list li[hidden] { display: none; }

/* ---- homepage ------------------------------------------------------------
   main.css:3-257. A short statement of holdings, a large search field, then a
   tag cloud sized on the legacy's a.tag_8 .. a.tag_15 ramp. */

.main-text {
  color: #5d5e80;
  text-align: center;
  padding: 5px 0 0;
}

.main-text h2 {
  font: normal 21px Helvetica, Arial, Verdana, sans-serif;
  padding: 0;
  margin: 0;
}

.main-text p {
  line-height: 19px;
  font-size: 13px;
  padding: 0 20px;
  margin: 0;
}

.main-search {
  text-align: center;
  padding-bottom: 10px;
}

.main-search form {
  padding: 10px 0 0;
}

.main-search h3 {
  color: #5d5e80;
  font: normal 15px Helvetica, Arial, sans-serif;
  margin: 0 0 10px;
}

/* The legacy's numbers, exactly (main.css:124-146): frame 532 wide, padding 5,
   inner 530x35 with 1px padding. No explicit heights — the frame's height is
   the content's: 35 + 2 + 10 = 47px, which is exactly the sprite band at -37px
   (rows 37-83). Any deviation spills the next band into view as a stray line,
   which is what the earlier border-box maths caused.
   BUT the boxes are BORDER-BOX at 542/530 here (content 532/528 + padding): a
   content-box element clamped by max-width:100% shrinks its CONTENT only and
   lets the paddings poke out — the frame stopped shrinking from the left while
   still overflowing right, an asymmetric squeeze visible below ~750 (reported).
   border-box makes max-width clamp the whole box, so it shrinks evenly on both
   sides; the inner is a percentage of the frame so the two stay in lockstep. */
.main-search-frame {
  width: 542px;
  max-width: 100%;
  margin: 0 auto 8px;
  padding: 5px;
  box-sizing: border-box;
  background: transparent url("/img/ui/main-search.png") left -37px no-repeat;
  /* TRACK THE BOX WIDTH: at natural 542px a narrower element CROPS the sprite's
     right end — its rounded corner went with it and the right border turned
     rectangular below ~600 (reported). background-size stretches the sprite's
     WIDTH to the element while pinning its height (204px), so the band math
     (-37px offset) is untouched and both rounded corners stay in frame.
     Vertically identical to before; horizontally the art compresses evenly. */
  background-size: 100% 204px;
  position: relative;
}

/* Focus lights the frame green: the same sprite, 47px further down (main.css:
   132-135, driven by #mainSearchBG.focus). Same size tracking as the rest rule. */
.main-search-frame:focus-within {
  background-position: left -84px;
  background-size: 100% 204px;
}

/* Inner field: percentage width so it follows the frame's symmetric shrink
   (reported: one-sided squeeze below ~750). Same border-box reasoning. */
.main-search-inner {
  width: 100%;
  height: 37px;
  padding: 1px;
  box-sizing: border-box;
  background: transparent url("/img/ui/main-search.png") -5px top no-repeat;
  /* +10px so the -5px lateral offset stays SYMMETRIC: at plain 100% the sprite
     ended 5px short of the element's right edge, exposing the frame's border
     underneath the inner's own stroke — a doubled, too-thick right border
     (reported). Oversizing by 2*5px puts the overhang equally on both sides
     and the inner's strokes sit exactly on the frame's. */
  background-size: calc(100% + 10px) 204px;
  display: flex;
  align-items: stretch;
  position: relative;
  z-index: 3;
}

/* appearance:none kills the UA bevel that `border: none` alone leaves on
   input[type=search] — a grey top/left and white bottom/right inset edge. The
   sprite supplies all the chrome, so the field must be completely flat. */
.main-search-inner input {
  flex: 1 1 auto;
  min-width: 0;
  height: 33px;
  padding: 0 10px;
  color: #5d5e80;
  font: bold 16px Helvetica, Arial, Verdana, sans-serif;
  background: transparent;
  border: none;
  border-radius: 0;
  outline: none;
  box-shadow: none;
  appearance: none;
  -webkit-appearance: none;
  box-sizing: border-box;
}

/* Safari adds a clear button and a decoration to type=search. */
.main-search-inner input::-webkit-search-decoration,
.main-search-inner input::-webkit-search-cancel-button,
.main-search-inner input::-webkit-search-results-button {
  -webkit-appearance: none;
  display: none;
}
.main-search-inner input::placeholder {
  color: #9999ba;
  font-weight: normal;
}

/* The legacy button: a 35px sprite from backgroundSet.png at -416px -103px, with
   the pressed state at -371px -103px (main.css:163-183). The label text is 1px
   and effectively invisible — the glyph carries the meaning. */
/* Inset from the frame's right edge so the button's square corners never cover
   the rounded ones. */
.main-search-inner button {
  flex: 0 0 auto;
  width: 35px;
  height: 33px;
  padding: 0;
  color: #fff;
  font-size: 1px;
  text-shadow: 0 1px 1px #fff;
  background: transparent url("/img/ui/sprite-set.png") -416px -103px no-repeat;
  border: none;
  border-radius: 0 10px 10px 0;
  cursor: pointer;
}
.main-search-inner button:hover,
.main-search-inner button:active {
  background-position: -371px -103px;
}

/* Suggester: drops under the field, same width, scrollable past ~8 rows. */
.main-search-suggest {
  position: absolute;
  left: 5px;
  right: 5px;
  top: calc(100% - 2px);
  z-index: 10;
  margin: 0;
  padding: 4px 0;
  list-style: none;
  background-color: #eeeefa;
  /* 1px rather than the 2px used by the filter select: the panel hangs off the
     field's frame, where a heavy edge reads as a second border. */
  border: 1px solid rgba(0, 0, 60, 0.14);
  border-radius: 0 0 12px 12px;
  /* No scrollbar, ever. Three groups of at most three rows plus their headings is
     the tallest this list can get, and that fits comfortably; a scrollbar here would
     also steal width from the rows and shift them as the list grows. */
  overflow: hidden;
  text-align: left;
}
.main-search-suggest[hidden] { display: none; }

.main-search-suggest li { margin: 0; }

.main-search-suggest a,
.main-search-suggest button {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  padding: 5px 12px;
  color: #5d5e80;
  font: normal 14px Helvetica, Arial, sans-serif;
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
}
.main-search-suggest a:hover,
.main-search-suggest a[aria-selected="true"],
.main-search-suggest button:hover,
.main-search-suggest button[aria-selected="true"] {
  background-color: #8d91a7;
  color: #fff;
  text-decoration: none;
}
.main-search-suggest .count {
  color: #9999ba;
  font-size: 11px;
}
.main-search-suggest a:hover .count,
.main-search-suggest a[aria-selected="true"] .count {
  color: #e6e6f2;
}

/* Group headings in the mixed tag/artist/comic list. Small caps-ish label on the
   panel ground, not a selectable row: the <li> carries role="presentation" so the
   listbox's arrow keys skip it. */
.main-search-suggest .suggest-head {
  padding: 6px 12px 3px;
  color: #9999ba;
  font: bold 10px Helvetica, Arial, sans-serif;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  pointer-events: none;
}
/* A rule between groups, but not above the first one. */
.main-search-suggest .suggest-head:not(:first-child) {
  border-top: 1px solid rgba(0, 0, 60, 0.08);
  margin-top: 4px;
}

.main-search-status {
  margin: 0 0 6px;
  font-size: 12px;
  color: #707090;
  min-height: 15px;
}

/* The cloud: full panel width, spaced like the Tags view's cloud (10px between
   tags, 1.5 line-height). No side margin — it stretches to the panel edges. */
.main-search-tags {
  /* Side bleed CLAMPED to .content's padding (same fix as .home-picks-band):
     -6px wherever the padding covers it, tapering to 0 below 1020 so the
     element never pushes past the viewport (measured 966-in-960 overflow at
     exactly 960, where the padding clamp hits 0 and a fixed -6px had nothing
     left to eat). Division by -1 because calc cannot multiply two lengths. */
  margin: 0 calc(clamp(0px, calc(6px * (100vw - 960px) / 60px), 6px) / -1);
  padding: 0 12px;
  text-align: center;
  line-height: 1.5;
  overflow-wrap: break-word;
}

.main-search-tags a {
  /* inline-block: each tag is an ATOMIC box the line breaker can move whole.
     As plain inline text with nowrap, a long run of tags found no break
     opportunities on real iOS and pushed past the viewport (user-reported on
     iPhone). Text inside still never wraps mid-tag. */
  display: inline-block;
  margin: 0 5px;
  color: #55557a;
  white-space: nowrap;
}
.main-search-tags a:hover {
  color: var(--link-hover);
  text-decoration: underline;
}

/* main.css:250-257 — the legacy's eight size steps. */
.main-search-tags a.tag_8 { font-size: 10px; }
.main-search-tags a.tag_9 { font-size: 13px; }
.main-search-tags a.tag_10 { font-size: 14px; }
.main-search-tags a.tag_11 { font-size: 15px; }
.main-search-tags a.tag_12 { font-size: 16px; }
.main-search-tags a.tag_13 { font-size: 17px; }
.main-search-tags a.tag_14 { font-size: 19px; }
.main-search-tags a.tag_15 { font-size: 21px; }

/* ---- homepage random picks ------------------------------------------------
   Two rows of the gallery grid under the tag cloud. The heading and grid are
   hidden until /js/home-picks.js fills them, so the band never appears empty. */

.home-picks {
  /* Heading gap above the band (ABOVE 700 only — a narrow block below 700
     zeroes it, so the phone band sits flush under the title row exactly like
     the other full-bleed bands; reported: my earlier edit removed the desktop
     gap too). */
  margin-top: 18px;
}

.home-picks-title {
  color: #5d5e80;
  font: normal 20px Arial, sans-serif;
  /* Centred over the band, which bleeds 6px past .content on each side. */
  text-align: center;
  padding: 0 3px 0;
  margin: 0;
}

/* The random-pick grid is the last thing on the page. The band colour stops at
   the grid's own edge and the panel's lavender ends there too: .body-bottom is a
   TRANSPARENT sprite (its corner pixels are see-through), so it decorates the
   dark page ground rather than covering anything. Overlapping it only revealed
   lavender through the transparency, which read as a white corner. */
.home-picks-band {
  background-color: var(--band);
  /* Vertical offset as .file-same does, so the band's colour reaches the cap,
     plus the matching 9px bottom radius so its corners curve with the sprite
     instead of squaring off over it. */
  position: relative;
  bottom: -9px;
  /* Side bleed CLAMPED to .content's padding: -6px wherever the padding covers
     it (≥1020), tapering with the padding below so the band never pushes past
     the viewport (measured 906-in-900 and 966-in-960 overflows with a fixed
     -6px). .content's padding is exactly clamp(0px, 6px*(100vw-960px)/60px, 6px),
     so mirroring it as a negative keeps the band flush to the panel's inner
     edge at every width. The vertical -9px bleed (the .grid-like part) stands
     at all widths. */
  margin: 0 calc(clamp(0px, calc(6px * (100vw - 960px) / 60px), 6px) / -1);
  border-radius: 0 0 9px 9px;
}

.home-picks .grid {
  background: none;
  border-bottom: none;
  margin: 0;
}

/* Below 700 the picks grid full-bleeds exactly like every other .grid (the
   phone block gives .grid margin: 0 -14px 8px over .content's 14px padding).
   Reported: .home-picks "isn't stretched fully below 700 and behaves
   differently than usual .grid" — the margin:0 override above, meant for the
   desktop band layout, held on phones too. This restores the identical
   geometry, and pulls the band out to the same bleed so the grid and its
   lavender ground form one full-width block. */
@media (max-width: 700px) {
  /* The BAND is the sizing box (it carries the lavender ground): pulled out to
     .grid's own -14px phone bleed so .home-picks reads full-width like the
     grids. The grid INSIDE keeps margin:0 — the phone .grid rule's -14px
     would double the band's bleed and push the grid past the viewport
     (measured 714-in-700). */
  .home-picks-band {
    margin-left: -14px;
    margin-right: -14px;
    /* Phone bleed is the FULL 10px of .body-bottom (not the base -9): the cap
     is 10px tall below 700, and a -9 overlap left its last 1px showing
     --panel under the band's lavender — a 1px gap at the bottom (reported).
     Bleeding the whole cap puts the band's colour on the panel's bottom edge. */
    bottom: -10px;
    border-radius: 0;
  }
  /* Phone heading geometry (reported): the desktop 18px gap above .home-picks
     goes — the band sits flush under the title row like the other full-bleed
     bands — and the title's line-box descender leaves a residual 1px white
     sliver, closed with a hair of negative margin. */
  .home-picks {
    margin-top: 0;
  }
  .home-picks-title {
    margin-bottom: -1px;
  }
}

/* ---- 404 -----------------------------------------------------------------
   From the legacy's 404.css. The page drops the site chrome and becomes a
   centred statement on the dark ground: a 160px "4 [icon] 4" lockup, a 40px
   headline, then two 700px paragraphs. Colours and shadows are the original's. */

.errors {
  padding: 100px 0;
  text-align: center;
  color: #9b9ba3;
  font: 12px "Trebuchet MS", Arial, sans-serif;
}

.errors h1 {
  color: #9b9ba3;
  font: bold 160px "Trebuchet MS", Arial, sans-serif;
  padding: 5px 0;
  margin: 0;
}

.errors h1 img {
  width: 152px;
  height: 170px;
  vertical-align: top;
  display: inline-block;
}

.errors h2 {
  color: #9b9ba3;
  font-size: 40px;
  text-shadow: 0 1px 6px #262626;
  padding: 0;
  margin: 0;
  display: block;
}

.errors p {
  width: 700px;
  max-width: 100%;
  color: #9b9ba3;
  font-size: 14px;
  text-shadow: 0 1px 3px #161616;
  padding: 30px 0 0;
  margin: 0 auto;
  display: block;
}

.errors a {
  color: #e9e9e9;
  font-size: 15px;
  text-shadow: 0 1px 6px #161616;
}
.errors a:hover { color: var(--accent); }

/* The holding page's heading, in the site's green. The lockup stays grey, like the
   404's: the numerals and paw are the same furniture on both pages, and colouring
   only the line of text keeps the two feeling related while making the holding page
   read as a notice rather than an error. */
.errors--soon h2 {
  color: var(--accent);
}

/* A grid that is the last thing on the page reaches down over the bottom cap and
   rounds its corners, instead of stopping short with its bottom border and leaving
   a strip of lavender below.

   Same technique as .tag-band and .home-picks-band: bottom:-9px moves the box over
   the cap's transparent rows, the 9px radius curves with the sprite, and the
   negative top margin takes back the layout space bottom leaves behind. Expressed
   as :last-child so it needs no class and applies to any page that ends in a grid
   -- the artist listing is the one today. */
.content > .grid:last-child,
.content > .grid.is-trailing {
  background-color: var(--band);
  position: relative;
  bottom: -9px;
  border-bottom: none;
  border-radius: 0 0 9px 9px;
  margin-top: -9px;
  /* The base .grid carries 8px of bottom margin, which pushes the box down and eats
     the 9px offset -- only 1px of the bleed survived until this was zeroed. .tag-band
     does the same for the same reason. */
  margin-bottom: 0;
}

/* Single-page tag listings have no pagers, so the grid is the last thing on the
   page. .tag-band is applied to the grid itself, not to a wrapper: a wrapper
   meant stripping the grid's own margin and bleed, which left a larger gap above
   it than every other listing has. As a second class it only overrides what it
   needs to, so the spacing to .pager is unchanged. */
.grid.tag-band {
  /* The band colour reaches down over the cap and rounds to the same 9px, so it
     curves with the sprite instead of stopping short and letting the lavender
     show through the cap's transparent corner. Same technique as
     .home-picks-band.

     bottom:-9px moves the box visually but leaves its layout space behind, which
     on the homepage falls below the band (nothing follows) and here showed as a
     9px gap under the pager. The negative top margin takes that space back, so
     the gap above stays the same as on every other listing while the bleed into
     the cap is preserved. */
  background-color: var(--band);
  position: relative;
  bottom: -9px;
  border-bottom: none;
  border-radius: 0 0 9px 9px;
  margin-top: -9px;
  margin-bottom: 0;
}

/* ---- tag cloud -----------------------------------------------------------
   The most-used tags above the full list. Sizes come from the page as inline
   font-size, on a log scale. Deliberately outside .tag-index: list-search.js
   filters .tag-index .tag-list only, so the cloud persists while searching. */

.tag-cloud {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  /* Centre each row of the cloud. */
  justify-content: center;
  gap: 2px 10px;
  padding: 10px 12px;
  /* Bleed to the panel's inner edge like .grid, with the same taper: -6px at
     full panel width, easing to 0 as the viewport reaches 960px. The fixed -6px
     held the bleed while .content's padding tapered away, so the cloud's right
     edge crossed the column below 1020 (measured: 906px page in a 900px
     viewport on /t). */
  margin: 0 calc(-6px * var(--header-collapse)) 12px;
  background-color: var(--band);
  border-top: 1px solid var(--band-border);
  border-bottom: 1px solid var(--band-border);
  line-height: 1.5;
}

.tag-cloud a {
  color: #55557a;
  text-decoration: none;
  white-space: nowrap;
}
.tag-cloud a:hover {
  color: var(--link-hover);
  text-decoration: underline;
}

/* ---- tag index -----------------------------------------------------------
   Multi-column list of every tag with its count, for /t.

   Scoped to .tag-index: the header's tag panel also uses .tag-list, and a bare
   .tag-list rule here leaked `columns: 216px` into it, laying the tags out in
   three columns. */

/* The index is TEXT, not a band: unlike .grid/.tag-cloud it must NOT bleed to
   the panel edge. Reference is the BORDER SPRITE's inner edge (the rail), not
   the panel edge: the rails sit --panel-side-air inside the panel (20px at the
   full width, easing to 0 at 960px), so holding a constant 16px gap TO THEM
   means padding = air + 16 - content-inset. At >=1020 that is exactly 0 (air 20
   + 16 = the column's own 36px inset), so the wide view is untouched; below,
   the padding takes over and the text-to-rail gap stays 16px at every width.
   The expression can never go negative (air + 16 >= content-inset across the
   whole range, equal at the top), so no max() guard is needed. */
.tag-index {
  padding: 0 calc(var(--panel-side-air) + 16px - var(--content-inset));
}

.tag-index .tag-list {
  /* 6 columns across the 948px content box: (948 - 5 x 20px gap) / 6 = ~141px.
     A width hint rather than column-count, so narrow viewports simply fit fewer
     columns instead of squeezing six unreadably thin ones. */
  columns: 141px;
  column-gap: 20px;
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 13px;
}

.tag-index .tag-list li {
  break-inside: avoid;
  display: flex;
  justify-content: space-between;
  gap: 8px;
  padding: 3px 0;
  border-bottom: 1px solid rgba(153, 153, 187, 0.18);
}

.tag-index .tag-list a {
  color: #55557a;
  /* Long tag names truncate rather than wrap: the list is a fixed-pitch grid of
     rows, and a wrapped name would make one row taller than its neighbours. */
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.tag-index .tag-list a:hover { color: var(--link-hover); }
.tag-index .tag-list span {
  color: var(--panel-muted);
  font-size: 11px;
  flex: 0 0 auto;
}

/* ---- gallery band -------------------------------------------------------
   gallery.css:68-205. The band bleeds to the panel edge; the tiles inside are
   square, matted, and 125px wide with an 8px gutter, giving the legacy ~133px
   pitch across a 940px field. */

.grid {
  display: grid;
  /* Dynamic rather than the legacy fixed 7 columns: same pitch on desktop,
     reflowing to 2 columns at 320px. */
  grid-template-columns: repeat(auto-fill, minmax(var(--cell-min), 1fr));
  gap: 20px var(--gutter);
  background: var(--band);
  border-top: 1px solid var(--band-border);
  border-bottom: 1px solid var(--band-border);
  padding: 8px 10px;
  /* Bleed to the panel's inner edge, in the same continuous way as the 960px
     bands: -6px at full panel width, easing to 0 as the viewport reaches 960px.
     Straight multiplication by --header-collapse (itself clamped to [0,1], so the
     product can never exceed 6px or go negative) — an earlier version wrapped this
     in a TWO-argument clamp(), which is invalid CSS: at computed-value time the
     whole margin fell back to 0px and the grid kept a 6px gap to the bar at every
     width (measured). */
  /* Symmetric with the top pager: 8px below the band, matching .pager's margin. */
  margin: 0 calc(-6px * var(--header-collapse)) 8px;
}

/* ---- card ---------------------------------------------------------------
   gallery.css:120-205. Structure is a matted square image followed by two text
   lines: the title in bold, then author and orientation in muted 11px. */

.card {
  display: block;
  text-align: center;
  color: var(--panel-text);
}

/* The mat: padding + outer border on the wrapper, inner border on the image.
   A single border loses the double-edged look that identifies the tile. */
.card-image {
  display: block;
  background: var(--tile);
  border: 1px solid var(--tile-border);
  padding: 4px;
  margin-bottom: 4px;
}

.card-image {
  display: block;
  background: var(--tile);
  border: 1px solid var(--tile-border);
  padding: 4px;
  margin-bottom: 4px;
}

/* gallery.css:124-131 — the legacy pins both dimensions (113x113 inside a 115x115
   anchor) rather than relying on the column being square. width:100% with
   aspect-ratio:1 leaves the height free to grow when the grid track is wider than
   the image, which is what broke the squares. */
.card img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  object-position: center;
  background: #6e799f;
  border: 1px solid var(--tile-img-border);
}

/* The safety net's appearance. The failed <img> has its src swapped to the site
   mark by img-fallback.js, so the element now legitimately displays the mark
   bitmap; it only needs the dark ground behind it (the mark artwork is white
   at low alpha). Do NOT set opacity here: an <img> element's opacity hides the
   element including its background, and the first version of this rule did
   exactly that, leaving a lavender tile with no mark (the page panel showing
   through an invisible image). */
img.img-fallback {
  background: #6e799f;
  object-fit: contain;
}

/* Flash tiles now carry real thumbnails, rendered from the SWF by
   scripts/gen-flash-thumbs.mjs. The old .card-flash and .same-flash
   no-JS/mark-placeholder rules (and public/img/ui/flash-mark.png) were retired
   2026-11: no emitter remained, and the mark now lives as an inline data URI
   in public/js/img-fallback.js. */

.card:hover { text-decoration: none; }
.card:hover .card-image {
  background: var(--tile-hover);
  border-color: var(--tile-hover-border);
}
.card:hover img { border-color: var(--tile-hover-img); }

.card-title,
.card-sub {
  display: block;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* gallery.css:190 — title bold 13px, sub-line 11px muted, fixed height so rows
   line up regardless of how long the text is. */
.card-title {
  font-size: 13px;
  font-weight: bold;
  color: var(--panel-text);
  line-height: 17px;
  height: 17px;
}

/* Untitled comics print a placeholder rather than a blank first line; italic
   and unbolded so it reads as a label, not a real title. */
.card-title.untitled {
  font-weight: normal;
  font-style: italic;
  color: var(--panel-muted, #636483);
}
.card-sub {
  font-size: 11px;
  font-weight: normal;
  color: var(--panel-muted);
  line-height: 15px;
  height: 30px;
  white-space: normal;
}

/* ---- pager (top) ---------------------------------------------------------
   layout.css:1286-1342 — navigatePages. Three floats in a 27px bar: a 120px
   sprite button left, a FIXED 699px centre slot, a 120px sprite button right.
   Disabled buttons (b) carry the same sprite as enabled ones (a); only :active
   swaps the crop. The label lives in <em>, which is reset to normal style. */

.pager {
  color: #666;
  /* min-height, not height: the 6px bottom padding was eating into a fixed 27px
     box under border-box, leaving 21px of content for a 27px-tall child — which
     is why the text sat high. */
  min-height: 27px;
  padding-bottom: 6px;
  /* Keep the buttons' margin to the edge constant relative to the BANDS, like
     .file-item: the bands sit --band-inset from the panel edge, the buttons 6px
     inside them, and .content already contributes --content-inset on top — so the
     padding nets it out (6 + band - content = 0 at the full panel, 6 below 960,
     where the buttons sit flush with the grid and sections bar). A flat
     band+6px here doubled with .content's inset and put the buttons 72px out at
     the full panel (measured). */
  padding-left: calc(var(--band-inset) + 6px - var(--content-inset));
  padding-right: calc(var(--band-inset) + 6px - var(--content-inset));
  /* No bottom margin: the legacy's navigatePages sets only padding-bottom, and the
     gap to the content comes from the band below. */
  margin: 0;
  /* Flex, not floats: the legacy float row (120px button / fixed 699px centre /
     120px button) cannot reflow, so below ~939px the buttons and the centre slot
     wrapped onto second lines one after another. Flex keeps the three parts on
     one row at every width, the centre taking the slack. */
  display: flex;
  align-items: center;
}

.pager a.pagingLeft,
.pager b.pagingLeft,
.pager a.pagingRight,
.pager b.pagingRight {
  display: block;
  flex: 0 0 auto;
  width: 120px;
  height: 27px;
  color: #3c5c3c;
  font: bold 13px Helvetica, Arial, Verdana, sans-serif;
  line-height: 27px;
  text-align: center;
  text-shadow: 0 1px 1px #eaf5ea;
  background: transparent url("/img/ui/sprite-set.png") -10px -406px no-repeat;
}
.pager a.pagingLeft,
.pager b.pagingLeft { margin-right: auto; }
.pager a.pagingRight,
.pager b.pagingRight { margin-left: auto; }

.pager a:active { background-position: -140px -406px; }
.pager a:hover { text-decoration: none; }

.pager em { font-style: normal; }

.pager .pagesInfo {
  /* flex:1 replaces the legacy fixed 699px centre slot: the slot was sized for
     the 940px bar and wrapped below ~939px. Taking the slack between the buttons
     centres the label at every width instead. */
  flex: 1;
  height: 27px;
  font-size: 13px;
  line-height: 27px;
  text-align: center;
  display: block;
}

/* Alone in the bar (the tags page has no Previous/Next, and a search hides them),
   so it takes the full width and centres rather than sitting in the 699px slot
   left of the buttons.

   Both cases are handled by making .pager a flex row and letting the span fill
   it. Float + width:auto depended on the block filling its parent, which the
   float context here made unreliable — that is why the tags text stayed left. */
.pager:has(> .pagesInfo:only-child),
.pager.is-searching {
  /* Already flex from the base rule; nothing to add — the span fills the row. */
}

.pager:has(> .pagesInfo:only-child) .pagesInfo,
.pager.is-searching .pagesInfo {
  /* width:auto + flex:1 both apply; keep the shorthand harmless. */
  width: auto;
  flex: 1;
  height: auto;
}

/* list-search.js sets `hidden` on the Prev/Next buttons while a query is active.
   The attribute alone loses to `.pager a, .pager b { display: block }`, so it is
   asserted here. Hiding them leaves .pagesInfo as the only child, which the
   :only-child rule above then centres. */
.pager a[hidden],
.pager b[hidden] {
  display: none;
}

/* ---- gtop: the numbered strip below the grid ----------------------------
   layout.css:1156-1290. Every cell is an 18x18 crop of backgroundSet.png
   (renamed sprite-set.png) and the DIGIT IS NOT TEXT: the sprite carries it, and
   a span is revealed only on hover for the tooltip-like effect. `b` is the
   current page; the arrows are a.pagingLeft / a.pagingRight with em hidden. */

.gtop {
  height: 28px;
  /* layout.css:1164-1165 — 5px above, 15px below. The bottom figure is kept
     because the strip sits closer to the grid than the top pager does. */
  padding: 5px 0 15px;
  margin: 0;
  text-align: center;
  font-size: 13px;
  color: #666;
  overflow: hidden;
}

.gtop > div {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 0;
}

.gtop a,
.gtop b,
.gtop .current {
  display: block;
  width: 18px;
  height: 18px;
  margin: 0 4px;
  color: #3c5c3c;
  font-weight: bold;
  font-size: 11px;
  letter-spacing: -1px;
  line-height: 18px;
  text-align: center;
  background: url("/img/ui/sprite-set.png") no-repeat;
}

/* Numbered cells: sprite shows the digit; the span is hidden until hover. */
.gtop a { background-position: -154px -282px; }
.gtop a:hover { background-position: -154px -310px; text-decoration: none; }
.gtop a:active { background-position: -154px -338px; }

.gtop a span { display: none; }
.gtop a:hover span {
  display: block;
  padding-top: 1px;
  text-shadow: 0 1px 1px #eaf5ea;
}

/* The sprite carries the digit for the current page too, so the text is hidden
   exactly as it is for links. */
.gtop .current {
  background-position: -182px -282px;
  color: transparent;
  text-shadow: none;
}

/* Gap marker: layout.css:1201 — plain grey text, no sprite. */
.gtop b.gap {
  background: none;
  color: #ccc;
  font-size: 14px;
  letter-spacing: 0;
}

/* Arrows. Disabled ones are b, enabled ones are a, each with its own crop. */
/* Arrow labels are hidden: the sprite draws the glyph. The selector must name
   .pagingLeft/.pagingRight — an earlier .arrow selector matched nothing, so
   "Previous" and "Next" rendered as text over the sprite. */
.gtop em { display: none; }
.gtop a.pagingLeft { background-position: -238px -282px; }
.gtop a.pagingLeft:hover { background-position: -238px -310px; }
.gtop a.pagingLeft:active { background-position: -238px -338px; }
.gtop a.pagingRight { background-position: -210px -282px; }
.gtop a.pagingRight:hover { background-position: -210px -310px; }
.gtop b.pagingLeft { background-position: -182px -338px; }
.gtop b.pagingRight { background-position: -182px -310px; }

/* ---- detail page --------------------------------------------------------
   file.css:1-117, 405-411. The legacy stacks a header bar, a tag line, then the
   file itself centred in the panel. There is no sidebar: author, category and
   date live in the header, and the stats sit either side of them. */

.file { position: relative; }

/* #fileHeader — 960x80, #dedeea, 9px top corners, raised 9px like #sections.
   The legacy sets width:960 with no box-sizing. The bar spans the PANEL, not the
   content column: .content pads its box with margin 30 + padding 6 per side, and the
   band cancels both with -36px margins and width 100%+72px, so it reaches the panel's
   edges at every viewport width while capping at 960px on wide screens. */
.file-header {
  max-width: 960px;
  /* Pull out to --band-inset from the panel edge: .content's content box is inset
     --content-inset, so cancelling the difference and re-adding the target lands the
     band's margin box exactly --band-inset inside the panel, tapering with the
     viewport below 1020px. */
  width: calc(100vw - 2 * var(--band-inset));
  margin-left: calc(var(--band-inset) - var(--content-inset));
  margin-right: calc(var(--band-inset) - var(--content-inset));
  /* Height is content-driven now: the legacy 80px assumed exactly one row of
     two 230px stat blocks; a redesign to a uniform pill row can wrap or
     scroll, so the box grows with its content. */
  min-height: 46px;
  /* Side padding DOUBLE the pill overhang (per user: put the left/right gap on
     .file-header itself): the pills' -6px margins pull the tint 6px into this
     padding, so 12px leaves a real 6px gap between the band's edge and the
     painted pill on each side. */
  padding: 12px 12px;
  color: #707090;
  font: bold 11px Helvetica, Arial, sans-serif;
  line-height: 16px;
  text-align: center;
  background-color: #dedeea;
  border-bottom: 1px solid #cdceda;
  border-radius: 9px 9px 0 0;
  position: relative;
  top: -9px;
  box-sizing: border-box;
  /* HORIZONTAL SCROLL fallback (the .file-same trick, per user): when the pill
     row no longer fits the panel, the whole strip swipes instead of hiding
     blocks or crunching text.

     BUT overflow-x beyond "visible" clips ALL descendants to the padding box
     EVEN WHEN NOTHING OVERFLOWS — with it permanently on, the tag bubble
     (which hangs below the strip) was cut off at the band's bottom edge
     (reported). So the scroll box exists ONLY below 600px, the regime where
     the pills have already shrunk to their 90px floor and genuinely cannot
     fit; at desktop widths there is no clip at all and the bubble floats
     freely over .file. */
  overflow: visible;
  scrollbar-width: none;
  /* ABOVE the file block: the tag bubble hangs below the strip into .file's
     territory, and DOM order alone left it UNDER .file. */
  z-index: 70;
}
@media (max-width: 600px) {
  .file-header { overflow-x: auto; }
}
.file-header::-webkit-scrollbar { display: none; }

/* The tag STRIP (renamed from .tag-band, which collided with the tag-index
   listing grid of the same class and blanked small tag pages — reported): a
   flat, toggleable list of the work's tags directly below the header band,
   replacing the floating bubble below 700px (per user) — the bubble is
   unreachable there because the horizontal-swipe scroll regime clips anything
   that leaves the band. Markup for both is always present; these rules swap
   which one the reader sees. Desktop keeps the bubble. */
.tag-strip {
  display: none;
  text-align: left;
  font-size: 13px;
  line-height: 22px;
  color: #636483;
  padding: 10px 12px;
  background: var(--band);
  border-bottom: 1px solid var(--band-border);
  /* NO top border (per user correction): the header's own bottom hairline is
     the divider above the open band; the band carries only its identical
     bottom rule. */
  border-top: none;
  /* TOP BLEED (per user): pulls the band up into the header's box so the two
     share one continuous ground with no visible seam — measured gap was 9px
     (the bands stack their borders and paddings); -9px cancels it.
     Bottom: small clearance only (per user: the earlier 12/14px was too
     large — the content below needs just a few px of separation). */
  margin: -9px calc(var(--band-inset) - var(--content-inset)) 9px;
  width: calc(100vw - 2 * var(--band-inset));
  /* Square corners (per user): the merged header+tag band reads better as one
     crisp rectangle; the header's own top rounding still caps the ensemble. */
  border-radius: 0;
}
.tag-strip a { color: #55557a; font-weight: bold; }
.tag-strip a:hover { color: var(--link-hover); }
.tag-strip .tag-sep { color: #a0a0c0; font-weight: normal; }
@media (max-width: 700px) {
  /* COLLAPSED by default; the same details toggle that drives the desktop
     bubble flips the band too (:has needs no JS — the summary click is the
     native toggle, menu-dismiss outside-click still works on the attribute). */
  .tag-strip { display: none; }
  .file-header:has(.tag-box[open]) + .tag-strip { display: block; }
  .tag-box .tag-list { display: none !important; }
  .tag-box > summary { cursor: pointer; }
  /* FULL-BLEED on all sides (per user): the same -14px pull out of .content's
     phone padding the header uses — declared inside the media query so it
     beats the band's own base margins (which resolve to 0 at this width and
     were resetting the bleed; media queries lose cascade ties to later base
     rules). */
  .file-header:has(.tag-box[open]) + .tag-strip {
    margin-left: -14px;
    margin-right: -14px;
    width: auto;
  }
  /* Header KEEPS its bottom hairline at every width (per user correction):
     the divider is always the header's own #cdceda rule; the open band below
     700 has no top border of its own, just its identical bottom rule. */
}

/* The single stat row. The legacy's two absolutely-positioned 230px blocks and
   the 250px-margin centred text are gone; every informational unit is now one
   uniform pill in one flex row: Tags, Author, Page (comics), Orientation,
   Category, Uploaded — interaction first, archival metadata last, so the
   scroll fallback trims the right end only. */
.file-header .stats {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: stretch;
  /* LEFT-ALIGNED with NO row padding (per user: "how it was in the original,
     115px width of each li, no paddings from left/right") — replaced the
     earlier centered/stretch variant.

     width:100% (NOT max-content/auto): the row is FORCED to the header's
     content box, so when the six pills stop fitting the flex algorithm sees
     the shortage immediately and distributes it via each li's flex-shrink
     (down to the 90px floor) BEFORE any horizontal scrolling engages (per
     user). Only past the floor — pills at 90px still can't fit — does the row
     overflow its box and the header's overflow-x:auto scrolling takes over. */
  justify-content: flex-start;
  gap: 10px;
  width: 100%;
}

/* Category and Uploaded pin to the strip's right edge (per user): the
   left cluster (Tags/Author/Page/Orientation) hugs the left, these two hug
   the right, and any leftover width opens between Orientation and Category. */
.file-header li.right { margin-left: auto; }
.file-header li.right + li.right { margin-left: 0; }

.file-header li {
  /* FIXED 115px pills at full width (legacy li width, per user's correction),
     but they SHRINK proportionally as the viewport narrows before the strip's
     horizontal-scroll fallback engages (per user): flex-shrink lets the flex
     algorithm divide the shortage among the six pills, floored at 70px each
     (below that the ellipsis eats real content). */
  text-align: center;
  flex: 1 1 auto;
  max-width: 115px;
  width: 115px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-width: 90px;
}

/* Long word values SHRINK to fit the 50px pill, floor at 8px, then cut with
   three dots (per user). CSS clamp on font-size proportional to the amount of
   overflow: the fixed-height p with overflow:hidden + white-space:nowrap plus
   text-overflow handles the cut. The scaling needs the text length, done via
   a scale() trick: compute once in the stylesheet is impossible, so the
   renderer sets a --shrink factor on the pill based on character count. */

/* Fixed LINE GEOMETRY so the two rows align across every pill. The VALUE area
   is one fixed 30px band and the CAPTION sits on one fixed line below;
   whatever the font size, value text vertically CENTRES in the band and the
   caption baseline is identical pill to pill. */
.stat-val, .file-header li p, .tag-box > summary p {
  color: #636483;
  /* NOT bold (reported); 17px per user. Long values SHRINK inside the 115px
     pill via the inline font-size the renderer computes (shrinkFont in
     worker/render.js, 8px floor); past the floor the nowrap + ellipsis cuts
     the rest with three dots. */
  font-family: Helvetica, Arial, sans-serif;
  font-weight: normal;
  font-size: 17px;
  line-height: 30px;
  height: 30px;
  padding: 0;
  margin: 0;
  display: block;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  width: 100%;
}
/* Numerals keep the display size; they are short and never overflow. */
.file-header li:not(.wide) p {
  font-size: 28px;
  font-weight: bold;
}
.file-header li p .stat-sub {
  /* Em-relative so the page indicator's " / 22" scales with the shrink. */
  font-size: 0.85em;
  font-weight: normal;
  color: #707090;
}
.file-header li strong {
  line-height: 11px;
  height: 11px;
  display: block;
  margin-top: 6px;
}

/* Links inside the value row: the author pill. */
.file-header li.artist p a {
  color: #636483;
  text-decoration: none;
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: bottom;
}
.file-header li.artist p a:hover { text-decoration: none; }

/* The work's own title, a separate centred line BELOW the strip (moved from
   above, per user). Plain text, not a link: the author pill keeps the link
   role. Omitted when untitled. */
.file-title {
  color: #636483;
  font: bold 15px Arial, sans-serif;
  text-align: center;
  margin: 0;
  padding: 0 0 6px;
}

/* #historyList p#allTags — the tag links, now inside the header's tag box rather
   than a separate strip below it (the legacy keeps #historyList display:none and
   reveals tags from the "Tags (N)" box in the header).

   The summary mimics #fileHeader li a (file.css:55-83): a 12px-radius block that
   fills #8d91a7 and turns its text white on hover. The native disclosure triangle
   is suppressed in every engine — list-style alone leaves it on Safari/Firefox. */
/* The tag pill fills its li completely (the hover background and the opened
   panel both anchor here): the details block stretches to the li's box, so the
   gray hover paints the FULL pill width, not a shrink-wrapped strip.
   Static, NOT relative: the opened panel anchors to the li itself (see the
   .tag-list rule) because stretching this box to 100% broke the panel's
   percentage/max-width resolution (bubble collapsed to the pill's 115px). */
.tag-box { display: block; width: 100%; }
.tag-box > summary {
  list-style: none;
  cursor: pointer;
  display: block;
  border-radius: 12px;
  /* The tint overhangs the pill by 6px on ALL FOUR sides, symmetrically, in
     EVERY state (rest/hover/open). One coherent mechanism ONLY: the box is
     the pill's size PLUS 12px in both dimensions (width/height calc), pulled
     back onto center with -6px margins on every side. No per-state geometry
     overrides — hover/open rules carry colour only.
     (An earlier mix of width+12px with partial margins doubled the bottom
     gap and left the right gap at zero — reported.) */
  box-sizing: border-box;
  padding: 6px;
  margin: -6px;
  width: calc(100% + 12px);
  height: calc(100% + 12px);
  transition: background-color 0.1s ease-in-out;
}
.tag-box > summary::-webkit-details-marker,
.tag-box > summary::marker { display: none; content: ""; }
.tag-box > summary:hover {
  /* Color only: geometry (padding/margins/width) is owned entirely by the
     base .tag-box > summary rule so the painted box is IDENTICAL in rest,
     hover and open states — restating it here made the box grow 6px at the
     bottom on hover (reported). */
  color: #fff;
  background-color: #8d91a7;
}
.tag-box > summary:hover p,
.tag-box > summary:hover strong { color: #fff; }
/* OPEN keeps the same tint as hover (reported: clicking away dropped the hover
   tint while the bubble stayed open — the pill went blank). The pill reads
   active for as long as the panel is open, like a pressed button. */
.tag-box[open] > summary {
  /* Color only — see the :hover rule above for why geometry is not restated. */
  color: #fff;
  background-color: #8d91a7;
}
.tag-box[open] > summary p,
.tag-box[open] > summary strong { color: #fff; }
/* The Author pill gets the SAME active tint (per user): hover on the pill
   darkens it exactly like the tag summary, with the same margin-compensated
   padding so the strip height never inflates. */
/* The Author pill carries its 6px breathing padding in the RESTING state too
   (per user: the padding should exist for both normal and active), so the
   hover tint paints over an identical box — no width change when it kicks
   in. Border-box padding plus matching negative margins keep the strip
   geometry byte-identical. */
.file-header li.artist {
  border-radius: 12px;
  box-sizing: border-box;
  padding: 6px;
  margin: -6px;
}
.file-header li.artist:hover {
  background-color: #8d91a7;
}
.file-header li.artist:hover p a,
.file-header li.artist:hover strong { color: #fff; }
.file-header li.artist:hover p a { text-decoration: none; }

/* "Other" (unattributed) renders as plain muted text, not a link -- no artist
   page exists for it, and the dimmer tone signals "not an artist". */
.file-header li.artist p .pill-other {
  color: #636483;
  opacity: 0.6;
}

/* The whole artist pill is one link: the anchor fills the li, so the Artist
   label is part of the click target too. Layout mirrors the other pills
   (block anchor, inherited geometry from li.artist's padding rule). */
.file-header li.artist > a.pill-link {
  display: block;
  color: inherit;
  text-decoration: none;
}
.file-header li.artist > a.pill-link p,
.file-header li.artist > a.pill-link strong {
  color: #636483;
}
/* Hover: same white-on-tint treatment as the tag pills (li.artist:hover rules
   above already flip the li background; the link children follow). */
.file-header li.artist:hover > a.pill-link p,
.file-header li.artist:hover > a.pill-link strong {
  color: #fff;
}
.tag-box > summary p { margin-bottom: 0; }
.tag-box > summary strong { line-height: 11px; display: block; margin-top: 6px; }

/* The opened panel, styled after the "Current" pill (.file-same .same-info b):
   dark #6e7285 ground, white text, rounded corners — larger than the pill because
   it holds the whole tag list.

   NOTE: anchors to .tag-box (position:relative above), which spans the whole
   pill; left:0 aligns the bubble's edge with the pill's left edge. */
/* Anchors to the pill's LI (position:relative on .stats li): left:0 aligns
   the bubble's edge with the pill's left edge, top below the whole li. The li
   is the stable full-width box; anchoring to the stretched .tag-box broke the
   panel's max-width resolution (measured: bubble collapsed to 115px). */
.stats li:has(> .tag-box) { position: relative; }
.tag-box .tag-list {
  position: absolute;
  /* Starts at the same line the strip ENDS on (per user): the pill's bottom
     edge plus the band's residual 12px bottom padding + 1px border — measured
     13px — lands the bubble's rim exactly on the band's lower border. */
  top: calc(100% + 13px);
  /* -6px (per user): aligns the bubble's rim with the summary's tinted
     overhang box, which also starts 6px left of the pill's rim. */
  left: -6px;
  /* Scales with the viewport (per user): the box sizes to its content, but
     BOTH caps narrow as the panel narrows — max-content sizes it up, the two
     caps fence it in: 948px at full band width, calc() tapering with 100vw
     below that. (A single min(max-content, calc()) is INVALID at
     computed-value time and silently collapsed the box to the pill's 115px —
     measured — the malformed-declaration trap again.) */
  width: max-content;
  max-width: min(948px, calc(100vw - 2 * var(--band-inset) - 12px));
  background-color: #6e7285;
  border-radius: 12px;
  padding: 8px 14px;
  text-align: left;
  z-index: 60;
  box-sizing: border-box;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
  /* Restore the header's type size: .file-header sets 11px, and the panel inherits
     it, which made the tags noticeably smaller than the rest of the page. */
  font-size: 13px;
}
.tag-box .tag-list a {
  color: #eee;
  font-weight: bold;
  line-height: 22px;
  display: inline;
}
/* Separators are their own elements, not ::after on the anchor: inside the link
   the comma was clickable and highlighted on hover. */
.tag-box .tag-list .tag-sep {
  color: #c8cad8;
  font-weight: normal;
}
.tag-box .tag-list a:hover { color: #fff; }

/* #file #fileItem (file.css:405-411) plus the a.fileShow mat (file.css:421-433).
   The image sits inside a #bfc0d1 frame with 3px padding and a 1px border.

   The horizontal margin matches the page's vertical rhythm (the header lifts -9px,
   the strip sits -9px), so the image has the same breathing room on all four
   sides. The width is left to fill the remaining space: a fixed 943px inside a
   948px content box left 5px of slack on the right only, so the two sides did not
   match. */
/* The 9px gutter on all four sides. The image is centred inside this box, so the
   mat shrink-wraps it and the gap stays equal whichever way the image is shaped. */
/* The 9px gutter on all four sides. min-width:0 lets the box shrink below the
   image's intrinsic width so the cap below actually applies. */
/* inline-block so the MAT shrinks to the image; inline-block here too so the ITEM
   shrinks to the mat. A plain block keeps the full column width at every viewport,
   which is what opened the widening black gaps beside narrow artwork as the panel
   narrowed: the item held its width while the mat shrank to the image.

   .file centres the item (text-align below) and the item centres the mat, so a
   shrink-wrapped item still sits mid-column at every width. */
.file {
  text-align: center;
}
/* Block-level and full-width, as in the legacy wide view: the ITEM spans .file
   and the MAT inside keeps its fixed 8px-per-side reserve (see .file-show's
   max-width below), so the artwork's gap to the panel border is constant instead
   of the column collapsing asymmetrically. The mat is inline-block and centred
   by the inherited text-align, so narrower artwork simply centres with equal
   gaps. (An earlier inline-block item shrink-wrapped to the mat, which traded the
   fixed border gap for a floating item.) */
.file-item {
  text-align: center;
  min-width: 0;
  display: block;
  width: auto;
  max-width: none;
  /* The item tracks the BANDS, not a constant: legacy has the 960px bands inset
     30px and the content column 30px + 6px padding, so the artwork sits only 6px
     further out than the band. Holding a flat 36px instead left 36px of lavender
     beside the full-bleed bands below 960 (vs 6px above 1020) -- visibly too
     large. band-inset tapers with the viewport exactly as the bands do, so the
     relationship (band + 6) survives at every width: 36px at the full panel,
     6px once the bands reach the panel edge. */
  margin: 0 calc(var(--band-inset) + 6px - var(--content-inset));
}

/* inline-block so the mat takes the IMAGE's size, not the container's. max-width
   is relative to .file-item, so the mat (image + 8px of padding and borders) can
   never spill outside it. */
.file-show {
  background-color: #bfc0d1;
  border: 1px solid #b4b5c5;
  padding: 3px;
  display: inline-block;
  /* content-box, so this 100% is the CONTENT width and the 3px padding plus 1px
     border on each side are added outside it. That is why the stage below reserves
     8px per side rather than relying on this cap. */
  box-sizing: content-box;
  max-width: calc(100% - 16px);
  text-align: left;
}

/* Native resolution only: never enlarged. max-width is measured against the mat's
   content box, which is itself capped at .file-item's width. */
.file-show img,
.file-show video {
  display: block;
  max-width: 100%;
  max-height: 933px;
  width: auto;
  height: auto;
  border: 1px solid #c5c6d7;
  background: #6e799f;
}

/* ---- Flash ---------------------------------------------------------------
   The container carries the movie's real aspect ratio as an inline style, read
   from the SWF header at build time. Until Ruffle is booted it holds a button;
   afterwards the <ruffle-player> fills the same box, so the page never jumps.
   The poster comes in as an inline background-image from worker/render.js; the mat
   underneath it is #6e799f so letterboxing around a differently-shaped poster still
   reads as a stage rather than as a gap. */

/* The flash stage, wrapped in the same .file-show mat a picture gets: #bfc0d1 with a
   3px pad and a #b4b5c5 edge.

   Ruffle paints the movie's own background colour over the whole stage, and many of
   these SWFs declare white -- so the poster and the mat are both hidden the moment the
   player mounts, leaving a white rectangle with an orange button on it. `mix-blend-mode:
   multiply` composites the stage over what is behind it instead of replacing it, so
   white areas let the poster through while the artwork itself still draws normally.
   This only works because the stage is transparent to begin with; Ruffle's canvas is. */
.ruffle-stage {
  position: relative;
  /* Native width, set inline per movie from the SWF header (worker/render.js); the
     height is derived from the aspect-ratio that travels with it.

     Why a definite width: the stage is an empty div -- the player inside is
     absolutely positioned, so it contributes no size. A box with no width collapses
     to its 2px border (measured: 940x2), and aspect-ratio only derives the axis that
     is MISSING, so one dimension has to be given. Taking it from the movie itself is
     what removed the hardcoded width that used to live here.

     The mat reserves the mat's own furniture (see .file-show above), so this only
     has to cap the stage at the space left inside it. */
  max-width: 100%;
  background-color: #6e799f;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  border: 1px solid #c5c6d7;
  overflow: hidden;
  isolation: isolate;
}

/* Play is ours, not Ruffle's: a small circle in the site's green accent (#c2e756)
   over the poster, so the stage shows artwork until the visitor asks for motion. The
   player is only mounted on click, which also keeps ~2 MB of WASM off every flash
   page. Ruffle's own button is suppressed by autoplay "on" once we do mount. */
.ruffle-play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 84px;
  height: 84px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: url("/img/ui/play.svg") no-repeat center / contain;
  cursor: pointer;
  /* The poster behind can be any brightness; the SVG carries its own dark disc for
     contrast, so no backdrop-filter is needed. */
  transition: transform 120ms ease, filter 120ms ease;
}

.ruffle-play:hover {
  transform: translate(-50%, -50%) scale(1.08);
  filter: brightness(1.15);
}

.ruffle-play:active {
  transform: translate(-50%, -50%) scale(0.97);
}

.ruffle-play:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

/* Once the player is mounted the button is gone; the player fills the stage. */
.ruffle-stage ruffle-player {
  display: block;
  width: 100%;
  height: 100%;
}

/* Play is Ruffle's own orange-circle button, drawn inside its shadow DOM. There is
   deliberately no button of ours: a look-alike would drift from Ruffle's artwork and
   would leave two controls on screen. */

/* Shown only when Ruffle rejects the movie, which happens for a minority of the
   older ActionScript 1/2 files. */
.ruffle-error {
  margin: 0;
  font: 13px "Source Sans Pro", Helvetica, Arial, sans-serif;
  color: #4a4763;
}

/* ---- #fileSame: strip of neighbouring thumbnails ------------------------
   file.css:451-569. A band in #d3d3e3 with 73px tiles, the current one filled
   #8e92a1 and labelled with a pill above it. */

.file-same {
  /* Mirrors .file-header exactly, inverted: same 960px cap, same 100vw-based width
     and the same --band-inset/-content-inset margins, with bottom:-9px instead of
     top:-9px. That is the whole trick — the two bands share one geometry so they
     land identically at every width. (An earlier copy diverged by a -20px width
     adjustment and a stray `margin: 0` that overrode the calc margins; both gone.)

     Appearance lives here; the horizontal padding belongs to the <ul> so the
     tiles are inset from the band's edges. */
  max-width: 960px;
  width: calc(100vw - 2 * var(--band-inset));
  margin-left: calc(var(--band-inset) - var(--content-inset));
  margin-right: calc(var(--band-inset) - var(--content-inset));
  position: relative;
  bottom: -9px;
  box-sizing: border-box;
  background-color: #d3d3e3;
  /* Top border only: the band sits against the panel's rounded bottom edge, so a
     bottom border would cut across the curve. The radius matches .file-header's
     9px, mirrored to the bottom corners. */
  border-top: 1px solid #aeb2c1;
  border-radius: 0 0 9px 9px;
  padding: 10px 0;
  /* Clip the tile strip at the band's edge: the ul is wider than the band on every
     width where all eleven tiles cannot fit, and without this the strip pushes the
     page sideways. The ul below becomes scrollable, so the cut tiles remain
     reachable by swiping. */
  overflow: hidden;
}

/* The list carries no appearance beyond the horizontal inset: it is just the
   flex row of tiles.

   space-between pins the first tile's left edge and the last tile's right edge to the
   padded box, so the strip lines up with .file-header and .file-item above it.

   That only holds for a FULL strip. space-between distributes whatever is left over,
   so with 11 tiles the 3px of slack becomes 7px gaps, but with 5 tiles the ~700px
   becomes 130px gaps and the row visibly falls apart. The :has() rule below applies
   the justification only when the eleventh tile exists; a shorter strip keeps the
   plain 7px gap and packs from the left, which is what a partial row should look
   like.

   The 7px gap is the figure that makes 11 tiles fit: 11 x 77 + 10 x 7 = 917 inside
   the 940px content box, leaving 3px that space-between absorbs. */
.file-same ul {
  list-style: none;
  margin: 0;
  padding: 0 10px;
  display: flex;
  flex-wrap: nowrap;
  gap: 7px;
  /* Horizontally scrollable without a scrollbar. Tiles that do not fit are clipped
     by the band's overflow:hidden; swipe (or drag) scrolls them into view. Keeping
     the gap fixed also means justify-content:space-between never stretches it:
     with an overflow the flex line is longer than the box, so there is no leftover
     space to distribute in the first place. */
  overflow-x: auto;
  scrollbar-width: none;
}
.file-same ul::-webkit-scrollbar {
  display: none;
}

/* A full strip: pin both ends. Fewer tiles: leave the gap alone. */
.file-same ul:has(> li:nth-child(11)) {
  justify-content: space-between;
}

.file-same li {
  width: 73px;
  height: 73px;
  text-align: center;
  background-color: #bfc0d1;
  border: 1px solid #b4b5c5;
  padding: 2px;
  margin: 0;
  box-sizing: content-box;
  position: relative;
  flex: 0 0 auto;
}
.file-same li:hover {
  background-color: #d7eb8d;
  border-color: #c4d781;
}
.file-same li.current {
  /* Accent-marked instead of the legacy "Current" pill: the pill floated at
     top:-20px above the tile (fragile positioning) and duplicated what a border
     can say quietly. Hover is the soft accent (#d7eb8d); current is the full
     --accent (#c2e756), so the two read as one vocabulary — soft on hover,
     strong when it IS this page. */
  background-color: var(--accent);
  border: 2px solid #9db537;
  padding: 1px;
}

.file-same li a {
  display: block;
  width: 71px;
  height: 71px;
  border: 1px solid #c5c6d7;
  position: relative;
}
.file-same li.current a {
  border-color: #fdfee9;
}
/* De-emphasise the current THUMBNAIL: it is the one thing in the strip that is
   NOT clickable, so muting it to greyscale reads as "you are here" while the
   colourful tiles invite clicks. Weaker settings verified imperceptible on
   73px thumbnails; full grayscale plus dimming is unmissable. */
.file-same li.current img {
  filter: grayscale(1) brightness(0.65);
  /* Smooth the transition if the current ever changes client-side. */
  transition: filter 0.15s ease-out;
}

/* (Retired 2026-11 with .card-flash above: the strip always emits <img>s now,
   and a failed one is swapped by img-fallback.js like any other.) */

.file-same li img {
  display: block;
  width: 71px;
  height: 71px;
  object-fit: cover;
}

/* The "Current" marker is retired: the tile is accent-bordered (see li.current)
   and needs no floating label. The .same-info span is no longer emitted by
   nearbyHtml; the rule stays only so cached Worker responses predating the
   change do not render a bare-text leftover. */
.file-same .same-info {
  display: none;
}
.file-same .same-info b {
  color: #eee;
  font-size: 11px;
  font-weight: bold;
  background-color: #6e7285;
  border-radius: 8px;
  padding: 1px 8px;
  display: inline-block;
}

/* ---- fixed browse arrows ------------------------------------------------
   file.css:690-717. 73x83 sprites pinned to the viewport corners, swapping to
   the right-hand crop on hover. */

.browse-prev,
.browse-next {
  width: 73px;
  height: 83px;
  display: block;
  position: fixed;
  bottom: 5px;
  z-index: 50;
  background-repeat: no-repeat;
  background-position: left center;
}
.browse-prev { left: 5px; background-image: url("/img/ui/paging-prev.png"); }
.browse-next { right: 5px; background-image: url("/img/ui/paging-next.png"); }
.browse-prev:hover,
.browse-next:hover { background-position: right center; }

/* Labels are for screen readers only; the sprite carries the arrow. */
.browse-prev span,
.browse-next span {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Comic page navigation keeps the shared pager look. */
.file .pager { margin-top: 12px; }

/* file.css — tags are bold comma-separated links on the band, not pills */
.tags {
  background: var(--band);
  border-top: 1px solid var(--band-border);
  border-bottom: 1px solid var(--band-border);
  padding: 5px 10px;
  /* Same bleed taper as .grid and .tag-cloud: -6px easing to 0 at 960px, so the
     strip follows the narrowing column instead of crossing it. */
  margin: 0 calc(-6px * var(--header-collapse));
  font-size: 12px;
  line-height: 24px;
}
.tags a {
  color: #79799a;
  font-weight: bold;
}
.tags a:hover { color: var(--link-hover); }
.tags a::after { content: ","; color: #a0a0c0; font-weight: normal; }
.tags a:last-child::after { content: ""; }

/* ---- author index ------------------------------------------------------- */

.author-list { columns: 3 200px; column-gap: 24px; }
.author-list h2 {
  font: normal 20px Arial, Helvetica, sans-serif;
  color: var(--heading);
  margin: 16px 0 6px;
  break-after: avoid;
}
.author-list a { display: block; font-size: 12px; break-inside: avoid; color: var(--link); }
.author-list span { color: var(--panel-muted); font-size: 11px; }

/* ---- footer -------------------------------------------------------------
   layout.css:594-695. The dark sprite sits on the page ground rather than on a
   solid fill, so it blends with the background instead of forming a band.
   #footBottom is 960px: copyright left, e-mail right, both #7b7b82. */

.site-footer {
  /* No --chrome fill: the sprite is drawn over the page ground, as in the
     original, where #mastfoot only sets a background image. */
  background: transparent url("/img/ui/footer.png") center top repeat-x;
  color: var(--chrome-text);
  font-size: 11px;
  line-height: 15px;
  padding-bottom: 5px;
  margin: 0 auto;
  /* Full width, not shrink-to-fit: body is a flex column, and without this the
     footer would hug its 960px .inner and the sprite would stop short of the
     viewport edges. */
  width: 100%;
  /* Push to the bottom of the viewport when the page is shorter than the screen.
     On a long page there is no free space to absorb, so this has no effect and the
     footer follows the content as before. */
  margin-top: auto;
}

/* layout.css:604-613 — #footBottom is width:960px with padding:0 10px, so the
   CONTENT box is 960 and the padding sits outside it. Using max-width:970 with the
   same padding made the content 950 and shifted everything 5px inward. */
.site-footer > .inner {
  width: 960px;
  max-width: 100%;
  margin: 0 auto;
  padding: 0 10px 15px;
  box-sizing: content-box;
  font-family: "Trebuchet MS", Helvetica, Arial, sans-serif;
  text-shadow: 0 1px 3px #161616;
  display: flex;
  align-items: baseline;
  /* No space-between: the legacy floats sit adjacent and the leftover width falls
     after the last block. Distributing it between items opened ~105px gaps. */
  justify-content: flex-start;
  gap: 0;
  flex-wrap: wrap;
}

.site-footer .logo {
  /* layout.css:651 — 90x30 crop of sprite-set.png at -152px -367px, the miniature
     wordmark that sits to the left of the copyright line. The text inside is
     hidden; the sprite carries the artwork. */
  width: 90px;
  height: 30px;
  margin: 0;
  padding: 0;
  flex: 0 0 auto;
  background: url("/img/ui/sprite-set.png") -152px -367px no-repeat;
  align-self: flex-start;
}
.site-footer .logo span { display: none; }

.site-footer .copyright {
  color: #7b7b82;
  font-size: 12px;
  padding: 0 5px;
  /* layout.css:675 — the original used padding-top: 12px; margin here so the flex
     row keeps its baseline alignment. */
  margin: 12px 0 0;
  /* layout.css:670 — fixed 300px block, not a flexible one */
  flex: 0 0 300px;
}

.site-footer .contact {
  color: #7b7b82;
  font-size: 10px;
  padding: 0 5px;
  margin: 12px 0 0;
  text-align: right;
  /* layout.css:683 — fixed 350px block, pushed to the right edge as the original
     float: right did. flex-basis 350 preserved, but SHRINKABLE: the rigid trio
     (logo 90 + copyright 300 + contact 350) totals 650+padding and forced the
     e-mail onto a second line from ~759px down, even though free space sat
     beside the copyright. text-align keeps the address glued to the right edge
     as it narrows; it only wraps when genuinely out of room. */
  /* layout.css:683 — fixed 350px block, pushed to the right edge as the original
     float: right did. Rebuilt responsively: basis AUTO (content width) rather than
     350, because with flex-wrap:wrap an item WRAPS before it ever shrinks — the
     flex algorithm breaks the line at the item's hypothetical size, so a 350px
     basis put the e-mail on row 2 from 759px down while ~180px of free space sat
     beside the copyright (measured). Auto basis keeps the hypothetical size at
     the text's own width; margin-left:auto still pins it to the right edge, so
     the rendered look at full width is unchanged from the legacy. */
  flex: 0 1 auto;
  min-width: 0;
  margin-left: auto;
}
.site-footer .contact a { color: #7b7b82; }
.site-footer .contact a:hover { color: var(--accent); }

/* ---- small screens ------------------------------------------------------ */

/* The cap sprites keep their BASE geometry at every width: body-borders.png is
   1020px of drawn art (rails plus cap), CENTER-positioned, so below the panel
   width the element simply crops it symmetrically — the same visual result as
   the legacy 1020px page on a smaller screen. The earlier experiment scaled the
   art to the element (calc(100% - 2*air)) which COMPRESSED the drawing and
   misaligned it from .body-mid's trimmed sprite (reported: "incorrect width
   between 980 and 700"). Centered + cropped is stable and correct at all
   widths; the ≤700 block flattens backgrounds entirely, as before. */
@media (max-width: 979px) {
  /* The footer's .inner is width:960 content-box (so 980 gross with its padding):
     the overflow starts at 980 — below the PANEL width but above the 950 block
     where the width:auto fix used to live — dragging the page sideways (measured
     980px pages in 951–979px viewports). Border-box width:auto follows the
     viewport at every narrower width. */
  .site-footer > .inner {
    width: auto;
    box-sizing: border-box;
  }
}

/* The panel furniture (caps, rails) dies at 950px: the bar now spans the full
   viewport and the rounded top corners become artifacts. Kill them here; the
   ≤700 block below removes the lift and the grid's bottom bleed when the
   backgrounds vanish entirely. */
@media (max-width: 950px) {
  .sections {
    border-top-left-radius: 0;
    border-top-right-radius: 0;
  }
  /* The footer .inner fix lives in the 979 block above — it overflows from
     980 down, so fixing it only at 950 left a drag between 950 and 980. */
  /* The rounded caps (body-borders.png) are drawn art, not CSS, and they persist
     down to 700 — appearing as rounded corners belonging to a design that has
     already squared off (everything else drops its radii at this boundary).
     Only the SPRITE goes: the elements keep their 14px/20px heights, because
     .body-top's height IS the gap between the header and the panel (removing the
     element closed that gap, reported). */
  /* The caps' geometry fix lives in the 979 block above: their background-size/
     position match .body-mid's --panel-side-air formula below 980, so the cap
     art stays ALIGNED with the scaling rail sprite instead of desyncing — no
     ghost line, and the sprite is kept rather than flattened. Heights stay
     (14px/20px): .body-top's height IS the header-to-panel gap. */
  /* Everything that bled sideways or downward to meet the CAP SPRITE has to stop:
     the cap's drawn corners are gone, so the bleeds only push content past the
     viewport (measured: .main-search-tags/.home-picks-band/.grid at L=-6 R=+6,
     a 772px page in a 764px viewport). Square, flush, no bleed.
     .home-picks-band is exempt from BOTH lists (reported: its gap before
     .body-bottom and the random-pick band's spacing to the grid above both
     regressed below 950) — it keeps its base -9px bleed and -6px side margins
     at every width, exactly like the trailing tag grid keeps its own. */
  .main-search-tags {
    margin-left: 0;
    margin-right: 0;
  }
  /* Home picks band: NO flattening here. It behaves exactly like the trailing
     tag grid (same treatment, per user "essentially the same thing as .grid"):
     the base rule's -9px bottom bleed and -6px side margins stand at EVERY
     width — they overlap the transparent cap area harmlessly where the sprite
     is retired, and zeroing them here caused the wrong behavior at 950–1020. */
  /* TRAILING grids (no pager below them — small tag pages, homepage picks):
     removed from this block — their square/radius treatment now lives in the
     dedicated 942px block below (reported: “for 942px and below, all
     non-paged .grid should have bottom border radius removed, as they are
     stretched to full width”). Above 942 the base radii stand. */
}

@media (max-width: 942px) {
  /* All NON-PAGED grids are full-width at these sizes, so the bottom radius
     that curves them into the cap sprite no longer corresponds to anything —
     the cap art is gone at this range and rounding reads as a glitch on a
     straight edge (reported). Paged grids keep the radius: the pager below
     still forms the rounded ensemble. Non-paged = the grid is the last grid in
     .content (small tag pages, homepage picks, the /t index). */
  /* Specificity: the base non-paded rule is .content > .grid:last-child
     (0-2-2), so the media selector must match it exactly or it loses the
     cascade regardless of order. Same prefixing as the 700px block's twin. */
  .content > .grid:last-child,
  .content > .grid.is-trailing {
    border-radius: 0;
  }
  /* The home picks band rounds its own corners (0 0 9px 9px) independently of
     the grid, so the rules above do not touch it — squared here too. */
  .home-picks-band {
    border-radius: 0;
  }
  /* .file-same (the "same artist" tile strip on detail pages) carries the
     same 9px bottom radius and shows the same artefact at full width (reported:
     "exactly the same issue with rounded borders at the same breakpoints").
     Squared alongside the grids and picks band. Radius only at this range;
     the vertical bleed is widened in the phone block below. */
  .file-same {
    border-radius: 0;
  }
}

@media (max-width: 700px) {
  /* The sections bar keeps its 9px LIFT from the base rule even on phones:
     flattening it to top:0 glued the bar flush against the first grid/pager
     below (measured gap 0px vs 9px at 701+, reported as "a gap on .sections
     and .grid when below 700px" — the visible symptom was the two touching).
     There is no cap sprite left to lift into, but the offset preserves the
     spacing relationship. The grid's bottom bleed still goes: no cap to curve
     into. */
  .grid:last-child,
  .grid.is-trailing {
    border-radius: 0;
  }
  /* Same specificity requirement as the 942px block above. */
  .content > .grid:last-child,
  .content > .grid.is-trailing {
    border-radius: 0;
  }
  /* .file-same and .home-picks-band: phone cap is 10px tall, so the bands
     bleed the FULL 10px (base -9 leaves a 1px --panel sliver at the bottom,
     reported twice). */
  .file-same,
  .home-picks-band {
    border-radius: 0;
    bottom: -10px;
  }
  /* The 1020px panel furniture (rails, caps) is dropped on phones: the side rails
     would eat 60px of a 320px viewport, and the caps are decorative. */
  .body-top,
  .body-bottom,
  .body-mid {
    /* The SPRITE art goes on phones, but NOT the panel colour: with
       background:none the caps' 14/20px strips turned TRANSPARENT, showing the
       dark page background — the panel bottom visually vanished (reported:
       "bottom gap disappears when below 700"). Solid panel colour keeps the
       panel a continuous block edge-to-edge; only the drawn art is lost. */
    background-color: var(--panel);
    background-image: none;
    max-width: none;
  }
  /* Below 700 the border sprites are GONE, so the space they reserved in the
     elements' heights goes too (user-tested: 10px instead of 20px for the
     bottom cap; 9px instead of 14px for the top). What remains is the portion
     that IS real spacing — the earlier 0px glue-overlap measurements happened
     only when the FULL height went, taking the header-to-panel gap with it. */
  .body-top { height: 9px; }
  .body-bottom { height: 10px; }

  .content {
    margin: 0;
    /* No TOP padding: the sections bar is meant to sit flush against the panel's
       top edge (the desktop rule above says the same), and 12px here showed as a
       gap above the bar (reported). The bar's own -9px lift then provides the
       spacing to the first child, exactly as at 701+. Bottom padding likewise
       goes: the last .grid already carries a 16px bottom margin, and stacking
       both produced a doubled gap below the grid (reported). */
    padding: 0 14px;
    min-height: 0;
  }
  /* Grid's bottom margin stays 8px like at 701+: the phone block's 16px DOUBLED
     the grid→pager gap at the 700 crossing (measured 16 vs 8, reported "the
     bottom margin of .grid getting larger"). The side bleeds (-14px) stay —
     that is the full-bleed behaviour, separate from vertical rhythm. */
  .grid { margin: 0 -14px 8px; gap: 14px 6px; }
  .tags { margin: 0 -14px; }
  /* The tag cloud is a BAND like .grid (same background, same border family),
     so it bleeds out of .content's phone padding the same way instead of
     sitting 14px in from the frame while the grid touches it. The desktop
     taper's margin nets to 0 here, so the phone block owns the bleed. */
  .tag-cloud { margin: 0 -14px 12px; }
  /* The tag INDEX is text, not a band: keep a readable margin. On phones
     .content supplies 14px of side padding; 6px more gives the same 20px of
     breathing room the wide view has, matching the detail-page item's inset. */
  .tag-index { padding: 0 6px; }
  /* The detail-page bands have no desktop clamp left at this width (their
     --band-inset/--content-inset based margins are 0 below 960), but .content's
     14px side padding insets them like everything else. Pull them back out the
     same way .grid/.tags do, so they go full-bleed on phones too. Without this
     the bands sat inset 14px AND overflowed 14px on the right — half in, half
     out of the viewport. */
  .file-header,
  .file-same,
  .tag-band {
    margin-left: -14px;
    margin-right: -14px;
    width: auto;
  }
  /* .sections shares the bands' viewport-based width formula, so it needs the
     same -14px pull out of .content's phone padding — without it the bar sat
     inset 14px left and overflowed 14px right (measured 713 in a 699 viewport). */
  .sections {
    margin-left: -14px;
    margin-right: -14px;
    width: auto;
    max-width: none;
  }

  .site-header > .inner {
    min-height: 0;
    padding: 0;
    background: none;
  }
  /* On a phone the 1020px banner art is cropped and illegible, so the wordmark
     becomes live text again and both sprites are dropped. The 201px of air would
     also waste most of the viewport. */
  .site-header .banner {
    height: auto;
    padding: 10px 12px;
    background: none;
    justify-content: flex-start;
  }
  /* On a phone the wordmark is real text again, so the hit area must shrink back
     to it -- the desktop overlay would cover the whole banner. */
  .site-header .banner::after {
    display: none;
  }
  .site-header .banner .brand {
    position: static;
    width: auto;
    height: auto;
    font-size: 18px;
    color: var(--chrome-text);
    padding: 0;
  }
  /* Reset the absolute positioning: on a phone the banner is plain text and the
     nav must flow below it as a normal row. */
  .site-header nav {
    position: static;
    width: 100%;
    height: auto;
    padding: 0;
    /* Spread the cells across the FULL width instead of hugging the right
       edge (reported): the base rule's justify-content:flex-end survives into
       the phone layout, packing the row right. space-between divides the row
       evenly — first cell at the left edge, last at the right, the growing
       gaps (from flex:1 below) distributed between the five fixed-basis cells. */
    justify-content: space-between;
  }
  /* 44px tap targets, deliberately larger than the legacy 30px cells */
  .site-header nav a {
    min-width: 0;
    flex: 1 1 auto;
    height: 44px;
    line-height: 44px;
    font-size: 14px;
    padding: 0 8px;
  }
  h1 { font-size: 20px; }
  /* The 120px sprite buttons are too wide for a phone; drop the sprite and use
     plain text links with 44px tap targets. */
  /* The 120px sprite buttons and the fixed 699px slot do not fit a phone, so
     drop the floats and lay the three parts out as a simple row. */
  .pager {
    height: auto;
    overflow: visible;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    font-size: 12px;
    /* Net .content's 14px phone padding out but keep the pager's own 6px band
       padding as the inset: -14 (not the -20 tried first, which made the box
       itself 402px wide in a 390px viewport — the right padding stuck out).
       Buttons then sit 6px from the edge, matching the grid and sections bar. */
    margin: 0 -14px;
  }
  .pager a.pagingLeft,
  .pager b.pagingLeft,
  .pager a.pagingRight,
  .pager b.pagingRight {
    float: none;
    /* Full desktop look, sprite and all: the 120px buttons FIT at every phone
       width (2x120 + ~80 label = 320, the minimum viewport), and matching the
       desktop exactly removes the 700-boundary visual jump (reported:
       "can they look the same as above 700"). The earlier text-link fallback
       (30px rows, no sprite) predates that; the sprite crops come back with
       the desktop 27px metrics. */
    width: 120px;
    height: 27px;
    line-height: 27px;
    padding: 0;
    background: transparent url("/img/ui/sprite-set.png") -10px -406px no-repeat;
  }
  .pager a:active { background-position: -140px -406px; }
  .pager .pagesInfo {
    float: none;
    width: auto;
    height: 27px;
    line-height: 27px;
  }
}

/* ---- below the panel width ------------------------------------------------

   The panel is 1020px wide. Some of the furniture inside it was sized for exactly
   that and never shrinks: .file-header and .file-same are 960px content-box, and the
   nav's cells are a fixed 120px flex-basis inside an absolutely positioned strip.
   Measured on /p/JNJh9: both bands stayed 960px wide at every viewport from 1019
   down to 700 while their parent .content shrank normally, overflowing by up to
   268px and dragging the page sideways. The nav overlapped the wordmark hit area
   below about 910px for the same reason.

   This block is LAST in the file on purpose. A media query adds no specificity, so
   an override written above the rule it targets loses on source order alone.

   Deliberately out of scope: .file-header's two 230px stat blocks (absolutely
   positioned, need a design decision) and anything below 700px, where the phone
   block above takes over. */
@media (max-width: 1019px) {
  /* The banner artwork is 1020px wide and fixed; below the panel width the sprite is
     clipped by the viewport anyway, so the banner box follows the viewport instead
     of holding 1020px and letting the nav overlay spill. Same fluid behaviour as
     .body-mid. */
  .site-header .banner,
  .site-header > .inner {
    max-width: 100%;
  }

  /* Nothing steps here anymore: the column insets taper continuously via the custom
     properties at :root, so the content column tracks the narrowing panel without a
     media-query jump. This block remains only for the nav, which needs a hard pin
     once the wordmark and the cells can no longer share 1020px. */
  /* The nav's cells were a fixed 120px basis inside an absolutely positioned strip
     pinned only to the right edge, so they could not shrink and collided with the
     wordmark hit area below ~910px. Below the panel width, pin the strip's left edge
     past the wordmark (which ends at 55 + 242 px) and let the cells divide the room;
     above 1019px none of this applies, so the full-width design is untouched. */
  .site-header nav {
    /* Floor = the wordmark hit area's right edge (55c + 242) plus 5px breathing
       room, which at c=1 is the original 302px pin. Because the brand's own offset
       scales by the same c, the nav can never slide under the wordmark at any
       width: the gap tracks 5px from full panel down to the phone layout. */
    left: calc(247px + 55px * var(--header-collapse));
    /* nowrap: with wrap the flex algorithm breaks the line instead of shrinking the
       cells, so the strip went to two rows below ~847px (five 120px cells + the
       247px pin) while there was still room to compress. nowrap forces the shrink
       path, which is what the 0 1 120px basis below expects. */
    flex-wrap: nowrap;
  }

  .site-header nav a {
    /* Same basis as above the boundary (120px) with SHRINK allowed but no grow:
     * flex:1 1 auto instantly re-expanded the cells into the space the pin freed
     * (120 -> 143px in one step), throwing every centred label sideways exactly at
     * the 1020 crossing. With 0 1 120px nothing moves until the viewport actually
     * presses on the strip; then the cells contract smoothly down to min-width. */
    flex: 0 1 120px;
    width: auto;
    min-width: 76px;
    /* No padding change at this boundary: the base cells carry padding:0, and any
       injected padding here steps the labels sideways the moment the viewport
       crosses 1020. The cells are flex-centred, so they need no internal padding
       until the phone block's own 0 8px tap-target padding takes over. */
    padding: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* Nothing here for the sections bar's radii — that is handled at the 950px
     boundary below, where the bar first spans the full panel width. The grid's
     bottom bleed stays as-is above 700 too: the bottom cap is still drawn by
     body-borders.png there, and the curve belongs to it. */

  /* No tile-trimming rules here. An earlier cut hid tiles by data-d thresholds
     (978/667/495), but that made the strip -- and with it .body-mid -- jump each time
     a band fired. The strip is now clipped by .file-same's overflow:hidden and
     swipeable via the ul's overflow-x:auto, so all eleven tiles can stay in the DOM
     at every width: the band downscales continuously to the 700px phone layout and
     the strip is simply cut at the band's edge. */

  /* ---- listing furniture that was sized for the 940px bar --------------------

     All of this lives inside .sections (the 42px bar) or the listing content, and
     all of it carried fixed widths tuned for the full panel: the search form's
     220px input plus its label, the sort dropdown's nowrap labels, the tag cloud's
     row. None of it can overflow below the panel width because .sections and
     .content already track the viewport -- the fixed widths kept going, dragging
     the page sideways (measured: 927px page in a 900px viewport on /comics, 701px
     form in a 545px one). Capping to the available line makes each row wrap or
     clip within the bar instead. */
  /* Search form: nothing to override anymore — the base rule makes it a flex row
     whose input shrinks continuously, so the old width:auto flip (a one-step
     collapse at exactly 1020px) is gone. Keep only the furniture that still
     needs the bar's width cap. */
  .menu-box,
  .menu-box .menu-list {
    max-width: 100%;
  }
  .tag-cloud {
    /* No max-width here: the element bleeds past its containing block with
       negative margins (see the base rule), and capping at 100% undoes the bleed —
       the over-constrained box then pinned the cloud to the content box's left
       edge and broke its symmetry below 1020 (measured: 20/28 at 1000px). The
       tapered margins alone fit every width. */
  }
}

/* The max-width:1019px block above is last, so it overrides the phone block
   wherever both match (<=700px). Two of its rules must not reach phones:
   min-width:76 on the nav cells (they cannot shrink far enough in a <=700px
   row and dragged the page 14px wide) and the 4px-tapered cell padding (the
   phone block's own 0 8px tap-target padding is the right value there).
   Same-last-wins logic, one block further down. */
@media (max-width: 700px) {
  .site-header nav a {
    min-width: 0;
    padding: 0 8px;
  }
  /* On phones the bands are pulled out of .content's padding with -14px margins
     (see .file-header above), so they sit at the very edge. The item keeps the
     same relationship to them as on desktop -- band + 6px -- by adding the 6px
     back instead of cancelling the desktop margin entirely. */
  .file-item {
    margin: 0 -8px;
  }
  /* The sections bar's row (5 tabs + search/filter + sort pill) cannot fit a
     phone width, and with nowrap the whole line spilled past the bar's edge,
     dragging the page (measured: 650px page in a 545px viewport on /comics).
     The bar becomes a horizontal swipe strip, same technique as the neighbour
     strip: overflow-x scrolls, the scrollbar is hidden. Height is content-driven
     because the pulled-out bar rides on .content's padding box. */
  .sections {
    overflow-x: auto;
    scrollbar-width: none;
  }
  .sections::-webkit-scrollbar {
    display: none;
  }
  /* Open filter behaves EXACTLY like above 700: the dropdown stays absolutely
     positioned and overlays the content below. Two things make that possible
     despite the swipe strip's scroll clip: while the <details> is open the bar's
     clip is suspended (overflow:visible) AND the tabs become compressible with
     ellipsis so the row fits the viewport at that moment — a visible-overflow
     bar whose row still spills drags the page sideways (the old failure, a
     650px-wide page in 390px), and with the shrunk row there is nothing left
     to spill. Replaces the previous expand-in-place workaround. */
  /* While the <details> is open the bar's clip is suspended (overflow:visible)
     so the panel can escape the swipe strip. The row itself NO LONGER
     RE-FLOWS (reported: "content in the strip jumps slightly as the filter is
     opened"): the old compression rules — tabs turning ellipsis-flex, search
     collapsing to a 60px floor, the pill span capping — existed to fit a
     PANEL WIDER THAN THE PILL; the panel now opens exactly pill-wide and
     overlays it in place, so there is nothing to make room for and stripping
     the rules kills the jump. */
  .sections:has(.menu-box[open]) {
    overflow: visible;
  }
  /* Right-align the open dropdown to the BAR's right edge: the panel hangs
     from the pill's left edge above 700 (L+126), which at phone widths runs the
     panel past the viewport (L=304 + W=126 = 430 in a 390px viewport — the sole
     cause of the page dragging while open; the row itself fits). Anchoring
     right keeps the whole panel inside the bar no matter where the pill sits. */
  .sections:has(.menu-box[open]) .menu-list {
    left: auto;
    right: 0;
  }
  /* Tabs still need the swipe strip (nowrap labels won't fit), but the
     search input and filter pill now COMPRESS CONTINUOUSLY like they do above
     700: no frozen widths, no separate phone rules. The base input floors at
     110px, the pill at 90px; the label ellipsizes. Unified behaviour across
     the whole range. */
  /* Sort dropdown: EXACTLY the pill's width on phones (reported: opening the
     filter widened the panel beyond the select). Base sizing is content-driven
     (min-width:100% plus natural width), so long nowrap labels stretched it;
     width:100% pins it to the pill and the links below ellipsize. */
  .menu-box .menu-list {
    width: 100%;
    max-width: 100%;
  }
  .menu-box .menu-list a {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: block;
  }
}

@media (max-width: 540px) {
  /* Hide the "Search:" and "Filter:" labels below 540px — but ONLY where BOTH
     controls share the bar (comics pages, detected via .sections:has on the
     two groups; per user: single-control subpages have plenty of room and
     keep their label). */
  .sections:has(.sections-search):has(.filter-group) .sections-search label,
  .sections:has(.sections-search):has(.filter-group) .filter-group label {
    display: none;
  }
}
