/* The order form, in parts: the stepped builder somebody fills in BEFORE they
 * buy - a part that unfolds at a time, the bulk ladder on the listing form, the
 * account picker, the running cart, and the pair of buttons that move between
 * them.
 *
 * Split off the tail of forms.css on 2026-08-26, at the seam that sheet's own
 * banner had marked since it was written. Not an arithmetic cut: forms.css is
 * the controls and the lines that REPORT a state, and every rule below belongs
 * to one composite widget that is finished the moment the order is placed. The
 * five pages a customer meets after paying draw none of it except `.check-row`.
 *
 * Linked IMMEDIATELY AFTER forms.css and the order is the cascade: these rules
 * sat below those in one file, so anything here that meets a forms.css selector
 * at equal specificity has always won, and moving the link is a redesign.
 *
 * No line count in this header, and none anywhere else in this directory. The
 * pair that used to justify these splits went 151 lines stale, and the fix that
 * replaced them went stale again inside a day. `wc -l static/css/*.css` answers
 * now and cannot rot; `docs/constants.toml` -> `max_code_loc` owns the ceiling. */

/* Open is the default state and it is written here rather than in the script,
   which is what makes the whole form work with nothing running: every part is
   on screen until gift-steps.js shuts one. The `hidden` attribute is what shuts
   it, because it is the one thing that takes a part out of the picture and out
   of the reading order at the same time. */
.qstep { position: relative; display: grid; grid-template-rows: 1fr; }
/* The clip the unfold needs. The row is what shrinks, and a grid item spills
   out of a row too short for it unless the item itself clips. The padding is
   room for a focus ring, which the clip would otherwise shave off the outside
   edge of every control in here, and the matching negative margin puts the box
   back where it was.

   The number is outline width plus outline offset, and it has to clear the
   WIDEST ring in here. That is the global `:focus-visible` in app.css at
   2px + 2px = 4px, which Back, Continue, `.btn-add` and the `.btn-block` submit
   all use; the fields' own 2px + 1px = 3px is the narrower one. It was 3px
   until 2026-08-02 and clipped every button ring by exactly a pixel - on both
   sides of the two that are `width: 100%`, which touch both edges. Change
   either rule and change this to match. */
.qstep-in { position: relative; min-height: 0; overflow: hidden; padding: 4px; margin: -4px; }
.qstep + .qstep { margin-top: var(--step-6); }
/* The gap ABOVE the first part, which nothing owned. `/rent` opens its card with
   a line offering the listings page, and the part heading under it sat flush
   against that sentence and read as its last line. The rule above spaces parts
   from each other; a part is the first thing after a paragraph only here, so
   this is written against the paragraph rather than against `:first-of-type`,
   which would fire on a card whose part really is the first thing in it. */
p + .qstep { margin-top: var(--step-6); }
/* One line of the cart, and one account the Robux may come out of. Five of
   either stacked with nothing between reads as one long form of fifteen
   controls, so each is given a rule and a number.
   The margin is on every line and not only between them: without it the first
   number sits flush under the part heading and reads as part of it. */
.qline, .qacct { margin-top: var(--step-8); }
.qline + .qline, .qacct + .qacct {
  padding-top: var(--step-8); border-top: 1px solid var(--border);
}
/* Showing one line at a time is the script's doing, and a rule drawn above the
   line that is showing separates it from nothing: the lines before it are
   display:none, which an adjacent sibling selector does not care about. */
.qline[hidden] + .qline { padding-top: 0; border-top: 0; }
/* Pulled up by exactly the top margin the field below it carries, so the number
   belongs to its line rather than floating between two. */
.qline-n {
  display: block; margin-bottom: calc(var(--step-6) * -1);
  font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--text-3);
}
/* `fit-content`, because this heading is a focus target. A block level heading
   takes the whole card, and the ring drawn round it read as an empty text field
   sitting above the real one rather than as the name of the part you were just
   moved to. Round the words it is unmistakably a heading. */
.qstep-t {
  width: fit-content;
  font-family: var(--font-display); font-size: 17px; font-weight: 600;
  letter-spacing: -0.01em; color: var(--text-1);
}
/* Which part of how many. A form that opens one part at a time hides its own
   length, so somebody deciding whether to start cannot tell what they are
   agreeing to; this is the whole of the answer.

   Inside the heading rather than above it, and that is the reason it is a span
   and not another `.qline-n`: the heading is the focus target and the one thing
   a screen reader announces on arrival, so a number outside it is a number only
   sighted readers ever get. It has to name its own font, because the heading
   sets the display face and everything in here would otherwise inherit it. */
.qstep-n {
  margin-left: var(--step-3);
  font-family: var(--font-body); font-size: 12px; font-weight: 500;
  letter-spacing: .08em; text-transform: uppercase; color: var(--text-3);
}

/* The one moment on this page that is allowed to be a moment: a hairline in the
   accent draws itself across the gap, and the new part unfolds under it. Both
   are the page's own vocabulary rather than a new one -- the nav underline
   already sweeps in from the left, and the result rows already arrive on this
   curve. Drawn in the middle of the gap between two parts, and only between
   two: nothing draws a line above the first. */
.qstep + .qstep::before {
  content: ""; position: absolute; left: 0; right: 0;
  top: calc(var(--step-3) * -1); height: 1px; transform-origin: left;
  background: linear-gradient(90deg, var(--accent), rgb(var(--accent-rgb) / .06));
}
/* Only ever set by the script, and only on a part it has just opened, so a
   page with no script never animates anything here. `backwards` holds the
   first frame through the delay: without it the part would sit open for
   120ms and then jump shut to start. */
.qstep.is-new { animation: qstep-open 460ms cubic-bezier(.16,.84,.44,1) 120ms backwards; }
.qstep.is-new::before { animation: qstep-rule 440ms cubic-bezier(.16,.84,.44,1); }
@keyframes qstep-open {
  from { grid-template-rows: 0fr; opacity: 0; }
  to { grid-template-rows: 1fr; opacity: 1; }
}
@keyframes qstep-rule { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* The bulk ladder on the listing form: three optional rows, each an order size
   beside what that size pays. A fieldset, so the three rows read as one
   question and its name is announced on the way into any of them; the browser's
   own border and padding go, because the card already draws the box this sits
   in, and `min-width` because a fieldset defaults to min-content and would
   refuse to shrink inside one.

   Flex and not a grid: a row is a pair of boxes that has to fall to one column
   on a narrow phone, and wrapping at the basis below does that on its own with
   no breakpoint to keep in step with every other one in these sheets. */
.tiers { margin-top: var(--step-8); padding: 0; border: 0; min-width: 0; }
/* It heads ten boxes, so it is a heading. At 13px `--text-2` it was pixel for
   pixel a `.field label`, and the whole bulk-discount section read as one more
   labelled input. */
.tiers legend { padding: 0; font-size: 15px; color: var(--text-1); }
/* The empty rungs, folded behind the browser's own disclosure. Five rows a
   listing is about a thousand pixels of blank boxes on a phone, and the page
   they are on carries no script to fold them with - so this is `<details>`,
   which needs none. Only the summary is styled; the default triangle stays,
   because it is the affordance every browser already draws for this. */
.tiers details > summary {
  color: var(--text-2); font-size: 14px; cursor: pointer;
  /* A FLOOR under the tap target and only a floor. `display: flex` would centre
     the words in it and would also take the summary off `display: list-item`,
     which is the only thing drawing the triangle the note above keeps on
     purpose. Symmetric padding inside a border-box 44px centres it anyway. */
  padding: var(--step-3) 0; min-height: 44px;
}
/* It declares `cursor: pointer` and answered one with nothing, which no other
   control here does. */
.tiers details > summary:hover { color: var(--text-1); }
.tiers details[open] > summary { margin-bottom: var(--step-3); }
.tier-row { display: flex; flex-wrap: wrap; gap: 0 var(--step-4); }
/* Wraps at 2 x 9rem plus the gap, so the pair falls to one column under a 304px
   card and needs no breakpoint. `min-width: 0` is what lets it: a flex item's
   automatic minimum is its own min-content, and a `.field` holding a text input
   reports the input's `size` attribute - 20 characters of 18px mono, about
   250px. Without this the two rows stacked correctly and then each one
   overflowed the card by 34px on a 320px phone, which is 216px of usable
   width. */
.tier-row .field { flex: 1 1 9rem; min-width: 0; }

/* Moving between the parts. Built by the script and only by the script, over a
   card that is showing one part at a time; with nothing running there are no
   parts to move between and none of this exists. Back is as wide as its word
   and Continue takes the rest, because they are not the same size of decision. */
.qnav { display: flex; gap: var(--step-3); margin-top: var(--step-5); }
.qnav .btn { margin-top: 0; }
/* Why Continue did nothing, said above it. A notice normally HEADS a card, so
   `.quote .notice` takes its top margin off; this one sits mid card between a
   field and the buttons and needs that space back. Same specificity, later in
   the sheet, which is the whole of why it wins. */
.notice.qnav-why { margin-top: var(--step-5); }
.btn-next { flex: 1; }
/* --border-int rather than the inherited --border-str: this one drops the
   raised fill for the page's own background, so the boundary is the only
   thing left saying it is a button. */
.btn-back {
  flex: 0 0 auto; color: var(--text-2); background: var(--bg-0);
  box-shadow: none; border-color: var(--border-int);
}
.btn-back:hover { color: var(--text-1); }

/* Opening a slot that is already on the page, which is a different act from
   moving to the next part, so it may not look like the button that does. An
   empty well rather than a raised button: the same inset the text fields carry,
   because what it opens IS another one of those. Only ever built by the script,
   over slots that were served folded away for having nothing in them. */
.btn-add {
  width: 100%; margin-top: var(--step-5);
  background: var(--bg-0); border: 1px solid var(--border-int); border-style: dashed;
  box-shadow: var(--shadow-inset);
  color: var(--text-2); font-size: 14px; font-weight: 500;
}
.btn-add:hover { border-color: rgb(var(--accent-rgb) / .9); color: var(--text-1); }   /* .9, see `.hit:hover` */
/* The argument the sheet already makes for `.btn-primary:disabled`, applied to
   the other button it is true of: `opacity: .6` composites the whole control,
   which drags this `--text-2` label to 3.69:1 and its edge to 1.93:1 - and that
   label is the only thing saying another gift cannot be added yet. `--text-3` at
   full opacity is 6.24:1 and still reads as inactive. */
.btn-add:disabled { opacity: 1; color: var(--text-3); }

/* The cart: one short row per gift added, above the single entry row that adds
   them. This is the whole of what five gifts looks like now. Before, "add
   another gift" unfolded a second entry form under the first and a third under
   that, so five gifts was five stacked forms and the button that builds the
   order sat somewhere below the fold.
   Capped and scrolled rather than left to grow, for the same reason the results
   list further up is a fixed height: what the card does to the page around it
   must not depend on how much is in it. */
/* `hidden auto` and not a bare `overflow-y`: with the other axis left at
   `visible` the spec computes it to `auto`, so a row wider than the box grew a
   horizontal scrollbar under a list that only ever scrolls down. Nothing is
   wide enough to need it now that both halves of a row truncate, and stating it
   is what keeps it that way.
   Padded and pulled back for the same 4px arithmetic as `.qstep-in` above: this
   is a second clip inside that one, and an outline is not part of scrollable
   overflow, so the ring on a focused `.qcart-l` or `.qcart-x` was cut off at
   both edges however much room the step around it left. */
.qcart {
  list-style: none; margin: calc(var(--step-5) - 4px) -4px -4px; padding: 4px;
  display: grid; gap: var(--step-2);
  max-height: 208px; overflow: hidden auto; overscroll-behavior: contain;
  scrollbar-width: thin; scrollbar-gutter: stable;
}
.qcart li { display: flex; align-items: center; gap: var(--step-2); }
/* The row IS the edit control. Pressing it puts that gift back in the entry row
   above, which is the same one row it was typed in the first time: editing a
   gift may not cost the page a second form any more than adding one may. */
.qcart-l {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: baseline; gap: var(--step-3);
  padding: var(--step-3) var(--step-4);
  background: var(--bg-1); color: var(--text-1);
  /* A control boundary, same as `.hit` above. */
  border: 1px solid var(--border-int); border-radius: var(--r-sm);
  font: inherit; font-size: 15px; text-align: left; cursor: pointer;
  transition: border-color 140ms ease-out;
}
.qcart-l:hover { border-color: rgb(var(--accent-rgb) / .9); }   /* .9, see `.hit:hover` */
.qcart-w {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The gift, and the long half of the row: an item name runs to the 80
   characters `ITEM_MAX` allows. Truncated exactly like the name beside it -
   `nowrap` on its own only guaranteed the row could not fit, and with no
   `min-width: 0` a flex item cannot shrink under its own content either. */
.qcart-g {
  min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--font-mono); font-size: 13px; color: var(--accent);
}
/* No border and no fill at rest, and the 44px stays. The row is the object; the
   cross is an affordance ON it. Drawn as a bordered box it was a second control
   of equal weight sitting against a bordered row, so a line the reader is only
   scanning arrived as two things to decide between. The edge comes back on hover
   and on focus, which is where a control has to say it is one.

   `transparent` rather than dropping the border, so nothing moves by a pixel
   when it appears. The 44px is the pointer target and is not negotiable at any
   visual weight - it is the whole reason this is comfortable with a thumb. */
.qcart-x {
  flex: 0 0 auto; width: 44px; height: 44px;
  background: none; color: var(--text-3);
  border: 1px solid transparent; border-radius: var(--r-sm);
  font: inherit; font-size: 20px; line-height: 1; cursor: pointer;
  transition: border-color 140ms ease-out, color 140ms ease-out, background-color 140ms ease-out;
}
.qcart-x:hover, .qcart-x:focus-visible {
  background: var(--bg-0); border-color: rgb(var(--accent-rgb) / .55); color: var(--text-1);
}

/* Where the Robux come from: a ladder of prices whose top rung is the
   customer's own account. A listing carries no username, no Roblox id and no
   owner, deliberately, so the price is not one column of a row about an account
   -- it IS the row, and the layout says so by setting it at the size a heading
   would be. Anything else here would be decoration filling a gap that is
   supposed to be empty.
   A label rather than a card with a control in it: the whole rung is the target,
   which is what makes these usable with a thumb. */
.rents { list-style: none; margin: var(--step-5) 0 0; padding: 0; display: grid; gap: var(--step-2); }
.rent {
  display: grid; grid-template-columns: auto auto 1fr; align-items: baseline;
  gap: var(--step-1) var(--step-3);
  padding: var(--step-3) var(--step-4);
  /* Same rule as `.hit`: this label IS the radio target, so its edge is a
     control boundary and not a seam. */
  background: var(--bg-1); border: 1px solid var(--border-int); border-radius: var(--r-sm);
  /* Both properties the checked rule below changes, not just the edge: the fill
     snapped while the border eased. `.hit` above already transitions both. */
  cursor: pointer; transition: border-color 140ms ease-out, background-color 140ms ease-out;
}
.rent:hover { border-color: rgb(var(--accent-rgb) / .9); }   /* .9, see `.hit:hover` */
.rent:has(.g-src:checked) { border-color: rgb(var(--accent-rgb) / .55); background: var(--surface-1); }
/* Arriving on a rung and having chosen it are two different states, and a
   keyboard reader is in the first one for as long as it takes to read the
   prices. The row is the control, so the row is what takes the ring - the 16px
   radio was carrying it alone inside a rung it is one twelfth of.
   A ring rather than the fill above, because a person can be on a rung they
   have not picked, and one of the two states may not read as the other.
   The radio's own ring goes only where this one replaces it: written this way
   round, an engine without `:has()` drops both declarations together and keeps
   the native ring rather than losing every trace of focus. */
.rent:has(.g-src:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.rent:has(.g-src:focus-visible) .g-src { outline: none; }
.g-src { accent-color: var(--accent); width: 16px; height: 16px; align-self: center; }
.rent-p {
  font-family: var(--font-mono); font-size: 19px; color: var(--text-1);
  white-space: nowrap;
}
.g-src:checked ~ .rent-p { color: var(--accent); }
.rent-u { font-size: 14px; color: var(--text-3); }

/* Three columns do not fit a phone. At 320px the card's content box is about
   216px, and after the radio, the gaps and a nowrap price the `1fr` track that
   holds "per 1,000 Robux, one of ours" gets roughly 87px - three or four
   wrapped lines against a one line price sitting on the first baseline, so
   every option in the list reads as a broken row. Two columns instead, and the
   caption on its own full width line under the price. */
@media (max-width: 30rem) {
  .rent { grid-template-columns: auto 1fr; }
  .rent-u { grid-column: 2 / -1; }
}

/* The receipt on the last part: one row per player, their gifts added up.
   Nothing writes it without a script, and an empty description list still
   carries the margin every `.bill` has, which would read as a gap somebody
   left in the card. */
#g-sum:empty { display: none; }
#g-sum dt { color: var(--text-1); font-size: 15px; min-width: 0; }
#g-sum dd { font-size: 14px; color: var(--accent); }
/* The one value here that is a sentence rather than a figure. Accent is how this
   receipt marks a NUMBER, so a plain description of the product wore the colour
   of a price and read as an alert on a page where nothing is wrong. */
#g-sum dd.bill-note { color: var(--text-2); line-height: 1.5; }

/* Somebody who asked for less movement gets the part outright. The block near
   the top of this sheet already switches every animation off with `!important`,
   which is what makes that happen; this is here so the two are read together,
   and because the unfold is a HEIGHT animation -- the one kind that leaves a
   part collapsed rather than merely unanimated if it is ever half disabled. */
@media (prefers-reduced-motion: reduce) {
  .qstep, .qstep.is-new { animation: none; grid-template-rows: 1fr; opacity: 1; }
  .qstep.is-new::before { animation: none; transform: none; }
}

/* the recording opt-in ---------------------------------------------------- */
/* One checkbox, and the only one on the site, which is why it is a rule here
   rather than a shared control: a second one earns the abstraction, this one
   does not. Sits inside the pay form, above the button, so the price it names
   is read before the press it changes. */
.check-row {
  display: flex; align-items: flex-start; gap: var(--step-3);
  margin-bottom: var(--step-4); text-align: left;
  font-size: 15px; line-height: 1.45; color: var(--text-2);
  cursor: pointer;
}
/* Same miss as the summary above: `cursor: pointer` and no hover. */
.check-row:hover { color: var(--text-1); }
/* The ROW is the control: a `<label>` toggles the input inside it, so the tap
   target is this whole box and not the 18px square below. Measured 23px tall at
   600, 768 and 900px, which is barely half of the 44px a finger needs, on the
   checkbox that adds money to a checkout.
   Why it went unseen for so long, and it is the useful half of this note: the
   sentence wraps to two lines at 390px and wraps again inside the 340-420px
   sidebar at 1440px, so it clears 44 on its own at BOTH widths anybody looks
   at. It fails only in the middle, where the panel is wide enough to fit it on
   one line. Found by `scripts/layout_check.py --case orders` on 2026-08-26,
   the first run of anything that could render this page at all.
   `min-height` and not padding, so the wrapped rows that already pass do not
   move a pixel. */
.check-row { min-height: 44px; }
/* Big enough to hit on a phone without a tap target rule of its own, and not
   shrunk by the flex row when the label beside it wraps to three lines. */
.check-row input { flex: none; width: 18px; height: 18px; margin-top: 2px; accent-color: var(--accent); }
