/**
 * Portfolio Grid — "Web Design Portfolio" section.
 *
 * Motion budget: hover transitions 150–250ms, ease-out with a quick initial
 * response and a gentle finish.
 */

/* Filtering is a navigation (ADR-0004), and this is what keeps it from reading
   as one: going from the index to a category's archive animates instead of
   blinking, and the Cards both pages share slide to their new places rather than
   being repainted somewhere else.

   Both documents have to opt in for the browser to do it, and this stylesheet
   only loads where the widget is — so the transition happens between grids and
   nowhere else on the site. A Card's own link is unaffected: a project's page
   has no grid, so nothing there answers this. */
@view-transition {
  navigation: auto;
}

/* The Cards animate; the page they sit on does not.

   By default the browser also cross-fades a snapshot of the whole viewport
   underneath them, blending two full-screen layers for every frame of the
   transition — which measured at roughly a third of the frames dropped, on a
   page whose only visible change is inside the grid. Muting that pair leaves
   the shuffle exactly as it was and costs nothing but a heading that swaps
   instead of fading.

   `mix-blend-mode` is not optional here: the pair is composited with
   `plus-lighter` so that a cross-fade holds its brightness, and two layers left
   at full opacity under that would add together into a washed-out page. */
::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}

/* Reduced motion asked for the change to be immediate, and it is not this
   stylesheet's place to skip the transition outright — the browser still needs
   to swap the documents. Cutting every duration to a frame does the same thing
   and cannot leave a half-faded page behind. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: 1ms !important;
    animation-delay: 0s !important;
  }
}

.webxd-portfolio {
  --portfolio-columns: 4;
  --portfolio-column-gap: 32px;
  --portfolio-row-gap: 50px;
  --portfolio-card-gap: 20px;
  --portfolio-card-radius: 5px;
  --portfolio-card-aspect: 1640 / 1024;
  --portfolio-background: #fff;
  --portfolio-text: #000;
  --portfolio-placeholder: #f2f2f2;

  --portfolio-chip-gap: 8px;
  --portfolio-chip-text: #444;
  --portfolio-chip-active-background: #f0efec;
  --portfolio-filters-margin: 40px;

  --portfolio-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --portfolio-hover-duration: 200ms;

  background: var(--portfolio-background);
}

.webxd-portfolio__grid {
  display: grid;
  grid-template-columns: repeat(var(--portfolio-columns), minmax(0, 1fr));
  column-gap: var(--portfolio-column-gap);
  row-gap: var(--portfolio-row-gap);
  list-style: none;
  margin: 0;
  padding: 0;
}

.webxd-portfolio__item {
  margin: 0;
}

/* A Portfolio Category with nothing filed under it. */
.webxd-portfolio__empty {
  margin: 0;
  color: var(--portfolio-chip-text);
  text-align: center;
}

/* Editor-only: what stands in for a grid that has no published work yet. */
.webxd-portfolio__notice {
  margin: 0 0 24px;
  padding: 12px 16px;
  border-radius: 5px;
  background: var(--portfolio-placeholder);
  color: var(--portfolio-text);
  font-size: 14px;
  line-height: 1.5;
}

.portfolio-card--placeholder {
  opacity: 0.55;
}

/* -------------------------------------------------------------------------- */
/* Filter chips                                                               */
/* -------------------------------------------------------------------------- */

.webxd-portfolio__filters {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--portfolio-chip-gap);
  margin-block-end: var(--portfolio-filters-margin);
}

/* Scoped through the grid rather than matched on its own class: Elementor's kit
   styles every link and button on the site with the brand colour, and a bare
   class ties with that and loses on source order — the chips would come out as a
   row of pink blocks. */
.webxd-portfolio .portfolio-chip {
  display: inline-block;
  padding: 6px 14px;
  appearance: none;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--portfolio-chip-text);
  font: inherit;
  font-size: 13px;
  line-height: 1.5;
  text-decoration: none;
  cursor: pointer;
  transition:
    background-color var(--portfolio-hover-duration) var(--portfolio-ease),
    color var(--portfolio-hover-duration) var(--portfolio-ease);
}

/* The chip a visitor is already on leads nowhere, and the pointer should not
   promise otherwise. */
.webxd-portfolio .portfolio-chip[aria-current="page"] {
  cursor: default;
}

/* The active chip is bold, and bold is wider — without this the whole row
   shuffles sideways every time one is chosen. Each chip is therefore always as
   wide as its own bold self: a zero-height copy of the label, laid out but never
   seen, holds that width. */
.webxd-portfolio .portfolio-chip::after {
  content: attr(data-label);
  display: block;
  height: 0;
  overflow: hidden;
  visibility: hidden;
  font-weight: 700;
}

.webxd-portfolio .portfolio-chip:hover {
  color: var(--portfolio-text);
  text-decoration: none;
}

.webxd-portfolio .portfolio-chip.is-active {
  background: var(--portfolio-chip-active-background);
  color: var(--portfolio-text);
  font-weight: 700;
}

.webxd-portfolio .portfolio-chip:focus-visible {
  outline: 2px solid var(--portfolio-text);
  outline-offset: 2px;
}

/* -------------------------------------------------------------------------- */
/* Card                                                                       */
/* -------------------------------------------------------------------------- */

.portfolio-card {
  display: flex;
  flex-direction: column;
  gap: var(--portfolio-card-gap);
}

/* Nothing here scales, tilts or bounces on *hover*: the clip playing is the
   whole effect, and a Card that moved would shift the grid under a pointer
   crossing it. The only transform is the click acknowledgement below, which is
   a press rather than a hover state and is over before the sheet arrives. */
.portfolio-card__media {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: var(--portfolio-card-aspect);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--portfolio-card-radius);
  overflow: hidden;
  background: var(--portfolio-placeholder);
  /* isolate keeps the crossfade off the page's compositing layers */
  isolation: isolate;
  -webkit-tap-highlight-color: transparent;
  transition: transform 140ms var(--portfolio-ease);
}

/* The click is answered by the Card itself, before the sheet has moved at all,
   so a slow open never reads as a click that missed. */
.portfolio-card.is-opening .portfolio-card__media {
  transform: scale(0.994);
}

/* The trigger is an anchor (ADR-0002); it must not read as body copy. */
a.portfolio-card__media {
  cursor: pointer;
  color: inherit;
  text-decoration: none;
}

.portfolio-card__media:focus-visible {
  outline: 2px solid var(--portfolio-text);
  outline-offset: 3px;
}

/* Scoped through the Card rather than matched on their own class: Elementor's
   frontend stylesheet sizes every `video` inside a document with `.elementor
   video`, and a bare class loses to it — the clip would then letterbox to its
   own aspect ratio instead of filling the still's bounds, and hovering would
   jump. Two classes outrank that without reaching for !important. */
.portfolio-card .portfolio-card__thumbnail,
.portfolio-card .portfolio-card__preview {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top;
  display: block;
}

.portfolio-card__preview {
  opacity: 0;
  /* the video sits above the still and simply fades in over it, so the
     bounds never change and there is no image-to-video jump */
  transition: opacity var(--portfolio-hover-duration) var(--portfolio-ease);
  pointer-events: none;
  will-change: opacity;
}

.portfolio-card.is-previewing .portfolio-card__preview {
  opacity: 1;
}

/* Hover overlay: a bottom scrim, the label, and the arrow pill. */

.portfolio-card__scrim {
  position: absolute;
  inset: 0;
  border-radius: var(--portfolio-card-radius);
  /* The label is white, and a Card's image may be white too. The ramp starts
     higher up and lands darker than the design's 40% so the bottom strip is a
     surface the label can sit on rather than a tint: at 55% black, white text
     clears 4.5:1 even over a near-white still. Starting at 50% keeps it a
     falloff rather than a band. */
  background: linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0) 50%,
    rgba(0, 0, 0, 0.55) 100%
  );
  opacity: 0;
  transition: opacity var(--portfolio-hover-duration) var(--portfolio-ease);
  pointer-events: none;
}

.portfolio-card__overlay {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 0 20px 14px;
  opacity: 0;
  transform: translateY(4px);
  transition:
    opacity var(--portfolio-hover-duration) var(--portfolio-ease),
    transform var(--portfolio-hover-duration) var(--portfolio-ease);
  pointer-events: none;
}

.portfolio-card__hover-label {
  font-size: 18px;
  font-weight: 700;
  line-height: 1.4;
  color: #fff;
  /* A short shadow under the glyphs themselves, for the bright detail the
     scrim alone can still lose the label against. Tight and low-opacity: it
     should read as an edge, not as a drop shadow. */
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
}

.portfolio-card__arrow {
  flex: none;
  display: grid;
  place-items: center;
  width: 35px;
  height: 35px;
  border-radius: 999px;
  background: #fff;
  /* The inline SVG draws itself with `currentColor`. */
  color: #000;
}

.portfolio-card__arrow svg {
  display: block;
  width: 16px;
  height: 15px;
}

.portfolio-card__media:hover .portfolio-card__scrim,
.portfolio-card__media:focus-visible .portfolio-card__scrim,
.portfolio-card.is-previewing .portfolio-card__scrim,
.portfolio-card__media:hover .portfolio-card__overlay,
.portfolio-card__media:focus-visible .portfolio-card__overlay,
.portfolio-card.is-previewing .portfolio-card__overlay {
  opacity: 1;
}

.portfolio-card__media:hover .portfolio-card__overlay,
.portfolio-card__media:focus-visible .portfolio-card__overlay,
.portfolio-card.is-previewing .portfolio-card__overlay {
  transform: translateY(0);
}

.portfolio-card__title {
  margin: 0;
  font-size: 20px;
  font-weight: 700;
  line-height: 1.4;
  text-align: center;
  text-wrap: balance;
  color: var(--portfolio-text);
}

/* Without hover there is no preview state, so the affordance stays put. */
@media (hover: none) {
  .portfolio-card__scrim,
  .portfolio-card__overlay {
    opacity: 1;
    transform: none;
  }
}

/* Row gap and type scale down here; the column count does not. That belongs to
   the responsive Columns control, which emits its own per-breakpoint rules
   against Elementor's configured breakpoints — a second set here would fight
   them on any site that moves one. */
@media (max-width: 767px) {
  .webxd-portfolio {
    --portfolio-row-gap: 36px;
  }

  .portfolio-card__title {
    font-size: 18px;
  }
}

/* -------------------------------------------------------------------------- */
/* Drawer                                                                     */
/* -------------------------------------------------------------------------- */

/* The Drawer is appended to the body, outside any widget, so nothing declared on
   `.webxd-portfolio` is in scope here — every token it shares with the grid has
   to be restated rather than inherited, and a `var()` reaching for one that is
   not below would silently fall back instead of failing. */
.portfolio-drawer {
  --portfolio-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --portfolio-card-aspect: 296 / 160;
  --portfolio-background: #fff;
  --portfolio-text: #000;
  --portfolio-muted-text: #33383f;
  --portfolio-placeholder: #f2f2f2;

  --drawer-duration: 420ms;
  --drawer-content-fade: 300ms;
  /* the sheet enters first and its content follows it in */
  --drawer-content-delay: 140ms;
  /* how much of the dimmed page stays visible above the sheet */
  --drawer-peek: 44px;
  --drawer-radius: 16px;

  position: fixed;
  inset: 0;
  z-index: 99999;
  display: flex;
  align-items: flex-end;
  visibility: hidden;
  pointer-events: none;
}

.portfolio-drawer.is-active {
  visibility: visible;
  pointer-events: auto;
}

/* The sheet's parts are shown and hidden with the `hidden` attribute, and every
   one of them sets a `display` that would otherwise outrank the browser's own
   rule for it. */
.portfolio-drawer [hidden] {
  display: none;
}

/* Focus is visible on whatever is in the sheet, not only on the Drawer's own
   chrome: the content is an authored document, and a link in it that shows
   nothing when focused strands a keyboard visitor inside the trap above. The
   Drawer's own controls state their own outline below and outrank this. */
.portfolio-drawer :focus-visible {
  outline: 2px solid var(--portfolio-text);
  outline-offset: 2px;
}

.portfolio-drawer__backdrop {
  position: absolute;
  inset: 0;
  cursor: pointer;
  background: rgba(8, 8, 10, 0.6);
  /* a light wash rather than a heavy blur — the page recedes, it doesn't melt */
  backdrop-filter: blur(2px) saturate(0.9);
  opacity: 0;
  transition: opacity 300ms var(--portfolio-ease);
}

.portfolio-drawer.is-open .portfolio-drawer__backdrop {
  opacity: 1;
}

/* Rounded on its top edge only, and stopping short of the top of the viewport
   so the page it came from is still visibly there behind it. */
.portfolio-drawer__sheet {
  position: relative;
  width: 100%;
  height: calc(100% - var(--drawer-peek));
  margin-block-start: auto;
  background: var(--portfolio-background);
  border-start-start-radius: var(--drawer-radius);
  border-start-end-radius: var(--drawer-radius);
  overflow: hidden;
  box-shadow: 0 -18px 60px rgba(0, 0, 0, 0.32);
  transform: translateY(100%);
  /* Quick to start and gentle to finish, and staying inside the 0–1 band the
     whole way: nothing here overshoots, springs or bounces. */
  transition: transform var(--drawer-duration) var(--portfolio-ease);
  will-change: transform;
}

.portfolio-drawer.is-open .portfolio-drawer__sheet {
  transform: translateY(0);
}

/* Sits in the exposed strip above the sheet, over the dimmed page.
   Scoped through the Drawer rather than matched on its own class: Elementor's
   kit styles every `button` on the site with the brand colour, and a bare class
   ties with that and loses on source order — the close control would then be a
   pink block floating over the dimmed page. */
.portfolio-drawer .portfolio-drawer__close {
  position: absolute;
  inset-block-start: 0;
  inset-inline-end: 14px;
  z-index: 2;
  display: grid;
  place-items: center;
  width: var(--drawer-peek);
  height: var(--drawer-peek);
  padding: 0;
  appearance: none;
  border: 0;
  border-radius: 0;
  background: none;
  color: #fff;
  cursor: pointer;
  opacity: 0;
  transition:
    opacity 240ms var(--portfolio-ease) 120ms,
    transform 150ms var(--portfolio-ease);
}

.portfolio-drawer.is-open .portfolio-drawer__close {
  opacity: 1;
}

.portfolio-drawer__close:hover {
  transform: scale(1.08);
}

/* White, because this one sits over the dimmed page rather than over the sheet.
   Scoped through the Drawer to outrank the blanket rule above. */
.portfolio-drawer .portfolio-drawer__close:focus-visible {
  outline: 2px solid #fff;
  outline-offset: -4px;
}

.portfolio-drawer__scroll {
  height: 100%;
  overflow-y: auto;
  /* The sheet's end is its own end: reaching it must not start moving the page
     behind. */
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

/* Everything in the sheet settles in just after it has begun moving — a layered
   entrance rather than the whole thing arriving at once. */
.portfolio-drawer__inner {
  opacity: 0;
  transform: translateY(10px);
  transition:
    opacity 280ms var(--portfolio-ease) var(--drawer-content-delay),
    transform 280ms var(--portfolio-ease) var(--drawer-content-delay);
}

.portfolio-drawer.is-open .portfolio-drawer__inner {
  opacity: 1;
  transform: translateY(0);
}

/* The skeleton and the arriving content share one cell, so the swap is a
   crossfade in place rather than one shoving the other down the sheet. */
.portfolio-drawer__body {
  display: grid;
  grid-template-areas: "stack";
  padding-block-end: 96px;
}

.portfolio-drawer__body > * {
  grid-area: stack;
  min-width: 0;
}

.portfolio-drawer__content {
  opacity: 0;
  transition: opacity var(--drawer-content-fade) var(--portfolio-ease);
}

.portfolio-drawer.is-loaded .portfolio-drawer__content {
  opacity: 1;
}

.portfolio-drawer__skeleton {
  display: flex;
  flex-direction: column;
  gap: 18px;
  min-height: 55vh;
  max-width: 1024px;
  width: 100%;
  margin-inline: auto;
  /* Stands where the content will: at the top of the sheet, off its edges. */
  padding: 24px;
  opacity: 0;
  transition: opacity var(--drawer-content-fade) var(--portfolio-ease);
}

.portfolio-drawer.is-waiting .portfolio-drawer__skeleton {
  opacity: 1;
}

.portfolio-drawer__shimmer {
  display: block;
  height: 28px;
  border-radius: 6px;
  background: linear-gradient(
      90deg,
      rgba(0, 0, 0, 0.05) 25%,
      rgba(0, 0, 0, 0.09) 37%,
      rgba(0, 0, 0, 0.05) 63%
    )
    0 0 / 400% 100%;
  animation: portfolio-drawer-shimmer 1.4s ease-in-out infinite;
}

.portfolio-drawer__shimmer:first-child {
  height: 46vh;
  border-radius: 10px;
}

.portfolio-drawer__shimmer:last-child {
  width: 60%;
}

@keyframes portfolio-drawer-shimmer {
  from {
    background-position: 100% 0;
  }
  to {
    background-position: 0 0;
  }
}

/* A project with nothing built says so, rather than opening onto a blank sheet. */
.portfolio-drawer__empty {
  max-width: 1024px;
  margin-inline: auto;
  padding: 24px;
  font-size: 17px;
  line-height: 1.6;
  color: var(--portfolio-muted-text);
  opacity: 0;
  transition: opacity var(--drawer-content-fade) var(--portfolio-ease);
}

.portfolio-drawer.is-loaded .portfolio-drawer__empty {
  opacity: 1;
}

body.portfolio-drawer-open {
  overflow: hidden;
}

@media (max-width: 767px) {
  .portfolio-drawer {
    --drawer-peek: 32px;
    --drawer-radius: 14px;
  }

  .portfolio-drawer__skeleton,
  .portfolio-drawer__empty {
    padding-inline: 18px;
  }
}

/* -------------------------------------------------------------------------- */
/* Reduced motion                                                             */
/* -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .portfolio-card__media,
  .portfolio-card__preview,
  .portfolio-card__scrim,
  .portfolio-card__overlay,
  .portfolio-drawer__backdrop,
  .portfolio-drawer__sheet,
  .portfolio-drawer__inner,
  .portfolio-drawer__close,
  .portfolio-drawer__content,
  .portfolio-drawer__skeleton,
  .portfolio-drawer__empty {
    transition-duration: 1ms;
    transition-delay: 0ms;
  }

  .portfolio-card__overlay,
  .portfolio-drawer__inner {
    transform: none;
  }

  /* No acknowledgement to see, so the Card does not move either. */
  .portfolio-card.is-opening .portfolio-card__media {
    transform: none;
  }

  /* The sheet appears where it will stay instead of sliding up to it. */
  .portfolio-drawer__sheet {
    transform: none;
    opacity: 0;
    transition: opacity 1ms linear;
  }

  .portfolio-drawer.is-open .portfolio-drawer__sheet {
    transform: none;
    opacity: 1;
  }

  .portfolio-drawer__backdrop {
    backdrop-filter: none;
  }

  .portfolio-drawer__shimmer {
    animation: none;
  }
}
