/* =========================================================
   Moving parts, the cards of a grid and what sorts them
   The search field, the category row, the grid, the card with its
   live preview, the pack card and the plates of a pack page. Shared
   by the library, the pack pages and the Library page of the landing,
   which sets it in its own shell. Needs the tokens of site.css, or
   the same names defined by the page that loads it.
   ========================================================= */

/* Search field */
.search {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 0 14px;
  height: 48px;
  transition: border-color 0.25s ease, background 0.25s ease;
}

.search:focus-within {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: var(--surface-high);
}

.search svg { flex: none; color: var(--label); transition: color 0.25s ease, transform 0.5s cubic-bezier(0.16, 1, 0.3, 1); }

/* Pointed at, the field wakes: its edge and ground lift, the glass leans in
   and takes the text colour, the words and the shortcut come up a step.
   Focus keeps its own look on top. */
.search input::placeholder { transition: color 0.25s ease; }
.search .shortcut { transition: color 0.25s ease, border-color 0.25s ease, background 0.25s ease; }

@media (hover: hover) {
  .search:hover:not(:focus-within) {
    border-color: var(--line-strong);
    background: var(--surface-high);
  }
  .search:hover svg { color: var(--text); transform: scale(1.08) rotate(-8deg); }
  .search:hover input::placeholder { color: var(--muted); }
  .search:hover .shortcut {
    color: var(--text);
    border-color: var(--line-strong);
    background: color-mix(in srgb, var(--text) 6%, transparent);
  }
}

@media (prefers-reduced-motion: reduce) {
  .search svg { transition: color 0.25s ease; }
  .search:hover svg { transform: none; }
}

.search input {
  flex: 1;
  background: none;
  border: 0;
  outline: 0;
  color: var(--text);
  font-family: inherit;
  font-size: 15px;
  font-weight: 430;
  min-width: 0;
}

.search input::placeholder { color: var(--label); }

/* The native clear button of type="search" shows up in system blue */
.search input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }

.shortcut {
  flex: none;
  color: var(--label);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 2px 7px;
  line-height: 1.6;
}

.info-line {
  color: var(--label);
  margin-top: 14px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
}

/* The filter row. Edge to edge like the masthead hairline above it, closed
   on a hairline below. It is not sticky: with the floating menu fixed at the
   top, a sticky row left a 72 px slot between the two where the cards kept
   scrolling through, and the row read as dropped there rather than placed.
   Six components fit on one screen; the row scrolls away with the title. */
.filterbar {
  margin: 0 calc(var(--gutter) * -1);
  padding: clamp(9px, 1.1vh, 14px) var(--gutter);
  border-bottom: 1px solid var(--line);
}

/* Main categories on the left, the way in to everything else on the right. */
.filterbar-row {
  display: flex;
  align-items: center;
  gap: clamp(8px, 1vw, 16px);
}

/* The names carry their own padding, so the gap only has to keep them from
   touching, and the row can wrap onto a second line on a narrow screen. */
.cats {
  display: flex;
  align-items: center;
  gap: clamp(1px, 0.3vw, 5px);
  flex-wrap: wrap;
  min-width: 0;
  margin-left: calc(clamp(10px, 0.85vw, 13px) * -1);
}

/* The main categories. They get their own class rather than a variant of
   `.chip`, because the Filters panel keeps the mono pills for interactions
   and techniques, and the whole point here is that the two rows do not look
   alike: this one is a list of names, in the page font, sentence case, with
   no border and no box at rest. Only the one in use gets a soft ground. */
.cat {
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
  flex: none;
  font-family: inherit;
  font-weight: 460;
  font-size: clamp(13px, 0.95vw, 15px);
  line-height: 1.25;
  color: var(--muted);
  background: none;
  border: 0;
  border-radius: 999px;
  padding: 6px clamp(10px, 0.85vw, 13px);
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.2s ease, background 0.2s ease;
}

.cat:hover { color: var(--text); }

.cat[aria-pressed="true"] {
  color: var(--text);
  background: var(--surface-high);
}

/* The count is there to be glanced at, not read: one step down and quiet. */
.cat .cat-count {
  font-size: 0.82em;
  color: var(--label);
  font-variant-numeric: tabular-nums;
}

.cat[aria-pressed="true"] .cat-count { color: var(--muted); }

/* =========================================================
   The component grid
   ========================================================= */

/* The grid fills the page: three columns, then two, then one. A fixed count
   rather than auto-fit, because auto-fit reads the minimum in pixels and a
   full-width page would have gone to four or five columns of postage stamps
   on a wide screen. */
.grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(14px, 1.4vw, 26px);
  padding: clamp(20px, 3vh, 34px) 0 clamp(40px, 7vh, 70px);
}

@media (max-width: 1100px) { .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 700px) { .grid { grid-template-columns: minmax(0, 1fr); } }

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 13px 13px 0;
  transition: border-color 0.3s ease, background 0.3s ease, transform 0.3s ease;
}

/* Without this rule the card's display: flex beats the [hidden] attribute
   and filtering hides nothing. */
.card[hidden] { display: none; }

.card:hover {
  border-color: var(--line-strong);
  background: var(--surface-high);
  transform: translateY(-2px);
}

/* The whole card leads where its frame leads: a second link under the
   frame covers the rest of it, the title and the summary included. It is
   out of the tab order, the frame being the one link a keyboard meets. */
.card-link {
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
}

/* The preview frame: the real demo, scaled down in an iframe */
.frame {
  position: relative;
  z-index: 2;
  display: block;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border-radius: 9px;
  background: var(--bg);
  border: 1px solid var(--line);
  text-decoration: none;
}

.frame iframe {
  position: absolute;
  top: 0;
  left: 0;
  border: 0;
  transform-origin: top left;
  pointer-events: none; /* the click belongs to the card */
}

.frame .loading {
  position: absolute;
  inset: 0;
  z-index: 3; /* above the iframe until the demo is there */
  display: grid;
  place-items: center;
  color: var(--label);
  pointer-events: none;
  transition: opacity 0.4s ease;
}

.frame.loaded .loading { opacity: 0; }

/* A recorded card (AB-129): the still of the rest pose, and over it the
   clip while it plays, both filling the frame the way the scaled demo does
   (the recordings are 1920 by 1200, the demo's 16 by 10). The clip shows
   only once it is playing: its first frame is the still, so nothing jumps. */
.frame .still,
.frame .clip {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  pointer-events: none;
}
.frame .clip { z-index: 1; opacity: 0; }
.frame.playing .clip { opacity: 1; }

/* On the title line, not over the preview: in the corner of the frame it sat
   on top of the demo's own logo. The auto margin keeps it against the title
   and leaves the category at the far end of the row. */
.badge {
  margin-right: auto;
  align-self: center;
  flex: none;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
  border-radius: 999px;
  padding: 2px 8px;
  line-height: 1.6;
}

.veil {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  opacity: 0;
  background: color-mix(in srgb, var(--bg) 40%, transparent);
  transition: opacity 0.3s ease;
}

.card:hover .veil { opacity: 1; }

.veil span {
  color: var(--text);
  background: color-mix(in srgb, var(--panel) 88%, transparent);
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  padding: 8px 16px;
}

.card-body {
  padding: 20px 4px 21px;
  display: flex;
  flex-direction: column;
  gap: 9px;
  flex: 1;
}

.card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
}

.card-head h2 {
  font-weight: 470;
  font-size: 19px;
  letter-spacing: -0.015em;
  line-height: 1.25;
}

.card-head .category { color: var(--label); white-space: nowrap; }

.summary {
  color: var(--muted);
  font-size: 14px;
  line-height: 1.55;
}

/* Trial, 24/09/2026: the cards without their description. Remove this
   rule to bring the text back. */
.card-body .summary { display: none !important; }

/* Two lines at most on a card: each spec carries a short card line for it,
   and the clamp only guards a card narrower than the line was written for. */
.card-body .summary {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}


/* Nothing found */
.empty {
  padding: 90px 0 120px;
  text-align: center;
  color: var(--label);
}

.empty strong { display: block; color: var(--text); font-weight: 450; font-size: 22px; margin-bottom: 10px; }

/* ==========================================================
   Packs (AB-98)
   ========================================================== */

/* ---- The pack card: the edges of two more cards stand behind it ---- */
.card--pack { position: relative; }

.pack-sheet {
  position: absolute;
  left: 16px;
  right: 16px;
  top: -7px;
  height: 8px;
  border: 1px solid var(--line);
  border-bottom: 0;
  border-radius: var(--radius) var(--radius) 0 0 / 8px 8px 0 0;
  background: color-mix(in srgb, var(--panel) 55%, var(--bg));
  pointer-events: none;
  transition: transform 0.45s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.pack-sheet + .pack-sheet {
  left: 32px;
  right: 32px;
  top: -13px;
  height: 7px;
  opacity: 0.6;
}

.card--pack:hover .pack-sheet { transform: translateY(-3px); }
.card--pack:hover .pack-sheet + .pack-sheet { transform: translateY(-6px); }

/* The count on the title line, where a card shows its category: the
   number in the text colour, the word as the category. */
.card--pack .card-head .category b { color: var(--text); font-weight: 500; }

/* On a card of the pack page, the one sign the whole thing is a link: the
   veil of the other cards would sit on the button playing. */
.pack-open {
  position: absolute;
  left: 50%;
  bottom: 5.5%;
  z-index: 4;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--text);
  background: color-mix(in srgb, var(--panel) 88%, transparent);
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  padding: 5px 13px;
  line-height: 1.6;
  white-space: nowrap;
  opacity: 0;
  transform: translate(-50%, 6px);
  transition: opacity 0.3s ease 0.12s, transform 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) 0.12s;
}

.pack-open svg { width: 11px; height: 11px; }

.card--item:hover .pack-open,
.card--item .frame:focus-visible .pack-open { opacity: 1; transform: translate(-50%, 0); }

/* A card of a pack shows its button on a plate until the demo is there:
   the same outline the small card of the library carried, so what flew in
   is what the demo replaces. */
.frame.has-plate { container-type: inline-size; }

.plate {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: grid;
  place-items: center;
  pointer-events: none;
  transition: opacity 0.4s ease;
}

.plate-pill {
  font-size: clamp(9px, 2.6cqw, 14px);
  line-height: 1;
  color: var(--muted);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 6px;
  padding: 1.1em 2em;
  margin-bottom: 1.2cqw;
}

.frame.loaded .plate { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .pack-sheet { transition: opacity 0.2s ease; transform: none; }
  .pack-open,
  .card--item:hover .pack-open { transition: opacity 0.2s ease; transform: translate(-50%, 0); }
  .card--pack:hover .pack-sheet,
  .card--pack:hover .pack-sheet + .pack-sheet { transform: none; }
}

/* ==========================================================
   The Black / white look, 24/09/2026: the previews
   ========================================================== */

/* The previews sit on their ground while their demo loads, as once it is
   there. */
.frame { background: var(--preview-ground); }
/* Square clip, rounded by a layer on top.
   Chrome rounding a scaled demo by itself leaves a light arc in each corner,
   seen on a dark demo (the preloader), whatever lies under it. So the frame
   clips square, without a border, and a layer above the demo draws the
   border and paints the corners in the solid colour of the card, which
   follows the card's hover. */
.frame {
  --frame-card: color-mix(in srgb, #ffffff 3%, var(--bg));
  border: 0;
  border-radius: 0;
  background-clip: padding-box;
}
.card:hover .frame { --frame-card: color-mix(in srgb, #ffffff 5.5%, var(--bg)); }
.frame::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  border: 1px solid color-mix(in srgb, #ffffff 8%, var(--frame-card));
  border-radius: 9px;
  box-shadow: 0 0 0 9px var(--frame-card);
  pointer-events: none;
  transition: box-shadow 0.3s ease, border-color 0.3s ease;
}
.frame .loading,
.frame .plate { color: var(--label); }

/* ==========================================================
   The viewfinder corners around a preview
   ========================================================== */

/* The same corners as the actions of a component page: they stand just
   outside the preview and close on it when the card is pointed at. The
   frame clips, so the card carries them. Its padding resolves against the
   card's width, which gives the layer the height of the 16 by 10 preview. */
.card::after {
  content: "";
  position: absolute;
  left: 8px;
  right: 8px;
  top: 8px;
  height: 0;
  padding-top: calc((100% - 26px) * 0.625 + 10px);
  z-index: 3;
  --m: 10px;
  --c: var(--text);
  background:
    linear-gradient(var(--c), var(--c)) 0 0 / var(--m) 1px, linear-gradient(var(--c), var(--c)) 0 0 / 1px var(--m),
    linear-gradient(var(--c), var(--c)) 100% 0 / var(--m) 1px, linear-gradient(var(--c), var(--c)) 100% 0 / 1px var(--m),
    linear-gradient(var(--c), var(--c)) 0 100% / var(--m) 1px, linear-gradient(var(--c), var(--c)) 0 100% / 1px var(--m),
    linear-gradient(var(--c), var(--c)) 100% 100% / var(--m) 1px, linear-gradient(var(--c), var(--c)) 100% 100% / 1px var(--m);
  background-repeat: no-repeat;
  opacity: 0;
  transform: scale(0.955);
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}

.card:has(.frame:focus-visible)::after {
  opacity: 1;
  transform: scale(1);
}

@media (hover: hover) {
  .card:hover::after {
    opacity: 1;
    transform: scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .card::after { transform: none; transition: opacity 0.2s ease; }
}
