/* ==========================================================================
   PromoChat â€” chat surface
   Loaded last, after theme.css, and every rule is scoped under .chat-shell.

   Why a sheet of its own: the chat rules were spread across app.css (structure),
   modules.css (bubbles and actions), responsive.css (the phone layout) and
   theme.css (colour), and theme.css resets several of the same surfaces at the
   end of the cascade. Anything written to change how the thread *behaves* had to
   either outrank four sheets from inside one of them, or land after all of them.
   This is the "after all of them" file. It owns motion, scroll and viewport;
   colour still belongs to theme.css and structure still belongs to app.css.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The height the thread is actually allowed to use

   --chat-h is written by chat-ux.js off visualViewport, in px. It matters on a
   phone: when the on-screen keyboard opens, iOS shrinks the visual viewport but
   leaves 100vh â€” and even 100dvh â€” reporting the full screen, so a thread sized
   that way puts its composer underneath the keyboard. The fallback keeps the
   first paint correct on every engine and covers the script being blocked.
   -------------------------------------------------------------------------- */
:root{ --chat-h:100dvh }

/* --------------------------------------------------------------------------
   2. Scrolling stays inside the thread

   Without overscroll-behavior the rubber-band at the top of the message list is
   handed to the page, so flicking up past the oldest message drags the whole
   app down and the header detaches. `contain` keeps the bounce in the list.
   -------------------------------------------------------------------------- */
.chat-shell .messages{
  overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;
  /* No scroll-behavior:smooth here. As a CSS default it also animates the jump
     the thread makes when it opens, so the list visibly crawls to the bottom
     instead of starting there. chat-ux.js asks for smooth per call instead. */
  scroll-behavior:auto
}
.chat-shell>aside{overscroll-behavior:contain}

/* --------------------------------------------------------------------------
   3. The clock inside a text bubble

   The stamp was a floated <small> placed after the text. A float is put on the
   line box it appears in only if there is room left on it, and the text before
   it had already taken that room â€” so any message whose last line ran near the
   bubble's width dropped its time onto a line of its own, with a band of empty
   bubble above it. Reserving width at the end of the text does not help a
   float: the reservation is what the float would have used.

   So the stamp comes out of flow and is pinned to the bubble's bottom-right,
   and the text carries a trailing spacer the width of the stamp. When the
   spacer fits on the last line, the time sits beside the words. When it does
   not, the spacer wraps and the time occupies exactly that new line rather than
   opening a gap. This is how every messenger does it, and it is the only
   arrangement that behaves for both short and full-width messages.

   Sent bubbles reserve more: they carry the delivery ticks as well.
   -------------------------------------------------------------------------- */
.chat-shell .bubble.type-text:not(.is-deleted)>small{
  float:none;position:absolute;right:11px;bottom:6px;top:auto;margin:0;
  line-height:1.2;pointer-events:none
}
.chat-shell .bubble.type-text:not(.is-deleted)>p::after{
  content:'';display:inline-block;width:48px;height:1px;vertical-align:bottom
}
.chat-shell .bubble.sent.type-text:not(.is-deleted)>p::after{width:64px}
/* An edited message says so before the clock, so it needs the room for that. */
.chat-shell .bubble.type-text:not(.is-deleted):has(.edited-tag)>p::after{width:100px}

/* Emoji-only bubbles have no bubble to pin a stamp inside â€” theirs stays in
   flow beside the glyph, the way it was. */
.chat-shell .bubble.is-emoji>p::after{display:none}
.chat-shell .bubble.is-emoji.type-text>small{position:static;float:right;margin:0 0 0 10px}

/* --------------------------------------------------------------------------
   4. Phone: the thread is sized by the visual viewport

   responsive.css sizes this pane at 100dvh. That is right until the keyboard is
   up, which is exactly when the composer has to stay reachable. Restated here
   at the same specificity so it wins by order, and only inside the breakpoint
   responsive.css owns, so the desktop split is untouched.
   -------------------------------------------------------------------------- */
@media(max-width:760px){
  .chat-shell>section{height:var(--chat-h)}
}

/* --------------------------------------------------------------------------
   4b. Phone: the thread arrives as a pane, not as a cut

   responsive.css swaps the two screens with display:none / display:grid, so
   opening a conversation replaced the list in a single frame â€” the reader is
   given no sense of having gone somewhere, and coming back is just as abrupt.

   The pane is laid out at all times now and parked off to the right, so both
   directions can move. Opening is a page load (each row is a real link), which
   gives no frame to transition from, so that direction is a keyframe played on
   arrival. Closing is the .chat-open class coming off, which a transition can
   animate on its own â€” and it does, without app.js's back button needing to
   know anything about it.

   visibility, not display, keeps the parked pane out of the tab order and away
   from a screen reader; it is switched with a zero-length delay so it holds
   through the closing animation and only then goes.
   -------------------------------------------------------------------------- */
@media(max-width:760px){
  /* A fixed pane sitting one screen to the right can still lengthen the page's
     scrollable width on some engines. */
  body:has(.chat-shell){overflow-x:hidden}

  .chat-shell>section{
    display:grid;
    transform:translateX(100%);
    visibility:hidden;
    transition:transform .26s cubic-bezier(.22,1,.36,1),visibility 0s .26s
  }
  body.chat-open .chat-shell>section{
    transform:none;
    visibility:visible;
    transition:transform .26s cubic-bezier(.22,1,.36,1),visibility 0s;
    animation:chat-pane-in .26s cubic-bezier(.22,1,.36,1)
  }
  @keyframes chat-pane-in{from{transform:translateX(100%)}}
}

@media(max-width:760px) and (prefers-reduced-motion:reduce){
  .chat-shell>section{transition:none}
  body.chat-open .chat-shell>section{animation:none;transition:none}
}

/* --------------------------------------------------------------------------
   5. Only genuinely new bubbles animate

   .is-arriving is put on a bubble by the poll when it appends one. The rule is
   here rather than in modules.css so it can state the entrance in one place,
   and so it is skipped outright for anyone who asked for less motion.
   -------------------------------------------------------------------------- */
.chat-shell .bubble.is-arriving{
  animation:chat-msg-in .22s cubic-bezier(.22,1,.36,1) backwards
}
@keyframes chat-msg-in{from{opacity:0;transform:translateY(8px) scale(.985)}}

/* --------------------------------------------------------------------------
   6. The composer field

   It is a <textarea> now, so none of the four sheets that styled
   `.message-box input` reach it any more â€” including theme.css, which is where
   its colours came from. All of it is restated here, in one place, rather than
   adding a textarea to four selectors across four files.

   The row aligns to the bottom: as the field grows, the send button stays level
   with the last line instead of drifting to the middle of a tall field.
   -------------------------------------------------------------------------- */
.chat-shell .message-box{align-items:flex-end}
.chat-shell .message-box textarea[name=body]{
  flex:1;min-width:0;
  /* One row to start, then it grows with the text up to a cap â€” past that the
     field scrolls, so the thread is never pushed off the screen by the box you
     are typing into. */
  max-height:132px;resize:none;overflow-y:auto;
  border:1px solid transparent;border-radius:20px;
  padding:10px 14px;
  font:inherit;font-size:14px;line-height:1.45;
  background:var(--raised,#f5f4f9);color:var(--ink,inherit);
  outline:0;
  scrollbar-width:thin
}
.chat-shell .message-box textarea[name=body]:focus{border-color:#aa91fa}
.chat-shell .message-box textarea[name=body]::placeholder{color:var(--muted)}

@media(max-width:760px){
  /* iOS zooms the page whenever a focused field is under 16px, and the zoom is
     never undone â€” the whole app is left slightly too large for the screen. */
  .chat-shell .message-box textarea[name=body]{
    font-size:var(--fs-input);min-height:var(--tap);padding:11px 14px;
    /* A lower cap than the desktop one: on a 390px screen 132px of field is
       most of the thread, and what you are replying to matters more than
       seeing the whole of what you are typing. */
    max-height:96px
  }
}

/* --------------------------------------------------------------------------
   7. The message you just sent, before the server has confirmed it

   app.js already posts in the background, but the bubble only appears once the
   round trip finishes â€” so on a slow connection the message vanishes from the
   composer and nothing takes its place for a second or two. The placeholder
   chat-ux.js drops in is a real sent bubble, held back a shade with a single
   hollow tick, and it is dropped the moment the confirmed one arrives.

   Understated on purpose: a placeholder that looks obviously fake is worse than
   a slow one, because it makes you doubt the message went.
   -------------------------------------------------------------------------- */
.chat-shell .bubble.is-pending{opacity:.72}
.chat-shell .bubble.is-pending .ticks{opacity:.5}
.chat-shell .bubble.is-failed{opacity:1}
.chat-shell .bubble.is-failed .ticks{color:#e5484d;opacity:1}

/* --------------------------------------------------------------------------
   7b. Swipe-to-reply

   The arrow is drawn behind the bubble's left edge and lights up once the drag
   has gone far enough to count, so the gesture tells you what it will do before
   you let go â€” a drag that only moves the bubble is a guess.

   The bubble returns under a transition; chat-ux.js turns that off for the
   duration of the drag so the bubble tracks the finger exactly, and hands it
   back on release.
   -------------------------------------------------------------------------- */
.chat-shell .messages .bubble{transition:transform .22s cubic-bezier(.22,1,.36,1)}
.chat-shell .messages .bubble.is-swiping::before{
  /* U+FE0E forces the text presentation. Without it the arrow is served as an
     emoji â€” a coloured tile that reads as a broken glyph next to the bubble. */
  content:'\21A9\FE0E';
  position:absolute;left:-30px;top:50%;transform:translateY(-50%);
  width:auto;height:auto;background:none;clip-path:none;
  font-size:16px;line-height:1;color:var(--muted);
  transition:color .14s ease,scale .14s ease
}
.chat-shell .messages .bubble.swipe-armed::before{color:var(--purple);scale:1.25}

/* .bubble:not(.is-grouped):before already draws the tail, and the sent bubble's
   sits on the right â€” the swipe arrow has to replace it for the length of the
   drag rather than fight it for the same pseudo-element. */
.chat-shell .messages .sent.is-swiping::before{left:auto;right:calc(100% + 12px)}

@media(prefers-reduced-motion:reduce){
  .chat-shell .messages .bubble{transition:none}
}

/* --------------------------------------------------------------------------
   8. The install prompt does not sit on the composer

   .install-bar is fixed at bottom:78px â€” clear of the bottom nav, which is the
   right place on every screen except this one. Opening a thread hides that nav
   and puts a full-screen pane at z-index 130; the bar is at 150, so it lands
   squarely on top of the message field. Typing a long message put the start of
   it behind the prompt. On the desktop split it covers the send button.

   Judgement call: it is hidden on Messages rather than moved. There is nowhere
   on this screen it can sit without covering either the thread or the composer,
   and it still appears on every other screen, so the offer is not lost.
   -------------------------------------------------------------------------- */
body:has(.chat-shell) .install-bar{display:none}

/* ==========================================================================
   9. Rhythm

   Everything below is about how the thread reads rather than how it behaves.
   ========================================================================== */

/* --------------------------------------------------------------------------
   9a. Bubbles: runs, not a stack of separate cards

   The thread put 8px between every pair of messages and pulled 6px back for a
   grouped one, so a run of three read as three objects with a slightly tighter
   join. A conversation is not a list â€” a run from one person is one block, and
   the space that matters is the space between blocks.

   So: 3px inside a run, 12px between runs, and the corner does the work the
   gap used to. The bubble is fully rounded except for the corner nearest its
   author, which is flattened on the first message of a run only. That is the
   same signal the tail gave, without a shape that has to be redrawn for every
   radius, theme and grouped state.

   The clip-path tails come off. They were pinned to a 10px radius â€” at 18px
   they sit in open space beside the bubble rather than on its edge.
   -------------------------------------------------------------------------- */
.chat-shell .messages{gap:3px}
.chat-shell .messages .bubble:not(.is-grouped){margin-top:9px}
/* The first bubble in the thread has nothing above it to be spaced from. */
.chat-shell .messages .bubble:first-child{margin-top:0}

/* :not(.is-emoji) throughout â€” an emoji-only message is not a bubble. app.css
   strips its background and padding down to nothing so the glyph stands on the
   thread by itself, and a rule written for bubbles will quietly put all of that
   back: in dark mode the leftover shadow traced a raised card around a single
   emoji. */
.chat-shell .messages .bubble:not(.is-emoji){
  max-width:min(68%,560px);
  padding:9px 13px 7px;
  border-radius:18px
}
/* The dark shadow is the same story, and it outranks app.css's box-shadow:none
   because it is written with the theme attribute in front of it. */
.chat-shell .messages .bubble.is-emoji{box-shadow:none}
/* responsive.css widens the bubble to 82% on a phone, and it has to keep
   winning: 68% of 390px leaves a column too narrow to read a sentence in. */
@media(max-width:760px){
  .chat-shell .messages .bubble:not(.is-emoji){max-width:84%}
}
.chat-shell .messages .bubble.is-grouped:not(.is-emoji){margin-top:0;border-radius:18px}
.chat-shell .messages .received:not(.is-grouped):not(.is-emoji){border-top-left-radius:6px}
.chat-shell .messages .sent:not(.is-grouped):not(.is-emoji){border-top-right-radius:6px}

/* The tail, in both its light and dark declarations. */
.chat-shell .messages .bubble:not(.is-grouped):before{content:none}

/* --------------------------------------------------------------------------
   9b. Day and unread separators

   The day pill carried a drop shadow, which lifted it off the thread as though
   it were a control. It is a label: it should sit in the surface, not on it.
   The unread rule is the one marker in the thread worth finding while scrolling
   back, so it gets the room to be found.
   -------------------------------------------------------------------------- */
.chat-shell .messages .day{
  background:var(--card);border:1px solid var(--line);box-shadow:none;
  color:var(--muted);margin:16px 0 6px;padding:5px 14px
}
.chat-shell .messages .unread-divider{margin:18px 2px 8px}

/* --------------------------------------------------------------------------
   9c. Thread list

   Three small things, all of them about telling rows apart at a glance:

   An initials avatar was one lilac gradient for everybody, so a list of six
   people was six identical circles and the name was doing all the work.
   chat-ux.js derives a hue from the name â€” the same name always lands on the
   same colour, on every device and every visit, because it is a hash and not a
   counter. Saturation and lightness are fixed, so no name can produce a circle
   that white initials fall off.

   The unread count arrives rather than appearing, and the online dot gets a
   halo instead of being a 11px flat disc against a photo.
   -------------------------------------------------------------------------- */
.chat-shell .avatar-fallback.tinted{
  background:linear-gradient(135deg,hsl(var(--hue) 62% 60%),hsl(calc(var(--hue) + 40) 68% 66%));
  color:#fff
}

.chat-shell .chat-person .unread{animation:chat-pop .3s cubic-bezier(.22,1,.36,1)}
@keyframes chat-pop{from{transform:scale(0)}}

.chat-shell .dot.online{box-shadow:0 0 0 3px rgba(25,185,130,.16)}

/* --------------------------------------------------------------------------
   9d. Voice notes as a waveform

   app.js already replaces the browser's own audio slab with a play button, a
   scrub track and a countdown. The track was a 3px rule with a fill, which
   reads as a progress bar â€” correct, but it says nothing about the recording.
   chat-ux.js fills it with bars whose heights come from the message id, so a
   note looks like itself every time it is drawn, and the played ones colour in.

   The rule and the fill are hidden rather than removed: with the script blocked
   the original track is still there and still scrubs.
   -------------------------------------------------------------------------- */
.chat-shell .voice-track.has-bars{
  display:flex;align-items:center;gap:2px;height:24px
}
.chat-shell .voice-track.has-bars:before,
.chat-shell .voice-track.has-bars .voice-fill{display:none}
.chat-shell .voice-track.has-bars i{
  flex:1 1 0;min-width:2px;min-height:3px;border-radius:2px;
  background:rgba(90,70,160,.26);transition:background .12s ease
}
.chat-shell .voice-track.has-bars i.played{background:var(--purple)}
/* The unplayed bars are a tint of ink, which disappears on the dark note
   surface â€” there they have to be a tint of light instead. */
html[data-theme="dark"] .chat-shell .voice-track.has-bars i{background:rgba(255,255,255,.26)}
@media(prefers-color-scheme:dark){
  html:not([data-theme="light"]) .chat-shell .voice-track.has-bars i{background:rgba(255,255,255,.26)}
}

/* --------------------------------------------------------------------------
   9e. Typing

   One dot pulsing in and out is a status light. Three that travel are the
   gesture everybody reads as "someone is writing" without looking at the words.
   -------------------------------------------------------------------------- */
.chat-shell .typing-line i{
  width:6px;height:6px;animation:chat-typing 1.25s ease-in-out infinite
}
.chat-shell .typing-line i:nth-of-type(2){animation-delay:.16s}
.chat-shell .typing-line i:nth-of-type(3){animation-delay:.32s}
.chat-shell .typing-line i+i{margin-left:-4px}
@keyframes chat-typing{
  0%,60%,100%{transform:translateY(0);opacity:.4}
  30%{transform:translateY(-5px);opacity:1}
}

/* ==========================================================================
   10. Messages as a surface of its own

   Every rule below is under `body:has(.chat-shell)`, so it reaches exactly one
   screen and cannot touch the other twenty.

   The idea: a conversation is not a document you read inside a dashboard. It
   fills the window, and its parts float as separate cards instead of being
   cells of one big panel â€” which is what gives a messenger its feel.

   The colours are PromoChat's own, straight from the theme tokens. An earlier
   pass painted this screen in a dark glass palette copied from the reference
   app; it looked good on its own and wrong in the product, because opening
   Messages felt like leaving PromoChat for something else. Layout is what makes
   a messenger read like a messenger â€” not the colour of it.

   Taking the window means the rail is not on screen, so the way out is built
   back in twice over: a burger that opens the app's own navigation drawer, and
   the shortcut row beside it. Both live in .chat-me-head.
   ========================================================================== */

body:has(.chat-shell){
  --chat-gap:14px;
  --chat-radius:20px;
  /* Held rather than left to the content. modules.css pins the in-thread side
     panel (search results, shared files) at top:70px, a number that matched the
     old fixed header row. Now that the header is a card of its own with a gap
     under it, the panel needs a figure it can be positioned from, and an auto
     height gives it none. */
  --chat-head-h:64px;
  background:var(--page)
}

/* --------------------------------------------------------------------------
   10a. The ground the cards sit on

   A wash of the brand purple at the corners, at an alpha low enough that it
   reads as light in the room rather than as a colour. It follows the theme,
   because it is built from the same token the buttons are.
   -------------------------------------------------------------------------- */
body:has(.chat-shell) .app-main{
  margin:0;padding:0;max-width:none;min-height:100vh;
  background-image:
    radial-gradient(120% 80% at 12% 0%,rgba(108,61,243,.10) 0%,transparent 58%),
    radial-gradient(100% 70% at 92% 100%,rgba(236,72,153,.07) 0%,transparent 60%);
  background-attachment:fixed
}

/* --------------------------------------------------------------------------
   10b. The shell steps aside â€” except the drawer

   The topbar and the page heading go. The rail does not: it becomes the
   drawer, at every width rather than only on a phone, so there is always a way
   back into the rest of the app. app.js already owns opening and closing it â€”
   the burger in .chat-me-head is a second [data-drawer-toggle] and needs no new
   behaviour.
   -------------------------------------------------------------------------- */
body:has(.chat-shell) .topbar,
body:has(.chat-shell) .page-heading,
body:has(.chat-shell) .site-footer{display:none}

body:has(.chat-shell) .sidebar{
  display:flex;position:fixed;top:0;bottom:0;left:0;
  width:min(86vw,300px);z-index:120;
  padding:calc(var(--safe-top) + 16px) 12px calc(var(--safe-bottom) + 20px);
  overflow-y:auto;overscroll-behavior:contain;
  background:var(--card);border-right:1px solid var(--line);
  box-shadow:14px 0 44px rgba(30,16,70,.20);
  transform:translateX(-102%);transition:transform .25s cubic-bezier(.22,1,.36,1)
}
body:has(.chat-shell) .sidebar.is-open{transform:translateX(0)}
body:has(.chat-shell) .nav-scrim{
  display:block;position:fixed;inset:0;z-index:110;
  background:rgba(24,16,48,.45)
}
body:has(.chat-shell) .nav-scrim[hidden]{display:none}

@media(prefers-reduced-motion:reduce){
  body:has(.chat-shell) .sidebar{transition:none}
}

/* --------------------------------------------------------------------------
   10c. The window
   -------------------------------------------------------------------------- */
body:has(.chat-shell) .chat-shell{
  position:fixed;inset:0;z-index:1;
  max-width:none;height:auto;min-height:0;
  margin:0;padding:var(--chat-gap);gap:var(--chat-gap);
  grid-template-columns:330px minmax(0,1fr);
  background:transparent;border:0;border-radius:0;box-shadow:none;overflow:visible
}

/* Four cards, one treatment. The message list is the exception: it keeps the
   tinted thread ground so a white incoming bubble still reads as a card on a
   surface rather than disappearing into it. */
body:has(.chat-shell) .chat-shell>aside,
body:has(.chat-shell) .chat-shell header,
body:has(.chat-shell) .messages,
body:has(.chat-shell) .message-box{
  background:var(--card);
  border:1px solid var(--line);
  border-radius:var(--chat-radius);
  box-shadow:var(--shadow)
}
body:has(.chat-shell) .messages{background:var(--chat-bg)}

body:has(.chat-shell) .chat-shell>aside{
  display:flex;flex-direction:column;overflow:hidden;border-right:1px solid var(--line)
}
body:has(.chat-shell) .chat-shell>section{
  background:transparent;gap:10px;
  grid-template-rows:var(--chat-head-h) minmax(0,1fr) auto auto
}
body:has(.chat-shell) .chat-shell header{height:var(--chat-head-h);padding:0 14px}

/* Search results and shared files, beside the thread. It is absolutely placed
   against the section, so it has to start below the header card rather than at
   the 70px the old fixed header sat at, and it becomes a card like the rest
   instead of a full-height slab with one border. */
body:has(.chat-shell) .chat-side{
  top:calc(var(--chat-head-h) + 10px);right:0;bottom:0;width:300px;
  background:var(--card);border:1px solid var(--line);
  border-radius:var(--chat-radius);box-shadow:var(--shadow)
}
body:has(.chat-shell) .messages{padding:14px 52px}
body:has(.chat-shell) .message-box{border-top:1px solid var(--line);padding:8px 10px}

/* --------------------------------------------------------------------------
   10d. The strip that stands in for the rail
   -------------------------------------------------------------------------- */
body:has(.chat-shell) .chat-me-head{
  display:flex;align-items:center;gap:6px;flex:none;
  padding:10px 8px 10px 8px;border-bottom:1px solid var(--line)
}
body:has(.chat-shell) .chat-burger{
  width:38px;height:38px;flex:none;display:grid;place-items:center;
  border:0;border-radius:11px;background:transparent;color:var(--muted);
  transition:background .16s ease,color .16s ease
}
body:has(.chat-shell) .chat-burger:hover{background:var(--soft);color:var(--purple)}
body:has(.chat-shell) .chat-burger .burger{
  display:flex;flex-direction:column;justify-content:center;gap:4px;width:18px;height:14px
}
body:has(.chat-shell) .chat-burger .burger i{
  display:block;height:2px;width:100%;border-radius:2px;background:currentColor
}

body:has(.chat-shell) .chat-me{
  display:flex;align-items:center;gap:9px;flex:1;min-width:0;
  padding:4px 6px;border-radius:12px;transition:background .16s ease
}
body:has(.chat-shell) .chat-me:hover{background:var(--soft)}
body:has(.chat-shell) .chat-me span{display:flex;flex-direction:column;min-width:0}
/* Both lines truncate. The burger and four shortcuts leave this block about
   half the sidebar's width, and a two-word name was breaking onto a second line
   and pushing the whole strip taller than the row beside it. */
body:has(.chat-shell) .chat-me b,
body:has(.chat-shell) .chat-me small{
  display:block;max-width:100%;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap
}
body:has(.chat-shell) .chat-me b{font-size:13.5px;color:var(--ink);line-height:1.3}
body:has(.chat-shell) .chat-me small{font-size:11px;color:var(--muted)}

body:has(.chat-shell) .chat-side-actions{display:flex;gap:2px;flex:none}
body:has(.chat-shell) .chat-side-actions>a{
  position:relative;width:36px;height:36px;display:grid;place-items:center;
  border-radius:11px;background:transparent;color:var(--muted);
  transition:background .16s ease,color .16s ease
}
body:has(.chat-shell) .chat-side-actions>a:hover{background:var(--soft);color:var(--purple)}
body:has(.chat-shell) .chat-side-actions .nav-icon{width:18px;height:18px}
/* .new-group-btn was a lilac pill with a "+ Group" label; here it is one of
   four icon buttons and must keep neither. */
body:has(.chat-shell) .new-group-btn{padding:0;font-size:0;background:transparent;font-weight:400}

/* --------------------------------------------------------------------------
   10e. The thread list

   Rows become cards with a little space around them rather than a table with
   hairlines, which is the one structural change that makes a list of people
   read as a messenger's list of people.
   -------------------------------------------------------------------------- */
body:has(.chat-shell) .chat-list-head{padding:10px 12px 6px}
body:has(.chat-shell) .chat-search{margin:0}
body:has(.chat-shell) .thread-list{flex:1;padding:2px 8px 8px}
body:has(.chat-shell) .chat-person{
  background:transparent;border:1px solid transparent;border-bottom:0;
  border-radius:14px;padding:10px;margin-bottom:2px;
  transition:background .16s ease,border-color .16s ease
}
body:has(.chat-shell) .chat-person:hover{background:var(--soft)}
body:has(.chat-shell) .chat-person.active{background:#f2edff;border-color:#d9caff}
html[data-theme="dark"] body:has(.chat-shell) .chat-person.active{
  background:var(--nav-hover);border-color:var(--purple)
}

/* --------------------------------------------------------------------------
   10f. The thread itself

   Bubbles are deliberately not restyled here. PromoChat's own purple-on-white
   is the product's face, and this screen keeps it.
   -------------------------------------------------------------------------- */

/* The sender's face, lifted out of the bubble it is rendered inside.

   Absolute rather than a flex row: chat.php keeps the avatar within the bubble
   so the thread is still a flat list of .bubble elements, which is what app.js
   diffs against when it decides whether new messages can be appended. The list
   makes room for it with its own side padding. */
body:has(.chat-shell) .bubble-avatar{
  position:absolute;bottom:0;width:34px;height:34px;line-height:0
}
body:has(.chat-shell) .bubble-avatar .avatar{
  width:34px;height:34px;font-size:12px;border:0
}
body:has(.chat-shell) .received .bubble-avatar{left:-44px}
body:has(.chat-shell) .sent .bubble-avatar{right:-44px}
/* An emoji-only message has no bubble to hang a face off, and the glyph is the
   whole point of it. */
body:has(.chat-shell) .is-emoji .bubble-avatar{display:none}

/* The jump-to-latest pill was positioned against a composer that used to be
   flush with the bottom of the window; it now has a card and a gap under it. */
body:has(.chat-shell) .jump-latest{bottom:96px}

/* "No conversation selected" is the whole right-hand side when nothing is open
   — the archived view with an empty list, or a brand new account. It was the
   only state with no card under it, so the text floated on the bare page while
   the thread list beside it sat on one. It takes the row the thread would have
   had, rather than the first auto row, or it hugs the top of the window. */
body:has(.chat-shell) .chat-shell>section>.chat-blank{
  grid-row:1/-1;align-self:stretch;
  display:grid;place-content:center;justify-items:center;text-align:center;
  gap:6px;padding:40px 24px;
  background:var(--card);border:1px solid var(--line);
  border-radius:var(--chat-radius);box-shadow:var(--shadow)
}
@media(max-width:760px){
  body:has(.chat-shell) .chat-shell>section>.chat-blank{
    border:0;border-radius:0;box-shadow:none
  }
}

/* --------------------------------------------------------------------------
   10g. Starting a group

   The form takes the conversation's place instead of replacing the screen, so
   it needs the rows a conversation would have had: the header slot, then the
   rest of the height to scroll in.
   -------------------------------------------------------------------------- */
body:has(.chat-shell) .chat-new-group{
  grid-row:2/-1;min-height:0;overflow:auto;overscroll-behavior:contain
}
body:has(.chat-shell) .chat-new-group .content-card{
  /* Centred rather than pinned left: the form is the whole point of the pane,
     and a narrow card against one edge leaves the eye with nowhere to go. */
  margin:0 auto;max-width:620px;
  border:1px solid var(--line);
  border-radius:var(--chat-radius);box-shadow:var(--shadow)
}

/* The menu hangs upward when the message is near the bottom of the list.
   chat-ux.js decides; this is only the geometry. */
body:has(.chat-shell) .msg-menu-panel.flips-up{top:auto;bottom:22px}
/* The header beside the back arrow already names this, and two headings one
   above the other read as a mistake. */
body:has(.chat-shell) .chat-new-group .content-card>h2{display:none}

/* app.css hides .chat-back outside the phone breakpoint, where it is the thread
   pane's own back button. This one is the only way out of the form, so it stays
   at every width. */
body:has(.chat-shell) .chat-back-link{
  width:38px;height:38px;flex:0 0 auto;display:grid;place-items:center;
  border-radius:11px;background:var(--soft);color:var(--purple);margin-left:0
}
body:has(.chat-shell) .chat-back-link:hover{background:#ece5ff}
body:has(.chat-shell) .chat-back-link .nav-icon{width:17px;height:17px}

@media(max-width:760px){
  /* There is no conversation open, so nothing has set .chat-open — but the
     form is the thing to look at, not the list behind it. */
  body:has(.chat-new-group) .chat-shell>section{
    transform:none;visibility:visible;pointer-events:auto
  }
  body:has(.chat-new-group) .chat-shell>aside{display:none}
  body:has(.chat-shell) .chat-new-group{padding:12px}
}

/* --------------------------------------------------------------------------
   10g. Phone

   The bottom nav is a second way off this screen, so the shell stops above it
   rather than covering it â€” except while a thread is open, where the nav is
   already hidden and the thread may have the whole window.

   The cards lose their gaps and corners here: on a 390px screen the space
   between them costs more than the separation is worth.
   -------------------------------------------------------------------------- */
@media(max-width:760px){
  body:has(.chat-shell) .chat-shell{
    position:fixed;inset:0;bottom:calc(var(--bottomnav-h) + var(--safe-bottom));
    display:grid;grid-template-columns:minmax(0,1fr);padding:0;gap:0
  }
  body.chat-open:has(.chat-shell) .chat-shell{bottom:0}

  body:has(.chat-shell) .chat-shell>aside{
    border:0;border-radius:0;max-height:none;height:100%;
    box-shadow:none;padding-top:var(--safe-top)
  }
  body:has(.chat-shell) .chat-shell>section{gap:0;background:var(--page)}
  body:has(.chat-shell) .chat-shell header,
  body:has(.chat-shell) .messages,
  body:has(.chat-shell) .message-box{
    border-radius:0;border-left:0;border-right:0;box-shadow:none
  }
  body:has(.chat-shell) .chat-shell header{border-top:0}
  body:has(.chat-shell) .messages{border:0;padding:12px 38px}
  body:has(.chat-shell) .message-box{border-bottom:0}

  /* Smaller face, less room given up to it: 84% of a 390px screen is already
     the whole line. */
  body:has(.chat-shell) .bubble-avatar,
  body:has(.chat-shell) .bubble-avatar .avatar{width:28px;height:28px;font-size:10px}
  body:has(.chat-shell) .received .bubble-avatar{left:-34px}
  body:has(.chat-shell) .sent .bubble-avatar{right:-34px}

  body:has(.chat-shell) .jump-latest{bottom:84px}

  /* modules.css clears the topbar here; on this screen the topbar is not on
     the page, so the panel starts under the thread's own header instead. */
  body:has(.chat-shell) .chat-side{
    inset:calc(var(--chat-head-h) + var(--safe-top)) 0 0;
    width:auto;border:0;border-radius:0;box-shadow:none
  }
}

@media(prefers-reduced-motion:reduce){
  .chat-shell .bubble.is-arriving{animation:none}
  .chat-shell .messages{scroll-behavior:auto}
  .chat-shell .chat-person .unread{animation:none}
  .chat-shell .typing-line i{animation:none;opacity:.7}
}
