/* The executors page.
 *
 * Same discipline as every other sheet here: no colour, duration or radius that
 * is not a token, because check-shell-css.mjs reads this file too and a raw
 * colour is a colour the contrast gate never saw.
 *
 * The layout is a card grid rather than a table. A table would be tighter, but
 * the row that matters most to a reader is "does this one work", and that is a
 * badge and two numbers, which read better stacked than in columns eight ems
 * wide on a phone.
 */

/* ── the live Roblox build ──────────────────────────────────────────────── */

.rbxnow { border-bottom: 1px solid var(--ln); }
.rbxnow h2 {
  display: flex; align-items: center; gap: var(--s-2);
  margin: 0 0 var(--s-3); font-size: var(--t-h3); font-weight: 600;
}
.rbxgrid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
  gap: var(--s-4); margin: 0;
}
.rbxgrid div { min-width: 0; }
.rbxgrid dt {
  color: var(--mut); font-size: var(--t-label); letter-spacing: var(--track-label);
  text-transform: uppercase; margin-bottom: 3px;
}
.rbxgrid dd {
  margin: 0; font-family: var(--mono); font-size: var(--t-sm);
  overflow-wrap: anywhere;
}
.rbxgrid dd.when { color: var(--mut); font-size: var(--t-xs); font-family: var(--font); margin-top: 2px; }

/* ── flags and scores ───────────────────────────────────────────────────── */

/* ── tags ───────────────────────────────────────────────────────────────────
 *
 * Every tag on a card lives in this one row, so they wrap as a group and line
 * up card to card. They used to be split between the heading row and here,
 * which is what pushed a long name into breaking mid-word and also meant two
 * cards with the same facts could look different depending on name length.
 *
 * They are filled rather than outlined. An outline-only pill on a dark ground
 * has almost no presence at this size, which is why the previous row read as
 * scattered text; a tinted fill gives each one a shape the eye can group. */
.exflags { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.pill {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px var(--s-2); border-radius: var(--rad-pill);
  border: 1px solid var(--ln); background: var(--inp); color: var(--mut);
  font-size: var(--t-xs); font-weight: 500; white-space: nowrap;
  transition: border-color var(--t-tint) ease, color var(--t-tint) ease;
}
.pill .ic { width: 12px; height: 12px; color: inherit; filter: none; }
.pill.mono { font-family: var(--mono); font-size: var(--t-xs); }
/* Colour is carried by the text and the hairline, with the fill left neutral.
   Four saturated fills side by side is a row of confetti, and the one that
   matters most (does it work today) stops standing out. */
.pill.ok { color: var(--ok); border-color: var(--ok); }
.pill.bad { color: var(--acc-txt); border-color: var(--acc-txt); }
.pill.free { color: var(--acc2); border-color: var(--acc2); }
.pill.cost { color: var(--fg); }
/* Our own promise, so it carries the brand accent and a little more weight than
   the facts around it without shouting the way an uppercase badge did. */
.pill.sup {
  color: var(--acc-txt); border-color: var(--acc-txt);
  /* --spot, not --glow-soft, for the reason spelled out at .excard.is-pick
     below: the soft glow token is rgba(255,255,255,.42), and 42% white behind
     a small pill on a near-black card is a grey box. It made the one flag that
     is our own promise read as the most disabled thing on the card.
     --spot is the brand purple at low alpha, so it reads as ours and lit. */
  background: var(--spot); font-weight: 600;
}

/* ── scores ─────────────────────────────────────────────────────────────── */

.exscores { display: grid; gap: 7px; }
.score { display: grid; grid-template-columns: 3.2rem 1fr 2.6rem; gap: var(--s-2); align-items: center; }
.score > span { color: var(--mut); font-size: var(--t-xs); letter-spacing: var(--track-label); }
.score > b { font-size: var(--t-xs); font-variant-numeric: tabular-nums; text-align: right; font-weight: 600; }
.track {
  height: 6px; border-radius: var(--rad-pill); background: var(--barbg);
  overflow: hidden; position: relative;
}
/* The fill grows from nothing the first time a card is painted.
 *
 * `--w` carries the real width so the animation has somewhere to land, and the
 * element is laid out at its final size from the start: only the transform
 * moves, so thirty of these animating at once cost no layout. */
.track i {
  display: block; height: 100%; width: var(--w, 0%);
  background: linear-gradient(90deg, var(--acc2), var(--acc));
  border-radius: var(--rad-pill);
  transform-origin: left center;
  animation: gh-score-grow var(--t-grow) var(--ease) both;
}
@keyframes gh-score-grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
/* A perfect score earns a little light. Nothing else on the card does this, so
   it stays a signal rather than decoration. */
.track i.full { box-shadow: 0 0 10px -2px var(--acc); }
@media (prefers-reduced-motion: reduce) {
  .track i { animation: none; }
  .excard, .excard::before, .expick .ic { transition: none; }
  .excard:hover { transform: none; }
}

.exruns { margin: 0; font-size: var(--t-sm); }
.exruns .dim { color: var(--mut); }
.exwhen { margin: 0; color: var(--mut); font-size: var(--t-xs); }

.exsource { padding: var(--s-4) var(--s-6); color: var(--mut); font-size: var(--t-xs); margin: 0; }

.exlegend {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2) var(--s-3);
  padding: var(--s-4) var(--s-6);
  border-bottom: 1px solid var(--ln);
  color: var(--mut); font-size: var(--t-sm);
}

.tag {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px var(--s-2); border-radius: var(--rad-pill);
  font-size: var(--t-label); letter-spacing: var(--track-label); text-transform: uppercase;
  font-weight: 600; white-space: nowrap;
  border: 1px solid var(--ln);
}
.tag .ic { width: 12px; height: 12px; }
.t-verified    { color: var(--ok);      border-color: var(--ok); }
.t-supported   { color: var(--acc2);    border-color: var(--acc2); }
.t-partial     { color: var(--warn);    border-color: var(--warn); }
.t-unsupported { color: var(--mut);     border-color: var(--ln); }

/* ── the grid ───────────────────────────────────────────────────────────── */

/* Separate surfaces, not table cells.
 *
 * This was a borderless grid of transparent cells, which produced two faults at
 * once and the owner saw both.
 *
 * The cards had NO BACKGROUND, so `body::before` showed straight through them:
 * a fixed 4.5% watermark of the banner, which does not scroll with the page.
 * Diagonal bands from that artwork therefore slid across the grid as you moved,
 * starting and stopping mid-card. On an open hero that reads as a watermark; over
 * a dense grid of bordered cells it reads as a rendering fault, which is exactly
 * what it was called.
 *
 * And nine cards in four columns leaves a last row with one card and three empty
 * cells. With a border-right on every card, that lone card drew a vertical rule
 * into nothing and the row's border-top drew a full-width line under emptiness.
 * A grid of separate surfaces has no cell to leave empty: the row simply ends.
 */
.exgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(17rem, 100%), 1fr));
  gap: var(--s-4);
  padding: var(--s-6);
  border-bottom: 1px solid var(--ln);
}
.exgrid > .count { grid-column: 1 / -1; padding: var(--s-10) var(--s-6); text-align: center; color: var(--mut); }

.excard {
  display: flex; flex-direction: column; gap: var(--s-3);
  padding: var(--s-5);
  background: var(--sur);
  border: 1px solid var(--ln);
  border-radius: var(--rad-large);
  min-width: 0;
  position: relative;
  isolation: isolate;
  overflow: hidden;
  /* So the name can size itself against the CARD rather than the viewport. A
     media query cannot see how wide this column ended up, which is the whole
     reason the name overflowed in the first place. */
  container-type: inline-size;
  transition: background-color var(--t-tint) ease, transform var(--t-move) var(--ease);
}
.excard:hover { background: var(--hov); border-color: var(--edge); transform: translateY(-2px); }
/* A rail that lights on hover, inset from the corners so it reads as light on
   the surface rather than as a line trying to follow a radius it cannot. */
.excard::before {
  content: ""; position: absolute; left: var(--s-5); right: var(--s-5); top: 0; height: 2px;
  border-radius: var(--rad-pill);
  background: var(--acc); box-shadow: 0 0 14px -1px var(--acc);
  transform: scaleX(0); transform-origin: center;
  transition: transform var(--t-move) var(--ease);
}
.excard:hover::before { transform: scaleX(1); }
.excard.is-off { opacity: 0.55; }

/* An executor that does not work on today's Roblox build is still worth
   listing, and should not look like one that does. Held back rather than
   hidden: somebody whose executor just broke came here to find that out. */
.excard.is-old .exlogo { filter: saturate(0.35); }
.excard.is-old:hover .exlogo { filter: none; }

/* ── the owner's pick ───────────────────────────────────────────────────── */

/* A recommended card reads differently before a single word is read.
 *
 * The glow sits UNDER the content on its own layer rather than on the border,
 * so it can be soft and wide without thickening the card's edge, which would
 * make the grid's hairlines look inconsistent from card to card. */
/* The owner's pick reads differently before a word is read: a warmer surface
   and a soft bloom from the top edge, both inside the card's own radius. */
.excard.is-pick { background: var(--hov); border-color: var(--edge); }
.excard.is-pick::after {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  /* --bloom, not --glow-soft. The soft glow token is rgba(255,255,255,...):
     white, and a white radial over a near-black card is a GREY wash, which is
     what made the two recommended cards look faded rather than favoured. The
     same mistake was in the home page fog and had the same cause, which is that
     a token named "glow" reads as "the brand light" and is not.
     The bloom is the brand purple, so the card looks lit instead of dusty. */
  background: radial-gradient(130% 90% at 50% 0%, var(--bloom), transparent 72%);
  opacity: 0.75;
  transition: opacity var(--t-tint) ease;
}
.excard.is-pick:hover::after { opacity: 1; }
.excard.is-pick::before { transform: scaleX(1); opacity: 0.7; }
.excard.is-pick:hover::before { opacity: 1; }

.expick {
  align-self: flex-start;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px var(--s-2) 3px 7px; border-radius: var(--rad-pill);
  background: var(--acc); color: var(--on-acc);
  font-size: var(--t-label); font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase;
  white-space: nowrap;
  box-shadow: inset 0 1px 0 var(--on-acc-sheen), 0 0 18px -6px var(--acc);
}
.expick .ic {
  width: 12px; height: 12px; color: inherit; filter: none;
  transition: transform var(--t-move) var(--ease);
}
.excard.is-pick:hover .expick .ic { transform: rotate(72deg) scale(1.1); }

.exhead { display: flex; align-items: center; gap: var(--s-3); }
/* The name gets the row to itself, and is NOT allowed to break inside a word.
 *
 * This carried `overflow-wrap: anywhere` while a wide uppercase badge shared the
 * row with it. At the narrowest column the two did not fit, and the rule did
 * exactly what it was asked to: it split the word. Real customers saw
 * "Potass ium" and "Madiu m", which reads as a broken page rather than a narrow
 * one. A product name is not a place to break a line, so the badge moved down to
 * the tag row and this now shrinks the type instead, down to a floor where it is
 * still comfortably readable. */
.exhead h3 {
  margin: 0; font-weight: 600;
  font-size: clamp(var(--t-sm), 4.2cqi, var(--t-h3));
  letter-spacing: var(--track-display); line-height: 1.15;
  flex: 1; min-width: 0;
  overflow-wrap: normal; word-break: keep-all; hyphens: none;
}

/* The logo, with a monogram underneath it.
 *
 * The image is from WEAO's CDN, which is somebody else's host. The letter tile
 * is not a placeholder that gets replaced; it stays there and the image covers
 * it, so a host that is slow, blocked or gone leaves a designed square rather
 * than a broken-image glyph. */
.exlogo {
  position: relative; flex: none;
  width: 38px; height: 38px; border-radius: var(--rad-soft);
  overflow: hidden; background: var(--inp); border: 1px solid var(--ln);
  display: grid; place-items: center;
  transition: border-color var(--t-tint) ease, transform var(--t-move) var(--ease);
}
.exlogo i {
  font-style: normal; font-weight: 600; font-size: var(--t-h3);
  color: var(--mut); line-height: 1;
}
.exlogo img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  background: var(--inp);
}
.excard:hover .exlogo { border-color: var(--acc); transform: scale(1.04); }
@media (prefers-reduced-motion: reduce) { .excard:hover .exlogo { transform: none; } }

.explat { margin: 0; color: var(--mut); font-size: var(--t-xs); }

.exstats { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); margin: 0; }
.exstats div { min-width: 0; }
.exstats dt {
  color: var(--mut); font-size: var(--t-label); letter-spacing: var(--track-label);
  text-transform: uppercase; margin-bottom: 2px;
}
.exstats dd {
  margin: 0; font-size: var(--t-h3); font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.exstats dd .dim { color: var(--mut); font-size: var(--t-sm); font-weight: 400; }

.exnote { margin: 0; color: var(--mut); font-size: var(--t-sm); line-height: var(--lh-body); }

.exget { width: 100%; margin-top: auto; }

/* Not a disabled button. A greyed control reads as "temporarily broken" and
   invites a click; a sentence reads as the fact it is. */
.exnolink {
  margin: auto 0 0; padding-top: var(--s-2);
  color: var(--mut); font-size: var(--t-xs); line-height: var(--lh-body);
  border-top: 1px solid var(--ln);
}

/* ── the requirement list ───────────────────────────────────────────────── */

.exfloor { border-bottom: 1px solid var(--ln); }
.exfloor h2 {
  display: flex; align-items: center; gap: var(--s-2);
  margin: 0 0 var(--s-2); font-size: var(--t-h3); font-weight: 600;
}
.floorlist { list-style: none; margin: 0 0 var(--s-4); padding: 0; display: grid; gap: 0; }
.floorlist li {
  display: grid; grid-template-columns: auto auto 1fr; align-items: baseline; gap: var(--s-3);
  padding: var(--s-3) 0; border-bottom: 1px solid var(--ln);
  font-size: var(--t-sm);
}
.floorlist li:last-child { border-bottom: 0; }
.floorlist .ic { width: 15px; height: 15px; color: var(--ok); align-self: center; }
.floorlist b { font-weight: 600; white-space: nowrap; }
.floorlist span { color: var(--mut); min-width: 0; }

@media (max-width: 620px) {
  .floorlist li { grid-template-columns: auto 1fr; }
  .floorlist span { grid-column: 2; }
  .exgrid { grid-template-columns: 1fr; }
}
