/* ============================================================
   costume-card.css — shared long "Costume" card (167x389)
   ============================================================
   Reverse-engineered from the real Unreal widget blueprint export
   (not guessed/approximated) -- see W_HeroInfo_CostumeItem.json/.cpp
   (04_UI/03_Contents/HeroInformation/Component/) and
   W_CardScrollList_Costume.json (04_UI/03_Contents/Costume/).
   Added 2026-09-01.

   Base card = 6 layers, back to front:
     Z1   grade frame/background            (Atl_Costume_01_Sprite_24..27)
     Z2   masked Big costume portrait        (512x512 CostumePortrait/Big)
     Z3   lower black shading overlay        (Atl_Costume_01_Sprite_35)
     Z4   costume/hero name labels           (added 2026-09-01, NOT in the
                                              real Unreal card -- see below)
     Z10  optional state/selection effects   (Img_Select / Img_Hot / Img_New / Img_Sale)
     Z100 transparent interaction target

   Z4 labels are a deliberate deviation from the real widget (which has no
   text widgets at all -- names come from a separate screen-level
   component, W_CostumeInfo_NameTag.json) -- added on explicit request.
   `font-family:'SKR Global','Lato',...` matches this site's shared font
   stack (`site-chrome.css`'s own @font-face, same fallback chain as
   `shop-item-card.css`) -- no new font import.

   Portrait geometry: the widget's ScaleBox scales the complete
   512x512 Big portrait to ScaleToFitY inside a 373px-tall viewport
   (373/512 = 0.728515625), producing a 373x373 square that is then
   clipped by an 8px-inset 151x373 viewport -- 111px cropped off each
   side. A hand-authored border-radius/clip-path is NOT a substitute:
   the real silhouette comes from Tex_Costume_Mask01.webp (a 512x512
   RGB LUMINANCE mask, visible strip x:152-358), scaled and masked at
   the SAME 373x373 size before the viewport clips it.

   Mask asset note (2 layered gotchas, both confirmed via real
   headless-Chrome renders, not assumed):
   1) Tex_Costume_Mask01.webp as extracted has no alpha channel (RGB
      grayscale, black=hidden/white=visible) -- a plain CSS alpha mask
      on that file is wrong everywhere (whole image reads as opaque).
      Fixed the same way as every other consumer in this repo: a
      pre-baked ALPHA-mask copy (RGB forced to white, alpha = source
      luminance, verified byte-identical to the source grayscale
      channel) staged at images/costumes/frames/Tex_Costume_Mask01.webp,
      used with plain default-alpha masking -- `mask-mode: luminance`
      is deliberately NOT used (cross-browser support is uneven, and
      it would read the now-flattened white RGB instead of the alpha
      we baked, masking nothing).
   2) `mask-image:url('<any local file>')` / `-webkit-mask-image`
      renders **completely blank under file://** in Chrome, even
      though the identical path loads fine as a plain `<img src>` --
      confirmed on this exact asset via headless render. Every page on
      this site is opened via file:// during local dev (CLAUDE.md), so
      an on-disk mask url() would only ever work post-deploy over
      https and silently fail locally the entire time it's being
      built. Fix: the alpha-mask above is inlined as a `data:` URI
      directly in `--costume-mask-data` below (masking a `data:` URI
      is not a file fetch, so it isn't subject to this restriction --
      also reconfirmed via headless render). It's ~600 bytes because
      the mask compresses extremely well; regenerate it (RGB white,
      alpha = luminance of a freshly re-extracted Tex_Costume_Mask01.
      webp, lossless WEBP, base64) if that source file ever changes,
      and keep the on-disk frames/Tex_Costume_Mask01.webp copy in sync
      as the readable source of truth even though the CSS itself
      doesn't fetch it.

   Frame selection (confirmed 2026-09-01 by pixel-diffing every
   candidate, not guessed): the widget's 5 GradeBG image slots share
   just 4 real sprites --
     Img_GradeBG4              -> Atl_Costume_01_Sprite_24  == Legendary (L)
     Img_GradeBG3               -> Atl_Costume_01_Sprite_25  == Rare (R)
     Img_GradeBG2               -> Atl_Costume_01_Sprite_26  == Uncommon (UC)
     Img_GradeBG1 / Img_GradeBG1_1 -> Atl_Costume_01_Sprite_27 == Common (C)
   costume-card.js exposes this as CostumeCard.GRADE_FRAME_FILE; it
   never recolors a generic frame with CSS. Points at the ORIGINAL,
   UNTOUCHED {l,r,uc,c}.webp files -- a same-day cleanup attempt
   (COMBINE/clean_costume_frame_speck.py, {l,r,uc,c}_clean.webp) wrongly
   treated the lower-left x:0-6/y:377-385 pixels as a stray artifact and
   erased them; they are real frame geometry connecting the left edge to
   the bottom edge, and removing them broke the corner. See
   COSTUME_CARD_NOTES.md -- do not reintroduce the _clean files.

   No card-gap value is exported by W_CardScrollList_Costume -- don't
   invent one here; the consuming page's own list/grid layout owns
   spacing.
*/

:root {
  /* Tex_Costume_Mask01.webp, RGB forced to white + alpha = source
     luminance (see header note above) -- do NOT point this at an
     on-disk url() instead, it will render blank under file://. */
  --costume-mask-data: url("data:image/webp;base64,UklGRlQCAABXRUJQVlA4TEcCAAAv/8F/EFVAkiQ3bKP/f3ouPsPY2M4C2Ai4sW2rbu6IOVYZ2MCv4f13xaxO1ABTA/SOwHbomDly/AswhXznTcCUH1+SmZkEDn12ywsen8FWk3t6RHb3obZ+gR4MMjNXVnbNBQPs3dXm2DUWHYB9XZyj0lD4kpBneWUG6Sj4sJVuKBhga91OOFxctRspSUhzdvV+qJXgq3grAaz24Ct6ExFL2Jz4li/x8RXtUeBLfLjIVb6DFhHfIuLDQaRHKuHnqt8+qz6gPrDKI5RQunewd059QH174iN96uNMfTinPi60hy/wqQ+oDz71AfWROfUxl9+pj4728CVAfqc+WH5AfDOnPvTHH0Z9+JCZzz7444/BvZkfGFx//OHJj8+Pv7P/7D/7z/6z/+w/+8/+s//sP/vP/rP/7D/7z/6z/+w/+8/+s//sP/vP/rP/7D/7z/6z/+w/+8/+s//sP/vP/rP/7D/7z/6z/+w/+8/+s//sP/vP/rP//vbxs/vj7w7z428X9fE3H34IG/nhfvw9pD4O8qMov1Ef59pDZlZ+D6mPjfxwxXeYUR9afRn1odUHRn2U1AcIqS/qg/FFeyDqi/pWe3yFNZFp3TeA9apvxcfnSo+fXdS0b/VQIz7UaI8v1Chvd1EjPtRoj9VDXVw2FiM7ZGbuYHzaA4n2KEd7gMhKD1CYbi4WgPFNO/NWKQyMLw3FdZ0w6IfwtRSN0iAz8wrAJB7qalIcCoSB8SUP9VU41IYNeh4afM0VGlzwWg0+g8czt4yGGu5Dr98DAA==");
}

.costume-card {
  position: relative;
  flex: 0 0 167px;
  width: 167px;
  height: 389px;
  min-width: 167px;
  min-height: 389px;
  isolation: isolate;
  transition: transform .18s ease, filter .18s ease;
}

/* Deliberately no overflow:hidden on the outer card -- Unreal clips
   the portrait ScaleBox, not necessarily a future external
   selection/effect layer sitting in Z10. */

/* Hover: a small lift + a glow colored by the card's own grade -- no
   outline/border change (deliberate, direct request 2026-09-01: "let them
   just glow, no outline", contrasted against the New-Unit banner tile's
   hover treatment below, which DOES use an outline). `filter:drop-shadow`
   (not `box-shadow`) so the glow follows the card's own real alpha
   silhouette (the frame art's rounded corners) instead of a rectangular
   box bleeding past them. Colors reuse the site's already-established
   hero-rarity palette for L/R (`.claude/rules/frontend-ui.md`'s
   `R:#5d48d7 L:#ea7e19`); UC/C have no existing token (that palette only
   covers hero rarities, and hero rarity has no UC/C tier) so their exact
   values were hand-specified directly: UC `#61b48a`, C `#9495a9`. */
.costume-card:hover { transform: translateY(-3px); }
.costume-card[data-grade="L"]:hover  { filter: drop-shadow(0 0 16px #ea7e19bb); }
.costume-card[data-grade="R"]:hover  { filter: drop-shadow(0 0 16px #5d48d7bb); }
.costume-card[data-grade="UC"]:hover { filter: drop-shadow(0 0 16px #61b48abb); }
.costume-card[data-grade="C"]:hover  { filter: drop-shadow(0 0 16px #9495a9bb); }

.costume-card__frame {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  width: 167px;
  height: 389px;
  pointer-events: none;
  user-select: none;
}

.costume-card__portrait-viewport {
  position: absolute;
  inset: 8px;
  z-index: 2;
  width: 151px;
  height: 373px;
  overflow: hidden;
  pointer-events: none;
}

/* ScaleToFitY scales the complete 512x512 Big portrait to 373x373
   (373/512 = 0.728515625); the resulting square is centered inside
   the 151px-wide clipped viewport (222px total overflow, 111px
   cropped from each side). See file header re: the alpha-baked mask
   asset -- do not add `mask-mode: luminance` back in. */
.costume-card__portrait-mask {
  position: absolute;
  top: 0;
  left: 50%;
  width: 373px;
  height: 373px;
  transform: translateX(-50%);

  -webkit-mask-image: var(--costume-mask-data);
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
  -webkit-mask-size: 100% 100%;
  mask-image: var(--costume-mask-data);
  mask-repeat: no-repeat;
  mask-position: center;
  mask-size: 100% 100%;
}

.costume-card__portrait {
  display: block;
  width: 373px;
  height: 373px;
  max-width: none;
  object-fit: fill;
  pointer-events: none;
  user-select: none;
}

/* Pixel-snapped to whole numbers, symmetric 6px/6px inset (167 - 6 - 6
   = 155) -- the Unreal slot's own fractional 6.5px/6.5px inset
   rasterizes asymmetrically in Chrome (fractional CSS lengths don't
   split evenly across the physical pixel grid). Explicit width:155px
   (not width:auto + right:6px) -- confirmed via real
   getBoundingClientRect() measurement that a replaced element (<img>)
   with height set and width:auto ignores left/right entirely and
   falls back to its OWN intrinsic ratio (CSS2.1 10.3.8 solves an
   absolutely-positioned replaced element's auto width from height +
   intrinsic ratio first: 156x248 native, from a height:248 shade
   asset), landing a 6px LEFT but only a 5px RIGHT gap -- still
   asymmetric, just a different asymmetry than the fractional one this
   change set out to fix. Setting width explicitly reproduces the real
   stretched Unreal canvas slot AND lands exactly 6px/6px; `right` is
   then over-constrained and ignored by design, so it's omitted rather
   than kept as a no-op declaration. */
.costume-card__bottom-shade {
  position: absolute;
  left: 6px;
  bottom: 7px;
  z-index: 3;
  display: block;
  width: 155px;
  height: 248px;
  pointer-events: none;
  user-select: none;
  /* Multiplies the sprite's own variable per-pixel alpha by 90% --
     applied to this <img> alone, never the card/labels/text. */
  opacity: 0.9;
}

/* Centered within the exact fade area (y:134-382, matching the bottom
   shade's own bounds) -- not part of the real Unreal card (see file
   header), added on explicit request. */
.costume-card__labels {
  position: absolute;
  left: 12px;
  right: 12px;
  top: 134px;
  height: 248px;
  z-index: 4;

  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;

  color: #fff;
  text-align: center;
  font-family: 'SKR Global','Lato', Arial, sans-serif;
  pointer-events: none;
  /* Shifts the centered text block down 45px without changing the
     box's own geometry (top:134/height:248 stay the fade area's real
     bounds) -- a translate, not a top/padding change, so this stays a
     pure visual nudge. */
  transform: translateY(45px);
}

.costume-card__costume-name {
  max-width: 100%;
  font-size: 15px;
  line-height: 1.15;
  font-weight: 700;
  text-wrap: balance;
  overflow-wrap: anywhere;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}

.costume-card__hero-name {
  max-width: 100%;
  font-size: 12px;
  line-height: 1.2;
  font-weight: 400;
  color: rgba(255, 255, 255, 0.82);
  text-wrap: balance;
  overflow-wrap: anywhere;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}

.costume-card__effects {
  position: absolute;
  inset: 0;
  z-index: 10;
  pointer-events: none;
}

.costume-card__hit-target {
  position: absolute;
  inset: 0;
  z-index: 100;
  display: block;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  font-family: inherit;
}

/* Horizontal, non-shrinking scroll list -- W_CardScrollList_Costume
   confirms Orientation:Horizontal, ScrollbarVisible:false. */
.costume-card-list {
  display: flex;
  flex-direction: row;
  overflow-x: auto;
  overflow-y: visible;
  scrollbar-width: none;
}

.costume-card-list::-webkit-scrollbar {
  display: none;
}
