/* ═══════════════════════════════════════════════
   Frame wall: any list shown as framed artwork in a looping carousel
   (app/components/FrameWall.tsx). Built for the careers roles and moved here
   on 2026-09-26 when it became the house treatment for EVERY card-like list
   (Bailey: "when theres anything that looks like a card based list").
   Mimics the home page's moods frames (moods.css):
   coloured frame border > cream mat > tinted artwork.
   ═══════════════════════════════════════════════ */

.frame-wall__wall {
  position: relative;
  /* Full-bleed out of the page container: a wall that stops at the text
     column reads as a widget, not a wall. Uses the viewport width rather
     than a negative margin pair so it cannot drift out of alignment. */
  width: 100vw;
  margin-left: 50%;
  transform: translateX(-50%);
  padding: var(--space-6) 0 var(--space-4);
  /* clip, not hidden: `hidden` on an x-axis makes the y-axis a scroll
     container too, which steals the page's scroll anchoring. */
  overflow-x: clip;

  /* ONE control for how big a picture is. Orientation below derives each
     frame's width from it, so every frame in the wall hangs on a common line
     whatever shape it is. At 390px it resolves to its 150px floor, which puts
     about two frames in a row and therefore four on screen across the two —
     the stated minimum. */
  --art-h: clamp(150px, 21vw, 300px);
  --frame-border: clamp(7px, 0.7vw, 12px);
  --mat-pad: clamp(8px, 1vw, 16px);
  --wall-gap: clamp(12px, 1.6vw, 26px);
  /* How far the lamp hangs above the frame, and the room a row must leave for
     it. DERIVED from the drop, not written twice, so the two cannot drift
     apart and clip the fixture: the cone starts 18px above the lamp and wants
     a little air over that. */
  --lamp-drop: clamp(22px, 2.2vw, 30px);
  --lamp-headroom: calc(var(--lamp-drop) + 26px);
}

/* A single row carries every role by itself, so its frames are set a little
   smaller: four of them across a desktop window rather than three and a bit,
   which is the same reading the two-row wall gives. The wall is one row
   whenever there are fewer than eight roles — see FrameWall.tsx, where the
   split is decided — which is the usual shape of the "other open roles" block
   on a job page. */
.frame-wall__wall[data-rows="1"] {
  --art-h: clamp(150px, 18vw, 250px);
}

.frame-wall__wall-row {
  /* The track the carousel scrolls. Both rows are driven together from one set
     of controls, and each one holds every open role three times over so the
     arrows can loop (see FrameWall.tsx). scroll-snap keeps a drag or a
     trackpad flick landing on a frame edge rather than mid-picture, and
     scroll-padding makes those snap points agree with the gutter the track is
     inset by — otherwise a snapped frame sits flush to the screen edge while a
     stepped one sits inside it. */
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scroll-padding-inline-start: var(--gutter);
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  padding: calc(var(--space-2) + 4px) 0;
  /* Bounded on a wide monitor. Not decoration: the loop shifts the reader by
     exactly one copy's width, which is only invisible while the window is
     NARROWER than a copy. Past that you would see the same role twice on one
     screen. At 1280px a copy of four frames is ~1450px, comfortably clear. */
  max-width: var(--content-width);
  margin-inline: auto;
}

.frame-wall__wall-row::-webkit-scrollbar { display: none; }

.frame-wall__wall-track {
  display: flex;
  align-items: flex-start;
  gap: var(--wall-gap);
  width: max-content;
  padding-inline: var(--gutter);
}

/* ── A frame ────────────────────────────────────── */

.frame-wall__frame {
  flex: 0 0 auto;
  position: relative;
  isolation: isolate;
  scroll-snap-align: start;
  /* For the wall stepping back around whatever you are looking at. */
  transition: opacity 0.35s ease;
}

/* The second and third copies of each row exist only so the arrows can loop.
   When a copy is no longer wider than the window — a row of three roles on a
   wide screen, say — two copies would be on screen at once and the same role
   would appear twice, so the component measures that case and switches the
   loop off. PER ROW, because the rows are not the same length: a job page
   listing seven other roles splits them four and three, and at 1280px the
   four loop while the three do not. Scoping this to the wall turned the
   controls off for both rows and hid four reachable roles. */
.frame-wall__wall-row[data-loop="off"] .frame-wall__frame--clone {
  display: none;
}

/* ── Palettes ────────────────────────────────────────────────────────────
   Each frame declares a background AND a foreground, because the tokens below
   are THEMED. In after-hours, --tempo-cream-dark and --tempo-sand are both
   #0A061A and --tempo-ink is #05030A — so the two nominally light palettes go
   near-black at night, and a foreground chosen once in JS (ink on "light",
   cream on "dark") left the role titles black on black. The foreground is
   flipped per theme here instead, next to the background it has to contrast
   with, which is the only place the pairing can be checked. */
.frame-wall__frame[data-palette="0"] {
  --frame-clr: var(--tempo-terracotta);
  --artwork-bg: var(--tempo-cream-dark);
  --art-fg: var(--tempo-ink);
}
.frame-wall__frame[data-palette="1"] {
  --frame-clr: #2D2420;
  --artwork-bg: var(--tempo-ink);
  --art-fg: var(--tempo-cream);
}
.frame-wall__frame[data-palette="2"] {
  --frame-clr: var(--tempo-sage);
  --artwork-bg: var(--tempo-sand);
  --art-fg: var(--tempo-ink);
}
.frame-wall__frame[data-palette="3"] {
  --frame-clr: #4A1F1F;
  --artwork-bg: #1E1610;
  --art-fg: var(--tempo-cream);
}

/* At night both "light" panels are near-black, so their type goes cream. */
html[data-tempo-time="after-hours"] .frame-wall__frame[data-palette="0"],
html[data-tempo-time="after-hours"] .frame-wall__frame[data-palette="2"] {
  --art-fg: var(--tempo-cream);
}

/* Orientation is a WIDTH decision against the shared picture height, which is
   how pictures hang: a common line, different shapes. Varying the height
   instead would leave dead space above every short frame. */
.frame-wall__frame[data-orientation="portrait"]  { --art-w: calc(var(--art-h) * 0.76); }
.frame-wall__frame[data-orientation="square"]    { --art-w: var(--art-h); }
.frame-wall__frame[data-orientation="landscape"] { --art-w: calc(var(--art-h) * 1.42); }

.frame-wall__frame-art {
  /* A picture frame: a full border on all four sides. Not a single-edge
     accent rule, which the house style bans. */
  border: var(--frame-border) solid var(--frame-clr, var(--tempo-terracotta));
  background: var(--frame-clr, var(--tempo-terracotta));
  box-shadow:
    0 8px 28px rgba(0, 0, 0, 0.12),
    0 2px 8px rgba(0, 0, 0, 0.06);
  transition:
    box-shadow 0.4s var(--ease-out-expo),
    transform 0.4s var(--ease-out-expo);
}

.frame-wall__frame-mat {
  background: var(--tempo-cream);
  padding: var(--mat-pad);
}

.frame-wall__frame-artwork {
  position: relative;
  width: var(--art-w);
  height: var(--art-h);
  background: var(--artwork-bg, var(--tempo-cream-dark));
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  /* Set once here so the caption inherits it and no text inside the frame
     needs a colour of its own. */
  color: var(--art-fg, var(--tempo-ink));
}

.frame-wall__frame-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  opacity: 0.55;
  z-index: 0;
  transition: opacity 0.5s var(--ease-out-expo), transform 0.7s var(--ease-out-expo);
}

/* The caption IS the artwork: the role set large, the details under it. No
   banner, no scrim — the panel is a flat colour, so the type sits directly on
   it the way a printed label does. */
.frame-wall__frame-caption {
  position: relative;
  z-index: 2;
  width: 100%;
  padding: clamp(10px, 1.1vw, 16px);
}

.frame-wall__frame-title {
  margin: 0;
  font-family: var(--font-display);
  /* Scaled off the shared picture height so every frame in the wall sets its
     role at the same size, whatever width it hangs at. */
  font-size: clamp(14px, calc(var(--art-h) * 0.105), 31px);
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: inherit;
  text-wrap: balance;
  /* A portrait frame on a phone is ~100px of picture wide. "Bartender" fits;
     a longer one-word title would otherwise set its own minimum and be clipped
     by the artwork rather than wrapping. */
  overflow-wrap: break-word;
}

/* THE WHOLE FRAME IS THE LINK. It wraps the moulding, the mat and the picture,
   so every pixel of a frame opens its role and carries the pointer cursor.
   This replaces a stretched `::after { inset: 0 }` on the title's link, which
   is the usual card trick and was silently broken here: `inset` resolves
   against the nearest POSITIONED ancestor, and the caption below is
   `position: relative` so it can sit over the picture — so the hit area was
   the caption strip and nothing else. Measured at 3 of 25 points across a
   frame before the fix, 25 of 25 after. */
.frame-wall__frame-link {
  display: block;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

/* No cursor rule of its own: base.css now carries
   `a *, button * { cursor: inherit }`, which is where that belongs — this was
   a site-wide bug, not a careers one. Measured here after the global fix: 149
   elements inside the wall's links, all pointer. */

.frame-wall__frame-meta {
  margin: 6px 0 0;
  font-size: clamp(9.5px, calc(var(--art-h) * 0.048), 13.5px);
  line-height: 1.35;
  letter-spacing: 0.01em;
  /* COLOUR EXPLICITLY, do not rely on inheriting from the artwork. globals.css
     colours every bare <p> with --color-text-secondary, which is an ink at 72%
     alpha — so on the two dark palettes this line rendered ink on near-black
     and simply was not there. The title, being an <h3>, inherited correctly
     and looked fine, which is what made the bug easy to miss.
     Stepped back with opacity rather than a second colour, so it can never
     drift out of contrast with the panel behind it. */
  color: var(--art-fg, var(--tempo-ink));
  opacity: 0.72;
}

/* An artwork image would put type over photography, where a flat colour can no
   longer be relied on. Only then does the panel get a wash — across the whole
   picture, not as a bar under it. */
.frame-wall__frame-artwork:has(.frame-wall__frame-img)::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to top, rgba(26, 24, 22, 0.78), rgba(26, 24, 22, 0.35));
}

.frame-wall__frame-artwork:has(.frame-wall__frame-img) {
  color: var(--tempo-cream);
}

/* ── Gallery track lighting ──────────────────────────────────────────────
   THE HOUSE TREATMENT FOR A WALL OF ART (Bailey, 2026-09-24: "why arent
   these using the art downlight effect as well? this is standard across the
   entire website"). The careers frames shipped with only the warm glow on the
   moulding, which is a third of it. A framed picture on this site hangs under
   a LAMP — a little track fixture above it — that throws a FLOOD CONE down the
   wall, and everything else on the wall dims while you are looking at one
   thing. Lifted from .page-events__wall-frame in events-pages.css, which is
   the closest relative: a wall of frames rather than a single hero frame.

   NOT the body::before dark room that spotlight.css gives .moods__frame and
   .tempo-frame. That one lifts the hovered frame to z-index 260 to punch
   through a fixed overlay, and it cannot work here: .frame-wall__wall is
   `transform: translateX(-50%)` for its full-bleed, and a transform creates a
   stacking context, so nothing inside it can rise above a fixed element
   painted at the root. The events wall reaches the same effect by dimming its
   own siblings, which is what is done below.

   The offsets are smaller than the events wall's. Its fixture hangs 44px above
   the frame with an 80px cone; that headroom has to live INSIDE the row, which
   is a horizontal scroll container and therefore clips both axes — so every
   pixel above the frame is paid for in permanent empty space above every row.
   These are sized to the space a wall of two rows can afford. */
@media (hover: hover) {
  /* HEADROOM FOR THE LAMP, inside the scroll container. A row with
     `overflow-x: auto` clips BOTH axes — `overflow-y: visible` computes to
     `auto` the moment the other axis is not visible — so the fixture and its
     cone, which hang above the frame, are cut off unless the space they need
     is padding within this box rather than margin outside it.
     Only on pointer devices: the lamp is a hover state, so a phone would
     otherwise carry ~50px of empty wall above each row to light a fixture it
     can never switch on. */
  .frame-wall__wall-row {
    padding-top: var(--lamp-headroom);
  }

  /* The rest of the wall steps back. Same value as the events wall. */
  .frame-wall__wall:has(.frame-wall__frame:hover) .frame-wall__frame:not(:hover) {
    opacity: 0.38;
  }

  /* The fixture. */
  .frame-wall__frame::after {
    content: "";
    position: absolute;
    top: calc(-1 * var(--lamp-drop));
    left: 50%;
    transform: translateX(-50%);
    width: 28px;
    height: 21px;
    background: linear-gradient(172deg, #3d3838 0%, #221e1e 42%, #111010 100%);
    border-radius: 4px 4px 8px 8px;
    box-shadow:
      0 0 0 1.5px rgba(90, 78, 75, 0.5),
      inset 0 -7px 10px rgba(255, 205, 50, 0.58),
      0 5px 15px 4px rgba(255, 215, 85, 0.70),
      0 10px 40px 13px rgba(255, 200, 55, 0.35),
      0 0 8px 3px rgba(255, 228, 110, 0.20);
    opacity: 0;
    transition: opacity 0.42s var(--ease-out-expo);
    pointer-events: none;
    z-index: 10;
  }

  /* The light it throws. z-index -1 puts it behind the picture and in front of
     the page, which is why the frame carries `isolation: isolate`. */
  .frame-wall__frame::before {
    content: "";
    position: absolute;
    top: calc(-1 * var(--lamp-drop) - 18px);
    left: 50%;
    transform: translateX(-50%);
    width: 240%;
    height: 112%;
    background: radial-gradient(
      ellipse 150px 76px at 50% 30px,
      rgba(255, 232, 130, 0.32) 0%,
      rgba(255, 218, 88, 0.18) 48%,
      rgba(255, 205, 58, 0.04) 78%,
      transparent 95%
    );
    -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 24px);
    mask-image: linear-gradient(to bottom, transparent 0%, black 24px);
    opacity: 0;
    transition: opacity 0.46s var(--ease-out-expo);
    pointer-events: none;
    z-index: -1;
  }

  .frame-wall__frame:hover::after,
  .frame-wall__frame:hover::before { opacity: 1; }

  .frame-wall__frame:hover .frame-wall__frame-art {
    box-shadow:
      0 0 0 2px var(--frame-clr, var(--tempo-terracotta)),
      0 0 28px 10px rgba(255, 218, 110, 0.24),
      0 0 64px 28px rgba(255, 200, 60, 0.10),
      0 20px 60px rgba(0, 0, 0, 0.18);
    transform: translateY(-6px);
  }
  .frame-wall__frame:hover .frame-wall__frame-img {
    opacity: 0.42;
    transform: scale(1.04);
  }
}

.frame-wall__frame:focus-within .frame-wall__frame-art {
  box-shadow:
    0 0 0 2px var(--frame-clr, var(--tempo-terracotta)),
    0 0 28px 10px rgba(255, 218, 110, 0.24),
    0 20px 60px rgba(0, 0, 0, 0.18);
  transform: translateY(-6px);
}

/* :focus, not only :focus-visible. These links sit inside a carousel, so a ring
   that appears whenever the link holds focus is genuinely useful — and
   focus-visible alone left the site's focus-ring audit finding twelve links
   with no indicator, because it probes with element.focus().
   TERRACOTTA, NOT CREAM: the ring used to be drawn inside the picture, where
   cream read against the panel. Now that the link wraps the whole frame the
   ring lands on the page background, where cream on cream is no ring at all. */
.frame-wall__frame-link:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 3px;
}

/* ── Carousel controls ───────────────────────────────────────────────────
   Mirrors the home page's moods__carousel-nav / nav-btn / dots, which is the
   site's live pager idiom: 40px square buttons, a 6px dot whose hit area is
   grown to 24px with invisible padding and background-clip, terracotta for
   the active one. */

.frame-wall__carousel-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-5);
  margin-top: var(--space-5);
  position: relative;
  z-index: 10;
}

.frame-wall__nav-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 1px solid var(--color-border-strong);
  background: transparent;
  color: var(--color-text-primary);
  flex-shrink: 0;
  cursor: pointer;
  transition:
    border-color var(--duration-fast) ease,
    color var(--duration-fast) ease,
    opacity var(--duration-fast) ease;
}

.frame-wall__nav-btn:hover:not(:disabled) {
  border-color: var(--tempo-terracotta);
  color: var(--tempo-terracotta);
}

.frame-wall__nav-btn:disabled {
  opacity: 0.25;
  cursor: default;
}

/* No focus rule of their own, deliberately. The site's global :focus-visible
   in globals.css rings them, and these are ordinary controls sitting on the
   page background — unlike the frame links below, which sit inside a picture
   and needed a ring of their own to be visible against it. Measured: the
   enabled Previous/Next and every dot paint a terracotta ring at 1440px. */

.frame-wall__dots {
  display: flex;
  align-items: center;
  gap: 8px;
}

.frame-wall__dot {
  /* 6px visible, 9px of invisible padding either side, clipped to the content
     box: a 24px touch target at 32px centres. Same treatment as .moods__dot
     and .events__dot. */
  width: 6px;
  height: 6px;
  padding: 9px;
  box-sizing: content-box;
  border-radius: 50%;
  border: none;
  background: var(--color-border-strong);
  background-clip: content-box;
  cursor: pointer;
  transition:
    background var(--duration-fast) ease,
    transform var(--duration-fast) ease;
}

.frame-wall__dot--active {
  /* background-COLOR, not the `background` shorthand. The shorthand resets
     every background longhand it does not mention, including the
     background-clip: content-box on the base rule that keeps the 9px touch
     padding invisible — so the active dot painted as a ~30px blob instead of
     a 9px one. events.css already carried the fix; moods.css was corrected the
     same way on 2026-09-23. */
  background-color: var(--tempo-terracotta);
  transform: scale(1.5);
}

/* ── Reduced motion ─────────────────────────────────────────────────────
   Nothing moves on its own any more, so this only quiets the hover and focus
   transitions. The paging scroll asks for "auto" instead of "smooth" in the
   component, because scroll-behavior set here would not reach scrollTo(). */
@media (prefers-reduced-motion: reduce) {
  .frame-wall__frame-art,
  .frame-wall__frame-img,
  .frame-wall__nav-btn,
  .frame-wall__dot {
    transition: none;
  }
}

/* ── Selectable frames ───────────────────────────────────────────────────
   A wall whose frames choose something on the page (the /collaborate/
   partner types) rather than open a page renders each frame as a <button>.
   Reset it to behave exactly like the link version. */
button.frame-wall__frame-link {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
}

/* The chosen frame is marked by a plain ring and nothing else. It must NOT
   light the lamp: the downlight is a hover state across the whole site, and a
   lamp left burning over one frame read as a bug (Bailey, 2026-09-26). */
.frame-wall__frame[data-selected="true"] .frame-wall__frame-art {
  outline: 2px solid var(--frame-clr, var(--tempo-terracotta));
  outline-offset: 5px;
}

/* ── Full-width walls ────────────────────────────────────────────────────
   Rows run edge to edge of the viewport instead of stopping at the content
   column (Bailey, 2026-09-26: "needs to be 100% viewport width"). The cap
   existed so a loop's seam stayed off screen; FrameWall compensates by
   repeating a short row's items inside each loop segment (see
   FULL_WIDTH_MIN_FRAMES there). */
.frame-wall__wall[data-width="full"] .frame-wall__wall-row {
  max-width: none;
}
