/*
    responsive.css - the only file in this repository that may hold a media query.

    Screen-size rules spread over several stylesheets are a trap : changing one breakpoint
    means finding every copy of it, and the copy that is missed keeps contradicting the
    others at a width nobody thinks to test. So every width-conditional rule the site has
    lives here, and this file is linked last on every page that carries the shared topbar,
    so that it wins on source order alone rather than by shouting.

    THE THREE WIDTHS

    1024px - navigation.
        Below it the topbar collapses to a hamburger drawer and the search bar becomes a
        text field plus a filter button. Chosen because the five tabs plus the theme
        toggle stop fitting on one row somewhere in the 900 to 1050 band, and 1024 is the
        conventional tablet-landscape edge.

    820px - card.
        A pre-existing component breakpoint, moved here unchanged from card.css. It widens
        the card from half width to full width and has nothing to do with the other two :
        a card at half of a 700px page is unreadably narrow well before the navigation
        needs to change.

    640px - gallery.
        Below it the grid drops to one column and the gem image spans the column. Above it
        the grid is exactly today's, so a 768px portrait tablet keeps the 300px grid.

    WHY THE RANGE SYNTAX

    The conditions are written (width <= N) rather than (max-width: N). The two forms mean
    exactly the same thing - both are inclusive, so a viewport of exactly N matches either
    of them - and the range form keeps each number searchable as a single literal instead
    of hiding it behind a property name.

    The honest consequence : a browser older than Safari 16.4 or Chrome 104 does not
    understand the range form and drops the whole block, so a visitor on one of those gets
    the desktop layout. That is a degradation to a page that still works, not a broken one.

    NO MIN-WIDTH COUNTERPART, EVER

    Nothing in this file may be written as a min-width condition. A rule that can only
    match below a width cannot change what a 1280px desktop renders, and that property is
    the whole acceptance argument for this work : the desktop is not allowed to move.

    Z-INDEX SCALE - every layer the site stacks, with its number

        (auto)  document flow
        1000    sticky search bar and the loading popup - today's values, unchanged
        1100    panel backdrop (drawer and filter panel dim layer)
        1200    panel itself

    1000 is kept rather than renumbered to something rounder. Renumbering would be a
    rendering change, and this file was introduced on the promise that it is not one.

    The width blocks below are in descending width order. All three conditions are "at most", so
    a 360px viewport matches all three ; reading them narrowest-last means the later block
    wins in exactly the order a reader expects.

    One block at the very end of the file is not about a width at all : it answers a visitor who
    has asked their device for less movement. It sits after the width blocks deliberately and its
    own comment says why that position is load-bearing.
*/

@media (width <= 1024px) {
  /* navigation : topbar and drawer */

  /* The two numbers the whole navigation is built on, written down once and read from here by
     everything else. The height of the closed row, the width the menu panel slides in at, and
     anything that later has to leave room for either of them all have to agree on the same
     figure ; two numbers that must agree are one number, and a second copy of either is a
     rendering fault waiting for the day somebody edits only one of them. */
  #nav-drawer {
    --drawer-width: min(80%, 300px);
    --topbar-height: 50px;

    /* Three columns whose outer two are equal, so the site's name sits optically in the middle
       of the window whatever the control to its left ends up costing : both 1fr columns take the
       same share of the leftover space, and the right-hand one is meant to stay empty.

       The alternative was taking the control out of the flow with position: absolute and centring
       what remains. It is refused, and the theme toggle is the demonstration : it is positioned
       that way today, and it is therefore drawn on top of this row instead of beside anything. A
       box that has left the flow reserves no space, so nothing can be centred against it.

       min-height and not height : a control sized to be comfortable under a finger must be free
       to push this row taller than the figure above rather than spill out of it. */
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    min-height: var(--topbar-height);

    /* The three declarations that let the menu slide in from the left instead of appearing. They
       only work together, and none of them means what it looks like on its own :

       - taking the row out of positioning is the load-bearing one. The shared stylesheet makes
         this row a positioned box, so without this line it would honour the offset below and
         paint itself - background, menu button and site name - a menu's width to the left of
         where it belongs, on every page that carries the row, at every width under the one this
         block answers. Out of positioning, the offset beside it has no effect whatever while the
         row is part of the flow, and it is safe to take out because the one box under this width
         that needed the row to be its anchor is the theme toggle, which is put back into the
         flow further down.
       - the offset is where the menu comes from, and it has to be a length. Moving between a
         length and "no offset at all" is not a movement a browser can be halfway through, so it
         jumps - and it jumps while a duration sits beside it looking perfectly correct in any
         tool that lists the declarations.
       - the duration is the movement itself.

       Only the opening is animated, and that is deliberate rather than missing. Whether a box is
       part of the flow is another thing a browser cannot be halfway through, so the moment the
       open state is dropped this is the row again in the same frame and no closing slide is ever
       seen. Holding the panel over the page for half of its exit and then snapping it back is
       worse than not animating the exit at all. */
    position: static;
    left: calc(-1 * var(--drawer-width));
    transition: left 200ms ease;
  }

  /* Both are placed by hand instead of being left to fall into the next free cell. When the menu
     opens, the hamburger is swapped for a close cross and automatic placement would slide the
     site name into the first column on that one state - the name would jump sideways as the menu
     opened. Naming the cell each one lives in makes the two states identical.

     justify-self keeps the hamburger the width of its own glyph : a block-level grid item with an
     automatic width is stretched to fill its column by default, which would spread the button
     across a third of the row and draw its glyph in the middle of that instead of at the edge. */
  .nav-toggle {
    display: block;
    grid-column: 1;
    grid-row: 1;
    justify-self: start;
  }

  .site-name {
    display: block;
    grid-column: 2;
    grid-row: 1;
    text-align: center;
    font-weight: 600;
  }

  /* The five navigation entries are drawn by the opened menu and by nothing else. Written as the
     closed row hiding them, rather than as this width hiding them and the open state putting them
     back : the open state then needs no counter-rule at all, and there is no pair of selectors
     whose relative strength has to go on being right. */
  #nav-drawer:not(.is-open) .tab {
    display: none;
  }

  /* The theme toggle leaves the closed row with them, and not for tidiness. It is positioned
     absolutely against the row's left edge, so its box lies over the menu button's : asked at
     the exact point a tap on that button lands, the page hands the tap to the toggle, and the
     menu cannot be opened at all while both are drawn. It comes back with the entries when the
     menu is opened, which is where it belongs anyway. */
  #nav-drawer:not(.is-open) #mode-switch {
    display: none;
  }

  /* The opened menu : the same element as the row above, pinned to the left edge of the window
     and as tall as it. Being the same element is the point rather than an economy - the site's
     name and the control beside it stay in the cells the closed row put them in, so the top line
     does not rearrange itself as the menu opens.

     Nothing here moves this element with a transform, a scale, a rotation, a filter, containment
     or a hint naming any of those. Every one of them makes this element the anchor of every
     box inside it that was declared against the window, so such a box would silently start
     following the menu instead of staying where the window put it. Driven on this page at a
     phone width : a window-anchored box planted inside the menu read a left edge of 0, and with
     a translation on the menu it read the translation instead. Animating the offset has no such
     effect, which is why the offset is what is animated.

     A list too long for the screen scrolls inside the menu, and the page behind it is not
     allowed to take over once that list reaches its end. The containment is asked for in its
     physical spelling on purpose : the spelling that follows the writing direction is not
     implemented in the browser this line exists for. */
  #nav-drawer.is-open {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    width: var(--drawer-width);
    align-content: start;
    grid-auto-flow: row;
    overflow-y: auto;
    overscroll-behavior-y: contain;
  }

  /* The close cross takes the menu button's own cell instead of sitting in the far corner. The
     site's name is a child of this element and travels with it, so putting the cross exactly
     where the button was is what keeps the top line from visibly jumping as the menu opens - and
     it leaves the cross under the thumb that just opened it. Held to the start of its column for
     the same reason the menu button is : a grid item with an automatic width is stretched to
     fill its column, which would draw the glyph a third of the way across the line rather than
     at its edge. */
  #nav-drawer.is-open .nav-toggle {
    display: none;
  }

  #nav-drawer.is-open .nav-close {
    grid-column: 1;
    grid-row: 1;
    justify-self: start;
  }

  /* One entry per line, each spanning the whole menu, so the list reads as a list rather than as
     a stack of buttons. The padding the links already carry is deliberately left alone : it is
     what makes the whole strip a target instead of just the words on it. */
  #nav-drawer.is-open .tab {
    grid-column: 1 / -1;
  }

  #nav-drawer.is-open .tab a {
    text-align: left;
  }

  /* The page currently being looked at, marked twice on purpose.

     The light theme declares its own version of this mark with a selector carrying an element
     name and two classes, and that outweighs a pair of classes however late the file holding it
     is loaded. So a single rule written with two classes would be obeyed in the dark theme and
     quietly ignored in the light one : the accent would stay under the entry, drawn in the other
     theme's colour, and neither a picture of one theme nor a sweep of screen widths would show
     it. Each theme therefore gets its own rule, each shaped to outweigh the theme rule it
     answers.

     The accent moves from the bottom edge to the left edge because in a vertical list a line
     under an entry reads as a divider between two of them rather than as a mark on one. */
  #nav-drawer.is-open .tab.active {
    background-color: #3a3a3a;
    border-bottom: none;
    border-left: 3px solid #4db6ff;
  }

  body.light-mode #nav-drawer.is-open .tab.active {
    background-color: #dcdcdc;
    border-bottom: none;
    border-left: 3px solid #007acc;
  }

  /* The theme toggle, put back into the flow. Everywhere else it is pinned to the left edge of
     the row it lives in, which is what makes it overlap whatever else is drawn at that corner :
     a box out of the flow reserves no space, so nothing in this menu could be laid out around
     it and it would be painted straight over the first entry. The offset is written out rather
     than left to be ignored, so that the two declarations read as one deliberate pair. */
  #mode-switch {
    position: static;
    left: auto;
  }

  /* Last of everything in the menu, under a hairline that separates it from the entries, and
     shaped like the entries above it rather than like the small bordered button it is on a wide
     screen. It is the FIRST child of this element in the document - the script that binds it
     finds it by id, and this row is the menu itself - so the ordering is what carries it to the
     end. The bottom margin keeps it clear of the very foot of the screen, where a phone draws
     its own gesture bar over whatever is there. */
  #nav-drawer.is-open #mode-switch {
    grid-column: 1 / -1;
    order: 1;
    justify-self: stretch;
    text-align: left;
    border: none;
    border-top: 1px solid #444;
    border-radius: 0;
    margin: 8px 0 24px;
    padding: 14px 24px;
  }

  body.light-mode #nav-drawer.is-open #mode-switch {
    border-top-color: #ccc;
  }

  /* The dimmed page fades in over the same time as the menu slides, so the two read as one
     movement rather than as one thing arriving on top of another.

     The dim layer is drawn from not being drawn at all, and a box that was not being drawn has
     no earlier value for a fade to start from - which is what the starting value below supplies.
     Without it the layer is simply there on the first frame, with the fade sitting in the file
     looking correct.

     Nothing is declared to hold the layer on screen while it leaves : it stops being drawn the
     instant the open state is dropped, which is exactly what the menu beside it does, so the two
     go away together. A later change that made only one of them linger would pull them apart. */
  .nav-backdrop {
    opacity: 0;
    transition: opacity 200ms ease;
  }

  .nav-backdrop.is-open {
    opacity: 1;
  }

  @starting-style {
    .nav-backdrop.is-open {
      opacity: 0;
    }
  }

  /* navigation : toolbar and filter panel */

  /* Below this width the toolbar keeps the text search and nothing else. Every other filter is
     drawn by the panel, and the closed panel draws nothing at all - which is what empties a band
     that otherwise stacks seven labelled groups of fields above the gems on every screen, all the
     time, wrapping like words on a 360px phone.

     This rule and the one below it are a pair, and their relative strength is the whole of how the
     pair works : this one is a single class, 0-1-0, and the open state is that class plus another
     on the same element, 0-2-0. The open state therefore wins wherever the two happen to sit in
     the file, and it goes on winning if somebody later reorders them. Read in the inspector rather
     than assumed. */
  .filter-panel {
    display: none;
  }

  /* The opened panel : a sheet over the whole screen, and the thing that scrolls, both at once.
     No wrapper is added to carry the scrolling, because every field inside is written once in the
     document and shares its markup with the wide screen's toolbar - a wrapper here would mean a
     second copy of something.

     NO HEIGHT IS WRITTEN ANYWHERE IN IT, and that is load-bearing rather than tidy. A box told to
     be as tall as the viewport is told to be as tall as the viewport with the phone's own toolbars
     retracted, which is not the height the page arrives at : the box hangs below the bottom of the
     screen by the height of those toolbars, and the band that ends up down there is the one
     carrying the two buttons. Pinning all four edges asks the browser for the answer instead, and
     it stays the right answer while the toolbars come and go.

     About the layer number, because it does not say what a reader will take it to say. The toolbar
     this panel lives in is a sticky box, and a sticky box creates a stacking context always,
     whatever its own layer is ; this panel is a descendant of it. So 1200 is a rank INSIDE the
     toolbar, and against the rest of the page the panel paints at the toolbar's own 1000. What
     actually keeps it above everything else is that nothing at body level goes over 1000 while it
     is open. The number is written on the same scale as every other layer in this file all the
     same, because inventing a second scale for one box is worse than the ambiguity ; this note is
     here so nobody reads it as a guarantee against a body-level layer above 1000.

     The colours are the toolbar's own, and the background is written into a property rather than
     into the rule because three bands further down have to carry exactly the same background or
     the fields scroll visibly through them. Two copies of a colour that must match is one of them
     changed on its own later.

     The containment is asked for in its physical spelling on purpose : the spelling that follows
     the writing direction is not implemented in the browser this line exists for. Without it, a
     drag continued past the end of the field list scrolls the gallery behind the panel.

     Side padding only. Padding at the top or the bottom would sit between the sheet's edge and the
     bands pinned to it, and a strip of panel with the fields sliding through it is exactly what
     those bands exist to prevent. */
  .filter-panel.is-open {
    --panel-background: #2a2a2a;

    display: flex;
    flex-wrap: wrap;
    align-content: start;
    position: fixed;
    inset: 0;
    z-index: 1200;
    overflow-y: auto;
    overscroll-behavior-y: contain;
    padding: 0 12px;
    background-color: var(--panel-background);
    color: #eaeaea;
  }

  body.light-mode .filter-panel.is-open {
    --panel-background: #e5e5e5;
    color: #1f1f1f;
  }

  /* The button that opens it. Nothing else here reveals it, and without it the panel cannot be
     opened at all at this width : the controller asks the stylesheet whether this button is drawn
     before it opens anything, rather than holding a copy of the width itself. How big it is and
     where it sits on the toolbar's row are decided further on ; here it only has to exist. */
  button.filters-toggle {
    display: block;
  }

  /* THE FIRST BAND : the heading and the cross, on one row at the top of the sheet.
     Both are pinned there rather than scrolled away with the fields, because this is the one
     screen in the site where the content is long enough to push everything else out of sight, and
     a visitor who cannot see the way out of a full-screen panel has no way out of it. Both carry
     the sheet's own background : a transparent pinned row shows the fields sliding underneath it. */
  .filter-panel.is-open .panel-title {
    order: 0;
    flex: 1 1 auto;
    position: sticky;
    top: 0;
    padding: 12px 0;
    font-weight: 600;
    background-color: var(--panel-background);
  }

  /* The cross takes its text colour from the panel rather than from the browser : a button does
     not inherit colour the way ordinary text does, so with the sheet's dark background behind it
     and the browser's own button colour on it the glyph would be black on near-black. */
  .filter-panel.is-open .panel-close {
    order: 0;
    flex: 0 0 auto;
    position: sticky;
    top: 0;
    background-color: var(--panel-background);
    color: inherit;
  }

  /* THE SECOND BAND : the render choice first, then the text search, then the seven filter groups,
     one per row.
     The render choice is not a filter - it hides no gem, it only picks which picture of the same
     gem is shown - and nothing on screen otherwise says which of the three you are looking at. So
     it comes first, above the filters, with a rule under it saying it is not one of them. It is
     the only border in the whole panel, which is what lets it mean that. */
  .filter-panel.is-open .filter-row {
    order: 1;
    flex-basis: 100%;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    column-gap: 8px;
    row-gap: 4px;
    padding-bottom: 12px;
    margin-bottom: 12px;
    border-bottom: 1px solid #444;
  }

  body.light-mode .filter-panel.is-open .filter-row {
    border-bottom-color: #ccc;
  }

  /* The text search, drawn under the one line in this sheet and above the seven filter groups.
     Under the line because that border means "the row above it is not a filter", so anything that
     is one has to sit below it ; above the groups because this is the field people came to use, and
     a sheet that opens on six numeric ranges before its text search puts the rare case in front of
     the common one.

     Its label needs no width declaration of its own : this panel already gives every label a full
     line, so the name sits above the field exactly as it does for every other control here. That is
     also why the toolbar's old rule hiding this one label off-screen was deleted rather than
     narrowed - the row it was saving 55 pixels on no longer holds this field, and a sheet where
     every control but one is named is worse than those pixels ever were. The field needs no width
     declaration either : this panel gives every input inside it a zero flex basis, and a flex basis
     decides a flex item's main size in place of the definite width the shared stylesheet puts on
     this particular field.

     Weights, so that nobody has to count them again : the label's selector is 0-3-1 and the field's
     is 0-3-0. Neither has anything to outweigh, since nothing else in the site sets an order on
     either element - they only have to reach. */
  .filter-panel.is-open label[for="inTextValue"],
  .filter-panel.is-open .filter-text {
    order: 2;
  }

  /* One row each, in the order the document lists them, and separated from each other by space
     rather than by a line. The one border in this panel is the one under the render choice, and
     that is what lets it read as "this one is not a filter" instead of as a divider like any
     other.

     Each of these is itself a wrapping row of its own, which is what the two rules below lay out
     inside. Worth knowing while reading them : the wide screen's stylesheet gives these same
     elements a display value that removes their box entirely, at one class, and every rule here
     carries three, so they reach without anything having to shout. */
  .filter-panel.is-open .filter-group {
    order: 3;
    flex-basis: 100%;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    column-gap: 8px;
    row-gap: 4px;
    margin-bottom: 12px;
  }

  /* A label opens its own line and its fields follow on the line underneath. That shape is what
     makes the fault this whole width was rewritten for impossible by construction rather than by
     luck : on a 360px screen the old row wrapped between a label and the field it names, three
     times over, leaving three fields on the page with no visible name and three names pointing at
     nothing. A label that always takes the full width cannot be separated from what follows it. */
  .filter-panel.is-open label {
    flex: 0 0 100%;
  }

  /* The four range labels are the exception : each stays beside its own field, so Min and Max sit
     on one line at about half the row each and the pair reads as the range it is. Named one by one
     rather than picked out by their position in the group - a rule that counts children silently
     starts naming something else the day a field is added. They have to carry the panel's own
     shape as well as their names, because the rule above them is written against the panel too and
     would otherwise win. */
  .filter-panel.is-open label[for="minLw"],
  .filter-panel.is-open label[for="maxLw"],
  .filter-panel.is-open label[for="minRI"],
  .filter-panel.is-open label[for="maxRI"] {
    flex: 0 0 auto;
  }

  /* The fields share the line their label opened, in equal shares of whatever is left of it.
     The zero basis and the minimum width are both load-bearing rather than decoration. The wide
     screen gives these fields definite widths of 40, 50 and 120 pixels, which a phone must be
     allowed to beat ; and a flex item refuses by default to shrink below the width its own content
     needs, so without the minimum a field would keep a width nothing in this file asked for and
     the row would run off the side of the screen. Together they beat the desktop widths without an
     override that shouts and without touching an id. */
  .filter-panel.is-open input,
  .filter-panel.is-open select {
    flex: 1 1 0;
    min-width: 0;
  }

  /* The facet count's operator is sized to the two characters it shows, so that it and the number
     beside it stay on one line together. They have to : the number field carries no label of its
     own, and the name to the left of the operator is what names the operator and the number as one
     control. Split them over two lines, two rows or two groups and there is a field on the screen
     that nothing names. */
  .filter-panel.is-open #nFacetOp {
    flex: 0 0 auto;
  }

  /* THE THIRD BAND : Apply and Reset, on one row at the foot of the sheet.
     Sticky, and sticky INSIDE the panel's own scroller - never fixed, and that is the single most
     important line in this block. When a phone raises its on-screen keyboard it shrinks only the
     part of the page the visitor is looking through, not the page itself, and the browsers that
     can be asked to shrink the page instead do not include the one on every iPhone. A row fixed to
     the bottom of the window therefore ends up underneath the keyboard with no gesture that can
     reach it : the visitor types into a filter and can no longer press the button that applies it.
     A sticky row gives up its pin at the end of the content it belongs to, so scrolling the panel
     always brings it back into view.

     They sit flush against the foot of the sheet on purpose : a margin below a pinned row is a
     strip the fields would be seen sliding through. A phone that draws its own gesture bar over
     the last few pixels therefore covers the bottom few pixels of a 44px-tall control, which
     leaves the rest of it comfortably tappable - the failure is cosmetic instead of functional,
     which is the whole reason for the height.

     Both carry the sheet's background and its text colour, for the same two reasons the pinned row
     at the top does. */
  .filter-panel.is-open .panel-apply,
  .filter-panel.is-open .panel-reset {
    order: 4;
    position: sticky;
    bottom: 0;
    min-height: 44px;
    background-color: var(--panel-background);
    color: inherit;
  }

  /* Roughly two to one, and said as two shares of the row rather than as two widths, so the
     proportion holds at every screen width without a second number to keep in step. Reset wipes
     seven groups of filters at once and takes effect the moment it is pressed, so it does not get
     the same weight as the button people opened the panel to press - but it stays plainly a button
     rather than a link hidden in a corner. */
  .filter-panel.is-open .panel-apply {
    flex: 2 1 0;
  }

  .filter-panel.is-open .panel-reset {
    flex: 1 1 0;
  }

  /* WHAT IS LEFT OF THE TOOLBAR, ARRANGED : one button, centred on its row, with the count of gems
     on the line underneath it and inside the same pinned box.

     This band is the one thing on the page that never scrolls away, so every pixel of it is paid at
     every scroll position for the whole of a visit rather than once on arrival. Left as it was - an
     ordinary block, with its controls wrapping at the edge of the screen like words in a sentence -
     the same handful of controls stacked into a band roughly a quarter of a phone screen tall. One
     row with one line under it is the whole of what has to be permanently on show.

     The centring is one declaration because the row genuinely holds one item. Every filter control
     this site has, the text search included, is inside the panel - which is taken out of the flow
     while it is open and drawn not at all while it is shut ; the Search button is not drawn at this
     width ; the line break the document carries is not drawn here either ; and the count below
     claims a whole line to itself. So there is nothing left for the button to share a line with,
     and nothing that could quietly join it later without somebody writing a new control into the
     document beside it on purpose. It costs a wide screen nothing, because the flex container these
     declarations describe only exists inside this width block at all.

     Only the two side edges are written here. The pin itself, the space under the bar and the layer
     it paints on come from the shared stylesheet and are deliberately not repeated : a second copy
     of where this bar sits is a second thing that has to be kept in step with the first, and there
     is no width at which the two are supposed to differ. Nor is anything given an overflow value
     between this bar and the window - a scrolling box anywhere above it takes over as the thing it
     pins against, and a bar pinned to a box that has scrolled away is a bar that scrolls away, with
     nothing on screen saying so until somebody scrolls. */
  .search-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    column-gap: 8px;
    row-gap: 6px;
    padding-left: 10px;
    padding-right: 10px;
  }

  /* The document carries a line break between the button and the count, and it stays there because
     a wide screen still uses it to put the count on a line of its own. A line break inside a row of
     this kind stops being a line break and becomes an item like any other, so left alone it would
     take a slot beside the one control this row is meant to hold, and centring that control would
     centre the pair of them instead. */
  .search-bar br {
    display: none;
  }

  /* The button is sized to its own text and never shrinks or grows, which is what keeps it at its
     own width inside a centred row : an item allowed to grow would take the whole line and there
     would be nothing left to centre, and one allowed to shrink would be narrower than its label
     whenever the label is long. So the box a finger aims at is exactly as wide as the words on it,
     and the number the button gains once filters are applied is always fully readable. */
  .filters-toggle {
    flex: 0 0 auto;
  }

  /* Search is not drawn at this width, and that is worth saying out loud rather than leaving to be
     discovered : no text search is reachable here at all until the panel is opened, which is how it
     was asked for and confirmed rather than a side effect of moving the field. Once the panel is
     open there are two ways to run one, and they are the only two : the field asks the phone for a
     Search key instead of a Return key and pressing it runs a search, and the panel's own Apply runs
     the same search alongside the filters. */
  .search-button {
    display: none;
  }

  /* The count takes the whole of the next line. A basis of the full row is what puts it there : it
     cannot share a line with anything, so the row wraps once and the count is the only thing on the
     line that opens - which is what keeps it inside the pinned bar rather than under it, scrolling
     away with the very gems it is counting.

     Its weight is a judgement and can be argued with. Where it sits is not : below the controls and
     inside the part of the page that stays on screen. */
  #gemCount {
    flex-basis: 100%;
    font-weight: 600;
  }

  /* One hairline under the bar, in each theme's own border colour - the pair the topbar already
     uses, so no new colour enters the site on this account. Once the introduction above has scrolled
     away this bar floats directly over a single large picture of a gem, and with nothing marking its
     lower edge the two read as one surface. A shadow would separate them just as well and would be
     the only shadow anywhere on the site, so the edge is a line.

     Written twice at the same shape rather than once with an exception underneath it. The two rules
     then weigh the same as each other and neither has to go on outweighing the other as either is
     edited ; and both are keyed off the class the page body carries, never the document element,
     which is where the same class is also written and where a rule would work by accident today.

     It costs one pixel of the very band this width exists to shrink. That is deliberate, and it is
     why the bar measures one pixel more than the arithmetic of the things inside it. */
  body:not(.light-mode) .search-bar {
    border-bottom: 1px solid #444;
  }

  body.light-mode .search-bar {
    border-bottom: 1px solid #ccc;
  }

  /* navigation : touch targets and input font size */

  /* The smallest box a finger lands on reliably is about 44 by 44, and the box that has to be that
     size is the control's OWN border box - the box the tap is delivered to. Padding is inside it
     and counts towards the 44 ; margin is outside it and does not, so space around a control makes
     it look roomier without making it any easier to hit.

     The other way of reaching 44 is refused deliberately. An invisible pseudo-element stretched
     past a control's edges does enlarge the area a finger can use, and it is invisible to every
     tool that measures a box : the control would go on reporting the size it always had, so the
     one piece of evidence anybody can check would contradict what is being claimed. Everything
     written here grows the element itself.

     Floors rather than fixed sizes, so a control whose own text needs more room than 44 takes it
     instead of spilling out of a box held at exactly 44.

     No display value is written alongside them, and that is what keeps this rule out of the
     question of which controls are drawn : a minimum size is inert on a control nothing draws, so
     the close cross stays hidden on the closed row and the menu button stays hidden while the menu
     is open, exactly as the rules above leave them.

     Three of the controls a finger meets at this width are deliberately not named here, because
     each already carries its size where its own shape is decided : the five menu entries carry the
     padding that makes the whole strip of each one tappable rather than just the words on it, and
     the panel's Apply and Reset carry their height on the row that pins them to the foot of the
     sheet. A second copy of any of those three is a number somebody later changes in one place
     only, and the two copies then disagree with nothing on screen saying so.

     The panel's own close cross IS named, and it is the one that is easy to miss. It is already as
     tall as the row it shares with the heading, so only its width was ever short - a cross is a
     narrow glyph and the button around it was the width of that glyph. A control that is tall
     enough and too narrow looks perfectly reasonable in a picture and is still a small target, and
     this is the way out of a sheet that covers the whole screen.

     One control on this row is not a target at all, rather than being a small one : Search is not
     drawn at this width. The rule that hides it says so out loud and names the two routes that
     replace it.

     The button that opens the filter panel is named here with the rest of them, and it used to
     carry a height floor of its own with no width floor beside it. That exception existed because
     the button shared its row with the text search : it was the one item on that row that never
     shrank, so every pixel of width floored on it was a pixel taken from the field, and the field's
     ability to give width up was what kept the two on one line. The field is not on that row any
     more - it is a row of the panel now - and the button is the only thing on it, so the reason for
     the exception is gone and the button takes the same pair of floors as every other target. The
     width floor still changes nothing on its own : the label is a word or two and a word already
     costs more than 44 pixels here. What it buys is that nobody has to work out, later, why one
     control in this list was missing half of it.

     The height is paid in the one band of the page that is never scrolled away, so it is worth
     saying rather than discovering : the row is as tall as this button, and the band is as tall as
     the row plus the count under it. That is what a control a finger can actually hit costs here,
     and it is the trade being made rather than an oversight. */
  button.nav-toggle,
  button.nav-close,
  button.panel-close,
  button.filters-toggle,
  #mode-switch {
    min-width: 44px;
    min-height: 44px;
  }

  /* Every field on this page, at the size below which a phone zooms the page in the moment one of
     them takes the focus - and leaves it zoomed, with the layout the visitor was reading now wider
     than the screen and no gesture that obviously undoes it. The zoom is the browser deciding the
     text is too small to type into ; the only thing that stops it is the text not being too small.

     All three kinds are named, and the dropdowns are named for the same reason as the text fields
     rather than for symmetry : the same zoom applies to a dropdown gaining the focus, so a select
     left out is a page that still zooms, on the one control nobody thought to check. The panel's
     own fields need no separate rule - every filter control on this page lives inside the toolbar
     in the document and is only drawn elsewhere by the panel - so this one rule reaches all twelve
     of them.

     There is a second way to stop the zoom, which is to pin the page's maximum scale in the
     document's viewport declaration, and it is refused. It does not make the text readable ; it
     takes zoom away from everybody, including the visitor who was pinching to read a caption and
     has nothing to do with any field.

     The rule lives inside this width block and must stay there. Set everywhere, it would be
     larger glyphs inside the fixed 28-pixel box a wide screen draws, and a visibly different
     toolbar on a machine that never zooms anything and never had the problem.

     Two declarations, not one, and the second is what makes the first work. The base stylesheet
     gives these fields a fixed height of 28 pixels under a border-box sizing, which leaves 18
     pixels of content once the border and the vertical padding are taken out - less than a 16
     pixel line box needs, so the text would be clipped rather than shown. Handing the height back
     to automatic is what releases the box ; the floor beside it then brings the field up to the
     size a finger needs, and the field is free to be taller still if its own text asks for it. */
  .search-bar input[type="text"],
  .search-bar input[type="number"],
  .search-bar select {
    height: auto;
    min-height: 44px;
    font-size: 16px;
  }
}

@media (width <= 820px) {
  /* card : the pre-existing component breakpoint, moved from card.css */
  .card { width: 100%; }
}

@media (width <= 640px) {
  /* gallery : single column */

  /* The one place the gem picture's width is written for a narrow screen, and the only one : the
     caption under the picture and the info box under that already read this same value, so the
     three boxes end up the same width by construction rather than because somebody kept three
     numbers in step.

     The cap is exact rather than round. It is one narrow phone's column - a 360 pixel screen less
     the card's own two 10 pixel margins - so on that screen the picture takes everything there is,
     and on anything wider it stops there rather than growing into a picture nobody asked for. The
     percentage beside the cap is what makes it stop at the screen instead of running off it, and
     it is measured against the card, which is what the rule below is for. */
  .gem-gallery {
    --gem-image-width: min(100%, 340px);
  }

  /* The card takes the whole row, and without this line the width above is inert. The wide screen
     gives the card a preferred width of 320 pixels ; the percentage above is measured against the
     card and not against the screen, so left at 320 the picture would resolve to 320, the cap
     would never be reached and the number would appear nowhere on the page. A preferred width of
     the whole row overshoots it by the card's own margins and shrinks straight back to exactly the
     width the cap was cut for.

     The space DECLARED under a card drops by 20 pixels, which is 20 pixels of empty screen
     recovered on every gem in a list people scroll through hundreds of. What is DRAWN between two
     cards is this figure plus the next card's own top margin : the cards are flex items, and a
     flex item's margins never collapse against anything. So a declaration of 30 draws 40, exactly
     as the previous declaration of 50 drew 60, and the two quantities are not to be confused when
     either one is next measured. The top margin is deliberately left alone - zeroing it to make
     the two numbers agree would also close up the space above the very first card and above every
     row of them. */
  .gif {
    flex-basis: 100%;
    margin-bottom: 30px;
  }

  /* The caption is given a width of its own, and this is the one declaration in this block that
     looks redundant and is not. It already carries a cap of the same value the picture uses, but a
     cap is not a width : the card is a column that centres what is inside it, so a child with no
     width of its own is drawn exactly as wide as its own text - measured at 167 pixels under a 300
     pixel picture, nowhere near a cap of 300. Given a definite width it fills the card, and the
     cap beside it then does what it always meant to and holds it to the picture's width on a
     screen wider than the cap. The text inside stays centred either way ; what changes is the box,
     and the box is what has to match the picture.

     This element is named a second time in the ordering group below, which decides where it is
     drawn rather than how wide it is. */
  .gif-name {
    width: 100%;
  }

  /* The picture first, its name directly under it, then the badges, then the info box a text
     search produces. The document lists the name before the picture, which is why the wide screen
     needs no rule here at all and why this one has to exist : a caption belongs under the thing it
     captions once the picture is the width of the screen.

     All four are numbered even though only one of them moves. Anything left unnumbered counts as
     zero, so numbering the name alone would send it below the badges instead of under the picture.
     Reversing the whole column instead was refused : that flips what centring means and reorders
     more children than the two anybody intended to move.

     This is safe for somebody navigating with a keyboard for one reason that is invisible in the
     four rules below : a card holds exactly one thing that can take the focus, the link on the
     name, so what the eye meets going down the card cannot disagree with what the keyboard reaches
     going through it. A second control inside a card - a score, a vote, a comment button - ends
     that, and these four numbers would then have to be settled against the document order rather
     than left as they are.

     The badges are allowed a second line here. Four of them measure about 250 pixels today and sit
     comfortably on one, so most cards will never use it ; a gem carrying a longer value wraps onto
     a second line instead of having its last badge cut off with an ellipsis, at a cost of about 20
     pixels of card height on that card alone. Those values are the gem's own cut data, which is
     what a reader came for. */
  .gif img,
  .gif video {
    order: 1;
  }

  .gif-name {
    order: 2;
  }

  .gem-badges {
    order: 3;
    flex-wrap: wrap;
  }

  .info-box {
    order: 4;
  }
}

/*
    SOMEBODY WHO ASKED THEIR DEVICE FOR LESS MOVEMENT GETS NONE

    This block is the LAST thing in the file, and that placement is the only reason it works. A
    media condition contributes no weight of its own to the rules inside it, so each rule here
    weighs exactly as much as the one it exists to overrule - an id against an id for the menu,
    a class against a class for the dim layer - and between two rules of equal weight the later
    one in the file wins. Filed above the width blocks, which is where a reader following their
    descending order would naturally put it, both declarations lose : the preference is silently
    not honoured and the block goes on looking perfectly correct. Nothing else in this repository
    reads a motion preference, so nothing would ever report it.

    Both halves are named. Honouring the preference on the menu alone would still leave the page
    behind it fading, which is half an animation rather than none.
*/
@media (prefers-reduced-motion: reduce) {
  #nav-drawer,
  .nav-backdrop {
    transition: none;
  }
}
