/* --- The band above the gallery (unique to this page) ---

   A visitor sees one continuous strip here, drawn by two sibling blocks. The intro contains the
   source links, click hint and result count and scrolls away with the page; the search controls
   underneath stay pinned to the top. Both halves share the same colours so they still read as one
   surface before scrolling begins. */
.gallery-intro,
.search-bar {
  width: 100%;
  background-color: #f6f6f6;
  color: black;
  text-align: center;
  font-size: 0.9rem;
}
.gallery-intro {
  display: grid;
  grid-template-columns: max-content max-content;
  align-items: baseline;
  justify-content: center;
  column-gap: 10px;
  padding: 10px 0 0;
}
.gallery-intro-status {
  display: contents;
}
.gallery-intro-status #gemCount {
  grid-column: 1 / -1;
  grid-row: 2;
  font-weight: 600;
}
.search-bar {
  position: sticky;
  /* The bar pins flush against the top of the viewport. This line is the only thing that
     decides where it sits - no script moves it, so what the stylesheet says is what happens
     at every scroll position. */
  top: 0;
  padding: 0 0 10px;
  z-index: 1000;
}
body.light-mode .gallery-intro,
body.light-mode .search-bar {
  background-color: #e5e5e5;
  color: #1f1f1f;
}
body:not(.light-mode) .gallery-intro,
body:not(.light-mode) .search-bar {
  background-color: #2a2a2a;
  color: #eaeaea;
}

/* The button that opens the filter panel, in the toolbar's own colours, which is why it is written
   here rather than beside the small-screen rules that reveal it : the bar's two colours and the
   colours of the one control standing on that bar stay in one place.

   It is drawn only on a small screen, where it is alone and centred on the pinned bar, and with no
   colour of its own it draws the browser's pale button face over a bar that is not pale. Both
   declarations are written out because a button does not inherit its text colour the way ordinary
   text does - the browser puts one on it, and the colour of the bar around it never arrives by
   itself. The values are read off the pair directly above rather than chosen, so no new colour
   enters the site. No width condition here either : this button is hidden at the wide layout a few
   lines below, so the colour has no box to paint there. */
body:not(.light-mode) button.filters-toggle {
  background-color: #2a2a2a;
  color: #eaeaea;
}

body.light-mode button.filters-toggle {
  background-color: #e5e5e5;
  color: #1f1f1f;
}

/* The desktop toolbar is one centred grid of five blocks on a single row. The width is bounded both
   by the viewport and by a readable maximum, so the grid cannot make the page scroll sideways near
   the mobile breakpoint.

   The five track sizes are not a taste. Two of them are sized to their own content and left out of the
   sharing : the numeric block holds six fields in five columns and has a width it cannot give up, and
   the block of buttons at the end is exactly as wide as the longest word written on a button. The other
   three share what is left in the proportion of the narrowest each can be drawn at, measured in a
   browser at the narrowest width this layout is ever used - 1025px, one pixel above the width where the
   panel becomes a sheet. That is what keeps the text search the field that grows when there is room and
   the field that gives way when there is not, which is the right way round : it is the only control
   here that can use any width at all.

   The material dropdown is why the last shared track matters more than it used to. Some material names
   run past thirty characters, and that block's own track is what holds every dropdown in it to the same
   width instead of letting one name push the toolbar off the side of the page. Nothing here names a
   width for it : the track caps the cell, the minimum width written on every field of these blocks
   stops a long name from widening the track, and the three dropdowns of that block therefore come out
   the same size as each other whatever the catalogue holds. */
.filter-panel {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 0.85fr) minmax(0, 0.62fr) max-content;
  gap: 10px;
  width: min(calc(100% - 32px), 1280px);
  margin: 0 auto;
  box-sizing: border-box;
  text-align: left;
}

.filter-block {
  min-width: 0;
  padding: 10px;
  border: 1px solid #555;
  border-radius: 6px;
  box-sizing: border-box;
  background-color: #2a2a2a;
}

body.light-mode .filter-block {
  border-color: #ccc;
  background-color: #e5e5e5;
}

/* These wrappers carry counting and mobile-row meaning, but their boxes are unnecessary inside
   the desktop blocks. Removing only those boxes lets the named controls occupy explicit grid cells. */
.filter-group,
.filter-row {
  display: contents;
}

/* The leftmost block : the index wheel, the design's type and the free-text search, one row each,
   with the label in the first column and the control in the second - the same two-column shape the
   frosting and output blocks use, so all four blocks draw their rows at the same three heights. */
.filter-information-block {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  grid-template-rows: repeat(3, 28px);
  align-items: center;
  column-gap: 8px;
  row-gap: 8px;
}

.filter-information-block label[for="indexWheel"] { grid-column: 1; grid-row: 1; }
.filter-information-block #indexWheel { grid-column: 2; grid-row: 1; }
.filter-information-block label[for="designType"] { grid-column: 1; grid-row: 2; }
.filter-information-block #designType { grid-column: 2; grid-row: 2; }
.filter-information-block label[for="inTextValue"] { grid-column: 1; grid-row: 3; }
.filter-information-block #inTextValue { grid-column: 2; grid-row: 3; }

.filter-information-block .filter-text {
  width: 100%;
  min-width: 0;
  margin: 0;
}

.filter-numeric-block {
  display: grid;
  grid-template-columns: max-content 40px minmax(8px, 1fr) max-content 40px;
  grid-template-rows: repeat(3, 28px);
  align-items: center;
  column-gap: 8px;
  row-gap: 8px;
}

.filter-numeric-block label[for="minLw"] { grid-column: 1; grid-row: 1; }
.filter-numeric-block #minLw { grid-column: 2; grid-row: 1; }
.filter-numeric-block label[for="maxLw"] { grid-column: 4; grid-row: 1; }
.filter-numeric-block #maxLw { grid-column: 5; grid-row: 1; }
.filter-numeric-block label[for="minRI"] { grid-column: 1; grid-row: 2; }
.filter-numeric-block #minRI { grid-column: 2; grid-row: 2; }
.filter-numeric-block label[for="maxRI"] { grid-column: 4; grid-row: 2; }
.filter-numeric-block #maxRI { grid-column: 5; grid-row: 2; }
.filter-numeric-block label[for="minFacetCount"] { grid-column: 1; grid-row: 3; }
.filter-numeric-block #minFacetCount { grid-column: 2; grid-row: 3; }
.filter-numeric-block label[for="maxFacetCount"] { grid-column: 4; grid-row: 3; }
.filter-numeric-block #maxFacetCount { grid-column: 5; grid-row: 3; }
.filter-numeric-block .facet-count-notice { grid-column: 1 / -1; grid-row: 4; }

.filter-frosting-block,
.filter-output-block {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  grid-template-rows: repeat(3, 28px);
  align-items: center;
  column-gap: 8px;
  row-gap: 8px;
}

.filter-frosting-block label[for="shapeSelect"] { grid-column: 1; grid-row: 1; }
.filter-frosting-block #shapeSelect { grid-column: 2; grid-row: 1; }
.filter-frosting-block label[for="frostedFacets"] { grid-column: 1; grid-row: 2; }
.filter-frosting-block #frostedFacets { grid-column: 2; grid-row: 2; }
.filter-frosting-block label[for="frostedPavillon"] { grid-column: 1; grid-row: 3; }
.filter-frosting-block #frostedPavillon { grid-column: 2; grid-row: 3; }

/* The two blocks shared the six lines above while both drew the same three rows. They are written
   apart now that they do not : the output block opens on the material rather than on the render
   choice, so one list of rows can no longer describe both. */
.filter-output-block label[for="materialSelect"] { grid-column: 1; grid-row: 1; }
.filter-output-block #materialSelect { grid-column: 2; grid-row: 1; }
.filter-output-block label[for="render-searchfield"] { grid-column: 1; grid-row: 2; }
.filter-output-block #render-searchfield { grid-column: 2; grid-row: 2; }
.filter-output-block label[for="sortOrder"] { grid-column: 1; grid-row: 3; }
.filter-output-block #sortOrder { grid-column: 2; grid-row: 3; }

.filter-block input,
.filter-block select {
  min-width: 0;
  margin: 0;
}

.filter-output-block .ordering-notice {
  grid-column: 1 / -1;
  grid-row: 4;
}

/* The block of buttons that closes the toolbar : one button per row, each at the height of a field so
   its three rows line up with the three rows of every block beside it.

   Only three of the four buttons written into it are drawn here. Apply belongs to the sheet a narrow
   screen opens and keeps the display value the rule further down gives it, so it takes no row and
   leaves no gap - which is what lets these rows be named one by one rather than counted. */
.filter-action-block {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: repeat(3, 28px);
  align-items: center;
  row-gap: 8px;
}

.filter-action-block .scroll-to-top-button { grid-row: 1; }
.filter-action-block .panel-reset { grid-row: 2; }
.filter-action-block .search-button { grid-row: 3; }

.filter-action-block .scroll-to-top-button,
.filter-action-block .panel-reset,
.filter-action-block .search-button {
  grid-column: 1;
  align-self: center;
  justify-self: stretch;
  min-height: 28px;
}

/* The panel's own furniture - its heading, its close cross, Apply and the button that opens it - is
   drawn only by the small-screen rules. They are in the document all the same : the same elements a
   small screen shows, written once.

   Reset is deliberately no longer in this list. It is the one control of the sheet the wide screen
   draws as well, in the block of buttons that closes the toolbar, so hiding it here would mean a
   second Reset button existing for that width alone - two buttons whose words and whose effect are
   then free to drift apart. A closed panel hides it along with everything else inside it, so none of
   this shows on a narrow screen. */
.panel-title,
.panel-close,
.panel-apply,
.filters-toggle {
  display: none;
}

/* what an open filter panel draws, and it is only a reveal

   This rule sits outside any screen-width block for the same reason the drawer's open state does :
   nothing carries the open state at rest, a panel is opened by a tap and never by a window size,
   so no width's picture can move on its account. All it does is bring back the four controls the
   block above hid - the heading, the close cross and the two buttons - because a control the base
   layer hides cannot be tapped and cannot take the focus either.

   Nothing else about the open panel is decided here. Its box, its layer, its colours, its bands and
   the order of its rows belong to the screen-width file, which is the only place that knows this
   panel is a sheet over the whole screen rather than the desktop grid.

   Why the panel is a sheet of its own rather than something drawn inside the toolbar - the part
   that is load-bearing rather than a look. The toolbar sits in the flow above the gem grid, so
   anything that changes its height moves everything below it, and the browser answers by
   re-scrolling the page to keep what the eye was on where it was. Measured on this page : four
   controls added to the toolbar grow it from 233px to 346px and the scroll offset follows, 500 to
   613. Out of the flow, the toolbar stays 233px and the offset stays 500. Both readings leave the
   grid at the same place on screen, so it is the offset itself that has to be left alone - a
   visitor who opens a panel and closes it has to come back to the number they left, not merely to
   the same view. Anyone later tempted to lay this panel out inside the toolbar is looking at the
   two numbers that say what it costs. */
.filter-panel.is-open .panel-title,
.filter-panel.is-open .panel-close,
.filter-panel.is-open .panel-apply {
  display: block;
}

/* Inputs & selects inside search bar */
.search-bar input[type="text"],
.search-bar input[type="number"],
.search-bar select {
  height: 28px;
  background-color: #2c2c2c;
  color: #eaeaea;
  border: 1px solid #555;
  border-radius: 4px;
  padding: 4px 6px;
  margin: 0 4px;
  box-sizing: border-box;
}
body.light-mode .search-bar input[type="text"],
body.light-mode .search-bar input[type="number"],
body.light-mode .search-bar select {
  background-color: #ffffff;
  color: #1f1f1f;
  border: 1px solid #ccc;
}

/* Compact widths. Classes rather than ids : an id selector outranks anything a later
   stylesheet can write with an ordinary class rule, so a field would keep its desktop
   width on a narrow screen with nothing in the inspector to explain why. The ids stay on
   the elements themselves - the filtering code looks every field up by id. */
.filter-number { width: 40px; }
.filter-number-wide { width: 50px; }
.filter-text { width: 120px; }

/* The line inviting the visitor to click a gem. Its size lives here rather than on the
   element, so a narrower screen can reach it - an attribute on the element itself outranks
   every stylesheet rule and nothing could have overridden it. */
.call-to-action { font-size: 20px; }

/* Links in the band follow the theme instead of being browser blue. The intro is named here as
   well as the toolbar because the three source links live in the intro now : left off, they
   would quietly turn default blue, and purple once visited, in both themes - a change nothing
   that measures boxes could ever report. */
.gallery-intro a,
.search-bar a { color: inherit; }
body.light-mode .gallery-intro a,
body.light-mode .search-bar a { color: #1f1f1f; }

/* The fixed sentences the search bar can show after a refused search. They are drawn like this site's
   other refusal lines rather than like something new : their shared class gives them this site's red,
   and the box under them comes from a colour the palette already declares.

   THE SURFACE IS WHAT MAKES IT READABLE AND IT IS NOT DECORATION. Read against the toolbar's own two
   colours, this red measures 5.17 to 1 on the dark theme and 4.46 to 1 on the light one - and 4.5 is
   the floor a sentence of ordinary size has to clear, so the light theme was under it. On the two
   palette surfaces below it measures 5.03 and 5.62. The rule watching this site's colours compares
   palette colours against palette surfaces and never against a colour written into a rule, so the
   toolbar's own two are outside what it can see and this pair was measured by hand.

   THE DISPLAY VALUE IS WRITTEN AGAINST "NOT HIDDEN" ON PURPOSE. A stylesheet of ours setting a
   display value at all outranks the browser's own rule for the attribute that hides this element,
   whatever the specificity of either, so a plain display value here would leave the sentence on
   screen for ever and nothing in the inspector would explain why.

   The twenty pixels of space the shared refusal rule opens underneath are taken back : that space is
   right under a form and wrong inside a toolbar, where it would push the whole gallery down for as
   long as the line is up. */
.ordering-notice:not([hidden]),
.facet-count-notice:not([hidden]) {
  display: inline-block;
}

.ordering-notice,
.facet-count-notice {
  margin-bottom: 0;
  padding: 6px 10px;
  border-radius: 5px;
  background-color: var(--surface-dark);
}

body.light-mode .ordering-notice,
body.light-mode .facet-count-notice {
  background-color: var(--surface-light);
}

/* --- Gallery grid --- */
.gem-gallery {
  /* The one place the gem image's width is written. The picture, its caption and its info
     box all read it from here, so a narrower screen changes one value. */
  --gem-image-width: 300px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.gif {
  margin: 10px;
  flex-basis: 320px;
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-bottom: 20px;
  /* Only so that the refusal line at the foot of this file has something to be drawn against, and it
     moves nothing on either layout : this box is given no offsets, and a box positioned relatively
     with no offsets is drawn exactly where the flow put it. Read in a browser either side of this one
     line at a phone width, every box of every card - the picture, the caption, the row, the badges,
     the info box and the distance drawn between two cards - came back identical. Worth saying,
     because a bare position on a card in a gallery reads like the start of something and is not. */
  position: relative;
}
/* The picture the card is built around, and the video that takes its place while a gem is
   animating. One rule for the two of them because they have to be the same box : the video is
   put where the picture was and taken away again, and a card that changed size between the two
   would shove every card below it up and down on each tap. */
.gif img,
.gif video {
  width: var(--gem-image-width);
  /* The height has to stay automatic : a ratio is ignored outright when both sizes are
     fixed. Written out rather than left implicit so it does not read as a leftover.
     The ratio is bare on purpose - pairing it with `auto` would let the box snap to the
     picture's own proportions the instant the file arrives, which is the jump this rule
     exists to prevent. */
  height: auto;
  aspect-ratio: 1 / 1;
  /* Today's technical drawings are about half again as wide as they are tall, and people
     read proportions off them. Inside a square box the default would stretch them ; this
     draws them at their true shape with empty bands instead. It stops doing anything the
     day the pictures themselves are square. */
  object-fit: contain;
  cursor: pointer;
}

/* Which of the two a card is drawing. The script writes and removes the state class and decides
   nothing about the look ; a card that has never been asked to animate holds no video at all, so
   the second rule matches nothing on almost every card on the page. */
.gif.is-animating img {
  display: none;
}

.gif:not(.is-animating) video {
  display: none;
}

/* What waits at the other end of Get design, said on the picture : a red "closed" where the page it
   opens shows the diagram without its cutting instructions, an orange "buyable" where it asks to be
   bought. Every other card is drawn plain, which is every free design and every design whose row says
   nothing at all.

   The badge sits over the picture rather than beside it, so it costs the card no height and the rows
   of gems keep the spacing they had. That is what the box below is for : the picture and the video
   are laid out exactly as before inside it, and the badge is positioned against it.

   It is given the picture's own width and nothing else, so it takes the size the picture took and no
   card changes shape whether it carries a badge or not.

   The badge takes no taps. It covers a corner of a picture whose whole surface flips the gem into its
   animation, and a dead spot there would read as the picture having stopped working. */
.gem-picture {
  position: relative;
  display: flex;
  width: var(--gem-image-width);
}

.gem-type-badge {
  position: absolute;
  top: 6px;
  right: 6px;
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 0.75rem;
  font-weight: bold;
  line-height: 1.5;
  pointer-events: none;
}

/* The two colours it is drawn in. The state class stays on the card, since that is what the script
   writes ; these rules reach the badge from it. */
.gif.gem-closed-design .gem-type-badge {
  background-color: var(--closed-design-badge-dark);
  color: var(--design-badge-text-dark);
}

body.light-mode .gif.gem-closed-design .gem-type-badge {
  background-color: var(--closed-design-badge-light);
  color: var(--design-badge-text-light);
}

.gif.gem-paid-design .gem-type-badge {
  background-color: var(--paid-design-badge-dark);
  color: var(--design-badge-text-dark);
}

body.light-mode .gif.gem-paid-design .gem-type-badge {
  background-color: var(--paid-design-badge-light);
  color: var(--design-badge-text-light);
}

.gif-name {
  max-width: var(--gem-image-width);
  word-wrap: break-word;
  margin: 5px 0 0;
  text-align: center;
  font-size: 0.8rem;
  height: 2.5em;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

/* Gallery titles look like links while acting as comment-panel controls. */
.gif-name .gem-name-comment {
  appearance: none;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: inherit;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
}
body.light-mode .gif-name .gem-name-comment {
  color: var(--link-light);
}
/* Dark mode: the same blue every other link on the site is written in, rather than
   the near-white this used to be, which read as ordinary text rather than as a link. */
body:not(.light-mode) .gif-name .gem-name-comment {
  color: var(--link-dark);
}

/* Hover/focus : the underline this always had, and now the site's own pointer colour with it, one
   value per theme because no single lightness of that hue is readable on both a dark card and a
   white one. */
body:not(.light-mode) .gif-name .gem-name-comment:hover,
body:not(.light-mode) .gif-name .gem-name-comment:focus-visible {
  color: var(--link-hover-dark);
  text-decoration: underline;
}
body.light-mode .gif-name .gem-name-comment:hover,
body.light-mode .gif-name .gem-name-comment:focus-visible {
  color: var(--link-hover-light);
  text-decoration: underline;
}

/* --- Info box (search highlights) --- */
.info-box {
  width: var(--gem-image-width);
  margin-top: 10px;
  padding: 10px;
  border: 1px solid #ccc;
  background-color: #f9f9f9;
  border-radius: 5px;
  max-height: 100px;
  overflow: auto;
  white-space: pre-wrap;
  word-wrap: break-word;
  color: #000;
}
body:not(.light-mode) .info-box {
  background: #403f48;
  border-color: #343438;
  color: #ccc;
}

/* --- Gem badges ---
   Moved to design-badge.css, because the "my submissions" page draws the same four chips under a
   design it is still working on and two copies of one rule drift. Every page that shows a badge
   links that file. */

/* --- The row of controls a card carries under its caption : the score, the two arrows and the
       speech bubble ---

   Modelled on the badges above, which is the nearest thing this file already had : a centred flex row
   with a gap, and no width of its own. The card is a column that centres its children, so a child
   with no width is drawn exactly as wide as its contents and centred by construction - writing a
   width here is the one thing that could stop that, at every screen size at once. */
.gem-action-row {
  --gem-action-control-height: 44px;

  display: flex;
  justify-content: center;
  align-items: center;
  gap: 8px;
  min-height: var(--gem-action-control-height);
  margin: 4px 0;
}

.loader {
  --color-1: #fff;
  --size: 1px;

  width: calc(160 * var(--size));
  height: calc(6 * var(--size));
  border-radius: calc(3 * var(--size));
  display: block;
  margin: calc(30 * var(--size)) auto;
  background: repeating-linear-gradient(
    90deg,
    var(--color-1) 0,
    var(--color-1) calc(10 * var(--size)),
    transparent calc(10 * var(--size)),
    transparent calc(18 * var(--size))
  );
  background-size: calc(18 * var(--size)) 100%;
  box-sizing: border-box;
  animation: march 0.6s linear infinite;
}

@keyframes march {
  0% {
    background-position: 0 0;
  }
  100% {
    background-position: calc(-18 * var(--size)) 0;
  }
}

.gem-action-row .loader {
  display: none;
}

.gem-action-row.is-loading-animation .loader {
  display: block;
  margin: 0 auto;
}

.gem-action-row.is-loading-animation .gem-score,
.gem-action-row.is-loading-animation .gem-vote,
.gem-action-row.is-loading-animation .gem-comment,
.gem-action-row.is-loading-animation .gem-shape-validation,
.gem-action-row.is-loading-animation .gallery-cut-button {
  display: none;
}

/* The score, held to the width of three characters and drawn in figures that are all the same width.
   Both halves are about the row not moving under a finger. The number is printed exactly as the
   server sent it, so it is `0` one moment and `-12` the next : a box that grows with its digits
   re-centres the whole row under the caption every time somebody votes, and the two arrows slide
   sideways while one of them is still being pressed. Tabular figures stop the same jitter between two
   numbers of the same length, where a proportional `1` is narrower than a `0`. */
.gem-score {
  min-width: 3ch;
  text-align: center;
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}

/* The three controls a finger has to be able to hit, at the size a finger needs, ON THE ELEMENT THAT
   RECEIVES THE TAP : the floors are on the buttons themselves and never on a box around them, because
   the tap is delivered to the button's own border box and a roomy parent buys nothing at all. Floors
   rather than fixed sizes, so a control whose own contents need more room takes it.

   A button inherits none of the card around it - the browser puts a face, a border and a text colour
   on it - so all four are written out here. The colour is the muted grey this site's secondary text
   already uses, one value per theme, and it is the only colour these controls have at rest. */
.gem-vote,
.gem-comment,
.gallery-cut-button,
.gem-shape-validation,
.gem-report-broken-link,
.gem-report-correction {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  /* The height variable is declared on the action row, so it resolves for these controls only while they sit
     inside one. They are also used in the comment panel's share row, where nothing declares it and the
     declaration would be dropped, leaving the button as tall as its icon. The fallback is the same finger-sized
     floor the row itself uses, so a control is never smaller than a fingertip wherever it is placed. */
  min-height: var(--gem-action-control-height, 44px);
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  color: var(--muted-dark);
  text-decoration: none;
}

body.light-mode .gem-vote,
body.light-mode .gem-comment,
body.light-mode .gallery-cut-button,
body.light-mode .gem-shape-validation,
body.light-mode .gem-report-broken-link,
body.light-mode .gem-report-correction {
  color: var(--muted-light);
}

.gallery-cut-button[hidden] {
  display: none;
}

.gem-action-row .gem-report-broken-link[hidden],
.gem-action-row .gem-report-correction[hidden] {
  display: none;
}

/* The size of the four drawings, in one place. Each vector also carries a size as attributes, which
   is the weakest thing there is in the cascade : it exists so that a row nothing has styled is still
   drawn at a sane size rather than at the 300 by 150 a browser gives a vector that names none, and
   this rule overrides it without having to win any argument.
   They are given no colour of their own. Each path is stroked in the current text colour, so the one
   declaration above paints the icon at the same time as the button it sits in. */
.gem-vote-icon,
.gem-comment-icon,
.gallery-cut-icon,
.gem-shape-validation-icon,
.gem-report-broken-link-icon,
.gem-report-correction-icon {
  width: 20px;
  height: 20px;
}

/* The supplied report drawings stay immutable files; masks let the button's current text colour
   paint them through every theme, hover, focus, and busy state. */
.gem-report-broken-link-icon,
.gem-report-correction-icon {
  display: block;
  flex: 0 0 auto;
  background-color: currentColor;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.gem-report-broken-link-icon {
  -webkit-mask-image: url("../broken-link-icon.svg");
  mask-image: url("../broken-link-icon.svg");
}

.gem-report-correction-icon {
  -webkit-mask-image: url("../edit-box-icon.svg");
  mask-image: url("../edit-box-icon.svg");
}

/* Under the pointer, the theme's own text colour - the same colour the caption above them is drawn
   in, so a control brightens without a new value entering the site. */
.gem-vote:hover,
.gem-comment:hover,
.gallery-cut-button:hover,
.gem-shape-validation:hover,
.gem-report-broken-link:hover,
.gem-report-correction:hover {
  color: var(--text-dark);
}

body.light-mode .gem-vote:hover,
body.light-mode .gem-comment:hover,
body.light-mode .gallery-cut-button:hover,
body.light-mode .gem-shape-validation:hover,
body.light-mode .gem-report-broken-link:hover,
body.light-mode .gem-report-correction:hover {
  color: var(--text-light);
}

/* The focus ring, and it matters more on this page than anywhere else on the site : until this row
   arrived a card held exactly one thing that could take the focus, and it now holds four, so somebody
   travelling the gallery with a keyboard has to be able to see which of the four they are on.
   Drawn for the keyboard and not left under the pointer after a tap, and held inside the button's own
   box by the negative offset so that two neighbouring rings can never overlap. */
.gem-vote:focus-visible,
.gem-comment:focus-visible,
.gallery-cut-button:focus-visible,
.gem-shape-validation:focus-visible,
.gem-report-broken-link:focus-visible,
.gem-report-correction:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

body.light-mode .gem-vote:focus-visible,
body.light-mode .gem-comment:focus-visible,
body.light-mode .gallery-cut-button:focus-visible,
body.light-mode .gem-shape-validation:focus-visible,
body.light-mode .gem-report-broken-link:focus-visible,
body.light-mode .gem-report-correction:focus-visible {
  outline-color: var(--accent-light);
}

/* The arrow matching the caller's own vote, FILLED in this site's accent rather than merely tinted :
   an outlined triangle reads as a control that is available and a solid one as a vote already cast,
   which is a difference the eye catches going down a gallery of cards.

   THE ATTRIBUTE IS THE SELECTOR, and that is the point of this rule rather than a detail of it. That
   attribute is at once what a screen reader announces about the control and what the eye is shown, so
   there is one source of truth for the two and no second class that has to be kept in step with it.

   The accent paints the vector and never the button's own text colour, and the difference is not
   cosmetic. Here the accent is a filled shape and not a sentence : measured against this site's own
   palette, the light half of the pair reads 4.28 to 1 on the light theme's page - past the 3 to 1 a
   graphical object has to reach and short of the 4.5 an ordinary sentence does. Moved onto `color` it
   would become one of this site's text colours, which is a claim the palette check measures and would
   refuse. Both the fill and the stroke are named because the shape is stroked in the button's colour
   at rest, and a blue triangle inside a grey outline is not what "filled" means. */
.gem-vote[aria-pressed="true"] .gem-vote-icon path {
  fill: var(--accent);
  stroke: var(--accent);
}

body.light-mode .gem-vote[aria-pressed="true"] .gem-vote-icon path {
  fill: var(--accent-light);
  stroke: var(--accent-light);
}

/* The administrator's tick, drawn in this site's own green once the outline behind it has been
   confirmed and in the same muted grey as every other control on the row until then. The state is read
   off the pressed attribute for the same reason the arrow above it is : that attribute is at once what
   a screen reader announces and what the eye is shown, so there is one source of truth for the two.

   Only the stroke is named, and unlike the arrow that is not an omission : the tick is an open path
   rather than a closed shape, so it is a line and there is nothing inside it to fill. The colour is
   the palette's own success green, which is the one both themes already carry for "this is done". */
.gem-shape-validation[aria-pressed="true"] .gem-shape-validation-icon path {
  stroke: var(--success-dark);
}

body.light-mode .gem-shape-validation[aria-pressed="true"] .gem-shape-validation-icon path {
  stroke: var(--success-light);
}

/* The bubble is what the count is positioned against, and being that is the whole of what this line
   does. */
.gem-comment,
.gallery-cut-button {
  position: relative;
}

/* The comment count, floating over the bubble's top-right corner the way an unread count sits on a
   phone's application icon rather than standing beside it on the row. That is what the arrangement
   buys, and it is why standing beside it was turned down : out of the flow, THE ROW IS EXACTLY THE
   SAME WIDTH AT NO BADGE, AT SEVEN AND AT NINE HUNDRED AND NINETY-NINE, so the arrows are drawn at
   the same place on every card of the gallery whatever each design has collected. It hangs outside
   the drawn bubble and well inside the button's own 44 pixels, so nothing clips it and it is never
   what a tap lands on.

   AND THE TRAP BESIDE IT, WHICH IS WORTH MORE THAN THE RULE ITSELF. The count is hidden by the
   `hidden` attribute, which works by a `display: none` the browser's own stylesheet supplies - and any
   `display` declared here outranks it. So none is declared above, and the rule below says the same
   thing in writing so that adding one later cannot go wrong in silence. Without one of the two, every
   card in the gallery draws a badge reading zero, on every design, and nothing anywhere reports it. */
.gem-comment-count,
.gallery-cut-count {
  position: absolute;
  top: 5px;
  right: 4px;
  min-width: 10px;
  padding: 0 3px;
  border: 1px solid var(--accent);
  border-radius: 8px;
  background-color: var(--surface-dark);
  color: var(--text-dark);
  font-size: 0.65rem;
  line-height: 14px;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.gem-comment-count[hidden],
.gallery-cut-count[hidden] {
  display: none;
}

body.light-mode .gem-comment-count,
body.light-mode .gallery-cut-count {
  border-color: var(--accent-light);
  background-color: var(--surface-light);
  color: var(--text-light);
}

/* --- The line a refused tap draws over a card's picture ---

   OUT OF THE DOCUMENT'S FLOW, and that is the whole rule rather than one way of drawing it. A notice
   inserted into the card's column makes that card taller for as long as it is shown, so every card
   below it moves down when it appears and back up ten seconds later - under a hand that is by then
   reaching for one of them. Measured on this page before this rule existed : the two rows of cards
   under a refused tap moved down by 41 pixels and back up again. Drawn over the picture, no card moves
   at all, because a box out of the flow takes no space from the ones around it.

   The cost was weighed and accepted : the middle of one render is covered for ten seconds. Anybody
   later moving this back into the card's column would be undoing that decision rather than tidying one
   up.

   Centred vertically rather than pinned to the top, and the card's own order is the reason : the
   caption is the first thing in a card and the picture is the third, so a notice at the top would
   cover the caption and the row of controls - the very arrow that was just tapped - instead of the
   picture it is meant to sit over. The picture is 340 of a card's roughly 470 pixels at a phone width,
   so the middle of the card is inside the picture at every width, with no number to keep in step and
   nothing measured from a script.

   It is given no layer number on purpose. A positioned box is painted after its unpositioned
   siblings, so it is over the picture by construction, and the card's one other positioned box - the
   one the picture and its badge sit in - is listed before it, so it is over that too ; a number here
   would add a card's refusal line to the site's own layer scale for nothing.

   The surface behind it is what makes the sentence readable, and it comes from a colour this site
   already has rather than a new one. The class the element already wears gives it this site's red and
   no box whatever, and red text on its own over a photographic render of a gem is not something a
   person can read. */
.gem-card-notice {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  margin: 0;
  padding: 8px;
  border-radius: 5px;
  background-color: var(--surface-dark);
  text-align: center;
  overflow-wrap: anywhere;
  pointer-events: none;
}

body.light-mode .gem-card-notice {
  background-color: var(--surface-light);
}

/* --- Shared report dialog --- */

.gem-report-backdrop,
.gem-report-dialog {
  display: none;
}

.gem-report-backdrop.is-open {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 1100;
  background-color: rgba(0, 0, 0, 0.5);
}

.gem-report-dialog {
  --gem-report-surface: var(--surface-dark);
  --gem-report-inset: var(--bg-dark);
  --gem-report-text: var(--text-dark);
  --gem-report-muted: var(--muted-dark);
  --gem-report-edge: var(--border-dark);
  --gem-report-accent: var(--accent);
  --gem-report-primary: #30444f;
  --gem-report-primary-text: var(--text-dark);
  color: var(--gem-report-text);
}

body.light-mode .gem-report-dialog {
  --gem-report-surface: var(--surface-light);
  --gem-report-inset: var(--bg-light);
  --gem-report-text: var(--text-light);
  --gem-report-muted: var(--muted-light);
  --gem-report-edge: var(--border-light);
  --gem-report-accent: var(--accent-light);
  --gem-report-primary: #387da8;
  --gem-report-primary-text: var(--surface-light);
}

.gem-report-dialog.is-open {
  display: flex;
  flex-direction: column;
  position: fixed;
  inset: 0;
  width: min(560px, calc(100% - 32px));
  height: fit-content;
  max-height: calc(100% - 32px);
  margin: auto;
  overflow: hidden;
  border: 1px solid var(--gem-report-edge);
  border-radius: 12px;
  background-color: var(--gem-report-surface);
  z-index: 1200;
}

.gem-report-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  padding: 4px 4px 4px 16px;
  border-bottom: 1px solid var(--gem-report-edge);
}

.gem-report-heading {
  flex: 1 1 auto;
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
}

.gem-report-title {
  flex: 0 0 auto;
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
}

.gem-report-design-name {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  color: var(--gem-report-muted);
  font-size: 16px;
  font-weight: 400;
  line-height: 1.5;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.gem-report-close,
.gem-report-cancel,
.gem-report-submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  border: 1px solid var(--gem-report-edge);
  border-radius: 5px;
  color: var(--gem-report-text);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.4;
  cursor: pointer;
}

.gem-report-close {
  flex: 0 0 44px;
  padding: 0;
  border-color: transparent;
  background: none;
}

.gem-report-cancel {
  padding: 8px 16px;
  background-color: var(--gem-report-inset);
}

.gem-report-submit {
  padding: 8px 16px;
  border-color: var(--gem-report-primary);
  background-color: var(--gem-report-primary);
  color: var(--gem-report-primary-text);
}

.gem-report-close:hover,
.gem-report-cancel:hover {
  color: var(--gem-report-text);
  background-color: var(--gem-report-inset);
}

.gem-report-dialog :focus-visible {
  outline: 2px solid var(--gem-report-accent);
  outline-offset: -2px;
}

.gem-report-body {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  padding: 16px;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior-y: contain;
}

.gem-report-form:not([hidden]) {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

.gem-report-field,
.gem-report-current-shape {
  display: grid;
  gap: 8px;
  min-width: 0;
}

.gem-report-field > label,
.gem-report-field-label {
  color: var(--gem-report-text);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.4;
}

.gem-report-field input,
.gem-report-field select,
.gem-report-field textarea {
  width: 100%;
  min-width: 0;
  min-height: 44px;
  padding: 8px;
  border: 1px solid var(--gem-report-edge);
  border-radius: 5px;
  background-color: var(--gem-report-inset);
  color: var(--gem-report-text);
  font: inherit;
  font-size: 16px;
  font-weight: 400;
  line-height: 1.5;
}

.gem-report-field textarea {
  min-height: 128px;
  resize: vertical;
}

.gem-report-field input:focus-visible,
.gem-report-field select:focus-visible,
.gem-report-field textarea:focus-visible {
  border-color: var(--gem-report-accent);
}

.gem-report-field input[aria-invalid="true"],
.gem-report-field select[aria-invalid="true"],
.gem-report-field textarea[aria-invalid="true"] {
  border-color: currentColor;
}

.gem-report-help,
.gem-report-counter {
  margin: 0;
  color: var(--gem-report-muted);
  font-size: 12px;
  font-weight: 400;
  line-height: 1.5;
  overflow-wrap: anywhere;
}

.gem-report-counter {
  text-align: right;
}

.gem-report-modal-error,
.gem-report-field-error,
.gem-report-correction-error {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.4;
  overflow-wrap: anywhere;
}

.gem-report-field-error,
.gem-report-correction-error {
  text-align: left;
}

.gem-report-shape-comparison {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
  min-width: 0;
}

.gem-report-current-shape-value {
  min-width: 0;
  color: var(--gem-report-muted);
  font-size: 16px;
  font-weight: 400;
  line-height: 1.5;
  overflow-wrap: anywhere;
}

.gem-report-frosted-row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  min-height: 44px;
  font-size: 16px;
  font-weight: 400;
  line-height: 1.5;
  cursor: pointer;
}

.gem-report-frosted-row input {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  margin: 0;
  accent-color: var(--gem-report-accent);
}

.gem-report-frosted-label {
  min-width: 0;
  overflow-wrap: anywhere;
}

.gem-report-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  min-width: 0;
  margin-top: 8px;
}

.gem-report-broken-link.is-report-busy,
.gem-report-correction.is-report-busy,
.gem-report-submit.is-report-busy {
  opacity: 0.6;
  cursor: progress;
}

/* --- The bar shown while a search is running and the gallery is still empty ---

   Its own name, like every other moving shape here. `.loader` above is the marching bar a card
   shows while its animation is being fetched, and the two would otherwise both answer to it.

   The gallery answers with every design at once, which is megabytes of JSON on a catalogue this
   size, so there is a real wait between asking and seeing anything. Without something here the page
   sits empty and reads as a search that found nothing. */
/* The sentinel the script watches to know the visitor has reached the end of what is drawn, at which
   point it asks for the next packet of designs. It carries nothing and is announced to nobody; it
   needs a height only because a box of no height is a fragile thing to ask a browser to notice. */
.gem-gallery-end {
  height: 1px;
}

.gallery-wait {
  /* Fixed rather than standing where the gallery will be, so it is centred on the screen itself at
     any size -- the same on a phone as on a wide monitor -- and stays put if the visitor scrolls
     while waiting.

     It shows for two different waits. On a new search the gallery below is empty and nothing is
     covered up. On the next packet of designs the gallery is full and this crosses it, which is
     deliberate: at that moment the visitor is at the bottom of a screenful of cards with nothing
     underneath, and a thin sweep is the difference between "more is coming" and "that was all".

     No `display` property here on purpose. This element is turned off with the hidden attribute,
     whose own `display: none` any display set here would silently beat, leaving the bar sweeping
     over a finished gallery. */
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  /* Under the search bar pinned at the top of this file, which is 1000, so a visitor can still
     retype a search while one is running. */
  z-index: 900;
  /* The track the bar sweeps along : most of the width on a phone, capped on a desktop so the
     sweep stays a sweep rather than becoming a slow crawl across a very wide screen. */
  width: min(70vw, 20rem);
}

.gallery-wait-bar {
  --color-1: #B3ACAC;
  --size: 1px;

  width: 100%;
  height: calc(4.8 * var(--size));
  display: inline-block;
  position: relative;
  overflow: hidden;
}
.gallery-wait-bar::after {
  content: '';
  width: calc(96 * var(--size));
  height: calc(4.8 * var(--size));
  background: var(--color-1);
  position: absolute;
  top: 0;
  left: 0;
  box-sizing: border-box;
  animation: hitZak 0.6s ease-in-out infinite alternate;
}

@keyframes hitZak {
  0% {
    left: 0;
    transform: translateX(-1%);
  }
  100% {
    left: 100%;
    transform: translateX(-99%);
  }
}

/* The line drawn across the gallery when a search matched nothing. A child of the grid rather than a
   sibling of it, so it is emptied out by the same call that empties the cards and cannot survive into
   the next search. It takes a whole row of the wrapping flex container, which is what puts it in the
   middle of the strip the cards would have filled rather than beside the first of them.

   The size is asked for in viewport width with a floor and a ceiling, so the sentence stays one line
   on a phone and does not grow to fill a very wide monitor. */
.gem-gallery-empty {
  flex-basis: 100%;
  text-align: center;
  padding: 4rem 1rem;
  font-size: clamp(1.5rem, 4vw, 2.75rem);
  font-weight: 600;
  line-height: 1.2;
  color: var(--muted-dark);
}

body.light-mode .gem-gallery-empty {
  color: var(--muted-light);
}
