/* ============================================================
   BaskMarket theme — "Immersive" (design 8a)
   Dark, cinematic marketplace with a popping orange accent.
   Near-black page, low-contrast surfaces, hairline borders,
   one confident orange. No textures, no display serif, no
   uppercase headings. The colour comes from the accent + photos.

     bg         #0C0C0E  page (near-black)
     surface    #17171A  cards
     surface-2  #111114  inset / "how it works" cards
     border     #232326  hairlines, card borders
     border-2   #3A3A40  inputs, outline buttons (more visible)
     text       #FFFFFF  headings / primary
     body       #B8B8C0  paragraph text
     muted      #8A8A92  meta / secondary
     orange     #E8630A  primary actions, links, price, active
     orange-hv  #F47B2A  hover
   Type: Schibsted Grotesk (400-800). Headings weight 800,
         letter-spacing -0.03em, NOT uppercase.
   ============================================================ */

/* Schibsted Grotesk, self-hosted (variable, 400–800 in one file per subset). Replaces the
   Google Fonts CDN: no third-party origins (UK/GDPR), no @import chain delaying font discovery,
   and App.razor preloads the latin file so the swap window is effectively zero. */
@font-face {
    font-family: 'Schibsted Grotesk';
    font-style: normal;
    font-weight: 400 900;
    font-display: swap;
    src: url('/fonts/schibsted-grotesk-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'Schibsted Grotesk';
    font-style: normal;
    font-weight: 400 900;
    font-display: swap;
    src: url('/fonts/schibsted-grotesk-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* :root:root out-specifies the MudThemeProvider's generated :root block */
:root:root {
    --mud-palette-primary: #E8630A;
    --mud-palette-primary-rgb: 232, 99, 10;
    --mud-palette-primary-text: #0C0C0E;
    --mud-palette-primary-darken: #E85A0C;
    --mud-palette-primary-lighten: #F47B2A;
    --mud-palette-primary-hover: rgba(232, 99, 10, 0.12);

    --mud-palette-secondary: #232326;
    --mud-palette-secondary-rgb: 35, 35, 38;
    --mud-palette-secondary-text: #FFFFFF;
    --mud-palette-secondary-darken: #17171A;
    --mud-palette-secondary-lighten: #3A3A40;
    --mud-palette-secondary-hover: rgba(255, 255, 255, 0.06);

    --mud-palette-tertiary: #9C9CA6;
    --mud-palette-tertiary-rgb: 156, 156, 166;
    --mud-palette-tertiary-text: #0C0C0E;

    --mud-palette-black: #0C0C0E;
    --mud-palette-white: #FFFFFF;

    --mud-palette-background: #1A1A20;
    --mud-palette-background-gray: #1A1A20;
    --mud-palette-surface: #24242A;
    --mud-palette-overlay-dark: rgba(0, 0, 0, 0.7);
    --mud-palette-overlay-light: rgba(23, 23, 26, 0.7);

    --mud-palette-appbar-background: #0C0C0E;
    --mud-palette-appbar-text: #FFFFFF;
    --mud-palette-drawer-background: #0C0C0E;
    --mud-palette-drawer-text: #FFFFFF;
    --mud-palette-drawer-icon: #8A8A92;

    --mud-palette-text-primary: #FFFFFF;
    --mud-palette-text-secondary: #8A8A92;
    --mud-palette-text-disabled: rgba(255, 255, 255, 0.3);

    --mud-palette-action-default: #B8B8C0;
    --mud-palette-action-default-hover: rgba(255, 255, 255, 0.08);
    --mud-palette-action-disabled: rgba(255, 255, 255, 0.28);
    --mud-palette-action-disabled-background: rgba(255, 255, 255, 0.08);

    --mud-palette-lines-default: #2C2C33;
    --mud-palette-lines-inputs: #3A3A40;
    --mud-palette-divider: #2C2C33;
    --mud-palette-divider-light: #1B1B1F;

    --mud-palette-table-lines: #2C2C33;
    --mud-palette-table-striped: rgba(255, 255, 255, 0.02);
    --mud-palette-table-hover: rgba(232, 99, 10, 0.06);

    --mud-palette-dark: #17171A;
    --mud-palette-dark-rgb: 23, 23, 26;
    --mud-palette-dark-text: #FFFFFF;
    --mud-palette-dark-darken: #0C0C0E;
    --mud-palette-dark-lighten: #232326;
    --mud-palette-dark-hover: rgba(23, 23, 26, 0.8);

    --mud-palette-gray-default: #8A8A92;
    --mud-palette-gray-light: #17171A;
    --mud-palette-gray-lighter: #111114;
    --mud-palette-gray-dark: #6A6A72;
    --mud-palette-gray-darker: #232326;

    --mud-palette-info: #4C8DFF;
    --mud-palette-info-rgb: 76, 141, 255;
    --mud-palette-info-text: #FFFFFF;
    --mud-palette-info-darken: #2E74EE;
    --mud-palette-info-lighten: #74A6FF;
    --mud-palette-info-hover: rgba(76, 141, 255, 0.12);

    --mud-palette-success: #22C07C;
    --mud-palette-success-rgb: 34, 192, 124;
    --mud-palette-success-text: #0C0C0E;
    --mud-palette-success-darken: #1AA96B;
    --mud-palette-success-lighten: #45D194;
    --mud-palette-success-hover: rgba(34, 192, 124, 0.12);

    --mud-palette-warning: #E0A422;
    --mud-palette-warning-rgb: 224, 164, 34;
    --mud-palette-warning-text: #0C0C0E;
    --mud-palette-warning-darken: #C68F16;
    --mud-palette-warning-lighten: #EDBB4C;
    --mud-palette-warning-hover: rgba(224, 164, 34, 0.12);

    --mud-palette-error: #F0563E;
    --mud-palette-error-rgb: 240, 86, 62;
    --mud-palette-error-text: #FFFFFF;
    --mud-palette-error-darken: #DB3E24;
    --mud-palette-error-lighten: #F47D6A;
    --mud-palette-error-hover: rgba(240, 86, 62, 0.12);

    --mud-default-borderradius: 12px;

    /* MudThemeProvider emits a family var per typography variant, not just the default one, and
       MudBlazor's own rules read the per-variant var (.mud-button reads --mud-typography-button-family,
       .mud-table-cell reads body2/subtitle2, and so on). Overriding only `default` left every
       MudButton, MudMenu label, chip and table cell on MudBlazor's stock Roboto stack — visible as
       the top nav rendering in a different face from the rest of the page. All 14 are set here so
       the whole MudBlazor surface inherits the brand face from one place. */
    --mud-typography-default-family: 'Schibsted Grotesk', 'Segoe UI', system-ui, sans-serif;
    --mud-typography-h1-family: var(--mud-typography-default-family);
    --mud-typography-h2-family: var(--mud-typography-default-family);
    --mud-typography-h3-family: var(--mud-typography-default-family);
    --mud-typography-h4-family: var(--mud-typography-default-family);
    --mud-typography-h5-family: var(--mud-typography-default-family);
    --mud-typography-h6-family: var(--mud-typography-default-family);
    --mud-typography-subtitle1-family: var(--mud-typography-default-family);
    --mud-typography-subtitle2-family: var(--mud-typography-default-family);
    --mud-typography-body1-family: var(--mud-typography-default-family);
    --mud-typography-body2-family: var(--mud-typography-default-family);
    --mud-typography-button-family: var(--mud-typography-default-family);
    --mud-typography-caption-family: var(--mud-typography-default-family);
    --mud-typography-overline-family: var(--mud-typography-default-family);

    /* ---- Brand tokens (Immersive) ---- */
    --eb-bg: #0C0C0E;
    --eb-page-bg: #1A1A20;
    --eb-surface: #24242A;
    --eb-surface-2: #111114;
    --eb-border: #2C2C33;
    --eb-border-2: #3A3A40;
    /* Segmented-control track. Deliberately NOT --eb-surface: the sex pills sit inside the filter
       bar, which is itself --eb-surface, so sharing the token left the group the same colour as its
       own container and it read as loose text rather than a control. One step up from the surface in
       dark, one step down from white in light — either way it separates from what it sits on. */
    --eb-seg-track: #33333B;
    --eb-seg-hover: #40404A;
    --eb-text: #FFFFFF;
    --eb-body: #B8B8C0;
    /* Lightened from #8A8A92 (which sat at exactly 4.51:1 on surface — AA with no headroom)
       to give muted meta text real contrast headroom everywhere. */
    --eb-muted: #9C9CA6;
    --eb-orange: #E8630A;
    --eb-orange-hover: #F47B2A;
    /* The orange of the never-flipping world — heroes, photo overlays, the near-black "how it
       works" plinth. --eb-orange and --eb-on-orange deepen and invert on light so a white label
       clears AA on a white page; these three don't, because the surface underneath them doesn't
       either: burnt orange sinks into a near-black hero (3.0:1), and a hero standing next to a
       bright-orange eyebrow with a burnt-orange button reads as two oranges in one composition. */
    --eb-orange-on-dark: #E8630A;
    --eb-orange-on-dark-hover: #F47B2A;
    --eb-on-orange-on-dark: #0C0C0E;

    /* Theme-flipping foreground/surface roles. Light overrides live in the
       :root:root[data-theme="light"] block below; in dark mode each equals its original value,
       so every token substitution made across this file is a visual no-op here (dark is unchanged). */
    --eb-orange-ink: #E8630A;   /* orange used as TEXT (links, price, chips) — darkens to a legible burnt orange in light */
    --eb-on-photo: #FFFFFF;     /* text/glyphs that always ride over a dark photo or hero scrim — never flips */
    --eb-on-photo-dim: #9C9CA6; /* the muted companion to --eb-on-photo: meta lines on those same
                                   never-flipping dark surfaces. Equals the dark theme's --eb-muted,
                                   and like --eb-on-photo it must NOT flip on light — the light
                                   theme's #63636B muted drops to 3.0:1 on the dark news hero. */
    --eb-on-orange: #0C0C0E;    /* text/glyphs on an orange fill, and the single enforcement point
                                   for it — previously copy-pasted at 18 sites. It flips WITH the
                                   fill: dark theme keeps near-black on the approved #E8630A
                                   (5.79:1); light deepens the fill to #B84A08 and turns the label
                                   white (5.22:1), because near-black on a bright orange chip reads
                                   muddy against a white page. The pair moves together or one of
                                   the two lands under 4.5:1 — never change one alone. */
    --eb-photo-bg: #1E1E22;     /* letterbox backing behind contained photos */
    /* The hairline around a form field's white body. On dark this is decoration — the white box
       against a near-black card is itself the cue that a field is there — so it keeps the soft
       #C9C9D0 it was hardcoded to. On light the surrounding surface is white too, which leaves the
       hairline as the ONLY thing marking the control: at 1.65:1 against its own fill it failed
       WCAG 1.4.11's 3:1 floor for a component boundary, and fields were effectively invisible.
       See the light block for the flipped value. */
    --eb-input-border: #C9C9D0;

    /* Semantic sex colours — a recorded exception to the One Orange rule: sex is data,
       and colour scans faster than reading a glyph on a 25-card grid. Bright, high-contrast
       tones so the glyph reads at a glance on the dark card surface. */
    --eb-sex-m: #5AB2FF;
    --eb-sex-f: #FF74B4;
    /* The same two hues pinned for glyphs on a surface that never flips (the card photo's spec
       chip). The light theme darkens the pair above for a white card; over a near-black photo
       chip #1668C4 measures 2.4:1, so the overlay keeps the bright values — the same exception
       --eb-orange-on-dark makes for orange. */
    --eb-sex-m-on-dark: #5AB2FF;
    --eb-sex-f-on-dark: #FF74B4;
    /* Welfare-advisory amber — another recorded One-Orange exception: a hereditary-defect
       warning must NOT read as the brand call-to-action, so caution gets its own hue. */
    --eb-welfare-accent: #E8A33D;
    --eb-welfare-border: rgba(232, 163, 61, 0.38);
    --eb-welfare-bg: rgba(232, 163, 61, 0.10);
    /* Recessive violet — the third recorded One-Orange exception. Recessive is the one inheritance
       mode you cannot see on the animal and the one that decides what a pairing produces, so it is
       the only mode that earns a hue; the other three carry the muted tag alone. 263deg sits 55deg
       off --eb-sex-m, which is the nearest hue on this page (a coloured sex glyph sits just above
       the pill row). Always paired with the REC tag, and always yields to welfare amber. */
    --eb-recessive-accent: #AF81F8;
    /* Validation/state hues, matching the documented semantic palette. These were referenced by the
       bulk-listing rules but never defined, so `color: var(--eb-error)` was invalid at computed-value
       time and the declaration dropped — the over-quota warning rendered in body colour and the
       "live" state pill lost both its colour and its color-mix() tint. Used as TEXT, so like
       --eb-orange-ink they darken on light; see the [data-theme="light"] block. */
    /* #F4644C, not the palette's #F0563E: as text on --eb-surface that measures 4.49:1 — AA by a
       hundredth. Nudged lighter along the same hue for real headroom (4.98:1 on surface, 5.59:1 on
       the page), exactly as --eb-muted was lifted off 4.51:1 above. MudBlazor's own error swatch
       keeps #F0563E; this token is only for our text. */
    --eb-error: #F4644C;
    --eb-success: #22C07C;
    /* Completes the semantic set. The admin status dots and pills reached for
       --mud-palette-info directly, which is tuned for a near-black surface and never flips — see
       the light block. Dark value equals that palette entry, so dark is unchanged. */
    --eb-info: #4C8DFF;
    --eb-tint-bg: rgba(232, 99, 10, 0.12);
    --eb-tint-border: rgba(232, 99, 10, 0.4);
    /* Orange used as TEXT on top of --eb-tint-bg. The tint lifts the background toward the orange
       it is made of, so --eb-orange-ink lands at 4.46:1 dark / 4.17:1 light there — under the floor
       in both, the same hundredths-short trap documented for --eb-error above. This is the step
       past it, measured on the composited tint: 5.53:1 dark, 5.22:1 light. */
    --eb-orange-on-tint: #F47B2A;
    --eb-display: 'Schibsted Grotesk', 'Segoe UI', system-ui, sans-serif;

    /* Legacy aliases — keep old class rules / stray references resolving */
    --eb-ink: #0C0C0E;
    --eb-cream: #0C0C0E;
    --eb-line: #2C2C33;
    --eb-surface-alias: #24242A;
    --eb-yellow: rgba(232, 99, 10, 0.12);
    --eb-yellow-deep: #F47B2A;
    --eb-purple: #E8630A;
    --eb-orange-text: #E8630A;
    --eb-shed: #8A8A92;
    --eb-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
    --eb-shadow-sm: 0 8px 24px rgba(0, 0, 0, 0.4);

    /* Immersive hero background — override --eb-hero-image with url(...) to drop in a photo */
    /* This is now the hero's normal state, not its failure state — the front door no longer
       promotes an arbitrary listing photo — so the lamp has to actually read. 0.34, up from 0.20,
       which rendered as nothing. The second radial was a 10% violet: a literal second accent, and
       the most visible non-orange thing in an empty panel. Deleted, not dimmed. */
    --eb-hero-fallback:
        radial-gradient(120% 90% at 82% 8%, rgba(232, 99, 10, 0.34) 0%, rgba(232, 99, 10, 0) 55%),
        linear-gradient(160deg, #1C1C22 0%, #0E0E11 58%, #090909 100%);
}

/* ============================================================
   LIGHT THEME — "The Day Vivarium"
   Activated by html[data-theme="light"] (set before first paint by the inline script in
   App.razor, toggled from the nav, and kept in sync with MudThemeProvider.IsDarkMode). The dark
   theme above stays the default; everything here is an override. Heroes and photo overlays are
   deliberately left dark in BOTH themes — a scrim over imagery reads the same either way, and the
   dark cinematic hero band is the brand's signature ("the animal is the hero"). Verified WCAG AA:
   ink 17.9:1, body 9.5:1, muted 5.9:1, burnt-orange text 5.2:1, sex-m 5.5:1, sex-f 5.3:1 on white.
   ============================================================ */
:root:root[data-theme="light"] {
    /* ---- Brand tokens ---- */
    --eb-bg: #FFFFFF;          /* app bar, footer, darkest chrome -> now the lightest */
    --eb-page-bg: #F4F4F6;     /* the neutral gallery-grey the page sits on (not a warm cream) */
    --eb-surface: #FFFFFF;     /* cards, tables, dialogs */
    --eb-surface-2: #EDEDF1;   /* recessed panels: table headers, inset strips */
    --eb-border: #E4E4E9;      /* hairlines */
    --eb-border-2: #CFCFD6;    /* stronger hairline: outlined buttons */
    /* 3.38:1 against the field's white fill (dark theme keeps #C9C9D0 — see the dark block).
       Deliberately darker than --eb-border-2: that token also paints hairlines that sit on the
       grey page, where a control boundary is not the only cue. */
    --eb-input-border: #8A8A94;
    --eb-seg-track: #E7E7EC;   /* grey track under the pills; the bar behind it is white */
    --eb-seg-hover: #FFFFFF;   /* an unselected pill lifts to white on hover */
    --eb-text: #17171A;        /* headings / primary text */
    --eb-body: #44454D;        /* paragraph text */
    --eb-muted: #63636B;       /* meta / labels (>=4.5:1 on white, page grey and surface-2) */
    /* Approved --eb-orange (#E8630A) still carries fills, badges, active state and focus; orange used
       as TEXT darkens to a burnt heat-lamp orange so links and prices clear 4.5:1 on white. */
    --eb-orange-ink: #B84A08;
    /* The fill under a label deepens to that same burnt orange and the label turns white: near-
       black on #E8630A measures 5.79:1 but reads heavy and muddy on a white page, and plain white
       on #E8630A is 3.38:1 — under the floor. #FFFFFF on #B84A08 is 5.22:1. Fills, badges, active
       state and focus rings all move together, so the light theme has one orange, not two.
       Hover deepens rather than warms here (6.63:1) — brightening would drop the white label. */
    --eb-orange-hover: #9E3F06;
    --eb-orange: #B84A08;
    --eb-on-orange: #FFFFFF;
    /* Darker rather than lighter here — on light the tint sits near white, so the text has to go
       down, not up. See the dark declaration for the measurements. */
    --eb-orange-on-tint: #A03F06;
    --eb-photo-bg: #ECECEF;    /* light letterbox backing behind contained photos */
    --eb-sex-m: #1668C4;
    --eb-sex-f: #C42B78;
    /* Darker amber so the welfare-note title/icon clear 4.5:1 on the white card. */
    --eb-welfare-accent: #9A6512;
    --eb-welfare-border: rgba(154, 101, 18, 0.30);
    --eb-welfare-bg: rgba(232, 163, 61, 0.13);
    /* #AF81F8 is 2.4:1 on the white card. Same hue, taken down to 5.5:1 as text. */
    --eb-recessive-accent: #7F45DE;
    /* Same treatment as --eb-orange-ink and --eb-welfare-accent: the dark theme's state hues are
       tuned for a near-black card and read at 3.4:1 / 2.4:1 as text on white. Darkened to 5.5:1
       and 5.4:1; the hue is unchanged, so a red still reads red. */
    --eb-error: #C4331C;
    --eb-success: #14794E;
    /* #4C8DFF is 2.4:1 on white — under the 3:1 floor for a status dot, which carries meaning and
       is the only thing distinguishing one state from another. Same hue, taken down to 4.6:1. */
    --eb-info: #1B5FD9;
    --eb-tint-bg: rgba(232, 99, 10, 0.12);
    --eb-tint-border: rgba(232, 99, 10, 0.34);
    --eb-shadow: 0 12px 32px rgba(20, 22, 40, 0.14);
    --eb-shadow-sm: 0 8px 24px rgba(20, 22, 40, 0.10);

    /* ---- MudBlazor palette (light). Our own --eb-* rules cover most chrome, but MudBlazor
       components we don't restyle read these directly. :root:root[data-theme] out-specifies both
       MudThemeProvider's generated :root block and the dark :root:root above. The accent follows
       --eb-orange down to the burnt fill and takes the white label with it — a MudButton we never
       restyled would otherwise paint near-black on #E8630A while ours reads white on #B84A08. ---- */
    --mud-palette-primary: #B84A08;
    --mud-palette-primary-rgb: 184, 74, 8;
    --mud-palette-primary-text: #FFFFFF;
    --mud-palette-primary-darken: #9E3F06;
    --mud-palette-primary-lighten: #C4530A;
    --mud-palette-background: #F4F4F6;
    --mud-palette-background-gray: #EDEDF1;
    --mud-palette-surface: #FFFFFF;
    --mud-palette-appbar-background: #FFFFFF;
    --mud-palette-appbar-text: #17171A;
    --mud-palette-drawer-background: #FFFFFF;
    --mud-palette-drawer-text: #17171A;
    --mud-palette-drawer-icon: #63636B;
    --mud-palette-text-primary: #17171A;
    --mud-palette-text-secondary: #63636B;
    --mud-palette-text-disabled: rgba(23, 23, 26, 0.38);
    --mud-palette-action-default: #44454D;
    --mud-palette-action-default-hover: rgba(0, 0, 0, 0.06);
    --mud-palette-action-disabled: rgba(23, 23, 26, 0.26);
    --mud-palette-action-disabled-background: rgba(23, 23, 26, 0.10);
    --mud-palette-lines-default: #E4E4E9;
    --mud-palette-lines-inputs: #CFCFD6;
    --mud-palette-divider: #E4E4E9;
    --mud-palette-divider-light: #EDEDF1;
    --mud-palette-table-lines: #E4E4E9;
    --mud-palette-table-striped: rgba(0, 0, 0, 0.02);
    --mud-palette-table-hover: rgba(232, 99, 10, 0.06);
    --mud-palette-overlay-dark: rgba(0, 0, 0, 0.5);
    --mud-palette-overlay-light: rgba(255, 255, 255, 0.7);
    /* the "dark" pseudo-surface MudBlazor uses for a few raised chrome bits -> a light surface */
    --mud-palette-dark: #EDEDF1;
    --mud-palette-dark-rgb: 237, 237, 241;
    --mud-palette-dark-text: #17171A;
    --mud-palette-dark-darken: #E0E0E6;
    --mud-palette-dark-lighten: #F4F4F6;
    --mud-palette-dark-hover: rgba(237, 237, 241, 0.8);
    --mud-palette-secondary: #EDEDF1;
    --mud-palette-secondary-text: #17171A;
    --mud-palette-secondary-darken: #E0E0E6;
    --mud-palette-secondary-lighten: #F4F4F6;
    --mud-palette-gray-default: #63636B;
    --mud-palette-gray-light: #EDEDF1;
    --mud-palette-gray-lighter: #F4F4F6;
    --mud-palette-gray-dark: #7A7A82;
    --mud-palette-gray-darker: #E4E4E9;
}

/* ---- Light theme exceptions: keep photos/heroes dark, restore the right foreground there ---- */

/* White glyphs that ride over a photo or a dark hero scrim. !important is deliberate: these sit on
   permanently-dark surfaces, and global-styles.css bleeds heading (body.abp-application-layout
   h1-h5, 0,2,2) and link (main a:not()..., 0,5,3) colours onto the Blazor host at a higher
   specificity than any practical selector here — so we must out-rank them outright. */
[data-theme="light"] .eb-hero-title,
[data-theme="light"] .eb-pricing-headline,
[data-theme="light"] .eb-photo-spec,
[data-theme="light"] .eb-heart,
[data-theme="light"] .eb-store-name,
[data-theme="light"] .eb-share-btn,
[data-theme="light"] .eb-social-btn,
[data-theme="light"] .eb-news-hero-title {
    color: var(--eb-on-photo) !important;
}

/* Bright heat-lamp orange where orange TEXT sits on a permanently-dark surface — burnt orange
   would sink into the near-black hero. !important for the same global-styles-bleed reason. */
[data-theme="light"] .eb-hero-eyebrow,
[data-theme="light"] .eb-store-meta-rating .eb-star,
[data-theme="light"] .eb-heart--on,
[data-theme="light"] .eb-news-tag,
[data-theme="light"] .eb-news-more,
[data-theme="light"] .eb-news-hero.has-cover .eb-news-date,
[data-theme="light"] .eb-news-hero:hover .eb-news-hero-title {
    color: var(--eb-orange-on-dark) !important;
}

/* Meta lines on the same never-flipping dark surfaces. The light theme's muted (#63636B) is
   tuned for a white page and only reaches 3.0:1 on the news hero's #17171A panel. */
[data-theme="light"] .eb-news-hero.has-cover .eb-news-readtime {
    color: var(--eb-on-photo-dim) !important;
}

/* Every MudBlazor variant that paints the primary colour as FOREGROUND. Mud drives these from
   --mud-palette-primary with its own !important, which outranks the light theme's orange-ink
   override, so they stayed bright #E8630A on a light surface:

     .mud-link.mud-primary-text        card titles (footer nav opts out)      2.85:1 on white
     .mud-button-text-primary          text buttons
     .mud-chip-color-primary           status and filter chips                2.59:1 on #F4F4F6

   DESIGN.md's rule is that orange used as TEXT darkens to --eb-orange-ink on light; these are
   where it was escaping. Only `color` is set — fills and borders keep the bright orange, so the
   outlined button still has its #E8630A edge. Retargeting --mud-palette-primary instead would
   have dragged every fill to burnt orange too. */
[data-theme="light"] .mud-link.mud-primary-text,
[data-theme="light"] .mud-button-text-primary,
[data-theme="light"] .mud-chip-color-primary.mud-chip-text,
[data-theme="light"] .mud-chip-color-primary.mud-chip-outlined {
    color: var(--eb-orange-ink) !important;
}

/* The featured news lead stays a dark spotlight panel in both themes (the cover variant's surface
   bg would otherwise flip white and drop out its white title). */
[data-theme="light"] .eb-news-hero.has-cover {
    background-color: #17171A;
}
[data-theme="light"] .eb-news-hero-excerpt {
    color: rgba(255, 255, 255, 0.82);
}

/* Nav chrome: the app bar is white in day mode, so the links read dark. */
[data-theme="light"] .eb-topnav .eb-nav-item,
[data-theme="light"] .eb-topnav .mud-button,
[data-theme="light"] .eb-topnav .mud-icon-button,
[data-theme="light"] .eb-fav-nav,
[data-theme="light"] .eb-chat-nav {
    color: rgba(23, 23, 26, 0.74);
}
[data-theme="light"] .eb-topnav .eb-nav-item:hover,
[data-theme="light"] .eb-topnav .mud-button:hover,
[data-theme="light"] .eb-topnav .mud-icon-button:hover,
[data-theme="light"] .eb-fav-nav:hover,
[data-theme="light"] .eb-chat-nav:hover {
    background-color: rgba(0, 0, 0, 0.05);
}

/* Floating chrome bars flip to light frosted glass — they overlay page content, not photos. */
[data-theme="light"] .eb-compare-tray {
    background: rgba(248, 248, 250, 0.9);
}
[data-theme="light"] .eb-store-sticky {
    background: rgba(248, 248, 250, 0.92);
}

/* Dark-mode-only translucent whites that would vanish on a light surface. */
[data-theme="light"] .eb-bulk-state--draft,
[data-theme="light"] .eb-fb-rail-btn:hover:not(:disabled) {
    background: rgba(0, 0, 0, 0.05);
}
[data-theme="light"] .eb-news-loadmore-spin {
    border-color: rgba(0, 0, 0, 0.16);
    border-top-color: var(--eb-orange);
}
[data-theme="light"] .eb-sk {
    background: linear-gradient(90deg, #E6E6EB 25%, #F1F1F4 50%, #E6E6EB 75%);
    background-size: 200% 100%;
}

/* ---- Base typography -------------------------------------------------- */

html, body {
    background-color: var(--eb-page-bg);
}

/* The closed nav drawer is position:fixed and parked just past the right edge (left: 100vw,
   width: 240px). Chrome counts that off-viewport fixed box in the document scroll width, so every
   page scrolled ~31px sideways on a phone. MudBlazor's own layout clips this; our custom layout
   replaced MainLayout and lost it.
   BOTH elements, and that is not belt-and-braces — it was measured. Root overflow propagates to
   the viewport and leaves `html` itself computing to visible, so html alone still scrolled 31px;
   body alone never sees the drawer at all, because a fixed element's containing block is the
   viewport rather than the body. Only the pair reaches zero.
   `clip`, not `hidden`: overflow-x: hidden makes an element a scroll container, which silently
   turns position: sticky into a no-op inside it — and the listing buy rail is sticky. `clip` cuts
   the overflow without creating a scroll container, so the rail keeps travelling. */
html,
body {
    overflow-x: clip;
}

/* WCAG 2.4.11 (Focus Not Obscured, AA in 2.2). The MudAppBar is fixed at 64px and several bars are
   bottom-anchored; without scroll padding, tabbing to a control near a viewport edge parks it
   underneath one — invisible to someone arriving by keyboard, fine for a mouse user who scrolled
   there deliberately. Bottom reuses the tray clearance already established at
   `body:has(.eb-compare-tray) .eb-footer` rather than inventing a second magic number.
   Also fixes the skip link: `#eb-main` previously landed under the app bar on the fragment jump.
   Known gap: on store pages `.eb-store-sticky` is a second fixed bar at top:64px, so the real top
   obstruction is ~117px and this rule does not fully clear it. No clean CSS-only fix — the
   scroll-timeline path reveals that bar without setting the `--on` class a :has() rule could target. */
html {
    scroll-padding-top: 64px;
    scroll-padding-bottom: calc(84px + env(safe-area-inset-bottom, 0px));
}

/* The Blazor host <body> also carries .abp-application-layout, which global-styles.css paints with
   a hardcoded dark bg at higher specificity than the bare `body` rule above. Re-assert the themed
   page bg here (eball-theme.css loads after global-styles.css, so same-specificity, later wins) so
   the host background follows the active theme instead of being pinned dark. */
body.abp-application-layout {
    background-color: var(--eb-page-bg);
    color: var(--eb-text);
}

body,
.mud-typography {
    font-family: 'Schibsted Grotesk', 'Segoe UI', system-ui, sans-serif;
    color: var(--eb-text);
}

/* The rule above ties MudBlazor's own `.mud-text-secondary` on specificity (both a single class)
   and this sheet loads after MudBlazor's, so ours won every tie — which meant every
   `Class="mud-text-secondary"` MudText in the app rendered at full primary strength. The muting
   was written into dozens of pages and silently did nothing; the shipping line under a listing
   price was the same white as the price. Two classes here so it outranks the tie rather than
   re-entering it, and routed through our own token so the light theme follows. */
.mud-typography.mud-text-secondary {
    color: var(--eb-muted);
}

.mud-typography-h1,
.mud-typography-h2,
.mud-typography-h3,
.mud-typography-h4,
.mud-typography-h5,
.mud-typography-h6 {
    font-family: var(--eb-display);
    font-weight: 800;
    letter-spacing: -0.03em;
    color: var(--eb-text);
}

.mud-typography-subtitle1 {
    font-weight: 700;
    letter-spacing: -0.01em;
}

a,
.mud-link {
    color: var(--eb-orange-ink);
}

a:hover,
.mud-link:hover {
    color: var(--eb-orange-hover);
}

:focus-visible {
    outline: 2px solid var(--eb-orange);
    outline-offset: 2px;
}

/* ---- App chrome: horizontal top nav + fixed-width container ---------------- */

:root:root {
    /* The page's content width, stepped up on 2026-08-12 from 1240px (sized for a 1280 screen) to
       1400px (a 1440 screen, keeping a 20px gutter each side). Anything that has to line its edge
       up with the container reads this token rather than repeating the number — the header search
       strip does, and it is the reason the two cannot drift apart again.

       Grid column counts are tuned to it: listings run 5-up and store cards 6-up above the cap,
       each dropping a column at max-width:1400px, below which the container shrinks with the
       viewport and the old steps take over unchanged. */
    --eb-container-max: 1400px;
}

.eb-container {
    width: 100%;
    max-width: var(--eb-container-max);
    margin-inline: auto;
    padding-inline: 28px;
    box-sizing: border-box;
}

.eb-page {
    padding-top: 32px;
    padding-bottom: 80px;
}

.mud-appbar {
    box-shadow: none;
    border-bottom: 1px solid var(--eb-border);
    background-color: var(--eb-bg);
}

.eb-topnav .mud-toolbar {
    padding: 0;
    width: 100%;
}

.eb-nav-inner {
    display: flex;
    align-items: center;
    gap: 4px;
    height: 100%;
    min-width: 0;
}

.eb-brand {
    display: flex;
    align-items: center;
    flex: 0 0 auto;
    margin-right: 28px;
    white-space: nowrap;
}

/* bask wordmark — the whole header brand. The image is taller than its letters (dot headroom),
   so 34px draws the lowercase at roughly the old 1.2rem text size. */
.eb-brand-mark {
    height: 34px;
    width: auto;
    display: block;
}

/* Theme-aware built-in wordmark: paper ink on the dark bar (default), black ink on the light bar.
   Both <img>s render; only the one matching the active app-bar colour is shown. (A custom
   admin-uploaded logo renders as a single .eb-brand-mark and is used for both themes.) */
.eb-brand-mark--light { display: none; }
[data-theme="light"] .eb-brand-mark--dark { display: none; }
[data-theme="light"] .eb-brand-mark--light { display: block; }

/* The nav and the hamburger are one switch, thrown at the width the bar actually needs rather than
   at whichever framework utility is nearest. Measured on the trimmed six-item public bar: the last
   control lands exactly on the container's right gutter at a 1024px viewport and 26px past the edge
   at 960 — and because nothing here overflows the document, a clipped control has no scrollbar to
   recover it. MudBlazor's `md` (960) was that clip; its `lg` (1280) hid the nav from every laptop
   that fits it. The signed-in bar carries two more dropdowns, so it keeps the wider switch. */
.eb-nav-links {
    display: none;
    align-items: center;
    gap: 4px;
}

.eb-mobile-menu {
    display: flex;
}

/* Exists only to catch the trigger's click before MudMenu's activator sees it — laid out as a
   pass-through so the bar's flex rhythm is unchanged. */
.eb-nav-trigger {
    display: flex;
    align-items: center;
}

@media (min-width: 1024px) {
    .eb-nav-links--public { display: flex; }
    .eb-mobile-menu--public { display: none; }
}

@media (min-width: 1280px) {
    .eb-nav-links--backoffice { display: flex; }
    .eb-mobile-menu--backoffice { display: none; }
}

/* ---- Phone nav drawer ---------------------------------------------------
   The panel is the app bar continued downward, not a card floating over the page: same
   --eb-bg chrome, one hairline down its leading edge, flat. It sits ABOVE the bar (MudBlazor
   puts the app bar at 1300 and drawers at 1100, which would have slid it under) because a
   drawer half-covered by the thing that opened it reads as a rendering fault — and because
   the bar's own controls must not stay tabbable behind an overlay. */
.mud-drawer.eb-nav-drawer {
    z-index: 1305;
    width: min(320px, 88vw);
    background-color: var(--eb-bg);
    border-left: 1px solid var(--eb-border);
    box-shadow: none;
}

.mud-overlay-drawer,
.mud-drawer-overlay {
    z-index: 1304;
}

@media (prefers-reduced-motion: reduce) {
    .mud-drawer.eb-nav-drawer,
    .eb-nav-drawer .mud-collapse-container {
        transition: none !important;
    }
}

.eb-nav-drawer-inner {
    display: flex;
    flex-direction: column;
    height: 100%;
    outline: none;
    /* Anchored to the end, so the right inset is the one that matters in landscape. */
    padding-right: env(safe-area-inset-right);
}

.eb-nav-drawer-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex: 0 0 auto;
    min-height: 56px;
    padding: 8px 8px 8px 20px;
    border-bottom: 1px solid var(--eb-border);
}

.eb-nav-drawer-title {
    font-size: 1rem;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: var(--eb-text);
}

.eb-nav-drawer .eb-nav-drawer-head .mud-icon-button {
    width: 44px;
    height: 44px;
    color: var(--eb-body);
}

/* The drawer's copy of the theme toggle and saved listings. Hidden by default: above the
   breakpoint below, the app bar still carries both and two live copies would be a duplicate
   tab stop for the same action. The title takes the slack so the row keeps its shape. */
.eb-nav-drawer-utility {
    display: none;
    align-items: center;
    gap: 2px;
    margin-inline-start: auto;
}

/* The doorway keeps the shape it has on the bar — the one filled control, not another row. */
.eb-nav-drawer .eb-nav-drawer-sell {
    flex: 0 0 auto;
    margin: 16px 20px 4px;
    min-height: 46px;
}

.eb-nav-drawer-menu {
    flex: 1 1 auto;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 8px 0 max(20px, env(safe-area-inset-bottom));
}

/* A hairline between top-level entries only. Nested links live inside the group's collapse
   container, so they are untouched and the rules keep reading as section boundaries. */
.eb-nav-drawer-menu > * + * {
    border-top: 1px solid var(--eb-border);
}

/* "Install app" sits at the foot of the drawer, below the scrolling nav list: it is a utility,
   not a destination, and putting it in the list would rank it alongside Ball Pythons. Outlined,
   because Sell is the drawer's one filled control and the One Orange rule gives it that alone.
   It carries the safe-area inset because it is now the last thing above the home indicator. */
.eb-nav-drawer .eb-install-app {
    flex: 0 0 auto;
    margin: 0 20px;
    margin-bottom: max(16px, env(safe-area-inset-bottom));
    min-height: 46px;
    border-color: var(--eb-border-2);
    color: var(--eb-body);
}

.eb-nav-drawer .eb-install-app:hover {
    border-color: var(--eb-orange);
    color: var(--eb-text);
}

/* iOS install instructions. The steps are a numbered list because the order is the instruction;
   the icons repeat the Share and Add glyphs Safari itself shows, so the visitor is matching
   pictures rather than parsing prose. */
.eb-install-blurb {
    margin: 0 0 16px;
    color: var(--eb-body);
    line-height: 1.6;
}

.eb-install-steps {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin: 0;
    padding: 0;
    list-style: none;
    counter-reset: eb-install-step;
}

.eb-install-step {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    border: 1px solid var(--eb-border);
    border-radius: 12px;
    background: var(--eb-surface-2);
    color: var(--eb-text);
    font-size: 0.92rem;
    line-height: 1.45;
    counter-increment: eb-install-step;
}

.eb-install-step::before {
    content: counter(eb-install-step);
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 22px;
    height: 22px;
    border-radius: 999px;
    /* --eb-orange-on-tint, not --eb-orange-ink: the ink token falls under 4.5:1 on this fill in
       both themes, which is why the trait chips use the same pairing. */
    background: var(--eb-tint-bg);
    color: var(--eb-orange-on-tint);
    font-size: 0.74rem;
    font-weight: 800;
}

.eb-install-step-icon {
    flex: 0 0 auto;
    color: var(--eb-muted);
}

.eb-nav-drawer .mud-nav-link {
    min-height: 52px;
    padding: 12px 20px;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--eb-body);
}

.eb-nav-drawer .mud-nav-link:hover,
.eb-nav-drawer .mud-nav-link:focus-visible {
    background-color: var(--eb-tint-bg);
    color: var(--eb-text);
}

/* A group's own row is the section itself: full ink, heavier, so an opened section reads as a
   heading with destinations under it rather than as one more equal link. */
.eb-nav-drawer .mud-nav-group > .mud-nav-link {
    color: var(--eb-text);
    font-weight: 700;
}

/* Indentation is the container's job, not the link's. MudBlazor pads nested links itself, by
   selectors that measured 56px on a group's own title and 48px on the links inside it — children
   sitting further LEFT than their parent, which is the one thing the hierarchy cannot do. So every
   row is flattened back to the same 20px gutter (the selector chain is deep enough to out-specify
   MudBlazor's at each depth) and each collapse container adds one step, compounding with nesting. */
.eb-nav-drawer.mud-drawer .mud-navmenu .mud-nav-link,
.eb-nav-drawer.mud-drawer .mud-navmenu .mud-nav-group .mud-nav-link,
.eb-nav-drawer.mud-drawer .mud-navmenu .mud-nav-group .mud-nav-group .mud-nav-link,
.eb-nav-drawer.mud-drawer .mud-navmenu .mud-nav-group .mud-nav-group .mud-nav-group .mud-nav-link {
    padding-left: 20px;
}

.eb-nav-drawer .mud-navgroup-collapse {
    padding-left: 18px;
}

/* One weight down inside a section, so an open group reads as heading + destinations. */
.eb-nav-drawer .mud-navgroup-collapse .mud-nav-item .mud-nav-link {
    font-size: 0.92rem;
    font-weight: 500;
}

/* Current page: the bar's active treatment — full ink, 800, on the orange tint the system already
   uses for a selected row. No coloured side stripe (DESIGN.md). MudBlazor's own active state paints
   the row orange on a white wash, which would have put a second orange voice under the Sell button
   and dropped the row to weight 500; these selectors are deep enough to out-specify it at each level. */
.eb-nav-drawer.mud-drawer .mud-navmenu .mud-nav-link.active,
.eb-nav-drawer.mud-drawer .mud-navmenu .mud-nav-group .mud-nav-link.active,
.eb-nav-drawer.mud-drawer .mud-navmenu .mud-nav-group .mud-nav-group .mud-nav-link.active,
.eb-nav-drawer.mud-drawer .mud-navmenu .mud-nav-link[aria-current="page"] {
    color: var(--eb-text);
    /* The only !important in this block, and it is answering one: MudBlazor ships
       `.mud-nav-link.active:not(.mud-nav-link-disabled) { font-weight: 500 !important }`, which no
       amount of specificity beats. Without it the current row was the *lightest* in the drawer. */
    font-weight: 800 !important;
    background-color: var(--eb-tint-bg);
}

.eb-nav-drawer .mud-nav-link .mud-icon-root {
    color: var(--eb-muted);
}

.eb-topnav .eb-nav-item,
.eb-topnav .mud-button {
    text-transform: none;
    font-weight: 600;
    font-size: 0.9rem;
    color: rgba(255, 255, 255, 0.75);
    letter-spacing: 0;
    border-radius: 999px;
}

.eb-topnav .eb-nav-item:hover,
.eb-topnav .mud-button:hover {
    color: var(--eb-text);
    background-color: rgba(255, 255, 255, 0.08);
}

/* Current section: full ink + heavier weight, mirrored by aria-current="page" in markup.
   The species groups are MudMenus, so the class lands on the wrapper and the trigger button
   inside it carries its own colour/weight — the descendant selectors are what make the active
   state visible on a dropdown rather than only on a plain link. */
.eb-topnav .eb-nav-item--active,
.eb-topnav .eb-nav-item--active .mud-button,
[data-theme="light"] .eb-topnav .eb-nav-item--active,
[data-theme="light"] .eb-topnav .eb-nav-item--active .mud-button {
    color: var(--eb-text);
    font-weight: 800;
}

/* The seller doorway — the one filled control in the bar, so it owns the orange.
   Ink text on orange in both themes (DESIGN.md, text-on-orange rule). The drawer carries the same
   control (the bar hides it below sm), and it sits outside .eb-topnav — hence the second selector
   on every rule here, or it renders as an unstyled dark pill. */
.eb-topnav .eb-sell-cta,
.eb-nav-drawer .eb-sell-cta {
    background-color: var(--eb-orange);
    color: var(--eb-on-orange);
    font-weight: 800;
    font-size: 0.9rem;
    text-transform: none;
    border-radius: 999px;
    padding: 6px 18px;
    margin-right: 4px;
    box-shadow: none;
}
/* Light-theme nav rules (higher specificity, ~line 354) would wash the ink text and
   wipe the orange fill on hover — re-assert at matching specificity. */
.eb-topnav .eb-sell-cta:hover,
.eb-nav-drawer .eb-sell-cta:hover,
[data-theme="light"] .eb-topnav .eb-sell-cta,
[data-theme="light"] .eb-nav-drawer .eb-sell-cta,
[data-theme="light"] .eb-topnav .eb-sell-cta:hover,
[data-theme="light"] .eb-nav-drawer .eb-sell-cta:hover {
    background-color: var(--eb-orange);
    color: var(--eb-on-orange);
}
.eb-topnav .eb-sell-cta:hover,
.eb-nav-drawer .eb-sell-cta:hover {
    filter: brightness(1.06);
}

/* --- Header morph search -------------------------------------------------
   The panel hangs off the app bar itself: MudAppBar is position:fixed, so an absolute child with
   top:100% renders as a full-width strip directly under the bar on every route and both bar
   heights. The input inherits the global white outlined-input treatment; only layout lives here. */
.eb-header-search-panel {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    display: flex;
    justify-content: flex-end;
    /* Right edge lands on the container's content edge — the same line the account controls end on,
       so the field hangs off the icon that opened it instead of floating at the far side of the bar.
       Collapses to the phone gutter once the viewport is narrower than the container. */
    padding: 10px max(16px, calc((100% - var(--eb-container-max)) / 2 + 28px)) 14px 16px;
    background: var(--eb-surface);
    border-bottom: 1px solid var(--eb-border);
    box-shadow: 0 12px 28px rgba(0, 0, 0, 0.18);
}

/* MudAutocomplete puts our Class on its inner input control, not on its root — so the root is the
   flex child that has to be sized, or the field fills the strip and sits at the far left of it. */
.eb-header-search-panel > .mud-autocomplete {
    flex: 0 1 560px;
}

.eb-header-search-input {
    width: 100%;
}

/* One suggestion row: the morph name, and the species tag that disambiguates it — "Albino" is a
   different gene in every catalogue that has one, so the tag is content, not decoration. */
.eb-header-search-hit {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    width: 100%;
}

/* The miss. Two lines, quiet, inside the same popover the hits use — a search that returns silence
   reads as broken. The query is echoed and clamped: it is whatever the visitor typed. */
.eb-header-search-empty {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 10px 16px;
}

.eb-header-search-empty-title {
    font-weight: 600;
    color: var(--eb-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.eb-header-search-empty-hint {
    font-size: 0.82rem;
    color: var(--eb-muted);
}

.eb-header-search-hit-species {
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--eb-muted);
    white-space: nowrap;
}

/* The visitor's own controls — theme, saved listings, account — read as one group rather than as
   three more items in a five-control run. The hairline is the bar's own border colour and only
   appears where there is room for it; on a phone the same rule would be noise. */
.eb-nav-utility {
    display: flex;
    align-items: center;
    gap: 4px;
}

.eb-nav-utility {
    margin-left: 10px;
    padding-left: 10px;
    border-left: 1px solid var(--eb-border);
}

/* Wider once there is room. The phone keeps the rule rather than losing it: five 44px controls in
   a row with no gaps was the chunking failure this divider exists to fix, and the phone is where
   the row is tightest — the bar measures ~60px of slack at 390, so 21px buys the grouping back. */
@media (min-width: 768px) {
    .eb-nav-utility {
        margin-left: 14px;
        padding-left: 14px;
    }
}

/* One icon box across the whole bar. MudBlazor's 48px default left the search and theme toggles a
   size apart from the 44px heart beside them — invisible at rest, obvious on hover, where the
   ripple circles disagree. */
.eb-topnav .eb-header-search-toggle,
.eb-topnav .eb-theme-toggle {
    width: 44px;
    min-width: 44px;
    height: 44px;
}

/* "Log in" was the one header control under the comfortable touch target — measured 64x37.2 at
   390px, while the brand, hamburger, theme toggle and heart all sit at exactly 44x44. It clears
   WCAG 2.2's 24x24 floor, but it is the anonymous visitor's only way in on a phone, so it gets the
   same target as everything beside it rather than the smallest one in the row. */
.eb-topnav .eb-login-cta {
    min-height: 44px;
}

/* NavToolbar icon buttons (chat / user) stay light on the dark bar */
.eb-topnav .mud-icon-button {
    color: rgba(255, 255, 255, 0.8);
}

.eb-topnav .mud-icon-button:hover {
    color: var(--eb-text);
}

/* the "Admin" dropdown label sits de-emphasised vs marketplace items */
.eb-nav-admin {
    color: var(--eb-muted) !important;
}

/* Phone chrome prioritises destinations and account actions over repeating the full site name.
   The mark remains a 44px home target; the menu sits at the familiar trailing edge. */
@media (max-width: 767.98px) {
    .eb-container {
        padding-left: max(16px, env(safe-area-inset-left));
        padding-right: max(16px, env(safe-area-inset-right));
    }

    .eb-page {
        padding-top: 24px;
        padding-bottom: 64px;
    }

    .eb-nav-inner {
        gap: 0;
    }

    .eb-topnav .eb-brand {
        min-height: 44px;
        margin-right: 0;
    }

    .eb-topnav .eb-brand-mark {
        width: auto;
        height: 26px;
        margin-right: 0;
        object-fit: contain;
    }

    .eb-mobile-menu {
        order: 10;
        flex: 0 0 44px;
    }

    .eb-mobile-menu .mud-icon-button,
    .eb-topnav .eb-theme-toggle,
    .eb-topnav .eb-fav-nav,
    .eb-topnav .eb-chat-nav {
        width: 44px;
        min-width: 44px;
        height: 44px;
        min-height: 44px;
    }
}

@media (max-width: 359.98px) {
    .eb-container {
        padding-left: max(12px, env(safe-area-inset-left));
        padding-right: max(12px, env(safe-area-inset-right));
    }

    .eb-topnav .eb-brand-mark {
        height: 24px;
    }

    /* Measured 2026-08-13: the bar's children need 352px (logo 62 + search 44 + utility 171 +
       hamburger 44 + gutters), so every viewport below that clipped the right-hand controls.
       Two of the four utility controls move into the drawer rather than shrinking all of them
       under the 44px touch target or hiding one outright — a hidden control with no second home
       is a deleted feature. 88px comes back, which clears 320px with room spare. */
    .eb-topnav .eb-theme-toggle,
    .eb-topnav .eb-fav-nav {
        display: none;
    }

    .eb-nav-drawer-utility {
        display: flex;
    }
}

/* ---- Immersive hero (Home) --------------------------------------------- */

/* The panel is the vitrine: photo area, the search pill straddling the seam, and the step plinth,
   all inside one 20px frame. The near-black base is a literal, not var(--eb-bg): heroes stay dark
   in BOTH themes (DESIGN.md, The Two-Theme Rule), and --eb-bg flips to #FFFFFF on light. */
.eb-hero-immersive {
    position: relative;
    margin-bottom: 56px;
    background: #0C0C0E;
    /* --eb-border-2 (#3A3A40), not --eb-border: on dark the panel sits at 1.13:1 against the page,
       so with the fainter hairline the "vitrine frame" the comment above describes did not visually
       exist. The light theme overrides this to a shadow — see the light block. */
    border: 1px solid var(--eb-border-2);
    border-radius: 20px;
    overflow: hidden;
}

/* The seam, light theme. --eb-border-2 flips to #CFCFD6, which draws a pale hairline around a
   black slab — the panel reads as a hole cut into the page rather than an object resting on it.
   A shadow instead: same near-black, but with weight and a soft edge. */
:root:root[data-theme="light"] .eb-hero-immersive {
    border-color: transparent;
    box-shadow: 0 24px 60px rgba(12, 12, 14, 0.28), 0 2px 8px rgba(12, 12, 14, 0.16);
}

.eb-hero-bg {
    position: relative;
    min-height: 440px;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    /* padding-top is not decorative: the content is bottom-aligned inside a min-height, so once it
       grows past that height (long headline, the credit block, a phone viewport) it would otherwise
       butt straight against the panel's top edge. */
    padding: 36px 48px 84px;
    background-image:
        linear-gradient(180deg, rgba(12, 12, 14, 0.5) 0%, rgba(12, 12, 14, 0.12) 42%, rgba(12, 12, 14, 0.96) 100%),
        var(--eb-hero-image, var(--eb-hero-fallback));
    background-size: cover;
    background-position: center;
}

.eb-hero-eyebrow {
    font-size: 0.74rem;
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--eb-orange-ink);
    margin-bottom: 16px;
    /* Legibility floor over bright patches of an admin/pick-of-week hero photo. */
    text-shadow: 0 1px 12px rgba(0, 0, 0, 0.45);
}

.eb-hero-title {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: clamp(2.6rem, 6vw, 4.8rem);
    line-height: 0.96;
    letter-spacing: -0.035em;
    color: var(--eb-text);
    margin: 0;
    /* 17ch + balance, not 15ch bare: at the desktop clamp ceiling the seeded headline broke as
       "Find the one that stops / you." — a one-word last line. balance evens the lines instead of
       filling the first, and it was already trusted for this heading on mobile. */
    max-width: 17ch;
    text-wrap: balance;
    text-transform: none;
}

.eb-hero-sub {
    /* 0.82, up from 0.72: the sub runs 52ch across, so its far end leaves the solid part of the
       scrim. Over the deepest photo patch it can now reach, 0.82 white holds AA; 0.72 did not. */
    color: rgba(255, 255, 255, 0.82);
    font-size: 1.05rem;
    line-height: 1.6;
    max-width: 52ch;
    margin: 18px 0 0;
    text-shadow: 0 1px 12px rgba(0, 0, 0, 0.4);
}

/* The account/seller actions, sitting between the sub and the search pill.

   Hierarchy is carried by SIZE, not by hue. Both this row's primary and the pill's Search button
   are orange because both are actions and orange is the only action colour we have (The One Orange
   Rule) — so the two are kept from competing by scale and plane instead: the pill is white, 67px
   tall, elevated and overlapping the seam; these are 46px, flat, and sit back on the photo. The
   pill stays the loudest thing in the panel, which is correct — buying needs no account.

   Literal whites and blacks throughout, like the rest of the hero: this panel never flips to the
   light theme (DESIGN.md, The Two-Theme Rule), so a token that flips would break it. */
.eb-hero-cta {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 28px;
}

.eb-hero-cta-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 46px;
    padding: 0 26px;
    border: 1px solid transparent;
    border-radius: 12px;
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 0.95rem;
    letter-spacing: -0.01em;
    text-decoration: none;
    white-space: nowrap;
    transition: background-color 140ms ease, border-color 140ms ease,
                color 140ms ease, box-shadow 140ms ease;
}

/* !important, and it is load-bearing. The competing rule is global-styles.css's body-link colour,
   `body.abp-application-layout main a:not(...)` — roughly (0,12,3) once its eleven :not() classes
   count, which nothing sane in this sheet can out-specify. That file carries an exclusion for
   .eb-hero-cta-btn, but it is served as a bare `/global-styles.css` with NO version query while
   this sheet is busted by `?v=`, so a browser or CDN holding yesterday's copy applies the old rule
   and paints an orange label on an orange fill — the button reads as a blank orange box.
   Declaring the colour here makes the button correct even against a stale global-styles.css. */
.eb-hero-cta-btn--primary {
    background-color: var(--eb-orange-on-dark) !important;
    color: var(--eb-on-orange-on-dark) !important;   /* the hero never flips; nor does its button */
}

/* The heat lamp switching on — the one shadow DESIGN.md spends on a button.

   The colour is restated, not inherited: these are <a> elements, and the sheet's bare
   `a:hover { color: var(--eb-orange-hover) }` (0,1,1) out-specifies the base
   `.eb-hero-cta-btn--primary` (0,1,0). Without this line the label turned the *same* orange as the
   fill it sits on and vanished on hover. Any future <a>-based button needs the same restatement. */
.eb-hero-cta-btn--primary:hover {
    /* !important here only because the resting rule above needs it — without a match, the resting
       declaration would win the cascade and the hover would never warm. */
    background-color: var(--eb-orange-on-dark-hover) !important;
    color: var(--eb-on-orange-on-dark) !important;
    box-shadow: 0 0 32px rgba(232, 99, 10, 0.32);
}

/* The scrim fill is legibility, not glass decoration: the hero photo is admin-set or pick-of-the-
   week, so a hairline-and-white button can land on an arbitrarily bright patch of snake. A flat
   dark wash under it holds the border and label readable whatever the photo does. */
.eb-hero-cta-btn--ghost {
    background-color: rgba(12, 12, 14, 0.42);
    border-color: rgba(255, 255, 255, 0.34);
    color: #FFFFFF !important;          /* same stale-global-styles defence as the primary */
}

.eb-hero-cta-btn--ghost:hover {
    border-color: var(--eb-orange-on-dark);
    color: var(--eb-orange-on-dark) !important;
}

/* The white pill is the seam. It is positioned against the photo area, not the panel, so it hangs
   over the join between the photo and the plinth below and stitches the two halves into one object.
   It is a <label>, so the whole pill — padding, icon and gaps included — focuses the field;
   cursor: text advertises that. */
.eb-hero-search {
    position: absolute;
    left: 48px;
    right: 48px;
    bottom: -34px;
    z-index: 2;
    background: #fff;
    border-radius: 16px;
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.45);
    display: flex;
    align-items: center;
    padding: 10px 10px 10px 24px;
    gap: 14px;
    cursor: text;
    transition: box-shadow 180ms cubic-bezier(0.22, 1, 0.36, 1),
                transform 180ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* The one authored moment on this surface: the front door answering. Focus the field from anywhere
   in the pill and it lifts off the plinth and warms its own edge — state feedback on the primary
   action, which is the only thing DESIGN.md spends shadow on (The Flat-at-Rest Rule). */
.eb-hero-search:focus-within {
    transform: translateY(-3px);
    box-shadow: 0 22px 56px rgba(0, 0, 0, 0.55),
                0 0 0 1px rgba(232, 99, 10, 0.55);
}

@media (prefers-reduced-motion: reduce) {
    .eb-hero-search { transition: none; }
    .eb-hero-search:focus-within { transform: none; }
}

/* The button keeps its own pointer — it submits, it doesn't focus the field. */
.eb-hero-search .eb-hero-search-btn { cursor: pointer; }

/* The bar is white in both themes (it overlaps the never-flipping hero), so these are literals
   like --eb-on-photo rather than theme-flipping tokens — but they are the *documented* White Card
   values, not the lighter greys this component drifted to. The old #8A8A8A placeholder measured
   3.45:1 on white and failed AA; axe never caught it because it does not evaluate ::placeholder. */
.eb-hero-search .eb-hero-search-icon {
    color: #55555C;         /* adornment grey, per the input spec */
    font-size: 1.15rem;
    line-height: 1;
}

.eb-hero-search input {
    flex: 1;
    border: none;
    outline: none;
    background: transparent;
    color: #0C0C0E;         /* input text: 19.6:1 on white */
    font-family: var(--eb-display);
    font-size: 0.98rem;
    min-width: 0;
}

.eb-hero-search input::placeholder {
    color: #6A6A72;         /* 5.41:1 on white */
    opacity: 1;             /* Firefox dims placeholders to 0.54 by default, which would undo it */
}

.eb-hero-search .eb-hero-search-btn {
    background: var(--eb-orange);
    color: var(--eb-on-orange);
    font-weight: 800;
    font-size: 0.92rem;
    padding: 13px 30px;
    border: none;
    border-radius: 12px;
    cursor: pointer;
    white-space: nowrap;
    transition: background 140ms ease;
}

.eb-hero-search .eb-hero-search-btn:hover {
    background: var(--eb-orange-hover);
}

@media (max-width: 720px) {
    .eb-hero-bg { padding: 26px 18px 18px; min-height: 360px; }
    .eb-hero-immersive { margin-bottom: 40px; }
    /* No room to hang the pill over a seam at this width — it goes static at the bottom of the
       photo area, still inside the panel, with the plinth carrying on below it. */
    .eb-hero-search {
        position: static;
        left: auto; right: auto; bottom: auto;
        margin-top: 22px;
        padding-left: 16px;
        gap: 10px;
        flex-wrap: wrap;
    }
    .eb-hero-title {
        max-width: none;
        font-size: clamp(2.25rem, 11vw, 3.2rem);
    }
    .eb-hero-search .eb-hero-search-btn {
        min-height: 44px;
        padding-inline: 18px;
    }
    /* Share the row rather than wrap: two labels this short still fit side by side at 360px once
       the padding comes in, and a wrapped second button would read as an afterthought. */
    .eb-hero-cta { margin-top: 22px; gap: 10px; }
    .eb-hero-cta-btn { flex: 1 1 auto; padding-inline: 16px; }
    /* The plinth's own phone padding lives with the .eb-how rules further down the sheet — it has
       to come AFTER the base .eb-hero-plinth rule to win, since a media query adds no specificity. */
}

/* ---- Section headings --------------------------------------------------- */

.eb-section-head {
    display: flex;
    align-items: center;
    margin-bottom: 20px;
}

.eb-section-title {
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 1.3rem;
    letter-spacing: -0.01em;
    color: var(--eb-text);
    /* These are now real <h2> headings; strip the UA heading margin so they sit flush
       in the flex .eb-section-head row exactly as the old <span> did. */
    margin: 0;
    line-height: 1.2;
}

.eb-section-link {
    font-size: 0.86rem;
    font-weight: 600;
    color: var(--eb-orange-ink);
    cursor: pointer;
}

.eb-section-link:hover {
    color: var(--eb-orange-hover);
}

/* On a phone the fixed Feedback rail sits against the right edge and covers the last third of
   any control parked there — elementFromPoint returns the rail, so the link is not merely
   clipped, it is unclickable. Reserve the rail's width, and give the link a real tap target
   while we are here. Scoped with :has() so heads that carry no right-hand control keep their
   full line width. */
@media (max-width: 700px) {
    .eb-section-head:has(.eb-section-link) { padding-right: 44px; }

    .eb-section-link {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
    }
}

.eb-scale-divider {
    height: 1px;
    width: 100%;
    margin: 12px 0 26px;
    background: var(--eb-border);
}

/* ---- Page titles ---------------------------------------------------------- */

.eb-page-title {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: clamp(2rem, 4vw, 2.6rem);
    line-height: 1.02;
    letter-spacing: -0.03em;
    text-transform: none;
    color: var(--eb-text);
    margin: 0 0 4px;
}

.eb-page-sub {
    color: var(--eb-muted);
    font-size: 0.92rem;
    margin-bottom: 26px;
}

/* The species' other name ("also known as royal pythons"). Quieter than the tagline: it is there
   for the buyer who uses that word and for the crawler, not as a second headline. */
.eb-page-aka {
    color: var(--eb-muted);
    font-size: 0.86rem;
    margin-bottom: 22px;
}

/* Where a tagline precedes it, that tagline's own 26px is already doing the section's spacing —
   reclaim it rather than stacking a second gap. On the morph pages it follows the H1 instead and
   keeps its own margin. */
.eb-page-sub + .eb-page-aka {
    margin-top: -18px;
}

/* highlight marks (legacy) — flat, no block highlight in the Immersive theme */
.eb-mark-yellow,
.eb-mark-orange {
    background: none;
    padding: 0;
    color: var(--eb-orange-ink);
}

/* breadcrumb line above detail pages */
.eb-crumb {
    font-size: 0.84rem;
    font-weight: 600;
    color: var(--eb-muted);
    margin-bottom: 18px;
}

/* detail crumb becomes a row: trail on the left, prev/next on the right */
.eb-crumb--row {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
}

.eb-crumb-trail {
    min-width: 0;
}

@media (max-width: 599.98px) {
    /* See ListingDetail.razor: the current page's own title is the one crumb segment a phone can
       spare, because the h1 under it is already saying it. */
    .eb-crumb-current {
        display: none;
    }
}

.eb-detail-nav {
    margin-left: auto;
    display: flex;
    gap: 8px;
    flex: 0 0 auto;
}

.eb-detail-nav-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.82rem;
    font-weight: 700;
    color: var(--eb-text);
    text-decoration: none;
    padding: 7px 14px;
    border: 1px solid var(--eb-border-2);
    border-radius: 999px;
    white-space: nowrap;
    transition: border-color 140ms ease, color 140ms ease, background-color 140ms ease;
}

.eb-detail-nav-btn:hover {
    border-color: var(--eb-orange);
    color: var(--eb-orange-on-tint);
    background-color: var(--eb-tint-bg);
}

/* boundary of the sequence — shown, but clearly inactive */
.eb-detail-nav-btn--off {
    color: var(--eb-muted);
    border-color: var(--eb-border);
    opacity: 0.5;
    cursor: default;
}

.eb-crumb a,
.eb-crumb .mud-link {
    color: var(--eb-text);
    text-decoration: none;
    font-weight: 600;
}

.eb-crumb a:hover,
.eb-crumb .mud-link:hover {
    color: var(--eb-orange-ink);
}

/* ---- Listing cards -------------------------------------------------------- */

.eb-card {
    background-color: var(--eb-surface);
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    box-shadow: none;
    transition: box-shadow 180ms ease, transform 180ms ease, border-color 180ms ease;
    overflow: hidden;
    cursor: pointer;
}

.eb-card:hover {
    border-color: var(--eb-orange);
    box-shadow: var(--eb-shadow);
    transform: translateY(-3px);
}

/* Browse results — responsive card grid, 5-up above the container cap and 4-up below it.
   5-up was tried once at the 1240px container and reverted: 217px columns left too little width
   for the text stack (title, spec, store, price) under even a 4:3 photo, so cards read as tall
   narrow rectangles. At the 1400px container the same five columns are 254px, which is the width
   the card body is now sized against — the meta row ellipsises its text there rather than
   clipping (see .eb-card-meta). Below the cap the container tracks the viewport, so the
   fifth column would shrink without limit; 1400px steps it back to the 282px 4-up case. */
.eb-card-grid {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 18px;
}

/* Ordering matters: every query here is max-width at equal specificity, so this block has to sit
   above the narrower ones or it would win at 320px too and put 4 cards on a phone. */
@media (max-width: 1400px) {
    .eb-card-grid { grid-template-columns: repeat(4, 1fr); }
}

@media (max-width: 1200px) {
    .eb-card-grid { grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 960px) {
    .eb-card-grid { grid-template-columns: repeat(2, 1fr); }
}

/* Phones keep two columns all the way down — there is deliberately no 1-up step.

   A single full-width card put one animal on screen at a time and made the photo far larger than
   the decision needs, so browsing became scrolling and comparing two morphs meant remembering the
   last one. Two tiles show four animals per screen, which is the point of a grid.

   The cost is width: a 390px phone gives ~170px per card against the 282px column the card body
   was sized for, and ~139px at 320px. The block below retunes the body for that, and every rule in
   it pays off a specific overflow rather than decorating. */
@media (max-width: 560px) {
    /* 18px between two ~170px cards spends 5% of the row on nothing.

       Both card grids are tuned together from here down. .eb-card-grid is browse, morph, store and
       favourites; .eb-bento is the home page's Latest Listings, which is a separate grid because of
       its pick-of-the-week hero tile. They were missed apart once already — browse went 2-up while
       home stayed 1-up, which just looks like the change did not ship. */
    :is(.eb-card-grid, .eb-bento) { gap: 12px; }

    /* pa-3 is 12px a side — 24px out of a 170px card. Asymmetric on purpose: the sides still need
       enough to hold the text off the card edge, but the top gap is doing nothing (the photo above
       it is already a hard edge) and the bottom sits under the price, which is the last thing in
       the stack. Trimming those two is what pulls the whole card up. */
    :is(.eb-card-grid, .eb-bento) .mud-card-content.pa-3 { padding: 7px 9px 8px !important; }

    /* The morph combo is the card's identity, so it keeps both lines and gives up only scale. */
    :is(.eb-card-grid, .eb-bento) .eb-card-title { font-size: 0.86rem; }

    /* Compare keeps its control and its accessible name; only the redundant visible word goes.
       "· 850g · Adult" plus a 62px label does not fit ~119px of content width at 320px, and the
       label is the one part a checkbox sitting beside a listing can afford to lose.

       Losing the label leaves an 18px box as the only target, so the row grows to a 44px touch
       square instead. That costs every phone card ~18px of height, which is the right trade: this
       is a touch-only breakpoint, and a control you cannot reliably hit is worse than a card you
       scroll slightly further. Padding with a negative margin was tried first and is wrong — it
       pulls the control past the card's padding edge and overflows the spec row by 12px. */
    :is(.eb-card-grid, .eb-bento) .eb-compare-toggle-text { display: none; }
    /* ...but only where that control actually exists. Compare is opt-in (Selectable), so browse and
       morph pages have it while home, store, favourites and similar-listings do not. Applied
       unconditionally this reserved a 44px thumb target on cards with nothing to press, which
       surfaced as ~26px of dead air in the text stack — the gap was the touch target's ghost. */
    :is(.eb-card-grid, .eb-bento) .eb-card-price-row:has(.eb-compare-toggle) { min-height: 44px; }
    :is(.eb-card-grid, .eb-bento) .eb-compare-toggle {
        /* width, not min-width: the base rule sets width:fit-content, which resolves against the
           16px box and then fights a min-width for the used size. The side padding goes with it —
           44px already is the target, so 2px either side only spilled the row by 4px. */
        width: 44px;
        min-height: 44px;
        padding: 4px 0;
        justify-content: center;
        flex: 0 0 auto;
    }

    /* Store name and rating stay on one line or they break the reserved-height text stack that
       keeps every card in a row the same height. Truncate the name rather than wrap it. */
    :is(.eb-card-grid, .eb-bento) .eb-store-line {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    /* Equipment cards still carry a chip row here; the listing card's spec strip moved onto the
       photo. The type steps down with the narrower column. */
    :is(.eb-card-grid, .eb-bento) .eb-card-spec { font-size: 0.74rem; }

    /* Close the vertical rhythm to match the narrower column. These are the four gaps between the
       photo and the price, and at 168px each one reads about half again as large as it does in a
       282px card — the same air that looks composed on desktop looks like a hole here. */
    :is(.eb-card-grid, .eb-bento) .eb-card-spec { margin: 2px 0 3px; }
    :is(.eb-card-grid, .eb-bento) .eb-card-meta { font-size: 0.72rem; margin: 2px 0 4px; }
    /* "CB'21 ♀ 2,500 g" and a "Sale −20%" flash share 152px of photo width at 320px. The chip is
       the half that can afford to shrink; below this it would start dropping the weight. */
    :is(.eb-card-grid, .eb-bento) .eb-photo-foot { left: 7px; right: 7px; bottom: 7px; gap: 5px; }
    :is(.eb-card-grid, .eb-bento) .eb-photo-spec { font-size: 0.68rem; gap: 4px; padding: 2px 7px; }
    :is(.eb-card-grid, .eb-bento) .eb-photo-cb { font-size: 0.65rem; }
    :is(.eb-card-grid, .eb-bento) .eb-photo-spec .eb-sex { font-size: 0.95rem; }
    :is(.eb-card-grid, .eb-bento) .eb-card-price-row { margin-top: auto; }
}

/* ---- The last card, when it is on its own ------------------------------------
   Fifteen listings over two columns leaves one card alone on the final row beside a card-shaped
   hole, which reads as a rendering fault rather than as the end of the page. The orphan takes the
   whole row and turns on its side instead - photo left, the same text stack right - so the page
   ends on a deliberate shape.

   `:last-child:nth-child(odd)` is the entire test: it can only be true when the count is odd,
   which at two columns is exactly when the last row holds one card. It is scoped to the two-column
   range; at three columns and wider a short last row reads as a grid, not as a fault. */
@media (max-width: 960px) {
    .eb-card-grid > .eb-card:last-child:nth-child(odd) {
        /* One value, read by the photo column and by the heart that has to stay on top of it. */
        --eb-orphan-photo: min(40%, 220px);
        grid-column: 1 / -1;
        /* The card sets flex-direction inline, so the row direction has to outrank the attribute. */
        flex-direction: row !important;
    }

    .eb-card-grid > .eb-card:last-child:nth-child(odd) .eb-card-photo-link {
        flex: 0 0 var(--eb-orphan-photo);
    }

    /* The heart is a control drawn for a photo - translucent black, blurred backdrop - and the
       card's top-right corner is now the title. Anchor it to the photo's right edge instead
       (44px button, 9px inset, same as the tile). */
    .eb-card-grid > .eb-card:last-child:nth-child(odd) .eb-heart {
        right: auto;
        left: calc(var(--eb-orphan-photo) - 53px);
    }

    /* The photo fills its column rather than keeping the tile's aspect: in a row the height is set
       by the text beside it, and a 4:3 box would leave a band of card colour under the image. */
    .eb-card-grid > .eb-card:last-child:nth-child(odd) .eb-card-photo {
        height: 100%;
        border-bottom: 0;
        border-right: 1px solid var(--eb-border);
    }

    .eb-card-grid > .eb-card:last-child:nth-child(odd) .eb-card-photo img,
    .eb-card-grid > .eb-card:last-child:nth-child(odd) .eb-card-photo > div {
        height: 100% !important;
        aspect-ratio: auto !important;
    }

    /* The text column is the flexible one, and min-width:0 is what lets the title ellipse inside it
       rather than push the card wider than its row. */
    .eb-card-grid > .eb-card:last-child:nth-child(odd) .mud-card-content {
        min-width: 0;
        justify-content: center;
    }
}

/* ---- ...and the same on the home page ----------------------------------------
   The bento is the same grid problem with the opposite parity and its own breakpoint, so it needs
   its own block rather than a longer selector list on the one above.

   Parity, because the first tile - the pick of the week - spans both columns and takes a row to
   itself here. That shifts every tile after it, so the card left alone on the last row is the
   even-numbered child, not the odd one: measured, the twelve tiles lay out 1 | 2+3 | 4+5 | 6+7 |
   8+9 | 10+11 | 12.

   Breakpoint, because the bento goes two across at 860px where the browse grid does it at 960px.
   Between the two it is still three across, where last-child parity means nothing. */
@media (max-width: 860px) {
    .eb-bento > .eb-card:last-child:nth-child(even) {
        /* One value, read by the photo column and by the heart that has to stay on top of it. */
        --eb-orphan-photo: min(40%, 220px);
        grid-column: 1 / -1;
        /* The card sets flex-direction inline, so the row direction has to outrank the attribute. */
        flex-direction: row !important;
    }

    .eb-bento > .eb-card:last-child:nth-child(even) .eb-card-photo-link {
        flex: 0 0 var(--eb-orphan-photo);
    }

    /* The heart is a control drawn for a photo - translucent black, blurred backdrop - and the
       card's top-right corner is now the title. Anchor it to the photo's right edge instead
       (44px button, 9px inset, same as the tile). */
    .eb-bento > .eb-card:last-child:nth-child(even) .eb-heart {
        right: auto;
        left: calc(var(--eb-orphan-photo) - 53px);
    }

    /* The photo fills its column rather than keeping the tile's aspect: in a row the height is set
       by the text beside it, and a 4:3 box would leave a band of card colour under the image. */
    .eb-bento > .eb-card:last-child:nth-child(even) .eb-card-photo {
        height: 100%;
        border-bottom: 0;
        border-right: 1px solid var(--eb-border);
    }

    .eb-bento > .eb-card:last-child:nth-child(even) .eb-card-photo img,
    .eb-bento > .eb-card:last-child:nth-child(even) .eb-card-photo > div {
        height: 100% !important;
        aspect-ratio: auto !important;
    }

    /* The text column is the flexible one, and min-width:0 is what lets the title ellipse inside it
       rather than push the card wider than its row. */
    .eb-bento > .eb-card:last-child:nth-child(even) .mud-card-content {
        min-width: 0;
        justify-content: center;
    }
}

.eb-card-photo {
    overflow: hidden;
    background-color: var(--eb-photo-bg);
    border-bottom: 1px solid var(--eb-border);
}

.eb-card-photo img {
    transition: transform 300ms ease;
}

.eb-card:hover .eb-card-photo img {
    transform: scale(1.04);
}

@media (prefers-reduced-motion: reduce) {
    .eb-card, .eb-card-photo img { transition: none; }
    .eb-card:hover { transform: none; }
    .eb-card:hover .eb-card-photo img { transform: none; }
}

/* Two lines, reserved rather than merely capped. Titles carry the morph combo, so their length
   varies wildly ("Pastel" vs "Banana Pastel Clown Het Piebald"); left unclamped, a 3-line title in
   one card gave its row-mates up to 42px of dead air, because the price row's margin-top:auto
   absorbed the difference. Reserving makes every card's text stack identical, so there is nothing
   left to absorb. Clamped at 2 rather than 1 — truncating the genetics is not an option, and the
   card gets its height back from the specimen chip and the merged meta row instead. */
.eb-card-title {
    color: var(--eb-text);
    font-weight: 600;
    line-height: 1.3;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    min-height: 2.6em; /* 2 lines at line-height 1.3 — the reserve depends on that value */
}

.eb-price {
    font-weight: 800;
    color: var(--eb-orange-ink);
    font-size: 1.15rem;
    letter-spacing: -0.01em;
    line-height: 1.3;
}

.eb-price .eb-cur {
    font-size: 0.86em;
    font-weight: 700;
    margin-right: 1px;
}

/* struck-through original price shown next to a sale price */
.eb-price-was {
    color: var(--eb-muted);
    font-size: 0.85rem;
    font-weight: 600;
    text-decoration: line-through;
}

/* Digits that stack — card prices, was-prices, the seller table's price column — take tabular
   figures so a column of prices aligns instead of wobbling on proportional 1s. */
.eb-price,
.eb-price-was,
.eb-c-price {
    font-variant-numeric: tabular-nums;
}

/* Detail-page context: the was-price sits beside the 2.4rem .eb-price-lg, so it steps
   up and takes a baseline nudge to align with the big figure. */
.eb-price-was--lg {
    font-size: 1.2rem;
    padding-bottom: 8px;
}

/* Sale price + was-price on one baseline (detail pages, both listing kinds). */
.eb-price-row {
    display: flex;
    align-items: flex-end;
    gap: 12px;
}

/* "On sale · ends N" line under the detail price. */
.eb-sale-note {
    color: var(--eb-orange-ink);
    font-weight: 700;
    font-size: 0.9rem;
}

/* "Sale −N%" badge over a card photo — bottom-right, since the heart owns the top-right corner */
/* Static: it is the right-hand child of .eb-photo-foot, which does the placing. Absolute, it
   overlapped the spec chip on a 170px phone card the moment a sale and a weight coincided. */
.eb-sale-badge {
    margin-left: auto;
    flex: 0 0 auto;
    font-size: 0.68rem;
    font-weight: 800;
    letter-spacing: 0.02em;
    color: var(--eb-on-orange-on-dark);
    background: var(--eb-orange-on-dark);
    border-radius: 6px;
    padding: 3px 8px;
}

.eb-label {
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0;
    text-transform: none;
    color: var(--eb-muted);
}

/* shipping shown as muted text (not a filled pill) in the card footer */
.eb-ship-pill {
    display: inline-block;
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--eb-muted);
    background: none;
    border-radius: 0;
    padding: 0;
}

.eb-chip-trait.mud-chip {
    font-size: 0.76rem;
    font-weight: 700;
    letter-spacing: 0;
    text-transform: none;
    background-color: var(--eb-tint-bg);
    border: 1px solid var(--eb-tint-border);
    color: var(--eb-orange-on-tint);
    border-radius: 999px;
}

/* The photo's bottom strip: the specimen chip left, the sale flash right. A strip rather than two
   absolutely-placed badges, so they cannot collide at any card width. pointer-events:none keeps the
   stretched photo link underneath working — nothing in here is interactive. */
.eb-photo-foot {
    position: absolute;
    left: 10px;
    right: 10px;
    bottom: 10px;
    display: flex;
    align-items: flex-end;
    flex-wrap: wrap;
    gap: 6px 8px;
    pointer-events: none;
}

/* The specimen chip — year, sex, weight in one dark-glass tag, the three facts a keeper reads
   first. They sit on the animal rather than in a row of their own beneath it, which is what pays
   for the shorter card. Blur is legibility here, not decoration: the chip lies over photography
   whose brightness we do not control. Permanently dark in both themes, like every photo overlay.
   No separators — a mono year, a coloured glyph and a number with a unit are already three
   distinct things, and dots would only add clutter over a photo. */
.eb-photo-spec {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    /* Never shrinks. On a 320px phone a sale flash leaves the chip 42px, which renders "CB'2…" —
       the chip drops to its own line above the flash instead of eating its own data. */
    flex: 0 0 auto;
    max-width: 100%;
    font-size: 0.76rem;
    font-weight: 700;
    line-height: 1.35;
    color: var(--eb-on-photo);
    /* 0.82, not the CB tag's old 0.75: the chip now carries the coloured sex glyph, and against a
       blown-out white photo the thinner glass put female at 3.6:1 — over the 3:1 graphical floor
       but under the 4.5:1 the system sets for a colour that carries meaning. */
    background: rgba(12, 12, 14, 0.82);
    border-radius: 6px;
    padding: 3px 9px;
    white-space: nowrap;
    overflow: hidden;
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
}

/* Mono for the year alone — a measurement, and the one place the system allows the face. */
.eb-photo-cb {
    font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace;
    font-size: 0.72rem;
    letter-spacing: -0.02em;
}

.eb-photo-weight {
    font-variant-numeric: tabular-nums;
}

/* Smaller than the spec strip's 1.2rem: the chip's own line box is 15px, and the glyph has a
   dark ground behind it here rather than the card surface, so it needs less mass to read. */
.eb-photo-spec .eb-sex {
    font-size: 1.05rem;
}

.eb-photo-spec .eb-sex--m { color: var(--eb-sex-m-on-dark); }
.eb-photo-spec .eb-sex--f { color: var(--eb-sex-f-on-dark); }

/* The card's single secondary row: maturity, category and the seller, which were three rows of
   the same muted grey. One line, never wrapping — the text stack's height is reserved so every
   card in a row matches, and a wrap would break that. Truncation runs right-to-left through the
   store name and its rating, so the animal's own data is the last thing to go. */
.eb-card-meta {
    display: flex;
    align-items: baseline;
    gap: 5px;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    color: var(--eb-muted);
    font-size: 0.78rem;
    line-height: 1.45;
    min-height: 1.45em;
    margin: 3px 0 7px;
}

.eb-card-meta > .eb-spec-dot { flex: 0 0 auto; }

.eb-card-meta > :is(.eb-spec-maturity, .eb-spec-category, .eb-store-line) {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The seller is the only part that gives way. Flex shrinks proportionally, so with all three
   present on a 254px card even a 1:3 ratio still clipped "Hognose" to "Hogno…" and "Adult" to
   "Ad…" — the animal's own two fields are short and are the reason the row exists, so they are
   pinned and the store name absorbs the whole deficit. */
.eb-card-meta > :is(.eb-spec-maturity, .eb-spec-category) { flex: 0 0 auto; }
.eb-card-meta > .eb-store-line { flex: 0 1 auto; }

.eb-store-line {
    color: var(--eb-muted);
    font-size: 0.78rem;
}

.eb-store-line .mud-link {
    color: var(--eb-muted);
}

.eb-store-line .mud-link:hover {
    color: var(--eb-text);
}

.eb-store-line .eb-star {
    color: var(--eb-orange-ink);
}

/* ---- Home "Featured today" grid + "how it works" -------------------------- */

/* Store cards carry a name, a location and a count rather than a photo-led text stack, so they
   take a narrower column than a listing does: 6-up is 211px at the 1400px container, near enough
   the 221px these cards already run at 5-up today. Steps back to 5 at the cap, then follows its
   existing ladder. Same source-order rule as .eb-card-grid — widest query first. */
.eb-feature-grid {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 16px;
}

@media (max-width: 1400px) {
    .eb-feature-grid { grid-template-columns: repeat(5, 1fr); }
}

@media (max-width: 1180px) {
    .eb-feature-grid { grid-template-columns: repeat(4, 1fr); }
}

@media (max-width: 960px) {
    .eb-feature-grid { grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 680px) {
    .eb-feature-grid { grid-template-columns: repeat(2, 1fr); }
}

/* Stays 2-up on phones — one store card per row wastes the viewport. */
@media (max-width: 460px) {
    .eb-feature-grid { gap: 12px; }
}

/* Breeders directory — A–Z letter headings */
.eb-letter-head {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 1.15rem;
    letter-spacing: -0.01em;
    color: var(--eb-orange-ink);
    margin: 28px 0 14px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--eb-border);
}

/* ---- Latest Stores cards (home) ------------------------------------------ */

.eb-store-card-link {
    display: block;
    height: 100%;
}

.eb-store-card {
    background-color: var(--eb-surface);
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    padding: 22px 16px 20px;
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    height: 100%;
    transition: box-shadow 180ms ease, transform 180ms ease, border-color 180ms ease;
}

.eb-store-card:hover {
    border-color: var(--eb-orange);
    box-shadow: var(--eb-shadow);
    transform: translateY(-3px);
}

@media (prefers-reduced-motion: reduce) {
    .eb-store-card { transition: none; }
    .eb-store-card:hover { transform: none; }
}

.eb-store-card-avatar {
    width: 64px;
    height: 64px;
    min-width: 64px;
    border-radius: 999px;
    background: var(--eb-tint-bg);
    color: var(--eb-orange-on-tint);
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 1.6rem;
    overflow: hidden;
    margin-bottom: 12px;
    padding: 7px;
    box-sizing: border-box;
}

.eb-store-card-avatar img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

.eb-store-card-name {
    font-family: var(--eb-display);
    font-weight: 600;
    font-size: 1rem;
    color: var(--eb-text);
    line-height: 1.3;
}

.eb-store-card-loc {
    font-size: 0.8rem;
    color: var(--eb-muted);
    margin-top: 2px;
}

.eb-store-card-meta {
    font-size: 0.8rem;
    color: var(--eb-body);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 8px;
}

.eb-store-card-meta .eb-star {
    color: var(--eb-orange-ink);
}

.eb-store-card-dot {
    color: var(--eb-muted);
}

/* ---- Batch action bar (My Animals multi-select) -------------------------- */

.eb-batch-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px;
    padding: 8px;
    background-color: var(--eb-tint-bg);
    border: 1px solid var(--eb-tint-border);
    border-radius: 12px;
}

/* The plinth the exhibit stands on: the base of the hero panel, inside its frame. Near-black
   literals throughout for the same reason as .eb-hero-immersive — this band never flips to light.
   padding-top clears the 34px of search pill that hangs down into it, plus breathing room. */
.eb-hero-plinth {
    display: block;
    background: #0C0C0E;
    border-top: 1px solid rgba(255, 255, 255, 0.09);
    /* 56 top, not 66: the pill hangs 34px into this band, so this is the overhang plus 22px of
       clearance. It cannot go below ~40 without the first step title touching the pill. */
    padding: 56px 48px 30px;
}

.eb-how {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0;
    margin: 0;
    border: none;
    background: none;
    padding: 0;
    border-radius: 0;
}

/* Three bays divided by hairlines, the way a caption rail under a display case is divided — not
   three cards, and not three unattached top rules floating on the page background (which is what
   these were, and why the sequence read as debris between the hero and the grid). */
.eb-how > div {
    border-top: none;
    border-left: 1px solid rgba(255, 255, 255, 0.10);
    padding: 0 30px;
}

.eb-how > div:first-child {
    border-left: none;
    padding-left: 0;
}

.eb-how > div:last-child {
    padding-right: 0;
}

/* An <h3> since these steps are real headings. Two consequences of that, both handled here.
   margin-top is pinned so the UA heading margin can't push the strip off its grid. And the colour
   is !important because global-styles.css blanket-styles headings inside the Blazor app:
   `body.abp-application-layout h3` (0,0,1,2) and `[data-theme="light"] body.abp-application-layout
   h3` (0,0,2,2) would repaint these titles white on dark and near-black on light. Two classes beat
   the first but not the second, and that sheet loads later, so a specificity tie loses too — same
   fight, same remedy as .mud-button-filled-primary above. */
/* White, not orange. These numerals are labels, not actions, and orange painted them as a third
   and fourth and fifth call to action in a panel that already holds two orange fills — at which
   point the scarcity The One Orange Rule depends on is gone. The 800 weight carries the hierarchy
   on its own. --eb-on-photo never flips, which is what this never-flipping band needs.
   !important stays for the reason it was added: global-styles.css blanket-styles headings inside
   the Blazor app — `body.abp-application-layout h3` (0,0,1,2) and its [data-theme="light"] twin
   (0,0,2,2) — and that sheet loads later, so a specificity tie loses too. */
.eb-how .eb-how-title {
    font-family: var(--eb-display);
    color: var(--eb-on-photo) !important;
    font-weight: 800;
    font-size: 1.08rem;
    letter-spacing: -0.01em;
    margin-top: 0;
    margin-bottom: 6px;
}

/* --eb-on-photo-dim for the same reason: --eb-muted flips to #63636B on light, which is invisible
   on this near-black band. This token is defined never to flip. */
.eb-how-body {
    font-size: 0.86rem;
    line-height: 1.6;
    color: var(--eb-on-photo-dim);
    max-width: 34ch;
}

@media (max-width: 760px) {
    /* Stacked, the divider has to move with the flow: hairlines between rows, not down their side. */
    .eb-how { grid-template-columns: 1fr; }

    .eb-how > div,
    .eb-how > div:first-child {
        border-left: none;
        border-top: 1px solid rgba(255, 255, 255, 0.10);
        padding: 16px 0 0;
    }

    .eb-how > div:first-child {
        border-top: none;
        padding-top: 0;
    }

    .eb-how-body { max-width: none; }
}

/* Phone: the plinth goes away entirely. Stacked, three steps cost roughly a whole viewport of
   scroll before the visitor has seen a single animal, and on a phone the listings have to come
   first. The steps are self-evident marketplace mechanics, not information the buyer is missing —
   so this drops the section and its heading together, leaving no dangling label in the a11y tree.
   Placed after the base .eb-hero-plinth rule: a media query carries no extra specificity, so the
   same declaration earlier in the sheet would simply lose to it. */
@media (max-width: 720px) {
    .eb-hero-plinth { display: none; }
}

/* ---- Filter rail / bar (Browse) ------------------------------------------- */

/* Wraps rather than placing a fixed set of children. The primary row is variable now that species
   are database rows: a category with a morph catalogue AND a type list (boas, hognoses) shows seven
   controls where ball pythons show six, and one an admin adds later could show more. The former
   six-column grid template had no column for a seventh child, so grid placed it in an implicit auto
   column and the row's min-content width exceeded the container — the bar ran off the right edge.
   Flex-wrap sizes from the controls themselves, so any count lays out and the surplus drops to a
   second row instead of overflowing. */
.eb-filter-bar {
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    background: var(--eb-surface);
    padding: 12px 16px;
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 8px;
    margin-bottom: 16px;
}

.eb-filter-bar .mud-input-outlined-border {
    border-radius: 10px;
}

/* Basis, not width: in a wrapping row the flex basis decides both the resting proportion and where
   the row breaks. min-width:0 is what lets a field shrink past its intrinsic width rather than
   forcing the overflow back.

   The max-width keeps a wrapped row honest. Six controls fill one row and never reach the cap, so
   the ball-python bar is unchanged; seven wrap, and without a ceiling the lone select left on the
   second row grows to consume the whole of it — an 850px "Maturity" dropdown. Capped, the surplus
   row stays left-aligned at a sane field width.

   Both selectors are needed because MudBlazor is inconsistent about where our Class lands:
   MudTextField puts it on its own root, but MudSelect and MudAutocomplete wrap themselves in an
   outer .mud-select and put it on the inner .mud-input-control — so the flex item for every dropdown
   in this bar is the wrapper, and sizing .eb-filter-item alone silently misses all of them. */
/* The bases are set so the widest case — morph picker, type, keyword, sex, maturity, More and sort,
   seven controls — still fits one row at the 1240px container, then wraps below it where the tablet
   rules take over. Wrapping breaks the line on the basis total before flex-shrink ever runs, so the
   basis, not the shrink factor, is what decides whether a row holds. */
.eb-filter-bar > .mud-select,
.eb-filter-bar > .eb-filter-item {
    flex: 0.9 1 132px;
    min-width: 0;
    max-width: 280px;
}

.eb-filter-bar > .mud-select.mud-autocomplete,
.eb-filter-bar > .eb-filter-item--wide {
    flex: 1.2 1 170px;
    max-width: 400px;
}

/* The inner control fills whichever wrapper it sits in. */
.eb-filter-bar .eb-filter-item {
    width: 100%;
    min-width: 0;
}

.eb-filter-bar .eb-filter-item--num {
    max-width: 110px;
    min-width: 90px;
}

/* Unlabeled controls (sex pills, More, Sort) carry no caption, so they must reserve the
   same 24px label headroom the stacked-label inputs get (:1912/:1920) — otherwise they
   center against the taller labelled cells and float ~11px above the input-box baseline
   ("filters not lined up"). Matching the top offset lines their box tops up with the fields. */
.eb-filter-bar .eb-seg {
    align-self: start;
    margin-top: 24px;
}

/* Inside the filter bar the sex pills wear the row's own language. Every neighbour here is a white
   outlined input, so a dark segmented control read as a different kind of thing — the group faded
   into the bar behind it while the fields in front of it were bright. Same instrument, dressed for
   its container: on the morph pages, where the surroundings are dark, .eb-seg keeps its dark track.
   The active pill stays orange in both, because that is the one thing the control is saying. */
.eb-filter-bar .eb-seg {
    background: #FFFFFF;
    border-color: #C9C9D0;
}

.eb-filter-bar .eb-seg-btn {
    color: #0C0C0E;
}

.eb-filter-bar .eb-seg-btn--on {
    color: var(--eb-on-orange);
}

.eb-filter-bar .eb-seg-btn:not(.eb-seg-btn--on):hover {
    background: #EDEDF1;
}

/* "More filters" toggle — sits with the primary filters; badge counts hidden active filters */
.eb-more-btn {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    border: 1px solid var(--eb-border-2);
    background: none;
    color: var(--eb-body);
    font-family: var(--eb-display);
    font-weight: 600;
    font-size: 0.86rem;
    min-height: 40px;
    padding: 8px 12px;
    border-radius: 10px;
    cursor: pointer;
    white-space: nowrap;
    align-self: start;
    margin-top: 24px; /* match the stacked-label headroom so it lines up with the input boxes */
    transition: border-color 140ms ease, color 140ms ease;
}
.eb-more-btn:hover { color: var(--eb-text); border-color: var(--eb-orange); }
.eb-more-btn--open { color: var(--eb-text); border-color: var(--eb-orange); }
.eb-more-count {
    background: var(--eb-orange);
    color: var(--eb-on-orange);
    font-size: 0.7rem;
    font-weight: 800;
    border-radius: 999px;
    min-width: 18px;
    height: 18px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0 5px;
}

/* Sort — a labelled menu button showing the current order at rest (not a mystery icon),
   pinned to the row's right edge on desktop. Matches the "More filters" button's weight. */
/* headroom to match the labelled input boxes. margin-left:auto only bites when the row has slack —
   on a filled row the growing fields have already eaten it — so its job is the wrapped case: a
   category with enough filters to break the row leaves sort pinned to the right edge under the other
   actions, rather than dangling alone at the left margin. */
.eb-sort-slot { align-self: start; margin-top: 24px; margin-left: auto; }
.eb-sort-btn { align-self: center; }
.eb-sort-btn .mud-button-root {
    border: 1px solid var(--eb-border-2);
    border-radius: 10px;
    color: var(--eb-body);
    font-family: var(--eb-display);
    font-weight: 600;
    font-size: 0.86rem;
    text-transform: none;
    min-height: 40px;
    padding: 8px 12px;
}
.eb-sort-btn .mud-button-root:hover { color: var(--eb-text); border-color: var(--eb-orange); }

/* Secondary filters — full-width collapse row inside the bar. flex-basis 100% (not the old
   grid-column: 1 / -1) is what forces its own row now that the bar is a wrapping flex container. */
.eb-filter-more {
    display: none;
    flex: 0 0 100%;
    gap: 8px;
    flex-wrap: wrap;
    padding-top: 12px;
    border-top: 1px solid var(--eb-border);
}
.eb-filter-more--open { display: flex; }

.eb-active-label {
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--eb-muted);
}

.eb-clear-link {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--eb-muted);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 3px;
    background: none;
    border: none;
    padding: 0;
}

.eb-clear-link:hover {
    color: var(--eb-text);
}

/* Quiet, always-visible help affordance next to the result count — the specialist genetics
   shorthand in every title/morph search has no other explanation anywhere on the page. */
.eb-morph-help {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--eb-muted);
    cursor: pointer;
    background: none;
    border: none;
    padding: 0;
}

.eb-morph-help .mud-icon-root { font-size: 1rem; }

.eb-morph-help:hover {
    color: var(--eb-text);
}

/* ---- Detail page ---------------------------------------------------------- */

.eb-gallery-main {
    border-radius: 16px;
    border: 1px solid var(--eb-border);
    background-color: var(--eb-surface);
    overflow: hidden;
    width: 100%;
    /* Sized by ratio, not a fixed px height, so the box hugs the photo at every
       viewport width (a fixed 480px left ~200px of dead space inside the frame
       on phones). 4:3 at the desktop column width ≈ the old 480px. */
    height: auto;
    aspect-ratio: 4 / 3;
    max-height: 520px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.eb-gallery-main img {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: 95%;
    object-fit: contain;
    display: block;
}

/* Thumbnail strip under the main gallery photo. */
.eb-thumb-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 8px;
}

/* Welfare advisory shown under the main photo when a listing carries a morph linked to a
   hereditary neurological ("wobble") defect. Caution amber, not brand orange — a warning must
   not read as a call to action. Quiet by design: informs the buyer, doesn't shout. */
.eb-welfare-note {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin-top: 12px;
    padding: 12px 14px;
    border: 1px solid var(--eb-welfare-border);
    background: var(--eb-welfare-bg);
    border-radius: 12px;
}

.eb-welfare-note-icon {
    font-size: 1.25rem;
    color: var(--eb-welfare-accent);
    flex: 0 0 auto;
    margin-top: 1px;
}

.eb-welfare-note-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.eb-welfare-note-title {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-welfare-accent);
}

.eb-welfare-note-list {
    margin: 2px 0 0;
    padding-left: 18px;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.eb-welfare-note-list li {
    font-size: 0.875rem;
    line-height: 1.5;
    color: var(--eb-text);
}

.eb-welfare-note-body {
    margin-top: 4px;
    font-size: 0.82rem;
    line-height: 1.5;
    color: var(--eb-muted);
}

/* Route to the welfare policy from the advisory itself — the page raising the concern is where
   a buyer wants the detail, not the footer. Welfare accent, not brand orange: this whole block
   must never read as a call to action. */
.eb-welfare-note-link {
    align-self: flex-start;
    margin-top: 6px;
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--eb-welfare-accent);
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* Skip link — off-screen until focused, then a real button over the app bar. */
.eb-skip-link {
    position: fixed;
    top: -100px;
    left: 16px;
    z-index: 2000;
    padding: 10px 16px;
    border-radius: 10px;
    background: var(--eb-orange);
    color: var(--eb-on-orange);
    font-weight: 700;
    font-size: 0.9rem;
    text-decoration: none;
    transition: top 140ms ease;
}

.eb-skip-link:focus,
.eb-skip-link:focus-visible {
    top: 12px;
}

@media (prefers-reduced-motion: reduce) {
    .eb-skip-link {
        transition: none;
    }
}

/* The skip target must not draw a focus ring of its own — the user asked to move, not to
   highlight a container. */
#eb-main:focus {
    outline: none;
}

/* Publish-run summary on the Traits admin. Shows only rows needing attention — a run where 40
   files are simply awaiting review must not print 40 lines of nothing-happened. */
.eb-morph-publish {
    margin: 0 0 20px;
    padding: 14px 16px;
    border: 1px solid var(--eb-border);
    border-radius: 12px;
    background: var(--eb-surface);
}

.eb-morph-publish-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 8px;
}

.eb-morph-publish-title {
    font-weight: 700;
    color: var(--eb-text);
}

.eb-morph-publish-list {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.eb-morph-publish-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 10px;
    font-size: 0.86rem;
    padding: 4px 0;
    border-top: 1px solid var(--eb-border);
}

.eb-morph-publish-row code {
    color: var(--eb-text);
}

.eb-morph-publish-outcome {
    font-weight: 600;
}

.eb-morph-publish-row--ok .eb-morph-publish-outcome { color: var(--eb-success); }
.eb-morph-publish-row--bad .eb-morph-publish-outcome { color: var(--eb-error); }
.eb-morph-publish-row--muted .eb-morph-publish-outcome { color: var(--eb-muted); }

.eb-morph-publish-msg {
    color: var(--eb-muted);
    flex: 1 1 240px;
}

.eb-morph-publish-foot {
    margin-top: 10px;
    font-size: 0.84rem;
    color: var(--eb-muted);
}

/* ---- Morph landing pages (/morphs/{slug}) --------------------------------- */

/* Breadcrumb: most arrivals come cold from a search result with no history behind them. */
.eb-crumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-bottom: 10px;
    font-size: 0.8rem;
    color: var(--eb-muted);
}

.eb-crumbs a {
    color: var(--eb-muted);
    text-decoration: none;
}

.eb-crumbs a:hover {
    color: var(--eb-orange-ink);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.eb-crumbs [aria-current="page"] {
    color: var(--eb-text);
    font-weight: 600;
}

/* The gene spec strip: the page's identity, and the specialist promise kept. Inheritance mode,
   super form, first-produced year and welfare status are DATA here, scanned the way a keeper scans
   sex and weight on a listing — not sentences buried in a paragraph. Availability leads and carries
   the orange, because price and action are the only things orange is allowed to mean. */
/* Flex, not grid: items grow to fill their row, so a 5-item strip never leaves a dead cell
   showing through where a 6th would be.
   The divisions are a 1px gap over a border-coloured container rather than a border per item.
   Per-item borders only drew vertically, which was invisible on desktop (one row) and wrong the
   moment the strip wrapped: on a phone the six items stacked into four rows with nothing between
   them, so "Super form: Super Pastel" and "Welfare: none documented" read as one paragraph. The
   gap draws both axes, and since flex items grow to fill their row there is never a stretch of
   bare track showing through. */
.eb-morph-spec {
    display: flex;
    flex-wrap: wrap;
    gap: 1px;
    margin-top: 14px;
    margin-bottom: 24px;
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    background: var(--eb-border);
    overflow: hidden;
}

.eb-morph-spec-item {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 3px;
    flex: 1 1 160px;
    padding: 12px 16px;
    background: var(--eb-surface);
}

/* Availability sits on the recessed surface so the eye lands on it first in the strip. */
.eb-morph-spec-item--lead {
    background: var(--eb-surface-2);
}

/* Phones keep two columns down to 320px. The 160px basis broke to one item per row there, turning
   a six-item strip into six stacked rows of mostly empty line — 85px of scroll bought nothing, on
   the screen where scroll is dearest. A full-width lead row was tried and cost the same. The
   labels are short enough to hold at 142px; values that don't ("Incomplete dominant") wrap, which
   is cheaper than a row. */
@media (max-width: 560px) {
    .eb-morph-spec-item {
        flex-basis: 120px;
        padding: 11px 13px;
    }
}

.eb-morph-spec-label {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-muted);
}

.eb-morph-spec-value {
    font-size: 0.95rem;
    font-weight: 700;
    line-height: 1.35;
    color: var(--eb-text);
}

.eb-morph-spec-price {
    display: block;
    margin-top: 1px;
    font-size: 0.86rem;
    font-weight: 700;
    color: var(--eb-orange-ink);
}

.eb-morph-spec-value--none {
    font-weight: 600;
    color: var(--eb-muted);
}

.eb-morph-spec-value--ok {
    font-weight: 600;
    color: var(--eb-body);
}

/* Welfare accent, never brand orange — a concern must not read as a call to action. */
.eb-morph-spec-value--warn {
    color: var(--eb-welfare-accent);
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* Page rhythm. .eb-section-title ships margin:0 for .eb-section-head flex rows; this page uses it
   bare, so unscoped every heading sat flush on the welfare note above and the grid below, and the
   H1's 4px bottom (sized for a tagline that never renders here) crammed it onto the spec strip.
   The scale: identity block tight (14px H1→strip via .eb-morph-spec), sections generous (40px
   above a heading, 16px below — more above than below), heading+tagline one voice (6px). */
.eb-morph-page .eb-section-title {
    margin: 40px 0 16px;
}

.eb-morph-page .eb-section-title:has(+ .eb-page-sub) {
    margin-bottom: 6px;
}

.eb-morph-page .eb-page-sub {
    margin-bottom: 18px;
}

/* The advisory must never merge with the stock heading; collapses into the 40px when no controls
   row follows. */
.eb-morph-page .eb-welfare-note {
    margin-bottom: 20px;
}

/* Sparse stock is the steady state on a permanent page, not the edge case: inventory churns
   underneath while the page stays. The shared 5-track grid kept its tracks whatever the count, so
   two animals sat beside three empty slots — a marketplace advertising that it is empty. auto-fit
   collapses the unused tracks, the max stops two cards stretching to half the page each, and
   justify-content packs them left so the row reads as "these two" rather than "three missing". */
/* Gated at 961px on purpose. The base grid already steps down to two columns at 960 and stays
   there on phones, so there is no empty-track problem left to solve down there — and because this
   rule sits after those max-width queries at equal specificity, an ungated version overrode them
   and pinned a phone's card to 240px inside a 358px container while every other page went full
   width. This only applies where the base grid is three columns or more, the only place a void
   appears. */
@media (min-width: 961px) {
    .eb-card-grid--sparse {
        grid-template-columns: repeat(auto-fit, minmax(200px, 240px));
        justify-content: start;
    }
}

/* Captions, because the two segmented groups were two adjacent identical orange "All" buttons.
   The groups already carried aria-labels, so assistive tech was told what sighted users weren't. */
.eb-morph-control-group {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.eb-morph-control-label {
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-muted);
}

/* Control row: the buyer already chose a gene, so this is where sort and compare become the
   primary actions. Reuses the browse page's .eb-seg pills and .eb-sort-btn so the two surfaces
   never teach two different instruments. */
.eb-morph-controls {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 14px;
    margin-bottom: 20px;
    padding: 12px 14px;
    border: 1px solid var(--eb-border);
    border-radius: 12px;
    background: var(--eb-surface);
}

.eb-morph-controls-count {
    margin-left: auto;
    font-size: 0.84rem;
    color: var(--eb-muted);
}

.eb-morph-empty-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 10px;
}

/* Below md the row becomes a panel behind the .eb-filter-toggle, exactly as the browse filter bar
   does — one collapse mechanism for the whole marketplace. Open, it stacks: seven controls wrapped
   into a phone-width row produced ragged half-rows and a 342px wall of chrome above the first
   animal. Breakpoint matches MudBlazor's md (960px), because the toggle that reveals it is
   d-md-none — any other number would leave a width with no way to open the panel. */
@media (max-width: 959.98px) {
    .eb-morph-controls {
        display: none;
    }

    .eb-morph-controls.eb-morph-controls--open {
        display: flex;
        flex-direction: column;
        align-items: stretch;
    }

    /* Segmented groups fill the panel and split it evenly, so the options read as one control
       rather than as pills floating at the left of an empty row. */
    .eb-morph-controls .eb-seg {
        width: 100%;
    }

    .eb-morph-controls .eb-seg-btn {
        flex: 1 1 0;
    }

    .eb-morph-controls .eb-sort-btn,
    .eb-morph-controls .eb-sort-btn .mud-button-root {
        width: 100%;
    }

    .eb-morph-controls .eb-sort-btn .mud-button-root {
        min-height: 44px;
    }
}

@media (max-width: 640px) {
    .eb-morph-controls-count {
        margin-left: 0;
        width: 100%;
    }
}

/* Related genes read as navigation, not as trait chips: an orange chip means "this animal
   carries this trait" everywhere else in the product. Same-hue tint, but a card-shaped link
   carrying the co-occurrence count that makes it useful to a breeder. */
.eb-morph-related {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.eb-morph-related-link {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-height: 44px;
    justify-content: center;
    padding: 8px 14px;
    border: 1px solid var(--eb-border);
    border-radius: 12px;
    background: var(--eb-surface);
    text-decoration: none;
    transition: border-color 140ms ease, transform 140ms ease;
}

.eb-morph-related-link:hover {
    border-color: var(--eb-orange);
    transform: translateY(-2px);
}

.eb-morph-related-name {
    font-size: 0.9rem;
    font-weight: 700;
    color: var(--eb-text);
}

.eb-morph-related-count {
    font-size: 0.76rem;
    color: var(--eb-muted);
}

@media (prefers-reduced-motion: reduce) {
    .eb-morph-related-link {
        transition: none;
    }

    .eb-morph-related-link:hover {
        transform: none;
    }
}

/* ---- Morph index (/{species}/morphs) ------------------------------------ */
/* A list, not a card grid: 33 identical tiles holding a name and a number is a container standing
   in for content. Rows read as a column you scan, which is how a keeper reads a gene list. */
.eb-morph-index-intro {
    max-width: 68ch;
    margin: 6px 0 0;
    color: var(--eb-body);
    line-height: 1.7;
}

.eb-morph-index-count {
    margin: 18px 0 10px;
    font-size: 0.8rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-muted);
}

/* Columns rather than a single long file: 33 genes down one column is 1500px of scroll for a
   list whose longest label is one word. Multi-column flows them without locking rows. */
.eb-morph-index-list {
    margin: 0;
    padding: 0;
    list-style: none;
    border-top: 1px solid var(--eb-border);
    column-count: 3;
    column-gap: 28px;
}

@media (max-width: 1023.98px) {
    .eb-morph-index-list { column-count: 2; }
}

@media (max-width: 599.98px) {
    .eb-morph-index-list { column-count: 1; }
}

.eb-morph-index-list li {
    break-inside: avoid;
}

.eb-morph-index-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    min-height: 52px;
    padding: 10px 10px 10px 0;
    border-bottom: 1px solid var(--eb-border);
    text-decoration: none;
    transition: background 140ms ease, padding-left 140ms ease;
}

.eb-morph-index-row:hover {
    background: var(--eb-tint-bg);
    padding-left: 10px;
}

.eb-morph-index-name {
    font-weight: 700;
    color: var(--eb-text);
}

.eb-morph-index-row:hover .eb-morph-index-name {
    color: var(--eb-orange-on-tint);
}

.eb-morph-index-stock {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex: 0 0 auto;
    font-size: 0.82rem;
    color: var(--eb-muted);
    text-align: right;
}

.eb-morph-index-price {
    font-weight: 700;
    color: var(--eb-orange-ink);
}

.eb-morph-index-stock--none {
    font-style: normal;
    opacity: 0.75;
}

/* The section page's route in. A link, not a button: it competes with nothing on that page. */
.eb-morph-index-cta {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-height: 44px;
    margin-top: 4px;
    color: var(--eb-orange-ink);
    font-weight: 700;
    font-size: 0.92rem;
    text-decoration: none;
}

.eb-morph-index-cta:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
}

.eb-morph-index-cta-icon {
    font-size: 1.1rem;
}

@media (prefers-reduced-motion: reduce) {
    .eb-morph-index-row { transition: none; }
}

/* Compact "details" badge on the My Animals table — sex icon · maturity · views */
.eb-animal-badge {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 3px 10px;
    border-radius: 999px;
    border: 1px solid var(--eb-border-2);
    background-color: var(--eb-surface-2);
    white-space: nowrap;
    font-size: 0.8rem;
    line-height: 1;
}

.eb-animal-badge .mud-icon-root { font-size: 1.05rem; }

.eb-animal-badge-txt {
    font-weight: 600;
    color: var(--eb-body);
}

.eb-animal-badge-views {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    color: var(--eb-muted);
}

.eb-animal-badge-views .mud-icon-root {
    color: var(--eb-muted);
    font-size: 0.95rem;
}

.eb-animal-badge-sep {
    width: 1px;
    height: 12px;
    background-color: var(--eb-border-2);
}

/* The thumbnails are real buttons now (keyboard access); this strips the UA button chrome so the
   row looks exactly as it did when they were bare images. The visual treatment stays on .eb-thumb
   below, which is still the <img>, so the active-border and hover rules are untouched. */
.eb-thumb-btn {
    padding: 0;
    border: 0;
    background: none;
    line-height: 0;
    border-radius: 10px;
    cursor: pointer;
}

.eb-thumb {
    cursor: pointer;
    border-radius: 10px;
    border: 1px solid var(--eb-border);
    opacity: 0.7;
    object-fit: cover;
    transition: opacity 150ms ease, border-color 150ms ease;
}

.eb-thumb-btn:hover .eb-thumb { opacity: 1; border-color: var(--eb-border-2); }

.eb-thumb:hover { opacity: 1; border-color: var(--eb-border-2); }

.eb-thumb.eb-thumb-active {
    border-color: var(--eb-orange);
    opacity: 1;
}

.eb-spec-table td {
    font-size: 0.86rem;
    font-weight: 600;
    color: var(--eb-text);
    border-bottom: 1px solid var(--eb-border) !important;
    padding: 8px 0;
}

.eb-spec-table td:first-child {
    color: var(--eb-muted);
    font-size: 0.82rem;
    font-weight: 600;
    letter-spacing: 0;
    text-transform: none;
    white-space: nowrap;
    width: 42%;
}

/* Listing telemetry under the spec table: view count and the short listing id. Deliberately the
   quietest text on the card — it is a support handle, not evidence about the animal. */
.eb-spec-meta {
    margin-top: 12px;
    font-size: 0.76rem;
    color: var(--eb-muted);
    letter-spacing: 0.01em;
}

/* Seller-written description on detail pages — multi-paragraph long-form, so it reads at the
   prose leading rather than MudBlazor body1's tighter default. */
.eb-detail-desc {
    line-height: 1.7;
}

/* detail price — large, white (per 8a); flat, no tag */
.eb-price-lg {
    font-size: 2.4rem;
    letter-spacing: -0.02em;
    color: var(--eb-text);
    background: none;
    border: none;
    border-radius: 0;
    padding: 0;
    box-shadow: none;
    transform: none;
    display: inline-block;
}

.eb-price-lg .eb-cur {
    color: var(--eb-text);
}

/* ---- Seller strip (listing detail / store) -------------------------------- */

.eb-seller-strip {
    background: var(--eb-surface);
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    padding: 16px 20px;
    display: flex;
    align-items: center;
    gap: 14px;
}

/* The card above the strip carries no bottom margin, so on the listing page the two touched and
   read as one element. Scoped to the column: the strip is used on other pages where it already
   sits clear. */
.eb-col-rail .eb-seller-strip {
    margin-top: 16px;
}

/* Same automatic-minimum trap as the grid columns, in flex this time: the name/location/licence
   block would not shrink below its content, so a long licensing authority pushed "Visit →" 11px
   past the right edge of the phone. The avatar and the link keep their size; only the text block
   gives, and its longest single tokens (a licence number, a council name) may break rather than
   push. */
.eb-seller-strip > * {
    min-width: 0;
}

.eb-seller-avatar,
.eb-seller-visit {
    flex: 0 0 auto;
}

.eb-seller-name,
.eb-seller-meta,
.eb-seller-licence {
    overflow-wrap: anywhere;
}

.eb-seller-avatar {
    width: 44px;
    height: 44px;
    min-width: 44px;
    border-radius: 999px;
    background: var(--eb-tint-bg);
    color: var(--eb-orange-on-tint);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 1.1rem;
    overflow: hidden;
}

.eb-seller-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.eb-seller-name {
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 0.95rem;
    color: var(--eb-text);
}

.eb-seller-strip .eb-seller-name a,
.eb-seller-strip .eb-seller-name .mud-link {
    color: var(--eb-text);
}

.eb-seller-meta {
    font-size: 0.78rem;
    color: var(--eb-muted);
}

.eb-seller-visit,
.eb-seller-strip a.eb-seller-visit {
    font-weight: 700;
    font-size: 0.86rem;
    color: var(--eb-orange-ink);
    text-decoration: none;
    cursor: pointer;
    white-space: nowrap;
}

.eb-seller-visit:hover {
    color: var(--eb-orange-hover);
}

/* ---- Static prose pages (privacy / cookies) ------------------------------- */

/* One prose system for every long-form surface — legal pages, article bodies, guides.
   (Two rival .eb-prose blocks used to coexist in this file, ~2,300 lines apart; each half-won
   the cascade, so neither surface rendered as authored. This is the only definition now.)
   Measure: 48em — about 806px on the guide body, deliberately wider than the 718px it started at.

   The history matters, because the obvious "fix" here is wrong. This was `70ch`, which is not 70
   characters: `ch` is the advance of the "0" glyph, and in Schibsted Grotesk that is 10.26px against
   7.68px for average running text, so `70ch` actually carried ~93 characters per line. Chasing the
   textbook 45–75 window took it to 32em and then 36em, and both were worse in the only way that
   counts — a long guide became a thin spine down the middle of a 1240px page, and a narrow column
   makes a 2,400-word article taller, not shorter. Scroll length was the complaint; narrowing the
   column is the one change guaranteed to increase it.

   So this is set for the page, not for the guideline. `em` remains the unit because it tracks the
   element's own font-size, letting one declaration govern the 0.95rem legal pages and the 1.05rem
   guide body together. Long lines are made readable here by leading rather than by measure: the
   guide body runs line-height 1.78, which keeps the return sweep easy to land.

   If this is ever revisited, the lever for "reads too dense" is subheads, images and callouts — the
   things that break a column into landmarks — not a narrower ribbon of text. */
.eb-prose {
    max-width: 48em;
    color: var(--eb-body);
    font-size: 0.95rem;
    line-height: 1.7;
}

.eb-prose > *:first-child { margin-top: 0; }
.eb-prose > *:last-child { margin-bottom: 0; }

.eb-prose h1 {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: clamp(2rem, 4vw, 2.6rem);
    line-height: 1.05;
    letter-spacing: -0.03em;
    text-transform: none;
    color: var(--eb-text);
    margin: 0 0 24px;
}

.eb-prose h2 {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 1.5rem;
    line-height: 1.25;
    letter-spacing: -0.02em;
    text-transform: none;
    color: var(--eb-text);
    margin: 36px 0 12px;
}

.eb-prose h3 {
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 1.2rem;
    color: var(--eb-text);
    margin: 28px 0 10px;
}

.eb-prose h4 {
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 1.05rem;
    color: var(--eb-text);
    margin: 22px 0 8px;
}

.eb-prose p { margin: 0 0 18px; }

.eb-prose ul,
.eb-prose ol {
    margin: 0 0 18px;
    padding-left: 24px;
}

.eb-prose li { margin-bottom: 6px; }

.eb-prose a {
    color: var(--eb-orange-ink);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.eb-prose a:hover { color: var(--eb-orange-hover); }

.eb-prose img {
    max-width: 100%;
    height: auto;
    border-radius: 12px;
    margin: 8px 0 20px;
}

.eb-prose blockquote {
    margin: 0 0 20px;
    padding: 4px 0 4px 20px;
    border-left: 3px solid var(--eb-orange);
    color: var(--eb-text);
}

.eb-prose code {
    background: var(--eb-surface-2);
    border: 1px solid var(--eb-border);
    border-radius: 6px;
    padding: 1px 6px;
    font-size: 0.9em;
}

.eb-prose pre {
    background: var(--eb-surface-2);
    border: 1px solid var(--eb-border);
    border-radius: 10px;
    padding: 16px;
    overflow-x: auto;
    margin: 0 0 20px;
}

.eb-prose pre code {
    background: none;
    border: none;
    padding: 0;
}

.eb-prose hr {
    border: none;
    border-top: 1px solid var(--eb-border);
    margin: 28px 0;
}

.eb-prose table {
    width: 100%;
    border: 1px solid var(--eb-border);
    border-radius: 12px;
    border-collapse: separate;
    border-spacing: 0;
    overflow: hidden;
    margin: 14px 0;
}

.eb-prose table th {
    background: var(--eb-surface-2);
    color: var(--eb-muted);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-size: 0.72rem;
    text-align: left;
    padding: 10px 14px;
}

.eb-prose table td {
    font-size: 0.88rem;
    color: var(--eb-body);
    padding: 10px 14px;
    border-top: 1px solid var(--eb-border);
    vertical-align: top;
}

/* ---- Footer --------------------------------------------------------------- */

/* Sticky-footer shell. On a short page — a store-less My Animals ends around y=250 — the footer
   used to run y=400–570 with 330px of bare charcoal below it, reading as a floating slab rather
   than the end of the page. The shell fills the viewport and the main column takes up the slack,
   so the footer sits either at the bottom of the fold or below real content, never mid-air.
   min-height counts the app-bar padding Mud puts on this element (border-box), so this adds no
   scrollbar to pages that already fill the screen. */
.mud-main-content {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

.mud-main-content > .eb-page {
    flex: 1 0 auto;
}

.eb-footer {
    background: var(--eb-bg);
    border-top: 1px solid var(--eb-border);
    padding: 48px 0 40px;
    margin-top: 56px;
}

/* Two blocks, not a spacer-pushed row: the old flex + <MudSpacer/> left a 406px void at 1440
   between a 70px mark and the link row, so the footer read as two unrelated fragments. Two equal
   tracks give each block a half to fill, so the separation is the gap between two columns of
   content rather than slack pushing a strip against the right edge — a content-sized second
   track reproduced the same void at 570px. Collapses to one column without a media query
   fighting a spacer. */
.eb-footer-inner {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: start;
    gap: 32px 64px;
}

.eb-footer-brand-block {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 18px;
    /* Wide enough to keep the one-line legal notice off a second line at its 0.75rem size. */
    max-width: 46ch;
}

.eb-footer-brand {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    min-height: 64px;
    text-decoration: none;
}

.eb-footer-mark {
    display: block;
    height: 64px;
    width: auto;
}

.eb-footer-legal {
    margin: 0;
    font-size: 0.75rem;
    line-height: 1.5;
    color: var(--eb-muted);
}

.eb-footer-mark--light {
    display: none;
}

[data-theme="light"] .eb-footer-mark--dark {
    display: none;
}

[data-theme="light"] .eb-footer-mark--light {
    display: block;
}

/* Columns, not a wrapping row. The links arrive from the CMS as one flat list, and a flex-wrap
   row left a ragged right edge at every width (4+4 at 390px, one long stripe at 1440). A fixed
   column count aligns both edges and gives the list a shape the CMS can grow into. Four fractions
   here so the list fills its half of the footer as a band instead of a narrow right-hand strip. */
.eb-footer-links {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    align-items: start;
    gap: 2px 24px;
    font-size: 0.8rem;
}

/* MudLink renders Color.Primary, whose .mud-primary-text carries !important — so the plain
   `.eb-footer-links a { color: var(--eb-muted) }` above never applied and all eight legal links
   rendered in the brand orange. Eight saturated links in the quietest region of the page is the
   One Orange Rule inverted: the footer shouted louder than the price on a listing card. */
/* Scoped through .eb-footer for specificity, not for reach: the light-theme rule at line ~355
   (`[data-theme="light"] .mud-link.mud-primary-text`) is 0-3-0 !important, so a 0-2-1 selector
   won on dark and lost on light — the links came back orange the moment the theme flipped. */
.eb-footer .eb-footer-links a,
.eb-footer .eb-footer-links a.mud-link {
    color: var(--eb-muted) !important;
    /* Give the legal links a comfortable tap target on mobile (they measured 18px tall). */
    display: inline-flex;
    align-items: center;
    min-height: 32px;
}

.eb-footer .eb-footer-links a:hover,
.eb-footer .eb-footer-links a.mud-link:hover {
    color: var(--eb-text) !important;
}

/* A menu item with children renders as a titled column: it takes one track of .eb-footer-links, so
   four CMS groups become the footer's four columns and each column reads top-to-bottom as one
   subject. Loose ungrouped links still flow into the same tracks around them.

   It stays a grid rather than a flex column so a group wider than ~300px (few groups, wide viewport)
   flows into two internal columns instead of a single-file ladder. That ladder is what the eleven
   species categories produced before they were removed: 363px-wide rows each holding one link. */
.eb-footer-group {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 0 24px;
    align-content: start;
}

/* Sentence case, not the uppercase this carried before — The No-Shouting Rule reserves uppercase
   for table headers and the hero eyebrow, and a footer column head is neither. */
.eb-footer-group-title {
    /* Full width, or the heading becomes the first cell of its own link grid. */
    grid-column: 1 / -1;
    color: var(--eb-text) !important;
    font-weight: 700;
    letter-spacing: -0.01em;
    font-size: 0.78rem;
    margin-bottom: 2px;
}

/* Between the phone breakpoint and desktop the half-track is too narrow for four link columns
   ("Acceptable Use" alone needs ~99px), so step down to two while the two blocks stay side by
   side.

   Columns that flow, not a grid that locks rows. As a 2-track grid each group is a grid item, so
   both groups on a row take the height of the taller one: Selling has four links to Company's
   three, and the difference showed as a void under "News" — twice, once per row, and 44px of it
   on a phone. Nothing is misaligned there, which is exactly why it reads as broken rather than as
   spacing. Multi-column packs the four groups into two continuous columns instead, so the only
   vertical space left is the margin below each group. It also reorders them column-major, which
   is the right reading order for a stack of subject lists.

   Above this width the grid is still correct: four groups in four tracks is one row, and a row of
   one cannot lock anything. */
@media (max-width: 1023.98px) {
    .eb-footer-links {
        display: block;
        column-count: 2;
        column-gap: 20px;
    }

    /* break-inside is what keeps a heading with its links; without it a column can break between
       "Legal" and "Terms". One internal track, explicitly: the base rule's auto-fill minimum is
       150px, which a group inside a 136px column at 320px cannot honour — it kept its 150px and
       ate the column gap. There is no room for a second internal column at these widths anyway. */
    .eb-footer-group {
        break-inside: avoid;
        grid-template-columns: 1fr;
        margin-bottom: 18px;
    }

    .eb-footer-group:last-child {
        margin-bottom: 0;
    }

    /* Loose ungrouped links (a CMS footer link with no parent) are the one thing multi-column
       cannot flow safely: as inline-flex children of a block they would run together on a line. */
    .eb-footer-links > a,
    .eb-footer-links > a.mud-link {
        display: flex;
    }
}

@media (max-width: 767.98px) {
    .eb-footer {
        margin-top: 40px;
        padding: 36px 0 max(32px, env(safe-area-inset-bottom));
    }

    /* One column: brand block, then the links beneath it. */
    .eb-footer-inner {
        grid-template-columns: 1fr;
        gap: 28px;
    }

    .eb-footer-brand-block {
        max-width: none;
    }

    /* The two columns and their flow come from the tablet block above; a phone only changes the
       type size, which buys back what the narrower measure costs in legibility. */
    .eb-footer-links {
        font-size: 0.85rem;
    }

    /* .mud-link is repeated here on purpose: the base rule needs it to beat MudBlazor's
       !important colour, and a media query adds no specificity — without it the 44px phone tap
       target loses to the base 32px. */
    .eb-footer .eb-footer-links a,
    .eb-footer .eb-footer-links a.mud-link {
        min-height: 44px;
    }
}

/* The narrowest phones keep both columns. They used to drop to one, on the grounds that two
   fixed tracks put "Acceptable Use" a few pixels from "Animal Welfare" — but the cost was a
   734px footer at 320px, a full screen and a half of links under a page the visitor has already
   finished reading. Multi-column tracks size themselves from the gap, and the longest label
   measures ~100px against a 143px column, so the collision the old rule paid for is not there.
   The gap tightens rather than the type shrinking: 0.85rem is already the floor for a tap
   target you are meant to hit while holding the phone one-handed. */
@media (max-width: 359.98px) {
    .eb-footer-links {
        column-gap: 14px;
    }
}

/* ---- Buttons -------------------------------------------------------------- */

.mud-button-root {
    border-radius: 999px;
    text-transform: none;
    font-weight: 700;
    letter-spacing: 0;
}

.mud-button-filled {
    box-shadow: none;
    border: none;
}

/* !important on the text colour, deliberately: a MudButton with an Href renders as an <a>, and
   global-styles.css colours content links orange from
   `body.abp-application-layout main a:not(.btn)…:not(.mud-button-root)…` — six classes deep once
   the :not() arguments count, so it outranks this single-class rule by a mile and loads later.
   That leaves the label orange on an orange fill: an unreadable button. The :not(.mud-button-root)
   exclusion there is supposed to prevent it, but relying on a defensive exclusion in a different
   stylesheet to keep 13 Href'd primary buttons legible is one edit (or one stale bundle) away from
   breaking. Text on orange is near-black in both themes, so pin it here and stop the fight. */
.mud-button-filled-primary {
    color: var(--eb-on-orange) !important;
    font-weight: 800;
    box-shadow: none;
    border-radius: 12px;
    background-color: var(--eb-orange);
}

.mud-button-filled-primary:hover {
    box-shadow: 0 0 32px rgba(232, 99, 10, 0.32);
    background-color: var(--eb-orange-hover);
}

/* The same fight, for the buttons that aren't MudButtons. `.eb-store-cta`, `.eb-share-btn` and
   `.eb-social-btn` are bespoke <a> elements, so the `:not(.mud-button-root)` exclusion above does
   not cover them and the content-link rule repaints their label orange:

     .eb-store-cta   orange label on an orange fill — 1.00:1 on dark, 1.83:1 on light. Invisible.
     .eb-share-btn   orange at rest (should be quiet) and orange-on-orange once hovered, which
     .eb-social-btn  inverts the documented "quiet by default, orange on hover" behaviour.

   Rule of thumb for anything added later: an <a> that carries its own background is a button, and
   the content-link colour will reach it. Pin its label here rather than adding another :not() to a
   list in a different stylesheet. Text on orange is near-black in both themes. */
.eb-store-cta,
.eb-store-cta:hover,
.eb-share-btn:hover,
.eb-social-btn:hover {
    color: var(--eb-on-orange) !important;
}

.eb-share-btn,
.eb-social-btn {
    color: var(--eb-text) !important;
}

.mud-button-outlined {
    border: 1px solid var(--eb-border-2);
    color: var(--eb-text);
    border-radius: 12px;
}

.mud-button-outlined:hover {
    border-color: var(--eb-orange);
    color: var(--eb-orange-ink);
    background-color: transparent;
}

/* DESIGN.md §5 Buttons: "Outlined: transparent fill, 1px Hairline Strong border, white text;
   hover turns border and text orange." Mud's -primary modifier paints both orange at rest, so
   any button passing Color.Primary rendered permanently in its own hover state — and roughly
   half the outlined buttons in the app pass it, which is what made the two treatments read as
   two different components. Corrected here rather than at the eleven call sites, so the twelfth
   cannot reintroduce it. !important because Mud sets these with !important of its own. */
.mud-button-outlined.mud-button-outlined-primary {
    border-color: var(--eb-border-2) !important;
    color: var(--eb-text) !important;
}

.mud-button-outlined.mud-button-outlined-primary:hover {
    border-color: var(--eb-orange) !important;
    color: var(--eb-orange-ink) !important;
}

/* ---- Cards / tables / inputs ---------------------------------------------- */

.mud-card {
    box-shadow: none;
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    background-color: var(--eb-surface);
}

.mud-card + .mud-card,
.mud-card.mt-4 {
    box-shadow: none;
}

.mud-paper {
    background-color: var(--eb-surface);
    color: var(--eb-text);
}

.mud-table {
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    box-shadow: none;
    overflow: hidden;
    background-color: var(--eb-surface);
}

.mud-table-head th,
.mud-table-cell.mud-table-head-cell {
    background-color: var(--eb-surface-2);
    color: var(--eb-muted) !important;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-size: 0.72rem;
}

.mud-table-head th .mud-button-root,
.mud-table-head th .mud-typography {
    color: var(--eb-muted) !important;
}

/* outlined inputs use the more-visible border, orange on focus */
.mud-input-outlined-border {
    border-color: var(--eb-border-2) !important;
    border-width: 1px !important;
    border-radius: 10px;
}

.mud-input.mud-input-outlined.mud-focused .mud-input-outlined-border {
    border-color: var(--eb-orange) !important;
    border-width: 2px !important;
}

.mud-input-root {
    color: var(--eb-text);
}

/* ---- White form inputs -------------------------------------------------- */
/* Tony's call (2026-07-15): outlined fields on the dark theme still read as
   low-contrast, so the input body is solid white with dark text — fields pop
   as distinct white cards. Labels/helper text that sit OUTSIDE the box stay
   light (they're over the dark card, handled by the theme defaults). */

.mud-input.mud-input-outlined {
    background-color: #FFFFFF;
    border-radius: 10px;
}

/* value / placeholder / textarea / select display text — dark on white
   (2-class selector beats the .mud-input-root white-text rule above) */
.mud-input.mud-input-outlined input,
.mud-input.mud-input-outlined textarea,
.mud-input.mud-input-outlined .mud-input-slot {
    color: #0C0C0E;
}

.mud-input.mud-input-outlined input::placeholder,
.mud-input.mud-input-outlined textarea::placeholder {
    color: #6A6A72;
    opacity: 1;
}

/* adornments (£, g), select arrow, clear + spin-button icons — dark on white */
.mud-input.mud-input-outlined .mud-input-adornment,
.mud-input.mud-input-outlined .mud-input-adornment-text,
.mud-input.mud-input-outlined .mud-input-adornment .mud-icon-root,
.mud-input.mud-input-outlined .mud-icon-button .mud-icon-root {
    color: #55555C;
}

/* hairline around the white body; orange on focus. Token, not a literal: on light theme this is
   the only thing that marks the field (see --eb-input-border). */
.mud-input.mud-input-outlined .mud-input-outlined-border {
    border-color: var(--eb-input-border) !important;
}
.mud-input.mud-input-outlined.mud-focused .mud-input-outlined-border {
    border-color: var(--eb-orange) !important;
}

/* floating label sits over the white body: dark at rest, orange on focus.
   When it shrinks it straddles the top border — half of it rides above the
   white box over the dark card. A white background chip (the Material "notch")
   keeps the dark label text on white in both halves, so it never half-vanishes. */
/* Stacked labels — the caption sits ABOVE the white box as a separate label,
   not floating in a notch on the border, so it reads as a distinct label and
   never overlaps the field edge. Text fields with no Label render no label
   element, so :has() leaves those (e.g. toolbar search) untouched. */
.mud-input-control:has(.mud-input-label-outlined) {
    margin-top: 24px;
}
/* MudBlazor's dense variant sets margin-top:8px at higher specificity
   (.mud-input-control-margin-dense.mud-input-outlined-with-label), which collapses the
   headroom the stacked caption needs while the label's -22px lift still applies — that jams
   the caption against the box's top edge (the "squashed" filter-bar labels). Restore 24px for
   dense inputs so the caption clears the field on every dense surface (Browse/Equipment filters). */
.mud-input-control.mud-input-control-margin-dense:has(.mud-input-label-outlined) {
    margin-top: 24px;
}
.mud-input-label.mud-input-label-outlined {
    /* Static caption ABOVE the box (not floating in a notch). Sized and weighted directly to the
       design-system label spec (0.78rem/600/muted — matching .eb-active-label and every other
       caption) instead of scaling 1rem down, so the captions no longer read anaemic. !important
       beats MudBlazor's own float transform + its white text-primary recolour on shrink. */
    font-size: 0.78rem !important;
    font-weight: 600 !important;
    line-height: 1.2;
    transform: translate(2px, -22px) !important;
    transform-origin: top left;
    color: var(--eb-muted) !important;
    background: transparent !important;
    padding: 0 !important;
    max-width: none !important;
    overflow: visible;
}
.mud-input-control:focus-within .mud-input-label.mud-input-label-outlined {
    color: var(--eb-orange-ink) !important;
}
/* close the outlined notch so the top border stays a continuous line */
.mud-input.mud-input-outlined .mud-input-outlined-border legend {
    max-width: 0 !important;
}

/* chips (status pills etc.) */
.mud-chip {
    font-weight: 700;
    border-radius: 999px;
}

.mud-chip.mud-chip-outlined {
    border-width: 1px;
}

/* rating stars in brand orange */
.mud-rating-item .mud-icon-root {
    color: var(--eb-orange-ink);
}

/* store / seller monogram avatars use the site body font */
.mud-avatar {
    font-family: var(--mud-typography-default-family);
    font-weight: 700;
}

/* tabs (store page) */
.mud-tabs-toolbar {
    border: 1px solid var(--eb-border);
    border-radius: 12px;
    background: var(--eb-surface);
}

.mud-tab {
    color: var(--eb-muted);
}

.mud-tab.mud-tab-active {
    color: var(--eb-text);
    font-weight: 800;
}

.mud-tab-slider {
    background-color: var(--eb-orange) !important;
    height: 3px;
}

/* alerts */
.mud-alert {
    border: 1px solid var(--eb-border);
    border-radius: 12px;
    box-shadow: none;
    background-color: var(--eb-surface);
    color: var(--eb-text);
}

/* expansion panels (store policy) */
.mud-expand-panel {
    border: 1px solid var(--eb-border);
    border-radius: 12px;
    box-shadow: none;
    overflow: hidden;
    background-color: var(--eb-surface);
}

/* Dense panels zero the content's vertical padding, so the text jams against
   the top/bottom of the card — restore breathing room below (and a little above). */
.mud-expand-panel .mud-expand-panel-content {
    padding-top: 8px !important;
    padding-bottom: 16px !important;
}

/* ---- Pager ------------------------------------------------------------------
   Our own control rather than MudPagination (see Pager.razor for why), wearing the same pill the
   Mud one was themed into below, so nothing about the page changes except how many numbers show. */
.eb-pager {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 6px;
}

.eb-pager-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 40px;
    height: 40px;
    padding: 0 10px;
    border: 1px solid var(--eb-border);
    border-radius: 999px;
    background: transparent;
    color: var(--eb-text);
    font-family: inherit;
    font-size: 0.9rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
    transition: border-color 140ms ease, color 140ms ease, background-color 140ms ease;
}

.eb-pager-btn:hover:not(:disabled) {
    border-color: var(--eb-orange);
}

/* Disabled rather than removed: the ends of the run are where the arrow count would otherwise
   change and shift every other control sideways under the thumb. */
.eb-pager-btn:disabled {
    opacity: 0.4;
    cursor: default;
}

.eb-pager-btn--on,
.eb-pager-btn--on:hover {
    background-color: var(--eb-orange);
    border-color: var(--eb-orange);
    color: var(--eb-on-orange);
}

/* Where the window sits in the whole run - the fact the anchored last-page number used to give
   away for free. */
.eb-pager-of {
    margin-left: 6px;
    color: var(--eb-muted);
    font-size: 0.8rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

@media (max-width: 419.98px) {
    .eb-pager {
        gap: 4px;
    }

    .eb-pager-btn {
        min-width: 36px;
        height: 36px;
        padding: 0 6px;
    }

    /* The count drops to its own line rather than squeezing the controls it describes. */
    .eb-pager-of {
        flex: 1 0 100%;
        margin: 2px 0 0;
        text-align: center;
    }
}

/* pagination */
.mud-pagination-item .mud-button-root {
    border: 1px solid var(--eb-border);
    border-radius: 999px;
    font-weight: 700;
    color: var(--eb-text);
}

.mud-pagination-item .mud-button-root:hover {
    border-color: var(--eb-orange);
}

.mud-pagination-item .mud-button-root.mud-button-filled-primary {
    background-color: var(--eb-orange);
    color: var(--eb-on-orange);
    border-color: var(--eb-orange);
}

/* ============================================================
   Admin / back-office chrome — bring the ABP module pages
   (Identity, CMS, OpenIddict, Settings, etc.) in line with
   the Immersive dark surfaces.
   ============================================================ */

.mud-dialog {
    border: 1px solid var(--eb-border);
    border-radius: 16px;
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.6) !important;
    overflow: hidden;
    background-color: var(--eb-surface);
}

.mud-dialog-title {
    font-family: var(--eb-display);
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--eb-text);
}

.mud-dialog .mud-tabs-toolbar,
.mud-table .mud-tabs-toolbar {
    border: none;
    border-radius: 0;
    background: transparent;
}

/* breadcrumbs under the admin PageHeader */
.mud-breadcrumbs {
    padding-inline: 0;
}

.mud-breadcrumb-item a,
.mud-breadcrumb-item .mud-typography {
    color: var(--eb-muted);
    font-weight: 600;
}

.mud-breadcrumb-item a:hover {
    color: var(--eb-orange-ink);
}

.mud-breadcrumbs .mud-icon-root {
    color: var(--eb-orange-ink);
}

/* toggles/switches use the brand accent when on */
.mud-switch-input:checked + .mud-switch-track {
    background-color: var(--eb-orange);
}

/* menus / dropdowns (nav "Admin", user menu, data-grid row actions) */
.mud-popover.mud-popover-open.mud-paper {
    border: 1px solid var(--eb-border);
    border-radius: 12px;
    box-shadow: 0 16px 40px rgba(0, 0, 0, 0.55) !important;
    overflow: hidden;
    background-color: var(--eb-surface);
}

/* Back-office navigation can contain dozens of permission-filtered destinations.
   Keep the popover inside the visible viewport and let touch/wheel users reach all of them. */
.mud-popover.mud-popover-open.mud-paper.eb-scrollable-nav-popover {
    max-height: calc(100vh - 76px);
    max-height: calc(100dvh - 64px - max(12px, env(safe-area-inset-bottom)));
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
    touch-action: pan-y;
    -webkit-overflow-scrolling: touch;
}

/* --- Species nav dropdowns -----------------------------------------------
   The panel used to open at y=51 while the app bar ends at 65: MudMenu anchors to its own root,
   and that root was the 37px pill sitting centred in a 64px bar, so 14px of the panel overlapped
   the bar and cut its hairline. Stretching the *anchor* to the toolbar's full height
   fixes it at the source — the pill stays 37px and centred, but the element the popover measures
   from now ends where the bar does. Nothing about the visible mark changes. */
.eb-topnav .eb-nav-links {
    align-self: stretch;
}

.eb-topnav .eb-nav-links > .mud-menu {
    align-self: stretch;
    display: flex;
    align-items: center;
}

/* Then hang it clear of the bar rather than flush to it: this panel is a rounded card with its own
   shadow, and rounded top corners butted against a straight edge read as a rendering fault. The gap
   also leaves the bar's hairline intact, which is what tells you the panel belongs to the bar
   instead of replacing it. */
.mud-popover.mud-popover-open.mud-paper.eb-nav-popover {
    margin-top: 6px;
    min-width: 208px;
}

/* One gutter for the whole panel. The headings were indented 16px and their own items 12px, so a
   group label never lined up with the destinations underneath it — the single loudest reason the
   panel read as cramped, ahead of its actual width. 36px rows rather than 32: this is a pointer
   menu of seven destinations, not a dense data list. */
/* The row you are standing on, marked the way the bar marks its active section — full ink and the
   heavier weight. The aria-current that drives it is on the same element, so the sighted and the
   announced states cannot drift apart. */
.eb-nav-popover [aria-current="page"] {
    color: var(--eb-text);
    font-weight: 800;
}

.eb-nav-popover .mud-menu-item,
.eb-nav-popover .mud-list-item {
    min-height: 36px;
    padding-inline: 16px;
}

/* Section headings inside the CMS / Admin / mobile dropdowns. The back-office menu runs
   long enough that a small caption alone reads as another row — give each group a hairline
   rule above it so the boundaries are unmissable. */
.eb-menu-section {
    display: block;
    margin-top: 6px;
    padding: 10px 16px 4px;
    border-top: 1px solid var(--eb-border);
    color: var(--eb-muted);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    line-height: 1.4;
    text-transform: uppercase;
}

/* No rule above the first group, and none between a group and its immediate
   sub-group (Administration → Identity management) — that would read as two breaks. */
.eb-menu-section:first-child,
.eb-menu-section + .eb-menu-section {
    margin-top: 0;
    border-top: 0;
}

.eb-menu-section + .eb-menu-section {
    padding-top: 2px;
}

.mud-list-item {
    color: var(--eb-text);
}

.mud-list-item:hover {
    background-color: var(--eb-tint-bg);
}

/* tree view (settings groups, file-management folders) selection */
.mud-treeview-item-content.mud-treeview-item-selected {
    background-color: var(--eb-tint-bg);
}

/* CmsKit Menu Management tree (/Cms/Menus/Items) — ABP 10.6.0 ships this page, and only
   this page, with MudTreeViewItem compiled as an unresolved tag, so the DOM carries
   literal <global::MudBlazor.MudTreeViewItem>/<BodyContent>/<ChildContent> elements
   instead of components. MudBlazor's own tree CSS never matches them, every level
   renders inline and flat, and no expander exists. Restore block layout and one indent
   per level so the hierarchy is readable. Real MudTreeViews (Settings, File Management)
   emit none of these element names, so these rules cannot reach them.
   Delete this block once ABP fixes the package. */
.mud-treeview global\:\:mudblazor\.mudtreeviewitem,
.mud-treeview bodycontent,
.mud-treeview childcontent {
    display: block;
}

/* 16 + 15 + 1px border = the parent's icon (20px) + gap-3 (12px), so a child's icon
   lands under its parent's label text. */
.mud-treeview childcontent {
    margin-left: 16px;
    padding-left: 15px;
    border-left: 1px solid var(--eb-border);
}

/* ---- ABP cookie-consent banner → Night Vivarium ------------------------- */
/* ABP renders it as a MudPaper with an inline primary (orange) background and a
   filled-info (blue) Accept button, inside a fixed wrapper at z-index 999999. Repaint
   it as a dark surface with an orange Accept, overriding ABP's inline styles. */
[style*="z-index: 999999"] > .mud-paper {
    background-color: var(--eb-surface) !important;
    color: var(--eb-body) !important;
    border-top: 1px solid var(--eb-border);
}

/* Same element, stated again at higher specificity, because two of our own rules collide on it.

   ABP renders this bar as `background-color: var(--mud-palette-primary); color: white` inline —
   byte-identical to what Volo.Chat writes on an own-message bubble. The chat rule far below
   (`.mud-paper[style*="mud-palette-primary"][style*="color: white"]`, ~line 8920) therefore also
   matches the consent bar, and at (0,3,0) it beats the (0,2,0) rule above even though both are
   !important — so the bar rendered in the chat bubble's 12%-alpha orange tint and read as
   transparent over the page.

   Repeating the attribute matches here takes this to (0,4,0), which wins outright and is immune to
   source order. The chat rule is deliberately left alone: it fixes a real 3.38:1 contrast failure
   on white-on-orange that DESIGN.md forbids by name, so narrowing it risks reintroducing that. The
   broad rule above is kept as the fallback for when ABP's inline style does not carry both
   fragments — then neither this rule nor the chat rule matches, and it applies unopposed. */
[style*="z-index: 999999"] > .mud-paper[style*="mud-palette-primary"][style*="color: white"] {
    background-color: var(--eb-surface) !important;
    color: var(--eb-body) !important;
    border: none;
    border-top: 1px solid var(--eb-border);
}

[style*="z-index: 999999"] .mud-paper p {
    color: var(--eb-body) !important;
}

[style*="z-index: 999999"] .mud-paper a,
[style*="z-index: 999999"] .mud-paper a.text-white {
    color: var(--eb-orange-ink) !important;
}

[style*="z-index: 999999"] .mud-button-filled-info {
    background-color: var(--eb-orange) !important;
    color: var(--eb-on-orange) !important;
    box-shadow: none !important;
}

[style*="z-index: 999999"] .mud-button-filled-info:hover {
    background-color: var(--eb-orange-hover) !important;
}

/* ============================================================
   Browse critique fixes (2026-07-15): visible keyboard focus,
   grouped/collapsible filter bar, specialist card data,
   recovering empty state.
   ============================================================ */

/* ---- Keyboard focus: make it unmistakable ------------------------------- */
/* The bare :focus-visible rule near the top was out-specified by MudBlazor's
   own outline:none. These class-scoped rules win it back so keyboard/screen-
   reader users can always see where they are. */
.mud-button-root:focus-visible,
.mud-icon-button:focus-visible,
.mud-link:focus-visible,
.eb-nav-item:focus-visible,
.eb-clear-link:focus-visible,
.eb-morph-help:focus-visible,
.eb-filter-toggle:focus-visible,
.eb-section-link:focus-visible,
.mud-pagination-item .mud-button-root:focus-visible,
.mud-chip:focus-visible,
a:focus-visible,
button:focus-visible {
    outline: 2px solid var(--eb-orange) !important;
    outline-offset: 2px;
}

/* Outlined inputs never reliably got MudBlazor's .mud-focused class, so focus was
   invisible on the white fields. :focus-within fires whenever the real input is focused. */
.mud-input.mud-input-outlined:focus-within .mud-input-outlined-border {
    border-color: var(--eb-orange) !important;
    border-width: 2px !important;
}

/* ---- Filter bar: clustering + mobile collapse --------------------------- */
/* Range pairs (£ from/to, weight from/to) read as one control, not two loose fields. */
.eb-filter-group {
    display: flex;
    gap: 4px;
    align-items: flex-start;
}

/* Mobile-only trigger; the bar itself collapses behind it so animals aren't
   pushed below a full-height filter wall on the first screen. */
.eb-filter-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    background: var(--eb-surface);
    color: var(--eb-text);
    border: 1px solid var(--eb-border-2);
    border-radius: 12px;
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 0.92rem;
    padding: 12px 16px;
    margin-bottom: 16px;
    cursor: pointer;
}

.eb-filter-toggle:hover {
    border-color: var(--eb-orange);
}

/* "Show N results" — collapses the open mobile filter panel back to the grid.
   Full-bleed orange primary; the count updates live as filters change. */
.eb-filter-done {
    width: 100%;
    background: var(--eb-orange);
    color: var(--eb-on-orange);
    border: none;
    border-radius: 12px;
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 0.95rem;
    padding: 12px 16px;
    margin-top: 4px;
    cursor: pointer;
    transition: background 140ms ease;
}

.eb-filter-done:hover {
    background: var(--eb-orange-hover);
}

/* No tablet-width special case. There used to be one that placed each child in a named 12-column
   span, which collided silently the moment a category added a seventh control — Type and Maturity
   both landed in columns 4–8. Percentage bases replaced it and were worse in a different way: with
   only two fields per row they grew to 546px, a morph picker wider than the results beneath it. The
   base rules already wrap here and their max-width holds the fields at a readable size, so the right
   number of tablet rules is none. */

@media (max-width: 959.98px) {
    .eb-filter-bar {
        display: none;
        flex-direction: column;
    }

    .eb-filter-bar.eb-filter-bar--open {
        display: flex;
    }

    /* In column mode the desktop flex-basis (a width hint) is read as a height, ballooning
       single-child inputs like Keyword. Reset to content-height, full-width stacking — and reset the
       desktop max-width with it, or every dropdown stops 280px short of the panel edge. */
    .eb-filter-bar > .mud-select,
    .eb-filter-bar > .mud-select.mud-autocomplete,
    .eb-filter-bar > .eb-filter-item--wide,
    .eb-filter-bar .eb-filter-item {
        flex: 0 0 auto;
        width: 100%;
        max-width: none;
    }

    .eb-filter-bar .eb-filter-group {
        width: 100%;
    }

    .eb-filter-bar .eb-seg {
        width: 100%;
        margin-top: 0; /* desktop label-headroom offset is meaningless in the stacked column */
    }

    .eb-filter-bar .eb-seg-btn {
        flex: 1 1 0;
    }

    .eb-filter-bar .eb-more-btn {
        width: 100%;
        min-height: 44px;
        justify-content: center;
        margin-top: 0;
    }

    .eb-filter-bar .eb-filter-item--num {
        flex: 1;
        width: auto;
        max-width: none;
    }

    /* Desktop right-pins the sort with margin-left:auto, which in column layout strands it
       as a lone icon on its own row. On mobile make it a full-width labelled button in flow. */
    .eb-filter-bar .eb-sort-slot {
        margin-left: 0;
        margin-top: 0;
        width: 100%;
    }

    .eb-filter-bar .eb-sort-btn,
    .eb-filter-bar .eb-sort-btn .mud-button-root {
        width: 100%;
    }

    .eb-filter-bar .eb-sort-btn .mud-button-root {
        min-height: 44px;
    }
}

/* ---- Result count + recovering empty state ----------------------------- */
.eb-results-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin: 20px 0 18px;
}

.eb-result-count {
    font-size: 0.86rem;
    font-weight: 700;
    color: var(--eb-body);
}

/* "Stores I follow" — a scope toggle, not a navigation control, so it wears the filter language:
   flat and hairline at rest, orange only once it is doing something. */
.eb-follow-filter {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    border: 1px solid var(--eb-border-2);
    border-radius: 999px;
    background: none;
    color: var(--eb-body);
    font-family: var(--eb-display);
    font-weight: 600;
    font-size: 0.8rem;
    padding: 5px 12px 5px 9px;
    cursor: pointer;
    white-space: nowrap;
    transition: color 120ms ease, border-color 120ms ease, background-color 120ms ease;
}

.eb-follow-filter:hover {
    color: var(--eb-text);
    border-color: var(--eb-orange);
}

.eb-follow-filter--on {
    background: var(--eb-tint-bg);
    border-color: var(--eb-orange);
    color: var(--eb-orange-on-tint);
}

.eb-loading-bar {
    border-radius: 999px;
}

.eb-empty {
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    background: var(--eb-surface-2);
    padding: 44px 24px;
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.eb-empty-title {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 1.2rem;
    letter-spacing: -0.01em;
    color: var(--eb-text);
}

.eb-empty-hint {
    color: var(--eb-muted);
    font-size: 0.9rem;
    margin-top: 6px;
}

/* ---- Specialist card data: sex · weight · maturity, freshness ---------- */
/* A proper spec strip so the specialist data (what a keeper scans first) reads as
   first-class, not an afterthought below the title. */
/* Two groups: the spec data left, the compare toggle right (Browse and morph pages only). The
   data truncates before the control shrinks — worst realistic case is "♀ · 1,250 g · Juvenile"
   plus the toggle at ~239px in the 256px body, so the ellipsis is a guard, not a normal state. */
.eb-card-spec {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: nowrap;
    gap: 8px;
    /* Reserved so the row is the same height whatever it holds: bare text measures 18.8px, text
       plus a sex icon 19.2px, and the compare toggle 25.8px (its 4px padding on an 18px line).
       Without this, a Browse card stood 7px taller than the identical card on a store or
       favourites page, which is the last of the mismatch this row's restructure set out to remove. */
    min-height: 26px;
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--eb-body);
    margin: 4px 0 7px;
}

/* Sex glyphs are colour-coded — a deliberate, recorded exception to the One Orange rule:
   male blue / female pink read instantly on a grid where the glyph alone must be parsed. */
.eb-sex {
    font-weight: 800;
    font-size: 1.2rem;
    color: var(--eb-text);
    line-height: 1;
}

.eb-sex--m { color: var(--eb-sex-m); text-shadow: 0 0 10px rgba(90, 178, 255, 0.35); }
.eb-sex--f { color: var(--eb-sex-f); text-shadow: 0 0 10px rgba(255, 116, 180, 0.35); }

.eb-spec-dot {
    color: var(--eb-muted);
}

.eb-spec-maturity {
    color: var(--eb-muted);
}

/* Category label on mixed-species grids (store page, favourites). Muted like the rest of the
   strip on purpose — it answers "what am I looking at", it isn't a filter and isn't a morph, so
   it must not compete with the chips or the price. */
.eb-spec-category {
    color: var(--eb-muted);
}

/* Free-text morph line for categories with no catalogue. Reads as the seller's own words where
   chips would otherwise sit, without borrowing the chip's implied "click to filter". */
.eb-morph-text {
    color: var(--eb-muted);
}

/* Group heading inside the category select (Snakes / Lizards). MudBlazor 9.7 has no select-item
   group, so these are disabled items — this makes them read as headings rather than dead options. */
.eb-select-heading {
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--eb-muted);
}

/* "New" freshness chip — a solid orange badge (orange means "notable" here) with a soft
   heat-lamp glow so freshness pops off the photo; top-left, clear of the bottom-right sale badge. */
.eb-new-badge {
    position: absolute;
    top: 10px;
    left: 10px;
    font-size: 0.68rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--eb-on-orange);
    background: var(--eb-orange);
    border: none;
    border-radius: 7px;
    padding: 4px 10px;
    box-shadow: 0 3px 14px rgba(232, 99, 10, 0.5);
}

/* ---- Active trait chips: force the documented tint fill ---------------- */
.eb-chip-trait.mud-chip,
.eb-chip-trait.mud-chip.mud-chip-outlined {
    background-color: var(--eb-tint-bg) !important;
    border: 1px solid var(--eb-tint-border) !important;
    color: var(--eb-orange-on-tint) !important;
}

.eb-chip-trait .mud-chip-close-button .mud-icon-root {
    color: var(--eb-orange-ink);
}

/* Removable morph filter (MorphChip). A real <button>, so it needs the chip's look applied
   directly rather than through .mud-chip, plus a visible focus ring — it is a tab stop now. */
.eb-chip-trait.eb-chip-remove {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 8px 3px 10px;
    font-size: 0.76rem;
    font-weight: 700;
    line-height: 1.5;
    cursor: pointer;
    background-color: var(--eb-tint-bg);
    border: 1px solid var(--eb-tint-border);
    color: var(--eb-orange-on-tint);
    border-radius: 999px;
    transition: background-color 140ms ease, border-color 140ms ease;
}

.eb-chip-trait.eb-chip-remove:hover {
    background-color: var(--eb-tint-border);
    border-color: var(--eb-orange);
}

.eb-chip-trait.eb-chip-remove:focus-visible {
    outline: 2px solid var(--eb-orange);
    outline-offset: 2px;
}

.eb-chip-trait.eb-chip-remove .mud-icon-root {
    color: var(--eb-orange-ink);
    font-size: 1rem;
}

/* clickable trait pills on the detail page — jump to Browse filtered by that trait */
.eb-chip-trait--link {
    cursor: pointer;
    transition: background-color 140ms ease, border-color 140ms ease;
}

.eb-chip-trait--link:hover {
    background-color: var(--eb-tint-border) !important;
    border-color: var(--eb-orange) !important;
}

/* Store name was too muted to read as a link on the card; body-grey gives it legible
   weight while staying secondary to the title and price. !important beats MudBlazor's
   Color.Tertiary class, which the markup sets and which otherwise wins on specificity. */
.eb-store-line .mud-link {
    color: var(--eb-body) !important;
}

.eb-store-line .mud-link:hover {
    color: var(--eb-text) !important;
}

/* ============================================================
   Cinematic card → detail morph (View Transitions API).
   The clicked listing photo expands out of the grid and into
   the detail hero as one continuous image. Same-document VT,
   coordinated with Blazor navigation in App.razor. Progressive
   enhancement: unsupported browsers / reduced-motion just
   navigate normally (no name is applied, no transition runs).
   ============================================================ */

/* The detail hero is the shared element's destination. The source (a grid card
   photo) is tagged with the same name in JS at click time, so only one element
   carries the name in each snapshot — the browser morphs between them. */
.eb-gallery-main img {
    view-transition-name: listing-hero;
}

@media (prefers-reduced-motion: no-preference) {
    /* The photo travels + scales along an ease-out-quint curve; long enough to read
       as physical, short enough to stay responsive. */
    ::view-transition-group(listing-hero) {
        animation-duration: 440ms;
        animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
    }

    /* Both snapshots are the same animal, so a soft crossfade masks the crop change
       (card = square cover, detail = contained) without a visible flicker. */
    ::view-transition-old(listing-hero),
    ::view-transition-new(listing-hero) {
        animation-duration: 440ms;
        animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
        mix-blend-mode: normal;
    }

    /* The rest of the page (grid ⇄ detail layout) crossfades a touch faster underneath
       the morph — cinematic without competing with the hero. */
    ::view-transition-old(root),
    ::view-transition-new(root) {
        animation-duration: 300ms;
        animation-timing-function: ease-out;
    }
}

/* ============================================================
   Compare: card select control, sticky tray, matrix modal.
   ============================================================ */

/* ---- Card "add to compare" control -------------------------------------- */
.eb-card { position: relative; }

/* The card advertises cursor: pointer but only the photo, title and store name were links, so
   clicking the price — the most natural target on a marketplace tile — did nothing. The photo
   link stretches over the whole card instead. .eb-card is the containing block (position: relative
   above) and the link itself is unpositioned, so inset: 0 resolves against the card.
   Anything that must stay independently clickable sits above this overlay:
   .eb-heart and .eb-compare-toggle are already z-index 2; the store link and the compare toggle are
   raised below. Known trade-off of the stretched-link pattern: text inside the card can no longer
   be selected by dragging. */
.eb-card-photo-link::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
}

/* The store name goes somewhere else entirely, so it must win against the overlay. */
.eb-card .eb-store-line {
    position: relative;
    z-index: 2;
}

/* Compare is a control, not navigation. z-index 3, not 2: its hit-slop pseudo-element reaches
   below the spec strip into .eb-store-line, which is also z-index 2 and later in the DOM, so at
   2 the store link would win the overlap and swallow taps on the toggle's bottom edge. */
.eb-card .eb-compare-toggle {
    position: relative;
    z-index: 3;
}

/* a picked card wears the orange ring, like hover */
.eb-card-selected {
    border-color: var(--eb-orange) !important;
    box-shadow: 0 0 0 1px var(--eb-orange) inset;
}

/* ---- Sticky compare tray ------------------------------------------------ */
.eb-compare-tray {
    position: fixed;
    left: 0;
    right: 0;
    /* Clears the cookie-consent bar while it is showing; --eb-consent-h is measured by
       window.eballConsent (App.razor) and is 0 once consent is accepted. Without this the
       consent bar's z-index 999999 covered this tray's buttons and ate every click. */
    bottom: var(--eb-consent-h, 0px);
    z-index: 1250; /* above page content, below MudBlazor's dialog (~1400) */
    background: rgba(12, 12, 14, 0.92);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-top: 1px solid var(--eb-border);
    padding: 12px 0 calc(12px + env(safe-area-inset-bottom));
    animation: eb-tray-up 240ms cubic-bezier(0.22, 1, 0.36, 1);
}

@keyframes eb-tray-up {
    from { transform: translateY(100%); }
    to { transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
    .eb-compare-tray { animation: none; }
}

.eb-compare-tray-inner {
    display: flex;
    align-items: center;
    gap: 14px;
}

.eb-compare-tray-label {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 0.95rem;
    color: var(--eb-text);
    white-space: nowrap;
}

.eb-compare-thumbs {
    display: flex;
    gap: 10px;
    overflow-x: auto;
    padding: 2px;
}

.eb-compare-thumb {
    position: relative;
    width: 46px;
    height: 46px;
    min-width: 46px;
    border-radius: 10px;
    border: 1px solid var(--eb-border-2);
    background: var(--eb-photo-bg);
}

.eb-compare-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: inherit;
    display: block;
}

.eb-compare-thumb-empty {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--eb-muted);
}

.eb-compare-thumb-remove {
    position: absolute;
    top: -7px;
    right: -7px;
    width: 18px;
    height: 18px;
    border-radius: 999px;
    background: var(--eb-surface);
    border: 1px solid var(--eb-border-2);
    color: var(--eb-text);
    font-size: 0.8rem;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}

.eb-compare-thumb-remove:hover {
    border-color: var(--eb-orange);
    color: var(--eb-orange-ink);
}

.eb-compare-hint {
    font-size: 0.82rem;
    color: var(--eb-muted);
    white-space: nowrap;
}

.eb-compare-clear {
    background: none;
    border: none;
    color: var(--eb-muted);
    font-weight: 600;
    font-size: 0.85rem;
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 3px;
    white-space: nowrap;
}

.eb-compare-clear:hover { color: var(--eb-text); }

.eb-compare-go {
    background: var(--eb-orange);
    color: var(--eb-on-orange);
    border: none;
    border-radius: 12px;
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 0.92rem;
    padding: 11px 26px;
    cursor: pointer;
    white-space: nowrap;
    transition: background 140ms ease;
}

.eb-compare-go:hover:not(:disabled) { background: var(--eb-orange-hover); }

.eb-compare-go:disabled {
    background: var(--eb-border-2);
    color: var(--eb-muted);
    cursor: default;
}

/* keep the footer clear of the fixed tray */
body:has(.eb-compare-tray) .eb-footer { padding-bottom: calc(84px + env(safe-area-inset-bottom)); }

@media (max-width: 640px) {
    .eb-compare-hint { display: none; }
    .eb-compare-go { padding: 11px 18px; }
}

/* ---- Compare matrix modal ---------------------------------------------- */
.eb-compare-dialog-title {
    font-family: var(--eb-display);
    font-weight: 800;
    letter-spacing: -0.01em;
    color: var(--eb-text);
}

.eb-compare-scroll { overflow-x: auto; }

.eb-compare-table {
    border-collapse: collapse;
    width: 100%;
}

.eb-compare-corner {
    width: 128px;
    position: sticky;
    left: 0;
    background: var(--eb-surface);
    z-index: 1;
}

.eb-compare-col {
    position: relative;
    padding: 6px 12px 12px;
    text-align: center;
    vertical-align: top;
    border-bottom: 1px solid var(--eb-border);
    min-width: 148px;
}

.eb-compare-col-remove {
    position: absolute;
    top: 2px;
    right: 6px;
    width: 22px;
    height: 22px;
    border-radius: 999px;
    background: var(--eb-surface-2);
    border: 1px solid var(--eb-border-2);
    color: var(--eb-muted);
    cursor: pointer;
    font-size: 0.9rem;
    line-height: 1;
    z-index: 2;
}

.eb-compare-col-remove:hover {
    border-color: var(--eb-orange);
    color: var(--eb-orange-ink);
}

.eb-compare-col-photo {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Fixed to the 4-up size so photos never grow when comparing fewer animals —
       the detail page is where you go for a bigger picture. */
    width: 260px;
    max-width: 100%;
    aspect-ratio: 1 / 1;
    border-radius: 12px;
    overflow: hidden;
    background: var(--eb-photo-bg);
    border: 1px solid var(--eb-border);
    margin: 0 auto 8px;
}

.eb-compare-col-photo img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.eb-compare-col-title {
    display: block;
    color: var(--eb-text);
    font-weight: 600;
    font-size: 0.84rem;
    line-height: 1.25;
    text-decoration: none;
}

.eb-compare-col-title:hover { color: var(--eb-orange-ink); }

.eb-compare-label {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--eb-surface);
    color: var(--eb-muted);
    font-weight: 600;
    font-size: 0.82rem;
    padding: 10px 16px 10px 4px;
    white-space: nowrap;
    border-bottom: 1px solid var(--eb-border);
}

.eb-compare-value {
    text-align: center;
    color: var(--eb-text);
    font-weight: 600;
    font-size: 0.86rem;
    padding: 10px 12px;
    border-bottom: 1px solid var(--eb-border);
}

.eb-compare-price {
    color: var(--eb-orange-ink);
    font-weight: 800;
    font-size: 1rem;
}

.eb-compare-was {
    color: var(--eb-muted);
    text-decoration: line-through;
    font-size: 0.8rem;
    margin-left: 6px;
}

.eb-compare-seller {
    color: var(--eb-body);
    text-decoration: none;
}

.eb-compare-seller:hover { color: var(--eb-text); }

.eb-compare-section {
    position: sticky;
    left: 0;
    background: var(--eb-surface-2);
    color: var(--eb-muted);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-size: 0.72rem;
    padding: 12px 16px;
    border-bottom: 1px solid var(--eb-border);
}

.eb-compare-trait-row .eb-compare-label {
    text-transform: none;
    color: var(--eb-body);
}

.eb-compare-dot {
    display: inline-block;
    width: 12px;
    height: 12px;
    border-radius: 999px;
    background: var(--eb-orange);
}

.eb-compare-dash { color: var(--eb-muted); }

/* a trait every animal shares is background context, not a differentiator */
.eb-compare-shared .eb-compare-label,
.eb-compare-shared .eb-compare-value {
    background: var(--eb-tint-bg);
}

/* ============================================================
   Favourites: card heart, labelled compare toggle, nav heart,
   detail save button + count, empty state.
   ============================================================ */

/* ---- Card heart (save) — always visible, top-right ---------------------- */
.eb-heart {
    position: absolute;
    top: 9px;
    right: 9px;
    z-index: 2;
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    border: none;
    background: rgba(12, 12, 14, 0.55);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
    color: var(--eb-text);
    cursor: pointer;
    transition: background-color 140ms ease, color 140ms ease;
}

.eb-heart .mud-icon-root { font-size: 1.3rem; }
.eb-heart:hover { background: rgba(12, 12, 14, 0.78); }
.eb-heart--on { color: var(--eb-orange-ink); }

.eb-heart--on .mud-icon-root {
    animation: eb-heart-pop 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

@keyframes eb-heart-pop {
    0% { transform: scale(1); }
    40% { transform: scale(1.35); }
    100% { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
    .eb-heart--on .mud-icon-root { animation: none; }
}

.eb-heart:focus-visible {
    outline: 2px solid var(--eb-orange);
    outline-offset: 2px;
}

/* ---- Labelled compare toggle, right of the card's spec strip ------------- */
/* A bare grey circle was too easy to miss; a labelled checkbox reads clearly as a select option.
   It shares the spec row rather than owning one, so it must never shrink or wrap — the spec data
   next to it truncates instead. */
.eb-compare-toggle {
    position: relative;
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    gap: 7px;
    padding: 4px 2px;   /* taller tap target on touch */
    width: fit-content;
    cursor: pointer;
    user-select: none;
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--eb-muted);
}

/* Invisible hit-slop: the visible chip stays compact so it doesn't inflate card height, but the
   tappable area reaches the 44px touch-target minimum via an absolutely-positioned pseudo-element
   (no layout impact — it doesn't push sibling content). */
.eb-compare-toggle::before {
    content: "";
    position: absolute;
    inset: -9px -4px;
}

.eb-compare-toggle-input {
    position: absolute;
    width: 0;
    height: 0;
    opacity: 0;
}

.eb-compare-toggle-box {
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    border-radius: 5px;
    border: 1.5px solid var(--eb-border-2);
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background-color 140ms ease, border-color 140ms ease;
}

.eb-compare-toggle-tick {
    color: transparent;
    font-size: 0.7rem;
    font-weight: 800; /* system weight ceiling is 800 (DESIGN.md) — don't reach for the file's 900 */
    line-height: 1;
}

.eb-compare-toggle:hover .eb-compare-toggle-box { border-color: var(--eb-orange); }

.eb-compare-toggle--on .eb-compare-toggle-box {
    background: var(--eb-orange);
    border-color: var(--eb-orange);
}

.eb-compare-toggle--on .eb-compare-toggle-tick { color: var(--eb-on-orange); }
.eb-compare-toggle--on .eb-compare-toggle-text { color: var(--eb-orange-ink); }

.eb-compare-toggle:has(.eb-compare-toggle-input:disabled) {
    opacity: 0.5;
    cursor: default;
}

.eb-compare-toggle-input:focus-visible ~ .eb-compare-toggle-box {
    outline: 2px solid var(--eb-orange);
    outline-offset: 2px;
}

/* ---- Nav heart with count ------------------------------------------------ */
.eb-fav-nav,
.eb-chat-nav {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 999px;
    color: rgba(255, 255, 255, 0.8);
    text-decoration: none;
    transition: color 140ms ease, background-color 140ms ease;
}

.eb-fav-nav:hover,
.eb-chat-nav:hover {
    color: var(--eb-text);
    background: rgba(255, 255, 255, 0.08);
}

.eb-fav-nav .mud-icon-root,
.eb-chat-nav .mud-icon-root { font-size: 1.3rem; }

.eb-fav-nav-badge,
.eb-chat-nav-badge {
    position: absolute;
    top: 2px;
    right: 2px;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: 999px;
    background: var(--eb-orange);
    color: var(--eb-on-orange);
    font-size: 0.68rem;
    font-weight: 800;
    line-height: 16px;
    text-align: center;
}

/* ---- Detail save button + count ----------------------------------------- */
.eb-save-row {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-bottom: 20px;
    /* Save + saved-count + the five share buttons do not fit the sidebar column on mobile;
       wrapping drops the share group onto its own line instead of squeezing the icons. */
    flex-wrap: wrap;
    row-gap: 10px;
}

.eb-save-btn {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    background: transparent;
    border: 1px solid var(--eb-border-2);
    color: var(--eb-text);
    border-radius: 999px;
    font-weight: 700;
    font-size: 0.9rem;
    padding: 9px 18px;
    cursor: pointer;
    transition: border-color 140ms ease, color 140ms ease, background-color 140ms ease;
}

.eb-save-btn .mud-icon-root { font-size: 1.1rem; }
.eb-save-btn:hover { border-color: var(--eb-orange); color: var(--eb-orange-on-tint); }

.eb-save-btn--on {
    border-color: var(--eb-orange);
    color: var(--eb-orange-on-tint);
    background: var(--eb-tint-bg);
}

.eb-save-btn:focus-visible {
    outline: 2px solid var(--eb-orange);
    outline-offset: 2px;
}

.eb-save-count {
    font-size: 0.85rem;
    color: var(--eb-muted);
}

/* ---- Favourites empty state --------------------------------------------- */
.eb-empty-heart {
    color: var(--eb-border-2);
    margin-bottom: 6px;
}

.eb-empty-heart .mud-icon-root { font-size: 2.6rem; }

/* ==== Home: bolder front door ============================================= */
/* Photo hero + bento-led "Latest Listings" + amplified section hierarchy.
   The most recent listing paints the hero backdrop and leads the grid. */

/* --- Hero with a real animal photo behind the headline ------------------- */
.eb-hero-bg--photo {
    /* 440, down from 560 via 500: at 560 the panel plus its plinth ran 737px — 82% of a 900px
       viewport — and the first listing card started 65px below the fold, on a site whose first
       principle is that the animal is the hero. 500 still read tall; this is the no-photo
       fallback's own height, which was always the better-proportioned of the two. */
    min-height: 440px;
    /* Layers, front→back:
       1. the heat lamp, top-right — the room's one warm source.
       2. an EDGE VIGNETTE. This is the layer the old stack was missing, and the reason a
          white-studio cutout turned the panel pale at the top-right: the diagonal scrim below
          fades to 0.24 by the right edge, so on a light-background photo a third of the frame
          left the dark room entirely and the hero read as a light hero — the one thing DESIGN.md
          says never flips. The vignette is transparent through the middle, where the animal is,
          and closes hard only at the perimeter, so it contains blowout without muddying the subject.
       3. the text column: a steeper ink ramp than before, holding near-solid to 34% and clear of
          the animal by ~74%, so the headline and the 52ch sub both sit in a solid room.
       4. the seam: ink gathering under the caption and the search pill.
       5. the photo, pinned right so the subject sits in the light rather than behind the words. */
    background-image:
        radial-gradient(64% 88% at 88% 16%, rgba(232, 99, 10, 0.22) 0%, rgba(232, 99, 10, 0) 60%),
        linear-gradient(97deg, #0C0C0E 0%, rgba(12, 12, 14, 0.96) 32%, rgba(12, 12, 14, 0.74) 50%, rgba(12, 12, 14, 0.20) 73%, rgba(12, 12, 14, 0.05) 100%),
        linear-gradient(180deg, rgba(12, 12, 14, 0) 44%, rgba(12, 12, 14, 0.34) 70%, rgba(12, 12, 14, 0.94) 100%),
        radial-gradient(76% 96% at 60% 62%, #FFFFFF 0%, #EDEDF0 26%, #9A9AA4 62%, #4E4E56 88%, #35353C 100%),
        linear-gradient(180deg, #6E6E78 0%, #B4B4BC 22%, #FFFFFF 52%),
        var(--eb-hero-image);
    /* The two multiply layers are what make this robust. A white-studio cutout (most breeder photos)
       has a backdrop brighter than anything else on the site, and no amount of flat ink fixes it:
       enough opacity to tame the corner also buries the animal. Multiply is proportional — it
       crushes highlights hard and midtones gently — so a pale backdrop drops into the room while
       the animal's yellows keep their detail and saturation.
       Two layers, two jobs: a tight photographic vignette (clear at the subject, closing hard at
       the perimeter) and a top-edge fall-off, because the top of the panel is scrim in every
       composition. The ink ramps then build the solid text column on an already-dark plate. */
    background-blend-mode: normal, normal, normal, multiply, multiply, normal;
    background-position: center, center, center, center, center, right center;
    background-size: cover, cover, cover, cover, cover, cover;
    background-repeat: no-repeat;
}

/* Guarantee headline/sub legibility no matter what the photo does behind them. */
.eb-hero-bg--photo .eb-hero-title { text-shadow: 0 2px 30px rgba(0, 0, 0, 0.55); }
.eb-hero-bg--photo .eb-hero-sub { text-shadow: 0 1px 18px rgba(0, 0, 0, 0.6); }

@media (max-width: 720px) {
    .eb-hero-bg--photo {
        min-height: 430px;
        /* Pinning the photo right is a desktop move — it puts the subject clear of the text column.
           On a phone the panel is narrower than it is tall, so `right center` crops a landscape shot
           down to its right-hand edge, which on a studio shot is backdrop: the animal disappears.
           Centre it instead; the copy overlays the whole panel at this width anyway. */
        background-position: center, center, center, center, center, center;
    }
}

/* --- Amplified section heads (the confident lead) ------------------------ */
.eb-section-head--lg {
    align-items: baseline;
    margin-bottom: 26px;
    margin-top: 4px;
}

.eb-section-title--lg {
    font-size: clamp(1.55rem, 2.6vw, 2.05rem);
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.05;
}

/* --- Bento grid: one large "pick" leads a field of tiles ----------------- */
.eb-bento {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 16px;
    align-items: stretch;
}

/* the featured card claims a 2×2 block at the top-left; the rest auto-flow around it */
.eb-bento > :first-child {
    grid-column: span 2;
    grid-row: span 2;
}

/* Tracks .eb-card-grid at the container cap so home and browse never disagree about how many
   listings a row holds — they were missed apart once already. Five columns leaves the pick of the
   week its 2×2 block with three tiles beside it instead of two, which is the shape the hero was
   drawn for. Widest query first, same reason as .eb-card-grid. */
@media (max-width: 1400px) {
    .eb-bento { grid-template-columns: repeat(4, 1fr); }
}

@media (max-width: 1180px) {
    .eb-bento { grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 860px) {
    .eb-bento { grid-template-columns: repeat(2, 1fr); }
    .eb-bento > :first-child { grid-column: span 2; grid-row: span 1; }
}

/* No 1-up step here either — the home grid follows the same rule as .eb-card-grid (see the note
   there). The 2-up rule above carries all the way down, and because it also keeps the pick of the
   week spanning both columns, a phone gets one full-width hero tile followed by pairs, which is the
   hierarchy this grid was built for rather than a flat single file of cards. */

/* --- Other-species lane: a card wearing its category ---------------------
   The home page's second listings band mixes categories, so each card carries a label the bento
   above never needs. It sits outside the card rather than on the photo: an overlay would compete
   with the sale and "new" badges that already live there, and a chip that is also the link into
   that section reads better as a caption than as another sticker.
   The wrapper is the grid item, so the card has to be told to fill the rest of the row. Every
   colour here is a token that already carries a light-theme value — no second block needed. */
.eb-species-lane-item {
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-width: 0;
}

.eb-species-lane-item > .mud-card {
    flex: 1 1 auto;
}

/* Scoped through the lane wrapper on purpose: global-styles.css styles `body.abp-application-layout
   a` — which the Blazor host's own body carries — and that beats a bare class, so an unscoped rule
   here rendered every chip in link orange. Orange is spent on price and the primary action (One
   Orange); these are captions until you point at one. */
.eb-species-lane-item a.eb-species-chip {
    align-self: start;
    max-width: 100%;
    padding: 3px 10px;
    border: 1px solid var(--eb-border);
    border-radius: 999px;
    background: var(--eb-surface);
    color: var(--eb-muted);
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    text-decoration: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: color 140ms ease, border-color 140ms ease, background-color 140ms ease;
}

.eb-species-lane-item a.eb-species-chip:hover,
.eb-species-lane-item a.eb-species-chip:focus-visible {
    color: var(--eb-orange-on-tint);
    border-color: var(--eb-tint-border);
    background: var(--eb-tint-bg);
    text-decoration: none;
}

/* --- Featured "pick of the week" card (2×2 tile) ------------------------- */
/* The photo grows to fill the tall card; a footer band carries title + price. */
.eb-card-featured .eb-card-photo-link {
    flex: 1 1 auto;
    min-height: 0;
    display: block;
}

.eb-card-featured .eb-card-photo {
    height: 100%;
    min-height: 340px;
}

/* Taken out of flow so the photo's own proportions cannot size the tile. In flow, the card's 4:5
   aspect resolved to 659px at 528px wide — taller than the two rows it spans — so grid grew BOTH
   rows to fit it, and because the bento stretches its items every small card beside the pick came
   out ~21px taller than the identical cards in the row below. The hero fills the height the
   ordinary cards set; it does not set it.
   :first-child, not img: the no-photo placeholder is a div carrying the same aspect ratio, and it
   was the one actually doing this on a card with no cover image. */
.eb-card-featured .eb-card-photo > :first-child {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    aspect-ratio: auto !important;
    object-fit: cover;
}

.eb-pick-badge {
    position: absolute;
    top: 14px;
    left: 14px;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 0.72rem;
    font-weight: 800;
    letter-spacing: 0.01em;
    color: var(--eb-on-orange-on-dark);
    background: var(--eb-orange-on-dark);
    border-radius: 8px;
    padding: 6px 12px;
    box-shadow: 0 6px 18px rgba(232, 99, 10, 0.36);
}

.eb-card-featured-footer {
    display: flex;
    align-items: flex-end;
    gap: 16px;
    padding: 18px 22px 20px;
    flex: 0 0 auto;
}

.eb-card-featured-info { min-width: 0; }

.eb-card-featured-title {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: clamp(1.1rem, 1.5vw, 1.4rem);
    line-height: 1.14;
    letter-spacing: -0.01em;
    color: var(--eb-text);
    margin-bottom: 7px;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.eb-card-featured-price {
    text-align: right;
    flex: 0 0 auto;
}

.eb-price--lg {
    font-size: 1.75rem;
    line-height: 1.08;
}

.eb-card-featured-price .eb-price-was { display: block; margin-top: 2px; }
.eb-card-featured-price .eb-ship-pill { display: block; margin-top: 5px; }

/* ==== Store page: living storefront ====================================== */

.eb-store-hero {
    position: relative;
    min-height: 380px;
    container-type: inline-size;
    border-radius: 20px;
    overflow: hidden;
    border: 1px solid var(--eb-border);
    background: var(--eb-hero-fallback);
    display: flex;
    align-items: flex-end;
    margin-bottom: 28px;
}

/* The seller's own stock forms the backdrop — an atmospheric, softly-blurred layer so the
   identity reads clearly and any studio watermarks on the source photos melt away. The sharp
   stock lives in the grid below, where the animals are genuinely the hero. */
.eb-store-hero-montage {
    position: absolute;
    inset: 0;
    display: flex;
    filter: blur(3px) saturate(1.06);
    transform: scale(1.08);
}

.eb-store-hero-montage img {
    flex: 1 1 0;
    min-width: 0;
    height: 100%;
    object-fit: cover;
}

.eb-store-hero--photos::after {
    content: "";
    position: absolute;
    inset: 0;
    background:
        radial-gradient(78% 120% at 88% 4%, rgba(232, 99, 10, 0.22) 0%, rgba(232, 99, 10, 0) 55%),
        linear-gradient(180deg, rgba(12, 12, 14, 0.5) 0%, rgba(12, 12, 14, 0.28) 32%, rgba(12, 12, 14, 0.9) 100%);
}

.eb-store-hero-body {
    position: relative;
    z-index: 1;
    width: 100%;
    align-self: stretch;
    box-sizing: border-box;
    padding: 28px 32px;
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 24px;
    flex-wrap: wrap;
}

.eb-store-hero-id {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 16px;
    min-width: 0;
    align-self: flex-start;
}

.eb-store-hero-text {
    text-align: left;
}

.eb-store-avatar-lg {
    width: 128px;
    height: 128px;
    flex: 0 0 auto;
    border-radius: 16px;
    overflow: hidden;
    background: color-mix(in srgb, var(--eb-surface) 70%, transparent);
    border: 1px solid var(--eb-border-2);
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 3rem;
    color: var(--eb-orange-ink);
}

.eb-store-avatar-lg img {
    object-fit: contain;
    display: block;
}

.eb-store-avatar-lg--logo {
    width: fit-content;
    height: fit-content;
    max-width: none;
    max-height: none;
    padding: 10px;
    border: 0;
    border-radius: 12px;
    overflow: hidden;
    background: color-mix(in srgb, var(--eb-surface) 70%, transparent);
}

.eb-store-avatar-lg--logo img {
    width: auto;
    height: auto;
    max-width: 35cqw;
    max-height: 152px;
}

.eb-store-name {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: clamp(1.7rem, 4vw, 2.8rem);
    line-height: 1.02;
    letter-spacing: -0.03em;
    color: var(--eb-text);
    margin: 0;
}

.eb-store-hero--photos .eb-store-name { text-shadow: 0 2px 26px rgba(0, 0, 0, 0.55); }

.eb-store-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    margin-top: 10px;
    color: rgba(255, 255, 255, 0.82);
    font-size: 0.9rem;
}

/* --- "Works with": the keeper's genetic signature ------------------------ */
/* The one statement a store page can make that a filtered grid cannot, so it
   sits between the hero and every control. No container — the genes are the
   content, and boxing them would make them furniture. */

.eb-store-signature {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 10px 14px;
    margin: 0 0 26px;
}

/* Two classes deep on purpose: global-styles.css colours every heading under
   body.abp-application-layout, and this label has to stay quieter than the genes
   it introduces. */
.eb-store-signature h2.eb-store-signature-label {
    font-family: inherit;
    font-size: 0.85rem;
    font-weight: 700;
    letter-spacing: 0;
    color: var(--eb-muted);
    margin: 0;
    flex: 0 0 auto;
}

.eb-store-signature-list {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    list-style: none;
    margin: 0;
    padding: 0;
}

/* Same tint vocabulary as the trait chips, so a gene reads as a gene everywhere.
   --eb-orange-on-tint, not --eb-orange-ink: the ink token measures 4.46:1 on this
   fill in dark and 4.17:1 in light, both under the floor. */
.eb-store-gene {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    min-height: 32px;
    padding: 0 13px;
    border-radius: 999px;
    background-color: var(--eb-tint-bg);
    border: 1px solid var(--eb-tint-border);
    color: var(--eb-orange-on-tint);
    font-size: 0.82rem;
    font-weight: 700;
    text-decoration: none;
    transition: background-color 140ms ease, border-color 140ms ease;
}

.eb-store-gene:hover,
.eb-store-gene:focus-visible {
    background-color: var(--eb-tint-border);
    border-color: var(--eb-orange);
    color: var(--eb-orange-on-tint);
}

/* How many of the store's animals carry the gene — the depth behind the claim.
   Separator drawn in CSS, matching the reading-time dot on guide labels. */
.eb-store-gene-count::before {
    content: "\00B7";
    margin-right: 7px;
    opacity: 0.5;
}

.eb-store-gene-count {
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
}

/* Phones tap these; 32px is a mouse size. Matches the 40px floor applied to the
   rest of the marketplace's phone targets. */
@media (max-width: 899.98px) {
    .eb-store-gene {
        min-height: 40px;
        padding: 0 15px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .eb-store-gene { transition: none; }
}

.eb-store-meta-item { display: inline-flex; align-items: center; gap: 4px; }
.eb-store-meta-item:not(:last-child)::after {
    content: "\00B7";
    margin-left: 14px;
    color: rgba(255, 255, 255, 0.4);
}
.eb-store-meta-item .mud-icon-root { font-size: 1rem; }
.eb-store-meta-rating .eb-star { color: var(--eb-orange-ink); }
.eb-store-meta-dim { color: rgba(255, 255, 255, 0.55); }

.eb-store-hero-actions {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 12px;
    flex: 0 0 auto;
    align-self: flex-end;
}

/* Store follow: a quiet secondary action beside the collection CTA. It follows
   the Flat-at-Rest rule and only gains the approved orange tint when active. */
.eb-store-follow {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    min-height: 42px;
    padding: 9px 15px;
    border: 1px solid rgba(255, 255, 255, 0.24);
    border-radius: 12px;
    background: rgba(12, 12, 14, 0.44);
    color: rgba(255, 255, 255, 0.9);
    font: 700 0.86rem/1 var(--eb-display);
    text-decoration: none;
    cursor: pointer;
    backdrop-filter: blur(8px);
    transition: color 140ms ease, border-color 140ms ease, background-color 140ms ease;
}
.eb-store-follow:hover {
    border-color: var(--eb-orange);
    color: var(--eb-text);
    background: rgba(12, 12, 14, 0.7);
}
.eb-store-follow--on {
    border-color: var(--eb-orange-on-dark);
    background: var(--eb-tint-bg);
    color: var(--eb-orange-on-dark);
}
.eb-store-follow:disabled { opacity: 0.55; cursor: wait; }
.eb-store-follow-count {
    display: inline-flex;
    min-width: 21px;
    height: 21px;
    padding-inline: 6px;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.12);
    color: inherit;
    font-size: 0.72rem;
}
.eb-store-follow--sm {
    min-height: 36px;
    padding: 7px 11px;
    border-radius: 10px;
    background: transparent;
}

/* --- Primary store CTA (shared by hero, sticky, empty states) --- */
.eb-store-cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--eb-orange);
    color: var(--eb-on-orange);
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 0.92rem;
    padding: 11px 22px;
    border: none;
    border-radius: 12px;
    cursor: pointer;
    white-space: nowrap;
    text-decoration: none;
    transition: background 140ms ease;
}
.eb-store-cta:hover { background: var(--eb-orange-hover); color: var(--eb-on-orange); }
.eb-store-cta:disabled { opacity: 0.45; cursor: not-allowed; }
.eb-store-cta--sm { padding: 7px 15px; font-size: 0.84rem; border-radius: 10px; }

/* --- Social sharing (quiet by default, orange on hover) --- */
.eb-share { display: flex; align-items: center; gap: 6px; }
.eb-share-label { font-size: 0.78rem; color: rgba(255, 255, 255, 0.7); margin-right: 2px; }
.eb-share-btn {
    width: 34px;
    height: 34px;
    border-radius: 9px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.1);
    color: var(--eb-text);
    border: 1px solid rgba(255, 255, 255, 0.16);
    cursor: pointer;
    text-decoration: none;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}
.eb-share-btn:hover { background: var(--eb-orange); color: var(--eb-on-orange); border-color: var(--eb-orange); }
.eb-share-btn .mud-icon-root { font-size: 1.05rem; }
.eb-share-btn:focus-visible { outline: 2px solid var(--eb-orange); outline-offset: 2px; }

/* Inline variant — listing / equipment detail. The rules above are tuned for the store hero, a
   dark photo scrim in BOTH themes, so their white-alpha fill and 70%-white label disappear on a
   white card. Same control, tokenised so it flips with the theme. */
.eb-share--inline { flex-wrap: wrap; }
.eb-share--inline .eb-share-label { color: var(--eb-muted); }
.eb-share--inline .eb-share-btn {
    background: var(--eb-surface-2);
    border-color: var(--eb-border-2);
    color: var(--eb-text);
}
.eb-share--inline .eb-share-btn:hover {
    background: var(--eb-orange);
    border-color: var(--eb-orange);
    color: var(--eb-on-orange);
}

/* The light theme forces .eb-share-btn to --eb-on-photo (white) with !important, because on the
   store hero these buttons sit on a photo scrim that stays dark in BOTH themes. The inline variant
   does not: it sits on a card that turns white, so that rule painted white glyphs on a near-white
   chip and the row of share icons vanished in light mode. Three classes plus !important to
   out-rank it — the hero rule is [data-theme] + one class, and it has to keep winning everywhere
   else. Hover is unaffected: the orange-fill rule is later in the sheet at equal specificity. */
[data-theme="light"] .eb-share--inline .eb-share-btn {
    color: var(--eb-text) !important;
}

/* --- Store social links (quiet by default, brand-orange on hover) --- */
.eb-store-social {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 14px;
}
.eb-social-btn {
    width: 34px;
    height: 34px;
    border-radius: 9px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.1);
    color: var(--eb-text);
    border: 1px solid rgba(255, 255, 255, 0.16);
    text-decoration: none;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}
.eb-social-btn:hover { background: var(--eb-orange); color: var(--eb-on-orange); border-color: var(--eb-orange); }
.eb-social-btn .mud-icon-root { font-size: 1.15rem; }

/* Section label inside the seller forms (e.g. My Store social-links group) */
.eb-form-section-label {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 1rem;
    color: var(--eb-text);
    margin: 8px 0 2px;
}

/* --- First-run "your store is live" panel (/my/store, immediately after create) ------------ */
/* Tinted rather than filled: this is a confirmation, not a call to action — the orange belongs on
   the "Add your first animal" button inside it, which is the only fill on the card. */
.eb-store-live {
    background: var(--eb-tint-bg);
    border: 1px solid var(--eb-tint-border);
}

/* The address is the point of the panel, so it is set at reading size and left selectable —
   a seller's first instinct here is to copy it. */
.eb-store-live-url {
    display: inline-block;
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 1.15rem;
    color: var(--eb-orange-on-tint);
    word-break: break-all;
    text-decoration: none;
}

.eb-store-live-url:hover {
    text-decoration: underline;
}

.eb-store-live-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    margin-top: 20px;
}

/* --- Condensed sticky identity bar (revealed after the hero scrolls away) --- */
.eb-store-sticky {
    position: fixed;
    top: 64px;
    left: 0;
    right: 0;
    z-index: 1000;
    background: rgba(12, 12, 14, 0.92);
    backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--eb-border);
    transform: translateY(-112%);
    transition: transform 280ms cubic-bezier(0.22, 1, 0.36, 1);
    will-change: transform;
}
.eb-store-sticky--on { transform: translateY(0); }

.eb-store-sticky-in {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 0;
}

.eb-store-avatar-sm {
    width: 32px; height: 32px; flex: 0 0 auto;
    border-radius: 8px; overflow: hidden;
    background: var(--eb-surface); border: 1px solid var(--eb-border-2);
    display: flex; align-items: center; justify-content: center;
    font-family: var(--eb-display); font-weight: 800; color: var(--eb-orange-ink); font-size: 0.9rem;
}
.eb-store-avatar-sm img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
    padding: 3px;
    box-sizing: border-box;
}

/* Store management: preserve the complete uploaded logo and keep the related
   publishing actions together in the right-hand identity panel. */
.eb-store-logo-preview {
    width: 160px;
    height: 160px;
    flex: 0 0 160px;
    padding: 12px;
    box-sizing: border-box;
    border: 1px solid var(--eb-border);
    border-radius: 16px;
    overflow: hidden;
    background: var(--eb-tint-bg);
}

.eb-store-logo-preview img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

.eb-store-logo-preview--fallback {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--eb-orange-ink);
    font-family: var(--eb-display);
    font-size: 3rem;
    font-weight: 800;
}

.eb-my-store-actions {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    padding: 0 16px 16px;
}

.eb-my-store-actions > .mud-button-root {
    margin: 0;
}

/* Store management: the inventory panel outranks the storefront settings below it. The emphasis is a
   full orange hairline, not a side stripe — DESIGN.md bans coloured side-stripe borders on cards,
   and this is a MudCard. It also matches how listing cards already signal emphasis (border turns
   orange), so the vocabulary is consistent. A tint fill is the documented alternative, but MudPaper
   supplies an opaque surface colour here and a translucent fill would let the page charcoal through,
   reading as recessed rather than primary. */
.eb-store-listing-hub {
    border: 1px solid var(--eb-tint-border);
}

.eb-store-listing-hub__content {
    display: flex;
    align-items: center;
    gap: 28px;
    padding: 20px 24px !important;
}

.eb-store-listing-hub__intro {
    flex: 0 1 260px;
    min-width: 210px;
}

.eb-store-listing-hub__intro .mud-typography-h6 {
    margin-bottom: 3px;
}

.eb-store-listing-hub__groups {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    flex: 1 1 600px;
    gap: 24px;
}

.eb-store-listing-hub__group {
    min-width: 0;
    padding-inline-start: 20px;
    border-inline-start: 1px solid var(--eb-border);
}

.eb-store-listing-hub__label {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 10px;
    color: var(--eb-text);
}

.eb-store-listing-hub__label .mud-icon-root {
    color: var(--eb-orange-ink);
    font-size: 1.2rem;
}

.eb-store-listing-hub__actions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
}

.eb-store-listing-hub__actions > .mud-button-root {
    min-width: 0;
    margin: 0;
    white-space: nowrap;
}

@media (max-width: 900px) {
    .eb-store-listing-hub__content {
        align-items: stretch;
        flex-direction: column;
        gap: 18px;
    }

    .eb-store-listing-hub__intro {
        flex-basis: auto;
        min-width: 0;
    }

    .eb-store-listing-hub__groups {
        flex-basis: auto;
    }
}

@media (max-width: 600px) {
    .eb-store-listing-hub__content {
        padding: 18px 16px !important;
    }

    .eb-store-listing-hub__groups {
        grid-template-columns: 1fr;
        gap: 18px;
    }
}

@media (max-width: 420px) {
    .eb-store-listing-hub__actions {
        grid-template-columns: 1fr;
    }
}

.eb-store-sticky-name { font-family: var(--eb-display); font-weight: 700; color: var(--eb-text); }
.eb-store-sticky-rating { color: var(--eb-body); font-size: 0.88rem; }
.eb-store-sticky-rating .eb-star { color: var(--eb-orange-ink); }
.eb-store-sticky .eb-store-cta { margin-left: auto; }

/* --- Instant filter bar --- */
.eb-store-filters {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 12px;
}
.eb-store-search { flex: 1 1 220px; max-width: 320px; }
.eb-store-sort { max-width: 210px; }

.eb-seg {
    display: inline-flex;
    background: var(--eb-seg-track);
    border: 1px solid var(--eb-border-2);
    border-radius: 10px;
    padding: 4px;
    gap: 4px;
}

.eb-seg-btn {
    border: none;
    background: none;
    color: var(--eb-text);
    font-family: var(--eb-display);
    font-weight: 600;
    font-size: 0.85rem;
    padding: 8px 12px;
    border-radius: 7px;
    cursor: pointer;
    transition: background 140ms ease, color 140ms ease;
}

.eb-seg-btn--on { background: var(--eb-orange); color: var(--eb-on-orange); }
/* The unselected options carry the full text colour, not the muted one. Every other control in the
   filter row is a bordered box that announces itself; these are bare words on a track, so muted grey
   made "Male" and "Female" read as disabled rather than as the two choices on offer. */
.eb-seg-btn:not(.eb-seg-btn--on):hover { background: var(--eb-seg-hover); }

/* A disabled option (e.g. "Hets" on a gene with none listed) reads as unavailable rather than
   inert-but-clickable. */
.eb-seg-btn:disabled {
    color: var(--eb-muted);
    opacity: 0.5;
    cursor: default;
}

/* Touch devices only: 8px padding gives a 35px-tall pill, under the 44px minimum a thumb needs.
   Gated on pointer:coarse so the desktop filter bar keeps its compact metrics. Applies to the
   browse filter bar as well as the morph pages — same component, same floor. */
@media (pointer: coarse) {
    .eb-seg-btn {
        min-height: 44px;
        padding: 8px 14px;
    }
}
.eb-seg-btn:focus-visible { outline: 2px solid var(--eb-orange); outline-offset: 2px; }

/* --- "The collection" --------------------------------------------------- */
/* Names the stock before the toolbar filters it. The count keeps its old muted
   weight and moves onto the same baseline, so nothing shouts. */

.eb-store-collection {
    display: flex;
    align-items: baseline;
    gap: 12px;
    /* More above than below: the heading belongs to the stock under it, not to the
       tab bar it would otherwise sit against. */
    margin: 14px 0 20px;
}

.eb-store-collection h2.eb-store-collection-title {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 1.25rem;
    letter-spacing: -0.02em;
    color: var(--eb-text);
    margin: 0;
}

.eb-store-collection-count {
    color: var(--eb-muted);
    font-size: 0.85rem;
}

/* --- Empty / prose / reviews --- */
.eb-store-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 64px 20px;
}
.eb-store-empty-icon { color: var(--eb-border-2); font-size: 3rem !important; }
.eb-store-empty-title { font-family: var(--eb-display); font-weight: 700; font-size: 1.2rem; color: var(--eb-text); }
.eb-store-empty-sub { color: var(--eb-muted); margin-top: 6px; }

.eb-store-prose { color: var(--eb-body); font-size: 0.98rem; line-height: 1.7; max-width: 70ch; }
.eb-store-prose-h { font-family: var(--eb-display); font-weight: 700; color: var(--eb-text); font-size: 1.15rem; margin: 20px 0 8px; }

.eb-store-review-form {
    background: var(--eb-surface);
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    padding: 20px;
    margin-bottom: 20px;
    max-width: 640px;
}
.eb-store-review { border-bottom: 1px solid var(--eb-border); padding: 14px 0; }
.eb-store-review-who { font-weight: 700; color: var(--eb-text); font-size: 0.9rem; }
.eb-store-review-when { color: var(--eb-muted); font-size: 0.8rem; }
.eb-store-review-text { color: var(--eb-body); margin-top: 6px; line-height: 1.6; }

/* --- MudTabs re-theme for the dark surface --- */
.eb-store-tabs .mud-tabs-toolbar { background: transparent; border-bottom: 1px solid var(--eb-border); }
.eb-store-tabs .mud-tab { color: var(--eb-muted); text-transform: none; font-family: var(--eb-display); font-weight: 600; }
.eb-store-tabs .mud-tab.mud-tab-active { color: var(--eb-text); }
.eb-store-tabs .mud-tab-slider { background: var(--eb-orange) !important; }

/* --- View-Transition grid morph on filter/sort change --- */
::view-transition-old(eb-grid),
::view-transition-new(eb-grid) {
    animation-duration: 280ms;
    animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
}

/* --------------------------------------------------------------------------
   Store page on a phone.

   A storefront is read differently on a phone than on a desk: the visitor is
   deciding whether this breeder has anything worth their time, so the identity
   has to establish trust in one glance and then get out of the way of the
   stock. Everything below trims the chrome rather than shrinking it.
   -------------------------------------------------------------------------- */
@media (max-width: 720px) {
    /* 380px of hero on an 844px screen pushed the first animal below the fold.
       320px still carries the montage and the full identity block, and the top
       of the grid now lands inside the first screen. */
    .eb-store-hero { min-height: 320px; margin-bottom: 20px; }
    .eb-store-hero-body { flex-direction: column; align-items: flex-start; padding: 18px; gap: 14px; }

    /* Keep the mark stacked above the name. Setting them side by side reclaims ~40px but forces
       the store name onto two lines at phone width, and a wrapped display line costs more
       identity than the height is worth. Take the height out of the mark instead. */
    .eb-store-hero-id { gap: 12px; }
    .eb-store-avatar-lg { width: 72px; height: 72px; font-size: 1.8rem; }
    .eb-store-avatar-lg--logo {
        width: fit-content;
        height: fit-content;
        max-width: none;
        max-height: none;
        padding: 8px;
    }
    /* An uploaded logo is sized by its own box, not the avatar square, so this — not
       .eb-store-avatar-lg — is what actually sets the hero's height for a store that has one.
       144px was over a third of the phone hero on its own. */
    .eb-store-avatar-lg--logo img {
        max-width: 40cqw;
        max-height: 96px;
    }

    /* Five photos across a phone are 70px slivers that read as noise. Three
       still says "this is their own stock". */
    .eb-store-hero-montage img:nth-child(n+4) { display: none; }

    /* The interpunct separators are drawn with ::after, so a wrapped meta row
       stranded one at the end of a line. Below this width the gap does the
       separating and the dots come off. */
    .eb-store-meta { gap: 6px 12px; margin-top: 8px; }
    .eb-store-meta-item:not(:last-child)::after { content: none; }

    /* Full-bleed primary action: the one thing a visitor is here to do, sized
       for a thumb rather than a cursor. */
    .eb-store-hero-actions {
        align-self: stretch;
        align-items: stretch;
        width: 100%;
        margin-top: auto;
        gap: 10px;
    }
    .eb-store-hero-actions .eb-store-cta { width: 100%; min-height: 44px; }
    .eb-store-hero-actions .eb-store-follow { width: 100%; min-height: 44px; }
    .eb-share { justify-content: flex-start; }

    /* The fixed bar is outside the page flow, and its own `padding: 10px 0`
       cancelled .eb-container's side padding, so the CTA sat hard against the
       screen edge and read as clipped. Restore the gutter and let the name —
       not the action — absorb the squeeze. */
    .eb-store-sticky { top: 56px; }
    .eb-store-sticky-in {
        gap: 10px;
        padding-inline: max(16px, env(safe-area-inset-left)) max(16px, env(safe-area-inset-right));
    }
    .eb-store-sticky-name {
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .eb-store-sticky-rating { flex: 0 0 auto; }
    .eb-store-sticky-in .eb-store-cta { flex: 0 0 auto; margin-left: auto; }

    /* MudBlazor gives every tab a fixed 160px slot, so four tabs wanted 640px in
       a 390px viewport and parked Equipment and Reviews off-screen behind two
       chevrons — half the storefront hidden behind a control nobody taps. Let
       the strip size to its labels and scroll under the thumb instead, with the
       right edge fading to show there is more. */
    .eb-store-tabs .mud-tabs-scroll-button { display: none; }
    .eb-store-tabs .mud-tabs-tabbar-content {
        overflow-x: auto;
        scrollbar-width: none;
        -webkit-overflow-scrolling: touch;
        scroll-snap-type: x proximity;
        mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent 100%);
    }
    .eb-store-tabs .mud-tabs-tabbar-content::-webkit-scrollbar { display: none; }
    /* !important because MudTabs writes `min-width:160px` as an inline style from its
       MinimumTabWidth parameter, and inline beats any stylesheet. Setting the parameter
       instead would emit `min-width:0` inline and take the desktop layout with it, so the
       override lives here where it can be scoped to the phone breakpoint. */
    .eb-store-tabs .mud-tab {
        min-width: 0 !important;
        padding: 0 14px;
        scroll-snap-align: start;
    }

    /* Search takes the full row; the pills and the sort share the next one. */
    .eb-store-filters { gap: 10px; }
    .eb-store-search { flex: 1 1 100%; max-width: none; }
    .eb-store-sort { flex: 1 1 140px; max-width: none; }
}

@media (max-width: 460px) {
    .eb-store-sticky-in .eb-store-follow span { display: none; }
    .eb-store-sticky-in .eb-store-follow {
        width: 40px;
        padding: 0;
        flex: 0 0 40px;
    }
}

/* Touch targets on the store page. Scoped to a coarse pointer so the desktop
   composition keeps its tighter rhythm — a laptop with a touchscreen gets the
   larger targets too, which is the point of asking about the pointer rather
   than the viewport. */
@media (pointer: coarse) {
    .eb-share-btn,
    .eb-social-btn,
    .eb-store-follow {
        width: 44px;
        height: 44px;
        border-radius: 11px;
    }
    .eb-store-filters .eb-seg-btn { min-height: 44px; min-width: 44px; padding-block: 10px; }
    .eb-store-sticky-in .eb-store-cta { min-height: 40px; }
}

@media (prefers-reduced-motion: reduce) {
    .eb-store-sticky { transition: none; }
    ::view-transition-old(eb-grid),
    ::view-transition-new(eb-grid) { animation: none; }
}

/* ==========================================================================
   The Lit Exhibit — the storefront's one authored moment

   Scrolling a storefront is walking up to a display case. The room behind the
   keeper's identity softens, darkens and pushes back as you approach, the heat
   lamp goes down with it, and their animals come up into the light instead.
   The identity doesn't snap into a sticky bar — it condenses into one.

   Two decisions carry the whole thing:

   1. ONE CLOCK. Every part of the handoff reads the same band of page scroll
      (--eb-hero-band). The stagger lives in the keyframes, not in a dozen
      separate animation-ranges, so the order things drop out in is readable in
      one place. The band is shorter than the hero is tall, so the condense
      completes while the hero is still on screen and the bar arrives to meet it.

   2. EVERYTHING STAYS ON THE COMPOSITOR. The deepening blur is an opacity
      cross-fade between two statically-filtered montage layers, never an
      animated `filter` (a full-bleed 380px blur repainted per scroll frame is
      what makes this kind of effect stutter). Panel parallax uses `translate`,
      the independent transform property, so a rule that already owns
      `transform` — the card hover lift — composes with it instead of losing to
      the animation's fill.

   Progressive by construction. The layers below are transparent at rest, so
   without scroll-driven animations (Firefox) or under reduced motion nothing
   here applies, and eballStoreSticky's IntersectionObserver keeps toggling the
   bar exactly as it did before.
   ========================================================================== */

:root:root {
    /* Kept next to the choreography rather than up in the token block: it is a
       measurement of this one interaction, not a value any other surface shares. */
    --eb-hero-band: 340px;
}

/* Inert at rest — see the note above. */
.eb-store-hero-montage--deep {
    filter: blur(18px) saturate(1.18);
    opacity: 0;
}

.eb-store-hero-dim {
    position: absolute;
    inset: 0;
    opacity: 0;
    /* The lamp is still in the room, just turned right down: a trace of the
       approved orange under deep black, so the dark still reads warm.
       Deliberately stops short of opaque — at full dim the deep-blurred stock has
       to still ghost through, or the hero's last visible band reads as an empty
       div rather than as a room with the lights off. */
    background:
        radial-gradient(78% 120% at 88% 4%, rgba(232, 99, 10, 0.07) 0%, rgba(232, 99, 10, 0) 46%),
        linear-gradient(180deg, rgba(12, 12, 14, 0.6) 0%, rgba(12, 12, 14, 0.76) 46%, rgba(12, 12, 14, 0.9) 100%);
}

/* -- The choreography ------------------------------------------------------ */

@keyframes eb-hero-recede { 0% { opacity: 0; } 22% { opacity: 0.06; } 100% { opacity: 1; } }
@keyframes eb-hero-push { from { transform: scale(1.08); } to { transform: scale(1.2); } }
@keyframes eb-hero-dim { 0% { opacity: 0; } 40% { opacity: 0.18; } 100% { opacity: 1; } }

/* Five depths, so the backdrop reads as a room and not a photograph. Each panel
   lags the page by a different amount — the further back, the more it lags. */
@keyframes eb-hero-drift-1 { to { translate: 0 1.5%; } }
@keyframes eb-hero-drift-2 { to { translate: 0 4.5%; } }
@keyframes eb-hero-drift-3 { to { translate: 0 2.5%; } }
@keyframes eb-hero-drift-4 { to { translate: 0 6%; } }
@keyframes eb-hero-drift-5 { to { translate: 0 3.5%; } }

/* The identity condenses toward its own top-left corner — which is within a few
   pixels of where the bar's mark sits, because the hero's 32px inset and
   .eb-container's 28px gutter put them in the same column. That near-vertical
   travel is what sells one object moving rather than two crossfading. */
@keyframes eb-hero-condense {
    0% { transform: none; opacity: 1; }
    62% { transform: translateY(-10px) scale(0.84); opacity: 1; }
    100% { transform: translateY(-17px) scale(0.68); opacity: 0; }
}

/* Shed in the order a visitor stops needing each line: the eyebrow first, then
   the social row, then the meta, and the actions last — they are the reason the
   page exists, and the bar has picked them up by the time they go. */
@keyframes eb-hero-shed-1 { 0%, 4% { opacity: 1; translate: 0 0; } 30%, 100% { opacity: 0; translate: 0 -8px; } }
@keyframes eb-hero-shed-2 { 0%, 10% { opacity: 1; translate: 0 0; } 40%, 100% { opacity: 0; translate: 0 -7px; } }
@keyframes eb-hero-shed-3 { 0%, 15% { opacity: 1; translate: 0 0; } 50%, 100% { opacity: 0; translate: 0 -6px; } }
@keyframes eb-hero-shed-4 { 0%, 20% { opacity: 1; translate: 0 0; } 56%, 100% { opacity: 0; translate: 0 -6px; } }

/* The bar arrives while the name is still fading, not after it has gone. */
@keyframes eb-hero-bar { 0%, 66% { transform: translateY(-112%); } 100% { transform: translateY(0); } }
@keyframes eb-hero-bar-in { 0%, 72% { opacity: 0; scale: 0.94; } 100% { opacity: 1; scale: 1; } }

/* The stock rises from ALREADY VISIBLE, never from nothing. A 42-animal storefront
   is scanned, not read, and starting at opacity 0 means a buyer scrolling at speed
   passes a wall of empty boxes — the exact opposite of "the animal is the hero".
   0.55 reads as coming up into the light; 0 reads as a page that hasn't loaded. */
@keyframes eb-stock-rise { from { opacity: 0.55; translate: 0 18px; } to { opacity: 1; translate: 0 0; } }

@supports (animation-timeline: scroll()) {
    @media (prefers-reduced-motion: no-preference) {
        /* The shared clock. Duration is deliberately unset: on a progress-based
           timeline an unset duration fills the range, and the `animation`
           shorthand would silently reset it to 0s and kill every rule below. */
        .eb-store-hero-montage,
        .eb-store-hero-montage img,
        .eb-store-hero-dim,
        .eb-store-kicker,
        .eb-store-social,
        .eb-store-meta,
        .eb-store-hero-actions,
        .eb-store-hero-id,
        .eb-store-sticky,
        .eb-store-sticky-in {
            animation-timeline: scroll(root block);
            animation-range: 0px var(--eb-hero-band);
            animation-fill-mode: both;
            /* Linear is correct for a scroll-linked animation: the visitor's
               scroll IS the easing, and a curve on top of it reads as lag. */
            animation-timing-function: linear;
        }

        .eb-store-hero-montage { animation-name: eb-hero-push; }
        /* Both names share the one timeline value above — animation-* lists cycle. */
        .eb-store-hero-montage--deep { animation-name: eb-hero-push, eb-hero-recede; }

        /* Selecting on the shared class drives the matching panel in both layers,
           so they stay in register through the cross-fade. */
        .eb-store-hero-montage img:nth-child(1) { animation-name: eb-hero-drift-1; }
        .eb-store-hero-montage img:nth-child(2) { animation-name: eb-hero-drift-2; }
        .eb-store-hero-montage img:nth-child(3) { animation-name: eb-hero-drift-3; }
        .eb-store-hero-montage img:nth-child(4) { animation-name: eb-hero-drift-4; }
        .eb-store-hero-montage img:nth-child(5) { animation-name: eb-hero-drift-5; }

        .eb-store-hero-dim { animation-name: eb-hero-dim; }

        .eb-store-hero-id {
            animation-name: eb-hero-condense;
            transform-origin: top left;
        }

        .eb-store-kicker { animation-name: eb-hero-shed-1; }
        .eb-store-social { animation-name: eb-hero-shed-2; }
        .eb-store-meta { animation-name: eb-hero-shed-3; }
        .eb-store-hero-actions { animation-name: eb-hero-shed-4; }

        .eb-store-sticky {
            animation-name: eb-hero-bar;
            /* The scroll clock owns the transform now; the class-toggle
               transition would fight it frame for frame. */
            transition: none;
        }

        .eb-store-sticky-in {
            animation-name: eb-hero-bar-in;
            transform-origin: left center;
        }

        /* The answer to the hero: as the room goes dark, the stock comes up into
           it. `translate` rather than `transform` so .eb-card:hover's lift still
           composes — with `transform` here the animation's forwards fill would
           hold translateY(0) and the signature card lift would stop working. */
        #eb-store-grid .eb-card {
            animation-name: eb-stock-rise;
            animation-timeline: view();
            animation-range: entry 5% entry 48%;
            animation-fill-mode: both;
            animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
        }

        /* The phone hero is 320px and its bar sits 8px higher, so the same
           choreography has to finish sooner or the handoff lands off-screen. */
        @media (max-width: 720px) {
            :root:root { --eb-hero-band: 230px; }
        }
    }
}

/* ==========================================================================
   News — card grid (home strip + /news), and article page (/news/{slug})
   ========================================================================== */

.eb-news-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 18px;
}

@media (max-width: 900px) {
    .eb-news-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 560px) {
    .eb-news-grid { grid-template-columns: 1fr; }
}

.eb-news-card-link {
    display: block;
    height: 100%;
}

.eb-news-card {
    background-color: var(--eb-surface);
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    overflow: hidden;
    height: 100%;
    display: flex;
    flex-direction: column;
    transition: box-shadow 180ms ease, transform 180ms ease, border-color 180ms ease;
}

.eb-news-card:hover {
    border-color: var(--eb-orange);
    box-shadow: var(--eb-shadow);
    transform: translateY(-3px);
}

@media (prefers-reduced-motion: reduce) {
    .eb-news-card { transition: none; }
    .eb-news-card:hover { transform: none; }
}

.eb-news-card-media {
    aspect-ratio: 16 / 9;
    background: var(--eb-surface-2);
    overflow: hidden;
}

.eb-news-card-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.eb-news-card-body {
    padding: 16px 18px 18px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    flex: 1;
}

.eb-news-card-date {
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-orange-ink);
}

.eb-news-card-title {
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 1.02rem;
    line-height: 1.3;
    color: var(--eb-text);
    margin: 0;
}

.eb-news-card-excerpt {
    font-size: 0.86rem;
    line-height: 1.5;
    color: var(--eb-body);
    margin: 0;
    /* Clamp to three lines so mixed-length excerpts keep the grid tidy. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* ---- Home strip: text-led card variant ---------------------------------- */
/* When an article has no cover, the media-mandatory card became a large empty
   "RM" box. The text variant drops the media band entirely and leads with type
   under a faint heat wash, so a cover-less strip reads as deliberate. */

.eb-news-card-title { transition: color 160ms ease; }
.eb-news-card:hover .eb-news-card-title { color: var(--eb-orange-ink); }

.eb-news-card--text {
    background:
        linear-gradient(180deg, rgba(232, 99, 10, 0.05) 0%, rgba(232, 99, 10, 0) 42%),
        var(--eb-surface);
}

.eb-news-card--text .eb-news-card-body { padding-top: 22px; gap: 10px; }
.eb-news-card--text .eb-news-card-title { font-size: 1.1rem; }
.eb-news-card--text .eb-news-card-excerpt { -webkit-line-clamp: 4; }

.eb-news-card-more {
    margin-top: auto;
    padding-top: 14px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.84rem;
    font-weight: 700;
    color: var(--eb-orange-ink);
}

.eb-news-card-more-arrow { transition: transform 160ms ease; }
.eb-news-card:hover .eb-news-card-more-arrow { transform: translateX(3px); }

@media (prefers-reduced-motion: reduce) {
    .eb-news-card-title, .eb-news-card-more-arrow { transition: none; }
    .eb-news-card:hover .eb-news-card-more-arrow { transform: none; }
}

/* ==========================================================================
   News index — "The Journal": a featured lead + a date-railed archive
   ========================================================================== */
/* Article data is thin and cover images are usually absent, so hierarchy is
   carried by size and rhythm, not by a media grid. The featured lead adapts to
   a cover photo when one exists and a heat-lit typographic panel when it does
   not; the archive below is a chronological, date-railed index that stays tidy
   at any count and never shows an empty image box. */

.eb-news-head { margin-bottom: 4px; }

/* Shared meta line: orange publish date + a quiet reading-time estimate. */
.eb-news-meta {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 14px;
}

.eb-news-date {
    font-size: 0.74rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-orange-ink);
}

.eb-news-readtime {
    font-size: 0.74rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-muted);
}

.eb-news-readtime::before {
    content: "·";
    margin-right: 8px;
    color: var(--eb-border-2);
}

/* ---- Featured lead ------------------------------------------------------- */

.eb-news-hero {
    position: relative;
    display: grid;
    overflow: hidden;
    border: 1px solid var(--eb-border);
    border-radius: 18px;
    text-decoration: none;
    transition: border-color 200ms ease, box-shadow 200ms ease;
}

/* Cover present: text | photo. Photo absent: a single heat-lit panel. */
.eb-news-hero.has-cover {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
    min-height: 320px;
    background: var(--eb-surface);
}

.eb-news-hero.is-textual {
    display: block;
    background:
        radial-gradient(85% 130% at 90% -15%, rgba(232, 99, 10, 0.20) 0%, rgba(232, 99, 10, 0) 60%),
        linear-gradient(160deg, #1C1C22 0%, #121216 60%, #0E0E11 100%);
}

.eb-news-hero:hover {
    border-color: var(--eb-tint-border);
    box-shadow: var(--eb-shadow);
}

.eb-news-hero-body {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    padding: clamp(28px, 4vw, 48px);
    position: relative;
    z-index: 1;
}

.eb-news-hero.is-textual .eb-news-hero-body { max-width: 66ch; }

.eb-news-tag {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--eb-orange-ink);
    margin-bottom: 16px;
}

.eb-news-tag-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--eb-orange);
    box-shadow: 0 0 8px rgba(232, 99, 10, 0.7);
}

.eb-news-hero-title {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: clamp(1.8rem, 3.2vw, 2.6rem);
    line-height: 1.08;
    letter-spacing: -0.03em;
    color: var(--eb-text);
    margin: 0 0 14px;
    text-wrap: balance;
    transition: color 180ms ease;
}

.eb-news-hero-excerpt {
    font-size: clamp(1rem, 1.4vw, 1.1rem);
    line-height: 1.6;
    color: var(--eb-body);
    margin: 0 0 24px;
    max-width: 58ch;
    text-wrap: pretty;
}

.eb-news-more {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: 0.92rem;
    font-weight: 700;
    color: var(--eb-orange-ink);
}

.eb-news-more-arrow { transition: transform 180ms ease; }

/* Underline that draws in from the left on hover */
.eb-news-more::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -4px;
    height: 1.5px;
    background: currentColor;
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 260ms cubic-bezier(0.2, 1, 0.3, 1);
}

.eb-news-hero-cover {
    position: relative;
    overflow: hidden;
    background: var(--eb-surface-2);
}

.eb-news-hero-cover img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 420ms cubic-bezier(0.2, 1, 0.3, 1);
}

.eb-news-hero:hover .eb-news-hero-title { color: var(--eb-orange-ink); }
.eb-news-hero:hover .eb-news-more::after { transform: scaleX(1); }
.eb-news-hero:hover .eb-news-more-arrow { transform: translateX(3px); }
.eb-news-hero.has-cover:hover .eb-news-hero-cover img { transform: scale(1.04); }

/* ---- Archive: date-railed index ----------------------------------------- */

.eb-news-list {
    list-style: none;
    margin: 40px 0 0;
    padding: 0;
    border-top: 1px solid var(--eb-border);
}

.eb-news-item {
    display: grid;
    grid-template-columns: 100px minmax(0, 1fr) auto;
    align-items: center;
    gap: 28px;
    padding: 22px 4px;
    border-bottom: 1px solid var(--eb-border);
    text-decoration: none;
}

.eb-news-item-rail {
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.eb-news-item-day {
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 0.92rem;
    letter-spacing: 0.01em;
    color: var(--eb-orange-ink);
    transition: color 160ms ease;
}

.eb-news-item-year {
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    color: var(--eb-muted);
}

.eb-news-item-body { min-width: 0; }

.eb-news-item-title {
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 1.18rem;
    line-height: 1.3;
    color: var(--eb-text);
    margin: 0 0 5px;
    text-wrap: pretty;
    transition: color 160ms ease;
}

.eb-news-item-excerpt {
    font-size: 0.9rem;
    line-height: 1.5;
    color: var(--eb-muted);
    margin: 0;
    max-width: 70ch;
    display: -webkit-box;
    -webkit-line-clamp: 1;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.eb-news-item-aside {
    display: flex;
    align-items: center;
    gap: 16px;
    justify-self: end;
}

.eb-news-item-thumb {
    width: 88px;
    height: 60px;
    border-radius: 10px;
    overflow: hidden;
    background: var(--eb-surface-2);
    flex: none;
    display: block;
}

.eb-news-item-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform 320ms cubic-bezier(0.2, 1, 0.3, 1);
}

.eb-news-item-readtime {
    margin-top: 4px;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-muted);
    white-space: nowrap;
}

.eb-news-item-arrow {
    color: var(--eb-muted);
    font-size: 1.15rem;
    transition: color 160ms ease, transform 160ms ease;
}

.eb-news-item:hover .eb-news-item-title { color: var(--eb-orange-ink); }
.eb-news-item:hover .eb-news-item-day { color: var(--eb-orange-hover); }
.eb-news-item:hover .eb-news-item-arrow { color: var(--eb-orange-ink); transform: translateX(3px); }
.eb-news-item:hover .eb-news-item-thumb img { transform: scale(1.05); }

/* ---- Load more ----------------------------------------------------------- */

.eb-news-loadmore-wrap {
    display: flex;
    justify-content: center;
    margin-top: 44px;
}

.eb-news-loadmore {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 12px 26px;
    border-radius: 999px;
    border: 1px solid var(--eb-border-2);
    background: transparent;
    color: var(--eb-text);
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 0.9rem;
    cursor: pointer;
    transition: border-color 160ms ease, color 160ms ease, background-color 160ms ease;
}

.eb-news-loadmore:hover:not(:disabled) {
    border-color: var(--eb-orange);
    color: var(--eb-orange-on-tint);
    background-color: var(--eb-tint-bg);
}

.eb-news-loadmore:disabled { opacity: 0.6; cursor: default; }

.eb-news-loadmore-spin {
    width: 15px;
    height: 15px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.22);
    border-top-color: var(--eb-orange);
    animation: eb-spin 0.7s linear infinite;
    display: inline-block;
}

/* ---- Empty state --------------------------------------------------------- */

.eb-news-empty-glyph {
    width: 64px;
    height: 64px;
    border-radius: 16px;
    display: grid;
    place-items: center;
    margin-bottom: 18px;
    border: 1px solid var(--eb-border);
    background:
        radial-gradient(circle at 50% 25%, rgba(232, 99, 10, 0.20) 0%, rgba(232, 99, 10, 0) 70%),
        var(--eb-surface);
}

.eb-news-empty-glyph span {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 1.1rem;
    letter-spacing: 0.04em;
    color: var(--eb-orange-ink);
}

/* ---- Loading skeleton ---------------------------------------------------- */

.eb-news-skeleton-hero {
    border: 1px solid var(--eb-border);
    border-radius: 18px;
    background: var(--eb-surface);
    min-height: 300px;
    display: flex;
    align-items: center;
    padding: clamp(28px, 4vw, 48px);
    margin-bottom: 40px;
}

.eb-news-skeleton-lines {
    display: flex;
    flex-direction: column;
    gap: 14px;
    width: 100%;
    max-width: 60ch;
}

.eb-news-skeleton-rows {
    list-style: none;
    margin: 0;
    padding: 0;
    border-top: 1px solid var(--eb-border);
}

.eb-news-skeleton-rows li {
    display: grid;
    grid-template-columns: 100px minmax(0, 1fr);
    gap: 28px;
    align-items: center;
    padding: 24px 4px;
    border-bottom: 1px solid var(--eb-border);
}

.eb-sk {
    display: block;
    height: 14px;
    border-radius: 8px;
    background: linear-gradient(90deg, #26262C 25%, #32323A 50%, #26262C 75%);
    background-size: 200% 100%;
    animation: eb-shimmer 1.4s ease-in-out infinite;
}

.eb-sk-meta { width: 190px; height: 12px; }
.eb-sk-title { width: 82%; height: 30px; border-radius: 10px; }
.eb-sk-title--short { width: 56%; }
.eb-sk-text { width: 92%; }
.eb-sk-text--short { width: 70%; }
.eb-sk-link { width: 130px; height: 16px; margin-top: 8px; }
.eb-sk-rail { width: 62px; height: 30px; }
.eb-sk-row { width: 72%; height: 22px; }

@keyframes eb-shimmer {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

@keyframes eb-spin {
    to { transform: rotate(360deg); }
}

/* ---- Accessibility utility ----------------------------------------------- */

.eb-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ---- Keyboard focus ------------------------------------------------------ */

.eb-news-hero:focus-visible,
.eb-news-item:focus-visible,
.eb-news-loadmore:focus-visible {
    outline: 2px solid var(--eb-orange);
    outline-offset: 3px;
}

.eb-news-hero:focus-visible { border-radius: 18px; }

/* ---- Motion & responsive ------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    .eb-news-hero, .eb-news-hero-title, .eb-news-more::after, .eb-news-more-arrow,
    .eb-news-hero-cover img, .eb-news-item-title, .eb-news-item-day,
    .eb-news-item-arrow, .eb-news-item-thumb img, .eb-news-loadmore { transition: none; }
    .eb-news-hero:hover .eb-news-more::after { transform: none; }
    .eb-news-hero:hover .eb-news-more-arrow,
    .eb-news-item:hover .eb-news-item-arrow { transform: none; }
    .eb-news-hero.has-cover:hover .eb-news-hero-cover img,
    .eb-news-item:hover .eb-news-item-thumb img { transform: none; }
    .eb-sk { animation: none; }
    .eb-news-loadmore-spin { animation-duration: 1.6s; }
}

@media (max-width: 760px) {
    .eb-news-hero.has-cover {
        grid-template-columns: 1fr;
        min-height: 0;
    }
    .eb-news-hero-cover {
        order: -1;
        aspect-ratio: 16 / 9;
    }
    .eb-news-hero-cover img { position: static; }
}

@media (max-width: 620px) {
    .eb-news-item {
        grid-template-columns: 1fr;
        gap: 8px;
        align-items: start;
    }
    .eb-news-item-rail {
        flex-direction: row;
        align-items: baseline;
        gap: 8px;
    }
    .eb-news-item-rail .eb-news-item-readtime { display: none; }
    .eb-news-item-aside { display: none; }
    .eb-news-skeleton-rows li { grid-template-columns: 1fr; gap: 10px; }
    .eb-sk-rail { width: 120px; height: 14px; }
}

/* ---- Article page -------------------------------------------------------- */

.eb-article {
    max-width: 760px;
    margin: 0 auto;
}

.eb-article-back {
    display: inline-block;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--eb-orange-ink);
    margin-bottom: 24px;
}

.eb-article-back:hover {
    color: var(--eb-orange-hover);
}

.eb-article-date {
    display: block;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-orange-ink);
    margin-bottom: 10px;
}

.eb-article-title {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: clamp(1.7rem, 4.5vw, 2.1rem);
    line-height: 1.15;
    letter-spacing: -0.02em;
    color: var(--eb-text);
    margin: 0 0 16px;
}

.eb-article-lead {
    font-size: clamp(1.05rem, 2.5vw, 1.15rem);
    line-height: 1.55;
    color: var(--eb-body);
    margin: 0 0 28px;
}

.eb-article-cover {
    width: 100%;
    border-radius: 16px;
    border: 1px solid var(--eb-border);
    margin-bottom: 32px;
    display: block;
}

/* ---- Prose (rendered article body) --------------------------------------- */

/* .eb-prose is defined once, with the other long-form type — see "One prose system" ~line 2170. */

/* ==========================================================================
   Articles — "Keeping guides": a calm, evergreen reading-room library
   ========================================================================== */
/* Deliberately quieter than News: no featured lead, no orange date rail, no
   heat-lit hero. Guides are timeless, so the publish date is dropped and a
   reading-time marker is the only meta — every row is equal, a shelf not a
   feed. All colour routes through the shared --eb-* tokens, so light mode is a
   no-op here except the load-more spinner ring. */

.eb-guides-head { margin-bottom: 26px; }

/* ---- Reading-room list --------------------------------------------------- */

.eb-guides-list {
    list-style: none;
    margin: 6px 0 0;
    padding: 0;
    border-top: 1px solid var(--eb-border);
}

.eb-guides-list > li { border-bottom: 1px solid var(--eb-border); }

.eb-guide-row {
    display: grid;
    grid-template-columns: 152px minmax(0, 1fr) 18px;
    align-items: center;
    gap: 26px;
    padding: 24px 16px;
    text-decoration: none;
    border-radius: 14px;
    transition: background-color 220ms ease;
}

.eb-guide-row:hover { background-color: var(--eb-surface); }

/* Cover plate — a calm reference image, left-led. Muted at rest, gently lifts on hover. */
.eb-guide-row-thumb {
    position: relative;
    width: 152px;
    aspect-ratio: 3 / 2;
    border-radius: 12px;
    overflow: hidden;
    background: var(--eb-surface-2);
    border: 1px solid var(--eb-border);
    flex: none;
}

.eb-guide-row-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    filter: saturate(0.96);
    transition: transform 480ms cubic-bezier(0.2, 1, 0.3, 1), filter 300ms ease;
}

.eb-guide-row-thumb.is-empty { display: grid; place-items: center; }

.eb-guide-row-mono {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 1.05rem;
    letter-spacing: 0.06em;
    color: var(--eb-muted);
}

.eb-guide-row-body { min-width: 0; }

.eb-guide-row-title {
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 1.18rem;
    line-height: 1.28;
    letter-spacing: -0.01em;
    color: var(--eb-text);
    margin: 0 0 7px;
    text-wrap: pretty;
    transition: color 180ms ease;
}

.eb-guide-row-excerpt {
    font-size: 0.92rem;
    line-height: 1.55;
    color: var(--eb-body);
    margin: 0 0 12px;
    max-width: 64ch;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Reading-time marker — the only meta. Quiet muted text, one small orange dot. */
.eb-guide-row-meta {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-muted);
}

.eb-guide-row-dot {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--eb-orange);
    flex: none;
}

.eb-guide-row-go {
    justify-self: end;
    color: var(--eb-muted);
    font-size: 1.1rem;
    opacity: 0.5;
    transition: color 180ms ease, transform 220ms cubic-bezier(0.2, 1, 0.3, 1), opacity 180ms ease;
}

.eb-guide-row:hover .eb-guide-row-title { color: var(--eb-orange-ink); }
.eb-guide-row:hover .eb-guide-row-thumb img { transform: scale(1.03); filter: saturate(1.02); }
.eb-guide-row:hover .eb-guide-row-go { color: var(--eb-orange-ink); opacity: 1; transform: translateX(3px); }

/* ---- Load more ----------------------------------------------------------- */

.eb-guides-loadmore-wrap {
    display: flex;
    justify-content: center;
    margin-top: 40px;
}

.eb-guides-loadmore {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 12px 26px;
    border-radius: 999px;
    border: 1px solid var(--eb-border-2);
    background: transparent;
    color: var(--eb-text);
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 0.9rem;
    cursor: pointer;
    transition: border-color 160ms ease, color 160ms ease, background-color 160ms ease;
}

.eb-guides-loadmore:hover:not(:disabled) {
    border-color: var(--eb-orange);
    color: var(--eb-orange-on-tint);
    background-color: var(--eb-tint-bg);
}

.eb-guides-loadmore:disabled { opacity: 0.6; cursor: default; }

.eb-guides-loadmore-spin {
    width: 15px;
    height: 15px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.22);
    border-top-color: var(--eb-orange);
    animation: eb-spin 0.7s linear infinite;
    display: inline-block;
}

/* ---- Empty state --------------------------------------------------------- */

.eb-guides-empty-glyph {
    width: 64px;
    height: 64px;
    border-radius: 16px;
    display: grid;
    place-items: center;
    margin-bottom: 18px;
    border: 1px solid var(--eb-border);
    background: var(--eb-surface);
    color: var(--eb-muted);
}

/* ---- Loading skeleton ---------------------------------------------------- */

.eb-guides-skeleton {
    list-style: none;
    margin: 6px 0 0;
    padding: 0;
    border-top: 1px solid var(--eb-border);
}

.eb-guides-skeleton > li {
    display: grid;
    grid-template-columns: 152px minmax(0, 1fr);
    gap: 26px;
    align-items: center;
    padding: 24px 16px;
    border-bottom: 1px solid var(--eb-border);
}

.eb-guides-sk-thumb {
    width: 152px;
    aspect-ratio: 3 / 2;
    height: auto;
    border-radius: 12px;
}

.eb-guides-sk-lines {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.eb-guides-sk-meta { width: 90px; height: 12px; }

/* ---- Detail: the guide masthead + reading column ------------------------- */
/* Distinct .eb-guide-* classes so the News .eb-article-* styles are untouched.
   A narrower column and larger measure than News make the read calmer; the
   header leads with a quiet "Keeping guide" label, not an orange date. */

.eb-guide {
    max-width: 720px;
    margin: 0 auto;
}

.eb-guide-back {
    display: inline-block;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--eb-orange-ink);
    margin-bottom: 26px;
}

.eb-guide-back:hover { color: var(--eb-orange-hover); }

.eb-guide-masthead { margin-bottom: 30px; }

.eb-guide-label {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--eb-muted);
    margin-bottom: 14px;
}

.eb-guide-label-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--eb-orange);
    box-shadow: 0 0 8px rgba(232, 99, 10, 0.6);
    flex: none;
}

.eb-guide-label-readtime::before {
    content: "·";
    margin: 0 9px 0 3px;
    color: var(--eb-border-2);
}

.eb-guide-title {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: clamp(1.9rem, 3vw, 2.4rem);
    line-height: 1.16;
    letter-spacing: -0.03em;
    color: var(--eb-text);
    margin: 0 0 18px;
    text-wrap: balance;
}

.eb-guide-lead {
    font-size: 1.16rem;
    line-height: 1.6;
    color: var(--eb-body);
    margin: 0;
    text-wrap: pretty;
}

.eb-guide-cover {
    width: 100%;
    border-radius: 16px;
    border: 1px solid var(--eb-border);
    margin-bottom: 34px;
    display: block;
}

/* Reading-room prose: a touch larger and airier than the shared .eb-prose base,
   with more room around subheads so long guides breathe. */
.eb-guide-body.eb-prose {
    font-size: 1.05rem;
    line-height: 1.78;
}

.eb-guide-body.eb-prose h2 { margin-top: 44px; }
.eb-guide-body.eb-prose h3 { margin-top: 34px; }

/* Headings carry an id (injected server-side by ArticleReadingAids) so the contents rail can target
   them and a section can be linked directly. Scroll-margin stops a jump landing the heading flush
   under the fixed app bar. */
.eb-guide-body.eb-prose :is(h2, h3) { scroll-margin-top: 88px; }

/* ---- Detail: the guide callout ------------------------------------------- */
/* A blockquote in a keeping guide is almost never a quotation — it is the author stopping to say
   "this part matters", which in this product is usually welfare. Giving it a surface and an orange
   rail makes the one passage a reader must not skim look different from the passages they may.
   Flat at rest, per the system: the rail carries it, not a shadow. */

.eb-guide-body.eb-prose blockquote {
    margin: 34px 0;
    padding: 20px 24px;
    border: 1px solid var(--eb-border);
    border-left: 3px solid var(--eb-orange);
    border-radius: var(--eb-radius-card, 14px);
    background: var(--eb-surface);
    color: var(--eb-text);
    font-style: normal;
}

.eb-guide-body.eb-prose blockquote > *:last-child { margin-bottom: 0; }

/* ---- Detail: morph references inside prose ------------------------------- */
/* A morph name that links to its own page is a *reference*, not a navigation link, and there may be
   a dozen in a guide. Dotted rather than solid keeps the One Orange voice while stopping the
   paragraph from reading as a link farm. */

.eb-prose a.eb-prose-morph {
    text-decoration-style: dotted;
    text-underline-offset: 3px;
}

.eb-prose a.eb-prose-morph:hover { text-decoration-style: solid; }

/* ---- Detail: the reading progress line ----------------------------------- */
/* Scroll-driven, so no JavaScript and no Blazor circuit traffic on every scroll frame. Browsers
   without scroll-linked animation simply never show it, which is why it is decoration-only and the
   contents rail carries the real "where am I" job. */

.eb-guide-progress {
    position: fixed;
    inset: 0 0 auto 0;
    height: 2px;
    background: var(--eb-orange);
    transform: scaleX(0);
    transform-origin: 0 50%;
    z-index: 1200;
    animation: eb-read-progress linear both;
    animation-timeline: scroll(root block);
}

@keyframes eb-read-progress {
    to { transform: scaleX(1); }
}

@supports not (animation-timeline: scroll(root block)) {
    .eb-guide-progress { display: none; }
}

/* ---- Detail: the contents rail ------------------------------------------- */
/* Below the breakpoint this is a closed <details> above the article; above it, the rail moves into
   the left gutter, which was previously dead space. The three-column grid keeps the text column
   itself centred — pushing the prose off-centre to make room would trade one reading problem for
   another. */

.eb-guide-toc {
    margin: 0 0 32px;
    border: 1px solid var(--eb-border);
    border-radius: var(--eb-radius-card, 14px);
    padding: 14px 18px;
    background: var(--eb-surface);
}

.eb-guide-toc-summary {
    cursor: pointer;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-muted);
}

.eb-guide-toc-list {
    list-style: none;
    margin: 14px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 9px;
}

.eb-guide-toc-list a {
    color: var(--eb-muted);
    text-decoration: none;
    font-size: 0.86rem;
    line-height: 1.4;
    border-left: 2px solid transparent;
    padding-left: 12px;
    display: block;
    transition: color 120ms ease, border-color 120ms ease;
}

.eb-guide-toc-list a:hover {
    color: var(--eb-text);
    border-left-color: var(--eb-orange);
}

/* A level-3 entry is a step inside a section, not a sibling of it. */
.eb-guide-toc-list li[data-level="3"] a {
    padding-left: 24px;
    font-size: 0.8rem;
}

/* 1280px, not 1180: the rail only earns its place once the gutter beside a 48em column is genuinely
   wide enough for it. Below this the contents list renders as an ordinary block above the article,
   which is the right answer on a laptop anyway. */
@media (min-width: 1280px) {
    .eb-guide {
        display: grid;
        /* Must track .eb-prose's max-width, or the centre column and the prose inside it disagree
           and the rail's gap stops being what it looks like. */
        grid-template-columns: minmax(0, 1fr) minmax(0, 48em) minmax(0, 1fr);
        align-items: start;
    }

    /* Everything the article already had stays in the centre column; only the rail claims a gutter. */
    .eb-guide > *:not(.eb-guide-toc) { grid-column: 2; }

    .eb-guide-toc {
        grid-column: 1;
        /* `span 99`, not `1 / -1`. The grid has no grid-template-rows, so its explicit row count is
           zero and `-1` resolves back to line 1 — the rail then occupies row 1 alone, which grows
           that row to the rail's full height and drops a screen of empty space between the back link
           and the masthead. Spanning implicit rows keeps it beside the article, not inside the first
           row of it. */
        grid-row: 1 / span 99;
        align-self: start;
        justify-self: end;
        width: 170px;
        margin: 0 32px 0 0;
        position: sticky;
        top: 96px;
        border: 0;
        border-left: 1px solid var(--eb-border);
        border-radius: 0;
        padding: 2px 0 2px 0;
        background: transparent;
    }

    /* Open and non-collapsible once it is a rail — a disclosure control in a permanent sidebar is a
       click that can only ever hide something useful. */
    .eb-guide-toc-summary {
        list-style: none;
        cursor: default;
        padding-left: 12px;
    }

    .eb-guide-toc-summary::-webkit-details-marker { display: none; }
}

/* ---- Detail: end-of-guide exits ----------------------------------------- */
/* Flat at rest, hairline only — this is a way out, not a second CTA competing
   with the marketplace. */

.eb-guide-next {
    margin-top: 52px;
    padding-top: 26px;
    border-top: 1px solid var(--eb-border);
}

.eb-guide-next-label {
    display: block;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--eb-muted);
    margin-bottom: 16px;
}

.eb-guide-next-links {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
}

.eb-guide-next-link {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 20px;
    border: 1px solid var(--eb-border-2);
    border-radius: 999px;
    font-size: 0.92rem;
    font-weight: 600;
    color: var(--eb-text);
    text-decoration: none;
}

.eb-guide-next-link:hover,
.eb-guide-next-link:focus-visible {
    border-color: var(--eb-orange);
    color: var(--eb-text);
}

.eb-guide-next-link--primary {
    background: var(--eb-orange);
    border-color: var(--eb-orange);
    color: var(--eb-on-orange);
}

.eb-guide-next-link--primary:hover,
.eb-guide-next-link--primary:focus-visible {
    background: var(--eb-orange-hover);
    border-color: var(--eb-orange-hover);
    color: var(--eb-on-orange);
}

/* ---- Home strip: compact, flat guide cards ------------------------------- */
/* Quieter than NewsCard — flat at rest (no lift/shadow), a hairline that warms
   on hover. Ties to the section via the reading-time dot. */

.eb-guides-cards {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 18px;
}

@media (max-width: 900px) { .eb-guides-cards { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .eb-guides-cards { grid-template-columns: 1fr; } }

.eb-guide-card-link { display: block; height: 100%; }

.eb-guide-card {
    height: 100%;
    display: grid;
    grid-template-columns: 92px minmax(0, 1fr);
    gap: 16px;
    align-items: center;
    padding: 14px;
    background: var(--eb-surface);
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    transition: border-color 180ms ease;
}

.eb-guide-card:hover { border-color: var(--eb-tint-border); }

.eb-guide-card-media {
    width: 92px;
    aspect-ratio: 1 / 1;
    border-radius: 10px;
    overflow: hidden;
    background: var(--eb-surface-2);
    flex: none;
}

.eb-guide-card-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform 420ms cubic-bezier(0.2, 1, 0.3, 1);
}

.eb-guide-card-media.is-empty { display: grid; place-items: center; }

.eb-guide-card-mono {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 0.9rem;
    letter-spacing: 0.06em;
    color: var(--eb-muted);
}

.eb-guide-card-body { min-width: 0; }

.eb-guide-card-title {
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 0.98rem;
    line-height: 1.32;
    color: var(--eb-text);
    margin: 0 0 8px;
    text-wrap: pretty;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    transition: color 160ms ease;
}

.eb-guide-card-meta {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-muted);
}

.eb-guide-card:hover .eb-guide-card-title { color: var(--eb-orange-ink); }
.eb-guide-card:hover .eb-guide-card-media img { transform: scale(1.04); }

/* ---- Keyboard focus ------------------------------------------------------ */

.eb-guide-row:focus-visible,
.eb-guides-loadmore:focus-visible,
.eb-guide-card-link:focus-visible {
    outline: 2px solid var(--eb-orange);
    outline-offset: 3px;
    border-radius: 14px;
}

/* ---- Light-theme: the only non-token-driven override (spinner ring) ------ */

[data-theme="light"] .eb-guides-loadmore-spin {
    border-color: rgba(0, 0, 0, 0.16);
    border-top-color: var(--eb-orange);
}

/* ---- Motion & responsive ------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    .eb-guide-row, .eb-guide-row-title, .eb-guide-row-thumb img, .eb-guide-row-go,
    .eb-guides-loadmore, .eb-guide-card, .eb-guide-card-title, .eb-guide-card-media img { transition: none; }
    .eb-guide-row:hover .eb-guide-row-thumb img,
    .eb-guide-row:hover .eb-guide-row-go,
    .eb-guide-card:hover .eb-guide-card-media img { transform: none; }
    .eb-guides-loadmore-spin { animation-duration: 1.6s; }
}

@media (max-width: 620px) {
    .eb-guide-row {
        grid-template-columns: 96px minmax(0, 1fr);
        gap: 16px;
        padding: 18px 10px;
    }
    .eb-guide-row-thumb { width: 96px; }
    .eb-guide-row-go { display: none; }
    .eb-guides-skeleton > li { grid-template-columns: 96px minmax(0, 1fr); gap: 16px; }
    .eb-guides-sk-thumb { width: 96px; }
    .eb-guide-card { grid-template-columns: 72px minmax(0, 1fr); }
    .eb-guide-card-media { width: 72px; }
}

/* ---- Listing Q&A ("Questions for the breeder") ---------------------------- */
/* Quiet, prose-like section: no cards, hairline-separated pairs, ~72ch measure.
   Orange stays on the send action and the store's answering voice only. */

.eb-qa-body {
    max-width: 72ch;
}

.eb-qa-count {
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--eb-muted);
    margin-left: 10px;
    vertical-align: 2px;
}

.eb-qa-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.eb-qa-pair {
    padding: 22px 0;
}

.eb-qa-pair + .eb-qa-pair {
    border-top: 1px solid var(--eb-border);
}

.eb-qa-pair:first-child {
    padding-top: 4px;
}

.eb-qa-q {
    margin: 0 0 4px;
    color: var(--eb-text);
    font-weight: 600;
    font-size: 1rem;
    line-height: 1.5;
    text-wrap: pretty;
}

.eb-qa-meta {
    font-size: 0.78rem;
    color: var(--eb-muted);
}

.eb-qa-answer {
    display: flex;
    gap: 12px;
    margin-top: 14px;
    padding-left: 4px;
}

.eb-qa-avatar {
    width: 34px;
    height: 34px;
    min-width: 34px;
    font-size: 0.9rem;
}

.eb-qa-answer-head {
    display: flex;
    align-items: baseline;
    gap: 6px;
    margin-bottom: 2px;
}

.eb-qa-store {
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 0.9rem;
    color: var(--eb-text);
}

.eb-qa-a {
    margin: 0;
    color: var(--eb-body);
    font-size: 0.95rem;
    line-height: 1.7;
    white-space: pre-line;
    text-wrap: pretty;
}

/* --- The asker's own pending questions (visible only to them) --- */

.eb-qa-mine {
    margin-top: 26px;
    padding: 14px 16px;
    background: var(--eb-tint-bg);
    border: 1px solid var(--eb-tint-border);
    border-radius: 12px;
}

.eb-qa-mine-title {
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--eb-orange-ink);
    margin-bottom: 8px;
}

.eb-qa-mine-row {
    display: flex;
    align-items: center;
    gap: 12px;
}

.eb-qa-mine-row + .eb-qa-mine-row {
    margin-top: 10px;
}

.eb-qa-mine-main {
    flex: 1;
    min-width: 0;
}

.eb-qa-mine-q {
    margin: 0;
    color: var(--eb-text);
    font-size: 0.92rem;
    line-height: 1.5;
}

.eb-qa-withdraw {
    flex-shrink: 0;
}

/* --- Compose / preview / sign-in --- */

.eb-qa-compose {
    margin-top: 26px;
}

.eb-qa-compose-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 10px;
    flex-wrap: wrap;
}

.eb-qa-preview {
    padding: 16px 18px;
    background: var(--eb-surface);
    border: 1px solid var(--eb-border);
    border-radius: 12px;
    animation: eb-qa-fade 150ms ease-out;
}

.eb-qa-preview-intro {
    font-size: 0.82rem;
    color: var(--eb-muted);
    margin-bottom: 8px;
}

.eb-qa-preview-text {
    margin: 0 0 6px;
    color: var(--eb-text);
    font-weight: 600;
    font-size: 1rem;
    line-height: 1.55;
}

.eb-qa-empty {
    margin: 0 0 4px;
    color: var(--eb-body);
    font-size: 0.95rem;
}

.eb-qa-footnote {
    font-size: 0.78rem;
    color: var(--eb-muted);
    line-height: 1.5;
    max-width: 46ch;
}

/* Keep the trailing "Open inbox →" link on one line inside the measure-capped footnote. */
.eb-qa-footnote a {
    white-space: nowrap;
}

.eb-qa-signin {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
    margin-top: 22px;
    color: var(--eb-body);
    font-size: 0.95rem;
}

@keyframes eb-qa-fade {
    from { opacity: 0; transform: translateY(2px); }
    to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .eb-qa-preview { animation: none; }
}

/* ---- Question inbox (/my/questions) --------------------------------------- */

.eb-qi-list {
    max-width: 860px;
}

.eb-qi-row {
    display: flex;
    gap: 16px;
    padding: 20px 0;
}

.eb-qi-row + .eb-qi-row {
    border-top: 1px solid var(--eb-border);
}

.eb-qi-thumb {
    width: 56px;
    height: 56px;
    min-width: 56px;
    border-radius: 10px;
    overflow: hidden;
    background: var(--eb-surface);
    border: 1px solid var(--eb-border);
    display: flex;
    align-items: center;
    justify-content: center;
}

.eb-qi-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.eb-qi-main {
    flex: 1;
    min-width: 0;
}

.eb-qi-meta {
    font-size: 0.78rem;
    color: var(--eb-muted);
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    margin-bottom: 4px;
}

.eb-qi-listing {
    color: var(--eb-body);
    font-weight: 600;
    text-decoration: none;
}

.eb-qi-listing:hover {
    color: var(--eb-orange-ink);
}

.eb-qi-q {
    margin: 0 0 10px;
    color: var(--eb-text);
    font-weight: 600;
    font-size: 1rem;
    line-height: 1.5;
    text-wrap: pretty;
}

.eb-qi-a {
    margin: 0 0 8px;
    color: var(--eb-body);
    font-size: 0.92rem;
    line-height: 1.65;
    white-space: pre-line;
}

.eb-qi-composer {
    margin-top: 4px;
}

.eb-qi-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
    margin-top: 8px;
}

.eb-qi-publish {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

.eb-qi-hint {
    font-size: 0.78rem;
    color: var(--eb-muted);
    max-width: 40ch;
}

@media (max-width: 560px) {
    .eb-qi-thumb { display: none; }
}

/* ---- Bulk add ("add a clutch") ------------------------------------------- */

.eb-bulk-shared .mud-card-header { padding-bottom: 0; }

/* Compact row list — one animal per dense line, hairline-separated inside a single card, so a
   whole clutch scans and fills fast. Fields are label-less (placeholders carry meaning) to keep
   each row short; a row's selected traits wrap onto their own line only when present. */
.eb-bulk-rows {
    border: 1px solid var(--eb-border);
    border-radius: 12px;
    background: var(--eb-surface);
    margin-bottom: 12px;
    overflow: hidden;
}

.eb-bulk-row {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 9px 12px 9px 34px;
}
.eb-bulk-row + .eb-bulk-row { border-top: 1px solid var(--eb-border); }

.eb-bulk-rownum {
    position: absolute;
    left: 12px;
    top: 17px;
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 0.82rem;
    color: var(--eb-muted);
}

.eb-bulk-f-title { flex: 1 1 170px; min-width: 150px; }
.eb-bulk-f-sex { flex: 0 0 116px; }
.eb-bulk-f-wt { flex: 0 0 92px; }
.eb-bulk-f-price { flex: 0 0 112px; }
.eb-bulk-f-traits { flex: 1 1 168px; min-width: 140px; }

.eb-bulk-row-actions {
    display: flex;
    gap: 2px;
    align-items: center;
    margin-left: auto;
}
.eb-bulk-row-dup { color: var(--eb-muted); }
.eb-bulk-row-dup:hover { color: var(--eb-orange-ink); }
.eb-bulk-row-remove { color: var(--eb-muted); }
.eb-bulk-row-remove:hover { color: var(--eb-error); }

/* A row's own traits wrap to a full-width line beneath its fields, only when it has any. */
.eb-bulk-row-chips {
    flex-basis: 100%;
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    align-items: center;
    padding: 0 0 2px 0;
}

.eb-bulk-addrow {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    justify-content: center;
    border: 1px dashed var(--eb-border-2);
    background: none;
    color: var(--eb-body);
    font-family: var(--eb-display);
    font-weight: 600;
    font-size: 0.9rem;
    padding: 12px;
    border-radius: 12px;
    cursor: pointer;
    transition: border-color 140ms ease, color 140ms ease;
}
.eb-bulk-addrow:hover:not(:disabled) { border-color: var(--eb-orange); color: var(--eb-text); }
.eb-bulk-addrow:disabled { opacity: 0.5; cursor: not-allowed; }

/* Sticky create bar — quota on the left, the primary create action on the right. Opaque
   page-bg so rows scroll cleanly beneath it. */
.eb-bulk-actionbar {
    position: sticky;
    bottom: 0;
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
    margin-top: 14px;
    padding: 14px 0;
    background: var(--eb-page-bg);
    border-top: 1px solid var(--eb-border);
    z-index: 3;
}

.eb-bulk-quota {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1 1 auto;
    font-size: 0.85rem;
}
.eb-bulk-quota-used { color: var(--eb-body); font-weight: 600; }
.eb-bulk-quota-adding { color: var(--eb-muted); font-size: 0.8rem; }
.eb-bulk-quota-over { color: var(--eb-error); font-weight: 600; font-size: 0.8rem; }
.eb-bulk-quota-link { color: var(--eb-orange-ink) !important; font-weight: 700; font-size: 0.8rem; text-decoration: none; }
.eb-bulk-quota-link:hover { color: var(--eb-orange-hover) !important; text-decoration: underline; }

/* ---- Finish phase ---- */

.eb-bulk-finish-head { margin: 4px 0 14px; }
.eb-bulk-finish-count { font-size: 0.85rem; font-weight: 600; color: var(--eb-muted); }

.eb-bulk-finish-item {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 14px 16px;
    border: 1px solid var(--eb-border);
    border-radius: 12px;
    background: var(--eb-surface);
    margin-bottom: 10px;
    transition: border-color 160ms ease;
}
.eb-bulk-finish-item--live { border-color: color-mix(in srgb, var(--eb-success) 55%, var(--eb-border)); }

.eb-bulk-finish-main { flex: 1; min-width: 0; }
.eb-bulk-finish-title {
    font-family: var(--eb-display);
    font-weight: 600;
    color: var(--eb-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.eb-bulk-finish-state { margin-top: 3px; }

.eb-bulk-finish-actions {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    justify-content: flex-end;
    flex-shrink: 0;
}

.eb-bulk-state {
    font-size: 0.72rem;
    font-weight: 700;
    padding: 2px 9px;
    border-radius: 999px;
    white-space: nowrap;
}
.eb-bulk-state--draft { color: var(--eb-muted); background: rgba(255, 255, 255, 0.06); }
.eb-bulk-state--ready { color: var(--eb-orange-on-tint); background: var(--eb-tint-bg); }
.eb-bulk-state--live { color: var(--eb-success); background: color-mix(in srgb, var(--eb-success) 16%, transparent); }

.eb-bulk-error-list { margin: 0; padding-left: 18px; font-size: 0.85rem; }

.eb-bulk-finish-footer {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 22px;
    flex-wrap: wrap;
}

@media (max-width: 640px) {
    .eb-bulk-finish-item { flex-wrap: wrap; }
    .eb-bulk-finish-actions { justify-content: flex-start; width: 100%; }
}

/* ======================================================================
   Beta feedback — launcher tab, submit dialog, public board, admin
   ====================================================================== */

/* ---- Launcher tab (fixed, right edge) --------------------------------- */
.eb-feedback-tab {
    position: fixed;
    right: env(safe-area-inset-right);
    top: 50%;
    transform: translateY(-50%);
    z-index: 1200; /* above page content, below compare tray (1250) and dialogs */
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 16px 9px;
    background: var(--eb-surface);
    border: 1px solid var(--eb-border-2);
    border-right: none;
    border-radius: 12px 0 0 12px;
    color: var(--eb-body);
    cursor: pointer;
    box-shadow: var(--eb-shadow-sm);
    transition: color 140ms ease, border-color 140ms ease, transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
}

.eb-feedback-tab:hover,
.eb-feedback-tab:focus-visible {
    color: var(--eb-text);
    border-color: var(--eb-orange);
    transform: translateY(-50%) translateX(-3px);
}

.eb-feedback-tab-ico { color: var(--eb-orange-ink); display: flex; }

.eb-feedback-tab-label {
    writing-mode: vertical-rl;
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 0.8rem;
    letter-spacing: 0.02em;
}

.eb-feedback-tab-dot {
    position: absolute;
    top: 7px;
    right: 7px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--eb-orange);
    box-shadow: 0 0 8px var(--eb-orange);
    animation: eb-fb-pulse 2.4s ease-in-out infinite;
}

@keyframes eb-fb-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.4; }
}

@media (prefers-reduced-motion: reduce) {
    .eb-feedback-tab { transition: none; }
    .eb-feedback-tab-dot { animation: none; }
}

@media (max-width: 600px) {
    .eb-feedback-tab { padding: 12px 7px; }
    .eb-feedback-tab-label { font-size: 0.74rem; }
}

/* ---- Type segmented control (dialog + board) -------------------------- */
.eb-fb-seg { display: flex; gap: 8px; }

.eb-fb-seg-btn {
    flex: 1 1 0;
    /* Flex items default to min-width:auto, so nowrap text refuses to shrink below its own content
       width no matter what flex-basis says — that's what let "Suggest an improvement" push the
       dialog's content into a horizontal scrollbar at MaxWidth.Small widths (the public board's
       toolbar has more room and never hit it). min-width:0 lets the item actually shrink; dropping
       nowrap lets the label wrap to a second line there instead of forcing overflow or a silent
       clip — the icon's own width already reserves its own space so it doesn't get squeezed too. */
    min-width: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    min-height: 44px;
    padding: 9px 12px;
    border-radius: 12px;
    cursor: pointer;
    background: var(--eb-surface-2);
    border: 1px solid var(--eb-border);
    color: var(--eb-body);
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 0.88rem;
    line-height: 1.25;
    text-align: center;
    transition: color 140ms ease, border-color 140ms ease, background 140ms ease;
}

.eb-fb-seg-btn:hover { color: var(--eb-text); border-color: var(--eb-border-2); }

.eb-fb-seg-btn--active {
    background: var(--eb-tint-bg);
    border-color: var(--eb-orange);
    color: var(--eb-orange-on-tint);
}

/* ---- Page-context chip ------------------------------------------------- */
.eb-fb-context {
    display: flex;
    align-items: center;
    gap: 7px;
    max-width: 100%;
    font-size: 0.78rem;
    color: var(--eb-muted);
    background: var(--eb-surface-2);
    border: 1px solid var(--eb-border);
    border-radius: 8px;
    padding: 7px 11px;
}

.eb-fb-context code {
    font-family: ui-monospace, Menlo, Consolas, monospace;
    color: var(--eb-body);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

/* ---- Login prompt (dialog, anonymous) --------------------------------- */
.eb-fb-login { text-align: center; padding: 10px 4px 6px; }
.eb-fb-login-ico { color: var(--eb-orange-ink); margin-bottom: 10px; }
.eb-fb-login-title {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 1.15rem;
    color: var(--eb-text);
    margin-bottom: 6px;
}
.eb-fb-login-hint {
    color: var(--eb-muted);
    font-size: 0.88rem;
    line-height: 1.5;
    max-width: 34ch;
    margin: 0 auto 18px;
}

/* ---- Board toolbar ---------------------------------------------------- */
.eb-fb-toolbar {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 22px;
}
.eb-fb-spacer { flex: 1 1 auto; }
.eb-fb-filter { max-width: 190px; }

@media (max-width: 640px) {
    .eb-fb-toolbar > .eb-fb-seg { width: 100%; }
    .eb-fb-filter { max-width: none; flex: 1 1 auto; }
    .eb-fb-spacer { display: none; }
}

/* ---- Board list + rows ------------------------------------------------ */
.eb-fb-list { display: flex; flex-direction: column; gap: 12px; }

.eb-fb-row {
    display: flex;
    gap: 14px;
    background: var(--eb-surface);
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    padding: 16px;
    transition: border-color 140ms ease;
}
.eb-fb-row:hover { border-color: var(--eb-border-2); }

.eb-fb-row-body { flex: 1 1 auto; min-width: 0; }

.eb-fb-row-meta {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 9px;
}

.eb-fb-row-title {
    display: inline-block;
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 1.05rem;
    letter-spacing: -0.01em;
    color: var(--eb-text);
    text-decoration: none;
}
.eb-fb-row-title:hover { color: var(--eb-orange-ink); }

.eb-fb-row-excerpt {
    color: var(--eb-body);
    font-size: 0.9rem;
    line-height: 1.55;
    margin: 6px 0 0;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.eb-fb-row-byline {
    color: var(--eb-muted);
    font-size: 0.78rem;
    margin-top: 11px;
}

/* ---- Vote rail -------------------------------------------------------- */
.eb-fb-rail {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    align-self: flex-start;
    min-width: 54px;
    padding: 7px 6px;
    background: var(--eb-surface-2);
    border: 1px solid var(--eb-border);
    border-radius: 12px;
}
.eb-fb-rail--lg { min-width: 62px; padding: 10px 8px; }

.eb-fb-rail-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 26px;
    border: none;
    border-radius: 8px;
    background: none;
    color: var(--eb-muted);
    cursor: pointer;
    transition: color 120ms ease, background 120ms ease;
}
.eb-fb-rail-btn:hover:not(:disabled) { color: var(--eb-text); background: rgba(255, 255, 255, 0.06); }
.eb-fb-rail-btn:disabled { cursor: default; opacity: 0.55; }
.eb-fb-rail-btn--active { color: var(--eb-orange-ink); }

.eb-fb-rail-score {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 1rem;
    color: var(--eb-text);
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
}
.eb-fb-rail--lg .eb-fb-rail-score { font-size: 1.25rem; }

/* ---- Type badge + status pill ----------------------------------------- */
.eb-fb-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 0.72rem;
    font-weight: 700;
    padding: 3px 9px 3px 7px;
    border-radius: 999px;
    background: var(--eb-tint-bg);
    color: var(--eb-orange-on-tint);
    border: 1px solid var(--eb-tint-border);
    white-space: nowrap;
}
.eb-fb-badge .mud-icon-root { font-size: 0.95rem; }

.eb-fb-status {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.74rem;
    font-weight: 600;
    color: var(--eb-body);
    background: var(--eb-surface-2);
    border: 1px solid var(--eb-border-2);
    border-radius: 999px;
    padding: 3px 10px;
    white-space: nowrap;
}
.eb-fb-status-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.eb-fb-dot--new { background: var(--eb-muted); }
.eb-fb-dot--review { background: var(--eb-info); }
.eb-fb-dot--planned { background: var(--eb-welfare-accent); }
.eb-fb-dot--fixed { background: var(--eb-success); }
.eb-fb-dot--dismissed { background: var(--eb-border-2); }
.eb-fb-dot--duplicate { background: var(--mud-palette-secondary); }

.eb-fb-duplicate {
    display: flex;
    align-items: center;
    gap: 7px;
    margin-top: 10px;
    color: var(--eb-muted);
    font-size: 0.82rem;
}
.eb-fb-duplicate a { color: var(--eb-orange-on-tint); font-weight: 700; }
.eb-fb-duplicate--detail {
    align-items: flex-start;
    padding: 13px 15px;
    margin: 0 0 18px;
    background: var(--eb-tint-bg);
    border: 1px solid var(--eb-tint-border);
}
.eb-fb-duplicate-title { color: var(--eb-text); font-weight: 700; }
.eb-fb-duplicate-hint { margin-top: 2px; font-size: 0.78rem; }

/* ---- Admin response panel (full border tint, no side-stripe) ---------- */
.eb-fb-response {
    margin-top: 12px;
    padding: 11px 14px;
    background: var(--eb-tint-bg);
    border: 1px solid var(--eb-tint-border);
    border-radius: 10px;
}
.eb-fb-response-label {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-orange-ink);
    margin-bottom: 4px;
}
.eb-fb-response-text { color: var(--eb-body); font-size: 0.9rem; line-height: 1.5; }

/* ---- Detail page ------------------------------------------------------ */
.eb-fb-detail { display: flex; gap: 18px; align-items: flex-start; }
.eb-fb-detail-body { flex: 1 1 auto; min-width: 0; }
.eb-fb-detail-title {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: clamp(1.6rem, 3.2vw, 2.1rem);
    line-height: 1.1;
    letter-spacing: -0.02em;
    color: var(--eb-text);
    margin: 10px 0 0;
    text-wrap: balance;
}
.eb-fb-detail-text {
    color: var(--eb-body);
    font-size: 0.98rem;
    line-height: 1.7;
    white-space: pre-wrap;
    max-width: 68ch;
}

/* ---- Admin table extras + manage dialog ------------------------------- */
.eb-fb-admin-table .eb-fb-admin-title {
    color: var(--eb-text);
    font-weight: 600;
    text-decoration: none;
}
.eb-fb-admin-table .eb-fb-admin-title:hover { color: var(--eb-orange-ink); }
.eb-fb-admin-duplicate { color: var(--eb-muted); font-size: 0.72rem; margin-top: 3px; }
.eb-fb-admin-duplicate a { color: var(--eb-orange-ink); }
.eb-fb-admin-score { font-family: var(--eb-display); font-weight: 800; font-variant-numeric: tabular-nums; }

.eb-fb-manage-head { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
.eb-fb-manage-score { color: var(--eb-muted); font-size: 0.82rem; font-weight: 600; }
.eb-fb-manage-title {
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 1.1rem;
    color: var(--eb-text);
    letter-spacing: -0.01em;
    margin-bottom: 6px;
}
.eb-fb-manage-details {
    color: var(--eb-body);
    font-size: 0.9rem;
    line-height: 1.55;
    margin: 0 0 16px;
    white-space: pre-wrap;
}

@media (max-width: 560px) {
    /* Detail page only: rail sits above the body, left-aligned. Board rows keep the compact vertical rail. */
    .eb-fb-detail { flex-direction: column; }
    .eb-fb-detail .eb-fb-rail { flex-direction: row; min-width: 0; align-self: flex-start; justify-content: flex-start; gap: 10px; }
    .eb-fb-detail .eb-fb-rail-score { min-width: 2ch; text-align: center; }
}

/* ============================================================
   REPORTING
   A quiet, secondary "report" affordance + the moderation queue.
   Reporting is a muted negative action — never the bright orange
   reserved for primary actions.
   ============================================================ */

.eb-report-row {
    margin-top: 20px;
    padding-top: 14px;
    border-top: 1px solid var(--eb-border);
}

.eb-report-row--page {
    display: flex;
    justify-content: center;
    margin-top: 32px;
    padding-top: 0;
    border-top: none;
}

.eb-report-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
    border: none;
    background: transparent;
    color: var(--eb-muted);
    font-size: 0.8rem;
    font-weight: 600;
    border-radius: 10px;
    cursor: pointer;
    transition: color 140ms ease, background-color 140ms ease;
}

.eb-report-btn:hover {
    color: var(--eb-text);
    background: rgba(255, 255, 255, 0.05);
}

.eb-report-btn:focus-visible {
    outline: 2px solid var(--eb-orange);
    outline-offset: 2px;
}

.eb-report-btn .mud-icon-root { font-size: 1rem; }

.eb-report-btn--done,
.eb-report-btn--done:hover {
    color: var(--eb-muted);
    background: transparent;
    cursor: default;
}

.eb-report-intro {
    color: var(--eb-body);
    font-size: 0.9rem;
    line-height: 1.55;
    margin: 0 0 16px;
}

.eb-report-urgent {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin: 0 0 16px;
    padding: 10px 12px;
    border: 1px solid var(--eb-welfare-accent);
    border-radius: 10px;
    background: rgba(224, 164, 34, 0.10);
    color: var(--eb-body);
    font-size: 0.82rem;
    line-height: 1.5;
}

.eb-report-urgent .mud-icon-root { color: var(--eb-welfare-accent); font-size: 1.1rem; margin-top: 1px; }

.eb-report-login {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 8px;
    padding: 24px 8px;
}

.eb-report-login-ico .mud-icon-root { font-size: 2rem; color: var(--eb-muted); }
.eb-report-login-title { font-family: var(--eb-display); font-weight: 800; font-size: 1.1rem; color: var(--eb-text); }
.eb-report-login-hint { color: var(--eb-body); font-size: 0.88rem; margin-bottom: 8px; }

/* Moderation queue status dots (reuse the .eb-fb-status-dot base shape) */
.eb-rep-dot--open { background: var(--eb-welfare-accent); }
.eb-rep-dot--review { background: var(--eb-info); }
.eb-rep-dot--actioned { background: var(--eb-success); }
.eb-rep-dot--dismissed { background: var(--eb-border-2); }

.eb-rep-target { display: inline-flex; align-items: flex-start; gap: 8px; }
.eb-rep-target > .mud-icon-root { font-size: 1.05rem; color: var(--eb-muted); margin-top: 2px; }
.eb-rep-target-type { color: var(--eb-muted); font-size: 0.72rem; margin-top: 2px; }

.eb-rep-count {
    display: inline-block;
    min-width: 1.6em;
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--eb-surface-2);
    border: 1px solid var(--eb-border);
    color: var(--eb-body);
    font-size: 0.78rem;
    font-weight: 700;
    text-align: center;
}

.eb-rep-count--multi {
    background: rgba(232, 99, 10, 0.12);
    border-color: rgba(232, 99, 10, 0.40);
    color: var(--eb-orange-ink);
}

.eb-rep-manage-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.eb-rep-manage-count { color: var(--eb-orange-ink); font-weight: 700; font-size: 0.8rem; }

.eb-rep-meta {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px 16px;
    margin: 12px 0 16px;
    padding: 12px 14px;
    background: var(--eb-surface-2);
    border: 1px solid var(--eb-border);
    border-radius: 12px;
}

.eb-rep-meta > div { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.eb-rep-meta dt { color: var(--eb-muted); font-size: 0.7rem; font-weight: 600; }
.eb-rep-meta dd { color: var(--eb-text); font-size: 0.88rem; margin: 0; word-break: break-word; }

.eb-rep-links { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 16px; }
.eb-rep-handled { color: var(--eb-muted); font-size: 0.78rem; margin-top: 12px; }

.eb-empty-sub { color: var(--eb-body); font-size: 0.9rem; margin-top: 4px; }

@media (max-width: 560px) {
    .eb-rep-meta { grid-template-columns: 1fr; }
}

/* ============================================================
   ADMIN SITE OVERVIEW
   Calm operational pulse: one dominant report, one compact
   lifecycle strip, then asymmetrical secondary detail.
   ============================================================ */

.eb-dash-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 24px;
    margin-bottom: 24px;
}

.eb-dash-heading {
    min-width: 0;
}

.eb-dash-heading .eb-page-sub {
    max-width: 64ch;
    margin-bottom: 0;
    text-wrap: pretty;
}

.eb-dash-controls {
    display: flex;
    align-items: flex-end;
    gap: 12px;
    flex: none;
}

.eb-dash-date {
    width: 280px;
}

.eb-dash-refresh {
    min-height: 44px;
    margin-bottom: 2px;
}

.eb-dash-loading {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.eb-dash-skeleton {
    border-radius: 14px;
}

.eb-dash-error {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
}

.eb-dash-update-line {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 24px;
    margin-bottom: 16px;
    color: var(--eb-body);
    font-size: 0.82rem;
    line-height: 1.45;
}

.eb-dash-update-dot {
    width: 8px;
    height: 8px;
    flex: none;
    border-radius: 999px;
    background: var(--eb-success);
}

.eb-dash-updated {
    margin-inline-start: auto;
    color: var(--eb-muted);
    white-space: nowrap;
}

.eb-dash-surface,
.eb-dash-lifecycle {
    background: var(--eb-surface);
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    box-shadow: none;
}

.eb-dash-surface {
    min-width: 0;
    padding: 24px;
}

.eb-dash-first-run {
    display: flex;
    align-items: center;
    gap: 16px;
    min-height: 160px;
}

.eb-dash-first-run > .mud-icon-root {
    color: var(--eb-orange-ink);
    font-size: 2rem;
}

.eb-dash-first-run h2 {
    margin: 0 0 4px;
    color: var(--eb-text);
    font-size: 1.3rem;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.eb-dash-first-run p {
    max-width: 62ch;
    margin: 0;
    color: var(--eb-body);
}

.eb-dash-lifecycle {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    margin-bottom: 16px;
    overflow: hidden;
}

.eb-dash-lifecycle-item {
    display: grid;
    grid-template-columns: 36px minmax(0, 1fr) auto;
    align-items: center;
    gap: 12px;
    min-width: 0;
    min-height: 112px;
    padding: 16px 18px;
    color: var(--eb-text);
    text-decoration: none;
    transition: background-color 160ms ease-out, color 160ms ease-out;
}

.eb-dash-lifecycle-item + .eb-dash-lifecycle-item {
    border-inline-start: 1px solid var(--eb-border);
}

a.eb-dash-lifecycle-item:hover {
    background: var(--eb-tint-bg);
    color: var(--eb-text);
}

.eb-dash-lifecycle-item > .mud-icon-root:first-child {
    color: var(--eb-muted);
    font-size: 1.5rem;
}

a.eb-dash-lifecycle-item:hover > .mud-icon-root:first-child,
a.eb-dash-lifecycle-item:hover .eb-dash-metric-arrow {
    color: var(--eb-orange-ink);
}

.eb-dash-metric-copy {
    display: flex;
    flex-direction: column;
    min-width: 0;
    gap: 2px;
}

.eb-dash-metric-label {
    overflow: hidden;
    color: var(--eb-muted);
    font-size: 0.78rem;
    font-weight: 600;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.eb-dash-metric-copy strong {
    color: var(--eb-text);
    font-size: 1.35rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
    line-height: 1.15;
}

.eb-dash-metric-copy > span:last-child {
    color: var(--eb-muted);
    font-size: 0.72rem;
    line-height: 1.35;
}

.eb-dash-metric-arrow {
    color: var(--eb-border-2);
    transition: color 160ms ease-out, transform 160ms ease-out;
}

a.eb-dash-lifecycle-item:hover .eb-dash-metric-arrow {
    transform: translateX(2px);
}

.eb-dash-change--up {
    color: var(--eb-success) !important;
}

.eb-dash-change--down {
    color: var(--eb-error) !important;
}

.eb-dash-primary-grid,
.eb-dash-secondary-grid {
    display: grid;
    gap: 16px;
}

.eb-dash-primary-grid {
    grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr);
    margin-bottom: 16px;
}

.eb-dash-secondary-grid {
    grid-template-columns: minmax(320px, 0.9fr) minmax(0, 1.1fr);
}

.eb-dash-section-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 24px;
}

.eb-dash-section-head h2 {
    margin: 0;
    color: var(--eb-text);
    font-size: 1.3rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 1.25;
    text-wrap: balance;
}

.eb-dash-section-head p {
    max-width: 54ch;
    margin: 4px 0 0;
    color: var(--eb-muted);
    font-size: 0.8rem;
    line-height: 1.5;
    text-wrap: pretty;
}

.eb-dash-period-facts {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 8px 16px;
    color: var(--eb-muted);
    font-size: 0.72rem;
    line-height: 1.4;
}

.eb-dash-period-facts span {
    white-space: nowrap;
}

.eb-dash-period-facts strong {
    color: var(--eb-text);
    font-size: 0.84rem;
    font-variant-numeric: tabular-nums;
}

.eb-dash-chart-wrap {
    min-width: 0;
}

.eb-dash-chart-canvas {
    position: relative;
}

.eb-dash-chart {
    display: block;
    width: 100%;
    height: 260px;
    overflow: visible;
}

.eb-dash-chart-grid {
    stroke: var(--eb-border);
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
}

.eb-dash-chart-y {
    position: absolute;
    inset: 18px auto 36px 0;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    width: 36px;
    color: var(--eb-muted);
    font-family: var(--eb-display);
    font-size: 0.72rem;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    text-align: end;
}

.eb-dash-chart-area {
    fill: var(--eb-tint-bg);
    stroke: none;
}

.eb-dash-chart-line {
    fill: none;
    stroke: var(--eb-orange);
    stroke-width: 2.5;
    stroke-linecap: round;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}

.eb-dash-chart-point {
    fill: var(--eb-surface);
    stroke: var(--eb-orange);
    stroke-width: 2.5;
    vector-effect: non-scaling-stroke;
}

.eb-dash-chart-axis {
    display: flex;
    justify-content: space-between;
    padding-inline-start: 44px;
    color: var(--eb-muted);
    font-size: 0.72rem;
    font-variant-numeric: tabular-nums;
}

.eb-dash-chart-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: 260px;
    color: var(--eb-muted);
    text-align: center;
}

.eb-dash-chart-empty .mud-icon-root {
    color: var(--eb-border-2);
    font-size: 2rem;
}

.eb-dash-count-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 28px;
    height: 28px;
    padding-inline: 8px;
    border-radius: 999px;
    background: var(--eb-tint-bg);
    color: var(--eb-orange-on-tint);
    font-size: 0.78rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

.eb-dash-attention-list {
    margin-inline: -24px;
}

.eb-dash-attention-row {
    display: grid;
    grid-template-columns: 36px minmax(0, 1fr) auto auto;
    align-items: center;
    gap: 12px;
    min-height: 72px;
    padding: 12px 24px;
    border-top: 1px solid var(--eb-border);
    color: var(--eb-text);
    text-decoration: none;
    transition: background-color 160ms ease-out;
}

.eb-dash-attention-row:first-child {
    border-top: 0;
}

a.eb-dash-attention-row:hover {
    background: var(--eb-tint-bg);
    color: var(--eb-text);
}

.eb-dash-attention-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 10px;
    background: var(--eb-surface-2);
    color: var(--eb-muted);
}

.eb-dash-attention-icon .mud-icon-root {
    font-size: 1.15rem;
}

.eb-dash-attention-row > span:nth-child(2) {
    display: flex;
    flex-direction: column;
    min-width: 0;
    gap: 2px;
}

.eb-dash-attention-row strong {
    overflow: hidden;
    color: var(--eb-text);
    font-size: 0.84rem;
    font-weight: 700;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.eb-dash-attention-row small {
    overflow: hidden;
    color: var(--eb-muted);
    font-size: 0.72rem;
    line-height: 1.4;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.eb-dash-attention-row b {
    color: var(--eb-text);
    font-size: 0.9rem;
    font-variant-numeric: tabular-nums;
}

.eb-dash-attention-row > .mud-icon-root {
    color: var(--eb-border-2);
    font-size: 1.1rem;
}

.eb-dash-all-clear {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 220px;
    color: var(--eb-success);
}

.eb-dash-all-clear > .mud-icon-root {
    font-size: 1.75rem;
}

.eb-dash-all-clear div {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.eb-dash-all-clear strong {
    color: var(--eb-text);
}

.eb-dash-all-clear span {
    color: var(--eb-muted);
    font-size: 0.8rem;
}

.eb-dash-section-link {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    min-height: 44px;
    color: var(--eb-orange-ink);
    font-size: 0.78rem;
    font-weight: 700;
    text-decoration: none;
    white-space: nowrap;
}

.eb-dash-section-link .mud-icon-root {
    font-size: 1rem;
}

.eb-dash-plan-list {
    display: flex;
    flex-direction: column;
    margin-inline: -24px;
}

.eb-dash-plan-row {
    display: grid;
    grid-template-columns: 36px minmax(0, 1fr) auto;
    align-items: center;
    gap: 12px;
    min-height: 84px;
    padding: 12px 24px;
    border-top: 1px solid var(--eb-border);
    color: var(--eb-text);
    text-decoration: none;
    transition: background-color 160ms ease-out;
}

.eb-dash-plan-row:first-child {
    border-top: 0;
}

.eb-dash-plan-row:hover {
    background: var(--eb-tint-bg);
    color: var(--eb-text);
}

.eb-dash-plan-row > .mud-icon-root {
    color: var(--eb-border-2);
    font-size: 1.1rem;
}

.eb-dash-plan-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 10px;
    background: var(--eb-tint-bg);
    color: var(--eb-orange-on-tint);
    font-size: 0.82rem;
    font-weight: 800;
}

.eb-dash-plan-mark--keeper { opacity: 0.72; }
.eb-dash-plan-mark--hobbyist { opacity: 0.86; }

.eb-dash-plan-copy {
    display: flex;
    flex-direction: column;
    min-width: 0;
    gap: 6px;
}

.eb-dash-plan-copy > span:first-child {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
}

.eb-dash-plan-copy strong {
    color: var(--eb-text);
    font-size: 0.84rem;
}

.eb-dash-plan-copy b {
    color: var(--eb-text);
    font-size: 0.9rem;
    font-variant-numeric: tabular-nums;
}

.eb-dash-plan-track {
    height: 5px;
    overflow: hidden;
    border-radius: 999px;
    background: var(--eb-surface-2);
}

.eb-dash-plan-track > span {
    display: block;
    height: 100%;
    min-width: 0;
    border-radius: inherit;
    background: var(--eb-orange);
}

.eb-dash-plan-copy small {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    color: var(--eb-muted);
    font-size: 0.72rem;
    font-style: normal;
    line-height: 1.4;
}

.eb-dash-plan-copy em {
    color: var(--eb-welfare-accent);
    font-style: normal;
    font-weight: 700;
}

.eb-dash-status-groups {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 24px;
}

.eb-dash-status-group {
    min-width: 0;
}

.eb-dash-status-group + .eb-dash-status-group {
    padding-inline-start: 24px;
    border-inline-start: 1px solid var(--eb-border);
}

.eb-dash-status-heading {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--eb-border);
}

.eb-dash-status-heading > span {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    color: var(--eb-text);
    font-size: 0.84rem;
    font-weight: 700;
}

.eb-dash-status-heading .mud-icon-root {
    color: var(--eb-muted);
    font-size: 1.1rem;
}

.eb-dash-status-heading small {
    color: var(--eb-orange-ink);
    font-size: 0.72rem;
    font-weight: 700;
    white-space: nowrap;
}

.eb-dash-status-group dl {
    margin: 0;
}

.eb-dash-status-group dl > div {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    min-height: 44px;
    border-bottom: 1px solid var(--eb-border);
}

.eb-dash-status-group dl > div:last-child {
    border-bottom: 0;
}

.eb-dash-status-group dt {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    color: var(--eb-body);
    font-size: 0.78rem;
}

.eb-dash-status-group dd {
    margin: 0;
    color: var(--eb-text);
    font-size: 0.84rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.eb-dash-status-dot {
    width: 8px;
    height: 8px;
    flex: none;
    border-radius: 999px;
    background: var(--eb-muted);
}

.eb-dash-status-dot--live { background: var(--eb-success); }
.eb-dash-status-dot--hold { background: var(--eb-welfare-accent); }
.eb-dash-status-dot--sold { background: var(--eb-info); }

@media (max-width: 1020px) {
    .eb-dash-primary-grid {
        grid-template-columns: 1fr;
    }

    .eb-dash-all-clear {
        min-height: 120px;
    }
}

@media (max-width: 840px) {
    .eb-dash-header {
        flex-direction: column;
    }

    .eb-dash-controls {
        width: 100%;
    }

    .eb-dash-lifecycle {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .eb-dash-lifecycle-item:nth-child(3) {
        border-inline-start: 0;
        border-top: 1px solid var(--eb-border);
    }

    .eb-dash-lifecycle-item:nth-child(4) {
        border-top: 1px solid var(--eb-border);
    }

    .eb-dash-secondary-grid {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 600px) {
    .eb-dash-error {
        flex-direction: column;
        align-items: stretch;
        gap: 12px;
    }

    .eb-dash-header {
        gap: 16px;
    }

    .eb-dash-controls {
        flex-direction: column;
        align-items: stretch;
    }

    .eb-dash-date {
        width: 100%;
    }

    .eb-dash-refresh {
        width: 100%;
        margin-bottom: 0;
    }

    .eb-dash-update-line {
        align-items: flex-start;
        flex-wrap: wrap;
    }

    .eb-dash-updated {
        width: 100%;
        margin-inline-start: 16px;
    }

    .eb-dash-lifecycle {
        grid-template-columns: 1fr;
    }

    .eb-dash-lifecycle-item + .eb-dash-lifecycle-item,
    .eb-dash-lifecycle-item:nth-child(3) {
        border-inline-start: 0;
        border-top: 1px solid var(--eb-border);
    }

    .eb-dash-lifecycle-item {
        min-height: 88px;
    }

    .eb-dash-surface {
        padding: 16px;
    }

    .eb-dash-section-head {
        flex-direction: column;
        margin-bottom: 16px;
    }

    .eb-dash-period-facts {
        justify-content: flex-start;
    }

    .eb-dash-chart {
        height: 220px;
    }

    .eb-dash-attention-list,
    .eb-dash-plan-list {
        margin-inline: -16px;
    }

    .eb-dash-attention-row,
    .eb-dash-plan-row {
        padding-inline: 16px;
    }

    .eb-dash-attention-row small {
        overflow: visible;
        text-overflow: clip;
        white-space: normal;
    }

    .eb-dash-status-groups {
        grid-template-columns: 1fr;
        gap: 24px;
    }

    .eb-dash-status-group + .eb-dash-status-group {
        padding-inline-start: 0;
        padding-top: 24px;
        border-inline-start: 0;
        border-top: 1px solid var(--eb-border);
    }
}

@media (prefers-reduced-motion: reduce) {
    .eb-dash-lifecycle-item,
    .eb-dash-metric-arrow,
    .eb-dash-attention-row,
    .eb-dash-plan-row {
        transition: none;
    }

    a.eb-dash-lifecycle-item:hover .eb-dash-metric-arrow {
        transform: none;
    }
}

/* ============================================================
   Notification preference matrix (/my/notifications)
   Rows = events, columns = Push / Email switches. Surface card
   with hairline rows; header follows the table-header treatment.
   ============================================================ */

.eb-notif-section-head {
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin-top: 36px;
}

.eb-notif-section-head .eb-section-title {
    margin-top: 0;
}

.eb-notif-saved {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--eb-success, #22C07C);
    opacity: 0;
    transition: opacity 180ms ease-out;
}

.eb-notif-saved.is-visible {
    opacity: 1;
}

.eb-notif-saved .mud-icon-root {
    font-size: 1rem;
}

.eb-notif-sub {
    margin-bottom: 14px;
}

.eb-notif-matrix {
    background: var(--eb-surface);
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    overflow: hidden;
}

.eb-notif-head,
.eb-notif-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 84px 84px;
    align-items: center;
    column-gap: 8px;
}

.eb-notif-head {
    padding: 10px 20px;
    background: var(--eb-surface-2);
    border-bottom: 1px solid var(--eb-border);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-muted);
}

.eb-notif-row {
    padding: 13px 20px;
}

.eb-notif-row + .eb-notif-row {
    border-top: 1px solid var(--eb-border);
}

.eb-notif-name {
    font-weight: 600;
    font-size: 0.95rem;
    color: var(--eb-text);
}

.eb-notif-desc {
    font-size: 0.82rem;
    line-height: 1.45;
    color: var(--eb-muted);
    margin-top: 2px;
}

.eb-notif-col {
    display: flex;
    justify-content: center;
    text-align: center;
}

/* The switch's built-in end-margin skews centring inside the fixed column. */
.eb-notif-col .mud-switch {
    margin: 0;
}

.eb-notif-hint {
    margin-top: 10px;
    font-size: 0.82rem;
    color: var(--eb-muted);
}

@media (max-width: 480px) {
    .eb-notif-head,
    .eb-notif-row {
        grid-template-columns: minmax(0, 1fr) 56px 56px;
    }

    .eb-notif-head {
        padding: 9px 14px;
    }

    .eb-notif-row {
        padding: 12px 14px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .eb-notif-saved {
        transition: none;
    }
}
/* ---- Seller listing manager: toolbar ------------------------------------- */

.eb-seller-toolbar {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

.eb-seller-toolbar .eb-seller-search {
    max-width: 280px;
}

/* Phone/tablet: the control stack is what stands between a seller and their
   list, so it collapses to three bands — a scrollable status rail, a
   full-width search, and the two bulk-entry routes side by side. The primary
   "Add animal" leaves the flow entirely for a thumb-reachable bottom bar. */
@media (max-width: 767.98px) {
    .eb-seller-toolbar {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 10px;
        margin-bottom: 16px;
    }

    .eb-seller-toolbar > .flex-grow-1 {
        display: none;
    }

    .eb-seller-toolbar > .mud-chipset,
    .eb-seller-toolbar > .eb-seller-search {
        grid-column: 1 / -1;
    }

    .eb-seller-toolbar .eb-seller-search {
        max-width: none;
    }

    /* A single scrolling rail beats a two-row pile of wrapped chips: it keeps
       the tab metaphor and bleeds past the gutter so the overflow is visible. */
    .eb-seller-toolbar > .mud-chipset {
        display: flex;
        flex-wrap: nowrap;
        gap: 6px;
        overflow-x: auto;
        overscroll-behavior-x: contain;
        scrollbar-width: none;
        margin-inline: calc(-1 * max(16px, env(safe-area-inset-left))) calc(-1 * max(16px, env(safe-area-inset-right)));
        padding-inline: max(16px, env(safe-area-inset-left)) max(16px, env(safe-area-inset-right));
        /* the trailing chip fades instead of being sliced, so the rail reads as
           scrollable rather than broken */
        mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
    }

    .eb-seller-toolbar > .mud-chipset::-webkit-scrollbar {
        display: none;
    }

    .eb-seller-toolbar > .mud-chipset > .mud-chip {
        flex: 0 0 auto;
        margin: 0;
    }

    .eb-seller-toolbar > .mud-button-root {
        width: 100%;
        min-height: 44px;
    }

    .eb-seller-primary {
        position: fixed;
        inset-inline: 0;
        bottom: 0;
        z-index: 90;
        display: flex;
        gap: 8px;
        padding: 10px max(16px, env(safe-area-inset-right)) max(10px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
        background-color: var(--eb-bg);
        border-top: 1px solid var(--eb-border);
    }

    .eb-seller-primary > .mud-button-root {
        flex: 1;
        min-height: 48px;
    }
}

@media (max-width: 359.98px) {
    .eb-seller-toolbar > .mud-chipset {
        margin-inline: calc(-1 * max(12px, env(safe-area-inset-left))) calc(-1 * max(12px, env(safe-area-inset-right)));
        padding-inline: max(12px, env(safe-area-inset-left)) max(12px, env(safe-area-inset-right));
    }
}

/* ---- Seller listing manager: rows as listing cards ----------------------- */

/* Below MudBlazor's table breakpoint the head row is hidden and every cell
   becomes its own labelled block — a ten-column seller table read as ten
   label/value lines per animal. Re-lay the row out as the card a seller
   actually scans: photo, identity, price, genetics, then the two controls
   they reach for. Cells are placed by eb-c-* class, not column position. */
@media (max-width: 959.98px) {
    .eb-seller-table {
        background: none;
        border: none;
        border-radius: 0;
        overflow: visible;
    }

    .eb-seller-table .mud-table-container,
    .eb-seller-table .mud-table-root,
    .eb-seller-table .mud-table-body {
        display: block;
    }

    .eb-seller-table .mud-table-body {
        border-top: none;
    }

    .eb-seller-table .mud-table-row {
        position: relative;
        display: grid;
        grid-template-columns: 84px minmax(0, 1fr) auto;
        grid-template-areas:
            "thumb  title   title"
            "thumb  price   status"
            "thumb  meta    weight"
            ".      updated updated"
            "traits traits  traits"
            "live   live    menu";
        column-gap: 10px;
        row-gap: 4px;
        align-items: center;
        padding: 14px;
        margin-bottom: 10px;
        background-color: var(--eb-surface);
        border: 1px solid var(--eb-border);
        border-radius: 14px;
    }

    /* A row-hover tint belongs to a table, not to cards — and on touch it sticks
       after a tap, where it would read as selection. Orange means selected here
       and nothing else, so the hover tint is suppressed. Both selectors carry
       MudBlazor's full chain because its own hover rule is six classes deep. */
    .eb-seller-table.mud-table-hover .mud-table-container .mud-table-root .mud-table-body .mud-table-row:hover {
        background-color: var(--eb-surface);
    }

    /* MudBlazor adds no class for a multi-selected row, so the card reads its
       own checkbox — without this, selection is only visible in a 30px box. */
    .eb-seller-table .mud-table-row:has(.mud-checkbox-input:checked),
    .eb-seller-table.mud-table-hover .mud-table-container .mud-table-root .mud-table-body .mud-table-row:has(.mud-checkbox-input:checked) {
        border-color: var(--eb-tint-border);
        background-color: var(--eb-tint-bg);
    }

    .eb-seller-table .mud-table-cell {
        display: block;
        border: none;
        padding: 0;
        text-align: start;
    }

    /* Padding only — keep this rule off `border`, or its extra class would
       outrank the per-cell divider rules below. */
    .eb-seller-table.mud-table-dense .mud-table-cell {
        padding: 0;
    }

    .eb-seller-table .mud-table-cell::before {
        display: none;
    }

    .eb-seller-table .mud-table-row .mud-table-cell:last-child {
        border-bottom: none;
    }

    /* Photo — the identity of the listing, so it gets real size. Centring here
       serves both states: the photo fills the box, the missing-photo icon sits
       in the middle of the empty plate. */
    .eb-seller-table .eb-c-thumb {
        grid-area: thumb;
        align-self: start;
        display: flex;
        align-items: center;
        justify-content: center;
        width: 84px;
        height: 84px;
        border-radius: 10px;
        overflow: hidden;
        background-color: var(--eb-photo-bg, #1E1E22);
        border: 1px solid var(--eb-border);
    }

    .eb-seller-table .eb-c-thumb .mud-icon-root {
        font-size: 1.6rem;
    }

    .eb-seller-table .eb-c-thumb img {
        width: 100%;
        height: 100%;
        object-fit: cover;
        border-radius: 0 !important;
        display: block;
    }

    /* Selection rides the photo corner, the way every photo list does it — it
       costs the layout no width and reads instantly. MudBlazor owns this cell's
       markup, so it is matched by what it contains rather than by a class. */
    .eb-seller-table .mud-table-row > .mud-table-cell:has(.mud-table-cell-checkbox) {
        position: absolute;
        top: 19px;
        left: 19px;
        z-index: 2;
        border-radius: 8px;
        line-height: 0;
    }

    .eb-seller-table .mud-table-cell-checkbox .mud-icon-button {
        width: 30px;
        height: 30px;
        padding: 5px;
    }

    /* The dark glass plate is earned by a photo underneath it. On a listing with
       no photo the plate is a light surface in the light theme, where dark glass
       and a white glyph would both be wrong — so that case keeps plain chrome. */
    .eb-seller-table .mud-table-row:has(.eb-c-thumb img) > .mud-table-cell:has(.mud-table-cell-checkbox) {
        background-color: rgba(12, 12, 14, 0.66);
        backdrop-filter: blur(3px);
    }

    .eb-seller-table .mud-table-row:has(.eb-c-thumb img) .mud-table-cell-checkbox .mud-icon-button {
        color: var(--eb-on-photo, #fff);
    }

    .eb-seller-table .mud-table-cell-checkbox .mud-icon-root {
        font-size: 1.2rem;
    }

    .eb-seller-table .eb-c-title {
        grid-area: title;
        align-self: start;
        min-width: 0;
    }

    /* In card form the title is the card's heading, so it takes the text colour
       and leaves orange to the price — the one-orange rule, applied. */
    .eb-seller-table .eb-c-title .mud-link {
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
        font-weight: 700;
        font-size: 0.92rem;
        line-height: 1.3;
        color: var(--eb-text) !important;
    }

    .eb-seller-table .eb-c-title .mud-typography-caption {
        font-size: 0.72rem;
        color: var(--eb-muted);
    }

    .eb-seller-table .eb-c-price {
        grid-area: price;
        font-weight: 700;
        font-size: 1.02rem;
    }

    .eb-seller-table .eb-c-status {
        grid-area: status;
        align-self: start;
        justify-self: end;
    }

    .eb-seller-table .eb-c-status .mud-chip {
        margin: 0;
        height: 22px;
        font-size: 0.7rem;
    }

    .eb-seller-table .eb-c-meta {
        grid-area: meta;
        min-width: 0;
    }

    /* The pill that carries sex · maturity · views earns its chrome in a table
       cell; inside a card it is just the animal's spec line, so it sheds the
       border and padding and is allowed to wrap rather than shove the weight. */
    .eb-seller-table .eb-c-meta .eb-animal-badge {
        display: flex;
        flex-wrap: wrap;
        padding: 0;
        border: none;
        background: none;
        white-space: normal;
        row-gap: 2px;
        line-height: 1.35;
    }

    .eb-seller-table .eb-c-weight {
        grid-area: weight;
        justify-self: end;
        font-size: 0.78rem;
        color: var(--eb-muted);
        white-space: nowrap;
    }

    /* Actions live at the card's trailing edge on the same strip as the live
       toggle: the two things a seller reaches for, kept together and in thumb
       reach, and costing the title none of its width. */
    .eb-seller-table .eb-c-menu {
        grid-area: menu;
        justify-self: end;
    }

    .eb-seller-table .eb-c-menu .mud-icon-button {
        width: 44px;
        height: 44px;
        padding: 10px;
        margin-right: -6px;
    }

    /* Genetics get their own band: the morph list is the product, and it is
       the one field that legitimately runs long. */
    /* Genetics get their own band below a hairline — it spans every column, so
       the rule reads as one continuous divider rather than a gapped stitch. */
    .eb-seller-table .eb-c-traits {
        grid-area: traits;
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        /* MudBlazor's mobile cell rule is space-between, which would fling the
           morph chips to the card edges instead of listing them. */
        justify-content: flex-start;
        gap: 5px;
        margin-top: 4px;
        padding-top: 10px;
        border-top: 1px solid var(--eb-border);
    }

    /* The "+N more traits" count is a hint, not a trait — it reads as one. */
    .eb-seller-table .eb-c-traits .mud-typography {
        font-size: 0.72rem;
        color: var(--eb-muted);
    }

    .eb-seller-table .eb-c-traits:empty {
        display: none;
    }

    .eb-seller-table .eb-c-traits .mud-chip {
        margin: 0;
        height: 22px;
        font-size: 0.7rem;
    }

    /* The toggle loses its column header in card form, so it takes the cell's
       own generated label back — an unlabelled switch is a guess. */
    .eb-seller-table .eb-c-live {
        grid-area: live;
        justify-self: start;
        display: flex;
        align-items: center;
        justify-content: flex-start;
        gap: 2px;
    }

    .eb-seller-table .eb-c-live::before {
        display: inline;
        content: attr(data-label);
        font-size: 0.72rem;
        font-weight: 600;
        color: var(--eb-muted);
    }

    /* Lowest-value data on the card: it closes the text block rather than
       competing for the action strip, and sits a step down in size and weight. */
    .eb-seller-table .eb-c-updated {
        grid-area: updated;
        justify-self: start;
        font-size: 0.7rem;
        font-weight: 400;
        color: var(--eb-muted);
        white-space: nowrap;
    }

    /* A bare date is ambiguous, so this is the one cell that keeps MudBlazor's
       generated label. */
    .eb-seller-table .eb-c-updated::before {
        display: inline;
        content: attr(data-label);
        padding-right: 4px;
    }

    /* The pager is the only table chrome left, so it reads as its own panel. */
    .eb-seller-table .mud-table-pagination {
        border: 1px solid var(--eb-border);
        border-radius: 12px;
        background-color: var(--eb-surface);
    }

    .eb-seller-table .mud-table-pagination-toolbar {
        flex-wrap: wrap;
        row-gap: 4px;
        min-height: 0;
        padding-block: 6px;
    }
}

/* Tablets have the width for two cards but not for ten columns, so the list
   becomes a two-up grid — each card back at the ~330px the layout is tuned for
   instead of one card stretched across the page. Below 700px two cards would be
   narrower than the phone card, so that band stays one-up. */
@media (min-width: 700px) and (max-width: 959.98px) {
    .eb-seller-table .mud-table-body {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 12px;
        align-items: start;
    }

    .eb-seller-table .mud-table-row {
        margin-bottom: 0;
    }

    /* "No animals yet" and the loading row are single cells — they get the
       full width rather than sitting in a half-width card. */
    .eb-seller-table .mud-table-body > .mud-table-row:only-child:not(:has(.eb-c-thumb)) {
        grid-column: 1 / -1;
    }
}

@media (max-width: 767.98px) {
    /* The page, not the table, carries the clearance for the fixed action bar,
       so the pager keeps its normal spacing above the footer. */
    body:has(.eb-seller-primary) .eb-page {
        padding-bottom: 92px;
    }

    /* "Rows per page" is chrome a phone can't afford; the select speaks for
       itself next to the range. */
    .eb-seller-table .mud-table-pagination-display > .mud-table-pagination-caption:first-child {
        display: none;
    }
}

/* ---- Batch action bar ---------------------------------------------------- */

@media (max-width: 767.98px) {
    .eb-batch-bar {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 8px;
        padding: 12px;
    }

    .eb-batch-bar > .mud-typography {
        grid-column: 1 / -1;
        margin: 0 !important;
    }

    .eb-batch-bar > .flex-grow-1 {
        display: none;
    }

    .eb-batch-bar > .mud-menu,
    .eb-batch-bar > .mud-button-root {
        width: 100%;
        margin: 0 !important;
    }

    .eb-batch-bar > .mud-menu .mud-button-root {
        width: 100%;
        margin: 0 !important;
    }

    .eb-batch-bar > .eb-batch-clear {
        grid-column: 1 / -1;
    }
}


/* MorphMarket import wizard — the three-step "how to get your export" list.
   Bootstrap's reset (global-styles.css, higher specificity on the Blazor host)
   flattens ol padding, so the indent is set here explicitly. */
.eb-import-steps {
    margin: 0;
    padding-left: 1.25rem;
    font-size: 0.875rem;
    line-height: 1.55;
    color: var(--eb-muted);
}

.eb-import-steps > li {
    margin-bottom: 0.35rem;
}

.eb-import-steps > li::marker {
    color: var(--eb-orange);
    font-weight: 700;
}

/* ── Chat (/chat, Volo.Chat MudBlazor UI) ─────────────────────────────────── */

/* The module sets `height: calc(100vh - 120px)` INLINE on its row and `height: 100%`
   inline on both panels. Below md the two panels stack, so their combined height is
   twice the row's — the overflow spills out of <main> and paints over the footer.
   Inline styles mean these overrides have to be !important. */
@media (max-width: 959.98px) {
    .eb-page > .mud-grid[style*="100vh"],
    .eb-page > .mud-grid[style*="100vh"] > .mud-grid-item {
        height: auto !important;
    }

    /* Contacts rail: as tall as it needs, capped so the conversation stays on screen. */
    .eb-page > .mud-grid[style*="100vh"] > .mud-grid-item:first-child > .mud-paper {
        max-height: 45vh;
    }

    /* Conversation panel needs a definite height or its message list can't scroll. */
    .eb-page > .mud-grid[style*="100vh"] > .mud-grid-item:last-child > .mud-paper {
        height: 70vh !important;
    }
}

/* Own-message bubble. Volo writes `background-color: var(--mud-palette-primary); color: white`
   INLINE — white on #E8630A measures 3.38:1, under the 4.5:1 floor, and it is the one thing
   DESIGN.md forbids by name in both §5 and §6. Recolouring the text to --eb-on-orange would pass
   (5.79:1), but it leaves a large saturated orange fill as the loudest area on the marketplace's
   whole contact mechanic — the One Orange Rule inverted. So drop the fill to the orange tint and
   let the text be ordinary body colour. The inline style is why these need !important. */
.mud-paper[style*="mud-palette-primary"][style*="color: white"] {
    background-color: var(--eb-tint-bg) !important;
    border: 1px solid var(--eb-tint-border);
    color: var(--eb-text) !important;
}

/* Listing link the chat linkifier (App.razor) builds from an inquiry's first line. Now the bubble
   is a tint rather than a fill, so the link can be brand orange again — it no longer has to hide
   inside its own background. */
.eb-chat-listing-link {
    color: var(--eb-orange-on-tint);
    font-weight: 700;
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* ============================================================
   Phone: tap targets and the filter bar
   ============================================================
   Measured at 390x844 on /royal-pythons: 31 interactive elements were under
   40x40, and the phone nav — the site's entire navigation on a phone — was
   the worst of them at 112x32. Each rule below names what it was. */

/* One scroll container, not two. The popover already scrolls (.eb-scrollable-nav-popover caps it
   to the viewport), and MudBlazor's list was scrolling inside it as well — measured 1043px of
   content in a 768px popover over a 766px list, so both tracks were live and a thumb-flick could
   catch the wrong one. The popover owns the scroll; the list just runs as long as it needs to. */
.eb-nav-popover .mud-menu-list {
    max-height: none;
    overflow: visible;
}

/* Pagination: 32x32 arrows and page numbers, at the bottom of every result set. */
.mud-pagination-item > .mud-button-root {
    min-width: 40px;
    min-height: 40px;
}

/* Fixed side tab, 35px wide. */
.eb-feedback-tab {
    min-width: 44px;
}

/* "What's a morph?" — 121x18. Padding grows the hit box, the equal negative
   margin cancels it again, so nothing moves and no neighbour is overlapped
   (the button stands alone on its line). */
.eb-morph-help {
    padding-block: 13px;
    margin-block: -13px;
}

@media (max-width: 899.98px) {
    /* The store name on an equipment card: 15px tall, and there are 24 of them
       in one screen of results. Grown downwards only — it eats the row's own
       8px bottom margin instead of overlapping the control above. */
    .eb-store-line .mud-link {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
    }

    /* ...but not on a listing card, where the seller sits inline in the meta sentence rather than
       owning a row of its own. A 44px block there inflated the whole row by 27px — most of the
       height this card was rebuilt to save. WCAG 2.2's inline exception covers it: the target's
       size is constrained by the line-height of the non-target text beside it. It still gets slop,
       taken as padding against an equal negative margin, so nothing moves. */
    .eb-card-meta .mud-link {
        display: inline;
        min-height: 0;
        padding-block: 6px;
        margin-block: -6px;
    }

    .eb-store-line.mb-2 {
        margin-bottom: 0 !important;
    }

    /* Filters scrolled away with the results, so refining meant scrolling back
       to the top of a long grid. 57px is the fixed app bar's height including
       its border — any larger and page content shows through the gap above.
       The container's gutters are empty (the card grid is inset by the same
       padding), so the pill needs no full-bleed backdrop to stay opaque. */
    .eb-filter-toggle {
        position: sticky;
        top: 57px;
        z-index: 5;
    }
}

/* --- /my/account: the identity rail ------------------------------------------------------- */
/* Deliberately not a card. The two form cards are the objects on this page; identity is the
   label beside them, so it stays on the page surface with a hairline rule doing the separating.
   A third bordered box would have read as a third editable thing. */

/* Identity leads on phones (it is first in the DOM) and sits right of the form on desktop, where
   MudGrid's flex row lets `order` do the swap without duplicating the markup. */
@media (min-width: 960px) {
    .eb-acct-main { order: 1; }
    .eb-acct-rail { order: 2; }

    .eb-acct-id {
        position: sticky;
        /* 57px app bar + the page's own top breathing room */
        top: 81px;
    }
}

.eb-acct-id-name {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 1.3rem;
    letter-spacing: -0.01em;
    line-height: 1.2;
    color: var(--eb-text);
    overflow-wrap: anywhere;
}

.eb-acct-id-handle {
    font-size: 0.86rem;
    color: var(--eb-muted);
    margin-top: 2px;
    overflow-wrap: anywhere;
}

.eb-acct-facts {
    margin: 16px 0 0;
    padding-top: 16px;
    border-top: 1px solid var(--eb-border);
    display: grid;
    gap: 12px;
}

.eb-acct-fact dt {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-muted);
    margin-bottom: 3px;
}

.eb-acct-fact dd {
    margin: 0;
    font-size: 0.9rem;
    color: var(--eb-body);
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
}

/* Long addresses at 4-col width would otherwise push the rail wider than its column. */
.eb-acct-email {
    overflow-wrap: anywhere;
    min-width: 0;
}

/* Same pill vocabulary as the bulk-listing state chips: 0.72rem, 700, full round, tinted fill. */
.eb-acct-flag {
    font-size: 0.72rem;
    font-weight: 700;
    padding: 2px 9px;
    border-radius: 999px;
    white-space: nowrap;
}

.eb-acct-flag--ok {
    color: var(--eb-success);
    background: color-mix(in srgb, var(--eb-success) 16%, transparent);
}

/* Caution reuses the welfare amber, not the brand orange: an unconfirmed address is a warning,
   and orange on this site means "act here". */
.eb-acct-flag--warn {
    color: var(--eb-welfare-accent);
    background: var(--eb-welfare-bg);
}

.eb-acct-note {
    margin: 16px 0 0;
    font-size: 0.8rem;
    line-height: 1.6;
    color: var(--eb-muted);
}

/* Explanatory line under a form section label or above a set of fields. */
.eb-acct-hint {
    margin: 0 0 14px;
    font-size: 0.82rem;
    line-height: 1.6;
    color: var(--eb-muted);
    max-width: 62ch;
}

.eb-acct-hint--lead {
    margin-bottom: 18px;
    color: var(--eb-body);
}

@media (max-width: 959.98px) {
    /* Stacked, the identity block needs to end before the first form card begins. */
    .eb-acct-id {
        padding-bottom: 4px;
    }
}

/* --- /my/account + app bar: the profile picture ------------------------------------------- */

.eb-acct-avatar-row {
    display: flex;
    align-items: center;
    gap: 14px;
}

.eb-acct-id-text { min-width: 0; }

.eb-acct-avatar {
    width: 72px;
    height: 72px;
    flex: none;
    border-radius: 50%;
    object-fit: cover;
    border: 1px solid var(--eb-border-2);
    background: var(--eb-photo-bg);
}

/* Initials, not a silhouette: an account with no photo still identifies its owner. Tinted
   rather than filled — this is an empty state, not something to act on. */
.eb-acct-avatar--empty {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 1.5rem;
    letter-spacing: -0.02em;
    color: var(--eb-orange-on-tint);
    background: var(--eb-tint-bg);
    border-color: var(--eb-tint-border);
}

.eb-acct-avatar-actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-top: 14px;
}

/* MudFileUpload wraps its trigger in a block-level div that would otherwise force the
   Remove button onto its own line. */
.eb-acct-avatar-actions .mud-file-upload { display: inline-flex; }

/* --- the app-bar account button ----------------------------------------------------------- */

.eb-account-nav-activator {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    color: rgba(255, 255, 255, 0.75);
    cursor: pointer;
}

.eb-account-nav-activator:hover,
.eb-account-nav-activator:focus-visible {
    color: var(--eb-on-photo);
    background: rgba(255, 255, 255, 0.08);
}

.eb-account-nav-avatar {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    object-fit: cover;
    /* Hairline, not a ring: the bar is Vivarium Black and a bare photo would bleed into it. */
    border: 1px solid var(--eb-border-2);
}

.eb-account-nav-avatar--lg {
    width: 40px;
    height: 40px;
}

/* The username row ABP contributes as a menu item is a header here, not a clickable row. */
.eb-account-nav-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 16px 12px;
    border-bottom: 1px solid var(--eb-border);
    margin-bottom: 4px;
}

.eb-account-nav-name {
    font-weight: 700;
    font-size: 0.92rem;
    color: var(--eb-text);
    overflow-wrap: anywhere;
}

/* The bar keeps its own light-on-dark treatment in both themes, so the activator's resting
   colour is overridden rather than tokenized. */
[data-theme="light"] .eb-account-nav-activator {
    color: rgba(255, 255, 255, 0.75);
}

/* The activator is a real <button>, so its UA chrome has to be cleared. */
.eb-account-nav-activator {
    appearance: none;
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
}

/* MudFileUpload is an input control: full width by default, and it lists the chosen file as a chip
   once picked. Neither belongs here — the avatar above IS the confirmation, and the file name of a
   picture you can already see is noise. */
.eb-acct-avatar-actions .mud-file-upload {
    display: inline-flex;
    width: auto;
    flex: none;
    margin: 0;
}

.eb-acct-avatar-actions .mud-file-upload-files {
    display: none;
}

/* --- seller licensing: the /my/store choice and the public badge ------------------------- */

.eb-licensing-choice {
    margin-bottom: 8px;
}

/* MudRadioGroup lays its options out in a row; a legal either/or where the second choice pulls in
   two more required fields reads better stacked, one decision per line. The rule has to sit on
   .mud-radio-group — the class parameter lands on the outer .mud-input-control, two levels up. */
.eb-licensing-choice .mud-radio-group.mud-radio-group {
    display: flex;
    flex-direction: column;
    /* Without this the column centres its items, indenting both options away from the section
       label they belong to. */
    align-items: flex-start;
    gap: 6px;
}

.eb-licensing-option {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 2px 0;
}

.eb-licensing-option-title {
    font-weight: 700;
    font-size: 0.92rem;
    color: var(--eb-text);
}

.eb-licensing-option-desc {
    font-size: 0.82rem;
    line-height: 1.5;
    color: var(--eb-muted);
}

/* Indented under the Licensed option so the two fields read as belonging to that choice rather
   than as two more independent questions. */
.eb-licensing-fields {
    border-left: 1px solid var(--eb-border);
    padding-left: 16px;
    margin: 4px 0 18px 12px;
}

/* The badge is a hairline pill, NOT a filled or green "verified" mark. bask does not check the
   number against any register, and a badge that looked like a verification would be a claim we
   cannot stand behind. Same reason it never uses the brand orange: orange means act here. */
.eb-licence-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 10px;
    border: 1px solid var(--eb-on-photo-dim);
    border-radius: 999px;
    font-weight: 700;
    font-size: 0.78rem;
}

.eb-licence-badge .mud-icon-root {
    font-size: 0.95rem;
}

/* The number itself, under the store name. The badge says what the seller is; this says what
   the Regulations actually require the advert to carry. */
/* Flex with a column gap, for the same reason as .eb-seller-licence on the listing card: the "·"
   glyphs that used to separate these segments began the next line whenever a segment wrapped. */
.eb-licence-line {
    margin-top: 8px;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: 10px;
    row-gap: 2px;
    font-size: 0.82rem;
    line-height: 1.6;
    color: var(--eb-on-photo-dim);
}

.eb-licence-line strong {
    color: var(--eb-on-photo);
    font-weight: 700;
}

/* Each licence segment wraps as one unit: "Newcastle upon Tyne City Council" broke after "City",
   stranding "Council" on a line of its own. A flex item still splits internally when it genuinely
   cannot fit, so nothing overflows. */
.eb-licence-part {
    display: inline-block;
}

/* Listing detail's seller strip sits on a card, not on the hero photo, so it uses the page
   tokens rather than the on-photo pair.
   Flex with a column gap, not "·" glyphs between the segments: a separator that travels with the
   segment after it leads the next line whenever that segment wraps — which is what the star rating
   did on this card — and one that stays with the segment before it strands at the end of a line.
   No CSS hides a separator that begins a line, so the gap is the separator. The store hero
   (.eb-licence-line) is built the same way. */
.eb-seller-licence {
    margin-top: 3px;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: 10px;
    row-gap: 2px;
    font-size: 0.78rem;
    line-height: 1.5;
    color: var(--eb-muted);
}

/* Council star rating. Never the review star's orange (--eb-orange-ink): the store hero already
   shows a buyer rating in orange stars, and two star scores in the same colour on one screen read
   as the same measure. Muted + an explicit "council rating" label keeps them apart. */
/* nowrap so the glyphs and their "council rating" label never split across lines — the label is
   what tells the two star scores on this page apart, and it is useless on its own. */
.eb-licence-stars {
    white-space: nowrap;
}

.eb-licence-stars-label {
    margin-inline-start: 5px;
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    opacity: 0.85;
}

/* ---- Sold listing: the honest tombstone -------------------------------------
   A sold animal keeps its page (PubliclyReachableAnimalSpecification). The photo stays in full
   colour — desaturating it would misrepresent the animal that was actually sold — and the band
   carries the whole status message. */
.eb-gallery-main--sold {
    position: relative;
}

.eb-sold-band {
    position: absolute;
    /* Above .eb-ld-photo's z-index:1 — without this the band paints under the photo and a sold
       animal reads as live stock. */
    z-index: 2;
    left: 0;
    right: 0;
    /* Across the lower third rather than dead centre: the head of a coiled snake is usually
       high in frame, and a centred band lands straight over it. */
    top: 62%;
    padding: 10px 0;
    background: rgba(12, 12, 14, 0.82);
    color: var(--eb-on-photo);
    font-family: var(--eb-font-display, inherit);
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    text-align: center;
}

/* On hold is a lighter statement than sold: the animal may come back, so the band sits over the
   photo without the finality of the sold slab. */
.eb-sold-band--hold {
    background: rgba(12, 12, 14, 0.62);
    font-size: 0.9rem;
}

.eb-hold-note {
    padding: 12px 14px;
    border: 1px solid var(--eb-hairline-strong);
    border-radius: 12px;
    background: var(--eb-surface-inset);
    color: var(--eb-body);
    font-size: 0.86rem;
    line-height: 1.55;
}

/* ---- Listing status on cards -----------------------------------------------------------------
   Reached only through the public status filter. Status is a fact about the animal, so the badge
   borrows the CB-year tag's dark-glass treatment rather than a colour: orange means "act on this",
   and a sold-only grid where every card wore an orange badge would say the opposite of the truth. */
.eb-status-badge {
    position: absolute;
    /* Top-LEFT, the same slot as the New and Pick badges it replaces in the markup. Top-right is the
       save heart's 44px target, and an on-hold card keeps its heart — the badge sat under it and the
       word was clipped to "ON H...". */
    top: 10px;
    left: 10px;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--eb-on-photo);
    background: rgba(12, 12, 14, 0.78);
    border-radius: 6px;
    padding: 4px 9px;
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
}

/* A closed card keeps its photo in full colour — the animal is still the record, and desaturating
   it would grey out an entire sold grid — but gives up the orange hover border, which reads as
   "this is available". */
.eb-card--closed:hover {
    border-color: var(--eb-hairline-strong);
}

.eb-price--closed {
    color: var(--eb-muted);
    font-weight: 700;
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
}

.eb-price-asked {
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-muted);
}

.eb-sold-ago {
    color: var(--eb-muted);
    font-size: 0.78rem;
    white-space: nowrap;
}

/* The phone card body is ~170px and the price row already carries "ASKED £525", so the sale date has
   nowhere to go on the same line — it ran off the card edge and was clipped mid-word. Below 560px it
   drops to its own full-width line under the price and the dot separator goes with it, since a
   leading "·" at the start of a new line separates nothing. */
@media (max-width: 560px) {
    .eb-price-asked { font-size: 0.66rem; }

    .eb-card-price-row { flex-wrap: wrap; row-gap: 2px; }
    .eb-card-price-row .eb-spec-dot { display: none; }

    .eb-sold-ago {
        flex-basis: 100%;
        font-size: 0.72rem;
        white-space: normal;
    }
}

/* The advertised price, struck. What it sold for is not published. */
.eb-price--sold {
    color: var(--eb-muted);
    text-decoration: line-through;
    text-decoration-thickness: 2px;
}

.eb-sold-note {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 14px 16px;
    border: 1px solid var(--eb-border);
    border-radius: 12px;
    background: var(--eb-surface-2);
}

.eb-sold-note-title {
    color: var(--eb-text);
    font-weight: 700;
}

.eb-sold-note-body {
    color: var(--eb-body);
    font-size: 0.92rem;
    line-height: 1.45;
}

/* Two routes onward, not one. Wraps on narrow columns rather than shrinking the labels. */
.eb-sold-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.eb-sold-actions .mud-button-root {
    flex: 1 1 auto;
}

/* ---- Enquiry dialog: the policy being agreed to, and the sent confirmation ---
   The checkbox used to ask for agreement to a policy collapsed further up the page. */
/* The seller's policy, quoted. One block used twice on the listing page: open on the keeper card,
   where it is reference a browsing buyer should not have to ask for, and collapsed in the inquiry
   dialog, where a 2000-character policy would otherwise push the consent checkbox and Send off the
   bottom of the panel. It replaces a MudExpansionPanels on the card, whose default 16px/24px header
   gutters indented the label past everything else on that card. */
.eb-policy-quote {
    padding: 12px 14px;
    margin-bottom: 12px;
    border: 1px solid var(--eb-border);
    border-radius: 10px;
    background: var(--eb-surface-2);
}

.eb-policy-quote-title {
    display: block;
    margin-bottom: 6px;
    color: var(--eb-muted);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

/* The height cap belongs to the body, not the box: on the collapsible form it would otherwise
   scroll the summary out of its own panel. */
.eb-policy-quote-body {
    max-height: 180px;
    overflow-y: auto;
    color: var(--eb-body);
    font-size: 0.9rem;
    line-height: 1.5;
    white-space: pre-line;
}

/* On the card the quote closes the keeper block, and the Report row below brings its own margin. */
.eb-policy-quote--card {
    margin: 10px 0 0;
}

/* A native <details>: keyboard-operable and screen-reader-announced for free, with no widget and
   no bound state field. The default marker is replaced by a chevron on the right edge so the row
   matches the disclosure affordances elsewhere on the page. */
.eb-policy-quote--collapsible > summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 0;
    cursor: pointer;
    list-style: none;
}

.eb-policy-quote--collapsible > summary::-webkit-details-marker {
    display: none;
}

.eb-policy-quote--collapsible > summary::after {
    content: "";
    width: 7px;
    height: 7px;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: translateY(-2px) rotate(45deg);
    transition: transform 140ms ease;
}

.eb-policy-quote--collapsible[open] > summary::after {
    transform: translateY(2px) rotate(-135deg);
}

.eb-policy-quote--collapsible[open] > summary {
    margin-bottom: 8px;
}

.eb-policy-quote--collapsible > summary:focus-visible {
    outline: 2px solid var(--eb-orange);
    outline-offset: 3px;
    border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
    .eb-policy-quote--collapsible > summary::after { transition: none; }
}

.eb-inquiry-sent {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 12px 8px 4px;
}

.eb-inquiry-sent-icon {
    margin-bottom: 12px;
    color: var(--eb-orange-ink);
    font-size: 2.4rem !important;
    height: 2.4rem;
    width: 2.4rem;
}

/* ---- The Exhibit: listing detail ---------------------------------------------
   The animal leads, the genetics answer, the breeder closes. Photo-first is not a
   slogan here — the hero was 4:3 capped at 520px beside a column of chrome, and on a
   phone it occupied a small fraction of a very long page. */
.eb-gallery-main {
    /* Taller cap and a squarer ratio: a coiled snake photographed from above is close to
       square, and 4:3 letterboxed most listings into grey bars down both sides. Still
       `contain` — cropping a listing photo would hide exactly the markings being sold. */
    aspect-ratio: 5 / 4;
    max-height: 680px;
}

.eb-gallery-main img {
    /* Was 95%, which floated the photo inside its own frame with a permanent hairline of
       backing showing top and bottom. The frame is the crop. */
    height: 100%;
}

/* ---- Genetics spine ---------------------------------------------------------
   One row per gene. Chips read as tags on a product; rows read as a statement of what
   this animal is, which is what a morph-first marketplace is for. */
.eb-genes {
    list-style: none;
    margin: 0 0 18px;
    padding: 0;
    border-top: 1px solid var(--eb-border);
}

.eb-gene {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 4px 12px;
    padding: 9px 0;
    border-bottom: 1px solid var(--eb-border);
}

/* Doubled class: global-styles.css styles every anchor inside body.abp-application-layout — which
   the Blazor host carries — at higher specificity than a bare class, so an unqualified rule here
   lost and every gene rendered orange. Five orange genes in a column is exactly the shouting the
   One Orange rule exists to stop; the accent belongs to the price and the CTA. */
.eb-gene .eb-gene-name.eb-gene-name {
    color: var(--eb-text);
    font-size: 1.02rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    text-decoration: none;
}

.eb-gene .eb-gene-name.eb-gene-name:hover,
.eb-gene .eb-gene-name.eb-gene-name:focus-visible {
    color: var(--eb-orange-ink);
    text-decoration: underline;
}

/* The advisory beside the gene that causes it, not exiled to a box about the listing. */
.eb-gene-flag {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    color: var(--eb-welfare-accent);
    font-size: 0.78rem;
    font-weight: 600;
    line-height: 1.3;
}

.eb-gene-flag .mud-icon-root {
    font-size: 1rem !important;
    height: 1rem;
    width: 1rem;
}

.eb-genes-outro {
    margin: -6px 0 18px;
    color: var(--eb-muted);
    font-size: 0.82rem;
    line-height: 1.5;
}

/* ---- Provenance ------------------------------------------------------------- */
/* Same inversion .eb-ld-card-title carried: an <h3> set uppercase and quieter than the rows it
   opens. The title voice, one step below the card heading above it. */
.eb-prov-title {
    margin: 0 0 12px;
    color: var(--eb-text);
    font-size: 0.9rem;
    font-weight: 800;
    letter-spacing: -0.01em;
}

.eb-prov-parent {
    display: flex;
    gap: 14px;
    padding: 9px 0;
    border-top: 1px solid var(--eb-border);
}

.eb-prov-parent:first-of-type {
    border-top: 0;
    padding-top: 0;
}

.eb-prov-role {
    /* Wide enough for "Last clutch laid" on one line — at 84px it wrapped and the value beside
       it stopped lining up with the rows above. */
    flex: 0 0 112px;
    color: var(--eb-muted);
    font-size: 0.86rem;
}

.eb-prov-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.eb-prov-name {
    color: var(--eb-text);
    font-weight: 600;
}

/* The parents' genes are context, not this animal's genetics — quieter than .eb-gene-name
   on purpose, so the two can never be misread for one another. */
.eb-prov-genes {
    color: var(--eb-body);
    font-size: 0.88rem;
    line-height: 1.45;
}

/* The buy rail was position:sticky here until the specifics moved in beneath it. Sticky does
   not push its later siblings, it slides over them, so a filled column and a sticky rail are
   mutually exclusive — and with the column filled the rail had ~160px of travel left, which is
   not worth an overlap bug. The floating action bar below keeps the CTA reachable instead. */

/* ---- Floating action bar ----------------------------------------------------
   Every width, not just phones: it replaces the sticky rail on desktop too. Hidden until the
   rail's own CTA leaves the viewport (the boot script in App.razor toggles --on against the
   [data-eb-rail-cta] sentinel), so the page never shows the same primary button twice — which
   it did at scroll 0 on a phone, price and all. Owns the bottom edge outright; CompareTray
   stands down on this route so the two never stack. */
.eb-buy-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1200;
    display: flex;
    align-items: center;
    gap: 18px;
    /* Full-bleed band, container-aligned contents: the bar spans the viewport so its top border
       reads as an edge, but the thumbnail lines up with the photo above it and the CTA with the
       right edge of the page rather than the right edge of the screen. Same arithmetic as
       .eb-container (max-width + 28px gutter), collapsing to a plain gutter below that width. */
    padding: 12px max(16px, calc((100% - var(--eb-container-max)) / 2 + 28px)) calc(12px + env(safe-area-inset-bottom));
    /* The chrome token, not the card token: this is the app bar's twin at the other edge of the
       screen, and it was reading as a card that happened to be stuck there. Theme-driven, so it is
       near-black on dark and white on light exactly as the top bar is. */
    background: var(--eb-bg);
    border-top: 1px solid var(--eb-border-2);
    /* An overlay separating from the page, in the overlay shadow vocabulary rather than a hairline
       of one (DESIGN.md: shadow is a response to state, and this is state). */
    box-shadow: 0 -12px 36px rgba(0, 0, 0, 0.42);
    /* Off-screen rather than display:none so it can slide, and so the CTA inside it is not a
       focusable target while it is hidden. */
    transform: translateY(110%);
    visibility: hidden;
    transition: transform 180ms ease-out, visibility 0s linear 180ms;
}

.eb-buy-bar--on {
    transform: translateY(0);
    visibility: visible;
    transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1), visibility 0s;
}

@media (prefers-reduced-motion: reduce) {
    .eb-buy-bar,
    .eb-buy-bar--on {
        transition: none;
    }
}

/* The bar's primary is the page's primary: the rail CTA's own 52px and 800 weight. It was a
   default MudButton at 37px — under the touch minimum, on the one control placed for a thumb. */
.eb-buy-bar .mud-button-root {
    flex: 0 0 auto;
    min-height: 52px;
    padding-inline: 30px;
    border-radius: 12px;
    font-size: 0.98rem;
    font-weight: 800;
    white-space: nowrap;
}

.eb-buy-bar-thumb {
    flex: 0 0 auto;
    width: 56px;
    height: 56px;
    border-radius: 12px;
    object-fit: cover;
    border: 1px solid var(--eb-border);
}

/* The only element allowed to give up space. min-width:0 because a flex item's automatic minimum
   size is its content, so without it the title would push the price and the CTA off the bar
   instead of ellipsing — the classic flexbox truncation trap. */
/* Floored, not free: with the strip shrinkable the flex algorithm would otherwise take the title
   to 0px between 620 and 780px - measured, an empty box where the animal's name goes. Below
   about 14 characters it identifies nothing, so the welfare cell ellipses before the title. */
.eb-buy-bar-info {
    flex: 1 2 auto;
    min-width: 120px;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.eb-buy-bar-title {
    color: var(--eb-text);
    font-weight: 800;
    font-size: 1.05rem;
    letter-spacing: -0.01em;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.eb-buy-bar-seller {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    color: var(--eb-muted);
    font-size: 0.8rem;
    font-weight: 600;
    line-height: 1.25;
}

.eb-buy-bar-store {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The licence as a tint chip rather than one more grey word: it is the fact in this line a buyer
   is actually scanning for. The trait chips' own fill, so nothing new enters the palette. */
.eb-buy-bar-licence {
    flex: 0 0 auto;
    padding: 2px 8px;
    border: 1px solid var(--eb-tint-border);
    border-radius: 999px;
    background: var(--eb-tint-bg);
    color: var(--eb-orange-on-tint);
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    white-space: nowrap;
}

/* ---- The condensed vitals strip ----------------------------------------------
   The same device as .eb-ld-stats further up the page: label over value, a hairline between
   cells. It fills the middle of the bar with the facts the strip itself carried, so scrolling
   past the strip no longer costs the buyer the numbers they are comparing.

   Shrinkable, and it gives width through the welfare cell: every other cell is rigid, so the
   strip degrades by ellipsing the one fact that can spare a character rather than by cutting
   three of them at once. */
.eb-buy-bar-vitals {
    flex: 0 1 auto;
    display: flex;
    align-items: center;
    min-width: 0;
}

.eb-buy-bar-stat {
    display: flex;
    flex-direction: column;
    gap: 1px;
    /* Rigid by default. Shrinking every cell in step turned a squeezed strip into three ellipsed
       fragments - "A... . 1,... . Woma / Hidden Ge..." - which says less than one whole fact. The
       strip sheds a cell at each tier instead, and only the cells below absorb what is left. */
    flex: 0 0 auto;
    min-width: 0;
    padding-inline: 13px;
    white-space: nowrap;
}

.eb-buy-bar-stat + .eb-buy-bar-stat {
    border-left: 1px solid var(--eb-border);
}

.eb-buy-bar-stat-label {
    color: var(--eb-muted);
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.eb-buy-bar-stat-value {
    display: inline-flex;
    align-items: center;
    /* No gap: a flex container puts one between the number and its unit span too, and "491 g" is
       not how a weight is written. The two icons carry their own margin instead. */
    gap: 0;
    /* Row-direction cells (below 1360px) give the value an automatic minimum of its own content,
       which is what let the gene name overflow its cell and sit on top of the price. */
    min-width: 0;
    color: var(--eb-text);
    font-size: 0.95rem;
    font-weight: 800;
    letter-spacing: -0.01em;
    font-variant-numeric: tabular-nums;
}

.eb-buy-bar-stat-unit {
    margin-left: 1px;
    color: var(--eb-muted);
    font-size: 0.78em;
    font-weight: 700;
}

/* The reserved sex hues, on the value only — the labels stay in the muted voice so the row of
   them reads as one line (DESIGN.md, Recorded exception — sex colours). */
.eb-buy-bar-stat--sex.eb-sex--m .eb-buy-bar-stat-value {
    color: var(--eb-sex-m);
}

.eb-buy-bar-stat--sex.eb-sex--f .eb-buy-bar-stat-value {
    color: var(--eb-sex-f);
}

.eb-buy-bar-sex.eb-buy-bar-sex {
    flex: 0 0 auto;
    margin-left: -3px;
    margin-right: 3px;
    font-size: 1.15rem !important;
    width: 1.15rem;
    height: 1.15rem;
    filter: drop-shadow(0 0 6px color-mix(in srgb, currentColor 45%, transparent));
}

/* Welfare amber owns its whole cell, label included: a hereditary-defect warning is not a data
   point sharing a voice with the others. */
.eb-buy-bar-stat--warn .eb-buy-bar-stat-label,
.eb-buy-bar-stat--warn .eb-buy-bar-stat-value {
    color: var(--eb-welfare-accent);
}

.eb-buy-bar-warn-ico.eb-buy-bar-warn-ico {
    flex: 0 0 auto;
    margin-right: 4px;
    font-size: 1.05rem !important;
    width: 1.05rem;
    height: 1.05rem;
}

/* Every cell's text ends in an ellipsis rather than being cut mid-word by the cell's own clip -
   "Adult" became "Ad" and "491g" became "1". The welfare cell is why it matters most: a gene name
   has no upper bound ("Hidden Gene Woma" is 214px of a 100px box on a phone), and the amber plus
   the first word surviving at any size is what keeps the flag a disclosure. */
.eb-buy-bar-stat--warn,
.eb-buy-bar-stat--soft {
    flex: 0 1 auto;
}

.eb-buy-bar-stat-text,
.eb-buy-bar-stat-word {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.eb-buy-bar-money {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    line-height: 1.1;
    padding-left: 6px;
}

.eb-buy-bar-price {
    color: var(--eb-orange-ink);
    font-size: 1.7rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.025em;
}

/* The struck original, so the bar carries the saving and not just the number — a lone "£530"
   over a sale listing reads as full price. */
.eb-buy-bar-was {
    color: var(--eb-muted);
    font-size: 0.82rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    text-decoration: line-through;
}

.eb-buy-bar-save {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 52px;
    height: 52px;
    border-radius: 12px;
    border: 1px solid var(--eb-border-2);
    background: transparent;
    color: var(--eb-body);
    cursor: pointer;
    transition: border-color 140ms ease, color 140ms ease;
}

.eb-buy-bar-save:hover {
    border-color: var(--eb-orange);
    color: var(--eb-text);
}

.eb-buy-bar-save--on {
    color: var(--eb-orange);
    border-color: var(--eb-orange);
}

/* ---- Narrowing: the same cells, collapsed to one line ------------------------
   Below the width that fits label-over-value, the cells shed their labels, dividers and padding
   and run inline with a separator — which is the line this bar carried before it had cells at
   all. One set of markup, two presentations; the alternative was a second copy of the same facts
   hidden at the opposite breakpoint. */
@media (max-width: 1359.98px) {
    .eb-buy-bar {
        gap: 14px;
    }

    .eb-buy-bar-vitals {
        gap: 0;
    }

    .eb-buy-bar-stat {
        flex-direction: row;
        align-items: baseline;
        gap: 0;
        padding-inline: 0;
        /* Whatever else happens, a cell paints inside its own box. This is the backstop under the
           ellipsis rules, not a substitute for them. */
        overflow: hidden;
    }

    .eb-buy-bar-stat + .eb-buy-bar-stat {
        border-left: 0;
    }

    .eb-buy-bar-stat + .eb-buy-bar-stat::before {
        content: "\00b7";
        margin: 0 7px;
        color: var(--eb-muted);
        font-weight: 700;
    }

    .eb-buy-bar-stat-label {
        display: none;
    }

    .eb-buy-bar-stat-value {
        font-size: 0.82rem;
        font-weight: 700;
        color: var(--eb-muted);
    }

    /* Born and delivery are the two the price row still states in full a short scroll away; the
       sex word goes with them and leaves the glyph, which is the fact at a glance. */
    .eb-buy-bar-stat--born,
    .eb-buy-bar-stat--delivery {
        display: none;
    }

    .eb-buy-bar-stat--sex .eb-buy-bar-stat-word {
        display: none;
    }

    .eb-buy-bar-sex.eb-buy-bar-sex {
        margin-left: 0;
        font-size: 1.05rem !important;
        width: 1.05rem;
        height: 1.05rem;
    }

    .eb-buy-bar-price {
        font-size: 1.45rem;
    }

    .eb-buy-bar-title {
        font-size: 0.98rem;
    }
}

/* ---- Shedding, one cell at a time -------------------------------------------
   Between the labelled strip and the phone layout the row cannot hold three cells, and three
   ellipsed fragments say less than one whole fact. So it sheds instead, in the order the price
   row a short scroll up already restates: maturity, then weight. The welfare flag never goes.

   A hidden cell stays in the sibling chain, so the separator rules keep firing across the gap it
   leaves - which is why the row led with a lone middot on every unsexed listing. */
@media (max-width: 899.98px) {
    .eb-buy-bar-stat--maturity {
        display: none;
    }

    .eb-buy-bar-vitals:not(:has(.eb-buy-bar-stat--sex)) .eb-buy-bar-stat--weight::before {
        content: none;
    }
}

@media (max-width: 699.98px) {
    .eb-buy-bar-stat--weight {
        display: none;
    }

    .eb-buy-bar-vitals:not(:has(.eb-buy-bar-stat--sex)) .eb-buy-bar-stat--warn::before {
        content: none;
    }
}

/* Under ~620px one row cannot carry it: the controls and the price need ~300px, which left the
   title too little to identify anything. So the title takes a row of its own and the vitals drop
   down beside the price. `display: contents` on the wrapper is what allows this — title and
   seller become direct children of the bar and can be placed independently. */
@media (max-width: 619.98px) {
    .eb-buy-bar {
        flex-wrap: wrap;
        gap: 8px 12px;
        padding-left: 14px;
        padding-right: 14px;
    }

    .eb-buy-bar-info {
        display: contents;
    }

    .eb-buy-bar-title {
        flex: 1 0 100%;
        order: -1;
    }

    .eb-buy-bar-seller {
        display: none;
    }

    /* Basis 0, not auto. Flex assigns items to lines by their hypothetical size before it shrinks
       anything, so a vitals line whose natural width is ~130px pushed the CTA onto a third row and
       made the bar 139px tall. At basis 0 it contributes nothing to that decision, then grows into
       whatever the price and the controls leave. */
    .eb-buy-bar-vitals {
        flex: 1 1 0;
        min-width: 0;
        overflow: hidden;
    }

    .eb-buy-bar .mud-button-root {
        padding-inline: 22px;
    }
}

@media (max-width: 459.98px) {
    /* The photo is the only part that competes for width without adding a word. */
    .eb-buy-bar-thumb {
        display: none;
    }

    /* Every remaining pixel on the control row goes to the welfare flag, which is the last thing
       in the strip still standing at this width. */
    .eb-buy-bar .mud-button-root {
        padding-inline: 16px;
    }

    .eb-buy-bar-price {
        font-size: 1.3rem;
    }
}

/* The reserve under the page, so the bar never covers the last of the content.

   :not(.eb-nav-inner) because the top nav's wrapper is also an .eb-container — it wants the
   max-width and the centring, not the page's bottom reserve. Unscoped, this put 124px of padding
   inside a 56px toolbar; padding does not compress, so the wrapper grew to 124px, the toolbar's
   align-items:center placed it at (56-124)/2 = -34, and every control in the bar rendered at
   y=-56 — above the top of the screen. The bar kept its height and its background, so it read as
   a navigation that had vanished, on listing pages only (the only ones with a buy bar) and only
   once the bar switched on, which on a phone is at scroll 0. */
body:has(.eb-buy-bar--on) .eb-container:not(.eb-nav-inner) {
    padding-bottom: 108px;
}

@media (max-width: 619.98px) {
    /* Two rows instead of one, so the reserve underneath grows to match. */
    body:has(.eb-buy-bar--on) .eb-container:not(.eb-nav-inner) {
        padding-bottom: 124px;
    }
}

/* At 320px the control row was two pixels over and wrapped to a third line - 163px of a 568px
   screen. Nothing is dropped; the two controls go to their 44px touch floor instead. */
@media (max-width: 359.98px) {
    .eb-buy-bar {
        gap: 8px 10px;
    }

    .eb-buy-bar .mud-button-root {
        padding-inline: 12px;
    }

    .eb-buy-bar-save {
        width: 44px;
        height: 44px;
    }

    /* Only the flag itself fits here - the gene name was down to two clipped letters, and the row
       was squeezing the amber triangle out with them. It holds its width now; the advisory is
       stated in full a short scroll up the page. */
    .eb-buy-bar-stat--warn {
        flex: 0 0 auto;
    }

    .eb-buy-bar-stat--warn .eb-buy-bar-stat-text {
        display: none;
    }

    .eb-buy-bar-warn-ico.eb-buy-bar-warn-ico {
        margin-right: 0;
    }

    /* The last few pixels the flag needs to fit whole. */
    .eb-buy-bar-money {
        padding-left: 0;
    }
}

/* ---- Short viewports: a phone held sideways ---------------------------------
   Keyed on height, not width: the same 852px viewport is roomy in portrait and 393px tall here,
   where the bar was taking a fifth of the screen above the photo that is the reason to be
   sideways at all. Everything compresses except the 44px touch floor on the two controls. */
@media (max-height: 480px) {
    .eb-buy-bar {
        gap: 12px;
        padding-top: 8px;
        padding-bottom: calc(8px + env(safe-area-inset-bottom));
    }

    .eb-buy-bar-thumb {
        width: 44px;
        height: 44px;
        border-radius: 10px;
    }

    .eb-buy-bar .mud-button-root {
        min-height: 44px;
        padding-inline: 20px;
    }

    .eb-buy-bar-save {
        width: 44px;
        height: 44px;
    }

    .eb-buy-bar-price {
        font-size: 1.3rem;
    }

    /* First to go: the store name is a screen up the page, and this row is a decision rather
       than an introduction. */
    .eb-buy-bar-seller {
        display: none;
    }

    body:has(.eb-buy-bar--on) .eb-container:not(.eb-nav-inner) {
        padding-bottom: 84px;
    }
}

/* ---- Bulk-add lineage heading ----------------------------------------------- */
.eb-bulk-lineage-head {
    margin-top: 4px;
    color: var(--eb-muted);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
}

/* ---- Listing detail: the two orders ----------------------------------------
   Phone: photo, decision block, evidence — source order, single column.
   Desktop: photo above evidence in one column, the decision block beside them spanning
   both rows. The span is what makes the sticky rail work: a grid item is only as tall as
   its own content, so a rail in a content-height item has nowhere to travel and sticky is
   a no-op. Spanning both rows gives it the full height of the page's main area. */
.eb-listing-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 16px;
}

/* A grid item's automatic minimum size is its CONTENT, not zero, so an item whose contents are
   wider than the track does not shrink — it overflows the track and keeps its own width. That is
   what put the whole column at 399px inside a 351px track on a 393px phone: the gallery's <img>
   is sized height-first (height:100% against an aspect-ratio box), which makes its min-content
   wider than the column, and every sibling inherited the blown-out width.
   `min-width: 0` opts each column out of that minimum so the track governs instead. Applied to the
   items rather than the track because the track was never the problem — it measured 351 correctly
   the whole time while its own children rendered past it. */
.eb-col-photo,
.eb-col-rail,
.eb-col-evidence {
    min-width: 0;
}

@media (min-width: 960px) {
    .eb-listing-grid {
        /* 1.75/1 rather than the old 7/5. The narrow column now holds cards instead of a lone
           buy box, and it needs less width than 5fr gave it — which buys the photo ~70px. The
           animal is the hero; the specifics beside it are not. */
        grid-template-columns: minmax(0, 1.75fr) minmax(320px, 1fr);
        /* The rail spans both rows, and when it is the taller column grid shares its surplus out
           across every row it spans — proportionally, so ~95px landed in row 1 and pushed the
           description that far off the bottom of the photo. Measured: rows came out 771/306 for a
           676px photo and 211px of description.
           The fix is the `1fr`, not the `auto`. Surplus from a spanning item goes to tracks whose
           max sizing function is intrinsic, and min-content/max-content/auto all qualify — so
           pinning row 1 to its content does nothing on its own (measured: still 771/306). A
           flexible track is not intrinsic, so once row 2 is 1fr it takes the whole surplus and
           row 1 collapses to exactly the photo. align-items:start keeps each item at the top of
           its own row, so the extra height falls below the last card where it cannot be seen. */
        grid-template-rows: auto 1fr;
        align-items: start;
    }

    .eb-col-photo    { grid-column: 1; grid-row: 1; }
    .eb-col-evidence { grid-column: 1; grid-row: 2; }
    /* Still spans both rows, but no longer stretched to fill them: nothing sticks inside it now,
       so a forced height would only re-create the empty space this change removed. */
    .eb-col-rail     { grid-column: 2; grid-row: 1 / span 2; }
}

/* ---- Item title (equipment) -------------------------------------------------
   A product title is an identifier, not a headline. Equipment titles carry brand, model,
   dimension and often a second product ("… 4ft Oak with Habistat Dimming Thermostat"), so at the
   headline scale they took four lines and 131px on a 380px phone — the price and the primary
   action were pushed toward the fold on a shorter handset, and the identifier was the loudest
   thing on a page whose only question is whether £185 is worth it. Animal titles are morph names
   and never hit this, which is why the reduction is scoped to the class rather than applied to
   .eb-page-title. */
.eb-item-title {
    /* Product names carry unbreakable tokens — codes, dimensions, hyphenated model names — that a
       normal wrap cannot split. Anywhere, not break-word: it only breaks a token that would
       otherwise overflow, so ordinary words still wrap at their spaces. */
    overflow-wrap: anywhere;
}

@media (max-width: 560px) {
    .eb-item-title {
        font-size: 1.55rem;
        line-height: 1.14;
    }
}

/* ---- Touch targets on the listing decision column ----------------------------
   Coarse pointers only, so the desktop composition keeps its metrics — the same bargain the
   store page and the filter bar already strike. These three sat at 20px, 30px and 17px tall:
   the ones a buyer reaches for after deciding (visit the seller, report the listing) and the
   one they reach for to go back. */
@media (pointer: coarse) {
    .eb-seller-visit {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
        padding-inline: 4px;
    }

    .eb-report-row .mud-button-root,
    .eb-report-row button {
        min-height: 44px;
    }

    .eb-crumb a {
        display: inline-block;
        padding-block: 10px;
    }

    /* Measured at 393px: 39.9px, 39.1px and 34.1px. All three are things a thumb reaches for —
       save it, open its morph page, step to the next animal. */
    .eb-save-btn,
    .eb-detail-nav-btn,
    .eb-ld-genes .eb-ld-gene.eb-ld-gene {
        min-height: 44px;
    }
}

/* ---- Listing details: vitals, facts, terms -----------------------------------
   Three tiers with deliberately different densities, replacing one 13-row table that
   used a single 8px interval for everything and spent 42% of its width on labels that
   restated their own values. Rhythm here is the point: the vitals breathe, the facts
   sit tight, the terms compress to a single run. */
.eb-vitals {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    /* Column gap and the dot's own margin are equal on purpose: the separator has to sit
       optically centred between two items, and 22px before / 16px after glued every dot to
       the item on its right. */
    gap: 10px 15px;
    margin: 0 0 16px;
    padding: 0 0 16px;
    list-style: none;
    border-bottom: 1px solid var(--eb-border);
}

.eb-vital {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--eb-text);
    font-size: 1.02rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    /* Weights and years stack visually across listings a buyer is comparing. */
    font-variant-numeric: tabular-nums;
}

/* Separators are drawn, not typed: a middot character between flex items would be a
   real DOM node that screen readers announce and that wraps to a line of its own. */
.eb-vital + .eb-vital::before {
    content: "";
    width: 3px;
    height: 3px;
    margin-right: 15px;
    border-radius: 50%;
    background: var(--eb-border-2);
}

/* "CB" and "g" are units, not values — they carry the meaning that lets the number go
   unlabelled, so they stay legible but step back from the figure itself. */
.eb-vital-pre,
.eb-vital-unit {
    color: var(--eb-muted);
    font-size: 0.82rem;
    font-weight: 600;
}

.eb-vital-pre { margin-right: 1px; }
.eb-vital-unit { margin-left: 2px; }

/* Facts that need naming. Two-up once there is room, so five rows read as three lines. */
.eb-facts {
    display: grid;
    grid-template-columns: 1fr;
    gap: 8px 24px;
    margin: 0;
}

@media (min-width: 600px) {
    .eb-facts {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

.eb-fact {
    display: flex;
    align-items: baseline;
    gap: 10px;
    min-width: 0;
}

.eb-fact dt {
    flex: 0 0 auto;
    color: var(--eb-muted);
    font-size: 0.82rem;
    font-weight: 600;
}

.eb-fact dd {
    margin: 0;
    min-width: 0;
    color: var(--eb-text);
    font-size: 0.9rem;
    font-weight: 600;
}

/* The listing's own terms and housekeeping: conditions and dates, read once and never
   compared, so they get the quietest tier and no row structure at all. */
.eb-listing-terms {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 14px;
    margin: 18px 0 0;
    padding-top: 14px;
    border-top: 1px solid var(--eb-border);
    color: var(--eb-muted);
    font-size: 0.78rem;
    line-height: 1.5;
}

.eb-listing-terms-quiet {
    opacity: 0.72;
}

/* Vitals sitting directly under a page title rather than inside a details card: no bottom rule,
   because the price block below already separates it. Used on equipment, where the strip replaced
   an eyebrow above the heading. */
/* Schibsted's tabular figures give the thousands comma a full digit cell, so
   "1,653" set tabular reads as "1 , 653". These vitals sit in one inline run —
   nothing aligns beneath them — so the detail page trades the column discipline
   back for a comma that touches its digits. The cards keep tabular. */
.eb-vitals--tight .eb-vital {
    font-variant-numeric: lining-nums;
}

.eb-vitals--tight {
    margin-bottom: 14px;
    padding-bottom: 0;
    border-bottom: 0;
}

/* ==========================================================================
   Listing detail — the dossier split (2026-08-26)
   --------------------------------------------------------------------------
   Everything a buyer reads runs down the left — name, genes, stat strip, the
   price and the enquiry, then the cards — while the gallery owns the right:
   the photo over a strip of thumbnails. Three grid items (stage, thumbs, info),
   and the DOM order is the phone order — photo, thumbs, everything else — so
   the phone needs no re-ordering.

   Equipment detail still uses .eb-listing-grid / .eb-col-* above; these classes
   are deliberately separate rather than a rename, because an equipment photo is
   a product shot with no genetics column to sit beside.
   ========================================================================== */

.eb-ld {
    display: flex;
    flex-direction: column;
    gap: 18px;
    margin-top: 18px;
}

/* ---- The stage: photo, its own light, and the gallery controls ----------- */

.eb-ld-stage {
    position: relative;
    overflow: hidden;
    min-height: 0;
    aspect-ratio: 4 / 3;
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    background-color: var(--eb-photo-bg);
}

/* The gallery is the stage: a listing carries at most four photos and they sit side by side in
   one native scroller, so a phone swipes them with the browser's own gesture and no JS drives the
   movement. scroll-snap-stop keeps one swipe to one photo — with four of them, flicking past two
   at a time loses the animal you were looking at. The scrollbar is hidden because the count badge
   and the thumbnail strip already say where you are. */
.eb-ld-rail {
    position: absolute;
    inset: 0;
    display: flex;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
}

.eb-ld-rail::-webkit-scrollbar {
    display: none;
}

.eb-ld-slide {
    position: relative;
    flex: 0 0 100%;
    overflow: hidden;
    scroll-snap-align: center;
    scroll-snap-stop: always;
}

/* The animal's own colour thrown across the column behind it. The photo itself
   stays `contain` — cropping hides the markings being sold — so without this a
   contained photo sits in a slab of flat grey wherever the column is taller than
   the image. Scaled past the edges so the blur has no visible border. Scoped to its own
   slide, so the backdrop travels with the photo instead of needing to be kept in step. */
.eb-ld-amb {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transform: scale(1.18);
    filter: blur(44px) saturate(1.25) brightness(0.5);
    /* The photo overlay stays dark in both themes (DESIGN.md, The Two-Theme
       Rule), so this is not re-lit for the light theme. */
}

.eb-ld-photo {
    position: relative;
    z-index: 1;
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
}

.eb-ld-nophoto {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    min-height: 240px;
}

/* The photo opens the viewer; the button is the visible promise of that. It takes
   the corner the count used to hold, and the count steps left. */
.eb-ld-zoom {
    position: absolute;
    z-index: 3;
    right: 16px;
    bottom: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 1px solid rgba(255, 255, 255, 0.28);
    border-radius: 999px;
    background: rgba(12, 12, 14, 0.55);
    color: var(--eb-on-photo);
    cursor: pointer;
    transition: border-color 140ms ease, color 140ms ease;
}

.eb-ld-zoom:hover,
.eb-ld-zoom:focus-visible {
    border-color: var(--eb-orange-on-dark);
    color: var(--eb-orange-on-dark);
}

.eb-ld-photo {
    cursor: zoom-in;
}

/* Under the stage, the whole strip fluid: four to a row at every width, so the
   thumbnails read as the gallery's index rather than floating chrome. */
.eb-ld-thumbs {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 12px;
}

.eb-ld-thumb {
    aspect-ratio: 4 / 3;
    padding: 0;
    overflow: hidden;
    border: 1px solid var(--eb-border-2);
    border-radius: 10px;
    background: var(--eb-photo-bg);
    cursor: pointer;
    transition: border-color 140ms ease, transform 140ms ease;
}

.eb-ld-thumb img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.eb-ld-thumb:hover {
    border-color: var(--eb-orange);
    transform: translateY(-2px);
}

.eb-ld-thumb--on {
    border: 2px solid var(--eb-orange);
}

/* Mono, like the CB-year tag on the cards: a count is measurement, not prose. */
.eb-ld-count {
    position: absolute;
    z-index: 3;
    /* Bottom-left: the zoom button owns the right corner. */
    left: 16px;
    bottom: 16px;
    padding: 4px 10px;
    border-radius: 6px;
    background: rgba(12, 12, 14, 0.7);
    backdrop-filter: blur(3px);
    color: var(--eb-on-photo);
    font-family: ui-monospace, Menlo, monospace;
    font-size: 0.78rem;
    font-weight: 700;
}

/* ---- The info column: everything the buyer reads ------------------------- */

.eb-ld-info {
    display: flex;
    flex-direction: column;
    gap: 16px;
    min-width: 0;
}

.eb-ld-info .eb-page-title {
    margin: 0;
}

/* The vitals as a stat strip: label over value, hairlines above and below, a
   divider between cells. Values 800 like the price — these are the three facts
   a buyer filters on. */
.eb-ld-stats {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    border-top: 1px solid var(--eb-border);
    border-bottom: 1px solid var(--eb-border);
}

.eb-ld-stat {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    padding: 14px 20px;
}

.eb-ld-stat:first-child {
    padding-left: 0;
}

.eb-ld-stat + .eb-ld-stat {
    border-left: 1px solid var(--eb-border);
}

.eb-ld-stat-label {
    color: var(--eb-muted);
    font-size: 0.74rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.eb-ld-stat-value {
    color: var(--eb-text);
    font-size: 1.3rem;
    font-weight: 800;
    letter-spacing: -0.01em;
    white-space: nowrap;
}

.eb-ld-stat-unit {
    margin-left: 2px;
    font-size: 0.78em;
    font-weight: 700;
    color: var(--eb-muted);
}

/* The sex keeps its hue from the cards. Only sex carries these two modifiers, so laying the value
   out as a row here affects nothing else in the strip. */
.eb-ld-stat-value--m,
.eb-ld-stat-value--f {
    display: inline-flex;
    align-items: center;
    gap: 3px;
}

.eb-ld-stat-value--m {
    color: var(--eb-sex-m);
}

.eb-ld-stat-value--f {
    color: var(--eb-sex-f);
}

/* Sized against the word beside it, not to MudBlazor's icon scale: at the default the mark sat
   well under the cap height of a 1.3rem/800 value and read as an afterthought. The negative left
   margin removes the icon's own side bearing so the row starts on the label's edge above it. The
   glow is the cards' treatment, in currentColor so it follows the theme's sex tokens rather than
   pinning a dark-theme blue and pink. */
.eb-ld-sex-ico.eb-ld-sex-ico {
    flex: 0 0 auto;
    margin-left: -4px;
    font-size: 1.6rem !important;
    width: 1.6rem;
    height: 1.6rem;
    filter: drop-shadow(0 0 7px color-mix(in srgb, currentColor 45%, transparent));
}

/* ---- The decision row --------------------------------------------------- */

.eb-ld-buy {
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-width: 0;
}

/* Price on the left, the enquiry pushed to the right, Save beside it. */
.eb-ld-buy-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px 16px;
}

.eb-ld-price {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.eb-ld-price-line {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 12px;
}

.eb-ld-ship {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    margin-top: -4px;
    color: var(--eb-muted);
    font-size: 0.84rem;
}

/* The how-a-live-animal-moves sentence, behind an info glyph (MudTooltip). */
.eb-ld-ship-info {
    display: inline-flex;
    align-items: center;
    color: var(--eb-muted);
    cursor: help;
}

.eb-ld-ship-info .mud-icon-root {
    font-size: 1rem !important;
    width: 1rem;
    height: 1rem;
}

.eb-ld-ship-info:hover,
.eb-ld-ship-info:focus-visible {
    color: var(--eb-text);
}

/* MudButton Large stops at 42px; the decision gets the mock's 52 and squarer
   corners than the pill controls around it. Doubled class outranks MudBlazor's
   own .mud-button-root radius. */
.eb-ld-buy-row .eb-ld-cta.eb-ld-cta {
    height: 52px;
    margin-left: auto;
    padding: 0 36px;
    border-radius: 12px;
    font-size: 1rem;
    font-weight: 800;
    white-space: nowrap;
}

/* ---- The meta row: social proof left, share targets right ---------------- */

.eb-ld-metarow {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 14px;
}

.eb-ld-meta {
    color: var(--eb-muted);
    font-size: 0.8rem;
    font-weight: 600;
}

.eb-ld-metarow .eb-share {
    margin-left: auto;
}

.eb-ld-card {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 18px 22px;
    border: 1px solid var(--eb-border);
    border-radius: 16px;
    background: var(--eb-surface);
}

.eb-ld-card--keeper {
    gap: 4px;
}

/* A heading, and it has to look like one. It was set in the uppercase data-label voice, which put
   an <h2> at 11.5px muted BELOW the 15px body text it introduces — the heading/body relationship
   inverted, and uppercase on a heading is the one thing The No-Shouting Rule names outright. The
   title voice instead: heavy, tight, sentence case. */
.eb-ld-card-title {
    margin: 0;
    color: var(--eb-text);
    font-size: 1rem;
    font-weight: 800;
    letter-spacing: -0.01em;
}

/* The second label inside the Details card, opening the keeper's own words. */
.eb-ld-card-title--desc {
    margin-top: 12px;
}

.eb-ld-genes {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Doubled class: global-styles.css styles every anchor inside
   body.abp-application-layout — which the Blazor host carries — at higher
   specificity than a bare class, so an unqualified rule loses and the gene text
   renders link-orange. Neutral at rest, orange on hover — DESIGN.md's recorded
   exception for this page. An orange hairline on every pill put five points of
   orange in the same 250px band as the one orange CTA, which is the One Orange
   Rule losing exactly where it is meant to be doing its work. */
.eb-ld-genes .eb-ld-gene.eb-ld-gene {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 8px 18px;
    border: 1px solid var(--eb-border-2);
    border-radius: 999px;
    color: var(--eb-text);
    font-size: 0.95rem;
    font-weight: 700;
    white-space: nowrap;
    text-decoration: none;
    transition: border-color 140ms ease, color 140ms ease;
}

.eb-ld-genes .eb-ld-gene.eb-ld-gene:hover,
.eb-ld-genes .eb-ld-gene.eb-ld-gene:focus-visible {
    border-color: var(--eb-orange);
    color: var(--eb-orange-ink);
}

.eb-ld-genes .eb-ld-gene--flagged.eb-ld-gene--flagged {
    border-color: color-mix(in srgb, var(--eb-welfare-accent) 55%, transparent);
    color: var(--eb-welfare-accent);
}

/* The inheritance mode, in the data-label voice: 0.72rem against the gene name's 0.95rem, so size
   alone keeps the name the thing you read first. The full term rides in the pill's title attribute
   (GeneLinkTitle) — the abbreviation is aria-hidden, and a sighted reader needs the word too. */
.eb-ld-genes .eb-ld-gene-mode {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-muted);
}

/* Recessive is the only mode with a hue — see --eb-recessive-accent. Doubled class for the same
   reason the base pill has one. */
.eb-ld-genes .eb-ld-gene--rec.eb-ld-gene--rec {
    border-color: color-mix(in srgb, var(--eb-recessive-accent) 55%, transparent);
}

.eb-ld-genes .eb-ld-gene--rec .eb-ld-gene-mode {
    color: var(--eb-recessive-accent);
}

/* A pill is only ever one colour. Welfare amber and the hover orange own the whole pill, tag
   included — a two-tone pill would read as two facts. Welfare outranks the recessive hue in the
   razor rather than here: both classes compute to (0,3,0), so source order would decide it, and a
   hereditary-defect warning must not be one refactor away from losing to a genetics label. */
.eb-ld-genes .eb-ld-gene--flagged .eb-ld-gene-mode,
.eb-ld-genes .eb-ld-gene.eb-ld-gene:hover .eb-ld-gene-mode,
.eb-ld-genes .eb-ld-gene.eb-ld-gene:focus-visible .eb-ld-gene-mode {
    color: inherit;
}

.eb-ld-genes .eb-ld-gene .mud-icon-root {
    font-size: 1rem !important;
    width: 1rem;
    height: 1rem;
}

/* The advisory names the gene that causes it. A pill carries the glyph; the
   sentence sits directly beneath the row it belongs to, so it stays a fact about
   Spider rather than boilerplate about the listing. */
.eb-ld-advisory {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.eb-ld-advisory li {
    display: flex;
    align-items: flex-start;
    gap: 7px;
    color: var(--eb-welfare-accent);
    font-size: 0.84rem;
    line-height: 1.45;
}

.eb-ld-advisory .mud-icon-root {
    flex: 0 0 auto;
    margin-top: 1px;
    font-size: 1rem !important;
    width: 1rem;
    height: 1rem;
}

.eb-ld-advisory-outro {
    margin: -4px 0 0;
    color: var(--eb-muted);
    font-size: 0.8rem;
    line-height: 1.5;
}

.eb-ld-facts {
    margin-top: 2px;
}

.eb-ld-lineage {
    margin-top: 4px;
    padding-top: 14px;
    border-top: 1px solid var(--eb-border);
}

.eb-ld-card--keeper .eb-seller-strip {
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
}

.eb-report-row--keeper {
    margin-top: 8px;
}

.eb-ld-desc {
    max-width: 70ch;
    margin: 2px 0 0;
    color: var(--eb-body);
    font-size: 0.95rem;
    line-height: 1.7;
    /* The keeper's own line breaks are content: they list feeding dates and
       sheds one per line, and collapsing them runs it into a paragraph. */
    white-space: pre-line;
}

.eb-ld > .eb-listing-terms {
    margin-top: 0;
}

/* ---- Desktop: the split — info left, gallery right ----------------------- */

@media (min-width: 960px) {
    .eb-ld {
        display: grid;
        /* 1 : 1.1 — every pixel the gallery gains is the animal larger, but the
           info column holds the reading, so it keeps near-parity. */
        grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
        /* Stage, thumbs, then a 1fr spacer row: the info column spans all three,
           so a column of prose taller than the gallery grows downward rather
           than stretching the photo's frame. */
        grid-template-rows: auto auto 1fr;
        column-gap: 48px;
        row-gap: 12px;
        margin-top: 22px;
        align-items: start;
    }

    .eb-ld-info {
        grid-column: 1;
        grid-row: 1 / span 3;
    }

    .eb-ld-stage {
        grid-column: 2;
        grid-row: 1;
    }

    .eb-ld-thumbs {
        grid-column: 2;
        grid-row: 2;
    }

    /* Rows 2-3 of column 2 used to be empty by construction — measured at 1440x1000 that is
       679x418px of charcoal, ~20% of the viewport, while the licence number, council rating and
       member-since line sat ~300px below the enquiry in column 1 and off the fold on a 900px
       laptop. Every trust signal on this page existed and none of it was where it had to work. */
    .eb-ld > .eb-ld-card--keeper {
        grid-column: 2;
        grid-row: 3;
        align-self: start;
    }

    .eb-ld > .eb-listing-terms {
        grid-column: 1;
        grid-row: 4;
    }
}

/* Between the phone full-bleed and the desktop split, a 4:3 stage took 832x624 of a 900px viewport
   — iPad, iPad Mini and Surface in portrait all landed on a first screen of photo and breadcrumb,
   with the title, genes, vitals, price and enquiry every one of them below the fold. Capped, the
   ambient backdrop letterboxes the contained photo, which is what it is there for. */
@media (min-width: 768px) and (max-width: 959.98px) {
    .eb-ld-stage {
        max-height: 48vh;
    }
}

/* ---- The photo viewer: every pixel the window has ------------------------ */

.eb-lightbox {
    position: fixed;
    inset: 0;
    z-index: 1500;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Dark in both themes: it is a photo surface (The Two-Theme Rule). */
    background: rgba(12, 12, 14, 0.96);
}

/* Focus lands here programmatically so Escape works at once; the ring belongs
   on the controls, not the room. */
.eb-lightbox:focus {
    outline: none;
}

.eb-lightbox-photo {
    max-width: 100vw;
    max-height: 100vh;
    object-fit: contain;
    cursor: default;
}

.eb-lightbox-btn {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    padding: 0;
    border: 1px solid rgba(255, 255, 255, 0.24);
    border-radius: 999px;
    background: rgba(12, 12, 14, 0.55);
    color: var(--eb-on-photo);
    cursor: pointer;
    transition: border-color 140ms ease, color 140ms ease;
}

.eb-lightbox-btn:hover,
.eb-lightbox-btn:focus-visible {
    border-color: var(--eb-orange-on-dark);
    color: var(--eb-orange-on-dark);
}

.eb-lightbox-close {
    top: 20px;
    right: 20px;
}

.eb-lightbox-prev {
    top: 50%;
    left: 20px;
    transform: translateY(-50%);
}

.eb-lightbox-next {
    top: 50%;
    right: 20px;
    transform: translateY(-50%);
}

.eb-lightbox-count {
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    padding: 4px 10px;
    border-radius: 6px;
    background: rgba(12, 12, 14, 0.7);
    color: var(--eb-on-photo);
    font-family: ui-monospace, Menlo, monospace;
    font-size: 0.78rem;
    font-weight: 700;
}

/* The page behind the viewer must not scroll under arrow keys or a wheel. */
:root:has(.eb-lightbox) {
    overflow: hidden;
}

/* ---- Phone: the photo takes the full width of the screen ---------------- */

@media (max-width: 767.98px) {
    .eb-ld-stage {
        /* Out through .eb-container's 16px gutter: a listing photo with a margin
           reads as a thumbnail of an animal, not the animal. */
        margin-inline: -16px;
        border-inline: 0;
        border-radius: 0;
    }

    /* Top-right on a phone: the stage is full-bleed here, so its bottom-right corner is the
       viewport's, and the site-wide feedback tab (fixed, right edge, top:50%) sat straight over
       the zoom button. The count keeps the bottom-left corner either way. */
    .eb-ld-zoom {
        top: 16px;
        bottom: auto;
    }

    .eb-ld-thumbs {
        gap: 8px;
    }

    .eb-ld-stat {
        padding: 12px 12px;
    }

    /* Price and Save share the first line; the enquiry takes a full-width line
       of its own, which is also what the thumb reaches for. */
    .eb-ld-buy-row .eb-ld-cta.eb-ld-cta {
        flex: 1 1 100%;
        order: 2;
        margin-left: 0;
    }

    .eb-ld-buy-row .eb-save-btn {
        margin-left: auto;
    }

    .eb-ld-card {
        padding-inline: 16px;
    }
}

/* Four cells across 351px left BORN 88px for the 95px it needs, rendering "CB 202". Two-up
   instead: the vitals are the facts a keeper filters on, and a clipped one is a wrong one. */
@media (max-width: 399.98px) {
    .eb-ld-stats {
        grid-auto-flow: row;
        grid-template-columns: 1fr 1fr;
    }

    .eb-ld-stat:nth-child(odd) {
        padding-left: 0;
        border-left: 0;
    }

    .eb-ld-stat:nth-child(n + 3) {
        border-top: 1px solid var(--eb-border);
    }
}

@media (max-width: 359.98px) {
    .eb-ld-stage {
        margin-inline: -12px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .eb-ld-thumb {
        transition: none;
    }

    .eb-ld-thumb:hover {
        transform: none;
    }
}

/* ═══════════════════════════════════════════════════════════════════════════
   Seller stats — /my/stats
   A diagnosis list, not a dashboard: the seller's own animals ranked so the
   ones needing work float. Every colour routes through a token, and the one
   accent stays orange — a struggling listing is named in words, never in red.
   ═══════════════════════════════════════════════════════════════════════════ */

.eb-stats-summary,
.eb-stats-list,
.eb-stats-demand {
    margin-top: 28px;
}

.eb-stats-figures {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
}

.eb-stat {
    flex: 1 1 150px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 16px 18px;
    background: var(--eb-surface);
    border: 1px solid var(--eb-border);
    border-radius: 14px;
}

.eb-stat-value {
    font-size: 1.9rem;
    font-weight: 800;
    line-height: 1;
    letter-spacing: -0.03em;
    color: var(--eb-text);
    font-variant-numeric: tabular-nums;
}

.eb-stat-label {
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--eb-muted);
}

.eb-stat-delta {
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--eb-orange-ink);
    font-variant-numeric: tabular-nums;
}

.eb-stats-locked,
.eb-stats-thin,
.eb-stats-note {
    color: var(--eb-muted);
    font-size: 0.86rem;
}

.eb-stats-locked {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 14px;
    margin-top: 12px;
    padding: 18px;
    background: var(--eb-surface-2);
    border: 1px solid var(--eb-border);
    border-radius: 14px;
}

    .eb-stats-locked p {
        margin: 0;
        flex: 1 1 260px;
    }

.eb-stats-thin {
    margin-top: 14px;
}

.eb-stats-empty {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 16px;
    margin-top: 32px;
    padding: 32px;
    background: var(--eb-surface);
    border: 1px solid var(--eb-border);
    border-radius: 14px;
}

.eb-stats-empty-lead {
    margin: 0;
    font-size: 1.05rem;
    color: var(--eb-text);
}

/* ── the ranked rows ──────────────────────────────────────────────────── */

.eb-stat-rows {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 14px;
}

.eb-stat-row {
    background: var(--eb-surface);
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    overflow: hidden;
}

    .eb-stat-row.is-open {
        border-color: var(--eb-border-2);
    }

.eb-stat-row-head {
    display: grid;
    grid-template-columns: 56px 1fr auto;
    gap: 14px;
    align-items: center;
    width: 100%;
    padding: 14px;
    background: none;
    border: 0;
    text-align: left;
    font: inherit;
    color: inherit;
    cursor: pointer;
}

    .eb-stat-row-head:hover {
        background: var(--eb-surface-2);
    }

    .eb-stat-row-head:focus-visible {
        outline: 2px solid var(--eb-orange);
        outline-offset: -2px;
    }

.eb-stat-thumb {
    width: 56px;
    height: 56px;
    border-radius: 10px;
    border: 1px solid var(--eb-border-2);
    background-color: var(--eb-photo-bg);
    background-size: cover;
    background-position: center;
}

.eb-stat-main {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}

.eb-stat-title {
    font-weight: 700;
    font-size: 0.96rem;
    letter-spacing: -0.01em;
    color: var(--eb-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.eb-stat-meta {
    font-size: 0.78rem;
    color: var(--eb-muted);
    font-variant-numeric: tabular-nums;
}

    .eb-stat-meta b {
        color: var(--eb-body);
        font-weight: 600;
    }

.eb-stat-right {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 6px;
}

.eb-stat-price {
    font-size: 1.02rem;
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--eb-orange-ink);
    font-variant-numeric: tabular-nums;
}

/* Outline, not a fill: the flag is a note, and an orange chip here would compete
   with the price for the row's one point of emphasis. */
.eb-stat-flag {
    font-size: 0.72rem;
    font-weight: 700;
    color: var(--eb-muted);
    border: 1px solid var(--eb-border-2);
    border-radius: 999px;
    padding: 2px 10px 3px;
    white-space: nowrap;
}

.eb-stat-detail {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 18px;
    border-top: 1px solid var(--eb-border);
    background: var(--eb-surface-2);
}

.eb-stat-advice {
    margin: 0;
    max-width: 62ch;
    font-size: 0.9rem;
    line-height: 1.65;
    color: var(--eb-text);
}

.eb-stat-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* ── charts ───────────────────────────────────────────────────────────── */

.eb-chart {
    margin: 14px 0 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.eb-chart-svg {
    width: 100%;
    height: auto;
    overflow: visible;
}

.eb-chart-caption {
    font-size: 0.78rem;
    color: var(--eb-muted);
}

.eb-bar {
    fill: var(--eb-orange);
    transition: fill 140ms ease;
}

    .eb-bar:hover {
        fill: var(--eb-orange-hover, var(--eb-orange));
    }

/* A recorded zero, drawn as a stub so it cannot be read as a missing week. */
.eb-bar-zero {
    fill: var(--eb-border-2);
}

.eb-bar-marker {
    fill: var(--eb-muted);
}

.eb-chart-baseline {
    stroke: var(--eb-border);
    stroke-width: 1;
}

.eb-spark {
    width: 96px;
    height: 26px;
    overflow: visible;
}

.eb-spark-line {
    fill: none;
    stroke: var(--eb-orange);
    stroke-width: 2;
    stroke-linejoin: round;
    stroke-linecap: round;
    vector-effect: non-scaling-stroke;
}

.eb-spark-fill {
    fill: var(--eb-orange);
    opacity: 0.14;
    stroke: none;
}

.eb-spark-end {
    fill: var(--eb-orange);
}

/* ── demand ───────────────────────────────────────────────────────────── */

.eb-demand-rows {
    display: flex;
    flex-direction: column;
    margin-top: 14px;
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    overflow: hidden;
}

.eb-demand-row {
    display: grid;
    grid-template-columns: 1fr auto auto;
    gap: 14px;
    align-items: baseline;
    padding: 12px 16px;
    background: var(--eb-surface);
}

    .eb-demand-row + .eb-demand-row {
        border-top: 1px solid var(--eb-border);
    }

.eb-demand-label {
    font-weight: 700;
    color: var(--eb-text);
}

.eb-demand-verbatim {
    margin-left: 8px;
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--eb-muted);
}

.eb-demand-counts {
    font-size: 0.82rem;
    color: var(--eb-muted);
    font-variant-numeric: tabular-nums;
}

.eb-demand-unmet {
    margin-left: 8px;
    color: var(--eb-orange-ink);
    font-weight: 700;
}

.eb-demand-gap {
    font-size: 0.72rem;
    font-weight: 700;
    color: var(--eb-muted);
    border: 1px solid var(--eb-border-2);
    border-radius: 999px;
    padding: 2px 10px 3px;
    white-space: nowrap;
}

@media (max-width: 700px) {
    .eb-stat-row-head {
        grid-template-columns: 48px 1fr;
        row-gap: 10px;
    }

    .eb-stat-thumb {
        width: 48px;
        height: 48px;
    }

    .eb-stat-right {
        grid-column: 1 / -1;
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        flex-wrap: wrap;
    }

    .eb-demand-row {
        grid-template-columns: 1fr;
        gap: 4px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .eb-bar {
        transition: none;
    }
}

/* ============================================================================
   Pricing (/pricing)
   ============================================================================
   The band is a hero by another name: it never flips theme, so everything inside it
   is painted with the never-flipping tokens (--eb-on-photo, --eb-orange-on-dark).
   Below it the page returns to normal surfaces and normal tokens. */

.eb-pricing-band {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.82fr);
    background: #0C0C0E;
    border: 1px solid var(--eb-border-2);
    border-radius: 20px;
    overflow: hidden;
    margin-bottom: 52px;
}

/* No photo configured, no panel — the copy column simply takes the whole band. */
.eb-pricing-band:not(:has(.eb-pricing-photo)) { grid-template-columns: minmax(0, 1fr); }

/* Same reasoning as .eb-hero-immersive: on light, a pale hairline around a black slab reads as a
   hole cut in the page. Weight and a soft edge instead. */
:root:root[data-theme="light"] .eb-pricing-band {
    border-color: transparent;
    box-shadow: 0 24px 60px rgba(12, 12, 14, 0.28), 0 2px 8px rgba(12, 12, 14, 0.16);
}

.eb-pricing-band-inner {
    padding: 52px 44px 48px;
    max-width: 640px;
}

/* The photo bleeds to three edges and dissolves into the band on the fourth, so there is no seam
   to align and no hard rectangle sitting inside a rounded panel. */
.eb-pricing-photo {
    background-image: var(--eb-pricing-image);
    background-size: cover;
    background-position: center;
    -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 38%);
    mask-image: linear-gradient(90deg, transparent 0%, #000 38%);
}

.eb-pricing-headline {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: clamp(2.1rem, 4vw, 3.2rem);
    line-height: 0.98;
    letter-spacing: -0.035em;
    color: var(--eb-on-photo);
    text-wrap: balance;
    margin: 0 0 16px;
}

.eb-pricing-sub {
    color: var(--eb-on-photo-dim);
    font-size: 1.02rem;
    line-height: 1.55;
    max-width: 42ch;
    margin: 0 0 20px;
}

/* The claim carries the one number the visitor came for, so it sits a step above the sub. */
.eb-pricing-claim {
    color: var(--eb-on-photo);
    font-size: 0.95rem;
    line-height: 1.65;
    max-width: 48ch;
    margin: 0;
    padding-top: 18px;
    border-top: 1px solid rgba(255, 255, 255, 0.12);
}

.eb-pricing-proof {
    list-style: none;
    margin: 16px 0 0;
    padding: 0;
    display: grid;
    gap: 8px;
}

.eb-pricing-proof li {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    color: var(--eb-on-photo-dim);
    font-size: 0.9rem;
    line-height: 1.5;
}

/* The tick rides a permanently-dark surface here, so it keeps the bright heat lamp in both
   themes rather than sinking to the burnt light-theme orange. */
.eb-pricing-proof .eb-included-tick { color: var(--eb-orange-on-dark); }

.eb-pricing-cta {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 28px;
}

.eb-pricing-alert { margin-bottom: 52px; }

/* ---- The ladder ---------------------------------------------------------- */

.eb-ladder { margin-bottom: 52px; }

/* The shared .eb-section-head is a bare flex row with no gap — every page that puts a control
   beside its heading supplies its own spacing. Scoped here rather than added to the shared rule,
   which a dozen other pages rely on staying tight. */
.eb-ladder .eb-section-head {
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 16px;
}

.eb-ladder-sub {
    color: var(--eb-body);
    font-size: 0.95rem;
    line-height: 1.7;
    max-width: 68ch;
    margin: 10px 0 24px;
}

.eb-ladder-note {
    color: var(--eb-muted);
    font-size: 0.86rem;
    line-height: 1.65;
    max-width: 68ch;
    margin: 20px 0 0;
}

.eb-plans {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 18px;
}

.eb-plan {
    position: relative;
    display: flex;
    flex-direction: column;
    background: var(--eb-surface);
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    padding: 24px 22px;
    transition: transform 160ms ease, border-color 160ms ease, box-shadow 160ms ease;
}

/* Orange marks the plan the visitor is on, or — for a stranger, who is on none — the plan the
   page recommends. Both are active state; neither is decoration. */
.eb-plan.is-marked { border-color: var(--eb-tint-border); }

.eb-plan-flag {
    position: absolute;
    top: -1px;
    right: 18px;
    padding: 4px 10px;
    border-radius: 0 0 6px 6px;
    background: var(--eb-tint-bg);
    color: var(--eb-orange-ink);
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.eb-plan:hover {
    transform: translateY(-3px);
    border-color: var(--eb-orange);
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
}

:root:root[data-theme="light"] .eb-plan:hover {
    box-shadow: 0 12px 32px rgba(12, 12, 14, 0.14);
}

.eb-plan-name {
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 1.05rem;
    letter-spacing: -0.01em;
    color: var(--eb-text);
    margin: 0;
}

/* Reserved, not merely capped: "Always free" and "£12 a month" and the no-price note are three
   different heights, and a jagged price row stops the columns reading as one table. */
.eb-plan-price {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 7px;
    min-height: 58px;
    margin-top: 10px;
}

.eb-plan-price .eb-price {
    font-size: 1.9rem;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

.eb-plan-per,
.eb-plan-noprice {
    color: var(--eb-muted);
    font-size: 0.86rem;
}

.eb-plan-noprice { line-height: 1.5; }

.eb-plan-rows {
    margin: 6px 0 0;
    border-top: 1px solid var(--eb-border);
}

.eb-plan-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 0;
    border-bottom: 1px solid var(--eb-border);
}

.eb-plan-row:last-child { border-bottom: none; }

.eb-plan-row dt {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-muted);
    margin: 0;
}

.eb-plan-row dd {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--eb-text);
    font-variant-numeric: tabular-nums;
    text-align: right;
}

.eb-plan-foot { margin-top: auto; padding-top: 20px; }

/* An <a>, so the colour is restated with !important against global-styles.css's body-link rule —
   see the note on .eb-hero-cta-btn--primary for why nothing in this sheet can out-specify it. */
.eb-plan-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    min-height: 44px;
    padding: 0 18px;
    border: 1px solid transparent;
    border-radius: 12px;
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 0.92rem;
    letter-spacing: -0.01em;
    text-decoration: none;
    background-color: var(--eb-orange) !important;
    color: var(--eb-on-orange) !important;
    transition: background-color 140ms ease, box-shadow 140ms ease;
}

.eb-plan-btn:hover {
    background-color: var(--eb-orange-hover) !important;
    color: var(--eb-on-orange) !important;
    box-shadow: 0 0 32px rgba(232, 99, 10, 0.32);
}

/* The global orange focus ring is invisible on an orange fill — it was ring-on-fill at 1:1, read
   only by its 2px gap. A page-coloured band between fill and ring gives it an edge to sit against. */
.eb-plan-btn:focus-visible {
    outline: none;
    box-shadow: 0 0 0 2px var(--eb-page-bg), 0 0 0 4px var(--eb-orange);
}

/* Where the offer has already been taken, the remaining action is administrative, not persuasive. */
.eb-plan-btn--quiet {
    background-color: transparent !important;
    border-color: var(--eb-border-2);
    color: var(--eb-text) !important;
}

.eb-plan-btn--quiet:hover {
    background-color: transparent !important;
    border-color: var(--eb-orange);
    color: var(--eb-orange-ink) !important;
    box-shadow: none;
}

/* Two lines reserved: "then £12 a month, or £120 a year" wraps where "No card." does not. */
.eb-plan-then {
    display: block;
    margin-top: 10px;
    min-height: 2.6em;
    color: var(--eb-muted);
    font-size: 0.8rem;
    line-height: 1.5;
}

/* ---- What every plan includes -------------------------------------------- */

.eb-included { margin-bottom: 44px; }

.eb-included-list {
    list-style: none;
    margin: 20px 0 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px 32px;
}

.eb-included-list li {
    display: flex;
    align-items: flex-start;
    gap: 11px;
    color: var(--eb-body);
    font-size: 0.95rem;
    line-height: 1.6;
}

.eb-included-tick {
    color: var(--eb-orange-ink);
    flex: 0 0 auto;
    margin-top: 2px;
}

/* ---- Objections ---------------------------------------------------------- */

.eb-faq { margin-bottom: 40px; }

.eb-faq-item {
    border-bottom: 1px solid var(--eb-border);
    max-width: 76ch;
}

.eb-faq .eb-section-head + .eb-faq-item { border-top: 1px solid var(--eb-border); }

.eb-faq-item > summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 16px 0;
    cursor: pointer;
    list-style: none;
    font-family: var(--eb-display);
    font-weight: 700;
    font-size: 0.98rem;
    letter-spacing: -0.01em;
    color: var(--eb-text);
}

.eb-faq-item > summary::-webkit-details-marker { display: none; }

/* Drawn, not a glyph: a 1px chevron built from the border box, rotating on open. */
.eb-faq-item > summary::after {
    content: "";
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-right: 1.5px solid var(--eb-muted);
    border-bottom: 1.5px solid var(--eb-muted);
    transform: translateY(-2px) rotate(45deg);
    transition: transform 160ms ease, border-color 160ms ease;
}

.eb-faq-item[open] > summary::after {
    transform: translateY(2px) rotate(-135deg);
    border-color: var(--eb-orange-ink);
}

.eb-faq-item > summary:hover { color: var(--eb-orange-ink); }

.eb-faq-item > p {
    margin: 0 0 18px;
    padding-right: 24px;
    color: var(--eb-body);
    font-size: 0.92rem;
    line-height: 1.7;
    max-width: 70ch;
}

.eb-pricing-legal {
    color: var(--eb-muted);
    font-size: 0.8rem;
    line-height: 1.6;
    margin: 0 0 24px;
}

.eb-pricing-legal-link { font-size: 0.8rem; }

@media (max-width: 1100px) {
    .eb-pricing-band { grid-template-columns: minmax(0, 1fr) minmax(0, 0.6fr); }
    .eb-pricing-band-inner { padding: 44px 34px 40px; }
}

@media (max-width: 960px) {
    /* The photo moves above the copy rather than disappearing: it is the page's only
       photograph, and a marketplace that hides its animals on phones is the wrong trade. */
    .eb-pricing-band,
    .eb-pricing-band:not(:has(.eb-pricing-photo)) { grid-template-columns: minmax(0, 1fr); }
    .eb-pricing-photo {
        grid-row: 1;
        min-height: 170px;
        -webkit-mask-image: linear-gradient(180deg, #000 40%, transparent 100%);
        mask-image: linear-gradient(180deg, #000 40%, transparent 100%);
    }
    .eb-pricing-band-inner { max-width: none; padding-top: 28px; }

    .eb-plans { grid-template-columns: 1fr; }
    .eb-plan-price { min-height: 0; }
    .eb-plan-then { min-height: 0; }
    .eb-included-list { grid-template-columns: 1fr; }
}

@media (max-width: 700px) {
    .eb-pricing-band-inner { padding: 24px 22px 30px; }
    .eb-pricing-cta .eb-hero-cta-btn { flex: 1 1 auto; padding-inline: 16px; }
    .eb-ladder .eb-section-head { flex-wrap: wrap; gap: 12px; }
}


/* ---- The member's own membership page ------------------------------------
   Shares the ladder's vocabulary on purpose: a member arriving from /pricing should
   recognise the same two facts, in the same order, in the same words. */

.eb-plans--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.eb-mem-current {
    display: flex;
    flex-direction: column;
    gap: 4px;
    background: var(--eb-surface);
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    padding: 22px;
    margin-bottom: 24px;
}

.eb-mem-current-label {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--eb-muted);
}

.eb-mem-current-plan {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 1.9rem;
    line-height: 1.1;
    letter-spacing: -0.03em;
    color: var(--eb-orange-ink);
}

.eb-mem-current-via { color: var(--eb-body); font-size: 0.9rem; }

.eb-mem-alert { margin-bottom: 24px; }

.eb-mem-offer {
    background: var(--eb-surface);
    border: 1px solid var(--eb-border);
    border-radius: 14px;
    padding: 22px;
    margin-bottom: 28px;
}

.eb-mem-offer-body {
    color: var(--eb-body);
    font-size: 0.95rem;
    line-height: 1.7;
    max-width: 68ch;
    margin: 8px 0 0;
}

.eb-mem-offer-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 18px; }

.eb-mem-offer-title {
    font-size: 1.05rem;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: var(--eb-text);
    margin: 0;
}

/* ---- The two regions -----------------------------------------------------
   The page's whole structure is one line: above it nothing takes a card, below it
   everything does. A hairline and real space are what draw it — the two commitments
   used to be sibling sections with nothing between them at all. */

.eb-mem-region { margin-bottom: 40px; }

.eb-mem-region + .eb-mem-region {
    border-top: 1px solid var(--eb-border);
    padding-top: 36px;
}

.eb-mem-region > .eb-section-title { margin: 0 0 16px; }

/* ---- The free period, inside the card that states the plan ----------------
   It was a bare <p> between two cards — the one fact the member came for, rendered as
   the least-styled text on the page. */

.eb-mem-free {
    margin-top: 18px;
    padding-top: 18px;
    border-top: 1px solid var(--eb-border);
}

.eb-mem-free-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 10px;
}

/* Deliberately not orange. The tier name above it already carries the page's one orange
   headline object; a second at this scale would dilute what orange means here. */
.eb-mem-free-days {
    font-family: var(--eb-display);
    font-weight: 800;
    font-size: 1.15rem;
    letter-spacing: -0.02em;
    color: var(--eb-text);
}

.eb-mem-free-ends { color: var(--eb-muted); font-size: 0.86rem; }

.eb-mem-free-next {
    color: var(--eb-body);
    font-size: 0.9rem;
    line-height: 1.6;
    max-width: 62ch;
    margin: 8px 0 0;
}

.eb-mem-free-switch {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    margin-top: 16px;
}

.eb-mem-free-hint { color: var(--eb-muted); font-size: 0.8rem; }

/* ---- Buying ---------------------------------------------------------------- */

.eb-mem-seg { margin-bottom: 20px; }

/* Above the grid, because it is a condition of the whole region and every button below
   it is dead until it is ticked. It used to sit underneath, which guaranteed a failed
   first attempt at the one moment money is involved. */
.eb-mem-consent {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 4px 12px;
    padding: 14px 16px;
    background: var(--eb-surface-inset);
    border: 1px solid var(--eb-border);
    border-radius: 12px;
    margin-bottom: 20px;
}

.eb-mem-consent-hint { color: var(--eb-muted); font-size: 0.8rem; }

/* MudCheckBox centres its box against the whole label. That is right for one line and wrong for
   four: on a phone this label wraps and the box drifts 36px below the sentence it belongs to. */
.eb-mem-consent .mud-checkbox { align-items: flex-start; }

/* A tier the free period already grants has no price to pay, so its foot carries a fact
   rather than a control — and the same reserved height as the buttons beside it. */
.eb-mem-covered-note {
    display: flex;
    align-items: center;
    min-height: 44px;
    color: var(--eb-muted);
    font-size: 0.86rem;
}

/* MudBlazor buttons land at 36px; the plan anchors on /pricing are 44. Same page, same row. */
.eb-mem-cta { min-height: 44px; }

@media (max-width: 960px) {
    .eb-plans--two { grid-template-columns: 1fr; }
}

/* ---- Browser surfaces ----------------------------------------------------
   Selection and the caret ship with the UA's own blue, which belongs to no design system
   here. Global on purpose: the cheapest place the theme was still leaking. */

/* Background only, no colour: the White Card Rule means an input's body is white with dark text
   while the page around it is near-black with white text, and forcing one selection colour makes
   the text invisible in whichever of the two it was not chosen for. */
::selection { background: var(--eb-tint-border); }

input, textarea, [contenteditable] { caret-color: var(--eb-orange); }
