/* ---- chat ---- */
.thread{display:flex;flex-direction:column;gap:14px;margin-bottom:20px}
#p-chat{display:none;flex-direction:column;min-height:calc(100dvh - 190px)}
#p-chat.on{display:flex}
#p-chat .thread{flex:1;justify-content:flex-end}
/* iMessage-style side bubbles — narrow max-width so they read as chat
   bubbles on the left/right sides of the page instead of eating the
   whole width. Client bubble on the right (paper accent), Portal on
   the left (subtle white-tint on the void, NO border — the tone alone
   differentiates it from the background). */
.msg{max-width:min(68%,520px);padding:12px 16px;border-radius:18px;
  font-size:15.5px;line-height:1.55;word-wrap:break-word;border:0}
.msg.me{align-self:flex-end;background:var(--paper);color:#000;
  border-bottom-right-radius:6px;font-weight:500}
/* Portal's messages — text is a slow-shimmering warm gold, so it feels
   like the assistant is speaking with a signature voice. Bubble bg kept
   via inset box-shadow trick because background is used for the text
   gradient. Cut to solid gold on reduced-motion. */
.msg.them{align-self:flex-start;border-bottom-left-radius:6px;
  background:linear-gradient(100deg,#FDE68A 15%,#FFF3C4 40%,#F0C674 55%,#FDE68A 85%);
  background-size:280% 100%;background-position:0% 50%;
  -webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;
  box-shadow:inset 0 0 0 999px rgba(255,255,255,.055);
  animation:gold-shine 9s ease-in-out infinite}
@keyframes gold-shine{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
@media(prefers-reduced-motion:reduce){.msg.them{animation:none;background-position:20% 50%}}
/* .who label and .why line inside Portal's messages should stay readable
   (mono/small metadata), not gold — reset them. */
.msg.them .who,.msg.them .why{
  -webkit-background-clip:initial;background-clip:initial;
  background:none;color:var(--muted);-webkit-text-fill-color:var(--muted)}
.msg.them .who{color:var(--faint);-webkit-text-fill-color:var(--faint)}
.msg.them.work{border-color:var(--accent-dim)}
.msg .who{display:block;font:500 10px var(--mono);letter-spacing:.14em;color:var(--faint);margin-bottom:6px}
.msg .why{margin-top:9px;padding-top:9px;border-top:1px solid var(--line);color:var(--muted);font-size:12.5px}
/* A held message. Not an error style — nothing broke and nothing was lost —
   but it must not read like an answer either, so it is the amber of a thing
   waiting rather than the red of a thing that failed. */
.msg.them.held{border-color:rgba(253,230,138,.34);background:rgba(253,230,138,.05)}
.msg .held-h{display:block;font-weight:600;margin-bottom:5px}
.msg .held-b{color:var(--muted);font-size:13.5px;line-height:1.6}
.daysep{align-self:center;font:500 10px var(--mono);letter-spacing:.16em;text-transform:uppercase;
  color:var(--faint);padding:6px 0 2px}
.ask{position:sticky;bottom:0;padding:24px 0 14px;max-width:660px;margin:0 auto;width:100%;
  background:linear-gradient(transparent,var(--bg) 26%)}
.starts{display:flex;gap:7px;overflow-x:auto;padding-bottom:13px;scrollbar-width:none;
  justify-content:center;flex-wrap:wrap}
.starts::-webkit-scrollbar{display:none}
.starts button{flex:none;cursor:pointer;background:var(--surface);border:1px solid var(--line);
  border-radius:99px;padding:9px 16px;font-size:14px;color:var(--muted);white-space:nowrap;
  transition:color .15s,border-color .15s}
.starts button:hover{border-color:var(--line-2);color:var(--text)}
.starts button:focus-visible{outline:2px solid var(--h4);outline-offset:2px}
/* AURA, NOT GLASS. The glow sits behind the capsule and swells on focus, so the
   composer itself reports whether Portal is thinking. */
/* Composer — no border by default so it floats cleanly on black; the aura
   glow behind it (::before) does the visual anchoring. Border only shows
   on focus/hover to signal the input state without a permanent frame. */
.ask-in{position:relative;display:flex;gap:10px;align-items:flex-end;background:var(--surface);
  border:1px solid transparent;border-radius:26px;padding:8px 8px 8px 18px;
  transition:border-color .3s,box-shadow .3s}
.ask-in:hover{border-color:var(--line)}
/* NO HUE UNDER THE THING PEOPLE TYPE INTO. Solomon, 2026-09-07: "get rid of
   the colors behind the text bar in chat."

   What was there: three radial gradients — pink --h1, gold --h3, teal --h2 —
   drifting under the composer. A rainbow beneath the one element on the page
   that is supposed to be a blank invitation, and the only place in the OS where
   the client's own words are the content.

   What the glow is FOR survives all three jobs it actually does: it anchors a
   borderless capsule on black, it swells on focus, and it swells again while
   Portal is thinking. None of those needed a colour — they needed a change in
   BRIGHTNESS, which is what a person reads at the edge of vision anyway. So it
   is one white wash now. It belongs to no palette, it cannot clash with an
   accent someone changes later, and the only colour in the composer is the
   text the client typed. */
.ask-in::before{content:"";position:absolute;inset:-22px;z-index:-1;border-radius:44px;
  background:radial-gradient(58% 140% at 50% 50%,rgba(255,255,255,.13),transparent 72%);
  opacity:.5;filter:blur(15px);transition:opacity .5s}
.ask-in:focus-within::before{opacity:.85}
.ask-in:focus-within{border-color:var(--line-2);box-shadow:0 3px 26px rgb(18 16 13 / .07)}
/* Thinking. With no hue left to shift, the composer reports by breathing in its
   own brightness — readable in peripheral vision, invisible to someone actually
   reading. The louder half of that report is the working line in the bubble. */
.ask-in.think::before{animation:askbreath calc(2.6s / var(--tempo)) ease-in-out infinite}
@keyframes askbreath{0%,100%{opacity:.55}50%{opacity:1}}
@media(prefers-reduced-motion:reduce){.ask-in.think::before{animation:none;opacity:.9}}
.ask textarea{flex:1;background:transparent;border:0;border-radius:0;
  padding:9px 0;resize:none;outline:none;max-height:150px;line-height:1.55;font-size:16.5px}
.ask .go{flex:none;width:38px;height:38px;border-radius:50%;background:var(--paper);color:#000;
  border:0;cursor:pointer;font-size:15px;display:grid;place-items:center;transition:transform .18s,background .15s}
.ask .go:hover{transform:scale(1.07)}
.ask .go:focus-visible{outline:2px solid var(--h4);outline-offset:3px}
.ask .go[disabled]{opacity:.32;cursor:default;transform:none}

/* ---- the two buttons sit on the text's line, not on the floor -------------
   Solomon, 2026-09-07: "adjust the add a file and send button to be centred
   properly in there — just needs to be moved up a bit."

   Why they were low. `.ask-in` is `align-items:flex-end`, which is the right
   choice: when the textarea grows to several lines the buttons must stay with
   the LAST line rather than floating up beside the first. But flex-end pins
   them to the bottom of the flex line, and the textarea's own 9px bottom
   padding sits below its text — so both buttons were parked under the words
   instead of beside them.

   The arithmetic, so the next person can redo it if the type changes:
     one row of the textarea = 16.5px x 1.55 line-height = 25.6px of text,
     plus its 9px top and bottom padding = 43.6px of content box.
     The text's optical centre is therefore 21.8px up from the bottom.
     send  is 38px tall -> its centre lands there at 21.8 - 19 = 2.8px  -> 3px
     clip  is 34px tall -> its centre lands there at 21.8 - 17 = 4.8px  -> 5px

   The two numbers differ because the buttons differ in size, and that is also
   what fixes the second, quieter problem: bottom-aligned, a 34px and a 38px
   circle have centres 2px apart, so they never looked like a pair. Now both
   centres sit on the same line as the text. */
.ask-in .clip{margin-bottom:5px}
.ask .go{margin-bottom:3px}
.hint{color:var(--faint);font-size:11.5px;margin-top:11px;text-align:center}
#p-chat .thread{max-width:660px;margin:0 auto;width:100%;padding-bottom:8px}


/* ---- attachments --------------------------------------------------------- */
.attached{display:flex;flex-wrap:wrap;gap:7px;margin:0 0 9px}
.attached[hidden]{display:none}
.chip{display:inline-flex;align-items:center;gap:8px;background:var(--surface);
  border:1px solid var(--line);border-radius:99px;padding:6px 6px 6px 13px;
  font:400 12.5px var(--sans);max-width:100%}
.chip b{font-weight:500;color:var(--text);white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;max-width:190px}
.chip em{font-style:normal;font-size:11px;color:var(--faint)}
.chip.err{border-color:rgba(253,164,175,.4)}
.chip.err em{color:var(--stop)}
.chip.up em{color:var(--muted)}
.chip-x{flex:none;width:20px;height:20px;border-radius:50%;background:transparent;border:0;
  color:var(--faint);font-size:15px;line-height:1;cursor:pointer}
.chip-x:hover{color:var(--text);background:var(--surface-2)}

.ask-in .clip{flex:none;width:34px;height:34px;border-radius:50%;background:transparent;
  border:1px solid var(--line-2);color:var(--muted);font:400 19px/1 var(--sans);
  cursor:pointer;transition:border-color .15s,color .15s}
.ask-in .clip:hover{border-color:var(--accent-dim);color:var(--text)}

/* The whole chat page is the drop target — aiming a dragged file at a 34px
   button is a task nobody should be set. */
#p-chat.dropping{outline:2px dashed var(--accent-dim);outline-offset:-10px;border-radius:var(--r)}

/* ---- the working line ------------------------------------------------------
   Three parts on one baseline: a braille spinner that never stops, a phrase
   from the client's own trade that changes every 2.4s, and a fast mono token
   that changes every 600ms. See scripts/thinking.js for why each exists.

   Sized and coloured to sit UNDER the reading line rather than compete with it.
   This appears while somebody is waiting for an answer they care about; if it
   pulls the eye harder than the answer does when it lands, it is wrong. Hence
   --muted for the words, --faint for the token, and no colour of its own. */
.wk{display:inline-flex;align-items:baseline;gap:9px;line-height:1.55}
.wk-dot{flex:none;font:400 13px/1 var(--mono);color:var(--h1);
  /* The one part that must never look frozen. Its own opacity float keeps it
     alive even in the 80ms between frames on a slow paint. */
  opacity:.9}
.wk-word{color:var(--muted);font-size:15px;transition:opacity .14s ease}
.wk-word.out{opacity:0}
.wk-tok{flex:none;font:600 10.5px/1 var(--mono);letter-spacing:.1em;color:var(--faint);
  background:var(--surface);border:1px solid var(--line);border-radius:5px;
  padding:3px 6px;min-width:34px;text-align:center}

/* AND IT HAS TO OPT OUT OF THE GOLD BUBBLE, which is what stopped it rendering.
   `.msg.them` paints its text by clipping a gradient to it — background-clip:text
   plus -webkit-text-fill-color:transparent — and text-fill-color INHERITS. So
   every part of this line was drawn with no fill: the token, which carries its
   own opaque background, showed as an empty pill; the spinner showed as a smudge
   of whatever gradient leaked through a 13px glyph; the word came out gold
   instead of --muted, competing with the answer the way the note above says it
   must not. The one component whose entire job is to prove Portal is alive was
   the one thing in the bubble you could not read.

   `.who` and `.why` opt out the same way further up. This is the third case, and
   it is stated as `currentColor` so each part keeps the colour its own rule
   above already gives it — one line, and nothing to re-sync if those change. */
.msg.them .wk,.msg.them .wk *{
  background:none;-webkit-background-clip:initial;background-clip:initial;
  -webkit-text-fill-color:currentColor}
.msg.them .wk-tok{background:var(--surface)}

/* Reduced motion: the spinner and the token stop moving, the words do not.
   Freezing the words too would leave a static line that says one thing forever
   — which is the state this whole component exists to get away from. */
@media (prefers-reduced-motion:reduce){
  .wk-word{transition:none}
}
