/* GENERATED by tools/build/gen_ui_tokens.py -- do not hand-edit.
 *
 * The game palette and the six sizes / three radii, straight out of
 * client/ui/skin.gd. One palette, two renderers: the island draws
 * through Skin, the panels through these.
 */
:root {
  --night: #F2E3C6;
  --panel: #FFFAF0;
  --panel-hi: #F7ECD8;
  --ink: #3A3A3A;
  --ink-dim: #8A8A8A;
  --line: #E2D0AC;
  --line-soft: #EFE3CB;
  --card-edge: #E0E0E0;
  --money: #2E8B45;
  --money-dark: #1D6630;
  --money-wash: #E7F4EA;
  --gold-dark: #9A6B12;
  --gold-mid: #E0A62E;
  --gold-glow: #FFE9A8;
  --silver: #B9C3CC;
  --bronze: #C98B5A;
  --shop-animals: #E0873A;
  --shop-fish: #35B0E8;
  --shop-farming: #57C84D;
  --shop-deco: #9B6BD6;
  --shop-land: #D8A93E;
  --sky: #35B0E8;
  --apple: #57C84D;
  --coral: #F2604C;
  --hen: #F26095;
  --sky-wash: #EAF6FD;
  --prize: #9B5DE5;
  --danger: #B4402F;

  --caption: 11px;
  --body: 13px;
  --title: 15px;
  --number: 17px;
  --display: 22px;
  --hero: 30px;
  --r-chip: 12px;
  --r-card: 18px;
  --r-panel: 26px;
  --drawn-detail: 8px;

  --sky-lip: #1A6B90;
  --sky-rim: #37B6F0;
  --apple-lip: #327C2B;
  --apple-rim: #5FDB54;
  --coral-lip: #963629;
  --coral-rim: #F6624D;
  --gold-mid-lip: #8B6516;
  --gold-mid-rim: #EBAE30;
  --danger-lip: #702318;
  --danger-rim: #CE4936;

  --av-sky: #2FA8DE;
  --av-blue: #3E77C4;
  --av-navy: #1E3566;
  --av-teal: #168C86;
  --av-green: #55A544;
  --av-forest: #1F6435;
  --av-gold: #C99A16;
  --av-orange: #E2702A;
  --av-rust: #8E4A16;
  --av-coral: #E0745C;
  --av-red: #BE2F35;
  --av-pink: #E070A6;
  --av-magenta: #962C79;
  --av-purple: #8F63C9;
  --av-charcoal: #4C5560;
}

/* GENERATED by tools/build/join_panels.py from web/panels/*.css -- edit the PARTS, never this file. parts:9b0b58b8 */
/* THE PANEL LAYER — the game's UI, in HTML, over the Godot canvas.
 *
 * 2026-08-19. Godot's 3D is not the problem and never was; its `Control`
 * system is, and it cost 14,575 of the client's 20,216 lines against 4,507 for
 * the whole island — including a 424-line re-implementation of scrolling and a
 * 556-line theme generator. The evidence that this works is already shipped:
 * the sign-in door in `shell.html` has been plain HTML over the canvas since
 * the beginning, in eight languages including two right-to-left ones, and it
 * is the one piece of this game's UI nobody has ever called cheap.
 *
 * Every shape here is Flutter's, to the number. The palette and the scale are
 * NOT typed in this file — `tokens.css` is generated from `client/ui/skin.gd`,
 * so the island and the panels cannot drift apart.
 */

/* The layer itself. Transparent to the finger everywhere except on a panel,
 * or the island underneath stops taking taps. */
#ui {
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  /* Leads with the launcher's `--lang-font`; see shell.html.
   *
   * **THE FALLBACK INSIDE `var()` IS LOAD-BEARING** (2026-08-31). Forty of the
   * sixty languages need no lazily-fetched face, and `applyLangFont()` says so
   * with `removeProperty('--lang-font')` — so `var(--lang-font)` resolved to
   * nothing at all. An empty custom property with NO fallback does not skip
   * that one entry: it makes the WHOLE declaration invalid at computed-value
   * time, the rest of the stack is thrown away, and the browser falls back to
   * its own default serif. Measured live on build 20260831-0538: English
   * computed `"Times New Roman"` and `CSS.getPlatformFontsForNode` named
   * `Times New Roman` as the face that drew the pixels — in every panel and on
   * the sign-in door — while `document.fonts.check('16px Roboto')` answered
   * true in the same browser. Roboto was present and was never reached.
   *
   * Roboto is named TWICE on purpose. The fallback stands in for the missing
   * FIRST entry; deleting the second copy would silently re-order the stack
   * for the twenty languages that DO set the property, which is the half that
   * works today. */
  font-family: var(--lang-font, Roboto), Roboto, "Noto Naskh Arabic", "Noto Sans Devanagari", system-ui, sans-serif;
  color: var(--ink);
  -webkit-tap-highlight-color: transparent;
}
#ui * { box-sizing: border-box; }

/* THE SCRIM. Flutter's barrier is 0x59000000; this game's own is a touch
 * heavier and warmer, and it is what the Godot shell already used. Tapping it
 * dismisses — a PRESS, not a release, because a drag that starts on the scrim
 * is still a dismissal and waiting for the release lets a finger slide onto
 * the card and cancel it by accident. */
#ui .scrim {
  position: absolute;
  inset: 0;
  background: rgba(37, 37, 37, 0.42);
  pointer-events: auto;
  opacity: 0;
  transition: opacity 0.14s ease-out;
}
#ui .open .scrim { opacity: 1; }

/* THE POPUP. Flutter's `GamePopup`: 18/44 inset, capped at 440, radius 18, a
 * 1.6 neutral rim, and a HARD four-pixel shadow with no blur — the thing v2's
 * Godot theme deleted and the single biggest reason its panels read as flat
 * white rectangles laid on the grass. */
#ui .popup {
  position: absolute;
  left: 50%;
  /* Clear of an iPhone home-screen app's clock and home bar (TODO 490); env() is 0 in a tab. */
  top: calc(44px + env(safe-area-inset-top, 0px));
  bottom: calc(44px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  width: calc(100% - 36px);
  max-width: 440px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  pointer-events: none;
}
#ui .card {
  pointer-events: auto;
  background: #fff;
  border: 1.6px solid var(--card-edge);
  border-radius: var(--r-card);
  box-shadow: 0 4px 0 rgba(0, 0, 0, 0.2);
  display: flex;
  flex-direction: column;
  max-height: 100%;
  overflow: hidden;
  /* TODO 543: a panel POPS -- from 90 % with a small overshoot, the HUD buttons' own spring --
   * where it used to slide 8 px. Still 0.24 s, still nothing moves the layout. */
  transform: translateY(10px) scale(0.9);
  opacity: 0;
  transition: transform 0.24s cubic-bezier(0.34, 1.5, 0.64, 1), opacity 0.14s ease-out;
}
#ui .open .card { transform: none; opacity: 1; }
/* A player who asked the phone for less motion gets the fade without the spring. */
@media (prefers-reduced-motion: reduce) {
  #ui .card { transform: none; transition: opacity 0.14s ease-out; }
}

/* The header band and its X. The title is centred on the PANEL, not on what is
 * left of it beside the button, so the padding is symmetric — and the X is
 * PHYSICALLY top-right in both directions, because in Arabic the whole app
 * mirrors and a close button that swaps corners is one you have to look for. */
#ui .head {
  position: relative;
  flex: 0 0 auto;
  padding: 16px 52px 10px;
  min-height: 46px;
  text-align: center;
  font-size: var(--title);
  font-weight: 800;
}
#ui .head.bare { padding: 0; }
/* The coop's head is a ROW -- picture, name, upgrade -- where every other
   panel's is a centred word. The side padding stays 52 so the X and the
   upgrade disc keep their corners; the picture sits inside it. */
#ui .head.coop-head {
  display: flex;
  align-items: center;
  gap: 10px;
  /* Two discs live in the right corner -- the X at 8 and the upgrade at 50,
     34 wide -- so the words stop at 92. Nothing lives in the left one. */
  padding-left: 14px;
  padding-right: 92px;
}
#ui .head.coop-head .title { flex: 1 1 auto; min-width: 0; }
#ui .coop-pic {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
}
#ui .coop-pic img { max-width: 100%; max-height: 100%; display: block; }
#ui .x {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 34px;
  height: 34px;
  border: 1.6px solid var(--card-edge);
  border-radius: 50%;
  background: #fff;
  display: grid;
  place-items: center;
  cursor: pointer;
  padding: 0;
}
/* The Material close is a FILLED outline, not a stroked cross. Flutter draws
 * it in the chrome ink at 17 in a 34 disc, and so does the Godot panel next
 * door. */
#ui .x { color: var(--ink); }
#ui .x svg { width: 17px; height: 17px; display: block; }
#ui .x:active { background: #f4f4f2; }

/* The body scrolls; the head and the X do not scroll away with it. Momentum
 * scrolling comes free — the 424 lines of `scroll_fix.gd` exist because
 * Godot's ScrollContainer never sees a drag whose press was eaten by a child
 * (godotengine/godot#21137, open since 2018). */
#ui .body {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 0 14px 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
#ui .body::-webkit-scrollbar { width: 0; }

/* THE CARD. One shape, one radius, one hairline — `GameCard`. */
#ui .tile {
  background: #fff;
  border: 1.4px solid var(--card-edge);
  border-radius: var(--r-card);
  padding: 10px 12px;
}
#ui .tile.chosen { background: var(--gold-glow); border-color: var(--gold-mid); }
#ui .tile.tap { cursor: pointer; }
#ui .tile.tap:active { transform: translateY(1px); }

/* THE BUTTON — Raf's pick off the 2026-08-25 option sheets ("more like
 * option 3", then "i chose option 2"): a ROUNDED CARD, not a pill. Corner
 * 20, a gentle top-light gradient of the face colour, and a wide soft
 * shadow in the face's own dark — no rim, no hard lip. The shell's door
 * buttons wore this language first; the panels now match them. Pressed, it
 * sinks 2px and the shadow tightens under it. */
#ui .btn {
  appearance: none;
  border: 0;
  background: linear-gradient(180deg, #4CBEF0, #31A6DD);
  color: #fff;
  font: 800 var(--title)/1 inherit;
  font-family: inherit;
  font-weight: 900;
  min-height: 46px;
  padding: 0 16px;
  border-radius: 20px;
  box-shadow: 0 8px 18px #1A6B9038;
  margin-bottom: 6px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
}
#ui .btn:active { transform: translateY(2px); filter: brightness(0.94); box-shadow: 0 3px 8px rgba(0, 0, 0, 0.18); }
#ui .btn[disabled] { opacity: 0.45; pointer-events: none; }
#ui .btn.gold { background: linear-gradient(180deg, #ECB542, #D89C24); box-shadow: 0 8px 18px #8B651638; }
#ui .btn.sell { background: linear-gradient(180deg, #66D35C, #49BB40); box-shadow: 0 8px 18px #327C2B38; }
#ui .btn.danger { background: linear-gradient(180deg, #F87361, #E65340); box-shadow: 0 8px 18px #96362938; }
/* THE ARMED FACE (Raf, 2026-08-28: "make the confirm blue instead of red").
   `.danger` stays -- the market's Pull and Withdraw and the given heart
   still mean it -- but a confirmation that also guards a BUY cannot wear
   the colour for discarding. Same gradient construction as its siblings so
   the armed button is the same object in a different colour, not a
   different button.

   **A DEEPER blue than the resting one, and that is the whole difficulty.**
   The base `.btn` is already this sky, so the money buy -- the one button
   in the game whose resting face is the plain blue -- would arm to exactly
   its own colour and the state change would be a word alone. A player
   mid-scroll reads colour before text, so the armed face has to be a shade
   nobody's resting face wears. */
#ui .btn.sky {
  background: linear-gradient(180deg, #2FA8E6, #1580C4);
  box-shadow: 0 8px 18px #0E5C8E45;
}
/* Each buy sits in its own flex row so `armSell`'s X lands BESIDE it; the
   pair itself is a one-column grid and would stack the X between them. */
#ui .buy-pair .buy-row { display: flex; gap: 7px; align-items: stretch; }
#ui .buy-pair .buy-row .btn { flex: 1 1 auto; min-width: 0; }
#ui .buy-pair .buy-row .btn.arm-x { flex: 0 0 auto; width: 46px; padding: 0; }
#ui .btn.soft { background: #fff; border: 1.4px solid #EEE5D2; color: var(--ink); box-shadow: 0 8px 18px #00000015; }
/* Copy, the moment it has copied — Flutter turns the same button green
   rather than swapping in a second one, so the state lives on the control
   the player is already looking at. */
#ui .btn.apple { background: linear-gradient(180deg, #66D35C, #49BB40); box-shadow: 0 8px 18px #327C2B38; }
#ui .btn.low { min-height: 36px; font-size: var(--body); border-radius: 15px; }
/* A purse chip that carries its own balance is two lines, not one: the name
   and, under it, what is in it. `flex-flow: column` because `.btn` lays its
   children in a row, and a five-figure balance beside the word `Money` is what
   overflows a 320 pt phone. The number is quiet on purpose — it is context for
   the choice, never the thing being chosen. */
#ui .btn.low > .purse-have {
  flex-basis: 100%;
  font-size: var(--caption);
  font-weight: 600;
  opacity: 0.72;
  line-height: 1.1;
  direction: ltr;
}
#ui .btn.low:has(> .purse-have) {
  flex-wrap: wrap;
  row-gap: 1px;
  padding-top: 5px;
  padding-bottom: 5px;
  min-height: 46px;
}

/* THE PRICE TAG, and it is deliberately not the button above. Flutter's
 * `_Price` — radius 18, 14/7, a THREE pixel lip and no rim. Drawn as the big
 * button the store came out as six fat pills, each louder than the pack it was
 * selling. */
#ui .price {
  appearance: none;
  border: 0;
  background: linear-gradient(180deg, #ECB542, #D89C24);
  color: #fff;
  font-family: inherit;
  font-weight: 900;
  font-size: var(--body);
  padding: 7px 14px;
  border-radius: var(--r-card);
  box-shadow: 0 4px 10px #8B651638;
  margin-bottom: 3px;
  cursor: pointer;
  white-space: nowrap;
  /* A price is a number: left to right in every language. */
  direction: ltr;
}
#ui .price:active { transform: translateY(3px); box-shadow: none; }
/* **A PRICE WITH A COIN IN IT IS A FLEX ROW** (Raf, 2026-09-10: *"fix the
 * buttons as gold icon and number are not centered correctly"*). Every OTHER
 * price in the store is a string the server chose -- `$4.99` -- and a button
 * centres its own text, so `.price` never needed to say how to lay one out.
 * The gold VIP rung is the first price that is two ELEMENTS, an inline `<svg>`
 * and a `<span>`, and inline elements sit on the TEXT BASELINE: the coin hung
 * below the digits by its own descender and the pair sat left of centre by the
 * word-space between them. Nothing was wrong with the padding.
 *
 * This is `.shop-pill`'s rule, which has drawn a coin beside a number since
 * the shop shelf was built -- lifted rather than re-derived, so the two agree
 * on the 5 px gap. `justify-content` is the half that matters here and not
 * there: a shelf pill is as wide as its contents, a rung's price is held at
 * `min-width: 92px` so the four rungs line up, and the slack inside it is
 * exactly what was off centre. */
/* **THE LIST PRICE, STRUCK THROUGH** (Raf, 2026-09-10: *"normal prize crossed
 * then discounted price 25% then vip icon"*). It rides INSIDE the button's own
 * `.coin` row, which is already `inline-flex` and already forced LTR, so the
 * order is coin, old number, new number, VIP mark in every language.
 *
 * Smaller and quieter than the price beside it, because it is the number that
 * is NOT being charged: at the same size and weight the eye reads the first
 * number it meets, which on a Buy button is the one thing that must not be
 * misread. `currentColor` at 62% keeps it legible on the gold fill and on the
 * white one without a second rule per button colour.
 *
 * `<s>` and not a class on a span: it is struck-out text, the element exists
 * for exactly that, and a screen reader says so without an aria attribute. */
#ui .btn .coin > s.was {
  font-size: 0.82em;
  font-weight: 800;
  opacity: 0.62;
  text-decoration-thickness: 1.5px;
}

#ui .price.gold {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
}

/* The two-shelf switch. Sky everywhere else in the game; GOLD here, because
 * this is the panel that sells gold. */
/* **ONE control split in two, not two cards with a gap** — Flutter's
 * `ShelfSwitch` is a single white card, radius 18, 1.6 rim, 4 px of padding,
 * whose halves are borderless r12 fills. Ours gave every half its own rim and
 * corner and pushed them 8 px apart, which reads as two things that happen to
 * be side by side; a segmented switch has to look like one object before the
 * fill can mean "this half". */
#ui .shelf {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0;
  padding: 4px;
  margin-bottom: 6px;
  background: #fff;
  border: 1.4px solid var(--card-edge);
  border-radius: var(--r-card);
}
#ui .shelf button {
  appearance: none;
  font-family: inherit;
  background: transparent;
  border: 0;
  border-radius: var(--r-chip);
  padding: 9px 8px;
  font-size: var(--body);
  font-weight: 900;
  color: var(--ink-dim);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  cursor: pointer;
}
#ui .shelf button[aria-selected="true"] {
  background: var(--gold-glow);
  border-color: var(--gold-mid);
  color: var(--gold-dark);
}

/* A perk: its mark in a 30 px gold square, then the sentence. The tile is most
 * of what makes a list of perks read as an offer rather than as three loose
 * sentences with pictures beside them. */
#ui .perk { display: flex; align-items: flex-start; gap: 10px; }
#ui .perk .mark {
  flex: 0 0 30px;
  width: 30px;
  height: 30px;
  border-radius: var(--r-chip);
  background: var(--gold-glow);
  display: grid;
  place-items: center;
}
#ui .perk .mark svg { display: block; }
#ui .perk p { margin: 4px 0 0; font-size: var(--body); line-height: 1.4286; }

/* One rung of a ladder: mark, what you get, what it costs. */
#ui .rung { display: flex; align-items: center; gap: 10px; }
/* **The prices line up.** Content-sized, `$1.99` is 96 px and `$99.99` is 124,
 * and the shelf's right edge zig-zags down the panel -- six buttons that happen
 * to be stacked rather than one ladder. A column of prices is read DOWNWARDS,
 * one number against the next, and a ragged edge is what stops that. */
#ui .rung > .price { flex: 0 0 auto; min-width: 92px; }
#ui .rung .what { flex: 1 1 auto; min-width: 0; }
#ui .rung .what b { display: block; font-size: var(--title); font-weight: 900; color: var(--gold-dark); }
/* **It WRAPS.** "+40% extra · Best value" is 128 px against a 114 px slot at
 * 320, and a nowrap line there ends as "+40% extra · Best…" -- a one-line
 * string does not overflow, it truncates, and the half it loses is the half
 * that sells the rung. The row is as tall as its tallest child either way. */
#ui .rung .what small {
  display: block;
  font-size: var(--caption);
  font-weight: 800;
  color: var(--apple);
  line-height: 1.25;
}

#ui .ask-title { text-align: center; font-size: var(--title); font-weight: 900; }
#ui .ask-title + .note { margin-top: 4px; }

/* Small print. ONE line per panel, and only where nothing else says it. */
#ui .note { text-align: center; font-size: var(--caption); color: var(--ink-dim); margin: 2px 0 0; line-height: 1.4286; }
#ui .note.warn { color: var(--coral); font-weight: 600; }
#ui .lede { text-align: center; font-size: var(--caption); color: var(--ink-dim); margin: 0; }

/* The gold balance box: it REPORTS rather than offers, and the fill is what
 * says so. */
#ui .balance {
  background: var(--gold-glow);
  border: 1.6px solid var(--gold-mid);
  border-radius: var(--r-card);
  padding: 12px 14px;
  text-align: center;
  font-size: var(--number);
  font-weight: 900;
  color: var(--gold-dark);
}

/* The converter. A real range input, which is the whole argument in one
 * control: Godot needed a hand-drawn track, a hand-drawn thumb and its own
 * drag handling for this. */
#ui .convert { display: flex; flex-direction: column; gap: 10px; }
#ui .convert .pair { display: flex; align-items: center; gap: 10px; direction: ltr; }
#ui .convert .amount {
  flex: 1 1 0;
  background: #fff;
  border: 1.4px solid var(--card-edge);
  border-radius: var(--r-card);
  padding: 8px 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: var(--number);
  font-weight: 900;
}
#ui .convert .arrow { flex: 0 0 auto; color: var(--ink-dim); }
#ui .convert .scrub { display: flex; align-items: center; gap: 10px; direction: ltr; }
/* **THE SCRUBBER, and it is a TOUCH control before it is a picture**
 * (Raf, 2026-08-28: *"improve all scrolls in game make it better!!! as many
 * players will use it!"*).
 *
 * Three things were wrong and only one of them was cosmetic.
 *
 * 1. **The track showed nothing.** It was one flat beige bar whatever the
 *    value, so the only reading of "how much" was the percent chip beside it.
 *    A slider whose track does not fill is a slider you have to read rather
 *    than see. The filled part is now the gold the button underneath is
 *    wearing, driven by `--fill`, which the JS keeps in step with the value.
 * 2. **Dragging it scrolled the panel.** No `touch-action`, so on a phone the
 *    browser claimed the gesture as a scroll and the thumb was hard to move
 *    at all -- the single worst thing about it on the device most players
 *    hold. `touch-action: none` gives the drag to the control.
 * 3. **The hit target was the track.** 10px of height is a third of what a
 *    finger needs. The INPUT is 34px tall now and the track is drawn inside
 *    it, so the thumb can be grabbed anywhere near it without the bar itself
 *    becoming a slab.
 *
 * Both sliders in the game -- the gold converter and the sell-how-many count
 * -- wrap in `.scrub`, so this is written against that and not against the
 * converter, which is why the sell one is fixed too. */
#ui .scrub input[type="range"] {
  appearance: none;
  -webkit-appearance: none;
  flex: 1 1 auto;
  width: 100%;
  height: 34px;
  background: transparent;
  outline: none;
  cursor: pointer;
  /* The drag belongs to the thumb, never to the panel behind it. */
  touch-action: none;
}
#ui .scrub input[type="range"]::-webkit-slider-runnable-track {
  height: 12px;
  border-radius: 999px;
  background:
    linear-gradient(180deg, #ECB542, #D89C24)
      left / var(--fill, 50%) 100% no-repeat,
    var(--line-soft);
  /* The same drop the gold button casts, so the filled bar and the button
   * under it read as one material rather than two golds. */
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.10), 0 2px 6px #8B651622;
}
#ui .scrub input[type="range"]::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 28px;
  height: 28px;
  /* centred on a 12px track: (28 - 12) / 2 */
  margin-top: -8px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid #D89C24;
  box-shadow: 0 3px 8px #8B651638, 0 1px 2px rgba(0, 0, 0, 0.10);
  cursor: pointer;
  transition: transform 90ms ease-out, box-shadow 90ms ease-out;
}
#ui .scrub input[type="range"]:active::-webkit-slider-thumb {
  transform: scale(1.10);
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.28);
}
/* Firefox draws the filled part itself, so it needs no `--fill`. */
#ui .scrub input[type="range"]::-moz-range-track {
  height: 12px;
  border-radius: 999px;
  background: var(--line-soft);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.10);
}
#ui .scrub input[type="range"]::-moz-range-progress {
  height: 12px;
  border-radius: 999px;
  background: linear-gradient(180deg, #ECB542, #D89C24);
}
#ui .scrub input[type="range"]::-moz-range-thumb {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid #D89C24;
  box-shadow: 0 3px 8px #8B651638, 0 1px 2px rgba(0, 0, 0, 0.10);
  cursor: pointer;
}
#ui .scrub input[type="range"]:active::-moz-range-thumb {
  transform: scale(1.10);
}
/* Keyboard reach: the thumb is the focus, so the ring goes on it. */
#ui .scrub input[type="range"]:focus-visible::-webkit-slider-thumb {
  outline: 3px solid var(--gold-glow);
  outline-offset: 1px;
}
#ui .convert .pct {
  flex: 0 0 auto;
  min-width: 62px;
  text-align: center;
  background: #fff;
  border: 1.4px solid var(--card-edge);
  border-radius: var(--r-card);
  padding: 7px 10px;
  font-size: var(--body);
  font-weight: 900;
}

/* RIGHT TO LEFT COMES FREE, and that is not a small thing: a Godot Theme
 * variation ignores `default_font`, so Arabic drew as tofu until somebody
 * found it, and the Noto fallback's line box made every panel a third taller
 * than the reference. The browser has neither problem. */
/* **THE X IS PHYSICALLY TOP-RIGHT IN BOTH LANGUAGES.** Flutter pins it there
 * (chrome.dart), and the reason is that a close is a chrome control rather
 * than a piece of the sentence -- a player who has learned where the corner is
 * does not re-learn it because the words changed. So it is `right`, not
 * `inset-inline-end`, and the head is padded 52 on BOTH sides so a centred
 * title clears it either way. Same for the upgrade disc beside it. */


/* ===========================================================================
 * THE COOP — the most-opened panel in the game.
 * ======================================================================== */

/* A HEAD THAT CAN BE REWRITTEN. The store's title is a constant; the coop's is
 * the flock's own name and it is the rename door, so it lives in its own span
 * rather than as the head's text — setting `textContent` on the head would
 * take the close button with it. */
/* **TWO LINES, not one.** A title that does not fit does not overflow — it
 * truncates, and that is invisible in a screenshot and in every viewport test.
 * `Fermes les plus aimées cette semaine` asked for 274 px of the 248 this
 * header gives it and shipped ending in an ellipsis; Turkish was cut too.
 * The X is absolutely positioned and the 52 px padding already clears it, so a
 * second line pushes nothing, and every title that fitted on one line still
 * does. The clamp stays, because three lines of title is a different bug. */
#ui .head .title {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* **The door carries a PENCIL since 2026-08-22** (Raf: *"add a something next
 * to the coop name to indicate that name of coop is editable"*), overturning
 * Flutter's rule that the title is its own label and a mark beside it would be
 * a second control. The counter-argument is his: a coop's title is the only
 * title in the game that opens anything, and it looks exactly like the eleven
 * that do not.
 *
 * Dim and small, INSIDE the title so it mirrors with the name rather than
 * against the card edge, and `vertical-align` rather than a flex row — the
 * title clamps to two lines and a flex parent would break the clamp. */
#ui .head .title.door { cursor: pointer; }
#ui .head .title.door:active { opacity: 0.55; }
#ui .head .title .pen {
  display: inline-block;
  margin-inline-start: 6px;
  vertical-align: -2px;
  color: var(--ink-dim);
}

/* The upgrade disc rides the head opposite the close. It stays after the
 * upgrade is bought and wears a tick — the caps two rows down read `100 / 100`,
 * which is what a FULL coop says too, so without this a player who just spent
 * a hundred gold gets no receipt at all. */
#ui .head .up {
  position: absolute;
  top: 8px;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(180deg, #ECB542, #D89C24);
  box-shadow: 0 4px 10px #8B651638;
  display: grid;
  place-items: center;
  padding: 0;
  cursor: pointer;
}
#ui .head .up[disabled] { opacity: 0.5; box-shadow: none; cursor: default; }
/* The mark is drawn in `currentColor`, so the DISC carries the ink. */
#ui .head .up { color: #fff; }
#ui .head .up svg { width: 17px; height: 17px; display: block; }
#ui .head .up { right: 50px; }

/* The one marker, and the SERVER picks which: feed beats full beats no male.
 * A healthy coop shows nothing at all and that emptiness is the feature. */
#ui .alert,
#ui .refusal {
  margin: 0 16px 8px;
  padding: 7px 12px;
  border-radius: var(--r-chip);
  font-size: var(--caption);
  line-height: 1.35;
  text-align: center;
}
#ui .alert { background: rgba(216, 76, 76, 0.12); color: var(--danger); }
#ui .refusal { background: var(--sky-wash); color: var(--ink); }

/* A CARD WITH A MARK DOWN ITS LEFT. Without the mark every card on the panel
 * is centred text between two hairlines, and five of them read as five
 * identical grey slabs. */
#ui .coop-row { display: flex; gap: 10px; align-items: flex-start; }
#ui .coop-mark {
  flex: 0 0 auto;
  width: 22px;
  display: grid;
  place-items: center;
  padding-top: 1px;
}
#ui .coop-mark svg { display: block; }
#ui .coop-col { flex: 1 1 auto; min-width: 0; display: grid; gap: 5px; }
/* The feed card is a plain tile -- the glyph pair is inline, not a left
 * mark -- so it takes the same rhythm directly. */
#ui .tile.feed { display: grid; gap: 5px; }
/* The trough about to run out. Hunger is the only thing in this game that
 * kills, so the card says so rather than leaving it to a red numeral. */
#ui .tile.low { border-color: var(--danger); }
#ui .tile.feed .fw { flex: 0 0 auto; }
#ui .coop-line {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: var(--body);
  font-weight: 800;
  justify-content: center;
  text-align: center;
}
/* **`flex: 0 1 auto`, not `1 1 auto`.** A grower takes every spare pixel, and
 * a row whose only child fills the row is a row that cannot be centred — the
 * text just sits at the start of a full-width box. It still SHRINKS, which is
 * what the ellipsis needs, and that is the whole reason the class exists. */
#ui .coop-line .grow { flex: 0 1 auto; min-width: 0; }
/* 12px of --ink-soft since 2026-08-26 (Raf: "the text and text color is
 * unreadable very hard to read") — 11px of #8A8A8A on white is ~3.5:1. */
#ui .coop-hint {
  margin: 0;
  font-size: 12px;
  color: var(--ink-soft);
  line-height: 1.35;
  text-align: center;
}
/* The column between a leading mark and a trailing control. Centring the row
 * above does nothing for these — the column IS the middle child and already
 * fills the gap — so the centring has to happen inside it. */
#ui .coop-col { text-align: center; }
#ui .coop-col .coop-line { justify-content: center; }
/* **The card title as ONE centred tag** (Raf, 2026-08-25: "make sure the egg
   icon and the text after are perfectly centered, if needed make them in a
   tag style together"). Icon and words share an inline-flex chip on a soft
   wash, centred by the line; a chevron or switch sits OUT OF FLOW at the
   inline end so it cannot drag the chip off centre. */
#ui .coop-line { position: relative; }
#ui .coop-line .head-tag {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: center;
  max-width: 100%;
  gap: 7px;
  padding: 3px 12px;
  border-radius: var(--r-chip);
  background: #F4EFE3;
}
/* Air between a head tag and the bar under it (Raf, 2026-08-25: "the egg
   fertility and the egg are too close and connected to the progress bar").
   On the GAUGE, not the tag: the door row wears the same tag with no bar,
   and a margin there would pad its card and drag the toggle off centre. */
#ui .coop-line + .gauge { margin-top: 8px; }
#ui .coop-line .chev,
#ui .coop-line .row-tail {
  position: absolute;
  inset-inline-end: 0;
  top: 50%;
  transform: translateY(-50%);
}
#ui .coop-hint.bad { color: var(--danger); }
#ui .chev {
  flex: 0 0 auto;
  align-self: center;
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
}

/* THE GAUGE. A share of a bar and nothing more — both numbers came off the
 * wire and nothing in the farm depends on the ratio between them. */
/* The bird card's wait: mark, bar, countdown. Always left to right -- the bar
   fills from the side every other bar in the game fills from, and a clock
   reads left to right in Arabic too. */
#ui .rowclock {
  display: flex;
  align-items: center;
  gap: 5px;
  direction: ltr;
}
#ui .rc-mark {
  flex: 0 0 auto;
  width: 13px;
  height: 14px;
  display: grid;
  place-items: center;
  color: var(--ink-dim);
}
#ui .rc-mark svg { display: block; }
#ui .rowclock .gauge { flex: 1 1 auto; min-width: 0; }
#ui .rc-time {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-variant-numeric: tabular-nums;
}
/* Five, not ten: this one runs under a card's worth of text rather than
   standing alone as a gauge. And it fills with INK, which is what Flutter's
   `_Bar` defaults to and the only one of the four bars in this panel that
   takes the default -- feed and fertility are money-green because they are
   about a supply running out, and how far through a wait you are is not. */
#ui .gauge.thin { height: 5px; }
#ui .gauge.thin > i { background: var(--ink); }
#ui .gauge {
  position: relative;
  height: 10px;
  border-radius: 999px;
  background: var(--line-soft);
  overflow: hidden;
}
#ui .gauge > i {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: 999px;
  background: var(--money);
  transition: width 0.2s ease-out;
  /* **It clips its own sheen.** Without this the highlight below is held in
   * only by the gauge's `overflow: hidden`, so it travels across the EMPTY
   * track as well as the filled part — which says the bar is filling, and the
   * whole point of the VIP gauge is that it is already full. */
  overflow: hidden;
}
#ui[dir="rtl"] .gauge > i { inset: 0 0 0 auto; }
#ui .gauge.low > i { background: var(--danger); }
#ui .gauge.gold > i { background: var(--gold-mid); }
/* **ALIVE, because a full still bar is indistinguishable from a farm that was
 * never in danger.** A sheen walking along the FILLED part — never a bar that
 * drains and refills, which would say the opposite of what VIP guarantees. */
#ui .gauge.vip > i::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg,
    rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.75) 50%,
    rgba(255, 255, 255, 0) 100%);
  transform: translateX(-100%);
  animation: farmyy-sheen 1.6s linear infinite;
}
@keyframes farmyy-sheen { to { transform: translateX(100%); } }

/* The VIP line REPLACES the Fill button rather than disabling it: a greyed
 * button says *you cannot*, and the point of VIP is *you need not*. */
#ui .vip-line {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--caption);
  color: var(--gold-dark);
  font-weight: 800;
}
#ui .vip-ring {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  border-radius: 999px;
  border: 2.5px solid var(--gold-glow);
  border-top-color: var(--gold-mid);
  animation: farmyy-ring 2s linear infinite;
}
@keyframes farmyy-ring { to { transform: rotate(360deg); } }

/* The two counts, ♀ then ♂, on the fertility card where Flutter has them.
 * CENTRED since 2026-08-25 — the card's line and gauge are centred and these
 * sat hard left under them (Raf's third screenshot: "improve and center the
 * eggs ui correctly as this sucks"). */
#ui .sexes { display: flex; align-items: center; justify-content: center; gap: 5px; font-size: 12px; color: var(--ink-soft); font-weight: 700; flex-wrap: wrap; }
/* A figure and its word are one thing: "Hens 15" must never split across
 * lines while the line around it wraps. */
#ui .sexes > span { white-space: nowrap; }
#ui .sexes .gap { width: 12px; }
#ui .sexes svg { display: block; }

/* The two clocks under the nest box, LTR in every language: a clock reads left
 * to right in Arabic too. Centred with the rest of the card, same day and
 * same screenshot as .sexes above. In the eggs TILE the pair wraps into two
 * lines — flex-wrap was already here waiting for it. */
#ui .clocks {
  display: flex;
  justify-content: center;
  gap: 4px 12px;
  flex-wrap: wrap;
  font-size: 12px;
  color: var(--ink-soft);
}

/* THE COOP DASHBOARD — Raf picked option 2 off the 2026-08-26 sheet, then
 * revised to option 3 the same night ("make it like this the '3' style from
 * sheet instead of the current '2'"): full-width rows — wash icon tile at
 * the start, name and figure sharing a line, a full-width gauge, one soft
 * caption — Fill inline on the feed row, the buy pair on the panel floor.
 * The readability order survives from the same day: secondary text is 12px
 * of --ink-soft (never 11px ink-dim), figures are 900-weight ink. */
#ui { --ink-soft: #625E55; }
#ui .coop-hint.start, #ui .clocks.start, #ui .sexes.start {
  text-align: start;
  justify-content: flex-start;
}
#ui .srow {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 12px;
}
#ui .sicon {
  width: 46px;
  height: 46px;
  border-radius: 13px;
  background: var(--panel-hi);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  align-self: center;
}
#ui .smain { display: grid; gap: 5px; min-width: 0; }
#ui .sname {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
#ui .sname .sword {
  font-size: var(--body);
  font-weight: 800;
  color: var(--ink);
  min-width: 0;
}
#ui .sval {
  font-size: 18px;
  font-weight: 900;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  direction: ltr;
  white-space: nowrap;
  line-height: 1.1;
}
#ui .sval small { font-size: 13px; font-weight: 800; color: var(--ink-soft); }
#ui .sval.bad, #ui .sval.bad small { color: var(--danger); }
#ui .smain .gauge { height: 7px; width: 100%; }
#ui .send { display: inline-flex; align-items: center; }
#ui .send .btn { margin-bottom: 0; min-height: 42px; }
/* THE DOOR SWITCH -- both states on screen at once (Raf, 2026-08-28: *"instead
 * of a toggle, make the birds in / out a button switch that looks good"*).
 *
 * Built as `.shelf` is built and for the same reason: ONE white card with a
 * rim and 3 px of padding, whose halves are borderless fills. Two chips with a
 * gap between them read as two buttons that happen to be adjacent, and the
 * fill can only mean "this half" once the pair reads as one object.
 *
 * The selected face is the game's own `.btn` gradient rather than a flat sky,
 * because every other pressed thing in this build has a face and a soft shadow
 * (his 2026-08-25 pick) and a flat rectangle beside them looks like a
 * different app. */
#ui .doorsw {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0;
  padding: 3px;
  background: #fff;
  border: 1.4px solid var(--card-edge);
  border-radius: 15px;
  /* **Forced LTR.** The arrow is drawn into the glyph and does not mirror, so
     the PAIR must not either -- in Arabic a mirrored row puts the
     right-pointing arrow on the left and the switch says the reverse of what
     it means. Same rule as the name pill and the feed/water pair. */
  direction: ltr;
}
#ui .doorsw button {
  appearance: none;
  font-family: inherit;
  background: transparent;
  border: 0;
  border-radius: 12px;
  padding: 7px 13px;
  cursor: pointer;
  display: grid;
  place-items: center;
  color: var(--ink-dim);
  transition: background 0.14s ease, color 0.14s ease, box-shadow 0.14s ease;
}
#ui .doorsw button svg { display: block; }
#ui .doorsw button[aria-selected="true"] {
  background: linear-gradient(180deg, #4CBEF0, #31A6DD);
  color: #fff;
  box-shadow: 0 3px 8px #1A6B9030;
}
#ui .doorsw button:active { transform: translateY(1px); }
/* Narrow phones: the row is icon + name + this, and 320 pt is the budget the
 * whole panel is measured against. */
@media (max-width: 360px) {
  #ui .doorsw button { padding: 7px 9px; }
}
/* **The switch IS the row** (Raf, 2026-08-29, off a screenshot: *"remove the
 * blue, make the red center and bigger (basicly the button)"*). The icon tile
 * and the name line are gone, so the three-column grammar has nothing to put
 * in columns 1 and 2 -- one full-width cell, and each half is now half a card
 * rather than a chip in the trailing slot. The narrow-phone rule above cannot
 * shrink these: `.doorrow .doorsw button` outranks `.doorsw button` whatever
 * the order, and there is nothing to shrink for -- the halves are `1fr` of the
 * card and carry no text to clip. */
#ui .srow.doorrow {
  grid-template-columns: minmax(0, 1fr);
  padding: 10px;
}
#ui .doorrow .doorsw { width: 100%; border-radius: 17px; }
/* The glyph is WIDE since 2026-08-29 (house, arrow, chick — his option 4) and
 * only 23 px tall at the size he settled on, so the PADDING is what gives the
 * half its height: 13 px against a 23 px picture is the 49 px the row had when
 * the mark was a 26 px square. */
#ui .doorrow .doorsw button { padding: 13px 0; border-radius: 14px; }
/* TODO 517: the count sits BESIDE the glyph, not under it -- a second grid row would make the switch the
 * tallest row on the card. Figures are tabular so a ticking count does not shake the glyph. */
#ui .doorsw button { grid-auto-flow: column; justify-content: center; gap: 8px; }
#ui .doorsw .doorn {
  font-weight: 800;
  font-size: 17px;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  direction: ltr;
}
#ui .smain .vip-line { justify-content: flex-start; }
#ui .acts { display: grid; gap: 7px; }
#ui .acts .btn { margin-bottom: 0; }
/* **ONE BUTTON PER ROW, and every label on ONE LINE** (Raf, 2026-08-26:
 * *"buy normal / rare chick button should be one line always not two
 * lines"*, with the screenshot where both read "Buy normal" over a clipped
 * second line). Two to a row gives each about 150 pt of a 390 pt panel, and
 * the shortest label the game has is "Buy rare chick" plus a coin and a
 * price — nothing fits that on one line in any language, and the longest,
 * `Acheter poussin normal` / `Beli anak ayam normal`, is not close. Shrinking
 * the type to force it would have to shrink it for Urdu and Hindi too.
 *
 * Full width, `nowrap`, and the label may SHRINK but the coin may not: a
 * price is the one thing on the button that must never be the half that gets
 * cut. `min-width: 0` on the word is what lets the ellipsis happen there
 * instead of overflowing the card. */
#ui .buy-pair {
  display: grid;
  grid-template-columns: 1fr;
  gap: 7px;
}
#ui .buy-pair .btn {
  font-size: var(--body);
  padding: 0 14px;
  white-space: nowrap;
}
#ui .buy-pair .btn > span:first-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
#ui .buy-pair .btn .coin { flex: 0 0 auto; }

/* Fill, OUTSIDE the feed card and as big as a buy button (Raf, 2026-08-26 —
 * third placement, and the one that settles it). It is a panel-level row like
 * `.buy-pair`, not furniture inside a stat card: the same full width, the same
 * shape, because it is the same kind of thing — the one action this panel
 * exists for when the trough is empty. `margin: 0 0 10px` gives it the stat
 * cards' own rhythm so it reads as a step in the list rather than as something
 * dropped between two of them. */
/* **AN ACTION LIVES IN THE CARD IT ACTS ON** (Raf, 2026-08-26, drawn on two
 * screenshots rather than typed: *"i want these buttons inside the cards just
 * like what i drew"*). `.srow` is a three-column grid — icon, body, trailing
 * slot — so the action spans all three and lands full width under the figure
 * it changes. Fill sits in the feed card; the buy pair in the Birds card.
 * The card's own 12 px padding is the inset, so nothing here sets a margin. */
#ui .srow-act { grid-column: 1 / -1; display: grid; margin-top: 4px; }
#ui .srow-act .btn { margin-bottom: 0; width: 100%; }

/* THE FLOCK. Two cards to a row, and every card exactly the same height
 * whatever it holds — fixed slots for the tier, the name, the clock and the
 * controls. Two columns of different heights is a staircase. */
#ui .flock {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px;
  /* **A card is its own height** (Raf, 2026-08-26: *"the sell button is very
   * far make it under feather"*). Stretching every card to the tallest in its
   * row is what put a hen's laying clock's worth of nothing between a
   * cockerel's last fact and his Sell button — the button obeyed
   * `margin-top: auto` and went to a floor that belonged to the bird beside
   * him. The staircase that alignment was avoiding is the smaller fault: a
   * button that follows its own card reads as part of that card, and a button
   * an inch below the words reads as part of nothing. */
  align-items: start;
}
/* TODO 528: the card a tap inside the house asked for -- lit in the sky ink, fading after two seconds. */
#ui .bird.focus { animation: bird-focus 2.4s ease-out 1; }
@keyframes bird-focus {
  0%, 55% { box-shadow: 0 0 0 3px #31A6DD, 0 4px 14px #1A6B9040; }
  100% { box-shadow: 0 0 0 0 #31A6DD00; }
}
#ui .bird {
  background: #fff;
  border: 1.4px solid var(--line);
  border-radius: var(--r-card);
  /* The extra four at the bottom is the SELL BUTTON'S LIP. Every button in
   * this game is a face over a darker plate six pixels below it, and a card
   * whose padding only clears the face draws the plate across its own edge. */
  padding: 8px 8px 12px;
  /* A COLUMN, so the last row can be pushed to the bottom. As a grid the rows
   * packed from the top and a hen with a hunger warning wore her Sell button
   * 18 px lower than the chick beside her. */
  display: flex;
  flex-direction: column;
  gap: 4px;
}
/* Dim, not disabled: the bird is not broken, it is elsewhere. */
#ui .bird.held { opacity: 0.72; }
#ui .bird .head-row { display: flex; align-items: center; gap: 4px; min-height: 18px; }
#ui .bird .head-row .gap { flex: 1 1 auto; }
/* THE PLATE. Full width and square since the plate became a photograph --
 * Flutter's 76px strip belonged to a drawn wash, not a picture with a ground
 * plane in it. Everything that is not a word sits in its corners. */
#ui .bird .shot.plate {
  position: relative;
  width: auto;
  height: auto;
  /* SQUARE, because the photograph is. A fixed 84px height cover-cropped a
   * width-dependent band out of the middle of the picture -- on a one-bird
   * grid it beheaded the chick. The full frame is the one that was approved. */
  aspect-ratio: 1 / 1;
  justify-self: stretch;
}
/* The picture IS the plate since 2026-08-24 ("render chicken on green land
 * and take picture"): an opaque photograph of the bird on pasture, filling
 * the box edge to edge. A chick is framed with more air by the photographer,
 * not scaled down here. */
#ui .bird .shot.plate img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--r-chip);
}
#ui .bird .shot.plate .pin { position: absolute; display: grid; place-items: center; }
#ui .bird .shot.plate .pin.tl { top: 3px; left: 4px; }
#ui .bird .shot.plate .pin.tr { top: 3px; right: 5px; }
#ui .bird .shot.plate .pin.bl { left: 4px; bottom: 4px; }
#ui .bird .shot.plate .pin.br { right: 4px; bottom: 4px; }
#ui .bird .shot.plate .way { color: var(--ink-dim); display: grid; place-items: center; }
#ui .bird .name {
  font-size: var(--body);
  font-weight: 800;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* THE NAME LINE, with the gender mark before the kind — Raf's option 6 plus
 * his own addition (2026-08-26: *"add gender icon (male/female icon) before
 * 'Chicken'"*). The row is centred as a pair, so a cockerel card and a hen
 * card put their badges in the same place and the shelf reads down a column.
 * `flex: 0 0 auto` on the badge and `min-width: 0` on the word: the word may
 * ellipsis, the badge may never shrink — a half-drawn gender sign is worse
 * than a trimmed noun. */
#ui .bird .name-row,
#ui .mk-card .name-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin: 6px 0 7px;
}
#ui .name-row .sexbadge { flex: 0 0 auto; display: block; }
#ui .name-row .name-word {
  min-width: 0;
  font-size: var(--body);
  font-weight: 900;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* THE CAPTIONED FACTS — rarity, colour, feather, one per line. The caption is
 * small, spaced and quiet; the value is heavy and end-aligned, so the eye runs
 * down the values as a column and only reads a caption when it needs one.
 *
 * `direction` is left ambient on purpose: in Arabic the caption belongs on the
 * right and the value on the left, and this row is language, not a physical
 * order like the gender badge or the feed pair. */
#ui .facts { display: grid; gap: 3px; margin-bottom: 6px; }
/* **THE ROW WRAPS BEFORE THE WORD IS CUT** (2026-08-31). A colour name is not
 * a reading like a clock or a count — it is the whole answer the row exists to
 * give, and half of it is worth nothing. *Cinnamon* had 72 px of word and
 * 56 px of room in an English card at 320, so it drew as *Cinnam*; Vietnamese
 * *Xanh mòng két* lost its last letter at Raf's own 390. Nine of thirteen
 * languages sampled were cutting one.
 *
 * Wrapping is the only fix that keeps the WORD: the value is a single word, so
 * there is nothing to reflow inside it and shrinking the box can only clip.
 * Given its own line it has the whole card. The caption and the value stay
 * together on one line whenever they fit, which is the common case and every
 * card the design was drawn against.
 *
 * `margin-inline-start: auto` replaces `justify-content: space-between`: that
 * only end-aligns a line holding TWO items, so the wrapped line would have
 * dropped its value to the start of the row and broken the column of values
 * the caption stack is read down. An auto margin does both cases. */
#ui .fact {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1px 6px;
  min-width: 0;
}
#ui .fact-lab {
  font-size: 9.5px;
  font-weight: 900;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--ink-soft);
  flex: 0 0 auto;
}
#ui .fact-val {
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  margin-inline-start: auto;
  font-size: 11.5px;
  font-weight: 900;
  color: var(--ink);
}
/* **`text-overflow` LIVED ON `.fact-val` AND DID NOTHING**, which is the whole
 * of the defect above. `.fact-val` is a flex container — it has to be, it
 * carries the paint dot beside the word — and `text-overflow` applies to a
 * block container's inline content, never to its flex items. So the box
 * clipped at `overflow: hidden` and the ellipsis was drawn nowhere: the player
 * could not even tell a word had been cut, which is worse than the cut.
 *
 * It belongs on the element that HOLDS THE TEXT. This is the last resort now
 * that the row wraps — a language whose colour name is longer than a whole
 * card gets a mark saying so instead of a silent stump. */
#ui .fact-word {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The paint itself, beside the word for it. A ring of card and a hairline of
 * ink so a white bird's dot is still a dot on a white card. */
#ui .fact-dot {
  flex: 0 0 auto;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  box-shadow: 0 0 0 1.5px #fff, 0 0 0 3px #00000014;
}
/* The tag row under the name (2026-08-25, "make sure the teir ans color and
   feather type are gender are tags correctly, just reognize the cards"):
   tier chip, then the feather feature as a chip in the same grammar. One row,
   centred, never wrapping -- a card is 152 px and two chips fit; a third
   would be a design change, not an overflow. */
#ui .bird .tags {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 3px 4px;
  min-height: 18px;
  overflow: hidden;
}
/* The feather chip: GREY (Raf, 2026-08-25: "make all the feather tags grey
   correctly") -- a feature, not a rank, so it wears no rank's colour. */
#ui .tier.feat {
  color: var(--ink-dim);
  border-color: var(--line);
  background: #EDEDF1;
}
/* The sex chip wears its pair's own family — blue cockerel, pink hen (Raf,
   2026-08-25) — and the glyph keeps its own stronger ink over the wash. */
#ui .tier.sex {
  display: inline-flex;
  align-items: center;
  gap: 3px;
}
#ui .tier.sex.m {
  color: var(--sky-lip);
  border-color: var(--sky);
  background: var(--sky-wash);
}
/* The hen chip follows the badge into pink — a chip and a badge on the same
 * card saying the same word in two different reds is worse than either. */
#ui .tier.sex.f {
  color: #A63164;
  border-color: var(--hen);
  background: #FDECF3;
}
/* The colour word INSIDE the tier chip, divided from the tier word so it
   reads as a tag riding a tag rather than a two-word rank. `painted` is the
   real thing — a pill washed in the plumage itself, ink set inline by
   luminance — and the divider form survives only as the no-paint fallback. */
#ui .tier .in-colour {
  margin-inline-start: 4px;
  padding-inline-start: 5px;
  border-inline-start: 1px solid currentColor;
}
#ui .tier .in-colour.painted {
  border-inline-start: 0;
  padding: 1px 7px;
  border-radius: 999px;
}
#ui .bird .state {
  min-height: 18px;
  font-size: var(--caption);
  color: var(--ink-dim);
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#ui .bird .state.bad { color: var(--danger); }
/* **A state line that is a SENTENCE wraps.** The default above is `nowrap`
 * with an ellipsis, which is right for a reading -- a clock, a percentage, one
 * word -- and wrong for a clause. The pen's herd cards carry two clauses a
 * bird has no analogue of ("Expecting, 38h to go", "Resting - ready in 14h")
 * and they are the whole reason a player can tell three identical ewes apart;
 * ellipsized at 140 px in sixty languages they say nothing. Same argument the
 * `.fixit` remedy line below is already allowed to wrap on, and given its own
 * name rather than borrowing that one, because a class called `fixit` on a
 * line that fixes nothing is a comment that lies. */
#ui .bird .state.wraps {
  white-space: normal;
  overflow: visible;
  line-height: 1.25;
}
/* The word and the countdown are two nodes now — the clock is rewritten every
 * second and cannot be part of a sentence that is rebuilt with it. */
#ui .bird .state.hunger {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 5px;
}
/* The remedy, under the countdown. **It WRAPS**, unlike every other line in
 * this slot: it is a sentence rather than a reading, and the eight languages
 * do not agree on its length — a `nowrap` here would ellipsize the only words
 * on the card that say what to do. The glyphs stay on the first line with the
 * text beside them, which is what `align-items: baseline` on a wrapping flex
 * row gives without a second container. */
#ui .bird .state.fixit {
  display: flex;
  /* The glyphs stay BESIDE the sentence and the sentence wraps under itself.
   * Wrapping the flex row instead stranded the bag and the drop alone on
   * their own line the moment the words needed two — true in French at this
   * width, and in five of the eight at 320 pt. */
  flex-wrap: nowrap;
  align-items: flex-start;
  justify-content: center;
  gap: 4px;
  white-space: normal;
  overflow: visible;
  line-height: 1.25;
}
#ui .bird .state.fixit .fw { flex: 0 0 auto; padding-top: 1px; }
#ui .bird .state.fixit span { flex: 0 1 auto; min-width: 0; }
#ui .bird .acts { min-height: 32px; display: flex; gap: 5px; align-items: center; justify-content: center; }
#ui .bird .acts .btn { flex: 1 1 auto; min-height: 30px; }
/* The last row, and it follows the facts rather than the card's floor. It
 * kept `margin-top: auto` and a reserved height so a reservation with nothing
 * to sell stayed the same height as a bird — which cost every card that is
 * shorter than its neighbour a gap where its button should be. The grid no
 * longer stretches (see `.flock`), so there is no floor to be pushed to. */
#ui .bird .under { display: flex; }
#ui .bird .under:not(:empty) { min-height: 30px; }
/* The caption size, which is Flutter's own choice for this one button: a
 * verb and a four-figure price on a card seventy pixels wide need the room. */
#ui .bird .under .btn { flex: 1 1 auto; min-height: 30px; font-size: var(--caption); }

/* THE TIER, on every card including a common one. A mark that appears only on
 * the good ones is unreadable — an unmarked bird would be either common or one
 * whose chip failed to draw. */
#ui .tier {
  font-size: 10px;
  font-weight: 800;
  padding: 1px 6px;
  border-radius: var(--r-chip);
  border: 1.2px solid var(--line);
  color: var(--ink-dim);
  /* A real wash, because a white chip on a white card is invisible (Raf,
     2026-08-25: "i cant see the common tier tag"). Rare and prize override. */
  background: #EDEDF1;
  white-space: nowrap;
}
#ui .tier.rare { color: var(--gold-dark); border-color: var(--gold-mid); background: var(--gold-glow); }
#ui .tier.prize { color: var(--prize); border-color: var(--prize); background: rgba(139, 92, 200, 0.12); }

/* A DISC. The game's one round button; gold where it spends gold. */
#ui .disc {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(180deg, #4CBEF0, #31A6DD);
  box-shadow: 0 4px 10px #1A6B9038;
  display: grid;
  place-items: center;
  padding: 0;
  cursor: pointer;
}
#ui .disc.gold { background: linear-gradient(180deg, #ECB542, #D89C24); box-shadow: 0 4px 10px #8B651638; }
#ui .disc:active { transform: translateY(2px); box-shadow: none; }
#ui .disc { color: #fff; }
#ui .disc svg { width: 16px; height: 16px; display: block; }

/* A SUB-SHEET. Capped at the same 440 as the panel it covers — the Godot one
 * ran the full 1920 px of Raf's monitor over a panel that was correctly 440. */
#ui .sheet {
  position: absolute;
  inset: 0;
  background: rgba(37, 37, 37, 0.42);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 18px;
  pointer-events: auto;
  z-index: 2;
}
#ui .sheet > div {
  width: 100%;
  /* **A flex item's `min-width` is `auto`**, which is its content's min-content
   * width -- so `width: 100%` is a suggestion and two buttons on one row can
   * push the sheet wider than the panel it covers. Measured at 320 pt in
   * French: 305 in a 282 box, with the sum's right column and the outer half
   * of the gold button off the screen edge. */
  min-width: 0;
  max-width: 404px;
  max-height: 100%;
  overflow-y: auto;
  /* WHITE, like the card it covers. `--panel` is the popup's own ground, not
   * a card's, and a cream sheet over a white panel reads as a different app. */
  background: #fff;
  border: 1.6px solid var(--line);
  border-radius: var(--r-panel);
  box-shadow: 0 4px 0 rgba(0, 0, 0, 0.2);
  padding: 16px;
  display: grid;
  gap: 10px;
}
#ui .sheet h3 {
  margin: 0;
  font-size: var(--title);
  font-weight: 800;
  text-align: center;
}
#ui .sheet .rows { display: grid; gap: 7px; }
#ui .sheet .rows > div {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-size: var(--body);
}
/* The mark ahead of the label. A FIXED box, centred, so three marks of three
 * different shapes — the egg is tall, the feed pair is wide, the bird is
 * square — still line their labels up. Flutter's is a 22-square for exactly
 * this. `align-self: center` because the row is baseline-aligned for its text
 * and a picture has no baseline worth sitting on. */
#ui .sheet .rows .mk {
  flex: 0 0 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  align-self: center;
}
#ui .sheet .rows .mk svg,
#ui .sheet .rows .mk img { display: block; }
/* **A photographed bird carries its own 62 px plate and must be told
 * otherwise.** `.mk` is `flex: 0 0 30px` and the shot overflowed it by
 * double — the confirmation's first row stood 62 px tall with a blank cream
 * slab in it, which reads as a picture that failed rather than as a bird.
 * A flex BASIS does not clamp a child; the child's own size does. */
#ui .sheet .rows .mk .shot {
  width: 26px;
  height: 26px;
  border-radius: 7px;
}
#ui .sheet .rows .what { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* `50 → 100` is a SUM, not a sentence, and a mirrored one reads as the upgrade
 * taking capacity away. */
#ui .sheet .rows .sum { direction: ltr; display: flex; gap: 6px; align-items: center; }
#ui .sheet .rows .sum .was { color: var(--ink-dim); }
#ui .sheet .rows .sum svg { display: block; color: var(--ink-dim); }
#ui .sheet .rows .said { direction: ltr; }
/* **Wrapping, never shrinking.** A button squeezed to an ellipsis no longer
 * says what it does, and Cancel / Upgrade is exactly the pair where that
 * matters. One under the other is still one choice. */
#ui .sheet .acts { display: flex; gap: 8px; flex-wrap: wrap; }
#ui .sheet .acts .btn { flex: 1 1 130px; }
#ui .sheet .picks { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px; }
/* **The destination is a PICTURE of the shed**, because with three chicken
 * coops the picker offered three word cards differing by a numeral and the one
 * thing a player recognises their own shed by is its paint. Same `objShot`,
 * same tint and same 44 px as the panel header — *"same as its ui"* names the
 * mechanism, not just the ask. */
#ui .sheet .move-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
}
#ui .move-pic { flex: 0 0 auto; }
/* **Centred, like every input in the game** (Raf, 2026-08-21: *"make sure all
 * user inputs are centered"*). The launcher's own email and code boxes have
 * been centred since the first build; these two classes were the exception,
 * and the farm-name ask is the one a new player meets first. */
#ui .sheet input[type="text"] {
  width: 100%;
  font: inherit;
  font-size: var(--body);
  text-align: center;
  padding: 10px 12px;
  border-radius: var(--r-chip);
  border: 1.4px solid var(--line);
  background: var(--panel-hi);
  color: var(--ink);
}

/* THE NEST BOX. Five eggs to a row is Raf's own number. */
#ui .nest {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 8px;
}
/* ONE BREED, ONE SECTION (Raf, 2026-09-12: *"breeds eggs are mixing"*). The
 * scroll lives here and not on each breed's grid: three sections each with
 * their own scroller is three places to look for the rest of a clutch. */
#ui .nestwrap { max-height: 280px; overflow-y: auto; display: grid; gap: 10px; }
#ui .nest-group { display: grid; gap: 6px; }
/* The heading is the breed's own hen and then her name (option 1 of
 * NESTBOX-options): a clutch and the flock that laid it read as one thing. */
#ui .nest-breed {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: 13px;
  font-weight: 700;
}
#ui .nest-face {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  flex: 0 0 34px;
  border-radius: 12px;
  background: var(--sky-wash);
  overflow: hidden;
}
#ui .nest-face > .shot { width: 30px; height: 30px; min-height: 0; margin: 0; }
#ui .nest-face > .shot img { width: 100%; height: 100%; object-fit: contain; }
#ui .nest > div { display: grid; gap: 4px; position: relative; }
#ui .nest > div.sellable { cursor: pointer; }
/* THE ONE EGG'S OWN SELL BUTTON. Hidden until its egg is tapped, and drawn
   ON the egg -- Raf, 2026-08-22: *"a button shows in egg"*. Same capsule as
   the wallet receipt: a tile is about sixty pixels wide, which is room for a
   coin and a figure and not for a word, so the word is the aria-label and
   the money ink is what says what pressing it does. */
/* THE ONE EGG'S OWN SELL BUTTON. `.btn.sell` carries the colour, the rim and
   the lip -- it is the same control the bird card sells with, and everything
   here is size and place. Across the egg's waist and as wide as the tile: a
   coin and a figure in a 26 px disc is two marks fighting over one, and the
   thing has to read as a BUTTON before it reads as a price. */
#ui .egg-sell {
  position: absolute;
  left: 1px;
  right: 1px;
  top: 60%;
  transform: translateY(-50%) scale(.7);
  direction: ltr;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: 26px;
  padding: 0 7px 0 5px;
  border-width: 1.6px;
  box-shadow: 0 3px 8px #327C2B38;
  font: 900 11px/1 inherit;
  opacity: 0;
  pointer-events: none;
  transition: opacity .12s ease, transform .12s ease;
}
#ui .egg-sell svg { flex: none; }
/* THE HINT: this egg is worth money and a tap will say how much. It rides the
   shell's own shoulder rather than sitting beside the tile, because there is
   no room beside a 53 px tile and because it is about the EGG. It goes as
   soon as the button it advertises is up — two green marks on one egg, one of
   them not pressable, is the worse state. */
#ui .egg-tag {
  position: absolute;
  top: 2px;
  right: 0;
  width: 17px;
  height: 17px;
  border-radius: 999px;
  background: #fff;
  border: 1.6px solid var(--apple);
  display: grid;
  place-items: center;
  box-shadow: 0 1px 0 rgba(0, 0, 0, .12);
  transition: opacity .12s ease;
}
#ui .egg-tag svg { width: 11px; height: 11px; }
#ui .nest > div.asking .egg-tag { opacity: 0; }
#ui .egg-sell:active { transform: translateY(calc(-50% + 3px)); box-shadow: none; }
/* THE HATCH, BOUGHT. Physically above the Sell -- Raf's words are *"on top of
   the sell egg"* -- and the bird card's own gold boost disc, not a second
   capsule: the tile is 53 px and the row below it already spends that width on
   a coin and a figure. It reveals with the sell on the same tap, so one press
   shows everything this egg can be asked. */
#ui .egg-boost {
  position: absolute;
  left: 50%;
  top: 26%;
  width: 28px;
  height: 28px;
  transform: translate(-50%, -50%) scale(.7);
  opacity: 0;
  pointer-events: none;
  transition: opacity .12s ease, transform .12s ease;
}
#ui .nest > div.asking .egg-boost {
  opacity: 1;
  pointer-events: auto;
  transform: translate(-50%, -50%) scale(1);
}
#ui .egg-boost:active { transform: translate(-50%, calc(-50% + 3px)); box-shadow: none; }
#ui .nest > div.asking .egg-sell {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(-50%) scale(1);
}
/* The egg steps back while its own price is up. Not hidden -- the player is
   deciding about THIS egg and has to keep seeing which one it is -- just far
   enough that the button is unambiguously the foreground. */
#ui .nest > div > svg { transition: opacity .12s ease; }
#ui .nest > div.asking > svg { opacity: .55; }
/* The EGG MARK only -- the direct child. It used to be every svg in the tile,
   which silently blew the coin on the per-egg Sell button up to the width of
   the whole tile. */
#ui .nest > div > svg { display: block; width: 100%; height: auto; }
/* The clock is INSIDE the bar: a tile is about sixty pixels wide, which is not
 * room for a picture, a bar and a line of text stacked. */
#ui .hatch {
  position: relative;
  height: 15px;
  border-radius: 999px;
  background: #F2EEE4;
  overflow: hidden;
}
#ui .hatch > i {
  position: absolute;
  inset: 0 auto 0 0;
  background: #E4D6AE;
}
#ui .hatch > span {
  position: absolute;
  inset: 0;
  direction: ltr;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2px;
  font-size: 9px;
  font-weight: 800;
  color: var(--ink);
}
#ui .hatch > span svg { width: 9px; height: 9px; }
#ui .empty {
  margin: 0;
  padding: 14px 0;
  text-align: center;
  font-size: var(--body);
  color: var(--ink-dim);
}
#ui .rule { height: 1px; background: var(--card-edge); margin: 2px 0; }

/* THE FERTILITY ROWS -- one per breed, no coop total (Raf, 2026-09-12).
 * The ring is a conic gradient with a hole punched in it: the arc IS the
 * percentage, so the picture and the figure beside it cannot drift. */
/* The fertility card carries a LIST, so it has no icon column: the mark
 * leads the heading instead (Raf, 2026-09-12). One column, and the mark is
 * sized down to sit on a line of text rather than to fill a 46px tile. */
#ui .srow.fert-card { grid-template-columns: minmax(0, 1fr); }
#ui .fert-head { justify-content: flex-start; align-items: center; gap: 9px; }
#ui .fert-head > .sicon {
  width: 30px;
  height: 30px;
  border-radius: 9px;
  flex: 0 0 auto;
}
#ui .fert-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 0;
  border-top: 1px solid var(--line);
}
#ui .fert-row:first-of-type { border-top: 0; padding-top: 4px; }
#ui .fert-ring {
  flex: 0 0 46px;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  display: grid;
  place-items: center;
}
#ui .fert-hole {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--panel);
  overflow: hidden;
  display: grid;
  place-items: center;
}
/* `plateShot` hands back the coop's own bird box, which is sized for a card.
 * It is scaled to the hole here rather than given a second class, so the
 * portrait, the colour wash and the waiting shimmer stay the card's. */
#ui .fert-hole > .shot { width: 38px; height: 38px; min-height: 0; margin: 0; }
#ui .fert-hole > .shot img { width: 100%; height: 100%; object-fit: contain; }
#ui .fert-mid { flex: 1; min-width: 0; }
#ui .fert-name { font-weight: 700; }
#ui .fert-sub { font-size: 12px; color: var(--ink-dim); }
#ui .fert-do { font-size: 12px; font-weight: 700; color: var(--danger); }
#ui .fert-pc { font-weight: 800; font-size: 19px; direction: ltr; }

/* TODO 408: a breed's heading inside the flock grid spans both columns. */
#ui .flock-breed {
  grid-column: 1 / -1;
  font-weight: 700;
  font-size: 13px;
  opacity: 0.8;
  padding: 6px 2px 0;
}
/* THE POND (TODO 508): a sheet over the water. The scrim is CLEAR -- the view above
 * is the point of the panel -- and the card sits at the bottom half, so the fish
 * swim in the top half of the screen and the controls never cover them. */
#ui .pond-panel .scrim { background: transparent; }
#ui .pond-panel .popup {
  top: 46%;
  bottom: calc(10px + env(safe-area-inset-bottom, 0px));
  justify-content: flex-end;
}
#ui .pond-panel .card {
  background: rgba(255, 255, 255, 0.96);
  max-height: 100%;
}
#ui .pond-stock { display: block; padding: 12px 12px 10px; }
#ui .pond-stock .sname { display: flex; justify-content: space-between; align-items: baseline; }
#ui .fish-grid { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
#ui .fish-card {
  display: grid;
  grid-template-columns: 58px minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px;
  padding: 5px 6px;
  border-radius: 12px;
  background: linear-gradient(180deg, #F2F8F9, #E6F1F3);
  border: 1px solid rgba(40, 90, 110, 0.12);
}
#ui .fish-pic {
  width: 58px;
  height: 40px;
  object-fit: contain;
  border-radius: 8px;
  background: radial-gradient(ellipse at 50% 40%, #CFE6EC, #A9CFD9);
}
#ui .fish-info { min-width: 0; }
#ui .fish-info b { display: block; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#ui .fish-sub { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; margin-top: 2px; font-size: 12px; color: var(--ink-soft, #5F5A52); }
#ui .fish-state.ok { color: #3E8E5A; }
#ui .fish-acts { display: flex; gap: 5px; align-items: center; }
#ui .fish-acts .btn.small { padding: 5px 8px; min-height: 30px; font-size: 12px; }
/* TODO 509: a shop card is the fish on top and its buy pair across the whole card under it. */
#ui .fish-card.shop-fish { grid-template-columns: 58px minmax(0, 1fr); row-gap: 6px; }
#ui .fish-card.shop-fish .buy-pair { grid-column: 1 / -1; margin: 0; }
#ui .fish-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 2px; }
#ui .fish-chips .chip {
  border: 1px solid rgba(40, 90, 110, 0.25);
  background: #fff;
  border-radius: 999px;
  padding: 5px 11px;
  font-size: 13px;
}
#ui .fish-chips .chip.on { background: #2B7A90; color: #fff; border-color: #2B7A90; }
/* A fish's card picture is WIDE (TODO 508): on the square market plate it is
 * shown whole, never cover-cropped, or a guppy loses the tail that is its colour. */
#ui .shot.fish-shot img,
#ui .bird .shot.plate.fish-shot img { object-fit: contain; padding: 6%; box-sizing: border-box; }


/* ===========================================================================
 * THE SHOP.
 * ======================================================================== */

/* Two to a row, and a lone last card is NOT stretched: filling the row would
 * make the last thing on the page look like the most important one. */
#ui .shop-grid {
  display: grid;
  /* **`minmax(0, 1fr)`, never `1fr`** (Raf, 2026-09-05, off a phone shot of the
   * Animals shelf: *"make sure all shop cards same size"* -- the left column's
   * cards were visibly narrower with smaller portrait plates than the right's).
   * `1fr` is shorthand for `minmax(auto, 1fr)`, so a column whose content has a
   * wide min-content -- a long species name that cannot break, a plate the
   * flex-min rules will not shrink -- takes more than its half and starves its
   * neighbour. The zero floor makes the two halves arithmetic. Every other
   * two-column grid in this file is spelled the same way for the same reason. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px;
  align-items: stretch;
}
#ui .shop-tile {
  background: #fff;
  border: 1.4px solid var(--card-edge);
  border-radius: var(--r-card);
  padding: 12px 10px 14px;
  display: grid;
  gap: 8px;
  justify-items: center;
  align-content: start;
  cursor: pointer;
}
#ui .shop-tile:active { transform: translateY(1px); }
/* Dimmed, not greyed to a different colour, so it is plainly the SAME card as
 * its neighbours with the lights off. */
#ui .shop-tile.locked { opacity: 0.55; cursor: default; }
#ui .shop-tile.locked:active { transform: none; }

/* The front page is DOORS and the deep pages are GOODS, and they are two
 * different cards for that reason: a 42 px washed square against a plate the
 * full width of the tile. */
#ui .shop-tile .pic {
  width: 42px;
  height: 42px;
  border-radius: var(--r-chip);
  background: color-mix(in srgb, currentColor 14%, #fff);
  display: grid;
  place-items: center;
}
#ui .shop-tile .plate {
  width: 100%;
  height: 66px;
  border-radius: var(--r-chip);
  background: color-mix(in srgb, currentColor 14%, #fff);
  display: grid;
  place-items: center;
}
/* A tile holding a real render takes the paper tone. See `plate()`: the
 * accent belongs to the mark, and a photograph is not a mark. */
#ui .shop-tile .pic.photo,
#ui .shop-tile .plate.photo { background: var(--panel-hi); }
/* **A PRODUCT TILE IS ITS PICTURE** (2026-09-20). Measured on the live
 * Vegetables shelf: tile 157x215, plate 135x66, picture 52x52 -- eight per
 * cent of the card was the thing being sold, and a shelf of twenty green
 * plants at 52 px is twenty green smudges. The bird tiles have been a
 * full-width square since 2026-08-29 for that same reason; this is that rule
 * applied to everything else that owns a real render. The picture is baked at
 * 448 px either way, so the resolution was being thrown away here and nothing
 * new is downloaded. Doors keep their 42 px chip: a category is not a thing. */
#ui .shop-tile .plate.photo {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
}
#ui .shop-tile .plate.photo .shot {
  width: 100%;
  height: 100%;
  border-radius: var(--r-chip);
}
/* **The coop tile wears the bird card's plate** (Raf, 2026-08-29: *"show the
 * animal png not the coop here make it like the card"*). Square and full
 * width, because that is what `.bird .shot.plate` is and the reason is the
 * same one written there: a fixed short strip cover-crops a width-dependent
 * band out of the middle of a photograph, and the band it takes out of a bird
 * is the head. The box keeps no wash of its own -- the portrait's `--plate` is
 * the plumage, and a category tint behind it would be a second colour arguing
 * with the one that means something. */
#ui .shop-tile .plate.beast,
#ui .shop-tile .pic.beast {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  background: transparent;
  border-radius: var(--r-chip);
}
#ui .shop-tile .beast .shot {
  width: 100%;
  height: 100%;
  border-radius: var(--r-chip);
}
/* `cover`, where the base `.shot img` is `contain`: the card's rule, and the
 * portrait is baked square so it crops nothing. */
#ui .shop-tile .beast .shot img { object-fit: cover; }
/* TODO 473: a FADING card stacks its pictures and the new one fades in over the old; the plate
 * under a bird eases to the new plumage with it. */
#ui .shop-tile .shot.fade { position: relative; transition: background-color 0.9s ease; }
#ui .shop-tile .shot.fade > img { position: absolute; left: 0; top: 0; width: 100%; height: 100%; }
#ui .shop-tile .shot.fade > img.fade-in { opacity: 0; transition: opacity 0.9s ease; }
#ui .shop-tile .shot.fade > img.fade-in.on { opacity: 1; }
#ui .shop-tile svg { display: block; }
/* A door's name is one step larger than a product's -- it is the whole content
 * of the card, where a product's shares the tile with a price. */
#ui .shop-name {
  font-size: var(--body);
  font-weight: 800;
  text-align: center;
  line-height: 1.3;
}
#ui .shop-name.big { font-size: var(--title); }
/* One rule about the whole shelf, said once above it. Dim and small: it is a
 * caption on the page, not a thing for sale, and it must not compete with the
 * first row of tiles. */
#ui .shelf-note {
  margin: 0 0 10px;
  font-size: var(--caption);
  color: var(--ink-dim);
  text-align: center;
  line-height: 1.35;
}
#ui .shop-sub {
  margin: 0;
  font-size: var(--caption);
  color: var(--ink-dim);
  text-align: center;
  line-height: 1.3;
}
#ui .shop-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  direction: ltr;
  padding: 3px 10px;
  border-radius: var(--r-chip);
  background: color-mix(in srgb, currentColor 14%, #fff);
  font-size: var(--caption);
  font-weight: 800;
}
/* A price the purse cannot cover. **The card stays live** — this is a colour,
 * not a disabled state: the tap still hands the object to the island and the
 * server's `short_money` still opens the store at the gap it named. Coral is
 * the game's refusal ink everywhere else, and this is the ghost's grammar
 * exactly: coral says *not this one* about a control that still works.
 *
 * The RING is what makes it readable at 15 px. A coral wash on a coral figure
 * is one value, and the pill sits under a picture of a coop rather than beside
 * a neighbour to compare it with — an outline is the difference a glance
 * catches. */
#ui .shop-pill.short {
  color: var(--coral);
  background: color-mix(in srgb, var(--coral) 12%, #fff);
  box-shadow: inset 0 0 0 1.4px color-mix(in srgb, var(--coral) 55%, transparent);
}

/* What the run below IS and how long it is, over a rule in the same accent. */
#ui .avail { display: grid; gap: 5px; margin-bottom: 4px; }
#ui .avail b { font-size: var(--body); font-weight: 800; }
#ui .avail i { display: block; height: 2px; border-radius: 2px; }

/* The back chevron sits where the upgrade disc does on the coop -- opposite
 * the close, and it carries the CATEGORY'S accent, which is what ties a
 * shelf's page to the tile that opened it. */
/* BACK IS PHYSICALLY LEFT and close is physically right, in both languages —
 * the same rule the X follows: a chrome control a player has learned the
 * corner of does not move because the words changed. The CHEVRON itself
 * mirrors, because it is a picture of going back rather than a picture of
 * going left. */
#ui .head .up.back {
  background: transparent;
  box-shadow: none;
  right: auto;
  left: 8px;
}
#ui .head .up.back svg { width: 20px; height: 20px; }

/* The regrow line under a bush or a tree: the same sentence, quieter,
   because it is the second thing a buyer wants and not the first. */
.shop-sub.dim { opacity: 0.62; }
/* The footprint reads as a MEASUREMENT, not a sentence: tabular digits so
   2x2 and 3x3 line up down a shelf, and a little more air round the sign. */
.shop-sub.span { font-variant-numeric: tabular-nums; letter-spacing: 0.06em; }

/* ---- TODO 469 (2026-09-23): the shop in SECTIONS, three to a row ----------------------
   Raf: "improve the shop ui and orginizations as i really dont like it". A category is one
   scrolling page: sticky chips on top (one per heading), a heading per section in the
   category's accent, and three product cards to a row. The front page keeps its two big
   doors to a row. */
#ui .shop-grid.three {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}
#ui .shop-grid.three .shop-tile {
  padding: 7px 6px 9px;
  gap: 5px;
}
#ui .shop-grid.three .shop-name {
  font-size: var(--caption);
  line-height: 1.25;
}
#ui .shop-grid.three .shop-pill { padding: 2px 7px; gap: 4px; }
#ui .shop-grid.three .shop-sub { font-size: 10px; line-height: 1.25; }
/* The chip row sticks to the top of the scrolling body and scrolls sideways when it is
   wider than the card. It is the panel's own white so the cards slide under it cleanly. */
#ui .shop-chips {
  /* The body is a flex COLUMN that overflows, and a flex child shrinks by default: without
     this the sticky row was squeezed to a 20 px sliver with its words cut off. */
  flex: 0 0 auto;
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
  margin: 0 -14px;
  padding: 6px 14px 8px;
  background: #fff;
  box-shadow: 0 6px 6px -6px rgba(0, 0, 0, 0.12);
}
#ui .shop-chips::-webkit-scrollbar { height: 0; }
#ui .shop-chip {
  flex: 0 0 auto;
  border: 1.4px solid var(--card-edge);
  background: #fff;
  color: var(--ink);
  border-radius: 999px;
  /* 10, where it was 12: the four Animals chips fit a 360 px phone without a sideways scroll. */
  padding: 6px 10px;
  font: inherit;
  font-size: var(--caption);
  font-weight: 800;
  white-space: nowrap;
  cursor: pointer;
}
#ui .shop-chip.on { color: #fff; border-color: transparent; }
#ui .shop-sec {
  flex: 0 0 auto;
  margin: 8px 2px 0;
  font-size: var(--body);
  font-weight: 800;
  letter-spacing: 0.01em;
}
#ui .shop-grid.three { flex: 0 0 auto; }

/* ---- TODO 541 (2026-09-27): the shop made a game ---------------------------------------
   Raf: "improve the shop gui in general make it better orginized and better ... this is a
   game it should look fun". The front doors wear their category -- a tint, a coloured edge
   and a chunky pressed-button bottom -- and show what is behind them as a picture. A search
   row sits above every page, and a product the farm already owns says how many. */
#ui .shop-tile.front {
  --door: var(--ink-dim);
  background: linear-gradient(170deg, color-mix(in srgb, var(--door) 10%, #fff),
    color-mix(in srgb, var(--door) 26%, #fff));
  border: 1.6px solid color-mix(in srgb, var(--door) 38%, #fff);
  box-shadow: 0 4px 0 color-mix(in srgb, var(--door) 48%, #fff);
  padding: 8px 8px 11px;
  gap: 7px;
  margin-bottom: 4px;
  transition: transform 0.08s ease, box-shadow 0.08s ease;
}
#ui .shop-tile.front:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 color-mix(in srgb, var(--door) 48%, #fff);
}
#ui .shop-tile.front.locked:active {
  transform: none;
  box-shadow: 0 4px 0 color-mix(in srgb, var(--door) 48%, #fff);
}
#ui .shop-tile.front .shop-name.big {
  color: color-mix(in srgb, var(--door) 70%, #1f2630);
  font-size: var(--title);
}
/* The picture window: the same height for a hen, a tank, a fountain and a mark, so the
   six doors are one grid of equal cards. White under a mark, the portrait's own plate
   under an animal. */
#ui .shop-tile.front .pic,
#ui .shop-tile .plate.hero {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  border-radius: calc(var(--r-card) - 4px);
  background: rgba(255, 255, 255, 0.78);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--door) 20%, transparent);
  overflow: hidden;
}
#ui .shop-tile.front .pic svg { width: 52px; height: 52px; }
#ui .shop-tile .plate.hero .shot { width: 100%; height: 100%; border-radius: 0; }
/* Every picture FILLS its window: the portraits and renders are baked square on their own
   ground, so `contain` framed them in pale bands. 4:3 crops an eighth off top and bottom,
   and the crop leans to the bottom: a head is what names the animal, feet are not. */
#ui .shop-tile .plate.hero .shot img { object-fit: cover; object-position: 50% 22%; }

/* How many the farm already has: a small green count on the picture's corner. */
#ui .shop-tile .plate { position: relative; }
#ui .shop-own {
  position: absolute;
  top: 4px;
  inset-inline-start: 4px;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  direction: ltr;
  padding: 2px 6px 2px 3px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.92);
  color: var(--leaf, #3f8f3a);
  font-size: 11px;
  font-weight: 800;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}
#ui .shop-own svg { display: block; }

/* The search row: one field between the head and the scrolling body. */
#ui .shop-seek {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 14px 8px;
  padding: 0 12px;
  height: 40px;
  border-radius: 999px;
  background: var(--panel-hi);
  border: 1.4px solid var(--card-edge);
  color: var(--ink-dim);
}
#ui .shop-seek svg { flex: 0 0 auto; }
#ui .shop-seek .field {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  border: 0;
  outline: 0;
  background: transparent;
  box-shadow: none;
  padding: 0;
  margin: 0;
  font: inherit;
  font-size: var(--body);
  color: var(--ink);
}
#ui .shop-seek .field::-webkit-search-cancel-button { cursor: pointer; }

/* TODO 591 (D-shop-021, Raf 2026-09-30: *"the dade of shop and pics need major improvments!"*): a PRODUCT card
   wears its shelf the way the front door does -- the shelf's tint laid thin, its edge, and the pressed-button
   bottom that sinks under the thumb -- so a page of goods reads as the same game as the doors that opened it.
   The picture keeps a white box of its own (a photograph is not a mark) and the name takes a little of the
   shelf's ink. The chip row fades at its end, so a row that scrolls says so. */
#ui .shop-grid.three .shop-tile:not(.front) {
  background: linear-gradient(170deg, color-mix(in srgb, var(--door) 5%, #fff),
    color-mix(in srgb, var(--door) 15%, #fff));
  border: 1.4px solid color-mix(in srgb, var(--door) 30%, #fff);
  box-shadow: 0 3px 0 color-mix(in srgb, var(--door) 38%, #fff);
  transition: transform 0.08s ease, box-shadow 0.08s ease;
}
#ui .shop-grid.three .shop-tile:not(.front):active {
  transform: translateY(2px);
  box-shadow: 0 1px 0 color-mix(in srgb, var(--door) 38%, #fff);
}
#ui .shop-grid.three .shop-tile.locked:not(.front):active {
  transform: none;
  box-shadow: 0 3px 0 color-mix(in srgb, var(--door) 38%, #fff);
}
#ui .shop-grid.three .shop-tile:not(.front) .plate.photo {
  background: #fff;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--door) 16%, transparent);
  overflow: hidden;
}
#ui .shop-grid.three .shop-tile:not(.front) .shop-name {
  color: color-mix(in srgb, var(--door) 45%, #1f2630);
}
#ui .shop-chips {
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 34px), transparent);
  mask-image: linear-gradient(90deg, #000 calc(100% - 34px), transparent);
}
#ui[dir="rtl"] .shop-chips {
  -webkit-mask-image: linear-gradient(270deg, #000 calc(100% - 34px), transparent);
  mask-image: linear-gradient(270deg, #000 calc(100% - 34px), transparent);
}


/* ===========================================================================
 * THE BAG, THE LAND CARD AND THE PALETTE.
 * ======================================================================== */
/* TWO TO A ROW, picture then the count then the name -- Raf's own words for
   the storage, 2026-08-02. A one-per-row list of identical trees was a page of
   the same word. */
#ui .bag-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
}
/* The wrap is the grid cell now; the tile fills it, so the corner coin
   anchors to the tile it sells and not to a collapsed strip. */
/* THE WALLET RECEIPT, when a panel is covering the canvas. Same shape as
   the engine's: the wallet pill's capsule, a coin in a tinted well, the
   figure at the title size. It is translated up and faded by one class
   change -- a receipt is an event, and an event does not need a frame
   loop. */
#ui .wchip {
  position: fixed;
  z-index: 40;
  transform: translate(-50%, -50%);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 10px 0 5px;
  border-radius: 999px;
  background: var(--panel);
  border: 1.4px solid var(--line);
  box-shadow: 0 4px 0 rgba(0, 0, 0, .12);
  font: 900 var(--title)/1 inherit;
  pointer-events: none;
  opacity: 0;
  transition: transform .95s cubic-bezier(.2, .8, .3, 1), opacity .45s ease;
}
#ui .wchip.fly { opacity: 1; transform: translate(-50%, calc(-50% - 26px)); }
#ui .wchip-well {
  width: 18px;
  height: 18px;
  border-radius: 999px;
  display: grid;
  place-items: center;
}
/* The colour is the DIRECTION, never the purse -- the purse is said by the
   coin sitting in the well. */
#ui .wchip.up { color: var(--money); }
/* TODO 543 row 16: one coin of an earning in flight to its wallet pill (90-mount `flyCoins`). */
#ui .wcoin {
  position: fixed;
  width: 20px;
  height: 20px;
  margin: -10px 0 0 -10px;
  pointer-events: none;
  z-index: 60;
  filter: drop-shadow(0 2px 1px rgba(0, 0, 0, 0.25));
}
#ui .wcoin > svg { display: block; width: 20px; height: 20px; }
/* TODO 543 row 17 (D-chrome-061): the reward burst. On <body>, over the island and under nothing that
 * takes a tap. Its centre sits a little above the middle of the screen, where the island is. */
.fy-burst {
  position: fixed;
  left: 50%;
  top: 42%;
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 70;
  --ink: var(--gold-mid);
  --glow: var(--gold-glow);
}
.fy-burst.prize { --ink: #B57CF2; --glow: #F1E6FF; }
.fy-burst .fy-flash {
  position: absolute;
  left: -90px;
  top: -90px;
  width: 180px;
  height: 180px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0%, var(--glow) 30%, transparent 70%);
}
.fy-burst .fy-ring {
  position: absolute;
  left: -50px;
  top: -50px;
  width: 100px;
  height: 100px;
  border-radius: 50%;
  border: 5px solid #fff;
  box-shadow: 0 0 0 3px var(--ink), 0 0 22px 4px var(--ink), inset 0 0 14px var(--glow);
}
.fy-burst .fy-spark {
  position: absolute;
  left: -11px;
  top: -11px;
  width: 22px;
  height: 22px;
  filter: drop-shadow(0 0 2px var(--ink)) drop-shadow(0 0 5px var(--ink));
}
.fy-burst .fy-spark > i {
  display: block;
  width: 100%;
  height: 100%;
  background: #fff;
  clip-path: polygon(50% 0, 61% 39%, 100% 50%, 61% 61%, 50% 100%, 39% 61%, 0 50%, 39% 39%);
}
.fy-burst .fy-spark.big { left: -16px; top: -16px; width: 32px; height: 32px; }
.fy-burst .fy-spark.big > i { background: var(--glow); }
#ui .wchip.down { color: var(--coral); }
#ui .wchip.up .wchip-well { background: color-mix(in srgb, var(--money) 16%, transparent); }
#ui .wchip.down .wchip-well { background: color-mix(in srgb, var(--coral) 16%, transparent); }

#ui .bag-wrap { position: relative; display: grid; }
#ui .bag-wrap .bag-tile { width: 100%; }
#ui .bag-sell {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 28px;
  height: 28px;
  border-radius: 14px;
  border: 1.4px solid var(--edge);
  background: var(--paper);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
}
#ui .bag-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 7px 6px;
  /* A button carries the browser's own type; the tile takes the panel's. */
  font: inherit;
  text-align: center;
}
/* The picture keeps its height whether or not the render has landed, or the
   grid re-flows under the player's finger when the portraits arrive. */
#ui .bag-pic {
  height: 62px;
  display: grid;
  place-items: center;
}
/* TODO 497: a plant's tile fades like its shop card -- the shop's stacking rules, in the bag. */
#ui .bag-pic .shot.fade { position: relative; }
#ui .bag-pic .shot.fade > img { position: absolute; left: 0; top: 0; width: 100%; height: 100%; }
#ui .bag-pic .shot.fade > img.fade-in { opacity: 0; transition: opacity 0.9s ease; }
#ui .bag-pic .shot.fade > img.fade-in.on { opacity: 1; }
#ui .bag-tile b { font-size: var(--body); font-weight: 800; }
#ui .bag-name {
  font-size: var(--caption);
  color: var(--ink-dim);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ONE row. Nine colours wrapping 7-then-2 read as a list that ran out of room;
 * a palette is a thing you compare across, so it has to be seen at once. The
 * chip shrinks to fit rather than the row breaking. */
#ui .paints {
  display: flex;
  gap: 6px;
  justify-content: center;
  /* The chosen chip's ring reaches 3 px past it, so the row needs its own
   * clearance or the ring sits on the button below. */
  margin: 2px 0 5px;
}
/* A chip, and the chosen one wears a RING rather than a tick: a tick over a
 * colour hides the colour. */
#ui .swatch {
  flex: 0 1 30px;
  min-width: 0;
  height: 30px;
  border-radius: var(--r-chip);
  border: 1.6px solid var(--line);
  padding: 0;
  cursor: pointer;
}
#ui .swatch.on { box-shadow: 0 0 0 3px var(--sky); }
/* The object's own colour. Not a hole -- a real choice, drawn as the paper the
 * card is printed on with a rule through it. */
#ui .swatch.none {
  background: repeating-linear-gradient(45deg, #fff 0 6px, var(--line-soft) 6px 12px);
}


/* **WHICH BREED THE TWO BUY BUTTONS STOCK** (item 39, 2026-09-10). One house
 * holds every breed of its animal, so the pair below this row needs to be told
 * which one it is buying.
 *
 * Deliberately `.swatch`'s grammar and not a new one: a flex row of chips, the
 * chosen one wearing a RING. The only difference is that a breed is a WORD and
 * a tint is a colour, so this chip is sized by its text and wraps -- five
 * chicken breeds do not fit one line on a 320 pt phone, and a row that scrolls
 * sideways hides the breeds past the edge. */
#ui .breed-pick {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: center;
  /* The chosen chip's ring reaches 3 px past it, the same clearance
   * `.paints` takes for the same reason. */
  margin: 2px 0 7px;
}
#ui .bchip {
  border-radius: var(--r-chip);
  border: 1.6px solid var(--line);
  background: #fff;
  color: var(--ink);
  font: inherit;
  font-size: 12px;
  padding: 5px 10px;
  cursor: pointer;
  /* A breed name is a NAME: it may shrink to fit but must never be cut, or the
   * row becomes five chips nobody can tell apart. */
  white-space: nowrap;
}
#ui .bchip.on { box-shadow: 0 0 0 3px var(--sky); border-color: var(--sky); }

/* **THE FACE STRIP** (Raf, 2026-09-12, option 1 of BREED-PICKER-options2).
 * It SCROLLS where `.breed-pick` above wrapped: a face is 74 px against a
 * name's 90-130, a picture cut at the edge plainly continues where a cut word
 * does not, and eleven duck breeds wrapped would be three rows of pictures
 * pushing the two buy buttons off the card. */
#ui .breed-strip {
  display: flex;
  gap: 8px;
  margin: 2px 0 9px;
  padding: 3px 2px 4px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
#ui .breed-strip::-webkit-scrollbar { display: none; }
#ui .bface {
  flex: 0 0 74px;
  width: 74px;
  border: 0;
  background: none;
  font: inherit;
  padding: 0;
  cursor: pointer;
  text-align: center;
  color: var(--ink);
}
#ui .bface-sq {
  display: grid;
  place-items: center;
  width: 68px;
  height: 68px;
  margin: 0 auto;
  border-radius: 16px;
  background: var(--sky-wash);
  overflow: hidden;
  border: 3px solid transparent;
}
#ui .bface.on .bface-sq { border-color: var(--sky); }
#ui .bface b {
  display: block;
  margin-top: 4px;
  font-size: 11px;
  line-height: 1.12;
  font-weight: 700;
}
#ui .bface.on b { color: var(--sky); }
/* `plateShot` hands back the card's own bird box; it is scaled to the frame
 * here rather than given a second class, so the portrait and the colour wash
 * stay the ones every other card draws. */
#ui .bface-sq > .shot { width: 62px; height: 62px; min-height: 0; margin: 0; }
#ui .bface-sq > .shot img { width: 100%; height: 100%; object-fit: contain; }


/* ===========================================================================
 * SETTINGS. Eight carded rows and seven pages behind them.
 *
 * The row is Flutter's `SettingsRow`: a mark on a wash tile of its own accent,
 * the name over its current value, the control at the right. The accent is set
 * per row rather than baked in, because the Delete row is coral and defaulting
 * it would move every image in the panel to recolour one line.
 * ======================================================================== */
#ui .set-row {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 11px;
}
#ui .set-tile {
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  border-radius: var(--r-chip);
  display: grid;
  place-items: center;
}
#ui .set-tile svg { width: 20px; height: 20px; display: block; }
#ui .set-col {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
#ui .set-col b {
  font-size: var(--title);
  font-weight: 800;
  color: var(--ink);
}
/* The value line, and it TRUNCATES rather than wraps: a masked address is long
 * and a two-line row breaks the rhythm of the eight. */
#ui .set-col i,
#ui .set-col i.user {
  font-style: normal;
  font-size: var(--caption);
  color: var(--ink-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#ui .set-pencil { flex: 0 0 auto; color: var(--ink-dim); display: grid; }
#ui .set-pencil svg { width: 18px; height: 18px; display: block; }

/* The account row's chip. **Coral in BOTH states, and FILLED**: this is the
 * one row the panel is allowed to shout on, and coloured text on cream does
 * not shout. */
#ui .set-chip {
  flex: 0 0 auto;
  background: var(--coral);
  color: #fff;
  font-size: var(--caption);
  font-weight: 800;
  padding: 4px 10px;
  border-radius: 999px;
  white-space: nowrap;
}

/* THE SWITCH. Flutter draws 40x24 and lays out 48; this is the same picture at
 * the Godot panel's 46x26. It is a picture of a state, not a control with an
 * opinion -- the knob moves when the server's patch lands. */
/* Flutter's `GameSwitch`, whose four colours are written down in its own file
   precisely so nothing is inherited: sky track when on, WASH when off, white
   thumb, and a 1.4 px sky outline in BOTH states -- an off switch is an empty
   sky slot, not a grey one. Green is this game's money colour and has no
   business saying on. */
#ui .sw {
  flex: 0 0 auto;
  appearance: none;
  border: 1.4px solid var(--sky);
  padding: 0;
  width: 44px;
  height: 26px;
  border-radius: 999px;
  background: var(--sky-wash);
  position: relative;
  cursor: pointer;
  transition: background 0.14s ease;
}
#ui .sw i {
  position: absolute;
  top: 1.6px;
  inset-inline-start: 1.6px;
  width: 20px;
  height: 20px;
  border-radius: 999px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
  transition: inset-inline-start 0.14s ease;
}
#ui .sw.on { background: var(--sky); }
/* 44 wide, 1.6 inset, a 20 thumb: the far rest is 44 - 20 - 1.6 - 1.6. */
#ui .sw.on i { inset-inline-start: 20.8px; }
/* Under a master that is off. It FADES rather than disappearing -- a setting
 * that vanishes is a setting the player concludes was never there. */
#ui .sw.dim { opacity: 0.45; }

/* The build id: quiet, last, centred, and the same string the map corner
 * carries. */
#ui .stamp {
  margin: 2px 0 0;
  /* A build id is a MACHINE string. Left in the ambient direction, Arabic
     reorders `20260819-1536` around the hyphen and the one line that answers
     "did what I shipped reach my phone" answers it wrong. */
  direction: ltr;
  text-align: center;
  font-size: var(--caption);
  color: var(--ink-dim);
}

/* A FACE. The mark in white on its tint, or the farm's initial when the mark
 * is 1 -- a letter, with no underline and nothing else. */
#ui .face {
  flex: 0 0 auto;
  border-radius: 999px;
  display: grid;
  place-items: center;
  color: #fff;
  overflow: hidden;
  /* Flutter's face carries a 1.6 px #E0E0E0 ring. Inset, so it cannot change
     the box the caller sized -- a face is placed against a name and a border
     would push the two apart by three pixels. */
  box-shadow: inset 0 0 0 1.6px var(--card-edge);
}
/* Nobody has chosen this one: the initial on the wash, in the accent. A white
   letter on a pale blue is invisible, and a solid disc would say a face was
   picked when none was. */
#ui .face.unset { background: var(--sky-wash); }
#ui .face.unset b { color: var(--sky); }
#ui .face b { color: #fff; font-weight: 800; line-height: 1; }
#ui .face svg { display: block; }

/* The pickers. Four faces to a row, five colours to a row, two languages --
 * Raf's numbers, and the language pair is his written note. */
/* Four to a row, and the CELLS shrink rather than the grid growing: a fixed
 * mark size overflowed the card by 34 px at 320 (an iPhone SE), which is a
 * whole column hanging off the edge. */
#ui .marks {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}
#ui .marks > * { min-width: 0; }
#ui .tint-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 6px;
}
#ui .lang-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
}
#ui .mark-card,
#ui .tint-card,
#ui .lang-card { padding: 8px; }
#ui .mark-card { display: grid; place-items: center; }
#ui .tint-chip {
  display: block;
  height: 40px;
  border-radius: var(--r-chip);
}
#ui .lang-card { min-width: 0; }
#ui .lang-card b {
  display: block;
  font-size: var(--title);
  font-weight: 800;
  color: var(--ink);
  overflow-wrap: anywhere;
}
#ui .lang-card i {
  display: block;
  font-style: normal;
  font-size: var(--caption);
  color: var(--ink-dim);
}
/* The chosen card IS the mark -- the sky wash with a sky rim, the same way
 * every chosen thing in the game is shown. No tick as well: that says it
 * twice, in the slot where the language's own name should be. */
#ui .mark-card.chosen,
#ui .tint-card.chosen,
#ui .lang-card.chosen {
  background: var(--sky-wash);
  border-color: var(--sky);
}

/* A field. One shape for the name, the address and the code. */
#ui .field {
  appearance: none;
  width: 100%;
  box-sizing: border-box;
  /* Centred — see the sheet's input above. `.mid` keeps its tracking and its
   * weight; what it no longer owns is the centring, which is everybody's. */
  text-align: center;
  background: #fff;
  border: 1.4px solid var(--line);
  border-radius: var(--r-card);
  color: var(--ink);
  font: inherit;
  font-size: var(--title);
  padding: 11px 12px;
}
#ui .field:focus { outline: none; border-color: var(--sky); }
#ui .field[disabled] { background: var(--panel-hi); color: var(--ink-dim); }
#ui .field.mid { letter-spacing: 3px; font-weight: 800; }
/* **The tracking belongs to the CODE, not to the invitation to type one.**
 * A placeholder at 3 px letter-spacing reads as a broken word -- "E n t e r
 * a  c o d e" -- where the same tracking on six capitals is what makes a
 * code legible. */
#ui .field.mid::placeholder {
  letter-spacing: normal;
  font-weight: 600;
}

/* The refusal, in the panel's one coral. */
#ui .note.bad { color: var(--coral); }

/* The linked page. `check_circle` at 46 in the money green -- Flutter's own
 * size and colour. Green and not sky: this is a thing that WORKED, and green
 * is what the game says that with everywhere else. */
#ui .linked-mark {
  display: grid;
  place-items: center;
  color: var(--money);
  padding: 6px 0 2px;
}
#ui .linked-mark svg { width: 46px; height: 46px; display: block; }
#ui .done {
  margin: 0;
  text-align: center;
  font-size: var(--display);
  font-weight: 900;
  color: var(--ink);
}


/* ===========================================================================
 * THE MARKET. Ten views on one stack.
 *
 * Every shape here is Flutter's by way of the Godot panel that ported it. The
 * one rule worth repeating at the top: a card in this panel may have to draw
 * an animal this client has never owned, so a listing leads with a MARK on a
 * wash tile rather than with a rendered bird.
 * ======================================================================== */

/* The four doors, two to a row. They were five full-width soft buttons with
 * the mark tucked into the left edge, so the market's front page read as a
 * settings list while the shop's read as a shelf. */
#ui .mk-doors {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px;
}
#ui .mk-door {
  appearance: none;
  font-family: inherit;
  background: #fff;
  border: 1.4px solid var(--card-edge);
  border-radius: var(--r-card);
  padding: 14px 10px 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}
#ui .mk-door:active { transform: translateY(1px); }
#ui .mk-door b { font-size: var(--body); font-weight: 800; color: var(--ink); text-align: center; }
#ui .mk-door .pic {
  position: relative;
  width: 54px;
  height: 54px;
  border-radius: var(--r-chip);
  background: var(--sky-wash);
  color: var(--sky);
  display: grid;
  place-items: center;
}
#ui .mk-door .pic svg { width: 34px; height: 34px; display: block; }
/* A storefront with a badge saying WHOSE: mine wears a home, everyone else's a
 * crowd. Sky on white, never white on sky — the inverse reads as absence. */
#ui .mk-door .badge {
  position: absolute;
  right: -3px;
  bottom: -3px;
  width: 20px;
  height: 20px;
  border-radius: 999px;
  background: var(--sky);
  color: #fff;
  border: 2px solid #fff;
  display: grid;
  place-items: center;
}
#ui .mk-door .badge svg { width: 12px; height: 12px; display: block; }

/* The shelf: one card per slot, packed left, soonest to expire first. */
/* THREE a row, which is Flutter's own count. At two, ten slots are five
 * rows of white and the panel is mostly empty shelf. */
/* **TWO to a row, the flock's own grid** (Raf, 2026-08-26). Three to a row put
 * a bird in 91 px, which is why every string on that card was drawn under the
 * scale and the picture was a 40 px thumbnail. The shelf now uses the coop
 * card whole, so the two panels teach the same reading. */
#ui .mk-shelf {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px;
  /* The shelf holds the coop's own card, so it stops stretching them for the
   * same reason `.flock` did: a card taller than its neighbour should not
   * decide where the neighbour's button sits. */
  align-items: start;
}
/* An empty slot and the buy-a-slot card stand as tall as the bird cards beside
 * them — `min-height` and not a fixed height, because a row of two birds is as
 * tall as the taller one and the grid stretches everything to match. */
#ui .mk-shelf .mk-add {
  min-height: 190px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
/* The bids and the clock, inside the flock card. The bid line is a figure and
 * holds its line; the sentence under it may wrap, because "nobody has bid" is
 * half again as long in Urdu and Hindi. */
#ui .mk-bids {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  margin-bottom: 4px;
  min-width: 0;
}
#ui .mk-bids i { font-style: normal; font-size: var(--caption); }
#ui .mk-bids i.bid { white-space: nowrap; display: inline-flex; align-items: center; gap: 4px; }
#ui .mk-bids i.dim { color: var(--ink-dim); text-align: center; overflow-wrap: anywhere; }
#ui .mk-slot {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 7px 5px;
  text-align: center;
}
/* **The name WRAPS; the figures do not.** A card this narrow cannot hold
 * "Any colour Chicken" on one line in any language, and a one-line string
 * does not overflow -- it truncates, which is the defect nothing catches by
 * eye. The grid stretches every card in a row to the tallest, so a second
 * line costs the layout nothing. The bid and the clock are short in all eight
 * languages and stay on their line. */
#ui .mk-slot > b {
  max-width: 100%;
  line-height: 1.2;
  overflow-wrap: anywhere;
}

/* The bid is a figure and holds its line. */
#ui .mk-slot > i.bid { white-space: nowrap; }
/* **"No offers yet" is a SENTENCE and is drawn small.** Flutter puts it at
 * 9.5 -- under the scale on purpose, in the one place a card is 91 px wide --
 * and it still needs to wrap: Urdu and Hindi are half again as wide as the
 * English. */
#ui .mk-slot > i.dim {
  font-size: 9.5px;
  line-height: 1.25;
  max-width: 100%;
  overflow-wrap: anywhere;
}
#ui .mk-slot .chip { font-size: 9.5px; padding: 1px 6px; }
#ui .shot.slot { width: 40px; height: 40px; border-radius: var(--r-chip); }
/* Nothing to photograph: the errand's glyph on the sky wash, which is the
 * tile every markless portrait in the game falls back to. */
#ui .shot.slot.bare { background: var(--sky-wash); color: var(--sky); }
#ui .mk-slot b { font-size: var(--caption); font-weight: 800; color: var(--ink); }
#ui .mk-slot i { font-style: normal; font-size: var(--caption); }
#ui .mk-slot i.dim { color: var(--ink-dim); }
#ui .mk-slot i.bid {
  color: var(--money-dark);
  font-weight: 800;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  /* A figure is a figure in every language. */
  direction: ltr;
}
#ui .mk-slot i.bid.gold { color: var(--gold-dark); }

/* **Quieter than a full slot.** Flutter's reason, and it is the whole of why
 * an empty shelf stops shouting: ten `+` cards wearing a listing's own edge
 * read as ten things demanding attention. */
#ui .mk-add {
  appearance: none;
  font-family: inherit;
  align-items: center;
  justify-content: center;
  color: var(--sky);
  border-color: var(--sky-wash);
  min-height: 84px;
}
#ui .mk-add b { color: var(--ink-dim); font-size: var(--caption); }

/* BUY ONE MORE SLOT — the `+` card wearing a price.
 *
 * **Gold, not sky**, which is the one place it departs from the `+` it is
 * built on: every other gold price in this game is drawn in gold, and a slot
 * that cost gold while looking like the free `+` beside it would be a card
 * that takes money without ever having said so.
 *
 * The figure is forced `ltr` like every coin-and-number pair in the app, so
 * digits read left to right in Arabic too; the card is a COLUMN so the mark
 * sits over the price rather than beside it — at 84px there is no room for a
 * horizontal pair that also has to centre. */
#ui .mk-buyslot {
  flex-direction: column;
  gap: 6px;
  color: var(--gold-mid);
  /* `--gold-glow` is the palette's pale gold; there is no
   * `--gold-wash`, and inventing one in a panel is what `tokens.css`
   * exists to stop -- the palette cannot fork. */
  border-color: var(--gold-glow);
}
#ui .mk-slotprice {
  direction: ltr;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-weight: 800;
  font-size: var(--body);
  color: var(--gold-dark);
}

/* A listing card — my own, somebody's shop, or one of my offers. */
/* The listing detail is the game's ONE bird card, held to the width the card
 * was designed at. Full-bleed it would draw a 340 px square photograph of one
 * hen, which is not the same card any more, it is a poster of it. */
#ui .mk-detail { width: 100%; max-width: 300px; margin-inline: auto; }
#ui .mk-card { display: flex; flex-direction: column; gap: 7px; }










/* The sex, as a mark. Square-ish rather than the word-chip's capsule, because
 * it holds one glyph and a capsule round a single symbol reads as a button. */
#ui .mk-chip.mk-sex {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 2px 7px;
}
#ui .mk-chip.mk-sex svg { display: block; }
/* Flutter's own two inks for the pair, and the same two the coop plate pins. */
#ui .mk-chip.mk-sex.hen { color: var(--hen); }
#ui .mk-chip.mk-sex.cock { color: var(--sky); }
/* The colour chip IS the colour: the bird's own plate wash behind it, a dot of
 * the plumage at full strength, and the name. The dot is what separates Ivory
 * from Opal -- two washes twelve per cent away from white are the same wash. */
#ui .mk-chip.mk-hue {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: var(--wash, var(--panel-hi));
  border-color: var(--hue, var(--line));
}

#ui .mk-chip {
  background: var(--panel-hi);
  border: 1.2px solid var(--line);
  border-radius: 999px;
  padding: 2px 9px;
  font-size: var(--caption);
  font-weight: 800;
  color: var(--ink-dim);
  white-space: nowrap;
}
#ui .mk-chip.sell { background: var(--apple); border-color: var(--apple); color: #fff; }
#ui .mk-chip.buy { background: var(--sky); border-color: var(--sky); color: #fff; }

#ui .mk-money { display: flex; align-items: center; gap: 8px; }
/* The label yields to the figure and trims rather than being squeezed: the
 * number is the fact, and a clipped Arabic label reads as a broken glyph. */
#ui .mk-money .lab {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--caption);
  color: var(--ink-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#ui .mk-money .fig {
  margin-inline-start: auto;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  direction: ltr;
  color: var(--money-dark);
  font-size: var(--number);
  font-weight: 900;
}
/* What I am committed to, in SKY rather than the money green: it is not what
 * the thing costs, and the two must not read as one ladder. */
#ui .mk-money.mine .fig { color: var(--sky); }
/* **The ink follows the coin.** A gold figure drawn in the money green is the
 * same lie the wrong coin would be, one step quieter -- and the two are on the
 * same card, one row apart, which is the whole reason a purse has a colour.
 * `.mine` (sky, "what I am committed to") stays ahead of it: that distinction
 * is about WHOSE number it is and outranks which purse it is in. */
#ui .mk-money.gold .fig { color: var(--gold-dark); }
#ui .mk-money.mine.gold .fig { color: var(--sky); }



/* The portrait and the words, then the button across the bottom. `min-width: 0`
 * on the words or a long colour name pushes the picture out of the card. */
#ui .mk-bird-top { display: flex; align-items: center; gap: 10px; }







/* **The purse, as a pair of exclusive chips.** Raf: *"one of them wither gold
 * or money"* -- so it is the duration row's own shape, which is already the
 * game's control for "pick exactly one of these". Each carries its COIN, and
 * the coin is what actually distinguishes them: the words Money and Gold are
 * two similar-length nouns in eight languages and the marks are not. */
#ui .mk-cur { display: flex; gap: 6px; margin-bottom: 10px; }
#ui .mk-cur .btn {
  flex: 1 1 auto;
  margin-bottom: 0;
  gap: 6px;
  /* The coin leads in every language: it is an object, not a word. */
  direction: ltr;
}
/* The unchosen chip is the soft white one and its coin keeps its own colour --
 * greying the mark would make the control read as unavailable rather than
 * unselected. */
#ui .mk-cur .btn svg { flex: 0 0 auto; }

/* =========================================================================
 * THE MARKET READS DOWN THE MIDDLE.
 *
 * Raf, 2026-08-22, twice within five minutes: *"make sure to center eveything
 * correctly in ui"*, and then *"also center here"* over the offer sheet. The
 * coop was centred an hour earlier and the market was not, so the game had two
 * grammars -- and the market is the panel a player spends longest reading.
 *
 * Centred as a GROUP, never as text. The RIBBON stays pinned to the leading
 * edge and the clock to the trailing one, because those two are a header and a
 * header that drifts inward stops being one. What centres is the body of the
 * card: the portrait and its words as one block, the chips under them, and the
 * money row.
 * ========================================================================= */
#ui .mk-face { justify-content: center; }
/* `0 1 auto`, never `1 1 auto`. A grower takes every spare pixel, and a
 * column that fills the row is a column that cannot be centred -- the text
 * just sits at the start of a full-width box. It still SHRINKS, which is what
 * the name's ellipsis needs. Same correction the coop line needed. */
#ui .mk-face .col { flex: 0 1 auto; text-align: center; }


/* The money row is a LABEL and a FIGURE, and centring it means centring the
 * pair -- so the label stops eating the free space and the auto margin that
 * pushed the figure to the edge goes with it. (That margin was already a
 * no-op here for exactly the reason it was load-bearing on a button: the
 * label's `flex: 1 1 auto` had eaten the gap first.) */
#ui .mk-money { justify-content: center; }
#ui .mk-money .lab { flex: 0 1 auto; }
#ui .mk-money .fig { margin-inline-start: 0; }

/* **A figure riding INSIDE a button**, which is what an Accept carrying its
 * own amount is. It is pushed to the trailing edge so a row of them line up
 * however long the word Accept is in that language, and it is forced LTR
 * because a number is a number. */
/* **`space-between` on the BUTTON, never an auto margin on the figure.** A
 * margin resolves against the element's OWN `direction`, and the figure is
 * forced LTR so a number reads left to right -- so `margin-inline-start: auto`
 * becomes `margin-left: auto` inside an RTL row and pushes the figure back
 * TOWARD the word instead of away from it. Measured in the Arabic bench: the
 * English card put 640 at the far edge and the Arabic one sat it against
 * "قبول" with the whole left half of the button empty. `justify-content`
 * follows the flex row's direction, which is the button's, which is the
 * panel's -- so one line is correct in both. */
/* **The word and its figure travel TOGETHER, centred** (Raf, 2026-08-22:
 * *"make the botton cenetered for accept (accept x money/gold icon)"*). They
 * were pushed to opposite ends so a column of them lined their figures up --
 * true, and it made one button read as two separate things with a gap between
 * them, which on a stack of two Accepts is exactly the wrong reading: the
 * amount IS what the word is agreeing to.
 *
 * `justify-content` and not a margin, for the reason the block below still
 * records: flex alignment follows the CONTAINER's direction, so this is right
 * in Arabic without an override, and an auto margin on the LTR-forced figure
 * is not. */
#ui .btn.has-fig { justify-content: center; gap: 10px; }
#ui .btn .fig {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  direction: ltr;
  font-weight: 900;
}

/* A shop's header: their face, their name and their code, then the pair. */
#ui .mk-head { display: flex; align-items: center; gap: 10px; }
#ui .mk-head .col { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
#ui .mk-head .col b {
  font-size: var(--title);
  font-weight: 800;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#ui .mk-head .col i { font-style: normal; font-size: var(--caption); color: var(--ink-dim); }
/* **Physical, in both languages.** Flutter puts `textDirection.ltr` on this
 * row so the pair keeps one order everywhere -- the same reasoning as the
 * farm-name pill, where "face then name" is a physical fact rather than a
 * reading order. Left to inherit, Arabic swapped the heart and the follow
 * chip round. */
#ui .mk-social { flex: 0 0 auto; display: flex; gap: 6px; direction: ltr; }
/* The SOCIAL panel's own heart, on a person row. Left exactly as it was: it
 * is a different control from the shop header's pair below — Flutter's
 * `_PersonRow` spells follow as a WORD and only the heart is a mark — and it
 * carries its count already. */
#ui .mk-follow,
#ui .mk-like {
  appearance: none;
  width: 32px;
  height: 32px;
  border-radius: 999px;
  border: 1.4px solid var(--line);
  background: #fff;
  color: var(--ink-dim);
  display: grid;
  place-items: center;
  padding: 0;
  cursor: pointer;
}
#ui .mk-follow svg,
#ui .mk-like svg { width: 15px; height: 15px; display: block; }

/* **A like is permanent inside its week.** The heart goes inert once given —
 * FULL coral and not pale, because pale reads as broken rather than as done. */
#ui .mk-like.on { background: var(--coral); border-color: var(--coral); color: #fff; }

/* THE SHOP HEADER'S PAIR — Flutter's `_Chip`: 34 high, radius 12, the map
 * button's white face and grey edge, a 17 px glyph, and the number on the
 * heart. **The state lives in the INK, never in a fill**: a filled disc is
 * the map button's grammar and puts a saturated blob either side of a
 * player's name.
 *
 * Its own class, because `mk-chip` is the listing card's Sell/Buy ribbon and
 * its sex and colour pills, and `mk-like` is the row heart above. */
#ui .mk-pair {
  appearance: none;
  height: 34px;
  min-width: 34px;
  border-radius: var(--r-chip);
  border: 1.6px solid var(--card-edge);
  background: #fff;
  color: var(--ink-dim);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 0 8px;
  direction: ltr;
  font-size: var(--body);
  font-weight: 800;
  font-family: inherit;
  cursor: pointer;
}
#ui .mk-pair svg { width: 17px; height: 17px; display: block; }
#ui .mk-pair b { font-weight: 800; }
/* Off, the follow chip's glyph is the colour of the ACTION it offers. */
#ui .mk-pair-follow { color: var(--sky); }
/* On, it offers to UNfollow, which is not a sky-coloured thing to do. */
#ui .mk-pair-follow.on { color: var(--ink-dim); }
/* Given: full coral, and the press is refused in JS where it costs no colour. */
#ui .mk-pair-like.on { color: var(--coral); }
#ui .mk-pair:disabled { cursor: default; }

/* The card-shaped wait. Not the word CONNECTING: nothing is wrong, a read is
 * simply in flight, and CONNECTING is what the corner says when the socket has
 * actually died. */
#ui .skeleton { display: flex; flex-direction: column; gap: 8px; }
#ui .skeleton .block {
  height: 78px;
  border-radius: var(--r-card);
  background: linear-gradient(90deg, var(--panel-hi), #fff, var(--panel-hi));
  background-size: 200% 100%;
  animation: mk-breathe 1.6s ease-in-out infinite;
}
@keyframes mk-breathe {
  0% { background-position: 0% 0; }
  100% { background-position: -200% 0; }
}
/* THE NATURE BOOK (TODO 540): two pages across on a phone, the animal on a sea-and-sky plate, its name,
 * and -- once seen -- one true sentence about it. An unseen page is the animal's dark shape. */
#ui .book-top { padding: 2px 4px 8px; }
#ui .book-count { display: block; font-size: 15px; color: var(--ink, #3B352C); }
#ui .book-hint { margin: 4px 0 0; font-size: 13px; line-height: 1.35; color: var(--ink-soft, #5F5A52); }
#ui .book-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px;
  align-items: start;
}
#ui .book-page {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 6px 7px 8px;
  border-radius: 12px;
  background: linear-gradient(180deg, #F4F8F9, #E9F1F2);
  border: 1px solid rgba(40, 90, 110, 0.12);
  min-width: 0;
}
#ui .book-plate {
  border-radius: 9px;
  background: radial-gradient(ellipse at 50% 38%, #DCEEF3, #AFD3DD);
  overflow: hidden;
}
#ui .book-pic { display: block; width: 100%; aspect-ratio: 1 / 1; object-fit: contain; }
#ui .book-page:not(.seen) .book-pic { filter: brightness(0) opacity(0.28); }
#ui .book-name { font-size: 14px; line-height: 1.2; overflow-wrap: anywhere; }
#ui .book-page:not(.seen) .book-name { color: var(--ink-soft, #5F5A52); }
#ui .book-fact { margin: 0; font-size: 12.5px; line-height: 1.35; color: var(--ink, #3B352C); }
#ui .book-unseen { margin: 0; font-size: 12px; color: var(--ink-soft, #5F5A52); font-style: italic; }
#ui .book-meta { display: flex; flex-direction: column; gap: 1px; font-size: 11.5px; color: var(--ink-soft, #5F5A52); }
/* TODO 533 round 2 (D-html_ui-023): the visitor's peek -- ONE animal's card, the owner's card with nothing to
   press, centred at the width a coop card would have on a two-column shelf and no wider. */
#ui .peek-one { max-width: 240px; margin: 4px auto 10px; }


/* ===========================================================================
 * SOCIAL. The players list, the weekly board and the invite card.
 * ======================================================================== */
#ui .so-row { display: flex; align-items: center; gap: 10px; }
#ui .so-col { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
#ui .so-col b,
#ui .so-col b.user {
  font-size: var(--title);
  font-weight: 800;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#ui .so-col i { font-style: normal; font-size: var(--caption); color: var(--ink-dim); }
/* The chevron on its own sky tile — the same tile a settings row gives its
 * glyph. On the BOARD it is bare and dim instead: a board row already carries
 * a medal, a face and a heart, and a fourth coloured pod would make the tap
 * the quietest thing on it. */
#ui .so-chev {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border-radius: var(--r-chip);
  background: var(--sky-wash);
  color: var(--sky);
  display: grid;
  place-items: center;
}
#ui .so-row.mine .so-chev,
#ui .so-rank ~ .so-chev { background: transparent; color: var(--ink-dim); }
#ui .so-chev svg { width: 18px; height: 18px; display: block; }

#ui .so-rank {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: 999px;
  background: var(--sky-wash);
  color: var(--sky);
  display: grid;
  place-items: center;
  font-size: var(--body);
  font-weight: 900;
  direction: ltr;
}
#ui .so-likes {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  direction: ltr;
  color: var(--coral);
}
#ui .so-likes b { font-size: var(--number); font-weight: 900; color: var(--ink); }
#ui .so-likes svg { width: 15px; height: 15px; display: block; }
/* **Outlined, not tinted.** A fill on a board whose rows already carry medals
 * reads as a fourth rank colour. */
#ui .so-row.mine { border-color: var(--sky); box-shadow: inset 0 0 0 1.4px var(--sky); }
/* How long is left to play the week, under the title and above the ranks. A
   clock beside its glyph is a READING, so it is one line and it never wraps —
   and it is forced LTR because a countdown counts down in every language. */
#ui .ct-ends {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  direction: ltr;
  margin-bottom: 10px;
  white-space: nowrap;
}
#ui .ct-ends svg { width: 14px; height: 14px; display: block; flex: 0 0 auto; }

/* **At 320 the pair does not fit, and nothing may be cut.** A glyph, the word
 * and its count come to about 141 px against a 127 px column on an iPhone SE.
 * Tightening the padding and the gaps buys most of it and wrapping buys the
 * rest: the count drops under the word rather than being clipped off the edge
 * of a button. Truncating the word instead would hide which shelf is which,
 * which is the whole content of the control. */
#ui .so-shelf button { position: relative; flex-wrap: wrap; row-gap: 2px; }
@media (max-width: 360px) {
  #ui .shelf button { padding: 8px 4px; gap: 5px; }
}
#ui .so-count {
  background: var(--panel-hi);
  border-radius: 999px;
  padding: 1px 7px;
  font-size: var(--caption);
  color: var(--ink-dim);
  direction: ltr;
}
/* **SKY, not the store's gold.** `.shelf`'s selected rule is gold because the
 * store sells gold; every other shelf in the game takes Flutter's default
 * accent, and gold stays reserved for numbers. Filled and reversed out, which
 * is what the white-chip rule below has always assumed. */
#ui .so-shelf button[aria-selected="true"] {
  background: var(--sky);
  border-color: var(--sky);
  color: #fff;
}
#ui .so-shelf button[aria-selected="false"] { color: var(--ink-dim); }
#ui .so-shelf button[aria-selected="false"] svg { color: var(--sky); }
#ui .so-shelf button[aria-selected="true"] .so-count { background: #fff; color: var(--sky); }
#ui .so-shelf button[aria-selected="false"] .so-count { background: var(--sky-wash); }

/* The code you hand out and the field you paste one into, divided by a rule.
 * The code is CENTRED, with the copy mark riding the label above it. */
#ui .so-code { display: flex; flex-direction: column; gap: 8px; }
/* The two ways to send, side by side and equal — Copy alone takes the whole
   width, which is what says "one way to send" rather than "one broken one". */
#ui .ref-send { display: flex; gap: 8px; }
#ui .ref-send .btn { flex: 1 1 0; min-width: 0; margin-bottom: 0; }
#ui .ref-send .btn span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* D-social-018 (TODO 549): the farm picture, framed by the panel, shown before it is sent. */
#ui .ref-pic { margin-top: 8px; display: flex; flex-direction: column; gap: 8px; }
#ui .ref-pic .btn { margin-bottom: 0; }
#ui .ref-shot { display: block; width: 100%; max-height: 300px; object-fit: contain; border-radius: 12px;
  background: var(--panel); cursor: pointer; }
/* A QR needs a light plate and quiet space or it stops being scannable and
   becomes decoration. 116, Flutter's own size: readable across a table,
   small enough not to be the panel. */
#ui .ref-qr {
  display: flex;
  justify-content: center;
  padding: 9px;
  margin: 2px auto 0;
  background: #fff;
  border-radius: 14px;
  border: 1.4px solid var(--line);
}
#ui .ref-qr svg { display: block; border-radius: 4px; }
/* WHAT THE LINK HAS PAID. One centred line, no card: a bordered slab whose
   whole content on the day a player first opens this panel is the number 0 is
   a box built to celebrate, celebrating nothing. `wrap` because Urdu and
   Turkish put a long word in front of two figures — it is a sentence with
   numbers in it, and the numbers may drop to a second line rather than be cut
   off the edge. */
#ui .ref-earned {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 5px 10px;
  margin: 2px 16px 12px;
}
#ui .ref-earned .lab {
  font-size: var(--caption);
  font-weight: 800;
  color: var(--ink-dim);
}
#ui .ref-earned .fig { display: inline-flex; align-items: center; gap: 5px; }
#ui .ref-earned .fig b {
  font-size: var(--number);
  font-weight: 900;
  color: var(--gold-dark);
}
/* The VIP half is a SENTENCE with a number in it ("7 VIP days"), so it reads
   at the body size — the gold figure is the headline and a second number at
   the numbers size would compete with it. */
#ui .ref-earned .fig.vip b { font-size: var(--body); font-weight: 800; }
#ui .ref-earned .fig svg { display: block; }
/* One friend's line, at the end of their row. */
#ui .ref-paid {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  direction: ltr;
}
#ui .ref-paid b { font-size: var(--body); font-weight: 900; color: var(--gold-dark); }
#ui .ref-paid svg { display: block; }
/* Words, not a zero. A coin-and-0 on every row reads as a column of failures,
   where the same zero in the headline is an honest total. */
#ui .ref-nil {
  flex: 0 0 auto;
  font-style: normal;
  font-size: var(--caption);
  color: var(--ink-dim);
  white-space: nowrap;
}
/* The pitch is body copy, centred, quiet. */
#ui p.pitch { text-align: center; line-height: 1.5; margin-bottom: 10px; }
#ui .so-code hr { border: 0; border-top: 1.2px solid var(--line-soft); margin: 2px 0; width: 100%; }
#ui .so-mine { text-align: center; cursor: pointer; }
#ui .so-lab {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: var(--caption);
  color: var(--ink-dim);
}
#ui .so-lab svg { width: 15px; height: 15px; display: block; }
/* Copied — the word AND the tick, in the money green. Flutter turns both, and
 * a 15 px clipboard swapping for a 15 px tick is not an answer on its own. */
#ui .so-lab.done { color: var(--money); }
#ui .so-mine b {
  display: block;
  font-size: var(--display);
  font-weight: 900;
  letter-spacing: 2px;
  color: var(--ink);
  direction: ltr;
}
/* A link is long: it shrinks rather than pushing the card wider. */
#ui .so-mine b.so-link {
  font-size: var(--body);
  letter-spacing: 0;
  overflow-wrap: anywhere;
}
/* **Full width, and the button UNDER it.** A field beside a button is a
 * field whose centre line is half the button's width left of the code printed
 * above it — on the one panel whose entire subject is a code, the code you
 * hand out and the code you paste in did not line up. Flutter measured this
 * and calls it option 6; v2 shipped the row it rules out. */
#ui .so-ask { display: flex; flex-direction: column; align-items: stretch; gap: 8px; }
#ui .so-ask .field { width: 100%; letter-spacing: 2.4px; font-weight: 800; }
#ui .so-ask .field::placeholder { letter-spacing: 0; font-weight: 600; }
#ui .so-ask .btn { width: 100%; margin-bottom: 0; }
/* Off until a code is typed. Pale AND unpressable, so it reads as waiting for
 * the field above rather than as broken — and it never sits under a thumb
 * that expects it to do something. */
#ui .so-ask .btn:disabled {
  opacity: .45;
  cursor: default;
  transform: none;
}
/* The refusal, under the button that earned it. It WRAPS: it is a sentence,
 * and eight languages disagree about how long this one is. */
#ui .so-ask .note.bad {
  margin: -2px 0 0;
  text-align: center;
  font-size: var(--caption);
  line-height: 1.35;
}

/* The person sheet, over the list it was opened from. */
#ui .so-sheet { display: flex; flex-direction: column; gap: 10px; }
#ui .so-head { display: flex; align-items: center; gap: 12px; }
/* THE SELL BUTTON ARMS ITSELF.
 *
 * Selling is the one irreversible thing a tap can do, and a full confirmation
 * panel was built for it and thrown away the same day (Raf, 2026-08-27, after
 * being shown four presentations of one: *"lets the button change into confeim
 * sell with an x too ... no confeimstion message or settings ... if in 5 sec
 * not clicked it goes back to orgiginal button"*). What is left is the button
 * itself: press it and it turns coral and reads *Confirm*, with an X beside it;
 * press it again and the thing is sold; leave it and it is a Sell button again
 * in five seconds.
 *
 * The button KEEPS ITS NODE — same classes, same slot, same width — so nothing
 * above it has to know. Only its face, its colour and the new sibling change.
 * See `armSell` in panels.js. */

/* The X, a `.btn.soft` at the sell button's own height. It never shrinks: the
   cancel is the half a thumb that has changed its mind goes for. */
#ui .arm-x {
  flex: 0 0 auto;
  align-self: stretch;
  padding: 0;
  margin-bottom: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink);
}
#ui .arm-x svg { display: block; }

/* THE BIRD CARD's floor. `.under` was already a flex row holding one
   full-width button; armed, it holds the confirm and the X, and the confirm
   keeps whatever is left. The card is 152 px, so the X is square at the row's
   own height and the word takes the rest. */
#ui .bird .under { gap: 5px; align-items: stretch; }
/* **`.btn.arm-x`, not `.arm-x`.** `#ui .bird .under .btn` sets `flex: 1 1 auto`
   and outranks a one-class selector, so the X grew to half the card and the
   word had nowhere to go. */
#ui .bird .under .btn.arm-x {
  flex: 0 0 auto;
  width: 32px;
  /* Zero, so the row stretches it to the CONFIRM's height. A `.btn.soft`
     carries a 1.4 px border and the sell button does not, so two buttons given
     the same min-height come out three pixels apart. */
  min-height: 0;
  /* A circle beside a pill, which is the pair the game already draws
     everywhere a round control sits next to a capsule. */
  border-radius: 999px;
}
#ui .bird .under .btn.arm-x svg { width: 14px; height: 14px; }
#ui .bird .under.arming .btn:first-child { padding: 0 6px; }

/* THE BAG's converter, whose Sell is a `.btn.gold.low` at the foot of a
   COLUMN — so the X lands under it, full width, rather than beside it. A
   40 px square hanging off the end of a column reads as a stray mark. */
#ui .convert .arm-x { width: 100%; min-height: 36px; border-radius: 15px; }

/* THE PER-EGG BASKET is 53 px across and holds a coin and a price: there is no
   room for a word, so its armed face is a tick and the X drops under it. Both
   are absolutely placed on the tile, like the two controls already there. */
#ui .egg-sell.sky { box-shadow: 0 3px 8px #1A6B9038; }
#ui .egg-sell.sky svg { width: 15px; height: 15px; }
#ui .egg-sell.danger svg { width: 15px; height: 15px; }
/* **The X takes the hint's own corner**, which is free precisely while the
   button is up — `.egg-tag` is hidden the moment the egg is asked about. Under
   the capsule it landed on the hatch clock, which is the one reading the
   player is deciding against. */
#ui .nest > div .btn.arm-x {
  position: absolute;
  top: 0;
  inset-inline-end: -2px;
  width: 22px;
  min-width: 22px;
  height: 22px;
  min-height: 22px;
  padding: 0;
  border-radius: 999px;
  border-color: var(--coral);
  color: var(--coral);
  z-index: 3;
}
#ui .nest > div .btn.arm-x svg { width: 12px; height: 12px; }

#ui .so-chips { display: flex; align-items: center; gap: 8px; }
#ui .so-chips .btn { flex: 1 1 auto; min-width: 0; margin-bottom: 0; }
#ui .so-chips .btn svg { flex: 0 0 auto; }
#ui .so-chips .btn span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The heart hugs its glyph and its number; the follow beside it takes the
   rest. Both are `.btn`, so the card is five of one shape rather than a 36 px
   pill, a 32 px circle and three 46 px buttons. */
#ui .so-heart { flex: 0 0 auto; min-width: 62px; padding: 0 14px; }
#ui .so-heart b { font-size: var(--number); font-weight: 900; }
/* **A given heart is FULL coral, never pale.** It is `disabled` because the
   week's like is spent, and the standard 0.45 would say the control is broken
   rather than that it has already done its job. */
#ui .so-heart.danger[disabled] { opacity: 1; }
#ui .mk-like.wide {
  width: auto;
  min-width: 32px;
  padding: 0 10px;
  gap: 5px;
  font-weight: 900;
  font-size: var(--body);
  display: inline-flex;
  align-items: center;
}

/* ---------------------------------------------------------------------------
 * THE PORTRAIT. Godot renders the real rigged body in the real colour and
 * hands it over as a `data:` PNG; this is only the frame it sits in.
 *
 * The box keeps its size whether or not the picture has arrived, so a card
 * cannot change height under the finger when one lands. `waiting` is the same
 * breathing wash the skeletons use — an empty white square would read as a
 * bird that failed to load rather than one still being drawn.
 * ------------------------------------------------------------------------ */
#ui .shot {
  display: grid;
  place-items: center;
  justify-self: center;
  width: 62px;
  height: 62px;
  border-radius: var(--r-chip);
  /* `--plate` is the bird's own plumage nine parts white, set inline off the
   * wire. The sky wash is what anything with no colour to show still gets. */
  background-color: var(--plate, var(--sky-wash));
  overflow: hidden;
}
#ui .shot img { width: 100%; height: 100%; display: block; object-fit: contain; }
/* A shimmer travelling ACROSS the plate, not a second colour laid over it.
 * `mk-breathe` animates `background-position`, which over a flat background
 * colour means nothing at all — this box carried the animation from the day it
 * was written and had never once moved. */
#ui .shot.waiting {
  background-image: linear-gradient(90deg,
    rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));
  background-size: 200% 100%;
  animation: mk-breathe 1.4s ease-in-out infinite;
}

/* On a market row it is the glyph tile's own square, so a listing that can be
 * photographed and one that cannot line up down the shelf. */
#ui .shot.mk-tile { width: 44px; height: 44px; justify-self: auto; }
/* A portrait standing in for a `PictureCard`'s 42 px tile. Larger than
 * Flutter's, because the plate carries the plumage here and a colour needs
 * room to be read as a colour. */
#ui .shot.pick { width: 56px; height: 56px; }
