/* THE MEDIA ROOM — the front page (view-media): the band of channel cards, the
   fan of artwork on each, the door in a panel heading, and the Now Playing block.
   Moved out of app.css on 15 Oct as part of the rebuild; same pixels. Loaded
   straight after app.css (see room-css in index.html). Work borrows the panel
   door and keeps its own ink for it under #workBody in app.css. */

/* ---- THE BAND, item 280 — TREATMENT B --------------------------------
   A FIELD BETWEEN TWO HAIRLINES. Not a slab: a rule above, a rule below,
   no fill, and a great deal of air. Matt chose this shape out of the three
   in tools/media-demo/media-area-ideas.html — "I kinda love B... it looks
   regal, which matches everything we are trying to match" — and the first
   build shipped the filled one instead. His words: "you gave me a, not be."
   This is the correction, and every rule below is part of it.

   WHY IT IS ALSO THE RIGHT ONE, not merely the one he picked: the whole
   app is hairlines and fields (items 205, 214-217). A filled, rounded,
   bordered slab sitting directly under the word Media is a box inside a
   box, which is the less Lucid of the two even though it was the more
   literal reading of the Smart-TV reference he sent.

   THE HAIRLINES TINT WHEN A CARD IS LIT, and nothing about the band's
   shape moves. Lighting a card in the filled version changed a fill, a
   border, a ring and a top edge all at once; here the field itself
   acknowledges it, which is the signal that works at the edge of vision.

   350px WAS B'S COST AND IT HAD TO COME DOWN. In the demo the band stood
   350px tall before the first section — a third of a laptop screen spent
   before anything is read, against the filled version's 174. Everything
   below is the budget that fixed it: the fan is a 104px band rather than a
   full square tile, each frame is cut to its own aspect instead of to 1:1,
   and the gaps are tight. It lands a little over 200 at 1440. */
.media-band{
  border-top:1px solid var(--glass-border);
  border-bottom:1px solid var(--glass-border);
  padding:var(--space-5) 0 var(--space-4); margin:0 0 var(--space-6);
  transition:border-color var(--dur-base) var(--ease-out); }
.media-band.lit{
  border-top-color:color-mix(in srgb, var(--area-media) 55%, transparent);
  border-bottom-color:color-mix(in srgb, var(--area-media) 55%, transparent); }
/* NOTHING ANYWHERE, so nothing is carrying the height: the fan is what
   makes a card tall and a first-run band has no fans in it at all. The
   band therefore takes LESS room empty than full, which is the answer to
   "six beautiful tiles all saying nothing yet". */
.media-band.is-empty{ padding:var(--space-4) 0 var(--space-4); }

/* ONE ROW, AND THE ROW IS THE POINT. grid-auto-flow:column with equal auto
   columns means the number of channels is the config's business and not the
   stylesheet's — four in a browser, five inside the Mac app, six when
   something is added — and none of them can wrap away out of sight. */
.media-tiles{ display:grid; grid-auto-flow:column; grid-auto-columns:minmax(0, 1fr); gap:var(--space-6); }

/* A CARD IS A FILTER, so it is a <button> with aria-pressed rather than a
   div with a handler: Enter, Space, a focus ring and a state a screen
   reader can read all come with the element. The exception is a first-run
   channel with nothing to press, which is a <div class="note"> — see
   areaTileHtml(); a button that cannot do anything is the one thing this
   app does not ship.

   NO FILL, NO BORDER, NO RADIUS. The card is not an object on the band; it
   is a column of the field. --fan-h is the one number the whole card is
   built out of, so the responsive rules at the bottom of this block change
   a single value rather than eleven.

   THE NAMES SIT ON ONE LINE ACROSS THE BAND, and that is what the grid is
   for. 4 September: the first build of this laid the card out as a block —
   fan, then name — so a channel with NO artwork pulled its name to the top
   of its column while its neighbours' names sat below their fans, and the
   row came out ragged. It never appeared in that build's own screenshots
   because every channel in its fixture had pictures; it appeared the moment
   a preview ran with one that did not, and it is PERMANENT for Social,
   which has no artwork and never will have any.

   Reserving the row is not the same as drawing an empty frame. Nothing is
   painted, so the rule that a card without a picture shows no picture
   stands exactly as it was; what is held is the space, so the label the eye
   scans across sits on one line. When every channel is first-run there are
   no fans at all, the row goes to zero, and the band still collapses. */
/* A COLUMN, NOT A BLOCK, AND THAT ONE WORD IS A BUG FIX — 4 September.
   A <button> CENTRES ITS OWN CONTENTS VERTICALLY. That is the user-agent
   behaviour and setting display:block on it does not undo it, so a card
   whose content was shorter than the row's tallest card had its whole stack
   — fan, name, rule, state — pushed down by half the difference. On the
   Fashion desk that was seventeen pixels: four cards with artwork sat
   seventeen pixels below the two without, and the row of names the whole
   band is scanned across came out ragged.

   It is the exact fault the paragraph below says was fixed when the band was
   built, arriving through a door nobody was watching — and it is invisible
   whenever every channel has a picture, which is why Media's own screenshots
   never showed it. Social has no artwork and never will; a feed channel
   whose newest items arrived without thumbnails has none either.

   display:flex with a column direction makes the button a real flex
   container, which stacks from the top and centres nothing. Everything below
   is unchanged: the children are still block-level boxes in a column, in the
   same order, at the same sizes. */
.media-tile{ --fan-h:104px;
  display:flex; flex-direction:column; align-items:stretch; justify-content:flex-start;
  width:100%; margin:0; padding:0;
  background:none; border:0; border-radius:0; color:inherit; font:inherit;
  text-align:left; cursor:pointer; }
/* A grid was tried first and was wrong: with no fan element to fill it, the
   NAME took the fan's row and stretched down it, which looked worse than
   the rag it was meant to fix. Padding on the card that has no fan is the
   whole correction, and it moves nothing on the cards that do. */
/* AND ONLY WHEN THERE IS A ROW OF PICTURES TO ALIGN TO — 4 September.
   `:not(.is-empty)` was the wrong question. is-empty means every channel is
   FIRST-RUN, and a band can have nothing to fan without being that: the
   Fashion desk on its first day has five shelves offering sites and a sixth
   saying nobody has a birthday coming, which is a fact rather than an empty
   state — so the band was not is-empty, every card reserved a fan's worth of
   height, and the page opened with a hundred and thirty pixels of nothing
   above six names. That is the showroom TV playing a blue screen that item
   280 was written to refuse, arriving by a different road.

   :has(.media-fan) asks the question that was actually meant: reserve the
   row when there IS a row. */
.media-band:has(.media-fan) .media-tile:not(:has(.media-fan)){
  padding-top:calc(var(--fan-h) + 12px); }
.media-tile.note{ cursor:default; }
.media-tile:focus-visible{ outline:2px solid var(--area-media); outline-offset:4px; }
button.media-tile:hover .media-tile-name{ color:var(--area-media); }
button.media-tile:hover .media-tile-rule{
  background:color-mix(in srgb, var(--area-media) 32%, transparent); }

/* ---- THE FAN ---------------------------------------------------------
   THREE PIECES, NOT ONE, overlapping at slight angles, so the card reads
   as a stack you could go through rather than as one picture cropped to
   death. This is the difference from the filled version that is easiest to
   miss and hardest to unsee: one image is a photograph of a channel, three
   is the channel itself.

   THE GEOMETRY IS HERE AND NOT IN THE MARKUP, because it depends on how
   wide the tile is, which depends on how many channels the config has and
   how wide the window is — neither of which areaFanHtml() can see. What
   the markup passes down is data-n (how many pieces there are) and
   data-shape (what shape they are), because a rule cannot count its own
   children before deciding how to lay them out.

   EVERY PIECE IS SIZED OFF --fan-h AND NOT OFF THE TILE'S WIDTH. A rotated
   box grows by roughly its other dimension times sin(angle), and the demo's
   first pass put posters at 96% of a square starting at 10% down, which put
   their bottom edge at 106% — the third poster printed its title over the
   channel name below it and the crests broke out through the top hairline.
   Driving everything from the height means the fan cannot outgrow the band
   however wide or narrow the column gets. */
/* display:block IS LOAD-BEARING HERE, not tidiness. Every element on a card
   is a <span>, because a <button> may not contain a <div> — and height on an
   inline box is ignored, so the first build of this fan collapsed to nothing
   and all twelve pieces hung outside the field with the words on top of
   them. The screenshots caught it; nothing else would have. */
.media-fan{ display:block; position:relative; height:var(--fan-h); margin:0 0 var(--space-3); }
/* ANCHORED TO THE LEFT EDGE OF THE COLUMN, not centred in it. Centred, the
   fan floated in the middle of the tile with the channel name sitting under
   its left-hand corner, and the two read as two things rather than as a
   picture with its name under it. The name is the thing everything else on
   the card lines up with. */
.media-fan .media-art{
  position:absolute; top:50%; left:0; display:block; overflow:hidden;
  transform:translateY(-50%) translateX(var(--dx, 0px)) rotate(var(--rot, 0deg));
  border-radius:var(--radius-control);
  border:1px solid color-mix(in srgb, var(--text) 14%, transparent);
  background:color-mix(in srgb, var(--text) 6%, transparent);
  box-shadow:var(--elev-raised);
  transition:opacity var(--dur-base) var(--ease-out); }
.media-fan .media-art img{ width:100%; height:100%; object-fit:cover; display:block; }
/* A CREST GETS NO FRAME, NO SHADOW AND NO RADIUS. It is a logo on
   transparency, and a card drawn behind a shield is a picture of a sticker.
   `contain` is already the config's way of saying "this is a shape, not a
   rectangle", so it is what switches the frame off as well. */
.media-fan .media-art.contain{
  border:0; border-radius:0; background:transparent; box-shadow:none; }
.media-fan .media-art.contain img{ object-fit:contain; }

/* THE SHAPE OF A FRAME BELONGS TO THE KIND OF THING IN IT. A poster is 2:3,
   a sleeve and a crest are square, a video thumbnail is 16:9, and the
   answer to that is not to stretch any of them into one house shape — it
   is to cut each to its own and let the fan be a fan of the right objects.
   --sp is how far apart that shape wants to sit; it is tuned so all three
   fans come out about two --fan-h wide, which is what lets one set of
   breakpoints govern all of them. */
.media-fan[data-shape="poster"] .media-art{
  height:calc(var(--fan-h) * .88); aspect-ratio:2 / 3; --sp:.62; }
.media-fan[data-shape="square"] .media-art{
  height:calc(var(--fan-h) * .82); aspect-ratio:1 / 1; --sp:.55; }
.media-fan[data-shape="wide"] .media-art{
  height:calc(var(--fan-h) * .60); aspect-ratio:16 / 9; --sp:.40; }

/* THE FIRST PIECE IS THE MOST IMPORTANT ONE, so it is centred and on top
   and the other two sit behind it. art() hands them over in priority
   order, which means the newest score, the track that is playing, the
   poster of tonight's first pick — the thing the section underneath is
   actually about — is the one at the front of the stack. */
.media-fan .media-art:nth-child(1){ --dx:calc(var(--fan-h) * var(--sp)); --rot:0deg; z-index:3; }
.media-fan .media-art:nth-child(2){ --dx:0px; --rot:-7deg; z-index:1; }
.media-fan .media-art:nth-child(3){ --dx:calc(var(--fan-h) * var(--sp) * 2); --rot:7deg; z-index:1; }
/* TWO IS NOT THREE WITH A GAP IN IT. With only two pieces the front one
   moves off centre and the pair reads as a stack leaning one way, which is
   what two things on a shelf look like; left where it was it read as a
   picture with something stuck to the side of it. One piece is centred and
   straight, and is simply a picture — which is correct, and is what a
   channel with one team or one poster honestly has. */
.media-fan[data-n="2"] .media-art:nth-child(1){
  --dx:calc(var(--fan-h) * var(--sp) * .85); --rot:3deg; }
.media-fan[data-n="2"] .media-art:nth-child(2){ --dx:0px; --rot:-6deg; }

/* ---- THE WORDS, IN THE CLEAR ----------------------------------------
   THE NAME IS SET IN THE APP'S OWN INK UNDER THE PICTURE, NEVER OVER IT,
   and there is no scrim because there is nothing to scrim. Text over
   arbitrary artwork is a contrast reading nobody can make in advance — the
   poster is a different photograph every day — so the words sit on the
   page's own ground where they measure the same as every other line in the
   app. That is the second thing the filled version got wrong. */
.media-tile-name{ display:block; font-family:var(--font-display); font-size:var(--type-lead);
  font-weight:600; color:var(--text); line-height:1.2;
  transition:color var(--dur-base) var(--ease-out); }
/* LIT IS A RULE, NOT A FILL. A 2px rule under the name in --area-media,
   the name itself taking the area colour, and the field's own hairlines
   tinting above and below. Three signals, none of which moves anything by
   a pixel — the band's shape does not change when a card lights, which was
   the whole complaint against the filled version's growing border. The
   rule is drawn transparent rather than absent so that lighting a card
   cannot reflow the two lines under it. */
.media-tile-rule{ display:block; height:2px; border-radius:var(--radius-tight); margin:var(--space-2) 0 0;
  background:transparent; transition:background var(--dur-base) var(--ease-out); }
.media-tile-state{ display:block; font-size:var(--type-body); line-height:1.45;
  color:var(--text-dim); margin-top:var(--space-2); }
.media-tile.on .media-tile-rule{ background:var(--area-media); }
.media-tile.on .media-tile-name{ color:var(--area-media); }
.media-tile.on .media-tile-state{ color:var(--text); }
/* AN HONEST COUNT IS DIM AND A FIRST-RUN CHANNEL IS FULL INK, which is the
   opposite of the usual weighting and is deliberate. "14 on your list" is a
   number he glances at; "You follow no teams yet" is the one card on the
   band with something to do about it. */
.media-tile.empty .media-tile-state{ color:var(--text); }
/* Waiting on a round trip. Quieter than either, because it is a sentence
   that is about to be replaced by a real one. */
.media-tile.pending .media-tile-state{ color:var(--text-faint); }

/* THE UNLIT CARDS STEP BACK — and this is the one place where the demo's
   own number could not be taken literally. Treatment B drops the unlit
   cards to 30% opacity, which is lovely on a mock and is a contrast
   failure here: tests/test-readability.js measures every line of text from
   the pixels actually on screen, and an 11.5px line at 30% over this
   ground comes out near 1.6:1 against a required 4.5, on all fourteen
   themes. It would also contradict the one rule of item 280 that survived
   this rebuild untouched — the whole band stays on screen while one
   channel is showing, precisely so the overview is never lost — and a card
   nobody can read is a card that is not there.

   So the step back is put where it costs nothing. THE PICTURES drop to
   28%, and that is where almost all of a card's visual weight lives; the
   WORDS step down one ink token instead of fading. The row recedes and
   stays readable, which is what the effect was for. */
.media-band.lit .media-tile:not(.on) .media-fan{ opacity:.28; }
.media-band.lit .media-tile:not(.on) .media-tile-name{ color:var(--text-dim); }

/* THE INVITATION, on a channel that has never been set up. The card has
   stopped being a filter — with nothing below it to filter, a filter has
   no job — so the one control it carries is this, named in the words of
   the button on the other end of it. Underlined rather than boxed, because
   a filled chip on a band with no fill anywhere else is the slab coming
   back in miniature. */
.media-tile-do{ display:inline-block; margin-top:var(--space-3); font-size:var(--type-body); font-weight:600;
  color:var(--area-media); line-height:1.5;
  border-bottom:1px solid color-mix(in srgb, var(--area-media) 45%, transparent);
  transition:border-color var(--dur-fast) var(--ease-out); }
button.media-tile.invite:hover .media-tile-do{ border-bottom-color:var(--area-media); }

/* ---- WHERE THE THIRD PIECE STOPS BEING WORTH DRAWING -----------------
   THE OTHER COST THE DEMO NAMED: "three fanned pieces of art in a 230px
   tile is a lot of edges, and on a laptop the third one is barely
   visible." A sliver of a picture is worse than no third picture — it
   reads as a rendering fault rather than as a stack.

   The number that decides it is how much of a back piece is left showing,
   which is --fan-h * --sp, and the fan needs about two --fan-h of column
   to hold three. Four channels inside the 1240px page give about 275px a
   column at full width and about 185px at 1240 and below, which is where
   the third piece starts being a rumour. So: three above 1240, two down to
   900, one below that. Below 560 the row becomes two-up — a single row of
   four at phone width is four slivers nothing can be read in — and the
   columns get wide again, so the second piece comes back with them.
   Two by two is still the whole set on one band and still the whole
   overview, which is the thing the rejected chevron would have taken
   away. It is the row giving up its shape, not its job. */
@media (max-width: 1240px){
  .media-tile{ --fan-h:94px; }
  .media-fan[data-n="3"] .media-art:nth-child(3){ display:none; }
  .media-fan[data-n="3"] .media-art:nth-child(1){
    --dx:calc(var(--fan-h) * var(--sp) * .85); --rot:3deg; }
  .media-fan[data-n="3"] .media-art:nth-child(2){ --dx:0px; --rot:-6deg; }
  .media-tiles{ gap:var(--space-5); }
}
@media (max-width: 900px){
  .media-tile{ --fan-h:86px; }
  .media-fan .media-art:nth-child(2),
  .media-fan .media-art:nth-child(3){ display:none; }
  .media-fan .media-art:nth-child(1){ --dx:0px; --rot:0deg; }
  .media-fan[data-n="3"] .media-art:nth-child(1){ --dx:0px; --rot:0deg; }
  .media-tiles{ gap:var(--space-4); }
}
@media (max-width: 560px){
  .media-tiles{ grid-auto-flow:row; grid-template-columns:repeat(2, minmax(0, 1fr));
    gap:var(--space-5) var(--space-5); }
  /* SHORTER THAN THE COLUMN COULD HOLD, on purpose. Two-up doubles the
     band's height, and a band that fills a phone screen before the first
     section is the 350px problem coming back through the side door. */
  .media-tile{ --fan-h:74px; }
  .media-fan[data-n="2"] .media-art:nth-child(2),
  .media-fan[data-n="3"] .media-art:nth-child(2){ display:block; }
  .media-fan[data-n="2"] .media-art:nth-child(1),
  .media-fan[data-n="3"] .media-art:nth-child(1){
    --dx:calc(var(--fan-h) * var(--sp) * .85); --rot:3deg; }
  .media-fan[data-n="2"] .media-art:nth-child(2),
  .media-fan[data-n="3"] .media-art:nth-child(2){ --dx:0px; --rot:-6deg; }
}

/* ---- THE TWO KINDS OF UPGRADE — item 286 -----------------------------
   Matt, 4 September: "We can have espn already loaded, but 'login to make it
   yours' on some channels." There are two things an empty card can be asking
   for and they must not read as the same request:

     CONNECT AN ACCOUNT asks for a PASSWORD and gives back your own data. It
     involves somebody else's service, it is the only kind that can fail for
     a reason nobody here controls, and it is the one worth setting in the
     area's own colour — the same weight as the lit rule, because it is the
     one press on the band that is a commitment.

     MAKE A CHOICE asks for a PREFERENCE, costs nothing, needs no account and
     is changed as easily as it was made. It is set in ordinary ink with the
     same underline, because it is an ordinary thing to do.

   The class comes from `invite.kind` in the config; areaTileHtml() also
   writes it to data-upgrade so the copy rule can be held by a harness rather
   than by whoever is reading the card that day. */
.media-tile-do.choose{
  color:var(--text);
  border-bottom-color:color-mix(in srgb, var(--text) 34%, transparent); }
button.media-tile.invite:hover .media-tile-do.choose{ border-bottom-color:var(--text); }

/* The door in a block's heading, sitting straight after the provenance note
   rather than pushed to the far edge with margin-left:auto. At the site-wide
   1240px an edge-aligned chip is four hundred pixels from the title it
   belongs to, which reads as a stray control rather than as part of the
   heading. Baseline-aligned with the rest of the head by .wl-panel-head.

   A REAL BUTTON SINCE ITEM 280, because it is now the only route from this
   page into the page it summarises — the row of tabbable doors that used to
   carry that job has become a row of filters. So it takes what a button
   brings and undoes only the three things a user-agent button style would
   otherwise impose on .ht-verb. */
.media-panel-door{ transform:translateY(-1px); font-family:inherit; background:transparent;
  line-height:1.5; }
.media-panel-door:focus-visible{ outline:2px solid var(--area-media); outline-offset:2px; }

/* THE DOOR WHEN THE CHANNEL IS THE WHOLE PAGE — item 1747.
   Pressing a channel card leaves one panel on screen, and this chip stops
   being one way on among four and becomes the ONLY one. At the weight above
   it reads as a second filter tag sitting beside the first, which is how
   "clicking Listening gives the mini player and never the full view" happens
   to somebody who is not looking for a small grey word.
   So alone on the page it takes the area's own ink, a hairline of its own and
   the chevron every other way-on in Lucid carries. It is still the same
   button in the same place — nothing moves, it is only finally legible as a
   door. The chevron leans in on hover, which is the app's standing gesture
   for "this goes somewhere". */
.media-door-go{ font-size:var(--type-lead); line-height:1; margin-left:var(--space-2); display:inline-block;
  transition:transform var(--dur-fast) var(--ease-out); }
.media-panel-door:not(.solo) .media-door-go{ display:none; }
.media-panel-door.solo{ color:var(--area-media);
  border:1px solid color-mix(in srgb, var(--area-media) 34%, transparent);
  border-radius:var(--radius-pill); padding:var(--space-1) var(--space-3) var(--space-1); margin-left:var(--space-1);
  transition:border-color var(--dur-fast) var(--ease-out),
             background-color var(--dur-fast) var(--ease-out); }
.media-panel-door.solo:hover{
  border-color:color-mix(in srgb, var(--area-media) 70%, transparent);
  background:color-mix(in srgb, var(--area-media) 10%, transparent); }
.media-panel-door.solo:hover .media-door-go{ transform:translateX(3px); }

/* NOW PLAYING. The seek bar and the transport are the Listening page's own
   .mp-seek / .mp-bar elements, unchanged — see mediaPlayingHtml() for why
   this block must not grow controls of its own. */
/* Capped, unlike the blocks around it. The page runs to the site-wide
   1240px and the lists on it are right to; a seek bar four feet long is not
   — it turns a two-second scrub into a mouse journey, and the thumb ends up
   nowhere near the track it belongs to. */
.media-now{ display:flex; gap:var(--space-4); align-items:flex-start; max-width:640px;
  border:1px solid var(--glass-border); border-radius:var(--radius-card); background:var(--glass-bg);
  padding:var(--space-4) var(--space-4); }
.media-now-art{ width:76px; height:76px; border-radius:var(--radius-card); object-fit:cover; flex:none;
  background:var(--bg-mid); }
.media-now-main{ min-width:0; flex:1; }
.media-now-t{ font-size:var(--type-lead); font-weight:650; color:var(--text); line-height:1.3; }
.media-now-s{ font-size:var(--type-body); color:var(--text-dim); margin-top:1px; }
.media-now-on{ font-size:var(--type-body); color:var(--text-faint); margin-top:var(--space-1); }
.media-now-seek{ margin-top:var(--space-2); }

/* MEASURED, NOT GUESSED. tests/test-readability.js failed on exactly four
   strings on this page in the light theme, and all four are Home-tile
   classes doing a job they were not tuned for.

   .ht-time is --text-faint, which clears 4.5:1 against a TILE'S OWN FILL and
   lands at 4.1:1 against the bare page these rows sit on — the tile was
   doing half the work and this page has no tile. The W badge is 11px of
   tinted green, and 11px is the size at which antialiasing eats a stroke
   thinner than a pixel: darkening the token barely moves it, more ink does.

   Scoped to #mediaBody rather than changed globally, because the Home tiles
   ARE tuned at those values and pass at them. */
/* --text-dim was still 4.32:1 at 10.5px mono, so this is full ink and a
   little more of it. That is not a compromise on this page: the number at
   the end of a score row IS the thing he came to read, and "yesterday" at
   the end of a feed row is one word. The size difference against the 13.5px
   title beside it is what carries the hierarchy, not the greyness. */
#mediaBody .ht-time{ color:var(--text); font-size:var(--type-body); }
#mediaBody .sport-badge{ font-size:var(--type-body); }
#mediaBody .sport-badge.won{ color:color-mix(in srgb, var(--green-text) 55%, var(--text)); }
@media (max-width: 560px){
  .media-now{ flex-direction:column; }
  .media-now-art{ width:64px; height:64px; }
}
