/* The home page's card drawings, and nothing else.
 *
 * Split out of app.css on 2026-08-25, when the claim cards took that sheet past
 * the 750 line ceiling. Linked immediately after app.css, which keeps every one
 * of these rules in the same place in the cascade it was in before the move.
 *
 * The cart RECEIPT that shared this file went with the four stacked sections it
 * sat in: the page is one card grid now, and the cart is one of its four
 * drawings rather than a second object further down. `.ledger` and its six
 * rules are gone with it, and so is the `article.tile:hover` override, which
 * existed because the claim cards were `<article>` and did not lift. All four
 * are links now and all four lift, which is `motion.css`'s rule doing its job.
 *
 * The bare `.pip` rules are NOT here. Four pages draw a dot, so that base stays
 * in app.css; only the drawing's own overrides are below.
 *
 * MOTION. One idea: something crossing its own drawing. A rate filling from
 * send to receive, a price picked inside its band, a light over the game's own
 * button, and a light down the cart with each line resolving under it. Every
 * one is `transform` and `opacity` on a single element, so it composites, and
 * every one is written `from` a state the element does not itself declare - the
 * element's own rule IS the resting state. `motion.css` closes with
 * `animation: none !important` under `prefers-reduced-motion`, so a reader who
 * asked for less movement gets a filled rail, a picked price, three settled
 * outcome marks and no sweep, fully drawn, rather than a drawing stranded at
 * zero. Nothing here needs a second opt out, and nothing here may take
 * `forwards`.
 *
 * THE CART IS THE ONE THAT DOES NOT LOOP, since 2026-08-26, and the exception
 * is the point rather than an oversight. A rate arriving and an owner picking a
 * price are ongoing states and a loop is a true picture of them. An order
 * settles once, so a receipt that kept re-scanning itself would be drawing a
 * thing that does not happen. Do not "make it consistent". */

/* The drawing under each card ---------------------------------------------- */

/* The drawing IS the floor of the card, edge to edge.
   It was a bordered box floating in a 25-33px gutter inside the card's own
   border, which is a box drawn inside a box, and it grew to whatever height its
   row was stretched to: measured 2026-08-25, two rows spread across 250px on
   one card while four rows ran at 50px each on its neighbour. The operator read
   that off the page as empty space that "doesn't do it justice", against
   proxiesthatwork.com/#features, where the card is `padding: 0` and the visual
   reaches all three edges.
   So: the negative margin cancels `--tile-pad` on the sides and the bottom, and
   `.fv-box` below fills whatever height the row hands this card, WITHOUT that
   height reaching the rows inside it - see the rule's own comment, which is where
   that distinction is kept. The card's own `overflow: hidden` clips the bottom
   corners to its radius, so this needs no radius of its own.
   NO RULES ANYWHERE IN HERE, and neither of the two that were comes back. The
   tint and `--shadow-inset`'s dark top edge already say this region is not the
   words above it, so a hairline over a tonal change said it twice - and four
   stacked in one card is the spreadsheet the operator read off the cart on
   2026-08-25. Separation is tone and space, as on every reference he named. */
.fv {
  margin: var(--step-5) calc(-1 * var(--tile-pad)) calc(-1 * var(--tile-pad));
  flex: 1 1 auto; display: flex; flex-direction: column; justify-content: flex-end;
}
/* The BAND stretches, the ROWS do not, and that distinction is the whole rule.
   `flex: 0 0 auto` here left the slack ABOVE the drawing instead: measured on the
   live page 2026-08-26, the rent card carried 39px of dead space between its
   sentence and its first row, and the gift card 12px. That is the same void the
   operator read off this grid, smaller. Pairing the cards by drawing density
   shrank it from 250px to 39px but cannot reach zero, because it depends on two
   cards happening to carry the same number of lines.
   Growing the box is NOT the `space-evenly` mistake that rule replaced. That one
   stretched the ROWS - two rows across 250px, 118px each. These rows keep
   `.fv-row`'s own padding and are CENTRED in whatever height is left, so the row
   rhythm is constant (measured 50-54px on all four cards) while the tinted region
   varies (134-219px). A taller band of tone reads as a band; a taller ROW reads
   as a spreadsheet with air in it. */
.fv-box {
  flex: 1 1 auto; display: flex; flex-direction: column; justify-content: center;
  background: rgba(255,255,255,.022); box-shadow: var(--shadow-inset);
  padding: var(--step-2) var(--tile-pad);
  font-family: var(--font-mono); font-size: 12px; line-height: 1.5; color: var(--text-3);
}
/* One row height everywhere, which is the half of this the reader actually
   sees: `space-evenly` gave a two row drawing 118px per row and a four row
   drawing 50px, on the same grid, in the same eyeline.
   16px and not 12px because this padding IS the row boundary now: 32px against
   an 18px line reads as two rows, the 24px the deleted rule left did not. */
.fv-row { display: flex; align-items: center; gap: var(--step-3); padding: var(--step-4) 0; }
/* Pushed right by the margin and not by `space-between`, so a row of three
   parts still puts its figure on the edge. */
.fv-v { margin-left: auto; color: var(--text-1); }
.fv-back { color: var(--ok); }

/* The dot the gift prompt uses, and it is the only one left in here. The cart's
   two `data-s="ok"` rows and its `warn` row carried three more until 2026-08-26,
   and `.fv .pip[data-s="ok"]` went with them: the cart's lines are told apart by
   whether they carry a PICTURE now, and a dot beside a picture is the same fact
   said twice. `docs/CONVENTIONS.md` wants a word beside a colour, and the row
   that was credited back still ends in the word "back". */
.fv .pip { width: 6px; height: 6px; margin-right: 0; }

/* THE FOUR REGISTERS -------------------------------------------------------
 *
 * Everything below this line exists because all four cards drew the SAME
 * device: a `.fv-box` of `.fv-row` label/value pairs in 12px mono, holding a
 * gift prompt, a receipt, an exchange rate and a rental price. That is the
 * operator's own recorded tell for machine-written design - one label/value
 * device carrying unlike content - and it is what he was reading when he asked
 * for art in these tiles.
 *
 * So the answer is not a picture in each of the four frames, which would be the
 * same mistake at higher fidelity. Each card gets the register its own product
 * has: a product shot, a receipt, a typographic pair, a number. The BAND stays
 * shared, because the band is the card's floor and the operator accepted it;
 * what stands on the floor is what differs.
 *
 * ART LANDS ON TWO OF THE FOUR, and the two are not a budget. A gift and a cart
 * line are things with a picture, and that picture is a file we already fetched
 * and committed for the shelf. A swap rate and a rental price have no object in
 * them at all, so anything drawn there would be an illustration of nothing,
 * which is the decoration being objected to. Do not "finish the set". */

/* THE PRODUCT SHOT, on the gift card. A row and not a column, so the picture
   stands beside its name rather than over a table. `.fv-box` is a column with
   `justify-content: center`, and both of those are cancelled here rather than
   worked around: the body takes the free space with `flex`, so there is none
   left for a justification to place.
   More vertical padding than `.fv-box` gives, because that 8px is sized for a
   text row and this band holds a 96px object. */
.fv-shot { flex-direction: row; align-items: center; gap: var(--step-5); padding-block: var(--step-5); }
/* `min-width: 0` is load bearing and invisible without it: a flex item's floor
   is its content, so the prompt row's pill and button would refuse to shrink
   and push the whole band past the card's edge on a phone. */
.fv-shot-b { flex: 1 1 auto; min-width: 0; }
/* The item, LIT. The glow is behind the subject and not behind the page: these
   files carry a real alpha channel (checked, corner pixel is 0 alpha), so the
   gradient reads as light falling on the object rather than as a plate under
   it. `static/css/CLAUDE.md`'s rejection log turns down a soft radial glow as a
   PAGE background, which is a glow lighting nothing; this one lights something.
   `aspect-ratio` beside the width with `width`/`height` on the tag, which is
   `.item-ic`'s arrangement on the shelf and for the same reason: the square is
   reserved before the bytes land, so the card does not jump. */
.fv-art {
  flex: 0 0 auto; width: 96px; aspect-ratio: 1; height: auto;
  background: radial-gradient(circle at 50% 46%,
              rgb(var(--accent-rgb) / .20), rgb(var(--accent-rgb) / 0) 68%);
}
/* The body face, which is the point: 12px mono is the receipt's voice and this
   is a product's name. Two declarations rather than a class of its own, because
   `.item-name` is already what the shelf calls this and the colour and leading
   are right as they stand. */
.fv-shot .item-name { font-family: var(--font-body); font-size: 16px; }

/* THE RECEIPT, on the cart card. One picture per line at the size a receipt
   uses, which is small: the line is identified by its NAME here, where on the
   shelf the picture is the product and runs at 64px.
   The same span with nothing in it holds the cell for the line that has no art,
   so four names keep one left edge. `height: auto` with `aspect-ratio` resolves
   the square off the width on an empty element exactly as it does on an image,
   so one rule covers both and there is no second number to keep matching. */
.fv-av { flex: 0 0 auto; width: 28px; aspect-ratio: 1; height: auto; border-radius: var(--r-sm); }

/* WHAT HAPPENED TO THE LINE, at the far edge of the row.
   The left cell is the picture and already holds a meaning, so the outcome
   takes the price side, beside the figure it qualifies. That is also the half
   of this rule that keeps the deleted `.pip` dots deleted: they were on the
   left and they were a STATUS, which is a different thing from an outcome and
   is why re-adding one would say the same fact twice.
   14px and not `.ic`'s 20, which is `.eyebrow .ic`'s answer for the same
   problem - a 20px glyph beside a 12px mono row reads as the row's heading.
   `.ic` is in app.css and this sheet is linked after it, so the two width
   declarations are equal specificity and this one wins on order.
   RESERVED ON ALL FOUR ROWS, `billed` included, which draws nothing. A total
   whose figure did not line up with the figures it totals reads as a bug, and
   an empty cell is the same answer the empty `.fv-av` already gives on the
   left for the line that has no art. Reserving it here from first paint is
   also what keeps the animation below off the layout entirely: the space
   exists before the marks do, so nothing shifts when they land. */
.fv-mk { flex: none; width: 14px; height: 14px; }
.fv-ok { color: var(--ok); }
/* The line that did not go out takes the ramp's own grey, NOT `--danger`.
   Two reasons and neither is squeamishness. The shape is what carries this -
   a cross against a tick is legible with the colour taken away, which is what
   `docs/CONVENTIONS.md` means by refusing colour alone - so the hue is not
   being asked to do any work, and a third ink introduced on one 14px glyph is
   an accent hue per element, which is the operator's own recorded tell.
   And the row already ends in "back" set in `--ok`: the money is accounted
   for on the same line. The cross says WHICH line did not go, the word says
   what happened to it, and neither repeats the other. */
.fv-no { color: var(--text-3); }

/* THE TYPOGRAPHIC PAIR, on the swap card. Monochrome and no raster.
   The rail between the two is the one this card already had; it is doing the
   work an arrow would, which is as well, since none of the three subset faces
   carries U+2192 either. */
.fv-pair { flex-direction: row; align-items: center; gap: var(--step-5); padding-block: var(--step-5); }
.fv-coin { display: flex; flex-direction: column; gap: var(--step-2); }
.fv-tk {
  font-family: var(--font-display); font-size: 34px; font-weight: 600;
  letter-spacing: -0.02em; line-height: 1; color: var(--text-1);
}

/* The game's own gift prompt: the typed name in a pill, and the game's button
   beside it. `.pip.live` is the dot the rest of the site already uses for a
   live thing, which is what an avatar reads as at 6px, and its pulse is
   `motion.css`'s. */
.fv-prompt { flex-wrap: wrap; }
.fv-pill {
  display: inline-flex; align-items: center; gap: var(--step-2);
  padding: var(--step-1) var(--step-3); border-radius: var(--r-pill);
  color: var(--text-1); background: rgb(var(--accent-rgb) / .12);
  box-shadow: inset 0 0 0 1px rgb(var(--accent-rgb) / .30);
}
.fv-cta {
  position: relative; overflow: hidden;
  margin-left: auto; padding: var(--step-1) var(--step-4); border-radius: var(--r-sm);
  color: var(--accent-ink); background: linear-gradient(180deg, var(--accent-lift), var(--accent));
}

/* The rate, and the price band. A rail and not a bar chart: two bars whose
   widths encoded a ratio would be a third copy of figures that are served, and
   the drawing would go wrong on the day one of them moved with nobody watching.
   A line that runs from one figure to the other cannot go stale.
   A `.fv-row` first, so this only says what is different about it: a wider gap.
   Its own `padding-block: var(--step-4)` went when `.fv-row` moved TO 16px. The
   same number written twice is the copy that goes stale. */
.fv-rate { gap: var(--step-5); }
/* Not `margin-left: auto`: this row holds two figures with the band between
   them, and the auto margin threw the first one across to meet the second.
   THE NUMERIC REGISTER, and 32px is the lead card's own price size rather than
   a new one. The range is what this card sells, and at 16px inside a 12px table
   the one figure somebody came for was the quietest thing in the card.
   `tabular-nums` is not decoration here: without it the two ends of the range
   are set on different widths, and the band between them moves as the served
   figures change. `.mono` in `app.css` sets the same pair for the same reason,
   and this cannot borrow it - the box is already the mono face, so the class
   would carry one useful declaration and a redundant one. */
.fv-rate .fv-v {
  margin-left: 0; font-size: 32px; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums; line-height: 1.1;
}
/* One class in two contexts: a flex child between two figures on the rent card,
   and a block between two rows on the swap card. `flex: 0 0 auto` is the load
   bearing half. `.fv-box` is a COLUMN flex container, so a growing `flex` there
   is a growing HEIGHT, and the 2px line filled the card. The rent row is the
   one place it grows, and it grows sideways. */
.fv-line {
  position: relative; display: block; flex: 0 0 auto; min-width: 40px;
  height: 2px; margin: var(--step-2) 0; border-radius: var(--r-pill);
  background: var(--border-str);
}
.fv-rate .fv-line { flex: 1 1 40px; }
/* The same two figures with a WORD between them instead of a track, which is
   what `/rent` draws since 2026-08-26. A filled bar three inches from the box
   where an owner types their price reads as a slider already set, and the
   operator read the low end off it as the value. Left aligned rather than
   spread, because a range is one fact and pushing its ends apart is what made
   the thing between them look like a control. */
.fv-band { gap: var(--step-3); }
.fv-to { color: var(--text-3); font-size: 15px; }
/* The third context, added with the pair. Written beside the rent card's line
   rather than under the pair's own rules, so the two places this class is
   allowed to grow sideways are read together and neither is found on its own. */
.fv-pair .fv-line { flex: 1 1 24px; }
.fv-fill {
  position: absolute; inset: 0; transform-origin: left; border-radius: inherit;
  background: linear-gradient(90deg, rgb(var(--accent-rgb) / .35), var(--accent));
  animation: fv-run 5600ms cubic-bezier(.16,.84,.44,1) infinite;
}
/* The rate arriving. It fades out at full width and restarts at zero, which is
   a reset nothing can see: a bar of no width paints nothing. Holding the far
   end for a third of the loop is what keeps this from reading as a spinner. */
@keyframes fv-run {
  from { transform: scaleX(0); }
  55%, 88% { transform: scaleX(1); opacity: 1; }
  to { transform: scaleX(1); opacity: 0; }
}
/* The owner picking a price inside the band. Both ends of the loop are the same
   value, so it turns around instead of snapping, and the element's own
   `transform` below is a price already chosen - which is what a reader who
   turned motion off is left looking at. After `.fv-fill` and at the same
   specificity, so this animation is the one that runs. */
.fv-pick {
  transform: scaleX(.5);
  animation: fv-pick 7200ms cubic-bezier(.16,.84,.44,1) infinite;
}
@keyframes fv-pick {
  from { transform: scaleX(.34); }
  30%, 62% { transform: scaleX(.68); }
  to { transform: scaleX(.34); }
}

/* The light over the game's button, on the rail's own tempo so the two cards in
   the top row move together. Opacity zero at both ends, so with the animation
   off there is nothing on the button at all. */
.fv-cta::after {
  content: ""; position: absolute; inset: 0; opacity: 0; pointer-events: none;
  background: linear-gradient(100deg, rgba(255,255,255,0), rgba(255,255,255,.5), rgba(255,255,255,0));
  animation: fv-sweep 5600ms ease-in-out infinite;
}
@keyframes fv-sweep {
  from { transform: translateX(-140%); }
  12% { opacity: 1; }
  34%, to { opacity: 0; transform: translateX(140%); }
}

/* The cart reconciling itself, ONCE, when the drawing is on screen.
   It was a 6400ms `infinite` until 2026-08-26 and the loop is the whole change.
   A light that keeps sweeping a receipt already settled says nothing after the
   first pass - frame 400 tells a reader what frame 1 did - and that is exactly
   the test `motion.css` applied when it deleted the hero bloom. What replaced
   it is not less motion but motion that RESOLVES: the light runs down the
   receipt and each line lands its outcome as the light reaches it. That is the
   card's own subject, so it can only be drawn once, because the order settles
   once.
   The sweep is 60% of the box and starts its own height above it, so 300% of
   itself carries it clear of the bottom. Percentages here are of the ELEMENT,
   which is what makes this hold at any number of rows without a pixel in it.
   `linear`, where every other drawing on this page uses a curve. A scan moves
   at a scan's speed, and it is also the half that makes the marks below
   computable: the sweep's centre sits at `-30% + 180 x p` of the box, so a row
   centred at c% is reached at `p = (c + 30) / 180`, and an eased sweep would
   put every one of those delays somewhere else.
   `.is-in` is added by `motion.js` to the DRAWING and not to the card - see
   that file for the measurement that forced it. The base rule holds no
   animation at all, so with no script there is no sweep, and `opacity: 0` at
   both ends means there is nothing to see rather than something stranded. */
.fv-cart { position: relative; overflow: hidden; }
.fv-cart::after {
  content: ""; position: absolute; left: 0; right: 0; top: -60%; height: 60%;
  opacity: 0; pointer-events: none;
  background: linear-gradient(180deg, rgb(var(--accent-rgb) / 0),
              rgb(var(--accent-rgb) / .10), rgb(var(--accent-rgb) / 0));
}
.fv-cart.is-in::after { animation: fv-scan 2000ms linear 140ms backwards; }
@keyframes fv-scan {
  from { transform: translateY(0); }
  14% { opacity: 1; }
  70%, to { opacity: 0; transform: translateY(300%); }
}

/* Each line landing its outcome under the light.
   `from` only, and the mark's own rule above IS the resting state, so this
   whole block is additive: `animation: none` - which is what the reduced
   motion reader gets, what a browser running no script gets, and what a
   reader who never scrolls the drawing into view gets - leaves three marks
   fully drawn rather than a column of nothing. Nothing here may take
   `forwards` and nothing here may put the hidden state in a base rule; either
   one turns the finished state into something only an animation can reach.
   `backwards` is what applies the `from` through the delay. Without it the
   mark is painted, hidden and painted again, which is a flicker on the one
   element the eye is being sent to.
   `svg.fv-mk` and not `.fv-mk`: the fourth cell is an empty span holding the
   total's alignment, and it has nothing to resolve.
   The delays are the sweep's own arithmetic, against row centres measured on
   the served page at 1440x900 on 2026-08-26 - 15.26%, 39.36% and 63.45% of
   the box - so each mark lands as the light crosses its line rather than on a
   rhythm of its own. They come AFTER the shorthand because `animation:` resets
   `animation-delay` and would undo them, and they are written as `:nth-child`
   rules rather than a custom property set in the markup because the CSP
   carries no nonce and an inline style is dropped. */
.fv-cart.is-in svg.fv-mk {
  animation: fv-mark 300ms cubic-bezier(.16,.84,.44,1) backwards;
}
.fv-cart.is-in .fv-row:nth-child(1) .fv-mk { animation-delay: 640ms; }
.fv-cart.is-in .fv-row:nth-child(2) .fv-mk { animation-delay: 910ms; }
.fv-cart.is-in .fv-row:nth-child(3) .fv-mk { animation-delay: 1180ms; }
@keyframes fv-mark { from { opacity: 0; transform: scale(.5); } }
/* What was actually billed, under the line that was not. A `.fv-row` first, so
   it keeps the row's spacing and this only says which one it is. */
.fv-foot .fv-v { color: var(--text-1); font-size: 14px; }
