/* ==========================================================================
   Shared cross-page components — visual recipes that showed up identical
   (or near-identical) in more than one page-specific stylesheet get defined
   ONCE here instead of copy-pasted, so there's a single template to tune
   instead of several drifting copies. Page-specific files keep only what's
   actually unique to that page (grid column counts, card widths, etc).
   ========================================================================== */

/* --- Line clamp: caps a card's title/excerpt at exactly 2 lines AND
   reserves that same 2-line space even when the real text only needs 1 —
   so every card in a grid is the same height by construction, regardless
   of which one happens to have a longer headline (e.g. the Portfolio
   archive's "Basecamp Analytics — Feature Walkthrough" wrapping to 2 lines
   next to otherwise-1-line titles was the actual cause of the listing
   grid resizing between filters, not anything about the grid or
   pagination logic — a longer title only ever grows into space that was
   already reserved for it now). min-height is set per selector below (2×
   that element's own line-height, in em so it tracks its own font-size)
   since that part genuinely depends on each context's type scale; only
   the clamping mechanics are shared here. --- */
.clamp-2 {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

/* Same mechanic, 3 lines — for the odd wider/shorter block (e.g. the Shop
   banner's excerpt) where 2 lines would cut off too early but unbounded
   wrapping risks the same "one longer block resizes everything around it"
   problem .clamp-2 solves for cards. */
.clamp-3 {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}

/* --- Card hover-lift: grid/list cards that lift + brighten their border +
   deepen their shadow on hover (Shop, Blog, "related products" strip,
   "other services" strip, blog "related posts").
   transition lives HERE (on the :hover rule, not the base card class) for
   .shop__card/.blog__card specifically: both are also [data-filter-item]
   (filter-pill.css) targets, which declares its OWN transition (opacity +
   transform, with a per-card stagger delay) on that same element. Two
   different `transition` shorthands competing for the same `transform`
   property is a straight cascade fight — whichever rule wins takes over
   ALL of transition's sub-values for that property, including delay, so
   a losing [data-filter-item] would silently drop its stagger and its
   opacity transition entirely (not just look different — the fade/stagger
   stops happening at all). Declaring it on :hover here instead (class +
   pseudo-class = higher specificity than [data-filter-item]'s single
   attribute selector) wins ONLY while hovered, leaving [data-filter-item]
   free to own `transform` the rest of the time. .works__card (Portfolio)
   never had this fight in the first place — it has no hover-lift of its
   own to conflict with. .product__related-card and .article__related-card
   are also absent — their own hover (css/product.css, css/article.css)
   only shifts border-color, deliberately with no lift/shadow/zoom, so a
   hovered "You might also like" / "Keep reading" card doesn't visually
   jump around while the carousel might be mid-slide. */
.shop__card:hover,
.blog__card:hover,
.services__more-card:hover,
.services__more-card:focus-visible {
  transform: translateY(-4px);
  border-color: var(--color-border-strong);
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.32);
  transition: transform var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard);
}

/* The «Інші послуги» strip cards are NOT [data-filter-item]s (unlike shop/blog
   cards), so they can safely carry the transition on the BASE state — otherwise
   the lift/shadow only eases IN (from the :hover rule) and snaps back instantly
   on hover-OUT, which read as abrupt. A gentle 360ms eases both directions. */
.services__more-card {
  transition: transform 360ms var(--ease-standard),
    border-color 360ms var(--ease-standard),
    box-shadow 360ms var(--ease-standard);
}
.services__more-card:hover,
.services__more-card:focus-visible {
  transition-duration: 360ms;
}

/* --- Card media: aspect-ratio box + image that zooms slightly on the
   card's hover. Aspect ratio is the one thing that legitimately varies per
   context (sized to how each grid crops its thumbnails), everything else
   is identical. --- */

/* .shop__card-media and .product__related-media each keep their own
   16:9 ratio in css/shop.css / css/product.css (matching the site's
   product-photography standard elsewhere) rather than this shared 4:3 —
   everything else about them (display/position/overflow, the shared img
   rule below) still comes from here. */
.services__more-media {
  display: block;
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
}

/* .blog__card-media keeps its own 16:9 ratio in css/blog.css (matching
   the site's product-photography standard used on Shop/Product) rather
   than this shared 16:10 — everything else about it (display/position/
   overflow, the shared img rule below) still comes from here. */
.shop__card-media,
.product__related-media,
.blog__card-media {
  display: block;
  position: relative;
  overflow: hidden;
}

/* .article__related-media keeps its own 16:9 ratio in css/article.css
   (matching the Blog grid card's own ratio, since these are the same
   posts) rather than this shared box — everything else about it
   (display/position/overflow, the shared img rule below) still comes
   from here. */

.shop__card-media img,
.product__related-media img,
.services__more-media img,
.blog__card-media img,
.article__related-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 650ms var(--ease-standard);
}

/* .shop__card, .blog__card, .product__related-card and
   .article__related-card are deliberately absent here — their images
   stay still on hover (.shop__card and .blog__card only lift; see their
   own notes in css/shop.css / css/blog.css, .product__related-card's and
   .article__related-card's in css/product.css / css/article.css for why
   each was excluded), so they keep the shared aspect-ratio/object-fit box
   above but not this zoom trigger. */
.services__more-card:hover .services__more-media img {
  transform: scale(1.05);
}

/* --- Media badge: small rounded label overlaid on a card's thumbnail
   (category/type tag). --- */

.shop__card-badge,
.product__related-badge,
.blog__card-badge,
.article__related-badge {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  height: 26px;
  padding: 0 12px;
  display: inline-flex;
  align-items: center;
  border-radius: var(--radius-sm);
  background: rgba(5, 5, 6, 0.72);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.14);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text);
  /* Category/type label — chrome, not readable content, so not selectable. */
  -webkit-user-select: none;
  user-select: none;
}

/* --- Rating stars: the 5-star SVG row used on the Shop cards/featured
   banner and the Product detail page. Size is the one thing that varies by
   context (a card's rating reads smaller than the product panel's own), so
   it's set per selector — only the shared fill color lives here. --- */

.shop__card-star,
.shop__featured-star,
.product__rating-star,
.product__review-stars svg,
.product__related-star {
  fill: var(--color-orange);
}

/* --- Media play button: the rounded-rectangle gradient play control shown
   over a video thumbnail (homepage portfolio mosaic + Portfolio archive
   page share this exact recipe). Fixed, even-integer box centered via
   top/left + negative margins rather than percentage/aspect-ratio +
   translate(-50%,-50%) — that combination caused a sub-pixel jitter once
   the hover scale transition settled, so every instance uses this fixed-
   pixel approach instead. --- */

.works__play,
.portfolio__play {
  width: 74px;
  height: 56px;
  position: absolute;
  top: 50%;
  left: 50%;
  margin: -28px 0 0 -37px;
  transform: scale(0.94);
  border-radius: var(--radius-sm);
  background: var(--gradient-brand-btn);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.16);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 950ms var(--ease-standard);
}

.works__trigger:hover .works__play,
.works__trigger:focus-visible .works__play,
.portfolio__trigger:hover .portfolio__play,
.portfolio__trigger:focus-visible .portfolio__play {
  transform: scale(1);
}

.works__play::after,
.portfolio__play::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: 0 0 14px 2px rgba(91, 42, 134, 0.4), 0 0 26px 4px rgba(192, 41, 139, 0.32),
    0 0 38px 6px rgba(255, 122, 26, 0.2);
  opacity: 0;
  transition: opacity 1125ms var(--ease-standard);
  pointer-events: none;
}

.works__trigger:hover .works__play::after,
.works__trigger:focus-visible .works__play::after {
  opacity: 1;
}

.portfolio__play:hover::after {
  opacity: 1;
}

.works__play svg,
.portfolio__play svg {
  width: 20px;
  height: 20px;
  margin-left: 2px;
}

/* ==========================================================================
   Touch / tablet / mobile: no hover behaviour at all — just the image and a
   tap. This fires on real touch devices (hover: none) AND at any viewport
   ≤1024px (a narrow window with a mouse, and the tablet/mobile previews,
   both still report hover: hover — width is what actually matters for these
   layouts). The card shows its static thumbnail with the play badge always
   visible so it reads as tappable (tapping opens the lightbox via
   js/portfolio.js / js/works.js regardless); the muted preview video never
   starts, the play badge never scales, and its hover glow and the dark
   .portfolio__shade overlay are both off — nothing reacts to a pointer,
   because there isn't one. The Portfolio archive's own .works__shade is
   left as-is (out of scope — only the homepage mosaic was flagged).
   ========================================================================== */
@media (hover: none), (max-width: 1024px) {
  .works__preview,
  .portfolio__preview {
    opacity: 1 !important;
  }

  .portfolio__shade {
    display: none;
  }

  .works__preview-video,
  .portfolio__preview-video {
    display: none;
  }

  /* No play badge on either grid — just the image, tap still opens the
     video (js/portfolio.js). */
  .works__play,
  .portfolio__play {
    display: none;
  }

  /* Kill the works card's own hover motion too: no thumbnail zoom, no dark
     shade overlay — the archive card is just its static thumbnail here. */
  .works__thumb {
    transform: none !important;
    transition: none;
  }

  .works__shade {
    display: none;
  }
}

/* Play badge shrinks on narrow screens (echoes the old site's own
   .products-block__video-btn: 80px desktop -> 60px tablet -> 40px phone)
   so it stays proportionate to the much narrower mosaic/grid tiles instead
   of dominating them. Margin is re-derived each step to keep it centered
   (-half height, -half width). */
@media (max-width: 1024px) {
  .works__play,
  .portfolio__play {
    width: 62px;
    height: 46px;
    margin: -23px 0 0 -31px;
  }
}

@media (max-width: 767px) {
  .works__play,
  .portfolio__play {
    width: 50px;
    height: 38px;
    margin: -19px 0 0 -25px;
  }
}

/* ==========================================================================
   Video lightbox — shared by the homepage mosaic, the Portfolio archive and
   the Services spotlight carousels. Single source of truth here (loaded on
   every page) instead of being duplicated per stylesheet.
   ========================================================================== */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-8);
  background: rgba(5, 5, 6, 0.92);
  opacity: 0;
  visibility: hidden;
  transition: opacity 700ms var(--ease-standard), visibility 700ms;
}

.lightbox.is-open {
  opacity: 1;
  visibility: visible;
}

.lightbox__frame {
  position: relative;
  width: 100%;
  max-width: 1100px;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: #000;
  box-shadow: 0 40px 120px rgba(0, 0, 0, 0.6);
  transform: scale(0.96);
  transition: transform 700ms var(--ease-standard);
}

.lightbox.is-open .lightbox__frame {
  transform: scale(1);
}

.lightbox__frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.lightbox__close {
  position: absolute;
  top: -52px;
  right: 0;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-sm);
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border-strong);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 375ms var(--ease-standard);
}

.lightbox__close:hover {
  background: var(--color-bg-elevated-hover);
}

.lightbox__close svg {
  width: 16px;
  height: 16px;
}

@media (max-width: 767px) {
  .lightbox {
    padding: var(--space-4);
  }
  .lightbox__close {
    top: -46px;
  }
}
