/* ============================================================
   CASE STUDY LAYOUT — Factor+ and Refer a Friend

   Promoted to canonical (Aug 2026) after the layout trial won out over the
   original linear case-study format. The linear versions are preserved at
   /archive/factor-plus-linear/ and /archive/raf-linear/ for reference — not
   linked from the live site, excluded from deploy.

   Modelled on avaa-architectes.fr/projets/architecte-pyla-sur-mer, measured
   rather than eyeballed: a flex row with no max-width, left column 929/1245
   (74.6%) carrying a plain vertical stack of images, right column 316/1245
   (25.4%) as `position: sticky; top: 0`. The images scroll; the panel doesn't.

   The one addition to their model: on that site "Voir plus" only expands the
   text inside a fixed-width panel. Here the panel itself widens as well, so
   the collapsed state can stay genuinely minimal — a summary a recruiter reads
   in thirty seconds — and the full case study still gets a comfortable measure
   when asked for.

   Loaded by /factor-plus/ and /raf/.
   ============================================================ */

/* Scroll anchoring off for the whole page. The collapse animates a large block
   shrinking above the image stack; anchoring sees that as content shift and
   "helpfully" re-scrolls to keep the images stable — which fights the
   deliberate scroll-to-top on collapse and drags the viewport back down
   mid-animation. Every scroll on this page is intentional; anchoring has
   nothing to protect. */
html { overflow-anchor: none; }

.pv-body {
  margin: 0;
  background: var(--bg-cream);
  color: var(--text-body);
  font-family: var(--font-sans);
}

.pv-wrap {
  display: flex;
  align-items: flex-start;
  min-height: 100vh;
}

/* ---------- left: the image stack ---------- */

/* The media column has two modes.

   COLLAPSED (summary): a plain scrolling stack, so a visitor who isn't reading
   can still browse every image. `display: contents` dissolves the stage groups
   so their figures flow directly in this column.

   EXPANDED (reading): a sticky stage that cross-fades to the current chapter's
   imagery. Previously the panel's scroll handler called window.scrollTo() to
   drag a figure into view — a programmatic smooth scroll running against the
   reader's own scroll, which is what made the page feel like it was fighting
   back. In stage mode nothing scrolls the page at all. */
.pv-media {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 36px;
}

.pv-stage-group { display: contents; }

.pv-wrap.is-expanded .pv-media {
  position: sticky;
  top: 0;
  height: 100vh;
  display: block;
  gap: 0;
}

.pv-wrap.is-expanded .pv-stage-group {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
  padding: 56px 48px;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.45s ease, visibility 0.45s;
}

.pv-wrap.is-expanded .pv-stage-group.is-active { opacity: 1; visibility: visible; }

.pv-wrap.is-expanded .pv-stage-group .pv-fig {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* height-bounded so two or three figures share the stage without overflowing */
.pv-wrap.is-expanded .pv-stage-group .pv-shot { max-height: 62vh; }
.pv-wrap.is-expanded .pv-stage-group .pv-shot.is-real { height: auto; }
.pv-wrap.is-expanded .pv-stage-group .pv-shot.is-real img { max-height: 62vh; object-fit: contain; }

.pv-shot {
  position: relative;
  width: 100%;
  background: var(--surface);
  border-bottom: 1px solid var(--rule-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 32px;
  color: var(--text-muted);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.pv-shot span { max-width: 44ch; line-height: 1.7; }
.pv-shot.is-tall  { aspect-ratio: 4 / 3; }
.pv-shot.is-wide  { aspect-ratio: 16 / 9; }
.pv-shot.is-panel { aspect-ratio: 21 / 9; }

.pv-shot img,
.pv-shot video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* ---------- right: the sticky panel ---------- */

.pv-side {
  flex: 0 0 26%;
  max-width: 420px;
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg-cream);
  border-left: 1px solid var(--rule);
  transition: flex-basis 0.55s cubic-bezier(0.22, 1, 0.36, 1),
              max-width  0.55s cubic-bezier(0.22, 1, 0.36, 1),
              padding-left 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}

/* The widen. Both properties have to move: flex-basis alone is capped by
   max-width, and max-width alone loses to the basis. */
.pv-wrap.is-expanded .pv-side {
  flex-basis: 46%;
  max-width: 760px;
}

.pv-side-inner {
  flex: 1 1 auto;
  overflow-y: auto;
  /* `contain` was blocking scroll chaining: once the panel reached its own
     top, scrolling up with the cursor over it stopped moving the page too,
     stranding the reader partway down. `auto` hands the remaining scroll back
     to the page at both ends, which is what the reader expects. */
  overscroll-behavior: auto;
  /* Top inset clears the sticky nav (z-index 100), which otherwise paints over
     the first ~101px of this scroll region and clips whatever reaches the top.

     Deliberately CONSTANT rather than toggled with the bar's visibility. The
     conditional version changed .pv-side's height by 101px every time the bar
     auto-hid, which moved the panel's scrollHeight/clientHeight on every
     change of scroll direction and made the whole interaction stutter. A fixed
     inset costs a little headroom in the rare hidden-bar-plus-panel-at-top
     state and keeps the geometry completely stable. */
  padding: 101px 36px 36px;
  -webkit-overflow-scrolling: touch;
}

.pv-side-inner::-webkit-scrollbar { width: 6px; }
.pv-side-inner::-webkit-scrollbar-thumb { background: var(--rule); border-radius: 0; }

/* ---------- panel type ---------- */

/* way home — the sidebar pages have no site nav, so the wordmark carries it */
.pv-home {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-primary);
  margin-bottom: 30px;
}
.pv-home:hover { color: var(--text-hover); }

/* The single label rule for this layout. Every uppercase micro-label in the
   panel resolves here — the collapsed view had drifted to three variants
   (11px/0.16em, 10px/600/0.14em, 10px/400/0.1em), which is exactly the kind
   of quiet inconsistency TYPE-SYSTEM.md exists to prevent. */
.pv-kicker,
.pv-meta dt,
.pv-result-label,
.pv-contents-label,
.pv-chapter-num,
.pv-fig-num,
.pv-contents button::before {
  font-family: var(--font-sans);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
  line-height: 1.5;
}

.pv-kicker {
  display: block;
  margin-bottom: 24px;
}

.pv-title {
  font-family: var(--font-sans);
  font-weight: 600;
  letter-spacing: -0.01em;
  font-size: clamp(26px, 2.2vw, 31px);
  line-height: 1.2;
  color: var(--text-primary);
  margin: 0 0 18px;
}

.pv-summary p {
  font-size: 15px;
  line-height: 1.7;
  color: var(--text-body);
  margin: 0 0 16px;
}

.pv-summary p:last-child { margin-bottom: 0; }
.pv-summary strong { color: var(--text-primary); font-weight: 600; }

/* ---------- the toggle ---------- */

.pv-toggle {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  margin: 24px 0 0;
  font-family: var(--font-sans);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-primary);
  cursor: pointer;
  border-bottom: 1px solid var(--text-primary);
  padding-bottom: 4px;
  transition: opacity 0.2s ease;
}

.pv-toggle:hover { opacity: 0.6; }
.pv-toggle .pv-toggle-less { display: none; }
.pv-wrap.is-expanded .pv-toggle .pv-toggle-more { display: none; }
.pv-wrap.is-expanded .pv-toggle .pv-toggle-less { display: inline; }

/* ---------- meta table ---------- */

.pv-meta {
  margin: 32px 0 0;
  padding: 0;
  border-top: 1px solid var(--rule);
}

.pv-meta div {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  padding: 11px 0;
  border-bottom: 1px solid var(--rule-soft);
}

.pv-meta dt { margin: 0; flex-shrink: 0; }

.pv-meta dd {
  font-size: 15px;
  color: var(--text-primary);
  margin: 0;
  text-align: right;
  line-height: 1.5;
}

/* ---------- headline results ---------- */

/* No top border: the meta table above already closes with its own bottom
   rule, so this stacked a second hairline a few pixels below the first. The
   spacing alone separates the blocks. */
.pv-results {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px 16px;
  margin-top: 34px;
}

.pv-result-value {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 22px;
  line-height: 1;
  color: var(--text-primary);
  margin: 0 0 5px;
}

.pv-result-label { margin: 0; }

/* ---------- the expandable body ---------- */

/* Collapsed by grid-rows rather than max-height: it animates to the content's
   real height, so there is no guessed magic number to outgrow. */
.pv-full {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}

.pv-wrap.is-expanded .pv-full { grid-template-rows: 1fr; }

.pv-full-inner { overflow: hidden; min-height: 0; }

/* Chapters were nine visually identical blocks — same rule, same small label,
   same spacing — so there was no sense of moving between them. The index is
   now a large display numeral and the gap between chapters is doubled, giving
   each one a clear opening beat. */
.pv-chapter {
  padding-top: 56px;
  margin-top: 56px;
  border-top: 1px solid var(--rule);
}

.pv-chapter-num {
  display: block;
  margin-bottom: 14px;
  font-family: var(--font-display) !important;
  font-size: 30px !important;
  font-weight: 500 !important;
  letter-spacing: 0 !important;
  text-transform: none !important;
  line-height: 1;
  color: var(--text-muted);
}

.pv-chapter h2 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 20px;
  line-height: 1.25;
  color: var(--text-primary);
  margin: 0 0 16px;
}

.pv-chapter p {
  font-size: 15px;
  line-height: 1.7;
  color: var(--text-body);
  margin: 0 0 14px;
}

.pv-chapter p:last-child { margin-bottom: 0; }

.pv-minihead {
  font-size: 10px !important;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted) !important;
  margin: 24px 0 8px !important;
}

.pv-quote {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-size: 18px;
  line-height: 1.4;
  color: var(--text-primary);
  margin: 24px 0;
  padding-left: 18px;
  border-left: 2px solid var(--rule);
}

/* ---------- contents (collapsed state) ----------
   The collapsed panel showed four numbers and no hint that a real argument sat
   underneath. Listing the chapter titles gives the reader the shape of the
   story and a reason to open it — and each row is a direct entry point, so
   nobody has to expand and then hunt. */

.pv-contents {
  margin-top: 32px;
  padding-top: 24px;
  border-top: 1px solid var(--rule);
}

.pv-contents-label { display: block; margin-bottom: 12px; }

.pv-contents ol { list-style: none; margin: 0; padding: 0; counter-reset: pvc; }

.pv-contents li { counter-increment: pvc; }

.pv-contents button {
  display: flex;
  gap: 12px;
  width: 100%;
  text-align: left;
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--rule-soft);
  padding: 11px 0;
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.5;
  color: var(--text-body);
  cursor: pointer;
  transition: color 0.2s ease;
}

.pv-contents button::before {
  content: counter(pvc, decimal-leading-zero);
  flex-shrink: 0;
  padding-top: 2px;
}

.pv-contents button:hover { color: var(--text-primary); }

/* the same drawn-in rule as the homepage index — one hover language */
.pv-contents button { position: relative; }

.pv-contents button::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 1px;
  background: var(--text-primary);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
}

.pv-contents button:hover::after,
.pv-contents button:focus-visible::after { transform: scaleX(1); }

/* The chapters themselves replace this once open. */
.pv-wrap.is-expanded .pv-contents { display: none; }

/* ---------- active image ----------
   Only while expanded: reading is the mode where focus helps, browsing is not.
   Backed out entirely for reduced-motion users. */
/* Scoped to the split view: the dim exists to focus the synced figure, and
   below 1000px there is no sync — unscoped, it faded the whole image column
   to 42% with nothing active. */
@media (prefers-reduced-motion: no-preference) and (min-width: 1001px) {
  .pv-wrap.is-expanded .pv-fig {
    opacity: 0.42;
    transition: opacity 0.5s ease;
  }
  .pv-wrap.is-expanded .pv-fig.is-active { opacity: 1; }
}


/* ---------- editorial apparatus ---------- */

/* Figures: numbered captions under every image. A numbered figure claims
   "this image is evidence", which is the register a case study wants — and
   when real screens land, the captions are already doing the annotating. */
.pv-fig { margin: 0; }   /* <figure> UA default is 1em 40px; see git blame */

.pv-fig-cap {
  padding: 12px 28px 0;
  font-size: 12px;
  line-height: 1.6;
  color: var(--text-muted);
  max-width: 68ch;
}

.pv-fig-num { color: var(--text-primary); margin-right: 10px; }

/* Standfirst: same size and family as the summary, differentiated by colour
   only. A third typeface (Lora) and a third text size were both tried here —
   each one pulled the eye to the styling instead of the words, which is the
   opposite of what a deck is for. */
.pv-summary p.pv-standfirst,
.pv-standfirst {
  color: var(--text-primary);
}

/* Spine: the current chapter title running vertically in the seam between
   the columns, like a book spine. The gutter is RESERVED (padding-left on
   the expanded panel) rather than overlaid, so the text sits in cream space
   and can never collide with an image edge once real screens exist. */
.pv-spine {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 52px;
  display: flex;
  align-items: center;
  justify-content: center;
  writing-mode: vertical-rl;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.pv-spine.is-visible { opacity: 1; }

.pv-wrap.is-expanded .pv-side { padding-left: 52px; }

@media (max-width: 1000px) {
  .pv-spine { display: none; }
  .pv-wrap.is-expanded .pv-side { padding-left: 0; }
}

/* ---------- responsive ----------
   The two-column model needs real width. Below 1000px the panel stops being
   sticky and sits above the images as a normal block — a 26% sticky rail on a
   phone would leave neither column readable. */

@media (max-width: 1000px) {
  .pv-wrap { flex-direction: column; }
  /* Summary first. The document keeps images before the panel for the split
     view; on mobile the panel is the front door, so it takes order -1. */
  .pv-side {
    order: -1;
    position: static;
    height: auto;
    flex-basis: auto;
    max-width: none;
    width: 100%;
    border-left: 0;
    border-bottom: 1px solid var(--rule);
  }
  .pv-media { gap: 28px; }
  .pv-fig-cap { padding: 10px 24px 0; }
  .pv-wrap.is-expanded .pv-side { flex-basis: auto; max-width: none; }
  .pv-side-inner { overflow: visible; padding: 32px 24px 36px; }
  .pv-media { width: 100%; position: static; height: auto; }
  .pv-wrap.is-expanded .pv-media { position: static; height: auto; display: flex; gap: 28px; }
  .pv-stage-group,
  .pv-wrap.is-expanded .pv-stage-group {
    display: contents;
    opacity: 1;
    visibility: visible;
  }
  .pv-wrap.is-expanded .pv-stage-group .pv-shot { max-height: none; }
  .pv-wrap.is-expanded .pv-stage-group .pv-shot.is-real img { max-height: none; }
  .pv-results { grid-template-columns: repeat(4, 1fr); }
}

@media (max-width: 620px) {
  .pv-results { grid-template-columns: 1fr 1fr; }
}

/* Touch targets — same 44px floor the live site uses. The toggle is the one
   interactive element that was well under it (~21px). */
@media (any-pointer: coarse) {
  .pv-toggle {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding-bottom: 0;
  }
  .pv-contents button { min-height: 44px; align-items: center; }
  .pv-contents button::before { padding-top: 0; }
}

/* mid-size titles follow the site-wide grotesque switch */
.pv-chapter h2 {
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 17px;
  line-height: 1.4;
}

.pv-result-value {
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 20px;
}

/* Real screenshots sit on their own light ground — the placeholder's grey
   surface would show through a screenshot's own white chrome. */
.pv-shot.is-real {
  aspect-ratio: auto;
  padding: 0;
  background: #fff;
  display: block;
}

.pv-shot.is-real img { position: static; width: 100%; height: auto; object-fit: contain; }

/* Product mark. Inlined rather than an <img> so the wordmark can take
   currentColor and stay legible in both themes; the gold "PLUS" is fixed
   brand colour and carries through unchanged. */
.fp-logo {
  display: block;
  width: 132px;
  height: auto;
  color: var(--text-primary);
  margin-bottom: 18px;
}

/* stage entrances: the incoming chapter's imagery rises a few pixels as it
   fades in — decorative, so reduced-motion users keep the plain cross-fade */
@media (prefers-reduced-motion: no-preference) {
  .pv-wrap.is-expanded .pv-stage-group {
    transform: translateY(14px);
    transition: opacity 0.45s ease, visibility 0.45s, transform 0.55s cubic-bezier(0.22, 1, 0.36, 1);
  }
  .pv-wrap.is-expanded .pv-stage-group.is-active { transform: none; }
}


/* MOBILE PASS (see styles.css) — the panel rules live here, so their mobile
   overrides must too: this sheet loads after styles.css on case pages. */
@media (max-width: 700px) {
  .pv-title { font-size: 22px; line-height: 1.25; }
  /* two-up spec cells, label stacked over value: right-aligned values in a
     half-width column wrapped one word per line */
  .pv-meta { display: grid; grid-template-columns: 1fr 1fr; column-gap: 20px; }
  .pv-meta div {
    flex-direction: column;
    align-items: flex-start;
    gap: 3px;
    padding: 10px 0;
  }
  .pv-meta dd { text-align: left; font-size: 14px; }
}
