/*
 * Server card — structure.
 *
 * ALMOST NO LITERAL COLOURS IN THIS FILE. Everything visual comes from theme-default.css via
 * custom properties; if you find yourself typing a hex value here, it belongs in the theme
 * instead. Two literals remain and are known: the brand accent on the wordmark, and one inset
 * ring on the plate. Both predate the theme file and are worth moving.
 *
 * No dependencies. The chart is inline SVG, the drifting tags are two CSS animations, and the
 * grid is plain CSS grid — there is nothing to vendor, host or keep updated.
 *
 * Layout follows the approved mockup: a centred title plate, the actions directly under it,
 * the WarmupServer wordmark, then one labelled band per fact. The band label is a full-width
 * bar and its value sits underneath — the card reads as a spec sheet, which is what a player
 * comparing seventy servers is actually doing.
 */

.wsc-grid {
  display: grid;
  /*
   * DENSITY IS ONE NUMBER, AND IT IS A SETTING.
   *
   * `card_min_width` in config.php; cards.js writes it onto this element as `--wsc-card-min`.
   * The 300px here is the fallback for a JS-less embed of this stylesheet, not a second knob.
   *
   * One number is enough because everything inside a card is sized as a proportion of the
   * card's own width (see the token block below): 200 gives the compact widget density, 300 the
   * live site's card, 440 the full-page treatment, and the design holds at all three.
   */
  grid-template-columns: repeat(auto-fill, minmax(var(--wsc-card-min, 300px), 1fr));
  /*
   * Measured off the live page rather than chosen: there the gutter is about a ninth of a card's
   * width. 14px against a 348px card was 4%, which packed the grid tight enough that the cards
   * read as one block of dark rather than as seventy separate things to compare.
   *
   * Proportional, so it holds at every density the one width knob produces — the same reason
   * everything inside a card is sized from the card's own width. Clamped so a very narrow column
   * does not lose its gutter altogether and a very wide one does not drift apart.
   */
  gap: clamp(12px, calc(var(--wsc-card-min, 300px) * 0.11), 44px);
}

/* ── The card ─────────────────────────────────────────────────────────────────────────── */

/*
 * THE CARD SIZES ITSELF FROM ITS OWN WIDTH.
 *
 * The card this replaces is a PNG — 350x569 natural, stretched to whatever the column is. That
 * makes every proportion inside it fixed: the wordmark is always 8.5% of the card's width, the
 * band labels always 4.5%, whatever width the card ends up at. Our first version used fixed
 * pixels instead, so a 444px-wide card carried 15px type: the same layout at 40% of the
 * reference's optical size, which is exactly why it read as small and empty next to his.
 *
 * `container-type: inline-size` turns the card into a query container and `cqw` = 1% of its
 * width, so these tokens reproduce the PNG's proportions at ANY column width. The numbers below
 * are measured off the live card, not chosen: glyph-ink height divided by card width, converted
 * to a font size.
 *
 * The clamps are the one deliberate departure. A PNG scaled to 180px is simply a blurry PNG; HTML
 * at 180px would be 8px type, which is unreadable rather than merely small. So each token has a
 * legibility floor and a ceiling that stops a wide column turning the card into a poster.
 * Between roughly 240px and 400px — every density we are likely to ship — the proportions are
 * the live card's exactly.
 */
.wsc-card {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  container-type: inline-size;

  /* Band label and value text — the card's base size, everything else is relative to it. */
  --wsc-fs:        clamp(11px, 4.5cqw, 20px);
  /* The wordmark. Twice the base, as on the live card, where it is the largest thing by far. */
  --wsc-fs-brand:  clamp(17px, 9.6cqw, 38px);
  --wsc-fs-title:  clamp(13px, 6.4cqw, 26px);
  --wsc-fs-btn:    clamp(10px, 4.7cqw, 18px);
  /* Status tag, note line, drifting rank tags — deliberately below the base. */
  --wsc-fs-small:  clamp(10px, 3.6cqw, 14px);
  /* Chart axis numbers. Smallest thing on the card, so the floor bites earliest. */
  --wsc-fs-axis:   clamp(8px, 2.4cqw, 12px);
  --wsc-pad:       clamp(7px, 3cqw, 14px);

  background: var(--wsc-bg);
  border: 1px solid var(--wsc-border);
  border-radius: var(--wsc-radius);
  color: var(--wsc-text);
  font: var(--wsc-fs)/1.4 "Segoe UI", system-ui, -apple-system, sans-serif;
  /* Colour and shadow only — a transform here would nudge the neighbouring cards in the grid
     and make the whole page twitch as the cursor crosses it. */
  transition: border-color 180ms ease-out, box-shadow 180ms ease-out;
}

/* The card is a target: it carries four buttons and a chart worth reading. Saying so on hover
   is the difference between a grid of pictures and a grid of things you can use. */
.wsc-card:hover,
.wsc-card:focus-within {
  border-color: var(--wsc-hover-border);
  box-shadow: var(--wsc-hover-glow);
}

/* A listed server that has never reported. Dimmed rather than hidden: the card is in the
   publish list, so something is wrong, and hiding it would hide the problem too. */
.wsc-card.is-dark { opacity: .5; }

/* ── Title plate ──────────────────────────────────────────────────────────────────────── */

.wsc-plate {
  padding: var(--wsc-pad) var(--wsc-pad) calc(var(--wsc-pad) * .8);
  background: var(--wsc-plate-bg);
  text-align: center;
}

.wsc-plate__title {
  margin: 0;
  color: var(--wsc-title-text);
  font-size: var(--wsc-fs-title);
  font-weight: 700;
  line-height: 1.28;
  /* Underlined per the mockup, offset so descenders in "Nürnberg" are not struck through. */
  font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 2px;
  text-wrap: balance;
}

/* ── Actions ──────────────────────────────────────────────────────────────────────────── */

.wsc-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  /* Scales with the buttons. A fixed 6px gap that looked right beside 11px labels reads as
     buttons stuck together once they are half again as large. */
  gap: calc(var(--wsc-pad) * .6);
  padding: 0 calc(var(--wsc-pad) * .85) calc(var(--wsc-pad) * .5);
  background: var(--wsc-plate-bg);
}

/* The copy row leaves the gap between the two rows; this one closes the plate. */
.wsc-actions--primary { padding-bottom: calc(var(--wsc-pad) * .8); }

.wsc-btn {
  padding: .35em .8em;
  border: 1px solid var(--wsc-action-edge);
  border-radius: var(--wsc-radius);
  background: var(--wsc-action-bg);
  color: var(--wsc-action-text);
  cursor: pointer;
  font: inherit;
  font-size: var(--wsc-fs-btn);
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  /* Colour only — a scale or size change here would reflow the row under the cursor. */
  transition: background-color 180ms ease-out;
}

.wsc-btn:hover { background: var(--wsc-action-hover); }
.wsc-btn:focus-visible { outline: 2px solid var(--wsc-label-text); outline-offset: 2px; }

/* Instant Connect is the primary action and takes its own line, as in the mockup. */
/* Instant Connect takes its own line, as in the mockup. Sentence case and no tracking: the
   uppercase version was the loudest thing on a card whose job is to be scanned. */
.wsc-btn--primary {
  flex: 0 0 auto;
  padding: .4em 1.35em;
  font-size: calc(var(--wsc-fs-btn) * 1.08);
}

.wsc-btn.is-copied::after { content: " ✓"; }

/*
 * On a touchscreen the buttons meet the 44px minimum; on a mouse they do not have to.
 *
 * Proportional sizing already took them from 25px to 35px, which is usable but still under the
 * threshold, and these four are the only things on the card anyone taps. `pointer: coarse` is
 * the finger, so desktop geometry is untouched.
 */
@media (pointer: coarse) {
  .wsc-btn { min-height: 44px; }
}

/* ── Wordmark ─────────────────────────────────────────────────────────────────────────── */

/*
 * The wordmark is the largest thing on the card, by a wide margin.
 *
 * On the live card it is 8.5% of the card's width — more than twice the band text and nearly
 * double the title. Ours was 3.4%, which is why the header read as a caption rather than as a
 * masthead. This is the single biggest visual difference between the two cards.
 */
.wsc-brand {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: calc(var(--wsc-pad) * .55) var(--wsc-pad);
  background: var(--wsc-plate-bg);
  font-size: var(--wsc-fs-brand);
  font-weight: 800;
  letter-spacing: -.02em;
  line-height: 1.05;
}

/*
 * Sized so the flag's ink matches the wordmark's cap height, which is the relationship the live
 * card has (flag 5.8% of card width, cap height 6.5%).
 *
 * Its WIDTH cannot be matched and that is worth stating: the live card draws a real flag image,
 * 2.87:1, a banner. This is an emoji, and its aspect is fixed by the platform's font at about
 * 1.4:1 — so ours reads as a stamp beside his banner no matter what size it is set at. Closing
 * that gap needs fifteen inline SVGs (the city table in servers.json), not a CSS value.
 *
 * Where the platform ships no flag font (Windows) the glyph degrades to the two-letter code,
 * which stays legible at this size and repeats the bracket in the title.
 */
.wsc-brand__flag {
  margin-right: .3em;
  font-size: 1.05em;
  line-height: 1;
}

.wsc-brand__mark   { color: var(--wsc-title-text); }
.wsc-brand__accent { color: #e8443a; }

/* The bracketed region code, dimmed so the city stays the thing you read first. */
.wsc-plate__code {
  color: var(--wsc-muted);
  font-weight: 700;
}

/* ── Labelled bands ───────────────────────────────────────────────────────────────────── */

/* The chart's label bar must not stretch with it, only the chart body does. */
.wsc-band__label {
  flex: 0 0 auto;
  margin: 0 calc(var(--wsc-pad) * .35);
  padding: .22em .75em;
  /* Horizontal gradient, sampled from the live cards. A flat fill reads as a table header;
     the gradient plus the hairline is what makes the row look like a plate rather than a bar.
     Inset from the card edge on both sides, as on the live site — the full-bleed version was
     the main reason ours looked harder-edged than his. */
  background: linear-gradient(90deg, var(--wsc-label-from), var(--wsc-label-to));
  border: 1px solid var(--wsc-label-edge);
  border-radius: var(--wsc-radius);
  color: var(--wsc-label-text);
  font-size: var(--wsc-fs);
  /* 600, not 700, and no tracking. Heavy uppercase with wide letter-spacing is what made the
     card read as shouting next to his. */
  font-weight: 600;
  text-transform: uppercase;
}

/*
 * The size is stated, not inherited.
 *
 * These are <p> elements, and a host page with a `p { font-size: 13px }` rule of its own — the
 * demo page had exactly that — beats an inherited value and silently shrinks half the card
 * while the labels beside it stay correct. A class selector outranks an element selector, so
 * repeating the token here is what makes the component safe to drop into someone else's CSS.
 */
.wsc-band__value {
  flex: 0 0 auto;
  margin: 0;
  padding: calc(var(--wsc-pad) * .4) var(--wsc-pad) calc(var(--wsc-pad) * .65);
  font-size: var(--wsc-fs);
  overflow-wrap: anywhere;
}

/*
 * The server name is pinned to two lines.
 *
 * Same reason as the status slot: a one-line hostname made that card shorter, its chart taller,
 * and the row uneven. Two lines is what a real CS2 hostname needs at this width; anything
 * longer is clipped rather than allowed to push the chart around.
 */
.wsc-band__value--name {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  min-height: calc(2 * 1.4em);
}

/* Host:port and the player counts are data, not prose — a monospace face keeps digits aligned
   between cards, which is the whole point of putting seventy of them in a grid. */
.wsc-mono {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-variant-numeric: tabular-nums;
}

/*
 * The address line is plain, and deliberately so.
 *
 * It briefly carried a raised z-index and an opaque background, so a drifting tag would pass
 * behind it and leave the digits readable. It did — and it looked broken: the tag was CLIPPED
 * mid-word every time it crossed, which reads as a rendering fault rather than as a design.
 *
 * A tag on top of the address for a moment is honest and self-correcting, because the tag keeps
 * moving. A tag sliced in half is neither. So the tags float above everything now (see the drift
 * layer below) and this line is left alone.
 */
.wsc-band__value--host {
  display: flex;
  align-items: center;
  gap: .6em;
}

/* The dot says telemetry is arriving — NOT that the game port is reachable. A heartbeat proves
   the plugin and the API are alive and nothing more, which is why the tooltip says so rather
   than claiming "online". */
.wsc-dot {
  flex: 0 0 auto;
  width: .78em;
  height: .78em;
  border-radius: 2px;
  background: var(--wsc-dot-none);
}
.wsc-dot--fresh { background: var(--wsc-dot-fresh); }
.wsc-dot--stale { background: var(--wsc-dot-stale); }

/* ── Players ──────────────────────────────────────────────────────────────────────────── */

.wsc-players {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: calc(var(--wsc-pad) * .7);
  padding: calc(var(--wsc-pad) * .4) var(--wsc-pad) calc(var(--wsc-pad) * .55);
}

.wsc-players__count {
  flex: 0 0 auto;
  font-size: calc(var(--wsc-fs) * 1.06);
  font-weight: 700;
}

/* Bots, when there is a count — beside the human total and deliberately quieter than it,
   because they are not part of the answer to "is it worth joining". The count comes from the
   A2S probe today (the API's field is in the contract and always null). */
.wsc-players__bots {
  flex: 0 0 auto;
  font-size: calc(var(--wsc-fs) * 0.86);
  font-weight: 600;
  color: var(--wsc-muted);
  white-space: nowrap;
}

/*
 * NO `overflow: hidden` here.
 *
 * It is the obvious way to round the segment corners, and it silently clips the hover tooltip,
 * which sits above the bar — the tooltip simply never appeared. The rounding is done on the
 * first and last segment instead, which costs two rules and keeps the tooltip visible.
 */
.wsc-bar {
  display: flex;
  flex: 1 1 auto;
  height: clamp(11px, 3.9cqw, 20px);
  background: var(--wsc-rank-empty);
  border-radius: var(--wsc-radius);
}

/*
 * Each segment is hoverable and names itself.
 *
 * It used to carry a `title`, which produced the browser's native tooltip: a grey box that
 * appears after a second somewhere near the pointer, in the OS font, styled by nothing. On a
 * card this deliberately designed that reads as a bug. This is the same information, instantly,
 * in the card's own voice — and the segment brightens so it is obvious WHICH one is being
 * described.
 */
.wsc-bar__seg {
  position: relative;
  height: 100%;
  transition: filter 150ms ease-out;
}

.wsc-bar__seg:first-child { border-radius: var(--wsc-radius) 0 0 var(--wsc-radius); }
.wsc-bar__seg:last-child  { border-radius: 0 var(--wsc-radius) var(--wsc-radius) 0; }
/* A single segment on a full server is both ends at once. */
.wsc-bar__seg:only-child   { border-radius: var(--wsc-radius); }

/*
 * Highlight the one, do NOT dim the rest.
 *
 * The first version dropped every other segment to brightness(.55): technically it isolated
 * the hovered one, and it read as the control being disabled — the whole bar went grey the
 * moment the pointer touched it. Nothing is disabled. So the others stay exactly as they were
 * and the hovered segment brightens and gains a light edge, which is the difference between
 * "this one" and "not those".
 */
.wsc-bar__seg:hover {
  filter: brightness(1.4) saturate(1.15);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .75);
}

.wsc-bar__seg::after {
  content: attr(data-label);
  position: absolute;
  bottom: calc(100% + 7px);
  left: 50%;
  z-index: var(--wsc-z-tip);
  padding: 3px 7px;
  border: 1px solid var(--wsc-border);
  border-radius: var(--wsc-radius);
  background: var(--wsc-tip-bg);
  box-shadow: var(--wsc-tag-shadow);
  color: var(--wsc-tip-text);
  font-size: var(--wsc-fs-small);
  font-weight: 600;
  white-space: nowrap;
  opacity: 0;
  transform: translate(-50%, 3px);
  transition: opacity 140ms ease-out, transform 140ms ease-out;
  pointer-events: none;
}

.wsc-bar__seg:hover::after {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* The bar is 18px tall; extending the hit area vertically makes it easy to grab without
   demanding precision. NOT horizontally — a couple of pixels each side and adjacent segments
   overlap, so the pointer near a boundary names the wrong rank. */
.wsc-bar__seg::before {
  content: "";
  position: absolute;
  inset: -6px 0;
}
.wsc-bar__seg--legend { background: var(--wsc-rank-legend); }
.wsc-bar__seg--pro    { background: var(--wsc-rank-pro); }
.wsc-bar__seg--other  { background: var(--wsc-rank-other); }
.wsc-bar__seg--vip    { background: var(--wsc-rank-vip); }
.wsc-bar__seg--f2p    { background: var(--wsc-rank-f2p); }
/* Humans on a row the A2S probe built (start data, no tiers known): one neutral colour, not a
   tier's. See barSegments. */
.wsc-bar__seg--player { background: var(--wsc-rank-player); }
/* Drawn last and clipped to the free track (cards.js barSegments), so it can never push the
   humans or exceed the slot count. */
.wsc-bar__seg--bot    { background: var(--wsc-rank-bot); }

/* ── Chart ────────────────────────────────────────────────────────────────────────────── */

/* Outer band padding; the framed plot sits inside it. */
.wsc-chart {
  /* Grows to fill whatever height the tallest card in the row sets. Cards in a grid stretch to
     a common height, and with a fixed-height chart the shorter ones ended in a band of dead
     black — the chart is the only element here that can absorb slack without distortion. */
  flex: 1 1 auto;
  display: flex;
  /* The live card gives its plot 54% of the card's width in height. Ours carries a title plate
     the PNG does not, so it takes a little less and lets flex make up the rest. */
  min-height: clamp(78px, 38cqw, 200px);
  /* No bottom padding: the chart is the last thing in the card and runs to its edge. */
  padding: calc(var(--wsc-pad) * .5) var(--wsc-pad) 0;
}

.wsc-plot {
  position: relative;
  flex: 1 1 auto;
  /* Only the y-axis gutter, and it is derived from the axis type size rather than guessed —
     the labels are two digits wide, so the gutter has to grow when they do. The line runs to
     the frame on the other three sides: a plot with even padding all round wastes the width
     the shape needs most. */
  padding: 2px 2px 2px calc(var(--wsc-fs-axis, 9px) * 2.4);
  background: var(--wsc-chart-bg);
  border: 1px solid var(--wsc-chart-frame);
  /* Flush with the bottom of the card, so the card's own border is the plot's bottom edge —
     two 1px lines stacked would read as one heavy one. */
  border-bottom: 0;
  border-radius: 2px 2px 0 0;
}

.wsc-chart__svg {
  display: block;
  width: 100%;
  height: 100%;
  min-height: clamp(64px, 32cqw, 180px);
}

/*
 * Y-axis labels are HTML, not <text> inside the SVG.
 *
 * The plot uses preserveAspectRatio="none" so the line stretches to whatever width the card
 * ends up at — which is right for the line and wrong for everything else: it scaled the axis
 * numbers horizontally too and left them visibly squashed. HTML labels are laid out by the
 * browser at their natural proportions and cannot be distorted by the viewBox.
 */
.wsc-plot__pending {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: var(--wsc-fs-small);
  color: var(--wsc-muted);
  pointer-events: none;
}

/*
 * "history unavailable" — a store that exists and cannot be read.
 *
 * Deliberately dimmer than the status tags rather than louder. This is a fault on OUR side, not
 * news about the game server, and seventy cards shouting about our own permissions problem would
 * read to a visitor as seventy broken servers. It only has to be visibly NOT the same words as
 * "collecting history", so that whoever is looking for the reason finds one.
 */
.wsc-plot__pending--error {
  color: var(--wsc-danger);
  opacity: 0.75;
}

/*
 * Positioned against the PLOT AREA, not against the plot.
 *
 * The area is the box the svg fills; the plot is that box plus its padding. An absolutely
 * positioned child measures against the padding box, so a label at "50%" sat half a padding
 * taller than the gridline it names — every label about 2px off its own line, consistently, in
 * a way that reads as sloppy rendering rather than as a bug. Sharing the box with the svg makes
 * the two agree by construction instead of by two numbers being kept in step by hand.
 *
 * `right: calc(100% + 2px)` puts the label just outside the area's left edge, in the gutter the
 * plot's padding reserves for it.
 */
.wsc-plot__axis {
  position: absolute;
  right: calc(100% + 2px);
  width: calc(var(--wsc-fs-axis, 9px) * 2.1);
  color: var(--wsc-muted);
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: var(--wsc-fs-axis, 9px);
  line-height: 1;
  text-align: right;
  transform: translateY(-50%);
  pointer-events: none;
}

/*
 * The readout. One box shared by the svg, the dot and the bubble, so a percentage means the same
 * thing to all three — the plot itself carries the axis gutter as padding and would offset them.
 */
.wsc-plot__area {
  position: relative;
  width: 100%;
  height: 100%;
}

/* Nothing is visible until the pointer is over the plot, and nothing intercepts it either: the
   area itself takes the events, its children must not steal them mid-move. */
.wsc-hover__guide,
.wsc-hover__dot,
.wsc-hover__tip {
  opacity: 0;
  pointer-events: none;
  transition: opacity .08s linear;
}

.wsc-plot__area.is-hovering .wsc-hover__guide,
.wsc-plot__area.is-hovering .wsc-hover__dot,
.wsc-plot__area.is-hovering .wsc-hover__tip { opacity: 1; }

.wsc-hover__guide {
  stroke: var(--wsc-hover-guide);
  stroke-width: 1;
  /* The svg is stretched horizontally, which would thin this line at wide card sizes. */
  vector-effect: non-scaling-stroke;
}

.wsc-hover__dot {
  position: absolute;
  width: var(--wsc-hover-dot-size, 7px);
  height: var(--wsc-hover-dot-size, 7px);
  margin: calc(var(--wsc-hover-dot-size, 7px) / -2) 0 0 calc(var(--wsc-hover-dot-size, 7px) / -2);
  border-radius: 50%;
  background: var(--wsc-chart-line);
  box-shadow: 0 0 0 2px var(--wsc-chart-bg);
}

/* A hole has no value to sit on. The dot stays at mid-height as an anchor for the bubble, hollow
   so it cannot be read as a measurement. */
.wsc-hover__dot.is-empty {
  background: transparent;
  border: 1px solid var(--wsc-hover-guide);
  box-shadow: none;
}

.wsc-hover__tip {
  position: absolute;
  top: 2px;
  transform: translateX(-50%);
  padding: 2px 6px;
  border-radius: 3px;
  /* The border is not decoration. The plot background is itself a near-black, so a translucent
     bubble over it has no visible edge and the text reads as floating on the curve. */
  border: 1px solid var(--wsc-hover-tip-edge);
  background: var(--wsc-hover-tip-bg);
  color: var(--wsc-hover-tip-fg);
  font-size: var(--wsc-fs-small);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  z-index: 2;
}

/* Near an edge the bubble would hang off the card, so it stops centring on the point and pins
   to the side instead. */
.wsc-hover__tip.is-start { transform: translateX(0); }
.wsc-hover__tip.is-end   { transform: translateX(-100%); }

.wsc-chart__grid { stroke: var(--wsc-chart-grid); stroke-width: 1; }
.wsc-chart__tick { stroke: var(--wsc-chart-frame); stroke-width: 1; }
.wsc-chart__line {
  fill: none;
  stroke: var(--wsc-chart-line);
  stroke-width: 1.8;
  stroke-linejoin: round;
  stroke-linecap: round;
}

/* A break in the line is a period the server was not reporting. The chart says "no data", not
   "zero players" — the two are different and every previous version conflated them. */
.wsc-chart__gap {
  stroke: var(--wsc-chart-gap);
  stroke-width: 1;
  stroke-dasharray: 2 2;
  opacity: .45;
}

/* ── Status ───────────────────────────────────────────────────────────────────────────── */

/*
 * The warning sits INSIDE the plot, bottom right, and only when there is one.
 *
 * It used to have a row of its own below the chart, and that row had to be reserved on every
 * card whether or not it held anything: cards in a grid row stretch to the tallest, so a card
 * with a tag was 34px taller and the quiet ones handed those 34px to their flexing chart. The
 * card you most wanted to read ended up with the smallest chart.
 *
 * Overlaying removes the row and the problem together. The corner is free exactly when the tag
 * exists — every state that produces one is a full or nearly-full server, so the line is pinned
 * to the top of the plot and the bottom right is empty track.
 */
.wsc-status {
  position: absolute;
  right: 4px;
  bottom: 4px;
  z-index: var(--wsc-z-tip);
  padding: .32em .8em;
  border-radius: var(--wsc-radius);
  font-size: var(--wsc-fs-small);
  font-weight: 700;
  white-space: nowrap;
  pointer-events: none;
}

/*
 * Only ever ONE, and only when there is something to say.
 *
 * The green "clean fair F2P users welcome" is gone: it appeared on almost every card, which
 * made it decoration rather than information, and a badge that is always on teaches people to
 * stop reading badges. A card with room says nothing — the players bar already shows that.
 */
.wsc-status--kickrisk    { background: var(--wsc-tag-kickrisk-bg);   color: var(--wsc-tag-kickrisk-fg); }
.wsc-status--fullvip     { background: var(--wsc-tag-fullvip-bg);    color: var(--wsc-tag-fullvip-fg); }
.wsc-status--fulllegend  { background: var(--wsc-tag-fulllegend-bg); color: var(--wsc-tag-fulllegend-fg); }
.wsc-status--full        { background: var(--wsc-tag-full-bg);       color: var(--wsc-tag-full-fg); }
.wsc-status--stale       { background: var(--wsc-tag-stale-bg);      color: var(--wsc-tag-stale-fg); }

.wsc-note {
  flex: 0 0 auto;
  margin: 0 var(--wsc-pad) calc(var(--wsc-pad) * .75);
  color: var(--wsc-muted);
  font-size: var(--wsc-fs-small);
}

/* ── Drifting rank tags ───────────────────────────────────────────────────────────────── */

/*
 * The whole card is the playing field, as in the mockup — the tags cross the title, the bands
 * and the chart alike.
 *
 * Two nested elements, two animations: the wrapper travels X, the inner element travels Y,
 * both `linear infinite alternate`. That is the entire bounce — no library, no JavaScript, no
 * requestAnimationFrame. Transform-only, so it composites on the GPU and never triggers layout.
 *
 * `container-type: size` lives here and not on the card: it requires a size that does not
 * depend on the element's own contents, and on the card — a flex column of auto height — the
 * block size would resolve to 0 and every `100cqh` with it. This layer takes its size from
 * `inset: 0`, which is definite.
 */
/*
 * The tags bounce around the WHOLE card, over everything, clipped by nothing.
 *
 * They were confined to the region below the header for a while, to keep them off the Instant
 * Connect button and the wordmark. Seen against the live page that read as a shuttle trapped in
 * a box rather than as something roaming, which is not what was asked for — and the one value
 * that was then protected by z-index instead got its tag sliced in half every pass.
 *
 * So: the full card, and the top of the stack below tooltips. Nothing occludes a tag and a tag
 * occludes nothing for long, because it never stops moving. `--wsc-tag-area-top` is kept as a
 * knob for anyone who wants the old behaviour back.
 */
.wsc-drift {
  position: absolute;
  inset: var(--wsc-tag-area-top, 0%) 0 var(--wsc-drift-floor, 0px) 0;
  /* Above every band, every button and the address — below tooltips, which are a deliberate
     answer to a pointer and outrank an ambient one. */
  z-index: calc(var(--wsc-z-tip) - 1);
  pointer-events: none;
  container-type: size;
}

/*
 * The status warning keeps its own clearance, and it is the only thing that does.
 *
 * Measured before this existed: sampling seventeen cards at 10 Hz for 45 seconds, a tag sat over
 * the warning in 0.6% of samples and the worst case covered three quarters of it. Rare, and
 * severe when it happens — the one message trying to tell you something is the one being sat on,
 * and unlike an address you cannot guess what it said.
 *
 * Geometry, not detection: the roaming layer's floor is lifted above the warning's top edge and
 * the Y animation stops at `100cqh - 100%`, so a tag's bottom can never pass its container's
 * bottom. Overlap is arithmetically impossible rather than merely unlikely, and it costs nothing
 * per frame — the alternative was reading two rectangles every frame on seventy cards, forcing a
 * layout read on exactly the elements built to avoid one.
 *
 * `:has()` scopes it to cards that carry a warning, about a quarter of them; the rest keep the
 * full height. Where `:has()` is unsupported the floor stays 0 and the behaviour is the old one.
 */
.wsc-card:has(.wsc-status) .wsc-drift {
  /* The warning is 2.54x its own font size from the bottom of the card, measured. Rounded up to
     2.8 so the tag clears it with a visible gap rather than grazing it. */
  --wsc-drift-floor: calc(var(--wsc-fs-small) * 2.8);
}

.wsc-drift__x {
  position: absolute;
  top: 0;
  left: 0;
  width: max-content;
  /* The duration is written per card by cards.js from the `tag_drift_seconds` setting. The
     fallback is what a JS-less embed of this stylesheet would get, not a second knob. */
  animation: wsc-drift-x var(--wsc-tag-drift-x, 29s) linear infinite alternate;
  animation-delay: var(--wsc-drift-delay-x, 0s);
}

.wsc-drift__y {
  display: block;
  animation: wsc-drift-y var(--wsc-tag-drift-y, 19s) linear infinite alternate;
  animation-delay: var(--wsc-drift-delay-y, 0s);
}

/* 100cqw is the card's width; the 100% in a translate resolves against the tag's own box. The
   difference is exactly how far it can travel before its far edge touches the card's. */
@keyframes wsc-drift-x { to { translate: calc(100cqw - 100%) 0; } }
@keyframes wsc-drift-y { to { translate: 0 calc(100cqh - 100%); } }

/*
 * The ring is not decoration. These tags cross the whole card, including the red action
 * buttons — a red "3 PROs playing" on a red button is invisible without an edge, and the whole
 * point of the tag is that it catches the eye.
 */
.wsc-tag {
  padding: .28em .8em;
  border: 1px solid var(--wsc-tag-ring);
  border-radius: 999px;
  box-shadow: var(--wsc-tag-shadow);
  font-size: var(--wsc-fs-small);
  font-weight: 700;
  white-space: nowrap;
}

.wsc-tag--legend { background: var(--wsc-rank-legend); color: var(--wsc-tag-presence-fg); }
.wsc-tag--pro    { background: var(--wsc-rank-pro);    color: var(--wsc-tag-presence-fg-pro); }

/*
 * A permanently moving element is an accessibility problem, not a taste one. Pinned, not
 * hidden — the information stays, only the motion goes.
 */
@media (prefers-reduced-motion: reduce) {
  .wsc-drift__x,
  .wsc-drift__y { animation: none; }

  /* Parked at the TOP, not the bottom. Bottom put them permanently over the plot — which is
     exactly where a quiet server's line sits — so the one reader who cannot have the tags move
     out of the way was the one reader they never moved off. */
  .wsc-drift {
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    gap: 5px;
    padding: 14px;
    container-type: normal;
  }
  .wsc-drift__x { position: static; }
}

/* ── Page furniture ───────────────────────────────────────────────────────────────────── */

.wsc-banner {
  margin-bottom: 14px;
  padding: 9px 12px;
  border-radius: var(--wsc-radius);
  background: var(--wsc-tag-fulllegend-bg);
  color: var(--wsc-tag-fulllegend-fg);
  font: 13px/1.4 "Segoe UI", system-ui, sans-serif;
}

.wsc-banner[hidden] { display: none; }

/* Nothing published.
 *
 * Deliberately NOT styled like .wsc-banner: the banner is the outage voice, and wearing it for a
 * list an operator emptied on purpose is the exact confusion this replaced. Quiet, centred, one
 * line — a page with nothing on it rather than a page that broke.
 *
 * grid-column:1/-1 because it is a child of .wsc-grid and would otherwise sit in the first column
 * with the rest of the row empty beside it. */
.wsc-empty {
  grid-column: 1 / -1;
  margin: 0;
  padding: 40px 12px;
  text-align: center;
  color: var(--wsc-muted);
  font: 14px/1.5 "Segoe UI", system-ui, sans-serif;
}

/*
 * The map row, with an optional thumbnail.
 *
 * Optional by construction — cards.js removes the <img> when the file is missing, so the row
 * degrades to exactly the text it was before. Drop a PNG into widget/img/maps/ named after the
 * map id (de_mirage.png) and it appears; no code change, no list to maintain.
 *
 * Sized to the line rather than to pixels so it tracks the card's density. `object-fit: contain`
 * and no outline: the badges are pentagons on a transparent background, and a box-shadow on an
 * <img> draws around the element's box, not around what is painted in it. */
.wsc-band__value--map {
  display: flex;
  align-items: center;
  gap: .5em;
}
.wsc-map__thumb {
  flex: 0 0 auto;
  width: 1.6em;
  height: 1.6em;
  object-fit: contain;
}
