/* ==========================================================================
   Preheat: the desk. Baker app only, width-gated.

   Loaded by application.html.erb and not by storefront.html.erb. Be honest
   about what that buys: today it is 238 gzipped bytes of CSS against a
   storefront payload of about 14.6 KB, so as a saving it rounds to nothing.
   The reason to keep the file is the trajectory rather than the number. Every
   screen that gets a desk pass adds rules here, all of them useless to a
   customer on a cold phone tap with a 1.5s LCP budget, and the alternative is
   noticing that in a year. One line in one layout is a cheap way never to have
   to think about it again.

   Two house rules. They are conventions, not checks. Nothing enforces them; an
   earlier version of this comment claimed a script did, which is worse than
   saying nothing:

   1. Every class named here is already defined in components.css or
      screens.css. This file varies existing components; it invents none.
   2. Every rule here is `@media screen and (min-width: ...)`, gated on
      .app--web. `screen and` because a media query in paged media evaluates
      against the page area, and US Letter landscape is about 979px, so a bare
      min-width: 900px matches on paper. That matters especially here, because
      this file loads AFTER the @media print block in screens.css that used to
      have the last word on the frame.

   What is actually checked, by a browser rather than a regex, is in
   spec/system/desktop_gate_spec.rb: a shell User-Agent gets the phone geometry
   at 1280, and no reading column narrows as the viewport widens.
   ========================================================================== */

/* ---------- The desk tier ------------------------------------------------

   Two compositions, deliberately not three: a screen either fills the width or
   it is content plus a sticky rail. The obvious middle option, a narrow
   measured column in a wide frame, is what makes an app look unfinished, with
   a 620px column stranded beside a sidebar and the content's right edge
   jumping between screens in one navigation.

   Which one a screen is does not need a name. A desk screen containing a
   .split is content plus a rail; one without fills the width. So the flag is a
   boolean and CSS reads the composition off the markup, rather than off an
   enum that then has to agree with the markup.

   1280 rather than 1200: the frame is max-width capped, so at 1200 the split
   content column comes out at 544px against 524px in the tier below. Not a
   regression, just not enough of a change to justify the whole composition
   rearranging. 1280 buys 100px and is where laptops sit. */

@media screen and (min-width: 1280px) {
  /* rivet:context: EVERY rule in this block requires [data-desk], not just
     .app--web, and the difference is a bug that shipped.

     .split's grid was gated on the web class alone, so any template containing
     one laid out in two columns whether or not its screen had a desk pass. Not
     hypothetical: shops#update re-renders shops/edit on a validation failure,
     ScreenWidth knew about `edit` and not `update`, so a failed save dropped
     the frame back to 560px and then divided it into a 136px column beside a
     340px rail. The form was unusable and nothing went red, because no
     screenshot in the suite is of a form that failed to save.

     A composition is a property of a designed screen, so it asks for the flag
     the screen carries rather than for the surface it is on. */

  /* The frame takes the room beside the sidebar, up to a cap. Without this
     .split has nothing to divide: the grid computes to a 160px content column
     against a fixed 340px rail, which is worse than the single column it
     replaced.

     The attribute sits on the frame rather than on <main> so this can be a
     plain descendant selector. It was a :has() looking one element down, which
     bought a whole selector feature to read a flag that could be written one
     element higher. */
  .app--web .app__frame[data-desk] {
    max-width: var(--app-width-desk);
  }

  .app--web .app__frame[data-desk] .screen {
    padding-inline: var(--gutter-wide);
    gap: 18px;
  }

  /* rivet:context: align-items is the load-bearing declaration here, and it
     used to be written as align-self on the child, where it did nothing. A
     grid item stretches to its row's height by default, so a sticky child is
     already as tall as the thing it is meant to stay beside and has nowhere to
     stick to. Measured with the rail taller than the sticky half: remove this
     and that column's height goes 411px to 747px and its top goes 28px to -75px
     after a scroll. Deleting the align-self changed neither. */
  .app--web .app__frame[data-desk] .split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--rail-width);
    align-items: start;
    gap: var(--gutter-wide);
  }

  /* rivet:context: margin-block: 0 is a bug fix and the bug is invisible on a
     phone. `.spacer` is `margin-top: auto`, which on a phone pushes the primary
     action toward the thumb and is exactly right. In grid an auto margin
     absorbs the free space BEFORE alignment runs, so it beats the align-items
     above: measured, a rail shorter than its summary sat 480px down its own
     row with that much empty cream above it. Reachable on a cancelled order,
     which drops most of the action stack while the summary still carries
     inspiration photos.

     overflow-wrap, and NOT min-width: 0. minmax(0, 1fr) already caps the
     content track's automatic minimum, so min-width changes nothing here,
     measured. The overflow comes from the FIXED rail, where a long unbroken
     string cannot shrink the track: 108px of horizontal page scroll at every
     width from 1280 up, out of one email address. `.label` carries the same
     declaration for the same reason. */
  .app--web .app__frame[data-desk] .split > * {
    margin-block: 0;
    overflow-wrap: anywhere;
  }

  /* The sticky half becomes a real box again. On a phone it is
     display: contents, so its children sit directly in .screen and take
     .screen's gap; here it has to reproduce that gap itself.

     Which half sticks is a per-screen decision, and the rule is that the
     SHORTER half sticks. Pinning the taller one achieves nothing, because the
     taller one is what scrolls. */
  .app--web .app__frame[data-desk] .split__sticky {
    display: flex;
    flex-direction: column;
    gap: 18px;
    position: sticky;
    top: var(--gutter-wide);
  }

  /* The sticky half supplies its own gap, so a top margin a child carried for
     the phone's benefit is double spacing here. Same reasoning as the
     margin-block reset on .split's own children, one level down. */
  .app--web .app__frame[data-desk] .split__sticky > * {
    margin-block: 0;
  }

  .app--web .app__frame[data-desk] .desk-only {
    display: revert;
  }

  /* A .rows of links has no value column, so it must not take the two-track
     key/value grid the desk tier gives every other .rows__row. */
  .app--web .app__frame[data-desk] nav.rows .rows__row {
    display: block;
  }

  /* The non-rail half of a split whose content is peer sections rather than
     one form. The same flex column .split__sticky becomes, without the
     sticking: the taller half is what scrolls, so pinning it achieves
     nothing. 18px matches the gap .screen gives these sections on a phone.

     This replaced a multicol .panels that balanced the shop screen's sections
     by height. The balance was measured and right for five sections, and two
     releases later two more sections had grown under it and the columns read
     as broken: tops that never aligned, a break the browser moved every time
     the content changed. A rail does not balance against anything, which is
     why it survives the next section this screen grows. */
  .app--web .app__frame[data-desk] .split__stack {
    display: flex;
    flex-direction: column;
    gap: 18px;
  }

  .app--web .app__frame[data-desk] .split__stack > * {
    margin-block: 0;
    overflow-wrap: anywhere;
  }

  /* rivet:context: .rows stops being space-between once it is wide, and this is
     the rule the whole desk tier exists to make possible.

     A .rows__row is flex with space-between, which is right at 460px: "Serves"
     and "12" are close enough to read as one fact. Give the same row 700px and
     they are two unrelated facts at opposite ends of a line. That is why the
     frame is not simply widened for every screen, and why the two have to
     land together: widening without this makes the app worse, not emptier.

     A two-track grid pins the key column so values line up down the card. 15em
     rather than a pixel value so the column tracks the type rather than
     fighting it. */
  .app--web .app__frame[data-desk] .rows__row {
    display: grid;
    grid-template-columns: minmax(0, 15em) minmax(0, 1fr);
    gap: 0 20px;
  }

  .app--web .app__frame[data-desk] .rows__value {
    text-align: left;
  }

  /* rivet:context: 14em, and the arithmetic is the whole rule. .rows__row is
     14px and .rows__row--total is 15px, so an em-based track resolves WIDER on
     the total than on every row above it: measured, values at x=246 down the
     card and the total's at x=261, a 15px step on the one line the eye goes to.
     14em at 15px is 210px, which is 15em at 14px exactly.

     The comment that used to sit above said the total "needs no rule of its own:
     it inherits this and resolves 15em against its own larger font size". That
     is true, and it is the bug, described as if it were the reason. */
  .app--web .app__frame[data-desk] .rows__row--total {
    grid-template-columns: minmax(0, 14em) minmax(0, 1fr);
  }

  /* rivet:context: A rail is 340px, which is NARROWER than the phone the
     space-between layout was designed for, so the grid above is the wrong
     medicine inside one: a 15em key column leaves about 78px for the value, and
     less on a total row. space-between is not a fallback in a rail, it is the
     correct rendering for a column that narrow.

     .invoice is exempt for a different reason. That screen is a print preview:
     the content half is 696px against the 739px a US Letter page gives it at
     default margins. This file is screen-only, so @media print reverts these
     rows anyway, and without the exemption the preview would lay out
     differently from the page it is previewing. It also means ui/_invoice needs
     no change at all, so the baker's copy and the customer's do not fork. */
  .app--web .app__frame[data-desk] .split__rail .rows__row,
  .app--web .app__frame[data-desk] .invoice .rows__row {
    display: flex;
    justify-content: space-between;
  }

  .app--web .app__frame[data-desk] .split__rail .rows__value,
  .app--web .app__frame[data-desk] .invoice .rows__value {
    text-align: right;
  }

  /* rivet:context: The rail is placed explicitly, which decouples column order
     from DOM order, and that is what lets a screen write its rail FIRST.

     Auto-placement puts the first child in column 1, so every screen with a
     rail would have to write its content half first. The quote builder cannot:
     a phone opening a sent quote has to meet the total, the expiry and the link
     before 1300px of editor, and DOM order IS the phone's reading order, which
     is not tradeable. The orders board cannot either, for the same reason about
     its stage filter.

     The cost is an invariant: inside a .split with a rail, the rail half must
     carry .split__rail. Leave it off and :not() matches both halves, they both
     land in column 1, and the two columns stack on top of each other. */
  .app--web .app__frame[data-desk] .split > .split__rail {
    grid-column: 2;
    grid-row: 1;
  }

  .app--web .app__frame[data-desk] .split > :not(.split__rail) {
    grid-column: 1;
  }

  /* rivet:context: overflow-wrap: anywhere above is written for an email address
     in a 696px column. In a 340px rail it reaches ordinary English and broke
     "deposit" as "depos / it". A short mark that must not break says so. */
  .app--web .app__frame[data-desk] .split__rail .nowrap {
    white-space: nowrap;
  }

  /* rivet:context: 60px rather than the 44px floor, and the floor is not what
     is being relaxed. 44px is a THUMB minimum and it still governs the customer
     date picker, which renders from the same partial on a layout that never
     loads this file. Here the cell is 93px wide with a mouse pointing at it,
     and a 93 by 44 box reads as a squashed pill rather than a day. The count
     under the number is what a baker is actually reading.

     Left-aligned for the same reason: a number centred in a wide short box has
     nothing to sit against. */
  .app--web .app__frame[data-desk] .cal__day {
    min-height: 60px;
  }

  /* rivet:context: The stage filter becomes a vertical list in the rail, and
     the overflow reset is the load-bearing half. .pills is a horizontal flex
     strip with overflow-x: auto and hidden scrollbars, which is right on a
     phone where five stages do not fit 390px. In a 340px rail it does the same
     thing and clips "Done" off the end behind no visible affordance at all: a
     filter you cannot see the last option of. */
  .app--web .app__frame[data-desk] .split:not(.split--lead) .split__rail .pills {
    flex-direction: column;
    align-items: stretch;
    overflow-x: visible;
    gap: 4px;
    padding-bottom: 0;
  }

  .app--web .app__frame[data-desk] .split:not(.split--lead) .split__rail .pill {
    display: flex;
    justify-content: space-between;
    border-radius: var(--radius-chip);
    padding: 10px 14px;
  }

  /* rivet:context: The second composition a wide screen is allowed: the rail
     leads the content instead of sitting beside it. Two shapes total, and this
     is not a third one — .split--lead is still content plus a rail, turned
     ninety degrees.

     It exists because a filter is not context. A rail beside the content says
     "here is more about what you are looking at"; the stage pills say what you
     are looking at AT ALL, and putting them on the right gave the board a
     second vertical navigation list facing the sidebar across the content, on
     the wrong edge, reading like a mail app's folders. The eye met two orders
     before it met the word Inquiry.

     display: block rather than a one-column grid, and only because it is one
     declaration instead of three: in flow the band and the list stack without
     needing their columns and rows respecified. Both stick, which was worth
     measuring — an earlier version of this note claimed a grid item's sticky
     travel is bounded by its own grid area so a one-column grid could never
     work, and Chrome puts the band at top: 0 after a 448px scroll either way.

     Sticky is the property being preserved from the rail version and the only
     reason this is not simply "put the filter first". Done is the stage you
     scroll, even windowed by month, and the control that gets you out of it
     used to leave the viewport on the first flick.

     It comes from .split__sticky and is deliberately not re-declared here.
     That class is not optional on the wrapper anyway: it is what makes it
     `display: contents` on a phone, without which the div would break the
     column it sits in. Restating `position: sticky` in this block made the two
     sources cover for each other, and the spec for the behaviour could not be
     made to fail by deleting either one.

     The background is what makes the stickiness legible. Without it the cards
     scroll through the gaps between the pills. */
  .app--web .app__frame[data-desk] .split--lead {
    display: block;
  }

  .app--web .app__frame[data-desk] .split--lead > .split__rail {
    top: 0;
    z-index: 2;
    gap: 12px;
    margin-bottom: 18px;
    padding-block: 14px;
    background: var(--cream);
  }

  /* rivet:context: The card stops being four stacked lines and becomes two,
     with the repeating facts in tracks of their own. It is not a table: no
     header, no sort, no column widths the eye has to be taught, no truncation,
     and it is still one link.

     The two .row--between wrappers go display: contents so their children
     become grid items of the card itself. They stay in the inheritance chain,
     which is why the muted 13px on .order-card__when still reaches the date
     and the money inside it.

     Placement is explicit because DOM order is the phone's order and is not
     negotiable: the badge is written next to the name, and it belongs at the
     far right here.

     The name is a fixed measure and only the title flexes, which the board
     needed once it stopped sharing its row with a rail. Two fr tracks divided
     a 750px column tolerably and a 1064px one badly: a fifty-pixel name like
     "Mia R." sat in a 258px track and opened a gap to the title wide enough to
     read as a missing column. Names are short and bounded; a cake description
     is the thing that actually varies. */
  .app--web .app__frame[data-desk] .order-card {
    display: grid;
    grid-template-columns: minmax(0, 13em) minmax(0, 1fr) 150px 110px 100px;
    align-items: baseline;
    gap: 2px 18px;
  }

  .app--web .app__frame[data-desk] .order-card__head,
  .app--web .app__frame[data-desk] .order-card__when {
    display: contents;
  }

  .app--web .app__frame[data-desk] .order-card__name { grid-column: 1; grid-row: 1; }
  .app--web .app__frame[data-desk] .order-card__title { grid-column: 2; grid-row: 1; }
  .app--web .app__frame[data-desk] .order-card__date { grid-column: 3; grid-row: 1; }
  .app--web .app__frame[data-desk] .order-card__money { grid-column: 4; grid-row: 1; text-align: right; }
  .app--web .app__frame[data-desk] .order-card .badge { grid-column: 5; grid-row: 1; justify-self: end; }
  .app--web .app__frame[data-desk] .order-card__age { grid-column: 2; grid-row: 2; }

  /* The margins were vertical rhythm for a stack of lines. In a grid the
     row-gap does that job and the margins push the tracks off each other. */
  .app--web .app__frame[data-desk] .order-card__title,
  .app--web .app__frame[data-desk] .order-card__when,
  .app--web .app__frame[data-desk] .order-card__age {
    margin-top: 0;
  }

  /* rivet:context: The thread becomes one line, the way an inbox row is one
     line, and the preview gets the width the tiling was spending on gutters. On
     a phone the name and the time share a row and the preview sits under them;
     here all three are one row, so the eye runs down a column of names and a
     column of times with the message between.

     Same mechanism as .order-card: the .row--between wrapper goes
     display: contents so its two children become grid items of the card, and
     placement is explicit because DOM order is the phone's order and the time
     is written next to the name.

     The preview is clamped to one line rather than allowed to wrap. That is a
     truncation, which .order-card deliberately does not do, and it is right
     here for the opposite reason: a DM is not a fact you need in full from the
     list, it is a reminder of which conversation this is, and a two-line row
     among one-line rows breaks the column the whole change exists to make. */
  .app--web .app__frame[data-desk] .thread-card {
    display: grid;
    grid-template-columns: minmax(0, 15em) minmax(0, 1fr) 130px;
    align-items: baseline;
    gap: 0 18px;
  }

  .app--web .app__frame[data-desk] .thread-card__head {
    display: contents;
  }

  /* The row is explicit on all three and not just the column. DOM order is
     name, time, preview, so auto-placement puts the preview past column 2 and
     wraps it to a second row: the name and the time sit on one line and the
     message drops below them, which is the phone layout with a hole in it. */
  .app--web .app__frame[data-desk] .thread-card__name { grid-column: 1; grid-row: 1; }
  .app--web .app__frame[data-desk] .thread-card__preview { grid-column: 2; grid-row: 1; }
  .app--web .app__frame[data-desk] .thread-card__when { grid-column: 3; grid-row: 1; text-align: right; }

  .app--web .app__frame[data-desk] .thread-card__preview {
    margin-top: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* rivet:context: A cap on the line, not on the column. The frame is capped at
     1120px because a reading column stops improving past a measure, and prose
     inside it was never given the same courtesy: the auto-reply screen ran a
     hint the full 1064px at about 177 characters a line, and the help text under
     a settings field ran 111. Comfortable is 45 to 90, and the eye loses its
     place returning from the end of a line to the start of the next one.

     ch rather than px so it tracks the type: it is the width of a "0", so this
     is a statement about characters and stays one if a font size moves.

     .empty is excluded because it has a cap of its own and is centred, and a
     second max-width would fight the margin: auto that centres it. */
  .app--web .app__frame[data-desk] p:not(.empty),
  .app--web .app__frame[data-desk] .hint,
  .app--web .app__frame[data-desk] .lede,
  .app--web .app__frame[data-desk] .field__help {
    max-width: 68ch;
  }

  /* Only the measure and the padding. The card itself is in components.css and
     unconditional: it was written here first, which gave it to the one surface
     that needed it least, since a baker signs up on her phone and this is what
     almost every screen is on her first day. 34em rather than a pixel value so
     it tracks the type. */
  .app--web .app__frame[data-desk] .empty {
    max-width: 34em;
    padding: 40px 32px;
  }

  /* rivet:context: A full-width primary action is a PHONE pattern. It exists so
     a thumb cannot miss it, and at 1064px it is a slab: the "Pantry" button on
     the recipes screen was a metre of white with one word in the middle of it.

     Capped by default at the desk tier and un-capped in the two places where
     full width is still right: inside a 340px rail, where a block button is the
     correct weight, and inside a card, where the button belongs to the card's
     width rather than the screen's. The un-capping rules carry the same prefix
     so they win on specificity rather than on source order. */
  .app--web .app__frame[data-desk] .btn--block {
    max-width: 22em;
  }

  .app--web .app__frame[data-desk] .split__rail .btn--block,
  .app--web .app__frame[data-desk] .card .btn--block,
  .app--web .app__frame[data-desk] .invoice .btn--block {
    max-width: none;
  }

  /* A search field is not a reading column. A 1064px box for a first name puts
     the caret nowhere near where the eye is. Capped on the input itself so this
     needs no wrapper class that would mean nothing on a phone. */
  .app--web .app__frame[data-desk] .input[type="search"] {
    max-width: 26em;
  }

  /* A catalogue rather than a list, for the screens that are a set of peers you
     tap into. 300px against the 1120px capped frame is three across at every
     desk width, so the count does not change under the baker as she resizes.
     auto-fill and not auto-fit: one recipe should be one card at card width,
     not one card stretched across the frame. */
  .app--web .app__frame[data-desk] .card-grid {
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 14px;
  }
}

@media screen and (min-width: 1280px) {
  /* Every selector below is compound on .app--solo, which is the class the
     layout emits when nothing is beside the column: signed out, or a shell
     drawing its own navigation. Nothing signed in can pick any of it up, and
     the gate is the only thing that is both signed out and a browser.

     That also settles what this block is doing beside .split. ScreenWidth is
     now a boolean — "has this screen had a desk pass" — and what composition a
     designed screen actually uses is read off the template, because the
     template is the thing that already says. .split says content plus a rail;
     .auth--wide says a form with the reason for it beside. Neither needs an
     enum to agree with. */

  /* Sign in, forgot and reset: the column gets NARROWER than the frame it sits
     in, and that is the point. Measured at 1440 today, the email field is
     522px across for a value that is never more than about 30 characters, and
     the Sign in button is 522px across for two words. What a desk buys this
     screen is air and type size; it has no more content to show, so span is
     the one thing it must not buy.

     This does not touch the width ladder in spec/system/desktop_gate_spec.rb.
     That ratchet measures the signed-in reading column, and the gate column
     still only widens: 354 on a phone, 380 from 900, 420 from here. */
  .app--web.app--solo .auth {
    max-width: 420px;
    gap: 24px;
  }

  .app--web.app--solo .auth__card {
    padding: 34px;
    gap: 22px;
  }

  .app--web.app--solo .auth__title {
    font-size: 32px;
  }

  .app--web.app--solo .auth__brand img {
    width: 40px;
    height: 40px;
  }

  .app--web.app--solo .auth__brand .title {
    font-size: 28px;
  }

  /* Registration is the one gate screen with something to put beside the form,
     because it is the only one where the reader has not decided yet. On sign
     in a value proposition next to the password box is noise: she decided
     months ago and is trying to get in.

     The frame is already 1120 by the time this matches: registrations#new is
     in ScreenWidth::DESIGNED, so the layout puts data-desk on the frame and
     the rule at the top of this file widens it. Adding it to that list is not
     a workaround for the width — it is the true statement the list is for,
     that somebody has now drawn this screen for a desk. */
  /* A capped pitch column rather than 1fr, and the grid centred inside the
     frame. 1fr hands the pitch every pixel the card does not want, which put
     330px of cream between a left-hugging sentence and the form it belongs to,
     so the two halves read as two screens. */
  .app--web.app--solo .auth--wide {
    max-width: none;
    display: grid;
    grid-template-columns: minmax(0, 400px) 420px;
    grid-template-rows: auto auto;
    align-items: start;
    justify-content: center;
    gap: 16px 64px;
  }

  /* The pitch spans both rows and centres against the whole form, so the
     headline sits opposite the middle of the card rather than its top edge. */
  .app--web.app--solo .auth--wide .auth__aside {
    grid-column: 1;
    grid-row: 1 / 3;
    align-self: center;
    gap: 20px;
  }

  .app--web.app--solo .auth--wide .auth__card,
  .app--web.app--solo .auth--wide .auth__intro {
    grid-column: 2;
  }

  /* The one line on this screen that is not the form goes back under the card,
     where the eye already is, rather than under the pitch. */
  .app--web.app--solo .auth--wide .auth__intro {
    text-align: left;
  }

  /* Same sentence the landing page opens with, in the product's serif instead
     of the landing site's grotesque. The words carry over because it is the
     same promise; the type changes because she has walked in the door. */
  .app--web.app--solo .auth--wide .auth__title {
    font-size: 44px;
  }

  .app--web.app--solo .auth--wide .auth__points {
    flex-direction: column;
    gap: 11px;
    font-size: 14.5px;
  }
}

/* ---------- Wider than the work --------------------------------------------

   rivet:context: Nothing happens. There is deliberately no tier above the desk
   one, and the absence is the decision.

   There used to be a rule at 1600 that centred the sidebar and the frame
   together, on the reasoning that an app pinned to the left of a 27 inch
   monitor reads as unfinished. It does not: what it actually produced was a
   full-height navigation rail floating in the middle of nowhere with a stripe
   of cream down its left, which reads far worse and was spotted on a real
   screen within a day. A full-height rail belongs on the edge of the viewport;
   that is what makes it a rail rather than a panel.

   So the sidebar stays flush left at every width and the surplus falls to the
   right of the content, which is what every left-navigation app does. The frame
   is capped at --app-width-desk, so past about 1350px the extra space stops
   being given to the content and simply sits there. That is the correct
   outcome: a reading column does not improve past a certain measure, and the
   alternative is a line of text a metre wide. */
