/* ==========================================================================
   ALBI HOME — REFERENZEN
   Section head, filter bar, project mosaic, closing CTA panel.
   Depends on tokens.css + base.css + components.css.

   Every number below is measured from ALBI-HOME-final.fig (node 42:2185) or
   read back from the 1440x9649 export — see the comment above each block.
   ========================================================================== */

.projects-section {
  /* 30px between tiles at 1440, never below 12px on a phone. */
  --proj-gap: clamp(0.75rem, 2.08vw, 1.875rem);
}

/* --------------------------------------------------------------------------
   1. SECTION HEAD
   Uses the shared .section-head component; only the right column is extended
   here, because this is the one section whose supporting copy carries a
   button under it.
   -------------------------------------------------------------------------- */
.section-head__aside {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-6);            /* 32px, prose baseline -> button top */
}

/* --------------------------------------------------------------------------
   2. FILTER BAR
   Figma: 1296x116 panel, #121519, 6px radius. Chips 49px tall, 3px radius,
   32px side padding, 27px apart; the active one carries the gold gradient.

   The panel's own inset is the one place this deviates from the file. The
   design leaves the "Filter" label 7px from the panel edge while the right
   side has 96px — a loose Figma group rather than an intended asymmetry, and
   at 7px the label reads as a mistake. It ships evenly padded instead.
   -------------------------------------------------------------------------- */
.projects-filter {
  display: flex;
  align-items: flex-start;
  gap: var(--space-5) 27px;
  margin-top: var(--section-stack);
  padding: 33px 28px;             /* 116px tall around a 49px chip, as drawn */
  border-radius: var(--radius-sm);
  background-color: var(--bg-filter);
}

/* Matching the chip height centres the label against the FIRST row of chips
   rather than against the whole block, so it stays put when they wrap to a
   second row on tablet instead of drifting into the gap between rows. */
.projects-filter__label {
  display: flex;
  align-items: center;
  min-height: 49px;
  color: var(--text-accent);
  font-weight: var(--fw-medium);
  flex: none;
}

.projects-filter__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 27px;
}

.chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 49px;               /* clears the 44px touch minimum */
  padding-inline: var(--space-6);
  border: 1px solid var(--border-chip);
  border-radius: var(--radius-xs);
  background-color: transparent;
  color: var(--text-primary);
  font-size: var(--fs-body);
  line-height: 1.2;
  white-space: nowrap;
  transition:
    background-color var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out),
    color var(--dur-base) var(--ease-out);
}
.chip:hover {
  border-color: var(--gold-400);
  background-color: var(--a-gold-12);
}
.chip:active { transform: translateY(1px); }

/* Pressed state. The gradient is the same one every gold CTA uses, so the
   active chip and the section button read as one family. */
.chip[aria-pressed="true"] {
  border-color: transparent;
  background: var(--grad-gold);
  color: var(--white);
  font-weight: var(--fw-medium);
}
.chip[aria-pressed="true"]:hover { background: var(--grad-gold); }

/* The chips do nothing without JS, and a dead control is worse than no
   control — every project is already on the page, so hide the bar entirely. */
.no-js .projects-filter { display: none; }

.projects-status {
  margin-top: var(--space-4);
  color: var(--text-muted);
  font-size: var(--fs-sm);
}

/* --------------------------------------------------------------------------
   3. THE MOSAIC
   1296 = 363 + 30 + (6 x 125.5) + (5 x 30). Seven tracks: the tall tile owns
   the first, everything else divides the remaining six.

   Nothing is placed explicitly. Each slot declares only how far it spans and
   auto-placement lands it exactly where the design has it:

       col:  1  |  2   3   4  |  5   6   7
       row 1:  1 |      2      |      3
       row 2:  1 |   4   |   5   |   6
       row 3:  ------------ 7 ------------

   Because the rules key off :nth-child(7n+k) the pattern repeats every seven
   projects, so the client can keep adding photos and the mosaic just grows.
   -------------------------------------------------------------------------- */
.projects {
  display: grid;
  grid-template-columns: minmax(0, 363fr) repeat(6, minmax(0, 125.5fr));
  gap: var(--proj-gap);
  margin-top: var(--section-stack);
}

.project {
  position: relative;
  overflow: hidden;               /* clips the image to the tile's radius */
  border-radius: var(--radius-sm);
  background-color: var(--bg-card);   /* holds the shape while the photo loads */
}

/* THE RULE THAT MAKES ANY SOURCE IMAGE WORK.
   The tile owns the shape; the photo fills it from the centre and is cropped
   on the long axis. Whatever the client uploads — portrait, square, panorama,
   any pixel size — it renders exactly as the design draws this slot.

   The image is taken OUT OF FLOW deliberately. In flow it reports its own
   intrinsic ratio as content height, which the tall tile then imposes on the
   grid rows: at tablet it stretched slot 1 to 618px while the two tiles
   beside it added up to 396, tearing a gap between them. Out of flow the tile
   has no content height, so its size comes only from its aspect-ratio — or,
   for slot 1, from the rows it spans. */
.project picture { display: contents; }
.project__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  transition: transform var(--dur-slow) var(--ease-out);
}

/* --- The tile is a button ------------------------------------------------
   A real <button>, not a div with a click handler: that is what makes it
   reachable by Tab, operable with Enter and Space, and announced as something
   you can activate. It fills the tile and becomes the containing block for
   the absolutely-positioned image above. */
.project__open {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  cursor: zoom-in;
}
.project__open:focus-visible { outline-offset: -3px; }

/* Disabled until projects.js enables it. Every rule below is written against
   :enabled so a tile shows no affordance at all until the lightbox exists to
   answer the click — the photographs still render, they just do not pretend
   to be pressable. */
.project__open:disabled { cursor: default; }
.project__open:disabled .project__veil { display: none; }

/* Veil + badge. Hidden until hover so the mosaic stays a wall of photographs
   at rest, exactly as designed. */
.project__veil {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  opacity: 0;
  background: var(--grad-tile-veil);
  transition: opacity var(--dur-base) var(--ease-out);
}
.project__zoom {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  border-radius: var(--radius-pill);
  background: var(--grad-gold);
  color: var(--text-on-gold);
  box-shadow: var(--shadow-md);
  transform: scale(.82);
  transition: transform var(--dur-base) var(--ease-spring);
}
.project__zoom .icon { width: 22px; height: 22px; }

/* Pointer devices only. On a touchscreen :hover sticks after a tap, leaving
   the veil burned onto whichever tile was pressed last. */
@media (hover: hover) {
  .project__open:enabled:hover .project__veil,
  .project__open:enabled:focus-visible .project__veil { opacity: 1; }
  .project__open:enabled:hover .project__zoom,
  .project__open:enabled:focus-visible .project__zoom { transform: scale(1); }
  .project__open:enabled:hover .project__img,
  .project__open:enabled:focus-visible .project__img { transform: scale(1.06); }
  .project__open:enabled:active .project__img { transform: scale(1.02); }
}

/* Touch devices get a small permanent badge instead — without a hover state
   there is otherwise nothing telling anyone the photographs open. */
@media (hover: none) {
  .project__veil {
    opacity: 1;
    background: none;
    place-items: end;
    padding: var(--space-3);
  }
  .project__zoom {
    width: 36px;
    height: 36px;
    transform: none;
    background: var(--a-black-64);
    color: var(--white);
    box-shadow: none;
  }
  .project__zoom .icon { width: 16px; height: 16px; }
}

@media (prefers-reduced-motion: reduce) {
  .project__open:enabled:hover .project__img,
  .project__open:enabled:focus-visible .project__img,
  .project__open:enabled:active .project__img { transform: none; }
}

/* Slot 1 — the tall tile. Deliberately has NO aspect-ratio: it spans both
   rows and stretches to whatever they add up to (205 + 30 + 324 = 559), which
   keeps it locked to the rows beside it at every width. */
.project:nth-child(7n+1) { grid-column: span 1; grid-row: span 2; }

/* Slots 2-3 — the wide pair. 3 tracks = 376.5 + 60 = 436.5 (design 436). */
.project:nth-child(7n+2),
.project:nth-child(7n+3) { grid-column: span 3; aspect-ratio: 436 / 205; }

/* Slots 4-6 — the portrait trio. 2 tracks = 251 + 30 = 281 (design 278). */
.project:nth-child(7n+4),
.project:nth-child(7n+5),
.project:nth-child(7n+6) { grid-column: span 2; aspect-ratio: 278 / 324; }

/* Slot 7 — the full-bleed banner. */
.project:nth-child(7n+7) { grid-column: 1 / -1; aspect-ratio: 1296 / 354; }

/* --- Filtered view --------------------------------------------------------
   A mosaic only tiles when every slot is filled, so hiding tiles would punch
   holes in it. Any filter other than "Alle Projekte" therefore drops to an
   even grid, which packs cleanly at any count. Three classes of specificity
   beats every :nth-child rule above, including the ones inside media
   queries — so this holds at every breakpoint regardless of source order. */
.projects.is-filtered {
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
.projects.is-filtered > .project {
  grid-column: auto;
  grid-row: auto;
  aspect-ratio: 4 / 3;
}

.project[hidden] { display: none; }

/* Filtering swaps content in place, so the incoming tiles fade rather than
   snap. Entering only — leaving tiles are removed outright. */
.projects.is-switching > .project:not([hidden]) {
  animation: project-in var(--dur-slow) var(--ease-out) both;
}
@keyframes project-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

.projects-empty {
  margin-top: var(--section-stack);
  padding: var(--space-8) var(--space-5);
  border: 1px dashed var(--border-line);
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  text-align: center;
}

/* --------------------------------------------------------------------------
   4. CLOSING CTA PANEL
   Figma: 1296x332, 6px radius, 57px top and bottom padding, centred.
   The background is a photograph of a door set already carrying its gold
   wash; --grad-gold-band sits underneath so the panel is never bare while the
   image loads, or if it fails.
   -------------------------------------------------------------------------- */
.cta-panel {
  margin-top: var(--section-stack);
  padding: clamp(2.25rem, 4vw, 3.5625rem) clamp(1.25rem, 3vw, 2.5rem);
  border-radius: var(--radius-sm);
  background-color: var(--gold-500);
  background-image: var(--grad-gold-band);
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  text-align: center;
}
@supports (background-image: image-set(url("x.webp") type("image/webp") 1x)) {
  .cta-panel {
    background-image:
      image-set(
        url("../img/cta-doors-1296.webp") type("image/webp") 1x,
        url("../img/cta-doors-1296.jpg")  type("image/jpeg") 1x
      );
  }
}

/* CONTRAST — the worst pairing on the site, and a deliberate one.
   The Figma file sets WHITE type on this gold panel. Measured against the
   brightest pixel of the background image (#E4B156):

       white .................. 1.96:1   heading AND body both fail
       --text-on-gold #141007 . 9.69:1   passes AAA

   AA needs 4.5:1 for the 18px body and 3.0:1 for the 40px bold heading, so
   white misses both — and unlike the gold button, this is a full paragraph,
   not a four-word label. It ships as drawn because it is the client's panel
   on their brand colour, but every foreground here reads from ONE token, so
   the fix is a single line with no other visual change:

       --cta-panel-fg: var(--text-on-gold);

   That recolours the heading, the body copy, the ghost button's label and its
   1px border together — the border matters too, since WCAG 1.4.11 wants 3:1
   for a control's boundary and white gives it the same 1.96:1. */
.cta-panel {
  --cta-panel-fg: var(--white);
  color: var(--cta-panel-fg);
}

.cta-panel__title {
  font-size: var(--fs-h2);
  color: var(--cta-panel-fg);
}
.cta-panel__text {
  max-width: 699px;               /* as drawn */
  margin: var(--space-3) auto 0;
  color: var(--cta-panel-fg);
  font-size: var(--fs-lead);
  line-height: var(--lh-normal);
}
.cta-panel .actions {
  justify-content: center;
  gap: 27px;                      /* as drawn */
  margin-top: clamp(1.75rem, 3vw, 3.125rem);
}

/* The shared ghost button hovers to a 12% gold wash, which is invisible on a
   gold panel. On this ground it lifts with white instead. */
.cta-panel .btn--ghost {
  border-color: var(--cta-panel-fg);
  color: var(--cta-panel-fg);
}
.cta-panel .btn--ghost:hover {
  border-color: var(--cta-panel-fg);
  background-color: var(--a-white-16);
  color: var(--cta-panel-fg);
}

/* --------------------------------------------------------------------------
   5. BREAKPOINTS
   Only the track count and the spans change — the tile styling above is
   shared, and the filtered view needs no per-breakpoint rules at all.
   -------------------------------------------------------------------------- */

/* --- Tablet: 768-1023. Six even tracks. The tall tile takes the left half
       and the wide pair stacks beside it, so nothing drops below ~300px. --- */
@media (max-width: 1023.98px) {
  .projects { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .project:nth-child(7n+1) { grid-column: span 3; grid-row: span 2; }
}

/* --- Phone: below 768. The mosaic would shrink the trio to ~140px, so the
       wide tiles go full width and only the portrait pair stays side by side.
       Slots 1 and 6 get calmer crops — a 0.65 portrait at full width is
       500px+ tall on a phone and pushes everything else off the screen. --- */
@media (max-width: 767.98px) {
  .project:nth-child(7n+1) {
    grid-column: 1 / -1;
    grid-row: auto;
    aspect-ratio: 4 / 3;
  }
  .project:nth-child(7n+2),
  .project:nth-child(7n+3) { grid-column: 1 / -1; }
  .project:nth-child(7n+4),
  .project:nth-child(7n+5) { grid-column: span 3; }
  .project:nth-child(7n+6) { grid-column: 1 / -1; aspect-ratio: 4 / 3; }
  .project:nth-child(7n+7) { aspect-ratio: 2 / 1; }

  /* Stack the label above the chips — side by side it steals the width the
     chips need and forces a third row. */
  .projects-filter {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-4);
    padding: var(--space-5) var(--space-5);
  }
  .projects-filter__chips { gap: 10px 12px; }
  .chip {
    flex: 1 1 auto;
    min-height: 46px;
    padding-inline: var(--space-4);
    font-size: var(--fs-sm);
  }
}

/* Below 480 the two portrait tiles are ~150px wide — narrow enough that the
   room in them stops reading. One per row from here down. */
@media (max-width: 479.98px) {
  .project:nth-child(7n+4),
  .project:nth-child(7n+5) { grid-column: 1 / -1; }
  .projects.is-filtered { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  .projects.is-switching > .project:not([hidden]) { animation: none; }
}

/* ==========================================================================
   6. LIGHTBOX
   Opens from a project tile. Purpose-built rather than vendored: the site's
   CSP is default-src 'self', so a CDN bundle is blocked outright, and there
   is no build step to inline one — a library would have to be committed whole
   and then re-skinned to these tokens anyway.
   ========================================================================== */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-lightbox);
  display: grid;
  place-items: center;
  padding: clamp(0.75rem, 2vw, 2rem);
}

.lightbox__backdrop {
  position: absolute;
  inset: 0;
  background-color: var(--a-black-88);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out);
}
.lightbox.is-open .lightbox__backdrop { opacity: 1; }

.lightbox__dialog {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-5);
  max-width: 100%;
  max-height: 100%;
  opacity: 0;
  transform: scale(.96);
  transition:
    opacity var(--dur-base) var(--ease-out),
    transform var(--dur-slow) var(--ease-spring);
}
.lightbox.is-open .lightbox__dialog { opacity: 1; transform: none; }

.lightbox__figure {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  min-height: 0;
}

/* THE FRAME IS SIZED, NOT THE IMAGE — and it is sized so it can never
   distort. The three terms are the whole trick:

     88vw ................. never wider than the viewport allows
     --lb-w * 2 ........... never more than twice the pixels the file holds
     78vh * --lb-ar ....... the width at which the HEIGHT would hit 78vh

   Folding the height limit into a width via the aspect ratio means only one
   dimension is ever constrained, so the box keeps its ratio exactly and the
   radius and shadow always hug the photograph.

   Doing this with max-width/max-height instead does not work: those only ever
   shrink, so an image smaller than the viewport is pinned to its natural size
   and a 436x205 export floats lost in the middle of a 1440 screen.

   pan-y hands vertical gestures back to the browser while horizontal drags
   stay ours for the swipe. */
.lightbox__frame {
  aspect-ratio: var(--lb-ar, 3 / 2);
  width: min(88vw, calc(var(--lb-w, 1200px) * 2), calc(78vh * var(--lb-ar, 1.5)));
  touch-action: pan-y;
}
.lightbox__frame picture { display: contents; }

/* The frame already carries the photo's exact ratio, so contain never
   letterboxes — it is here as insurance against sub-pixel rounding. */
.lightbox__img {
  display: block;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: contain;
  border-radius: var(--radius-sm);
  background-color: var(--bg-card);
  box-shadow: var(--shadow-lg);
}

.lightbox__caption {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: var(--space-2) var(--space-4);
  max-width: 68ch;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  text-align: center;
}
.lightbox__count { color: var(--text-accent); font-weight: var(--fw-medium); }

/* --- Controls ----------------------------------------------------------- */
.lightbox__btn {
  position: absolute;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border: 1px solid var(--border-line);
  border-radius: var(--radius-pill);
  background-color: var(--a-black-64);
  color: var(--text-primary);
  transition:
    background-color var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out),
    color var(--dur-base) var(--ease-out);
}
.lightbox__btn:hover {
  border-color: var(--gold-400);
  background: var(--grad-gold);
  color: var(--text-on-gold);
}
.lightbox__btn:active { transform: translateY(1px); }
.lightbox__btn[disabled] { opacity: .35; pointer-events: none; }

.lightbox__btn--close { top: 0; right: 0; transform: translate(25%, -25%); }
.lightbox__btn--prev  { top: 50%; left: 0;  margin-top: -24px; transform: translateX(-140%); }
.lightbox__btn--next  { top: 50%; right: 0; margin-top: -24px; transform: translateX(140%); }
.lightbox__btn--close:active { transform: translate(25%, -25%) translateY(1px); }
.lightbox__btn--prev:active  { transform: translateX(-140%) translateY(1px); }
.lightbox__btn--next:active  { transform: translateX(140%) translateY(1px); }

/* Below ~1100 there is no room beside the image for the arrows, so they move
   onto the photo's edges instead of floating outside it. */
@media (max-width: 1099.98px) {
  .lightbox__btn--prev { transform: translateX(var(--space-3)); }
  .lightbox__btn--next { transform: translateX(calc(-1 * var(--space-3))); }
  .lightbox__btn--prev:active { transform: translateX(var(--space-3)) translateY(1px); }
  .lightbox__btn--next:active { transform: translateX(calc(-1 * var(--space-3))) translateY(1px); }
  .lightbox__btn--close { transform: translate(0, var(--space-3)); }
  .lightbox__btn--close:active { transform: translate(0, var(--space-3)) translateY(1px); }
}

@media (max-width: 599.98px) {
  .lightbox { padding: var(--space-2); }
  .lightbox__btn { width: 44px; height: 44px; }
  .lightbox__frame {
    width: min(94vw, calc(var(--lb-w, 1200px) * 2), calc(68vh * var(--lb-ar, 1.5)));
  }
  .lightbox__caption { font-size: var(--fs-xs); }
}

/* Directional entrance, so pressing "next" reads as moving forward through
   the set rather than the picture simply being replaced. */
@keyframes lb-from-right { from { opacity: 0; transform: translateX(28px); } to { opacity: 1; transform: none; } }
@keyframes lb-from-left  { from { opacity: 0; transform: translateX(-28px); } to { opacity: 1; transform: none; } }
.lightbox__frame.is-next .lightbox__img { animation: lb-from-right var(--dur-slow) var(--ease-out); }
.lightbox__frame.is-prev .lightbox__img { animation: lb-from-left  var(--dur-slow) var(--ease-out); }
