/* ============================================================
   Text Kelly — from the iOS 26 (Community) mockup, node 1:2.
   Values pulled with design/extract.py; re-run it after the
   mockup changes. Anything marked APPROX has no CSS equivalent.
   ============================================================ */
:root {
  /* ---- straight from the Figma frame ---- */
  --bg:          #FFFFFF;   /* frame fill */
  --sent:        #0088FF;   /* "My chat" Union fill */
  --on-sent:     #F2F2F7;   /* sent bubble text */
  --recv:        #D0D2D5;   /* "Other chat" Union fill */
  --on-recv:     #333333;   /* received bubble text */
  --stamp:       #8E8E93;   /* "Monday at 11:04PM" */
  --placeholder: #CCCCCC;   /* "iMessage" in the composer */
  --glass-btn:   rgba(255,255,255,.68);  /* 45x45 r22.5 circles */
  --glass-pill:  rgba(255,255,255,.80);  /* the Name pill */
  --tint:        #0088FF;
  /* ---- derived, for the parts the mockup has no token for ---- */
  --up:          #1DA35A;
  --down:        #D6452F;
  --hair:        rgba(0,0,0,.10);
  --field:       rgba(255,255,255,.72);
  --label:       #333333;   /* chrome text: the rail name, sheet headings */
  --surface:     #FFFFFF;   /* a sheet lifted off the page */
  /* SF Pro at the mockup's own weights. 510 and 590 are variable-font
     weights; on Apple hardware -apple-system resolves real SF and honours
     them, elsewhere they round to 500/600. */
  --sf: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro", "Segoe UI",
        Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --bubble-size: 15.2px;    /* mockup value; real iMessage is 17px */
  --bubble-lh:   22px;
  --bubble-ls:   -0.43px;
  --col: 34rem;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:#000; --sent:#0A84FF; --on-sent:#FFF; --recv:#26252A; --on-recv:#F2F2F7;
    --stamp:#8E8E93; --placeholder:#5A5A5E;
    --glass-btn:rgba(60,60,67,.55); --glass-pill:rgba(60,60,67,.7);
    --hair:rgba(255,255,255,.12); --field:rgba(44,44,46,.72);
    --label:#F2F2F7; --surface:#1C1C1E;
    --up:#30D158; --down:#FF6B52;
  }
  :root:not([data-theme="light"]) .scrim { background: rgba(0,0,0,.42); }
  :root:not([data-theme="light"]) .rail .name:hover,
  :root:not([data-theme="light"]) .act:hover .act-ico { background: rgba(70,70,74,.9); }
  :root:not([data-theme="light"]) .rail::before {
    background: linear-gradient(to bottom,
      rgba(10,10,12,.96) 0%, rgba(10,10,12,.92) 46%,
      rgba(10,10,12,.55) 76%, rgba(10,10,12,0) 100%);
  }
}
:root[data-theme="dark"] {
  --bg:#000; --sent:#0A84FF; --on-sent:#FFF; --recv:#26252A; --on-recv:#F2F2F7;
  --stamp:#8E8E93; --placeholder:#5A5A5E;
  --glass-btn:rgba(60,60,67,.55); --glass-pill:rgba(60,60,67,.7);
  --hair:rgba(255,255,255,.12); --field:rgba(44,44,46,.72);
  --label:#F2F2F7; --surface:#1C1C1E;
  --up:#30D158; --down:#FF6B52;
}
:root[data-theme="dark"] .scrim { background: rgba(0,0,0,.42); }
:root[data-theme="dark"] .rail .name:hover,
:root[data-theme="dark"] .act:hover .act-ico { background: rgba(70,70,74,.9); }
:root[data-theme="dark"] .rail::before {
  background: linear-gradient(to bottom,
    rgba(10,10,12,.96) 0%, rgba(10,10,12,.92) 46%,
    rgba(10,10,12,.55) 76%, rgba(10,10,12,0) 100%);
}
*, *::before, *::after { box-sizing: border-box; }
/* Leptos wraps an island in its own <leptos-island> element, so THAT is body's
   flex child — not the .thread inside it. Without this the thread cannot take
   the slack and the composer floats up under the messages. */
leptos-island {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background:
    radial-gradient(120% 340px at 22% -40px, rgba(120,190,255,.30), transparent 70%),
    radial-gradient(90% 300px at 88% -60px, rgba(160,235,205,.28), transparent 72%),
    var(--bg);
  background-repeat: no-repeat;
  background-attachment: fixed;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  color: var(--on-recv);
  font-family: var(--sf);
  -webkit-font-smoothing: antialiased;
  padding: 0;
}
img { max-width: 100%; }
[hidden] { display: none !important; }
/* ---------- the rail ---------- */
/* Sticky glass, the way a Messages nav behaves: the thread runs underneath it
   rather than stopping at it. The blur has the page wash to work against, which
   is the only reason it reads as glass at all. */
.rail { position: sticky; top: 0; z-index: 10; flex: none; }
/* The mockup has no bar and no rule — the controls float and the content
   dissolves under them (iOS 26's scroll edge effect). So the glass lives on its
   own layer and is MASKED to fade out downward: that fades the blur as well as
   the tint, which a background gradient alone cannot do. A hairline here reads
   as a web header; the whole point is that it should not. */
.rail::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  -webkit-backdrop-filter: blur(28px) saturate(180%);
  backdrop-filter: blur(28px) saturate(180%);
  /* nearly opaque where the avatar sits, gone by the bottom edge — a message
     scrolling past should read as dissolving, not as competing with the name */
  background: linear-gradient(to bottom,
    rgba(255,255,255,.96) 0%, rgba(255,255,255,.92) 46%,
    rgba(255,255,255,.55) 76%, rgba(255,255,255,0) 100%);
  -webkit-mask-image: linear-gradient(to bottom, #000 62%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 62%, transparent 100%);
}
.rail-inner { position: relative; }
.rail-inner {
  max-width: var(--col); margin: 0 auto;
  display: flex; flex-direction: column; align-items: center; gap: .45rem;
  padding: .85rem 1rem 1.6rem;
}
/* the mockup's avatar is 63x63 r33 */
.rail .av {
  width: 54px; height: 54px; border-radius: 27px;
  overflow: hidden;   /* the portrait brings its own circular ground */
  box-shadow: 0 2px 10px rgba(0,0,0,.12);
}
/* the "Name" pill: r22.5, white @ .80 */
.rail .av { border: none; cursor: pointer; padding: 0; }
.avatar { display: block; width: 100%; height: 100%; object-fit: cover; }
/* the name is the entrypoint: iOS opens the contact from here, so it is a control */
.rail .name {
  font-family: inherit; cursor: pointer;
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 15px; font-weight: 510; line-height: 18px; color: var(--label);
  padding: 4px 10px 4px 14px; border-radius: 22.5px;
  background: var(--glass-pill);
  border: .5px solid rgba(255,255,255,.7);
  box-shadow: 0 1px 8px rgba(0,0,0,.08);
}
.rail .name:hover { background: rgba(255,255,255,.94); }
/* U+203A renders at whatever size and weight the fallback font feels like,
   which never matched the name. Drawn instead, at the name's cap height. */
.rail .name .chev { display: flex; align-items: center; color: var(--stamp); }
.rail .name .chev svg { width: 6.5px; height: 11px; display: block; }
.rail .av:focus-visible, .rail .name:focus-visible {
  outline: 2px solid var(--tint); outline-offset: 3px;
}
/* ---------- contact card ---------- */
/* Not a sheet — it grows out of the + it belongs to, the way an iOS menu
   springs from the control that opened it, and it leaves the composer
   sitting above the scrim so the thread never feels taken away. */
/* iOS does not drop a flat black wash behind a menu — it puts a material
   there: the content behind goes soft and slightly dim, so it reads as
   pushed back rather than switched off. */
.scrim {
  position: fixed; inset: 0; z-index: 4;
  background: rgba(0,0,0,.16);
  -webkit-backdrop-filter: blur(22px) saturate(140%);
  backdrop-filter: blur(22px) saturate(140%);
  opacity: 0; pointer-events: none;
  transition: opacity .28s ease;
}
.scrim.open { opacity: 1; pointer-events: auto; }
.card {
  position: absolute; left: 1rem; right: 1rem; bottom: 100%;
  max-height: min(72vh, 34rem); overflow-y: auto;
  padding: 1.5rem 1.4rem 1.6rem;
  border-radius: 24px;
  background: var(--surface);
  border: .5px solid var(--hair);
  box-shadow: 0 12px 44px rgba(0,0,0,.20), 0 2px 8px rgba(0,0,0,.08);
  /* the + sits 22px in from the left edge of the bar, half a bar-height below */
  transform-origin: 22px calc(100% + 30px);
  opacity: 0; transform: scale(.9) translateY(12px);
  pointer-events: none;
  transition: opacity .2s ease, transform .34s cubic-bezier(.32,.72,0,1);
}
.card.open { opacity: 1; transform: none; pointer-events: auto; }
@media (prefers-reduced-motion: reduce) { .card { transition: opacity .2s ease } }
.card-head { display: flex; flex-direction: column; align-items: center; gap: .3rem; }
.card-av {
  width: 68px; height: 68px; border-radius: 34px;
  overflow: hidden;
  box-shadow: 0 4px 18px rgba(0,0,0,.16);
}
.card-head h2 {
  margin: .45rem 0 0; font-size: 22px; font-weight: 590; letter-spacing: -.026em;
  color: var(--label);
}
.card-num {
  font-size: 15px; font-weight: 510; color: var(--tint);
  text-decoration: none; font-variant-numeric: tabular-nums;
}
.card-num:focus-visible { outline: 2px solid var(--tint); outline-offset: 3px; border-radius: 6px; }
/* the round action tiles iOS puts under a contact's name */
.acts { display: flex; justify-content: center; gap: 1.75rem; padding: 1.1rem 0 .2rem; }
.act {
  display: flex; flex-direction: column; align-items: center; gap: .35rem;
  background: none; border: none; padding: 0; cursor: pointer;
  font-family: inherit; text-decoration: none;
}
.act-ico {
  width: 46px; height: 46px; border-radius: 23px;
  display: grid; place-items: center;
  background: var(--glass-btn); border: .5px solid var(--hair);
  color: var(--tint);
  -webkit-backdrop-filter: blur(30px) saturate(180%);
  backdrop-filter: blur(30px) saturate(180%);
  box-shadow: 0 2px 14px rgba(0,0,0,.08);
}
.act-ico svg { width: 22px; height: 22px; }
.act-lbl { font-size: 12px; font-weight: 510; color: var(--stamp); }
.act:hover .act-ico { background: rgba(255,255,255,.92); }
.act:focus-visible { outline: 2px solid var(--tint); outline-offset: 3px; border-radius: 12px; }
.card-note {
  margin: 1.15rem 0 0; font-size: 14px; line-height: 1.5; color: var(--label);
  text-align: left; text-wrap: pretty;
}
.card-note + .card-note { margin-top: .7rem; }
/* ---------- press, don't select ---------- */
/* A native app does not smear a blue selection across its own chrome when you
   tap a button, and neither should this. Everything is unselectable by
   default; the things that are genuinely *content* opt back in below. */
body {
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;   /* no grey flash on iOS */
}
button, a, label, .rail, .composer, .card, .stamp, .receipt, .typing {
  -webkit-touch-callout: none;   /* no long-press callout on the avatar */
}
img, svg { -webkit-user-drag: none; user-select: none; }

/* content: the bubbles carry wallet addresses and numbers worth copying,
   and a text field must always be selectable to be usable */
.b, .card-note, .card-num, input {
  -webkit-user-select: text;
  user-select: text;
  cursor: auto;
}
.b { cursor: text; }

/* ---------- transcript ---------- */
.thread {
  max-width: var(--col); width: 100%; margin: 0 auto; padding-inline: 1rem;
  display: flex; flex-direction: column;
  flex: 1 1 auto;   /* takes the slack so the composer sits at the bottom */
  padding-bottom: .75rem;
}
/* "Yesterday at 8:34PM" — SF Pro 510, 12/14.3, #8E8E93 */
.stamp {
  text-align: center; color: var(--stamp);
  font-size: 12px; font-weight: 510; line-height: 14.3px;
  padding: 15px 0;
}
.stamp b { font-weight: 590; }
/* Within one speaker's run the mockup stacks bubbles tight; when the turn
   changes it opens up. 17px is measured off the frame: "Other chat 3" ends
   at y=179, "My chat 2" starts at 196. */
.row { display: flex; }
.row + .row { margin-top: 2px; }
.row.k + .row.u, .row.u + .row.k { margin-top: 17px; }
.row.k { justify-content: flex-start; }
.row.u { justify-content: flex-end; }
/* body: 36 tall at one line, r18 */
.b {
  position: relative;
  max-width: 74%;
  min-height: 36px;
  padding: 7px 14px;
  border-radius: 18px;
  font-size: var(--bubble-size);
  font-weight: 590;
  line-height: var(--bubble-lh);
  letter-spacing: var(--bubble-ls);
  word-wrap: break-word;
  animation: pop .18s ease-out;
}
@keyframes pop { from { transform: scale(.94); opacity: .4 } to { transform: scale(1); opacity: 1 } }
@media (prefers-reduced-motion: reduce) { .b { animation: none } }
.row.k .b { background: var(--recv); color: var(--on-recv); }
.row.u .b { background: var(--sent); color: var(--on-sent); }
/* Tails, traced from the mockup's own Union path rather than approximated.
   The viewBox crops the full bubble outline down to the tail corner, so the
   curve is exactly Apple's. Masked, so one asset works in both themes. */
.row.tail .b::after {
  content: ""; position: absolute; bottom: -6px;
  width: 19px; height: 12px;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%; mask-size: 100% 100%;
}
.row.k.tail .b::after {
  left: 0; background: var(--recv);
  -webkit-mask-image: var(--tail-left); mask-image: var(--tail-left);
}
.row.u.tail .b::after {
  right: 0; background: var(--sent);
  -webkit-mask-image: var(--tail-right); mask-image: var(--tail-right);
}
:root {
  --tail-left: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 30 19 12'><path d='M84 0C93.9411 0 102 8.05888 102 18C102 27.9411 93.9411 36 84 36H18.8661C18.8078 36 18.7588 36.0392 18.7129 36.0752C13.5052 40.146 10.8311 42.1064 7.40332 41.9932C7.23823 41.9875 7.15959 41.792 7.26855 41.668C9.48409 39.1574 10.2157 37.8847 10 36.9424C9.85314 35.9429 7.94338 33.4177 6.66983 32.0004C6.6364 31.9632 6.60062 31.9295 6.56202 31.8976C2.55531 28.5963 0 23.5969 0 18C0 8.05888 8.05888 0 18 0H84Z' fill='black'/></svg>");
  --tail-right: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='79 30 19 12'><path d='M19 0C9.05888 0 1 8.05888 1 18C1 27.9411 9.05888 36 19 36H79.1338C79.1922 36 79.2412 36.0392 79.2871 36.0752C84.4948 40.146 87.1689 42.1064 90.5967 41.9932C90.7618 41.9875 90.8404 41.792 90.7314 41.668C88.5159 39.1574 87.7843 37.8847 88 36.9424C88.1469 35.9429 90.0566 33.4177 91.3302 32.0004C91.3636 31.9632 91.3994 31.9295 91.438 31.8976C95.4447 28.5963 98 23.5969 98 18C98 8.05888 89.9411 0 80 0H19Z' fill='black'/></svg>");
}
.b .n { font-variant-numeric: tabular-nums; }
.b .up { color: var(--up); font-weight: 700; }
.b .down { color: var(--down); font-weight: 700; }
.row.u .b .up, .row.u .b .down { color: var(--on-sent); }
.b .addr { font-family: var(--mono); font-size: 13px; word-break: break-all; }
.receipt {
  align-self: flex-end; color: var(--stamp);
  font-size: 11px; font-weight: 510; padding: 4px 4px 0;
}
.typing {
  display: inline-flex; gap: 5px; align-items: center;
  background: var(--recv); padding: 12px 14px; border-radius: 18px;
}
.typing i { width: 8px; height: 8px; border-radius: 50%; background: var(--stamp);
  animation: bnc 1.4s ease-in-out infinite; }
.typing i:nth-child(2) { animation-delay: .18s }
.typing i:nth-child(3) { animation-delay: .36s }
@keyframes bnc { 0%,60%,100% { opacity: .35 } 30% { opacity: .95 } }
@media (prefers-reduced-motion: reduce) { .typing i { animation: none; opacity: .6 } }
/* ---------- composer ---------- */
/* The mockup floats this over a wallpaper: 45x45 glass circles, a 280x45
   glass field, and a 180px white gradient fading the thread out behind it
   (the "Dégardé" layer: white .02 → .70 → .90 → 1). */
.composer {
  position: sticky; bottom: 0; z-index: 5; flex: none; margin-top: 1.5rem;
  background: var(--bg);
}
.composer::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: 28px;
  background: linear-gradient(to bottom,
    rgba(255,255,255,0), var(--bg));
  pointer-events: none;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .composer::before {
    background: linear-gradient(to bottom, rgba(0,0,0,0), var(--bg));
  }
}
:root[data-theme="dark"] .composer::before {
  background: linear-gradient(to bottom, rgba(0,0,0,0), var(--bg));
}
.composer .inner {
  position: relative;   /* the card hangs off the top of this */
  max-width: var(--col); margin: 0 auto; padding: .5rem 1rem 1.25rem;
}
.bar { display: flex; align-items: center; gap: 8px; }
/* 45x45 r22.5, white @ .68, with the mockup's soft drop shadow. APPROX: the
   real thing is a Figma GLASS effect (refraction + specular) with an 80px
   background blur and a 40px layer blur, which CSS cannot express. */
.plus, .send {
  width: 45px; height: 45px; border-radius: 22.5px; flex: none;
  display: grid; place-items: center; border: .5px solid var(--hair);
  background: var(--glass-btn);
  -webkit-backdrop-filter: blur(30px) saturate(180%);
  backdrop-filter: blur(30px) saturate(180%);
  box-shadow: 0 2px 18px rgba(0,0,0,.10);
  cursor: pointer;
}
.plus {
  font-size: 20px; font-weight: 590; color: var(--on-recv);
  transition: transform .3s cubic-bezier(.32,.72,0,1);
}
.plus.open { transform: rotate(45deg); }
@media (prefers-reduced-motion: reduce) { .plus { transition: none } }
.send { background: var(--sent); border-color: transparent; color: #fff; }
.send:focus-visible, .plus:focus-visible { outline: 2px solid var(--tint); outline-offset: 2px; }
/* inert until the island hydrates — see thread.rs */
.send:disabled { opacity: .45; cursor: default; }
.bar form { flex: 1; display: flex; min-width: 0; }
.bar input {
  flex: 1; height: 45px; min-width: 0;
  border: .5px solid var(--hair); border-radius: 22.5px;
  background: var(--field);
  -webkit-backdrop-filter: blur(30px) saturate(180%);
  backdrop-filter: blur(30px) saturate(180%);
  padding: 0 18px;
  font-family: inherit; font-size: 17px; font-weight: 510; line-height: 20.3px;
  color: var(--on-recv); letter-spacing: 0; outline: none;
  text-overflow: ellipsis;
}
.bar input::placeholder { color: var(--placeholder); font-weight: 510; }
.bar input:focus { border-color: var(--tint); }
