:root {
  --ink: #1c2b3a;
  --frame-black: #0e1a24;
  --metal-light: #eaf4fb;
  --metal: #a9cbe4;
  --metal-dark: #4f7996;
  --steel-1: #cfe4f2;
  --steel-2: #7fa8c4;
  --paper: #eef6fb;
  --paper-dark: #d7e8f3;
}

html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  overflow: hidden;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  background:
    radial-gradient(circle at 50% 16%, rgba(146, 190, 224, 0.22), transparent 34%),
    linear-gradient(145deg, #10202e, #1c3648 58%, #0b1620);
}

.page-shell {
  position: relative;
  width: 100%;
  height: 100dvh;
  overflow: hidden;
}

/*
 * REAL TRADING CARD PROPORTIONS
 *
 * A physical trading card is 63mm x 88mm, i.e. an exact
 * width:height ratio of 63:88 (~0.7159), portrait orientation.
 *
 * 630px is the FIXED reference design width (63 x 10, a clean
 * number to verify the math). The height is NEVER set directly -
 * .card-front below uses "aspect-ratio: 63 / 88", so the browser
 * always derives an exact 880px height from this fixed 630px
 * width, giving a reference card that is always rendered
 * internally at its full 630x880 design size.
 *
 * IMPORTANT: this width is intentionally a plain fixed 630px, NOT
 * a viewport-relative min(). Using a viewport-relative width here
 * was a real bug: on narrow (mobile) screens it shrunk the CSS
 * width itself, which - combined with aspect-ratio - also shrunk
 * the card's total height, while every fixed-size element inside
 * it (padding/border in px, the info-table's icon/label column
 * widths in px, all font-size values in rem) stayed at their
 * absolute, unshrunk size. That fixed "overhead" then ate up a
 * much larger share of the smaller mobile canvas, squeezing the
 * flexible info rows down to heights smaller than their own text,
 * which made the text overflow those rows.
 *
 * The fix is to always render the card at this one fixed 630x880
 * reference size, and let fitCard() in script.js be the ONLY
 * thing responsible for fitting it to any viewport, via a single
 * uniform CSS "transform: scale(...)" on this element.
 */
.card-stage {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 630px;
  transform: translate(-50%, -50%);
  transform-origin: center;
  transition: none;
}

/*
 * Two SEPARATE transition-duration classes, applied one at a time
 * by script.js, so the fall and the return can each have their
 * own independent speed instead of sharing a single duration:
 *
 * - .card-falling: kept deliberately slow/dramatic (6s), matching
 *   the original design and the length of caduta.mp4.
 * - .card-returning: slowed down from an earlier, too-fast 1s to
 *   3s instead, giving the return movement itself more visible
 *   weight/drama while still remaining noticeably quicker than
 *   the 6s fall. Must match RETURN_DURATION_MS in script.js.
 *
 * Only one of the two is ever present on .card-stage at a time;
 * neither is present outside of the download animation sequence,
 * so normal resize/orientation-change repositioning (handled by
 * fitCard) stays instant the rest of the time (see the base
 * .card-stage rule above, "transition: none").
 */
.card-stage.card-falling {
  transition: transform 6s cubic-bezier(0.65, 0, 0.35, 1);
}

.card-stage.card-returning {
  transition: transform 3s cubic-bezier(0.65, 0, 0.35, 1);
}

.card-perspective {
  width: 100%;
  perspective: 950px;
}

/*
 * --rotate-y defaults to 180deg (back face showing) so the card
 * reveals its back immediately on first paint, before any
 * JavaScript has even run - avoiding a "flash" where the front
 * would be visible for an instant first. initializeCardRotation()
 * in script.js is responsible for animating this back to 0deg
 * (front) automatically after a short delay on first load, and
 * also for the interactive drag-to-rotate behaviour afterwards.
 */
.profile-card {
  --rotate-x: 0deg;
  --rotate-y: 180deg;
  position: relative;
  width: 100%;
  transform: rotateX(var(--rotate-x)) rotateY(var(--rotate-y));
  transform-style: preserve-3d;
  transform-origin: center;
  will-change: transform;
}

.card-face {
  width: 100%;
  border-radius: 26px;
  overflow: hidden;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  box-shadow:
    0 3px 7px rgba(0, 0, 0, 0.55),
    0 18px 34px rgba(0, 0, 0, 0.43),
    0 38px 70px rgba(0, 0, 0, 0.34);
}

/*
 * "aspect-ratio: 63 / 88" is what actually enforces the real
 * card's proportions: given .card-front's width (100% of
 * .card-stage, border-box), the browser computes its height as
 * width * 88 / 63, always, at any scale.
 */
.card-front {
  position: relative;
  aspect-ratio: 63 / 88;
  margin: 0;
  padding: 13px;
  border: 5px solid #0b1b26;
  background:
    linear-gradient(120deg, rgba(255,255,255,.10), transparent 24%),
    linear-gradient(135deg, #6fa3c4, #3a6786 55%, #7cb2d4);
}

.card-back {
  position: absolute;
  inset: 0;
  background: #000;
  transform: rotateY(180deg);
}

/*
 * The frame is a flex column that always fills the exact height
 * .card-front's aspect-ratio produced (height:100%). Its four
 * sections below use flex:0 0 auto (title bar, photo, type bar -
 * each sized by its own natural content/aspect-ratio) except the
 * last one, .details-panel, which uses flex:1 1 auto to
 * automatically absorb whatever height remains.
 */
.ornamental-frame {
  height: 100%;
  display: flex;
  flex-direction: column;
  padding: 9px;
  border: 3px solid var(--frame-black);
  border-radius: 18px;
  background:
    radial-gradient(circle at 30% 18%, rgba(255,255,255,.28), transparent 28%),
    linear-gradient(135deg, var(--steel-1), var(--metal-dark) 45%, var(--steel-1) 72%, var(--steel-2));
  box-shadow: inset 0 0 0 3px rgba(234, 244, 251, 0.45);
}

.title-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 7px 10px;
  border: 3px solid var(--frame-black);
  border-radius: 13px;
  background: linear-gradient(var(--metal-light), var(--metal) 58%, var(--steel-1));
  box-shadow: inset 0 2px 0 rgba(255,255,255,.55), inset 0 -2px 0 rgba(20,22,24,.28);
}

.card-title {
  margin: 0;
  flex: 1 1 auto;
  min-width: 0;
  color: var(--ink);
  font-family: "Cinzel", Georgia, "Times New Roman", serif;
  font-size: 2rem;
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-shadow: 0 1px 0 rgba(255,255,255,.45);
}

.title-socials {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
}

.title-social-button,
.title-social-button:hover,
.title-social-button:focus,
.title-social-button:active {
  display: inline-flex !important;
  width: 28px !important;
  height: 28px !important;
  min-width: 28px !important;
  max-width: 28px !important;
  min-height: 28px !important;
  max-height: 28px !important;
  align-items: center;
  justify-content: center;
  border: 2px solid #0e1a24;
  background: #16222d !important;
  transform: none !important;
  transition: none !important;
  box-shadow: none !important;
}

.title-social-button i {
  color: var(--metal-light) !important;
  font-size: 0.95rem;
}

/*
 * Three <video> elements are stacked in the same .portrait-panel:
 * .portrait-video-fall (the resting/intro video, always visible
 * by default), .portrait-video-dance (played on tap/click on the
 * photo, ballo.mp4/ballo2.mp4 chosen at random), and
 * .portrait-video-return (played while the card returns after a
 * download, rientro.mp4). All three share this base rule (sizing,
 * object-fit, drag/selection prevention), then position:absolute
 * + inset:0 stacks them exactly on top of one another, and
 * opacity/z-index below control which one is actually visible at
 * any time.
 */
.portrait-image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  -webkit-user-drag: none;
  user-select: none;
  -webkit-user-select: none;
  pointer-events: none;
  position: absolute;
  inset: 0;
}

.portrait-video-fall {
  position: relative;
  z-index: 1;
}

.portrait-video-dance {
  z-index: 2;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.portrait-video-dance.is-active {
  opacity: 1;
}

/*
 * Highest z-index of the three: in the unlikely case a visitor
 * taps the photo (triggering the dance video) at the exact moment
 * a download-triggered return is also in progress, the return
 * video is the one that stays visible on top, since it is tied to
 * a much shorter, time-critical animation window.
 */
.portrait-video-return {
  z-index: 3;
  opacity: 0;
  transition: opacity 0.15s ease;
}

.portrait-video-return.is-active {
  opacity: 1;
}

/*
 * The photo/video supplied is exactly 1774 x 887 px (2:1 ratio).
 * The frame uses the same exact aspect-ratio so the image fills
 * it perfectly with no cropping and no empty bands.
 */
.portrait-panel {
  position: relative;
  flex: 0 0 auto;
  width: 100%;
  aspect-ratio: 1774 / 887;
  height: auto;
  margin: 8px 4px;
  overflow: hidden;
  border: 4px solid var(--frame-black);
  background: #0d0e10;
  box-shadow: inset 0 0 0 2px var(--steel-2);

  display: flex;
  align-items: center;
  justify-content: center;
}

.type-bar {
  flex: 0 0 auto;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 10px;
  margin-top: 8px;
  border: 3px solid var(--frame-black);
  border-radius: 13px;
  background: linear-gradient(var(--metal-light), var(--metal) 58%, var(--steel-1));
  box-shadow: inset 0 2px 0 rgba(255,255,255,.55), inset 0 -2px 0 rgba(14,26,36,.28);
  color: var(--ink);
}

.type-text {
  flex: 1 1 auto;
  min-width: 0;
  text-align: left;
  font-family: "Cinzel", Georgia, "Times New Roman", serif;
  font-size: 1.2rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.type-socials {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
}

/*
 * This is the flexible section: flex:1 1 auto makes it absorb
 * exactly whatever height remains after title-bar + photo +
 * type-bar have taken their own natural/aspect-ratio-driven
 * height. min-height:0 is required so a flex item is allowed to
 * become smaller than its content's default minimum size.
 */
.details-panel {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  margin-top: 8px;
  padding: 10px;
  border: 4px solid var(--frame-black);
  background:
    linear-gradient(rgba(255,255,255,.4), rgba(255,255,255,.1)),
    radial-gradient(circle at 20% 18%, #f6f7f8, var(--paper) 62%, var(--paper-dark));
  box-shadow: inset 0 0 22px rgba(60, 66, 71, 0.18);
}

/*
 * There are 3 info rows (.details-list) and 4 ability rows
 * (.abilities-block), 7 in total. Giving the two blocks flex-grow
 * of 3 and 4 respectively - and every individual row inside them
 * flex:1 1 0 - makes ALL 7 rows share the details-panel's
 * available height in exactly equal parts, automatically, at any
 * card size.
 */
.details-list {
  flex: 3 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  margin: 0 0 6px;
}

/*
 * TRUE 2-COLUMN, 50/50 TABLE LAYOUT
 *
 * Exactly 2 grid tracks, each "minmax(0, 1fr)": since both tracks
 * use the identical fr unit, the browser always splits the
 * available width EXACTLY in half between them - column 1
 * (icon + label, wrapped together in .info-label-cell so they
 * behave as a single grid item) and column 2 (the value), each
 * permanently guaranteed 50% of the row's width, at any card
 * size.
 */
.details-list li,
.ability-row {
  flex: 1 1 0;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  column-gap: 12px;
  padding: 5px 0;
  color: var(--ink);
}

.details-list li + li,
.ability-row + .ability-row {
  border-top: 1px solid rgba(45, 50, 54, 0.22);
}

.abilities-block {
  flex: 4 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  border-top: 2px solid rgba(45, 50, 54, 0.55);
  padding-top: 6px;
}

.ability-row-strong-top {
  border-top: 2px solid rgba(45, 50, 54, 0.55) !important;
  margin-top: 2px;
  padding-top: 5px;
}

/*
 * Left column: icon + label, side by side, occupying the left
 * half (minmax(0, 1fr) track above). min-width:0 lets this flex
 * container shrink below its content's natural width just like
 * the value column does, so it also participates correctly in
 * the 50/50 split instead of forcing the column wider.
 */
.info-label-cell {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.detail-icon,
.ability-row > .info-label-cell > i {
  flex: 0 0 auto;
  color: #202327;
  font-size: 1.2rem;
}

.details-list strong,
.ability-row strong {
  flex: 1 1 auto;
  min-width: 0;
  font-family: "Cinzel", Georgia, "Times New Roman", serif;
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  line-height: 1.15;
  white-space: normal;
  overflow-wrap: break-word;
  word-break: break-word;
}

/*
 * ":not(.info-label-cell)" is required here: the label wrapper
 * (column 1) is ALSO a <span> (.info-label-cell), so the plain
 * selector "li > span" alone would incorrectly match it too and
 * apply these value-specific styles to the label wrapper as well.
 */
.details-list li > span:not(.info-label-cell),
.ability-row > span:not(.info-label-cell) {
  min-width: 0;
  text-align: right;
  font-size: 1.18rem;
  line-height: 1.28;
  white-space: normal;
  overflow-wrap: break-word;
  word-break: break-word;
  hyphens: auto;
}

.ability-row a {
  color: #1b1e21;
  font-weight: 600;
  text-decoration: underline;
}

.card-back-image {
  display: block;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: fill;
  border-radius: inherit;
  -webkit-user-drag: none;
  user-select: none;
  -webkit-user-select: none;
  pointer-events: none;
}

/*
 * The vCard button lives outside the rotating, ratio-locked card
 * (a sibling of .card-stage in the DOM), so it never rotates, is
 * never affected by the card's aspect-ratio or its JS scale
 * transform, and is always fixed on screen.
 *
 * Its "bottom" distance is recalculated dynamically in JS
 * (positionDownloadButtonEquidistant) so that the gap between the
 * card and the button always equals the gap between the button
 * and the bottom of the screen.
 *
 * Layout: label on top, download icon below, both
 * centered, uppercase text, green background.
 */
.vcard-fixed-button,
.vcard-fixed-button:hover,
.vcard-fixed-button:focus,
.vcard-fixed-button:active,
.vcard-fixed-button:visited {
  position: fixed;
  left: 50%;
  bottom: 30px;
  transform: translateX(-50%) !important;
  z-index: 20;

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

  padding: 12px 26px !important;
  border-radius: 18px;
  border: 3px solid #1d5c34;
  color: #ffffff;
  background: linear-gradient(#2fae59, #1f8a44 55%, #177036) !important;
  transition: none !important;
  scale: 1 !important;
  box-shadow:
    0 3px 6px rgba(0, 0, 0, 0.4),
    0 8px 16px rgba(0, 0, 0, 0.3) !important;

  height: auto !important;
  width: auto !important;
}

.vcard-fixed-label {
  font-family: "Cinzel", Georgia, "Times New Roman", serif;
  font-size: 1.2rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-align: center;
  line-height: 1.25;
  color: #ffffff;
}

.vcard-fixed-button i {
  color: #ffffff !important;
  font-size: 1.7rem;
  line-height: 1;
}

/*
 * Desktop (mouse): rotation only happens while the left
 * button is held and dragged, so the card shows a "grab"
 * cursor at rest and a "grabbing" cursor while dragging.
 * Buttons/links keep a normal pointer cursor, since a plain
 * click on them still opens their destination as usual.
 */
@media (hover: hover) and (pointer: fine) {
  .profile-card {
    cursor: grab;
  }

  .profile-card.is-dragging {
    cursor: grabbing;
  }

  .profile-card .title-social-button {
    cursor: pointer;
  }

  .profile-card.is-dragging .title-social-button {
    cursor: grabbing;
  }
}

.profile-card {
  touch-action: none;
}

.profile-card.is-dragging {
  user-select: none;
  -webkit-user-select: none;
}

/*
 * Only the standalone download button below is viewport-width
 * responsive: it lives OUTSIDE .card-stage, so it is never scaled
 * by fitCard()'s transform and needs its own breakpoint sizing.
 */
@media (max-width: 600px) {
  .vcard-fixed-button,
  .vcard-fixed-button:hover,
  .vcard-fixed-button:focus,
  .vcard-fixed-button:active,
  .vcard-fixed-button:visited {
    /*
     * "bottom" is intentionally left out here: it is always
     * recalculated dynamically in JS to keep the button
     * equidistant between the card and the screen edge on every
     * screen size, including this one.
     */
    padding: 9px 18px !important;
  }

  .vcard-fixed-label {
    font-size: 0.975rem;
  }

  .vcard-fixed-button i {
    font-size: 1.4rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .profile-card {
    transform: none !important;
    will-change: auto;
  }

  .card-back {
    display: none;
  }
}
