/* ==========================================================================
   PLAYER — CITY PROMO FEED

   The one screen in the cabinet that is openly advertising, so it is allowed
   to shout: a billboard at the top, a ticker under it, and posts written by
   arena owners rendered as posts — cover, author, headline, their own words.

   Three things do the selling. The price tag, stamped on the cover with the
   number the post is actually about. The counters under the headline, which
   are the same claim with evidence attached. And the burn bar along the
   bottom edge of every cover, saying how much of the run is already gone.

   It stays inside the page's language all the same. Every colour is a
   --reset-* token, every card wears the 2px outline and the hard offset
   shadow, and the only thing this file invents is --promo-accent: the hue one
   card is tinted with, rotated per position so a feed of four reads as four
   different ads rather than one wall of coral. The price tag is the one
   exception — its colour comes from the kind of offer, not the position,
   because a discount and a freebie are not the same news.
   ========================================================================== */

.pl-promos-view {
  --promo-accent: var(--reset-coral);
  /* Covers are photographs of dark arenas. A dark plate under them keeps the
     letterboxing invisible in the light theme too. */
  --promo-plate: #0f131a;
}

/* ===== Billboard =========================================================
   The masthead of the section: what this feed is, and the city control that
   decides everything below it, side by side so the choice is never more than
   a glance away from the offers it produced. */

.pl-promo-board {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(20rem, .68fr);
  gap: clamp(1rem, 2.6vw, 2.4rem);
  align-items: center;
  overflow: hidden;
  margin-bottom: 1.1rem;
  padding: clamp(1.2rem, 3vw, 2.2rem);
  border: var(--pl-border);
  border-radius: var(--pl-radius);
  background:
    radial-gradient(circle at 8% 120%, color-mix(in srgb, var(--reset-coral) 26%, transparent), transparent 46%),
    radial-gradient(circle at 92% -25%, color-mix(in srgb, var(--reset-sun) 26%, transparent), transparent 46%),
    var(--reset-panel-raised);
  box-shadow: var(--pl-lift-hi);
}

/* The screen-print hatch a real poster is cheap enough to have. It is drawn in
   the page's own line colour, so it survives the light theme, and it fades out
   before the copy starts so nothing is ever read through it. */
.pl-promo-board::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: repeating-linear-gradient(135deg,
    var(--reset-line) 0 1px,
    transparent 1px 9px);
  -webkit-mask-image: linear-gradient(160deg, #000, transparent 62%);
  mask-image: linear-gradient(160deg, #000, transparent 62%);
  pointer-events: none;
}

/* Two soft lights drifting behind the copy. They are the only thing on the
   screen that moves on its own, which is what keeps a static billboard from
   reading as a screenshot of one. */
.pl-promo-board__blob {
  position: absolute;
  z-index: 0;
  border-radius: 50%;
  filter: blur(38px);
  opacity: .55;
  pointer-events: none;
}

.pl-promo-board__blob--a {
  top: -6rem;
  left: 32%;
  width: 16rem;
  height: 16rem;
  background: color-mix(in srgb, var(--reset-sun) 60%, transparent);
  animation: pl-promo-drift 14s ease-in-out infinite;
}

.pl-promo-board__blob--b {
  right: -4rem;
  bottom: -7rem;
  width: 14rem;
  height: 14rem;
  background: color-mix(in srgb, var(--reset-coral) 55%, transparent);
  animation: pl-promo-drift 18s ease-in-out infinite reverse;
}

@keyframes pl-promo-drift {
  50% { transform: translate3d(2.5rem, 1.5rem, 0) scale(1.12); }
}

.pl-promo-board__copy,
.pl-promo-picker { position: relative; z-index: 1; }

.pl-promo-board__kicker {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  margin: 0 0 .5rem;
  color: var(--reset-ink-coral);
}

/* The live tell: a dot with a ring pushing out of it, the beat a broadcast
   badge uses. */
.pl-promo-dot {
  position: relative;
  width: .5rem;
  height: .5rem;
  border-radius: 50%;
  background: var(--reset-coral);
}

.pl-promo-dot::after {
  content: "";
  position: absolute;
  inset: -.25rem;
  border: 2px solid var(--reset-coral);
  border-radius: 50%;
  animation: pl-promo-ping 1.8s cubic-bezier(0, 0, .2, 1) infinite;
}

@keyframes pl-promo-ping {
  0% { opacity: .9; transform: scale(.6); }
  80%, 100% { opacity: 0; transform: scale(1.5); }
}

.pl-promo-board__title {
  display: flex;
  flex-wrap: wrap;
  gap: 0 .5ch;
  max-width: 16ch;
  margin: 0;
  font-size: clamp(1.7rem, 3.6vw, 3rem);
  line-height: 1;
  text-wrap: balance;
}

/* The city is the second half of the headline, so it is drawn as one:
   highlighted like a marker stripe rather than dropped into a field label. */
.pl-promo-board__title em {
  padding: 0 .3em;
  border-radius: .35em;
  background: linear-gradient(180deg, transparent 45%, color-mix(in srgb, var(--reset-sun) 85%, transparent) 45%);
  color: inherit;
  font-style: normal;
}

.pl-promo-board__lead {
  max-width: 52ch;
  margin: .8rem 0 0;
  color: var(--reset-muted);
  font-size: var(--fs-sm);
  line-height: 1.6;
}

.pl-promo-board__perks {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  margin: 1rem 0 0;
  padding: 0;
  list-style: none;
}

.pl-promo-board__perks li {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .3rem .7rem;
  border: 2px solid var(--reset-line-strong);
  border-radius: 999px;
  background: color-mix(in srgb, var(--reset-panel) 70%, transparent);
  font-size: var(--fs-2xs);
  font-weight: 800;
}

.pl-promo-board__perks i { color: var(--reset-ink-coral); }

/* ===== City picker =======================================================
   Deliberately not a text field with a Save button beside it. The button
   opens a sheet, the sheet writes the choice into the profile, and the chips
   under it turn the two or three cities a player alternates between into one
   tap each. */

.pl-promo-picker {
  padding: clamp(.85rem, 2vw, 1.1rem);
  border: var(--pl-border);
  border-radius: var(--pl-radius-sm);
  background: color-mix(in srgb, var(--reset-panel) 92%, transparent);
  box-shadow: var(--pl-lift-sm);
}

.pl-promo-picker__label {
  margin: 0 0 .5rem;
  color: var(--reset-muted);
}

.pl-promo-picker__button {
  display: flex;
  align-items: center;
  gap: .7rem;
  width: 100%;
  min-height: 56px;
  padding: .55rem .8rem;
  border: var(--pl-border);
  border-radius: var(--pl-radius-sm);
  background: var(--reset-control-surface);
  color: var(--reset-tile-ink);
  text-align: left;
  font: inherit;
  box-shadow: var(--pl-lift-sm);
  cursor: pointer;
  transition: transform .15s ease, box-shadow .15s ease;
}

.pl-promo-picker__button:hover {
  box-shadow: var(--pl-lift);
  transform: translate(-1px, -1px);
}

.pl-promo-picker__button:active {
  box-shadow: 1px 1px 0 var(--reset-shadow);
  transform: translate(2px, 2px);
}

.pl-promo-picker__button:focus-visible {
  outline: 3px solid var(--reset-sun);
  outline-offset: 2px;
}

.pl-promo-picker__pin {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 2.3rem;
  height: 2.3rem;
  border: 2px solid var(--reset-cream);
  border-radius: 50%;
  background: var(--reset-coral);
  color: var(--reset-tile-ink);
}

.pl-promo-picker__text {
  display: grid;
  min-width: 0;
  gap: .1rem;
}

.pl-promo-picker__text b {
  overflow: hidden;
  font-size: var(--fs-md);
  line-height: 1.15;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pl-promo-picker__text small {
  color: color-mix(in srgb, var(--reset-tile-ink) 62%, transparent);
  font-size: var(--fs-2xs);
  font-weight: 700;
}

.pl-promo-picker__caret {
  margin-left: auto;
  color: color-mix(in srgb, var(--reset-tile-ink) 55%, transparent);
}

.pl-promo-picker__quick {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem;
  margin-top: .6rem;
}

.pl-promo-picker__quick:empty { display: none; }

.pl-promo-quick {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .28rem .65rem;
  border: 2px solid var(--reset-line-strong);
  border-radius: 999px;
  background: var(--reset-panel-raised);
  color: var(--reset-muted);
  font: inherit;
  font-size: var(--fs-2xs);
  font-weight: 800;
  cursor: pointer;
  transition: transform .14s ease, box-shadow .14s ease, color .14s ease, border-color .14s ease;
}

.pl-promo-quick:hover:not(:disabled) {
  border-color: var(--reset-cream);
  color: var(--reset-cream);
  box-shadow: var(--pl-lift-sm);
  transform: translate(-1px, -1px);
}

.pl-promo-quick i { color: var(--reset-ink-coral); font-size: .7em; }
.pl-promo-quick:disabled { cursor: progress; opacity: .6; }

.pl-promo-picker__note {
  min-height: 1.2em;
  margin: .5rem 0 0;
  color: var(--reset-muted);
  font-size: var(--fs-2xs);
  font-weight: 700;
  line-height: 1.4;
}

.pl-promo-picker__note.is-ok { color: var(--reset-ink-ok); }
.pl-promo-picker__note.is-bad { color: var(--reset-ink-danger); }

/* ===== Ticker ============================================================
   A strip of moving type. It carries nothing a player needs — the city and
   the count are already stated twice above — and that is the point: it is the
   piece of furniture that says "advertising" before a word is read. */

.pl-promo-strip {
  display: flex;
  overflow: hidden;
  margin-bottom: 1.6rem;
  border: var(--pl-border);
  border-radius: 999px;
  background: var(--reset-sun);
  box-shadow: var(--pl-lift-sm);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 4%, #000 96%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 4%, #000 96%, transparent);
}

.pl-promo-strip:empty { display: none; }

.pl-promo-strip__run {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 1.1rem;
  padding: .42rem 1.1rem .42rem 0;
  color: var(--reset-tile-ink);
  font-family: "JetBrains Mono", monospace;
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  white-space: nowrap;
  animation: pl-promo-marquee 26s linear infinite;
}

.pl-promo-strip__run i { font-style: normal; opacity: .5; }

@keyframes pl-promo-marquee { to { transform: translateX(-100%); } }

/* ===== Feed ============================================================== */

.pl-promo-feed-head { align-items: center; }

.pl-promo-feed {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem;
}

.pl-promo-card {
  position: relative;
  display: grid;
  grid-template-rows: auto 1fr;
  min-width: 0;
  overflow: hidden;
  border: var(--pl-border);
  border-radius: var(--pl-radius);
  background: var(--reset-panel-raised);
  box-shadow: var(--pl-lift);
  cursor: pointer;
  transition: transform .18s ease, box-shadow .18s ease;
}

.pl-promo-card:hover {
  box-shadow: var(--pl-lift-hi), 0 0 26px color-mix(in srgb, var(--promo-accent) 30%, transparent);
  transform: translate(-2px, -2px);
}

.pl-promo-card:focus-within {
  outline: 3px solid var(--reset-sun);
  outline-offset: 2px;
}

/* The first offer in the city gets the space a poster needs. */
.pl-promo-card.is-featured {
  grid-column: 1 / -1;
  grid-template-columns: minmax(0, 1.25fr) minmax(21rem, 1fr);
  grid-template-rows: minmax(20rem, 34vw);
  max-height: 32rem;
}

.pl-promo-card__media {
  position: relative;
  min-height: 13.5rem;
  overflow: hidden;
  border-bottom: var(--pl-border);
  background: var(--promo-plate);
}

.pl-promo-card.is-featured .pl-promo-card__media {
  min-height: 0;
  border-right: var(--pl-border);
  border-bottom: 0;
}

.pl-promo-card__media img,
.pl-promo-card__fallback {
  display: block;
  width: 100%;
  height: 100%;
  min-height: inherit;
}

.pl-promo-card__media img {
  object-fit: cover;
  transition: transform .45s cubic-bezier(.2, .8, .2, 1);
}

.pl-promo-card:hover .pl-promo-card__media img { transform: scale(1.04); }

.pl-promo-card__fallback {
  display: grid;
  place-items: center;
  background:
    radial-gradient(circle, color-mix(in srgb, var(--promo-accent) 40%, transparent), transparent 62%),
    var(--promo-plate);
  color: var(--promo-accent);
  font-size: 3rem;
}

/* Ink laid over a photograph nobody vetted: the scrim is what guarantees the
   stickers and the flag clear contrast on a white cover as well as a black
   one. */
.pl-promo-card__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(7, 9, 13, .5) 0%, rgba(7, 9, 13, 0) 32%),
    linear-gradient(0deg, rgba(7, 9, 13, .78) 0%, rgba(7, 9, 13, 0) 45%);
  pointer-events: none;
}

/* The shine, borrowed from the loot grid: one skewed highlight swept across
   the cover on hover. */
.pl-promo-card__glare {
  position: absolute;
  inset: -40% -60%;
  z-index: 1;
  background: linear-gradient(105deg, transparent 42%, rgba(255, 255, 255, .38) 50%, transparent 58%);
  transform: translateX(-120%);
  transition: transform .6s ease;
  pointer-events: none;
}

.pl-promo-card:hover .pl-promo-card__glare { transform: translateX(120%); }

.pl-promo-card__stickers {
  position: absolute;
  z-index: 2;
  top: .75rem;
  right: .75rem;
  left: .75rem;
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  align-items: flex-start;
  justify-content: space-between;
}

.pl-promo-sticker {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .3rem .62rem;
  border: 2px solid rgba(255, 255, 255, .8);
  border-radius: 999px;
  background: rgba(8, 10, 15, .8);
  color: #fff;
  font-size: .7rem;
  font-weight: 800;
  backdrop-filter: blur(8px);
}

.pl-promo-sticker--time { color: var(--reset-ok); }

/* Under a day the countdown stops being a fact and starts being pressure —
   the fill flips to the alarm colour and breathes. */
.pl-promo-sticker--time.is-urgent {
  border-color: #fff;
  background: var(--reset-danger);
  color: var(--reset-tile-ink);
  animation: pl-promo-pulse 1.7s ease-in-out infinite;
}

@keyframes pl-promo-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(240, 106, 106, .55); }
  70% { box-shadow: 0 0 0 .5rem rgba(240, 106, 106, 0); }
}

/* The poster's slapped-on corner sticker, tilted like the count badge on a
   loot tile so the two read as the same hand. It shares the cover's bottom row
   with the price tag, and gives it the left-hand side. */
.pl-promo-flag {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .28rem .7rem;
  border: 2px solid var(--reset-cream);
  border-radius: 999px;
  background: var(--promo-accent);
  color: var(--reset-tile-ink);
  font-size: var(--fs-2xs);
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  box-shadow: var(--pl-lift-sm);
  transform: rotate(-3deg);
}

.pl-promo-card__body {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 1rem;
}

.pl-promo-card.is-featured .pl-promo-card__body {
  justify-content: center;
  padding: clamp(1.1rem, 2.6vw, 1.9rem);
}

/* ===== The post ==========================================================
   A person wrote this. The author line, the headline and the body are set the
   way a post is set — not as the fields of a record. */

.pl-promo-author {
  display: flex;
  align-items: center;
  gap: .55rem;
  min-width: 0;
}

.pl-promo-author__mark {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 2.1rem;
  height: 2.1rem;
  border: 2px solid var(--reset-cream);
  border-radius: 50%;
  background: var(--promo-accent);
  color: var(--reset-tile-ink);
  font-family: var(--heading-font-family);
  font-size: var(--fs-sm);
  font-weight: 900;
}

.pl-promo-author__text {
  display: grid;
  min-width: 0;
  gap: .05rem;
}

.pl-promo-author__text b {
  overflow: hidden;
  font-size: var(--fs-sm);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pl-promo-author__text small {
  display: flex;
  align-items: center;
  gap: .3rem;
  overflow: hidden;
  color: var(--reset-muted);
  font-size: var(--fs-2xs);
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pl-promo-author__text small i { color: var(--reset-ink-coral); }

.pl-promo-author__tag {
  flex: 0 0 auto;
  margin-left: auto;
  padding: .2rem .55rem;
  border: 2px solid var(--reset-line-strong);
  border-radius: 999px;
  color: var(--reset-muted);
  font-family: "JetBrains Mono", monospace;
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.pl-promo-card h3 {
  margin: .75rem 0 .5rem;
  font-size: clamp(1.15rem, 2vw, 1.5rem);
  line-height: 1.12;
  text-wrap: balance;
}

.pl-promo-card.is-featured h3 { font-size: clamp(1.5rem, 3vw, 2.2rem); }

.pl-promo-post {
  min-width: 0;
  color: var(--reset-muted);
  font-size: var(--fs-sm);
  line-height: 1.6;
}

.pl-promo-post > :first-child { margin-top: 0; }
.pl-promo-post > :last-child { margin-bottom: 0; }
.pl-promo-post p { margin: 0 0 .6rem; }

.pl-promo-post .is-lead {
  color: var(--reset-cream);
  font-size: var(--fs-base);
  font-weight: 600;
}

.pl-promo-post ul,
.pl-promo-post ol {
  display: grid;
  gap: .3rem;
  margin: 0 0 .7rem;
  padding: 0;
  list-style: none;
  counter-reset: pl-promo-line;
}

.pl-promo-post li {
  position: relative;
  padding-left: 1.5rem;
}

/* The owner typed a dash; it is drawn as the accent tick the rest of the card
   is tinted with. */
.pl-promo-post ul li::before {
  content: "";
  position: absolute;
  top: .58em;
  left: .3rem;
  width: .45rem;
  height: .45rem;
  border: 2px solid var(--reset-cream);
  border-radius: 50%;
  background: var(--promo-accent);
}

.pl-promo-post ol li::before {
  counter-increment: pl-promo-line;
  content: counter(pl-promo-line);
  position: absolute;
  top: .1em;
  left: 0;
  display: grid;
  place-items: center;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
  background: color-mix(in srgb, var(--promo-accent) 34%, transparent);
  color: var(--reset-cream);
  font-family: "JetBrains Mono", monospace;
  font-size: .68em;
  font-weight: 700;
}

.pl-promo-post b { color: var(--reset-cream); }

/* Any number the post puts a % or a ₸ next to is the offer itself. Marking it
   is the one editorial liberty taken with somebody else's copy, and it is why
   the deal is legible from across the room. */
.pl-promo-post mark {
  padding: 0 .25em;
  border-radius: .3em;
  background: color-mix(in srgb, var(--reset-sun) 82%, transparent);
  color: var(--reset-tile-ink);
  font-weight: 800;
}

/* In the card the post is a preview: clamped, and faded out rather than cut
   mid-letter, so it is obvious there is more of it behind the button. */
.pl-promo-post--clamp {
  position: relative;
  max-height: 7.6rem;
  overflow: hidden;
}

.pl-promo-card.is-featured .pl-promo-post--clamp { max-height: 10.5rem; }

.pl-promo-post--clamp::after {
  content: "";
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  height: 2.6rem;
  background: linear-gradient(180deg, transparent, var(--reset-panel-raised));
  pointer-events: none;
}

.pl-promo-card__more {
  align-self: flex-start;
  margin-top: .4rem;
  padding: .2rem 0;
  border: 0;
  background: none;
  color: var(--reset-ink-coral);
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: 800;
  cursor: pointer;
}

.pl-promo-card__more i {
  margin-left: .35rem;
  transition: transform .16s ease;
}

.pl-promo-card__more:hover { text-decoration: underline; }
.pl-promo-card__more:hover i { transform: translateX(3px); }

.pl-promo-card__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .6rem .7rem;
  margin-top: auto;
  padding-top: 1rem;
}

/* A run of dates is one object. Broken across three lines to make room for the
   button beside it, it stops being a date at all - so it never wraps, and the
   button drops under it instead when the card is narrow. */
.pl-promo-card__date {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  color: var(--reset-muted);
  font-family: "JetBrains Mono", monospace;
  font-size: var(--fs-2xs);
  font-weight: 700;
  white-space: nowrap;
}

.pl-promo-card__foot .pl-btn {
  flex: 0 0 auto;
  margin-left: auto;
}
.pl-promo-card__cta { background: var(--promo-accent); }

/* ===== Empty and loading ================================================= */

.pl-promo-empty {
  grid-column: 1 / -1;
  display: grid;
  justify-items: center;
  align-content: center;
  min-height: 18rem;
  padding: 2rem;
  border: 2px dashed var(--reset-line-strong);
  border-radius: var(--pl-radius);
  background: color-mix(in srgb, var(--reset-panel) 78%, transparent);
  text-align: center;
}

.pl-promo-empty__icon {
  display: grid;
  place-items: center;
  width: 4rem;
  height: 4rem;
  border: var(--pl-border);
  border-radius: 50%;
  background: var(--reset-sun);
  color: var(--reset-tile-ink);
  box-shadow: var(--pl-lift-sm);
  font-size: 1.35rem;
}

.pl-promo-empty h3 { margin: .9rem 0 .35rem; font-size: var(--fs-lg); }
.pl-promo-empty p { max-width: 52ch; margin: 0; color: var(--reset-muted); line-height: 1.55; }
.pl-promo-empty .pl-btn { margin-top: 1rem; }

.pl-promo-card--skeleton { cursor: default; pointer-events: none; }
.pl-promo-card--skeleton:hover { box-shadow: var(--pl-lift); transform: none; }

.pl-promo-skeleton {
  display: block;
  border-radius: .5rem;
  background: linear-gradient(90deg,
    var(--reset-panel) 20%,
    var(--reset-panel-raised) 45%,
    var(--reset-panel) 70%);
  background-size: 220% 100%;
  animation: pl-promo-shimmer 1.25s linear infinite;
}

.pl-promo-skeleton--image { min-height: 13.5rem; border-radius: 0; }
.pl-promo-card.is-featured .pl-promo-skeleton--image { height: 100%; }
.pl-promo-skeleton--short { width: 38%; height: .75rem; }
.pl-promo-skeleton--title { width: 74%; height: 1.75rem; margin: .8rem 0; }
.pl-promo-skeleton--line { width: 100%; height: .7rem; margin-top: .45rem; }
.pl-promo-skeleton--line:last-child { width: 68%; }

@keyframes pl-promo-shimmer { to { background-position: -220% 0; } }

/* ===== City sheet ========================================================
   Server-ranked results from the global catalogue plus cities with live
   offers. Only the visible result window is rendered in the sheet. */

.pl-promo-city-dialog { width: min(30rem, 100%); }

.pl-promo-city-dialog h2 { margin: .2rem 0 .3rem; font-size: var(--fs-xl); }

.pl-promo-city-dialog__lead {
  margin: 0 0 1rem;
  padding-right: 2rem;
  color: var(--reset-muted);
  font-size: var(--fs-sm);
  line-height: 1.5;
}

.pl-promo-city-search {
  position: relative;
  display: flex;
  align-items: center;
}

.pl-promo-city-search i {
  position: absolute;
  left: .85rem;
  color: var(--reset-muted);
  pointer-events: none;
}

.pl-promo-city-search input {
  width: 100%;
  min-height: 48px;
  padding: .6rem .9rem .6rem 2.4rem;
  border: var(--pl-border);
  border-radius: 999px;
  outline: 0;
  background: var(--reset-control-surface);
  color: var(--reset-tile-ink);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 700;
}

.pl-promo-city-search input:focus-visible {
  border-color: var(--reset-sun);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--reset-sun) 34%, transparent);
}

.pl-promo-city-list {
  display: grid;
  gap: .4rem;
  max-height: min(22rem, 46vh);
  margin-top: .8rem;
  overflow-y: auto;
  padding-right: .2rem;
}

.pl-promo-city-option {
  display: flex;
  align-items: center;
  gap: .7rem;
  width: 100%;
  min-height: 52px;
  padding: .5rem .7rem;
  border: 2px solid var(--reset-line-strong);
  border-radius: var(--pl-radius-sm);
  background: var(--reset-panel-raised);
  color: var(--reset-cream);
  text-align: left;
  font: inherit;
  cursor: pointer;
  transition: transform .14s ease, box-shadow .14s ease, border-color .14s ease;
}

.pl-promo-city-option:hover:not(:disabled) {
  border-color: var(--reset-cream);
  box-shadow: var(--pl-lift-sm);
  transform: translate(-1px, -1px);
}

.pl-promo-city-option:focus-visible {
  outline: 3px solid var(--reset-sun);
  outline-offset: 2px;
}

.pl-promo-city-option__pin {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 2rem;
  height: 2rem;
  border: 2px solid var(--reset-cream);
  border-radius: 50%;
  background: color-mix(in srgb, var(--reset-sun) 70%, transparent);
  color: var(--reset-tile-ink);
  font-size: var(--fs-xs);
}

.pl-promo-city-option__text {
  display: grid;
  min-width: 0;
  gap: .1rem;
}

.pl-promo-city-option__text b {
  overflow: hidden;
  font-size: var(--fs-sm);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pl-promo-city-option__text small {
  color: var(--reset-muted);
  font-size: var(--fs-2xs);
  font-weight: 700;
}

.pl-promo-city-option__mark {
  margin-left: auto;
  color: var(--reset-ink-ok);
}

.pl-promo-city-option.is-current {
  border-color: var(--reset-ok);
  background: color-mix(in srgb, var(--reset-ok) 14%, var(--reset-panel-raised));
}

.pl-promo-city-option.is-current .pl-promo-city-option__pin { background: var(--reset-ok); }
.pl-promo-city-option:disabled { cursor: progress; opacity: .65; }

.pl-promo-city-empty {
  padding: 1.4rem .6rem;
  color: var(--reset-muted);
  text-align: center;
  font-size: var(--fs-sm);
}

.pl-promo-city-empty i { margin-right: .35rem; color: var(--reset-sun); }

.pl-promo-city-dialog__note {
  min-height: 1.2em;
  margin: .7rem 0 0;
  color: var(--reset-ink-danger);
  font-size: var(--fs-xs);
  font-weight: 700;
}

.pl-promo-city-source {
  margin: .45rem 0 0;
  color: var(--reset-muted);
  font-size: var(--fs-2xs);
  text-align: right;
}

.pl-promo-city-source a { color: inherit; text-underline-offset: .15em; }

/* ===== Reader ============================================================
   The post at full size. The cover runs edge to edge, the text keeps the
   shape its author gave it, and the two things a player does next — get
   there, or open whatever the arena linked — sit under it as buttons. */

.pl-promo-reader__card {
  --promo-accent: var(--reset-coral);
  width: min(46rem, 100%);
  padding: 0;
}

.pl-promo-reader__cover {
  position: relative;
  height: clamp(11rem, 28vw, 17rem);
  overflow: hidden;
  border-bottom: var(--pl-border);
  background: var(--promo-plate);
}

.pl-promo-reader__cover img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.pl-promo-reader__cover::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(0deg, rgba(7, 9, 13, .8), rgba(7, 9, 13, 0) 55%);
  pointer-events: none;
}

.pl-promo-reader__fallback {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  background:
    radial-gradient(circle, color-mix(in srgb, var(--promo-accent) 42%, transparent), transparent 62%),
    var(--promo-plate);
  color: var(--promo-accent);
  font-size: 3.4rem;
}

/* Cover furniture shares one row rather than two corners, so a narrow phone
   wraps it instead of stacking the price tag on top of the countdown. */
.pl-promo-reader__overlay {
  position: absolute;
  z-index: 1;
  right: 1rem;
  bottom: 1rem;
  left: 1rem;
  display: flex;
  flex-wrap: wrap-reverse;
  align-items: flex-end;
  justify-content: space-between;
  gap: .6rem;
}

.pl-promo-reader__stickers {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
}

.pl-promo-reader__inner { padding: clamp(1rem, 3vw, 1.6rem); }

.pl-promo-reader__inner h2 {
  margin: .8rem 0 .2rem;
  font-size: clamp(1.4rem, 3.4vw, 2.1rem);
  line-height: 1.1;
  text-wrap: balance;
}

.pl-promo-reader__inner .pl-promo-post { margin-top: .9rem; }

.pl-promo-reader__facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: .5rem;
  margin: 1.1rem 0 0;
}

.pl-promo-fact {
  display: flex;
  align-items: center;
  gap: .55rem;
  padding: .6rem .7rem;
  border: 2px solid var(--reset-line-strong);
  border-radius: var(--pl-radius-sm);
  background: color-mix(in srgb, var(--reset-panel) 70%, transparent);
}

.pl-promo-fact > i {
  flex: 0 0 auto;
  color: var(--reset-ink-coral);
}

.pl-promo-fact span {
  display: grid;
  min-width: 0;
  gap: .05rem;
}

.pl-promo-fact small {
  color: var(--reset-muted);
  font-family: "JetBrains Mono", monospace;
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.pl-promo-fact b { font-size: var(--fs-sm); line-height: 1.25; }

.pl-promo-reader__actions {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-top: 1.1rem;
}

.pl-promo-reader__actions .pl-btn--primary { background: var(--promo-accent); }

.pl-promo-reader__status {
  min-height: 1.1em;
  margin: .6rem 0 0;
  color: var(--reset-ink-ok);
  font-size: var(--fs-2xs);
  font-weight: 700;
}

/* ===== The price tag =====================================================
   The number the post is actually about, lifted out of the owner's sentence
   and stamped on the cover. It is the loudest object on the screen on purpose:
   at a glance down the feed it is the only thing that has to be readable.

   The colour comes from what kind of offer it is rather than from the card's
   accent — a discount is yellow, a freebie is green — so the shape of the
   deal is legible before the words are. */

.pl-promo-card__tags {
  position: absolute;
  z-index: 2;
  right: .8rem;
  bottom: 1rem;
  left: .8rem;
  display: flex;
  flex-wrap: wrap-reverse;
  align-items: flex-end;
  gap: .5rem;
}

.pl-promo-deal {
  --deal-bg: var(--reset-sun);
  display: inline-grid;
  gap: .05rem;
  padding: .4rem .75rem .45rem;
  border: var(--pl-border);
  border-radius: 14px;
  background: var(--deal-bg);
  color: var(--reset-tile-ink);
  box-shadow: var(--pl-lift);
  transform: rotate(-3deg);
  transform-origin: bottom left;
}

.pl-promo-deal b {
  font-family: var(--heading-font-family);
  font-size: clamp(1.3rem, 2.6vw, 1.75rem);
  font-weight: 900;
  line-height: .98;
  letter-spacing: -.01em;
  white-space: nowrap;
}

.pl-promo-deal small {
  font-family: "JetBrains Mono", monospace;
  font-size: .56rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  opacity: .72;
}

.pl-promo-deal--bogo { --deal-bg: var(--reset-coral); }
.pl-promo-deal--gift { --deal-bg: var(--reset-ok); }
.pl-promo-deal--price { --deal-bg: var(--reset-sky); }

.pl-promo-card.is-featured .pl-promo-deal {
  padding: .55rem 1rem .6rem;
  border-radius: 16px;
}

.pl-promo-card.is-featured .pl-promo-deal b { font-size: clamp(1.7rem, 3.6vw, 2.6rem); }
.pl-promo-card.is-featured .pl-promo-deal small { font-size: .62rem; }

/* ===== Burn bar ==========================================================
   How much of the run is already gone, drawn along the bottom edge of the
   cover the way a story bar is. No number, no colour — until the last fifth,
   when the fill turns and the card starts arguing for tonight. */

.pl-promo-burn {
  position: absolute;
  z-index: 2;
  right: 0;
  bottom: 0;
  left: 0;
  height: 5px;
  background: rgba(8, 10, 15, .55);
}

.pl-promo-burn::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--burn, 0%);
  background: var(--promo-accent);
  transition: width .4s linear;
}

.pl-promo-burn.is-late::after { background: var(--reset-danger); }

/* ===== Billboard counters ================================================
   Three facts counted off the feed. They sit under the slogan because a claim
   with a number under it is an offer, and one without is a slogan. */

.pl-promo-board__stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  gap: .45rem;
  max-width: 34rem;
  margin-top: 1.05rem;
}

.pl-promo-board__stats[hidden] { display: none; }

.pl-promo-stat {
  display: grid;
  gap: .1rem;
  padding: .5rem .85rem;
  border: 2px solid var(--reset-line-strong);
  border-radius: 14px;
  background: color-mix(in srgb, var(--reset-panel) 74%, transparent);
}

.pl-promo-stat b {
  font-family: var(--heading-font-family);
  font-size: clamp(1.2rem, 2.4vw, 1.65rem);
  font-weight: 900;
  line-height: 1;
}

.pl-promo-stat:first-child b { color: var(--reset-ink-coral); }

.pl-promo-stat small {
  color: var(--reset-muted);
  font-size: var(--fs-2xs);
  font-weight: 800;
  letter-spacing: .06em;
  line-height: 1.25;
  text-transform: uppercase;
  text-wrap: balance;
}

/* Promise and proof are the same three claims, so only one of them is on
   screen at a time: the perks hold the billboard up until the feed can put
   real numbers there. */
.pl-promo-board__perks[hidden] { display: none; }

/* ===== Shelf =============================================================
   Chips over the feed. They only appear when the answers differ, and each one
   carries its own count so a player can see the size of the cut before
   pressing it. */

.pl-promo-filters {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  margin: 0 0 1rem;
}

.pl-promo-filters[hidden] { display: none; }

.pl-promo-chip {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .48rem .95rem;
  border: 2px solid var(--reset-line-strong);
  border-radius: 999px;
  background: var(--reset-panel-raised);
  color: var(--reset-muted);
  font: inherit;
  font-family: var(--heading-font-family);
  font-size: var(--fs-xs);
  font-weight: 800;
  text-transform: uppercase;
  cursor: pointer;
  transition: transform .14s ease, box-shadow .14s ease, background .14s ease, color .14s ease, border-color .14s ease;
}

.pl-promo-chip i { font-size: .82em; color: var(--reset-ink-coral); }

.pl-promo-chip b {
  min-width: 1.15rem;
  padding: 0 .3rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--reset-cream) 14%, transparent);
  font-family: "JetBrains Mono", monospace;
  font-size: .92em;
  text-align: center;
}

.pl-promo-chip:hover {
  border-color: var(--reset-cream);
  color: var(--reset-cream);
  box-shadow: var(--pl-lift-sm);
  transform: translate(-1px, -1px);
}

.pl-promo-chip:focus-visible {
  outline: 3px solid var(--reset-sun);
  outline-offset: 2px;
}

/* The chosen shelf wears the same ember fill the rail's own active tab does —
   one active state for the whole cabinet, and a fill whose ink is legible in
   both themes. */
.pl-promo-chip.is-on {
  border-color: transparent;
  background: var(--pl-ember-ramp);
  color: var(--pl-ember-ink);
  box-shadow: var(--pl-lift-sm), var(--pl-glow-soft);
}

.pl-promo-chip.is-on i { color: var(--pl-ember-ink); }
.pl-promo-chip.is-on b { background: color-mix(in srgb, var(--pl-ember-ink) 16%, transparent); }

/* ===== Countdowns ========================================================
   Under a day the badge stops rounding and starts running. Tabular figures
   keep it from twitching on every tick. */

.pl-promo-sticker--time b { font-weight: 800; }

.pl-promo-sticker--time.is-clock b {
  font-family: "JetBrains Mono", monospace;
  font-size: .95em;
  letter-spacing: .04em;
  font-variant-numeric: tabular-nums;
}

.pl-promo-fact__left {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-size: var(--fs-sm);
  font-weight: 800;
  line-height: 1.25;
}

.pl-promo-fact__left i { color: var(--reset-ink-coral); }
.pl-promo-fact__left.is-urgent { color: var(--reset-ink-danger); }
.pl-promo-fact__left.is-urgent i { color: var(--reset-ink-danger); }

.pl-promo-fact__left.is-clock b {
  font-family: "JetBrains Mono", monospace;
  letter-spacing: .04em;
  font-variant-numeric: tabular-nums;
}

/* ===== Nothing here, but something next door =============================
   A city with no offers is a dead end unless the feed says where the offers
   are. The chips under the empty state are the same one-tap switch the picker
   uses, so the way out is the way in. */

.pl-promo-empty__cities {
  width: min(34rem, 100%);
  margin-top: 1.4rem;
  padding-top: 1.1rem;
  border-top: 2px dashed var(--reset-line-strong);
}

.pl-promo-empty__cities p {
  margin: 0 0 .6rem;
  color: var(--reset-muted);
  font-size: var(--fs-2xs);
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.pl-promo-empty__cities > div {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  justify-content: center;
}

.pl-promo-quick b {
  padding: 0 .35rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--reset-sun) 80%, transparent);
  color: var(--reset-tile-ink);
  font-family: "JetBrains Mono", monospace;
  font-size: .92em;
}

/* ===== Reader ============================================================ */

.pl-promo-reader__tags {
  display: flex;
  margin-left: auto;
}

.pl-promo-reader__tags:empty { display: none; }

/* ===== Arrival ===========================================================
   The feed is a page of advertising, and advertising arrives. Six cards is
   more than any city has shown so far, and the last one is still on screen a
   quarter of a second after the first. */

@media (prefers-reduced-motion: no-preference) {
  .pl-promo-feed > .pl-promo-card {
    animation: pl-promo-rise .45s cubic-bezier(.2, .8, .2, 1) backwards;
  }

  .pl-promo-feed > .pl-promo-card:nth-child(2) { animation-delay: .05s; }
  .pl-promo-feed > .pl-promo-card:nth-child(3) { animation-delay: .1s; }
  .pl-promo-feed > .pl-promo-card:nth-child(4) { animation-delay: .15s; }
  .pl-promo-feed > .pl-promo-card:nth-child(5) { animation-delay: .2s; }
  .pl-promo-feed > .pl-promo-card:nth-child(n + 6) { animation-delay: .25s; }
}

@keyframes pl-promo-rise {
  from { opacity: 0; transform: translateY(14px); }
}

/* ===== Responsive ======================================================== */

@media (max-width: 1080px) {
  .pl-promo-board { grid-template-columns: minmax(0, 1fr); }
  .pl-promo-board__title { max-width: 22ch; }

  /* The rail is still on screen down here, so a side-by-side poster leaves the
     post about 300px wide - narrow enough to clip the arena's own name. The
     cover goes back on top, the words get the whole card, and the poster keeps
     the full width of the feed. */
  .pl-promo-card.is-featured {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto 1fr;
    max-height: none;
  }

  .pl-promo-card.is-featured .pl-promo-card__media {
    min-height: 16rem;
    border-right: 0;
    border-bottom: var(--pl-border);
  }
}

@media (max-width: 780px) {
  .pl-promo-feed { grid-template-columns: minmax(0, 1fr); }
  .pl-promo-card.is-featured { grid-column: auto; }
  .pl-promo-card.is-featured .pl-promo-card__media { min-height: 15rem; }
  .pl-promo-card.is-featured .pl-promo-card__body { padding: 1rem; }
  .pl-promo-card.is-featured h3 { font-size: clamp(1.3rem, 6vw, 1.8rem); }
  .pl-promo-card.is-featured .pl-promo-post--clamp { max-height: 7.6rem; }
}

@media (max-width: 780px) {
  .pl-promo-card.is-featured .pl-promo-deal { padding: .45rem .8rem .5rem; }
  .pl-promo-card.is-featured .pl-promo-deal b { font-size: clamp(1.5rem, 7vw, 2rem); }
}

@media (max-width: 620px) {
  .pl-promo-board { margin-bottom: .9rem; padding: 1rem; }
  .pl-promo-board__title { font-size: clamp(1.5rem, 8vw, 2.1rem); }
  .pl-promo-board__perks li { font-size: .62rem; }
  .pl-promo-board__stats { gap: .35rem; }
  .pl-promo-stat { flex: 1 1 8rem; padding: .45rem .6rem; }
  .pl-promo-filters { flex-wrap: nowrap; overflow-x: auto; margin-right: -1rem; padding: .1rem 1rem .35rem 0; scrollbar-width: none; }
  .pl-promo-filters::-webkit-scrollbar { display: none; }
  .pl-promo-chip { flex: 0 0 auto; }
  .pl-promo-card__tags { right: .65rem; bottom: .85rem; left: .65rem; }
  .pl-promo-deal b { font-size: 1.35rem; }
  .pl-promo-strip { margin-bottom: 1.2rem; }
  .pl-promo-card__media,
  .pl-promo-card.is-featured .pl-promo-card__media { min-height: 12.5rem; }
  .pl-promo-card__foot { align-items: stretch; flex-direction: column; }
  .pl-promo-card__foot .pl-btn { width: 100%; }
  .pl-promo-reader__card { width: 100%; }
  .pl-promo-reader__actions .pl-btn { width: 100%; }
  .pl-promo-city-list { max-height: 50vh; }
}


/* ===== Mobile performance profile ========================================
   Matches the block at the foot of player.css: same predicate, same rule —
   only per-frame work is removed, and never the thing it was signalling.

   The two drifting lights are the most expensive layer anywhere in the
   cabinet. Each is a quarter-megapixel box under a 38px Gaussian blur, and
   the drift scales it — which means the blur cannot be rasterised once and
   moved by the compositor, it has to be re-run at a new radius on every
   frame, for as long as the screen is open. Held still they cost one raster
   and nothing after it, and a soft light that does not drift still reads as
   a soft light: this is the same trade the case theatre's orbs already
   made. */
@media (max-width: 900px), (pointer: coarse) {
  /* Drawn once, at a smaller radius. The blob is a wash behind the copy
     rather than a shape, so the softness it gives up is not visible; the
     raster it stops asking a phone GPU for is. */
  .pl-promo-board__blob {
    filter: blur(24px);
    animation: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .pl-promo-burn::after { transition: none; }
  .pl-promo-board__blob,
  .pl-promo-dot::after,
  .pl-promo-strip__run,
  .pl-promo-sticker--time.is-urgent,
  .pl-promo-skeleton { animation: none; }
  .pl-promo-card,
  .pl-promo-card__media img,
  .pl-promo-card__glare { transition: none; }
  .pl-promo-card:hover { transform: none; }
  .pl-promo-card:hover .pl-promo-card__media img { transform: none; }
  .pl-promo-card:hover .pl-promo-card__glare { transform: translateX(-120%); }
}
