/* ==========================================================================
   Bunn Design Co. — homepage "Selected Work" showcase

   Scoped to index.html only. Sits on top of site.css + studio.css and reuses
   their tokens/type/spacing/reveal system exactly — nothing in here
   introduces a new colour, font or easing curve. Two parts:
     1. .work-feature-list / .work-feature — the large case-study layout that
        replaced the old card grid.
     2. .nb-* — the Northbound device showcase: a CSS-built browser frame and
        phone frame, auto-playing a full-page screenshot scroll on entry
        (see showcase.js — not tied to page-scroll position).
   ========================================================================== */

.work-feature-list {
  display: grid;
  gap: var(--s-7);
}

/* devices on the left, project text on the right — a fixed two-column
   layout (never centred), matching the reference composition. Devices get
   the wider share since they're the visual centrepiece. */
.nb-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(0, .72fr);
  gap: var(--s-6);
  align-items: center;
}
/* second-featured variant: text first (left) then devices (right) — the
   grid tracks swap width to match (devices still get the wider share,
   it's just the second column now), same gap and vertical centring. */
.nb-layout--mirror { grid-template-columns: minmax(0, .72fr) minmax(0, 1.3fr); }

.work-feature__head { max-width: 60ch; }
.work-feature__title { margin-top: var(--s-2); max-width: 14ch; }
.work-feature__meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: .55rem;
  margin-top: var(--s-3);
  font-family: var(--display); font-weight: 600; font-size: var(--t-sm);
  color: var(--ink-55);
}
.work-feature__dot { width: 3px; height: 3px; border-radius: 50%; background: var(--ink-14); flex: none; }
.work-feature__desc { margin-top: var(--s-3); max-width: 44ch; font-size: var(--t-lead); color: var(--ink-70); text-wrap: pretty; }
.work-feature__actions { margin-top: var(--s-5); }

/* ==========================================================================
   Device showcase — left-aligned within its column, never centred
   ========================================================================== */
/* plain block, not flex: .nb-devices-link's width comes from a real,
   definite value (max-width capped against .nb-showcase's own grid-track
   width), so a block element's natural left-start position is all that's
   needed to keep this unstretched and unstretched — no flex/justify-content
   required, which avoids a width:100%-of-auto circular reference through
   the link wrapper (that collapsed the whole frame to content-min-width on
   first pass here). */
.nb-showcase { display: block; }

.nb-devices-link {
  display: block;
  width: 100%;
  max-width: 820px;
  border-radius: 14px; /* matches the desktop frame's own radius, for a clean focus ring */
  transition: transform .4s var(--about-ease-out);
}
.nb-devices-link:focus-visible {
  outline: 2px solid var(--blue); outline-offset: 6px;
}

.nb-devices {
  position: relative;
  width: 100%;
  /* room for the phone's overhang on the right/bottom without it being
     clipped by any ancestor's overflow */
  padding: clamp(6px, 1vw, 14px) clamp(48px, 8vw, 92px) clamp(30px, 6vw, 68px) 0;
}
/* second-featured variant: phone overhangs the lower-LEFT corner instead —
   same overhang amount, just mirrored, so the padding moves to the left
   edge instead of the right. */
.nb-devices--mirror {
  padding-left: clamp(48px, 8vw, 92px);
  padding-right: 0;
}

/* ---------- desktop browser frame ---------- */
.nb-frame--desktop {
  position: relative;
  width: 100%;
  border: 1.5px solid var(--about-green-35);
  border-radius: 14px;
  background: var(--paper);
  box-shadow: 0 28px 64px rgba(17, 17, 17, .10);
  overflow: hidden; /* rounds the bar + viewport corners */
  transition: border-color .35s ease, box-shadow .4s ease;
}
.nb-frame__bar {
  height: 40px;
  display: flex; align-items: center; gap: .5rem;
  padding-inline: .8rem;
  border-bottom: 1px solid var(--about-green-16);
  background: var(--paper-2);
}
.nb-frame__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ink-14); flex: none; }
/* The address pill was the single worst responsive offender on the site, and
   not for any visible reason. It is a flex item with min-width:0, so it looks
   shrinkable — but the URL inside is one unbroken token ("leasideautospa
   .livebuild"), and an un-clipped text node's min-content width is the width
   of its longest word. That set a ~237px floor on the browser frame, which
   set a 360px floor on the grid track holding it, which pushed the entire
   Selected Work row 60px past a 320px viewport. Clipping the text with an
   ellipsis removes the floor entirely and the frame becomes genuinely fluid. */
.nb-frame__url {
  margin-left: .5rem;
  flex: 1 1 auto;
  min-width: 0;
  height: 20px;
  display: flex; align-items: center;
  padding-inline: .65rem;
  border-radius: 999px;
  background: var(--paper);
  border: 1px solid var(--ink-14);
  font-family: var(--mono, var(--display));
  font-size: 11px; letter-spacing: .02em; color: var(--ink-55);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.nb-frame--desktop .nb-frame__viewport {
  position: relative;
  width: 100%;
  height: clamp(280px, 22vw, 380px);
  overflow: hidden; /* this is the ONLY layer clipping the scrolling website preview itself */
  background: var(--paper-2);
}

/* the "WEBSITE DEMO" badge — a sibling of the <img> inside .nb-frame__viewport,
   not a descendant of it, so it never inherits the screenshot's own
   translateY animation (siblings aren't affected by each other's
   transforms). It's positioned directly against the viewport's own box
   (its nearest positioned ancestor), so it always sits at the top-left of
   the actual website content — below the separate .nb-frame__bar above it,
   which stays in normal flow and reserves its own real 40px of height, so
   the bar (dots + address pill) is never covered. overflow:hidden on the
   viewport only ever clips content that extends past its edges; the badge,
   inset well within it, never does. */
.nb-badge {
  position: absolute; top: 12px; left: 12px; z-index: 2; pointer-events: none;
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .38rem .7rem .38rem .6rem;
  max-width: calc(100% - 24px);
  border-radius: 999px;
  border: 1px solid var(--about-green-35);
  background: var(--paper);
  box-shadow: 0 6px 16px rgba(17, 17, 17, .10);
  font-family: var(--display); font-weight: 600; font-size: 10px;
  letter-spacing: .09em; text-transform: uppercase; color: var(--ink-70);
  white-space: nowrap;
}
/* Retro Fitness's own nav sits top-left of its screen too — the badge
   moves to the top-right on that card so the two never overlap. */
.nb-badge--right { left: auto; right: 12px; }
.nb-badge__dot {
  width: 6px; height: 6px; flex: none; border-radius: 50%;
  background: var(--blue);
  box-shadow: 0 0 0 3px var(--about-green-16);
}
/* "live" variant — a real project, not a demo, so the dot reads as an
   actual lit indicator light rather than a flat colour swatch: a brighter
   green core with a glossy highlight (a radial gradient, lighter at the
   upper-left like light catching a real LED) plus a soft two-layer glow
   bleeding outward. A ring still expands and fades from it on a loop; the
   dot itself stays lit throughout, never fully off between pulses. */
.nb-badge__dot--live {
  position: relative;
  background: radial-gradient(circle at 35% 30%, #B9E59A, #6FA84E 75%);
  box-shadow:
    0 0 0 3px var(--about-green-16),
    0 0 5px 1px rgba(122, 196, 90, .9),
    0 0 11px 3px rgba(122, 196, 90, .5);
}
.nb-badge__dot--live::before {
  content: "";
  position: absolute; inset: -4px;
  border-radius: 50%;
  background: #7AC45A;
  opacity: .55;
  animation: nbLivePulse 2.4s var(--about-ease) infinite;
}
@keyframes nbLivePulse {
  0%   { transform: scale(.5); opacity: .5; }
  75%, 100% { transform: scale(2.2); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .nb-badge__dot--live::before { animation: none; opacity: 0; }
}

/* ---------- phone frame ---------- */
.nb-frame--phone {
  position: absolute;
  right: clamp(-40px, -7vw, -16px);
  bottom: clamp(-26px, -5vw, -12px);
  width: clamp(112px, 14vw, 172px);
  aspect-ratio: 9 / 19.3;
  z-index: 3;
  border: 1.5px solid var(--about-green-35);
  border-radius: 26px;
  background: var(--paper);
  box-shadow: 0 20px 44px rgba(17, 17, 17, .14);
  padding: 10px 6px;
  transition: transform .3s var(--about-ease-out), border-color .35s ease, box-shadow .4s ease;
}
/* second-featured variant: same size and overhang amount, mirrored to the
   lower-left corner instead of the lower-right. */
.nb-devices--mirror .nb-frame--phone {
  right: auto;
  left: clamp(-40px, -7vw, -16px);
}
.nb-frame__notch {
  position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
  width: 34%; height: 5px; border-radius: 999px;
  background: var(--ink-14);
}
.nb-frame--phone .nb-frame__viewport {
  position: relative;
  width: 100%; height: 100%;
  border-radius: 16px;
  overflow: hidden;
  background: var(--paper-2);
}

/* the scrubbed screenshot itself — a single tall image translated via JS,
   transform only (never top/left), so this never triggers layout */
.nb-frame__shot {
  display: block; width: 100%; height: auto;
  will-change: transform;
}

/* hover: the muted-green border brightens toward the full accent; the
   phone lifts by a few px. Both restrained — secondary devices, not the
   focal point of the hover. The whole composition is also a magnetic link
   (see [data-magnetic] in studio.css/js), so hovering nudges it slightly
   toward the pointer too, the same language as the About page's CTA
   buttons. */
.nb-devices-link:hover .nb-frame--desktop,
.nb-frame--desktop:hover {
  border-color: var(--blue);
  box-shadow: 0 28px 64px var(--about-green-16);
}
.nb-devices-link:hover .nb-frame--phone,
.nb-frame--phone:hover {
  transform: translateY(-6px);
  border-color: var(--blue);
}

/* ---------- scroll depth ----------
   The two devices drift a few pixels in OPPOSITE directions as the section
   passes the middle of the viewport, which is the whole trick: parallax
   between two objects in one composition reads as depth, where the same
   amount applied to both would just read as the section sliding. Tiny
   amounts — 14px against a 380px-tall frame — because the moment it becomes
   noticeable as movement it stops working as depth.

   Both ride `translate`, not `transform`: the phone's entrance and its hover
   lift are already using `transform`, and `translate` composes with it
   instead of fighting for the property. showcase.js writes --nb-depth. */
.nb-frame--desktop { translate: 0 calc(var(--nb-depth, 0px) * -0.36); }
.nb-frame--phone { translate: 0 var(--nb-depth, 0px); }
@media (prefers-reduced-motion: reduce) {
  .nb-frame--desktop, .nb-frame--phone { translate: none; }
}

/* ---------- entrance ----------
   Desktop: the base .about-reveal treatment (rise + blur-to-sharp) applied
   to the whole .nb-devices group. Phone: its own modifier, entering
   afterward (via the delay below) and from the lower right specifically,
   rather than sharing the desktop's straight-up motion. */
.nb-devices.about-reveal { transition-duration: .9s, .9s, .7s; }

.nb-frame--phone {
  opacity: 0;
  transform: translate(22px, 26px) scale(.92);
  filter: blur(3px);
  transition: opacity .8s var(--about-ease-out) .22s, transform .8s var(--about-ease-out) .22s, filter .6s ease .22s;
}
.nb-devices.about-reveal.is-in .nb-frame--phone {
  opacity: 1; transform: none; filter: blur(0);
}
/* mirrored entrance to match the mirrored resting corner — enters from the
   lower LEFT instead */
.nb-devices--mirror .nb-frame--phone { transform: translate(-22px, 26px) scale(.92); }
@media (prefers-reduced-motion: reduce) {
  .nb-frame--phone { opacity: 1; transform: none; filter: none; transition: none; }
  .nb-devices-link { transition: none; }
}
.no-js .nb-frame--phone { opacity: 1; transform: none; filter: none; }

/* ==========================================================================
   Responsive
   ========================================================================== */
@media (max-width: 900px) {
  /* minmax(0,1fr), not 1fr: a bare `fr` track has an automatic minimum of
     min-content, so any stubborn child (see .nb-frame__url above) can force
     the track — and therefore the whole section — wider than the viewport.
     minmax(0,...) lets the track actually shrink. */
  .nb-layout { grid-template-columns: minmax(0, 1fr); }
  /* single column: centring reads better than a hard-left edge once the
     text drops below, so the fixed-width block gets auto side margins */
  .nb-devices-link { max-width: 560px; margin-inline: auto; }
  .nb-devices { padding-right: clamp(40px, 11vw, 66px); padding-bottom: clamp(26px, 7vw, 50px); }
  .nb-devices--mirror { padding-right: 0; padding-left: clamp(40px, 11vw, 66px); }
  .work-feature__head { max-width: none; }
  .work-feature__desc { max-width: 56ch; }
}

/* ---------- phones: stop overlapping, start stacking ----------
   The overhanging phone is the right composition on a wide screen — it reads
   as one designed object, and the negative offset is what makes it feel
   layered rather than pasted on. Below ~620px that same composition stops
   working: the overhang has to come out of a viewport that is already too
   narrow for the desktop frame, so either the frame shrinks to a useless
   thumbnail or the phone hangs off the edge and gets clipped. Both are worse
   than simply changing the arrangement.

   So on phones the two devices become a deliberate vertical pair instead: the
   browser frame runs the full width of the column at a taller viewport (there
   is no side-by-side text competing for the space any more, so it can afford
   the height), and the phone sits centred beneath it at a size where its
   screen is actually legible. Nothing is scaled down, nothing is cropped, and
   the overhang padding that existed only to make room for the overlap is
   removed — which is where the empty side margins on mobile were coming from. */
@media (max-width: 620px) {
  .nb-devices, .nb-devices--mirror {
    padding: 0;
    display: flex; flex-direction: column; align-items: center;
    gap: var(--s-3);
  }
  .nb-devices-link { max-width: none; margin-inline: 0; }

  /* taller screen: on a phone this frame is the only thing in its row, so a
     16:10-ish window shows meaningfully more of the site than the wide,
     short desktop crop did */
  .nb-frame--desktop { width: 100%; }
  .nb-frame--desktop .nb-frame__viewport { height: clamp(230px, 62vw, 330px); }

  /* Out of the corner and into the flow, centred, at a real size.

     Both width AND height are declared, and that is load-bearing rather than
     belt-and-braces. While the phone was position:absolute, `aspect-ratio`
     alone gave it a definite height. As a static flex item it does not: the
     frame contains a 390x9619 screenshot, and a flex item's automatic minimum
     size is its content, so the image's intrinsic height won over the aspect
     ratio and the frame rendered 3441px tall — which is what was making the
     Selected Work rows several thousand pixels deep on a phone. Deriving the
     height from the same custom property keeps the 9:19.3 proportion exact
     while making it a definite length that the viewport's overflow can clip
     against. flex:none stops the column from stretching it back out. */
  .nb-frame--phone,
  .nb-devices--mirror .nb-frame--phone {
    --nb-phone-w: clamp(126px, 40vw, 168px);
    position: static;
    right: auto; left: auto; bottom: auto;
    flex: none;
    width: var(--nb-phone-w);
    height: calc(var(--nb-phone-w) * 19.3 / 9);
    margin: 0;
  }
  /* the entrance came in from the overhang corner, which no longer exists —
     it now rises straight up like the frame above it */
  .nb-frame--phone,
  .nb-devices--mirror .nb-frame--phone { transform: translateY(20px) scale(.95); }
  .nb-devices.about-reveal.is-in .nb-frame--phone { transform: none; }
  /* and the hover lift is meaningless on a touch screen */
  .nb-devices-link:hover .nb-frame--phone,
  .nb-frame--phone:hover { transform: none; }

  /* The scroll-depth parallax is switched off here, and that is a design
     decision rather than a performance one. It works above this breakpoint
     because the phone OVERLAPS the browser frame: moving the two in opposite
     directions is what reads as one object with depth. Stacked vertically,
     the same opposing motion has nothing to be depth relative to — it just
     looks like two separate boxes sliding apart. */
  .nb-frame--desktop, .nb-frame--phone { translate: none; }
}

/* Above that, the desktop frame stays visible and playing on every device;
   the .nb-frame--phone sizing above (clamp-based) keeps it from disappearing
   or overflowing between 620px and 900px, where the overlap still fits. */

/* ---------- reduced motion: static, useful preview ---------- */
@media (prefers-reduced-motion: reduce) {
  .nb-frame--desktop .nb-frame__viewport { height: clamp(240px, 19vw, 330px); }
  .nb-frame__shot { transform: none !important; }
}

.no-js .nb-frame--desktop .nb-frame__viewport { height: clamp(240px, 19vw, 330px); }
