/* ============================================================
   liquid girls — buddy list
   an IM client, not a website. two people in it, records
   arriving as completed file transfers.

   one typographic logic:
     tahoma / UI sizes   people and their controls
     monospace           anything that came off a machine —
                         filenames, properties, clocks, cat nos
     handwriting         the marks the two of them leave on things

   and one symbol logic — four marks, theirs, drawn once and
   used everywhere. the client is stamped by the people it
   belongs to, it is not a re-skin:
     appmark  the record with the cat ears. the software's own
              icon, and the tile whoever is talking gets
     arrive   the appmark with a file dropping into it. every
              transfer wears this instead of a page icon
     dab      a blunt marker tap. it is what "online" looks
     loop     one freehand ring. it means "this is the one you
              are looking at" — on the release selector, on the
              buddy, on the record that is playing

   three records, three physical histories — ONE handling
   logic, mutated per release. the sleeve went on a flatbed
   scanner; everything you see is that scan:
     lg001  the scan, photocopied. contrast, toner, and the
            corner the drum starved
     lg002  the same scan, gone round once with a marker
     lg003  the same scan with a paper insert stuck over it

   the chrome stays quiet on purpose. the band infects it in
   exactly one place: one sticker on the title bar. everything
   else about them arrives as a file or as a line of chat.
   ============================================================ */

:root{
  --ui: Tahoma, Verdana, "Segoe UI", "DejaVu Sans", sans-serif;
  --mono: Consolas, "Lucida Console", "Courier New", monospace;
  --hand: "Segoe Script", "Bradley Hand ITC", "Bradley Hand", "Comic Sans MS", cursive;

  --desk-1: #d6dade;
  --desk-2: #aeb4bc;
  --w-hi:    #fdfefe;
  --w-bg:    #f0f2f5;
  --w-panel: #e4e8ed;
  --w-sunk:  #d9dee4;
  --paper:   #f7f2e7;
  --copy:    #f4f5f0;
  --paper-2: #ece4d2;
  --bar-1:   #47617f;
  --bar-2:   #27384f;
  --line:    #b3bcc6;
  --line-d:  #8b95a2;

  --ink:     #161f2d;
  --ink-2:   #4f5e74;
  --ink-3:   #78838f;
  --pen:     #1b2a49;

  /* pink means one thing in this whole file: not read yet */
  --pink:    #ff2d7d;

  /* the freehand ring, as one reusable mark. non-scaling stroke
     so it stays a pen width at any size it is stretched to. */
  --loop: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 34' preserveAspectRatio='none'%3E%3Cpath d='M93 5C62 1 13 4 5 16c-7 11 23 15 47 14 27-1 60-6 64-13 4-6-13-11-29-12' fill='none' stroke='%231b2a49' stroke-width='2' stroke-linecap='round' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E");

  --r: 3px;
}

*,*::before,*::after{ box-sizing:border-box; }

html{ -webkit-text-size-adjust:100%; }

body{
  margin:0;
  font-family:var(--ui);
  font-size:12px;
  line-height:1.4;
  color:var(--ink);
  background:var(--desk-2);
  background-image:radial-gradient(125% 95% at 50% 0%, var(--desk-1) 0%, var(--desk-2) 100%);
  background-attachment:fixed;
}

h1,h2,h3,p,ul,li,figure,dl,dt,dd{ margin:0; padding:0; }
ul{ list-style:none; }
img{ max-width:100%; display:block; }
button{ font:inherit; color:inherit; }
svg{ display:block; }

.sr{
  position:absolute; width:1px; height:1px; overflow:hidden;
  clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap;
}

:focus-visible{ outline:2px solid var(--ink); outline-offset:2px; }
.win__bar :focus-visible{ outline-color:#eef4fb; }
.lb :focus-visible{ outline-color:#fff; }

/* ---- the marks. all of them draw with currentColor, so one
   mark works on ink and on paper without a second copy. ---- */

.appmark{
  flex:0 0 auto; display:block;
  width:15px; height:15px;
  color:#eef4fb;
}
.appmark svg{ width:100%; height:100%; }

.buds__mark{
  flex:0 0 auto; display:block;
  width:14px; height:14px;
  color:var(--ink);
}
.buds__mark svg{ width:100%; height:100%; }

/* online is a blunt marker tap, not a dot */
.dot{
  flex:0 0 auto; display:block;
  width:9px; height:9px;
  color:var(--ink);
}
.dot svg{ width:100%; height:100%; display:block; }

/* the transfer icon: the appmark with a file dropping into it */
.arrive{
  flex:0 0 auto; display:block;
  width:14px; height:14px;
  color:var(--ink-2);
}
.arrive svg{ width:100%; height:100%; display:block; }

/* ============================================================
   desk + window chrome
   ============================================================ */

/* the messenger is the composition, and a messenger is a small
   window: it sits on the desk at a size you could actually leave
   open all day, and the desk stays visible around it. the width is
   deliberately held down — a chat window stretched to the edges of
   a monitor stops being a chat window and becomes a page, and the
   white inside it then reads as empty layout instead of as a
   conversation with nowhere left to grow.

   everything inside it is dense because there is no room to be
   slack — records and conversation both. */
.desk{
  min-height:100vh;
  padding:10px;
  display:flex;
  align-items:flex-start;
  justify-content:center;
}

.win{
  width:min(960px,100%);
  height:calc(100vh - 20px);
  max-height:1040px;
  display:flex;
  flex-direction:column;
  background:var(--w-bg);
  border:1px solid var(--line-d);
  border-radius:5px 5px 3px 3px;
  overflow:hidden;
  box-shadow:0 0 0 1px rgba(255,255,255,.4) inset,
             6px 6px 0 rgba(22,31,45,.1),
             0 18px 34px -20px rgba(22,31,45,.6);
  transition:width .18s ease;
}
.win.is-wide{ width:min(1120px,100%); }

.win__bar{
  position:relative;
  flex:0 0 28px; height:28px;
  display:flex; align-items:center; gap:7px;
  padding:0 5px 0 8px;
  color:#eef4fb;
  background:linear-gradient(180deg,var(--bar-1),var(--bar-2));
  border-bottom:1px solid #1d2c44;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.26);
  user-select:none;
}
.win__title{
  font-family:var(--ui); font-weight:700;
  font-size:11px; letter-spacing:0; line-height:1;
  text-shadow:0 1px 0 rgba(0,0,0,.4);
}
.win__sub{
  font-size:9.5px; color:rgba(238,244,251,.7);
  padding-left:7px; border-left:1px solid rgba(238,244,251,.28);
}
.win__btns{ margin-left:auto; display:flex; gap:3px; }
.wb{
  width:20px; height:17px; padding:0;
  display:grid; place-items:center;
  font-family:var(--ui); font-size:9px; font-weight:700; line-height:1;
  color:#eef4fb; cursor:pointer;
  border:1px solid rgba(238,244,251,.34);
  border-radius:2px;
  background:linear-gradient(180deg,rgba(255,255,255,.3),rgba(255,255,255,.05));
}
.wb:hover{ background:rgba(255,255,255,.45); color:var(--ink); border-color:rgba(255,255,255,.6); }
.wb[aria-pressed="true"]{ background:var(--ink); border-color:#eef4fb; color:#eef4fb; }
.wb--x:hover{ background:var(--ink); border-color:#eef4fb; color:#eef4fb; }

/* ---- the one sticker on the chrome. a paper scrap with the
   cat off the first label, stuck on the title bar. ---- */
.bar__stk{
  flex:0 0 auto;
  width:21px; height:19px; margin-left:3px;
  display:grid; place-items:center;
  background:var(--paper);
  box-shadow:0 1px 2px rgba(10,14,20,.5), 0 0 0 1px rgba(22,31,45,.28);
  transform:rotate(-6deg);
  clip-path:polygon(0 0,100% 0,100% 100%,0 100%,0 0);
}
.bar__stk svg{ width:15px; height:15px; }

.win__body{
  flex:1 1 auto; min-height:0;
  display:grid;
  grid-template-columns:var(--buds-w,180px) minmax(0,1fr);
}
.win.is-slim .win__body{ --buds-w:44px; }

/* ============================================================
   buddy list
   ============================================================ */

.buds{
  min-height:0;
  display:flex; flex-direction:column;
  background:var(--w-panel);
  border-right:1px solid var(--line);
  overflow:hidden;
}
.buds__top{
  flex:0 0 auto;
  display:flex; flex-direction:column;
}
/* the panel reads the way a buddy list does: names from the top,
   and the furniture that belongs to you — your own personal
   message, the note, the button — stacked at the bottom. the
   space between them is the room the list would grow into. */
.buds__low{
  flex:1 1 auto; min-height:0;
  display:flex; flex-direction:column; justify-content:flex-end;
}

.buds__head{
  display:flex; align-items:center; gap:5px;
  padding:6px 8px 5px;
  border-bottom:1px solid var(--line);
}
.buds__me{ font-weight:700; font-size:11px; line-height:1; }
.buds__count{ margin-left:auto; font-family:var(--mono); font-size:9px; color:var(--ink-2); }

.buds__scroll{ overflow:visible; padding:5px 0 6px; }

.buds__group{
  padding:1px 8px 4px;
  font-size:9px; font-weight:700; color:var(--ink-2);
}
.buds__gcount{ font-weight:400; }

.buddy{
  display:grid;
  grid-template-columns:30px minmax(0,1fr);
  gap:7px;
  padding:7px 8px 8px;
  cursor:pointer;
  border-left:2px solid transparent;
}
.buddy:hover{ background:#dbe0e7; }
.buddy.is-focus{ background:#d3dae4; border-left-color:var(--ink); }

/* hand-drawn buddy tiles — aria presses hard in cream on ink,
   tano is a thin ballpoint t whose crossbar is a cat */
.buddy__av{
  position:relative;
  width:30px; height:30px;
  display:grid; place-items:center;
  border:1px solid var(--line-d); border-radius:2px;
}
.buddy__av svg{ width:100%; height:100%; }
.buddy[data-who="aria"] .buddy__av{ background:var(--ink); }
.buddy[data-who="tano"] .buddy__av{ background:var(--w-hi); }
.buddy__av .dot{
  position:absolute; right:-4px; bottom:-4px;
  width:9px; height:9px;
  box-shadow:0 0 0 1.5px var(--w-panel);
}
/* the ring means "this is the one you are looking at". it is the
   same freehand circle the release selector wears. */
.buddy.is-focus .buddy__av::after{
  content:""; position:absolute; inset:-4px;
  pointer-events:none;
  background-image:var(--loop);
  background-size:100% 100%;
  background-repeat:no-repeat;
}
/* a nudge lands on a person, so it lands on her tile: the same
   freehand ring, drawn fast, on its way out as soon as it is read. */
.buddy.is-nudged .buddy__av::after{
  content:""; position:absolute; inset:-4px;
  pointer-events:none;
  background-image:var(--loop);
  background-size:100% 100%;
  background-repeat:no-repeat;
  animation:nudge-ping .6s ease-out both;
}
@keyframes nudge-ping{
  0%{ opacity:0; transform:scale(.68) rotate(-8deg); }
  30%{ opacity:1; }
  100%{ opacity:0; transform:scale(1.2) rotate(5deg); }
}

/* people first, and people get three lines: who she is, what she
   has typed into her own personal message box this week, and
   where she is. the record she is playing sits on the name line,
   small, because it is a status and not a sleeve. */
.buddy__txt{
  min-width:0;
  display:grid;
  grid-template-columns:minmax(0,1fr) auto;
  align-items:center;
  column-gap:6px;
}
.buddy__name{
  grid-column:1; grid-row:1;
  font-size:12px; font-weight:700; line-height:1.25;
}
.buddy__pm{
  grid-column:1 / -1; grid-row:2;
  font-size:9px; line-height:1.45; color:var(--ink-2);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
/* a file that is playing is broadcast from her name, the way msn
   broadcast whatever was on your machine. the filename is long and
   it is the point, so this one line is allowed to wrap. */
.buddy.is-np .buddy__pm{
  white-space:normal; overflow:visible; overflow-wrap:anywhere;
}
.buddy__note{ display:none; }
.buddy__city{
  grid-column:1 / -1; grid-row:3;
  font-size:9px; line-height:1.45; color:var(--ink-3);
}

.buddy__np{
  grid-column:2; grid-row:1;
  display:flex; align-items:center; gap:4px;
  min-width:0;
  font-family:var(--mono); font-size:9px; color:var(--ink-2);
}
.buddy__thumb{
  flex:0 0 auto; width:20px; height:20px;
  border:1px solid var(--line-d); background:#101418;
  object-fit:cover;
}

.buds__pm{ padding:8px 8px 9px; border-top:1px solid var(--line); background:#dde2e9; }
.buds__pmlabel{ display:block; font-size:8.5px; color:var(--ink-3); }
.buds__pmtext{ margin-top:3px; font-size:9px; line-height:1.45; color:var(--ink); }

/* ---- nothing else is taped into the panel. the personality in
   here comes out of the exchange, not out of props. ---- */

.buds__foot{ padding:6px 8px 8px; border-top:1px solid var(--line); background:var(--w-panel); }

/* ============================================================
   buttons — one register for every control in the client
   ============================================================ */

.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:6px;
  height:23px; padding:0 11px;
  font-family:var(--ui); font-size:11px; font-weight:700;
  text-decoration:none; cursor:pointer;
  color:var(--ink);
  border:1px solid var(--line-d);
  border-radius:var(--r);
  background:linear-gradient(180deg,#fdfefe,#dde2ea);
  box-shadow:inset 0 1px 0 #fff, 0 1px 0 rgba(22,31,45,.12);
}
.btn:hover{ background:linear-gradient(180deg,#fff,#e6ecf4); border-color:var(--ink); }
.btn:active{ box-shadow:inset 1px 1px 0 rgba(22,31,45,.22); }

.btn--nudge{ width:100%; }

/* ---- the transport ----
   one strip, and it is never blank. where the play head is, how
   long the file is, the bar between them, and the state in words.
   a transport that shows nothing until you press it is a mystery,
   and this is the only control in the client that makes a noise. */

.tr{
  flex:1 1 150px; min-width:0;
  display:flex; align-items:center; gap:8px;
}
.tr__btn{
  flex:0 0 auto;
  display:inline-flex; align-items:center; justify-content:center; gap:6px;
  height:26px; padding:0 12px;
  font-family:var(--ui); font-size:11.5px; font-weight:700;
  cursor:pointer; color:var(--ink);
  border:1px solid var(--line-d);
  border-radius:var(--r);
  background:linear-gradient(180deg,#fdfefe,#dde2ea);
  box-shadow:inset 0 1px 0 #fff, 0 1px 0 rgba(22,31,45,.12);
}
.tr__btn:hover{ background:linear-gradient(180deg,#fff,#e6ecf4); border-color:var(--ink); }
.tr__btn:active{ box-shadow:inset 1px 1px 0 rgba(22,31,45,.22); }
.tr__tri{
  width:0; height:0;
  border-left:8px solid currentColor;
  border-top:5px solid transparent;
  border-bottom:5px solid transparent;
}
/* two bars, because the button pauses the file rather than
   throwing the head away: press it again and it picks up where it
   stopped, which is the only way a seek bar is worth having. */
.tr__pause{ display:none; gap:3px; }
.tr__pause i{ width:3px; height:9px; background:currentColor; }
.file.is-playing .tr__btn .tr__tri{ display:none; }
.file.is-playing .tr__btn .tr__pause{ display:inline-flex; }
.file.is-playing .tr__btn{
  color:#eef4fb; border-color:var(--ink);
  background:linear-gradient(180deg,#33465f,#1b2a41);
}
.file.is-playing .tr__btn:hover{ color:#eef4fb; border-color:var(--ink); }

/* the head position and the length of the file, both in the
   machine's own register, both on screen from the start. */
.tr__time{
  flex:0 0 auto;
  display:inline-flex; align-items:baseline; gap:3px;
  font-family:var(--mono); font-size:10.5px; line-height:1;
  color:var(--ink-2);
  font-variant-numeric:tabular-nums;
}
.tr__el{ font-weight:700; color:var(--ink); }
.tr__slash{ color:var(--line-d); }
.tr__tot{ font-style:normal; color:var(--ink-3); }
.tr__track{
  position:relative;
  flex:1 1 40px; min-width:44px;
  height:8px;
  background:var(--w-sunk);
  border:1px solid var(--line);
  border-radius:1px;
  overflow:hidden;
  /* the bar is the head: it takes a click, a drag and an arrow */
  cursor:pointer;
  touch-action:none;
}
.tr__track:hover{ border-color:var(--ink); }
.tr__fill{
  position:absolute; top:0; bottom:0; left:0; width:0;
  background:var(--ink);
  transition:width .25s linear;
}
/* while the file is running the head is driven off the wall clock
   every frame, so the fill is exact and the easing would only lag
   it behind the record. */
.file.is-playing .tr__fill{ transition:none; }
.tr__state{
  flex:0 0 auto;
  font-family:var(--mono); font-size:9.5px;
  color:var(--ink-3);
}
.file.is-playing .tr__state{ color:var(--ink); }

/* ============================================================
   chat panel — head + crate index
   ============================================================ */

.chat{
  min-height:0; min-width:0;
  display:flex; flex-direction:column;
  background:var(--w-hi);
}

.chat__head{
  flex:0 0 auto;
  display:flex; align-items:center; gap:12px; flex-wrap:wrap;
  padding:6px 10px;
  background:var(--w-bg);
  border-bottom:1px solid var(--line);
}
.chat__who{ display:flex; align-items:center; gap:6px; min-width:0; }
.av{
  width:24px; height:24px;
  display:grid; place-items:center;
  border:1px solid var(--line-d); border-radius:2px;
}
.av svg{ width:100%; height:100%; }
.av--a{ background:var(--ink); margin-right:-10px; }
.av--t{ background:var(--w-hi); }
.chat__names{ margin-left:9px; min-width:0; }
.chat__title{ font-size:12px; font-weight:700; line-height:1.15; }
.chat__amp{ font-weight:400; color:var(--ink-2); }
.chat__sub{ font-family:var(--mono); font-size:9px; color:var(--ink-2); }

/* the history control. three conversations, three dates: the whole
   chronology of the friendship sits above the fold even when the
   log has only scrolled as far as the first record, and every chip
   is a jump into a dated thread rather than a year tab. the one
   you are looking at is ringed in marker; the one that is playing
   carries the note, and the note is also a way back to it. */
.hist{ margin-left:auto; display:flex; align-items:center; gap:6px; }
.hist__label{ font-size:9px; color:var(--ink-3); }
.crate{ display:flex; gap:5px; align-items:center; }
.crate__chip{
  position:relative;
  display:flex; align-items:center; gap:6px;
  padding:3px 8px 3px 3px;
  cursor:pointer;
  font-family:var(--mono); font-size:11px; color:var(--ink-2);
  border:1px solid var(--line);
  border-radius:2px;
  background:var(--w-hi);
}
.crate__chip img{
  width:22px; height:22px; object-fit:cover;
  border:1px solid var(--line-d); background:#101418;
}
.crate__chip:hover{ color:var(--ink); background:#fff; }
.crate__date{ font-family:var(--mono); font-size:10.5px; }
.crate__yr{ font-family:var(--mono); font-size:9.5px; color:var(--ink-3); }
/* the note means a file is still running, and it sits on the date
   it belongs to. clicking it goes back to the transport that is
   making the noise. */
.crate__np{ display:none; font-size:10px; color:var(--ink); }
.crate__chip.is-playing .crate__np{ display:inline; }
.crate__chip[aria-current="true"]{
  border-color:var(--ink); color:var(--ink); background:#fff;
  font-weight:700;
}
/* the ring is drawn, not switched on: it comes back every time the
   strip moves, because the year you are in is being circled by
   hand as you scroll past its rule. */
.crate__chip[aria-current="true"]::before{
  content:""; position:absolute; inset:-5px -6px;
  pointer-events:none;
  background-image:var(--loop);
  background-size:100% 100%;
  background-repeat:no-repeat;
  animation:ring-in .42s cubic-bezier(.2,.7,.3,1) both;
}
@keyframes ring-in{
  from{ opacity:0; transform:scale(.8) rotate(-6deg); }
  to{ opacity:1; transform:none; }
}
/* the unread tick. the only pink in the chrome, and it is small:
   it sits inside the chip so it can never hang off the window. it
   clears the moment the record it belongs to is on screen. */
.crate__chip.is-unread::after{
  content:"";
  position:absolute; top:3px; right:4px;
  width:7px; height:7px; border-radius:50%;
  background:var(--pink);
  box-shadow:0 0 0 1px var(--w-hi);
}

/* ============================================================
   chat log
   ============================================================ */

.chat__log{
  flex:1 1 auto; min-height:0;
  overflow:auto;
  padding:9px 12px 8px;
  background:var(--w-hi);
  scrollbar-color:#c0c8d2 #e6e9ee;
  scrollbar-width:thin;
}
.chat__log::-webkit-scrollbar{ width:15px; }
.chat__log::-webkit-scrollbar-track{ background:#e6e9ee; border-left:1px solid var(--line); }
.chat__log::-webkit-scrollbar-thumb{
  background:linear-gradient(90deg,#dfe4ea,#bfc7d2);
  border:1px solid var(--line-d); border-radius:2px;
}
.chat__log::-webkit-scrollbar-thumb:hover{ background:linear-gradient(90deg,#d2d9e2,#adb7c5); }

/* the archive is a conversation, so it breaks on days and not on
   catalogue years: the rule is the line a messenger draws when the
   date changes, and the year rides on it as the small part. */
.divider{
  display:flex; align-items:center; gap:8px;
  padding:4px 0 8px;
}
.divider::before,
.divider__r{ content:""; flex:1 1 auto; height:1px; background:var(--line); }
.divider__d{
  font-family:var(--ui);
  font-size:10.5px; color:var(--ink-2);
}
.divider__y{
  font-family:var(--mono);
  font-size:9.5px; letter-spacing:.06em;
  color:var(--ink-3);
}
.divider__y::before{ content:"·"; margin:0 5px; color:var(--line-d); }

/* ---- messages, grouped into runs ----
   a run of lines from one person is one block: avatar, name and
   clock appear once, at the top of it.

   the spacing is the other half of the grouping. inside a burst
   the lines sit on top of each other, because they are one
   thought arriving in pieces; between bursts there is a pause,
   and around a transfer there is a longer one, because a file
   landing is the event the whole conversation is built out of. */

.msg{
  display:grid;
  grid-template-columns:26px minmax(0,1fr);
  gap:8px;
  padding-bottom:3px;
  /* backwards, not both: a filling-forward transform animation
     makes the row a containing block, and the open-with menu that
     hangs off it would then be positioned against the row. */
  animation:pop .2s ease-out backwards;
}
@keyframes pop{
  from{ opacity:0; transform:translateY(3px); }
  to{ opacity:1; transform:none; }
}
.msg--cont{ padding-bottom:0; }
.msg--runEnd{ padding-bottom:14px; }
.msg--cont .msg__av{ visibility:hidden; }
.msg--cont .msg__meta{ display:none; }

.msg__av{
  width:26px; height:26px;
  display:grid; place-items:center;
  border:1px solid var(--line-d); border-radius:2px;
}
.msg__av svg{ width:100%; height:100%; }
.msg[data-who="aria"] .msg__av{ background:var(--ink); }
.msg[data-who="tano"] .msg__av{ background:var(--w-bg); }

.msg__body{ min-width:0; }
.msg__meta{ display:flex; align-items:baseline; gap:6px; margin-bottom:1px; }
.msg__name{ font-size:11px; font-weight:700; }
.msg[data-who="tano"] .msg__name{ color:var(--ink-2); }
.msg__time{ font-family:var(--mono); font-size:9px; color:var(--ink-2); }
/* every clock in this log is one instant said twice — the same
   minute is 3am in one city and 4pm in the other — so a stamp is
   never left to be read as one number. the city it belongs to sits
   beside the stamp, and the other city's time is on the hover. the
   stamp itself stays a clock and nothing else. */
.msg__tz,
.msg__sending{ font-family:var(--mono); font-size:9px; }
.msg__tz{ color:var(--ink-3); }
.msg__sending{ color:var(--ink-2); }
.msg__text{
  max-width:58ch;
  font-size:12.5px; line-height:1.45; color:var(--ink);
  overflow-wrap:anywhere;
}

/* what they draw inside a line of chat. same pen as everything
   else they have marked up, at the size you draw in a margin. */
.doodle{
  display:inline-block;
  width:14px; height:14px;
  margin-left:4px;
  vertical-align:-2.5px;
  color:var(--pen);
}
.doodle svg{ width:100%; height:100%; display:block; }

.msg--me{ grid-template-columns:minmax(0,1fr) 26px; justify-items:end; }
.msg--me .msg__av{ grid-column:2; grid-row:1; background:var(--w-bg); }
.msg--me .msg__body{ grid-column:1; grid-row:1; text-align:right; }
.msg--me .msg__meta{ flex-direction:row-reverse; }
.msg--me .msg__name{ color:var(--ink-2); }
.msg--me .msg__text{ margin-left:auto; }

/* ============================================================
   the release arrives as a completed file transfer

   a transfer is a row, not a card. what a messenger puts on
   screen when a file lands is: a small scan of the sleeve, the
   name she gave it, the facts of the transfer itself, and a
   transport. nothing in it stands between the two of them and
   the next line of chat.

   the record is still a record and the numbers are still the
   numbers — they are just not in the way. the scan opens in the
   viewer, at the size a sleeve is worth looking at; the
   machine's property list sits behind one disclosure. a record
   is not a specification and a chat is not a shop window.
   ============================================================ */

.msg--file{ padding-top:3px; padding-bottom:13px; }

.file{
  --thumb: 84px;
  --pad: 4px;
  --stk: 14px;
  position:relative;
  margin-top:2px;
  /* a transfer is as wide as what is in it, and no wider. a wav
     carries a transport, so it is the wide one; a picture carries
     a picture, so it stops where its own longest line stops. both
     of them leave the rest of the row alone, because the next
     thing she says comes right after. */
  width:min(100%,600px);
  border:1px solid var(--line-d);
  border-radius:var(--r);
  background:#fff;
  box-shadow:1px 1px 0 rgba(22,31,45,.07);
}
/* a picture is not a file with a bar in it: the scan, the name it
   was given, what came off the camera and one action — so the card
   is exactly that wide. */
.file--paper{ width:fit-content; max-width:100%; }
.file.is-new{ border-color:var(--ink-2); }
.file.is-playing{ border-color:var(--ink); }
/* a file that has not finished arriving: the same transfer the
   facts line is counting, drawn as a bar along the bottom edge of
   the card. it is the only progress bar in the client, and it is
   gone in a second. */
.file.is-receiving::after{
  content:""; position:absolute; left:0; right:0; bottom:0; height:2px;
  background:var(--ink-2);
  border-radius:0 0 var(--r) var(--r);
  transform-origin:left center;
  animation:recv-in .9s linear both;
}
@keyframes recv-in{ from{ transform:scaleX(0); } to{ transform:scaleX(1); } }
/* while it is counting the megabytes in, the line is already saying
   how many there are, so the size is not said twice. */
.file.is-receiving .fact--mb{ display:none; }

/* the row: the scan, then everything a machine would say about
   the file, on three tight lines. */
.file__row{
  display:grid;
  grid-template-columns:auto minmax(0,1fr);
  gap:11px;
  align-items:start;
  padding:8px 10px 9px;
}
.file__info{ min-width:0; display:flex; flex-direction:column; gap:3px; }

/* the filename is what she actually called it, brackets and all.
   it is the title of the thing, and it is set to be read. */
.file__name{
  min-width:0;
  font-family:var(--mono); font-size:11px; font-weight:700; line-height:1.35;
  color:var(--ink);
  overflow-wrap:anywhere;
}
/* pink is one thing in this client, and it is spelled out. it
   clears when the record is on screen, never before. */
.file__new{
  display:none;
  margin-left:6px;
  padding:0 5px 1px;
  font-family:var(--ui); font-size:9px; font-weight:700;
  white-space:nowrap;
  color:#fff; background:var(--pink);
  /* a scrap of pink tape, torn on the right */
  clip-path:polygon(0 0,100% 0,88% 50%,100% 100%,0 100%);
}
.file.is-new .file__new{ display:inline-block; }

/* the facts of the transfer, on one line, where a machine would
   put them: what came off the bounce, not a spec sheet. the four
   that matter at a glance, and the rest behind the disclosure. */
.file__facts{
  display:flex; flex-wrap:wrap; align-items:center;
  gap:2px 0;
  font-family:var(--mono); font-size:10.5px; line-height:1.5; color:var(--ink-2);
}
.file__facts > * + *::before{
  content:"·"; margin:0 6px; color:var(--line-d);
}
/* the transfer mark rides on the facts line, not above it */
.file__facts .arrive{
  display:inline-block; vertical-align:-2.5px;
  width:13px; height:13px; margin-right:4px;
  color:var(--ink-2);
}
.file__state{ color:var(--ink-2); }

/* ============================================================
   the record's physical history

   ONE handling logic for all three: the sleeve went on a flatbed
   scanner, and everything you are looking at is that scan. a
   paper margin round the print, the print's own hard edge
   sitting on the paper. that is the base — and it is flat,
   because a scan is flat. no lamp, no falloff, no drop shadow
   lifting the sheet off the page: the moment a record has a soft
   grey glow over it, it stops being a record and becomes a
   product shot.

   each release mutates the same scan, and only the mutation:
     copy   the scan came out of a photocopier. contrast, toner
            grain, and the corner the drum starved of toner
     sleeve the scan, gone round once with a marker
     label  the scan with a paper insert stuck over it
   ============================================================ */

.file__frame{
  position:relative;
  width:var(--thumb,84px);
  flex:0 0 auto;
  padding:var(--pad);
  background:var(--paper);
  /* one hard edge and nothing else. the sheet is a sheet. */
  box-shadow:0 0 0 1px rgba(22,31,45,.3);
}
.file__art{
  position:relative; z-index:1;
  display:block;
  width:100%; aspect-ratio:1;
  padding:0; margin:0;
  cursor:zoom-in;
  border:0; background:transparent;
  overflow:hidden;
  box-shadow:0 0 0 1px rgba(22,31,45,.38);
}
.file__art img{ width:100%; height:100%; object-fit:cover; }
.file__art::after{
  content:""; position:absolute; inset:0; pointer-events:none;
  box-shadow:inset 0 0 0 0 var(--ink);
  transition:box-shadow .14s ease;
}
.file__art:hover::after{ box-shadow:inset 0 0 0 2px var(--ink); }
.file__frame.is-big .file__art{ cursor:default; }
.file__frame.is-big .file__art::after{ content:none; }

/* the ring again: the record that is playing is ringed in marker,
   same pen as the selector and the buddy */
.file.is-playing .file__frame::after{
  content:""; position:absolute; inset:-4px; z-index:4;
  pointer-events:none;
  background-image:var(--loop);
  background-size:100% 100%;
  background-repeat:no-repeat;
}

/* handwriting — one hand, three kinds of pressure. at thumbnail
   size there is no margin to write in, so it waits for the
   viewer, where the sleeve is a sleeve again. */
.file__scrawl{ display:none; }
.file__scrawl--ballpoint{
  font-weight:400; font-size:13.5px; color:var(--ink-2);
  transform:rotate(1.2deg);
}
.file__frame.is-big .file__scrawl{
  display:block;
  position:absolute; z-index:5;
  left:calc(var(--pad) + 1px); right:calc(var(--pad) + 1px);
  bottom:calc(var(--pad) * .5);
  font-family:var(--hand);
  font-size:15px; font-weight:700; line-height:1.05;
  color:var(--pen);
  transform:rotate(-2.2deg);
  opacity:.92;
  pointer-events:none;
  overflow-wrap:anywhere;
}

/* --- lg001: the scan, photocopied ---
   the mutation is generation loss: it went through a copier, so
   the print is flattened to high-contrast grey, the sheet is
   copier paper rather than sleeve paper, there is toner grain
   over everything, and the drum starved one corner. no second
   pass, no roller band, no annotation — nothing that would read
   as a layer floating over the artwork. */

.file--copy .file__frame{
  background:var(--copy);
  box-shadow:0 0 0 1px rgba(22,31,45,.22);
}
.file--copy .file__art img{
  filter:grayscale(1) contrast(1.45) brightness(1.07);
}
.file--copy .file__art{
  box-shadow:0 0 0 1px rgba(22,31,45,.42);
}
.file__toner{
  position:absolute; inset:var(--pad); z-index:3;
  pointer-events:none;
  mix-blend-mode:multiply;
  opacity:.4;
  background-image:radial-gradient(rgba(22,31,45,.6) .5px, transparent .9px);
  background-size:3px 3px;
}
/* the toner starved here: paper shows straight through, with the
   hard torn edge a starving drum actually leaves */
.file__starve{
  position:absolute; inset:var(--pad); z-index:3;
  pointer-events:none;
  background:var(--copy);
  clip-path:polygon(100% 62%,100% 100%,46% 100%,60% 91%,50% 81%,70% 72%,84% 66%);
}

/* --- lg002: the same scan, gone round once with a marker ---
   one freehand loop drawn round the print and overshot at the
   top, and the same pen used once more in the margin. the only
   difference from the base is that this sleeve has a deeper
   bottom margin — which is exactly where you write. */

.file__pen{
  position:absolute; inset:var(--pad); z-index:4;
  pointer-events:none;
}
.file--sleeve .file__frame{ padding:var(--pad) var(--pad) calc(var(--pad) * 2); }
.file--sleeve .file__pen{ inset:var(--pad) var(--pad) calc(var(--pad) * 2); }
/* marker is opaque: the strokes carry a paper-coloured halo so
   they still read where they cross the black print */
.file__pen svg{
  width:100%; height:100%; overflow:visible;
  filter:drop-shadow(0 0 1.7px var(--paper)) drop-shadow(0 0 1px var(--paper));
}
.file__pen path,
.file__pen ellipse{
  fill:none; stroke:var(--pen); stroke-linecap:round; stroke-linejoin:round;
  opacity:.8;
}

/* --- lg003: the same scan, with a paper insert over it ---
   the doodles off the two records that came before, re-cut as
   paper stickers and stuck over the black. two of them at
   thumbnail size, all four in the viewer — a corner is a corner
   at any scale, and the corner is the whole point. */

.file--label .file__frame{ background:var(--paper-2); }
.file--label .file__art{
  clip-path:polygon(0 0,100% 0,100% 84%,94% 88%,99% 92%,91% 96%,96% 100%,0 100%);
}
/* the insert is a cluster of paper scraps stuck over the bottom
   left corner of the print, the way you stick anything down: on
   top of the thing it belongs to, hanging off the edge. */
.file__stickers{
  position:absolute; left:calc(var(--stk) * -.22); bottom:calc(var(--stk) * -.28);
  z-index:6;
  display:flex; align-items:flex-end;
  pointer-events:none;
}
.file__stk{
  flex:0 0 auto;
  width:var(--stk); height:var(--stk); padding:1px;
  display:grid; place-items:center;
  background:#fff;
  box-shadow:0 1px 2px rgba(10,14,20,.45), 0 0 0 1px rgba(10,14,20,.18);
}
.file__stk svg{ width:100%; height:100%; }
.file__stk:nth-child(1){ transform:rotate(-10deg) translate(3%,10%); }
.file__stk:nth-child(2){ transform:rotate(8deg) translate(-27%,-10%); }
.file__stk:nth-child(3){ transform:rotate(-5deg) translate(-53%,13%); display:none; }
.file__stk--txt{
  width:calc(var(--stk) * .88); height:calc(var(--stk) * .74); padding:0 1px;
  font-family:var(--hand); font-size:calc(var(--stk) * .5); font-weight:700;
  color:var(--pen);
  background:var(--paper);
  transform:rotate(-13deg) translate(-83%,-37%);
  display:none;
}
.file__frame.is-big .file__stk:nth-child(3),
.file__frame.is-big .file__stk--txt{ display:grid; }

/* --- the action line ---
   a transport, a disclosure and one destinations control, and
   that is everything a transfer offers. the transport takes the
   slack because it is the only one of the three that is doing
   something continuous. */

.file__act{
  display:flex; flex-wrap:wrap; align-items:center; gap:6px 9px;
  margin-top:1px;
}

/* --- the disclosure ---
   the numbers off the bounce, the way a machine would list them.
   they are the machine's, not the conversation's, so they arrive
   when they are asked for and not before. */
.file__more{
  flex:0 0 auto;
  height:24px; padding:0 8px;
  font-family:var(--ui); font-size:10.5px; font-weight:700;
  cursor:pointer; color:var(--ink-2);
  background:transparent;
  border:1px solid var(--line);
  border-radius:2px;
}
.file__more::after{ content:"▾"; margin-left:5px; font-size:9px; }
.file__more[aria-expanded="true"]{ color:var(--ink); border-color:var(--ink); background:#fff; }
.file__more[aria-expanded="true"]::after{ content:"▴"; }
.file__more:hover{ color:var(--ink); border-color:var(--ink); background:#fff; }

/* --- open with ---
   where else the file also is. one control, because a row of
   platform names standing open in a conversation is a row of
   platform names and not what arrived. the menu is fixed so the
   log it belongs to can never clip it. */
.ow{ position:relative; flex:0 0 auto; margin-left:auto; }
.ow__btn{
  display:inline-flex; align-items:center; gap:5px;
  height:24px; padding:0 9px;
  font-family:var(--ui); font-size:10.5px; font-weight:700;
  cursor:pointer; color:var(--ink-2);
  background:transparent;
  border:1px solid var(--line);
  border-radius:2px;
}
.ow__btn:hover,
.ow__btn[aria-expanded="true"]{ color:var(--ink); border-color:var(--ink); background:#fff; }
.ow__car{ font-size:9px; }
.ow__menu{
  position:fixed; z-index:90;
  min-width:136px; padding:3px;
  background:#fff;
  border:1px solid var(--line-d);
  border-radius:2px;
  box-shadow:2px 2px 0 rgba(22,31,45,.2);
}
.ow__menu[hidden]{ display:none; }
.ow__item{
  display:block;
  width:100%;
  padding:6px 9px;
  font-family:var(--ui); font-size:11px; line-height:1.2;
  color:var(--ink); text-decoration:none; text-align:left;
  background:none; border:0;
  border-radius:2px;
  cursor:pointer;
}
.ow__item:hover,.ow__item:focus-visible{ background:var(--w-sunk); }
.ow__sep{ display:block; height:1px; margin:3px 0; background:var(--line); }

/* ---- liner notes ----
   the notes open as what they are: a text file on white, in the
   machine's register, with the machine's own line at the foot. */
.note{
  width:100%;
  background:#fff;
  border:1px solid var(--line-d);
}
.note__bar{
  padding:4px 7px;
  font-family:var(--mono); font-size:10px; color:var(--ink-2);
  background:var(--w-panel);
  border-bottom:1px solid var(--line);
}
.note__body{
  padding:9px 10px 11px;
  font-family:var(--mono); font-size:11px; line-height:1.65;
  color:var(--ink);
  overflow-wrap:anywhere;
}
.note__body p{ min-height:1em; }

.file__panel{
  padding:8px 10px 10px;
  border-top:1px solid var(--line);
  background:var(--w-bg);
  border-radius:0 0 var(--r) var(--r);
}
.file__panel[hidden]{ display:none; }
.file__props{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(140px,1fr));
  gap:2px 18px;
}
.file__props > div{
  min-width:0;
  display:grid; grid-template-columns:auto minmax(0,1fr);
  gap:9px; align-items:baseline;
}
.file__props dt{
  font-family:var(--ui);
  font-size:9.5px; line-height:1.7;
  color:var(--ink-3);
}
.file__props dd{
  font-family:var(--mono);
  font-size:10.5px; line-height:1.7;
  letter-spacing:.01em;
  color:var(--ink-2);
  overflow-wrap:anywhere;
}

/* ============================================================
   the paper transfers — the label as a physical object

   the same row with a different payload: a scan of the doodle, a
   photograph printed off a phone, a sleeve gone over in marker
   before it was posted. no transport, because you cannot play a
   jpeg, and the name on each of them is written by hand because
   that is how it was written.

   the two hands are the two hands that are already all over this
   client: aria presses a blunt marker into the paper, tano works
   in thin ballpoint. the handwriting is the only place in the
   log where the two of them are told apart by pressure.
   ============================================================ */

.file--paper .file__name--hand{
  font-family:var(--hand);
  line-height:1.25; letter-spacing:0;
}
/* the pressure is the difference: aria's marker sits heavy and
   dark on the paper, tano's ballpoint is thin and grey-blue. */
.file--paper .file__name--marker{ font-size:14px; font-weight:700; color:var(--pen); }
.file--paper .file__name--ballpoint{ font-size:13px; font-weight:400; color:var(--ink-2); }

.file__glyph{
  display:inline-block; width:13px; height:13px;
  margin-left:6px; vertical-align:-2px;
  color:var(--pen);
}
.file__glyph svg{ width:100%; height:100%; display:block; }

.file__scrap{
  font-family:var(--hand);
  font-size:12px; line-height:1.35;
  color:var(--pen);
  transform:rotate(-.7deg);
  transform-origin:left center;
}
.file__scrap--ballpoint{ font-weight:400; color:var(--ink-2); transform:rotate(.5deg); }

/* --- the crop ---
   these are pictures of one part of one object, so they are crops
   of the record's own scan and not a second drawing of it. */
.file__crop{
  width:100%; height:100%;
  object-fit:cover;
  transform-origin:50% 50%;
}
.file__crop--doodle{ transform:scale(3.1); transform-origin:27% 49%; }
.file__crop--faces{ transform:scale(2.2); transform-origin:64% 64%; }
.file__crop--label{ transform:scale(1.7); transform-origin:50% 45%; }

/* --- the copy: it went through the copier, so it has toner on it --- */
.file--copy.file--paper .file__frame{ background:var(--copy); }
.file--copy.file--paper .file__crop{
  filter:grayscale(1) contrast(1.5) brightness(1.06);
}

/* --- the dither: printed off a phone, one screen of dots, no
   grey, and the cat marked on the picture in ballpoint --- */
.file--dither .file__frame{ background:#fff; }
.file--dither .file__crop{ filter:grayscale(1) contrast(1.75) brightness(1.1); }
.file__dots{
  position:absolute; inset:var(--pad); z-index:3;
  pointer-events:none;
  mix-blend-mode:multiply;
  opacity:.5;
  background-image:radial-gradient(rgba(16,20,24,.95) .7px, transparent 1.1px);
  background-size:2px 2px;
}
.file__pin{
  position:absolute; z-index:5;
  left:calc(var(--pad) + 3%); bottom:calc(var(--pad) + 3%);
  width:28%; height:28%;
  color:var(--pen);
  pointer-events:none;
}
.file__pin svg{ width:100%; height:100%; display:block; }

/* --- the mark-up: the sleeve, underlined and arrowed in marker
   before it was posted, with the note written in the margin --- */
.file--mark .file__frame{ background:var(--paper-2); }
.file__markup{
  position:absolute; inset:var(--pad); z-index:4;
  pointer-events:none;
}
.file__markup svg{ width:100%; height:100%; display:block; }
.file__markup path{
  fill:none; stroke:var(--pen); stroke-width:3.6;
  stroke-linecap:round; stroke-linejoin:round;
  opacity:.82;
}
.file--mark .file__frame.is-big{ padding-bottom:calc(var(--pad) * 2 + 4px); }
.file--mark .file__frame.is-big .file__markup{ inset:var(--pad) var(--pad) calc(var(--pad) * 2 + 4px); }

/* the one action a picture has */
.file__open1{
  flex:0 0 auto;
  height:24px; padding:0 10px;
  font-family:var(--ui); font-size:10.5px; font-weight:700;
  cursor:pointer; color:var(--ink-2);
  background:transparent;
  border:1px solid var(--line);
  border-radius:2px;
}
.file__open1:hover{ color:var(--ink); border-color:var(--ink); background:#fff; }

/* ---- typing indicator ---- */

.typing{
  display:flex; align-items:center; gap:7px;
  padding:0 0 13px 34px;
  font-size:10.5px; color:var(--ink-2);
}
.typing__av{
  width:20px; height:20px;
  display:grid; place-items:center;
  border:1px solid var(--line-d); border-radius:2px;
}
.typing__av svg{ width:100%; height:100%; }
.typing[data-who="aria"] .typing__av{ background:var(--ink); }
.typing[data-who="tano"] .typing__av{ background:var(--w-bg); }
.typing__dots{ display:inline-flex; gap:3px; }
.typing__dots i{
  width:3px; height:3px; border-radius:50%;
  background:var(--ink-2);
  animation:blip 1s infinite ease-in-out;
}
.typing__dots i:nth-child(2){ animation-delay:.15s; }
.typing__dots i:nth-child(3){ animation-delay:.3s; }
@keyframes blip{
  0%,60%,100%{ opacity:.25; transform:translateY(0); }
  30%{ opacity:1; transform:translateY(-2px); }
}

/* ---- system line (nudges etc) ---- */

.sysline{
  flex:0 0 auto;
  padding:4px 11px;
  text-align:center;
  font-size:10px; color:var(--ink);
  background:var(--w-sunk);
  border-top:1px solid var(--line);
}
.sysline[hidden]{ display:none; }

/* ---- now playing ----
   one record runs at a time, and while one is running the client
   always says so in the same place: which file, where the head is,
   and a control that puts the transport back in front of you
   however far down the archive you have scrolled. the buddy list
   says it too, from her name, the way msn broadcast whatever was
   on your machine — this is the same fact with a way back to it. */
.np{
  flex:0 0 auto;
  display:flex; align-items:center; gap:8px;
  padding:5px 9px;
  background:var(--w-sunk);
  border-top:1px solid var(--line);
}
.np[hidden]{ display:none; }
.np__note{ flex:0 0 auto; font-size:10px; color:var(--ink); }
.np__txt{
  flex:1 1 auto; min-width:0;
  display:flex; align-items:baseline; gap:6px;
  font-family:var(--mono); font-size:10px; line-height:1.3;
  white-space:nowrap; overflow:hidden;
}
.np__cat{ flex:0 0 auto; font-weight:700; color:var(--ink); }
.np__file{ min-width:0; overflow:hidden; text-overflow:ellipsis; color:var(--ink-2); }
.np__time{
  flex:0 0 auto;
  font-family:var(--mono); font-size:10px; color:var(--ink-2);
  font-variant-numeric:tabular-nums;
}
.np__btn{
  flex:0 0 auto;
  height:20px; padding:0 8px;
  font-family:var(--ui); font-size:10px; font-weight:700;
  cursor:pointer; color:var(--ink-2);
  background:transparent;
  border:1px solid var(--line);
  border-radius:2px;
}
.np__btn:hover{ color:var(--ink); border-color:var(--ink); background:#fff; }

/* ============================================================
   composer

   one contract, and it is written under the box so there is no
   guessing at what the button does: the line goes into the thread
   you are reading, and they usually answer.
   ============================================================ */

.compose{
  flex:0 0 auto;
  padding:7px 9px 6px;
  background:var(--w-bg);
  border-top:1px solid var(--line);
}
.compose__row{ display:flex; gap:6px; }
.compose__in{
  flex:1 1 auto; min-width:0;
  height:25px; padding:0 7px;
  font:400 11px/1 var(--ui); color:var(--ink);
  background:#fff;
  border:1px solid var(--line-d);
  border-radius:2px;
  box-shadow:inset 1px 1px 0 rgba(22,31,45,.12);
}
.compose__in::placeholder{ color:var(--ink-3); }
.compose__in:focus{
  outline:none; border-color:var(--ink);
  box-shadow:inset 1px 1px 0 rgba(22,31,45,.08);
}
.compose__hint{
  margin-top:4px;
  font-family:var(--mono); font-size:9px; line-height:1.2;
  color:var(--ink-3);
}

/* ============================================================
   artwork lightbox
   ============================================================ */

.lb{
  position:fixed; inset:0; z-index:80;
  display:grid; place-items:center;
  padding:22px;
  background:rgba(22,31,45,.72);
}
.lb[hidden]{ display:none; }
.lb__card{
  position:relative;
  width:min(460px,100%);
  max-height:calc(100vh - 44px);
  overflow:auto;
  padding:11px;
  background:var(--w-bg);
  border:1px solid var(--line-d);
  border-radius:var(--r);
  box-shadow:6px 6px 0 rgba(22,31,45,.3);
}
/* the viewer is where the sleeve is a sleeve: the same scan, the
   same mutation, at the size the handling was done for. */
.lb__art{ display:flex; justify-content:center; }
.lb__art .file{
  width:100%; margin:0;
  border:0; background:none; box-shadow:none;
}
.lb__art .file__frame{
  --thumb:100%;
  --pad:10px;
  --stk:34px;
  max-width:none;
}
.lb__alt{
  margin-top:9px;
  font-family:var(--mono); font-size:9.5px; line-height:1.6; color:var(--ink-2);
}
.lb__x{
  position:absolute; top:15px; right:15px;
  /* a crop is scaled inside a frame that does not clip, so in the
     viewer the print runs past its own edge and would sit on top of
     the close. the close is chrome and chrome wins. */
  z-index:6;
  width:22px; height:20px; padding:0;
  display:grid; place-items:center;
  font-size:12px; font-weight:700; line-height:1;
  cursor:pointer; color:#fff;
  background:rgba(22,31,45,.72);
  border:1px solid rgba(255,255,255,.4);
  border-radius:2px;
}
.lb__x:hover{ background:var(--ink); border-color:#fff; }

/* ============================================================
   nudge
   ============================================================ */

@keyframes shake{
  0%,100%{ transform:translate(0,0) rotate(0); }
  12%{ transform:translate(-8px,4px) rotate(-.4deg); }
  26%{ transform:translate(7px,-4px) rotate(.4deg); }
  40%{ transform:translate(-6px,-3px) rotate(-.3deg); }
  55%{ transform:translate(5px,3px) rotate(.3deg); }
  70%{ transform:translate(-4px,2px) rotate(-.2deg); }
  85%{ transform:translate(2px,-1px) rotate(.1deg); }
}
@keyframes barflash{
  0%,100%{ background:linear-gradient(180deg,var(--bar-1),var(--bar-2)); }
  35%{ background:linear-gradient(180deg,#33465f,#1b2a41); }
}
/* a nudge is the whole window, and it is short: the bar flashes
   while the window moves, and the window is back inside half a
   second. with motion turned down the window does not move at all
   — the title bar flashes instead, which is the same message. */
.win.is-shaking{ animation:shake .42s cubic-bezier(.36,.07,.19,.97) both; }
.win.is-shaking .win__bar{ animation:barflash .42s ease-out both; }
.win.is-nudging .win__bar{ animation:barflash .42s ease-out 3 both; }

@media (prefers-reduced-motion:reduce){
  .win.is-nudging .win__bar{
    animation-duration:.42s !important;
    animation-iteration-count:3 !important;
  }
}

/* ============================================================
   focus mode — clicking a buddy dims the other one
   ============================================================ */

.chat__log[data-focus] .msg{ opacity:.26; transition:opacity .18s ease; }
.chat__log[data-focus="aria"] .msg[data-who="aria"]{ opacity:1; }
.chat__log[data-focus="tano"] .msg[data-who="tano"]{ opacity:1; }

/* ============================================================
   mobile — the same client, the same behaviour.

   ONE scrolling model at every width: a fixed messenger shell
   with the chat scrolling inside it and the composer pinned to
   the bottom of the shell. nothing floats over the conversation
   and nothing escapes the window.

   what changes is only how much of the furniture survives. the
   buddy list is not a panel on a phone, it is one thin status
   line: who is online, and the nudge. the releases keep the
   index, because jumping between records is behaviour and not a
   box. and the attachment keeps the proportions it has on the
   desktop — thumb, title, transport — because that is the thing
   that makes the log read as messaging at 390.
   ============================================================ */

@media (max-width:860px){
  /* dvh where it exists: the composer has to stay above the
     on-screen keyboard, and 100vh does not know about it. */
  html,body{ height:100%; overflow:hidden; }
  @supports (height:100dvh){ html,body{ height:100dvh; } }
  .desk{
    height:100%; min-height:0; padding:0; display:block;
    background-image:none; background:var(--w-bg);
  }
  .win{
    width:100%; height:100%; max-height:none;
    border:0; border-radius:0; box-shadow:none;
    overflow:hidden;
  }
  .win.is-wide{ width:100%; }
  .win__bar{ flex:0 0 26px; height:26px; }
  .wb--x{ display:none; }
  .win__sub{ display:none; }
  .bar__stk{ margin-left:auto; }

  .win__body{ display:flex; flex-direction:column; min-height:0; }

  /* --- the buddy list, as a thin status line --- */
  .buds{
    order:1; flex:0 0 26px; height:26px;
    flex-direction:row; align-items:center; gap:8px;
    padding:0 7px 0 9px;
    background:var(--w-panel);
    border-right:0; border-bottom:1px solid var(--line);
    overflow:hidden;
  }
  /* the minimize button does on a phone what it says on the box */
  .win.is-slim .buds{ display:none; }
  .buds__head{ display:none; }
  .buds__top{
    flex:1 1 auto; min-width:0;
    flex-direction:row; align-items:center;
    padding:0; border:0;
  }
  .buds__scroll{
    flex:1 1 auto; min-width:0;
    display:flex; align-items:center; gap:7px;
    padding:0; overflow:hidden;
  }
  .buds__list{ display:flex; align-items:center; gap:9px; min-width:0; }
  .buds__group{ order:2; display:block; padding:0; font-size:9px; }
  .buddy{
    grid-template-columns:20px auto;
    gap:5px; padding:0;
    background:none; border:0; border-radius:0;
  }
  .buddy:hover,.buddy.is-focus{ background:none; }
  .buddy__av{ width:20px; height:20px; }
  .buddy__name{ font-size:10.5px; }
  .buddy__city,.buddy__np,.buddy__pm{ display:none; }
  /* at this width the status line is a status line and there is no
     room for the filename, so the mark stands in for it. */
  .buddy.is-np .buddy__note{ display:inline; margin-left:3px; font-size:9px; color:var(--ink-2); }
  .buddy__av .dot{ width:8px; height:8px; right:-3px; bottom:-3px; box-shadow:0 0 0 1.5px var(--w-panel); }
  .buddy.is-focus .buddy__av::after{ inset:-3px; }

  .buds__low{
    flex:0 0 auto;
    flex-direction:row; align-items:center;
    padding:0; border:0; background:none;
  }
  .buds__pm{ display:none; }
  .buds__foot{ padding:0; border:0; background:none; }
  .btn--nudge{ width:auto; height:19px; padding:0 8px; font-size:10px; }

  /* the conversation is the only thing that moves */
  .chat{ order:2; flex:1 1 auto; min-height:0; min-width:0; }
  .chat__head{
    flex:0 0 auto;
    padding:0; gap:0;
    background:var(--w-bg);
  }
  .chat__who{ display:none; }
  .hist{ margin:0; width:100%; gap:0; }
  .hist__label{ display:none; }
  .crate{ width:100%; gap:0; }
  .crate__chip{
    flex:1 1 0; justify-content:center; gap:4px;
    padding:4px 3px;
    font-size:10.5px;
    border:0; border-right:1px solid var(--line); border-radius:0;
  }
  .crate__chip:last-child{ border-right:0; }
  .crate__chip img{ width:19px; height:19px; }
  .crate__date{ font-size:10px; }
  .crate__yr{ font-size:9px; }
  /* the ring needs room the strip does not have: the current
     release is underlined instead, and the underline lives on
     ::before so the unread tape keeps ::after to itself. */
  .crate__chip[aria-current="true"]{ background:#fff; }
  .crate__chip[aria-current="true"]::before{
    content:""; inset:auto 0 0 0; height:3px;
    background:var(--pen); background-image:none;
  }
  .crate__chip.is-unread::after{ top:3px; right:6px; }

  .chat__log{ flex:1 1 auto; min-height:0; overflow:auto; padding:8px 9px 6px; }
  .divider{ padding:3px 0 7px; }
  .msg{ grid-template-columns:22px minmax(0,1fr); gap:6px; padding-bottom:2px; }
  .msg--cont{ padding-bottom:0; }
  .msg--runEnd{ padding-bottom:11px; }
  .msg__av{ width:22px; height:22px; }
  .msg--me{ grid-template-columns:minmax(0,1fr) 22px; }
  .typing{ padding-left:28px; }

  /* --- the attachment, at 390 ---
     the artwork and the name she gave the file are what carry at
     this width; the transfer's own metadata is cut back to the
     two facts that mean something on a phone, and everything
     that is a destination collapses into the one open-with
     control. the transport keeps its own line, because the head
     position and the length of the file are the two things you
     look at while it plays. */
  .file{ --thumb:68px; --pad:3px; --stk:13px; width:100%; }
  /* the picture is still a picture at this width: it stops where its
     own longest line stops, and the conversation keeps the rest. */
  .file--paper{ width:fit-content; max-width:100%; }
  .file__row{ gap:9px; padding:7px 8px 8px; }
  .file__name{ font-size:10.5px; }
  .file__facts{ font-size:10px; }
  .fact--state{ display:none; }
  .fact--state + .fact::before{ content:none; margin:0; }
  .tr{ flex:1 1 100%; gap:6px; }
  .tr__btn{ height:28px; padding:0 10px; font-size:11px; }
  .tr__time{ font-size:10px; gap:2px; }
  .tr__track{ flex:1 1 30px; min-width:30px; }
  .tr__state{ font-size:9px; }
  .file__more{ height:26px; padding:0 9px; font-size:11px; }
  .ow{ margin-left:auto; }
  .ow__btn{ height:26px; padding:0 9px; font-size:11px; }
  .ow__item{ padding:8px 10px; font-size:12px; }
  .file__open1{ height:26px; padding:0 10px; font-size:11px; }
  .file--paper .file__name--marker{ font-size:13.5px; }
  .file--paper .file__name--ballpoint{ font-size:12.5px; }
  .file__scrap{ font-size:11.5px; }  .file__props{ grid-template-columns:repeat(auto-fit,minmax(132px,1fr)); gap:2px 12px; }

  .lb{ padding:12px; }
  .lb__card{ padding:9px; }
}

@media (max-width:400px){
  .buds{ gap:6px; padding-right:6px; }
  .buds__list{ gap:7px; }
  .buddy__name{ font-size:10px; }
  .file{ --thumb:64px; }
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{ animation-duration:.001ms !important; animation-iteration-count:1 !important; }
  .win{ transition:none; }
  .tr__fill{ transition:none; }
  /* the transfer still counts itself in, but it does not sweep */
  .file.is-receiving::after{ animation:none; transform:scaleX(0); }
}

/* ============================================================
   composition

   the messenger is the composition, and it is a window on a desk,
   not a page: it holds a width you could leave open beside
   something else, and the desk shows on both sides of it. what is
   inside is dense; what is around it is wallpaper.

   and the attachment is a transfer row at every width — one
   line of the conversation among the others, with the record
   inside it still a record, and the row only as wide as what is
   actually in it. the frame around the chrome does not move.
   ============================================================ */

.win{
  width:min(960px,100%);
  height:calc(100vh - 20px);
}
.win.is-wide{ width:min(1120px,100%); }

/* a scan, not a product shot: the sheet has one hard edge and no
   lamp falling across it. */
.file__frame::before{ content:none; background:none; }

@media (max-width:860px){
  .win{
    width:100%; height:100%; max-height:none;
    border:0; border-radius:0; box-shadow:none;
  }
}

/* the record is the thing. one handling logic, three mutations, and the
   frame around the chrome does not move. */
