/* Small additions on top of Tailwind. */

[x-cloak] { display: none !important; }

html { -webkit-tap-highlight-color: transparent; }

/* Strip the spinner from money inputs — agents type digits, they don't nudge. */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
input[type="number"] { -moz-appearance: textfield; }

/* Subtle flash when HTMX swaps the commission preview, so the change is noticed. */
#commission-preview.htmx-swapping { opacity: 0.55; }
#commission-preview { transition: opacity 120ms ease-in-out; }

/* Tabular figures keep money columns aligned. */
.font-mono, td, .tabular { font-variant-numeric: tabular-nums; }

/* Chart.js runs with maintainAspectRatio off, so a canvas sizes itself to its
   parent — the height attribute on the element is ignored. Every chart is
   wrapped in a height-constrained div; this is the backstop if one is ever
   added without a wrapper. */
canvas { max-height: 60vh; }

@media print {
  aside, nav, form, .no-print, button { display: none !important; }
  body { background: #fff; }
}

/* Alpine sets x-cloak on elements that must not appear until it has decided
   whether to show them. The table/grid switch renders BOTH layouts and toggles
   one off, so without this the page flashes duplicated content on every load. */
[x-cloak] { display: none !important; }

/* The bundle chooser draws its own clear button, positioned and coloured with
   the rest of the page. WebKit adds a second one of its own inside every
   type="search" field, so the box showed two crosses side by side.

   Scoped to that one input rather than every search field: the header search
   has no button of its own and its native cross is the only way to clear it. */
input[data-bundle-search]::-webkit-search-cancel-button,
input[data-bundle-search]::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
}

/* The chat screen is the only page that fills the window instead of scrolling
   with it, which is what makes a chat feel like a chat: the list and the
   thread each scroll on their own, the message box stays on the bottom edge,
   and the page itself never moves.

   It was a 384px box (`max-h-96`) sitting in the middle of an ordinary padded
   page — six or seven messages visible on a 1080p screen with the rest of the
   window empty below it. Hand-written because the stylesheet is prebuilt and
   committed with no Tailwind step in this project, and it contains no
   `h-screen`, `min-h-screen` or `h-dvh` to reach for.

   4rem is the topbar, which is `sticky h-16`. `dvh` second so that a browser
   which knows it wins: on a phone, `vh` counts the address bar as though it
   were never there, which pushes the message box below the bottom edge. */
/* The chat screen is the window minus the topbar — and minus whatever else
   `main` is carrying that day.
 
   It was a flat `calc(100dvh - 4rem)`, which is right only when nothing else
   is on the page. `main` also renders the subscription banner and any flash
   message *above* the block, so on a day with either of those the screen was
   taller than the space left for it: the whole page scrolled, the topbar
   slid away, and there were two scrollbars down the right — one for the page
   and one for the thread.
 
   A flex column fixes it properly. `main` is the window minus the topbar,
   the banner and the messages take the height they need, and the chat takes
   what is left however much that is. */
.chat-main {
  padding: 0;
  height: calc(100vh - 4rem);
  height: calc(100dvh - 4rem);
  display: flex;
  flex-direction: column;
  /* `clip`, not `hidden`. Hidden still makes a scroll container — one
     nothing can scroll with a wheel, but which the browser will happily
     scroll itself to bring an anchor into view. A reply link ending in
     `#chat-form` did exactly that: the whole screen slid up inside a box
     that shows no scrollbar, leaving the thread pushed off the top and a
     page of empty dark below it. `clip` makes no scroll container at all,
     so there is nothing to scroll. `hidden` first for anything too old to
     know the newer keyword. */
  overflow: hidden;
  overflow: clip;
}

.chat-screen {
  flex: 1 1 auto;
  min-height: 0;
  padding: 0.75rem;
}

/* Grid and flex children refuse to shrink below their content by default, so
   without this the message list grows to fit every message and pushes the box
   off the screen instead of scrolling inside its own pane. */
.chat-screen > *, .chat-screen [data-pane] { min-height: 0; }

/* "Amina is typing…" sits under the name in the chat header. chat.js empties
   it when the typing stops, and an empty line would still hold its height and
   nudge everything under it — so an empty one is simply not there. */
#chat-typing:empty { display: none; }

/* The faint pattern behind a conversation.

   Drawn in CSS rather than served as an image: it is two repeating gradients,
   it costs no request, and it scales on any screen without a second file for
   retina. The chat screen is the one place in this app with a background of
   its own, and a flat slab behind bubbles reads as an unfinished page.

   Kept very low contrast on purpose — this sits under text people read all
   day, and a pattern you notice after ten minutes is a pattern that is too
   strong. Both themes get their own, because a light pattern on a dark
   ground is a grid of glowing dots. */
.chat-wallpaper {
  background-color: rgb(248 250 252);
  background-image:
    radial-gradient(rgb(226 232 240) 1px, transparent 1px),
    radial-gradient(rgb(226 232 240) 1px, transparent 1px);
  background-size: 28px 28px;
  background-position: 0 0, 14px 14px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .chat-wallpaper {
    background-color: rgb(2 6 23);
    background-image:
      radial-gradient(rgb(30 41 59) 1px, transparent 1px),
      radial-gradient(rgb(30 41 59) 1px, transparent 1px);
  }
}

:root[data-theme="dark"] .chat-wallpaper,
.dark .chat-wallpaper {
  background-color: rgb(2 6 23);
  background-image:
    radial-gradient(rgb(30 41 59) 1px, transparent 1px),
    radial-gradient(rgb(30 41 59) 1px, transparent 1px);
}

/* Somebody who would rather not have it. Respected without being asked. */
@media (prefers-reduced-motion: reduce) {
  .chat-wallpaper { background-image: none; }
}

/* A message's action menu, pinned to the window rather than to the bubble.

   It used to be an ordinary absolutely-positioned panel inside the bubble.
   That cannot work here: the thread scrolls, and anything positioned inside
   a scrolling box is clipped by it — so on the last message in a
   conversation, which is exactly the one people act on, the menu was cut in
   half by the bottom edge and the emoji row was unreachable.

   Fixed to the viewport escapes the clipping. The script places it next to
   the button that opened it and flips it when it would fall off an edge;
   with the script off it falls back to the bottom of the screen, which is
   where a phone would put it anyway. */
.msg-menu {
  position: fixed;
  z-index: 60;
  left: 50%;
  bottom: 1rem;
  transform: translateX(-50%);
  width: 14rem;
  max-height: 70vh;
  overflow-y: auto;
}

/* While the chat screen is open, the page itself does not scroll.

   The screen is already the window minus the topbar, so there should be
   nothing to scroll — but a fragment in the address (`#message-15`, which is
   what a quote links to) makes a browser scroll whatever it can to bring the
   target into view, and it will scroll the document if that is all it has.
   The result was the thread pushed off the top with a page of empty dark
   below it.

   `:has()` rather than a class on `<body>`: the chat screen is the only page
   that wants this, and it already announces itself with `.chat-main`. */
html:has(.chat-main), body:has(.chat-main) {
  overflow: hidden;
}

/* The message a quote jumped to, held for a moment so the eye finds it.

   Hand-written for the same reason the chat screen's height is: the Tailwind
   build in this project is a committed file, and a utility that was not in
   it when it was generated styles nothing at all. This one is applied from
   JavaScript, which the template style guard never reads — so an unbuilt
   utility here would have failed silently and for ever. */
.chat-found {
  outline: 2px solid rgb(22 163 74);
  outline-offset: 2px;
  border-radius: 1rem;
  transition: outline-color 200ms ease-out;
}

/* ---------------------------------------------------------------------------
   Where the keyboard is
   ---------------------------------------------------------------------------

   Every text input in this app already shows a ring when it is focused —
   `focus:ring-brand-600` appears on fifty-two of them. No button or link had
   anything at all, so somebody working down a form with Tab could see where
   they were right up until they reached Save, and then could not.

   Written here rather than as `focus-visible:` utilities in the templates
   because the Tailwind file in this project is committed as generated, and it
   contains no `focus-visible` variant: those classes would have been added to
   forty-six buttons and styled nothing. Done once here, it covers every
   button, link and menu on every screen, including the ones added later.

   `:focus-visible` rather than `:focus` so a mouse click does not leave a ring
   behind on the button that was just pressed — the reason the plain outline
   was widely turned off in the first place.

   The brand green is legible on the light background; dark mode gets a lighter
   tone of it, for the same reason the wallpaper does. */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
[role="button"]:focus-visible,
[tabindex]:not([tabindex="-1"]):focus-visible {
  outline: 2px solid rgb(12 107 78);
  outline-offset: 2px;
  border-radius: 0.5rem;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) a:focus-visible,
  :root:not([data-theme="light"]) button:focus-visible,
  :root:not([data-theme="light"]) summary:focus-visible,
  :root:not([data-theme="light"]) [role="button"]:focus-visible {
    outline-color: rgb(52 211 153);
  }
}

:root[data-theme="dark"] a:focus-visible,
:root[data-theme="dark"] button:focus-visible,
:root[data-theme="dark"] summary:focus-visible,
:root[data-theme="dark"] [role="button"]:focus-visible,
.dark a:focus-visible,
.dark button:focus-visible,
.dark summary:focus-visible,
.dark [role="button"]:focus-visible {
  outline-color: rgb(52 211 153);
}

/* ---------------------------------------------------------------------------
   The dot that says it is listening
   ---------------------------------------------------------------------------

   Hand-written for the same reason the chat screen's height is: the Tailwind
   file here is committed as generated and has no `animate-pulse` in it, so
   that utility would have styled nothing at all — a recording indicator that
   sits perfectly still, which reads as "not recording".

   `prefers-reduced-motion` is honoured: somebody who has asked their phone to
   stop moving things should still be able to tell it is recording, so the dot
   stays visible and simply stops pulsing. */
.is-recording {
  animation: wakala-pulse 1.4s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

@keyframes wakala-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}

@media (prefers-reduced-motion: reduce) {
  .is-recording { animation: none; }
}

/* ---------------------------------------------------------------------------
   Nothing in the chat is wider than the screen
   ---------------------------------------------------------------------------

   The rule above says grid and flex children refuse to shrink below their
   content and fixes it for height. Width has exactly the same default and was
   never fixed, and on a phone it is the one that shows: a bubble is capped at
   `max-w-lg` — thirty-two rem, five hundred and twelve pixels — which is
   wider than the screen it is being read on. One photograph, and the column
   it sits in refuses to be narrower than the picture.

   That alone would only have meant a sideways scroll. `.chat-main` is
   `overflow: clip`, for the good reason written above it, and clip makes no
   scroll container at all — so whatever was pushed past the right edge could
   not be reached by any means. What went past the edge was the Send button,
   on the composer and on the voice recorder both. The chat looked whole and
   could not be used.

   So: let every box in here shrink, and cap the things with an intrinsic
   width of their own — a picture, a player — at the space available. */
.chat-screen,
.chat-screen [data-pane],
.chat-screen [data-message-id],
.chat-screen [data-message-id] > * {
  min-width: 0;
}

/* A picture, a player and a preview all carry a width of their own that a
   flex row will otherwise honour in full. */
.chat-screen img,
.chat-screen audio,
.chat-screen video {
  max-width: 100%;
}

/* And the bubble itself. `max-w-lg` is right on a laptop and far too wide on
   a handset, so it is capped again against the width actually available. The
   percentage leaves room for the avatar and the gap beside it. */
.chat-screen [data-message-id] > div {
  max-width: min(32rem, 85%);
}
