/* Articles: the feed, the cards and the reading page.
 *
 * A SEPARATE STYLESHEET, deliberately. app.css is ~1,100 lines and another
 * session is actively working in it; everything here is additive and new, so
 * splitting the file removes the merge entirely rather than managing it.
 *
 * Nothing here defines a custom property. Every colour, size and space is a
 * token from app.css's :root, so this file inherits the site's palette by
 * construction and cannot drift from it — if the accent changes there, these
 * cards change with it and nobody has to remember to look.
 *
 * Class names are all `post-*`, `feed-*`, `side-*` or `piece-*`. `.card` and
 * `.tag` were already taken in app.css and mean other things; `.card` in
 * particular is a generic section wrapper whose border-top drew a rule
 * through every article card before these were renamed.
 *
 * The site is dark-only — app.css defines the one ramp and base.html sets
 * <meta name="color-scheme" content="dark">. This file follows that rather
 * than inventing a second theme nothing else honours.
 *
 * Every NAMED colour here comes from app.css's tokens and inverted for free.
 * The literals did not, and they were the whole cost of the turn from paper
 * to ink: two white hairlines on a card that stopped being dark, and a set of
 * near-black shadows that became invisible against a near-black page. The
 * shadows are gone rather than re-tuned — see the elevation note below. The
 * scrim over a card photo is still a literal black and is meant to be: it
 * darkens a photograph, not a surface, so it does not belong to the theme.
 */

/* --- the two-column landing layout ------------------------------------- */

.feed-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: var(--s7);
  align-items: start;
  padding-top: var(--s6);
}

.feed { min-width: 0; }
.feed-wide { max-width: 1100px; margin: 0 auto; padding-top: var(--s6); }

/* The feed heading carries the page, so it is set heavy and sits on a rule in
 * the accent rather than a hairline in the ink. Previously it was 17px at the
 * same weight as a card title, which left the whole page reading flat — no
 * element announced itself as the top of a hierarchy. */
.feed-head {
  margin: 0 0 var(--s5);
  padding-bottom: var(--s3);
  border-bottom: 3px solid var(--a-deep);
  font: 800 var(--t-h2)/1.1 var(--sans);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--a-deep);
}

.feed-empty {
  margin: 0;
  padding: var(--s8) 0;
  text-align: center;
  color: var(--ink-3);
  font-size: var(--t-body);
}

.feed-more { margin: var(--s5) 0 0; }

/* --- the card ----------------------------------------------------------- */

/* Cards need weight. A 1px hairline made the feed read as a wireframe next
 * to any site that commits to a card — the picture was doing all the work
 * and the container was apologising for being there.
 *
 * The first answer was a shadow and a lift, which was wrong twice over. It
 * contradicted app.css's stated rule — DEPTH: value, not shadow — that I had
 * not read before writing it. And it only looked like it worked because the
 * page was cream: the same two shadows on ink render nothing, because they
 * were near-black blurs against a near-black ground.
 *
 * What carries the weight now is the step from --bg to --bg-raised. That is
 * the rule's own mechanism, it is legible on ink in a way a blur is not, and
 * it needed no tuning when the ramp turned over. The hover keeps the lift and
 * the accent border; movement and colour still read on a dark page. */
.post {
  background: var(--bg-raised);
  border: 1px solid var(--rule);
  border-radius: 10px;
  overflow: hidden;
  transition: transform 0.18s ease, border-color 0.18s ease;
}

.post:hover {
  border-color: var(--a-deep);
  transform: translateY(-3px);
}

/* The whole card is one link. `display: grid` with the text row set to 1fr
 * makes every card in a row the same height with the byline pinned to the
 * bottom, which is what stops a two-word headline sitting next to a
 * three-line one and looking like a mistake. */
/* Flex rather than a two-row grid, because a card has a variable number of
 * children: picture + text, or strap + text, or picture + strap + text when
 * the picture is tall and the labels sit under it instead of over it. The
 * grid version stretched whichever child landed in the `1fr` row. The text
 * block is the one that grows, which is what keeps bylines aligned across a
 * row of unequal cards. */
.post-hit {
  display: flex;
  flex-direction: column;
  height: 100%;
  text-decoration: none;
  color: inherit;
}

/* The picture and the words are two blocks. Grouping the words is what lets a
 * tall card put the two side by side instead of stacking a portrait image on
 * top of a headline and leaving half the card empty. */
.post-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
}

.post-text { flex: 1; }

.post-shot {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--bg-sunk);
  overflow: hidden;
}

/* `object-position` above centre, because the author supplies the pictures and
 * a promo graphic is usually PORTRAIT with the player's head near the top. The
 * first one through here was 399x501; cropped dead-centre to 16:9 the card
 * showed a torso and a basketball, with the face and the wordmark cut off.
 *
 * 22% rather than `top`: it rescues a portrait without shoving a landscape
 * photo's horizon to the bottom of the frame. A card is a thumbnail, so the
 * crop has to be forgiving of both and perfect for neither. */
.post-shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 22%;
}

/* A PORTRAIT picture goes BESIDE the words, not above them.
 *
 * Two wrong answers came before this one. Cropping a 941x1176 infographic to
 * a 16:9 banner gave a zoomed strip of its own middle. Letterboxing it inside
 * the same banner instead gave an enormous black field with a small picture
 * marooned in the centre, which read as an image that had failed to load.
 *
 * Both were attempts to force a tall picture through a layout built for a
 * wide one. A portrait image in a wide card wants a COLUMN, so the card turns
 * horizontal: picture at its own proportions on the left, headline and
 * standfirst filling the space on the right. Nothing cropped, nothing padded,
 * and the card is the same height as the picture rather than the picture
 * being stretched to the height of the card. */
/* THE LEAD CARD goes horizontal: picture at its own proportions on the left,
 * headline and standfirst filling the space on the right. Whole image, no
 * crop, no padding, and the card is as tall as the picture rather than the
 * picture being stretched to the height of the card. */
.post-lead.post-tall .post-hit { flex-direction: row; align-items: stretch; }

.post-lead.post-tall .post-shot {
  aspect-ratio: auto;
  flex: 0 0 auto;
  height: auto;
  align-self: stretch;
  background: var(--bg-sunk);
}

.post-lead.post-tall .post-shot img {
  width: auto;
  height: 100%;
  max-height: 560px;
  object-fit: contain;
}

/* The words take the rest and sit centred against the picture, which stops a
 * short headline floating at the top of a tall card. */
.post-lead.post-tall .post-body {
  justify-content: center;
  min-width: 0;
  padding: var(--s5);
}

.post-lead.post-tall .post-strap { padding: 0 0 var(--s3); }
.post-lead.post-tall .post-text { flex: 0 0 auto; padding: 0; }
.post-lead.post-tall .post-by { margin: var(--s4) 0 0; }

/* A GRID CARD is ~390px wide and has no room for two columns — tried it, the
 * headline came out at two words a line. So the thumbnail is a 4:3 crop
 * anchored to the TOP, which is where a promo graphic puts its wordmark and
 * its subject. It loses the bottom of the image, which is the right trade at
 * this size: the lead card and the article page both show it whole, and a
 * letterboxed picture marooned in a small card reads as one that failed to
 * load. */
.post:not(.post-lead).post-tall .post-shot {
  aspect-ratio: 4 / 3;
  height: auto;
}

.post:not(.post-lead).post-tall .post-shot img {
  object-fit: cover;
  object-position: center top;
}

/* A scrim, because the author supplies the pictures and a white jersey
 * behind white text is unreadable. Sized to the label rows rather than the
 * whole image so it does not grey the picture out. */
.post-over {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-start;
  padding: var(--s3);
  background: linear-gradient(
    to bottom,
    rgb(0 0 0 / 55%) 0%,
    rgb(0 0 0 / 0%) 30%,
    rgb(0 0 0 / 0%) 62%,
    rgb(0 0 0 / 62%) 100%
  );
}

.post-over time {
  font: 600 var(--t-micro)/1 var(--sans);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--n0);
}

/* The text header a card gets when there is no picture. Same information,
 * same order, so a mixed feed reads as one thing. */
.post-strap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s4) var(--s4) 0;
}

.post-strap time {
  font: 600 var(--t-micro)/1 var(--sans);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.post-tag {
  display: inline-block;
  padding: var(--s1) var(--s2);
  font: 700 var(--t-micro)/1.2 var(--sans);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: var(--s-ground);
  color: var(--n7);
  border-radius: var(--r-sm);
}

/* On a text card there is no photo to sit on, so the pill becomes an outline
 * in the accent rather than a solid crimson block — the same weight of signal
 * without shouting on a white background. */
/* Solid, not outlined. An outlined chip at 10px on paper is nearly invisible
 * -- it was reading as grey text rather than as a label. Forest on the text
 * card, crimson over a photograph, so the two never compete. */
.post-tag.flat {
  background: var(--a-deep);
  color: var(--n0);
  border: none;
}

.post-text {
  display: flex;
  flex-direction: column;
  padding: var(--s4);
}

/* A card title is the thing people scan, so it is set a size up and heavier.
 * It was var(--t-read) -- the same size as the standfirst beneath it -- which
 * is why nothing on the card read as the headline. */
.post-text h3 {
  margin: 0;
  font: 700 var(--t-h3)/1.25 var(--serif);
  letter-spacing: -0.016em;
  color: var(--ink);
  text-transform: none;
}

.post-stand {
  margin: var(--s2) 0 0;
  font-size: var(--t-fine);
  line-height: 1.55;
  color: var(--ink-2);
}

/* `margin-top: auto` inside the flex column pins the byline to the bottom of
 * the card rather than letting it sit under the standfirst. A card with a
 * picture and a card without are different heights, and in a row they stretch
 * to match — without this the two bylines land at different heights and the
 * row reads as misaligned rather than as two cards of different kinds. */
.post-by {
  margin: auto 0 0;
  padding-top: var(--s3);
  font-size: var(--t-micro);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.post-hit:hover h3 { color: var(--a-deep); }

/* --- the featured card -------------------------------------------------- */

/* The top row: two stories across, not one across the whole column.

   A single full-width lead asked the artwork to stretch to the full column
   width. The images are uploaded at a fixed size, so the browser scaled
   every one of them up and the quality went at exactly the place a reader
   looks first. Two across halves the width each image has to cover.

   The headline shrank with it, and that is the layout doing its job rather
   than a separate decision. A full-width card invites a --t-h1 headline;
   --t-h1 does not fit in half a column, so the type has to be honest about
   how much room one story has actually earned. */
.post-top {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s5);
  margin-bottom: var(--s5);
}

.post-lead { margin-bottom: 0; }
.post-lead .post-text { padding: var(--s4); }

.post-lead .post-text h3 {
  font-size: var(--t-h3);
  line-height: 1.2;
  letter-spacing: -0.012em;
}

.post-lead .post-stand {
  font-size: var(--t-read);
  line-height: 1.5;
  max-width: 62ch;
}

/* --- the grid ----------------------------------------------------------- */

.post-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s5);
}

.feed-wide .post-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* --- the sidebar -------------------------------------------------------- */

.feed-side {
  display: flex;
  flex-direction: column;
  gap: var(--s5);
  position: sticky;
  top: var(--s4);
}

.side-card {
  background: var(--bg-raised);
  border: 1px solid var(--rule);
  border-radius: 10px;
  padding: var(--s4);
}

/* The card that sells the season pass.
 *
 * It replaces a list of twenty-five tool links, which was a wall of 11px grey
 * and the least-read thing on the site. Inverted into the deep accent so it
 * reads as a statement rather than another panel — the only inverted element
 * above the fold, which is what makes it carry.
 *
 * The price is never written here or in the template. It comes from
 * `billing.PRODUCTS`, which is the same literal Stripe is charged from, so
 * the card cannot advertise a figure the checkout disagrees with.
 *
 * No class or id here contains ad / ads / advert / sponsor / promo / banner.
 * A blocker's default list matches those as substrings and would hide this
 * before anyone saw it — which on the one card that earns money is the
 * expensive place to learn it. */
.side-card.pitch {
  background: var(--a-deep);
  border-color: var(--a-deep);
  color: var(--n1);
}

.side-card.pitch > h2 { color: var(--n0); }
.side-card.pitch .side-lede { color: var(--a-ink); }

.pitch-points {
  margin: 0 0 var(--s4);
  padding: 0;
  list-style: none;
  border-top: 1px solid rgb(0 0 0 / 16%);
}

.pitch-points li {
  position: relative;
  padding: var(--s2) 0 var(--s2) var(--s4);
  border-bottom: 1px solid rgb(0 0 0 / 16%);
  font-size: var(--t-fine);
  line-height: 1.45;
  color: var(--n2);
}

/* A rule rather than a bullet glyph: it lines up at any font size and does
 * not inherit a list marker's colour, which on an inverted card is the thing
 * that usually goes wrong. */
.pitch-points li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1.05em;
  width: 10px;
  height: 2px;
  background: var(--a-ink);
}

.side-card.pitch .btn {
  width: 100%;
  justify-content: center;
  background: var(--n0);
  color: var(--a-deep);
  border-color: var(--n0);
  font-weight: 700;
}

.side-card.pitch .btn:hover { background: var(--a-lite); border-color: var(--a-lite); }

/* The price, set large and in the serif, because it is the argument.
 *
 * $14 for a season is the strongest thing this card has to say and burying
 * it in body copy wastes it. The qualifier rides alongside at body size so
 * the number is never read without its term. */
.pitch-cost {
  margin: 0 0 var(--s2);
  font: 400 var(--t-h1)/1 var(--serif);
  letter-spacing: -0.02em;
  color: var(--n0);
}

.pitch-cost em {
  margin-left: var(--s2);
  font: 600 var(--t-fine)/1 var(--sans);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  font-style: normal;
  color: var(--a-ink);
}

.pitch-alt {
  margin: var(--s3) 0 0;
  font-size: var(--t-micro);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-align: center;
  color: var(--a-ink);
}

.side-card > h2 {
  margin: 0 0 var(--s2);
  font: 700 var(--t-fine)/1.3 var(--sans);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--ink);
}

.side-card > h3 {
  margin: var(--s4) 0 var(--s1);
  font: 700 var(--t-micro)/1.3 var(--sans);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.side-lede {
  margin: 0 0 var(--s3);
  font-size: var(--t-fine);
  line-height: 1.55;
  color: var(--ink-2);
}

.side-list { margin: 0; padding: 0; list-style: none; }

.side-list a {
  display: block;
  padding: var(--s1) 0;
  font-size: var(--t-fine);
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}

.side-list a:hover { color: var(--a-deep); border-bottom-color: var(--a-lite); }

/* --- the reading page --------------------------------------------------- */

.piece { max-width: 68ch; margin: 0 auto; padding-top: var(--s7); }

.piece-head { margin-bottom: var(--s5); }

.piece-meta {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin: 0 0 var(--s4);
  font: 600 var(--t-micro)/1 var(--sans);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.piece-head h1 {
  margin: 0;
  font: 600 var(--t-h1)/1.15 var(--serif);
  letter-spacing: -0.022em;
  color: var(--ink);
  text-transform: none;
}

.piece-stand {
  margin: var(--s4) 0 0;
  font-size: var(--t-lede);
  line-height: 1.45;
  color: var(--ink-2);
  font-family: var(--serif);
}

/* The article page does NOT crop. A card is a thumbnail and has to be one
 * shape; the piece itself is the one place the picture the author chose can
 * be seen as they made it, so it keeps its own proportions and is capped by
 * height instead. A tall graphic that filled the screen would push the
 * opening paragraph below the fold, which is the only thing the cap is for. */
.piece-shot {
  display: block;
  max-width: 100%;
  max-height: 72vh;
  width: auto;
  height: auto;
  margin: 0 auto var(--s6);
  border-radius: var(--r-sm);
  background: var(--bg-sunk);
}

/* The body is the one place on this site set for continuous reading rather
 * than for scanning a table, so it gets the serif at a real reading size and
 * a measure that stops well short of the column. */
.piece-body {
  font: 400 var(--t-read)/1.7 var(--serif);
  color: var(--ink);
}

.piece-body > :first-child { margin-top: 0; }
.piece-body p { margin: 0 0 var(--s4); }

.piece-body h2 {
  margin: var(--s7) 0 var(--s3);
  font: 600 var(--t-h3)/1.25 var(--serif);
  letter-spacing: -0.012em;
  color: var(--ink);
  text-transform: none;
}

.piece-body h3 {
  margin: var(--s6) 0 var(--s2);
  font: 700 var(--t-fine)/1.3 var(--sans);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--ink-2);
}

.piece-body a { color: var(--a-deep); text-decoration-color: var(--a-lite); }
.piece-body a:hover { color: var(--a-mid); }

.piece-body ul, .piece-body ol { margin: 0 0 var(--s4); padding-left: var(--s5); }
.piece-body li { margin-bottom: var(--s2); }

.piece-body blockquote {
  margin: var(--s5) 0;
  padding-left: var(--s4);
  border-left: 2px solid var(--a-lite);
  color: var(--ink-2);
  font-style: italic;
}

.piece-body code {
  font: 400 0.88em/1.4 var(--mono);
  background: var(--bg-sunk);
  padding: 0.1em 0.3em;
  border-radius: var(--r-sm);
}

.piece-body pre {
  margin: 0 0 var(--s4);
  padding: var(--s4);
  overflow-x: auto;
  background: var(--bg-sunk);
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
}

.piece-body pre code { background: none; padding: 0; font-size: var(--t-fine); }

.piece-body img { max-width: 100%; height: auto; border-radius: var(--r-sm); }

/* Tables get to be wider than the prose.
 *
 * The body is set to a 68ch reading measure, which is right for sentences and
 * too narrow for a seven-column table — the first one through here wrapped
 * "Nickeil Alexander-Walker" onto two lines and made its row double height.
 * A table is scanned rather than read, so it is allowed to break the measure
 * symmetrically and sit wider than the text around it.
 *
 * Markdown emits a bare <table> with nothing to wrap it in, so the scroll has
 * to live on the element itself — hence `display: block` at narrow widths
 * rather than a container. */
.piece-body table {
  width: calc(100% + 2 * var(--s7));
  margin: 0 calc(-1 * var(--s7)) var(--s5);
  border-collapse: collapse;
  font: 400 var(--t-body)/1.5 var(--sans);
}

.piece-body table td:first-child,
.piece-body table th:first-child { white-space: nowrap; }

/* Numeric columns read better right-aligned and in a column of equal-width
 * digits, so 141 and 12 line up on the units. */
.piece-body table td:not(:first-child):not(:nth-child(2)) {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.piece-body table th:not(:first-child):not(:nth-child(2)) { text-align: right; }

.piece-body th, .piece-body td {
  padding: var(--s2) var(--s3);
  border-bottom: 1px solid var(--rule);
  text-align: left;
}

.piece-body th { font-weight: 700; border-bottom-color: var(--ink); }

.piece-body hr { margin: var(--s7) 0; border: none; border-top: 1px solid var(--rule); }

.piece-more { max-width: 1100px; margin: var(--s9) auto 0; }

/* --- narrower ----------------------------------------------------------- */

/* The sidebar drops under the feed before the cards go single-column: at
 * ~1080px the feed column is still wide enough for two cards, and collapsing
 * both at once wastes most of the width. */
@media (max-width: 1080px) {
  .feed-wrap { grid-template-columns: minmax(0, 1fr); gap: var(--s6); }
  .feed-side { position: static; }
  .feed-wide .post-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 720px) {
  .post-grid, .feed-wide .post-grid, .post-top { grid-template-columns: minmax(0, 1fr); gap: var(--s4); }
  /* No room for two columns on a phone, so a tall card stacks like any other
     and the picture is capped so it does not fill the screen before a word of
     the headline. */
  .post-lead.post-tall .post-hit { flex-direction: column; }
  .post-lead.post-tall .post-shot { align-self: auto; width: 100%; }
  .post-lead.post-tall .post-shot img { width: 100%; max-height: 440px; }
  .post-lead.post-tall .post-body { justify-content: flex-start; padding: 0; }
  .post-lead.post-tall .post-strap { padding: var(--s4) var(--s4) 0; }
  .post-lead.post-tall .post-text { padding: var(--s4); }
  .post-lead .post-text { padding: var(--s4); }
  .post-lead .post-text h3 { font-size: var(--t-h3); }
  .post-lead .post-stand { font-size: var(--t-body); }
  .piece { padding-top: var(--s5); }
  .piece-meta { flex-wrap: wrap; gap: var(--s2); }
  /* No room to break the measure on a phone, so the table scrolls instead of
     squeezing nine columns into 375px. */
  .piece-body table {
    display: block;
    width: 100%;
    margin-inline: 0;
    overflow-x: auto;
    white-space: nowrap;
  }
}

/* --- print --------------------------------------------------------------- */

@media print {
  .feed-side, .post-shot, .post-over { display: none; }
  .post, .side-card { border: none; }
  .piece-body { font-size: 11pt; }
}
