/* NILSportsTech home, phone build.

   Everything here is behind a max-width query and this file loads last, so the
   desktop page cannot be reached from it. That is the point of the split: the
   shared sheets had grown mobile rules inline, and every edit to one was an
   edit to the other -- a stray value in a base rule squashed the desktop Ask
   bar at every width before anyone noticed.

   Rules for the directory and Watch views live here too, even though their
   desktop styles sit in nilsportstech-nil-apps-view.css. Load order is what
   decides: this file comes after both, so a selector of equal weight wins.

   The breakpoint is 760px, and it lives only here -- no script branches on it,
   so this file is the single place it is written down.

   Depends on the tokens in nilsportstech-network-v2.css. Loads after it. */

@media (max-width: 760px) {
  :root { --gutter: 16px; }

  /* The ticker is decoration at this width -- it stays, because it is the one
     thing on the page that says the network is live, but thinner and quieter
     so it costs less of a short screen. */
  /* The page now reaches under the notch (viewport-fit=cover), so whatever sits
     at the very top has to hold itself clear of the status bar. The ticker is
     first, so it carries the inset for the whole page. */
  .nst-ticker { height: calc(28px + env(safe-area-inset-top)); padding-top: env(safe-area-inset-top); }
  /* The shell reserves exactly what the band takes. Desktop pads 36px, which is
     the desktop height -- on a phone the band is 28 plus the notch inset, so a
     flat 36 left a white sliver under it. */
  .nst { padding-top: calc(28px + env(safe-area-inset-top)); }
  /* On the views where the ticker is hidden, the header inherits the job. */
  body:has(.nst-watchview:not([hidden])) .nst-head { padding-top: calc(12px + env(safe-area-inset-top)); }
  .nst-ticker__run { font-size: 12px; gap: 24px; padding-left: 16px; }

  /* Header: brand and actions share the top line, the ask field takes the one
     under it. Stacked at full width the three rows ate 570px before any
     content -- more than half the screen. */
  /* The buttons are sized first and the brand takes the rest.
   *
   * It used to be `auto 1fr`: the brand column was floored at the full lockup
   * -- burger, circle, NILSPORTSTECH, strapline -- and the actions got only
   * what survived. On a 390px screen that left 138px for two buttons that need
   * more, so LOG IN was drawn over the end of the wordmark.
   *
   * Reversing which column is elastic fixes it at the source: `auto` on the
   * actions means "as wide as the two buttons actually are", and the brand's
   * `minmax(0, 1fr)` can shrink to whatever is left without ever pushing into
   * them. */
  .nst-head { grid-template-columns: minmax(0, 1fr) auto; gap: 12px 10px; padding: 12px var(--gutter); }
  /* Brand and the two actions share the top line; the ask field takes the one
     under it. The rows are set explicitly because the ask form comes first in
     the DOM -- left to auto-placement it lands above the buttons, which puts
     sign-in below the search box rather than at the top of the page. */
  /* min-width: 0 so the flex row inside can actually shrink -- without it the
     lockup keeps its full width no matter what the grid column offers, which
     is how the wordmark ended up running under LOG IN. */
  .nst-brand { grid-column: 1; grid-row: 1; min-width: 0; }
  /* The wordmark is the logo: it never wraps, never ellipses and never shrinks.
     Everything else in the row gives way around it. */
  .nst-brand__text { min-width: 0; }
  .nst-brand__word { white-space: nowrap; flex-shrink: 0; }
  .nst-brand__tag { white-space: nowrap; }
  .nst-brand__word { font-size: 18px; letter-spacing: .10em; }
  /* --mark drives width, height and both clamps at once, so the circle cannot
     be inflated past the size the phone layout sets for it. */
  .nst-brand__mark { --mark: 30px; font-size: 15px; }
  /* Sits under a wordmark that is 18px here rather than 23px, so the strapline
     comes down with it -- at its desktop size it read as a second line of equal
     weight instead of a note beneath the name. */
  .nst-brand__tag { font-size: 7px; letter-spacing: .14em; }
  /* The buttons state the width they need instead of accepting whatever is
     left. `min-width: 0` let the actions column be squeezed to nothing while
     the brand kept its full lockup, so LOG IN was laid over the end of the
     wordmark rather than sitting beside it. min-content is the two buttons at
     their own size -- the brand column gives way to it. */
  /* Never squeezed: the column is `auto`, so this is the width the two buttons
     ask for and the brand fits around it. */
  .nst-head__actions { grid-column: 2; grid-row: 1; justify-content: flex-end; gap: 7px; flex-shrink: 0; }
  .nst-head__actions .nst-btn { padding: 9px 13px; font-size: 12px; letter-spacing: .06em; }
  .nst-ask { grid-column: 1 / -1; grid-row: 2; padding: 11px 16px; gap: 10px; }
  .nst-ask__label { font-size: 13px; letter-spacing: .12em; }
  /* The 900px rule hides the placeholder to stop it overflowing. There is room
     for a short one here, and an empty field reads as broken -- but the full
     sentence still does not fit, so it ellipses rather than running under the
     edge of the pill. 16px, or iOS zooms the page when the field takes focus. */
  .nst-ask__input { font-size: 16px; text-overflow: ellipsis; }
  .nst-ask__input::placeholder { color: #5c5c64; }
  .nst-ask__key { display: none; }

  /* Nav becomes a drawer. A scrolling strip put the current view half off the
     left edge and hid the rest behind a swipe nobody was told about; four
     destinations fit a list, and it costs no row of its own.

     Hard against the left edge, ahead of the mark: the menu is the first thing
     on the line, so the thumb finds it without hunting. Its own left padding
     supplies the gutter, which is why the box starts at 0 -- an icon centred in
     a 40px square would otherwise sit visibly inboard of the type below it. */
  .nst-nav__burger {
    display: inline-flex; align-items: center; justify-content: flex-start;
    width: calc(var(--gutter) + 22px); height: 44px;
    margin-left: calc(var(--gutter) * -1); padding-left: var(--gutter);
    flex: 0 0 auto; cursor: pointer; color: var(--text);
  }
  .nst-nav__bars { position: relative; width: 18px; height: 2px; background: currentColor; flex: 0 0 auto; }
  .nst-nav__bars::before, .nst-nav__bars::after {
    content: ""; position: absolute; left: 0; width: 18px; height: 2px; background: currentColor;
    transition: transform .18s ease, opacity .18s ease;
  }
  .nst-nav__bars::before { top: -6px; }
  .nst-nav__bars::after { top: 6px; }
  /* Open: the bars cross. */
  /* The burger lives inside the header now, so these reach it as a descendant
     of a following sibling rather than as a sibling itself. */
  .nst-nav__switch:checked ~ .nst-head .nst-nav__bars { background: transparent; }
  .nst-nav__switch:checked ~ .nst-head .nst-nav__bars::before { transform: translateY(6px) rotate(45deg); }
  .nst-nav__switch:checked ~ .nst-head .nst-nav__bars::after { transform: translateY(-6px) rotate(-45deg); }

  /* Closed by default; the checkbox opens it. Rows, not a scrolling row. */
  .nst-nav {
    display: none; flex-direction: column; align-items: stretch;
    gap: 0; padding: 0 0 8px; overflow: visible;
  }
  .nst-nav__switch:checked ~ .nst-nav { display: flex; }
  .nst-nav > span[data-view], .nst-nav > a {
    padding: 14px var(--gutter); border-bottom: 1px solid var(--line);
  }
  .nst-nav .is-active { border-bottom: 1px solid var(--line); padding-bottom: 14px; }
  /* Out of the flow entirely, not just invisible: hidden it still held its
     width, so the drawer ended on an empty row. */
  .nst-nav__meta { display: none; }

  /* Hero: full-bleed, with the rail stacked under it rather than beside it.

     Both the columns and the rows have to be restated. The base rule sets a
     single fixed-height row, and a later ":has(.is-portrait)" rule re-asserts
     the two-column shape at a specificity this block cannot beat by class
     alone -- so a portrait hero kept its 300px side column, and the rail was
     drawn over the title and caption instead of below them. */
  .nst-hero,
  .nst-hero:has(.nst-hero__stage.is-portrait) {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto;
  }
  .nst-hero__stage, .nst-hero:has(.nst-hero__stage.is-portrait) .nst-hero__stage {
    height: min(74vh, 560px); border-radius: 0;
  }
  /* A portrait stage holds the clip and the copy in one grid: 360px of film
     over a 214px body is 574px, which did not fit the 560px above -- so the
     stack hung 8px past the top of the frame and 6px past the foot, and the
     hero read as sitting crooked in its own box rather than centred. The frame
     takes the height its contents actually come to. */
  .nst-hero:has(.nst-hero__stage.is-portrait) .nst-hero__stage.is-portrait {
    height: auto; min-height: min(74vh, 560px);
  }
  /* The block rounds on all four corners, and the rail stacked underneath it
     fills the bottom two -- so the frame read as rounded on top and cut off at
     the foot. The rail carries the bottom pair now, and the hero keeps the top,
     which makes the two read as one card rather than two boxes. */
  .nst-hero { border-radius: var(--r); }
  /* The blurred backdrop runs on phones too -- same treatment as the desktop
     stage. A portrait clip never quite fills the frame, and flat black around it
     reads as letterboxing; the wash makes the edges belong to the clip. */

  /* The replies run in that wash, the way they do on a wide screen -- it is the
     one part of the frame with nothing in it. The desktop rule hides them below
     1200px and sizes the column off (100% - 620px) / 2, which goes negative
     here; this measures the strip that is actually beside a 9:16 clip in a
     398px stage, about 98px.

     That width is the whole design. Everything is scaled to read in it: no
     avatars, the handle on its own line above the remark, small type and tight
     leading. Two lines of remark then it clips -- a long comment truncates
     rather than growing a column taller than the clip beside it. */
  .nst-hero__chat {
    display: flex;
    left: 8px; right: auto; bottom: auto;
    top: 50%; transform: translateY(-50%);
    /* The strip beside a 9:16 clip in this stage, less a margin off each edge.
       No min-width: a floor wider than the strip pushes the column back over
       the footage, which is what it exists to avoid -- at 390px the gutter is
       82px and an 84px floor overran the clip by two pixels. */
    width: calc((100% - 203px) / 2 - 14px);
    max-height: 300px; gap: 8px;
    /* The base rule clips to hidden so a long run of remarks cannot spill up the
       frame. The figures hang below this box, and hidden ate them. */
    overflow: visible;
  }
  /* A landscape clip fills the stage, so there is no strip to sit in -- those
     keep the desktop behaviour of running over the footage's bottom-left. */
  .nst-hero__stage:not(.is-portrait) .nst-hero__chat {
    top: auto; transform: none; bottom: 150px;
    width: min(190px, 52%);
  }
  .nst-hero__chat[hidden] { display: none; }
  /* No portrait: at this width a 26px avatar and a 10px gap is a third of the
     column, and the handle already says who is talking. */
  .nst-hero__msg { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .nst-hero__msg-avatar { display: none; }
  .nst-hero__msg-body { font-size: 10px; line-height: 1.3; }
  .nst-hero__msg-who { font-size: 8px; letter-spacing: .06em; }
  .nst-hero__msg-text {
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden;
  }

  .nst-hero__body { left: var(--gutter); right: var(--gutter); bottom: 26px; gap: 14px; }
  .nst-hero__title { font-size: clamp(30px, 8.6vw, 40px); -webkit-line-clamp: 3; }
  .nst-hero__caption { font-size: 15px; line-height: 1.45; -webkit-line-clamp: 3; display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
  /* The kicker ran off the screen: section, rule, avatar, name and sport on one
     line needs ~460px. The rule goes and the line wraps. */
  .nst-hero__kicker { flex-wrap: wrap; gap: 8px 10px; font-size: 10px; }
  .nst-hero__kicker .nst-rule { display: none; }
  .nst-hero__by { min-width: 0; display: flex; align-items: center; gap: 8px; }

  /* Plays, likes and replies come back. The desktop sheet hides them below
     1200px because they live in the empty column beside a portrait clip, and
     narrower than that they land on the caption -- which is exactly what they
     did here.

     The stage becomes the column instead of the frame they float in: the copy
     and the figures are both bottom-anchored siblings, so making the stage a
     flex column that ends at the bottom stacks them in DOM order -- counts,
     then body -- with no fixed offset to guess and nothing to keep in step as
     the headline wraps to two lines or three. */
  /* align-items, not justify-items. The centring in the base sheet is written
     for a grid, and this stage is a column flexbox -- justify-items and
     align-content do nothing here, so the clip fell to the start of the cross
     axis and sat 107px from the left edge with 267px of empty frame on its
     right. In a column flexbox the cross axis is the horizontal one.

     justify-content stays flex-end: on this axis that is the vertical, which is
     what holds the copy against the foot of the frame. */
  .nst-hero__stage {
    display: flex; flex-direction: column; justify-content: flex-end;
    align-items: center;
    padding-bottom: 26px;
  }
  /* Centring shrinks every child to its content width, and these three are
     meant to span the frame: the wash, the copy under the clip, and the counts. */
  .nst-hero__scrim, .nst-hero__body, .nst-hero__counts { align-self: stretch; }
  /* inset:auto has to cover the landscape case too. The base sheet pins the
     counts with "left:auto; right:var(--gutter)" when the clip fills the frame,
     and those offsets still shift a relatively positioned box -- so the figures
     floated out into the middle of the footage instead of sitting under the
     buttons with everything else. */
  .nst-hero__body { position: relative; inset: auto; }

  /* The figures go in the right-hand strip, centred on the clip.

     The replies own the left one and grow as remarks arrive, so anything
     sharing that column gets pushed around by every new line. The right strip is
     empty and stays empty, which is what makes this position hold still.

     Stated for both clip states so nothing moves when "is-portrait" lands after
     the video reports its size -- that late switch is what made the line flash
     across the frame on load.

     Stacked, not one line: the strip is about 98px, which will not hold
     "9.5K plays  522 likes" side by side. */
  .nst-hero__counts,
  .nst-hero__stage:not(.is-portrait) .nst-hero__counts,
  .nst-hero__stage.is-portrait .nst-hero__counts {
    position: absolute; z-index: 3;
    left: auto; right: 8px; bottom: auto;
    top: 50%; transform: translateY(-50%);
    width: calc((100% - 203px) / 2 - 14px);
    display: flex; flex-direction: column; align-items: flex-end;
    text-align: right; max-width: none; padding: 0; margin: 0;
    gap: 3px; font-size: 10px;
    justify-self: end; pointer-events: auto;
  }
  /* Each figure on its own row, and the gap is what separates it from its label
     -- a space between two flex items collapses away. */
  .nst-hero__counts > span { display: flex; align-items: baseline; gap: .34em; }
  .nst-hero__counts[hidden] { display: none; }
  .nst-hero__body { width: auto; padding: 0 var(--gutter); }
  /* Thumb-sized without making the line taller than the type around it. */
  .nst-hero__talk { min-height: 28px; display: inline-flex; align-items: center; }
  .nst-hero__actions { gap: 8px; }
  .nst-hero__actions .nst-btn { padding: 13px 20px; font-size: 13px; letter-spacing: .10em; }
  .nst-btn--orange { padding: 13px 22px; font-size: 14px; }

  /* "Around the network" stops being a side rail and becomes a rail you swipe,
     so it keeps its place beside the hero rather than adding eight rows of
     scroll between the hero and the first section. */
  .nst-upnext { border-top: 1px solid var(--line); height: auto; }
  .nst-upnext [data-upnext] {
    display: flex; gap: 10px; overflow-x: auto; overflow-y: hidden;
    padding: 12px var(--gutter); scrollbar-width: none;
    scroll-snap-type: x mandatory; scroll-padding-left: var(--gutter);
    -webkit-overflow-scrolling: touch;
  }
  .nst-upnext [data-upnext]::-webkit-scrollbar { display: none; }
  /* min-width:0 or the basis is a suggestion. A flex item will not shrink
     below the intrinsic width of its content, so a long headline blew the
     tile out to 564px against a 200px basis and the rail ran off both edges
     of the screen. The title is already clamped and ellipsised; this lets it
     be. */
  .nst-upnext__item {
    flex: 0 0 200px; min-width: 0; flex-direction: column; align-items: stretch;
    gap: 8px; padding: 0; scroll-snap-align: start;
  }
  .nst-upnext__item.is-playing { background: none; }
  .nst-upnext__item.is-playing::before { inset: auto 0 -6px 0; width: auto; height: 2px; }
  .nst-upnext__thumb { width: 100%; height: 112px; }
  /* One line, cut with dots. Clamped to two, a long headline wrapped and pushed
     its byline and play count a line lower than the tile beside it -- so a row
     of cards had their footers at three different heights. Every tile is the
     same height now and the rail reads as a row. */
  .nst-upnext__title {
    font-size: 16px;
    display: block; -webkit-line-clamp: none;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    /* And the two-line floor goes with the clamp. The desktop rule reserves
       calc(2 * 1.08em) so a wrapped headline cannot push its byline lower than
       the tile beside it -- but nothing wraps here, so a one-line title like
       "Week 2 highlights" held an empty second line and left a gap above the
       athlete's name. */
    min-height: 0;
  }
  .nst-upnext__head { padding: 2px var(--gutter) 0; }
  /* "Scroll for the whole network" is a desktop instruction -- on a phone the
     rail already scrolls and the page below it is obvious. */
  .nst-upnext__foot { display: none; }

  /* The transport bar docks to the bottom of the viewport. Thumb, time and the
     up-next line go: what is left is what a phone player needs -- what is
     playing, a scrubber, play/pause and sound. */
  .nst-playing {
    /* Docked, full width. A floating inset pane was tried and the docked bar is
       better: the transport is the one thing that must never move or be scrolled
       past, and an edge-to-edge strip at the bottom of the screen reads as part
       of the device rather than as a card laid on the page.

       The glass surface below is what keeps it from being page furniture. */
    position: fixed; inset: auto 0 0 0; z-index: 40;
    grid-template-columns: 1fr auto auto;
    grid-template-areas: "track time time" "title toggle sound" "next next next";
    gap: 6px 12px; padding: 8px var(--gutter) calc(8px + env(safe-area-inset-bottom));
    /* Glass, not a panel.

       This was rgba(.94) over a 12px blur -- at that opacity the blur has almost
       nothing to do, so the bar read as a solid slab bolted to the bottom of the
       film. The point of sitting over the clip is that you can still see the clip.

       Half-transparent with a deep blur and a saturation lift, which is what
       stops a dark bar over dark footage going muddy: the colour underneath comes
       through rather than being averaged into grey. The hairline goes light-on-
       dark instead of a border colour, so the top edge reads as the edge of a
       pane and not as a rule drawn across the page. */
    background: rgba(11, 11, 14, .46);
    backdrop-filter: blur(30px) saturate(1.7);
    -webkit-backdrop-filter: blur(30px) saturate(1.7);
    /* White at low alpha rather than a grey border colour, so the top edge
       catches light like the rim of a pane instead of drawing a rule across the
       film. */
    border-top: 1px solid rgba(255, 255, 255, .13);
    /* Cast upward onto the film, so the bar sits over the clip rather than
       being flush with it, plus the sheen along its top edge. */
    box-shadow:
      0 -14px 36px rgba(0, 0, 0, .44),
      inset 0 1px 0 rgba(255, 255, 255, .10);
  }
  /* No glass without the blur.

     A browser that cannot blur would otherwise leave the bar at 52% over moving
     film, with the scrubber and the clock unreadable against whatever passed
     underneath. Those get the solid panel back. */
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .nst-playing { background: rgba(11, 11, 14, .95); }
  }
  .nst-playing__thumb { display: none; }
  /* The clock stays. How far through a clip is and how long it runs is the
     thing a scrubber cannot say on its own, and hiding it left a phone with a
     bar it could not read -- the same rule that was hiding UP NEXT. It rides at
     the end of the scrubber's row rather than taking one of its own. */
  .nst-playing__time { grid-area: time; justify-self: end; font-size: 10px; white-space: nowrap; }
  /* What is coming next, on its own row under the transport. It was hidden
     outright with the thumbnail and the clock, so a phone rolled from one clip
     into another with no warning and no way to stop it -- the countdown, PLAY
     NOW and CANCEL were all desktop-only. The row is hidden until the clip is
     actually ending, so it costs nothing until it has something to say. */
  .nst-playing__next {
    grid-area: next; display: flex; align-items: center; gap: 8px;
    flex-wrap: wrap; padding-top: 2px;
  }
  .nst-playing__next[hidden] { display: none; }
  .nst-playing__nextlabel { font-size: 8px; }
  /* The title takes what is left, so PLAY NOW and CANCEL keep their room. */
  .nst-playing__nexttitle {
    flex: 1; min-width: 0; font-size: 10px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .nst-playing__skip, .nst-playing__cancel { font-size: 9px; padding: 5px 9px; }
  /* The scrubber, on glass.

     The base track is a 16% white film, which is the grey line that shows through
     a half-transparent pane. Slightly heavier and taller so it reads as a channel
     cut into the glass, with the played part in the accent rather than in ink. */
  .nst-playing__track {
    grid-area: track;
    height: 5px;
    background: rgba(255, 255, 255, .18);
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, .28);
  }
  /* The played run gets a glow, so the accent looks lit rather than painted. */
  .nst-playing__track > span {
    box-shadow: 0 0 10px rgba(255, 74, 21, .55);
  }
  .nst-playing__text { grid-area: title; }
  .nst-playing__title { font-size: 15px; }
  .nst-playing__btn:nth-of-type(1) { grid-area: toggle; }
  .nst-playing__btn:nth-of-type(2) { grid-area: sound; }
  /* Buttons on glass. The base fill is a 12% white film, which disappears
     against a pane that is already half-transparent -- so these get a little
     more of it, their own hairline, and a blur of their own. That last part is
     what makes a control read as sitting *on* the pane rather than cut out of
     it. */
  .nst-playing__btn {
    width: 40px; height: 40px; font-size: 15px;
    /* White films, not grey fills. A grey button on a glass pane is the one
       thing that makes the whole control look like a widget. */
    background: rgba(255, 255, 255, .14);
    border: 1px solid rgba(255, 255, 255, .16);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .14);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
  }
  /* Play is the one filled control: the accent, so the pane has one bright thing
     on it and the eye knows where to press. */
  .nst-playing__btn[data-playing-toggle] {
    background: var(--orange);
    border-color: transparent;
    color: var(--ink);
    box-shadow: 0 4px 16px rgba(255, 74, 21, .40);
  }
  /* The step arrows stay ghosted -- they are the quiet pair either side of
     play, and giving them a pane of their own would make three equal buttons. */
  .nst-playing__btn--step {
    background: none; border-color: transparent; backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  /* The docked bar is out of flow, so the page has to reserve its height at the
     bottom or the bar sits on top of the last thing on the page -- which is the
     footer. Reserved on the page rather than the footer's own padding: the
     footer sets its text against its bottom edge, so padding there moves the
     edge without moving the type off the bar. It is only reserved while a bar
     is actually docked -- [hidden] means nothing is playing. */
  .nst:has(.nst-playing:not([hidden]))  {
    padding-bottom: calc(80px + env(safe-area-inset-bottom));
  }

  /* Sections. The heads were taking three lines before any content: a 32px
     title, the count beside it, and SEE ALL wrapped onto a row of its own.

     Two lines now -- title with SEE ALL opposite it, count underneath. The title
     comes down to 24px, which is still the largest type in the block but no
     longer the size of the hero headline it sits below. */
  .nst-section { padding-top: 32px; }
  .nst-section--roster { margin-top: 32px; padding: 28px var(--gutter) 36px; }
  .nst-section__head {
    display: grid; grid-template-columns: 1fr auto; align-items: baseline;
    gap: 2px 12px; padding-bottom: 10px;
  }
  .nst-section__head h2, .nst-display { font-size: 24px; grid-column: 1 / -1; grid-row: 1; }
  /* Second row carries both: the count on the left, the way out on the right.
     Beside the headline they were two different kinds of thing sharing a
     baseline, and the link floated with nothing to sit against. Together on the
     small-type line they read as one row of furniture under the title. */
  .nst-section__meta { grid-column: 1; grid-row: 2; font-size: 9px; letter-spacing: .14em; }
  .nst-section__all { grid-column: 2; grid-row: 2; margin-left: 0; font-size: 9px; letter-spacing: .14em; white-space: nowrap; justify-self: end; }
  /* Two metas in one head -- the count and "SWIPE →" -- land in the same cell
     and overlap. The second one goes to the right of the row, where SEE ALL
     sits on the other sections. */
  .nst-section__meta ~ .nst-section__meta { grid-column: 2; justify-self: end; }

  /* The big head wraps its eyebrow and title in a div, so there is no h2 for the
     row-1 rule to catch. Pin the block to row 1 or the link, which is placed on
     row 2, drops below it instead of sitting alongside. */
  .nst-section__head--big > div { grid-column: 1; grid-row: 1; }
  .nst-section__head--big .nst-section__all { grid-row: 1; align-self: end; }
  .nst-section__head--big .nst-display { font-size: 24px; }
  .nst-stat { padding: 14px var(--gutter); }
  .nst-stat__value { font-size: 34px; }

  /* Recruiting. This is what broke worst: the two-column grid held at 390px, so
     the lead offer got a ~55px column and set one character per line. */
  .nst-offers { grid-template-columns: 1fr; gap: 8px; }
  /* Split, not stacked. The picture ran as a 68px thumbnail above the copy with
     the rest of that line empty -- a stamp in a corner rather than the biggest
     thing on the board. It takes a third of the card down the left and runs the
     full height of it, and the copy has the other two thirds. */
  .nst-biggest {
    grid-template-columns: 34% minmax(0, 1fr); min-height: 0; gap: 14px;
    align-items: stretch; padding: 18px 18px 20px 0;
  }
  /* Every part of the desktop rule has to be undone: it pins the picture to the
     second column, sizes it 180x180 and centres it in a grid of its own. */
  .nst-biggest__crest {
    grid-column: 1; grid-row: 1; display: block;
    width: 100%; height: 100%; min-height: 168px;
    justify-self: stretch; align-self: stretch; place-items: initial;
    /* The desktop rule gives this a 16:10 ratio, which fights the 100% height
       here and collapsed the frame to zero width on a phone. Height wins on
       this layout -- the picture sits in a fixed-height row beside the copy. */
    aspect-ratio: auto;
  }
  /* Film gets the same treatment as a still; the desktop rule only names img. */
  .nst-biggest__crest :is(img, video) { width: 100%; height: 100%; object-fit: cover; border-radius: 0; }
  .nst-biggest__body { grid-column: 2; grid-row: 1; }
  .nst-biggest__name { font-size: 26px; }
  .nst-biggest__prog { font-size: 21px; }
  .nst-biggest__title { font-size: 20px; max-width: none; }
  .nst-biggest__caption { font-size: 13px; max-width: none; }
  /* The athlete's face stays -- the picture down the left is the moment, this
     is the person it happened to. Smaller, so the name still gets its line. */
  .nst-biggest__who { gap: 9px; }
  .nst-biggest__who .nst-avatar { width: 32px; height: 32px; flex-shrink: 0; }
  .nst-programs__num { font-size: 26px; }

  /* Recruiting is the section the page is named for, and at the sizes the table
     inherited it read as a settings list -- 17px names, a 64px thumb, four lines
     of small type crammed into 100px of row. It is the same three facts, given
     the room to be the biggest thing in the block.

     Five columns at 390px truncated every name to "D..", so it is still two
     stacked lines beside the picture; what changes is the scale. */
  .nst-offerrow {
    grid-template-columns: 1fr 104px;
    grid-template-areas: "who thumb" "school thumb" "date thumb";
    gap: 6px 16px; padding: 18px 0;
  }
  /* The header row labels columns that no longer exist. */
  .nst-offerrow--head { display: none; }
  .nst-offerrow__who { grid-area: who; }
  /* What they were offered is the headline of the row, not a footnote to it. */
  .nst-offerrow__school { grid-area: school; font-size: 19px; line-height: 1.15; }
  .nst-offerrow__date { grid-area: date; font-size: 11px; }
  /* Big enough to read as a photograph rather than an icon, and square so a
     column of them lines up whatever shape the source image is. */
  .nst-offerrow__thumb { grid-area: thumb; align-self: center; width: 104px; height: 104px; }
  .nst-offerrow__tag { display: none; }
  .nst-offerrow__name { font-size: 22px; }
  .nst-offerrow__meta { font-size: 10px; }
  /* The avatar beside the name comes up with the type around it. */
  .nst-offerrow__who .nst-avatar { width: 34px; height: 34px; font-size: 13px; }

  /* Most watched: portrait frames, one per row.

     A fixed height was the wrong control. At 420px the frame was still wider
     than it was tall on a 430px screen, and a 16:9 poster dropped into it left
     black bands above and below the picture -- the card read as a horizontal
     strip with the film floating in the middle of it.

     aspect-ratio makes the frame itself vertical and lets cover crop the source
     to fill it, so the picture reaches all four edges. Height is released for
     the same reason: with a ratio set, any fixed height just fights it. */
  .nst-watched { gap: 10px; padding-top: 10px; }
  .nst-watched__col { gap: 10px; }
  .nst-watched__lead { height: auto; aspect-ratio: 4 / 5; border-radius: 22px; }
  .nst-watched__tile { flex: none; height: auto; min-height: 0; aspect-ratio: 4 / 5; border-radius: 22px; }
  .nst-watched__title { font-size: 34px; line-height: .98; }
  .nst-watched__tile .nst-watched__title { font-size: 26px; }
  .nst-watched__body { left: 20px; right: 20px; bottom: 20px; }
  .nst-watched__rank { font-size: 11px; padding: 6px 11px; }
  .nst-watched__tile .nst-watched__rank { font-size: 11px; }
  .nst-watched__time { font-size: 12px; padding: 5px 10px; }

  /* By sport runs side to side instead of stacking.

     One card per row turned a short list of sports into a long scroll of
     full-width slabs, and nothing on screen said there were more. As a rail the
     row reads at a glance and the next card is cut by the right edge -- that
     sliver is the only thing that tells a thumb it can swipe.

     The negative margin pulls the rail out past the section gutter so cards
     actually reach the screen edge; the matching padding keeps the first one
     lined up with the headline above it. 78vw leaves roughly a fifth of the
     following card showing. */
  .nst-sports {
    display: flex; grid-template-columns: none;
    gap: 10px; overflow-x: auto; scrollbar-width: none;
    margin: 0 calc(var(--gutter) * -1);
    padding: 14px var(--gutter) 2px;
    scroll-snap-type: x proximity; scroll-padding-left: var(--gutter);
    -webkit-overflow-scrolling: touch;
  }
  .nst-sports::-webkit-scrollbar { display: none; }
  .nst-sportcard {
    flex: 0 0 78vw; scroll-snap-align: start;
    aspect-ratio: 4 / 3; border-radius: 22px;
  }
  /* The 7-card desktop arrangement gives its 4th card a double row and drops
     the ratio; in a single row there are no rows to span. */
  .nst-sports:has(> :nth-child(7):last-child) > :nth-child(4) { grid-row: auto; }
  .nst-sports:has(> :nth-child(7):last-child) > :nth-child(4).nst-sportcard { aspect-ratio: 4 / 3; }

  /* Cards and reels: reels stay two across -- they are portrait, and one per
     row would put a single 9:16 frame on a whole screen. */
  .nst-card, .nst-grid--4 .nst-card { height: 300px; }

  /* NIL apps three across. One 300px card per row showed a single face per
     screen and made a 77-athlete roster feel like a short list; at three across
     nine land at once and the section reads as a network.

     Square, because these are faces -- a portrait crop cuts foreheads and a
     landscape one leaves a band of gym behind the head. The type has to come
     down with the frame: 36px was set for a 300px card and would run off a
     130px one. */
  /* `grid-auto-flow: row` matters as much as the columns here. The network
     sheet turns every .nst-grid--4 into a sideways rail below 900px, and a
     rail ignores grid-template-columns entirely -- so this rule set three
     columns that were never used, the cards auto-sized to their own content,
     and the row came out as twelve columns of 100px, 124px, 118px, 210px...
     with each frame a different height and the tops ragged. Naming the flow
     is what turns it back into the three-across grid the rest of this block
     is written for. */
  .nst-section--roster .nst-grid--4 {
    grid-auto-flow: row;
    /* minmax(0, 1fr), not 1fr. A bare 1fr is min-content wide, so a card with
       a long name or a wide picture pushed its own column out and the three
       came to 106, 124 and 118 -- three different frame widths, and because
       the frame is square, three different heights. Zero as the floor lets
       every column be exactly a third. */
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 8px 8px;
    overflow-x: visible;
  }

  /* Name under the picture, not on it. Over the image the type had to fight
     whatever the photo was doing -- a light jersey, a scoreboard, a face -- and
     needed a heavy gradient to stay readable, which buried the face the card
     exists to show. Below it, the picture is clean and the name is flat text.

     So the card is no longer a fixed square with everything absolutely placed
     inside it: it is a square frame with a caption after it. */
  .nst-section--roster .nst-card,
  .nst-section--roster .nst-grid--4 .nst-card {
    height: auto; aspect-ratio: auto; background: none; overflow: visible;
    display: flex; flex-direction: column; gap: 6px;
  }
  /* The frame is the square -- the img is absolutely positioned against it. */
  .nst-section--roster .nst-card__frame {
    position: relative; aspect-ratio: 1; overflow: hidden;
    border-radius: 14px; background: var(--hatch);
  }
  /* No wash needed now that nothing sits on the picture. */
  .nst-section--roster .nst-card__scrim { display: none; }
  .nst-section--roster .nst-card__body {
    position: static; inset: auto; padding: 0;
  }
  /* Every caption is the same height whatever the name does, so the pictures
     stay on a shared baseline across the row. Without this a two-line name
     ("CHARLES WOODSON JR.") or a wrapped tally pushed its own row down and the
     grid stopped reading as a grid.

     One line each for the name and the two meta rows: the name clamps, and the
     meta lines ellipsize rather than wrapping. */
  .nst-section--roster .nst-card__name {
    font-size: 13px; line-height: 1.1; height: 1.1em;
    display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical;
    overflow: hidden; word-break: break-word;
  }
  .nst-section--roster .nst-card__line {
    font-size: 8px; line-height: 1.3; padding-top: 2px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* The index stays on the picture -- it is a rank, and one glyph in a corner
     does not cover a face. Squared off to match the frame it sits in; the
     circle was a token from the old overlaid card. */
  .nst-section--roster .nst-card__index {
    top: 6px; right: 6px; width: auto; height: auto;
    padding: 3px 5px; font-size: 8px; border-radius: 5px;
  }
  .nst-section--roster .nst-avatar { display: none; }
  .nst-reels { grid-template-columns: repeat(2, 1fr); gap: 6px; }
  .nst-rail { gap: 6px; scroll-snap-type: x proximity; scroll-padding-left: var(--gutter); }
  .nst-rail > * { scroll-snap-align: start; }

  /* Player overlay: the clip gets the screen, and nothing else takes height it
     does not need.

     Two faults fed each other. The sheet was pinned to height:100% while the
     stage held 16:9, so a portrait clip played as a strip across the middle and
     roughly 900px of empty card ran below the caption -- most of the screen was
     neither film nor text. The sheet is content-height now, and the stage takes
     the shape of what is in it. */
  .nst-player { padding: 0; place-items: stretch; }
  /* The whole screen. Content-height and pushed to the bottom, the sheet left
     the page showing above it -- you tapped a clip and got a player halfway
     down, with the header you had just left still visible over the top of it.
     Full height, and the film takes whatever the caption does not. */
  .nst-player__box {
    width: 100%; height: 100svh; max-height: 100svh; border-radius: 0;
    display: flex; flex-direction: column; margin: 0;
  }
  /* The film fills what is left rather than claiming a fixed share: the caption
     under it is a couple of lines, so a fixed 68vh stage left a band of dead
     sheet between them on a tall screen. */
  .nst-player__stage {
    aspect-ratio: auto; height: auto; min-height: 0;
    border-radius: 0; flex: 1 1 auto;
  }
  /* contain, not cover: a reel must not be cropped to fill a wide box, and with
     the stage now matching the clip there are no bars left to hide. */
  .nst-player__stage video { object-fit: contain; }
  .nst-player__meta { padding: 16px var(--gutter) calc(20px + env(safe-area-inset-bottom)); flex: 0 1 auto; overflow-y: auto; }
  .nst-player__title { font-size: 22px; }
  .nst-player__caption { font-size: 14px; margin-top: 10px; }
  /* Clear of the notch, and of iOS's own control cluster in the top corners of
     a playing <video> -- at 12px the two sat on top of each other and the way
     out of the sheet was the one thing that could not be pressed. */
  .nst-player__x {
    width: 44px; height: 44px; border-radius: var(--r-pill);
    top: max(12px, env(safe-area-inset-top)); right: 12px;
    background: rgba(8,8,10,.82); backdrop-filter: blur(6px);
  }

  /* The in-frame transport is the only way to control a YouTube clip -- its own
     controls are stripped -- so on a touch screen it has to be thumb-sized. The
     track keeps a 4px line but takes a 20px hit area through its ::before. */
  .nst-transport { gap: 10px; padding: 20px 14px 14px; }
  .nst-transport__btn { width: 42px; height: 42px; font-size: 15px; }
  .nst-transport__btn--sound { width: auto; height: 36px; padding: 0 14px; }
  .nst-transport__time { min-width: 0; font-size: 10px; }
  .nst-transport__track { height: 4px; position: relative; }
  .nst-transport__track::before { content: ""; position: absolute; inset: -10px 0; }

  /* Footer: closed up and given a shape.

     72px of margin sat on top of the last section's own bottom padding, so the
     page ended on a band of empty black taller than the footer itself. And
     stacking a row built for space-between left four centered lines floating
     with nothing holding them -- justify-content does no work once the flex
     direction is column.

     Two rows instead: the brand and the legal links share a line, pinned to
     opposite edges the way they are on desktop, and the tally sits under them
     on a rule of its own. */
  .nst-foot {
    margin-top: 28px; padding: 18px var(--gutter) calc(18px + env(safe-area-inset-bottom));
    display: grid; grid-template-columns: 1fr auto; align-items: center;
    gap: 12px 14px; font-size: 10px;
  }
  /* Placed by hand. The brand runs two lines, so leaving the legal links to
     auto-placement dropped them past it onto a row of their own instead of
     sitting opposite. */
  .nst-foot__brand { grid-area: 1 / 1; gap: 3px; min-width: 0; }
  .nst-foot__line { font-size: 12px; }
  .nst-foot__legal { grid-area: 1 / 2; gap: 14px; justify-self: end; }
  /* The counts read as a caption for the whole footer, so they run the width
     under both and pick up the divider. */
  .nst-foot > [data-foot-meta] {
    grid-area: 2 / 1 / 3 / -1; padding-top: 11px;
    border-top: 1px solid var(--line); color: var(--dim);
  }

  /* The directory's own phone rules live in nilsportstech-nil-apps-view.css --
     that sheet loads after this one, so its 1180px block would outrank
     anything written here at the same specificity. */

  /* Watch is a feed here, not a player with a grid under it.

     The desktop shape -- one stage, a queue beside it, then every other clip as
     tiles below -- asks the reader to pick before they can watch. On a phone the
     tiles become the player: one clip per screen, snapped, playing whichever one
     you have scrolled to. So the stage and its queue go; they would be the same
     clip twice, once above the feed and once inside it.

     The chips stay. They are the one control the feed cannot express, and they
     stick to the top so the filter is reachable mid-scroll. */
  .nst-watchview__lead { display: none; }
  .nst-watchview__head { display: none; }
  .nst-watchview__more { display: none; }

  .nst-watchview__topics {
    position: sticky; top: 0; z-index: 5;
    /* Stuck to the top once the header scrolls away, so the chips carry the
       status bar's inset themselves at that point. */
    gap: 6px; padding: calc(10px + env(safe-area-inset-top)) var(--gutter) 10px;
    overflow-x: auto; flex-wrap: nowrap; scrollbar-width: none;
    background: rgba(8,8,10,.86); backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--line);
  }
  .nst-watchview__topics::-webkit-scrollbar { display: none; }
  .nst-topic { flex: 0 0 auto; }

  /* An index, not a feed. This grid used to be the feed itself: one
     full-height tile per screen, snapped, autoplaying. Watch has a real feed
     now -- tapping any clip opens it -- so a phone had to swipe through a
     screen-per-clip just to reach a screen-per-clip. Two of the same thing,
     one inside the other.

     Two across at a browsable size, so a phone can see what is on the network
     before committing to watching it. */
  /* A mosaic, not a column of pairs. Two across put four clips on a phone
     screen at most and read as a list with pictures; three across with every
     fourth tile taking a double square breaks the rhythm and roughly doubles
     what is in view.

     Rows are a fixed track rather than the tiles' own ratio, which is what
     lets a big tile span two of them and the small ones tuck around it. */
  .nst-watchview__grid {
    display: grid; grid-template-columns: repeat(3, 1fr);
    /* Taller tracks, so the whole run is bigger on the screen. */
    grid-auto-rows: 92px; gap: 5px;
    /* Fills its own holes. The mosaic leaves a gap whenever the next tile is
       wider than the space left on the row, and dense lets a later small tile
       drop back into it -- without it four rows carried one tile where three
       fit. It reorders, which is right here: the grid is an index to pick from
       rather than a sequence to read. */
    grid-auto-flow: dense;
    padding: 10px calc(var(--gutter) / 2) 0;
  }
  .nst-watchtile {
    position: relative; height: auto; border-radius: var(--r-sm);
    overflow: hidden; background: #0d0d11;
    display: flex; flex-direction: column;
    grid-row: span 2;
  }
  /* Every fourth one takes the width of two and the height of two. Anchored to
     the left column so the run stays on a three-wide rhythm rather than
     drifting a column further right on every repeat. */
  /* Two by two, not two by three. Against tiles that stand two rows tall a
     three-row feature never divides evenly, so every one of them opened a gap
     beside it that nothing could fill. */
  .nst-watchtile:nth-child(7n + 1) { grid-column: span 2; grid-row: span 2; }
  /* The ticker goes while Watch is open -- it belongs to the front page's
     moments and is 28px of marquee above a feed. The header stays: it holds the
     menu, and hiding it would leave the feed with no way back to the other
     views. What changes is that it scrolls away with the first clip instead of
     standing over it. */
  .nst-watchview:not([hidden]) { --feed-top: 52px; }
  body:has(.nst-watchview:not([hidden])) .nst-ticker { display: none; }
  .nst-watchtile:hover { transform: none; }
  /* The picture fills whatever the tile turned out to be: the mosaic sets the
     shape, so a fixed ratio here would leave a band of dead tile under every
     frame that did not match it. */
  .nst-watchtile__shot {
    position: absolute; inset: 0; aspect-ratio: auto;
    border-radius: 0; background: #000;
  }
  /* cover here, not contain. At tile size a contained reel is a strip of
     picture with black above and below; the index is for recognising a clip,
     and the feed is where it plays whole. */
  .nst-watchtile__shot img, .nst-watchtile__shot video { object-fit: cover; }
  /* The base sheet positions img and video inside the frame but not iframe --
     it never mounts one here. Unstyled it lays out as an inline box in the
     corner rather than filling the tile. pointer-events off so a swipe scrolls
     the feed instead of being caught by YouTube's own surface. */
  .nst-watchtile__shot iframe {
    position: absolute; inset: 0; width: 100%; height: 100%;
    border: 0; z-index: 1; pointer-events: none;
  }
  /* Back over the picture, since the picture is now the whole tile. Only the
     big ones carry a title -- at 118px square there is no room for type that
     is not covering the clip, and the feed says everything the moment it
     opens. */
  .nst-watchtile__scrim {
    background: linear-gradient(to top, rgba(8,8,10,.9) 0%, rgba(8,8,10,.4) 34%, rgba(8,8,10,0) 62%);
  }
  .nst-watchtile__body {
    position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
    padding: 8px 9px 9px;
  }
  .nst-watchtile__title { display: none; }
  .nst-watchtile:nth-child(7n + 1) .nst-watchtile__title {
    display: -webkit-box; font-size: 14px; -webkit-line-clamp: 2;
  }
  /* The name alone on a small tile, and it has to fit: at 135px wide the
     avatar and a letter-spaced name ran out of room and truncated mid-word. */
  /* No letter spacing and a small face: at 135px wide the spaced name ran out
     of room and truncated mid-word on every small tile. */
  .nst-watchtile__who { padding-top: 0; font-size: 9px; letter-spacing: 0; gap: 4px; }
  .nst-watchtile__who .nst-avatar { width: 14px; height: 14px; font-size: 7px; }
  /* The size and spacing live on the name, not on the row around it -- setting
     them on the parent left the child at 15px and .1em, which is what kept
     truncating "Malakhi Murphy" mid-word on a 135px tile. */
  .nst-watchtile__name { font-size: 9px; letter-spacing: .02em; min-width: 0; }
  .nst-watchtile:nth-child(7n + 1) .nst-watchtile__name { font-size: 12px; letter-spacing: .06em; }
  .nst-watchtile:nth-child(7n + 1) .nst-watchtile__who { padding-top: 4px; font-size: 11px; }
  /* Section and date are a third line under a 200px frame. The feed says both
     the moment it opens. */
  .nst-watchtile__foot { display: none; }
  /* The mark comes back: these are stills again, and a tap is what they are
     for. */
  .nst-watchtile__top { top: 6px; left: 6px; }
  /* Small enough to mark a tile rather than sit on it. At 38px it was a
     quarter of a 135px frame, covering the play it was pointing at. */
  .nst-watchtile__play { display: grid; width: 20px; height: 20px; }
  .nst-watchtile:nth-child(7n + 1) .nst-watchtile__play { width: 30px; height: 30px; }
}

@media (max-width: 420px) {
  /* The strapline goes before the logo does.
   *
   * Below this width the row cannot hold burger + circle + NILSPORTSTECH +
   * HOME BASE FOR ATHLETES and still leave the two buttons their own size --
   * and what gave way was the wordmark, which ended up underneath LOG IN. The
   * strapline is a note about the network rather than part of the mark, so it
   * is the right thing to lose: the logo stays whole and the buttons stay
   * beside it instead of on top of it. */
  .nst-brand__tag { display: none; }

  .nst-head__actions .nst-btn--light { font-size: 0; letter-spacing: 0; gap: 5px; }
  .nst-head__actions .nst-btn--light::after {
    content: "CREATE APP"; font-size: 12px; letter-spacing: .06em;
  }
  .nst-head__actions .nst-btn--light b { font-size: 15px; }
}

@media (max-width: 760px) and (pointer: coarse) {
  /* The tabs, not every child. "> *" also caught .nst-nav__meta and gave it
     display:inline-flex -- last rule in the last sheet, so it undid the
     display:none meant to hide it and the "130 apps · updated" line stayed on
     the strip. */
  .nst-nav > span[data-view], .nst-nav > a { min-height: 40px; display: inline-flex; align-items: center; }
  .nst-apps .nst-chip, .nst-topic { min-height: 40px; display: inline-flex; align-items: center; }
  .nst-apps .nst-sort select { min-height: 44px; }
  .nst-offerrow__all { min-height: 44px; }
}

/* --- phone -----------------------------------------------------------------

   The masthead stops being a layered frame. On a wide screen the athlete is the
   backdrop and the copy sits over it, held legible by a left-to-right ramp; at
   phone measure there is no "beside the headline" for a photo to occupy, so the
   frame, the copy and the name plate stack, and the rail goes under them.

   These rules have to live here rather than in nilsportstech-network-v2.css:
   this sheet loads second, so its 1180px block outranks anything written there
   at equal specificity. */

@media (max-width: 760px) {
  /* Four stacked rows, every child placed explicitly -- the desktop rule spans
     the rail down "1 / -1" of a second column that no longer exists. */
  .nst-apps .nst-masthead { height: auto; grid-template-columns: 1fr; grid-template-rows: auto auto auto auto; }
  /* Out of the layer and into the flow. The desktop rule pins this to
     "inset: 0 420px 0 0", which is a 420px hole on a 390px screen. */
  /* The photo runs behind the name plate rather than stopping above it: it
     spans its own row and the one holding the athlete's name, offers and
     moments, so that copy reads as an overlay on the picture the way it does on
     a wide screen. Absolute, so it fills those two rows without adding height of
     its own -- the row heights come from the copy on top of it. */
  .nst-apps .nst-spot {
    /* Both take the same cells, so the plate sits on the picture without
       either one being pulled there by a measurement.

       The picture used to be absolute against the masthead's own box with
       top and bottom pinned to 0 -- which is the whole element, taller than
       the two rows it is meant to cover. It ran 143px past the name plate and
       out of the section, and the filter chips underneath were drawn straight
       through it. In the grid it ends where its rows do. */
    position: relative; inset: auto;
    grid-column: 1; grid-row: 1 / 4;
    height: auto; border-radius: 0; z-index: 0;
  }
  .nst-apps .nst-spot__body {
    grid-column: 1; grid-row: 3; align-self: end; z-index: 1;
  }
  /* A spacer for the top of the frame, since the photo no longer occupies a row
     on its own -- without it the headline would start at the very top edge. */
  .nst-apps .nst-masthead::before { content: ""; grid-column: 1; grid-row: 1; height: 92px; }
  /* Everything in those rows sits over the picture. */
  .nst-apps .nst-masthead__lede,
  .nst-apps .nst-spot__body { position: relative; z-index: 1; }
  /* Only the foot ramp survives: the horizontal one exists to keep a headline
     legible over the photo, and the headline is no longer on it. */
  /* A wash heavy enough for the copy that now sits on the picture end to end.

     It has to hold at both extremes: the eyebrow and headline near the top, and
     the name plate at the foot. The roster is real photography -- bright grass,
     white jerseys, floodlit gyms -- so the ramp is built for the worst case
     rather than the average one. At .35 the headline was sitting on sunlit turf
     and disappearing into it. */
  .nst-apps .nst-spot__shot::after {
    background:
      linear-gradient(rgba(8,8,10,.86) 0%, rgba(8,8,10,.66) 26%,
                      rgba(8,8,10,.72) 58%, rgba(8,8,10,.95) 100%);
  }
  /* Higher up the frame than the 30% the wide layout uses. The box is much
     taller than it is wide here, so a cover-crop takes a narrow vertical slice
     -- centred on 30% that slice was mostly torso and ground. */
  .nst-apps .nst-spot__shot img { object-position: 50% 18%; }
  .nst-apps .nst-masthead__lede { grid-column: 1; grid-row: 2; padding: 14px var(--gutter) 0; }
  .nst-apps .nst-masthead__title { font-size: clamp(26px, 7vw, 32px); }
  .nst-apps .nst-masthead__body { font-size: 14px; padding-top: 7px; }
  .nst-apps .nst-spot__body { grid-column: 1; grid-row: 3; padding: 10px var(--gutter) 14px; }
  .nst-apps .nst-spot__name { font-size: 22px; }
  /* What moved is a rail on a wide screen -- a fixed column that scrolls inside
     itself beside the copy. Stacked, it became forty rows in the page's own
     flow: 2,294px of feed sitting between the reader and the directory they
     tapped Apps to see. The first tile was three and a half screens down.

     It keeps its own scroll here instead, at a height that shows three or four
     rows and says there are more by cutting the fourth. */
  .nst-apps .nst-moving {
    grid-column: 1; grid-row: 4; border-left: 0; border-top: 1px solid var(--line);
    padding: 14px var(--gutter); max-width: 100%; min-width: 0;
  }
  .nst-apps .nst-moving > [data-moving] {
    max-height: 190px; overflow-y: auto;
    -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
  }
  /* Tag above the sentence. The fixed 52px tag column plus a line of prose does
     not fit, and the text was running off the right edge. */
  .nst-apps .nst-moving__item { flex-direction: column; align-items: flex-start; gap: 3px; padding: 7px 0; font-size: 13px; }
  .nst-apps .nst-moving__tag { width: auto; }
  .nst-apps .nst-moving__tag--commit { padding: 3px 8px; }

  /* Filters are one row that scrolls, not two stacked.

     They were split deliberately -- scope on one line, sports on the next, so
     the shape said they were different questions -- but with the sort under
     both that is three rows of chrome, 165px, standing in front of the first
     athlete on a 844px screen. One row costs the reader a sideways swipe to
     reach the far sports and gives back a quarter of the fold.

     The whole row scrolls rather than each set: the sets sit inside it at
     content width, so scope, sports and sort run as one strip. */
  .nst-apps .nst-filters {
    display: flex; flex-direction: row; align-items: center; flex-wrap: nowrap;
    overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
    padding: 12px var(--gutter); gap: 8px;
  }
  .nst-apps .nst-filters::-webkit-scrollbar { display: none; }
  .nst-apps .nst-filters__set {
    flex: 0 0 auto; flex-wrap: nowrap; overflow: visible;
    padding: 0; gap: 6px; width: auto; min-width: 0;
  }
  .nst-apps .nst-filters__set > * { flex: 0 0 auto; }
  /* Lighter than the block they were: less ink per chip, so a row of them reads
     as a set of options rather than a wall. */
  .nst-apps .nst-chip {
    padding: 8px 14px; font-size: 13px; letter-spacing: .10em;
    border-color: #26262c; white-space: nowrap;
  }
  /* The pill's own selected state is the orange one; this white border was for
     the boxed chips it replaced. */
  .nst-apps .nst-chip.is-on { border-color: var(--orange); }
  /* Back in: with one row the divider is doing its original job again, marking
     where scope ends and sports begin. */
  .nst-apps .nst-filters__split { display: block; flex: 0 0 auto; height: 22px; }
  /* Travels with the pills at the end of the strip rather than taking its own
     full-width line. */
  .nst-apps .nst-sort { flex: 0 0 auto; margin-left: 4px; width: auto; padding: 0; }
  .nst-apps .nst-sort select { flex: 0 0 auto; min-height: 40px; }
  /* Three across, square. One 380px card per row showed a single app per screen
     and made a 130-app directory feel like a short list you could reach the end
     of; nine land at once now, and scrolling it reads as a network.

     Square because these are faces and logos -- a tall crop cuts foreheads off
     and a wide one leaves a band of gym behind the head. The type comes down
     with the frame: 30px was set for a full-width card and would run off a
     ~140px one. */
  .nst-apps .nst-appgrid { grid-template-columns: repeat(3, 1fr); gap: 6px; padding: 6px var(--gutter); }
  /* Portrait rather than square: three across is the shape you want, so the only
     room left to give a tile is height. 2:3 makes each one bigger without
     letting a fourth into the row, and the extra depth is where a face actually
     lives -- a square crop was cutting them at the chin. */
  .nst-apps .nst-app { height: auto; aspect-ratio: 2 / 3; border-radius: 14px; }
  /* The wash was tuned to hold a headline over the bottom third of a 380px
     card. At this size that buried most of the picture, so it starts lower and
     lands lighter -- enough to carry the name, not enough to hide the face. */
  .nst-apps .nst-app__scrim {
    background: linear-gradient(#0000 40%, rgba(8,8,10,.62) 70%, rgba(8,8,10,.95));
  }
  .nst-apps .nst-app__body { left: 9px; right: 9px; bottom: 9px; }
  /* One line, then an ellipsis: a wrapped third line pushed the name out of the
     frame on the cards with long handles. */
  /* break-word, or a single long handle -- DIRECTIONVOLLEYBALL, with nowhere to
     break -- runs straight out of the frame instead of wrapping in it. */
  .nst-apps .nst-app__name {
    font-size: 15px; line-height: 1.04;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden; overflow-wrap: anywhere;
  }
  /* The sport/position line and the tag row are detail for a card with room.
     At a third of the width they became two rows of noise under the name. */
  .nst-apps .nst-app__line, .nst-apps .nst-app__tags { display: none; }
  /* Corner furniture scales too, or a 10px flag with 9px of padding is a
     quarter of the tile. */
  /* Kept clear of the index in the opposite corner and clipped rather than
     allowed to run: "FIRST ON THE NETWORK" is wider than the tile, and at full
     length it crossed the badge and ran off the frame. */
  .nst-apps .nst-app__flag {
    top: 6px; left: 6px; font-size: 7px; padding: 3px 5px; gap: 4px; letter-spacing: .1em;
    max-width: calc(100% - 40px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .nst-apps .nst-app__dot { width: 4px; height: 4px; }
  .nst-apps .nst-app__index { top: 6px; right: 6px; font-size: 8px; padding: 3px 5px; }
  .nst-apps .nst-cta { padding: 40px var(--gutter); gap: 20px; }
  .nst-apps .nst-cta .nst-btn--orange { font-size: 15px; padding: 16px 26px; }
}


@media (max-width: 760px) {
  /* No theme switch on a phone.

     The header row is already carrying the mark, LOG IN and START AS ATHLETE
     in one line, and a fourth control tipped it over -- the button landed on
     top of the wordmark. A phone follows the reader's system setting instead,
     which is the setting they actually keep, and the switch stays on desktop
     where there is room for it. */
  .nst-theme { display: none; }
}

@media (max-width: 760px) {
  /* Daylight, as a row of the menu.

     The round switch cannot ride in the header on a phone -- that line already
     carries the mark, LOG IN and START AS ATHLETE, and a fourth control landed
     on the wordmark. It reads better here anyway: a menu row says what it does
     in words rather than asking the reader to know what a sun means. */
  .nst-nav__theme {
    display: flex; align-items: center; gap: 12px;
    width: 100%; padding: 14px var(--gutter);
    border: 0; border-bottom: 1px solid var(--line);
    background: none; cursor: pointer; text-align: left;
    font-family: var(--cond); font-size: 15px; letter-spacing: .16em;
    text-transform: uppercase; color: var(--label);
  }
  .nst-nav__theme svg { width: 17px; height: 17px; flex: 0 0 auto; }
  .nst-nav__theme b { font-weight: inherit; font-size: inherit; letter-spacing: inherit; }
  /* The row names the theme it moves to, and carries that theme's mark. On the
     dark page it offers the light one -- a sun, reading LIGHT MODE. The names
     were the wrong way round: it read DARK MODE beside a sun while the page was
     already dark, which is a switch telling you where you are rather than where
     it takes you. */
  .nst-nav__theme .nst-theme__moon,
  .nst-nav__theme .nst-nav__theme-dark { display: none; }
  :root[data-theme="light"] .nst-nav__theme .nst-theme__sun,
  :root[data-theme="light"] .nst-nav__theme .nst-nav__theme-light { display: none; }
  :root[data-theme="light"] .nst-nav__theme .nst-theme__moon { display: block; }
  :root[data-theme="light"] .nst-nav__theme .nst-nav__theme-dark { display: inline; }
}

/* On a phone the roster's captions sit under the picture, not on it.

   The rules above move them off deliberately -- "no wash needed now that
   nothing sits on the picture" -- so the light ink the desktop card needs is
   wrong here: the names went white on the white page. They take the page's
   own colours instead.

   Same for the app grid, which does the same thing at this width. */
@media (max-width: 760px) {
  :root[data-theme="light"] .nst-section--roster .nst-card {
    color: var(--text);
    --text: #16161a;
    --muted: #55555e;
    --dim: #77777f;
    --label: #6b6b73;
  }
  :root[data-theme="light"] .nst-section--roster .nst-card__name { color: #16161a; }
  :root[data-theme="light"] .nst-section--roster .nst-card__line { color: #55555e; }
}

/* The topics band and the watch tiles take the page in daylight.

   The band is a hardcoded rgba(8,8,10,.86) -- a dark strip that pins over the
   grid as it scrolls, right on the dark page and a black bar across a white
   one. The tiles are the same: --well-raised is restated by the light palette,
   but the mobile sheet paints them from a literal, so they stayed near-black
   while their captions followed the page down to near-black with them. */
@media (max-width: 760px) {
  :root[data-theme="light"] .nst-watchview__topics {
    background: rgba(255, 255, 255, .92);
    border-bottom: 1px solid var(--line);
  }
  /* The tile keeps light copy here. On a wide screen the caption sits under
     the thumbnail on the card's surface, so it takes the page's ink -- but at
     this width it is printed over the picture, which is its own background and
     does not turn with the theme. The card behind it still goes white for the
     corners the picture does not reach. */
  :root[data-theme="light"] .nst-watchtile {
    background: #fff;
    border: 1px solid var(--line);
  }
  /* Beats the wide-screen rule, which gives these the page's ink because there
     the caption is under the thumbnail rather than on it. */
  :root[data-theme="light"] .nst-watchtile__title { color: #f4f4f2; }
  :root[data-theme="light"] :is(.nst-watchtile__who, .nst-watchtile__meta) { color: #d8d8d4; }
}
