/* NOT CURRENTLY REGENERABLE (2026-09-13). The header below says this file is generated from
   ~/geneticdesigner/game/game.css and must not be hand-edited. That stopped being true: measured that day,
   before this note was added, re-running the port would have deleted 114 lines that exist only in this
   file, fixes made here and never carried to GD's source among them, while GD's file carries changes this
   one never took. ~/geneticdesigner/scripts/port_game_css_to_sbi.py now refuses such a write (GD 18de5f0).
   Reconcile the two files before re-porting. */
/* ◈ GAME LAYER — SEED BANK INTERNATIONAL edition.
   GENERATED from ~/geneticdesigner/game/game.css by scripts/port_game_css_to_sbi.py — do not hand-edit;
   edit GD's file and re-run. 640px breakpoint; this site's accent is green.

   2026-09-03 — THE LAYER IN THIS SITE'S SCHEME. Operator: "the new dark mode in some screens is a
   bit extreme and the menu bar is a bit way too off from the original scheme ... modify the entire
   site at once but keep it more moderate."
   It is NOT a return to the plain white layer he rejected on 2026-09-02 as "visual basic and not
   futuristic": the glass, the blur, the hairline and lip, the mono type, the motion and the radii
   all stay exactly as they are. Only the palette is re-pointed — paper instead of night, this
   site's ink scale, its green. The tab bar's own copy of the shared rules is GONE from this header:
   the ported body defines it (at 640px via MAP), so there is one definition, and the pinned
   five-column grid that used to live here cannot re-appear. */
:root { --sbi-accent: #3f9a5f; }

/* ══ THE RADII THIS SHEET HAS BEEN ASKING FOR ALL ALONG ════════════════════════════════════
   29 declarations read var(--g-r-panel), var(--g-r-card) or var(--g-r-ctl) with no fallback, and
   not one of those three properties was defined anywhere in the repository. CSS gives no warning
   for that and falls back to nothing useful: an unresolved var() makes the declaration INVALID AT
   COMPUTED-VALUE TIME, so the property takes its initial value — and for border-radius that is 0.

   So the create menu, its three buttons, their icon tiles, the crop card, the write box, the
   notification badges and the console panels have all been drawing square corners while their own
   source said rounded, for as long as those lines have existed. Nothing errored; it just looked
   cheap. tests/css-vars-defined.test.mjs now refuses a fallback-less var() that nothing defines —
   witnessed red on exactly these 29 uses before this block existed.

   The scale is the one the rest of this sheet already works in: a control is 10, a card is 12,
   a panel is 16. */
:root {
  --g-r-ctl: 10px;      /* buttons, inputs, small badges */
  --g-r-card: 14px;     /* cards, tabs, icon tiles */
  --g-r-panel: 18px;    /* sheets and overlays */
}
/* ══ THE SITE'S ONE MEASURE, AND THE MOTION IT MOVES WITH ══════════════════════════════════
   2026-09-24. Operator: "theres a lot of issues and conosistency and margin and width issues",
   inside "keep elevating our sbi so it gets closer to parity with our gage green group".

   MEASURED IN A BROWSER AT 1440 BEFORE ANYTHING CHANGED. Seven page widths and eight gutters
   across one publication:

       /                  1120 / 34      /breeder/<slug>     980 / 22
       /community.html    1120 / 34      /store/<slug>       980 / 22
       /shop.html         1180 /  0      /registry/<slug>/   900 / 20
       /breeders.html      680 /  0      /payouts.html       760 / 22
       /grower.html        980 / 22      wide-viewport override on / and /registry: 1200 / 40

   Nothing was wrong with any single number. The inconsistency IS the defect, and it is the kind
   you feel before you can name it: every navigation re-seats the text block under the eye.

   Gage Green Group solves it with exactly one container, and this is read off that site's own
   app/globals.css rather than guessed:

       .ggg-container { @apply mx-auto max-w-6xl px-4 sm:px-6; }   1152px, 16px / 24px
       .kit-wide   { max-width: 1040px; margin: 0 auto; }
       .kit-narrow { max-width:  720px; margin: 0 auto; }

   One shell, one gutter pair, three steps inside it. A page picks a step; it does not invent one.
   Gate: tests/one-measure-one-gutter.test.mjs, witnessed red on all eight surfaces first.

   WHY THESE LIVE IN THE GAME LAYER, of all places. They must resolve on every page, and measured
   in-browser this is the only stylesheet every page loads — /, /shop.html, /breeder/<slug>,
   /registry/<slug>/ and /registry-index.html share game.css and nothing else. The radius tokens
   above are here for that same reason. The --r-ctl/-card/-panel/-pill aliases in
   assets/registry-core.*.css resolve ONLY on /registry/* — measured, they come back empty on the
   other four — so anything site-wide has to be named here. The alternative was a new <link> on
   fifteen pages plus a second request to serve five numbers. */
:root {
  --measure: 1152px;          /* the page shell — GGG .ggg-container / max-w-6xl */
  --measure-wide: 1040px;     /* a wide block inside it — GGG .kit-wide */
  --measure-text: 720px;      /* prose, forms, statements — GGG .kit-narrow */
  --gutter: 24px;             /* GGG px-4 sm:px-6 — 24 above the 640 breakpoint */
  --gutter-sm: 16px;          /*                    16 below it */

  /* MOTION, from the same file. Carried so the two properties MOVE alike and not merely look
     alike — the easing is the part a person reads as quality without being able to point at it. */
  --ease-out-expo: cubic-bezier(.16, 1, .3, 1);
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1);
  --dur: 220ms;               /* GGG --default-transition-duration */

  /* ELEVATION — ambient light plus key light, two shadows to a step, which is what stops a card
     reading as a sticker. GGG's --shadow-sm…xl, its oklch ink converted to this site's #111. */
  --shadow-sm: 0 1px 2px -1px rgba(17,17,17,.10), 0 1px 3px -1px rgba(17,17,17,.06);
  --shadow-md: 0 4px 12px -4px rgba(17,17,17,.12), 0 2px 6px -3px rgba(17,17,17,.08);
  --shadow-lg: 0 18px 40px -20px rgba(17,17,17,.28), 0 6px 16px -10px rgba(17,17,17,.14);
  --shadow-xl: 0 36px 70px -30px rgba(17,17,17,.40), 0 12px 28px -16px rgba(17,17,17,.18);

  /* TYPE — eight steps. The strain record page had FOURTEEN distinct pixel sizes and 49 of its 75
     type rules sat at or under 11px, the UPOV specification table at 10.5px and the buy button at
     10px. Operator: "the actual strain data that epole care about is still not the main focus and
     tiny". --t-label is the ONLY step under 12px and it is for tracked uppercase labels, which is
     the one job a 10px glyph does well. Data never uses it. */
  /* THE CANON'S SCALE, not a new one. WEB_DESIGN_CANON.md §4 already
     fixes this for both properties — 32/24 title, 20 section heading, 17 card or record name,
     15 body, 13.5 secondary, 12 eyebrow — and the first version of this block invented a
     parallel 10/12/13/14/16/18/22/28, which is exactly the parallel-tree move Const #7 forbids.
     A second scale is not a scale. These are the canon's numbers, named. */
  --t-label: 12px;   /* eyebrow and label — UPPERCASE, tracking .08em, and nothing else */
  --t-data: 13.5px;  /* secondary: captions, citations, dense tabular data. The canon's one
                        legitimate half-step; every other half-pixel size is drift. */
  --t-body: 15px;    /* running body — the paragraph a person came to read */
  --t-lead: 17px;    /* a card's name, a record's name */
  --t-h3: 20px;      /* section heading */
  --t-h2: 24px;
  --t-h1: 32px;
  /* ORNAMENT, outside the text scale because they are not type: an avatar initial, an unlock
     glyph. Named rather than left off, which is how 36/40/54 became three sizes for two jobs. */
  --t-d1: 36px;
  --t-d2: 54px;
}
@media (max-width: 640px) { :root { --gutter: 16px; } }

/* ══ ELEVATION AND THE GROUND IT SITS ON ═══════════════════════════════════════════════════
   Operator: "eveyrthing needs visual elevation and motiondev and more smooth perfectionism".

   A shadow on a white box on a white page is invisible, and that is exactly what this site was:
   html and body painted #ffffff and every panel painted #ffffff, separated by a 1px line. Depth
   was doing no work because there was no ground to lift off. Gage Green Group's surface is
   #f5f5f7 with white cards over it — Apple's arrangement, and the reason its panels read as
   objects rather than as regions of a page.

   So: the page gets a ground, the sheet stays white, and the hairline is joined by a real
   two-light shadow. Nothing is repainted — only what is BEHIND the paint. */
:root { --ground: #f5f5f7; }
body { background: var(--ground); }
/* THE SHELL IS PAINT, NOT AN OBJECT. The first version of this put --shadow-sm on .frame and
   .wrap, and design-system-parity caught what that actually did: /breeder/<slug> went from two
   corner radii to three. The breeder dossier's .wrap has no background and no border of its own, so
   it was never counted as a box that draws a corner — a shadow made it one, and it turned out to be
   carrying an 18px radius nobody could see. A page shell with rounded corners and a drop shadow is
   a floating sheet, and these shells run min-height:100vh, so it would have been a sheet with a
   rounded bottom edge somewhere past the fold.
   GGG's own container carries no shadow either — .ggg-container is `mx-auto max-w-6xl px-4`, pure
   layout. Elevation belongs to cards and panels, which are objects. The shell just needs to be the
   white the ground is measured against. */
.frame, .wrap { background: #fff; }

/* A card lifts toward the cursor, on the spring, and the shadow grows with it. 2px is the whole
   movement — enough to read as a response, small enough that a grid of them does not wobble. */
.sc-card, .reg-section, .cat, .card {
  transition: transform var(--dur) var(--ease-spring), box-shadow var(--dur) var(--ease-out-expo);
}
.sc-card:hover, .cat:hover, .card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.sc-card:active, .cat:active, .card:active { transform: translateY(0); box-shadow: var(--shadow-sm); }
/* An open panel is nearer than a closed one. */
.reg-section:not(.is-closed) { box-shadow: var(--shadow-sm); }

/* ══ MOTION THAT THE SCROLL DRIVES, WITH NO JAVASCRIPT ═════════════════════════════════════
   GGG's own scroll reveal, carried across. animation-timeline: view() hands the animation's clock
   to the element's position in the viewport, so there is no observer, no listener and no frame
   budget — the compositor does it.

   THE TWO GUARDS ARE NOT OPTIONAL and they are the reason this is safe to ship:
     · @supports (animation-timeline: view()) — a browser without it never enters the block, so
       content is never left at opacity 0 by a feature that did not arrive. Fail-visible.
     · prefers-reduced-motion: no-preference — a reader who has asked for stillness gets it.
   `linear` is the correct timing function here: the scroll is the clock, and easing a clock that a
   person is already moving by hand fights their hand. */
@keyframes sbi-reveal {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    /* NOT details.breeder-block. A reveal suits a large block arriving on its own; the breeder
       list is SEVENTY-TWO rows of ~72px stacked directly on each other, so at any scroll position
       a whole band of them sits inside its entry phase at once and is held part-way through
       opacity 0 -> 1. The effect is a grey wash over the list that moves with the scroll.
       Operator: "why is everything greyed out when i scroll down on the breeders list ... its
       like got a opacity layer over it". Mine, added 2026-09-24 when this reveal was carried over
       from Gage Green Group — correct for sections and cards, wrong for a directory.
       `.cat` is dropped too: nothing on this site carries that class (measured: 0 elements), so
       it was only ever a selector nobody could see working or failing. */
    .reg-section, .sc-card {
      animation: sbi-reveal linear both;
      animation-timeline: view();
      animation-range: entry 4% cover 24%;
    }
  }
}

/* The finish GGG applies once, globally, and this site never received. All of it is either
   invisible-until-absent (font smoothing, legibility) or guarded by a motion preference. */
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; }
/* Hand-set line breaks the browser can do better than a person can: balance stops a heading
   leaving one orphan word on its last line, pretty stops a paragraph doing it. */
h1, h2, h3, .text-balance { text-wrap: balance; }
p { text-wrap: pretty; }
/* Figures that line up in a column, wherever a number is compared to another number. */
.tnum, td.price, .col-price, .char-value, .upov-table td { font-variant-numeric: tabular-nums; }

#profileConsole, #feedConsole, #gdHud, .gd-tabbar, #gdBoot, .pf-crop, .pf-create, .fx-toasts, .fx-unlock, .gd-notes { --gd-purple: #8cb6a1; --gd-ink: #111111; --gd-mono: ui-monospace, Menlo, monospace; }
/* The consoles are paper with depth — a frosted card over the page, not a black slab and not a
   flat white box: the hairline, the inset lip and the drop shadow are what keep it dimensional. */
#profileConsole.console, #feedConsole.console { background: rgba(252, 252, 252, .97) !important; color: #111111 !important; border: 1px solid rgba(17, 17, 17, .16) !important; box-shadow: inset 0 1px 0 rgba(255, 255, 255, .9), 0 -10px 60px rgba(17, 17, 17, .18) !important; }
#profileConsole .console-inner { display: grid; gap: 8px; }
#profileConsole .console-header, #feedConsole .console-header { display: flex; justify-content: space-between; align-items: center; color: #22683d; border-bottom: 1px solid rgba(17, 17, 17, .16); padding-bottom: 4px; letter-spacing: .08em; text-transform: uppercase; font-size: var(--t-data); }
#profileConsole .console-header button, #feedConsole .console-header button { color: #22683d !important; border-color: rgba(17, 17, 17, .22) !important; }
#profileConsole .cm-meta { font-size: var(--t-label); color: #5f5f5f; letter-spacing: .1em; text-transform: uppercase; }
#profileConsole .cm-backbtn { color: #22683d; }

@media (max-width: 640px) {
  /* the bar itself comes from the shared layer; these are this page's own accommodations for it */
  body { padding-bottom: calc(78px + env(safe-area-inset-bottom, 0px)); }
  .mobile-cart-bar { bottom: calc(76px + env(safe-area-inset-bottom, 0px)) !important; }
  /* keep the catalogue's blocks in flow (the cards under the bar in the first measurement turned out
     to be the unpainted content of CLOSED breeder blocks — Chrome keeps it laid out for find-in-page) */
  #inventoryContainer { display: flow-root; }
}
/* ── boot splash ─────────────────────────────────────────────────────────── */
html.gd-booting { overflow: hidden; }
#gdBoot {
  position: fixed; inset: 0; z-index: 99999; background: #05060f; color: #c9b6ff;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  font-family: var(--game-mono); cursor: pointer;
  opacity: 1; transition: opacity .45s var(--ease-out-expo);
  -webkit-user-select: none; user-select: none;
}
#gdBoot.gd-boot-out { opacity: 0; pointer-events: none; }
#gdBoot .gd-boot-ascii { position: absolute; inset: 0; display: block; animation: gd-boot-in 1.1s ease both; }
#gdBoot .gd-boot-word, #gdBoot .gd-boot-sub, #gdBoot .gd-boot-hint { position: relative; z-index: 1; }
#gdBoot .gd-boot-hint { position: absolute; }
#gdBoot .gd-boot-word {
  /* the site's own wordmark, measured live 2026-09-02: Inter 700, uppercase, -0.01em, white */
  font-family: var(--game-sans); font-weight: 700; text-transform: uppercase;
  margin-top: 20px; font-size: clamp(30px, 5.2vw, 50px); letter-spacing: -0.01em; line-height: 1.06;
  color: #fff; opacity: 0; transform: translateY(6px); animation: gd-boot-word .8s ease .6s both;
  text-shadow: 0 0 18px var(--g-lit-7, rgba(140, 182, 161, .7));
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
}
#gdBoot .gd-boot-word b { color: inherit; font-weight: inherit; }   /* all white, like the header's wordmark */
#gdBoot .gd-boot-sub {
  /* the header's own info line, measured live 2026-09-02: Inter 400, 14px, 0.16em, lavender */
  font-family: var(--game-sans); font-weight: 400;
  margin-top: 8px; font-size: var(--t-data); letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--g-hi-3, #22683d); opacity: 0; animation: gd-boot-word .6s ease 1.25s both;
}
#gdBoot .gd-boot-hint {
  font-family: var(--game-sans);
  position: absolute; bottom: calc(18px + env(safe-area-inset-bottom, 0px)); font-size: var(--t-label); letter-spacing: .16em;
  text-transform: uppercase; color: #5c5878; opacity: 0; animation: gd-boot-blink 1.4s ease 1.5s infinite;
}
@keyframes gd-boot-in   { from { opacity: 0; transform: scale(.94); } to { opacity: 1; transform: none; } }
@keyframes gd-boot-word { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes gd-boot-blink { 0%, 100% { opacity: 0; } 50% { opacity: .9; } }
@media (prefers-reduced-motion: reduce) {
  #gdBoot .gd-boot-ascii, #gdBoot .gd-boot-word, #gdBoot .gd-boot-sub { animation: none; opacity: 1; transform: none; }
  #gdBoot .gd-boot-hint { animation: none; opacity: .9; }
}

/* ── desktop HUD (the phone has the tab bar; the desktop gets a mini status bar) ── */
/* ── THE LAYER'S PALETTE, AS TOKENS ──────────────────────────────────────────────────────
   Operator 2026-09-03, of the other storefront: "the new dark mode in some screens is a bit
   extreme and the menu bar is a bit way too off from the original scheme ... modify the entire
   site at once but keep it more moderate."
   Measured there: the page is #efefef with #111 ink, and this layer sat on it as a near-black
   LAVENDER glass — rail rgba(20,15,36,.58), tab text #9d8fb8, console ink #e9e9f4. Correct for
   this dark storefront, a foreign object on that light institutional one.
   Every value below is the literal that was already here, so THIS site renders byte-identically.
   The other site re-themes by setting the tokens — one block, no fork, structure untouched. */
:root {
  --g-surface: #0b0c1a;
  --g-surface-2: rgba(8, 9, 18, .96);
  --g-surface-3: rgba(8, 9, 18, .97);
  --g-surface-4: rgba(8, 9, 18, .94);
  --g-glass: rgba(20, 15, 36, .58);
  --g-scrim: rgba(5, 6, 15, .55);
  --g-scrim-2: rgba(5, 6, 15, .62);
  --g-scrim-3: rgba(5, 6, 15, .78);
  --g-raise: rgba(255, 255, 255, .03);
  --g-raise-2: rgba(255, 255, 255, .04);
  --g-raise-3: rgba(255, 255, 255, .05);
  --g-ink: #e9e9f4;
  --g-ink-2: #c9c4dc;
  --g-ink-3: #a8a3c6;
  --g-ink-4: #8b86a8;
  --g-ink-5: #8a85a6;
  --g-ink-6: #6f6a8a;
  --g-ink-7: #9d8fb8;
  --g-ink-8: #cfc6ea;
  --g-line: rgba(17, 17, 17, .16);
  --g-line-2: rgba(17, 17, 17, .18);
  --g-line-3: rgba(17, 17, 17, .35);
  --g-line-4: rgba(17, 17, 17, .4);
  --g-line-5: rgba(17, 17, 17, .5);
  --g-lip: rgba(255, 255, 255, .055);
  --g-lip-2: rgba(255, 255, 255, .08);
  --g-lip-3: rgba(255, 255, 255, .12);
  --g-hi: #22683d;
  --g-hi-2: #2c7d4c;
  --g-hi-3: #22683d;

  /* the accent family: the flat colour, its alpha ramp, and the LIT state on the
     bar and the rail — the one the operator called "way too off from the original scheme". */
  --g-accent: #8cb6a1;
  --g-accent-06: rgba(140, 182, 161, .06);
  --g-accent-08: rgba(140, 182, 161, .08);
  --g-accent-1: rgba(140, 182, 161, .1);
  --g-accent-12: rgba(140, 182, 161, .12);
  --g-accent-15: rgba(140, 182, 161, .15);
  --g-accent-16: rgba(140, 182, 161, .16);
  --g-accent-18: rgba(140, 182, 161, .18);
  --g-accent-2: rgba(140, 182, 161, .2);
  --g-accent-22: rgba(140, 182, 161, .22);
  --g-accent-25: rgba(140, 182, 161, .25);
  --g-accent-3: rgba(140, 182, 161, .3);
  --g-accent-35: rgba(140, 182, 161, .35);
  --g-accent-4: rgba(140, 182, 161, .4);
  --g-accent-45: rgba(140, 182, 161, .45);
  --g-accent-5: rgba(140, 182, 161, .5);
  --g-accent-6: rgba(140, 182, 161, .6);
  --g-lit-18: rgba(140, 182, 161, .18);
  --g-lit-22: rgba(140, 182, 161, .22);
  --g-lit-30: rgba(140, 182, 161, .30);
  --g-lit-5: rgba(140, 182, 161, .5);
  --g-lit-7: rgba(140, 182, 161, .7);
}

/* ── THE GAME LAYER OWNS ITS OWN TYPE AND ITS OWN BOX MODEL ────────────────────────────
   Measured 2026-09-03 across three pages: the SAME shared components resolved to different
   typefaces (home '"JetBrains Mono", ui-monospace…', generated pages the two-item fallback)
   and different sizes (the rail avatar 26px on one page, 28px on another — no border-box
   reset there, so its 1px border was added OUTSIDE the 26). A shared component that inherits
   its box model and its typeface from whatever page it lands on is not shared.
   These are DEFAULTS: a page that declares its own tokens still wins. */
:root {
  /* PRIVATE TO THE LAYER, deliberately not --gd-sans / --gd-mono. Those are the PAGE's tokens,
     and every page declares its own slightly different list, so the shared chrome inherited a
     different family list on the home page than on a generated one — the same face, a different
     stack, and a real risk of a different face the day one page's list changes. The layer's own
     chrome uses the layer's own type. Colour still themes from the site (--glass-*, --gd-purple):
     a storefront should be able to recolour the layer, not restyle its type out from under it. */
  --game-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --game-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
/* A <button> does not inherit font-family or line-height from its page: the UA resets both.
   Left to the page, the same bar was Inter on one and Arial on another, and the rail stood
   383px tall on two pages and 381px on a third. A shared component sets its own. */
.gd-tabbar { font-family: var(--game-sans); line-height: 1.3; }
.gd-tabbar .gd-tab, #gdHud, #gdHud .gd-hud-btn { font-family: inherit; line-height: 1.3; }
#gdHud { font-family: var(--game-mono); }
/* TYPE AT EVERY WIDTH, LAYOUT PER WIDTH. These lived only inside the media queries, so at the
   width where a control is hidden it fell back to the PAGE's `button {}` rule — 11px with 2px
   tracking on one storefront page, the UA's 13.3px on another. Hidden is not "does not matter":
   it is the state nobody looks at, which is where drift starts. */
.gd-tabbar .gd-tab { font-size: var(--t-label); letter-spacing: .08em; text-transform: uppercase; color: var(--g-ink-7, #9d8fb8); }
#gdHud .gd-hud-btn { font-size: var(--t-label); letter-spacing: .1em; text-transform: uppercase; color: var(--g-ink-8, #cfc6ea); }
/* AND THE LAYER'S BUTTONS RESIST THE PAGE'S OWN `button {}` RULE. Measured 2026-09-03 on
   seedbankinternational.com/breeders.html, which styles every button on the page:
   `button{margin-top:16px;width:100%;padding:13px;font-size: var(--t-label);letter-spacing:2px}`. The
   shared bar inherited the 16px top margin — 82px tall there, 66px everywhere else. Only the
   properties an unscoped element rule can INJECT are reset; everything the layer actually
   styles is left to the layer's own rules below. */
#gdHud .gd-hud-btn, .gd-tabbar .gd-tab, #feedConsole button, #profileConsole button,
.gd-notes button, .fx-unlock button {
  margin: 0; width: auto; min-width: 0; box-shadow: none;
}

#gdHud, #gdHud *, .gd-tabbar, .gd-tabbar *, #feedConsole, #feedConsole *,
#profileConsole, #profileConsole *, .fx-toasts, .fx-toasts *, .fx-unlock, .fx-unlock *,
.gd-notes, .gd-notes * { box-sizing: border-box; }

/* ── A KEYBOARD USER CAN ALWAYS SEE WHERE THEY ARE ───────────────────────────────────────
   Measured 2026-09-03 by tabbing 24 controls on each site: the other storefront was clean, this
   one had TWO of its own search boxes — the grow-shop search and the strain finder on the hero —
   reachable with no visible focus at all. Both carried an inline `outline:none` with nothing put
   in its place, and there are eight more inline `outline:none` inputs behind them, so patching
   the two would have left the class alive.
   :focus-visible, so a MOUSE click still shows nothing; only keyboard focus draws the ring.
   !important because the suppressions are inline styles, and :where() so this adds no specificity
   of its own — any deliberate, visible focus style a component defines still wins the look. */
:where(a[href], button, input, select, textarea, summary, [tabindex]:not([tabindex="-1"])):focus-visible {
  outline: 2px solid var(--sbi-accent, #c2a3ff) !important;
  outline-offset: 2px !important;
  border-radius: var(--g-r-ctl);
}

#gdHud { display: none; }
@media (min-width: 641px) {
  #gdHud {
    display: flex; align-items: stretch; gap: 0;
    position: fixed; right: 16px; bottom: 16px; z-index: 1400;
    background: var(--glass-bg, var(--g-glass, rgba(20, 15, 36, .58)));
    -webkit-backdrop-filter: var(--glass-blur, blur(18px)); backdrop-filter: var(--glass-blur, blur(18px));
    border: 1px solid var(--glass-edge, var(--g-line, rgba(17, 17, 17, .16)));
    box-shadow: inset 0 1px 0 var(--glass-lip, var(--g-lip, rgba(255, 255, 255, .055))), 0 8px 30px rgba(0, 0, 0, .35);
    border-radius: var(--g-r-panel); padding: 4px; font-family: var(--game-mono);
    color: var(--g-ink-8, #cfc6ea); font-size: var(--t-label); letter-spacing: .12em; text-transform: uppercase;
  }
  #gdHud .gd-hud-btn {
    appearance: none; background: none; border: 0; color: inherit; font: inherit; letter-spacing: inherit;
    text-transform: inherit; cursor: pointer; display: flex; align-items: center; gap: 8px;
    padding: 8px 12px; border-radius: var(--g-r-card); min-height: 40px; transition: background var(--dur) var(--ease-out-expo), color var(--dur) var(--ease-out-expo);
  }
  #gdHud .gd-hud-btn:hover, #gdHud .gd-hud-btn:focus-visible { background: var(--g-lit-22, rgba(140, 182, 161, .22)); color: #fff; }
  #gdHud .gd-hud-btn[aria-current="true"] { background: var(--g-lit-30, rgba(140, 182, 161, .30)); color: #fff; }
  #gdHud .gd-hud-btn svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
  #gdHud .gd-hud-sep { width: 1px; margin: 8px 2px; background: var(--g-line-2, rgba(17, 17, 17, .18)); }
  #gdHud .gd-hud-avatar {
    width: 26px; height: 26px; border-radius: 50%; background: var(--g-accent-35, rgba(140, 182, 161, .35)); border: 1px solid var(--g-line-3, rgba(17, 17, 17, .35));
    display: grid; place-items: center; overflow: hidden; font-size: var(--t-label); color: #fff; flex: none;
  }
  #gdHud .gd-hud-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
  #gdHud .gd-hud-name { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.15; }
  #gdHud .gd-hud-name small { font-size: var(--t-label); color: var(--g-ink-7, #9d8fb8); letter-spacing: .14em; text-transform: uppercase; }
  #gdHud .gd-hud-bar { width: 64px; height: 3px; background: var(--g-lip-2, rgba(255, 255, 255, .08)); border-radius: var(--g-r-ctl); overflow: hidden; margin-top: 4px; }
  #gdHud .gd-hud-bar i { display: block; height: 100%; width: 0; background: linear-gradient(90deg, var(--g-accent, #8cb6a1), var(--g-hi-2, #2c7d4c)); transition: width .6s var(--ease-out-expo); }
  #gdHud .gd-hud-n {
    min-width: 17px; height: 17px; padding: 0 4px; border-radius: var(--g-r-ctl); background: var(--gd-purple, var(--g-accent, #8cb6a1)); color: #fff;
    font-size: var(--t-label); line-height: 17px; text-align: center; font-variant-numeric: tabular-nums;
  }
  #gdHud .gd-hud-n[hidden] { display: none; }
  #gdHud .gd-hud-n.is-live { background: #2f9a5a; }
}
@media (min-width: 641px) and (prefers-reduced-motion: reduce) { #gdHud .gd-hud-btn, #gdHud .gd-hud-bar i { transition: none; } }
@media (min-width: 641px) { .gd-tabbar { display: none; } }
@media (prefers-reduced-motion: reduce) { .gd-tabbar .gd-tab { transition: none; } }

/* ── phone tab bar ────────────────────────────────────────────────────────────────────
   THE WHOLE BAR LIVES HERE, not in one page's <style>. Measured 2026-09-03 at 390px:
   the home page had a fixed glass bar at the bottom of the SCREEN, and every one of the
   483 generated pages had `position: static; display: block` — the same markup, unstyled,
   parked 7,027px down at the bottom of the DOCUMENT. The bar was never on those pages'
   screens at all. It looked wired because the markup was there and the icons drew.
   The cause: hud.js and the injector are shared, this stylesheet is shared, and the CSS
   that turns the markup into a bar was page-local. Now it is not.

   NO PINNED COLUMN COUNT either: it was repeat(5) here and repeat(4) in the page, so
   adding a destination silently wrapped Profile onto a second row. Column flow makes the
   bar hold whatever it is given, at any count. */
@media (max-width: 640px) {
  nav.gd-tabbar[role="navigation"], .gd-tabbar {
    display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; grid-template-columns: none;
    position: fixed; left: 8px; right: 8px; bottom: 8px;
    /* ABOVE the panels, never under them: the bar is persistent navigation, and the tab that
       lights up to show which surface is open is meaningless if that surface covers it. */
    z-index: 1400;
    border-radius: var(--g-r-panel); padding: 8px 4px;
    padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px));
    background: var(--glass-bg, var(--g-glass, rgba(20, 15, 36, .58)));
    -webkit-backdrop-filter: var(--glass-blur, blur(18px) saturate(132%));
    backdrop-filter: var(--glass-blur, blur(18px) saturate(132%));
    border: 1px solid var(--glass-edge, var(--g-line, rgba(17, 17, 17, .16)));
    box-shadow: inset 0 1px 0 var(--glass-lip, var(--g-lip, rgba(255, 255, 255, .055)));
  }
  nav.gd-tabbar .gd-tab, .gd-tabbar .gd-tab {
    appearance: none; background: none; border: 0; cursor: pointer;
    font-family: var(--game-sans);
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 4px; padding: 8px 2px 8px; min-height: 52px; border-radius: var(--g-r-ctl);
    color: var(--g-ink-7, #9d8fb8); font-size: var(--t-label); letter-spacing: .08em; text-transform: uppercase;
    transition: color var(--dur) var(--ease-out-expo), background var(--dur) var(--ease-out-expo), transform var(--dur) var(--ease-out-expo);
  }
  .gd-tabbar .gd-tab svg { width: 20px; height: 20px; stroke: currentColor; fill: none;
    stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
  .gd-tabbar .gd-tab:active { transform: scale(.94); }
  .gd-tabbar .gd-tab[aria-current="true"] { color: #fff; background: var(--g-lit-30, rgba(140, 182, 161, .30)); }
  .gd-tabbar .gd-tab .gd-badge { position: absolute; transform: translate(15px, -16px);
    min-width: 16px; height: 16px; padding: 0 4px; border-radius: var(--g-r-ctl);
    background: var(--gd-purple, var(--g-accent, #8cb6a1)); color: #fff; font-size: var(--t-label); line-height: 16px;
    font-variant-numeric: tabular-nums; }
  .gd-tabbar .gd-tab .gd-badge[hidden] { display: none; }
  /* nothing may hide behind the bar */
  body { padding-bottom: calc(78px + env(safe-area-inset-bottom, 0px)); }
  .gd-tab .gd-tab-avatar { width: 20px; height: 20px; border-radius: 50%; overflow: hidden; display: none; }
  .gd-tab .gd-tab-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .gd-tab.has-avatar svg { display: none; }
  .gd-tab.has-avatar .gd-tab-avatar { display: block; }
  #profileConsole .console-inner { padding-bottom: calc(76px + env(safe-area-inset-bottom, 0px)) !important; }
}
/* THE BAR SHOWS ITS ICONS ALONE (operator 2026-09-14: the labels "dont need text just representative labels"). The words were
   9.5px, under the site's 12px floor. They stay in the markup, hidden visually, so each tab keeps its name for screen readers. */
@media (max-width: 640px) {
  .gd-tabbar .gd-tab > span:not(.gd-badge):not(.gd-tab-avatar) {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
    overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  }
  .gd-tabbar .gd-tab svg, .gd-tab .gd-tab-avatar { width: 24px; height: 24px; }
}

/* ── profile console (the character sheet) ───────────────────────────────── */
/* The OAuth anchors carry display:inline-flex, which outranks the UA's [hidden]{display:none} —
   the buttons rendered while hidden (caught on the first screenshot, 2026-09-02). */
#profileConsole [hidden] { display: none !important; }
#profileConsole .pf-sheet { display: grid; gap: 12px; }
#profileConsole .pf-id { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
#profileConsole .pf-avatar {
  /* ONE circle, not two. This carried a solid 2px ring AND the XP progress ring is drawn as a
     second concentric circle at inset:-6px — on the dark ground they merged, on paper they read
     as two (operator 2026-09-03: "theres like two avatar circles in the profile sheet").
     The progress ring is the ring; the photo keeps a hairline so it still has an edge. */
  width: 72px; height: 72px; border-radius: 50%; background: var(--g-accent-25, rgba(140, 182, 161, .25)); border: 0;
  display: grid; place-items: center; overflow: hidden; font-size: var(--t-h2); color: #fff; flex: none; position: relative;
}
#profileConsole .pf-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
#profileConsole .pf-avatar input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
#profileConsole .pf-who h3 { font-size: var(--t-h3); margin: 0 0 2px; color: var(--g-ink, #e9e9f4); }
#profileConsole .pf-who .pf-handle { font-size: var(--t-data); letter-spacing: 1.4px; text-transform: uppercase; color: var(--g-ink-6, #6f6a8a); }
#profileConsole .pf-who .pf-bio { font-size: var(--t-data); color: var(--g-ink-2, #c9c4dc); margin-top: 8px; max-width: 560px; line-height: 1.55; }
#profileConsole .pf-level { display: grid; gap: 4px; min-width: 200px; flex: 1; }
#profileConsole .pf-level .pf-lv { display: flex; justify-content: space-between; font-size: var(--t-label); letter-spacing: 1.2px; text-transform: uppercase; color: var(--g-ink-6, #6f6a8a); }
#profileConsole .pf-level .pf-lv b { color: var(--g-hi, #22683d); }
#profileConsole .pf-level .pf-xp { height: 6px; background: var(--g-accent-12, rgba(140, 182, 161, .12)); border: 1px solid var(--g-accent-3, rgba(140, 182, 161, .3)); border-radius: var(--g-r-ctl); overflow: hidden; }
#profileConsole .pf-level .pf-xp i { display: block; height: 100%; width: 0; background: linear-gradient(90deg, var(--g-accent, #8cb6a1), var(--g-hi-2, #2c7d4c)); transition: width .7s var(--ease-out-expo); }
#profileConsole .pf-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(104px, 1fr)); gap: 8px; }
#profileConsole .pf-stat { border: 1px solid var(--g-accent-3, rgba(140, 182, 161, .3)); background: var(--g-raise, rgba(255, 255, 255, .03)); padding: 8px 8px; }
#profileConsole .pf-stat b { display: block; font-size: var(--t-h3); color: var(--g-ink, #e9e9f4); font-variant-numeric: tabular-nums; }
#profileConsole .pf-stat span { font-size: var(--t-label); letter-spacing: 1.2px; text-transform: uppercase; color: var(--g-ink-6, #6f6a8a);
  /* A two-word label must not break across lines — "GROW LOGS" wrapping mid-phrase is the
     kind of thing that makes a page read as unfinished (operator 2026-09-08). Measured
     across 320-1600px: it wrapped at 680px. nowrap + a floor on the column keeps every
     label on one line at every width, and the tracking tightens rather than the words break. */
  white-space: nowrap; overflow-wrap: normal; hyphens: none; }
#profileConsole .pf-tabs { display: flex; gap: 2px; flex-wrap: wrap; border-bottom: 1px solid var(--g-accent-35, rgba(140, 182, 161, .35)); }
#profileConsole .pf-tab {
  background: none; border: 0; border-bottom: 2px solid transparent; color: var(--g-ink-6, #6f6a8a); font-family: inherit; font-size: var(--t-data);
  letter-spacing: 1.4px; text-transform: uppercase; padding: 8px 8px; cursor: pointer; margin-bottom: -1px; min-height: 40px;
}
#profileConsole .pf-tab[aria-selected="true"] { color: var(--g-hi, #22683d); border-bottom-color: var(--gd-purple, var(--g-accent, #8cb6a1)); }
#profileConsole .pf-tab .pf-n { font-size: var(--t-label); color: var(--g-ink-7, #9d8fb8); margin-left: 4px; }
#profileConsole .pf-pane { max-height: 62vh; overflow-y: auto; padding-top: 8px; }
#profileConsole .pf-empty { font-size: var(--t-data); color: var(--g-ink-6, #6f6a8a); padding: 12px 0; line-height: 1.6; }
#profileConsole .pf-row { display: flex; gap: 8px; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--g-accent-18, rgba(140, 182, 161, .18)); font-size: var(--t-data); }
#profileConsole .pf-row .pf-k { font-size: var(--t-label); letter-spacing: 1.2px; text-transform: uppercase; color: var(--g-ink-6, #6f6a8a); min-width: 62px; }
#profileConsole .pf-row .pf-t { flex: 1; min-width: 0; }
#profileConsole .pf-row .pf-t b { color: var(--g-ink, #e9e9f4); }
#profileConsole .pf-row .pf-t small { display: block; color: var(--g-ink-6, #6f6a8a); font-size: var(--t-label); margin-top: 2px; }
/* THE LIGHT-GROUND STOP, because this row is on the WHITE profile sheet, not the dark
   console. I moved every --g-accent use to ggg-300 (#8cb6a1) on the assumption they all sat
   on #0b0c1a; contrast-aa measured this one at 2.25:1 and refused. ggg-700 is 7.54 on white.
   One ramp, and each ground takes the stop it can carry — WEB_DESIGN_CANON.md §2a. */
/* NOT THROUGH --gd-purple. That token is Genetic Designer's name for its accent and, in
   SBI's copy of this shared stylesheet, it resolves to the DARK-console stop — correct for
   the 30 uses that sit on #0b0c1a and wrong for this one, which is on the white profile
   sheet. contrast-aa measured it at 2.25:1 twice: once when I moved every accent to ggg-300
   assuming a dark ground, and again when I left the --gd-purple link in front of the fix. */
#profileConsole .pf-row .pf-q { font-variant-numeric: tabular-nums; color: var(--g-accent-ink, #2f5d4a); white-space: nowrap; }
#profileConsole .pf-chip {
  display: inline-block; font-size: var(--t-label); letter-spacing: 1px; text-transform: uppercase; padding: 4px 8px; margin: 0 4px 4px 0;
  border: 1px solid var(--g-accent-4, rgba(140, 182, 161, .4)); color: var(--g-hi, #22683d); background: var(--g-accent-06, rgba(140, 182, 161, .06));
}
#profileConsole .pf-chip.is-ok { border-color: rgba(127, 211, 155, .5); color: #7fd39b; background: rgba(127, 211, 155, .1); }
#profileConsole .pf-chip.is-warn { border-color: rgba(255, 143, 122, .6); color: #ff8f7a; background: rgba(255, 143, 122, .1); }
#profileConsole .pf-photos { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 8px; }
#profileConsole .pf-photos a { display: block; aspect-ratio: 1; overflow: hidden; border: 1px solid var(--g-accent-25, rgba(140, 182, 161, .25)); background: var(--g-raise, rgba(255, 255, 255, .03)); }
#profileConsole .pf-photos img { width: 100%; height: 100%; object-fit: cover; display: block; }
#profileConsole .pf-form { display: grid; gap: 8px; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); align-items: end; }
#profileConsole .pf-form label { display: grid; gap: 4px; font-size: var(--t-label); letter-spacing: 1.2px; text-transform: uppercase; color: var(--g-ink-6, #6f6a8a); }
#profileConsole .pf-form input, #profileConsole .pf-form select, #profileConsole .pf-form textarea {
  background: var(--g-raise-2, rgba(255, 255, 255, .04)); border: 1px solid var(--g-accent-5, rgba(140, 182, 161, .5)); color: var(--g-ink, #e9e9f4); padding: 8px 8px;
  font-family: inherit; font-size: 16px; min-height: 40px; width: 100%; box-sizing: border-box;
}
#profileConsole .pf-form textarea { min-height: 72px; resize: vertical; }
#profileConsole .pf-form .pf-wide { grid-column: 1 / -1; }
#profileConsole .pf-btn {
  background: var(--gd-purple, var(--g-accent, #8cb6a1)); border: 1px solid var(--gd-purple, var(--g-accent, #8cb6a1)); color: #fff; font-family: inherit; font-size: var(--t-data);
  letter-spacing: 1.5px; text-transform: uppercase; padding: 8px 12px; cursor: pointer; min-height: 44px;
}
#profileConsole .pf-btn.is-ghost { background: none; color: var(--g-hi, #22683d); }
#profileConsole .pf-btn:disabled { opacity: .5; cursor: default; }
#profileConsole .pf-links a { display: inline-block; margin: 0 8px 8px 0; font-size: var(--t-data); letter-spacing: 1px; text-transform: uppercase; color: var(--g-hi, #22683d); }
#profileConsole .pf-note { font-size: var(--t-data); color: var(--g-hi, #22683d); border-left: 2px solid var(--gd-purple, var(--g-accent, #8cb6a1)); background: var(--g-accent-08, rgba(140, 182, 161, .08)); padding: 8px 8px; }
#profileConsole .pf-note.is-bad { color: #ff8f7a; border-left-color: #ff8f7a; background: rgba(255, 143, 122, .1); }
#profileConsole .pf-auth { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; border-radius: var(--g-r-card); padding: 16px !important; border: 1px solid var(--g-accent-45, rgba(140, 182, 161, .45)); padding: 12px; font-size: var(--t-data); color: var(--g-ink-6, #6f6a8a); }
#profileConsole .pf-auth input { flex: 1 1 220px; min-width: 190px; max-width: 340px; border-radius: var(--g-r-ctl); background: var(--g-raise-2, rgba(255, 255, 255, .04)); border: 1px solid var(--g-accent-5, rgba(140, 182, 161, .5)); color: var(--g-ink, #e9e9f4); padding: 8px 8px; font-family: inherit; font-size: 16px; min-height: 40px; }
#profileConsole .pf-auth a.pf-oauth { background: none; border: 1px solid var(--gd-purple, var(--g-accent, #8cb6a1)); color: var(--g-hi, #22683d); font-size: var(--t-data); letter-spacing: 1px; text-transform: uppercase; padding: 8px 12px; text-decoration: none; min-height: 40px; display: inline-flex; align-items: center; }
#profileConsole .pf-auth a.pf-oauth.is-discord { background: #5865F2; border-color: #5865F2; color: #fff; }
#profileConsole .pf-guest { display: grid; gap: 12px; }
#profileConsole .pf-guest .pf-guest-hero { font-size: var(--t-data); line-height: 1.7; color: var(--g-ink-2, #c9c4dc); }
#profileConsole .pf-guest .pf-guest-hero b, #profileConsole .pf-guest ul b { color: var(--g-hi, #22683d); }
#profileConsole .pf-guest ul { margin: 0; padding-left: 16px; font-size: var(--t-data); line-height: 1.8; color: var(--g-ink-2, #c9c4dc); }
@media (prefers-reduced-motion: reduce) { #profileConsole .pf-level .pf-xp i { transition: none; } }

/* ── edit pane v2: avatar crop, link editor, even stat rows, scrollable tab strip (2026-09-02) ── */
#profileConsole .pf-id { position: relative; }
#profileConsole .pf-avatar-edit {
  position: absolute; left: 0; top: 54px; width: 72px; text-align: center; font-family: inherit; font-size: var(--t-label); letter-spacing: .14em; text-transform: uppercase;
  background: rgba(0, 0, 0, .55); color: #fff; border: 0; border-radius: 0 0 36px 36px; padding: 4px 0 8px; cursor: pointer; line-height: 1;
}
#profileConsole .pf-avatar-edit:hover, #profileConsole .pf-avatar-edit:focus-visible { background: var(--gd-purple, var(--g-accent, #8cb6a1)); }
#profileConsole .pf-links a { display: inline-flex; align-items: center; gap: 4px; margin: 0 8px 8px 0; font-size: var(--t-label); letter-spacing: .08em; text-transform: uppercase; text-decoration: none; }
#profileConsole .pf-links svg, #profileConsole .pf-linkicon svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
#profileConsole .pf-grid { grid-template-columns: repeat(6, 1fr); }
#profileConsole .pf-lbl { font-size: var(--t-label); letter-spacing: 1.2px; text-transform: uppercase; color: var(--g-ink-4, #8b86a8); margin-bottom: 8px; }
#profileConsole .pf-lbl small { text-transform: none; letter-spacing: 0; color: var(--g-ink-6, #6f6a8a); margin-left: 8px; }
#profileConsole .pf-linkrow { display: grid; grid-template-columns: 28px 130px 1fr 34px; gap: 8px; align-items: center; margin-bottom: 8px; }
#profileConsole .pf-linkicon { display: grid; place-items: center; width: 28px; height: 28px; color: var(--g-hi, #22683d); }
#profileConsole .pf-linkrow input[aria-invalid="true"] { border-color: #ff8f7a; }
#profileConsole .pf-linkdel { appearance: none; background: none; border: 1px solid rgba(255, 143, 122, .5); color: #ff8f7a; width: 34px; height: 34px; border-radius: var(--g-r-ctl); cursor: pointer; font-size: var(--t-h3); line-height: 1; }
#profileConsole .pf-actions { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
#profileConsole .pf-hint { font-size: var(--t-label); color: var(--g-ink-4, #8b86a8); line-height: 1.5; }
#profileConsole .pf-hint b { color: var(--g-hi, #22683d); }
.pf-crop { position: fixed; inset: 0; z-index: 2000; background: rgba(5, 6, 15, .82); display: grid; place-items: center; padding: 16px; }
.pf-crop-card { background: var(--g-surface, #0b0c1a); border: 1px solid var(--g-line-3, rgba(17, 17, 17, .35)); padding: 16px; display: grid; gap: 12px; justify-items: center; color: var(--g-ink, #e9e9f4); font-family: inherit; max-width: 100%; }
.pf-crop-title { font-size: var(--t-label); letter-spacing: .18em; text-transform: uppercase; color: var(--g-hi, #22683d); }
.pf-crop-stage { position: relative; overflow: hidden; touch-action: none; background: #000; border-radius: var(--g-r-ctl); }
.pf-crop-stage canvas { display: block; cursor: grab; }
.pf-crop-mask { position: absolute; inset: 0; pointer-events: none; box-shadow: 0 0 0 999px var(--g-scrim-2, rgba(5, 6, 15, .62)) inset; border-radius: 50%; }
.pf-crop-zoom { display: flex; align-items: center; gap: 8px; font-size: var(--t-label); letter-spacing: .14em; text-transform: uppercase; color: var(--g-ink-4, #8b86a8); width: 100%; }
.pf-crop-zoom input { flex: 1; accent-color: var(--gd-purple, var(--g-accent, #8cb6a1)); }
.pf-crop-hint { font-size: var(--t-label); color: var(--g-ink-6, #6f6a8a); letter-spacing: .06em; }
.pf-crop-actions { display: flex; gap: 8px; }
.pf-crop .pf-btn { background: var(--gd-purple, var(--g-accent, #8cb6a1)); border: 1px solid var(--gd-purple, var(--g-accent, #8cb6a1)); color: #fff; font-family: inherit; font-size: var(--t-data); letter-spacing: 1.5px; text-transform: uppercase; padding: 8px 12px; cursor: pointer; min-height: 40px; }
.pf-crop .pf-btn.is-ghost { background: none; color: var(--g-hi, #22683d); }
@media (max-width: 640px) {
  /* spatial pass for the phone: one column of identity, three-by-two stats, a tab strip that
     scrolls instead of wrapping, single-column forms. Six cells ÷ three columns = no hanging row. */
  #profileConsole .pf-id { flex-direction: column; align-items: center; text-align: center; gap: 8px; }
  #profileConsole .pf-avatar-edit { left: 50%; transform: translateX(-50%); }
  #profileConsole .pf-who { display: grid; justify-items: center; }
  #profileConsole .pf-level { width: 100%; min-width: 0; }
  #profileConsole .pf-grid { grid-template-columns: repeat(3, 1fr); gap: 8px; }
  #profileConsole .pf-stat { padding: 8px 8px; text-align: center; }
  #profileConsole .pf-stat b { font-size: var(--t-h3); }
  #profileConsole .pf-stat span { font-size: var(--t-label); letter-spacing: .06em; white-space: nowrap; text-transform: uppercase; }
  #profileConsole .pf-tabs { flex-wrap: nowrap; overflow-x: auto; scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch; scrollbar-width: none; margin: 0 -4px; padding: 0 4px; }
  #profileConsole .pf-tabs::-webkit-scrollbar { display: none; }
  #profileConsole .pf-tab { flex: 0 0 auto; scroll-snap-align: center; }
  #profileConsole .pf-form { grid-template-columns: 1fr; }
  #profileConsole .pf-linkrow { grid-template-columns: 28px 1fr 34px; grid-template-areas: "icon label del" "url url url"; }
  #profileConsole .pf-linkicon { grid-area: icon; } #profileConsole .pf-linklabel { grid-area: label; } #profileConsole .pf-linkurl { grid-area: url; } #profileConsole .pf-linkdel { grid-area: del; }
  #profileConsole .pf-row { flex-wrap: wrap; }
}

/* ── stash v2: a photo grid, public/private per item, the provenance seal (2026-09-02) ── */
#profileConsole .pf-stash-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }
#profileConsole .pf-card { border: 1px solid var(--g-accent-35, rgba(140, 182, 161, .35)); background: var(--g-raise, rgba(255, 255, 255, .03)); display: grid; grid-template-rows: auto 1fr auto; overflow: hidden; position: relative; }
#profileConsole .pf-card-img { aspect-ratio: 1; background: var(--g-accent-08, rgba(140, 182, 161, .08)); display: grid; place-items: center; color: var(--g-hi, #22683d); cursor: pointer; overflow: hidden; }
#profileConsole .pf-card-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
#profileConsole .pf-card-img svg { width: 34px; height: 34px; stroke: currentColor; fill: none; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; opacity: .8; }
#profileConsole .pf-card-body { padding: 8px 8px 8px; display: grid; gap: 4px; min-width: 0; }
#profileConsole .pf-card-body b { font-size: var(--t-data); color: var(--g-ink, #e9e9f4); line-height: 1.25; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#profileConsole .pf-card-body small { font-size: var(--t-label); color: var(--g-ink-4, #8b86a8); letter-spacing: .04em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#profileConsole .pf-card-body p { font-size: var(--t-label); color: var(--g-ink-2, #c9c4dc); line-height: 1.45; margin: 2px 0 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
#profileConsole .pf-card-foot { display: flex; gap: 4px; align-items: center; padding: 8px 8px 8px; border-top: 1px solid var(--g-accent-2, rgba(140, 182, 161, .2)); }
#profileConsole .pf-card-foot select { flex: 1; min-width: 0; font-size: 16px; padding: 4px 4px; background: var(--g-raise-2, rgba(255, 255, 255, .04)); border: 1px solid var(--g-accent-4, rgba(140, 182, 161, .4)); color: var(--g-ink, #e9e9f4); font-family: inherit; min-height: 34px; }
#profileConsole .pf-ico { appearance: none; background: none; border: 1px solid var(--g-accent-4, rgba(140, 182, 161, .4)); color: var(--g-hi, #22683d); width: 34px; height: 34px; border-radius: var(--g-r-ctl); cursor: pointer; display: grid; place-items: center; flex: none; }
#profileConsole .pf-ico svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
#profileConsole .pf-ico[aria-pressed="true"] { background: rgba(127, 211, 155, .15); border-color: rgba(127, 211, 155, .6); color: #7fd39b; }
#profileConsole .pf-ico.is-danger { color: #a3281a; border-color: rgba(255, 143, 122, .45); }
#profileConsole .pf-seal { position: absolute; top: 6px; left: 6px; display: inline-flex; align-items: center; gap: 4px; font-size: var(--t-label); letter-spacing: .12em; text-transform: uppercase; padding: 4px 8px; background: var(--g-scrim-3, rgba(5, 6, 15, .78)); color: #7fd39b; border: 1px solid rgba(127, 211, 155, .5); }
#profileConsole .pf-seal.is-unsigned { color: var(--g-hi, #22683d); border-color: var(--g-line-5, rgba(17, 17, 17, .5)); }
#profileConsole .pf-seal svg { width: 11px; height: 11px; stroke: currentColor; fill: none; stroke-width: 2; }
#profileConsole .pf-vis { position: absolute; top: 6px; right: 6px; font-size: var(--t-label); letter-spacing: .12em; text-transform: uppercase; padding: 4px 8px; background: var(--g-scrim-3, rgba(5, 6, 15, .78)); color: var(--g-ink-4, #8b86a8); border: 1px solid rgba(139, 134, 168, .4); }
#profileConsole .pf-vis.is-public { color: #7fd39b; border-color: rgba(127, 211, 155, .5); }
#profileConsole .pf-photo-pick { display: flex; gap: 8px; align-items: center; }
#profileConsole .pf-photo-pick .pf-thumb { width: 52px; height: 52px; border: 1px dashed var(--g-accent-5, rgba(140, 182, 161, .5)); display: grid; place-items: center; color: var(--g-hi, #22683d); overflow: hidden; flex: none; cursor: pointer; }
#profileConsole .pf-photo-pick .pf-thumb img { width: 100%; height: 100%; object-fit: cover; }
#profileConsole .pf-photo-pick .pf-thumb svg { width: 20px; height: 20px; stroke: currentColor; fill: none; stroke-width: 1.6; }
#profileConsole .pf-seg { display: inline-flex; border: 1px solid var(--g-accent-45, rgba(140, 182, 161, .45)); }
#profileConsole .pf-seg button { appearance: none; background: none; border: 0; color: var(--g-ink-4, #8b86a8); font-family: inherit; font-size: var(--t-label); letter-spacing: .12em; text-transform: uppercase; padding: 8px 12px; cursor: pointer; min-height: 40px; }
#profileConsole .pf-seg button[aria-pressed="true"] { background: var(--gd-purple, var(--g-accent, #8cb6a1)); color: #fff; }
#profileConsole .pf-edit-inline { grid-column: 1 / -1; border: 1px solid var(--g-accent-45, rgba(140, 182, 161, .45)); padding: 8px; display: grid; gap: 8px; }
@media (max-width: 640px) {
  #profileConsole .pf-stash-grid { grid-template-columns: repeat(2, 1fr); gap: 8px; }
  #profileConsole .pf-card-foot { flex-wrap: wrap; }
  #profileConsole .pf-card-foot select { flex: 1 1 100%; }
}

/* ── the "+" composer ── */
.pf-crop-mask.is-square { border-radius: 0; box-shadow: none; outline: 2px solid rgba(255, 255, 255, .55); outline-offset: -2px; }
.pf-composer { width: min(520px, 100%); justify-items: stretch; }
.pf-composer textarea { width: 100%; box-sizing: border-box; background: var(--g-raise-2, rgba(255, 255, 255, .04)); border: 1px solid var(--g-accent-5, rgba(140, 182, 161, .5)); color: var(--g-ink, #e9e9f4); padding: 8px 8px; font-family: inherit; font-size: 16px; resize: vertical; min-height: 64px; }
.pf-composer input, .pf-composer select { width: 100%; box-sizing: border-box; background: var(--g-raise-2, rgba(255, 255, 255, .04)); border: 1px solid var(--g-accent-5, rgba(140, 182, 161, .5)); color: var(--g-ink, #e9e9f4); padding: 8px 8px; font-family: inherit; font-size: 16px; min-height: 40px; }
.pf-composer .pf-lbl { font-size: var(--t-label); letter-spacing: 1.2px; text-transform: uppercase; color: var(--g-ink-4, #8b86a8); }
.pf-composer .pf-seg { display: inline-flex; border: 1px solid var(--g-accent-45, rgba(140, 182, 161, .45)); }
.pf-composer .pf-seg button { appearance: none; background: none; border: 0; color: var(--g-ink-4, #8b86a8); font-family: inherit; font-size: var(--t-label); letter-spacing: .12em; text-transform: uppercase; padding: 8px 12px; cursor: pointer; min-height: 40px; }
.pf-composer .pf-seg button[aria-pressed="true"] { background: var(--gd-purple, var(--g-accent, #8cb6a1)); color: #fff; }
.pf-composer .pf-seg button:disabled { opacity: .4; cursor: default; }
.pf-thumbs { display: flex; gap: 8px; flex-wrap: wrap; }
.pf-thumb-img { position: relative; width: 72px; height: 72px; overflow: hidden; border: 1px solid var(--g-accent-45, rgba(140, 182, 161, .45)); }
.pf-thumb-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pf-thumb-img button { position: absolute; top: 2px; right: 2px; width: 20px; height: 20px; border-radius: 50%; border: 0; background: rgba(5, 6, 15, .8); color: #fff; font-size: var(--t-data); line-height: 20px; cursor: pointer; padding: 0; }
.pf-thumb-add {
  /* SIZE TO THE LABEL, not to a guess. It was a fixed 72px square holding "Camera or library"
     — seventeen characters at 8px with tracking — so the words sat outside the tile (operator
     2026-09-03: "post photos + CAMERA OR LIBRARY is like outside the bounds of the box").
     min-width keeps the tile square when the label is short, padding lets it grow when it is
     not, and the label is centred either way. */
  min-width: 72px; width: auto; height: 72px; padding: 0 8px; box-sizing: border-box;
  text-align: center; line-height: 1.25; border: 1px dashed var(--g-accent-6, rgba(140, 182, 161, .6)); background: none; color: var(--g-hi, #22683d); display: grid; place-items: center; gap: 2px; font-family: inherit; font-size: var(--t-label); letter-spacing: .08em; text-transform: uppercase; cursor: pointer; padding: 4px; }
.pf-thumb-add svg { width: 20px; height: 20px; stroke: currentColor; fill: none; stroke-width: 2; }

/* ── FX: toasts, unlock reveal, burst (2026-09-02, "game feel") ── */
.fx-toasts { position: fixed; left: 50%; bottom: calc(92px + env(safe-area-inset-bottom, 0px)); transform: translateX(-50%); z-index: 2500; display: grid; gap: 8px; pointer-events: none; width: min(420px, calc(100vw - 32px)); }
@media (min-width: 641px) { .fx-toasts { left: auto; right: 16px; bottom: 76px; transform: none; } }
.fx-toast { display: flex; align-items: center; gap: 8px; background: var(--g-surface-4, rgba(8, 9, 18, .94)); color: var(--g-ink, #e9e9f4); border: 1px solid var(--g-line-3, rgba(17, 17, 17, .35)); padding: 8px 12px; font-family: var(--game-mono); font-size: var(--t-label); letter-spacing: .06em; opacity: 0; transform: translateY(8px) scale(.98); transition: opacity var(--dur) var(--ease-out-expo), transform var(--dur) var(--ease-out-expo); box-shadow: 0 10px 30px rgba(0, 0, 0, .35); }
.fx-toast.is-in { opacity: 1; transform: none; }
.fx-toast.is-out { opacity: 0; transform: translateY(-6px); }
.fx-toast.is-bad { border-color: rgba(255, 143, 122, .6); color: #ffb4a6; }
.fx-toast.is-ok { border-color: rgba(127, 211, 155, .5); }
.fx-toast-ico { font-size: var(--t-body); line-height: 1; }
.fx-toast-txt { flex: 1; min-width: 0; }
.fx-toast-pts { color: #7fd39b; font-variant-numeric: tabular-nums; }
.fx-burst { position: fixed; inset: 0; z-index: 2600; pointer-events: none; }
.fx-unlock { position: fixed; inset: 0; z-index: 2400; background: rgba(5, 6, 15, .72); display: grid; place-items: center; padding: 16px; opacity: 0; transition: opacity var(--dur) var(--ease-out-expo); }
.fx-unlock.is-in { opacity: 1; }
.fx-unlock-card { width: min(360px, 100%); text-align: center; background: var(--g-surface, #0b0c1a); border: 1px solid var(--g-line-4, rgba(17, 17, 17, .4)); padding: 24px 20px 20px; color: var(--g-ink, #e9e9f4); font-family: var(--game-mono); transform: scale(.92) translateY(8px); transition: transform var(--dur) var(--ease-out-expo); }
.fx-unlock.is-in .fx-unlock-card { transform: none; }
.fx-unlock-card.is-silver { border-color: rgba(220, 220, 235, .55); } .fx-unlock-card.is-gold { border-color: rgba(255, 210, 110, .7); box-shadow: 0 0 40px rgba(255, 210, 110, .18); }
.fx-unlock-kicker { font-size: var(--t-label); letter-spacing: .3em; text-transform: uppercase; color: var(--g-hi, #22683d); }
.fx-unlock-ico { font-size: var(--t-d2); line-height: 1.2; margin: 8px 0 8px; filter: drop-shadow(0 0 18px rgba(17, 17, 17, .6)); }
.fx-unlock-name { font-size: var(--t-h3); letter-spacing: .06em; text-transform: uppercase; color: #fff; }
.fx-unlock-desc { font-size: var(--t-label); color: var(--g-ink-4, #8b86a8); margin-top: 8px; line-height: 1.5; }
.fx-unlock-meta { display: flex; justify-content: center; gap: 12px; margin-top: 8px; font-size: var(--t-label); letter-spacing: .14em; text-transform: uppercase; color: var(--g-ink-4, #8b86a8); }
.fx-unlock-meta b { color: #7fd39b; }
.fx-unlock-ok { margin-top: 16px; background: var(--gd-purple, var(--g-accent, #8cb6a1)); border: 1px solid var(--gd-purple, var(--g-accent, #8cb6a1)); color: #fff; font-family: inherit; font-size: var(--t-data); letter-spacing: 1.5px; text-transform: uppercase; padding: 8px 20px; cursor: pointer; min-height: 40px; }

/* ── continuity: consoles slide in, panes cross-fade, images fade in, skeletons instead of "Loading…" ── */
@keyframes gd-console-in { from { opacity: 0; transform: translate(-50%, 14px); } to { opacity: 1; transform: translate(-50%, 0); } }
.console.active { animation: gd-console-in .26s cubic-bezier(.2, .8, .2, 1) both; }
@media (max-width: 768px) { @keyframes gd-console-in { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } } }
@keyframes gd-pane-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
#profileConsole .pf-pane > * { animation: gd-pane-in .22s ease both; }
#profileConsole img[data-fade] { opacity: 0; transition: opacity var(--dur) var(--ease-out-expo); } #profileConsole img[data-fade].is-loaded { opacity: 1; }
.pf-skel { display: grid; gap: 12px; }
.pf-skel i { display: block; background: linear-gradient(90deg, var(--g-raise-3, rgba(255, 255, 255, .05)), var(--g-lip-3, rgba(255, 255, 255, .12)), var(--g-raise-3, rgba(255, 255, 255, .05))); background-size: 200% 100%; animation: pf-shimmer 1.2s linear infinite; border-radius: var(--g-r-ctl); }
@keyframes pf-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) { .console.active, #profileConsole .pf-pane > *, .fx-toast, .fx-unlock-card { animation: none; transition: none; } }

/* ── progression: the XP ring, the catalogue, the quest log ── */
#profileConsole .pf-ring circle { fill: none; stroke-width: 3; }
#profileConsole .pf-ring .bg { stroke: var(--g-accent-25, rgba(140, 182, 161, .25)); } #profileConsole .pf-ring .fg { stroke: var(--g-hi-2, #2c7d4c); stroke-linecap: round; transition: stroke-dashoffset .9s var(--ease-out-expo); }
#profileConsole .pf-avatar { overflow: visible; }
#profileConsole .pf-avatar > img, #profileConsole .pf-avatar-inner { border-radius: 50%; overflow: hidden; width: 100%; height: 100%; display: grid; place-items: center; box-shadow: inset 0 0 0 1px var(--g-line-3, rgba(17, 17, 17, .35)); }
#profileConsole .pf-next { font-size: var(--t-label); color: var(--g-ink-4, #8b86a8); letter-spacing: .06em; margin-top: 2px; }
#profileConsole .pf-next b { color: var(--g-hi, #22683d); }
#profileConsole .pf-cat { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }
#profileConsole .pf-ach { border: 1px solid var(--g-accent-35, rgba(140, 182, 161, .35)); background: var(--g-raise, rgba(255, 255, 255, .03)); padding: 8px; display: grid; gap: 4px; position: relative; }
#profileConsole .pf-ach.is-locked { opacity: .55; filter: grayscale(1); }
#profileConsole .pf-ach.is-locked:hover { opacity: .85; }
#profileConsole .pf-ach .ico { font-size: var(--t-h2); line-height: 1.1; }
#profileConsole .pf-ach b { font-size: var(--t-data); color: var(--g-ink, #e9e9f4); letter-spacing: .04em; }
#profileConsole .pf-ach small { font-size: var(--t-label); color: var(--g-ink-4, #8b86a8); line-height: 1.4; }
#profileConsole .pf-ach .tier { position: absolute; top: 8px; right: 8px; font-size: var(--t-label); letter-spacing: .14em; text-transform: uppercase; padding: 2px 8px; border: 1px solid var(--g-line-3, rgba(17, 17, 17, .35)); color: var(--g-hi, #22683d); }
#profileConsole .pf-ach.t-silver .tier { border-color: rgba(220, 220, 235, .5); color: #5b5b6b; } #profileConsole .pf-ach.t-gold .tier { border-color: rgba(255, 210, 110, .6); color: #7a5200; }
#profileConsole .pf-ach .bar { height: 4px; background: var(--g-accent-15, rgba(140, 182, 161, .15)); border-radius: var(--g-r-ctl); overflow: hidden; margin-top: 4px; }
#profileConsole .pf-ach .bar i { display: block; height: 100%; background: linear-gradient(90deg, var(--g-accent, #8cb6a1), var(--g-hi-2, #2c7d4c)); }
#profileConsole .pf-ach .pts { font-size: var(--t-label); color: #15603a; letter-spacing: .1em; }
#profileConsole .pf-quest { border: 1px solid rgba(127, 211, 155, .35); background: rgba(127, 211, 155, .06); padding: 8px 12px; display: grid; gap: 8px; }
/* THE SAME DARK PALETTE, ONE SURFACE OVER. The feed's captions were the loud case; the profile
   sheet carries the identical family — mint for a count, gold for a gold tier, silver for
   silver, red for danger — and every one of them was painting itself onto the white page at
   under 2.3:1. Each keeps its meaning and gains the weight it needs to be read. */
#profileConsole .pf-quest .q-title { font-size: var(--t-label); letter-spacing: .18em; text-transform: uppercase; color: #15603a; display: flex; justify-content: space-between; }
#profileConsole .pf-quest .q-title button { background: none; border: 0; color: var(--g-ink-4, #8b86a8); font-family: inherit; font-size: var(--t-label); cursor: pointer; }
#profileConsole .pf-quest ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
#profileConsole .pf-quest li { display: flex; align-items: center; gap: 8px; font-size: var(--t-data); color: var(--g-ink-2, #c9c4dc); cursor: pointer; }
#profileConsole .pf-quest li.is-done { color: var(--g-ink-6, #6f6a8a); text-decoration: line-through; }
#profileConsole .pf-quest li .q-box { width: 16px; height: 16px; border: 1px solid rgba(127, 211, 155, .6); display: grid; place-items: center; font-size: var(--t-label); color: #15603a; flex: none; }
#profileConsole .pf-feed { display: grid; gap: 4px; margin-top: 12px; }
#profileConsole .pf-feed div { display: flex; justify-content: space-between; font-size: var(--t-label); color: var(--g-ink-4, #8b86a8); border-bottom: 1px solid var(--g-accent-15, rgba(140, 182, 161, .15)); padding: 4px 0; }
#profileConsole .pf-feed b { color: #15603a; }

/* ── the bell: a notifications panel above the HUD ── */
.gd-notes { position: fixed; right: 16px; bottom: 76px; z-index: 1450; width: min(360px, calc(100vw - 32px)); background: var(--g-surface-2, rgba(8, 9, 18, .96)); border: 1px solid var(--g-line-3, rgba(17, 17, 17, .35)); box-shadow: 0 12px 40px rgba(0, 0, 0, .4); color: var(--g-ink, #e9e9f4); font-family: var(--game-mono); font-size: var(--t-label); display: grid; }
.gd-notes-head { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; border-bottom: 1px solid rgba(17, 17, 17, .2); letter-spacing: .14em; text-transform: uppercase; font-size: var(--t-label); color: var(--g-hi, #22683d); }
.gd-notes-head button { background: none; border: 0; color: var(--g-ink-4, #8b86a8); font-family: inherit; font-size: var(--t-label); letter-spacing: .1em; text-transform: uppercase; cursor: pointer; }
.gd-notes-list { max-height: 50vh; overflow-y: auto; }
.gd-note { padding: 8px 12px; border-bottom: 1px solid rgba(17, 17, 17, .12); display: grid; gap: 2px; cursor: pointer; }
.gd-note.is-unread { background: var(--g-accent-12, rgba(140, 182, 161, .12)); }
.gd-note b { color: #fff; font-weight: normal; letter-spacing: .04em; } .gd-note span { color: var(--g-ink-4, #8b86a8); } .gd-note small { color: #5c5878; font-size: var(--t-label); }
.gd-notes-empty { padding: 16px 12px; color: var(--g-ink-4, #8b86a8); }

/* ── the "+" create menu, and the game layer's own type (never the document's serif) ── */
#profileConsole, #gdHud, .gd-tabbar, .gd-notes, .fx-toast, .fx-unlock-card, .pf-crop-card, .pf-create-card { font-family: var(--game-sans); }
#profileConsole .console-header, #gdHud, .gd-notes, .fx-toast, .fx-unlock-kicker, .fx-unlock-meta, .pf-crop-title, .pf-crop-zoom, .pf-crop-hint, .pf-create-card small { font-family: var(--game-mono); }
#profileConsole .pf-plus { width: 40px; height: 40px; border-radius: 50%; background: var(--gd-purple, var(--g-accent, #8cb6a1)); border: 1px solid var(--gd-purple, var(--g-accent, #8cb6a1)); color: #fff; font: 700 22px/1 Inter, system-ui, sans-serif; cursor: pointer; margin-right: 8px; display: grid; place-items: center; box-shadow: 0 0 0 0 var(--g-lit-5, rgba(140, 182, 161, .5)); transition: transform var(--dur) var(--ease-out-expo), box-shadow var(--dur) var(--ease-out-expo); }
#profileConsole .pf-plus:hover { transform: scale(1.06); box-shadow: 0 0 0 6px var(--g-lit-18, rgba(140, 182, 161, .18)); }
#profileConsole .pf-plus[hidden] { display: none; }
.pf-create { position: fixed; inset: 0; z-index: 2300; background: var(--g-scrim, rgba(5, 6, 15, .55)); display: grid; place-items: end center; opacity: 0; transition: opacity var(--dur) var(--ease-out-expo); }
.pf-create.is-in { opacity: 1; }
.pf-create-card { width: min(520px, 100%); background: var(--g-surface, #0b0c1a); border: 1px solid var(--g-line-4, rgba(17, 17, 17, .4)); border-bottom: 0; border-radius: var(--g-r-panel) var(--g-r-panel) 0 0; padding: 12px 12px calc(14px + env(safe-area-inset-bottom, 0px)); display: grid; gap: 8px; position: relative; transform: translateY(24px); transition: transform var(--dur) var(--ease-out-expo); }
.pf-create.is-in .pf-create-card { transform: none; }
@media (min-width: 641px) { .pf-create { place-items: center; } .pf-create-card { border-radius: var(--g-r-panel); border-bottom: 1px solid var(--g-line-4, rgba(17, 17, 17, .4)); padding-bottom: 12px; } }
.pf-create-card [data-make] { display: flex; align-items: center; gap: 12px; text-align: left; background: var(--g-raise, rgba(255, 255, 255, .03)); border: 1px solid var(--g-line-2, rgba(17, 17, 17, .18)); color: var(--g-ink, #e9e9f4); padding: 12px 12px; cursor: pointer; font-family: inherit; border-radius: var(--g-r-card); min-height: 64px; transition: background var(--dur) var(--ease-out-expo), border-color var(--dur) var(--ease-out-expo); }
.pf-create-card [data-make]:hover, .pf-create-card [data-make]:focus-visible { background: var(--g-accent-18, rgba(140, 182, 161, .18)); border-color: var(--g-line-5, rgba(17, 17, 17, .5)); }
.pf-create-card .ico { width: 40px; height: 40px; border-radius: var(--g-r-card); background: var(--g-accent-25, rgba(140, 182, 161, .25)); display: grid; place-items: center; color: var(--g-hi, #22683d); flex: none; }
.pf-create-card .ico svg { width: 20px; height: 20px; stroke: currentColor; fill: none; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.pf-create-card b { display: block; font-size: var(--t-body); letter-spacing: .02em; }
.pf-create-card small { display: block; font-size: var(--t-label); color: var(--g-ink-4, #8b86a8); margin-top: 2px; line-height: 1.4; }
.pf-create-x { position: absolute; top: -44px; right: 0; width: 36px; height: 36px; border-radius: 50%; background: rgba(8, 9, 18, .9); border: 1px solid var(--g-line-4, rgba(17, 17, 17, .4)); color: #fff; font-size: var(--t-h3); cursor: pointer; }
@media (min-width: 641px) { .pf-create-x { top: -14px; right: -14px; } }
/* the profile console slides in as one piece; animate the INNER so a site's own .console transform is untouched */
.console.active { animation: none; }
@keyframes gd-inner-in { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
#profileConsole.active .console-inner { animation: gd-inner-in .26s cubic-bezier(.2, .8, .2, 1) both; }

/* ── panel pass (2026-09-02): the HUD as a right-edge rail, a scrim, masks, custom controls, two columns ── */
@media (min-width: 641px) {
  /* CENTRED BY LAYOUT, NOT BY TRANSFORM. `top:50%; translateY(-50%)` on an element whose height
     is fractional lands the whole rail on a half pixel — measured live 2026-09-04:
     translateY(-183.32px) on a 366.64px-tall rail, on EVERY page of both sites. A composited
     layer at a fractional offset smears its text at 1x and looks perfect at 2x, which is exactly
     what the operator reported: "blurry on one of my screens but not on the sharper mac screen".
     inset + margin auto is the same centring the consoles already use, and layout positions get
     pixel-snapped for text rasterisation where transforms do not. */
  #gdHud { right: 12px; top: 0; bottom: 0; margin-block: auto; height: fit-content; transform: none; flex-direction: column; padding: 8px; gap: 2px; border-radius: var(--g-r-panel); }
  #gdHud .gd-hud-btn { flex-direction: column; gap: 4px; padding: 8px 8px; min-width: 64px; font-size: var(--t-label); letter-spacing: .1em; text-align: center; text-transform: uppercase; }
  #gdHud .gd-hud-btn svg { width: 19px; height: 19px; }
  #gdHud .gd-hud-sep { width: auto; height: 1px; margin: 4px 8px; }
  #gdHud .gd-hud-name { align-items: center; }
  #gdHud .gd-hud-n { position: absolute; top: 4px; right: 6px; }
  #gdHud .gd-hud-btn { position: relative; }
  .gd-notes { right: 88px; bottom: auto; top: 50%; transform: translateY(-50%); }
  .fx-toasts { right: 88px; bottom: 24px; }
}
.pf-scrim { position: fixed; inset: 0; z-index: 999; background: var(--g-scrim-2, rgba(5, 6, 15, .62)); -webkit-backdrop-filter: blur(10px) saturate(90%); backdrop-filter: blur(10px) saturate(90%); animation: gd-scrim-in .25s ease both; }
.pf-scrim[hidden] { display: none; }
/* the scrim fades in over 250ms; under a refusal of motion it should simply be there */
@media (prefers-reduced-motion: reduce) {
  .pf-scrim, .pf-scrim * { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}
@keyframes gd-scrim-in { from { opacity: 0; } to { opacity: 1; } }
#profileConsole .pf-close { width: 44px; height: 44px; border-radius: 50%; background: var(--g-raise-2, rgba(255, 255, 255, .04)); border: 1px solid var(--g-line-3, rgba(17, 17, 17, .35)); color: var(--g-hi, #22683d); cursor: pointer; display: grid; place-items: center; padding: 0; }
#profileConsole .pf-close svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; }
#profileConsole .pf-close:hover { background: var(--g-accent-25, rgba(140, 182, 161, .25)); color: #fff; }
#profileConsole .pf-plus { width: auto; min-width: 44px; height: 44px; border-radius: var(--g-r-panel); padding: 0 12px 0 8px; gap: 8px; display: inline-flex; align-items: center; font: 700 14px/1 Inter, system-ui, sans-serif; letter-spacing: .12em; text-transform: uppercase; }
#profileConsole .pf-plus-glyph { font-size: var(--t-h3); line-height: 1; }
@media (max-width: 640px) { #profileConsole .pf-plus { padding: 0; width: 44px; } #profileConsole .pf-plus-word { display: none; } }
#profileConsole .pf-tabs { -webkit-mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 28px), transparent); mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 28px), transparent); }
@media (min-width: 641px) { #profileConsole .pf-tabs { -webkit-mask-image: none; mask-image: none; } }
/* .gh-tickwrap IS NOT OURS TO STYLE, and claiming it here silently killed two fixes in index.html.
   Operator, 2026-09-25: "ticker cuts off halfway midrun on the mobile sbi."
   This rule used to read `.gd-tickwrap, .gh-tickwrap` and applied an 8%/92% ramp to both. .gh-tickwrap
   is the seed bank's masthead marquee, and index.html styles it twice on purpose:
     · a FIXED 76px ramp, because a percentage ramp scales with the container — on a 390px phone the
       frame is 336px, so 8% is a 27px fade, narrower than one cultivar name at 14px, and names cut
       off mid-glyph instead of dissolving. That is the operator's 2026-08-23 report, and the fix
       carries a six-line comment explaining itself.
     · `mask-image: none` below 700px.
   Both are `.gh-tickwrap` — specificity 0,1,0, identical to this rule — and the inline <style> closes
   at index.html:999 while game.css is linked at :1015. Later wins. So the 76px ramp and the mobile
   opt-out have BOTH been dead for as long as this selector has existed, while the comment upstairs
   went on describing a cure that was not in effect. Measured in a real browser at 390px before this
   line changed: computed mask-image was `rgb(0,0,0) 8%`, never 76px, never none.
   The lesson is the shape, not the pixel: a shared stylesheet that reaches for another surface's
   class overrides that surface's own decisions from a file its author is not reading. */
.gd-tickwrap { -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
.kingdom-bar .kingdom-btn { background: rgba(8, 9, 18, .72) !important; -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); }
@media (max-width: 1100px) { .kingdom-bar { flex-wrap: nowrap !important; overflow-x: auto; justify-content: flex-start !important; scrollbar-width: none; -webkit-mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 28px), transparent); mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 28px), transparent); } .kingdom-bar::-webkit-scrollbar { display: none; } .kingdom-bar .kingdom-btn { flex: 0 0 auto; } }
#profileConsole .pf-max { display: inline-block; font-size: var(--t-label); letter-spacing: .2em; padding: 2px 8px; border: 1px solid rgba(255, 210, 110, .6); color: #7a5200; margin-right: 8px; vertical-align: middle; text-transform: uppercase; }
#profileConsole .pf-sheet.is-max .pf-xp i { background: linear-gradient(90deg, #ffd26e, #fff2c2); }
#profileConsole .pf-status { display: inline-flex; align-items: center; gap: 8px; flex: 1; min-width: 0; background: var(--g-raise-2, rgba(255, 255, 255, .04)); border: 1px solid var(--g-accent-4, rgba(140, 182, 161, .4)); color: var(--g-ink, #e9e9f4); font-family: inherit; font-size: var(--t-label); letter-spacing: .08em; text-transform: uppercase; padding: 0 8px; min-height: 34px; cursor: pointer; border-radius: var(--g-r-ctl); }
#profileConsole .pf-status i, .pf-menu i { width: 7px; height: 7px; border-radius: 50%; background: #7fd39b; flex: none; }
.pf-menu { position: fixed; z-index: 2350; min-width: 170px; background: var(--g-surface, #0b0c1a); border: 1px solid var(--g-line-4, rgba(17, 17, 17, .4)); box-shadow: 0 12px 40px rgba(0, 0, 0, .5); display: grid; padding: 4px; font-family: Inter, system-ui, sans-serif; }
.pf-menu [role="option"] { display: flex; align-items: center; gap: 8px; background: none; border: 0; color: var(--g-ink, #e9e9f4); font: 12px/1 Inter, system-ui, sans-serif; letter-spacing: .08em; text-transform: uppercase; padding: 8px 8px; cursor: pointer; text-align: left; border-radius: var(--g-r-ctl); min-height: 36px; }
.pf-menu [role="option"]:hover, .pf-menu [role="option"][aria-selected="true"] { background: var(--g-accent-25, rgba(140, 182, 161, .25)); }
#profileConsole .pf-vis { cursor: pointer; display: inline-flex; align-items: center; gap: 4px; font-family: inherit; }
#profileConsole .pf-vis svg { width: 11px; height: 11px; stroke: currentColor; fill: none; stroke-width: 2; }
#profileConsole .pf-card-img.is-empty { flex-direction: column; gap: 4px; color: var(--g-ink-4, #8b86a8); font-size: var(--t-label); letter-spacing: .12em; text-transform: uppercase; }
#profileConsole .pf-card-img.is-empty svg { width: 26px; height: 26px; }
#profileConsole .pf-card-img.is-empty:hover { color: var(--g-hi, #22683d); background: var(--g-accent-16, rgba(140, 182, 161, .16)); }
#profileConsole .pf-foot { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; padding-bottom: 4px;
  border-top: 1px solid var(--g-line, rgba(194,163,255,.16)); padding-top: 12px; }
#profileConsole .pf-foot .pf-btn { font-size: var(--t-label); letter-spacing: .1em; padding: 8px 12px; min-height: 34px; opacity: .85; }
#profileConsole .pf-foot .pf-btn:hover, #profileConsole .pf-foot .pf-btn:focus-visible { opacity: 1; }
@media (min-width: 1000px) {
  #profileConsole .pf-sheet { grid-template-columns: 340px 1fr; gap: 20px; align-items: start; }
  #profileConsole .pf-side { display: grid; gap: 12px; position: sticky; top: 0; }
  #profileConsole .pf-main { min-width: 0; }
  #profileConsole .pf-id { flex-direction: column; align-items: center; text-align: center; }
  #profileConsole .pf-who { display: grid; justify-items: center; }
  #profileConsole .pf-level { width: 100%; }
  #profileConsole .pf-grid { grid-template-columns: repeat(3, 1fr); }
  #profileConsole .pf-pane { max-height: 70vh; }
  #profileConsole .pf-avatar-edit { left: 50%; transform: translateX(-50%); }
}
/* the rail owns the right 112px of the viewport: page containers may not extend under it. A wide
   screen is unaffected (its containers never reach that far); a tablet or a 1280px desktop with a
   1200px frame is pulled in so no control at the right edge is ever covered (panel finding #1). */
body.has-rail main, body.has-rail .frame, body.has-rail .footer, body.has-rail > footer { max-width: calc(100vw - var(--rail-w, 90px) - 36px) !important; margin-left: 8px !important; margin-right: calc(var(--rail-w, 90px) + 24px) !important; }
/* ...but ONLY ONCE. On SBI, .frame is a DESCENDANT of main, so both selectors above match and
   the rail reservation is applied TWICE, nested. The inner frame then exceeds the space its own
   margins leave it and main's align-items:center centres the overflow, pushing its left edge
   NEGATIVE — content is clipped off the left of the screen.
   Measured live 2026-09-04, .frame left edge: home -34px at 768, 1024 AND 1280 (a stock MacBook
   width); registry -34 at 768, -44 at 1024. At 1024 the registry lost the first character of
   every heading — "36,041 TOTAL ACCESSIONS" read as "041 TOTAL ACCESSIONS", a different and
   smaller number — while a wide dead gutter sat on the right.
   An inner .frame must not reserve rail space again; main already did. Inert on GD, which has
   no .frame element at any width (measured, not assumed). Found by the parallel SBI session. */
body.has-rail main .frame { max-width: var(--measure) !important; margin-left: auto !important; margin-right: auto !important; }
/* 2026-09-24: that was `max-width: 100%`, which kept the rail from being reserved twice (the
   point of the rule) but also let the rail decide the page width. With the rail open at 1440 the
   strain record measured 1292px against 1152px everywhere else — the one measure, lost to a
   layout the reader did not ask for. The measure caps it and auto margins centre it in whatever
   main has left; below 1152 of remaining space the cap simply does not bind, which is the old
   behaviour. Rail space is still reserved exactly once, by main. */
@media (min-width: 641px) {
  /* the rail is compact and fixed-width: a long name never widens it into the page */
  #gdHud { width: 92px; box-sizing: border-box; }
  #gdHud .gd-hud-btn { width: 100%; min-width: 0; padding-left: 4px; padding-right: 4px; }
  #gdHud .gd-hud-name { max-width: 100%; align-items: center; }
  #gdHud .gd-hud-name > span, #gdHud .gd-hud-name small { display: block; max-width: 76px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: center; }
  #gdHud .gd-hud-bar { width: 56px; }
}
@media (min-width: 641px) and (max-width: 1100px) { #gdHud .gd-hud-btn { min-width: 56px; padding: 8px 8px; } }

/* ── accessibility seat (2026-09-02): contrast, target sizes, label size, card row wrap ── */
#profileConsole .pf-stat span, #profileConsole .pf-lv, #profileConsole .pf-next, #profileConsole .pf-lbl, #profileConsole .pf-row .pf-k, #profileConsole .pf-row .pf-t small, #profileConsole .pf-card-body small, #profileConsole .pf-hint, #profileConsole .pf-empty, #profileConsole .pf-who .pf-handle, #profileConsole .pf-ach small, #profileConsole .pf-feed div, #profileConsole .pf-quest li.is-done, #profileConsole .cm-meta, .fx-unlock-desc, .fx-unlock-meta, .pf-crop-hint, .pf-crop-zoom, .pf-create-card small, .gd-note span { color: var(--g-ink-3, #a8a3c6); }
#profileConsole .pf-quest li .q-box { width: 24px; height: 24px; }
#profileConsole .pf-quest li { min-height: 32px; }
#profileConsole .pf-avatar-edit { font-size: var(--t-label); letter-spacing: .1em; padding: 8px 0 8px; top: 50px; }
#profileConsole .pf-vis { min-height: 26px; padding: 4px 8px; font-size: var(--t-label); }
#profileConsole .pf-card-foot { flex-wrap: wrap; }
#profileConsole .pf-card-foot .pf-status { flex: 1 1 100%; }
#profileConsole .pf-ico { width: 40px; height: 40px; }

/* the splash text is centred whatever the host page's body does (SBI's flex body left-aligned it on the phone) */
#gdBoot .gd-boot-word, #gdBoot .gd-boot-sub, #gdBoot .gd-boot-hint { width: 100%; text-align: center; box-sizing: border-box; padding: 0 16px; }
#gdBoot .gd-boot-hint { left: 0; right: 0; }
/* the page is hidden from the FIRST paint when a splash is about to run (html.gd-booting is set by an inline snippet) */
html.gd-booting body > *:not(#gdBoot) { visibility: hidden; }

/* ── the feed (2026-09-02): the community as a feed on the game shell — cards, threads, nested comments, the inbox ── */
#feedConsole, .fd-write, .fd-why { font-family: var(--game-sans); }
#feedConsole .console-header, .pf-crop-title { font-family: var(--game-mono); }
/* THE SHELL — for both consoles. Measured 2026-09-02: every generated sub-page (GD strain pages,
   community/grow/disclosure, SBI registry pages) carries ZERO .console rules, so the profile console
   had no geometry there at all; and each home page's own .console (GD: a bottom sheet with !important
   geometry; SBI: a centred card faded in by opacity) won on some property and broke the panel. So the
   game layer carries the complete geometry itself, at higher priority: one dark bottom sheet, the same
   on every page of both sites. */
#profileConsole.console, #feedConsole.console { display: none !important; position: fixed !important; inset: 0 !important;  transform: none !important; width: min(980px, 94vw) !important; max-width: min(980px, 94vw) !important; height: fit-content !important; max-height: min(88vh, 940px) !important; margin: auto !important; box-sizing: border-box !important; overflow-y: auto !important; -webkit-overflow-scrolling: touch; z-index: 1000 !important; opacity: 1 !important; pointer-events: auto !important; transition: none !important var(--dur) var(--ease-out-expo); background: var(--g-surface-3, rgba(8, 9, 18, .97)) !important; color: var(--g-ink, #e9e9f4) !important; border: 1px solid rgba(17, 17, 17, .3) !important; border-bottom: 0 !important; border-radius: var(--g-r-panel) var(--g-r-panel) 0 0 !important; box-shadow: 0 -20px 80px rgba(0, 0, 0, .6) !important; padding: 14px 16px calc(76px + env(safe-area-inset-bottom, 0px)) !important; font-size: var(--t-data); line-height: 1.5; }
#profileConsole.console.active, #feedConsole.console.active { display: block !important; }
@media (max-width: 640px) { #profileConsole.console, #feedConsole.console { inset: 0 !important; margin: 0 !important; transform: none !important; width: 100vw !important; max-width: 100vw !important; height: 100dvh !important; max-height: 100dvh !important; border: 0 !important; border-radius: 0 !important; padding: 10px 12px calc(76px + env(safe-area-inset-bottom, 0px)) !important; } }
#feedConsole [hidden] { display: none !important; }
#feedConsole .console-inner { display: grid; gap: 8px; max-width: 720px; margin: 0 auto; }
#feedConsole.active .console-inner { animation: gd-inner-in .26s cubic-bezier(.2, .8, .2, 1) both; }
#feedConsole .console-header { display: flex; align-items: center; gap: 8px; color: var(--g-hi, #22683d); border-bottom: 1px solid var(--g-accent-35, rgba(140, 182, 161, .35)); padding-bottom: 8px; letter-spacing: .08em; text-transform: uppercase; font-size: var(--t-data); }
#feedConsole .fd-hbtn, #feedConsole .pf-close { width: 44px; height: 44px; border-radius: 50%; background: var(--g-raise-2, rgba(255, 255, 255, .04)); border: 1px solid var(--g-line-3, rgba(17, 17, 17, .35)); color: var(--g-hi, #22683d); cursor: pointer; display: grid; place-items: center; padding: 0; position: relative; flex: none; }
#feedConsole .fd-hbtn svg, #feedConsole .pf-close svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
#feedConsole .fd-hbtn:hover, #feedConsole .pf-close:hover { background: var(--g-accent-25, rgba(140, 182, 161, .25)); color: #fff; }
#feedConsole .fd-hbtn.is-warn { color: #a3281a; border-color: rgba(255, 143, 122, .5); }
#feedConsole .fd-hbtn i { position: absolute; top: -2px; right: -2px; min-width: 18px; height: 18px; border-radius: var(--g-r-ctl); background: #7fd39b; color: #05060f; font: 700 12px/18px Inter, system-ui, sans-serif; text-align: center; font-style: normal; padding: 0 4px; }
#feedConsole .fd-new { height: 44px; border-radius: var(--g-r-panel); padding: 0 12px 0 8px; display: inline-flex; align-items: center; gap: 8px; background: var(--gd-purple, var(--g-accent, #8cb6a1)); border: 1px solid var(--gd-purple, var(--g-accent, #8cb6a1)); color: #fff; font: 700 12px/1 Inter, system-ui, sans-serif; letter-spacing: .12em; text-transform: uppercase; cursor: pointer; flex: none; }
#feedConsole .fd-new svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 2.2; stroke-linecap: round; }
@media (max-width: 640px) { #feedConsole .fd-new { width: 44px; padding: 0; justify-content: center; } #feedConsole .fd-new span { display: none; } }
#feedConsole .fd-body { display: grid; gap: 8px; }
#feedConsole .fd-signin { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; border: 1px solid var(--g-accent-45, rgba(140, 182, 161, .45)); padding: 12px 12px; font-size: var(--t-data); color: var(--g-ink-2, #c9c4dc); line-height: 1.5; }
#feedConsole .fd-signin span { flex: 1; min-width: 200px; }
#feedConsole .fd-compose { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left; background: var(--g-raise, rgba(255, 255, 255, .03)); border: 1px solid rgba(17, 17, 17, .22); color: var(--g-ink-4, #8b86a8); padding: 8px 12px; border-radius: var(--g-r-panel); cursor: text; font: 16px/1.3 Inter, system-ui, sans-serif; min-height: 56px; }
#feedConsole .fd-compose:hover, #feedConsole .fd-compose:focus-visible { border-color: var(--g-line-5, rgba(17, 17, 17, .5)); color: var(--g-ink-2, #c9c4dc); }
#feedConsole .fd-compose-hint { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#feedConsole .fd-compose svg { width: 22px; height: 22px; stroke: var(--g-hi, #22683d); fill: none; stroke-width: 2.2; stroke-linecap: round; flex: none; }
#feedConsole .fd-tabs { display: flex; align-items: center; gap: 2px; border-bottom: 1px solid var(--g-accent-35, rgba(140, 182, 161, .35)); overflow-x: auto; scrollbar-width: none; }
#feedConsole .fd-tabs::-webkit-scrollbar { display: none; }
/* The row scrolls, and with the scrollbar hidden a clipped last label reads as broken rather
   than as "there is more". A mask on the trailing edge says scrollable without adding chrome:
   it fades only when the row actually overflows, because mask-image on a row that fits would
   dim a perfectly visible tab for no reason. */
#feedConsole .fd-tabs { scroll-snap-type: x proximity; scroll-padding-left: 2px; }
#feedConsole .fd-tabs > * { scroll-snap-align: start; }
@supports (mask-image: linear-gradient(#000, #000)) {
  #feedConsole .fd-tabs.is-scrollable {
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 34px), transparent 100%);
    mask-image: linear-gradient(90deg, #000 calc(100% - 34px), transparent 100%);
  }
}
#feedConsole .fd-tab { background: none; border: 0; border-bottom: 2px solid transparent; color: var(--g-ink-5, #8a85a6); font: 600 14px/1 Inter, system-ui, sans-serif; letter-spacing: .1em; text-transform: uppercase; padding: 12px 12px; cursor: pointer; margin-bottom: -1px; min-height: 44px; white-space: nowrap; flex: none; }
#feedConsole .fd-tab[aria-selected="true"] { color: var(--g-ink, #e9e9f4); border-bottom-color: var(--gd-purple, var(--g-accent, #8cb6a1)); }
#feedConsole .pf-seg { display: inline-flex; border: 1px solid var(--g-accent-4, rgba(140, 182, 161, .4)); border-radius: var(--g-r-ctl); overflow: hidden; flex: none; margin-left: 8px; }
#feedConsole .pf-seg button { background: none; border: 0; color: var(--g-ink-5, #8a85a6); font: 600 12px/1 Inter, system-ui, sans-serif; letter-spacing: .1em; text-transform: uppercase; padding: 0 8px; min-height: 32px; cursor: pointer; }
#feedConsole .pf-seg button[aria-pressed="true"] { background: var(--g-accent-35, rgba(140, 182, 161, .35)); color: #fff; }
#feedConsole .fd-count { font-size: var(--t-label); letter-spacing: .14em; text-transform: uppercase; color: var(--g-ink-3, #a8a3c6); }
#feedConsole .fd-list { display: grid; gap: 8px; }
#feedConsole .fd-card { display: grid; gap: 8px; background: rgba(255, 255, 255, .025); border: 1px solid var(--g-line, rgba(17, 17, 17, .16)); border-radius: var(--g-r-panel); padding: 12px 12px; transition: border-color var(--dur) var(--ease-out-expo), opacity var(--dur) var(--ease-out-expo); }
#feedConsole .fd-card:hover { border-color: rgba(17, 17, 17, .34); }
#feedConsole .fd-card.is-hiding, #feedConsole .fd-comment.is-hiding { opacity: .35; }
#feedConsole .fd-head { display: flex; align-items: center; gap: 8px; }
#feedConsole .fd-avatar { width: 38px; height: 38px; border-radius: 50%; background: var(--g-accent-3, rgba(140, 182, 161, .3)); border: 1px solid var(--g-line-4, rgba(17, 17, 17, .4)); color: #fff; display: grid; place-items: center; font: 700 16px/1 Inter, system-ui, sans-serif; flex: none; }
#feedConsole .fd-avatar.sm { width: 28px; height: 28px; font-size: var(--t-label); }
#feedConsole .fd-avatar.is-else { background: rgba(255, 255, 255, .06); color: var(--g-ink-2, #c9c4dc); }
#feedConsole .fd-who { flex: 1; min-width: 0; display: grid; gap: 2px; }
#feedConsole .fd-who b { font-size: var(--t-body); color: var(--g-ink, #e9e9f4); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#feedConsole .fd-who small { font-size: var(--t-label); color: var(--g-ink-3, #a8a3c6); display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
#feedConsole .fd-tag { display: inline-flex; align-items: center; gap: 4px; color: #15603a; }
#feedConsole .fd-tag svg, #feedConsole .fd-cults svg { width: 12px; height: 12px; stroke: currentColor; fill: none; stroke-width: 1.8; stroke-linecap: round; }
#feedConsole .fd-seal { font-size: var(--t-label); letter-spacing: .1em; text-transform: uppercase; color: #15603a; border: 1px solid rgba(127, 211, 155, .5); padding: 1px 8px; border-radius: var(--g-r-ctl); }
#feedConsole .fd-seal.is-gold { color: #7a5200; border-color: rgba(255, 210, 110, .6); }
#feedConsole .fd-score { font: 700 12px/1 var(--game-mono); color: var(--g-hi, #22683d); border: 1px solid var(--g-line-4, rgba(17, 17, 17, .4)); padding: 4px 8px; border-radius: var(--g-r-ctl); flex: none; }
#feedConsole .fd-title { margin: 0; font-size: var(--t-body); line-height: 1.3; color: var(--g-ink, #e9e9f4); cursor: pointer; }
#feedConsole [data-open]:focus-visible, #feedConsole .fd-tab:focus-visible, #feedConsole .fd-act:focus-visible { outline: 2px solid var(--g-hi, #22683d); outline-offset: 2px; border-radius: var(--g-r-ctl); }
#feedConsole .fd-photos { display: grid; gap: 4px; border-radius: var(--g-r-card); overflow: hidden; }
#feedConsole .fd-photos.n1 { grid-template-columns: 1fr; }
#feedConsole .fd-photos.n2, #feedConsole .fd-photos.n4 { grid-template-columns: 1fr 1fr; }
#feedConsole .fd-photos.n3 { grid-template-columns: 2fr 1fr; grid-template-rows: 1fr 1fr; }
#feedConsole .fd-photos.n3 > :first-child { grid-row: 1 / 3; }
#feedConsole .fd-photos > * { display: block; position: relative; aspect-ratio: 1; background: var(--g-raise-2, rgba(255, 255, 255, .04)); cursor: pointer; }
#feedConsole .fd-photos.n1 > * { aspect-ratio: 4 / 3; }
#feedConsole .is-full .fd-photos { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
#feedConsole .is-full .fd-photos > * { aspect-ratio: 1; }
#feedConsole .is-full .fd-photos > :first-child { grid-column: 1 / -1; aspect-ratio: 4 / 3; }
#feedConsole .fd-photos img { width: 100%; height: 100%; object-fit: cover; display: block; }
#feedConsole .fd-photos b { position: absolute; inset: 0; display: grid; place-items: center; background: var(--g-scrim, rgba(5, 6, 15, .55)); color: #fff; font-size: var(--t-h3); }
/* A DARK THEME'S PALETTE ON A WHITE PAGE. Measured live 2026-09-19: this rendered #d9d5ea on
   white — 1.43:1, against the 4.5:1 a body of text needs — so every caption a grower wrote was
   the least readable thing on the page. #a0e6b9, #7fd39b, #ffd26e and #ff8f7a are the same
   family; each keeps its meaning and gains the weight that meaning needs on white. */
#feedConsole .fd-text { font-size: var(--t-body); line-height: 1.55; color: #1f1f1f; overflow-wrap: anywhere; }
#feedConsole .fd-text p { margin: 0 0 8px; }
#feedConsole .fd-text p:last-child { margin-bottom: 0; }
#feedConsole .fd-text.is-clamp { display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical; overflow: hidden; cursor: pointer; }
#feedConsole .fd-text a, #feedConsole .cm-link { color: #15603a; text-decoration: underline; text-decoration-color: rgba(21, 96, 58, .45); text-underline-offset: 2px; }
#feedConsole .fd-log { display: flex; flex-wrap: wrap; gap: 8px 16px; font-size: var(--t-data); color: var(--g-ink-5, #8a85a6); border-top: 1px solid var(--g-accent-2, rgba(140, 182, 161, .2)); padding-top: 8px; }
#feedConsole .fd-log b { color: var(--g-ink, #e9e9f4); font-variant-numeric: tabular-nums; }
#feedConsole .fd-smoke { flex-basis: 100%; color: var(--g-ink-2, #c9c4dc); line-height: 1.5; }
#feedConsole .fd-foot, #feedConsole .fd-cact { display: flex; align-items: center; gap: 4px; }
/* 44px, not 36. .fd-act IS the vote button — the control the operator could not hit on the
   first community post. That turned out to be pool exhaustion, but the target was undersized
   too, and a 36px vote button on a phone is its own reason to miss. .fd-reply .pf-btn below
   already used 44, so the standard was known here and applied unevenly. */
#feedConsole .fd-act { display: inline-flex; align-items: center; gap: 8px; background: none; border: 1px solid transparent; color: var(--g-ink-5, #8a85a6); font: 600 14px/1 Inter, system-ui, sans-serif; padding: 0 8px; min-height: 44px; border-radius: var(--g-r-panel); cursor: pointer; }
#feedConsole .fd-act svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
#feedConsole .fd-act:hover { background: var(--g-raise-3, rgba(255, 255, 255, .05)); color: var(--g-ink, #e9e9f4); }
#feedConsole .fd-act[aria-pressed="true"] { color: var(--g-hi, #22683d); }
#feedConsole .fd-act[aria-pressed="true"] svg { fill: currentColor; }
#feedConsole .fd-act.is-danger { color: #a3281a; margin-left: auto; }
#feedConsole .fd-act.is-danger:hover { background: rgba(255, 143, 122, .12); }
#feedConsole .fd-act[data-report] { margin-left: auto; }
#feedConsole .fd-else { text-decoration: none; color: inherit; }
#feedConsole .fd-else > .fd-head > svg { width: 18px; height: 18px; stroke: var(--g-ink-5, #8a85a6); fill: none; stroke-width: 1.8; flex: none; }
#feedConsole .fd-cults { display: flex; align-items: center; gap: 8px; font-size: var(--t-data); color: #15603a; }
#feedConsole .fd-comments { display: grid; gap: 8px; }
#feedConsole .fd-reply { display: flex; align-items: center; gap: 8px; }
#feedConsole .fd-reply input { flex: 1; min-width: 0; background: var(--g-raise-2, rgba(255, 255, 255, .04)); border: 1px solid var(--g-accent-5, rgba(140, 182, 161, .5)); color: var(--g-ink, #e9e9f4); padding: 0 12px; font: 16px/1 Inter, system-ui, sans-serif; min-height: 44px; border-radius: var(--g-r-panel); }
#feedConsole .fd-reply .pf-btn { background: var(--gd-purple, var(--g-accent, #8cb6a1)); border: 1px solid var(--gd-purple, var(--g-accent, #8cb6a1)); color: #fff; font: 700 12px/1 Inter, system-ui, sans-serif; letter-spacing: .1em; text-transform: uppercase; padding: 0 12px; min-height: 44px; border-radius: var(--g-r-panel); cursor: pointer; display: inline-flex; align-items: center; gap: 8px; }
#feedConsole .fd-reply .pf-btn svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2; }
#feedConsole .fd-reply .pf-btn:disabled { opacity: .5; }
#feedConsole .fd-reply.is-inline { margin-top: 8px; }
#feedConsole .fd-comment { display: flex; gap: 8px; padding: 8px 0 0; }
#feedConsole .fd-cbody { flex: 1; min-width: 0; display: grid; gap: 4px; border-left: 2px solid var(--g-accent-22, rgba(140, 182, 161, .22)); padding-left: 8px; }
#feedConsole .fd-cwho b { font-size: var(--t-data); color: var(--g-ink, #e9e9f4); }
#feedConsole .fd-cwho small { font-size: var(--t-label); color: var(--g-ink-3, #a8a3c6); margin-left: 8px; }
#feedConsole .fd-comment .fd-text { font-size: var(--t-data); }
#feedConsole .fd-kids { display: grid; }
#feedConsole .fd-kids .fd-comment { margin-left: 0; }
#feedConsole .fd-kids .fd-kids .fd-kids .fd-cbody { border-left-color: var(--g-accent-1, rgba(140, 182, 161, .1)); }
#feedConsole .fd-answers { color: #15603a; margin-left: 8px; }
#feedConsole .fd-reply-to { font-size: var(--t-label); color: #15603a; white-space: nowrap; flex: none; }
#feedConsole .fd-blurb { margin: 0; font-size: var(--t-data); color: var(--g-ink-3, #a8a3c6); line-height: 1.5; }
#feedConsole .fd-out { display: inline-flex; align-items: center; gap: 4px; font-size: var(--t-label); letter-spacing: .08em; text-transform: uppercase; color: var(--g-ink-3, #a8a3c6); white-space: nowrap; }
#feedConsole .fd-out svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 1.8; }
.fd-write-hint { margin: 0; font-size: var(--t-label); color: var(--g-ink-3, #a8a3c6); line-height: 1.5; }
.fx-toast.fd-undo .fd-undo-btn { margin-left: 8px; background: none; border: 1px solid var(--g-line-5, rgba(17, 17, 17, .5)); color: var(--g-hi, #22683d); font: 700 12px/1 Inter, system-ui, sans-serif; letter-spacing: .1em; text-transform: uppercase; padding: 8px 8px; border-radius: var(--g-r-ctl); cursor: pointer; min-height: 28px; }
#feedConsole .fd-convo { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left; background: rgba(255, 255, 255, .025); border: 1px solid var(--g-line, rgba(17, 17, 17, .16)); color: inherit; padding: 8px 12px; border-radius: var(--g-r-card); cursor: pointer; font-family: inherit; }
#feedConsole .fd-convo .fd-cbody { border: 0; padding: 0; }
#feedConsole .fd-convo b { font-size: var(--t-body); color: var(--g-ink, #e9e9f4); display: flex; align-items: center; gap: 8px; }
#feedConsole .fd-convo small { font-size: var(--t-data); color: var(--g-ink-5, #8a85a6); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
#feedConsole .fd-when { color: var(--g-ink-3, #a8a3c6); font-size: var(--t-label); flex: none; }
#feedConsole .fd-dot { width: 8px; height: 8px; border-radius: 50%; background: #7fd39b; display: inline-block; }
#feedConsole .fd-dm { display: grid; gap: 8px; max-height: 55vh; overflow-y: auto; padding: 4px 0; }
#feedConsole .fd-msg { max-width: 78%; justify-self: start; background: var(--g-raise-3, rgba(255, 255, 255, .05)); border-radius: var(--g-r-panel) 14px 14px 4px; padding: 8px 12px; font-size: var(--t-body); line-height: 1.45; display: grid; gap: 2px; }
#feedConsole .fd-msg.is-mine { justify-self: end; background: var(--g-accent-35, rgba(140, 182, 161, .35)); border-radius: var(--g-r-panel) 14px 4px 14px; }
#feedConsole .fd-msg small { font-size: var(--t-label); color: var(--g-ink-3, #a8a3c6); }
#feedConsole .pf-empty { font-size: var(--t-data); color: var(--g-ink-5, #8a85a6); padding: 16px 0; line-height: 1.6; text-align: center; }
/* #fdSignin is this button: the single conversion action for a signed-out visitor, 4px short. */
#feedConsole .pf-btn { background: var(--gd-purple, var(--g-accent, #8cb6a1)); border: 1px solid var(--gd-purple, var(--g-accent, #8cb6a1)); color: #fff; font: 700 12px/1 Inter, system-ui, sans-serif; letter-spacing: .1em; text-transform: uppercase; padding: 0 12px; min-height: 44px; cursor: pointer; border-radius: var(--g-r-ctl); }
/* FOLLOWING is a held state, so it reads as outlined rather than as another call to action —
   the filled purple says "do this", and you have already done it. Disabled is the honest look
   while the server is still deciding whether you follow: the button must not guess. */
#feedConsole .pf-btn.is-on { background: none; color: var(--g-ink, #e9e9f4); border-color: var(--g-line-4, rgba(17, 17, 17, .4)); }
#feedConsole .pf-btn:disabled { opacity: .55; cursor: default; }
.fd-write { display: grid; gap: 8px; }
.fd-write textarea, .fd-write input { background: var(--g-raise-2, rgba(255, 255, 255, .04)); border: 1px solid var(--g-accent-5, rgba(140, 182, 161, .5)); color: var(--g-ink, #e9e9f4); padding: 8px 12px; font: 16px/1.5 Inter, system-ui, sans-serif; width: 100%; box-sizing: border-box; border-radius: var(--g-r-ctl); resize: vertical; }
.fd-write-note { margin: 0; font-size: var(--t-data); color: var(--g-hi, #22683d); border-left: 2px solid var(--gd-purple, var(--g-accent, #8cb6a1)); background: var(--g-accent-08, rgba(140, 182, 161, .08)); padding: 8px 8px; }
.fd-write-note.is-bad { color: #ff8f7a; border-left-color: #ff8f7a; background: rgba(255, 143, 122, .1); }
.pf-menu.fd-why { z-index: 2400; }
@media (min-width: 641px) {
    /* EVERY OTHER CONSOLE clears the rail too. The rule below names #profileConsole and
     #feedConsole by id, so the rest ran underneath: measured 2026-09-04 with checkout OPEN,
     12 of 25 fields under the rail on GD at 768/1024/1280 and 11 of 23 on SBI at 768/1024 —
     the shipping selector "DOMESTIC SEEDS ($15.00)" among them. That is the buy path.
     WIDTH ONLY, deliberately. The two sites anchor their consoles differently — GD is
     left:0/right:0 full-bleed at 768 and left:50% + translateX(-50%) at 1280; SBI is
     transform-centred throughout — so touching left/right/transform fixes one and throws the
     other off screen (I did exactly that first: GD's consoles landed at [326,954] in a 768
     viewport). Capping the width to the centred-safe figure clears the rail under BOTH
     anchorings without knowing which is in use. */
  body.has-rail .console { max-width: calc(100vw - 2 * (var(--rail-w, 90px) + 24px)) !important; }
  body.has-rail #profileConsole.console, body.has-rail #feedConsole.console { max-width: min(1100px, calc(100vw - var(--rail-w, 90px) - 72px)) !important; right: calc(var(--rail-w, 90px) + 24px) !important; transform: none !important; }

  /* EVERY console, not two of them by id. This named #profileConsole and #feedConsole only,
     so the other six ran under the rail: measured 2026-09-04 with SBI's checkout OPEN, 11 of
     23 fields sat under the rail at 768 and 1024 — including the shipping selector reading
     "DOMESTIC SEEDS ($15.00)". That is the buy path, on a tablet. The sites carry
     checkout/compare/fieldNotes/genealogy/growShop/terminal (SBI) and checkout/genome (GD)
     besides the two the rule knew about; a class selector covers whatever is added next. */
  /* A READING MEASURE. The feed had one; the profile did not, so its prose ran the full
     width of the panel — an 1,100px line is about twice what anyone reads comfortably. */
  #feedConsole .console-inner, #profileConsole .console-inner { max-width: 760px; margin-inline: auto; width: 100%; }
  #feedConsole .fd-card { padding: 12px 16px; }
}
@media (prefers-reduced-motion: reduce) { #feedConsole.active .console-inner { animation: none; } }

/* UI seat (2026-09-02): one control language in the tab row, a labelled score, a neutral queue icon, a lighter reply box, a distinct Close on the phone */
#feedConsole .fd-hbtn.is-warn { color: var(--g-hi, #22683d); border-color: var(--g-line-3, rgba(17, 17, 17, .35)); }
#feedConsole .fd-score::after { content: "LOG"; font-size: var(--t-label); letter-spacing: .1em; margin-left: 4px; opacity: .7; text-transform: uppercase; }
#feedConsole .fd-tabs .pf-seg { border: 0; border-radius: 0; margin-left: 8px; align-self: stretch; }
#feedConsole .fd-tabs .pf-seg button { border-bottom: 2px solid transparent; margin-bottom: -1px; padding: 0 8px; min-height: 44px; }
#feedConsole .fd-tabs .pf-seg button[aria-pressed="true"] { background: none; color: var(--g-ink, #e9e9f4); border-bottom-color: var(--gd-purple, var(--g-accent, #8cb6a1)); }
#feedConsole .fd-reply.is-inline input { border-color: var(--g-accent-35, rgba(140, 182, 161, .35)); }
#feedConsole .pf-close { background: var(--g-lip-2, rgba(255, 255, 255, .08)); }
@media (max-width: 640px) { #feedConsole .console-header { gap: 12px; } }
#feedConsole .fd-answers.is-gone { color: var(--g-ink-3, #a8a3c6); font-style: italic; }
/* the in-place sign-in code (2026-09-02) */
#profileConsole #pfCode { flex: 1; min-width: 160px; background: var(--g-raise-2, rgba(255, 255, 255, .04)); border: 1px solid var(--g-accent-5, rgba(140, 182, 161, .5)); color: var(--g-ink, #e9e9f4); padding: 8px 12px; font: 700 22px/1 ui-monospace, Menlo, monospace; letter-spacing: .22em; min-height: 44px; text-align: center; }
#profileConsole .pf-auth-alt { flex-basis: 100%; font-size: var(--t-label); color: var(--g-ink-3, #a8a3c6); }
#profileConsole .pf-linkbtn { background: none; border: 0; padding: 0; color: var(--g-hi, #22683d); font: inherit; text-decoration: underline; cursor: pointer; min-height: 0; }

/* claims + the moderation queue, moved into the feed console (2026-09-02) */
#feedConsole .fd-claim { display: grid; gap: 8px; border: 1px solid rgba(17, 17, 17, .25); border-radius: var(--g-r-card); padding: 12px; }
#feedConsole .fd-claim-h { margin: 0; font-size: var(--t-body); color: var(--g-ink, #e9e9f4); }
#feedConsole .fd-code { margin: 0; font: 700 26px/1 var(--game-mono); letter-spacing: .18em; color: #15603a; text-align: center; padding: 12px; border: 1px dashed rgba(17, 17, 17, .5); border-radius: var(--g-r-ctl); }
#feedConsole .fd-note { margin: 0; font-size: var(--t-data); color: var(--g-hi, #22683d); border-left: 2px solid var(--gd-purple, var(--g-accent, #8cb6a1)); background: var(--g-accent-08, rgba(140, 182, 161, .08)); padding: 8px 8px; }
#feedConsole .fd-note.is-ok { color: #15603a; border-left-color: #15603a; background: rgba(127, 211, 155, .1); }
#feedConsole .fd-note.is-bad { color: #a3281a; border-left-color: #a3281a; background: rgba(255, 143, 122, .1); }

/* The "post a grow" invitation on a cultivar's own page. It reuses the feed's compose strip so
   it is the same control the community console shows, not a second thing that looks like it. */
.gd-addgrow { margin-top: 12px; }
.gd-addgrow .fd-compose { width: 100%; }
/* A <button> DOES NOT INHERIT font-family — the same lesson this file already learned for
   .gd-tabbar, one component along. `.gd-addgrow .fd-compose` set only a width, so outside
   #feedConsole the compose button fell to the UA default and rendered ARIAL: measured
   2026-09-23 as the single stray family on a GD strain page (Inter, JetBrains Mono, Arial).
   Both storefronts carry this file and both carried the defect. */
.fd-compose, .fd-compose-hint { font-family: var(--game-sans); }


/* ── PROFILE SHEET: GRID CHILDREN MUST BE ABLE TO SHRINK ────────────────────────────────────
   Operator 2026-09-04: "the profile section on the left is still lobsided boxes and still
   overlapping the column on the right." A grid or flex item defaults to min-width:auto, which
   means it REFUSES to shrink below its content's minimum — a long word, an email address, a URL
   — and then overflows its track into the neighbouring column. It is the same fault that pushed
   the NOTIFY ME button 8px out of its row, and it is the standard cause of exactly this symptom.
   Every child of the sheet's grids can shrink now, and long unbroken strings wrap instead of
   forcing a track wider than its share. */
#profileConsole .pf-grid > *, #profileConsole .pf-form > *, #profileConsole .pf-linkrow > *,
#profileConsole .pf-photos > *, #profileConsole .pf-pane > *, #profileConsole .pf-id > *,
/* AND THE SHEET'S OWN TWO COLUMNS — without this the phone scrolls sideways (2026-09-19).
   A grid item's automatic minimum size is its MIN-CONTENT size, so .pf-main refused to go
   narrower than the tab strip laid out in one line: measured at a 390px viewport, .pf-side and
   .pf-main were each 529px wide and the document scrolled to 551. The tabs are already built to
   scroll on a phone — flex-wrap:nowrap + overflow-x:auto, with a mask fading the right edge to
   say so — and that design could never take effect, because the container grew instead of the
   strip scrolling. One missing min-width:0 defeated the whole mechanism. */
#profileConsole .pf-sheet > *, #profileConsole .pf-main > * {
  min-width: 0; }
#profileConsole .pf-pane, #profileConsole .pf-grid, #profileConsole .pf-form { max-width: 100%; }
#profileConsole .pf-who, #profileConsole .pf-who h3, #profileConsole .pf-links a,
#profileConsole .pf-hint, #profileConsole .pf-note { overflow-wrap: anywhere; }

/* The XP ring is the ONLY circle around the avatar and it is concentric BY CONSTRUCTION:
   centred on the avatar's own box rather than offset by a negative inset that any padding,
   border or box-sizing change could throw off (reported off-centre twice). */
#profileConsole .pf-avatar { position: relative; box-sizing: border-box; }
#profileConsole .pf-ring {
  /* MEASURED OFF-CENTRE BY dx=-21 dy=-21 AT EVERY VIEWPORT (operator 2026-09-08: "offset circle
     above the avatar area"). The percentage-margin centring above was the third attempt and the
     one that broke it: a percentage margin on an absolutely positioned box does not resolve
     against the 72px avatar the way the arithmetic assumed, so the ring landed 21px up and left.
     Let the four insets define the box instead — every edge 6px outside the avatar, so the ring
     is concentric by definition with no percentage arithmetic to get wrong. width/height are
     explicitly auto so a stale width declaration cannot re-break it. */
  position: absolute; inset: -8px; width: auto; height: auto;
  margin: 0; transform: rotate(-90deg); pointer-events: none; }

/* ── A grower, opened from their name on a post ──────────────────────────────
   The endpoint (api/profile.js) always existed; nothing ever linked to it, so
   the inbox could say "open a grower's profile to write to them" while no such
   door was on the page. Operator 2026-09-04: "also why cant i click into the
   profiles? like on a post". The author is now the door, on cards and comments. */
#feedConsole .fd-avatar.is-who,
#feedConsole .fd-wholink { appearance: none; -webkit-appearance: none; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; text-align: left; }
#feedConsole .fd-wholink { background: none; border: 0; display: flex; flex-direction: column; justify-content: center; min-height: 44px; max-width: 100%; }
#feedConsole .fd-wholink > b { transition: color var(--dur) var(--ease-out-expo); }
#feedConsole .fd-wholink:hover > b, #feedConsole .fd-wholink:focus-visible > b { color: var(--g-accent-ink, #c2a3ff); text-decoration: underline; text-underline-offset: 2px; }
/* .is-who is the avatar that OPENS the grower — a real button at 38px. Only the interactive
   one grows; the decorative .fd-avatar and the 28px .sm in comments are unchanged. The name
   beside it fills the same 44px row, so the header gains nothing in height from either. */
/* SELLING FROM THE STASH. The price field is revealed by the For-sale toggle rather than
   sitting there greyed out — an input you cannot use is a question the reader has to answer
   before they can ignore it. The chip on the card is how a grower sees, at a glance, which of
   their things are actually listed. */
.pf-sale, .pf-price { display: flex; flex-direction: column; gap: 4px; }
.pf-salehint { margin: 2px 0 0; }
.pf-chip.is-sale { display: inline-block; margin-top: 4px; font: 700 11px/1.6 Inter, system-ui, sans-serif;
  letter-spacing: .08em; padding: 0 8px; border-radius: 999px;
  background: var(--g-accent-3, rgba(140, 182, 161, .3)); color: var(--g-hi, #22683d); }
#feedConsole .fd-avatar.is-who { width: 44px; height: 44px; transition: border-color var(--dur) var(--ease-out-expo), transform var(--dur) var(--ease-out-expo); }
#feedConsole .fd-avatar.is-who:hover { border-color: var(--g-accent-ink, #c2a3ff); transform: translateY(-1px); }
#feedConsole .fd-cwho .fd-wholink { display: inline-block; }

#feedConsole .fd-invite { margin: 16px 2px 4px; padding: 12px 16px; text-align: center;
  border: 1px dashed var(--g-line-3, rgba(17, 17, 17, .35)); border-radius: var(--g-r-card);
  color: var(--g-ink-2, #c9c5e0); font-size: var(--t-data); line-height: 1.5; background: var(--g-fill, rgba(17, 17, 17, .05)); }
#feedConsole .gw { display: grid; gap: 12px; }
#feedConsole .gw-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
/* Two actions now sit here, not one. On a phone the avatar (56px) plus a handle plus Follow and
   Message cannot share a 400px row without crushing the name, so below 460px the pair drops to
   its own full-width line and splits it. Const #3: the defect lives between the viewports. */
#feedConsole .gw-head .gw-id { flex: 1 1 46%; }
@media (max-width: 460px) {
  #feedConsole .gw-head .gw-id { flex: 1 1 100%; }
  #feedConsole .gw-head .pf-btn { flex: 1 1 calc(50% - 6px); padding: 0 8px; }
}
#feedConsole .gw-face { width: 56px; height: 56px; border-radius: 50%; flex: none; display: grid; place-items: center;
  background: var(--g-accent-3, rgba(140, 182, 161, .3)); border: 1px solid var(--g-line-4, rgba(17, 17, 17, .4));
  color: #fff; font: 700 22px/1 Inter, system-ui, sans-serif; }
#feedConsole .gw-id { flex: 1; min-width: 0; display: grid; gap: 4px; }
#feedConsole .gw-id h3 { margin: 0; font: 700 19px/1.2 Inter, system-ui, sans-serif; color: var(--g-ink, #e9e9f4); overflow: hidden; text-overflow: ellipsis; }
#feedConsole .gw-id small { font-size: var(--t-label); color: var(--g-ink-3, #a8a3c6); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The name reads as a name, not as a link — it carries the heading's own colour and only
   admits to being clickable on hover. A profile heading in link-blue would look like
   chrome; this is the grower's name, and it happens to take you to them. */
#feedConsole .gw-perma { color: inherit; text-decoration: none; }
#feedConsole .gw-perma:hover { text-decoration: underline; text-underline-offset: 3px; }
#feedConsole .gw-tick { font-style: normal; color: var(--g-accent-ink, #c2a3ff); font-size: var(--t-body); }
#feedConsole .gw-bio { margin: 0; font-size: var(--t-data); line-height: 1.55; color: var(--g-ink-2, #c9c5e0); }
#feedConsole .gw-stats { display: flex; flex-wrap: wrap; gap: 16px; padding: 8px 0; border-block: 1px solid var(--g-line, rgba(17, 17, 17, .16)); }
#feedConsole .gw-stat { font-size: var(--t-label); color: var(--g-ink-3, #a8a3c6); }
#feedConsole .gw-stat b { font: 700 16px/1 var(--game-mono, ui-monospace, monospace); color: var(--g-ink, #e9e9f4); margin-right: 4px; }
#feedConsole .gw-badges { display: flex; flex-wrap: wrap; gap: 8px; }
#feedConsole .gw-badge { display: inline-flex; align-items: center; gap: 8px; padding: 4px 8px; border-radius: var(--g-r-ctl);
  border: 1px solid var(--g-line, rgba(17, 17, 17, .16)); background: var(--g-fill, rgba(17, 17, 17, .05));
  font-size: var(--t-label); color: var(--g-ink-2, #c9c5e0); }
#feedConsole .gw-badge i { font-style: normal; color: var(--g-accent-ink, #c2a3ff); }
#feedConsole .gw-h { margin: 4px 0 0; font: 600 12px/1 var(--game-mono, ui-monospace, monospace); letter-spacing: .12em; text-transform: uppercase; color: var(--g-ink-3, #a8a3c6); }
#feedConsole .gw-threads { display: grid; gap: 8px; }
#feedConsole .gw-thread { display: grid; gap: 4px; width: 100%; text-align: left; padding: 12px 12px; cursor: pointer;
  border: 1px solid var(--g-line, rgba(17, 17, 17, .16)); border-radius: var(--g-r-card);
  background: var(--g-fill, rgba(17, 17, 17, .05)); transition: border-color var(--dur) var(--ease-out-expo), background var(--dur) var(--ease-out-expo); }
#feedConsole .gw-thread:hover { border-color: var(--g-line-4, rgba(17, 17, 17, .4)); background: var(--g-fill-2, rgba(17, 17, 17, .09)); }
#feedConsole .gw-thread b { font-size: var(--t-body); color: var(--g-ink, #e9e9f4); }
#feedConsole .gw-thread small { font-size: var(--t-label); color: var(--g-ink-3, #a8a3c6); }
#feedConsole .gw-stash { display: grid; grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); gap: 8px; }
#feedConsole .gw-item { margin: 0; display: grid; gap: 4px; min-width: 0; }
#feedConsole .gw-item img { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: var(--g-r-card); border: 1px solid var(--g-line, rgba(17, 17, 17, .16)); display: block; }
#feedConsole .gw-item figcaption { font-size: var(--t-label); line-height: 1.35; color: var(--g-ink-3, #a8a3c6); overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
@media (max-width: 560px) {
  #feedConsole .gw-face { width: 46px; height: 46px; font-size: var(--t-h3); }
  #feedConsole .gw-id h3 { font-size: var(--t-body); }
  #feedConsole .gw-stash { grid-template-columns: repeat(auto-fill, minmax(94px, 1fr)); }
}

/* ══ THE LAYER, IN THIS SITE'S OWN SCHEME (2026-09-03) ═══════════════════════════════════
   Operator: "the new dark mode in some screens is a bit extreme and the menu bar is a bit way
   too off from the original scheme ... can we modify the entire site at once but keep it more
   moderate?"

   MEASURED before this block: the page is #efefef with #111 ink and a green accent, and the
   shared layer sat on it as near-black LAVENDER glass — rail rgba(20,15,36,.58), tab label
   #9d8fb8, console ink #e9e9f4, active tab a violet wash. Correct for the dark storefront it
   was designed on; a foreign object here.

   MODERATE ON PURPOSE. Nothing about the layer's STRUCTURE changes — same panels, radii,
   spacing, type, motion, icons. Only the palette is re-pointed at this site's own scheme:
   paper instead of night, the registry's ink scale, its hairline weight, its green. Every
   value below is a token the layer already reads, so this is one block and no fork. */
:root {
  /* surfaces — paper, not night */
  --g-surface: #ffffff;
  --g-surface-2: rgba(255, 255, 255, .97);
  --g-surface-3: rgba(255, 255, 255, .98);
  --g-surface-4: rgba(255, 255, 255, .96);
  --g-glass: rgba(252, 252, 252, .88);
  /* a console still dims the page behind it — softly, and in the site's own ink */
  --g-scrim: rgba(17, 17, 17, .26);
  --g-scrim-2: rgba(17, 17, 17, .32);
  --g-scrim-3: rgba(17, 17, 17, .42);
  /* raised tiles read as a faint grey wash on paper, never a white one */
  --g-raise-0: rgba(17, 17, 17, .018);
  --g-raise: rgba(17, 17, 17, .026);
  --g-raise-2: rgba(17, 17, 17, .036);
  --g-raise-3: rgba(17, 17, 17, .046);
  /* ink, inverted onto the registry's own scale */
  --g-ink: #111111;
  --g-ink-2: #2f2f2f;
  --g-ink-3: #565656;
  --g-ink-4: #5f5f5f;
  --g-ink-5: #5f5f5f;
  --g-ink-6: #6b6b6b;
  --g-ink-7: #565656;
  --g-ink-8: #2f2f2f;
  /* hairlines at this site's rule weight */
  --g-line: rgba(17, 17, 17, .13);
  --g-line-2: rgba(17, 17, 17, .16);
  --g-line-3: rgba(17, 17, 17, .22);
  --g-line-4: rgba(17, 17, 17, .26);
  --g-line-5: rgba(17, 17, 17, .32);
  --g-lip: rgba(255, 255, 255, .9);
  --g-lip-2: rgba(17, 17, 17, .07);
  --g-lip-3: rgba(17, 17, 17, .11);
  /* highlights: green, dark enough to READ as text on paper */
  --g-hi: #22683d;
  --g-hi-2: #2c7d4c;
  --g-hi-3: #3f9a5f;
  /* the accent, one shade down from the site's #3f9a5f so white-on-accent still reads */
  /* ONE RAMP, TWO STOPS — health's own green ramp (GGG Natural --color-ggg-*), adopted for
     parity across the four properties. A single hex cannot serve both grounds: measured,
     ggg-700 #2f5d4a is 7.54:1 on white and 2.58:1 on this console (a hard fail), while
     ggg-300 #8cb6a1 is 8.62:1 here and 2.25:1 on white. So the token says which ground it
     is for, and contrast-aa proves each use. WEB_DESIGN_CANON.md §2a. */
  --g-accent: #8cb6a1;        /* ggg-300 — on the DARK console */
  --g-accent-ink: #2f5d4a;    /* ggg-700 — on WHITE sheets and the light page */
  --g-accent-06: rgba(140, 182, 161, .06);
  --g-accent-08: rgba(140, 182, 161, .08);
  --g-accent-1: rgba(140, 182, 161, .10);
  --g-accent-12: rgba(140, 182, 161, .12);
  --g-accent-15: rgba(140, 182, 161, .15);
  --g-accent-16: rgba(140, 182, 161, .16);
  --g-accent-18: rgba(140, 182, 161, .18);
  --g-accent-2: rgba(140, 182, 161, .20);
  --g-accent-22: rgba(140, 182, 161, .22);
  --g-accent-25: rgba(140, 182, 161, .16);
  --g-accent-3: rgba(140, 182, 161, .18);
  --g-accent-35: rgba(140, 182, 161, .20);
  --g-accent-4: rgba(140, 182, 161, .22);
  --g-accent-45: rgba(140, 182, 161, .24);
  --g-accent-5: rgba(140, 182, 161, .26);
  --g-accent-6: rgba(140, 182, 161, .30);
  /* the LIT state on the bar and the rail — the one that read as violet */
  --g-lit-18: rgba(140, 182, 161, .12);
  --g-lit-22: rgba(140, 182, 161, .14);
  --g-lit-30: rgba(140, 182, 161, .18);
  --g-lit-5: rgba(140, 182, 161, .26);
  --g-lit-7: rgba(140, 182, 161, .34);
}
/* The breeder's mark wherever a page names the breeder — registry pages, breeder pages, any
   page the shell reaches. Same size language as the list chips, no box unless the art needs one. */
.bm-mark { height: 16px; width: auto; max-width: 46px; object-fit: contain; vertical-align: -3px; margin-right: 8px; flex: 0 0 auto; }
.bm-mark.is-light { background: #3a3a3a; border-radius: var(--g-r-ctl); padding: 2px; box-sizing: content-box; }
.bm-mark.is-hero { height: 30px; max-width: 84px; margin-right: 8px; vertical-align: -6px; }
a.has-bm { display: inline-flex; align-items: center; }

/* The layer's FILL colour. It reaches this site as --gd-purple -> --sbi-accent (#3f9a5f), and
   white sits on it: measured 3.50:1 on the community console's SIGN IN button, under the 4.5
   a 12px label needs. One shade down is 5.04:1 and still unmistakably this site's green.
   Hairlines and underlines keep --sbi-accent; only the filled surfaces move. */
#profileConsole, #feedConsole, #gdHud, .gd-tabbar, #gdBoot, .pf-crop, .pf-create,
.fx-toasts, .fx-unlock, .gd-notes, #checkoutConsole { --gd-purple: #8cb6a1; }

/* The lit states set `color:#fff`, which is right over a dark wash and invisible over a light
   one. On paper the lit label goes DARK GREEN instead. */
.gd-tabbar .gd-tab[aria-current="true"],
#gdHud .gd-hud-btn[aria-current="true"],
#gdHud .gd-hud-btn:hover,
#gdHud .gd-hud-btn:focus-visible { color: #10462a; }
/* ── THE DOCUMENT SURFACE FOLLOWS THIS SITE'S GROUND ─────────────────────────────────────
   Measured 2026-09-03 across nine surfaces: geneticdesigner.io is dark everywhere (luminance
   6-10); THIS site is light everywhere (239-255) with dark overlay consoles. /game/doc.css was
   ported from the dark site unchanged, so clicking "Terms" here dropped the reader out of the
   site's world — the exact fault that sheet was written to fix, in reverse. The sheet's palette
   is tokens with dark defaults; these are this site's values, and game.css loads after doc.css
   so they win.

   Ink and ground are the registry's own: near-black on the same off-white the rest of the site
   uses, with the green accent for section numbers. */
.doc {
  --doc-bg: #fafafa;
  --doc-ink-c: #1a1a1a;
  --doc-mut-c: #5f5f5f;
  --doc-line-c: rgba(0, 0, 0, .14);
  --doc-head: #000;
}
.doc .disc { background: rgba(140, 182, 161, .08); border-color: rgba(140, 182, 161, .35); color: #333; }
.doc .disc b { color: #000; }
.doc .card { border-color: rgba(0, 0, 0, .16); background: #fff; }
.doc .card .nm { color: #000; }
.doc .card .cov { color: #5f5f5f; }
.doc .card .desc { color: #333; }
.doc .aff { color: #7a7a7a; }
/* ── SBI-only overrides (appended last, so they win) ─────────────────────────────────────
   SBI's home is light and has no sparkle field: its button row keeps its own soft back and wraps as
   it always did. 2026-09-02: GD's opaque DARK back reached this row and made CANNABIS / TERMINAL /
   GENEALOGY unreadable (muted text on near-black). */
.kingdom-bar .kingdom-btn { background: var(--soft, #fafafa) !important; -webkit-backdrop-filter: none !important; backdrop-filter: none !important; }
.kingdom-bar .kingdom-btn.active { background: #000 !important; color: #fff !important; }
/* A button that carries its OWN dark style (QI RESEARCH is the row's primary) keeps it — the blanket
   soft back above turned its white text invisible on near-white (operator 2026-09-02: "the last button
   at the top doesnt have text in mobile"). */
.kingdom-bar .kingdom-btn[style*="background:#000"] { background: #000 !important; color: #fff !important; border-color: #000 !important; }
@media (max-width: 1100px) { .kingdom-bar { flex-wrap: wrap !important; overflow: visible !important; justify-content: center !important; -webkit-mask-image: none !important; mask-image: none !important; } .kingdom-bar .kingdom-btn { flex: 0 1 auto !important; } }

/* ══ SBI FACELIFT (2026-09-02) ═══════════════════════════════════════════════════════════════
   Operator: "sbi mobile fonts still look over sized and not proportional to each other, some big some
   small, the overall site still needs a facelift" · "some fonts are serif on sbi and i think sans serif
   is better for this — keep it consistent please".
   MEASURED at 390px before this block: FOUR families on one screen (ui-monospace, Charter, EB Garamond,
   Inter), body copy in two different serifs, and text from 9px to 21px with letter-spacing from normal
   to 3px. This installs ONE voice: Inter for everything a person reads, the mono kept only for the
   registry's own labels and the masthead, on a scale where every step is a real step. */
:root { --serif: Inter, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; --sans: Inter, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; }
body, p, li, td, th, dd, dt, blockquote, figcaption, .intro, .dek, .note, .prose, .mission,
.breeder-peek, .search-wrap input, input, textarea, select, button, .cart-total,
h1, h2, h3, h4, h5, h6 { font-family: Inter, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; }
body { -webkit-font-smoothing: antialiased; }
/* the scale — phone first. Nothing a person reads is under 13px; nothing shouts at 21px. */
@media (max-width: 700px) {
  body, p, li, td, .intro, .dek, .prose, .breeder-peek { font-size: var(--t-body) !important; line-height: 1.55 !important; letter-spacing: normal !important; }
  .mission, .registry-stat-item, .page-subtitle, .sub { font-size: var(--t-label) !important; line-height: 1.5 !important; letter-spacing: .06em !important; }
  h1, .regmast, .mast, .page-title { font-size: var(--t-h3) !important; line-height: 1.25 !important; letter-spacing: .06em !important; }
  h2, .sec-title, .kingdom-title { font-size: var(--t-body) !important; line-height: 1.3 !important; }
  /* h3 was 15px against h2's 16px — a step no reader can see, and exactly the crowding the
     surface census now counts. 14px gives the scale a real 16 / 14 / 12 rhythm. */
  h3 { font-size: var(--t-data) !important; }
  label, .lbl, .chip, .fchip, .kingdom-btn, .breadcrumb, .search-label, .bg-name, .bg-count,
  .registry-stat-item, .breeder-count, .breeder-profile-link { font-size: var(--t-label) !important; letter-spacing: .06em !important; }
  /* 9px and 10px uppercase mono was the "some big some small": it read as noise beside 15px body */
  [style*="font-size: var(--t-label)"], [style*="font-size: var(--t-label)"], [style*="font-size: var(--t-label)"], [style*="font-size:10px"], [style*="font-size: 10px"], [style*="font-size: var(--t-data)"], [style*="font-size: var(--t-data)"] { font-size: var(--t-data) !important; }
  .search-hint { display: none !important; }                 /* the placeholder already says it */
  /* 16px IS A PLATFORM CONSTRAINT, NOT A TYPE DECISION, so it is a literal and not a scale step.
     iOS Safari zooms the whole page when a focused input is under 16px, and there is no 16 on the
     canon's scale — 15 is body and 17 is lead. A mechanical sweep onto the scale put this at 15px
     and would have shipped a page that jumps every time a phone user taps the search box. The
     comment saying so was right here and the sweep ran straight over it. */
  input, textarea, select { font-size: 16px !important; }
}
/* THE FLOOR — EVERY WIDTH, not just the phone. The first pass scoped this to max-width:700px, and a
   desktop sweep then found 385 elements under 11.5px on the registry index alone: breeder names at
   9.5px, cultivar counts and table ranks at 9px. Small text is not a phone problem; it was simply
   measured on a phone first. max(12px, 1em) resolves to the PARENT's size, so nothing grows — it only
   stops falling. The game layer's own chrome keeps its deliberate small caps.

   IT ALSO OUTRANKS ALMOST EVERYTHING, WHICH IS NOT OBVIOUS FROM READING IT. `:where()` adds no
   specificity — that was the intent — but `:not()` takes the specificity of its ARGUMENT, and
   four of the arguments below carry an id. The selector therefore scores (4,0,1), so no
   class-based rule anywhere on the site can set a font-size at all: whatever it declares, the
   floor re-resolves the element to max(12px, its parent's size).
   Measured 2026-09-09: `.breeder-block .breeder-name { font-size: var(--t-data) }` and a 9px PROFILE
   link were both written, both matched their elements, and both computed to 16px. They looked
   correct in the sheet and did nothing. If you need an element smaller than its parent, this
   rule is why you cannot have it — change the floor deliberately, do not paper over it with
   !important, and re-run tests/type-floor-hidden.test.mjs. */
/* THE MARKETPLACE POSTER SIZES ITS OWN TYPE (2026-10-02): every size in marketplace-card.css .pp-* is a
   canon step at or above 12px, and inside the floor all of them computed to the parent's 16px, so the
   strain name (20px) shrank and the hover panel's labels (12px) swelled until it overflowed the card. */
/* THE ORDERS ADDRESS FITS ON ONE LINE (2026-09-14): its box leaves the floor so its own rule can size it to the screen, never
   under 12px. Inside the floor it computed to 16px whatever it declared, and broke inside ".COM" on a phone. */
body :where(p, span, div, a, b, strong, em, small, li, td, th, dd, dt, button, label, summary, figcaption):not(.gd-tabbar *):not(#gdHud *):not(#profileConsole *):not(#feedConsole *):not(.fx-toast *):not(.gd-notes *):not(#gdBoot *):not(.orders-address):not(.pp *)
  { font-size: max(12px, 1em); }
/* MEASURED, not guessed — the classes still rendering at 9-10px after the floor, including (worst) the
   BUY button, and the breeder grid that fills the registry index. */
.filter-chip, .reg-panel-header, .lb-rank, .provenance-badge, .rf-title, .rf-hint, .tier-badge,
.chip-label, .stock-tag, .footer-registry-link, .breeder-link-section, .search-label,
.bg-name, .bg-count, .registry-stat-item, .breadcrumb,
table th, table td, .reg-table th, .reg-table td { font-size: var(--t-label) !important; letter-spacing: .04em !important; }
.cta-buy-btn, .buy-btn, .add-to-cart { font-size: var(--t-data) !important; letter-spacing: .08em !important; min-height: 44px; display: inline-flex !important; align-items: center; justify-content: center; }
.breeder-profile-link { font-size: var(--t-label) !important; }
/* .kingdom-btn — THE SITE'S PRIMARY NAV, and the floor was silently setting its size.
   index.html declares `.kingdom-btn { font-size: var(--t-label) }`. The floor above scores (4,0,1) and beats
   it, so every chip computed to max(12px, its parent's 16px) = 16px — a third wider than declared.
   It is in the escape list inside @media (max-width:700px) and was missing from this one, so the
   phone rendered the intended size and the desktop did not.

   What that cost, measured at 1440: the eight chips need 1009px of text plus 7x6px of gap = 1051px
   inside a 1050px bar. ONE PIXEL over, so "QI RESEARCH" wrapped alone onto a second row — a single
   orphaned chip under seven, which reads as a bug rather than a layout. At the declared 12px the
   same eight need ~805px and the row has ~245px to spare, so the fix is not a tighter gap or a
   narrower chip; it is the nav rendering at the size it always asked for. Verified at 1440, 1280,
   1180 and 1024. Per this file's own instruction: the floor is not papered over with a wider
   selector, the element is added to the measured escape list, and type-floor-hidden re-run. */
.kingdom-btn { font-size: var(--t-label) !important; letter-spacing: .06em !important; }
/* An inline style beats a rule that is not !important, and the registry pages carry hundreds of
   inline-sized divs (9px provenance notes, 10px institute lines). Raising them by SIZE rather than
   with a blanket !important, so nothing set LARGER inline is shrunk. */
[style*="font-size: var(--t-label)"],
[style*="font-size: var(--t-label)"],
[style*="font-size:8.5px"],
[style*="font-size: 8.5px"],
[style*="font-size: var(--t-label)"],
[style*="font-size: var(--t-label)"],
[style*="font-size: var(--t-label)"],
[style*="font-size: var(--t-label)"],
[style*="font-size:10px"],
[style*="font-size: 10px"],
[style*="font-size:10.5px"],
[style*="font-size: 10.5px"],
[style*="font-size: var(--t-data)"],
[style*="font-size: var(--t-data)"],
[style*="font-size:11.5px"],
[style*="font-size: 11.5px"] { font-size: var(--t-data) !important; }


/* the console header stopped fitting: a fixed-width title plus four buttons measured 520px inside a
   390px phone (operator: "profile is oversized and doesnt fit when it loads in mobile · too wide") */
#profileConsole .console-header, #feedConsole .console-header { flex-wrap: nowrap; min-width: 0; gap: 8px; }
#profileConsole .console-header > span:first-of-type, #feedConsole .console-header > span:first-of-type,
#profileConsole #pfTitle, #feedConsole #fdTitle { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 0 1 auto; }
@media (max-width: 700px) { #profileConsole .console-header, #feedConsole .console-header { font-size: var(--t-label); letter-spacing: .06em; } }
/* the breeder blocks: the PROFILE link is absolutely positioned top-right, so the summary must reserve
   that column or the name wraps under it and the cultivar count is cut off with a hole on the right
   (operator 2026-09-02) */
/* the mark: one size for all of them, and a dark tile for art drawn light (it vanishes on white).
   normalize_logos.py trims each file to what is actually drawn and centres it, so a wide wordmark and
   a round badge carry the same weight here. */
.breeder-block .breeder-logo { object-fit: contain !important; }
.breeder-block .breeder-logo.is-mono { display: inline-grid; place-items: center; font: 700 12px/1 Inter, system-ui, sans-serif; color: #6b6b6b; background: #fafafa; letter-spacing: 0; }
/* A mark drawn in white needs a dark ground to be visible at all — but #14151f on this site's
   near-white page read as a stray black box beside the white ones (operator 2026-09-03: "you
   can see like a box that is not in the middle for some reason two boxes"). Same geometry,
   same border weight as every other chip; only the ground differs, and it is this site's own
   ink rather than a colour carried over from the dark storefront. */
/* A mark drawn in white still needs a ground to be visible; every other mark now has no box
   at all, so this one gets the smallest chip that does the job rather than a full bordered
   tile beside borderless neighbours. */
.breeder-block .breeder-logo.is-light { background: #3a3a3a !important; border: 0 !important; border-radius: var(--g-r-ctl) !important; padding: 4px !important; }
/* IN THE FLESH — a listing card led by its photograph. The image sits in a fixed 4:3 frame so a
   tall shot and a wide one make the same card, and object-fit crops rather than distorts. */
/* No width of its own (2026-09-14). The shelf is a grid whose columns size every card (index.html .sc-row and
   .sc-card); the 190px and 158px written here for the old flex row left picture cards narrower than their
   columns, with 78px and 198px gaps between them. */
.sc-card.sc-shot .sc-img { aspect-ratio: 4 / 3; overflow: hidden; border: 1px solid var(--line, #e3e3e3); background: #f4f4f4; margin-bottom: 8px; }
.sc-card.sc-shot .sc-img img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform var(--dur) var(--ease-out-expo); }
.sc-card.sc-shot:hover .sc-img img { transform: scale(1.04); }
@media (prefers-reduced-motion: reduce) { .sc-card.sc-shot .sc-img img { transition: none; } }
/* THE BREEDER ROW. It was: the name in a bare text node, the count after it, and a PROFILE link
   absolutely positioned over the right edge — which a closed <details> lays out in its hidden slot,
   nowhere near the row (measured 2026-09-02: block top 3380, link top 16509). Every row therefore
   showed an empty column, and the name wrapped under it. The link now lives inside the expanded
   panel; the row is [mark] name · count, one line, ellipsis. */
details.breeder-block > summary { display: flex !important; align-items: center; gap: 8px; }
.breeder-block .breeder-name { flex: 1 1 0%; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.breeder-block .breeder-count { flex: 0 0 auto; margin-left: auto; white-space: nowrap; }
.breeder-block .breeder-peek { flex-basis: 100%; }

/* MOBILE: THE NAME IS THE ROW. Measured 2026-09-09 at a 390px viewport, after the operator
   reported "every breeder is formatted weird": 44 of the 70 breeder names were cut off by the
   ellipsis declared directly above — "3RD COAST GEN…", "AB SEED COMPA…", "APOGEE SELECTI…",
   "CANDYMAN SEE…", "CHEF BUDZ GEN…". A list where two thirds of the entries are truncated is a
   list you cannot tell apart, which is the whole job of this row.
   The row is [arrow][mark][name][PROFILE] and the fixed furniture left the name 151px, while
   the names want 170-348px. ONE LINE CANNOT HOLD THIS LIST AT ANY LEGIBLE SIZE — the longest,
   "STRAYFOX GARDENZ CIRCLE OF BLESSINGS", measures 348px at the rendered 16px and still
   overruns 151px at 12px; only 55 of 70 would fit even after shrinking the type to 12px. So the
   name takes a SECOND LINE rather than a smaller font: shrinking type until a name fits is not
   the same as making it readable.
   The furniture gives up only what it can afford. The mark keeps a fixed slot — that is what
   makes every name start on the same x down the list — but a narrower one; PROFILE keeps its
   whole word and just tightens.
   The two-line box is RESERVED on every row, filled or not, so the list keeps one rhythm
   instead of jumping between one- and two-line rows. Same device, same reason, as the
   .breeder-lead slot: rows that vary with their content read as ragged. Row height is
   unchanged at 55px, because two 16px lines occupy the space the 30px mark already claimed. */
@media (max-width: 640px) {
  details.breeder-block summary .breeder-logo { width: 44px; }
  .breeder-block .breeder-name {
    white-space: normal;
    line-height: 1.2;
    /* NO LINE CLAMP. A clamp was tried here first and it re-created the defect on a different
       axis: with -webkit-line-clamp:2 a name too long for two lines is cut VERTICALLY, so the
       menu still read "ALOHA ISLAND..." while a width-based truncation check reported zero
       truncated names — scrollWidth equals clientWidth when the cut is horizontal.
       Instead the two-line box below is a FLOOR, not a ceiling: the 67 names that fit two lines
       all sit on the same rhythm, and the three that do not (the longest is "STRAYFOX GARDENZ
       CIRCLE OF BLESSINGS") take a third line rather than lose their name. Making all seventy
       rows a quarter taller to spare three of them is the worse trade. */
    /* THE BACKSTOP. break-word breaks ONLY a word that cannot fit its box on its own, so it
       never touches a name that wraps normally — but it means no breeder name can ever be
       clipped horizontally again, whatever gets added to this list or however narrow the
       phone. A broken word still tells you which breeder it is; a clipped one does not. */
    overflow-wrap: break-word;
  }
  .breeder-block .breeder-id { min-height: 2.4em; justify-content: center; }
  .breeder-block .breeder-open { padding: 4px 8px; letter-spacing: .5px; }
}

/* NARROW PHONES. At 320px the name box came out 96px — narrower than the single WORDS in it
   ("AFICIONADO", "TERPWIZARD", "MANIFESTATIONS") — so eleven names were still cut. The only
   lever that works here is WIDTH: the mark gives up another 10px and PROFILE tightens.

   TYPE SIZE IS NOT A LEVER ON THIS PAGE, and it is worth writing down why, because the obvious
   fix looks like it works and does not. This sheet sets a body-wide floor:
       body :where(p, span, div, a, ...):not(.gd-tabbar *):not(#gdHud *)... { font-size: max(12px, 1em) }
   `:where()` adds no specificity, but `:not()` takes the specificity of its ARGUMENT, and four
   of those arguments carry an id — so the floor scores (4,0,1) and outranks any class rule.
   `.breeder-block .breeder-name { font-size: var(--t-data) }` was written here, measured, found to
   compute at 16px, and REMOVED rather than left in looking effective. Same for a 9px PROFILE.
   The floor is deliberate and has its own test; do not fight it with !important.
   line-height IS still a lever — it is not in the floor rule — which is why 1.25 stays.

   So at 320px a few long names take a third line. Nothing is hidden, which is the part that
   matters; 390px — the phone most readers actually hold — sits at 99% one height, every name
   whole. */
@media (max-width: 380px) {
  details.breeder-block summary .breeder-logo { width: 34px; }
  .breeder-block .breeder-name { line-height: 1.25; }
  /* the reserved slot must equal exactly two lines at this line-height, or rows that use both
     lines stand 2px taller than rows that use one. 2 x 1.25em = 2.5em. */
  .breeder-block .breeder-id { min-height: 2.5em; }
  .breeder-block .breeder-open { letter-spacing: 0; padding: 4px 8px; }
}
/* the breeder grid on the registry index: one column on a phone, name and count on ONE line */
@media (max-width: 700px) {
  .breeder-grid a { min-height: 44px; padding: 8px 8px !important; }
  .breeder-grid .bg-name { flex: 1 1 auto; min-width: 0; }
  .breeder-grid .bg-count { flex: 0 0 auto; }
}

/* EVERY CONTROL A FINGER CAN REACH IS 44PX ON A TOUCH SCREEN (2026-09-13). tests/touch-target-size.test.mjs read
   this sheet for the first time that day and found nine shared controls under 44px with no touch raise. Each grows
   only on a coarse pointer, so the desktop layout is unchanged, and each sits in a container that grows or wraps
   (the dock is a column flex, the tab row wraps or scrolls, the segment controls are inline-flex, the foot row wraps,
   the toast sizes to its content), so growing a control moves its neighbours instead of overlapping them. The
   thumbnail's remove button keeps its 20px circle exactly where it sat, 2px in from the corner, painted as a gradient
   on a 44px button whose remaining area is hit area inside the tile (the tile clips overflow, so the area cannot
   extend outside it). */
@media (pointer: coarse) {
  #gdHud .gd-hud-btn { min-height: 44px; }
  #profileConsole .pf-tab { min-height: 44px; }
  .pf-crop .pf-btn { min-height: 44px; }
  #profileConsole .pf-seg button { min-height: 44px; }
  .pf-composer .pf-seg button { min-height: 44px; }
  .pf-thumb-img button { top: 0; right: 0; width: 44px; height: 44px; box-sizing: border-box; padding: 2px 2px 20px 20px; border-radius: 0; background: radial-gradient(circle at calc(100% - 12px) 12px, rgba(5, 6, 15, .8) 10px, transparent 10.5px); line-height: 20px; }
  #profileConsole .pf-foot .pf-btn { min-height: 44px; }
  #feedConsole .pf-seg button { min-height: 44px; }
  .fx-toast.fd-undo .fd-undo-btn { min-height: 44px; }
}

/* ══ THE SIGNED-OUT SHEET ══════════════════════════════════════════════════════════════════
   This is the surface that decides whether anyone posts anything, and at 390px it was a wall of
   body text, a browser-default bulleted list with its own hanging indent, a grey box of an input
   and a flat squared-off button — four different visual languages, none of them ours. Measured
   2026-09-19 on a live phone screenshot.

   Nothing here changes what it says or which colours it uses. It changes the geometry: one control
   height, one radius, one rhythm, and a list that reads as a list of things you get rather than a
   paragraph someone put dots in front of. */
#profileConsole .pf-guest { gap: 20px; }

/* The lead reads as a lead: the promise on its own line, the detail under it, a measure that does
   not run to 70 characters on a phone. */
#profileConsole .pf-guest .pf-guest-hero {
  font-size: var(--t-body);
  line-height: 1.65;
  /* 46ch is the readable measure on a phone and an odd narrow column inside a 1000px sheet, where
     the paragraph wrapped at half the width of everything under it. */
  max-width: 62ch;
  margin: 2px 0 0;
}
@media (max-width: 560px) {
  #profileConsole .pf-guest .pf-guest-hero { max-width: 46ch; }
}
#profileConsole .pf-guest .pf-guest-hero b {
  display: block;
  font-size: var(--t-data);
  letter-spacing: .08em;
  margin-bottom: 8px;
}

/* WHAT YOU GET, AS A LIST OF THINGS RATHER THAN A PARAGRAPH WITH DOTS. No markers, no hanging
   indent; each line is a row with its name and its sentence, separated by a hairline. */
#profileConsole .pf-guest ul {
  margin: 4px 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--t-data);
  line-height: 1.55;
  display: grid;
  gap: 0;
}
#profileConsole .pf-guest ul li {
  padding: 12px 0;
  border-top: 1px solid var(--g-line-2, rgba(17, 17, 17, .12));
}
#profileConsole .pf-guest ul li:last-child { padding-bottom: 2px; }
#profileConsole .pf-guest ul b {
  display: block;
  font-size: var(--t-label);
  letter-spacing: .1em;
  margin-bottom: 4px;
}

/* ONE CONTROL HEIGHT, ONE RADIUS. The field and the button stood at different heights with
   different corners; on a phone they stacked as two unrelated objects. 44px is the touch target
   this file already settled on elsewhere (.pf-crop, .pf-foot) and never applied here. */
#profileConsole .pf-auth {
  gap: 8px;
  padding: 16px !important;
  align-items: stretch;
}
#profileConsole .pf-auth #pfAuthLead {
  flex-basis: 100%;
  font-size: var(--t-label);
  line-height: 1.5;
  margin-bottom: 2px;
}
#profileConsole .pf-auth input {
  min-height: 46px;
  padding: 0 12px;
  font-size: 16px;            /* 16px or iOS zooms the page on focus */
  border-radius: var(--g-r-ctl);
}
#profileConsole .pf-auth .pf-btn,
#profileConsole .pf-auth a.pf-oauth {
  min-height: 46px;
  border-radius: var(--g-r-ctl);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* The button itself, wherever it sits in the console: a filled control, not a banner. The old
   14px with 1.5px tracking in full caps reads as a 2013 web app. */
#profileConsole .pf-btn {
  font-size: var(--t-data);
  font-weight: 700;
  letter-spacing: .06em;
  border-radius: var(--g-r-ctl);
  padding: 0 16px;
  min-height: 44px;
  transition: transform var(--dur) var(--ease-out-expo), filter var(--dur) var(--ease-out-expo);
}
#profileConsole .pf-btn:active { transform: translateY(1px); }
#profileConsole .pf-btn:hover:not(:disabled) { filter: brightness(1.06); }

/* ON A PHONE THE ROW IS A STACK, AND EVERY CONTROL IS FULL WIDTH. Half-width buttons beside a
   shrunken field is how this looked at 390px. */
@media (max-width: 560px) {
  /* nowrap MATTERS HERE. The row this replaces is `flex-wrap: wrap`, and a WRAPPING COLUMN does
     not stack — it starts a second column beside the first. Measured at 390px: the lead sat at
     x=29 and the field and button at x=250, running 192px off the right of a 366px sheet. */
  #profileConsole .pf-auth { flex-direction: column; flex-wrap: nowrap; }
  #profileConsole .pf-auth input,
  #profileConsole .pf-auth .pf-btn,
  #profileConsole .pf-auth a.pf-oauth { width: 100%; max-width: none; flex: 1 1 auto; }
}

/* ══ THE STASH EDITOR ══════════════════════════════════════════════════════════════════════
   The form a grower uses to list what they hold, photographed through tests/_harness-stash-form.html
   because it renders only behind a sign-in. At 1440 it was a single 460px column of squared grey
   boxes down the left third of the sheet, ten fields deep, with a segmented control that read as a
   green block jammed against a white one. Nothing about it invited anyone to post.

   The markup already carries the right bones — every field is a <label>, and the long ones are
   .pf-wide — so this is geometry and rhythm, not a rewrite. */
#profileConsole .pf-form {
  gap: 12px 16px;
  /* WIDER COLUMNS, SO A ROW IS TWO FIELDS AND NOT FIVE. minmax(160px, 1fr) with auto-fit packed
     five columns into a wide sheet and one into a narrow one, with nothing in between. */
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  align-items: start;
}
#profileConsole .pf-form label {
  gap: 8px;
  font-size: var(--t-label);
  letter-spacing: .09em; text-transform: uppercase;
      }
/* ONE CONTROL, ONE SHAPE — the same 46px and 10px radius as the sign-in card, so a person moving
   from one to the other is looking at the same product. */
#profileConsole .pf-form input,
#profileConsole .pf-form select,
#profileConsole .pf-form textarea {
  min-height: 46px;
  padding: 12px 12px;
  border-radius: var(--g-r-ctl);
  font-size: 16px;                 /* 16px or iOS zooms the page when a field takes focus */
  line-height: 1.35;
}
#profileConsole .pf-form textarea { min-height: 104px; padding-top: 12px; }
#profileConsole .pf-form select {
  /* a native select keeps its own arrow; the padding has to leave room for it */
  padding-right: 32px;
}

/* THE SEGMENTED CONTROL, AS A CONTROL. Two flat rectangles sharing a hard edge read as a broken
   box; a pill with inset segments reads as a switch, which is what it is. */
#profileConsole .pf-seg {
  border-radius: var(--g-r-ctl);
  padding: 4px;
  gap: 4px;
  background: var(--g-raise-2, rgba(17, 17, 17, .04));
  min-height: 46px;
  align-items: center;
}
#profileConsole .pf-seg button {
  border-radius: var(--g-r-ctl);
  min-height: 38px;
  padding: 0 16px;
  font-size: var(--t-label);
  letter-spacing: .06em;
  font-weight: 600;
  transition: background var(--dur) var(--ease-out-expo), color var(--dur) var(--ease-out-expo);
}

/* The photograph is the first thing a listing is judged on, so its control is not a 52px afterthought. */
#profileConsole .pf-photo-pick { align-items: center; gap: 12px; }
#profileConsole .pf-photo-pick .pf-thumb { width: 72px; height: 72px; border-radius: var(--g-r-card); }
#profileConsole .pf-photo-pick .pf-thumb svg { width: 24px; height: 24px; }

/* The actions sit together at the end of the form rather than wherever the grid left them. */
#profileConsole .pf-form .pf-form-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  align-items: center;
  margin-top: 4px;
  padding-top: 12px;
  border-top: 1px solid var(--g-line-2, rgba(17, 17, 17, .12));
}

@media (max-width: 560px) {
  /* one column, and the two actions are full-width so the primary one is a thumb's width */
  #profileConsole .pf-form { grid-template-columns: 1fr; gap: 12px; }
  #profileConsole .pf-form .pf-form-actions { flex-direction: column-reverse; align-items: stretch; }
  #profileConsole .pf-form .pf-form-actions .pf-btn { width: 100%; }
  #profileConsole .pf-seg { display: flex; }
  #profileConsole .pf-seg button { flex: 1 1 0; }
}

/* ══ THE STASH, AFTER YOU HAVE POSTED ══════════════════════════════════════════════════════
   The cards are what a grower sees once a listing exists, and they were 150px wide: every title
   cut mid-word with an ellipsis ("Keeper #2, the purple o…", "Gage Green Gro…"), a pale green
   block where the photograph goes, and edit and archive as 26px targets under it. A listing that
   looks like that is one nobody wants to make a second of. */
#profileConsole .pf-stash-grid {
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
  gap: 12px;
  align-items: stretch;          /* ragged card heights read as broken, not as variety */
}
#profileConsole .pf-card {
  border-radius: var(--g-r-card);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
#profileConsole .pf-card-img {
  /* a 4:3 frame holds a plant better than a square, and is what the marketplace card uses */
  aspect-ratio: 4 / 3;
  gap: 8px;
}
#profileConsole .pf-card-addphoto {
  font-size: var(--t-label);
  letter-spacing: .1em;
  text-transform: uppercase;
  opacity: .8;
}
#profileConsole .pf-card-body { padding: 12px 12px 8px; gap: 4px; flex: 1 1 auto; }
/* TWO LINES, NOT AN ELLIPSIS MID-WORD. A name a grower chose is the point of the card. */
#profileConsole .pf-card-body b {
  white-space: normal;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: var(--t-body);
  line-height: 1.3;
}
#profileConsole .pf-card-body small {
  white-space: normal;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.45;
}
#profileConsole .pf-card-body p {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* The visibility chip is a control a thumb has to hit, and it sat at 9px in a dark scrim. */
#profileConsole .pf-vis {
  top: 8px; right: 8px;
  font-size: var(--t-label);
  padding: 4px 8px;
  border-radius: 999px;
  min-height: 26px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
/* The foot is the row you act from: a status you can read and two targets you can hit. */
#profileConsole .pf-card-foot { gap: 8px; padding: 8px 12px 12px; }
#profileConsole .pf-status { min-height: 38px; border-radius: var(--g-r-ctl); padding: 0 8px; }
#profileConsole .pf-ico { width: 38px; height: 38px; border-radius: var(--g-r-ctl); }

@media (max-width: 560px) {
  /* two up on a phone, still wide enough for a name to breathe */
  #profileConsole .pf-stash-grid { grid-template-columns: repeat(2, 1fr); gap: 8px; }
  #profileConsole .pf-card-body { padding: 8px 8px 8px; }
}

/* THE VISIBILITY CHIP HAS TO READ ON A PHOTOGRAPH AND ON AN EMPTY CARD. It was 9px in a dark
   scrim — right over a dark plant photo, and washed-out grey on the pale placeholder a card shows
   before anyone adds one, which is the state every brand-new listing starts in. A solid chip with
   its own contrast works on both, and says which state it is in words as well as colour. */
#profileConsole .pf-vis {
  background: rgba(17, 17, 17, .72);
  color: #fff;
  border: 1px solid rgba(255, 255, 255, .28);
  font-weight: 600;
  backdrop-filter: blur(6px);
}
#profileConsole .pf-vis.is-public {
  background: var(--g-hi, #22683d);
  color: #fff;
  border-color: rgba(255, 255, 255, .34);
}
#profileConsole .pf-vis svg { width: 13px; height: 13px; stroke: currentColor; fill: none; stroke-width: 1.8; }

/* ONE ROW. The status pill and the two actions wrapped onto a second line at card width, which
   made every card in the grid a different height and the archive button land under the edit one. */
#profileConsole .pf-card-foot { flex-wrap: nowrap; }
#profileConsole .pf-card-foot .pf-status { flex: 1 1 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#profileConsole .pf-card-foot .pf-ico { flex: 0 0 auto; }

/* ONE CARD PER ROW ON A PHONE. Two-up looked efficient and left every card with a 150px foot, so
   the status a grower reads at a glance was clipped to "HOLD…" beside two buttons. This is the
   view someone MANAGES their stash from, not a browse grid: the photograph wants the width and
   the three controls want the room. Two-up returns as soon as there is space for both. */
@media (max-width: 560px) {
  #profileConsole .pf-stash-grid { grid-template-columns: 1fr; gap: 12px; }
  #profileConsole .pf-card-img { aspect-ratio: 16 / 10; }
  #profileConsole .pf-card-body { padding: 12px 12px 8px; }
  #profileConsole .pf-card-foot { padding: 8px 12px 12px; }
}
@media (min-width: 420px) and (max-width: 560px) {
  #profileConsole .pf-stash-grid { grid-template-columns: repeat(2, 1fr); }
  #profileConsole .pf-card-img { aspect-ratio: 4 / 3; }
}


/* ══ THE PHOTOGRAPH IS THE POST ════════════════════════════════════════════════════════════
   On a phone the card framed every photograph in 14px of padding inside a bordered box, so a
   grower's picture arrived two-thirds the width of the screen it was being looked at on. The
   picture is the thing people come for; it gets the full width, and the words keep their margin.
   Desktop keeps the card, where a bounded column is what makes a feed readable rather than a
   wall. */
@media (max-width: 560px) {
  #feedConsole .fd-card {
    border-left: 0;
    border-right: 0;
    border-radius: 0;
    padding: 12px 0 16px;
    gap: 12px;
  }
  /* everything but the photograph keeps the margin */
  #feedConsole .fd-card > *:not(.fd-photos) { padding-left: 16px; padding-right: 16px; }
  #feedConsole .fd-card .fd-photos { border-radius: 0; }
  #feedConsole .fd-card:hover { border-color: var(--g-line, rgba(17, 17, 17, .16)); }
}

/* A VOTE AND A REPLY ARE THE TWO THINGS ANYONE DOES HERE, so they read as controls rather than
   as two small numbers in grey. Same 44px target the file already settled on, now with a shape
   around it that says it can be pressed. */
#feedConsole .fd-act {
  border-radius: 999px;
  padding: 0 12px;
  font-weight: 600;
  transition: background var(--dur) var(--ease-out-expo), color var(--dur) var(--ease-out-expo);
}
#feedConsole .fd-act:hover { background: rgba(17, 17, 17, .05); }
#feedConsole .fd-act[aria-pressed="true"] { color: var(--g-hi, #22683d); background: rgba(34, 104, 61, .1); }

/* EDGE TO EDGE MEANS THE SCREEN'S EDGE. The card's own padding came off, and the photographs still
   sat 12px in on each side — #feedConsole carries that padding and the card is nested inside it.
   Measured at 390px: console x=0 w=390 with 12px either side, card and photos x=12 w=366. The
   photograph reclaims exactly that 12px and nothing else does, so the words keep their margin. */
@media (max-width: 560px) {
  #feedConsole .fd-card .fd-photos { margin-left: -12px; margin-right: -12px; }
}

/* ══ THE COMPOSER — THE FLOW THAT DECIDES WHETHER ANYONE POSTS ═════════════════════════════
   openComposer() mounts its card on document.body, OUTSIDE #profileConsole, and almost every
   control style in this file is scoped to that console. So the posting flow inherited nothing.
   Measured in the harness at 390px, which mirrors that body mount:

     #pcPost  (the Post button)   48 × 24px, rgba(239,239,239,.3), 0px radius  — the browser's
                                  own disabled button, a quarter of the 44px this file
                                  standardised on, and the primary action of the whole flow
     Cancel                       66 × 24px, the browser's default grey
     a chosen photograph          313 × 243px — unsized, so one picture swallows the card

   Nothing was broken in the code; the styles simply never reached here. The controls below are
   the same geometry the sign-in card and the stash editor use, so a person moving between them
   is looking at one product. */
.pf-crop-card .pf-btn,
.pf-composer .pf-btn {
  appearance: none;
  background: var(--gd-purple, var(--g-accent, #22683d));
  border: 1px solid var(--gd-purple, var(--g-accent, #22683d));
  color: #fff;
  font-family: inherit;
  font-size: var(--t-data);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  border-radius: var(--g-r-ctl);
  padding: 0 16px;
  min-height: 44px;
  cursor: pointer;
  transition: transform var(--dur) var(--ease-out-expo), filter var(--dur) var(--ease-out-expo);
}
.pf-crop-card .pf-btn:active,
.pf-composer .pf-btn:active { transform: translateY(1px); }
.pf-crop-card .pf-btn:hover:not(:disabled),
.pf-composer .pf-btn:hover:not(:disabled) { filter: brightness(1.06); }
.pf-crop-card .pf-btn.is-ghost,
.pf-composer .pf-btn.is-ghost {
  background: none;
  color: var(--g-hi, #22683d);
}
.pf-crop-card .pf-btn:disabled,
.pf-composer .pf-btn:disabled { opacity: .45; cursor: default; }

/* A PHOTOGRAPH IN THE TRAY IS A THUMBNAIL, NOT THE PHOTOGRAPH. */
.pf-composer .pf-thumbs { gap: 8px; align-items: center; }
.pf-composer .pf-thumb {
  width: 72px; height: 72px;
  border-radius: var(--g-r-card);
  overflow: hidden;
  display: grid;
  place-items: center;
  flex: none;
  border: 1px solid var(--g-line-2, rgba(17, 17, 17, .14));
}
.pf-composer .pf-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pf-composer .pf-thumb-add { min-height: 72px; border-radius: var(--g-r-card); }

/* the caption is where the post actually gets written */
.pf-composer textarea,
.pf-composer input[type="text"],
.pf-composer input:not([type]),
.pf-composer select {
  border-radius: var(--g-r-ctl);
  min-height: 46px;
  padding: 12px 12px;
  font-size: 16px;                 /* 16px or iOS zooms the page on focus */
  line-height: 1.45;
}
.pf-composer textarea { min-height: 96px; }

/* the actions sit at the end of the card, primary last, and go full width on a phone */
.pf-crop-card .pf-crop-actions {
  gap: 8px;
  justify-content: flex-end;
  width: 100%;
  padding-top: 12px;
  border-top: 1px solid var(--g-line-2, rgba(17, 17, 17, .12));
}
.pf-crop-card { border-radius: var(--g-r-card); padding: 20px; gap: 12px; }
.pf-crop-title { font-size: var(--t-label); letter-spacing: .14em; }

/* THE SAME SWITCH THE STASH EDITOR GOT. Two defects were visible in the 1440 shot and neither is
   subtle: the three choices packed to the left of a full-width bordered box, leaving a third of
   the row empty and reading as a control that failed to load; and the selected segment's fill ran
   square into the container's rounded corner. A pill with inset segments cures both — the fill
   can never touch the outer radius, and each choice takes an equal share of the row. */
.pf-composer .pf-seg {
  display: flex;
  border: 1px solid var(--g-line-2, rgba(17, 17, 17, .14));
  border-radius: var(--g-r-ctl);
  padding: 4px;
  gap: 4px;
  background: var(--g-raise-2, rgba(17, 17, 17, .04));
  min-height: 46px;
  align-items: center;
  width: 100%;
}
.pf-composer .pf-seg button {
  flex: 1 1 0;
  min-width: 0;
  border-radius: var(--g-r-ctl);
  min-height: 38px;
  padding: 0 12px;
  font-size: var(--t-label);
  letter-spacing: .06em;
  font-weight: 600;
  transition: background var(--dur) var(--ease-out-expo), color var(--dur) var(--ease-out-expo);
}

@media (max-width: 560px) {
  .pf-crop-card .pf-crop-actions { flex-direction: column-reverse; }
  .pf-crop-card .pf-crop-actions .pf-btn { width: 100%; }
  /* three choices at 390px do not fit on one line; two rows of readable targets beat one row of
     wrapped ones */
  .pf-composer .pf-seg { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
  .pf-composer .pf-seg button:first-child { grid-column: 1 / -1; }
}


/* ══ WHO A GROWER IS ═══════════════════════════════════════════════════════════════════════
   The side column is the identity, and it was the weakest surface on the site. Photographed at
   1440 and 390 before this block, four defects, in the order a reader meets them:

     1. THE "CHANGE" CONTROL SAT ON TOP OF THE FACE — a full-width dark bar across the bottom
        third of a 72px avatar. On a photograph it covered the person. The button is absolutely
        positioned and .pf-id was its containing block, so it could only ever be placed by
        guessing at the avatar's position inside a flex row. renderSheet() now wraps the avatar
        and its control in one box (.pf-avatar-wrap), which is what lets the control become a
        badge ON THE CORNER instead of a bar across the middle.
     2. A COMPLETED STEP'S CHECKMARK WAS STRUCK THROUGH. line-through on the <li> ran straight
        through the tick in its box, so "done" read as "cancelled".
     3. THE SIX COUNTS WERE SQUARE HAIRLINE BOXES while every other card on the site had been
        given a radius — the one surface still reading as an unstyled table.
     4. THE LINKS WERE BARE TEXT with no target, in a centred row that wrapped mid-list.

   The avatar also grows: 72px was a thumbnail of a person, on the page that is about them. */
#profileConsole .pf-avatar-wrap { position: relative; display: inline-grid; place-items: center; flex: none; }
#profileConsole .pf-avatar { width: 96px; height: 96px; font-size: var(--t-d1); }

/* A BADGE ON THE CORNER, NOT A BAR ACROSS THE FACE. The word "Change" set in a pill measures
   ~80px against a 96px circle, so even pinned to the corner it still lay across the picture —
   measured, not guessed. A camera glyph in a 32px disc sits where the circle's arc has already
   turned away, covers a crescent of background rather than a face, and is the gesture every
   phone owner already knows. hud.js carries the accessible name; this is only its shape. */
#profileConsole .pf-avatar-edit {
  position: absolute;
  left: auto; top: auto;
  right: -3px; bottom: -3px;
  transform: none;
  width: 32px; height: 32px; min-width: 0; min-height: 0;
  padding: 0;
  border-radius: 50%;
  border: 2px solid var(--g-paper, #fff);
  background: var(--g-hi, #22683d);
  color: #fff;
  display: grid;
  place-items: center;
  cursor: pointer;
  box-shadow: 0 1px 4px rgba(17, 17, 17, .28);
}
#profileConsole .pf-avatar-edit svg { width: 15px; height: 15px; display: block; }
#profileConsole .pf-avatar-edit:hover,
#profileConsole .pf-avatar-edit:focus-visible { background: var(--g-hi-2, #2c7d4c); }

/* the name, and the two lines that qualify it */
#profileConsole .pf-who h3 { font-size: var(--t-h3); font-weight: 700; letter-spacing: -.01em; line-height: 1.2; }
#profileConsole .pf-who .pf-handle { font-size: var(--t-label); letter-spacing: .1em; }
#profileConsole .pf-who .pf-bio,
#profileConsole .pf-next { text-wrap: balance; }   /* no one-word last line in a 340px column */

/* LINKS ARE TAPPABLE THINGS, so they look like it: a chip with an edge and a real target. */
#profileConsole .pf-links { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 8px; }
#profileConsole .pf-links a {
  margin: 0;
  min-height: 32px;
  padding: 0 12px;
  gap: 8px;
  border: 1px solid var(--g-line-2, rgba(17, 17, 17, .14));
  border-radius: 999px;
  background: var(--g-raise-2, rgba(17, 17, 17, .03));
  font-size: var(--t-label);
  font-weight: 600;
  max-width: 100%;
}
#profileConsole .pf-links a span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#profileConsole .pf-links a:hover { border-color: var(--g-accent-45, rgba(140, 182, 161, .45)); }

/* THE COUNTS, AS CARDS. Same radius and ground as the stash cards and the composer, so the page
   reads as one thing; the number leads and the label is quiet under it. */
#profileConsole .pf-grid { gap: 8px; }
#profileConsole .pf-stat {
  border: 1px solid var(--g-line-2, rgba(17, 17, 17, .10));
  background: var(--g-raise-2, rgba(17, 17, 17, .03));
  border-radius: var(--g-r-card);
  padding: 12px 8px 8px;
  text-align: center;
  display: grid;
  gap: 2px;
  align-content: center;
}
#profileConsole .pf-stat b { font-size: var(--t-h3); font-weight: 700; line-height: 1.1; letter-spacing: -.01em; }
#profileConsole .pf-stat span { font-size: var(--t-label); letter-spacing: .08em; white-space: nowrap; text-transform: uppercase; }

/* the level row and its bar */
#profileConsole .pf-level { gap: 8px; }
#profileConsole .pf-level .pf-lv { font-size: var(--t-label); letter-spacing: .1em; align-items: baseline; }
#profileConsole .pf-level .pf-xp { height: 8px; border-radius: 999px; }
#profileConsole .pf-level .pf-xp i { border-radius: 999px; }

/* THE FIRST-WEEK CHECKLIST, as a card rather than a rectangle — and a done step reads as done
   rather than as cancelled, because the rule no longer runs through its own checkmark. */
#profileConsole .pf-quest { border-radius: var(--g-r-card); padding: 12px 12px 12px; gap: 8px; }
#profileConsole .pf-quest .q-title { font-size: var(--t-label); letter-spacing: .14em; align-items: center; }
#profileConsole .pf-quest .q-title button {
  font-size: var(--t-label); letter-spacing: .1em; text-transform: uppercase;
  min-height: 24px; padding: 0 8px; border-radius: 999px;
}
#profileConsole .pf-quest .q-title button:hover { background: rgba(127, 211, 155, .18); }
/* 44 FOR A FINGER. This pill sat at 24px on all seven pages — the only control on the site
   still under the floor — while the rule immediately below it had already learned the lesson
   for the quest ROWS ("44, NOT 34"). The button is the dismiss on a quest card: a miss taps
   the row behind it and opens a file picker or leaves for the community, which is the exact
   harm that comment describes. It stays compact on a mouse, where 44px would bloat a HUD that
   is correct as it is — the rule is not "nothing under 44px", it is "nothing under 44px that a
   finger can reach". tests/touch-target-size.test.mjs. */
@media (pointer: coarse) {
  #profileConsole .pf-quest .q-title button { min-height: 44px; padding: 0 12px; }
}
#profileConsole .pf-quest ul { gap: 2px; }
/* 44, NOT 34. A quest row is not a label with a cursor on it — tapping one opens a file picker,
   switches tabs, opens the composer, or leaves for the community. That is the same class of
   control as .add-btn and .kingdom-btn, which index.html holds at 44px in fifteen places. I had
   it at 34 because it looked tidier in a 340px column; tidier is not a reason to make this
   storefront's own floor apply everywhere except the one surface that asks a new grower to tap
   five things in a row. (fang-35 measured it and filed it rather than quietly changing it.) */
#profileConsole .pf-quest li { min-height: 44px; font-size: var(--t-data); gap: 8px; border-radius: var(--g-r-ctl); padding: 0 8px; }
#profileConsole .pf-quest li:hover { background: rgba(127, 211, 155, .14); }
/* 24px is a floor, not a preference. This rule sits after the 24px one above it and took the
   quest box to 20, under the touch minimum the A11Y sheet assertion enforces. The rounded
   corner and smaller glyph that came with it are kept; only the box returns to 24. */
#profileConsole .pf-quest li .q-box { width: 24px; height: 24px; border-radius: var(--g-r-ctl); font-size: var(--t-label); }
#profileConsole .pf-quest li.is-done .q-box {
  /* line-through on the row ran through the tick as well, so a finished step read as a cancelled
     one. The box keeps its mark; only the words are struck. */
  text-decoration: none;
  background: var(--g-hi, #22683d);
  border-color: var(--g-hi, #22683d);
  color: #fff;
}

@media (max-width: 560px) {
  /* on a phone the card IS the page, so the photograph gets the room it deserves */
  #profileConsole .pf-avatar { width: 104px; height: 104px; font-size: var(--t-d1); }
  #profileConsole .pf-avatar-edit { width: 36px; height: 36px; }   /* a real thumb target */
  #profileConsole .pf-stat { padding: 12px 8px; border-radius: var(--g-r-card); }
  #profileConsole .pf-stat b { font-size: var(--t-h3); }
  #profileConsole .pf-links a { min-height: 36px; font-size: var(--t-label); }
}

/* ══ THE REST OF THE PROFILE ═══════════════════════════════════════════════════════════════
   The stash pane and the side column had a design pass; these five did not. Photographed at
   1440 and 390 together (tests/_harness-profile-panes.html), and the serious one is the first:

   THE PHOTOGRAPHS WERE RENDERED AS THUMBNAILS. `repeat(auto-fill, minmax(96px, 1fr))` across a
   760px pane gives seven columns of ~105px — a strip of stamps on the surface whose entire
   content IS the pictures. A grower's photo pane is the thing they show people. It gets a real
   grid: three or four large squares, tight gaps, no decorative border competing with the image.
   (The squaring itself was already right — aspect-ratio 1 with object-fit: cover — so a 0.70
   portrait and a 1.70 landscape both crop square, which is what the fixture proves.) */
#profileConsole .pf-photos { grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); gap: 8px; }
#profileConsole .pf-photos a {
  border: 0;
  border-radius: var(--g-r-ctl);
  background: var(--g-raise-2, rgba(17, 17, 17, .04));
  box-shadow: inset 0 0 0 1px var(--g-line-2, rgba(17, 17, 17, .10));
  transition: transform var(--dur) var(--ease-out-expo);
}
#profileConsole .pf-photos a:hover { transform: scale(1.012); }

/* A BUTTON AND ITS EXPLANATION BELONG ON THE SAME LINE while there is room for both. The hint
   was dropping full-width under the button and leaving it stranded. */
#profileConsole .pf-actions { align-items: center; row-gap: 8px; }
#profileConsole .pf-actions .pf-hint { flex: 1 1 260px; min-width: 0; margin: 0; }

/* THE SCORE IS WHY THE ROW EXISTS. A documented run's quality is what earns the showcase, and it
   was rendering as small dim text adrift at the left edge, aligned to nothing. */
#profileConsole .pf-row .pf-q {
  font-weight: 700;
  font-size: var(--t-body);
  min-width: 34px;
  text-align: right;
  align-self: center;
}
#profileConsole .pf-row { border-radius: var(--g-r-ctl); padding: 8px 8px; }
#profileConsole .pf-row[data-open]:hover,
#profileConsole .pf-row[style*="cursor"]:hover { background: var(--g-raise-2, rgba(17, 17, 17, .035)); }

/* A LOCKED BADGE SHOULD READ AS NOT-YET, NOT AS BROKEN. The washed-out card and its hairline
   progress bar said "disabled"; the point of the locked state is to be wanted. */
#profileConsole .pf-ach { border-radius: var(--g-r-card); }
#profileConsole .pf-ach .bar { height: 6px; border-radius: 999px; overflow: hidden; }
#profileConsole .pf-ach .bar i { border-radius: 999px; }

@media (max-width: 560px) {
  /* three across, tight — the grid every grower already knows how to read */
  #profileConsole .pf-photos { grid-template-columns: repeat(3, 1fr); gap: 4px; }
  #profileConsole .pf-photos a { border-radius: var(--g-r-ctl); }
  #profileConsole .pf-actions .pf-hint { flex-basis: 100%; }
}

/* THE BAND BETWEEN THE TWO LAYOUTS, WHICH NOBODY HAD LOOKED AT. Below 641 the identity block is
   a centred column; at 960 and up it is the narrow side column. Between the two it is a flex ROW,
   and measured at 768 the row gave the bio 518px and squeezed the level meter to NINETY — tier,
   points, the XP bar and the next-tier line all fighting for 90px, and overflowing the viewport
   by 19 to do it. The progress ladder is the thing that makes someone come back; it does not
   live in a gutter. On its own full-width line it reads the same as it does at both other sizes. */
@media (min-width: 641px) and (max-width: 959px) {
  #profileConsole .pf-level { flex: 1 0 100%; min-width: 0; }
}

/* ══ THE OVERLAYS SIZE THEMSELVES THE WAY EVERYTHING ELSE ASSUMES ════════════════════════════
   Found by tests/no-sideways-scroll.test.mjs on its first run, in code committed an hour earlier.

   There is no global border-box in this sheet — it is applied element by element (line 190 does
   exactly this for .gd-notes), so anything that forgets it and carries padding is WIDER THAN ITS
   CONTAINER, and `max-width: 100%` does not save it: under content-box, max-width caps the
   CONTENT box and the padding and border are added on top.

   .pf-crop-card carries `padding: 20px` and a 1px border, so it measured 408px inside a 366px
   track — 42px over, at every width. In this harness that scrolled the page sideways. In
   production it is worse and quieter: the card lives in `.pf-crop { position: fixed; inset: 0;
   place-items: center }`, so the overflow does not scroll anything — roughly ten pixels of each
   edge of the posting card is simply CLIPPED OFF THE SCREEN on a phone, with no scrollbar and
   nothing to drag. A screenshot cannot show it, because the screenshot is cropped to the same
   viewport that is doing the cropping.

   These are the body-level components — the ones mounted outside every console, which is why
   nothing else had already given them a box model. */
.pf-crop, .pf-crop *,
.pf-create, .pf-create *,
.pf-crop-card, .pf-crop-card * { box-sizing: border-box; }

/* ══ THE POST HEADER, WHEN A POST HAS EARNED SOMETHING ═════════════════════════════════════
   A documented grow can carry three seals at once — verified buyer, showcase, keeper — and the
   header was not built for that. Photographed at 390 and 1440 (tests/_harness-feed-card.html):
   the meta line wrapped to four lines of heavy outlined boxes, each about as loud as the post's
   own title, and .fd-head centred the avatar against the pile so it hung level with the THIRD
   line instead of beside the name. The post it happened to was the best post on the page.

   Two changes. The avatar aligns to the name, because that is the line it belongs to. And a seal
   becomes a quiet filled chip: earned marks should be legible and calm, not compete with the
   photograph underneath them. (feed.js stopped prefixing each one with " · " in the same commit —
   chips are separated by the space between them, and a wrapped separator ended a line on a dot.) */
#feedConsole .fd-head { align-items: flex-start; }
#feedConsole .fd-avatar { margin-top: 1px; }
#feedConsole .fd-who small { row-gap: 4px; line-height: 1.4; }

#feedConsole .fd-seals { display: inline-flex; flex-wrap: wrap; gap: 4px; margin-left: 4px; }
#feedConsole .fd-seal {
  font-size: var(--t-label);
  letter-spacing: .09em;
  border: 0;
  border-radius: 999px;
  padding: 4px 8px;
  background: rgba(47, 125, 79, .11);
  color: #15603a;
  font-weight: 600;
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis; text-transform: uppercase;
      }
#feedConsole .fd-seal.is-gold { background: rgba(160, 110, 0, .12); color: #7a5200; }

/* the count above the feed is a label, not a sentence — it was setting at body size */
#feedConsole .cm-meta { font-size: var(--t-label); letter-spacing: .14em; text-transform: uppercase; color: #5f5f5f; }

/* ══ THE PHONE TYPE FLOOR, APPLIED TO THE GAME LAYER TOO ═══════════════════════════════════
   This sheet already declares the policy at `@media (max-width: 700px)`: body text 16px, small
   labels 12px, and any INLINE font-size between 9 and 11px forcibly raised to 12. It is a good
   rule and the game layer never honoured it, because that block reaches `p, li, td` and inline
   styles — and everything here is a class on a span or a button.

   Measured at 390px across the five signed-in harnesses, 2026-09-19: FIFTEEN shipped rules set
   text below the floor, down to NINE pixels — the MAX marker on a maxed profile, the tier word
   on every badge, the six stat labels, the public/private pills on a stash card, the first-steps
   checklist, and the seals I had just added at 10px. Nine-pixel type is not a style choice on a
   handheld screen; it is text a person has to bring the phone closer to read.

   Nothing here changes the desktop sizes — those are set deliberately and read fine at arm's
   length on a large screen. This is only the floor. */
@media (max-width: 700px) {
  #profileConsole .pf-stat span,
  #profileConsole .pf-max,
  #profileConsole .pf-ach .tier,
  #profileConsole .pf-quest .q-title,
  #profileConsole .pf-quest .q-title span,
  #profileConsole .pf-quest .q-title button,
  #profileConsole .pf-who .pf-handle,
  #profileConsole .pf-level .pf-lv,
  #profileConsole .pf-level .pf-lv span,
  #profileConsole .pf-links a,
  #profileConsole .pf-lbl small,
  #profileConsole .pf-vis,
  #profileConsole .pf-card-addphoto,
  #profileConsole .pf-chip,
  #profileConsole .pf-quest li .q-box,
  #feedConsole .fd-seal,
  #feedConsole .fd-tag,
  .pf-crop-title { font-size: var(--t-label); }

  /* the six stat labels are the tightest of these — at 12px "FOLLOWERS" needs its tracking back
     to stay on one line in a third of a 390px screen */
  #profileConsole .pf-stat span { letter-spacing: .02em; }
}

/* ══ THE CONVERSATION, ON A PHONE ══════════════════════════════════════════════════════════
   Caught by tests/no-sideways-scroll.test.mjs the moment the thread and the feed card joined its
   page list — and the screenshots had already been taken and read as fine, because a screenshot
   is cropped to the same viewport that is cropping the content. At 390 the thread ran to 401 and
   the feed card to 403.

   SAME ROOT CAUSE AS THE PROFILE SHEET, one console over: a flex or grid item's automatic minimum
   size is its MIN-CONTENT size, and a nested comment's min-content is the whole chain of fixed
   indents plus the deepest line's longest word. .fd-cbody already carried `min-width: 0`; the
   items that hold it did not, so the thread grew instead of the text wrapping. This is the same
   list the profile console has had for a while — the feed console never got one.

   AND THE INDENT ITSELF. Every level costs an avatar, a gap, a rule and its padding — 50px, and
   three levels is 150 of a 358px column before a word is set. comment() already flattens past the
   third level so an argument cannot walk off the screen; this makes the three levels it does draw
   affordable, so the flattening is a belt rather than the only thing holding it up. */
#feedConsole .fd-comments > *,
#feedConsole .fd-kids > *,
#feedConsole .fd-comment > *,
#feedConsole .fd-cbody > *,
#feedConsole .fd-card > *,
#feedConsole .fd-head > * { min-width: 0; }

@media (max-width: 560px) {
  #feedConsole .fd-kids .fd-comment { gap: 8px; }
  #feedConsole .fd-kids .fd-avatar.sm { width: 24px; height: 24px; font-size: var(--t-label); }
  #feedConsole .fd-kids .fd-cbody { padding-left: 8px; }
}

/* ══ THE TWO DOORS INTO POSTING ════════════════════════════════════════════════════════════
   openCreate() is the "+" menu and openWrite() is the box a post is typed into. Photographed
   together (tests/_harness-create-write.html) because a person meets them one after the other.

   THE REFUSAL MESSAGE COULD NOT BE READ. .fd-write-note.is-bad is #ff8f7a on a translucent tint —
   a salmon chosen for the dark surface this layer used to have. Measured on the light card it
   sits at 2.22:1, against a 4.5 floor. It is the single most important sentence in the flow: it
   is what the site says when it will not take your post, and someone who cannot read it does not
   know what to change. The contrast gate could not see it, and said so — it reports translucent
   backgrounds as not judged — which is the honest kind of blind spot, and this is it being closed.

   THE MENU'S DESCRIPTIONS WERE SET IN MONOSPACE. This sheet uses mono deliberately for labels,
   kickers and meta, and that is a good house voice. These are not labels: they are three full
   sentences explaining what each door does, sitting directly above a card whose own equivalent
   sentence (.fd-write-hint) is set in the body face. Same flow, same kind of sentence, two faces. */
.fd-write-note.is-bad {
  color: #a3281a;
  border-left-color: #a3281a;
  background: rgba(163, 40, 26, .07);
}
.pf-create-card small { font-family: var(--game-sans); }

/* A FORM IS LEFT-ALIGNED. .pf-crop-card centres its children, which is right for the photo
   cropper it was written for — the image is the content and it belongs in the middle. The write
   box borrows the same card and is not that: it is a title, a textarea, a photo tray, a field and
   a hint, and it sat centred while the composer, which a person meets in the same flow minutes
   earlier, sat left. Two sibling cards, two alignments, no reason. */
.pf-crop-card.fd-write { justify-items: stretch; }
.pf-crop-card.fd-write .pf-crop-title,
.pf-crop-card.fd-write .fd-write-hint { text-align: left; }
.pf-crop-card.fd-write .pf-thumbs { justify-content: flex-start; }

/* ══ FOUR THINGS THE CONTRAST GATE COULD NOT SEE ═══════════════════════════════════════════
   contrast-aa judges a declared colour against a KNOWN ground, and reports translucent
   backgrounds as not judged — an honest limit, honestly stated. Everything below sits on one.
   Found by compositing each translucent layer down to the first opaque ground, the way a screen
   does, across all seven signed-in harnesses at 390px.

     span.pf-avatar-inner  1.19:1 (needs 3)    white initial on a pale mint disc
     span.fd-avatar.sm     1.22:1 (needs 4.5)  the same, on every comment
     span.pf-chip.is-ok    1.70:1 (needs 4.5)  "verified buyer" in mint on mint
     button.pf-linkdel     2.22:1 (needs 4.5)  the × that removes a link

   THE AVATAR IS THE EMBARRASSING ONE. Before a grower uploads a photograph their avatar is their
   initial, and it was white on a 30%-opacity green — which over white paper is a pale mint. The
   letter was very nearly not there, on every profile and beside every comment in the thread, and
   it survived a dozen screenshots of mine because a letter you cannot read still looks like a
   design. The disc keeps its tint; the letter goes dark, which is also how a tinted initial
   avatar is normally drawn.

   The two chips carry colours picked for the dark surface this layer used to have — #7fd39b and
   #ff8f7a — and now match what the feed's seals were given: a quiet tint with legible ink. */
#profileConsole .pf-avatar,
#profileConsole .pf-avatar-inner,
#feedConsole .fd-avatar { color: #15603a; font-weight: 700; }

#profileConsole .pf-chip.is-ok {
  border-color: transparent;
  background: rgba(47, 125, 79, .11);
  color: #15603a;
}
#profileConsole .pf-chip.is-warn {
  border-color: transparent;
  background: rgba(163, 40, 26, .07);
  color: #a3281a;
}
#profileConsole .pf-linkdel { color: #a3281a; }


/* ══ MOTION, AND WHO IS ALLOWED TO REFUSE IT ═══════════════════════════════════════════════
   This file is loaded by EVERY page (index, community, registry-index, shop, breeders, grower
   and the 2,003 registry records), which is why the rule lives here and nowhere else.

   Measured 2026-09-21 with the preference actually set in a browser, not grepped for:
   assets/registry-core.27967d46.css carried four transitions and honoured the preference ZERO
   times across all 2,003 registry pages. index.html honoured it in FOUR places and still ran
   eight animations at a reader who had refused them — a 60s marquee, three 1.6s infinite
   blinking dots, four 4s infinite glowing stat counters and an entrance slide. Mentioning
   prefers-reduced-motion is not the same as obeying it, and only driving the browser shows the
   difference. tests/motion-can-be-refused is the standing guard; it found all of these.

   The reset touches ONLY duration and iteration — never layout, colour or size — so nothing
   shifts position when it fires. The page simply arrives already settled. The homepage marquee
   is handled separately with animation-play-state:paused so the names stay on screen and only
   stop travelling; a reader who needs stillness should not also lose the content. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ══ CROSS-DOCUMENT VIEW TRANSITIONS — native, no library ═══════════════════════════════════
   Asked whether we need motion.dev: measured first. The site already carries 63 transitions,
   26 animations, 18 keyframes and 371 transforms, all native, with NO animation library in the
   dependency tree. Walking the genome is the core act here — accession to parent to landrace,
   page after page — and every step was a hard white flash. One declaration makes that a
   crossfade the browser runs on the compositor: no JavaScript, no dependency, no bytes on the
   wire, and on a site that has already been hit by a RAT, no new third-party runtime on the
   public frontend. Browsers without support ignore it and navigate exactly as they do today.
   The reset above disarms it for anyone who refused motion. */
@view-transition { navigation: auto; }
::view-transition-old(root) { animation-duration: 180ms; }
::view-transition-new(root) { animation-duration: 180ms; }
