/* THE message bubble — one spec for BOTH sides of the line (William,
   2026-08-07): the guest app and the desk link this same sheet, so the studio
   sees exactly what the guest sees. Colour follows the VOICE, not the screen:
   rainyseason speaks in the house intuition blue (--hard-rain), the guest on
   the all symbol's OFF tone (--wx-off), the soft white ink on both — all from
   the nine-colour stack (William, 2026-08-07). Each app keeps its own .msg layout (sides, widths);
   this sheet owns the bubble, the links and the estate card. Requires
   tokens.css. */
.bubble {
  padding: 8px 12px; line-height: 1.45; font-family: var(--font-code);
  font-size: var(--t-lede); letter-spacing: -0.01em;
  white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word;
  color: var(--ink); border-radius: var(--r);
}
.bubble--guest { background: var(--wx-off, #101010); } /* the guest on the all symbol's OFF tone */
.bubble--desk { background: var(--hard-rain); } /* rainyseason speaks in the blue (William, 2026-08-07) */
/* A link in a message keeps its bubble's ink and underlines — the words stay
   one voice; the underline is the signal. */
.bubble .msg-link { color: inherit; text-decoration: underline; text-underline-offset: 2px; overflow-wrap: anywhere; }
/* A reply's quote — who said it and a short excerpt — sits as the bubble's
   first child on a quiet wash of the bubble's own ink, no accent bar
   (William, 2026-08-07). Ink inherits the voice at quiet rungs; a tappable
   quote (a button on the guest app) jumps to the original. */
.msg-quote {
  display: block; width: 100%; text-align: left;
  appearance: none; background: color-mix(in srgb, currentColor 8%, transparent);
  border: 0;
  border-radius: var(--r); padding: 5px 9px 6px; margin: 0 0 7px;
  font: inherit; color: inherit; cursor: default;
}
button.msg-quote { cursor: pointer; }
.msg-quote__who {
  display: block; font-size: 10px; line-height: 1.3; letter-spacing: 0.08em;
  opacity: 0.72;
}
.msg-quote__text {
  display: block; font-size: 11px; line-height: 1.4; opacity: 0.72;
  overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow-wrap: anywhere;
}
/* The time + tick ride INSIDE the bubble (William, 2026-08-07), tucked to the
   last line's right — the messaging convention. The float shares a short line
   and drops below a full one. Meta ink INHERITS the bubble's own voice (the
   soft white on both grounds now) at a quiet opacity; only a delivered tick
   lights, in the intuition blue. */
.bubble .msg-meta, .bubble .meta {
  float: right; display: inline-flex; align-items: center; gap: 5px;
  margin: 9px -5px -3px 10px; padding: 0;
  font-size: 10px; line-height: 1; color: inherit;
}
.bubble time { font-size: 10px; color: inherit; opacity: 0.62; }
.bubble .ack { color: inherit; opacity: 0.62; }
.bubble .ack.is-delivered { color: var(--hard-rain, #3029E7); opacity: 1; } /* the intuition blue (William, 2026-08-07) */
.bubble .ack.is-delivered svg { filter: drop-shadow(0 0 3px color-mix(in srgb, var(--hard-rain, #3029E7) 55%, transparent)); }
/* OUR OWN unfurl — an estate link draws the page's card: share image + title
   + the domain, from the local catalog (site-pages.json). One card per
   message, seated under the bubble on the message's own side. */
.link-card {
  display: flex; flex-direction: column; width: min(100%, 260px);
  border: 1px solid var(--line-strong); border-radius: var(--r); overflow: hidden;
  background: var(--g-elev); text-decoration: none;
  transition: border-color 140ms ease;
}
.link-card:hover, .link-card:focus-visible { border-color: var(--ink-3); outline: none; }
.link-card__img { display: block; width: 100%; aspect-ratio: 1.91 / 1; object-fit: cover; }
.link-card__text { display: flex; flex-direction: column; gap: 2px; padding: 9px 11px 10px; text-align: left; }
.link-card__title { font-family: var(--font-code); font-size: 12.5px; font-weight: 500; line-height: 1.35; color: var(--ink); }
.link-card__domain { font-family: var(--font-code); font-size: 10px; letter-spacing: var(--track-micro); color: var(--ink-3); }

/* An ATTACHMENT inside a bubble — a picture inline or a PDF download chip, drawn
   by the ONE shared module (attach.js) so both sides of the line show it the
   same. A picture-only bubble sheds its text padding and ground so the image
   fills it (.bubble--media), with the time + tick floated over the corner. */
.bubble--media { position: relative; padding: 0; background: none; overflow: hidden; }
.bubble--media .msg-meta, .bubble--media .meta {
  float: none; position: absolute; right: 8px; bottom: 7px; margin: 0;
  padding: 2px 7px; border-radius: 999px;
  background: color-mix(in srgb, #000 45%, transparent); color: #fff;
}
.bubble--media .ack, .bubble--media time { color: #fff; }
.msg-att { margin: 0; }
.bubble:not(.bubble--media) .msg-att--image { display: block; margin: 2px 0 8px; }
/* The picture is a button: tapping it opens the full-size lightbox. Reset the
   button chrome so only the image shows; the whole picture is visible in the
   thread (scaled to fit, never cropped), and the lightbox shows it full. */
.msg-att__open {
  display: block; appearance: none; -webkit-appearance: none;
  background: none; border: 0; padding: 0; margin: 0;
  width: fit-content; max-width: 100%; cursor: zoom-in; border-radius: var(--r);
}
.msg-att__open:focus-visible { outline: 2px solid color-mix(in srgb, currentColor 40%, transparent); outline-offset: 2px; }
.msg-att__img {
  display: block; max-width: min(300px, 100%); max-height: 340px; width: auto; height: auto;
  border-radius: var(--r); background: var(--g-elev);
}
.bubble:not(.bubble--media) .msg-att--file { display: block; margin: 2px 0 6px; }
.msg-att__file {
  display: flex; align-items: center; gap: 10px; text-decoration: none; color: inherit;
  padding: 9px 11px; border: 1px solid var(--line-strong); border-radius: var(--r);
  background: color-mix(in srgb, currentColor 8%, transparent);
  min-width: 0; max-width: min(100%, 280px);
}
.msg-att__file:hover, .msg-att__file:focus-visible { border-color: var(--ink-3); outline: none; }
.msg-att__icon {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 8px;
  background: color-mix(in srgb, currentColor 14%, transparent);
  font-family: var(--font-code); font-size: 10px; letter-spacing: 0.06em;
}
.msg-att__info { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.msg-att__name { font-family: var(--font-code); font-size: 12px; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.msg-att__size { font-family: var(--font-code); font-size: 10px; opacity: 0.62; }
.msg-att[data-state="loading"] {
  min-height: 120px; min-width: 160px; border-radius: var(--r);
  background: color-mix(in srgb, currentColor 8%, transparent);
}
.msg-att[data-state="error"] { font-family: var(--font-code); font-size: 11px; opacity: 0.6; padding: 8px 0; }

/* The full-picture viewer — an in-page overlay both sides share, never a new
   tab (the desk webview refuses off-origin/blob navigations). A tap anywhere,
   the close mark, or Esc dismisses it. */
.att-lightbox {
  position: fixed; inset: 0; z-index: 99999;
  display: flex; align-items: center; justify-content: center;
  padding: 24px; background: rgba(0, 0, 0, 0.9);
  cursor: zoom-out; -webkit-tap-highlight-color: transparent;
  animation: attLightIn 140ms ease;
}
.att-lightbox__img {
  max-width: 100%; max-height: 100%; width: auto; height: auto;
  object-fit: contain; border-radius: 6px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.att-lightbox__x {
  position: fixed; top: 12px; right: 16px;
  appearance: none; background: none; border: 0; cursor: pointer;
  color: #fff; font-size: 30px; line-height: 1; opacity: 0.85;
}
.att-lightbox__x:hover, .att-lightbox__x:focus-visible { opacity: 1; outline: none; }
@keyframes attLightIn { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .att-lightbox { animation: none; } }

/* The file picker input, shared by both composers. It must stay RENDERED, not
   display:none — a display:none file input will not open its picker on iOS /
   WKWebView even when a label triggers it. So hide it visually while keeping it
   in the layout (the standard visually-hidden recipe). */
.attach-input {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
