/* discovery.css - how a reader FINDS something: the trending rail, the standings
 * board, the rankings list and the advanced search form.
 *
 * THE TWO BANDS ARE TWO QUESTIONS, and the paint says so. The rail is COVERS -
 * eight of them, wide, scrolling, ranked by velocity - and the board is ROWS,
 * five to a panel, each carrying the figure it was sorted on. They read as
 * different objects on purpose: they used to be one list rendered at two sizes,
 * and a reader who cannot tell them apart at a glance is being shown the same
 * answer twice. Nothing below may make the board look like a small rail.
 *
 * A FEATURE STYLESHEET, loaded by pages/home.html, pages/rankings.html and
 * pages/search.html. main.css is the foundation plus the shared components and
 * is not this feature's to edit, so everything the four surfaces need that
 * main.css does not already own lives here. It is loaded from the extra_css
 * block, i.e. after main.css, which is what lets .disc-board__panel and .adv
 * re-set the padding they inherit from .panel at the same specificity.
 *
 * WHAT IS BORROWED, NOT REDECLARED
 *   .panel        the box under a leaderboard column
 *   .scroll-x     the rail's native horizontal scrolling and snap type
 *   .cover-wrap   position + the inner hairline on every cover anchor
 *   .badges       the hot / new / 18+ pills, top-left on a cover
 *   .cover .cover-md .cover-sm     the images themselves
 *   .chapter-link .sr-only .meta   the shared text bits
 *   .card-rank + .sect + .chip     the rankings page is assembled entirely from
 *                                  components that already exist; the only rule
 *                                  it adds here is the list that holds the rows
 *
 * THE RULES FROM main.css STILL APPLY, and this file keeps them:
 *   1. No literal colours. Every value below is a token from tokens/<theme>.css,
 *      so the dark theme needs nothing here. A GRADIENT IS A COLOUR for the
 *      purposes of that rule: the five sweeps this file paints are --grad-*
 *      tokens, declared and contrast-measured in tokens/default.css, not
 *      linear-gradient() calls written here. A site that re-themes gets them
 *      re-derived; a re-check of the ratios happens in one file.
 *   3. Anything that lifts writes transform: var(--lift); every duration is one
 *      of the --dx-* tokens declared below, which is var(--t-fast)'s contract in
 *      this file's own namespace (it is not loaded on every page main.css is).
 *      main.css's prefers-reduced-motion block clamps every transition and every
 *      animation on the page, so honouring the preference costs this file one
 *      line. The rail's own scrolling is the one motion it cannot reach:
 *      discovery.js reads the media query itself and passes behavior:'auto'.
 *   4. Hover changes colour, border, shadow and --lift only - never a size.
 *   5. :visited before :hover on anything that tracks read state.
 *   6. Focus is main.css's one ring; nothing here overrides --ring, because
 *      every control in this file sits on a plain surface rather than inside a
 *      filled plane.
 *
 * WHERE THE GRADIENTS GO, AND WHY ONLY THERE
 * Four surfaces on these two pages earn one, and they are all either CHROME, a
 * PLATE carrying a white label, or a SCRIM over artwork nobody controls:
 *   .disc-band__icon   --grad-tile-brand, the band's own hue
 *   .disc-board__icon  --grad-tile-rank, likewise - the board sits under a rank
 *                      band, so its three tiles are the rank hue and not three
 *                      unrelated ones. The three panels do ask three different
 *                      questions, and the answer to that is their three GLYPHS
 *                      and their three labels, not three hues: the hue belongs
 *                      to the band, and three of them in one row would read as
 *                      three bands that happened to collide.
 *   .disc-seg          --grad-brand on the pill that is selected, which is the
 *                      only chip fill on either page
 *   .trend-card        --grad-cover-scrim under the numeral, --grad-glass under
 *                      the two pills
 *   .lb-row__num       --grad-rank-1/2/3, the podium
 * Nothing else. The row a title sits in (.lb-row), the panel behind it and the
 * rail's own background stay flat: a sweep behind running text changes the
 * contrast of that text as the eye travels along it.
 *
 * The badges that sit on cover ART cannot use a surface token - they are over an
 * unknown image - and --scrim alone is not enough: at .62 over white artwork
 * white text is 5.03:1 with nothing spare for a translucent numeral. Both scrim
 * tokens paint it twice, compositing to .86 (11.68:1 in light, 17.93:1 in dark),
 * which is why they are two-layer values.
 *
 * MOTION. Two moving parts, both transform-and-opacity only, so neither can
 * cost a layout pass on the phones this rail is built for:
 * * the period control's plate SLIDES between Today / This week / This month on
 *   a single pseudo element, driven by two custom properties js/discovery.js
 *   measures off the chosen pill. Animating left and width instead would relay
 *   out the band header for the whole 220ms.
 * * the arrows FADE in when the rail turns out to overflow, and every control
 *   here answers a press with a 1px translate.
 * main.css's prefers-reduced-motion block clamps every duration below to one
 * frame; the block at the foot of this file takes the press travel out, which
 * is the one thing a duration cannot answer.
 */


/* ================================================================ MOTION === */

/* This file's own motion values. They are not the --m-* set in
   features/motion.css and not the --ix-* set in features/interactions.css: all
   three stylesheets ship on different pages, so each one names its own and no
   load order can decide which definition of "an entrance" wins. */
:root {
  --dx-fast: 150ms;
  --dx-in: 220ms;
  --dx-ease: cubic-bezier(.2, .6, .2, 1);
  --dx-press: translateY(1px);
  /* The same press as a bare LENGTH, for the rail arrows: they are already
     centred with translateY(-50%), so the press has to be added inside that
     calc - a second transform function would replace the centring, and
     substituting `none` into a calc would invalidate the declaration. */
  --dx-press-y: 1px;
}

/* NO `to` FRAME ON PURPOSE. The implicit one is the element's own opacity, so
   an arrow revealed while it is already at an end of the scroll fades to ITS
   .45 rather than to 1 and then blinking down. A written `to: 1` would also
   outrank that [disabled] rule permanently - a filled animation beats every
   normal declaration in the cascade - so this shape is not a trick, it is the
   only spelling that leaves the disabled state able to speak. */
@keyframes dx-fade-in {
  from { opacity: 0; }
}


/* ============================================================ BAND HEADER === */

/* The trending band's own header: a gradient tile, a title with a one-line
   subtitle, and the period control pushed to the far end. Deliberately NOT
   .sect - the chip-on-a-rule band is the page's structural signature and marks
   a section of the catalogue, while this header carries a control and has to
   read as one object with the rail underneath it. */
.disc-band-wrap { margin: var(--space-6) 0 var(--space-5); }

.disc-band {
  align-items: center;
  border-bottom: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
  padding-bottom: var(--space-3);
}

.disc-band__icon {
  align-items: center;
  background-image: var(--grad-tile-brand);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-1);
  color: var(--on-brand);
  display: inline-flex;
  flex: 0 0 auto;
  height: 40px;
  justify-content: center;
  width: 40px;
}

.disc-band__text { flex: 1 1 auto; min-width: 0; }
.disc-band__title { font-size: var(--fs-h2); }
.disc-band__sub { color: var(--text-muted); font-size: var(--fs-meta); }


/* ================================================== THE SEGMENTED CONTROL === */

/* Three pills in a well. They are ANCHORS carrying tab semantics, not buttons:
   with JavaScript off a tab that cannot switch a panel has to still be
   somewhere to go, and each one is a real link to its own /rankings/ page.
   discovery.js preventDefaults them and moves aria-selected instead. */
.disc-seg {
  background: var(--surface-inset);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  display: inline-flex;
  gap: var(--space-1);
  margin-left: auto;
  padding: var(--space-1);
  position: relative;
}

/* THE PLATE THAT SLIDES BETWEEN THE THREE PERIODS. One pseudo element for the
   control, moved by two custom properties js/discovery.js measures off the
   chosen pill: --seg-x is where along the track it sits, --seg-s scales a 100px
   reference box to that pill's width. The three labels are different lengths,
   so the indicator has to change size as well as position - and it does it with
   scaleX, because width is a layout property and a 220ms width animation would
   re-lay-out the band header on every frame of it.
   [data-seg] is stamped by the script AFTER the first measurement, so a visitor
   with no JavaScript keeps the plain painted pill below. */
.disc-seg[data-seg]::before {
  background-image: var(--grad-brand);
  border-radius: var(--r-pill);
  bottom: var(--space-1);
  box-shadow: var(--shadow-1);
  content: "";
  left: 0;
  position: absolute;
  top: var(--space-1);
  transform: translateX(var(--seg-x, 0px)) scaleX(var(--seg-s, 0));
  transform-origin: left center;
  transition: transform var(--dx-in) var(--dx-ease);
  width: 100px;
}

.disc-seg__btn {
  align-items: center;
  border-radius: var(--r-pill);
  color: var(--text-2);
  display: inline-flex;
  font-size: var(--fs-meta);
  font-weight: 600;
  justify-content: center;
  letter-spacing: var(--track-chip);
  min-height: 32px;
  padding: 0 var(--space-3);
  /* Above the sliding plate. */
  position: relative;
  transition: background var(--dx-fast) ease-out, color var(--dx-in) var(--dx-ease),
              transform var(--dx-fast) ease-out;
  white-space: nowrap;
  z-index: 1;
}
.disc-seg__btn:hover {
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
}
.disc-seg__btn:active { transform: var(--dx-press); }
/* The selected pill is a PLATE with a white label, so it is the --grad-brand
   sweep between --brand-fill and --chip-popular and never anything built on
   --brand: --brand lightens in the dark theme and takes the label to 4.47:1.
   Both stops of this one hold it at 6.29:1 and 5.70:1 in either theme. The
   hover swaps the IMAGE, not the shorthand - `background:` would drop the
   gradient for a frame and the pill would flash flat. */
.disc-seg__btn[aria-selected="true"] {
  background-image: var(--grad-brand);
  box-shadow: var(--shadow-1);
  color: var(--on-brand);
}
.disc-seg__btn[aria-selected="true"]:hover {
  background-image: var(--grad-brand-hover);
  color: var(--on-brand);
}
/* ONE PLATE, NOT TWO: with the indicator live the chosen pill keeps its ink and
   gives up its fill, so what the eye follows between the three is a single
   object moving rather than one plate switching off as another switches on. The
   hover fill goes with it - a plate that is already there cannot light up.
   `background: transparent` is the shorthand on purpose here: it has to reset
   the gradient LAYER as well as the colour, which is exactly what it does. */
.disc-seg[data-seg] .disc-seg__btn[aria-selected="true"],
.disc-seg[data-seg] .disc-seg__btn[aria-selected="true"]:hover {
  background: transparent;
  box-shadow: none;
  color: var(--on-brand);
}


/* =================================================================== RAIL === */

.disc-rail { position: relative; }

/* One panel per period; the two that are not showing carry the hidden
   attribute, which main.css already turns into display:none. The panel is the
   scroller (.scroll-x supplies overflow and scroll-snap-type), so the rail
   works with no JavaScript at all - a finger, a trackpad or the arrow keys
   scroll it, and the tabindex is what gives a keyboard that last one. */
.disc-rail__panel:focus-visible { border-radius: var(--r-card); }
/* Snapping lands a card at the track's own inset rather than hard against the
   clipped edge, so an arrow press always finishes on a whole card. */
.disc-rail__panel { scroll-padding-inline: var(--space-1); }

.disc-rail__track {
  display: flex;
  gap: var(--space-3);
  /* The track carries the padding, not the scroller: a lifted cover inside an
     overflow:auto box is clipped without it, and so is a focus ring on the
     first card. */
  padding: var(--space-1) var(--space-1) var(--space-2);
}

/* Overlaid rather than parked in the header, because the thing they act on is
   the rail and the eye should not have to travel to find them. They ship with
   the hidden attribute and discovery.js reveals them, and only when the panel
   actually overflows: an arrow that scrolls nothing is a control that lies. */
.disc-rail__arrow {
  align-items: center;
  /* They FADE in on the frame the script un-hides them, which is the difference
     between a control that arrived and one that was always there and you simply
     had not noticed it. A keyframe rather than a transition because they come
     from display:none, which has no start value to transition from. No fill
     mode: when the fade is over the arrow is back on its own opacity, which is
     what keeps [disabled] below meaningful. */
  animation: dx-fade-in var(--dx-in) var(--dx-ease) backwards;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-2);
  color: var(--text-2);
  cursor: pointer;
  display: inline-flex;
  height: 36px;
  justify-content: center;
  position: absolute;
  /* 40%, not 50%: the cards are a cover with a title under them, so the middle
     of the CARD is below the middle of the artwork the arrow should sit on. */
  top: 40%;
  transform: translateY(-50%);
  transition: background var(--dx-fast) ease-out, border-color var(--dx-fast) ease-out,
              box-shadow var(--dx-fast) ease-out, color var(--dx-fast) ease-out,
              opacity var(--dx-fast) ease-out, transform var(--dx-fast) ease-out;
  width: 36px;
  z-index: 3;
}
.disc-rail__arrow--prev { left: var(--space-1); }
.disc-rail__arrow--next { right: var(--space-1); }
.disc-rail__arrow:hover:not([disabled]) {
  background: var(--brand-soft);
  border-color: var(--brand);
  color: var(--brand-strong);
}
/* The press keeps the centring translate in the transform, or the button would
   jump to the top of the rail on the way down. */
.disc-rail__arrow:active:not([disabled]) { transform: translateY(calc(-50% + var(--dx-press-y))); }
/* At an end of the scroll. Still focusable-looking rather than removed, so the
   rail does not reflow as the visitor scrolls it. */
.disc-rail__arrow[disabled] {
  box-shadow: var(--shadow-1);
  color: var(--text-muted);
  cursor: default;
  opacity: .45;
}


/* ============================================================ TREND CARDS === */

/* THE CELL WIDTH IS A CLAMP, NOT A NUMBER. It was a flat 138px at every width,
   which is the same snap main.css took out of .slide: a rail whose cells are one
   size until a breakpoint decides otherwise is a rail that jumps a whole cell
   while the reader zooms through it. Same shape as .slide's
   clamp(126px, 26vw, 150px) and solved the same way - 24vw holds the cap from
   ~575px up, which is where the old flat value was already correct, and the
   floor puts two-and-three-quarter cells on a 360px phone instead of two and a
   half. 138px is the cap and not a round number: it is --cover-md, so the
   artwork is never drawn larger than the file behind it. */
.trend-card {
  display: flex;
  flex: 0 0 clamp(116px, 24vw, 138px);
  flex-direction: column;
  gap: var(--space-2);
  /* The snap point. .scroll-x sets proximity, so a half-scrolled rail settles
     on a card edge without fighting a deliberate flick. */
  scroll-snap-align: start;
}

.trend-card__cover { transition: transform var(--dx-fast) ease-out; }
.trend-card:hover .trend-card__cover { transform: var(--lift); }

/* The bottom scrim, so the rank numeral and the chapter pill have something to
   sit on whatever the artwork is. z-index 1 puts it over the <img> (a pseudo
   element paints under its element's children until it is given one) and under
   the overlays at 2; .badges is already at 1 and comes after it in paint order,
   so the hot / new / 18+ pills stay on top of it too. */
.trend-card__cover::before {
  background-image: var(--grad-cover-scrim);
  border-radius: var(--r-card);
  content: "";
  inset: 0;
  pointer-events: none;
  position: absolute;
  z-index: 1;
}

.trend-card__rank {
  bottom: 2px;
  color: var(--badge-ink);
  font-size: 30px;
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  left: var(--space-2);
  line-height: 1;
  /* Translucent: it is a position marker on top of artwork, and the <ol> is
     what actually carries the ordering. */
  opacity: .78;
  position: absolute;
  z-index: 2;
}

/* Two dark-glass pills on the artwork - see the header for why the scrim is
   painted twice, and tokens/default.css for the ratios it buys. 11px/700 is the
   same type as .badge, so a rating and a "Hot" badge on one cover read as one
   family. */
.trend-card__rating,
.trend-card__chapter {
  background-image: var(--grad-glass);
  border-radius: var(--r-pill);
  color: var(--badge-ink);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  line-height: 1;
  padding: 4px var(--space-2);
  position: absolute;
  z-index: 2;
}
.trend-card__rating { right: var(--space-1); top: var(--space-1); }
/* The star is CSS content, not 8 more inline SVGs per rail. --warn is a signal
   colour and this is a graphic, not a word, which is exactly what it is for. */
.trend-card__rating::before {
  color: var(--warn);
  content: "\2605";
  margin-right: 3px;
}
.trend-card__chapter { bottom: var(--space-1); right: var(--space-1); }

/* Clamped to two lines like every other card title on the site: a manga title
   is what a reader scans for, and one line of it plus an ellipsis is a guess.
   `anywhere` is the guard for the untranslated single-word titles. */
.trend-card__title {
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  display: -webkit-box;
  font-size: var(--fs-body);
  font-weight: 600;
  line-height: var(--lh-head);
  overflow: hidden;
  overflow-wrap: anywhere;
}
/* :visited before :hover - the visited colour is a read cue and the other order
   makes hover unreachable on a series the visitor has already opened. */
.trend-card__link { color: var(--text); }
.trend-card__link:visited { color: var(--link-visited); }
.trend-card__link:hover { color: var(--link-hover); }
/* The whole card is one destination wearing two anchors, so pointing anywhere
   on it answers the same as pointing at the words. Last, so it also wins over
   :visited. */
.trend-card:hover .trend-card__link { color: var(--link-hover); }


/* ========================================================== THE STANDINGS === */

/* The band's own line, above the three panels: it is what tells a reader why
   this band is not the rail they just scrolled past. Muted and one line - the
   panels' own notes carry the detail. */
.disc-board__sub {
  color: var(--text-muted);
  font-size: var(--fs-meta);
  margin-bottom: var(--space-3);
}

/* NO COLUMN COUNT AND NO BREAKPOINT. Three panels get three columns wherever
   three fit, two where two do and one on a phone, counted off --fit-board - so
   the row folds smoothly as the reader zooms instead of snapping at a width
   somebody picked. main.css declares the same shape at .page-home scope for the
   front page; this is the rule for the component ANYWHERE, so the board does not
   depend on the page it is on to know how to lay itself out. auto-fit rather
   than auto-fill: there are exactly three panels, and a fourth empty track would
   leave the row a quarter short of the container it is meant to fill. */
.disc-board__grid {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--fit-board), 100%), 1fr));
}

/* Built on .panel and only re-setting what three columns in one main column
   cannot afford. The three panels are grid items and therefore already the same
   height; nothing inside them has to be pushed to the foot any more, now that
   the "Full rankings" pill that used to live there is gone - the band's own
   heading is the one link to /rankings/. */
.disc-board__panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3);
}

/* Heading and note are one block with a tight gap of their own, so the note
   belongs to the heading rather than floating between it and the first row. */
.disc-board__intro { display: flex; flex-direction: column; gap: 2px; }

/* WHAT THIS PANEL RANKS, in one line. It is the difference between three
   standings and three unexplained lists: "Top rated" and "Most read" are only
   distinguishable if each says which number it sorted on. */
.disc-board__note { color: var(--text-muted); font-size: var(--fs-meta); }

.disc-board__head {
  align-items: center;
  color: var(--text);
  display: flex;
  font-size: var(--fs-body);
  gap: var(--space-2);
}
/* The band's own hue, as a tile. It was a flat --brand-soft plate carrying
   --brand-strong ink, which is the correct pairing for a plate that INVERTS
   between themes - and the reason this one does not need that rule any more is
   that a --grad-tile-* sweep is built out of --chip-* stops, which do not
   invert. So the glyph is plain white in both themes, on 5.70:1 and 6.29:1. */
.disc-board__icon {
  align-items: center;
  background-image: var(--grad-tile-rank);
  border-radius: var(--r-card);
  color: var(--chip-ink);
  display: inline-flex;
  flex: 0 0 auto;
  height: 26px;
  justify-content: center;
  width: 26px;
}

.disc-board__list { display: flex; flex-direction: column; gap: var(--space-1); }

.lb-row {
  align-items: center;
  border-radius: var(--r-card);
  display: flex;
  gap: var(--space-2);
  padding: var(--space-1);
  /* Background only, and that is the whole hover: there is no border on this
     row in either state, so nothing can appear under the pointer and push the
     five rows below it down a pixel. */
  transition: background var(--dx-fast) ease-out;
}
/* The tint, and nothing else: no lift and no size change, or a five-row list
   would jump under the cursor while it is being read. */
.lb-row:hover { background: var(--brand-soft); }

/* THE RANK LADDER, and the only place on this page where a gradient sits under
   a character. It is a COUNTER, not a row: 26px of plate carrying one digit,
   which is a plate in the sense the token file means it, while the row behind
   the title stays flat. Ranks four and five keep the flat --rank-num tint and
   its --brand-strong ink (6.61:1 light, 5.02:1 dark) - that is the podium
   signal, three plates and two tints, and it costs no extra hue. */
.lb-row__num {
  align-items: center;
  background: var(--rank-num);
  border-radius: var(--r-card);
  color: var(--rank-num-ink);
  display: inline-flex;
  flex: 0 0 26px;
  font-size: var(--fs-body);
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  height: 26px;
  justify-content: center;
  line-height: 1;
}
/* The ink for all three podium steps; the template puts --top on the first
   three rows and the nth-child rules below say WHICH step each one is. Written
   as a background-IMAGE over the tint so the flat colour stays underneath as
   the fallback the top three never fall back to. */
.lb-row__num--top { color: var(--chip-ink); }
.disc-board__list > .lb-row:nth-child(1) .lb-row__num--top { background-image: var(--grad-rank-1); }
.disc-board__list > .lb-row:nth-child(2) .lb-row__num--top { background-image: var(--grad-rank-2); }
.disc-board__list > .lb-row:nth-child(3) .lb-row__num--top { background-image: var(--grad-rank-3); }

.lb-row__cover { flex: 0 0 36px; }
.lb-row__body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.lb-row__title {
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  display: -webkit-box;
  font-size: var(--fs-body);
  font-weight: 600;
  line-height: var(--lh-head);
  overflow: hidden;
  overflow-wrap: anywhere;
}
.lb-row__link { color: var(--text); }
.lb-row__link:visited { color: var(--link-visited); }
.lb-row__link:hover { color: var(--link-hover); }
.lb-row:hover .lb-row__link { color: var(--link-hover); }

.lb-row__meta {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
}
/* THE FIGURE THE PANEL SORTED ON, and its unit beside it. This is the pair that
   makes the board a standings table rather than five more titles: --text-2 and
   700 for the number so the eye can run down the column, --text-muted and normal
   weight for the word after it, which is a label and not data. Tabular figures
   so 1.2k and 985 line up down five rows.
   MEASURED ON BOTH PLANES, because .lb-row tints to --brand-soft under the
   pointer and a row that is legible at rest and not while it is being pointed at
   is not legible. --text-2 is 10.35:1 / 9.26:1 in light and 11.47:1 / 10.77:1 in
   dark; --text-muted, the weaker of the two, is 5.41:1 / 4.84:1 and 6.64:1 /
   6.24:1 - the hovered light figure is the worst case on this component and it
   clears. */
.lb-row__metric {
  color: var(--text-2);
  font-size: var(--fs-meta);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}
/* Only the rated panel. --warn is a signal colour and this is a graphic rather
   than a word, which is exactly what the token rule allows it for. */
.lb-row__metric--score::before {
  color: var(--warn);
  content: "\2605";
  margin-right: 3px;
}
.lb-row__sub { color: var(--text-muted); font-size: var(--fs-meta); }
.lb-row__chapter { font-size: var(--fs-meta); }

.disc-board__empty {
  color: var(--text-muted);
  font-size: var(--fs-meta);
  padding: var(--space-5) 0;
  text-align: center;
}

/* .disc-board__more IS GONE, and this note is here so it does not come back.
   Each panel used to end in a "Full rankings" pill carrying that panel's
   period - three of them, in a band whose own heading is already a link to
   /rankings/. That is two controls doing one job in one band, which is the
   thing the client asked to stop seeing. The heading stays because it is the
   band's own route out; the three period URLs the pills carried are still
   linked from the rail's period tabs above and from the period tabs on
   /rankings/ itself, so nothing lost a crawl path. */


/* ======================================================== RANKINGS PAGE === */

/* The page is components: .sect for the band, .chip for the period tabs,
   card_rank for the rows, .pager for the paging. This is the only rule it
   needs. The ladder tint on .sidebar-popular__list is deliberately NOT copied
   here - it belongs to a top-ten list, and a paginated ranking would restart
   the gradient on every page, which would say "top three" about rows 25-27. */
.rankings__list { display: flex; flex-direction: column; gap: var(--space-2); }


/* ================================================== THE ADVANCED SEARCH === */

/* ONE GET FORM ON /search/, and every control in it is a real input. There is no
 * script anywhere in this section: the state each control is in comes from the
 * URL when the page renders, and from the input's own :checked while the visitor
 * is working - which is why the paint below is driven by SIBLING SELECTORS off
 * the inputs and never by a class the template printed. A class printed from the
 * server describes the state the page ARRIVED in and then sits there lying the
 * moment anything is clicked with JavaScript off.
 *
 * WHY THE GENRE CONTROL IS TWO CHECKBOXES. A three-way radio is the obvious
 * spelling of include / exclude / ignore and it cannot be used here: a radio
 * group is always submitted, so an untouched genre would put an empty parameter
 * in the URL - one per genre, on every search, forever. A checkbox is submitted
 * only when it is checked. The cost is that both boxes on one genre can be
 * checked at once; EXCLUDE WINS, in the view and in the four rules below that
 * paint it, so the page cannot show a state the query did not run.
 *
 * COLOUR IS NEVER THE ONLY SIGNAL. Included is a filled plate AND a filled
 * marker; excluded is danger ink AND a line through the name AND a ringed minus.
 * Every pairing here is one main.css already states somewhere else - danger ink
 * on the card surface, --on-brand on --brand-fill, --brand-strong on
 * --brand-soft - so the six presets are measured on this component for free by
 * test_theming's declared_pairs(), and no new token was needed for any of it.
 *
 * IT RESERVES ITS OWN SPACE. The summary is one --tap row whether or not it
 * carries a count, the panel is opened by the SERVER from the URL rather than by
 * a script after paint, and both long lists inside it scroll in a bounded box -
 * so the panel opens to a height this stylesheet decides rather than one the
 * size of the site's genre vocabulary decides. */

/* .panel supplies the box; this re-sets the padding at equal specificity, which
   only works because the page loads this file after main.css. */
.adv {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  padding: var(--space-3) var(--space-4);
}

/* The keyword row, and the page's only search field. .search / .search-input /
   .search-btn are main.css's own header control, borrowed whole: the magnifier,
   the pill and the suggest dropdown's positioning context all come with them. */
.adv__head { display: flex; gap: var(--space-2); }
.adv__q { flex: 1 1 auto; min-width: 0; }
.adv__go { flex: 0 0 auto; }

/* THE SORT BAR, out in front of the disclosure because sorting is not filtering.
   The legend sits on the same line as its chips wherever there is room and drops
   above them when there is not - one flex row, no breakpoint. */
.adv-sort {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
}
.adv-sort__label { margin-bottom: 0; }

.adv__more { min-inline-size: 0; }

/* ONE --tap ROW, ALWAYS, open or shut and whatever it carries. The disclosure
   above the results is the one control on this page that can move everything
   under it, so its own height is not allowed to be a variable. */
.adv__summary {
  align-items: center;
  color: var(--text-2);
  cursor: pointer;
  display: flex;
  flex-wrap: wrap;
  font-size: var(--fs-meta);
  gap: var(--space-1) var(--space-2);
  list-style: none;
  min-height: var(--tap);
}
.adv__summary::-webkit-details-marker { display: none; }
.adv__summary::after { content: "\25BE"; margin-left: auto; padding-left: var(--space-2); }
.adv__more[open] .adv__summary::after { content: "\25B4"; }
.adv__summary:hover { color: var(--brand-strong); }
.adv__summary-text { font-weight: 700; letter-spacing: var(--track-chip); text-transform: uppercase; }
/* HOW MANY FILTERS ARE ON, on the shut panel. Without it a visitor who arrives on
   a shared link and does not open the disclosure has no way to tell that four of
   the results were filtered away. It sits inside the --tap row above and cannot
   change its height. */
.adv__summary-count {
  align-items: center;
  background: var(--brand-fill);
  border-radius: var(--r-pill);
  color: var(--on-brand);
  display: inline-flex;
  font-weight: 700;
  justify-content: center;
  line-height: 1;
  min-width: 20px;
  padding: 3px 6px;
}

/* AS MANY COLUMNS AS FIT, and no count and no breakpoint anywhere: the tracks
   come off --fit-fact, the same floor the series page gives its attribute list,
   so the panel folds smoothly under zoom instead of snapping at a width somebody
   picked. Nine stacked fieldsets on a 1280px page is a panel that has to be
   scrolled past rather than read.

   The genre fieldset takes the whole row, because its own grid is counting
   tracks inside it and a 300px column would leave it one chip wide. :has() is an
   enhancement: without it the fieldset simply sits in one column and its chips
   count themselves down to fit, which still works. */
.adv__body {
  display: grid;
  gap: var(--space-5) var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--fit-fact), 100%), 1fr));
  padding-top: var(--space-4);
}
.adv__body > .adv-set:has(.tri-grid),
.adv__body > .adv__actions { grid-column: 1 / -1; }

/* A fieldset's default border, padding and - the one that bites - its intrinsic
   minimum width all have to go, or the grids inside it cannot shrink to a 320px
   phone and the panel scrolls sideways. */
.adv-set {
  border: 0;
  margin: 0;
  min-inline-size: 0;
  padding: 0;
}
.adv-set__label {
  color: var(--text-muted);
  font-size: var(--fs-meta);
  letter-spacing: var(--track-chip);
  margin-bottom: var(--space-2);
  padding: 0;
  text-transform: uppercase;
}
.adv-set__hint { color: var(--text-muted); font-size: var(--fs-meta); margin-bottom: var(--space-3); }
.adv-set > .adv-field + .adv-field { margin-top: var(--space-3); }

/* NO COLUMN COUNT AND NO BREAKPOINT: the grid counts its own tracks off
   --fit-genre, the same floor main.css gives the genre table, so it is one
   column on a 320px phone and six on a wide page - at every zoom level rather
   than at three of them. The scroller is what bounds the panel's open height;
   2px of padding keeps a focus ring on the first chip from being clipped by it. */
.tri-grid {
  display: grid;
  gap: var(--space-2);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--fit-genre), 100%), 1fr));
  max-block-size: min(52vh, 22rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 2px;
}

/* One genre: a name that requires it and a minus that shuts it out, in one pill.
   overflow:hidden is what lets the two labels fill the pill's rounded ends. */
.tri {
  align-items: stretch;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--chip-radius);
  display: flex;
  /* --tap AND NOT --chip-h, which is what every other chip on the site is. This
     one is two targets in one pill and the smaller of them is a 24px stub of it,
     which measured 24x30 on a 320px phone - inside WCAG 2.2's 24px floor with
     nothing to spare, for the control this whole feature turns on. It costs the
     panel no height: .tri-grid is a bounded scroller, so a taller chip changes
     how many are visible and not how far the results move. */
  min-height: var(--tap);
  min-width: 0;
  overflow: hidden;
  position: relative;
}

.tri__in {
  align-items: center;
  color: var(--text-2);
  cursor: pointer;
  display: flex;
  flex: 1 1 auto;
  font-size: var(--fs-meta);
  font-weight: 600;
  gap: var(--space-2);
  min-width: 0;
  padding: 0 var(--space-2) 0 var(--space-3);
  transition: background var(--dx-fast) ease-out, color var(--dx-fast) ease-out;
}
.tri__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tri__count { font-weight: 400; margin-left: auto; opacity: .75; }

/* COLOUR IS NEVER THE ONLY SIGNAL, so the included state has a filled marker as
   well as a filled plate, and the excluded state has a line through the name and
   a thickened ring as well as danger ink. currentColor throughout: the marker is
   drawn in whatever ink the chip is already carrying, so it needs no token and
   no second contrast pairing. */
.tri__mark {
  border: 1px solid currentColor;
  border-radius: 3px;
  flex: 0 0 auto;
  height: 13px;
  opacity: .5;
  width: 13px;
}

.tri__ex {
  align-items: center;
  border-left: 1px solid var(--border-strong);
  color: var(--text-muted);
  cursor: pointer;
  display: flex;
  flex: 0 0 var(--tap);
  justify-content: center;
  transition: background var(--dx-fast) ease-out, color var(--dx-fast) ease-out;
}
.tri__ex-mark {
  align-items: center;
  border: 1px solid currentColor;
  border-radius: var(--r-pill);
  display: inline-flex;
  font-weight: 700;
  height: 16px;
  justify-content: center;
  line-height: 1;
  width: 16px;
}

.tri__in:hover { background: var(--brand-soft); color: var(--brand-strong); }
.tri__ex:hover { background: var(--surface-alt); color: var(--danger-ink); }

/* INCLUDED. */
.tri__box--in:checked ~ .tri__in { background: var(--brand-fill); color: var(--on-brand); }
.tri__box--in:checked ~ .tri__in .tri__mark { background: currentColor; opacity: 1; }

/* EXCLUDED, and these four rules come AFTER the two above ON PURPOSE: at equal
   specificity the later one wins, so a genre that is somehow ticked in both
   groups paints as excluded - which is exactly what read_criteria does with it.
   The page cannot show a state the query did not run. */
.tri__box--ex:checked ~ .tri__in {
  background: var(--surface);
  color: var(--danger-ink);
  text-decoration: line-through;
}
.tri__box--ex:checked ~ .tri__in .tri__mark { background: transparent; opacity: .35; }
.tri__box--ex:checked ~ .tri__ex { color: var(--danger-ink); }
.tri__box--ex:checked ~ .tri__ex .tri__ex-mark { border-width: 2px; }

/* The pill's own edge follows the state it is in. :has() is an ENHANCEMENT and
   nothing depends on it: without it the chip still reads as included or excluded
   from its fill, its marker and the line through its name. */
.tri:has(.tri__box--in:checked) { border-color: var(--brand-fill); }
.tri:has(.tri__box--ex:checked) { border-color: var(--danger-ink); }

/* THE RING IS DRAWN ON THE LABEL, because the input it belongs to is 1px of
   clipped .sr-only and a ring around that is a ring nobody can see. Inset, so
   the pill's own overflow:hidden cannot clip it. */
.tri__box--in:focus-visible ~ .tri__in,
.tri__box--ex:focus-visible ~ .tri__ex {
  outline: 2px solid var(--ring, var(--brand));
  outline-offset: -2px;
}

/* ONE CHOICE OUT OF N, as a chip. Deliberately its own class rather than
   main.css's .chip: that one is an anchor's paint, and the two would fight over
   :hover and :focus-visible on a <label> that is wrapping a radio. Same object to
   look at, same tokens, drawn for a form control. */
.rchip { display: block; }
.rchip__face {
  align-items: center;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--chip-radius);
  color: var(--text-2);
  cursor: pointer;
  display: inline-flex;
  font-size: var(--fs-meta);
  font-weight: 600;
  gap: var(--space-1);
  line-height: var(--lh-body);
  min-height: var(--chip-h);
  padding: var(--space-1) var(--space-3);
  transition: background var(--dx-fast) ease-out, border-color var(--dx-fast) ease-out,
              color var(--dx-fast) ease-out;
}
.rchip__face:hover {
  background: var(--brand-soft);
  border-color: var(--brand);
  color: var(--brand-strong);
}
.rchip__count { font-weight: 400; opacity: .75; }
.rchip__box:checked + .rchip__face {
  background: var(--brand-fill);
  border-color: var(--brand-fill);
  box-shadow: var(--shadow-1);
  color: var(--on-brand);
}
/* Without this, the hover above would repaint a chosen chip to the pale plate
   while its label stayed white. */
.rchip__box:checked + .rchip__face:hover {
  background: var(--brand-fill-hover);
  border-color: var(--brand-fill-hover);
  color: var(--on-brand);
}
.rchip__box:focus-visible + .rchip__face {
  outline: 2px solid var(--ring, var(--brand));
  outline-offset: 2px;
}
.rchip__face:active { transform: var(--dx-press); }

/* A FINGER IS NOT A VIEWPORT. The radio chips stay at --chip-h, because they are
   the same object as the directory's filter and sort chips and a search page
   whose chips are taller than /manga/'s would be the second visual language this
   control exists to avoid. On a coarse pointer they take the full target - a
   query on the INPUT DEVICE and not on the width, because a 1280px touchscreen
   needs it and a 320px window driven by a mouse does not.
   .tri is NOT in here: it is already --tap at every width, for the reason given
   where it is declared. */
@media (pointer: coarse) {
  .rchip__face { min-height: var(--tap); }
}

/* Author, artist and the two year bounds: free text, because those vocabularies
   run to five figures and a year range has no list to pick from. Bottom-border
   only - the panel is already a box, and a box inside a box is the boxed web
   form this design is not. NO RULE HERE TAKES THE OUTLINE OFF: main.css's one
   ring is drawn outside the box and is what marks focus. */
.adv-field { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.adv-field__label {
  color: var(--text-muted);
  font-size: var(--fs-meta);
  letter-spacing: var(--track-chip);
  text-transform: uppercase;
}
.adv-field__input {
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--border-strong);
  border-radius: 0;
  color: var(--text);
  min-height: var(--tap);
  padding: 0 2px;
  transition: border-color var(--dx-fast) ease-out;
  width: 100%;
}
.adv-field__input:hover,
.adv-field__input:focus { border-bottom-color: var(--brand); }

/* TWO FIELDS ABREAST, AND NOT A YEAR-SPECIFIC RULE: the credits pair and the
   year range are the same object, so they share one. The basis is --fit-genre
   rather than a percentage, which is what makes the pair fold to one column when
   it no longer fits - on a 320px phone, and equally inside the same panel at
   250% zoom, with no breakpoint to disagree with either. A bare `flex: 1 1 0`
   never folds: two 140px boxes on a 320px screen are two fields nobody can read
   a name in. */
.adv-pair { display: flex; flex-wrap: wrap; gap: var(--space-3) var(--space-4); }
.adv-pair > .adv-field { flex: 1 1 min(var(--fit-genre), 100%); }

/* SHIPPED TO EVERYONE AND REVEALED BY THE PRE-PAINT ATTRIBUTE. Family-safe is a
   cookie the server may not read - see THE CACHE RULE - so the one sentence that
   depends on it is in every copy of this page and CSS decides whether it shows.
   Without it, a reader with family-safe on who asks for adult titles only is
   served a page that hides every row it found and looks broken. */
.adv-safe-note { display: none; }
html[data-safe="1"] .adv-safe-note {
  color: var(--text-muted);
  display: block;
  font-size: var(--fs-meta);
  margin-top: var(--space-2);
}

.adv__actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }


/* ========================================================= APPLIED FILTERS === */

/* WHY THIS PAGE FOUND FOUR THINGS, above the rows and outside the panel. Each
   chip is a link to the same search minus that one filter, so removing one is an
   ordinary navigation - no script, and the URL it lands on is the shareable one.
   Built on .chip, which already owns the shape and the hover. */
.applied { margin-bottom: var(--space-4); }
.applied__label {
  color: var(--text-muted);
  font-size: var(--fs-meta);
  letter-spacing: var(--track-chip);
  margin-bottom: var(--space-2);
  text-transform: uppercase;
}
.applied__chip { min-height: var(--tap); }
.applied__group { font-weight: 400; opacity: .75; }
.applied__value { font-weight: 700; }
.applied__x { font-size: 1.15em; line-height: 1; opacity: .7; }
/* An excluded genre reads the same here as it does in the panel. The hover hands
   the colour back, because .chip:hover repaints the plate underneath it and
   danger ink was measured on the card surface, not on that one. */
.applied__chip--ex .applied__value { color: var(--danger-ink); text-decoration: line-through; }
.applied__chip--ex:hover .applied__value { color: inherit; }
.applied__clear { min-height: var(--tap); }

/* Which half of a request actually ran, when a keyword under the floor arrived
   next to filters that did. --danger-ink because it is a correction and not a
   caption; it is measured on the page and the card in every preset already. */
.search-note {
  color: var(--danger-ink);
  margin-bottom: var(--space-3);
}
.search-fallback { margin-top: var(--space-6); }


/* ============================================================ BREAKPOINTS === */

@media (max-width: 768px) {
  /* Touch scrolls the rail directly, so the arrows are dead weight - and they
     would be sitting on top of the covers they are meant to reveal. */
  .disc-rail__arrow { display: none; }

  /* The control drops under the title and the three pills share the width, so
     each one is a comfortable target rather than a 60px pill in a corner. */
  .disc-seg { margin-left: 0; width: 100%; }
  .disc-seg__btn { flex: 1 1 0; min-height: 38px; }

  /* NOTHING IN THE ADVANCED PANEL HAS A BREAKPOINT, and nothing may grow one:
     .adv__body, .tri-grid and .adv-pair each count their own tracks off a floor,
     so the panel folds at whatever width it runs out of room at rather than at
     one somebody picked here - which is what makes it behave the same under
     browser zoom as it does on a narrow window. A rule here could only disagree
     with them. The one thing that IS device-conditional is target size, and that
     is a (pointer: coarse) query up beside the controls it grows.

     THE BOARD'S BREAKPOINT IS GONE and must not come back: .disc-board__grid
     counts its own tracks off --fit-board, so it is already one column on a
     phone, two on a tablet and three on the page - at every zoom level rather
     than at three of them. A rule here would only be able to disagree. */
}


/* ================================================================ MOTION === */

/* main.css answers the preference once for every duration on the page, so the
   slide, the fade and the tints above are already single frames here. The press
   travel is not a duration - it would still move, just instantly - so the token
   is taken out, which is exactly what main.css does with --lift. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --dx-press: none;
    --dx-press-y: 0px;
  }
}
