/* ==========================================================================
   Opleague — cards, tables and the small repeating pieces
   --------------------------------------------------------------------------
   Most of the site is these pieces with different words in them. Measured
   from the Figma mockup, Q53m705DcCXQlGQr8ibfCl.

   Sections:
     1. Pills and badges
     2. Stat blocks
     3. Game tiles
     4. Replay cards
     5. Tables: match history, leaderboard, comparison
     6. Pricing tiers
     7. Rank badges
     8. The demand bar
     9. Reason cards and step lists
   ========================================================================== */


/* 1. Pills and badges ----------------------------------------------------- */

.opl-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 9px;
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--opl-white);
  background: var(--opl-gray);
  white-space: nowrap;
}

.opl-pill--live     { background: var(--opl-critical); }
.opl-pill--live::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--opl-white);
}
.opl-pill--soon     { background: var(--opl-gray); }
.opl-pill--vote     { background: var(--opl-slate); }
.opl-pill--accent   { background: var(--opl-accent); }
.opl-pill--win      { background: var(--opl-good); }
.opl-pill--loss     { background: var(--opl-critical); }
.opl-pill--supporter{ background: var(--opl-risk); }

/* Practice rounds. Deliberately quieter than WIN and LOSS, because the
   point of a practice round is that it does not count. */
.opl-pill--unranked { background: var(--opl-unranked); }

/* The pale version used on light cards next to a title. */
.opl-pill--quiet {
  background: var(--opl-fog);
  color: var(--opl-slate);
}


/* 2. Stat blocks ---------------------------------------------------------- */
/* The rating / record / win rate / APM row on a player banner. */

.opl-stats { display: flex; gap: var(--opl-s8); flex-wrap: wrap; }
.opl-stat  { display: flex; flex-direction: column; gap: 2px; }
.opl-stat__value {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: 26px;
  line-height: 1.1;
  color: var(--opl-white);
}
.opl-stat__label {
  font-family: var(--opl-font-head);
  font-weight: 600;
  font-size: var(--opl-size-micro);
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--opl-silver);
}
.opl-band--light .opl-stat__value,
.opl-band--paper .opl-stat__value { color: var(--opl-ink); }
.opl-band--light .opl-stat__label,
.opl-band--paper .opl-stat__label { color: var(--opl-gray); }


/* 3. Game tiles ----------------------------------------------------------- */
/* Each game keeps its own colour in the art, on purpose: Mike asked for the
   game headers to read as promotional imagery rather than uniform chrome.
   Only the art is per-game. The chrome around it stays league blue. */

.opl-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 340px;
  padding: var(--opl-s5);
  overflow: hidden;
  color: var(--opl-white);
  text-decoration: none;
  background: var(--opl-blue-700);

  /*
   * The dark-band pair, not the light one. A black shadow on a near-black
   * band does nothing; the inset rim along the top is what actually reads
   * as raised here. These ARE links, so they lift.
   */
  box-shadow: var(--opl-shadow-dark);
  transition: box-shadow 160ms ease, transform 160ms ease;
}
.opl-tile:hover,
.opl-tile:focus-visible { box-shadow: var(--opl-shadow-dark-lift); transform: translateY(-3px); }
.opl-tile__art {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: linear-gradient(180deg, var(--opl-tile-top, #0E2F44) 0%, var(--opl-tile-bottom, #081A27) 100%);
}
/*
 * The drawing. Deliberately quiet: a tile has one job, which is to say
 * which game it is, so the art sits well under the name rather than
 * competing with it. Anything louder would be a prettier version of the
 * bug fixed right below, where the badge covered the title.
 *
 * currentColor, so each tile tints its own art from the palette already in
 * this file instead of shipping four coloured copies of the same shapes.
 */
.opl-tile__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  color: var(--opl-tile-ink, #7FB7D8);

  /*
   * .36 rather than .30, decided by looking at the rendered tiles rather
   * than by taste. Three of the four read fine either way; StarCraft is
   * thin pale lines on a blue tile, the lowest contrast pairing of the
   * set, and it was the one disappearing. Raising the floor lifts that one
   * without the darker tiles becoming busy, which is better than four
   * per-game opacities to maintain.
   */
  opacity: .36;
}

/* A dark wash at the bottom so the title always has something to sit on,
   whatever the art behind it turns out to be. */
.opl-tile__art::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(3,12,20,0) 40%, rgba(3,12,20,.78) 100%);
}
/*
 * Lift everything above the art. The badge is excluded ON PURPOSE, and
 * leaving it out is the whole fix.
 *
 * THE BUG THIS CAUSED, live on the home page until 2026-09-20.
 *
 * This selector is `.opl-tile` plus `:not(.opl-tile__art)`, and a :not()
 * is scored by what is inside it, so it weighs two classes. The badge rule
 * below weighs one. The blanket rule therefore WON, the badge computed
 * position:relative instead of absolute, and its top and left stopped
 * anchoring it to the corner and started shoving it out of the flow
 * instead. In a column flex box that ends at flex-end, that dropped it on
 * top of the game name.
 *
 * All four tiles on the front page, every visitor, with the game name
 * unreadable underneath. Measured before fixing: computed position
 * relative on all four, badge and name boxes overlapping by two pixels of
 * their tops and far more of their bodies.
 *
 * Excluding it here is better than out-specifying it below, because an
 * arms race between two rules about the same property is how it broke.
 * Now exactly one rule positions the badge.
 */
.opl-tile > *:not(.opl-tile__art):not(.opl-tile__badge) { position: relative; z-index: 1; }

.opl-tile__badge { position: absolute; top: var(--opl-s5); left: var(--opl-s5); z-index: 2; }
.opl-tile__name {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-h4);
  letter-spacing: 1.5px;
  text-transform: uppercase;
}
.opl-tile__sub {
  font-family: var(--opl-font-head);
  font-weight: 500;
  font-size: var(--opl-size-eyebrow);
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--opl-silver);
  margin-top: 4px;
}

/*
 * Per-game art colors, from the mockup.
 *
 * These are the only thing separating one tile from another, because the
 * site has no game artwork and will not use the publishers'. So a game
 * with no entry here is not merely undecorated: .opl-tile__art falls
 * back to the StarCraft blue, and it renders as an exact duplicate of
 * the StarCraft tile.
 *
 * That is what the roster swap did on 2026-09-19. Chess, Helldivers and
 * Aliens went on with tile values chess, helldivers and aliens, none of
 * which had a rule, so the home page showed four identical blue tiles
 * with different words on them. Fixed here, and the three retired games
 * removed so the list matches the roster.
 *
 * KEEP THIS IN STEP WITH THE ROSTER. Adding a game without adding a
 * colour is invisible in code review and obvious on the home page.
 */
.opl-tile--starcraft  { --opl-tile-top: #0E2F44; --opl-tile-bottom: #081A27; --opl-tile-ink: #7FC4EA; }  /* blue     */
.opl-tile--chess      { --opl-tile-top: #23262B; --opl-tile-bottom: #121417; --opl-tile-ink: #C9CFD6; }  /* graphite */
.opl-tile--helldivers { --opl-tile-top: #33270D; --opl-tile-bottom: #1D1607; --opl-tile-ink: #F0C25A; }  /* amber    */
.opl-tile--aliens     { --opl-tile-top: #102A1C; --opl-tile-bottom: #08170F; --opl-tile-ink: #6FD39B; }  /* green    */


/* 4. Replay cards --------------------------------------------------------- */

.opl-card {
  background: var(--opl-surface-1);
  border: var(--opl-hairline);
  padding: var(--opl-s6);
  display: flex;
  flex-direction: column;
  gap: 14px;

  /*
   * Resting elevation. A hairline alone disappears at 1px on a phone, and
   * the whole tournaments band then reads as one undifferentiated column
   * rather than as three things.
   */
  box-shadow: var(--opl-shadow-card);
}

/* Only when the whole card is a link. A lift on something you cannot press
   is the shadow telling a lie about what it is. */
a.opl-card { transition: box-shadow 140ms ease, transform 140ms ease; }
a.opl-card:hover,
a.opl-card:focus-visible { box-shadow: var(--opl-shadow-lift); transform: translateY(-2px); }
.opl-card__head { display: flex; align-items: center; justify-content: space-between; gap: var(--opl-s3); }
.opl-card__event {
  font-family: var(--opl-font-head);
  font-weight: 600;
  font-size: var(--opl-size-eyebrow);
  color: var(--opl-gray);
}
.opl-card__date {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-eyebrow);
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--opl-accent);
}
.opl-card__players { display: flex; align-items: baseline; gap: 14px; }
.opl-card__player {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-body);
  color: var(--opl-night);
}
.opl-card__player--away { color: var(--opl-gray); }
.opl-card__score {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-body);
  color: var(--opl-accent);
}
.opl-card__maps { font-size: var(--opl-size-meta); color: var(--opl-gray); }
.opl-card__rule { border: 0; height: 1px; background: var(--opl-fog); margin: 0; }
.opl-card__actions { display: flex; gap: var(--opl-s5); }
.opl-card__actions a {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-eyebrow);
  letter-spacing: 0.5px;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--opl-night);
}
.opl-card__actions a + a { color: var(--opl-gray); }

/* A practice round is still downloadable. That is the point of it. */
.opl-card--practice .opl-card__event { color: var(--opl-steel); }


/* 5. Tables --------------------------------------------------------------- */

.opl-table { width: 100%; border-collapse: collapse; background: var(--opl-white); }
.opl-table th {
  background: var(--opl-paper);
  font-family: var(--opl-font-head);
  font-weight: 600;
  font-size: var(--opl-size-eyebrow);
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--opl-gray);
  text-align: left;
  padding: 13px var(--opl-s5);
}
.opl-table td {
  padding: var(--opl-s4) var(--opl-s5);
  border-top: var(--opl-hairline);
  font-size: var(--opl-size-meta);
  color: var(--opl-gray);
  vertical-align: middle;
}
.opl-table .opl-cell--strong {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-body-sm);
  color: var(--opl-night);
}
.opl-table .opl-cell--num {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-body-sm);
  color: var(--opl-night);
  white-space: nowrap;
}
.opl-table .opl-cell--center { text-align: center; }
/* Was --opl-mist, 1.60 on white. A muted cell still has to be readable. */
.opl-table .opl-cell--muted  { color: var(--opl-gray); }

/* Wide tables scroll rather than squash. */
.opl-table-scroll { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.opl-table-scroll > .opl-table { min-width: 640px; }


/* 6. Pricing tiers -------------------------------------------------------- */

/*
 * align-items was flex-start, so the three tiers were different heights and
 * the row ended in a ragged edge: Amateur stopped short while Pro ran on,
 * purely because Pro has more bullet points. On a pricing page that reads as
 * unfinished, and it is the page asking for money. stretch makes the cards
 * agree, which is what a comparison is for.
 */
.opl-tiers { display: flex; gap: var(--opl-s5); align-items: stretch; flex-wrap: wrap; }
.opl-tier {
  flex: 1 1 300px;
  display: flex;
  flex-direction: column;
  background: var(--opl-white);
  border: var(--opl-hairline);
  box-shadow: var(--opl-shadow-card);
}
.opl-tier__head { padding: var(--opl-s6); display: flex; flex-direction: column; gap: 14px; }
.opl-tier__name { font-family: var(--opl-font-head); font-weight: 700; font-size: var(--opl-size-h3); color: var(--opl-night); }
.opl-tier__desc { font-size: var(--opl-size-meta); color: var(--opl-gray); }
.opl-tier__price {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: 42px;
  line-height: 1;
  color: var(--opl-night);
}
.opl-tier__period { font-size: var(--opl-size-label); color: var(--opl-gray); }
.opl-tier__rule { border: 0; height: 1px; background: var(--opl-fog); margin: 0; }

.opl-tier__features { list-style: none; margin: 0; padding: var(--opl-s6); display: flex; flex-direction: column; gap: 13px; }
.opl-tier__features li { display: flex; gap: var(--opl-s3); align-items: flex-start; font-size: var(--opl-size-meta); }
.opl-tier__features .opl-yes,
.opl-tier__features .opl-no {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: 12px;
  flex: none;
  width: 14px;
}
.opl-tier__features .opl-yes { color: var(--opl-good); }
.opl-tier__features .opl-no  { color: var(--opl-gray); }
.opl-tier__features li:has(.opl-no) { color: var(--opl-gray); }

/* The highlighted tier is dark, so every colour inside it has to change or
   it renders as invisible text on a dark card. This caught me once. */
.opl-tier--featured { background: var(--opl-blue-600); border-color: var(--opl-blue-600); color: var(--opl-white); }
.opl-tier--featured .opl-tier__name,
.opl-tier--featured .opl-tier__price { color: var(--opl-white); }
.opl-tier--featured .opl-tier__desc,
.opl-tier--featured .opl-tier__period { color: var(--opl-silver); }
.opl-tier--featured .opl-tier__rule { background: var(--opl-blue-300); }
.opl-tier--featured .opl-tier__features .opl-yes { color: #7FD6A8; }
.opl-tier--featured .opl-tier__features .opl-no  { color: var(--opl-steel); }
.opl-tier--featured .opl-tier__features li       { color: var(--opl-white); }
.opl-tier--featured .opl-tier__features li:has(.opl-no) { color: var(--opl-steel); }

.opl-tier__flag {
  align-self: stretch;
  text-align: center;
  padding: 6px;
  background: var(--opl-accent);
  color: var(--opl-white);
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: 1px;
  text-transform: uppercase;
}


/* 7. Rank badges ---------------------------------------------------------- */
/* One hue, deeper means higher. Gold and silver were dropped because they
   clashed with a cool palette. */

.opl-rank {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 26px;
  padding: 4px 8px;
  background: var(--opl-rank-c);
  color: var(--opl-white);
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-label);
  letter-spacing: 0.5px;
}
.opl-rank--best { background: var(--opl-rank-best); }
.opl-rank--a    { background: var(--opl-rank-a); }
.opl-rank--b    { background: var(--opl-rank-b); }
.opl-rank--c    { background: var(--opl-rank-c); }
.opl-rank--d    { background: var(--opl-rank-d); }


/* 8. The demand bar ------------------------------------------------------- */
/* On a coming-soon page: N of 50 players have asked for this. */

.opl-demand { display: flex; flex-direction: column; gap: var(--opl-s3); }
.opl-demand__title {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-body);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--opl-white);
}
.opl-demand__track { height: 10px; width: 100%; background: var(--opl-blue-300); }
.opl-demand__fill  { height: 100%; background: var(--opl-accent); }


/* 9. Reason cards and step lists ------------------------------------------ */

.opl-reasons { display: flex; gap: var(--opl-s5); flex-wrap: wrap; }
.opl-reason {
  flex: 1 1 260px;
  background: var(--opl-surface-1);
  border: var(--opl-hairline);
  padding: var(--opl-s6);
  display: flex;
  flex-direction: column;
  gap: var(--opl-s3);
}
.opl-reason h3 {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-label);
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--opl-night);
}
.opl-reason p { margin: 0; font-size: var(--opl-size-meta); color: var(--opl-gray); }

/* The striped rule list used on Rules and Ranks. Stripes alternate, so any
   insert has to re-alternate the whole list or it goes out of phase. */
/* Read rather than pressed, so resting elevation only and no hover. */
.opl-steps { border: var(--opl-hairline); box-shadow: var(--opl-shadow-card); }
.opl-step {
  display: flex;
  gap: var(--opl-s6);
  padding: var(--opl-s5) 22px;
  background: var(--opl-white);
}
.opl-step:nth-child(odd) { background: var(--opl-surface-1); }
.opl-step__label {
  flex: none;
  width: 200px;
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-label);
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--opl-night);
}
.opl-step__body { margin: 0; font-size: var(--opl-size-meta); color: var(--opl-gray); }

/*
 * The step's title on its own line.
 *
 * The markup is a <strong> followed by a <span> inside a <span>, all three
 * inline, so the heading and the sentence ran together as one paragraph:
 * "Get the classic client Subscribers get the league client pack." The
 * <strong> carries .opl-h3 so it was BIGGER than the text it was welded to,
 * which is what made it look like a mistake rather than a style.
 *
 * Found the same way as the badge overlapping the game name, by measuring
 * the rendered page rather than reading the markup: the title box and the
 * body box genuinely share a row, with the body starting to the left of
 * where the title ends because the span wraps the full column width.
 *
 * Blockifying the heading is the whole fix. The same three classes build
 * the setup steps on the game pages, so both are corrected by this.
 */
.opl-step__body > strong { display: block; margin-bottom: 6px; color: var(--opl-night); }


/* Responsive -------------------------------------------------------------- */
/* Breakpoints live in responsive.css, which is enqueued after this file.
   Keeping them in one place means "why is this wrong on a phone" has a
   single file to open. Do not add media queries here. */


/* 10. Home page blocks ----------------------------------------------------
   Everything below exists because the home page in the mockup needs it and
   nothing already in this file fit. The replay card up in section 4 is a
   different shape: it is about a match that already happened. These are
   about a bracket that has not.
   ========================================================================== */

/* A plain responsive grid. Collapses in responsive.css, not here. */
.opl-grid { display: grid; gap: var(--opl-s6); }
.opl-grid--3 { grid-template-columns: repeat(3, 1fr); }
.opl-grid--4 { grid-template-columns: repeat(4, 1fr); }


/* The upcoming-tournament card. */
.opl-card--event { gap: 10px; }
.opl-card__when {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-h4);
  line-height: var(--opl-line-tight);
  color: var(--opl-night);
  margin: 0;
}
.opl-card__format { font-size: var(--opl-size-body-sm); color: var(--opl-slate); margin: 0; }
.opl-card__prize {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-body-sm);
  color: var(--opl-accent);
  margin: 0;
}
/* Spots left is deliberately quiet. It is a fact, not a sales line. */
.opl-card__spots { font-size: var(--opl-size-meta); color: var(--opl-gray); margin: 0; }
.opl-card__spots--cold { color: var(--opl-steel); }


/* The hero. Taller than a page header because it carries the mark, the
   wordmark and the promise. Padding is Mike's halved figure, kept here as
   one number so it stays easy to change. */
.opl-hero { padding-block: var(--opl-s10); text-align: center; }
.opl-hero__mark {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 10px 14px;
  border: 3px solid currentColor;
  line-height: 1;
  margin-bottom: var(--opl-s6);
}
.opl-hero__mark span {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: 34px;
  letter-spacing: 1px;
}
.opl-hero__get {
  display: block;
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: var(--opl-track-eyebrow);
  text-transform: uppercase;
  color: var(--opl-ember);
  margin-bottom: 6px;
}
.opl-hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--opl-s5);
  justify-content: center;
  margin-top: var(--opl-s8);
}

/* index.php and the archive header reference this. It was referenced before
   it existed, so the header band fell back to plain --dark. */
.opl-band--header { padding-block: var(--opl-s9); }


/* Nova. Her three races sit in a column beside the copy. */
.opl-nova { display: grid; grid-template-columns: 1.6fr 1fr; gap: var(--opl-s9); align-items: center; }
.opl-nova__races { display: flex; flex-direction: column; gap: var(--opl-s4); }
.opl-race {
  display: flex;
  align-items: baseline;
  gap: var(--opl-s4);
  padding: 12px 14px;
  border: var(--opl-hairline-dark);
}
.opl-race__code {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-body-sm);
  color: var(--opl-white);
}
.opl-race__name {
  font-family: var(--opl-font-head);
  font-weight: 600;
  font-size: var(--opl-size-micro);
  letter-spacing: var(--opl-track-eyebrow);
  text-transform: uppercase;
  color: var(--opl-silver);
}
/* The paywall line. Says what costs money and what does not, in one place. */
.opl-nova__gate { font-size: var(--opl-size-meta); color: var(--opl-silver); margin: var(--opl-s5) 0 0; }


/* A section intro: heading plus one line under it. Centered by default. */
.opl-sectionhead { text-align: center; margin-bottom: var(--opl-s8); }
.opl-sectionhead .opl-lede { margin-inline: auto; max-width: 62ch; }

/*
 * The left-aligned variant, which is most of them: 34 section heads
 * against 6 centered ones.
 *
 * It has to exist as a class because the templates used to do it with
 * style="text-align:left" on the element, and that overrides the text
 * alignment WITHOUT touching margin-inline:auto on the lede. So the
 * heading went left, the paragraph's text went left, and the
 * paragraph's BOX stayed centered. Measured on /games/: the h2 started
 * at x=465 and its intro at x=683, floating 218px in from the heading
 * it belonged to, on 18 section intros across nine templates.
 *
 * Resetting the margin is the whole point of the class. Keep the two
 * declarations together.
 */
.opl-sectionhead--left { text-align: left; }
.opl-sectionhead--left .opl-lede { margin-inline: 0; }

/* The quiet text link that ends a section. */
.opl-more {
  display: inline-block;
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-eyebrow);
  letter-spacing: 0.5px;
  text-transform: uppercase;
  text-decoration: none;
}


/* 11. Coming Soon stacking ------------------------------------------------
   The stats row, the demand bar and the "no date" line are three separate
   blocks with no margins of their own, so they need spacing between them
   when they sit in the same band.
   ========================================================================== */

.opl-stats + .opl-demand { margin-top: var(--opl-s8); }
.opl-demand + .opl-lede  { margin-top: var(--opl-s7); }


/* 12. The About essay -----------------------------------------------------
   About is the longest page on the site: eight numbered chapters arguing
   one point. These are the blocks that argument needs. They are deliberately
   plain, because the page is a read, not a dashboard.
   ========================================================================== */

/* A numbered chapter head. The numeral is small and quiet; the claim is big. */
.opl-chapter { margin-bottom: var(--opl-s7); }
.opl-chapter__num {
  display: block;
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: var(--opl-track-eyebrow);
  text-transform: uppercase;
  color: var(--opl-accent);
  margin-bottom: var(--opl-s3);
}
.opl-band--dark .opl-chapter__num,
.opl-band--dark-mid .opl-chapter__num { color: var(--opl-accent-lift); }
.opl-chapter__title {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-h2);
  line-height: var(--opl-line-tight);
  margin: 0;
}

/* The amateur / professional pair. Two definitions, side by side, because
   the whole page turns on the difference between them. */
.opl-defs { display: grid; grid-template-columns: 1fr 1fr; gap: var(--opl-s6); margin: var(--opl-s7) 0; }
.opl-def { border: var(--opl-hairline); padding: var(--opl-s6); background: var(--opl-white); }
.opl-def__word {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-h3);
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--opl-night);
}
.opl-def__origin { display: block; font-size: var(--opl-size-micro); letter-spacing: 0.6px; text-transform: uppercase; color: var(--opl-gray); margin: 6px 0 var(--opl-s4); }
.opl-def__body { margin: 0; font-size: var(--opl-size-body-sm); color: var(--opl-slate); }

/* A pulled line. Used once, for the lifeguard. */
.opl-pull { margin: var(--opl-s8) 0; padding-left: var(--opl-s6); border-left: 3px solid var(--opl-accent); }
.opl-pull p { margin: 0; font-size: var(--opl-size-lede); line-height: var(--opl-line-body); }
.opl-pull cite {
  display: block;
  margin-top: var(--opl-s4);
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: var(--opl-track-eyebrow);
  text-transform: uppercase;
  font-style: normal;
  color: var(--opl-gray);
}
.opl-band--dark .opl-pull,
.opl-band--dark-mid .opl-pull { border-left-color: var(--opl-ember); }
.opl-band--dark .opl-pull cite,
.opl-band--dark-mid .opl-pull cite { color: var(--opl-silver); }

/* The single big claim. One label, one sentence, nothing else on the row. */
.opl-bigstat { margin: var(--opl-s8) 0; text-align: center; }
.opl-bigstat__label {
  display: block;
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: var(--opl-track-eyebrow);
  text-transform: uppercase;
  color: var(--opl-ember);
  margin-bottom: var(--opl-s4);
}
.opl-bigstat__line {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-h2);
  line-height: var(--opl-line-tight);
  margin: 0;
}

/* What Opleague does / what you do. Paired rows, because the point is the
   pairing: every line on the left has a line on the right it cannot cover. */
.opl-split { width: 100%; border-collapse: collapse; margin-top: var(--opl-s6); }
.opl-split th {
  text-align: left;
  padding: var(--opl-s4) var(--opl-s5);
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-label);
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--opl-night);
  border-bottom: 2px solid var(--opl-accent);
  width: 50%;
}
.opl-split td {
  padding: var(--opl-s4) var(--opl-s5);
  font-size: var(--opl-size-body-sm);
  color: var(--opl-slate);
  border-bottom: var(--opl-hairline);
  vertical-align: top;
}
.opl-split tbody tr:nth-child(odd) td { background: var(--opl-surface-1); }
.opl-split caption { caption-side: top; text-align: left; font-size: var(--opl-size-meta); color: var(--opl-gray); padding-bottom: var(--opl-s4); }


/* 13. Legal documents -----------------------------------------------------
   Terms, Privacy and the Authenticity Policy share one shape: a sticky
   contents list on the left and numbered sections on the right. Named
   .opl-doc rather than .opl-legal because .opl-legal is already the footer's
   copyright line and the two are nothing to do with each other.
   ========================================================================== */

.opl-doc { display: grid; grid-template-columns: 240px 1fr; gap: var(--opl-s9); align-items: start; }
/*
 * Let a wide child scroll instead of widening the column.
 *
 * A grid track sized 1fr is minmax(auto, 1fr), and that auto minimum
 * is the item's min-content. Put a .opl-table-scroll in here, whose
 * table carries min-width:640px so the columns stay readable, and the
 * track resolves to 640px however narrow the page is. The scroll
 * wrapper never gets the chance to scroll, and BOTH grid children get
 * stretched to 640, so the sidebar goes off the edge as well.
 *
 * Found 2026-09-19 on the rebuilt Pricing page, the first page to put
 * a scrollable table inside .opl-doc: 24 elements over the edge at
 * every phone width, clean at 768 and up. min-width:0 lets the track
 * shrink and hands the overflow back to the scroll wrapper, which is
 * what it is for.
 */
.opl-doc > * { min-width: 0; }

.opl-doc__nav { position: sticky; top: var(--opl-s6); }
.opl-doc__nav h2 {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: var(--opl-track-eyebrow);
  text-transform: uppercase;
  color: var(--opl-gray);
  margin: 0 0 var(--opl-s4);
}
.opl-doc__toc { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.opl-doc__toc a { font-size: var(--opl-size-meta); text-decoration: none; color: var(--opl-slate); }
.opl-doc__toc a:hover,
.opl-doc__toc a:focus-visible { color: var(--opl-accent); text-decoration: underline; }

.opl-doc__sec + .opl-doc__sec { margin-top: var(--opl-s8); }
/* Anchors land under the sticky nav otherwise. */
.opl-doc__sec { scroll-margin-top: var(--opl-s7); }
.opl-doc__head { display: flex; align-items: baseline; gap: var(--opl-s5); margin-bottom: var(--opl-s5); }
.opl-doc__num {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-label);
  color: var(--opl-accent);
  flex: none;
}
.opl-doc__title {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-h3);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--opl-night);
  margin: 0;
}
.opl-doc__sec p { font-size: var(--opl-size-body-sm); color: var(--opl-slate); }
.opl-doc__sec p + p { margin-top: var(--opl-s4); }

/* The closing note. Both pages end by inviting you to say a clause reads
   badly, which is the most useful sentence on either of them. */
.opl-doc__note { margin-top: var(--opl-s9); border-left: 3px solid var(--opl-ember); padding: var(--opl-s5) var(--opl-s6); background: var(--opl-surface-1); }
.opl-doc__note h2 {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-label);
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--opl-night);
  margin: 0 0 var(--opl-s3);
}
.opl-doc__note p { margin: 0; font-size: var(--opl-size-meta); color: var(--opl-slate); }


/* 14. How It Works --------------------------------------------------------
   Three setup steps, each with a checklist, plus the callouts that stop
   people wasting an evening on the wrong game build.
   ========================================================================== */

/* A callout. Two kinds: the subscriber gate, and the warning. */
.opl-callout { border-left: 3px solid var(--opl-accent); padding: var(--opl-s5) var(--opl-s6); background: var(--opl-surface-1); box-shadow: var(--opl-shadow-card); }
.opl-callout__title {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: var(--opl-track-eyebrow);
  text-transform: uppercase;
  color: var(--opl-accent);
  margin: 0 0 var(--opl-s3);
}
.opl-callout h2, .opl-callout h3 { margin: 0 0 var(--opl-s4); font-size: var(--opl-size-h3); }
.opl-callout p { margin: 0; font-size: var(--opl-size-body-sm); }
.opl-callout p + p, .opl-callout > * + p { margin-top: var(--opl-s4); }

/* The warning is ember because it is the one that costs you an evening. */
.opl-callout--warn { border-left-color: var(--opl-ember); }
.opl-callout--warn .opl-callout__title { color: var(--opl-ember); }

/* On a dark band a white-ish card would shout, so the callout inverts. */
.opl-band--dark .opl-callout,
.opl-band--dark-mid .opl-callout { background: rgba(255, 255, 255, 0.05); border-left-color: var(--opl-ember); }
.opl-band--dark .opl-callout__title,
.opl-band--dark-mid .opl-callout__title { color: var(--opl-ember); }

/* A setup step. Bigger than .opl-step because each one carries a list. */
.opl-howto + .opl-howto { margin-top: var(--opl-s9); padding-top: var(--opl-s9); border-top: var(--opl-hairline); }
.opl-howto__head { display: flex; align-items: baseline; gap: var(--opl-s6); margin-bottom: var(--opl-s5); }
.opl-howto__num {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: 34px;
  line-height: 1;
  /* Was --opl-mist at 1.60 on white. Even as a large numeral that fails the
     3.0 large-text threshold, and these numerals carry the step order. */
  color: var(--opl-gray);
  flex: none;
}
.opl-howto__title {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-h3);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--opl-night);
  margin: 0;
}

/* The per-step checklist. Numbered, because the order matters. */
.opl-checks,
.opl-ticks { list-style: none; margin: var(--opl-s5) 0 0; padding: 0; counter-reset: opl-check; display: flex; flex-direction: column; gap: 10px; }
.opl-checks li,
.opl-ticks li { display: flex; gap: var(--opl-s4); align-items: baseline; font-size: var(--opl-size-body-sm); color: var(--opl-slate); }
.opl-checks li::before {
  counter-increment: opl-check;
  content: counter(opl-check);
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  color: var(--opl-accent);
  flex: none;
  min-width: 14px;
}

/* What you need: a label column and a plain-English column. */
.opl-specs { margin: 0; display: grid; grid-template-columns: 160px 1fr; gap: var(--opl-s4) var(--opl-s6); }
/* Column-width variants for the same list.
   These were inline style attributes on the <dl> in four templates, which
   meant the responsive rule collapsing .opl-specs to one column could never
   win: an inline style beats a media query regardless of specificity. On a
   phone the ranks page was holding a fixed 220px first column and leaving
   about 68px for the value. As classes they collapse like everything else,
   because responsive.css loads last. */
.opl-specs--wide   { grid-template-columns: 220px 1fr; }
.opl-specs--narrow { grid-template-columns: 110px 1fr; }
.opl-specs--tight  { grid-template-columns: 90px 1fr; }
.opl-specs--split  { grid-template-columns: 1fr auto; }
.opl-specs dt {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-label);
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--opl-night);
}
.opl-specs dd { margin: 0; font-size: var(--opl-size-body-sm); color: var(--opl-slate); }


/* 15. Ranks and scoring ---------------------------------------------------
   The page has to show its working, so it needs a way to print two formulas
   without them looking like decoration, a way to show a rating swing as a
   matched pair, and a distribution chart.
   ========================================================================== */

/* The formulas. Monospace, because they are meant to be checked. */
.opl-formula + .opl-formula { margin-top: var(--opl-s6); }
.opl-formula__label {
  display: block;
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: var(--opl-track-eyebrow);
  text-transform: uppercase;
  color: var(--opl-accent);
  margin-bottom: var(--opl-s3);
}
.opl-formula__math {
  display: block;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--opl-size-body-sm);
  line-height: 1.6;
  color: var(--opl-night);
  background: var(--opl-surface-1);
  border: var(--opl-hairline);
  padding: var(--opl-s5);
  overflow-x: auto;
}

/* A worked example: what the winner gains and the loser drops, side by side
   so the asymmetry is the thing you notice. */
.opl-swing { display: flex; gap: var(--opl-s6); margin: var(--opl-s5) 0; }
.opl-swing__half { display: flex; flex-direction: column; gap: 2px; }
.opl-swing__value { font-family: var(--opl-font-head); font-weight: 700; font-size: 24px; line-height: 1; }
.opl-swing__value--up   { color: var(--opl-good); }
.opl-swing__value--down { color: var(--opl-critical); }
.opl-swing__label {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: var(--opl-track-eyebrow);
  text-transform: uppercase;
  color: var(--opl-gray);
}

/* Where everyone sits. A plain column chart, labelled, no library. */
.opl-dist { display: flex; align-items: flex-end; gap: var(--opl-s3); height: 180px; margin: var(--opl-s7) 0 0; }
.opl-dist__col { flex: 1 1 0; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 6px; height: 100%; }
.opl-dist__n { font-family: var(--opl-font-head); font-weight: 700; font-size: var(--opl-size-micro); color: var(--opl-gray); }
.opl-dist__bar { width: 100%; background: var(--opl-accent); min-height: 2px; }
/* An empty rank still gets a visible stub, otherwise the chart reads as if
   those ranks do not exist rather than as if nobody is in them yet. */
.opl-dist__bar--empty { background: var(--opl-fog); }
.opl-dist__label {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: 0.4px;
  color: var(--opl-slate);
}


/* 16. Bracket and game rows ----------------------------------------------
   One row shape serves the Tournaments list and the Games index: a marker
   on the left, the substance in the middle, a status, and one action. A
   grid rather than a table because the middle column stacks three lines.
   ========================================================================== */

.opl-tabs { display: flex; gap: var(--opl-s5); border-bottom: var(--opl-hairline); }
.opl-tab {
  padding: 0 2px var(--opl-s4);
  margin-bottom: -1px;
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-label);
  letter-spacing: 0.6px;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--opl-gray);
  border-bottom: 2px solid transparent;
}
.opl-tab:hover, .opl-tab:focus-visible { color: var(--opl-accent); }
.opl-tab--on { color: var(--opl-night); border-bottom-color: var(--opl-accent); }

.opl-brackets { list-style: none; margin: var(--opl-s6) 0 0; padding: 0; }
.opl-bracket {
  display: grid;
  grid-template-columns: 96px 1fr auto auto;
  gap: var(--opl-s6);
  align-items: center;
  padding: var(--opl-s5) 0;
  border-bottom: var(--opl-hairline);
}
.opl-bracket__date {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-h4);
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--opl-accent);
}
.opl-bracket__main { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.opl-bracket__game {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: var(--opl-track-eyebrow);
  text-transform: uppercase;
  color: var(--opl-gray);
}
.opl-bracket__title {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-h4);
  color: var(--opl-night);
  text-decoration: none;
}
a.opl-bracket__title:hover, a.opl-bracket__title:focus-visible { color: var(--opl-accent); }
.opl-bracket__format { font-size: var(--opl-size-meta); color: var(--opl-slate); }
.opl-bracket__spots { font-size: var(--opl-size-meta); color: var(--opl-gray); white-space: nowrap; }

/* A bracket with no date is not a thing you can sign up for, so it reads
   quieter than one that is. */
.opl-bracket--demand .opl-bracket__date { color: var(--opl-steel); }


/* 17. The podium ----------------------------------------------------------
   Top three, with first place raised. Source order is 1, 2, 3 so a screen
   reader and a phone read them in the right order; the visual reordering is
   done with flex order at wide widths only.
   ========================================================================== */

.opl-podium { display: flex; gap: var(--opl-s5); align-items: flex-end; margin-bottom: var(--opl-s8); }
.opl-podium__slot {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
  padding: var(--opl-s6) var(--opl-s5);
  background: var(--opl-surface-1);
  border: var(--opl-hairline);
}
.opl-podium__slot--1 { order: 2; padding-block: var(--opl-s8); border-color: var(--opl-accent); }
.opl-podium__slot--2 { order: 1; }
.opl-podium__slot--3 { order: 3; }
.opl-podium__pos {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: 30px;
  line-height: 1;
  /* Was --opl-mist, which is 1.42 on this card and fails even the 3.0
     large-text threshold, and these numerals are the rank. */
  color: var(--opl-quiet-lg);
}
.opl-podium__slot--1 .opl-podium__pos { color: var(--opl-accent); }
.opl-podium__name {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-h4);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--opl-night);
}
.opl-podium__meta { font-size: var(--opl-size-meta); color: var(--opl-gray); }


/* 18. Setup page ----------------------------------------------------------
   Download rows, the two-track comparison, and the gated network ID.
   ========================================================================== */

.opl-dl { display: grid; grid-template-columns: 44px 1fr auto; gap: var(--opl-s5); align-items: center; border: var(--opl-hairline); background: var(--opl-white); padding: var(--opl-s5) var(--opl-s6); }
.opl-dl + .opl-dl { margin-top: var(--opl-s5); }
.opl-dl__mark {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: 20px;
  line-height: 1;
  text-align: center;
  color: var(--opl-accent);
}
/* The 1.16.1 client is the one we cannot hand you, so its marker warns. */
.opl-dl--warn .opl-dl__mark { color: var(--opl-ember); }
.opl-dl__body { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.opl-dl__kind {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: var(--opl-track-eyebrow);
  text-transform: uppercase;
  color: var(--opl-gray);
}
.opl-dl__name { font-family: var(--opl-font-head); font-weight: 700; font-size: var(--opl-size-h4); color: var(--opl-night); }
.opl-dl__desc { font-size: var(--opl-size-body-sm); color: var(--opl-slate); }
.opl-dl__meta { font-size: var(--opl-size-micro); color: var(--opl-gray); }

/* The league network ID. Subscriber-only, so what shows publicly is the
   fact that it exists, never the value. */
.opl-idbox { border: var(--opl-hairline-dark); background: rgba(255,255,255,0.05); padding: var(--opl-s6); }
.opl-idbox__label {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: var(--opl-track-eyebrow);
  text-transform: uppercase;
  color: var(--opl-ember);
  display: block;
  margin-bottom: var(--opl-s3);
}
.opl-idbox__value {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 22px;
  letter-spacing: 3px;
  color: var(--opl-silver);
}


/* 19. Forms ---------------------------------------------------------------
   The contact form, and the bot trap's hiding place.
   ========================================================================== */

/* ---------------------------------------------------------------- sign in
 * wp_login_form() emits WordPress's own markup and none of our classes, so
 * without this the login page is default browser inputs and a tiny grey
 * button sitting on an Opleague band. That is exactly how it shipped on
 * 2026-09-20 and Mike sent a screenshot of it, correctly.
 *
 * Rather than hand-rolling a form and posting it ourselves, which would
 * mean owning authentication, the core markup is kept and dressed to match
 * every other form on the site. Same field treatment as the contact form,
 * same 44px minimum, same focus ring.
 */
.opl-login {
  max-width: 460px;
  padding: var(--opl-s7);
  background: var(--opl-white);
  border: var(--opl-hairline);
  box-shadow: var(--opl-shadow-card);
}

/* A login form the full width of a desktop screen is its own kind of wrong. */
#opl-login p { margin: 0 0 var(--opl-s5); }
#opl-login label {
  display: block;
  margin-bottom: 6px;
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: var(--opl-track-eyebrow);
  text-transform: uppercase;
  color: var(--opl-gray);
}
#opl-login input[type="text"],
#opl-login input[type="password"] {
  font-family: var(--opl-font-body);
  font-size: var(--opl-size-body-sm);
  color: var(--opl-night);
  background: var(--opl-white);
  border: var(--opl-hairline);
  padding: 11px var(--opl-s5);
  min-height: 44px;
  width: 100%;
}
#opl-login input[type="text"]:focus-visible,
#opl-login input[type="password"]:focus-visible { outline: 2px solid var(--opl-accent); outline-offset: 2px; }

/*
 * The checkbox and its label on one line, with the label's own uppercase
 * treatment turned off. "KEEP ME SIGNED IN" shouting in small caps beside a
 * tick box reads as a warning rather than a convenience.
 */
#opl-login .login-remember label {
  display: flex;
  align-items: center;
  gap: var(--opl-s3);
  margin: 0;
  font-family: var(--opl-font-body);
  font-size: var(--opl-size-meta);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--opl-steel);
}
#opl-login .login-remember input { width: 18px; height: 18px; min-height: 0; margin: 0; }

/*
 * The submit, dressed as .opl-btn--primary without being able to carry the
 * class. wp_login_form() emits a plain <input type="submit"> and gives us
 * no hook to add a class to it, so it is styled by position instead. These
 * values mirror .opl-btn and .opl-btn--primary in opleague.css; if those
 * change, this has to follow, which is why it says so here.
 */
#opl-login .login-submit { margin-bottom: 0; }
#opl-login .login-submit input[type="submit"] {
  display: block;
  width: 100%;
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-eyebrow);
  letter-spacing: var(--opl-track-label);
  text-transform: uppercase;
  padding: 15px 24px;
  min-height: 44px;
  border: 1px solid transparent;
  border-radius: var(--opl-radius);
  background: var(--opl-accent);
  color: var(--opl-white);
  box-shadow: var(--opl-shadow-card);
  cursor: pointer;
  transition: background-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
#opl-login .login-submit input[type="submit"]:hover {
  background: var(--opl-accent-mid);
  box-shadow: var(--opl-shadow-lift);
  transform: translateY(-1px);
}

/* ------------------------------------------------------------- contact
 * The form is the point of the page, so it gets the wide column.
 *
 * This used .opl-doc, 240px 1fr, which is the documents layout: a narrow
 * contents list beside a long read. On this page that put the form in the
 * 240px column and four lines of contact detail in the wide one, so the
 * thing the page exists for was squeezed into a sidebar. Its own grid now,
 * weighted the other way.
 */
.opl-contact {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: var(--opl-s9);
  align-items: start;
}
.opl-contact > * { min-width: 0; }

/* The form, in a card, so it reads as a thing you fill in rather than
   fields floating on a band. */
.opl-form--card {
  padding: var(--opl-s7);
  background: var(--opl-white);
  border: var(--opl-hairline);
  box-shadow: var(--opl-shadow-card);
}

.opl-contact__ways { display: flex; flex-direction: column; gap: var(--opl-s5); }

/*
 * Four different answers, so four blocks rather than a run of headings.
 * Email, Discord, sponsors and response time were a single column of text
 * with nothing between them, and a reader had to parse where one stopped.
 */
.opl-way {
  padding: var(--opl-s5) var(--opl-s6);
  background: var(--opl-surface-2);
  border-left: 3px solid var(--opl-accent);
  box-shadow: var(--opl-shadow-card);
}
.opl-way h2 {
  margin: 0 0 6px;
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-label);
  letter-spacing: var(--opl-track-eyebrow);
  text-transform: uppercase;
  color: var(--opl-accent);
}
.opl-way p { margin: 0; font-size: var(--opl-size-meta); color: var(--opl-steel); }

/* ---------------------------------------------------------- featured
 * The players band on the home page. Sits on a dark band, so it takes the
 * dark elevation pair, and each card is a link so each card lifts.
 */
.opl-featured {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--opl-s5);
  margin-top: var(--opl-s8);
}

/*
 * THE GALLERY. Mike, 2026-09-20: make Players to watch roll.
 *
 * Scroll-snap, which is CSS. That is the whole design decision. This site
 * has almost no JavaScript on purpose, the nav dropdowns use none at all,
 * and a carousel is the classic place where accessibility dies: focus
 * trapped in an off-screen slide, auto-advance stealing a click, arrows
 * that a keyboard cannot reach.
 *
 * A scrollable row avoids all of it by not being a carousel. Touch, a
 * trackpad, a mouse wheel over it, and the keyboard arrows on a focused
 * container all work with nothing loaded, and every card is a real link in
 * the tab order whether it is currently on screen or not. There is no
 * auto-advance to steal anything, which also means nothing to pause on
 * hover and nothing for prefers-reduced-motion to switch off.
 *
 * The container is focusable, like .opl-table-scroll already is, so
 * somebody on a keyboard can reach the scroll area itself rather than
 * only tabbing through its contents.
 */
.opl-featured--roll {
  display: flex;
  grid-template-columns: none;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-left: 0;
  -webkit-overflow-scrolling: touch;

  /* Room for the hover lift, which would otherwise be clipped by the
     scroll container the moment a card rises. */
  padding-block: 6px;

  /* A soft right edge, the same hint the wide tables use, so it reads as
     "there is more" rather than "it ends abruptly here". */
  -webkit-mask-image: linear-gradient(90deg, #000 92%, transparent 100%);
          mask-image: linear-gradient(90deg, #000 92%, transparent 100%);
}
.opl-featured--roll:focus-visible { outline: 2px solid var(--opl-accent); outline-offset: 4px; }

.opl-featured--roll > .opl-feat {
  flex: 0 0 clamp(240px, 30%, 320px);
  scroll-snap-align: start;
}

.opl-feat {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: var(--opl-s6);
  background: var(--opl-blue-700);
  border: 1px solid var(--opl-blue-300);
  color: var(--opl-white);
  text-decoration: none;
  box-shadow: var(--opl-shadow-dark);
  transition: box-shadow 160ms ease, transform 160ms ease;
}
.opl-feat:hover,
.opl-feat:focus-visible { box-shadow: var(--opl-shadow-dark-lift); transform: translateY(-3px); }

/*
 * The portrait. Square, because --opl-radius is 0 and a circle would be
 * the only round thing on the whole site.
 *
 * Fixed aspect and object-fit cover, so a photo somebody uploads at any
 * shape is cropped rather than squashed, and every card in the row stays
 * the same height whatever people upload.
 */
.opl-portrait {
  display: block;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  margin: calc(var(--opl-s6) * -1) calc(var(--opl-s6) * -1) var(--opl-s4);
  background: var(--opl-blue-800, #081A27);
}
.opl-portrait__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* On the profile: beside the name, not above it, and a fixed size so a
   long name never squeezes the picture into a sliver. */
.opl-profilehead { display: flex; align-items: center; gap: var(--opl-s6); }
.opl-portrait--profile { flex: none; width: 128px; margin: 0; border: 1px solid var(--opl-blue-300); }

/* The drawn stand-in has no photographic detail to lose, so it scales. */
.opl-portrait__img--drawn { object-fit: fill; }

.opl-feat__eyebrow {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: var(--opl-track-eyebrow);
  text-transform: uppercase;
  color: var(--opl-ember);
}
.opl-feat__name {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-h3);
  letter-spacing: 1px;
}
.opl-feat__plays {
  font-family: var(--opl-font-head);
  font-weight: 500;
  font-size: var(--opl-size-eyebrow);
  letter-spacing: var(--opl-track-label);
  text-transform: uppercase;
  color: var(--opl-lift, #7FB2CC);
}
.opl-feat__bio { font-size: var(--opl-size-meta); color: var(--opl-mist); }
.opl-feat__more {
  margin-top: auto;
  padding-top: var(--opl-s4);
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-eyebrow);
  letter-spacing: var(--opl-track-label);
  text-transform: uppercase;
  color: var(--opl-accent-halo);
}

.opl-featured__cta {
  margin-top: var(--opl-s8);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--opl-s5);
}
.opl-featured__cta .opl-lede { margin: 0; flex: 1 1 320px; }

/* ------------------------------------------------------- announcement
 * The bar across the very top, above the nav.
 *
 * Three tones from the palette rather than a colour picker. Ember is the
 * house colour and the one warm note the whole site allows itself, so the
 * bar is the only place it appears at full width; that is the point of it.
 */
.opl-announce { width: 100%; }
.opl-announce__inner {
  max-width: var(--opl-content-wide);
  margin-inline: auto;
  padding: 10px var(--opl-s8);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--opl-s5);
}
.opl-announce__text {
  margin: 0;
  font-family: var(--opl-font-head);
  font-weight: 600;
  font-size: var(--opl-size-label);
  letter-spacing: .4px;
}
.opl-announce__link {
  flex: none;
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-eyebrow);
  letter-spacing: var(--opl-track-label);
  text-transform: uppercase;
  text-decoration: none;
  padding: 6px 14px;
  border: 1px solid currentColor;
  color: inherit;
}
.opl-announce__link:hover,
.opl-announce__link:focus-visible { background: rgba(255,255,255,.16); }

/* Pushed to the edge, because it is the least important thing in the row
   and should not sit between the message and its own button. */
.opl-announce__close {
  margin-left: auto;
  flex: none;
  background: none;
  border: 0;
  color: inherit;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  /* 44px for a thumb, like every other control on the site. */
  min-width: 44px;
  min-height: 44px;
}
.opl-announce__close:hover,
.opl-announce__close:focus-visible { background: rgba(255,255,255,.16); }

.opl-announce--ember { background: var(--opl-ember); color: var(--opl-ember-ink); }
.opl-announce--alert { background: var(--opl-critical, #A32F2F); color: #FFFFFF; }
.opl-announce--quiet { background: var(--opl-blue-300); color: var(--opl-white); }

.opl-form { display: flex; flex-direction: column; gap: var(--opl-s5); }
.opl-field { display: flex; flex-direction: column; gap: 6px; }
.opl-field label {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: var(--opl-track-eyebrow);
  text-transform: uppercase;
  color: var(--opl-gray);
}
.opl-field input,
.opl-field select,
.opl-field textarea {
  font-family: var(--opl-font-body);
  font-size: var(--opl-size-body-sm);
  color: var(--opl-night);
  background: var(--opl-white);
  border: var(--opl-hairline);
  padding: 11px var(--opl-s5);
  /* 43px computed, one pixel under the 44 a thumb wants. These are the
     registration and contact fields, which is exactly where a mis-tap
     costs something. Measured 2026-09-19. */
  min-height: 44px;
  width: 100%;
}
.opl-field textarea { min-height: 150px; resize: vertical; }
.opl-field input:focus-visible,
.opl-field select:focus-visible,
.opl-field textarea:focus-visible { outline: 2px solid var(--opl-accent); outline-offset: 1px; }

/* The bot trap. Hidden with a class so it can be excluded from any
   "focus the first field" helper, and kept out of the tab order. */
.opl-trap { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; }

.opl-notice { border-left: 3px solid var(--opl-good); background: var(--opl-surface-1); padding: var(--opl-s5) var(--opl-s6); margin-bottom: var(--opl-s6); }
.opl-notice--bad { border-left-color: var(--opl-critical); }
.opl-notice p { margin: 0; font-size: var(--opl-size-body-sm); }


/* 20. Question and answer -------------------------------------------------
   Native <details>, so it works with JavaScript off, is keyboard operable
   without any help, and the browser's own find can search inside it.
   ========================================================================== */

.opl-qa { border-bottom: var(--opl-hairline); }
.opl-qa__q {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--opl-s5);
  padding: var(--opl-s5) 0;
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-h4);
  color: var(--opl-night);
}
.opl-qa__q::-webkit-details-marker { display: none; }
.opl-qa__q::after { content: "+"; color: var(--opl-accent); font-size: 22px; line-height: 1; flex: none; }
.opl-qa[open] .opl-qa__q::after { content: "\2013"; }
.opl-qa__q:focus-visible { outline: 2px solid var(--opl-accent); outline-offset: 2px; }
.opl-qa__a { padding-bottom: var(--opl-s6); }
.opl-qa__a p { font-size: var(--opl-size-body-sm); color: var(--opl-slate); }
.opl-qa__a p + p { margin-top: var(--opl-s4); }

/* Two-column grid, used by the results list. */
.opl-grid--2 { grid-template-columns: repeat(2, 1fr); }

/* The signup result line. Ember so it is visible against the dark footer
   without competing with the button. */
.opl-signup__note--said { color: var(--opl-ember); font-weight: 600; }


/* 21. The bracket grid ----------------------------------------------------
   Rounds side by side, each round half the height of the one before. Rows
   are generated from the entrant count, so an eight-player night does not
   render sixteen empty boxes.
   ========================================================================== */

.opl-bracketgrid { display: flex; gap: var(--opl-s6); align-items: stretch; min-width: 620px; }
.opl-bracketgrid__round { flex: 1 1 0; display: flex; flex-direction: column; gap: var(--opl-s4); }
.opl-bracketgrid__label {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  letter-spacing: var(--opl-track-eyebrow);
  text-transform: uppercase;
  color: var(--opl-gray);
  margin: 0 0 var(--opl-s3);
}
/* Each round's matches spread over the same height, so a later round's
   boxes sit between the two they came from, as a bracket should read. */
.opl-bracketgrid__round { justify-content: space-around; }
.opl-bracketgrid__match {
  margin: 0;
  padding: 12px var(--opl-s5);
  border: var(--opl-hairline);
  background: var(--opl-white);
  font-family: var(--opl-font-head);
  font-weight: 600;
  font-size: var(--opl-size-micro);
  letter-spacing: 0.5px;
  color: var(--opl-gray);
  text-align: center;
  white-space: nowrap;
}

/* 23. PLAYER DIRECTORY
   ------------------------------------------------------------------
   Cards for /players/. The whole card is one anchor rather than a card
   with a link inside it: a name, a rank and three numbers are all part
   of the same answer to "who is this", so they should all be the same
   target. It also means one focus stop per player instead of two, which
   is the difference between tabbing through six players and twelve. */

.opl-players {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--opl-s5);
  margin: var(--opl-s7) 0 0;
  padding: 0;
  list-style: none;
}

.opl-player { margin: 0; }

.opl-player__link {
  display: flex;
  flex-direction: column;
  gap: var(--opl-s3);
  height: 100%;
  padding: var(--opl-s6);
  background: var(--opl-surface-1);
  border: 1px solid rgba(15, 18, 20, 0.08);
  box-shadow: var(--opl-shadow-card);
  color: inherit;
  text-decoration: none;
  transition: box-shadow 140ms ease, transform 140ms ease;
}

.opl-player__link:hover,
.opl-player__link:focus-visible {
  box-shadow: var(--opl-shadow-lift);
  transform: translateY(-2px);
}

/* The focus ring is on the anchor, so it has to clear the card edge. */
.opl-player__link:focus-visible { outline-offset: 3px; }

.opl-player__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--opl-s3);
}

.opl-player__name {
  font-family: var(--opl-font-head);
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--opl-ink);
  /* Player names are user-chosen and some of them are long and unbroken. */
  overflow-wrap: anywhere;
}

.opl-player__meta {
  font-size: 13px;
  color: var(--opl-gray);
}

.opl-player__stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--opl-s2) var(--opl-s5);
  margin-top: auto;
  padding-top: var(--opl-s3);
  border-top: 1px solid rgba(15, 18, 20, 0.08);
  font-size: 12px;
  color: var(--opl-gray);
}

.opl-player__stats strong {
  display: block;
  font-family: var(--opl-font-head);
  font-size: 17px;
  font-weight: 700;
  color: var(--opl-ink);
}

.opl-player__go {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--opl-accent);
}

.opl-player__link:hover .opl-player__go { text-decoration: underline; }

@media (max-width: 640px) {
  .opl-players { grid-template-columns: 1fr; gap: var(--opl-s4); }
  .opl-player__link { padding: var(--opl-s5); }
}

/* Someone who has asked their machine not to animate should not be given
   a card that jumps under the pointer. */
@media (prefers-reduced-motion: reduce) {
  .opl-player__link { transition: none; }
  .opl-player__link:hover,
  .opl-player__link:focus-visible { transform: none; }
}

/* A tick list, for "what you get". Same layout as .opl-checks, which is
   numbered because those are steps you take in order. These are not steps
   and numbering them implies a sequence that is not there. */
.opl-ticks li::before {
  content: "¹3";
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-micro);
  color: var(--opl-accent);
  flex: none;
  min-width: 14px;
}

.opl-band--dark .opl-ticks li,
.opl-band--dark .opl-ticks li::before { color: inherit; }

/* ---------------------------------------------------------------- the profile editor
 *
 * NAMED opl-efield AND NOT opl-field, and that is the second attempt.
 *
 * .opl-field already exists two hundred lines above this and belongs to
 * the contact and register forms. Writing these rules against the same
 * name put them LATER in the same stylesheet, so on every specificity
 * tie they won, and a page nobody had touched quietly changed. The
 * visible symptom here was the switches: .opl-field label sets uppercase
 * grey small-caps, which landed on the checkbox captions and pulled them
 * off their boxes.
 *
 * A shared class is only shared if both users want the same thing. These
 * two do not: one is a short contact form, the other is a long settings
 * page.
 *
 * A member filling in their own page. Deliberately plain: this is a form
 * somebody works through once and then comes back to twice a year, so it
 * is built for reading down rather than for looking impressive.
 *
 * ONE COLUMN, ALWAYS, at every width. A two-column form makes the eye
 * choose a path at every row, and a long settings form is the one place
 * where a single obvious path beats density. It also means there is no
 * responsive behaviour here to get wrong.
 *
 * The groups are real <fieldset>s with real <legend>s rather than headings
 * over divs, because a screen reader then announces which section a field
 * belongs to when it reaches it, which is the entire difference between
 * "Show" and "What is public: Show".
 */
.opl-editor__who {
  padding-bottom: var(--opl-s5);
  border-bottom: var(--opl-hairline);
  margin-bottom: var(--opl-s7);
  display: flex;
  flex-wrap: wrap;
  gap: var(--opl-s4) var(--opl-s6);
  align-items: baseline;
  justify-content: space-between;
}

.opl-editor__group {
  border: var(--opl-hairline);
  padding: var(--opl-s6);
  margin: 0 0 var(--opl-s7);
  box-shadow: var(--opl-shadow-card);
  background: var(--opl-white);
}

.opl-editor__group > legend {
  font-family: var(--opl-font-head);
  font-weight: 700;
  font-size: var(--opl-size-h4);
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--opl-accent);
  padding: 0 var(--opl-s4);
  margin-inline-start: calc(var(--opl-s4) * -1);
}

.opl-editor__intro {
  color: var(--opl-steel);
  font-size: var(--opl-size-body-sm);
  margin: 0 0 var(--opl-s6);
}

.opl-efield { margin-bottom: var(--opl-s6); }
.opl-efield:last-child { margin-bottom: 0; }

.opl-efield__label {
  display: block;
  font-family: var(--opl-font-head);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--opl-slate);
  margin-bottom: var(--opl-s3);
}

.opl-efield input[type="text"],
.opl-efield input[type="number"],
.opl-efield textarea {
  width: 100%;
  font: inherit;
  color: var(--opl-ink);
  background: var(--opl-white);
  border: 1px solid var(--opl-steel);
  border-radius: var(--opl-radius);
  padding: 10px 12px;
}

/* A textarea holding pipe-separated lines is read as a small table, so it
   gets the monospace it is actually being used as. */
.opl-efield--lines textarea {
  font-family: Consolas, "Courier New", monospace;
  font-size: 13px;
  line-height: 1.7;
  white-space: pre;
  overflow-x: auto;
}

.opl-efield input:focus-visible,
.opl-efield textarea:focus-visible {
  outline: 2px solid var(--opl-ember);
  outline-offset: 1px;
  border-color: var(--opl-ember);
}

.opl-efield__help {
  margin: var(--opl-s3) 0 0;
  font-size: var(--opl-size-body-sm);
  color: var(--opl-steel);
}

/* The switches. A generous hit area, because the label is the target on a
   phone and a 14px checkbox on its own is not. */
.opl-efield__check {
  display: flex;
  align-items: flex-start;
  gap: var(--opl-s4);
  cursor: pointer;
  padding: var(--opl-s3) 0;
}
.opl-efield__check input { margin-top: 3px; width: 18px; height: 18px; flex: none; accent-color: var(--opl-ember); }
.opl-efield__check span { font-weight: 600; }

.opl-imagefield img,
.opl-efield__static img {
  border: var(--opl-hairline);
  margin-bottom: var(--opl-s4);
  object-fit: cover;
}

.opl-efield__clear {
  background: none;
  border: 0;
  padding: 0 var(--opl-s3);
  color: var(--opl-critical);
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}

.opl-editor__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--opl-s5);
  align-items: center;
  margin-bottom: var(--opl-s8);
}

/* Why some things are missing, said on the page rather than only in a
   commit message. A form that silently lacks a field somebody expects
   reads as broken; one that explains reads as considered. */
.opl-editor__note {
  border-left: 3px solid var(--opl-ember);
  background: var(--opl-surface-1);
  padding: var(--opl-s5) var(--opl-s6);
}
.opl-editor__note h3 { margin-top: 0; }
.opl-editor__note p:last-child { margin-bottom: 0; }

.opl-notice--good { border-left-color: var(--opl-good); }

/* The owner's edit link on their own profile. Sits on the dark header
   band, so the outline variant's currentColor gives it a white edge
   without a second rule. */
.opl-profile__mine { margin: var(--opl-s6) 0 0; }

/* A result that DID reach the ladder. The counterpart to --unranked,
   added 2026-09-20 with the badge on the results page. Green because the
   site only ever uses green for a win or a pass, and "this counted" is
   the pass. */
.opl-pill--ranked   { background: var(--opl-good); }

/* A voided result. Added 2026-09-20 with the void field.
 *
 * QUIETER, NEVER HIDDEN, AND NEVER STRUCK THROUGH. The result stays on
 * the site on purpose: deleting it would change the other player's
 * record with no explanation anywhere, and a ladder that can be edited
 * is not a ladder you can check. But it should not read as a live
 * result either, so the scoreline loses its weight and the reason gets
 * the emphasis instead.
 *
 * Strikethrough was the obvious choice and is the wrong one: a screen
 * reader announces nothing for it, so the whole meaning would live in
 * a visual effect. The badge carries it in words instead.
 */
.opl-card--void .opl-card__players { opacity: .55; }
.opl-card--void .opl-card__event,
.opl-card--void .opl-card__maps { color: var(--opl-steel); }

.opl-card__void {
  margin: 0 0 var(--opl-s4);
  padding: var(--opl-s3) var(--opl-s4);
  border-left: 3px solid var(--opl-unranked);
  background: var(--opl-paper);
  color: var(--opl-slate);
  font-size: var(--opl-size-body-sm);
}
