/* 推しルーム — the front page: a shelf of acrylic stands.
   A thumbnail is a cut-out, so the page treats each broadcaster's character as the thing the
   culture around it already treats it as: an アクリルスタンド. A die-cut figure in a clear plate,
   standing in a round base. That is not a metaphor reached for -- a transparent avatar standing on
   somebody's desktop already is one.

   Flat, apart from the stand itself. No shadow under anything, no reflection, no bevel; the shelf
   is a line. What has depth is the object, and it has depth because it really does: see stands.js,
   which extrudes the plate from the picture's own outline.
   Two designs that were not chosen are kept in ../../front-page-alternates/. */
:root {
  /* Warm taupe rather than paper-white. Paper-white was tried first and failed on the content: the
     plate is clear and its backing is white, so on a near-white wall a pale character -- white
     dress, blonde hair -- dissolves into the page. The ground has to be dark enough for white to
     be a colour. Light overall is still the point: a shelf of merchandise belongs in a lit room. */
  --wall: #ddd0be;
  --wall-2: #d2c3ae;
  --shelf: #a98f70;       /* the shelf edge */
  --plate: #ffffff;       /* the base, and the backing the character is printed onto */
  --ink: #2e2822;
  --ink-2: #6d6155;
  --ink-3: #8a7c6a;
  --on-air: #d8452f;
  /* The sign over the shelf. Violet, and not another red: the 配信中 tag is the ONLY red on this
     page, and it is small -- put a red title above it and the red stops meaning "on now". Violet is
     also the near-opposite of the taupe wall, so the sign reads as printed on top of the room
     rather than as more of the room, and it is the colour the phrase 願えば叶う already lives in
     (七夕, おみくじ, a wish on a star). Two tones of one hue, not two hues: the slant is what
     separates the promise from the name, so the colour does not have to do it a second time. */
  --title: #5b2f86;
  --title-2: #7a55a3;
  --pad: 22px;
  --fig-pad: 7px;   /* the gap between neighbouring stands, held INSIDE the item so their
                       shelf rules still meet -- see .fig */
}
* { box-sizing: border-box; }

/* The stands are drawn here, over the wall and under everything with words on it. ABSOLUTE, and as
   tall as the whole document -- stands.js sizes it -- so that the pixels are part of what the phone
   scrolls rather than something redrawn after the scroll. Fixed, the compositor moved the cards and
   the canvas stayed put until the main thread caught up, and the stands visibly trailed their own
   labels on a flick. Pointer-events off: the DOM card is still the thing you click. */
#gl {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 0;
  pointer-events: none;
}
#top, main, footer { position: relative; z-index: 1; }

/* The face is IN this file, not fetched beside it. On a phone it kept coming out in the
   system font: the woff2 is a separate request, font-display is block, and anything that
   delays or blocks that request past the block period -- a slow first connection, a data
   saver, an extension -- paints the fallback instead. At 3 kB the font is smaller than the
   round trip it costs, so it travels with the stylesheet and there is nothing left to
   fail. font-display stays for the same reason it was there: with the bytes already here,
   it never has anything to do.
   Thirty characters of a fifteen-thousand-glyph face. Cut by tools/mkkeifont.py, which also
   writes it back into this line -- do not hand-edit the base64, and add to that script's
   CHARS before setting anything new in this family. */
@font-face {
  font-family: "Oshi Mark";
  src: url("data:font/woff2;base64,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") format("woff2");
  font-display: block;
}

html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--wall);
  color: var(--ink);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Hiragino Kaku Gothic ProN",
               "Yu Gothic UI", Meiryo, sans-serif;
}

/* Air above and below the sign, scaled to the screen so a phone is not given a desktop's worth of
   it -- the floors keep it generous on a small screen anyway. */
#top {
  padding: clamp(34px, 6vw, 54px) var(--pad) clamp(24px, 4vw, 36px);
  text-align: center;
  display: grid;
  justify-items: center;
  gap: 14px;
}
/* ONE line: the promise runs straight into the name, the way it would be said out loud. Baseline
   aligned, so the small type sits on the same ground as the big type instead of floating. */
/* The size lives HERE, on the line, and both parts are fractions of it -- the em of a child is its
   parent's font-size, and .wish is a sibling of .mark, not a child, so sizing it off the name means
   the name's size has to be the line's size. */
h1 {
  margin: 0;
  font-weight: 400;
  line-height: 1;
  /* Three columns: the phrase, THE NAME, the shout. The two outer columns are 1fr and therefore
     equal, so what sits in the middle of the page is 推しルーム itself -- both of the things hung
     off it are outside the count. (Absolute positioning would centre the name too, but the phrase
     would then reach past the left edge on a phone; here everything is still in flow, so the line
     as a whole is what has to fit.) */
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: baseline;
  white-space: nowrap;
  /* It is a sign, not a sentence: dragging across it should not paint it blue. Everything else on
     the page stays selectable -- an avatar's name and a broadcaster's name are worth copying. */
  user-select: none;
  -webkit-user-select: none;
  /* The name is 4.4em and each of the two outer columns is the width of the wider thing hung off
     it (the tilted phrase, 2.4em), so the line measures about 9.2em all in. 9.5vw keeps that
     inside any screen down to about 280px without ever needing to break, and the cap is where it
     stops growing on a desktop. (9.5 was tried and overflows a 300px screen by 2px -- the line is
     nearly all of the content box at that width, so there is no room to round up.) */
  font-size: min(9vw, 84px);
}
/* Only this part is tilted. The BOX is turned; the letters are not touched -- no skew, so every
   stroke keeps the weight and the angle the type designer drew, and a skewed 願 is a different,
   worse 願. inline-block because a transform does nothing to an inline box. The margin is the
   clearance the tilt needs: turning a line this wide lifts its right end a few pixels toward the
   名前, and a transform does not push anything out of its way. */
.wish {
  display: inline-block;
  justify-self: end;                /* against the name, not floating in its column */
  font-family: "Oshi Mark", system-ui, sans-serif;
  font-size: .39em;                 /* of the name, so the pair scales as one lockup */
  letter-spacing: .04em;
  margin-right: .6em;
  color: var(--title-2);
  /* Lifted clear of the baseline it is laid out on, so its high end breaks the line of the name's
     top -- which is what stops the pair reading as two words in a row and makes it a thing stuck
     on above. A transform, so the lift costs no layout: the box stays where the flex row put it. */
  transform: translateY(-1.1em) rotate(-11deg);
}
/* The name and its ！ are DRAWINGS, not text -- three faces that may not be served as webfonts
   (see home.html). The span around each is what the grid aligns: an empty inline box has a strut,
   and a strut has a baseline, which is the one thing a bare <svg> does not reliably give a grid
   row. Both numbers below come from tools/mktitle.py and are specific to these glyphs in these
   faces: the height is the ink's own, and the negative vertical-align is exactly how far the ink
   drops below the baseline, put back so the row's baseline is the word's. */
.mark { font-size: 1em; line-height: 1; }
.mark svg {
  display: inline-block;
  width: auto;                      /* the viewBox keeps the ratio */
  height: .864em;                   /* the ink's own height, from tools/mktitle.py */
  vertical-align: -.100em;          /* and how far it drops below the baseline, put back */
  fill: var(--title);
}
/* The ！ stays real text, in けいふぉんと, on its full-width box -- the only part of the name that
   is not a drawing. It is nudged DOWN to stand on the same line the name's foot does: measured,
   やんちゃポップ's 推 reaches 0.100em below the baseline while けいふぉんと's ！ stops at 0.012em,
   so 0.088em is exactly the gap between the two feet. Relative, so nothing moves in the layout --
   only where it is painted. */
.bang {
  font-family: "Oshi Mark", system-ui, sans-serif;
  font-size: 1em;
  letter-spacing: .03em;
  color: var(--title);
  position: relative;
  top: .088em;
}
/* The ！ is in the THIRD column, which is why the name lands dead centre: 推しルーム is the title,
   and the shout after it is punctuation. Counting it would push the name half a character to the
   left of the page's middle -- the same reason the phrase on the other side is not counted either.
   It keeps its full-width box, air and all: a shout has room around it. */
.bang { justify-self: start; }

main { flex: 1 0 auto; padding: 6px var(--pad) 40px; }

/* A label on the shelf. Its left edge is the GRID's left edge -- the same line the first stand's
   board starts on -- because a heading indented differently from the thing it heads reads as
   belonging to something else. Same max-width and centring as #shelf, so the two agree at every
   width without either knowing the other's number. */
.secHead {
  position: relative;               /* the ⓘ's card hangs off THIS, not off the button -- see .tipBody */
  max-width: 1180px;
  margin: 0 auto 26px;
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: "Oshi Mark", system-ui, sans-serif;
  font-size: 20px;
  font-weight: 400;                 /* the face has one weight; 700 would only fake it */
  letter-spacing: .04em;
  color: var(--ink);
}
/* What is not built yet, said plainly. Set apart from the shelf above it so it does not
   look like a caption for the last row. */
.secHead.soon { margin-top: 46px; }

/* The ⓘ and its card, built to match the one the viewer has had all along (#hint / #hintBody in
   viewer.css): a small round mark, and a soft-cornered dark card that reads through what is behind
   it. Same offer, same shape, same product -- the numbers here differ only where this page's scale
   does. */
.tip { display: inline-flex; }
.tipBtn {
  width: 15px;
  height: 15px;
  padding: 0;
  display: grid;
  place-items: center;
  border: 1px solid var(--ink-3);
  border-radius: 50%;
  background: none;
  color: var(--ink-2);
  /* Serif, like the viewer's -- an "i" in a circle is a mark, and a text-face i is just a letter. */
  font: 700 10px/1 Georgia, "Times New Roman", serif;
  cursor: pointer;
  transition: color .15s, border-color .15s, background .15s;
}
/* Filled while ITS CARD is open -- the state worth showing. Hover is pointer-only: a tap leaves
   :hover stuck on what it touched, so on a phone the button would keep the pressed look after the
   card had gone. */
.tip.open .tipBtn { color: var(--plate); border-color: var(--ink); background: var(--ink); }
@media (hover: hover) {
  .tipBtn:hover { color: var(--ink); border-color: var(--ink); }
}
/* Anchored to the HEADING's left edge, not to the button. Hung off the button it started wherever
   the words ended -- 150px in -- and a 275px card from there runs off the right of a phone.
   From the heading's edge it starts where everything else on the page starts, and max-width does
   the rest. */
.tipBody {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 3;
  width: max-content;
  max-width: min(76vw, 320px);
  /* Not a bordered rectangle -- that is every browser's own tooltip, and this page has a material of
     its own. It is a piece of the acrylic: frosted white that lets the wall through, a hairline in
     the shelf's own brown rather than a black rule, and corners soft enough to belong with the
     lettering. The same card the viewer opens from its ⓘ, said in this page's palette instead of
     that page's dark one. */
  /* Top-heavy by a pixel on purpose. A line box is not centred on its ink: the font reserves room
     for a descent the kana barely use, so measured here the text sat 1.15px above the middle of an
     evenly padded card. 12 over 11 puts it back. */
  padding: 12px 14px 11px;
  border: 1px solid rgba(169, 143, 112, .45);
  border-radius: 10px;
  background: rgba(255, 255, 255, .86);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--ink);
  /* The page's own text face, not the display one -- the heading is a sign, this is a sentence. It
     has to be named: inheriting from .secHead would put it in けいふぉんと, whose subset holds only
     the handful of characters the masthead needs, so most of this line would fall back and the rest
     would not, one word in two faces. */
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Hiragino Kaku Gothic ProN",
               "Yu Gothic UI", Meiryo, sans-serif;
  font-size: 12.5px;
  font-weight: 400;
  line-height: 1.7;
  letter-spacing: 0;
  text-indent: 0;
  opacity: 0;
  visibility: hidden;
  transform: translateY(4px);
  transition: opacity .18s, transform .18s, visibility .18s;
}
/* Rises the last few pixels into place as it appears -- 4px, the same movement and the same 180ms
   the viewer's panel uses, so the two feel like one product. */
.tipBody.show { opacity: 1; visibility: visible; transform: translateY(0); }
/* The one thing in the sentence anybody has to remember: the app's name, in the sign's colour. The
   rest is the instruction and stays quiet. */
/* Wide screens put it BESIDE the ⓘ instead of under the heading: there is room out there, and a
   card that opens sideways from the mark leaves the shelf below it uncovered. The anchor moves with
   it -- on a phone the card hangs off the heading's left edge, because from the button it would run
   off the right of the screen; here it hangs off the button itself. The rise becomes a slide, in
   the direction it came from. */
@media (min-width: 700px) {
  .tip { position: relative; }
  .tipBody {
    top: 50%;
    left: calc(100% + 10px);
    transform: translate(-4px, -50%);
  }
  .tipBody.show { transform: translate(0, -50%); }
}

.tipBody b { color: var(--title); font-weight: 700; }
@media (prefers-reduced-motion: reduce) {
  .tipBody { transition: opacity .14s; transform: none; }
  .tipBody.show { transform: none; }
}

/* The shelves. Each item draws its own bottom rule and they meet, so a row of them is one
   continuous board however many fit -- no fixed row height to keep in step with the column width.
   Which is why the horizontal gap lives INSIDE the item as padding rather than in the grid. */
/* Mobile first, and on a phone the count is FIXED at three rather than derived from a minimum
   width. Two reads as a list of two things rather than a shelf; four would be reached at 444px and
   then hand over to the rule below as a drop from four to two, which looks like a broken layout.
   Three at every phone width is predictable and is what the shelf wants, and it holds all the way
   to 600 -- which is where the rule below happens to also start at three, so the count never drops
   as the window widens. */
#shelf {
  max-width: 1180px;
  margin: 0 auto;
  display: grid;
  /* minmax(0, ...) and not a bare 1fr: a plain 1fr will not go below its content's min-content
     width, and the caption's time-and-viewers line is nowrap, so on a 300px screen the three
     columns insisted on being wider than the page. With a zero floor the column wins and the time
     ellipsises, which is what .w-time is set up for. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: 0;
  row-gap: 26px;
}
/* Anything wider than a phone, all the way up, on one minimum. 170px is chosen from the far end:
   it is what lands on SIX across the 1180 page, so each stand is about 197px rather than the 168
   seven gave. It happens to serve the middle too -- three at 600, four at 768, five at 900 -- so
   the count only ever climbs as the window widens, with no boundary to step over. */
@media (min-width: 600px) {
  #shelf {
    grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
    row-gap: 34px;
  }
}

/* The pointing hand, on EVERY stand -- a finished broadcast cannot be opened, but it is still a
   thing on a shelf that you reach for, and the shelf should not go dead under the pointer.
   32px with a 64 for retina: a CSS cursor is capped at 128 by browsers and at 32 by Windows itself,
   so the small one is what most people actually get. The hotspot is the fingertip, measured off the
   artwork (205..209 of 512 across, first opaque row at 25) rather than guessed.
   `pointer` is the fallback that runs if the image cannot be loaded. */
.stand {
  display: block;
  text-decoration: none;
  color: inherit;
  /* No blue box on tap. A card is a thing on a shelf, and the flash of a form control's highlight
     over it reads as "you pressed a button that did nothing" on the finished ones especially. */
  -webkit-tap-highlight-color: transparent;
  cursor: url(/point-32.png) 13 2, pointer;
  cursor: image-set(url(/point-32.png) 1x, url(/point-64.png) 2x) 13 2, pointer;
}
/* Nothing in the DOM moves on hover -- the stand hops, and that happens in WebGL. This animation is
   empty on purpose: it exists only to time taking the class off again, and removing it on
   animationend is what stops the hop repeating while the pointer rests on a card. Keep its length
   equal to HOP_MS in stands.js. */
@keyframes hop { from { } to { } }
.stand.hop .tag { animation: hop 420ms both; }
.stand:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; border-radius: 2px; }
/* A finished broadcast is not dimmed anywhere -- not the figure (see sync() in stands.js) and not
   its label. The 配信中 tag being there or not is the whole difference, and it is one you can
   read; fading the rest only made the shelf look half switched off.
   (They keep the pointing cursor too -- see .stand. What they lose is the link, not the invitation
   to look.) */

/* The board is drawn HERE, under the figure, not under the whole item -- put it on .stand and it
   lands below the label, which is a line under a caption rather than a shelf. Full column width so
   neighbouring rules meet into one continuous board; the horizontal inset is padding INSIDE it. */
.fig {
  position: relative;
  display: block;
  padding: 0 var(--fig-pad);
  /* Thick enough to be a board rather than a rule. At 2px it read as an underline under each
     picture; a shelf is a thing with a front edge, and this is the whole of how that is said. */
  border-bottom: 5px solid var(--shelf);
}
/* An empty box. Its rectangle is the only thing the WebGL layer reads -- the picture, the die-cut
   margin and the base are all drawn into it by stands.js, and the bottom edge of this box IS the
   shelf line the base sits on.
   The ratio is the square shot plus the base's thickness: 1 + DISC_H (0.02) = 1.02. No allowance
   for the clearance under the soles, because that is no longer a constant -- stands.js measures
   each picture's own sole and hangs it from there, so a figure whose boots reach lower simply sits
   higher in the box. Keep DISC_H in step with stands.js. */
.slot { aspect-ratio: 1 / 1.02; }

/* The same box with nothing in it. It had no size of its own at all, so a card with no picture
   collapsed to the height of the little silhouette and pulled its shelf line up to somewhere around
   the middle of its neighbours -- one broken-looking card in an otherwise even row. Holding the
   ratio is the whole fix: every stand's board now ends on the same line whether or not there is a
   figure standing on it.
   Two things arrive here. A broadcast that has not put a character up yet, which says so in words,
   and a character whose cut-out has not been uploaded, which keeps the silhouette. */
.none {
  aspect-ratio: 1 / 1.02;
  display: grid;
  place-items: center;
  color: var(--ink-3);
}
/* Full ink, not the muted one the silhouette gets. This is the card's message, and the only thing
   in a box the eye goes to first -- a figure would be at full strength there, and so is this. */
/* Sat 4px lower than the box's middle. Dead centre is where a figure's MASS goes, and a figure
   fills the box; a single line of type is a thin thing in a tall space, and centring it by its own
   box put it high of where the eye reads the middle to be. Nudged with position rather than a
   transform, because the hop owns this element's transform -- and relative offsets do not move the
   layout, so the board underneath still ends on the same line as every other card's. */
.none.prep {
  /* けいふぉんと, the same face the headings above the shelf are set in. It is the only text on
     the page that stands where a figure would, so it is set in the page's own voice rather than in
     the system font the small print uses. Its five characters and the ellipsis were added to the
     subset -- see tools/mkkeifont.py, and add to CHARS there before setting anything new in it. */
  font-family: "Oshi Mark", system-ui, sans-serif;
  font-size: 14px;
  color: var(--ink);
  letter-spacing: .04em;
  text-align: center;
  position: relative;
  top: 4px;
}
@media (max-width: 599px) { .none.prep { font-size: 12.5px; } }

/* ...and it hops when you point at it, like everything else on the shelf. There is no figure here
   for the WebGL layer to throw, so the words are thrown instead, on the SAME curve: the keyframes
   below are hopRise() and hopLean() from stands.js sampled every 8.6% -- which is HOP_AIR/10, so
   the arc gets ten steps and the settle four. Hence linear: the shape is in the samples, and an
   easing function between them would be a second curve on top of the one being copied.
   The two constants come over as well. LIFT is 0.05 of the card's WIDTH, and this box is 1.02 wide
   for every 1 tall, so 5/1.02 = 4.9% of its own height is the same distance. The angle is set per
   hop by hopCard() in home.js, from the same 2-5 degrees either way. */
@keyframes standHopText {
  0%     { transform: translateY(0) rotate(0deg); }
  8.6%   { transform: translateY(calc(-4.9% * 0.36)) rotate(calc(var(--lean, 3deg) * 0.587)); }
  17.2%  { transform: translateY(calc(-4.9% * 0.64)) rotate(calc(var(--lean, 3deg) * 0.853)); }
  25.8%  { transform: translateY(calc(-4.9% * 0.84)) rotate(calc(var(--lean, 3deg) * 0.976)); }
  34.4%  { transform: translateY(calc(-4.9% * 0.96)) rotate(calc(var(--lean, 3deg) * 0.997)); }
  43%    { transform: translateY(-4.9%)             rotate(calc(var(--lean, 3deg) * 0.933)); }
  51.6%  { transform: translateY(calc(-4.9% * 0.96)) rotate(calc(var(--lean, 3deg) * 0.810)); }
  60.2%  { transform: translateY(calc(-4.9% * 0.84)) rotate(calc(var(--lean, 3deg) * 0.640)); }
  68.8%  { transform: translateY(calc(-4.9% * 0.64)) rotate(calc(var(--lean, 3deg) * 0.439)); }
  77.4%  { transform: translateY(calc(-4.9% * 0.36)) rotate(calc(var(--lean, 3deg) * 0.222)); }
  /* Down. What is left is the settle: it rocks the other way and stops, with no rebound -- one was
     tried in the WebGL version and taken out again, and this is copying that decision too. */
  86%    { transform: translateY(0) rotate(0deg); }
  89.5%  { transform: translateY(0) rotate(calc(var(--lean, 3deg) * -0.127)); }
  93%    { transform: translateY(0) rotate(calc(var(--lean, 3deg) * -0.180)); }
  96.5%  { transform: translateY(0) rotate(calc(var(--lean, 3deg) * -0.127)); }
  100%   { transform: translateY(0) rotate(0deg); }
}
/* Same 420ms as .tag's timer above and as HOP_MS in stands.js -- all three have to match or the
   class comes off mid-arc and the words stop in the air. */
.stand.hop .none.prep { animation: standHopText 420ms linear both; }

/* On air. A tag clipped to the plate, the way a shop tags the one that is out of the case.
   Left, flush with the figure: the containing block is .fig's PADDING box, so the offset has to be
   the same padding the figure is inset by or the tag hangs off the side of the stand. */
.on {
  position: absolute;
  top: 2px;
  left: var(--fig-pad);
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--on-air);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .1em;
  white-space: nowrap;
}
.on i { width: 5px; height: 5px; border-radius: 50%; background: #fff; }

/* The shelf tag, under the board. */
.tag { padding: 9px 8px 0; display: grid; gap: 2px; }
.who {
  font-size: 12.5px;
  font-weight: 700;
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.host { font-size: 11px; color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* One line, and its height is held whether or not there is anything in it yet -- a caption that
   grows a line when the data lands moves every card below it. */
.when {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 1.4em;
  font-size: 10.5px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.w-time { overflow: hidden; text-overflow: ellipsis; }
/* The eye keeps its place at the right end: the time is what gets clipped on a narrow card, not
   the number of people who came. */
.w-eyes { flex: none; display: flex; align-items: center; gap: 2px; margin-left: auto; }
.w-eyes svg { fill: currentColor; opacity: .75; display: block; }
.w-eyes b { font-weight: 600; }

.note { margin: 44px 0; text-align: center; font-size: 13px; color: var(--ink-2); }

/* Centred both ways. The padding used to be lopsided (18 above, 32 below) from when the footer sat
   at the very bottom of the window; with two lines in it, an even band with the text in the middle
   of it is what reads as a footer rather than as a paragraph that ran out of page. */
footer {
  flex: none;
  padding: 26px var(--pad);
  min-height: 96px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
  font-size: 11.5px;
  color: var(--ink-3);
  background: var(--wall-2);
}
footer p { margin: 0; }
/* Smaller and quieter than the line above it: it is a notice, not something anyone came to read. */
.tm { margin-top: 8px; font-size: 10.5px; color: var(--ink-2); opacity: .8; }

/* And no selection on a touch screen. A tap that lands slightly as a drag paints the caption blue,
   which on a phone is never what was meant -- there is no cursor there to have aimed with. Gated on
   there being no hover, so a mouse can still select an avatar's name and a broadcaster's name,
   which are worth copying. */
@media (hover: none) {
  .stand { user-select: none; -webkit-user-select: none; }
}

@media (max-width: 480px) {
  :root { --pad: 14px; --fig-pad: 5px; }
  /* Two thirds of the 20px it is on a desktop. The heading is a label on the shelf, and on a phone
     the shelf is three stands wide -- at 20px it was competing with the masthead above it. */
  .secHead { font-size: 14px; margin-bottom: 20px; }
  .who { font-size: 12px; }
  .host { font-size: 10.5px; }
  .when { font-size: 10px; }
  .on { font-size: 9px; padding: 3px 7px; letter-spacing: .06em; }
}
@media (prefers-reduced-motion: reduce) {
  .stand.hop .tag { animation: none; }
  .stand.hop .none.prep { animation: none; }
}
