html, body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, Roboto, 'Helvetica Neue', Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Reserve the scrollbar gutter so pages that toggle a vertical scrollbar
   (e.g. opening a detail view) don't shift horizontally. */
html {
  scrollbar-gutter: stable;
}

/* MudBlazor's dialog scroll-lock pads the body by the scrollbar width to make up for the
   scrollbar it hides. The reserved gutter above means the scrollbar never goes away, so that
   padding is a second compensation and visibly jerks the page sideways on every dialog open.
   Its overflow:hidden still does the real work (body's overflow propagates to the viewport). */
body.scroll-locked {
  padding-right: 0;
}

h1:focus {
  outline: none;
}

/* ── Elevation shadow color ──────────────────────────────────────────────
   Pure-black shadows vanish on dark surfaces, so cards/popovers lose their
   lift in dark mode. Drive every elevation off these vars; the body.uma-dark
   class (set by MainLayout) swaps in a much deeper shadow that actually reads
   on a dark background. Light values keep the original soft look. */
:root {
  --uma-shadow-soft: rgba(0, 0, 0, 0.12);
  --uma-shadow-strong: rgba(0, 0, 0, 0.22);
  /* The game's support-card silhouette. The frame is baked into the card image with transparent,
     rounded corners, so the clip only has to match those corners: rounded on three, near-square on
     the top-right where the type badge sits. Two tokens because the two art sizes carry DIFFERENT
     corner radii - the small thumbnail rounds tighter than the full art - so one value shaved the
     colored frame on whichever surface it didn't fit. Each token is measured just at the frame's
     own corner (a hair under it) so the clip removes only already-transparent pixels: the frame is
     never cut, and the card reads with the same outline everywhere it's used.
       -thumb: SupportCardArt (browser grid, deck picker, compare, slot sheet) - loads thumb.webp.
       -full:  deck slots and the training-sim board - load full.webp. */
  --uma-card-radius-thumb: 9.6% 2.5% 9.6% 9.6%;
  --uma-card-radius-full: 9.6% 2.5% 9.6% 9.6%;

  /* Split-slot geometry, shared by every surface that draws a deck slot carved into wedges:
     DeckSlot (the builder) and CmDeckSlotThumb (a meet's read-only strip). Tokens rather than
     repeated literals because Blazor scopes CSS per component, so the two cannot share a rule, and
     these numbers only work as a set - change one without the others and the rarity frame separates
     from the art it is meant to ring.
       -inset-x/y: the frame texture's own ring thickness (21px of 648x864), so the art sits in the
         frame's opening instead of running flush to its outer edge, where a sub-pixel disagreement
         shows a sliver outside the frame.
       -seam-w/h: the inset box the seam svg spans, 100% less twice each inset. Sized explicitly
         because an svg is a replaced element and would otherwise take its intrinsic size.
       -radius: the same 52px arc the frame texture carries, symmetric where the app's card
         silhouette clips its top-right - a split drops that asymmetry or the art rounds past the
         frame on that corner. */
  --uma-split-inset-x: 3.24%;
  --uma-split-inset-y: 2.43%;
  --uma-split-seam-w: 93.52%;
  --uma-split-seam-h: 95.14%;
  --uma-split-radius: 8% / 6%;
}

body.uma-dark {
  --uma-shadow-soft: rgba(0, 0, 0, 0.5);
  --uma-shadow-strong: rgba(0, 0, 0, 0.66);
}

/* ── Global MudBlazor overrides ─────────────────────────────────────────── */

/* Defined border on all papers/cards (MudBlazor default is borderless) */
.mud-paper, .mud-card {
  border: 1px solid var(--mud-palette-lines-default);
}

/* Dialogs take the sidebar/main tint rather than MudBlazor's surface colour, so a modal reads as
   the page continuing rather than as a lighter sheet floating over it. The panels inside keep their
   own surface, which is what gives them separation from this. Keep in sync with
   .uma-sidebar/.uma-main in MainLayout.razor.css.
   Written as a descendant selector on purpose: MudBlazor.min.css is linked after app.css in
   _Host.cshtml, so a bare .mud-dialog ties on specificity and loses to its own
   background-color: var(--mud-palette-surface). */
.mud-dialog-container > .mud-dialog {
  background-color: color-mix(in srgb, var(--mud-palette-primary) 5%, var(--mud-palette-surface));
}

/* Race-search autocomplete dropdown: clip the popover so only the inner list scrolls
   (otherwise the popover and the list each show a scrollbar - a double scrollbar). */
.planner-search-popover {
  overflow: hidden;
}

.planner-search-popover .mud-list {
  overflow-y: auto;
}

/* Planner day-race popover: match the sidebar/main tint (.uma-sidebar/.uma-main) instead of the
   lighter default popover surface. Keep this in sync with MainLayout.razor.css. */
.planner-day-popover {
  background-color: color-mix(in srgb, var(--mud-palette-primary) 5%, var(--mud-palette-surface)) !important;
}

/* Aptitude uma search: lay the results out as a 3-column grid of portrait tiles instead of a
   vertical list. The popover is widened past the (narrow) search box so three tiles fit.

   MudAutocomplete's MaxHeight lands as an inline max-height + overflow-y:auto on the popover, and
   MudBlazor's own `.mud-list { max-height: inherit; overflow-y: auto }` makes the list inside scroll
   too - two nested scrollbars side by side. `!important` beats the inline rule so only the list
   scrolls; dropping the list's padding keeps its last row inside the popover's clipped box. */
.apt-uma-popover {
  overflow: hidden !important;
  /* 30rem on desktop, but never wider than the viewport or it runs off-screen on a phone. */
  min-width: min(30rem, 92vw);
  max-width: 92vw;
}

.apt-uma-popover .mud-list {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 4px;
  max-height: inherit;
  overflow-y: auto;
  padding-top: 0;
  padding-bottom: 0;
}

.apt-uma-popover .mud-list-item {
  display: block;
  padding: 6px 4px;
}

.apt-uma-tile {
  width: 100%;
}

.apt-uma-icon {
  object-fit: cover;
  object-position: top;
  border-radius: 6px;
}

.apt-uma-name {
  width: 100%;
  line-height: 1.15;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

/* On a phone MudBlazor anchors the popover's left edge to the (nearly full-width) search box,
   so a 92vw popover runs off the right and clips the third tile column. The v9 popover is
   position:fixed with a JS-set inline left/top, so pin its horizontal placement to the viewport
   here (our !important beats MudBlazor's non-important inline left); top stays untouched, so it
   still opens anchored under the search box. */
@media (max-width: 900px) {
  .apt-uma-popover {
    left: 2vw !important;
    width: 96vw !important;
    min-width: 0 !important;
    max-width: 96vw !important;
  }

  .apt-uma-popover .mud-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Support-card debug picker (Training Debug): same 3-column grid-of-tiles popover as the aptitude uma
   search, sized for a card icon instead of a portrait. Same single-scrollbar handling - see above. */
.support-ac-popover {
  overflow: hidden !important;
  min-width: min(30rem, 92vw);
  max-width: 92vw;
}

.support-ac-popover .mud-list {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 4px;
  max-height: inherit;
  overflow-y: auto;
  padding-top: 0;
  padding-bottom: 0;
}

.support-ac-popover .mud-list-item {
  display: block;
  padding: 6px 4px;
}

.support-ac-tile {
  width: 100%;
}

.support-ac-icon {
  object-fit: cover;
  object-position: top;
  border-radius: 6px;
}

.support-ac-name {
  width: 100%;
  line-height: 1.15;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

@media (max-width: 900px) {
  .support-ac-popover {
    left: 2vw !important;
    width: 96vw !important;
    min-width: 0 !important;
    max-width: 96vw !important;
  }

  .support-ac-popover .mud-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Accent border utilities - opt-in colored edge on any element.
   Color via --uma-accent inline var (defaults to primary). */
.accent-top {
  border-top: 3px solid var(--uma-accent, var(--mud-palette-primary)) !important;
}

.accent-right {
  border-right: 4px solid var(--uma-accent, var(--mud-palette-primary)) !important;
}

.accent-bottom {
  border-bottom: 4px solid var(--uma-accent, var(--mud-palette-primary)) !important;
}

.accent-left {
  border-left: 4px solid var(--uma-accent, var(--mud-palette-primary)) !important;
}

/* Auto-fill tile grid for the detail-page "other versions" / support-card panels.
   Fixed-size cells that pack tightly and wrap, so short lists don't leave big centered
   gaps and long lists stay dense. Shared by CharacterOtherVersionsPanel,
   CharacterSupportsPanel and SupportOtherCardsPanel. */
.uma-link-tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
  gap: 8px;
}

/* RelatedCardsPanel: match the outline other detail cards get from MudPaper Outlined.
   Global (not scoped) because Blazor won't stamp the scope attr onto MudExpansionPanels' DOM. */
.related-outlined {
  border: 1px solid var(--mud-palette-lines-default);
  border-radius: var(--mud-default-borderradius);
}

/* Section title color */
.mud-paper .mud-typography-h6 {
  color: var(--mud-palette-primary);
}

/* Hover lift on clickable cards */
.mud-card.cursor-pointer {
  transition: transform 120ms ease, box-shadow 120ms ease, border-color 120ms ease;
}

/* Only on real-pointer devices: on touchscreens :hover sticks after a tap, so skip it. */
@media (hover: hover) {
  .mud-card.cursor-pointer:hover {
    transform: translateY(-2px);
    border-color: var(--mud-palette-primary);
    box-shadow: 0 6px 20px var(--uma-shadow-soft);
  }
}

/* Skill source tiles (SkillSourceTile.razor), same shape as the skill tile. Global because the
   class lands on a MudLink, whose DOM scoped CSS can't reach. */
.skill-source-tile {
  display: block;
  padding: 12px 14px;
  border: 1px solid var(--mud-palette-lines-default);
  border-left: 4px solid var(--mud-palette-primary);
  border-radius: 10px;
  background-color: var(--mud-palette-surface);
  transition: transform 120ms ease, box-shadow 120ms ease, border-color 120ms ease;
}

@media (hover: hover) {
  .skill-source-tile:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 18px var(--uma-shadow-soft);
  }
}

/* The name half can shrink so a long name wraps instead of shoving the pills off the tile. */
.skill-source-tile-main {
  min-width: 0;
}

.skill-source-tile-pills {
  flex: 0 0 auto;
}

/* Support card tiles (browser grid + deck picker) - see SupportCardTile.razor. These live
   here because scoped CSS can't reach a MudCard's / MudText's root element.

   The tile paints NOTHING: no fill, no border, no radius. The card image carries its own frame with
   transparent, rounded corners baked in (the silhouette), and the tile box is the card PLUS the
   label below it - so any box the tile paints traces a taller rectangle than the card and reads as
   a misaligned corner however it is rounded. Leaving the art's silhouette as the only shape is what
   makes the corners line up, by definition.

   Two classes, because MudBlazor's sheet is loaded after this file: a single-class .support-tile
   rule silently loses to .mud-paper / .mud-card / .mud-paper-outlined at equal specificity. The
   old single-class `border: none` here never actually applied, which is why picker cards carried a
   stray MudBlazor outline. */
.support-tile.mud-card {
  background-color: transparent;
  border: none;
  border-radius: 0;
}

/* Hover keeps the lift (from .mud-card.cursor-pointer:hover) but moves the elevation onto the art.
   The tile box is the card PLUS the label below it, so a shadow cast off the tile traced a tall
   rounded rectangle at the tile's own radius - a shape the card never has, which is what made the
   corners read as misaligned on hover. .support-art already carries the card's exact silhouette, and
   an element's own box-shadow is painted outside its border box, so its overflow:hidden doesn't eat
   it. Casting from there means the hover shape IS the card. */
@media (hover: hover) {
  .support-tile.cursor-pointer:hover {
    box-shadow: none;
  }

  .support-tile.cursor-pointer:hover .support-art {
    box-shadow: 0 6px 20px var(--uma-shadow-soft);
  }
}

/* The deck-builder drag ghost (created by window.umaDeckDrag in _Host.cshtml, so it belongs to no
   component and can't be scoped). Parked offscreen because setDragImage snapshots a RENDERED
   element: display:none or a detached node gives a blank ghost, and any on-screen position would
   flash it into the layout. */
/* Positioning only. The ghost is a canvas whose size is set inline from window.umaDeckDrag.size,
   so nothing here decides how big it is - a CSS box that could disagree with the drawn pixels is
   exactly what made Firefox mis-size the old img-based ghost. Parked offscreen between drags and
   moved under the cursor for the frame dragstart needs (see umaDeckDrag.place). */
.uma-drag-ghost {
  position: fixed;
  top: -1000px;
  left: -1000px;
  pointer-events: none;
}

/* Deck-picker variant: just a pointer cursor over the art (the border is already gone above). */
.support-tile-plain {
  cursor: pointer;
}

/* Unavailable picker card: inert (the art greys via SupportCardArt's Dimmed flag). */
.support-tile-disabled {
  cursor: default;
  pointer-events: none;
}

/* Keep tiles a uniform height: a long title or name truncates instead of wrapping.
   These target MudText, a child component, so the rule must live here (global) rather than in
   a tile's scoped .razor.css - Blazor does not stamp the scope attribute onto child-component
   markup, so a scoped selector would never match and long names would overflow the grid track
   (which on mobile widens the page and breaks the fixed bottom nav's positioning). */
.support-tile-title,
.support-tile-name,
.char-tile-title,
.char-tile-name {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The character and support browse tiles are wrapped in a link so the browser can open them in a
   new tab. The anchor is pure plumbing and must not add a box of its own. Global because the class
   lands on a MudLink, whose DOM scoped CSS can't reach. */
.char-tile-link,
.support-tile-link {
  display: block;
}

/* Skill tiles (SkillTile.razor) and skill browse cards (SkillBrowseCard.razor). Both are links, so
   their box lives here for the same reason. The inner name/description rules stay scoped, since
   those sit on raw markup. */
.uma-skill-tile {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--mud-palette-lines-default);
  border-left: 4px solid var(--uma-accent, var(--mud-palette-primary));
  border-radius: 10px;
  background-color: var(--mud-palette-surface);
  cursor: pointer;
  transition: transform 120ms ease, box-shadow 120ms ease, border-color 120ms ease;
}

/* Real-pointer only: on touch, :hover sticks after a tap. */
@media (hover: hover) {
  .uma-skill-tile:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 18px var(--uma-shadow-soft);
  }
}

/* Presentational use (e.g. a dropdown item): keep the look, drop the lift and the click
   affordance so it sits flush in a list. */
.uma-skill-tile-static {
  cursor: default;
}

.uma-skill-tile-static:hover {
  transform: none;
  box-shadow: none;
}

/* Size variants. Medium is the default above. */
.uma-skill-tile-sm {
  gap: 8px;
  padding: 6px 10px;
  border-radius: 8px;
}

.uma-skill-tile-lg {
  gap: 14px;
  padding: 16px 18px;
}

/* height:100% so a card in a grid cell fills the row rather than leaving a gap under the shortest
   tile - the row height is set by whichever name/description wraps most. */
.uma-skill-card {
  display: flex;
  align-items: center;
  height: 100%;
  gap: 12px;
  padding: 14px;
  border: 1px solid var(--mud-palette-lines-default);
  border-left: 4px solid var(--uma-accent, var(--mud-palette-primary));
  border-radius: 10px;
  background-color: var(--mud-palette-surface);
  cursor: pointer;
  transition: transform 120ms ease, box-shadow 120ms ease, border-color 120ms ease;
}

@media (hover: hover) {
  .uma-skill-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 18px var(--uma-shadow-soft);
  }
}

/* The unique-skill chip on a character's portrait card (CharacterPortraitCard.razor) - a skill
   tile in miniature, and a link for the same reason. */
.uma-card-unique {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  padding: 8px 12px;
  border: 1px solid var(--mud-palette-lines-default);
  border-left: 4px solid var(--uma-accent, var(--mud-palette-warning));
  border-radius: 10px;
  background-color: var(--mud-palette-surface);
  cursor: pointer;
  transition: transform 120ms ease, box-shadow 120ms ease, border-color 120ms ease;
}

@media (hover: hover) {
  .uma-card-unique:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 18px var(--uma-shadow-soft);
  }
}

/* The icon+title tiles listing a uma's other versions / its support cards
   (CharacterLinkCard.razor, SupportLinkCard.razor). The anchor is pure plumbing. */
.uma-link-tile-link {
  display: block;
}

/* AccountMenu's activator, restyled from a Material button into something that sits flush with the
   sidebar's nav links above it. Global for the same reason as the tile titles: every element here is
   rendered by a child component (MudMenu's activator wrapper, MudButton, MudText), so a scoped
   selector would never match. */
.uma-account,
.uma-account .mud-menu-activator {
  width: 100%;
}

.uma-account-row {
  justify-content: flex-start;
  min-height: 42px;
  padding: 0 12px;
  border-radius: 9px;
  text-transform: none;
  font-weight: 500;
  color: var(--mud-palette-text-primary);
}

.uma-account-row .mud-button-label {
  width: 100%;
}

/* Long usernames truncate rather than widen the sidebar. */
.uma-account-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  text-align: left;
}

/* Belt-and-braces for phones: never let a stray over-wide child scroll the page sideways. A
   horizontally overflowing page widens the layout viewport, which detaches position:fixed
   elements (the bottom nav bar) so they drift on scroll and become hard to tap. */
@media (max-width: 900px) {
  html,
  body {
    overflow-x: hidden;
  }
}

/* Status band across the bottom of the art (e.g. "In deck" / "Uma in deck"). */
.support-tile-status {
  position: absolute;
  inset: auto 0 0 0;
  padding: 3px 0;
  text-align: center;
  font-size: 0.72rem;
  font-weight: 700;
  color: #fff;
}

/* Active nav link weight (MudBlazor default omits this) */
.mud-nav-link.active:not(.mud-nav-link-disabled) {
  font-weight: 600;
}

/* Grade badge base - color set via inline style per component */
.uma-planner-grade-badge {
  display: inline-block;
  font-size: 0.65rem;
  font-weight: 800;
  letter-spacing: 0.03em;
  padding: 1px 6px;
  border-radius: 4px;
  flex-shrink: 0;
  color: #fff;
  background: #757575;
}

/* ── Blazor runtime hooks (keep global - rendered outside any component) ── */

#blazor-error-ui {
  background: lightyellow;
  bottom: 0;
  box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
  display: none;
  left: 0;
  padding: 0.6rem 1.25rem 0.7rem 1.25rem;
  position: fixed;
  width: 100%;
  z-index: 1000;
}

#blazor-error-ui .dismiss {
  cursor: pointer;
  position: absolute;
  right: 0.75rem;
  top: 0.5rem;
}

/* Circuit-reconnect overlay. Hidden until Blazor adds components-reconnect-{show,failed,
   rejected} to #components-reconnect-modal; each state reveals its matching message. */
#components-reconnect-modal {
  display: none;
}

#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
  display: flex;
  position: fixed;
  inset: 0;
  z-index: 3000;
  align-items: center;
  justify-content: center;
  background: rgba(15, 23, 42, 0.55);
  backdrop-filter: blur(2px);
}

.uma-reconnect-card {
  background: var(--mud-palette-surface, #fff);
  color: var(--mud-palette-text-primary, #1a1a1a);
  border-radius: 12px;
  padding: 28px 32px;
  max-width: 360px;
  text-align: center;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
}

.uma-reconnect-spinner {
  width: 36px;
  height: 36px;
  margin: 0 auto 16px;
  border: 3px solid var(--mud-palette-primary, #1565c0);
  border-right-color: transparent;
  border-radius: 50%;
  animation: uma-reconnect-spin 0.8s linear infinite;
}

@keyframes uma-reconnect-spin {
  to {
    transform: rotate(360deg);
  }
}

.uma-reconnect-text {
  margin: 0;
  font-size: 0.95rem;
  color: var(--mud-palette-text-secondary, #555);
}

.uma-reconnect-text a {
  color: var(--mud-palette-primary, #1565c0);
  font-weight: 600;
}

/* Show only the message matching the current reconnect state. */
.uma-reconnect-when-show,
.uma-reconnect-when-failed,
.uma-reconnect-when-rejected {
  display: none;
}

.components-reconnect-show .uma-reconnect-when-show {
  display: block;
}

.components-reconnect-failed .uma-reconnect-when-failed,
.components-reconnect-rejected .uma-reconnect-when-rejected {
  display: block;
}

/* The spinner only makes sense while actively retrying (show); hide it once terminal. */
.components-reconnect-failed .uma-reconnect-spinner,
.components-reconnect-rejected .uma-reconnect-spinner {
  display: none;
}

.blazor-error-boundary {
  background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjY2LjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
  padding: 1rem 1rem 1rem 3.7rem;
  color: white;
}

.blazor-error-boundary::after {
  content: "An error has occurred."
}

/* ── Umamusume-themed boot loader: runner circling a random race track ───── */

/* Brand wordmark above the track */
.uma-brand {
  text-align: center;
  margin: 9vh 0 0.75rem 0;
  font-weight: 800;
  letter-spacing: 0.05em;
  font-size: 2rem;
  color: #2F62D4;
}

.uma-loader {
  position: relative;
  width: 448px;
  height: 244px;
  max-width: 92vw;
  margin: 0 auto 0.5rem auto;
}

.uma-loader-track {
  position: absolute;
  inset: 0;
  overflow: visible;
}

/* Infield (PeakJoy background-gray) */
.uma-track-grass {
  fill: #DCE6F6;
}

/* Running lane - soft blue-gray dirt with a primary-blue outer rail */
.uma-track-base {
  fill: none;
  stroke: #C6D5EF;
  stroke-width: 14;
  stroke-linejoin: round;
}

/* Outer rail: same stadium path, thin primary-blue stroke on top */
.uma-track-rail {
  fill: none;
  stroke: #2F62D4;
  stroke-width: 1.5;
  stroke-linejoin: round;
  opacity: 0.55;
}

/* ── Track markers ──────────────────────────────────────────────────────── */

.uma-mk-text {
  fill: #161E2C;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.03em;
}

/* Start/finish: dark line + white dashes = checkered band */
.uma-mk-finish-bg {
  stroke: #161E2C;
  stroke-width: 8;
}

.uma-mk-finish {
  stroke: #FFFFFF;
  stroke-width: 8;
  stroke-dasharray: 4 4;
}

/* Landmark arcs follow the curved lane, so they must not fill. */
.uma-mk-uphill,
.uma-mk-corner,
.uma-mk-spurt {
  fill: none;
  stroke-width: 8;
  stroke-linecap: butt;
  opacity: 0.9;
}

/* Slope / uphill - warm orange */
.uma-mk-uphill {
  stroke: #E0843C;
}

/* Final corner - gold (secondary) */
.uma-mk-corner {
  stroke: #D99B1F;
}

/* Last spurt - tertiary red */
.uma-mk-spurt {
  stroke: #D64550;
}

/* Start gate line - green (separate from the finish line) */
.uma-mk-startline {
  stroke: #2E9D5B;
  stroke-width: 4;
}

.uma-mk-start-text {
  fill: #2E9D5B;
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.03em;
}

/* Runner laps the oval; offset-distance = real Blazor load %, so it advances as
   assets download. offset-path matches the lane (1 SVG unit = 1px). */
.uma-loader-runner {
  position: absolute;
  top: 0;
  left: 0;
  offset-path: path("M230,210 L340,210 A80,80 0 0 0 340,50 L120,50 A80,80 0 0 0 120,210 Z");
  offset-distance: var(--blazor-load-percentage, 0%);
  offset-rotate: 0deg;
  offset-anchor: 50% 50%;
  transition: offset-distance 0.15s linear;
  animation: uma-gallop 0.45s ease-in-out infinite;
  filter: drop-shadow(0 2px 2px rgba(22, 30, 44, 0.3));
}

.uma-loader-runner img {
  display: block;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 3px solid #2F62D4;
  background: #FFFFFF;
  object-fit: cover;
}

/* Race name + distance, stacked in the centre of the oval */
.uma-loader-label {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0 90px;
  pointer-events: none;
}

.uma-loader-label .uma-loader-race {
  font-weight: 800;
  letter-spacing: 0.02em;
  color: #2F62D4;
  font-size: 1.25rem;
  line-height: 1.2;
}

.uma-loader-label .uma-loader-dist {
  margin-top: 0.15rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: #3D4759;
  font-size: 0.8rem;
}

.uma-loader-label .uma-loader-route {
  margin-top: 0.1rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #2F62D4;
  font-size: 0.72rem;
  opacity: 0.85;
}

/* Subtle gallop bob */
@keyframes uma-gallop {
  0%, 100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-3px);
  }
}

.loading-progress-text {
  text-align: center;
  font-weight: bold;
  margin-top: 0.25rem;
  color: #555;
}

code {
  color: #c02d76;
}

/* ── Deck Builder full-height panels ─────────────────────────────────────────
   These target MudPaper / MudTabs *component roots*, which Blazor scoped CSS
   cannot reach (the scope attribute isn't applied to child-component roots).
   The picker fills its column and scrolls its inner results (.card-search-results);
   the deck side (slots + Stats/Skills) is natural height and the *whole left column*
   scrolls as one (see .deck-col-left), so a short viewport reaches the skills instead
   of clipping them. */
.deck-title {
  flex: 0 0 auto;
  margin-bottom: 12px;
}

.deck-col-left > .mud-paper:first-child {
  flex: 0 0 auto;
}

.deck-aux {
  /* grow to fill spare height (so the panel reaches the column bottom) but never shrink
     below its content - when the viewport is too short the left column scrolls instead. */
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
}

/* Stats / Skills switch: two equal-width MudButtons acting as a tab control. */
.deck-aux-switch {
  flex: 0 0 auto;
  margin-bottom: 12px;
}

.deck-aux-body {
  flex: 0 0 auto;
}

.deck-pick-panel {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* Stacked layout (narrow screens / zoom): drop every internal scroll region and let the
   deck builder flow naturally down the page. Critically, nothing here may shrink below
   its content - every panel is flex: 0 0 auto with min-height: auto. A shrinkable,
   min-height:0 flex item (the columns, .deck-aux-body, and the picker's .card-search all
   were) collapses below its content, and with overflow visible it then spills and paints
   over the next panel - that was the picker overlapping the skills list. */
@media (max-width: 900px) {
  .deck-aux,
  .deck-aux-body,
  .deck-pick-panel,
  .deck-pick-panel .card-search,
  .deck-pick-panel .card-search-results {
    flex: 0 0 auto;
    min-height: auto;
    overflow: visible;
  }
}

/* ── Compare page panels ─────────────────────────────────────────────────────
   These style MudPaper / MudStack component roots that scoped CSS can't reach. The page flows with
   the main scroll; the simulator card (left) is content-sized and its matrix scrolls horizontally on
   its own, while the picker card (right) fills the sticky sidebar box and scrolls its inner results. */
.compare-title {
  margin-bottom: 12px;
}

/* Centre the empty-state hint in the left column until cards are added. */
.compare-view-empty {
  max-width: 28rem;
  margin: 0 auto;
  padding: 24px 0;
}

/* The picker fills its column (see .compare-col-right) and scrolls its own results. It fills the
   column height via flex so the inner .card-search / .card-search-results (flex:1 1 0 min-height:0)
   get a resolved height instead of collapsing to a sliver. */
.compare-pick-panel {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* Mobile picker dialog: on phones the picker moves out of the right column into a full-screen dialog
   (matching the Options dialog). This body wrapper fills the dialog content and is a flex column so the
   reused .compare-pick-panel keeps filling the height and scrolling its own results, exactly as it does
   in the desktop sidebar (its search box stays put while the grid scrolls under it). */
.compare-picker-dialog-body {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* ── Roster builder layout ───────────────────────────────────────────────────
   Centre each row and keep it its natural size (these target component root elements like
   .roster-current that scoped CSS can't reach). The card art is capped to a slice of the viewport
   in the component's own CSS so the whole tool fits a phone screen without the controls below it
   being pushed off. */
.roster-ingest > * {
  width: 100%;
  max-width: 52rem;
  margin-left: auto;
  margin-right: auto;
  flex: 0 0 auto;
}

/* ── Browse lists: pinned header + scrolling results ─────────────────────────
   Opt-in (the page's root .browse-page makes .uma-main a non-scrolling flex column,
   see MainLayout.razor.css). The page title sits on the canvas; the filters and the
   results share one card below it (.browse-card), inside which the header stays put
   and only .browse-results scrolls. Shared by the Characters / Supports / Skills
   pages, so it lives here rather than in scoped CSS. */
.browse-page {
  height: 100%;
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

.browse-title {
  flex: 0 0 auto;
}

/* The filters + results card. Same shape as the deck picker's .deck-pick-panel: a MudPaper
   that fills the remaining column and is itself a flex column, so its header can stay put
   while the results scroll inside the card rather than the card growing past the viewport.
   Global because it is set through Class= on a MudPaper, whose root scoped CSS can't reach. */
.browse-card {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.browse-head {
  flex: 0 0 auto;
}

.browse-results {
  padding-top: 4px;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

/* On narrow screens the shell already lets the page scroll; restore natural flow. Nothing here
   may shrink below its content: a flex-grow item with min-height:0 collapses to zero height
   inside an auto-height parent, which would leave the card painted behind its own tile grid. */
@media (max-width: 900px) {
  .browse-page {
    height: auto;
    flex: 0 1 auto;
    overflow: visible;
  }

  .browse-card,
  .browse-results {
    flex: 0 0 auto;
    min-height: auto;
    overflow: visible;
  }
}

.mud-input-control.mud-input-control-margin-dense.mud-input-outlined-with-label {
  margin-top: 0 !important;
  margin-bottom: 0 !important;
}

/* ── Mobile touch polish ─────────────────────────────────────────────────────
   Removes the 300ms tap delay on interactive surfaces and gives standard buttons a
   comfortable touch height on phones. Icon buttons are intentionally left alone: some
   live in dense layouts (e.g. the planner cell's quick-delete) where a forced 44px would
   cover their content - those need per-component handling, not a blanket rule. */
.mud-button-root,
.mud-icon-button,
.mud-nav-link,
.mud-chip,
.mud-menu-item,
.mud-card.cursor-pointer,
.support-tile.cursor-pointer {
  touch-action: manipulation;
}

@media (max-width: 900px) {
  .mud-button-root.mud-button {
    min-height: 44px;
  }
}


/* ── CM planner (/cm). GLOBAL ONLY BY NECESSITY ──────────────────────────────
   Everything here is applied through a `Class=` on a MudBlazor component, so Blazor
   never stamps the owning component's scope attribute onto the rendered DOM and a
   scoped .razor.css rule would silently do nothing. Per-component layout that sits on
   plain HTML lives in each component's scoped .razor.css instead. ── */

/* On MudSelect (CmMeetHeader). Width lives inline on the component so it hits the input
   root reliably; this is only a safety cap on very narrow viewports. */
.cm-meet-select {
  max-width: 100%;
}

/* On MudPaper / MudIcon. The section card shell, shared by the summary and track panels. */
.cm-card {
  height: 100%;
}

.cm-card-icon {
  vertical-align: -4px;
  margin-right: 4px;
  color: var(--mud-palette-text-secondary);
}

/* On MudText, everywhere de-emphasised body text is needed. Global because Blazor scopes
   CSS per component and the muted tone is shared far beyond any one of them. Note this is
   the palette's *text*-secondary (a muted grey), NOT Color.Secondary (the gold accent),
   which the rules forbid on text. */
.uma-text-muted {
  color: var(--mud-palette-text-secondary);
}

/* On MudTextField / MudSelect / MudNumericField, across the detail, deck-row, stat-row,
   edit-bar and schedule-instant editors. */
.cm-edit-grow {
  flex: 1 1 220px;
  min-width: 180px;
}

.cm-edit-narrow {
  flex: 0 1 150px;
  min-width: 120px;
}

/* On MudNumericField (CmStatRow): centred value with the stat's identity colour underlined.
   The inner selectors reach MudBlazor's own input DOM. */
.cm-stat-edit-field {
  width: 100%;
}

.cm-stat-edit-field .mud-input-slot {
  text-align: center;
  font-weight: 600;
}

.cm-stat-edit-field .mud-input.mud-input-outlined::after {
  content: "";
}

.cm-stat-edit-field .mud-input-outlined-border {
  border-bottom: 2px solid var(--cm-stat-color);
}

/* On MudNumericField (CmStatSkillRow): the "how many of this skill" box. */
.cm-stat-need-count-field {
  flex: 0 0 90px;
}

/* On MudTextField (CmGridRowLabel). */
.cm-gridboard-label-field {
  margin-top: 0;
}

/* On MudDropZone. The drop targets of both drag boards. .cm-drop-can is the containers'
   CanDropClass (see CmDragGridBoard, CmDragTierBoard): with ApplyDropClassesOnDragStarted every
   zone that can take the dragged uma lights up as soon as the drag starts. MudBlazor has no
   per-zone hover class, so the stronger "you are over this one" state is plain :hover, which the
   zone still receives mid-drag because mud-drop-zone-drag-block kills pointer events on its
   children. */
.cm-tier-drop {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-content: flex-start;
  min-height: 92px;
  padding: 6px;
  margin-top: 6px;
  border: 2px dashed var(--mud-palette-lines-default);
  border-radius: 6px;
  transition: border-color 120ms ease, background-color 120ms ease;
}

.cm-tier-drop:empty::after {
  content: "drop umas here";
  align-self: center;
  padding: 0 6px;
  font-size: 0.75rem;
  color: var(--mud-palette-text-disabled);
}

.cm-tier-drop.cm-drop-can {
  border-color: color-mix(in srgb, var(--mud-palette-primary) 55%, transparent);
  background: color-mix(in srgb, var(--mud-palette-primary) 6%, transparent);
}

.cm-tier-drop.cm-drop-can:hover {
  border-color: var(--mud-palette-primary);
  background: color-mix(in srgb, var(--mud-palette-primary) 16%, transparent);
}

/* Cells look like the view's grid cells (solid rounded border), turning dashed on drag-over. */
.cm-gridboard-drop {
  display: flex;
  flex: 1 1 auto;
  flex-wrap: wrap;
  gap: 6px;
  align-content: center;
  justify-content: center;
  align-items: center;
  min-height: 92px;
  height: 100%;
  padding: 6px;
  border: 1px solid var(--mud-palette-lines-default);
  border-radius: 6px;
  transition: border-color 120ms ease, background-color 120ms ease;
}

/* An empty cell says so, rather than reading as a rendering gap. */
.cm-gridboard-drop:empty::after {
  content: "+";
  font-size: 1.25rem;
  line-height: 1;
  color: var(--mud-palette-text-disabled);
}

.cm-gridboard-drop.cm-drop-can {
  border: 2px dashed color-mix(in srgb, var(--mud-palette-primary) 55%, transparent);
  background: color-mix(in srgb, var(--mud-palette-primary) 6%, transparent);
}

.cm-gridboard-drop.cm-drop-can:hover {
  border: 2px dashed var(--mud-palette-primary);
  background: color-mix(in srgb, var(--mud-palette-primary) 16%, transparent);
}

/* The uma pool's drop zone (CmUmaPool): an auto-filling icon grid so it collapses on phones. It is
   also where a placed uma goes to be removed, so during a drag it reads as the discard target -
   error-coloured, not primary, to say "this takes it off the board". */
.cm-pool-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr));
  gap: 6px;
  align-content: start;
  min-height: 60px;
  padding: 6px;
  border: 2px dashed var(--mud-palette-lines-default);
  border-radius: 6px;
  transition: border-color 120ms ease, background-color 120ms ease;
}

.cm-pool-grid.cm-drop-can {
  border-color: color-mix(in srgb, var(--mud-palette-error) 55%, transparent);
  background: color-mix(in srgb, var(--mud-palette-error) 6%, transparent);
}

.cm-pool-grid.cm-drop-can:hover {
  border-color: var(--mud-palette-error);
  background: color-mix(in srgb, var(--mud-palette-error) 14%, transparent);
}

/* EventRequirements' race-name row (MudStack): a global rule because the class targets a child
   MudBlazor component's own rendered root, which scoped .razor.css can't reach. */
.event-requirement-races {
  margin: 2px 0 4px 27px;
}

