/* ============================================================
   The pinned CRM section: four notifications become one inbox.

   Layout and motion only. The app replica inside the stage is css/crm-ui.css.

   THE SPOTLIGHT IS GONE. No dim layer, no mask, no mask-composite, no zoom,
   no pan, no clamped camera. That machinery was deleted rather than disabled,
   because a disabled camera is a camera somebody turns back on. The window
   resolves once and then HOLDS COMPLETELY STILL while only its contents
   change. Everything that moves after that moves inside it.

   Motion rule for this file: transform and opacity only. Nothing here may
   transition a layout property.

   Default state is the fallback: no pin, four marks in a row, the app window
   full width with the thread open and the reply already written. The pin is
   opt-in, applied by js/crm-spotlight.js only once it has confirmed the
   viewport is at least MIN_WIDTH (1024px) wide and motion is welcome. So no-JS, reduced
   motion and phones all land on the same layout without a second
   implementation.
   ============================================================ */

.crm-spot__track { position: relative; }

.crm-spot__grid {
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 var(--gutter, 32px);
  display: grid;
  gap: 32px;
}

/* ---------- the copy column ---------- */
.crm-spot__copy {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 20px;
}

.crm-spot__beat em {
  font-style: normal;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: #7c3aed;
}
.crm-spot__beat h3 {
  margin: 10px 0 10px;
  font-size: clamp(22px, 2.4vw, 32px);
  line-height: 1.08;
  letter-spacing: -.02em;
}
.crm-spot__beat p {
  margin: 0;
  color: #5d5962;
  font-size: 15.5px;
  line-height: 1.6;
  max-width: 46ch;
}

/* The stage clips its actors. It is the one box here that is NEVER
   transformed, so it is the only clip a transformed child cannot escape. */
.crm-spot__stage { position: relative; }

/* ---------- fallback: the four marks as a static row ---------- */
.crm-spot__marks {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: clamp(14px, 5vw, 34px);
  margin: 0 0 22px;
  pointer-events: none;
}
.crm-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  flex: none;
}
.crm-mark svg { display: block; width: 100%; height: 100%; }

/* The notification cards and the flying badge are motion devices with nothing
   to say at rest, so unpinned they are not rendered at all. What the claim
   needs at rest is the four marks above and the badge sitting in the app's own
   brand slot below, and both of those survive. */
.crm-spot__notes,
.crm-badge { display: none; }

/* The end state has the thread open, so the pane's empty state has nothing to
   say at rest either. */
.crm-ui__pane-empty { opacity: 0; }

/* Unpinned, the sidebar's brand slot carries the real badge image. Pinned, the
   slot is empty and the flying badge lands in it. Same 32px box either way. */
.crm-ui__brand-mark img { width: 32px; height: 32px; display: block; }

/* ============================================================
   PINNED
   ============================================================ */

/* 420vh: four beats, and the notification stack needs its own runway before
   the inbox ever appears. */
.crm-spot--pinned .crm-spot__track { height: 420vh; }

.crm-spot--pinned .crm-spot__sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  align-items: center;
}

/* Pinned, the grid gives up the 1180px prose measure and takes the section's
   full gutter-to-gutter width. §5b wants the window at up to 980px and the
   copy column still needs 300px beside it, and 1180 cannot pay for both: at
   1440 it left the window 676px, which is the size the spec enlarged it FROM.
   The copy column is capped rather than proportional so the window keeps
   every pixel a wider viewport hands over, up to its own 980px ceiling. */
.crm-spot--pinned .crm-spot__grid {
  max-width: none;
  grid-template-columns: minmax(280px, 340px) minmax(0, 1fr);
  gap: 44px;
  align-items: center;
  width: 100%;
  /* The section's own clamp(82px, 8vw, 128px) block padding is what the head
     used to sit in. With the head gone that padding is dead space inside a
     100vh sticky, so the grid is optically centred against the viewport
     instead of inheriting the section's rhythm. */
  padding-block: 0;
}

.crm-spot--pinned .crm-spot__copy {
  position: relative;
  display: block;
  /* Tall enough to hold the longest beat, and no taller. Measured beat heights
     at the four pinning widths, tallest first:
       1280  293.8  (beat 0, the copy wraps hardest here)
       1440  273.1
       1024  269.1
       1366  262.4
     306px clears the worst of them by 12px, which is the gap the progress rule
     wants under the last line. At 340 the rule floated in dead space below the
     copy at every width, which is what made the whole column read bottom-heavy
     against the stage. At 260 - the value before that - the four-line first
     beat overran its own box and the rule drew THROUGH the last line. */
  min-height: 306px;
}

/* Stage scrubs continuously, copy STEPS. The beat index is discrete and its
   tweens are eased, because a beat caught half way between two states is
   saying nothing. The mismatch against the continuously driven stage is what
   stops the section reading as one flat slider. */
.crm-spot--pinned .crm-spot__beat {
  position: absolute;
  inset: 0;
  opacity: 0;
  transform: translateY(16px);
  transition: opacity .45s cubic-bezier(.22,.61,.36,1), transform .45s cubic-bezier(.22,.61,.36,1);
  pointer-events: none;
}
.crm-spot--pinned .crm-spot__beat.is-active {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

/* Progress bar drawn from the active beat's index, so it cannot disagree with
   the copy. */
.crm-spot--pinned .crm-spot__copy::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -6px;
  width: 92px;
  height: 3px;
  border-radius: 2px;
  background: linear-gradient(to right, #4c1d95 var(--crm-progress, 25%), rgb(76 29 149 / .14) var(--crm-progress, 25%));
}

/* §5b: 560px, and the window inside it is min(980px, 100%). Copy legibility at
   this size is the reason for enlarging it. The stage does NOT clip: the
   notification stack is deliberately taller than the window it collapses into,
   and clipping the top of the stack is what turned the clutter into a neat
   row. Nothing here ever leaves the grid column. */
.crm-spot--pinned .crm-spot__stage { height: 560px; }

/* ---------- the four notification cards ---------- */
/*
 * The opening is a phone's notification shade, not four floating icons. Each
 * card carries its app icon, a guest name and a line of what they wrote, and
 * they slide in one after another and pile up slightly overlapped and rotated.
 * That mess IS the problem statement, so the cards have to be readable: a
 * blurred stack of grey rectangles says nothing, four real messages say
 * "this is your morning".
 *
 * Geometry is shared with js/crm-spotlight.js and the two must agree:
 *   card       306 x 72
 *   icon slot  44px square, 12px in from the left edge
 *              so its centre sits at -(306/2) + 12 + 22 = -119 from the
 *              card's own centre. That is the constant the marks layer uses to
 *              ride the cards before it detaches from them.
 */
.crm-spot--pinned .crm-spot__notes {
  display: block;
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

.crm-note {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 306px;
  height: 70px;
  margin: -35px 0 0 -153px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 14px 0 12px;
  border-radius: 18px;
  background: rgb(255 255 255 / .94);
  border: 1px solid rgb(76 29 149 / .10);
  box-shadow: 0 18px 40px -16px rgb(25 23 28 / .32), 0 1px 3px rgb(25 23 28 / .06);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  will-change: transform, opacity;
}

/* The hole the icon leaves behind when it detaches. It is the same cloud tile
   the inbox rows use, so the card reads as a row that has lost its mark. */
.crm-note__slot {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: #f3f3f5;
}

.crm-note__body { flex: 1; min-width: 0; }
.crm-note__top {
  margin: 0 0 2px;
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: #5d5962;
}
.crm-note__top b { font-weight: 700; }
.crm-note__top span { margin-left: auto; font-weight: 600; letter-spacing: 0; text-transform: none; }
.crm-note__name { margin: 0; font-size: 13.5px; font-weight: 700; color: #19171c; line-height: 1.25; }
.crm-note__text {
  margin: 0;
  font-size: 12.5px;
  color: #5d5962;
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------- the marks, once they are actors ---------- */
/*
 * The marks live in the STAGE, never inside a card and never inside the
 * window. Riding the cards is done in JS by adding the card's own transform to
 * the icon-slot offset above, which is the only way an icon can leave a
 * rotating card without inheriting the rotation on the way out. A mark
 * parented to a card would tumble as it flew.
 */
.crm-spot--pinned .crm-spot__marks {
  display: block;
  position: absolute;
  inset: 0;
  margin: 0;
  z-index: 3;
  pointer-events: none;
}
.crm-spot--pinned .crm-mark {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 44px;
  height: 44px;
  margin: -22px 0 0 -22px;
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 6px 16px -4px rgb(25 23 28 / .28);
  will-change: transform, opacity;
}
.crm-spot--pinned .crm-mark svg { width: 26px; height: 26px; }

/* ---------- the badge ---------- */
/*
 * The payoff. Four apps resolve into this, it travels to the app's own brand
 * slot, and it BECOMES the sidebar logo with the wordmark fading in beside it.
 * It is a static image element and is never redrawn, recoloured or re-cropped.
 *
 * 120px base box, parked at scale 32/120 = 0.2667 so it lands at the app's
 * real 32px brand mark. The park point is measured off the replica's own DOM,
 * never hardcoded, so it survives a resize.
 */
.crm-spot--pinned .crm-badge {
  display: block;
  position: absolute;
  left: 50%;
  top: 50%;
  width: 120px;
  height: 120px;
  margin: -60px 0 0 -60px;
  /* z-index 2, UNDER the marks at 3, not over them. At 4 the four marks
     dissolved BEHIND the badge, which reads as vanishing; underneath it they
     dissolve in front of it, which reads as the badge emerging out of them.
     Still above the notes at 1 and above the window, which is positioned with
     z-index:auto and therefore paints at level 0. */
  z-index: 2;
  opacity: 0;
  will-change: transform, opacity;
}

/* Band B only. Once parked the badge is a 32px logo in a white card and a
   drop shadow under it would give the app's own sidebar a floating icon. */
.crm-spot--pinned .crm-badge.is-flying { filter: drop-shadow(0 14px 34px rgb(76 29 149 / .32)); }

/* Pinned, the wordmark is JS-driven: it fades in as the badge settles beside
   it. Unpinned it is simply there. */
.crm-spot--pinned .crm-ui__brand-name { opacity: 0; }
.crm-spot--pinned .crm-ui__brand-mark img { display: none; }

/* The window is what Band B resolves. It scales from 0.94 to 1 and then NEVER
   transforms again for the rest of the section. */
.crm-spot--pinned .crm-ui { will-change: transform, opacity; opacity: 0; }

/* ---------------------------------------------------------------
   Between 900 and 1080 the copy column and a 980px window will not both fit,
   so the window takes what is left rather than the grid overflowing.
   --------------------------------------------------------------- */
@media (max-width: 1399px) {
  .crm-spot--pinned .crm-spot__grid {
    grid-template-columns: minmax(260px, 300px) minmax(0, 1fr);
    gap: 32px;
  }
}
@media (max-width: 1279px) {
  .crm-spot--pinned .crm-spot__grid {
    grid-template-columns: minmax(236px, 268px) minmax(0, 1fr);
    gap: 24px;
  }
  .crm-spot--pinned .crm-spot__beat p { font-size: 15px; }
}

/* ============================================================
   PHONE - the same sequence, in portrait
   ============================================================

   The pin used to bail out under 1024px, and the reason was sound: a 980px
   desktop window on a 390px screen is either a 40% scale nobody can read or a
   horizontal scrollbar. What was wrong was the conclusion. The answer was
   never a second, lesser animation with its own driver, its own bands and its
   own set of bugs - it was to stop rendering a DESKTOP window down there.

   `.crm-spot--phone` is added by js/crm-spotlight.js alongside `--pinned`
   under 1024px. It reshapes the SAME replica into a portrait phone at 1:1
   type size: the sidebar becomes a top bar (the brand slot stays, so the badge
   still parks on a real measured target), the conversations list takes the
   screen, and the thread pane rides over it the way a phone app pushes a
   detail view. Every band, every constant and every actor is unchanged.

   Nothing is scaled. The phone is 1:1 or it is not shipped.
   ============================================================ */

/* ---------- interleaving the copy with the animation ----------
   Under 1024 the copy column and the stage are not two columns any more, they
   are one flow, and the <ol> stacked all four steps ABOVE the animation: a
   phone reader finished Steps 01-04 and only then scrolled into the four cards
   arriving, so the animation illustrated copy they had left a screen ago.

   Both wrappers go display:contents so their children become items of one flex
   column and can be ordered against each other. Nothing is duplicated: there
   is still exactly one copy of each beat in the document, in reading order for
   the fallback, and the DOM order the pin needs above 1024 is untouched.

   Scoped to the UNPINNED section. It was written when the pin bailed out
   under 1024 and the section arrived on a phone as one stacked flow; the
   pin now runs at every width, so with motion on there is no flow to
   interleave - the phone replica IS the animation and the copy steps beside
   it. What is left is exactly the case this was written for and the only one
   still reachable: JS off, or reduced motion, where the class never lands,
   the section is a stacked flow, and the ordering problem is identical.

   Each step now sits with the moment it describes:
     Step 01  the clutter          above the four marks and the pile
     Step 02  one list instead     between the pile and the inbox
     Step 03  the history is there
     Step 04  you answer from here below the inbox that shows both
   -------------------------------------------------------------- */
@media (max-width: 1023.98px) {
  .crm-section:not(.crm-spot--pinned) .crm-spot__grid { display: flex; flex-direction: column; }
  .crm-section:not(.crm-spot--pinned) .crm-spot__copy,
  .crm-section:not(.crm-spot--pinned) .crm-spot__stage { display: contents; }
  .crm-section:not(.crm-spot--pinned) .crm-spot__beat:nth-child(1) { order: 1; }
  .crm-section:not(.crm-spot--pinned) .crm-spot__marks { order: 2; }
  .crm-section:not(.crm-spot--pinned) .crm-spot__notes { order: 3; }
  .crm-section:not(.crm-spot--pinned) .crm-spot__beat:nth-child(2) { order: 4; }
  .crm-section:not(.crm-spot--pinned) .crm-ui { order: 5; }
  .crm-section:not(.crm-spot--pinned) .crm-spot__beat:nth-child(3) { order: 6; }
  .crm-section:not(.crm-spot--pinned) .crm-spot__beat:nth-child(4) { order: 7; }
}

/* The .crm-spot--mobile rules that stood here are gone with the class.
   That mode - the stacked, unpinned reveal under 1024 - no longer exists:
   the pin runs at every width and the replica becomes a phone instead.
   The measurements banked in them (the -6px bleed against the 14px
   gutter, the 520px card cap) belonged to full-width flowed cards, which
   the phone does not have - its cards are stage-positioned against a
   250px device. Kept in git, not here. */

.crm-spot--phone {
  /* ONE width, and the phone's height, the stage and the notification cards
     are all derived from it. The svh term is what stops the device growing
     taller than the viewport it has to share with the copy: at 33svh the
     phone is 66svh and the beat block keeps the rest. Measured against 844
     (iPhone 15) and 667 (SE) - at a flat 330px the copy's fourth line was
     rendering underneath the phone on both.

     30svh, not 33, and 70vw, not 84. At 33/84 the device measured 278 x 557
     inside a 390 x 844 screen: 71% of the width and 66% of the height, with
     the beat block taking most of what was left. Nothing overlapped, but
     there was no air anywhere in the frame and the whole section read heavy.
     At 30/70 it is 253 x 506 on the same screen, which leaves ~175px of
     unclaimed height once the beat and the gaps are paid for. That space IS
     the fix - the beat has not lost a word, it has been given a margin.

     The inner UI is stepped down with the device below, because a shell that
     shrinks while its type does not is exactly what "blown up" looks like. */
  --crm-phone-w: min(300px, 70vw, 30svh);
}

/* Shorter runway. Four beats still, but the reader is scrolling a phone and
   420vh of thumb is a section people leave. */
.crm-spot--phone .crm-spot__track { height: 360vh; }

/* Two units, two different jobs, and using only one of them is the bug.
   `100svh` is the viewport with the mobile browser chrome EXPANDED, `100lvh`
   is it with the chrome collapsed. The chrome is expanded on the first frame
   and collapses the moment you scroll - so a sticky locked to svh keeps its
   smaller height while the real viewport grows to lvh, and a dead white band
   opens at the bottom. That is exactly the "only appears once you start
   scrolling" white space.

   So: the BOX is lvh, which is the tallest the viewport ever gets, and there
   is never a band under it. The usable CONTENT area is pulled back to svh by
   padding out the difference, so on the first frame - chrome still expanded -
   the phone's composer is not sitting under the address bar. As the chrome
   collapses the inset goes to zero on its own and the content reclaims the
   space. Nothing is measured in JS and nothing has to be re-measured. */
.crm-spot--phone .crm-spot__sticky {
  --chrome-inset: 0px;
  height: 100svh;
  box-sizing: border-box;
}

@supports (height: 100lvh) {
  .crm-spot--phone .crm-spot__sticky {
    --chrome-inset: calc(100lvh - 100svh);
    height: 100lvh;
    padding-bottom: var(--chrome-inset);
  }
}

.crm-spot--phone .crm-spot__grid {
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  gap: 18px;
  padding: 18px 16px 22px;
  align-content: center;
  justify-items: center;
}

/* One line of copy, not four. The beat block keeps stepping - the stepped copy
   is half the section - but it gets a phone's worth of room. */
.crm-spot--phone .crm-spot__copy {
  /* The beats are absolutely positioned inside this box, so it must reserve
     the full height of the larger mobile reading scale below. */
  min-height: 196px;
  width: 100%;
  max-width: 420px;
}

/* Shared mobile reading scale for all four CRM beats, both here and wherever
   this component is embedded. The grey explanation is 50% larger and the
   black heading is 10% larger than the previous phone treatment. */
.crm-spot--phone .crm-spot__beat h3 { font-size: 20px; line-height: 1.16; margin: 6px 0 7px; }
.crm-spot--phone .crm-spot__beat p { font-size: 19px; line-height: 1.45; }
.crm-spot--phone .crm-spot__beat em { font-size: 10px; letter-spacing: .16em; }
.crm-spot--phone .crm-spot__beat { visibility: hidden; transition: none; }
.crm-spot--phone .crm-spot__beat.is-active { visibility: visible; }

/* The stage is the phone's own box, so the cards pile against the device
   rather than against the viewport. */
.crm-spot--phone .crm-spot__stage {
  height: auto;
  width: var(--crm-phone-w);
  display: flex;
  align-items: center;
  justify-content: center;
}

/* The handset is a physical stage, not one of the actors. Keep its bezel and
   Dynamic Island solid while the inbox content resolves inside it. */
.crm-spot--phone .crm-ui__board{
  opacity:var(--crm-content-opacity,1);
  will-change:opacity;
}

/* A 120px badge consumed nearly half the phone width during the merge. The
   smaller mobile box still owns the handoff without hiding the interface. */
.crm-spot--phone .crm-badge{
  width:80px;
  height:80px;
  margin:-40px 0 0 -40px;
}

/* Short mobile landscape cannot pay for a 196px copy band above a 2:1 phone.
   Keep the requested reading scale and place the device beside the copy. */
@media (max-width:1023px) and (max-height:500px) and (orientation:landscape) {
  .crm-spot--phone { --crm-phone-w:min(170px,22vw,43svh); }
  .crm-spot--phone .crm-spot__grid {
    grid-template-columns:var(--crm-phone-w) minmax(0,1fr);
    grid-template-rows:minmax(0,1fr);
    gap:24px;
    padding:10px 76px 10px 18px;
    align-items:center;
  }
  .crm-spot--phone .crm-spot__copy {
    grid-column:2;
    grid-row:1;
    min-height:196px;
    max-width:560px;
  }
  .crm-spot--phone .crm-spot__stage {
    grid-column:1;
    grid-row:1;
  }
}

/* 268 x 66, down from 306 x 70. The margin is half of each, and both of these
   are READ BACK by measureGeometry() rather than duplicated in the JS. */
/* Opaque on the phone. At 94% the card below shows THROUGH the one on top of
   it, and on a 12px overlap that is a whole name line ghosting up through the
   card above - which reads as a rendering fault, not as a pile. */
.crm-spot--phone .crm-note {
  background: #fff;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* The cards follow the device. A 268px card on a 220px phone (an SE) hangs
   55px past the stage on each side once the jitter is on it, and that is a
   horizontal scrollbar. They are clipped at the sticky rather than at the
   stage, so the pile still spills past the phone's edges - which is the point
   of a pile - without ever reaching the document. */
.crm-spot--phone .crm-spot__sticky { overflow-x: clip; }

.crm-spot--phone .crm-note {
  width: min(268px, calc(var(--crm-phone-w) - 12px));
  height: 66px;
  margin: -33px 0 0 calc(min(268px, calc(var(--crm-phone-w) - 12px)) / -2);
  border-radius: 16px;
  /* Symmetrical 12px, not 12/10. measureGeometry() in js/crm-spotlight.js
     derives the icon-slot offset as -(cardWidth / 2) + 34, and 34 is
     12 + 44/2. At 10px of left padding the marks rode the cards 2px off
     centre for the whole opening band. */
  padding: 0 12px;
}
.crm-spot--phone .crm-note__name { font-size: 13.5px; }
.crm-spot--phone .crm-note__text { font-size: 12.5px; }
.crm-spot--phone .crm-note__top { font-size: 10px; }

/* The four brand glyphs sit over the phone, not beside it. */
.crm-spot--phone .crm-spot__marks { gap: 14px; }
.crm-spot--phone .crm-mark { width: 44px; height: 44px; }

/* ============================================================
   THE REPLICA'S OWN TYPE SCALE, ON THE PHONE
   ============================================================

   crm-ui.css builds the replica at desktop sizes and the phone rules there
   step down the handful of nodes that broke outright. Everything else was
   still drawn at 1:1 desktop inside a 253px device, which is why the shell
   read as a screenshot someone had zoomed: 13px names and 38px channel tiles
   are correct on a 980px window and enormous on a device a quarter that wide.

   So the whole interior is stepped down together here, in one place, at
   roughly 0.8 of the desktop figure. The rule is proportion: a real phone
   renders SMALLER type in a SMALLER frame, and any node left at its desktop
   size is the one that gives the trick away.

   These live in crm-spotlight.css rather than crm-ui.css on purpose. This
   file loads second, so equal-specificity `.crm-spot--phone` rules land on
   top of the ones there without a specificity arms race, and the replica
   keeps one owner for its desktop sizes.

   Nothing outside `.crm-spot--phone` is touched, so desktop is untouched by
   construction: the class is only ever added under 1024px.
   ------------------------------------------------------------ */

/* ---- the shell ---- */
.crm-spot--phone .crm-ui__chrome { height: 22px; padding: 0 12px; }
.crm-spot--phone .crm-ui__url { font-size: 9px; }
.crm-spot--phone .crm-ui__board {
  padding: 28px 6px 6px;
  background: linear-gradient(var(--crm-white) 0 28px, var(--crm-mist) 28px);
  gap: 6px;
}

/* ---- the header strip ---- */
.crm-spot--phone .crm-ui__side { padding: 6px 8px; gap: 7px; }
.crm-spot--phone .crm-ui__brand { padding: 2px 4px; gap: 8px; }
/* The badge's park point is MEASURED off this box every resize, so shrinking
   it moves the landing with it rather than desynchronising the payoff. */
.crm-spot--phone .crm-ui__brand-mark { width: 26px; height: 26px; border-radius: 7px; }
.crm-spot--phone .crm-ui__brand-mark img { width: 26px; height: 26px; }
.crm-spot--phone .crm-ui__brand-name { font-size: 12.5px; }

/* ---- the conversations list ---- */
.crm-spot--phone .crm-ui__convos { border-radius: 15px; }
.crm-spot--phone .crm-ui__convos-head { padding: 8px 10px; min-height: 0; gap: 8px; }
.crm-spot--phone .crm-ui__convos-head h3 { font-size: 12px; }
.crm-spot--phone .crm-ui__convos-count { font-size: 9.5px; }
.crm-spot--phone .crm-ui__channel-bar { padding: 6px 10px; gap: 5px; }
.crm-spot--phone .crm-ui__tile { width: 30px; height: 30px; border-radius: 8px; }
.crm-spot--phone .crm-ui__tile svg { width: 17px; height: 17px; }
.crm-spot--phone .crm-ui__tile-badge { min-width: 14px; height: 14px; padding: 0 4px; font-size: 8.5px; top: -2px; right: -2px; }

.crm-spot--phone .crm-ui__rows { padding: 5px; }
.crm-spot--phone .crm-ui__row { padding: 8px 9px; gap: 9px; border-radius: 9px; }
.crm-spot--phone .crm-ui__row-mark { width: 28px; height: 28px; border-radius: 8px; }
.crm-spot--phone .crm-ui__row-mark svg { width: 16px; height: 16px; }
.crm-spot--phone .crm-ui__row-name { font-size: 11.5px; }
.crm-spot--phone .crm-ui__row-time { font-size: 9.5px; }
.crm-spot--phone .crm-ui__row-summary { font-size: 10.5px; line-height: 1.35; }
.crm-spot--phone .crm-ui__row-channel { font-size: 9.5px; }
.crm-spot--phone .crm-ui__pip { font-size: 8.5px; padding: 1px 5px; }

/* ---- the thread ---- */
.crm-spot--phone .crm-ui__conv { border-radius: 15px; }
.crm-spot--phone .crm-ui__pane-head { padding: 7px 9px; gap: 7px; }
.crm-spot--phone .crm-ui__back { width: 20px; height: 20px; }
.crm-spot--phone .crm-ui__pane-avatar { width: 26px; height: 26px; font-size: 9.5px; }
/* Scaled off the device rather than fixed, for the reason set out over the
   composer below: --crm-phone-w carries an svh term, so a short phone gets a
   narrow device however wide its screen is, and a fixed 11.5px name plus a
   fixed 9.5px stay line wrapped the head onto three lines at 320 x 720. */
.crm-spot--phone .crm-ui__pane-name { font-size: clamp(10px, calc(var(--crm-phone-w) * .0455), 11.5px); }
.crm-spot--phone .crm-ui__pane-stay { font-size: clamp(8.2px, calc(var(--crm-phone-w) * .0375), 9.5px); }
.crm-spot--phone .crm-ui__messages { padding: 8px 10px; gap: 9px; }
.crm-spot--phone .crm-ui__bubble-row { gap: 3px; max-width: 84%; }
.crm-spot--phone .crm-ui__bubble { font-size: 11px; padding: 7px 9px; border-radius: 9px; }
.crm-spot--phone .crm-ui__bubble-meta { font-size: 8.5px; gap: 4px; }

/* ---- the composer, and the one collision this pass exists to kill ----
   At 11px "Sending on WhatsApp" plus a 13px Change plus a 13px Send needed
   more than the 239px of interior the device has, so the label wrapped onto
   two lines and pushed itself under the Change control it is supposed to sit
   beside. The label drops to 9px, Change with it, and the Send button loses
   6px of horizontal padding: measured, the row is ~150px wide inside the
   ~186px the narrowest phone this ships to (320) leaves it.

   flex:none on the label, NOT min-width:0. Shrinking was tried first and is
   worse: a nowrap label in a shrinking box does not get smaller, it overflows
   its own right edge, and at 360 that put "Change" 15px into the Send button
   with nothing in the box model to say so. Unshrinkable, the row either fits -
   which at these sizes it does at every width from 320 up - or it overflows
   the composer, which the QA overflow assertion catches. A layout that fails
   loudly beats one that silently draws two controls on top of each other.

   And the sizes are calc'd off --crm-phone-w rather than fixed, because the
   device width is NOT a function of the screen width alone: --crm-phone-w
   carries a 30svh term, so a 320 x 720 screen gets a 216px device and a
   390 x 844 screen gets a 253px one. Fixed 9px/10px fitted 253 and overflowed
   216 - Send was sliced in half by the thread card's own overflow:hidden,
   which is exactly the silent failure the flex:none above is meant to avoid.
   One number drives the device, its interior and this row, so they cannot
   disagree at a size nobody measured. The clamps are the floor: below them
   the label stops being type and starts being texture. */
.crm-spot--phone .crm-ui__composer { padding: 7px calc(var(--crm-phone-w) * .032) 9px; }
.crm-spot--phone .crm-ui__composer-field { padding: 7px 9px; min-height: 44px; border-radius: 9px; }
.crm-spot--phone .crm-ui__composer-text { font-size: 11px; min-height: 16px; }
.crm-spot--phone .crm-ui__caret { height: 11px; }
.crm-spot--phone .crm-ui__composer-foot { margin-top: 6px; padding-top: 0; gap: 6px; }
.crm-spot--phone .crm-ui__send-via {
  font-size: clamp(7.6px, calc(var(--crm-phone-w) * .0356), 9px);
  gap: 5px;
  flex: none;
  white-space: nowrap;
}
.crm-spot--phone .crm-ui__send-via .crm-ui__dot { width: 5px; height: 5px; }
.crm-spot--phone .crm-ui__change { flex: none; }
.crm-spot--phone .crm-ui__send {
  font-size: clamp(8.4px, calc(var(--crm-phone-w) * .0395), 10px);
  padding: 5px calc(var(--crm-phone-w) * .04);
  border-radius: 9px;
  flex: none;
}

@media (max-width: 768px) {
  .crm-spot__grid { padding: 0 14px; gap: 26px; }
  .crm-spot__marks { gap: 18px; margin-bottom: 18px; }
  .crm-mark { width: 46px; height: 46px; }
}


@media (prefers-reduced-motion: reduce) {
  .crm-spot__beat { transition: none; }
  .crm-ui__caret { animation: none; opacity: 0; }
}

/* ============================================================
   THE DESKTOP PHONE
   ============================================================

   The "and it is on your phone too" beat. A second, nearer device in front of
   the window - not a second animation. It reads the same `eased` as
   everything else, so it cannot arrive on its own schedule and cannot drift
   away from the window it is standing in front of.

   Depth is the whole trick. Across its band the window moves ZERO pixels: it
   resolved at 0.58 and holds still for the rest of the section. The phone
   moves 120, tilts, scales and deepens its shadow over the same frames. That
   difference in rate at a nearer plane is what makes it read as an object in
   the room rather than a rectangle pasted onto a screenshot.
   ============================================================ */

.crm-phone {
  position: absolute;
  right: 40px;
  bottom: -28px;
  z-index: 4;
  width: 232px;
  height: auto;
  aspect-ratio: 809 / 1667;
  /* Driven by js/crm-spotlight.js across the arrival band. */
  --crm-phone-shade: .26;
  --crm-screen-radius: 30px;
  pointer-events: none;
  /* The tilt has to rotate about the bottom edge or the phone pivots around
     its own middle and reads as a swinging sign rather than a device being
     stood up. */
  transform-origin: 50% 92%;
  will-change: transform, opacity;
}

/* Under 1024px the whole replica is already a phone, so a second one is just
   a duplicate claim. Removed from the layout, not faded. */
.crm-spot--phone .crm-phone { display: none; }

.crm-phone__shell {
  height: 100%;
  display: flex;
  flex-direction: column;
  position: relative;
  box-sizing: border-box;
  padding: 3.55% 4.35% 3.75%;
  border: 0;
  border-radius: 0;
  background: transparent url("../public/brand/devices/iphone-frame.webp") center / 100% 100% no-repeat;
  overflow: visible;
  box-shadow: none;
  filter:
    drop-shadow(0 24px 22px rgb(25 23 28 / var(--crm-phone-shade)))
    drop-shadow(0 2px 4px rgb(25 23 28 / .10));
}

/* The supplied iPhone is the real outer device. Its wallpaper sits behind
   the live CRM content, while this top layer preserves the Dynamic Island in
   front of the status bar exactly where it belongs. */
.crm-phone__shell::after {
  content: "";
  position: absolute;
  z-index: 20;
  top: 3.45%;
  left: 35.65%;
  width: 28.7%;
  height: 4.08%;
  border-radius: 999px;
  background: #000;
  pointer-events: none;
}

/* The live screen sits over an illustrated wallpaper in the supplied PNG.
   Black beneath the rounded surface keeps that wallpaper from peeking out at
   either end and reads as the real inner bezel. */
.crm-phone__shell::before {
  content: "";
  position: absolute;
  z-index: 0;
  /* Match the width-relative shell padding on this fixed-aspect device. The
     tighter screen radius fills the live UI's corner cutouts without covering
     the supplied metal frame. */
  inset: 1.72% 4.35% 1.82%;
  border-radius: var(--crm-screen-radius);
  background: #000;
  pointer-events: none;
}

.crm-phone__shell > * {
  position: relative;
  z-index: 1;
}

.crm-phone__status {
  position: absolute;
  z-index: 3;
  top: 3.55%;
  left: 4.35%;
  right: 4.35%;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 7px 14px 3px;
  font-size: 10px;
  font-weight: 700;
  color: #17151b;
  background: transparent;
  border-radius: 0;
}
.crm-phone__status-right { display: flex; gap: 3px; align-items: center; }
.crm-phone__status-right i { width: 4px; height: 4px; border-radius: 50%; background: rgb(25 23 28 / .34); }

.crm-phone__head {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  /* The status row is overlaid above this padding. Making the header the
     first full-height surface lets the top radius curve through the screen
     instead of being squeezed into a 20px strip. */
  padding: 26px 12px 8px;
  border-bottom: 1px solid rgb(25 23 28 / .08);
  background: #fff;
  border-radius: var(--crm-screen-radius) var(--crm-screen-radius) 0 0;
}
.crm-phone__avatar {
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: rgb(124 58 237 / .10);
  color: #7c3aed;
  font-size: 10px;
  font-weight: 800;
}
.crm-phone__identity { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.crm-phone__name { font-size: 12px; font-weight: 700; color: #17151b; }
.crm-phone__channel { display: flex; align-items: center; gap: 4px; font-size: 10px; color: #6b6672; }
.crm-phone__channel i { width: 6px; height: 6px; border-radius: 50%; background: #25d366; }

.crm-phone__messages {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 6px;
  padding: 10px;
  background: #f7f6f8;
}
.crm-phone__bubble {
  margin: 0;
  max-width: 84%;
  padding: 7px 10px;
  border-radius: 14px;
  font-size: 11px;
  line-height: 1.38;
  opacity: 0;
  will-change: transform, opacity;
}
.crm-phone__bubble[data-direction="outbound"] {
  align-self: flex-end;
  background: #7c3aed;
  color: #fff;
  border-bottom-right-radius: 5px;
}
.crm-phone__bubble[data-direction="inbound"] {
  align-self: flex-start;
  background: #fff;
  color: #17151b;
  border: 1px solid rgb(25 23 28 / .08);
  border-bottom-left-radius: 5px;
}

.crm-phone__composer {
  flex: none;
  display: flex;
  align-items: flex-end;
  gap: 6px;
  padding: 8px 10px 12px;
  border-top: 1px solid rgb(25 23 28 / .08);
  background: #fff;
  border-radius: 0 0 var(--crm-screen-radius) var(--crm-screen-radius);
  overflow: hidden;
}
.crm-phone__field {
  flex: 1;
  min-height: 30px;
  padding: 7px 10px;
  border-radius: 15px;
  background: #f3f3f5;
  font-size: 11px;
  line-height: 1.35;
  color: #17151b;
}
.crm-phone__send {
  flex: none;
  padding: 7px 11px;
  border-radius: 15px;
  font-size: 11px;
  font-weight: 700;
  background: rgb(124 58 237 / .12);
  color: rgb(124 58 237 / .5);
}
.crm-phone__send.is-lit { background: #7c3aed; color: #fff; }

/* Only the pin hides it. Without JS, and with reduced motion, the phone is
   simply there and finished - same rule as every other actor in the section. */
.crm-spot--pinned .crm-phone { opacity: 0; }


/* ---------------------------------------------------------------
   WHERE the phone parks, and why it is not where you would guess.

   Two positions were built and measured before this one.

   1. Bottom-right, over the window's corner. It covered the thread pane and
      the Send control - the exact thing Step 04's copy points at ("You answer
      from here"). A second device that hides the payoff it echoes is worse
      than no second device.

   2. Its own reserved lane, with the window sized narrower to leave room.
      Measured at 1440: the conversation pane fell to 282px, under the 300px
      floor this section was already fixed for once. The rail and the
      conversations panel are ALREADY yielding everything they have at 1440
      (210px and 156px against their 224/304 nominals), so there is no
      horizontal room on this layout to reserve. Not a tuning problem - the
      width is simply not there.

   So the phone overlaps, and it overlaps the one region that is inert at the
   end of the section: the lower half of the sidebar, which by 0.62 is empty
   space and an account chip. It hangs past the window's bottom edge, which is
   what makes it read as a device standing in FRONT of the screen rather than
   a panel inside it.

   The window still never moves or resizes after 0.58. Nothing about the
   window changed to make room; the phone went somewhere the window was not
   using.
   --------------------------------------------------------------- */
.crm-phone { left: 8px; right: auto; bottom: -34px; }

/* Under 1360 the sidebar itself is down to ~184px and the phone would cross
   into the conversations list, so it goes rather than degrading the replica
   it is supposed to be advertising. */
@media (max-width: 1359px) {
  .crm-phone { display: none; }
}

/* The section showed the product and then dead-ended. This is its way out. */
.crm-section__action{
  position:relative;
  z-index:2;
  width:min(100%,var(--max));
  margin:clamp(40px,4vw,64px) auto 0;
  display:flex;
  justify-content:center;
}

/* ============================================================
   The /crm clone
   Same section, same driver, same markup. Two differences, both because of
   where it sits on that page rather than because it is a different section.
   ============================================================ */

/* On the homepage this section is a chapter in a run of chapters and takes its
   ground from js/site.js's chapter theming. On /crm there is no such run, so
   it paints its own, and it paints the same colour the chapter theme would
   have given it. */
.crm-section--page { background: var(--mist); }

/* The arrival reveal. Opt-in via data-crm-reveal (see the block at the end of
   js/crm-spotlight.js).

   On the homepage this section is a chapter in a run of chapters and can be
   fully present on its first frame. On /crm it is the payoff to the black
   wipe, and copy already sitting there when the wipe lifts throws the arrival
   away. So the first beat and the CTA are held back until the section is
   actually on screen.

   This does NOT introduce a second opinion about beat opacity. The pinned
   rules above own that, and all this does is hold the ACTIVE beat back - one
   extra class deep, so it outranks .crm-spot--pinned .crm-spot__beat.is-active
   - and then let go. The fade you see on release is that same rule's own .45s
   transition. One owner, one transition, no cross-fade between two systems.

   The initial state comes from .crm-spot--reveal, which only the script adds,
   so with no JS nothing is ever hidden behind a class that never comes off. */
.crm-spot--reveal:not(.is-revealed) .crm-spot__beat.is-active { opacity: 0; }

.crm-spot--reveal .crm-section__action {
  opacity: 0;
  transform: translate3d(0, 26px, 0);
}
.crm-spot--reveal.is-revealed .crm-section__action {
  opacity: 1;
  transform: none;
  /* Behind the copy, not with it: the CTA is the last thing the section says
     and it should not arrive before the first thing it says. */
  transition: opacity .62s var(--ease-out) .22s, transform .62s var(--ease-out) .22s;
}

@media (prefers-reduced-motion: reduce) {
  .crm-spot--reveal:not(.is-revealed) .crm-spot__beat.is-active { opacity: 1; }
  .crm-spot--reveal .crm-section__action { opacity: 1; transform: none; }
}

/* ---- Desktop canvas ceiling ------------------------------------------------
   The hero is positioned with clamps that all top out at roughly a 1630px
   viewport: left:clamp(56px,7.6vw,124px) plus the two font clamps. Past that
   the hero stopped responding while every content section kept centring in the
   1344px column, so the page lost its spine as the screen grew: measured on the
   homepage, the hero h1 sat 484px left of the sections below it at 2560, and
   924px left of them at 3440. The stages that size themselves off the viewport
   (%-based hero card, the journey gallery) kept growing at the same time.

   Cap the stages at one canvas and centre them. Nothing moves at or below
   1680px, which covers every laptop and most desktops. hero-film.js measures
   stage.clientWidth and crm-spotlight.js measures sticky.offsetWidth, so both
   recompute against the capped box rather than the window. */
.crm-spot__track{
  width:min(100%,var(--canvas));
  margin-inline:auto;
}
