/* --- 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 four 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 four track sizes are not a taste. The numeric block holds six fields in five columns and is the
   only one with a width it cannot give up, so it is sized to its own content and left out of the
   sharing ; 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. */
.filter-panel {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 0.7fr) minmax(0, 0.58fr);
  gap: 10px;
  width: min(calc(100% - 32px), 1120px);
  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"],
.filter-output-block label[for="render-searchfield"] { grid-column: 1; grid-row: 1; }
.filter-frosting-block #shapeSelect,
.filter-output-block #render-searchfield { grid-column: 2; grid-row: 1; }
.filter-frosting-block label[for="frostedFacets"],
.filter-output-block label[for="sortOrder"] { grid-column: 1; grid-row: 2; }
.filter-frosting-block #frostedFacets,
.filter-output-block #sortOrder { 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; }

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

.filter-output-block .search-button {
  grid-column: 1 / -1;
  grid-row: 3;
  align-self: center;
  justify-self: stretch;
  min-height: 28px;
}

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

/* The panel's own furniture - its heading, its close cross, its two buttons 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. */
.panel-title,
.panel-close,
.panel-apply,
.panel-reset,
.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,
.filter-panel.is-open .panel-reset {
  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 card itself : red where the page it opens
   shows the diagram without its cutting instructions, orange 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.

   An outline and not a border, because a border is part of the box : two pixels of it on some cards
   and none on the others would draw those cards a different size and shift the row they sit in. An
   outline is painted outside the box and moves nothing. The offset keeps it clear of the picture
   without reaching the card beside it, which sits ten pixels away. */
.gif.gem-closed-design,
.gif.gem-paid-design {
  outline-style: solid;
  outline-width: 2px;
  outline-offset: 4px;
}

.gif.gem-closed-design {
  outline-color: var(--closed-design-outline-dark);
}

body.light-mode .gif.gem-closed-design {
  outline-color: var(--closed-design-outline-light);
}

.gif.gem-paid-design {
  outline-color: var(--paid-design-outline-dark);
}

body.light-mode .gif.gem-paid-design {
  outline-color: var(--paid-design-outline-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 .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-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-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-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-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-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-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-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 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. It is the only positioned box inside a card, and a
   positioned box is painted after its unpositioned siblings, so it is over the picture by
   construction ; 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. */
.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. Nothing is covered up: the gallery is empty for exactly as long as this shows.

     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%);
  }
}
