/* ============================================================
   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 <img> 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 {
  /* 132, not 92. The beats are absolutely positioned inside this box, so a
     box shorter than the tallest beat does not grow - it lets the last line
     render on top of whatever is underneath, which here is the phone. 148 is
     the measured height of the four-line beat at the type size below. */
  min-height: 148px;
  width: 100%;
  max-width: 420px;
}

/* One step down from 21/14/11. A 21px heading over a 14px paragraph is a
   desktop beat rendered on a phone: at 390 the first beat's heading ran two
   lines and its paragraph ran four, and between them they took 135px before
   the device had drawn a pixel. 18/12.75/10 says the same words in ~115px and
   reads as a caption on a step rather than a section header, which is what a
   numbered step IS. The copy is untouched - this is a type scale, not a cut. */
.crm-spot--phone .crm-spot__beat h3 { font-size: 18px; line-height: 1.16; margin: 5px 0 5px; }
.crm-spot--phone .crm-spot__beat p { font-size: 12.75px; line-height: 1.52; }
.crm-spot--phone .crm-spot__beat em { font-size: 10px; letter-spacing: .16em; }

/* 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;
}

/* 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: 6px; 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: 464px;
  /* Driven by js/crm-spotlight.js across the arrival band. */
  --crm-phone-shade: .26;
  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;
  border-radius: 30px;
  border: 6px solid #17151b;
  background: #fff;
  overflow: hidden;
  box-shadow:
    0 34px 60px -20px rgb(25 23 28 / var(--crm-phone-shade)),
    0 2px 8px rgb(25 23 28 / .10);
}

.crm-phone__status {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 7px 14px 3px;
  font-size: 10px;
  font-weight: 700;
  color: #17151b;
}
.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;
  padding: 6px 12px 8px;
  border-bottom: 1px solid rgb(25 23 28 / .08);
}
.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;
}
.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; }
}
