/* ======================================================================
   THE PORTFOLIO — circular project carousel
   Loaded only by gallery.html. Behaviour lives in portfolio-dial.js, the
   project list in projects.js.

   The composition is one photograph at a time, a caption beside it and a
   dial of circular project discs beneath. Everything here leans on the
   tokens in common.css; no colour, font or easing is re-declared.

   Two rules shape the whole sheet:

   1. The photography is never cropped. AL-SAMA's covers run from 0.45:1
      (a tall shopfront) to 2.23:1 (a panoramic facade), so any fixed frame
      with object-fit:cover would slice the signage out of half the
      portfolio. The frame is a fixed box and the picture is contained
      inside it — the picture keeps its own proportions and simply hangs
      in the space, which is also why the layout never jumps between
      projects.

   2. Gold is for rules, rings and fills. Brand gold measures 2:1 on cream,
      so every piece of gold-coloured *type* uses --brand-gold-text.
   ====================================================================== */

.pf {
  /* Clears the fixed navbar (60px) and, from 1200px up, the absolutely
     positioned logo and nav pill that sit 30px in from the top corners. */
  --pf-top: 104px;
  --pf-gutter: clamp(20px, 4vw, 64px);

  position: relative;
  box-sizing: border-box;
  min-height: 100vh;
  min-height: 100dvh;
  padding: var(--pf-top) var(--pf-gutter) clamp(20px, 3vh, 36px);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(272px, 30%);
  grid-template-rows: auto minmax(0, 1fr) auto;
  grid-template-areas:
    "masthead masthead"
    "frame    caption"
    "selector selector";
  column-gap: clamp(28px, 4vw, 72px);
  row-gap: clamp(16px, 2.4vh, 32px);
  background: var(--cream-bg);
}

/* ── Masthead: title, and the filter rail ───────────────────────────── */

.pf-masthead {
  grid-area: masthead;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: clamp(20px, 3vw, 48px);
  flex-wrap: wrap;
  padding-bottom: clamp(12px, 1.6vh, 20px);
  border-bottom: 1px solid var(--atelier-border);
}

.pf-eyebrow {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--brand-gold-text);
  margin-bottom: 6px;
}

.pf-heading {
  font-family: var(--font-headings);
  font-weight: 500;
  font-size: clamp(26px, 2.6vw, 38px);
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--atelier-noir);
}

.pf-filters {
  display: flex;
  align-items: center;
  gap: clamp(14px, 1.8vw, 26px);
  flex-wrap: wrap;
  justify-content: flex-end;
}

.pf-tabs {
  display: flex;
  align-items: center;
  gap: clamp(12px, 1.4vw, 20px);
}

/* Every control is at least 24px tall — these are the primary controls on
   the page and WCAG 2.2 (2.5.8) sets 24px as the floor. The padding buys
   the hit area without changing the baseline-aligned look. */
.pf-tab,
.pf-reset,
.pf-select,
.pf-search {
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: none;
  border-radius: 0;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1;
  color: var(--brand-gold-text);
  min-height: 24px;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-soft),
              border-color var(--dur-fast) var(--ease-soft);
}

.pf-tab {
  padding: 5px 0 6px;
  border-bottom: 1px solid transparent;
}

.pf-tab:hover,
.pf-reset:hover { color: var(--atelier-noir); }

.pf-tab.is-active {
  color: var(--atelier-noir);
  border-bottom-color: var(--brand-gold);
}

.pf-select {
  padding: 4px 15px 4px 0;
  background-image: url("data:image/svg+xml,%3Csvg width='8' height='5' viewBox='0 0 8 5' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0l4 5 4-5z' fill='%237D6400'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right center;
  max-width: 148px;
}

.pf-select option { background: #FFFFFF; color: #111111; }

.pf-search {
  padding: 4px 0 5px;
  width: 108px;
  cursor: text;
  border-bottom: 1px solid rgba(212, 175, 55, 0.45);
}

.pf-search::placeholder { color: var(--brand-gold-text); opacity: 0.7; }
.pf-search:focus { border-bottom-color: var(--brand-gold); }

.pf-reset { padding: 4px 0 5px; }

/* ── The photograph ─────────────────────────────────────────────────── */

.pf-frame {
  grid-area: frame;
  position: relative;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Horizontal swipes belong to the carousel, vertical ones to the page. */
  touch-action: pan-y;
}

/* ── The two layers, and the circle that opens between them ──────────
   Nothing here moves or scales. Each layer holds one photograph at full
   frame size, and the transition is carried entirely by the clip circle
   the script writes onto it: the arriving picture is a disc in the arc
   that rises, swells to a bubble in the middle of the frame and then
   bursts open across it; the picture it replaces contracts back into a
   bubble and drops into the arc. Because the photograph itself is never
   transformed, it can stay `contain`ed — no signage is ever cropped, and
   there is no layout work for the browser to do at 60fps. */

.pf-plate {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
}

/* Whichever layer is opening or closing sits above the settled one. */
.pf-plate.is-over { z-index: 2; }

.pf-plate img,
.pf-plate video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
  display: block;
}

/* ── Pause / play ────────────────────────────────────────────────────
   Sits in the corner of the frame, over the margin a contained
   photograph leaves rather than over the picture, and stays quiet until
   it is wanted. */

.pf-autoplay {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 3;
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: rgba(250, 249, 246, 0.72);
  border: 1px solid var(--atelier-border);
  border-radius: var(--radius-circle);
  color: var(--atelier-muted);
  cursor: pointer;
  opacity: 0.5;
  transition: opacity var(--dur-base) var(--ease-soft),
              color var(--dur-base) var(--ease-soft),
              border-color var(--dur-base) var(--ease-soft);
}

.pf-autoplay svg { width: 15px; height: 15px; display: block; }
.pf-autoplay .pf-autoplay-play { display: none; }
.pf-autoplay.is-paused .pf-autoplay-pause { display: none; }
.pf-autoplay.is-paused .pf-autoplay-play { display: block; }

/* Paused is a state worth seeing without hunting for it. */
.pf-autoplay.is-paused { opacity: 1; color: var(--brand-gold-text); border-color: var(--brand-gold); }

.pf-autoplay:hover,
.pf-autoplay:focus-visible { opacity: 1; color: var(--atelier-noir); border-color: var(--brand-gold); }

.pf.is-empty .pf-autoplay { display: none; }

.pf-empty {
  position: absolute;
  inset: 0;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  text-align: center;
  padding: 20px;
}

.pf.is-empty .pf-empty { display: flex; }

.pf-empty-line {
  font-family: var(--font-headings);
  font-size: clamp(22px, 2.4vw, 32px);
  color: var(--atelier-muted);
}

.pf-empty-hint {
  font-size: 13px;
  color: var(--brand-gold-text);
  border-bottom: 1px solid var(--atelier-line);
  padding-bottom: 3px;
}

.pf-empty-hint:hover { color: var(--atelier-noir); border-bottom-color: var(--brand-gold); }

/* ── The caption ────────────────────────────────────────────────────── */

.pf-caption {
  grid-area: caption;
  align-self: center;
  min-width: 0;
}

.pf.is-empty .pf-caption { visibility: hidden; }

.pf-counter {
  font-family: var(--font-headings);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--atelier-subtle);
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 18px;
}

.pf-counter::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--atelier-line);
}

.pf-counter b { font-weight: 500; color: var(--atelier-noir); }

.pf-meta {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--brand-gold-text);
  margin-bottom: 12px;
  min-height: 13px;
}

.pf-title {
  font-family: var(--font-headings);
  font-weight: 500;
  font-size: clamp(30px, 3.4vw, 54px);
  line-height: 1.04;
  letter-spacing: -0.025em;
  color: var(--atelier-noir);
  margin-bottom: 14px;
  overflow-wrap: break-word;
}

.pf-tagline {
  font-size: 15px;
  line-height: 1.6;
  color: #444444;
  max-width: 42ch;
  margin-bottom: 14px;
}

.pf-specs {
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--atelier-subtle);
  margin-bottom: 28px;
  min-height: 14px;
}

.pf-cta {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 15px 28px;
  background: var(--atelier-noir);
  color: var(--cream-bg);
  text-decoration: none;
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  border-radius: var(--radius-xs);
  transition: background-color var(--dur-base) var(--ease-soft);
}

.pf-cta:hover,
.pf-cta:focus-visible { background: var(--brand-gold-text); color: var(--cream-bg); }

.pf-cta span { transition: transform var(--dur-base) var(--ease-soft); }
.pf-cta:hover span { transform: translateX(4px); }

/* display:inline-flex above outranks the browser's [hidden] rule, so the
   projects with no detail page need this to actually hide their button. */
.pf-cta[hidden] { display: none; }

.pf-hint {
  margin-top: 20px;
  font-family: var(--font-body);
  font-size: 11px;
  line-height: 1.6;
  color: var(--atelier-subtle);
}

/* ── The dial of project discs ──────────────────────────────────────── */

.pf-selector {
  grid-area: selector;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(12px, 2vw, 32px);
}

.pf-arrow {
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid var(--atelier-border);
  border-radius: var(--radius-circle);
  color: var(--atelier-noir);
  cursor: pointer;
  transition: border-color var(--dur-base) var(--ease-soft),
              background-color var(--dur-base) var(--ease-soft),
              opacity var(--dur-base) var(--ease-soft);
}

.pf-arrow svg { width: 17px; height: 17px; display: block; }

.pf-arrow:hover:not(:disabled) {
  border-color: var(--brand-gold);
  background: rgba(212, 175, 55, 0.08);
}

.pf-arrow:disabled { opacity: 0.28; cursor: default; }

.pf-dial {
  position: relative;
  flex: 1 1 auto;
  height: 168px;
  max-width: 640px;
  overflow: hidden;
  /* The arc is dragged sideways by script; vertical stays with the page,
     and the browser's own back-swipe never gets a look in. */
  touch-action: pan-y;
}

.pf-dial-track {
  position: absolute;
  inset: 0;
}

/* One base size, scaled by transform. Animating scale rather than width
   keeps the whole dial on the compositor, so the turn stays smooth even
   with the full collection in the DOM. */
.pf-dot {
  --x: 0;
  --y: 0;
  --s: 1;

  position: absolute;
  left: 50%;
  top: 6px;
  width: 112px;
  height: 112px;
  padding: 0;
  border: none;
  border-radius: var(--radius-circle);
  overflow: hidden;
  background: var(--bg-secondary);
  cursor: pointer;
  opacity: 0.42;
  filter: grayscale(1);
  transform: translate(calc(-50% + var(--x) * 1px), calc(var(--y) * 1px)) scale(var(--s));
  transition: transform 0.62s var(--ease-strong),
              opacity 0.42s var(--ease-soft),
              filter 0.42s var(--ease-soft),
              box-shadow 0.42s var(--ease-soft),
              visibility 0s;
}

.pf-dot img,
.pf-dot video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
}

/* A disc with no picture yet (or a film, which has no still) reads as a
   quiet monogram rather than an empty hole. */
.pf-dot-mark {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-headings);
  font-size: 30px;
  color: var(--atelier-subtle);
  background: var(--bg-secondary);
}

.pf-dot:hover { opacity: 0.72; filter: grayscale(0.4); }

.pf-dot.is-active {
  opacity: 1;
  filter: none;
  cursor: default;
  box-shadow: 0 0 0 1px var(--brand-gold),
              0 0 0 7px rgba(212, 175, 55, 0.13),
              0 18px 34px -20px rgba(17, 17, 17, 0.55);
}

/* Outside the visible arc. visibility (not just opacity) so the hidden
   discs leave the tab order and the accessibility tree as well. */
.pf-dot[data-d="far"] {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: transform 0.62s var(--ease-strong),
              opacity 0.42s var(--ease-soft),
              filter 0.42s var(--ease-soft),
              visibility 0s linear 0.42s;
}

/* ── Wide screens: let the picture breathe ──────────────────────────── */

@media (min-width: 1600px) {
  .pf { grid-template-columns: minmax(0, 1fr) minmax(320px, 26%); }
  .pf-dial { max-width: 700px; }
}

/* ── Short desktops: the dial gives up height before the picture does ── */

@media (min-width: 1024px) and (max-height: 800px) {
  .pf { --pf-top: 92px; }
  .pf-dial { height: 140px; }
  .pf-dot { width: 92px; height: 92px; }
  .pf-specs { margin-bottom: 20px; }
}

/* ======================================================================
   MOBILE — a deliberate layout, not the desktop one squeezed.

   The picture stays large and uncropped, the caption sits under it where
   it can be read at arm's length, and the arc becomes a thumbnail rail
   the thumb can flick through. The rail scrolls inside itself, so the
   page never gains a horizontal scrollbar.
   ====================================================================== */

@media (max-width: 1023px) {
  .pf {
    --pf-top: 74px;
    --pf-gutter: clamp(16px, 5vw, 32px);
    display: flex;
    flex-direction: column;
    gap: 0;
    padding-bottom: 28px;
  }

  /* ── Holding the line against the site-wide mobile defaults ──────────
     mobile-common.css styles bare elements for every page below 1024px,
     and does it with !important: h1/h2/p are given fixed sizes, h2 is set
     uppercase, and every <button> becomes a padded 6px-radius pill. That
     is right for the rest of the site and ruinous here — it would square
     off the circular discs and push each photograph into a corner of its
     own button, and print the counter, the industry line and the tagline
     all at one size. What follows re-asserts only what this page needs.

     The button rule there is written `button:not(#back-to-top)`, and that
     id counts towards its weight — so a plain class cannot outrank it even
     with !important, and the rules for this page's buttons are hung off
     #pfRoot to win on specificity rather than on shouting louder. */

  #pfRoot .pf-dot,
  #pfRoot .pf-arrow,
  #pfRoot .pf-autoplay {
    padding: 0 !important;
    border-radius: var(--radius-circle) !important;
  }

  /* 48px square for the same reason as the arrows: the site-wide
     `min-height: 48px` for buttons would otherwise make an oval of it. */
  #pfRoot .pf-autoplay { width: 48px; height: 48px; opacity: 1; }

  #pfRoot .pf-tab,
  #pfRoot .pf-reset {
    padding: 8px 0 !important;
    border-radius: 0 !important;
    font-size: 14px !important;
    font-weight: 400 !important;
  }

  #pfRoot .pf-dot-mark { font-weight: 400 !important; }

  .pf-cta {
    padding: 16px 20px !important;
    border-radius: var(--radius-xs) !important;
    font-weight: 500 !important;
  }

  /* 16px keeps iOS from zooming the page when one of these takes focus. */
  .pf-select,
  .pf-search {
    font-size: 16px !important;
    padding: 8px 16px 8px 0 !important;
  }

  .pf-search { padding-right: 0 !important; }

  .pf-masthead {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
    padding-bottom: 12px;
  }

  .pf-heading { font-size: 26px !important; }
  .pf-eyebrow { font-size: 11px !important; }

  /* One line the thumb can flick along, rather than four wrapped rows
     eating a third of the screen. It scrolls inside itself, so the page
     never gains a horizontal scrollbar. */
  .pf-filters {
    justify-content: flex-start;
    flex-wrap: nowrap;
    /* An explicit width, not a stretched auto one: as a flex item this row
       otherwise takes its content width and pushes the page sideways
       instead of scrolling inside itself. */
    width: 100%;
    min-width: 0;
    gap: 16px;
    overflow-x: auto;
    scrollbar-width: none;
    padding-bottom: 2px;
  }

  .pf-filters::-webkit-scrollbar { display: none; }
  .pf-filters > * { flex: 0 0 auto; }

  .pf-tabs { gap: 16px; }

  .pf-select { max-width: none; width: auto; }
  .pf-search { width: 84px; }

  .pf-frame {
    flex: 1 1 auto;
    min-height: 30vh;
    margin: 14px 0 12px;
  }

  .pf-caption {
    order: 3;
    text-align: center;
    margin-top: 4px;
  }

  .pf-counter { justify-content: center; margin-bottom: 12px; }
  .pf-counter::before,
  .pf-counter::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--atelier-line);
  }

  .pf-counter { font-size: 13px !important; }
  .pf-meta { font-size: 11px !important; margin-bottom: 8px; }
  .pf-title {
    font-size: clamp(26px, 7.4vw, 38px) !important;
    letter-spacing: -0.025em !important;
    text-transform: none !important;
    line-height: 1.06;
    margin-bottom: 10px;
  }
  .pf-tagline { font-size: 14px !important; margin: 0 auto 10px; }
  .pf-specs { font-size: 12px !important; margin-bottom: 20px; }
  .pf-empty-line { font-size: clamp(20px, 5.4vw, 26px) !important; }
  .pf-cta { width: 100%; justify-content: center; }

  /* Rail + arrows. The arrows keep a 44px target and never leave the
     screen; the rail between them takes whatever width is left. */
  .pf-selector {
    order: 2;
    gap: 8px;
    width: 100%;
    min-width: 0;
  }

  /* 48px square, because the site-wide `min-height: 48px` for buttons would
     otherwise stretch a 44px circle into an oval. */
  #pfRoot .pf-arrow { width: 48px; height: 48px; }

  .pf-dial {
    height: auto;
    max-width: none;
    touch-action: auto;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
    overscroll-behavior-x: contain;
  }

  .pf-dial::-webkit-scrollbar { display: none; }

  .pf-dial-track {
    position: static;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 6px;
    width: max-content;
  }

  /* The arc geometry is desktop-only; here the discs are a plain row and
     the custom properties the script writes are simply ignored. */
  .pf-dot {
    position: static;
    flex: 0 0 auto;
    width: 54px;
    height: 54px;
    transform: none;
    scroll-snap-align: center;
    transition: transform 0.4s var(--ease-strong),
                opacity 0.3s var(--ease-soft),
                filter 0.3s var(--ease-soft),
                box-shadow 0.3s var(--ease-soft) !important;
  }

  .pf-dot[data-d="far"] {
    opacity: 0.42;
    visibility: visible;
    pointer-events: auto;
  }

  .pf-dot.is-active {
    transform: scale(1.28);
    margin: 0 8px;
    box-shadow: 0 0 0 1px var(--brand-gold),
                0 0 0 4px rgba(212, 175, 55, 0.16);
  }

  .pf-dot-mark { font-size: 18px; font-weight: 400; }

  .pf-hint { display: none; }
}

@media (max-width: 480px) {
  .pf-select { max-width: 88px; font-size: 12px; }
  .pf-search { width: 78px; font-size: 12px; }
  .pf-tab, .pf-reset { font-size: 12px; }
}

/* ── Reduced motion: the change is instant, nothing travels ──────────── */

@media (prefers-reduced-motion: reduce) {
  .pf-dot { transition-duration: 0.01ms; }
}
