/* Section.css
 * Shared section styles: project-card tiles, tile grids + row paging,
 * skeletons, and the browse-filter panel controls.
 * Used by the play/build/profile/admin pages.
 */

/* ====================
   TILE GRIDS
   ==================== */

/* Wrapping project grid. Positioned so the Show More control can anchor to
   the grid's bottom edge. */
/* Never fewer than two columns: the track minimum is a full-size tile
   (--tile-min, common/style.css), or half the grid less half a gap when that
   is smaller, so two tiles always fit across and shrink together on a phone
   rather than one taking the row -- and once even two cannot be read at that
   width they are "mini" and shed their words (below).
   Tiles fill their track with no width cap; the minimum keeps multi-column
   tracks under ~400px. (Empty states span the grid via the shared rule
   below.) */
.project-grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(
    auto-fill,
    minmax(min(var(--tile-min), calc(50% - var(--space-2) / 2)), 1fr)
  );
  gap: var(--space-2);
}

/* Every tile grid lives in a glass panel below the section's heading/text
   (the gallery-panel pattern -- there is no separate featured slot; the
   collection's first tile is the spotlight, below). Covers the game-tile
   grids (.project-grid) and the build page's asset browser (.find-grid). An emptied
   grid disappears entirely so a prompt-only panel keeps its rhythm. */
.section-glass .project-grid,
.section-glass .find-grid {
  margin-top: var(--space-2);
}

.section-glass .project-grid:empty,
.section-glass .find-grid:empty {
  display: none;
}

/* First-tile spotlight: a game-tile grid (.grid-hero, set statically in the
   markup; people grids omit it) enlarges its first tile to a 2x2 hero.
   Entirely CSS -- no JS state. Two gates:
   - width >=3 columns (the grid's panel 620px across: three --tile-min
     tracks and two gaps -- a container query, since the page inset changes
     with the screen and the viewport no longer says how wide a panel is) --
     at 1-2 columns there are no 1x1 tiles beside it to define the two-row
     height, so the first tile stays normal;
   - >=4 card siblings (`:nth-last-child(n + 4 of .project-card)`) -- with
     fewer, the hero's second row can't fill at the 4-column maximum and the
     spotlight would collapse to a short banner. Counting only .project-card
     siblings ignores the Show More control, and a lone placeholder card
     never qualifies, so empty-channel renders stay normal-sized.
   The span's two rows are sized by the 1x1 tiles next to it; the hero card
   stretches to fill (grid items stretch by default) and its thumbnail grows
   into the taller area, dropping the 16:9 lock so the video/screenshot
   covers it. Applies to the first real card AND the first skeleton, so a
   loading state doesn't reflow into the hero shape. */
@container (min-width: 620px) {
  .grid-hero > .project-card:first-child:nth-last-child(n + 4 of .project-card) {
    grid-column: span 2;
    grid-row: span 2;
    /* Fill the two-row area the side tiles define (a flex column with a
       flex:1 thumbnail won't stretch on its own over spanned auto rows). */
    height: 100%;
  }

  .grid-hero > .project-card:first-child:nth-last-child(n + 4 of .project-card) .project-thumbnail,
  .grid-hero > .project-card-skeleton:first-child:nth-last-child(n + 4 of .project-card)::after {
    aspect-ratio: auto;
    flex: 1;
  }
}

/* Show More control: the tile grid pages by rows (see Section.js
   initGridRowPaging). The control (.grid-more) reserves no space of its
   own -- it floats in the panel's bottom padding, filling the band between
   the grid's last row and the panel border and centering itself in it. Its
   base styling + the padding reservation now live in
   common/style.css, shared with the index Welcome scroll hint (which reuses
   the same .grid-more class). The static skeleton ships a .grid-more
   placeholder so the band reserves from first paint; see .grid-more-skeleton
   below. */

/* ====================
   GRID VIEW (the tutorials picker; see ModalTutorials)
   ==================== */

.section-part-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--tile-pick-min), 1fr));
  gap: var(--space-2);
  align-content: start;
}

.section-part-grid .project-card {
  max-width: calc(var(--tile-pick-min) * 2);
  width: 100%;
}

/* ====================
   PROJECT CARDS
   ==================== */

.project-card {
  position: relative;
  background: var(--color-surface);
  border: var(--border-width) var(--border-style) var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  padding: 0;
  cursor: pointer;
  transition: all 0.15s ease;
  display: flex;
  flex-direction: column;
  height: auto;
  /* The footer is pulled out of flow (below), which makes the card shorter
     than its row; size it to its content so the row's reserved height does
     not expose the card's surface as a gray bar below the thumbnail. */
  align-self: start;
  /* The footer's height, named so the button on the art can center itself in
     the part of the picture the footer leaves uncovered: a button's padding
     around its 18px line, plus the top border. The mini tile's own value is
     with the rest of mini mode, at the end of this file. */
  --project-footer-height: calc(var(--space-2) * 2 + 18px + var(--border-width));
  /* What a chip's word may grow to. Both are cut from the tile, so a narrower
     tile narrows its chips instead of letting the pair collide across the
     picture. A genre or award word is short and takes the smaller share; the
     record holder's is a username, so it takes what is left once its own
     chip, the collapsed chip across from it and the three gaps between and
     beside them are taken. */
  --label-max-short: calc(var(--tile-min) - 90px);
  --label-max: calc(var(--tile-min) - 75px);
}

/* Accent the border when the tile is "selected": its drawer open on touch
   (the .revealed class, toggled by the footer handle) or focused via keyboard
   (:focus-visible, not :focus-within -- a tapped footer button or handle
   takes focus too, and only the handle should light a touch tile). Pairs with
   the footer reveal so the same state lights the border and floats the menu.
   The hover affordance lives in the shared, hover-device-gated tile rule in
   style.css (gated so iOS's sticky :hover doesn't flash it on touch-scroll),
   and the orbiting glow that pairs with it lives in style.css's Running glow. */
.project-card.revealed,
.project-card:has(:focus-visible) {
  border-color: var(--color-accent);
}

.project-thumbnail {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: var(--color-background);
  flex-shrink: 0;
}

.project-screenshot {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.25s ease-out;
}

.project-screenshot.loaded {
  opacity: 1;
}

/* Overlay chips floating on the thumbnail, above the tile art: one glyph
   per genre (top-left, stacked vertically) and, top-right, an award badge
   for a ranked-stat leader and a medal for the leaderboard's record holder.
   All are icon-only at rest; hover/focus slides the text label out. The
   genre and award chips are real <button>s (keyboard operable, like the
   card's footer actions); the record chip only informs, so it is a span.
   All reset to opaque surface chips -- like floating bits of the
   header/footer strips -- so they stay legible over any art without a
   media-specific scrim, and flip cleanly with the theme. */
.project-genre-stack,
.project-badge-stack {
  position: absolute;
  top: var(--space-1);
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.project-genre-stack {
  left: var(--space-1);
  align-items: flex-start;
}

.project-badge-stack {
  right: var(--space-1);
  align-items: flex-end;
}

.project-genre-badge,
.project-award-badge,
.project-record-badge,
.project-tutorial-badge {
  display: inline-flex;
  align-items: center;
  appearance: none;
  -webkit-appearance: none;
  padding: var(--space-1);
  background: var(--color-surface);
  border: var(--border-width) var(--border-style) var(--color-border);
  border-radius: var(--radius-sm);
  color: var(--color-link);
  font-size: var(--font-size-sm);
  cursor: pointer;
  transition: color 0.15s ease;
}

/* Every chip reads the same way, mirroring the footer links: muted at rest,
   brightening on hover. Genre filters the browse view; award applies its
   sort; the record holder and the tutorial mark are read-only, so they
   brighten without a hand. */
.project-genre-badge:hover,
.project-award-badge:hover,
.project-record-badge:hover,
.project-tutorial-badge:hover {
  color: var(--color-link-light);
}

.project-genre-badge:focus-visible,
.project-award-badge:focus-visible {
  outline: var(--focus-outline);
}

.project-record-badge,
.project-tutorial-badge {
  cursor: default;
}

/* Chip labels are collapsed at rest (icon-only chip) and slide out on
   hover/focus. max-width carries the reveal -- wide enough for the longest
   label, small enough that the collapse doesn't lag the pointer leaving.
   line-height matches the 14px icon so the label never sets the chip's
   height, keeping the collapsed chip a perfect square. */
.project-badge-label {
  display: block;
  max-width: 0;
  margin-left: 0;
  overflow: hidden;
  white-space: nowrap;
  line-height: 14px;
  transition:
    max-width 0.25s ease-out,
    margin-left 0.25s ease-out;
}

/* Expanded by keyboard focus on any device, and on touch along with the
   footer drawer (the card's .revealed class, toggled by the footer handle --
   see SectionTemplate toggleTileFooter), so one tap opens everything the hover
   would have shown. */
.project-genre-badge:focus-visible .project-badge-label,
.project-award-badge:focus-visible .project-badge-label,
.project-card.revealed .project-badge-label {
  max-width: var(--label-max-short);
  margin-left: var(--space-1);
}

/* Mouse: also expand on hover. Scoped to hover devices so iOS's sticky
   :hover can't expand a chip you merely scrolled past. */
@media (hover: hover) and (pointer: fine) {
  .project-genre-badge:hover .project-badge-label,
  .project-award-badge:hover .project-badge-label,
  .project-record-badge:hover .project-badge-label,
  .project-tutorial-badge:hover .project-badge-label {
    max-width: var(--label-max-short);
    margin-left: var(--space-1);
  }
}

/* A username is longer than a genre word, so this one reveals to the wider
   of the two caps above and ends in an ellipsis when even that is short. */
.project-record-badge .project-badge-label {
  text-overflow: ellipsis;
}

.project-card.revealed .project-record-badge .project-badge-label {
  max-width: var(--label-max);
}

@media (hover: hover) and (pointer: fine) {
  .project-record-badge:hover .project-badge-label {
    max-width: var(--label-max);
  }
}

/* On a mini tile the chips stay glyphs whatever the pointer does: below that
   width the caps above leave too little for a word worth reading, and the
   pair would meet in the middle of the picture. Last of the label rules and as specific as the widest, so it
   outranks every reveal above by order. */
.grid-mini .project-card .project-genre-badge .project-badge-label,
.grid-mini .project-card .project-award-badge .project-badge-label,
.grid-mini .project-card .project-record-badge .project-badge-label {
  max-width: 0;
  margin-left: 0;
}

/* ...and each stack stops at two chips, so neither side runs past two rows
   down a picture that is not tall enough for more. */
.grid-mini .project-genre-stack > :nth-child(n + 3),
.grid-mini .project-badge-stack > :nth-child(n + 3) {
  display: none;
}

.project-genre-badge svg,
.project-award-badge svg {
  display: block;
  flex-shrink: 0;
  width: 14px;
  height: 14px;
}

/* Header strip above thumbnail */
.project-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-2);
  border-bottom: var(--border-width) var(--border-style) var(--color-border);
}

.project-header .project-title {
  min-width: 0; /* Required for flex truncation */
}

/* The title is the card's link (SectionTemplate.createProjectCard). Its
   ::after stretches over the whole tile -- the card is the containing block
   (position: relative, below) -- so a click anywhere on the art follows the
   link, with the browser's own middle-click / Cmd-click / "Open in new tab".
   z-index 1 lifts it over the thumbnail (positioned, for its badges). What
   else is clickable on the card paints above this layer: the badges at 2, the
   footer and header button at 1 as well, following it in the DOM. */
.project-header .project-title[href]::after,
.project-header .project-title-action::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* A card that opens by script rather than by link (a tutorial tile) has a
   button for a title, stretched over the tile the same way, so it opens from
   the keyboard too. Only the button's own chrome is reset; the title rules
   below still size and cut it like the link. */
.project-title-action {
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

/* The link stands for the whole card, so keyboard focus is shown by the card's
   accent border and ring (.project-card:has(:focus-visible), above), not by a
   second outline around the title text. */
.project-header .project-title[href]:focus-visible,
.project-header .project-title-action:focus-visible {
  outline: none;
}

/* Shared icon+label action buttons on project cards (header + footer). */
.project-header-btn,
.project-footer-btn {
  background: transparent;
  border: none;
  color: var(--color-link);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 4px;
  transition: all 0.15s ease;
  font-size: var(--font-size-sm);
}

/* No padding of its own: the strip's padding is the hit target's, and the
   chip's 18px line is what makes the header as tall as the footer (39px). */
.project-header-btn {
  position: relative; /* Above the title's stretched link */
  z-index: 1;
  padding: 0;
  flex-shrink: 0;
}

.project-header-btn svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

.project-header-btn-label {
  white-space: nowrap;
  max-width: 100px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.project-header-btn:hover {
  color: var(--color-link-light);
}

/* Footer action buttons. */
.project-footer-btn {
  flex: 1 1 0; /* Equal shares of the footer, whatever each word's width */
  min-width: 0;
  justify-content: center;
  padding: var(--space-2);
  overflow: hidden;
}

.project-footer-btn svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

.project-footer-label {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.project-footer-btn:hover {
  color: var(--color-link-light);
}

.project-footer-btn.danger:hover {
  color: var(--color-error);
}

/* The footer sits out of the card's flow, floated over the bottom of the
   thumbnail and hidden until the tile is in hand. The reveal trigger differs
   by input: hover or keyboard focus on mouse (below), and on touch a tap on
   the footer's handle (the .revealed class, toggled by SectionTemplate
   toggleTileFooter; it also expands the thumbnail's chip labels) -- so one
   tap on the art plays, as a click does on a hovered tile. It never clips:
   each button clips its own label, and on touch the handle pokes up past
   the footer's top edge. */
.project-footer {
  display: flex;
  align-items: center;
  border-top: var(--border-width) var(--border-style) var(--color-border);
  position: absolute;
  z-index: 1; /* Above the title's stretched link */
  left: 0;
  right: 0;
  bottom: 0;
  /* Gray bar with top border, floated up over the bottom of the thumbnail. */
  background: var(--color-surface);
  opacity: 0;
  transform: translateY(var(--space-3));
  transition:
    opacity 0.15s ease,
    transform 0.15s ease;
  pointer-events: none;
}

/* Revealed by keyboard focus on any device -- focus stays out of the hover
   gate below so tab-into-footer works on touchscreens too. */
.project-card:has(:focus-visible) .project-footer {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

/* Mouse: also reveal on hover. Scoped to hover devices so iOS's sticky :hover
   can't pin the footer to a tile you merely scrolled past. */
@media (hover: hover) and (pointer: fine) {
  .project-card:hover .project-footer {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
  }
}

/* A tile in hand darkens its own picture, on the same cue that slides the
   footer in. Where the tile draws a button on its art that is what the
   button reads against: its backdrop becomes a known one instead of
   whatever the picture happens to be, which for key art is usually busy and
   mid-tone -- the one case a glyph's own shadow handles worst. Every tile
   with a picture takes it, including the few that draw no button there (the
   tutorial picker's, an empty channel's placeholder); on those it is simply
   the hover state, which is the same thing it says everywhere else. Paints
   over the picture and under the badges and the button (both z-index 2), and
   takes no clicks, so the title's stretched link still has the whole tile. */
.project-thumbnail::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--overlay-black-25);
  opacity: 0;
  transition: opacity 0.15s ease;
  pointer-events: none;
}

.project-card:has(:focus-visible) .project-thumbnail::after,
.project-card.revealed .project-thumbnail::after {
  opacity: 1;
}

@media (hover: hover) and (pointer: fine) {
  .project-card:hover .project-thumbnail::after {
    opacity: 1;
  }
}

/* A control drawn on the tile's art rather than in a bar: the open hint in
   the middle of the picture, the footer's "..." handle at its top edge. Not
   a button to look at -- no surface, no border, just the glyph, with a
   shadow to seat it on the wash. White, because what it is read against is
   the scrimmed picture and not a page surface: the footer's muted gray is
   right on the solid strip it sits on and too quiet over key art, and a
   theme's text color would turn this one dark on a backdrop that stays dark
   in both themes. It holds that one color throughout -- white has no
   brighter to go, and hovering it already says its name (Tooltip.js). Each
   user positions itself and says when it shows; this says what it looks
   like. */
.project-art-btn {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: none;
  color: var(--overlay-white-100);
  /* One soft pass is enough now that the picture behind it is scrimmed: the
     shadow only has to seat the glyph against that wash. Full black rather
     than the page's own background, which in the light theme would leave it
     nothing to separate against. */
  filter: drop-shadow(0 0 5px var(--overlay-black-100));
  cursor: pointer;
}

.project-art-btn svg {
  display: block;
}

/* The footer's handle: a bare "..." overlaid on the art at the footer's top
   edge, centered, that slides the footer in and out on touch. It is a child
   of the footer, so it rides up with it and needs no knowledge of the
   footer's height. Hidden wherever hover exists -- hover does the sliding
   there. */
.project-footer-handle {
  display: none;
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  padding: var(--space-1) var(--space-2);
  pointer-events: auto;
}

/* Touch: the footer is a drawer. Closed, it sits fully below the card's edge
   (the card's overflow:hidden clips it) with only the handle poking up; the
   handle's tap slides it into place, just as hover does on a mouse. */
@media (hover: none) {
  .project-footer {
    opacity: 1;
    transform: translateY(100%);
  }

  .project-footer-handle {
    display: flex;
    /* Shown at rest here, over art the scrim has not darkened yet: a tight
       pass under the soft one keeps the white glyph legible on pale art. */
    filter: drop-shadow(0 0 2px var(--overlay-black-100))
      drop-shadow(0 0 5px var(--overlay-black-100));
  }

  .project-card.revealed .project-footer {
    transform: translateY(0);
    pointer-events: auto;
  }
}

/* A footer that is only a description (a tutorial tile's) holds nothing to
   click, so it never takes the pointer: a click on it lands on the title's
   stretched layer underneath and opens the tile, like a click on the art.
   The touch handle keeps its own. After all three reveals above (focus,
   hover, the touch drawer), which it has to outlast at equal weight. */
.project-card .project-footer:has(> .wizard-tile-desc) {
  pointer-events: none;
}

/* The open hint: the tile's own action (play or edit) as a button centered
   on the art the footer leaves uncovered -- the footer floats over the art's
   bottom, so the center is measured above its height. A tile at rest is
   just its art; a tile in hand shows what a click on the art does,
   since the footer no longer repeats that action as a button. Shown exactly
   when the footer is: keyboard focus, hover on a mouse, the drawer on touch.
   It is a link with the title's own href and open action, so it reads,
   hovers and opens like any other button (its title is its tooltip); above
   the title's stretched layer like the badges, and clickable only while
   shown, like the footer. Drawn as a control on the art (.project-art-btn),
   like the footer's handle. */
.project-open-hint {
  position: absolute;
  z-index: 2;
  top: calc((100% - var(--project-footer-height)) / 2);
  left: 50%;
  display: inline-flex;
  padding: var(--space-2);
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.75);
  transition:
    opacity 0.15s ease,
    transform 0.15s ease;
  pointer-events: none;
}

.project-card:has(:focus-visible) .project-open-hint,
.project-card.revealed .project-open-hint {
  opacity: 1;
  transform: translate(-50%, -50%);
  pointer-events: auto;
}

@media (hover: hover) and (pointer: fine) {
  .project-card:hover .project-open-hint {
    opacity: 1;
    transform: translate(-50%, -50%);
    pointer-events: auto;
  }
}

/* ====================
   MINI TILES
   ====================
   A grid whose tiles fall below the track floor takes .grid-mini (Section.js
   measures it). Everything a tile drops or swaps at that size is here rather
   than beside each base rule, so the whole of the small tile reads in one
   place. It is only ever a subtraction: words go, since they are what stops
   fitting, and the tile keeps every control it had. */

/* Buttons keep their glyphs and drop their words, all together. Each still
   names itself on hover, through its own dropped label (Tooltip.js). */
.grid-mini .project-footer-label,
.grid-mini .project-header-btn-label {
  display: none;
}

/* A shorter footer follows the glyph-only buttons, and the button on the art
   centers itself against it. */
.grid-mini .project-card {
  --project-footer-height: calc(var(--space-2) * 2 + 14px + var(--border-width));
}

/* Placeholder cards carry a footer only as a height spacer (its button is
   visibility:hidden). Out of flow it no longer reserves height, so drop it
   entirely -- otherwise its empty surface slides up on reveal. */
.project-card-placeholder .project-footer {
  display: none;
}

.project-title {
  color: var(--color-text);
  /* The footer's line (18px on its 12px text), so the header matches it in
     height with the same 10px padding and 1px border on each. */
  line-height: 18px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ====================
   EMPTY STATE
   ==================== */

.empty-state {
  text-align: center;
  padding: var(--space-5) var(--space-4);
  color: var(--color-text-dark);
}

.title-block {
  display: block;
  margin-bottom: var(--space-2);
}

/* Make empty states span full width in grid layouts */
.project-grid .empty-state {
  grid-column: 1 / -1;
  max-width: none;
}

/* ====================
   PLACEHOLDER TILES
   ==================== */

.project-placeholder-icon {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 48px;
  height: 48px;
  color: var(--color-text-dark);
}

.project-placeholder-icon svg {
  width: 100%;
  height: 100%;
}

/* ====================
   SKELETON TILES
   ==================== */

@keyframes skeleton-shimmer {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: -200% 0;
  }
}

/* Shared skeleton shimmer effect (the end screen's leaderboard rows ride
   it too, see scene/Leaderboard.css) */
.project-card-skeleton,
.grid-more-skeleton::before,
.profile-header-skeleton::before,
.leaderboard-row.is-loading .leaderboard-name,
.leaderboard-row.is-loading .leaderboard-value {
  background: linear-gradient(
    90deg,
    var(--color-background) 25%,
    var(--color-surface) 50%,
    var(--color-background) 75%
  );
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.5s ease-in-out infinite;
}

.project-card-skeleton {
  pointer-events: none;
}

/* Grid contexts (tile grids + standalone ones like the projects modal):
   the skeleton fills its grid track, and its height comes from the same
   anatomy as a loaded card: a header strip plus a 16:9 thumbnail pane (the
   footer floats out of flow, so it adds nothing). */

/* Header strip: a real text line inside header padding, with a floor at
   the header action button's height (its --font-size-sm label line at the
   body's 1.5 line-height, plus --space-1 button padding) -- every loaded
   card's header carries that row: Rename on private tiles, the author
   button elsewhere, and placeholder cards ship a hidden spacer to hold
   the same height. Token-derived so the strip tracks the real header
   through font or spacing changes. */
.project-grid .project-card-skeleton::before,
.section-part-grid .project-card-skeleton::before {
  content: '\00a0';
  display: block;
  padding: var(--space-2);
  min-height: calc(1.5 * var(--font-size-sm) + 2 * var(--space-1) + 2 * var(--space-2));
}

/* Thumbnail pane: same 16:9 as .project-thumbnail. */
.project-grid .project-card-skeleton::after,
.section-part-grid .project-card-skeleton::after {
  content: '';
  display: block;
  aspect-ratio: 16 / 9;
}

/* Skeleton stand-in for the grid's Show More control: the static markup
   ships one per tile grid so the control's spot in the panel's bottom
   padding shimmers on first paint (a loaded channel usually overflows its
   first row). The loader's grid re-render discards it; applyGridRowPaging
   appends the real control when the channel calls for one. Same element +
   classes as the real control (out of flow, so it never affects layout);
   a shimmer pill stands in for the icon + label. */
.grid-more-skeleton {
  pointer-events: none;
}

.grid-more-skeleton::before {
  content: '\00a0';
  width: 100px;
  border-radius: var(--radius-sm);
}

/* Match the skeleton row to the loaded grid's first row: most grids ship four
   skeleton tiles and one reveals per column (the fifth and sixth columns go
   without on those). The games gallery ships twenty-seven for a five-row
   skeleton with a 2x2 hero at every count --
   see play.css. Two columns always fit (see .project-grid), so two show from
   the start; the rest reveal as the grid's panel -- the query container,
   whose content box the grid spans -- reaches the width N columns need: N
   --tile-min tracks and N - 1 gaps of --space-2. A container query rather
   than the viewport, since the page inset changes with the screen and the
   viewport no longer says how wide a panel is. */
.project-grid > .project-card-skeleton:nth-child(n + 3) {
  display: none;
}

/* 3 columns: 620px of tiles. */
@container (min-width: 620px) {
  .project-grid > .project-card-skeleton:nth-child(3) {
    display: flex;
  }
}

/* 4 columns: 830px of tiles. */
@container (min-width: 830px) {
  .project-grid > .project-card-skeleton:nth-child(4) {
    display: flex;
  }
}

/* 5 columns: 1040px of tiles. */
@container (min-width: 1040px) {
  .project-grid > .project-card-skeleton:nth-child(5) {
    display: flex;
  }
}

/* 6 columns: 1250px of tiles, which the content cap's 1348 fits. */
@container (min-width: 1250px) {
  .project-grid > .project-card-skeleton:nth-child(6) {
    display: flex;
  }
}

/* Stagger animation for multiple skeletons. Unscoped (not .project-grid >)
   because other skeleton grids share it -- notably admin's larger tile
   grids, whose fifth cell still needs the :nth-child(5) step even though
   most wrapping project grids ship four skeleton tiles (the games gallery
   ships nine; see play.css). */
.project-card-skeleton:nth-child(2) {
  animation-delay: 0.1s;
}
.project-card-skeleton:nth-child(3) {
  animation-delay: 0.2s;
}
.project-card-skeleton:nth-child(4) {
  animation-delay: 0.25s;
}
.project-card-skeleton:nth-child(5) {
  animation-delay: 0.5s;
}

/* ====================
   BROWSE FILTER PANEL (shared by the games gallery and the build/profile/admin
   panels: selects + search composing into one results grid inside a glass
   section; page-specific skeleton/hero rules stay in each page's CSS)
   ==================== */

/* The panel itself is a standard glass section (.section-glass, common). The
   controls row holds the selects plus the search box; the search box grows
   to take the remaining width, and the whole row wraps on narrow viewports.
   The 10px gap matches every other search/actions toolbar (the builder's
   find-modal search row, the admin asset-browser header). */
.browse-filter-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  /* Space below the panel's title. */
  margin-top: var(--space-2);
  /* A query container, so the row's own width can decide which control
     drops when it wraps (the CTA rule below). */
  container-type: inline-size;
}

/* The select fields share one block, a grid of equal columns, so the
   gallery's Genre and Sort are always the same width: shrink-wrapped
   beside the search box they both take the wider one's width; on a line
   of their own they split it. The block grows too, but next to the search
   box's grow factor (below) its share of a shared line rounds to nothing:
   it only fills a line the search box has dropped off of, where the
   selects stretch into what the search box left. */
.browse-filter-fields {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: var(--space-2);
  flex: 1 1 auto;
}

/* Label paired inline with its control. */
.browse-filter-field {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.browse-filter-label {
  color: var(--color-text-dark);
  white-space: nowrap;
}

/* A glyph that stands in for the label on a narrow viewport (below), where
   the words come off: the markup puts it before the label and the panel's
   script fills it (Genre, Sort: ProjectBrowse; Channel: SectionChannelPanel). */
.browse-filter-icon {
  display: none;
  align-items: center;
  color: var(--color-text-dark);
}

/* Override the shared full-width block <select> base: filter controls size to
   content and sit inline. Height and text size come from .select-compact
   (common/style.css), which every element carrying this class also carries --
   that is what matches them to the search input (.form-inst-search). */
.browse-filter-select {
  display: inline-block;
  flex: 1 1 auto;
  width: auto;
  min-width: 150px;
}

/* Search box: a flex item that grows to fill the row past the selects
   (overrides the standalone .section-search width/margin below). Its basis
   is the least it asks for before the row wraps -- 200px keeps the
   gallery's Genre + Sort + search on one line down to a 620px row, one of
   home's paired panels on a laptop. The grow factor dwarfs the fields'
   (above) so that on a shared line the slack is all the search box's. */
.browse-filter-search {
  flex: 1000 1 200px;
  width: auto;
  margin-top: 0;
}

/* On narrow viewports the controls stack for comfortable taps: the selects
   share one row, splitting its width evenly (a lone select fills it) and
   trading their labels for their glyphs (a select without one is named by
   its first option), and the search box takes a full row of its own. */
@media (max-width: 768px) {
  .browse-filter-field {
    /* Below its widest option if it must, so two still share the row. */
    min-width: 0;
  }
  .browse-filter-search {
    display: flex;
    flex-basis: 100%;
    width: 100%;
  }
  .browse-filter-label {
    display: none;
  }
  .browse-filter-icon:not(:empty) {
    display: inline-flex;
  }
  .browse-filter-select {
    flex: 1;
    min-width: 0;
  }
}

/* The same compact row for a panel whose tiles are mini at any viewport --
   one of home's paired panels on a landscape phone: too narrow for words
   under its tiles is too narrow for words beside its selects. Repeated
   rather than shared because a media query and a selector cannot be joined
   in one rule. */
.section-glass:has(.grid-mini) .browse-filter-field {
  min-width: 0;
}
.section-glass:has(.grid-mini) .browse-filter-search {
  display: flex;
  flex-basis: 100%;
  width: 100%;
}
.section-glass:has(.grid-mini) .browse-filter-label {
  display: none;
}
.section-glass:has(.grid-mini) .browse-filter-icon:not(:empty) {
  display: inline-flex;
}
.section-glass:has(.grid-mini) .browse-filter-select {
  flex: 1;
  min-width: 0;
}

/* Optional CTA buttons riding the controls row (the build panel's Tutorials
   and New Project), last in the row so they sit at its far end past the
   search box. They share one wrapper, so the row treats them as one
   control, and a shared min-width keeps their widths equal regardless of
   label length, so they read as one control group. */
.browse-filter-actions {
  display: flex;
  gap: var(--space-2);
  /* Nothing while the search box shares the line (it grows into the
     slack); once the search box has dropped, it holds the pair at the end
     of the line. */
  margin-left: auto;
}

/* Below the row's one-line width the search box is what drops, to a full
   line of its own, while the select and the CTAs hold the first line -- the
   same drop as the gallery's row, where the search box is last and drops
   on its own. Here it is not last, so it is sent to the end of the row for
   the drop. The line is the row with everything at its least: the Channel
   label and select, the search box's 200px basis, both CTAs and the three
   gaps between (640px), rounded up a step for the label's font. Zero
   specificity, so the narrow-viewport rules below can order past it. */
@container (max-width: 660px) {
  :where(.browse-filter-controls:has(.browse-filter-actions)) .browse-filter-search {
    order: 1;
    flex-basis: 100%;
  }

  /* And the select gives up its comfortable floor so the CTAs can hold that
     first line with it. Without this it keeps its own width, takes the line
     alone and sends them to a third row. All three give way or none does:
     the flex item, the grid item inside it, and the native select, which
     asks for the width of its widest option. What is left is the row's own
     arithmetic -- a panel this narrow still fits the label, a usable select
     and both buttons, and one narrower than that is showing mini tiles,
     where the CTAs take a row of their own regardless (below). */
  :where(.browse-filter-controls:has(.browse-filter-actions)) .browse-filter-fields,
  :where(.browse-filter-controls:has(.browse-filter-actions)) .browse-filter-field,
  :where(.browse-filter-controls:has(.browse-filter-actions)) .browse-filter-select {
    min-width: 0;
  }
}

.browse-filter-cta {
  min-width: 100px;
}

/* On narrow viewports the CTAs drop below the search box onto one shared
   row of their own, splitting its width evenly (a lone CTA grows to fill
   it): select, search, then buttons. The search box may carry the same
   order from the drop rule above; the CTAs still follow it, by markup. */
@media (max-width: 768px) {
  .browse-filter-actions {
    order: 1;
    flex-basis: 100%;
  }
  .browse-filter-cta {
    flex: 1;
  }
}

.section-glass:has(.grid-mini) .browse-filter-actions {
  order: 1;
  flex-basis: 100%;
}
.section-glass:has(.grid-mini) .browse-filter-cta {
  flex: 1;
}

/* Results grid sits below the controls inside the same panel. Empty
   (between renders) it collapses so the panel doesn't carry dead space. */
.browse-filter-grid {
  margin-top: var(--space-2);
}

.browse-filter-grid:empty {
  margin-top: 0;
}

/* The search box's inner layout (icon + input + clear button). Width comes
   from its flex sizing (.browse-filter-search), so none is set here. */
.section-search {
  position: relative;
  display: flex;
  align-items: center;
}

/* Positioning/centering come from the shared .search-icon base in
   common/style.css; only color and size are section-specific. */
.section-search-icon {
  color: var(--color-text-dark);
}

.section-search-icon svg {
  width: 16px;
  height: 16px;
}

/* Extends the shared .form-inst-search base; only the trailing gutter for the
   clear (x) button is section-specific. */
.form-inst-search-section {
  padding-right: calc(var(--space-2) + 24px);
}

/* Clear (x) button, mirrors the search icon on the opposite edge */
.button-bare-inst-search-clear {
  position: absolute;
  right: var(--space-2);
  padding: 0;
  line-height: 0;
  appearance: none;
  -webkit-appearance: none;
  color: var(--color-text-dark);
}

.button-bare-inst-search-clear svg {
  width: 16px;
  height: 16px;
}

.button-bare-inst-search-clear:hover {
  color: var(--color-text);
}
