/* clients.css — the private client portal (clients.html only).
 *
 * Loaded ONLY by clients.html, alongside screen.css. Everything here is
 * built from the site's existing tokens (--dutchWhite, --darkSlateGray,
 * --radius) so it inherits the theme swap for free: cream/green in light,
 * green/cream in dark. Nothing in this file hardcodes a brand colour.
 */

/* `hidden` must always win - several blocks below are flex/grid, which
   would otherwise override the attribute's display:none. */
[hidden] {
  display: none !important;
}

.clients-page {
  min-height: 100vh;
}

/* The page is a single column; the wall and the gate both need the full
   content width minus the standard gutter. */
#main-content {
  padding: 0 20px;
}

.clients-title {
  font-family: ambroise-std, serif;
  font-weight: 700;
  font-size: clamp(38px, 6vw, 61px);
  line-height: 1.05;
  margin: 0;
  color: var(--darkSlateGray);
  word-wrap: break-word;
}

/* A gallery focuses its title on open so screen readers read it out. That is
   a script focus, so the browser's blue ring is replaced with something of
   our own - and it only appears for keyboard users. */
.clients-title:focus {
  outline: none;
}

.clients-title:focus-visible {
  outline: 3px solid var(--darkSlateGray);
  outline-offset: 8px;
  border-radius: 4px;
}

/* ==========================================================================
   GATE
   ========================================================================== */

.gate {
  padding: 18px 0 8px;
}

.gate-eyebrow,
.gallery-eyebrow {
  font-family: stratos, sans-serif;
  font-size: 0.72rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--darkSlateGray);
  opacity: 0.7;
  margin: 0 0 14px;
}

/* "Welcome back, Familie Vermeulen" - shown from the name this device
   remembered, so a returning client is greeted before typing anything. */
.gate-greeting {
  margin: -6px 0 16px;
  font-family: ambroise-std, serif;
  font-weight: 700;
  font-size: clamp(20px, 3.6vw, 28px);
  line-height: 1.2;
  color: var(--darkSlateGray);
}

/* Two-column card: the form on the left, the explanation on the right.
   The right column is a solid inverse panel, echoing the footer's
   "Big Green Closer" so the page reads as part of the same site. */
.gate-card {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  align-items: stretch;
  border: 2px solid var(--darkSlateGray);
  border-radius: var(--radius);
  background-color: var(--dutchWhite);
  box-shadow: 8px 8px 0 rgba(var(--darkSlateGray-rgb), 0.22);
  overflow: hidden;
}

.gate-main {
  padding: 36px 38px 32px;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.gate-lede {
  margin: 16px 0 0;
  font-size: 17px;
  line-height: 1.6;
  max-width: 46ch;
  color: var(--darkSlateGray);
  opacity: 0.9;
}

/* --- form ---------------------------------------------------------------- */

.gate-form {
  margin: 26px 0 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.gate-label {
  font-family: stratos, sans-serif;
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--darkSlateGray);
  opacity: 0.75;
}

.gate-input {
  width: 100%;
  padding: 16px 18px;
  border: 2px solid var(--darkSlateGray);
  border-radius: var(--radius);
  background-color: var(--dutchWhite);
  color: var(--darkSlateGray);
  font-family: stratos, sans-serif;
  font-size: 18px;
  letter-spacing: 0.06em;
  line-height: 1.3;
  transition: box-shadow 0.2s ease, background-color 0.2s ease;
}

.gate-input::placeholder {
  color: var(--darkSlateGray);
  opacity: 0.38;
  letter-spacing: 0.02em;
  font-size: 15px;
}

.gate-input:hover {
  background-color: rgba(var(--darkSlateGray-rgb), 0.04);
}

.gate-input:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px rgba(var(--darkSlateGray-rgb), 0.2);
}

/* Wrong code: a red-tinted border without inventing a new palette - it is
   the error state only, and falls back to the brand border in dark mode
   where the tint would be invisible. */
.gate-input.is-invalid {
  border-color: #b3261e;
  box-shadow: 0 0 0 4px rgba(179, 38, 30, 0.16);
}

html[data-theme="dark"] .gate-input.is-invalid {
  border-color: #ffb4ab;
  box-shadow: 0 0 0 4px rgba(255, 180, 171, 0.16);
}

.gate-remember {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 14px;
  line-height: 1.4;
  color: var(--darkSlateGray);
  opacity: 0.85;
  cursor: pointer;
  padding: 2px 0 6px;
}

.gate-remember input {
  width: 18px;
  height: 18px;
  margin: 1px 0 0;
  accent-color: var(--darkSlateGray);
  flex-shrink: 0;
  cursor: pointer;
}

/* Primary action: the site's standard bordered button - 2px brand border,
   --radius corners, solid fill at rest, fill-flip + small lift on hover.
   Deliberately the SAME shape as .gate-input, .gallery-btn and the footer
   CTA's hover, so the gate reads as one component instead of a pill
   dropped in from another design system. No hard offset shadow: no other
   button on the site rests on one. */
.gate-submit {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 52px;
  padding: 14px 26px;
  border: 2px solid var(--darkSlateGray);
  border-radius: var(--radius);
  background-color: var(--darkSlateGray);
  color: var(--dutchWhite);
  font-family: stratos, sans-serif;
  font-size: 0.85rem;
  line-height: 1.2;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow: none;
  transition: background-color 0.25s ease, color 0.25s ease,
              transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.gate-submit:hover,
.gate-submit:focus-visible {
  background-color: var(--dutchWhite);
  color: var(--darkSlateGray);
  transform: translateY(-2px);
}

.gate-submit:active {
  transform: translateY(0);
}

.gate-submit:focus-visible {
  outline: 2px solid var(--darkSlateGray);
  outline-offset: 3px;
}

.gate-submit:disabled {
  cursor: progress;
  transform: none;
  opacity: 0.7;
}

.gate-submit-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

/* Working state: the key icon becomes a spinner so the ~0.3s key
   derivation never looks like a dead click. */
.gate-submit-spinner {
  display: none;
  width: 17px;
  height: 17px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  flex-shrink: 0;
  animation: clients-spin 0.7s linear infinite;
}

.gate-submit.is-busy .gate-submit-spinner {
  display: block;
}

.gate-submit.is-busy .gate-submit-icon {
  display: none;
}

@keyframes clients-spin {
  to { transform: rotate(360deg); }
}

.gate-status {
  margin: 0;
  min-height: 1.4em;
  font-size: 14px;
  line-height: 1.5;
  color: var(--darkSlateGray);
  opacity: 0.9;
}

.gate-status.is-error {
  opacity: 1;
  color: #b3261e;
}

html[data-theme="dark"] .gate-status.is-error {
  color: #ffb4ab;
}

.gate-help {
  margin: 18px 0 0;
  font-size: 15px;
  line-height: 1.6;
  color: var(--darkSlateGray);
  opacity: 0.85;
}

.gate-help a {
  color: var(--darkSlateGray);
  text-decoration: underline;
  text-underline-offset: 3px;
  font-weight: 600;
}

.gate-help a:hover,
.gate-help a:focus-visible {
  text-decoration-thickness: 2px;
}

/* --- explanation panel --------------------------------------------------- */

.gate-side {
  padding: 34px 32px 30px;
  background-color: var(--darkSlateGray);
  color: var(--dutchWhite);
  display: flex;
  flex-direction: column;
  gap: 18px;
  border-left: 2px solid var(--darkSlateGray);
}

.gate-side-title {
  font-family: stratos, sans-serif;
  font-size: 0.72rem;
  font-weight: 400;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  margin: 0;
  color: var(--dutchWhite);
  opacity: 0.75;
}

.gate-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 20px;
  counter-reset: gate-step;
}

.gate-steps li {
  display: flex;
  gap: 14px;
  align-items: flex-start;
}

.gate-step-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  border: 2px solid var(--dutchWhite);
  border-radius: 50%;
}

.gate-step-icon svg {
  width: 18px;
  height: 18px;
  display: block;
}

.gate-step-body {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.gate-step-body strong {
  font-family: stratos, sans-serif;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--dutchWhite);
  font-size: 14px;
}

.gate-step-body span {
  font-size: 14px;
  line-height: 1.5;
  color: var(--dutchWhite);
  opacity: 0.85;
}

.gate-privacy {
  margin: auto 0 0;
  padding-top: 16px;
  border-top: 1px solid rgba(var(--dutchWhite-rgb), 0.25);
  font-size: 13px;
  line-height: 1.55;
  color: var(--dutchWhite);
  opacity: 0.75;
}

/* ==========================================================================
   GALLERY
   ========================================================================== */

.gallery-view {
  padding: 18px 0 24px;
}

.gallery-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  padding-bottom: 16px;
  border-bottom: 3px solid var(--darkSlateGray);
  margin-bottom: 20px;
}

.gallery-heading {
  min-width: 0;
}

.gallery-meta {
  margin: 10px 0 0;
  font-family: stratos, sans-serif;
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--darkSlateGray);
  opacity: 0.75;
}

.gallery-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* Shared button language for every action on this page: bordered pill,
   uppercase stratos, fill-flip on hover - identical to .navLink. */
.gallery-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 11px 18px;
  min-height: 44px;
  border: 2px solid var(--darkSlateGray);
  border-radius: var(--radius);
  background-color: var(--darkSlateGray);
  color: var(--dutchWhite);
  font-family: stratos, sans-serif;
  font-size: 0.76rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  line-height: 1.2;
  cursor: pointer;
  transition: background-color 0.22s ease, color 0.22s ease, transform 0.18s ease;
}

.gallery-btn svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

.gallery-btn-ghost {
  background-color: var(--dutchWhite);
  color: var(--darkSlateGray);
}

@media (hover: hover) and (pointer: fine) {
  .gallery-btn:hover,
  .gallery-btn:focus-visible {
    background-color: var(--dutchWhite);
    color: var(--darkSlateGray);
    transform: translateY(-1px);
  }

  .gallery-btn-ghost:hover,
  .gallery-btn-ghost:focus-visible {
    background-color: var(--darkSlateGray);
    color: var(--dutchWhite);
  }
}

.gallery-btn:focus-visible {
  outline: 2px solid var(--darkSlateGray);
  outline-offset: 3px;
}

.gallery-btn:disabled {
  cursor: not-allowed;
  opacity: 0.4;
  transform: none;
}

/* Send my picks: the one action on this page that leaves the device, so it
   carries the accent treatment - cream fill, green ink - instead of
   competing with the download buttons on equal footing. Flat like every
   other button here; the fill is what sets it apart, not a fake shadow. */
.gallery-btn-picks {
  background-color: var(--dutchWhite);
  color: var(--darkSlateGray);
  box-shadow: none;
}

.gallery-btn-picks:hover,
.gallery-btn-picks:focus-visible {
  background-color: var(--darkSlateGray);
  color: var(--dutchWhite);
  transform: translateY(-1px);
}

/* Toggled state of Select photos reads as a solid "on" pill. */
.gallery-btn[aria-pressed="true"] {
  background-color: var(--dutchWhite);
  color: var(--darkSlateGray);
  box-shadow: inset 0 0 0 2px var(--darkSlateGray);
}

.gallery-note {
  margin: 0 0 22px;
  max-width: 62ch;
  font-size: 16px;
  line-height: 1.6;
  color: var(--darkSlateGray);
  opacity: 0.9;
}

/* --- selection bar ------------------------------------------------------- */

.gallery-selection {
  position: sticky;
  top: 84px;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 12px 16px;
  margin-bottom: 18px;
  border: 2px solid var(--darkSlateGray);
  border-radius: var(--radius);
  background-color: var(--dutchWhite);
  box-shadow: 5px 5px 0 rgba(var(--darkSlateGray-rgb), 0.2);
}

.gallery-selection-count {
  margin: 0;
  font-family: stratos, sans-serif;
  font-size: 0.76rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--darkSlateGray);
}

.gallery-selection-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* --- bulk progress ------------------------------------------------------- */

.gallery-progress {
  margin: 0 0 20px;
}

.gallery-progress-bar {
  height: 10px;
  border: 2px solid var(--darkSlateGray);
  border-radius: 999px;
  overflow: hidden;
  background-color: var(--dutchWhite);
}

.gallery-progress-bar span {
  display: block;
  height: 100%;
  width: 0;
  background-color: var(--darkSlateGray);
  transition: width 0.2s ease;
}

.gallery-progress-label {
  margin: 8px 0 0;
  font-family: stratos, sans-serif;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--darkSlateGray);
  opacity: 0.8;
}
/* ==========================================================================
   WALL — justified rows, no cropping
   --------------------------------------------------------------------------
   clients-wall.js groups photos into rows whose aspect ratios add up to the
   container width, then sets --row-h on the row. Each tile's width is then
   `--ar * --row-h`, so photos keep their true proportions and the row ends
   flush with the edge - a contact sheet rather than a cropping grid.
   ========================================================================== */

.gallery-wall {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 200px;
}

.wall-row {
  display: flex;
  gap: 10px;
  height: var(--row-h, 220px);
}

.tile {
  position: relative;
  flex: 0 0 auto;
  width: calc(var(--ar, 1.5) * var(--row-h, 220px));
  border: 2px solid var(--darkSlateGray);
  border-radius: 12px;
  overflow: hidden;
  background-color: rgba(var(--darkSlateGray-rgb), 0.08);
  cursor: zoom-in;
  padding: 0;
  /* A placeholder shimmer while the ciphertext is still in flight. */
  background-image: linear-gradient(
    100deg,
    rgba(var(--darkSlateGray-rgb), 0.06) 30%,
    rgba(var(--darkSlateGray-rgb), 0.14) 50%,
    rgba(var(--darkSlateGray-rgb), 0.06) 70%
  );
  background-size: 220% 100%;
  animation: tile-shimmer 1.6s ease-in-out infinite;
  transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1),
              box-shadow 0.25s ease, border-color 0.2s ease;
}

@keyframes tile-shimmer {
  0% { background-position: 140% 0; }
  100% { background-position: -40% 0; }
}

.tile.is-ready {
  animation: none;
  background-image: none;
  background-color: var(--darkSlateGray);
}

.tile img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transition: opacity 0.45s ease, transform 0.4s ease;
}

.tile.is-ready img {
  opacity: 1;
}

.tile:focus-visible {
  outline: 3px solid var(--darkSlateGray);
  outline-offset: 3px;
}

@media (hover: hover) and (pointer: fine) {
  .tile.is-ready:hover {
    transform: translateY(-3px);
    box-shadow: 0 10px 22px rgba(15, 22, 20, 0.28);
  }

  .tile.is-ready:hover img {
    transform: scale(1.045);
  }
}

/* Frame number: quiet at rest, announced on hover/focus. */
.tile-badge {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 2;
  padding: 3px 8px;
  border-radius: 999px;
  background-color: rgba(15, 22, 20, 0.62);
  color: var(--dutchWhite);
  font-family: stratos, sans-serif;
  font-size: 0.66rem;
  letter-spacing: 0.12em;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.tile:hover .tile-badge,
.tile:focus-visible .tile-badge {
  opacity: 1;
}

/* Selection: the whole tile becomes a toggle target. */
.gallery-wall.is-selecting .tile {
  cursor: pointer;
}

.tile-check {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 3;
  width: 30px;
  height: 30px;
  /* Hidden until the gallery is in select mode, otherwise every tile reads
     as a checkbox and browsing feels like filling in a form. */
  display: none;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--dutchWhite);
  border-radius: 50%;
  background-color: rgba(15, 22, 20, 0.55);
  color: transparent;
  transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease;
}

.tile-check svg {
  width: 16px;
  height: 16px;
  display: block;
}

.gallery-wall.is-selecting .tile-check {
  display: flex;
}

.tile.is-selected .tile-check {
  background-color: var(--dutchWhite);
  color: var(--darkSlateGray);
  transform: scale(1.06);
}

.tile.is-selected {
  border-color: var(--dutchWhite);
  box-shadow: 0 0 0 3px var(--darkSlateGray), 0 0 0 6px var(--dutchWhite);
}

.tile.is-selected img {
  opacity: 0.82;
}

/* Per-tile download: only offered when the gallery allows downloads. */
.tile-download {
  position: absolute;
  right: 8px;
  bottom: 8px;
  z-index: 3;
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--dutchWhite);
  border-radius: 10px;
  background-color: rgba(15, 22, 20, 0.58);
  color: var(--dutchWhite);
  cursor: pointer;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.2s ease, transform 0.2s ease, background-color 0.2s ease, color 0.2s ease;
}

.tile-download svg {
  width: 16px;
  height: 16px;
  display: block;
}

.tile:hover .tile-download,
.tile:focus-within .tile-download {
  opacity: 1;
  transform: translateY(0);
}

.tile-download:hover,
.tile-download:focus-visible {
  background-color: var(--dutchWhite);
  color: var(--darkSlateGray);
}

.tile-download:focus-visible {
  outline: 2px solid var(--dutchWhite);
  outline-offset: 2px;
}

.tile.is-busy {
  cursor: progress;
}

.tile.is-busy .tile-download svg {
  animation: clients-spin 0.9s linear infinite;
}

.wall-empty,
.gallery-empty {
  margin: 40px 0;
  text-align: center;
  font-size: 16px;
  color: var(--darkSlateGray);
  opacity: 0.75;
}

/* ==========================================================================
   VIEWBOX — the inspector
   ========================================================================== */

.viewbox {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  flex-direction: column;
  /* Fully opaque on purpose: any alpha lets the gallery's tile gaps show
     through as faint bands behind the photo. */
  background-color: #0c1210;
  color: var(--dutchWhite);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.28s ease, visibility 0s linear 0.28s;
}

.viewbox.open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 0.28s ease, visibility 0s;
}

body.viewbox-open {
  overflow: hidden;
}

.viewbox-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 14px 18px;
  flex-shrink: 0;
}

.viewbox-counter {
  margin: 0;
  font-family: stratos, sans-serif;
  font-size: 0.74rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--dutchWhite);
  opacity: 0.85;
}

.viewbox-topactions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

.viewbox-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 42px;
  padding: 9px 14px;
  border: 2px solid var(--dutchWhite);
  border-radius: var(--radius);
  background-color: transparent;
  color: var(--dutchWhite);
  font-family: stratos, sans-serif;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease;
}

.viewbox-btn svg {
  width: 17px;
  height: 17px;
  flex-shrink: 0;
}

.viewbox-btn:hover,
.viewbox-btn:focus-visible {
  background-color: var(--dutchWhite);
  color: var(--darkSlateGray);
}

.viewbox-btn:focus-visible {
  outline: 2px solid var(--dutchWhite);
  outline-offset: 3px;
}

.viewbox-btn[aria-pressed="true"] {
  background-color: var(--dutchWhite);
  color: var(--darkSlateGray);
}

.viewbox-close {
  width: 42px;
  padding: 0;
  justify-content: center;
}

.viewbox-close .viewbox-btn-text {
  display: none;
}

/* --- stage --------------------------------------------------------------- */

.viewbox-stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 64px;
  overflow: hidden;
}

/* The frame hugs the photo, so its four corners are the photo's corners.
   The corners are inset by the frame's padding, which is what makes the
   brackets sit just outside the image instead of on top of it. */
.viewbox-frame {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  max-width: 100%;
  max-height: 100%;
  transition: opacity 0.25s ease;
}

.viewbox-image {
  display: block;
  max-width: calc(100vw - 168px);
  max-height: calc(100vh - 250px);
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 2px;
  opacity: 0;
  transition: opacity 0.3s ease;
  user-select: none;
  -webkit-user-drag: none;
}

.viewbox-frame.is-loaded .viewbox-image {
  opacity: 1;
}

.viewbox-corner {
  position: absolute;
  width: 26px;
  height: 26px;
  border: 2px solid var(--dutchWhite);
  opacity: 0.85;
  pointer-events: none;
}

.viewbox-corner.tl { top: 0; left: 0; border-right: none; border-bottom: none; }
.viewbox-corner.tr { top: 0; right: 0; border-left: none; border-bottom: none; }
.viewbox-corner.bl { bottom: 0; left: 0; border-right: none; border-top: none; }
.viewbox-corner.br { bottom: 0; right: 0; border-left: none; border-top: none; }

/* Loading pip in the middle of the frame while the photo decrypts. */
.viewbox-loading {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border: 3px solid rgba(249, 232, 193, 0.3);
  border-top-color: var(--dutchWhite);
  border-radius: 50%;
  animation: clients-spin 0.8s linear infinite;
  opacity: 0;
  transition: opacity 0.2s ease;
  pointer-events: none;
}

.viewbox-frame.is-loading .viewbox-loading {
  opacity: 1;
}

/* --- zoomed (100% pixels) ------------------------------------------------ */

/* In zoom mode the stage becomes the scroll container and the frame takes
   its natural pixel size, so panning is native scrolling - momentum on
   touch, no transform maths, no clamp bugs. */
.viewbox.is-zoomed .viewbox-stage {
  overflow: auto;
  padding: 24px;
  align-items: flex-start;
  justify-content: flex-start;
  cursor: grab;
}

.viewbox.is-zoomed .viewbox-stage.is-panning {
  cursor: grabbing;
  user-select: none;
}

.viewbox.is-zoomed .viewbox-frame {
  margin: auto;
  max-width: none;
  max-height: none;
}

.viewbox.is-zoomed .viewbox-image {
  max-width: none;
  max-height: none;
  width: auto;
  height: auto;
}

/* While zoomed the stage is the scroll container, so the arrow buttons
   would scroll out of place - the keyboard, the filmstrip and Esc still
   cover navigation, and dragging is how you look around. */
.viewbox.is-zoomed .viewbox-nav {
  display: none;
}

/* --- prev / next --------------------------------------------------------- */

.viewbox-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 5;
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 2px solid var(--dutchWhite);
  border-radius: var(--radius);
  background-color: rgba(12, 18, 16, 0.6);
  color: var(--dutchWhite);
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease, opacity 0.2s ease;
}

.viewbox-nav svg {
  width: 24px;
  height: 24px;
  display: block;
}

.viewbox-nav:hover,
.viewbox-nav:focus-visible {
  background-color: var(--dutchWhite);
  color: var(--darkSlateGray);
}

.viewbox-nav:focus-visible {
  outline: 2px solid var(--dutchWhite);
  outline-offset: 3px;
}

.viewbox-nav:disabled {
  opacity: 0.25;
  cursor: default;
}

.viewbox-prev { left: 8px; }
.viewbox-next { right: 8px; }

/* --- caption + filmstrip ------------------------------------------------- */

.viewbox-footer {
  flex-shrink: 0;
  padding: 10px 18px 6px;
  text-align: center;
}

.viewbox-caption {
  margin: 0;
  font-family: stratos, sans-serif;
  font-size: 0.8rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dutchWhite);
}

.viewbox-exif {
  margin: 5px 0 0;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: var(--dutchWhite);
  opacity: 0.6;
}

.viewbox-filmstrip {
  display: flex;
  gap: 8px;
  padding: 10px 18px 18px;
  overflow-x: auto;
  flex-shrink: 0;
  scrollbar-width: thin;
}

.viewbox-filmstrip:empty {
  display: none;
}

.film-item {
  flex: 0 0 auto;
  width: 68px;
  height: 48px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: 8px;
  overflow: hidden;
  background-color: rgba(249, 232, 193, 0.12);
  opacity: 0.5;
  cursor: pointer;
  transition: opacity 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}

.film-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.film-item.is-ready img {
  opacity: 1;
}

.film-item.is-current {
  border-color: var(--dutchWhite);
  opacity: 1;
}

.film-item:hover,
.film-item:focus-visible {
  opacity: 1;
  transform: translateY(-2px);
}

.film-item:focus-visible {
  outline: 2px solid var(--dutchWhite);
  outline-offset: 2px;
}

/* ==========================================================================
   FOOTER
   --------------------------------------------------------------------------
   The portal ends with the same footer as every other page: the "Contact"
   title-wrapper and the Big Green Closer panel, styled entirely by
   layout.css. Only the panel's inner blocks are added here - a row of link
   pills where the contact form sits on the main site, and the privacy line
   that replaces the social column.
   ========================================================================== */

.clients-footer-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

/* Same pill language as .contact-mailto-button: cream on the green panel,
   flipping to green-on-cream on hover. */
.clients-footer-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  min-height: 46px;
  padding: 12px 20px;
  border: 2px solid var(--dutchWhite);
  border-radius: var(--radius);
  background-color: var(--dutchWhite);
  color: var(--darkSlateGray);
  font-family: stratos, sans-serif;
  font-size: 0.78rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  line-height: 1.2;
  transition: background-color 0.22s ease, color 0.22s ease, transform 0.18s ease;
}

.clients-footer-cta svg {
  width: 17px;
  height: 17px;
  flex-shrink: 0;
}

.clients-footer-cta-ghost {
  background-color: transparent;
  color: var(--dutchWhite);
}

@media (hover: hover) and (pointer: fine) {
  .clients-footer-cta:hover,
  .clients-footer-cta:focus-visible {
    background-color: transparent;
    color: var(--dutchWhite);
    transform: translateY(-2px);
  }

  .clients-footer-cta-ghost:hover,
  .clients-footer-cta-ghost:focus-visible {
    background-color: var(--dutchWhite);
    color: var(--darkSlateGray);
  }
}

.clients-footer-cta:focus-visible {
  outline: 2px solid var(--dutchWhite);
  outline-offset: 3px;
}

/* On a phone the three pills stack and stretch, exactly like the single
   mailto CTA in the main site's footer. */
@media (max-width: 640px) {
  .clients-footer-actions {
    flex-direction: column;
    align-items: stretch;
  }

  .clients-footer-cta {
    width: 100%;
  }
}

.clients-footer-privacy {
  margin: 0;
  max-width: 62ch;
  padding-top: 16px;
  border-top: 1px solid rgba(var(--dutchWhite-rgb), 0.25);
  font-size: 13px;
  line-height: 1.6;
  color: var(--dutchWhite);
  opacity: 0.78;
}

/* ==========================================================================
   MARKS — stars and flags
   --------------------------------------------------------------------------
   Two places show a verdict: the inspector strip (where it is set) and a
   small badge on the wall tile (where it is remembered). Both are built on
   the page tokens, so dark mode swaps them for free.
   ========================================================================== */

.viewbox-marks {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
  flex-wrap: wrap;
  flex-shrink: 0;
  padding: 6px 18px 2px;
}

.viewbox-stars {
  display: flex;
  align-items: center;
  gap: 2px;
}

.star-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 10px;
  background-color: transparent;
  color: var(--dutchWhite);
  cursor: pointer;
  transition: background-color 0.2s ease, transform 0.2s ease;
}

.star-btn svg {
  width: 25px;
  height: 25px;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linejoin: round;
  transition: fill 0.18s ease, transform 0.18s ease;
}

/* Lit stars fill in. Everything up to and including the chosen value is
   filled by .is-on, which the viewbox sets on render. */
.star-btn.is-on svg {
  fill: currentColor;
}

.star-btn:focus-visible {
  outline: 2px solid var(--dutchWhite);
  outline-offset: 2px;
}

@media (hover: hover) and (pointer: fine) {
  .star-btn:hover svg {
    transform: scale(1.12);
  }

  .star-btn:hover {
    background-color: rgba(var(--dutchWhite-rgb), 0.12);
  }
}

.viewbox-flag {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 9px 16px;
  border: 2px solid var(--dutchWhite);
  border-radius: var(--radius);
  background-color: transparent;
  color: var(--dutchWhite);
  font-family: stratos, sans-serif;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease;
}

.viewbox-flag svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

.viewbox-flag:focus-visible {
  outline: 2px solid var(--dutchWhite);
  outline-offset: 3px;
}

/* Pressed = this frame is flagged. Solid cream reads as "on" against the
   dark inspector, matching every other pressed control on the page. */
.viewbox-flag[aria-pressed="true"] {
  background-color: var(--dutchWhite);
  color: var(--darkSlateGray);
}

/* No badge on the wall: a rating belongs to the frame you are looking at
   in the inspector, not to the contact sheet. Thumbnails stay photographs
   instead of turning into a wall of little score stickers. */

/* ==========================================================================
   FEEDBACK DIALOG
   --------------------------------------------------------------------------
   The one place on the page that sends anything. Built as a centred card on
   a dark scrim - the same visual weight as the inspector, so it does not
   read as a website pop-up.
   ========================================================================== */

.feedback {
  position: fixed;
  inset: 0;
  z-index: 2100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 16px;
  background-color: rgba(12, 18, 16, 0.72);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.24s ease, visibility 0s linear 0.24s;
}

.feedback.open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 0.24s ease, visibility 0s;
}

body.feedback-open {
  overflow: hidden;
}

.feedback-panel {
  width: 100%;
  max-width: 560px;
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 26px 26px 24px;
  border: 2px solid var(--darkSlateGray);
  border-radius: var(--radius);
  background-color: var(--dutchWhite);
  color: var(--darkSlateGray);
  box-shadow: 8px 8px 0 rgba(15, 22, 20, 0.4);
  transform: translateY(12px);
  transition: transform 0.24s cubic-bezier(0.22, 1, 0.36, 1);
}

.feedback.open .feedback-panel {
  transform: translateY(0);
}

.feedback-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}

.feedback-eyebrow {
  margin: 0 0 6px;
  font-family: stratos, sans-serif;
  font-size: 0.68rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--darkSlateGray);
  opacity: 0.7;
}

.feedback-title {
  margin: 0;
  font-family: ambroise-std, serif;
  font-weight: 700;
  font-size: clamp(26px, 5vw, 34px);
  line-height: 1.1;
  color: var(--darkSlateGray);
}

.feedback-close {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 2px solid var(--darkSlateGray);
  border-radius: 10px;
  background-color: transparent;
  color: var(--darkSlateGray);
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease;
}

.feedback-close svg {
  width: 16px;
  height: 16px;
}

.feedback-close:hover,
.feedback-close:focus-visible {
  background-color: var(--darkSlateGray);
  color: var(--dutchWhite);
}

.feedback-close:focus-visible {
  outline: 2px solid var(--darkSlateGray);
  outline-offset: 3px;
}

.feedback-lede {
  margin: 0;
  font-size: 15px;
  line-height: 1.6;
  color: var(--darkSlateGray);
  opacity: 0.85;
}

/* --- the list of picks ---------------------------------------------------- */

.feedback-summary {
  border: 2px solid rgba(var(--darkSlateGray-rgb), 0.22);
  border-radius: 12px;
  padding: 12px 14px;
  background-color: rgba(var(--darkSlateGray-rgb), 0.04);
}

.feedback-empty,
.feedback-summary-head {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--darkSlateGray);
  opacity: 0.8;
}

.feedback-summary-head {
  font-family: stratos, sans-serif;
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  opacity: 0.95;
  margin-bottom: 8px;
}

.feedback-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 168px;
  overflow-y: auto;
}

.feedback-list li {
  display: grid;
  grid-template-columns: 28px 1fr auto;
  align-items: baseline;
  gap: 10px;
  font-size: 13.5px;
  line-height: 1.4;
}

.feedback-list-frame {
  font-family: stratos, sans-serif;
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  color: var(--darkSlateGray);
  opacity: 0.6;
}

.feedback-list-file {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.feedback-list-verdict {
  font-family: stratos, sans-serif;
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--darkSlateGray);
}

/* --- fields --------------------------------------------------------------- */

.feedback-fields {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.feedback-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.feedback-label {
  font-family: stratos, sans-serif;
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--darkSlateGray);
  opacity: 0.75;
}

.feedback-optional {
  text-transform: none;
  letter-spacing: 0.02em;
  opacity: 0.6;
}

.feedback-input {
  width: 100%;
  padding: 11px 13px;
  border: 2px solid var(--darkSlateGray);
  border-radius: 10px;
  background-color: var(--dutchWhite);
  color: var(--darkSlateGray);
  font-family: stratos, sans-serif;
  font-size: 15px;
  line-height: 1.35;
  transition: box-shadow 0.2s ease;
}

.feedback-input:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px rgba(var(--darkSlateGray-rgb), 0.2);
}

.feedback-textarea {
  resize: vertical;
  min-height: 82px;
  font-family: inherit;
}

.feedback-include {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--darkSlateGray);
  cursor: pointer;
}

.feedback-include input {
  width: 18px;
  height: 18px;
  margin: 1px 0 0;
  accent-color: var(--darkSlateGray);
  flex-shrink: 0;
  cursor: pointer;
}

.feedback-status {
  margin: 0;
  min-height: 1.3em;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--darkSlateGray);
}

.feedback-status.is-error {
  color: #b3261e;
}

.feedback-status.is-success {
  font-weight: 600;
  opacity: 1;
}

html[data-theme="dark"] .feedback-status.is-error {
  color: #ffb4ab;
}

.feedback-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.feedback-actions .gallery-btn {
  flex: 1 1 auto;
  justify-content: center;
}

.feedback-actions .gallery-btn.is-busy {
  opacity: 0.7;
  cursor: progress;
}

@media (max-width: 640px) {
  .feedback-panel {
    padding: 20px 18px 18px;
    gap: 14px;
  }

  .feedback-fields {
    grid-template-columns: 1fr;
  }

  .feedback-actions {
    flex-direction: column-reverse;
  }

  .viewbox-marks {
    gap: 8px;
    padding: 4px 10px 0;
  }

  .star-btn {
    width: 40px;
    height: 40px;
  }

  .star-btn svg {
    width: 22px;
    height: 22px;
  }

  /* The flag keeps its icon; the label is what costs the width. */
  .viewbox-flag {
    padding: 8px 12px;
    font-size: 0.66rem;
  }
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */

@media (max-width: 960px) {
  .gate-card {
    grid-template-columns: 1fr;
  }

  .gate-side {
    border-left: none;
    border-top: 2px solid var(--darkSlateGray);
  }

  .gate-main {
    padding: 28px 24px 24px;
  }

  .gate-side {
    padding: 26px 24px 24px;
  }
}

@media (max-width: 768px) {
  #main-content {
    padding: 0 16px;
  }

  .clients-title {
    font-size: clamp(32px, 9vw, 42px);
  }

  .gate-card {
    box-shadow: 5px 5px 0 rgba(var(--darkSlateGray-rgb), 0.22);
  }

  .gate-submit {
    width: 100%;
    padding: 14px 18px;
  }

  /* Header stacks: title above, actions in a full-width row. */
  .gallery-header {
    align-items: flex-start;
    flex-direction: column;
    gap: 16px;
  }

  .gallery-actions {
    width: 100%;
  }

  .gallery-btn {
    flex: 1 1 auto;
    justify-content: center;
    padding: 11px 12px;
    font-size: 0.7rem;
  }

  .gallery-selection {
    position: static;
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }

  .gallery-selection-actions {
    width: 100%;
  }

  .gallery-selection-actions .gallery-btn {
    flex: 1 1 0;
  }

  /* The wall keeps a 140px row target on phones so a 3:2 photo is still a
     comfortable tap. */
  .wall-row {
    gap: 8px;
  }

  .gallery-wall {
    gap: 8px;
  }

  /* Viewer: controls move out of the way of the photo. */
  .viewbox-topbar {
    padding: 10px 12px;
    gap: 10px;
  }

  .viewbox-btn {
    min-height: 40px;
    padding: 8px 11px;
    font-size: 0.66rem;
  }

  .viewbox-btn-text {
    display: none;
  }

  .viewbox-btn svg {
    width: 18px;
    height: 18px;
  }

  .viewbox-stage {
    padding: 0 10px;
  }

  .viewbox-image {
    max-width: calc(100vw - 40px);
    max-height: calc(100vh - 230px);
  }

  .viewbox-corner {
    width: 18px;
    height: 18px;
  }

  .viewbox-frame {
    padding: 12px;
  }

  .viewbox-nav {
    width: 40px;
    height: 40px;
    background-color: rgba(12, 18, 16, 0.5);
  }

  .viewbox-nav svg {
    width: 20px;
    height: 20px;
  }

  .viewbox-prev { left: 2px; }
  .viewbox-next { right: 2px; }

  .viewbox-footer {
    padding: 8px 12px 4px;
  }

  /* The filmstrip costs too much vertical space on a phone - the counter
     and the arrows already cover navigation. */
  .viewbox-filmstrip {
    display: none;
  }
}

@media (max-width: 420px) {
  .gallery-actions {
    flex-direction: column;
    align-items: stretch;
  }
}

/* ==========================================================================
   MOTION PREFERENCES
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .tile,
  .tile img,
  .tile-download,
  .tile-check,
  .viewbox,
  .viewbox-image,
  .film-item,
  .gate-submit {
    transition: none !important;
    animation: none !important;
  }

  .tile:not(.is-ready) {
    background-image: none;
  }

  .gate-submit-spinner,
  .viewbox-loading,
  .tile.is-busy .tile-download svg {
    animation: none;
    border-top-color: var(--dutchWhite);
  }
}
