/* ─────────────────────────────────────────────────────────────────────────────
   lobbybuy-zipper.css — ZIPPER SKIN for the product-grid tier bar.

   PURELY COSMETIC. Nothing here is referenced by JS. It only restyles the
   existing .pcard-pbar-* nodes that catalog.js already renders and that
   pricebar.js already drives (fill width %, thumb left %, .unlocked class).
   The drag-to-preview interaction, the countdown label and the goal price are
   untouched.

   TO UNDO: delete the single <link> to this file in lobbybuy.html (line ~24).
   Nothing else in the codebase changes. Deleting this file alone is also safe.

   Palette is the LobbyBuy brand book (brand-assets/README.md) rather than the
   generic green of the reference mock:
     tape    Route Teal   var(--teal) → var(--teal-dark)
     teeth   silver/white
     slider  Dock Navy    var(--navy)
     open    Steel Mist   var(--steel)
   ───────────────────────────────────────────────────────────────────────────── */

.pcard-pbar-wrap {
  --zip-h:      16px;   /* tape height  */
  --zip-pitch:   9px;   /* tooth-to-tooth spacing */
  --zip-tooth:   4px;   /* tooth width  */
  --zip-row:     6px;   /* tooth height */
  --zip-stop:   16px;   /* space reserved at the right end for the top stop */
  margin-top: 8px;
}

/* The old 1px scale ticks are meaningless on a zipper — the teeth are the
   scale now. */
.pcard-pbar-wrap .pbar-tick { display: none; }

/* ── Open half: pale tape, teeth parted into two rows ─────────────────────── */
.pcard-pbar-track {
  height: var(--zip-h);
  border-radius: 99px;
  background-color: #EDF1F3;
  background-image:
    repeating-linear-gradient(90deg, #BFC9D0 0 var(--zip-tooth), transparent var(--zip-tooth) var(--zip-pitch)),
    repeating-linear-gradient(90deg, transparent 0 4.5px, #BFC9D0 4.5px 8.5px, transparent 8.5px var(--zip-pitch));
  background-position: left 4px top 1px, left 4px bottom 1px;
  background-size: calc(100% - var(--zip-stop)) var(--zip-row), calc(100% - var(--zip-stop)) var(--zip-row);
  background-repeat: no-repeat, no-repeat;
  box-shadow: inset 0 0 0 1px rgba(23, 50, 77, .10);
}

/* ── Top stop ──────────────────────────────────────────────────────────────
   A real zipper ends in a metal stop, not in teeth that fade into a rounded
   cap. Without it the last few percent of the track is a crescent-shaped
   sliver of half-cropped teeth — which is what made "1 order to unlock" look
   broken rather than nearly-closed. The stop gives the tape a real terminus,
   so the final gap is a clean, deliberate notch. */
.pcard-pbar-track::after {
  content: '';
  position: absolute;
  right: 3px;
  top: 50%;
  width: 4px;
  height: calc(var(--zip-h) - 5px);
  border-radius: 2px;
  background: var(--navy);
  opacity: .38;
  transform: translateY(-50%);
  pointer-events: none;
  transition: opacity .25s ease, background-color .25s ease;
}

/* ── Closed half: teal tape, teeth meshed together in the middle ──────────── */
.pcard-pbar-fill {
  height: var(--zip-h);
  /* Left cap rounds into the tape; the right edge is the CUT — the point the
     slider has reached — so it stays square. Two nested round caps within a
     few px of each other is what read as a rendering glitch near the end. */
  border-radius: 99px 3px 3px 99px;
  background-color: var(--teal);
  background-image:
    repeating-linear-gradient(90deg, #F4F7F8 0 var(--zip-tooth), transparent var(--zip-tooth) var(--zip-pitch)),
    repeating-linear-gradient(90deg, transparent 0 4.5px, #DCE4E7 4.5px 8.5px, transparent 8.5px var(--zip-pitch)),
    linear-gradient(180deg, rgba(255,255,255,.16), rgba(0,0,0,.14));
  background-position: left 4px top 2px, left 4px bottom 2px, 0 0;
  background-size: calc(100% - 8px) var(--zip-row), calc(100% - 8px) var(--zip-row), 100% 100%;
  background-repeat: no-repeat, no-repeat, repeat;
  box-shadow: inset 0 0 0 1px rgba(10, 107, 101, .55);
}

/* ── The unlock moment: the zipper is fully done up ───────────────────────
   Locked, the fill is a cut tape with a square right edge. Closed, it becomes
   one continuous green zipper: both caps round, teeth pulled tight into a
   single meshed column, and a one-shot ring pulse so the moment is felt and
   not just noticed. */
.pcard-pbar-fill.unlocked {
  background-color: var(--green);
  border-radius: 99px;
  /* The teeth MUST be restated here, not inherited from .pcard-pbar-fill.
     lobbybuy.css has `.pcard-pbar-fill.unlocked { background: var(--green) }` —
     a shorthand at specificity 0-2-0, which outranks the 0-1-0 rule above and
     resets background-image to none. Without this block the bar loses every
     tooth the instant it unlocks and renders as a plain green pill. */
  background-image:
    repeating-linear-gradient(90deg, #FFFFFF 0 var(--zip-tooth), transparent var(--zip-tooth) var(--zip-pitch)),
    repeating-linear-gradient(90deg, transparent 0 4.5px, #E8F0EA 4.5px 8.5px, transparent 8.5px var(--zip-pitch)),
    linear-gradient(180deg, rgba(255,255,255,.18), rgba(0,0,0,.16));
  background-position: left 4px top 3px, left 4px bottom 3px, 0 0;
  background-size: calc(100% - 8px) var(--zip-row), calc(100% - 8px) var(--zip-row), 100% 100%;
  background-repeat: no-repeat, no-repeat, repeat;
  animation: zip-closed .5s cubic-bezier(.34, 1.56, .64, 1) 1;
}

@keyframes zip-closed {
  0% {
    box-shadow: inset 0 0 0 1px rgba(21, 128, 61, .5), 0 0 0 0 rgba(45, 198, 83, .55);
    transform: scaleY(1);
  }
  45% {
    box-shadow: inset 0 0 0 1px rgba(21, 128, 61, .5), 0 0 0 5px rgba(45, 198, 83, .28);
    transform: scaleY(1.14);
  }
  100% {
    box-shadow: inset 0 0 0 1px rgba(21, 128, 61, .5), 0 0 0 9px rgba(45, 198, 83, 0);
    transform: scaleY(1);
  }
}

/* The stop is what the slider has just latched into — it goes green with the
   tape instead of sitting there as a leftover navy tick. */
.pcard-pbar-track:has(.pcard-pbar-fill.unlocked)::after {
  background: var(--green);
  opacity: .9;
}

/* ── Slider body ──────────────────────────────────────────────────────────── */
.pcard-pbar-thumb {
  /* 13px, not 15px: at "1 order to unlock" the remaining gap on a two-up
     mobile card is only ~10px wide, and a wider slider swallowed it whole —
     the bar looked full while the label still said one to go. */
  width: 13px;
  height: calc(var(--zip-h) + 8px);
  border: 1px solid rgba(23, 50, 77, .38);
  border-radius: 7px;
  background: linear-gradient(180deg, #FCFDFD 0%, #D3DADE 42%, #93A0A8 100%);
  transform: translate(-50%, -50%);
  box-shadow: 0 1px 4px rgba(23, 50, 77, .28);
  /* The slider rides ON TOP of everything else in the bar: the tape, the
     top stop, and the celebrate ring. It is the one part the finger
     grabs, so nothing may paint over it. Works because the thumb is
     already position:absolute, and .pcard-pbar-track sets no z-index of
     its own, so this competes directly with its siblings. */
  z-index: 3;
}

/* the navy grip slot on the slider face */
.pcard-pbar-thumb::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 7px;
  height: 4px;
  border-radius: 99px;
  background: var(--navy);
  opacity: .7;
  transform: translate(-50%, -50%);
}

/* Parked against the top stop, with a short latch bounce. */
.pcard-pbar-thumb.unlocked {
  /* Same shorthand trap as the fill: lobbybuy.css has
     `.pcard-pbar-thumb.unlocked { background: var(--green) }` at 0-2-0, which
     flattens the metal gradient into a flat green blob. The slider is metal
     whether the zipper is open or closed. */
  background: linear-gradient(180deg, #FCFDFD 0%, #D3DADE 42%, #93A0A8 100%);
  /* Slider stays FULLY metal when closed - no green trim. The tape going
     green is the unlock signal; the slider is hardware and never recolours. */
  border-color: rgba(23, 50, 77, .38);
  animation: zip-park .45s cubic-bezier(.34, 1.56, .64, 1) 1;
}
/* grip slot keeps its navy, same as the open state */
.pcard-pbar-thumb.unlocked::after { background: var(--navy); opacity: .7; }

@keyframes zip-park {
  0%   { transform: translate(-50%, -50%) scale(1); }
  40%  { transform: translate(-50%, -50%) scale(1.22); }
  100% { transform: translate(-50%, -50%) scale(1); }
}

.pcard-pbar-thumb:active { transform: translate(-50%, -50%) scale(1.12); }

/* The bar is now 4x taller, so the countdown line needs a little more air. */
.pcard-pbar-label { margin-top: 7px; }

/* ── Resting closed zipper (best price already reached) ────────────────────
   Rendered by the isBest branch in lobbybuy/catalog.js. There is no next tier
   to preview here, so nothing is draggable — the slider is parked on the stop
   as a full stop, not a handle. */
.pbar-best { cursor: default; }
.pbar-best .pcard-pbar-thumb { cursor: default; pointer-events: none; }
.pbar-best .pcard-pbar-thumb:active { transform: translate(-50%, -50%); }

/* ── Locked goal price ─────────────────────────────────────────────────────
   It should still READ as locked — muted, not the live teal of the price the
   resident pays today — but the old #c0cece sits at ~1.8:1 against white,
   which is below the 4.5:1 legibility floor and effectively invisible on a
   phone in daylight. Same "disabled" intent, ~4.7:1 contrast. */
.pcard-pbar-goal {
  color: #5F7387;
  font-size: 15px;
  opacity: 1;
}

/* Once it's the price they actually get, it stops being muted. The existing
   .unlocked price-flash keyframes still run on top of this. */
.pcard-pbar-goal.unlocked { color: var(--green); }

/* ── Desktop product detail ────────────────────────────────────────────────
   The detail bar reuses the same .pcard-pbar-* classes (with pd- ids), so it
   picks the zipper up for free on mobile. Desktop is the exception:
   lobbybuy.css scopes overrides under `#screen-product #pd-pbar-wrap` at
   0-2-1 specificity, which forced the tape back to a flat 8px line and the
   slider back to an 18px square. These rules match that specificity to win it
   back, and take the extra room a desktop panel offers — a taller tape with a
   wider tooth pitch, since this is the single biggest bar in the app.

   Media query mirrors the desktop block in lobbybuy.css (line ~1419). */
@media (min-width: 768px) and (min-height: 501px) {
  #screen-product #pd-pbar-wrap {
    --zip-h:     22px;
    --zip-pitch: 12px;
    --zip-tooth:  5px;
    --zip-row:    8px;
    --zip-stop:  20px;
  }
  #screen-product #pd-pbar-wrap .pcard-pbar-track {
    height: var(--zip-h);
    background-color: #FFFFFF;
    box-shadow: inset 0 0 0 1px #D6EBE9;
  }
  #screen-product #pd-pbar-wrap .pcard-pbar-fill {
    height: var(--zip-h);
  }
  #screen-product #pd-pbar-wrap .pcard-pbar-thumb {
    width: 17px;
    height: calc(var(--zip-h) + 10px);
    border-radius: 8px;
  }
  /* The panel behind this bar is teal-pale, so the locked goal price needs a
     touch more weight than it does on white to hold the same contrast. */
  #screen-product #pd-pbar-wrap .pcard-pbar-goal {
    font-size: 19px;
    color: #55697C;
  }
}

/* Narrow phones: two cards per row leaves ~110px of track, so shrink the
   rhythm rather than let the teeth crowd into a solid smear. */
@media (max-width: 380px) {
  .pcard-pbar-wrap {
    --zip-h:     14px;
    --zip-pitch:  8px;
    --zip-tooth: 3.5px;
    --zip-row:    5px;
    --zip-stop:  14px;
  }
}

/* Users who ask for less motion still get the zipper, just without the
   springy snap-back the drag preview uses. */
@media (prefers-reduced-motion: reduce) {
  .pcard-pbar-fill,
  .pcard-pbar-thumb { transition: none !important; animation: none !important; }
}
