/* =========================================================
   RAUNAK — CUSTOMER CHAT

   A viewport-height messaging layout: fixed header, one independently
   scrolling message list, fixed composer. The page itself never scrolls, so
   there is never a second scrollbar and the composer cannot be pushed off the
   bottom of a phone screen by a growing conversation.

   THE ONE IDEA
   ------------
   The shop and the customer are doing different things, so they do not look
   the same. The customer types; the pharmacy *writes something down*. So the
   customer's own messages are ink -- a filled, soft-cornered block -- and the
   pharmacy's replies are paper: no fill, a green rule down the left edge, and
   a serif, which is the typeface of a written note rather than of an app.

   That is why the bot bubble has no background and the user bubble has no
   rule. It is not a style choice applied twice; it is the same choice seen
   from both sides.

   The payment card, payment button and divider rules at the bottom are not
   decoration -- ``formatBotMessage`` in script.js emits those class names for
   the markdown the backend renders, so they are part of the message contract.
*/

* {
    box-sizing: border-box;
}

:root {
    /* ---- the palette ------------------------------------------------------
       A cold paper white, not a cream one: this is a dispensary, not a
       stationer's. The green is the chemist's cross deepened until it is a
       pine rather than a highlighter -- it means "the pharmacy", and it is
       deliberately NOT the same job as a success colour. */
    --dispensary: #0e5c46;
    --dispensary-deep: #0a4433;
    --dispensary-tint: #e8f1ee;
    --dispensary-soft: #bcd8ce;

    --ink: #101b2d;
    --muted: #5a6a70;
    --paper: #f6f8f7;
    --counter: #ffffff;
    --rule: #dde4e1;
    --rule-soft: #eaeff0;

    /* Attention and failure. The amber is the colour of a dosage warning on a
       label; the red is a clinical one rather than a pastel. */
    --clay: #9a6410;
    --clay-tint: #fdf6e7;
    --clay-rule: #f0dcae;
    --alert: #a32118;

    /* Type. See the note above each family at its first use. */
    --ui: "Archivo", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
        Roboto, sans-serif;
    --written: "Newsreader", ui-serif, Georgia, "Times New Roman", serif;
}

html,
body {
    height: 100%;
}

body {
    margin: 0;
    background: var(--paper);
    color: var(--ink);
    font-family: var(--ui);
    font-size: 16px;
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

/* The whole application is this one column. ``dvh`` rather than ``vh``: on a
   phone the address bar collapses as you scroll, and a ``vh``-sized column
   would leave the composer behind the browser chrome. */
.chat-app {
    position: relative;
    display: flex;
    flex-direction: column;
    height: 100dvh;
    max-height: 100dvh;
    max-width: 920px;
    margin: 0 auto;
    background: var(--paper);
    overflow: hidden;
}

@media (min-width: 921px) {
    .chat-app {
        border-left: 1px solid var(--rule);
        border-right: 1px solid var(--rule);
    }
}

/* =========================================================
   HEADER — fixed
   ========================================================= */

.chat-header {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 13px;
    padding: 15px 20px;
    background: var(--dispensary-deep);
    color: #fff;
    padding-top: max(15px, env(safe-area-inset-top));
}

/* The shop's mark. A rounded square rather than a circle: it echoes a
   medicine carton's corner, and it keeps the column of the layout --
   everything on this page sits on a 4px grid. */
.chat-avatar {
    width: 42px;
    height: 42px;
    flex: 0 0 42px;
    border-radius: 11px;
    display: grid;
    place-items: center;
    font-weight: 700;
    font-size: 19px;
    color: var(--dispensary-deep);
    background: var(--dispensary-soft);
}

.chat-identity {
    min-width: 0;
    flex: 1;
}

/* The shop's name is the heading. A customer's trust sits with the pharmacy
   they are buying from, so that is what the title says; the line below names
   what answers them instead of repeating the shop's own name back at it. */
.chat-identity h1 {
    margin: 0;
    font-family: var(--ui);
    font-size: 20px;
    font-weight: 700;
    /* Archivo is a variable font with a width axis. The wordmark is set wide
       -- 112% -- which is how signage is set, and it is the one place on the
       page where the width axis is used. */
    font-stretch: 112%;
    letter-spacing: -0.005em;
    line-height: 1.15;
}

.chat-subtitle {
    margin: 4px 0 0;
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: 12.5px;
    font-weight: 500;
    color: #a9c8bd;
}

/* script.js writes "Online" and "Reconnecting…" into this span with a capital,
   because it does not know where on the page it will land. Here it lands
   mid-sentence -- "Pharmacy assistant, online" -- and a capital in the middle
   of a sentence is wrong in a way that a status chip would not have been. The
   transform is presentational and lives with the presentation. */
#presence-text {
    text-transform: lowercase;
}

.presence-dot {
    width: 7px;
    height: 7px;
    flex: 0 0 7px;
    border-radius: 50%;
    background: #6ee7b7;
    box-shadow: 0 0 0 3px rgba(110, 231, 183, 0.2);
}

/* Reconnecting. Amber, and it pulses -- but only for people who have not
   asked motion to stop. See the reduced-motion block at the foot of the
   file. */
.presence-dot.offline {
    background: #f0b45b;
    box-shadow: 0 0 0 3px rgba(240, 180, 91, 0.2);
}

.chat-reset {
    flex: 0 0 auto;
    border: 1px solid rgba(255, 255, 255, 0.26);
    background: transparent;
    color: #e9f2ef;
    border-radius: 9px;
    padding: 8px 13px;
    font-family: var(--ui);
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
}

.chat-reset:hover {
    background: rgba(255, 255, 255, 0.12);
    border-color: rgba(255, 255, 255, 0.4);
}

/* =========================================================
   MESSAGE LIST — the only scrolling region
   ========================================================= */

/*
 * Everything above the composer. It is the box the "new message" pill is
 * positioned against, so the pill sits just above the composer however tall the
 * composer happens to be -- it grows when an attachment or an error is showing,
 * and a fixed offset would put the pill on top of it.
 *
 * It carries no scrolling of its own: the scrollbar belongs to ``.chat-scroll``
 * inside it, so there is still exactly one. This box only passes the height
 * through, which is why it needs ``min-height: 0`` as much as the scroller does.
 */
.chat-body {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.chat-scroll {
    flex: 1 1 auto;
    /* ``min-height: 0`` is what lets a flex child scroll instead of growing:
       without it the list expands the column and the composer leaves the
       viewport. */
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--paper);
}

/* The vertical rhythm of a conversation is not a constant. A message from the
   other speaker is a new beat and gets real space; two messages in a row from
   the same speaker are one thought continuing and get very little. The gap
   below is the *between-turns* space, and the rule further down tightens it
   for a same-speaker run. */
.chat-stream {
    padding: 22px 20px 10px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* A day boundary, marked the way a ledger rules one: a hairline that runs the
   full width with the label sitting in it. Quieter than a pill, and it reads
   as a division of the page rather than as a chip floating on it. */
.day-divider {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 8px 0 2px;
    color: var(--muted);
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.02em;
}

.day-divider::before,
.day-divider::after {
    content: "";
    flex: 1 1 auto;
    height: 1px;
    background: var(--rule);
}

.message {
    display: flex;
    max-width: 100%;
}

/* Same speaker twice running: pull the pair together so the run reads as one
   block. The divider and the first message of a run keep the full gap. */
.message + .message {
    margin-top: -10px;
}

.message.user {
    justify-content: flex-end;
}

.message.bot {
    justify-content: flex-start;
}

/* ---- the customer's own words: ink -----------------------------------------

   A filled block. Newsreader is NOT used here: what the customer types is
   input, not a written reply, and setting it in the interface's own grotesque
   keeps the serif meaning exactly one thing on this page. */

.bubble {
    position: relative;
    min-width: 0;
    font-size: 15px;
    line-height: 1.5;
    color: var(--ink);
    background: none;
    border: 0;
    border-radius: 0;
    padding: 0;
}

.message.user .bubble {
    max-width: min(78%, 520px);
    padding: 10px 14px 7px;
    border-radius: 15px 15px 5px 15px;
    background: var(--ink);
    color: #fff;
}

/* ---- the pharmacy's reply: paper ----------------------------------------

   No fill, a rule down the left edge, and a serif. The measure is capped in
   ``ch`` rather than only in ``%``: a percentage of a 920px column gives a
   line of well over 100 characters, which is past the point where the eye
   finds the next line reliably. 62ch is measured in the font that actually
   renders it, so it holds whatever the column is doing. */

.message.bot .bubble {
    max-width: min(82%, 62ch);
    padding: 1px 0 1px 16px;
    border-left: 2px solid var(--dispensary);
    font-family: var(--written);
    font-size: 16px;
    line-height: 1.62;
    /* A serif carries more ink than a grotesque at the same size, so it wants
       a touch more room between the lines. */
    color: var(--ink);
}

.bubble-text {
    /* Long words, URLs and unbroken product codes wrap rather than widening
       the page and producing a horizontal scrollbar. */
    overflow-wrap: anywhere;
    word-break: break-word;
    white-space: pre-wrap;
}

.bubble-text > :first-child {
    margin-top: 0;
}

.bubble-text > :last-child {
    margin-bottom: 0;
}

.bubble-text strong {
    font-weight: 650;
}

/* ---- lists in the pharmacy's replies ------------------------------------

   Lists come from `formatBotMessage` in ``script.js``. The bubble is
   `white-space: pre-wrap`, which a `ul`/`ol` does not inherit into anything
   harmful -- the element is block-level and each `<li>` holds only the item's
   own text -- but the browser's default `padding-left` of 40px is far too much
   inside a narrow bubble, and the default margins would add back the space the
   formatter has already trimmed around the list. So both are set explicitly.

   `.bubble-text > :first-child` and `:last-child` above zero these margins
   when the list is the first or last *element* of the message, which is what
   keeps a reply that is nothing but a list flush against the dispensary rule.

   Those two selectors count element siblings only, so a list with a line of
   prose above it is still "first child" and its top margin is zeroed as well.
   That is accepted rather than worked around: a list set off only by
   indentation and its markers still reads as a list, and the alternative --
   raising this rule's specificity above the pre-existing one -- would also
   remove the flush edge that rule exists to give the payment card. */
.bubble-text ul,
.bubble-text ol {
    margin: 6px 0;
    padding-left: 22px;
}

.bubble-text li {
    margin: 2px 0;
    /* Inherited from `.bubble-text` today. Repeated here so a long unbroken
       item keeps wrapping if that declaration is ever narrowed to direct
       children. */
    overflow-wrap: anywhere;
}

.bubble-text li::marker {
    color: var(--muted);
}

/* The time sits under the text it belongs to, on the side the text starts.

   It used to be right-aligned for every message, which was correct while both
   speakers were bubbles that hugged their text. The pharmacy's reply is no
   longer a bubble: it is a wide block of prose capped at a reading measure, so
   a right-aligned time landed at the far edge of that measure -- a long way
   from the paragraph it belonged to, and close enough to the customer's next
   message to read as theirs. */
.bubble-time {
    display: block;
    margin-top: 5px;
    font-size: 10.5px;
    line-height: 1;
    text-align: left;
    /* Figures that do not shift as they count. Every number on this page is
       set this way. */
    font-variant-numeric: tabular-nums;
    color: var(--muted);
    opacity: 0.75;
}

.message.user .bubble-time {
    text-align: right;
    color: rgba(255, 255, 255, 0.72);
    opacity: 1;
}

/* Typing indicator, shown while a request is in flight. */
.typing {
    display: flex;
    gap: 4px;
    padding: 5px 0;
}

.typing i {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--dispensary);
    opacity: 0.55;
    animation: chat-pulse 1.1s ease-in-out infinite;
}

.typing i:nth-child(2) {
    animation-delay: 0.15s;
}

.typing i:nth-child(3) {
    animation-delay: 0.3s;
}

@keyframes chat-pulse {
    50% {
        opacity: 0.2;
        transform: translateY(-2px);
    }
}

/* =========================================================
   "NEW MESSAGE" PILL
   ========================================================= */

/* Absolute against ``.chat-body``, whose bottom edge is the composer's top
   edge. It is a sibling of the scroller, not a child, so it stays put while the
   messages move underneath it. */
.jump-latest {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    bottom: 14px;
    z-index: 5;
    border: 1px solid var(--rule);
    background: var(--counter);
    color: var(--dispensary-deep);
    border-radius: 999px;
    padding: 8px 15px;
    font-family: var(--ui);
    font-size: 12.5px;
    font-weight: 650;
    cursor: pointer;
    box-shadow: 0 6px 20px rgba(16, 27, 45, 0.13);
}

.jump-latest:hover {
    border-color: var(--dispensary-soft);
    background: var(--dispensary-tint);
}

.jump-latest[hidden] {
    display: none;
}

/* =========================================================
   COMPOSER — fixed at the bottom
   ========================================================= */

.composer {
    flex: 0 0 auto;
    display: flex;
    align-items: flex-end;
    gap: 10px;
    padding: 12px 16px;
    padding-bottom: max(12px, env(safe-area-inset-bottom));
    border-top: 1px solid var(--rule);
    background: var(--counter);
}

.composer textarea {
    flex: 1 1 auto;
    min-width: 0;
    resize: none;
    border: 1px solid var(--rule);
    border-radius: 13px;
    padding: 12px 14px;
    font-family: var(--ui);
    /* 16px, not 15px. iOS Safari zooms the whole page in when a field smaller
       than 16px takes focus, and it does not zoom back out -- the customer is
       left on a magnified page mid-conversation. The floor is 16px, so this
       sits on it. */
    font-size: 16px;
    /* 1.45 rather than the inherited 1.55, so one line of text plus the padding
       lands on the shared 50px height below instead of under it. */
    line-height: 1.45;
    color: var(--ink);
    background: var(--paper);
    outline: none;
    max-height: 148px;
    overflow-y: auto;
}

/*
 * One height for the whole row.
 *
 * The textarea and the two buttons are laid out with ``align-items: flex-end``
 * so that a grown textarea grows upward while the controls stay on the bottom
 * edge. That only reads as aligned when all three are the *same* height, and
 * they were not: the textarea worked out at 45px while a button, inheriting
 * ``line-height`` from ``body``, came to ~47px. The two pixels showed up as a
 * crooked row.
 *
 * ``min-height`` rather than ``height``: script.js sets the textarea's height
 * on every input to make it grow, and a fixed height here would fight it. At
 * one line the height it sets is below the minimum and the box stays at 50;
 * past that the height it sets wins and the box grows.
 */
.composer textarea,
.composer button {
    min-height: 50px;
}

.composer textarea:focus {
    border-color: var(--dispensary);
    background: var(--counter);
    box-shadow: 0 0 0 3px rgba(14, 92, 70, 0.12);
}

.composer button {
    flex: 0 0 auto;
    border: none;
    border-radius: 13px;
    padding: 12px 22px;
    font-family: var(--ui);
    font-size: 15px;
    font-weight: 650;
    color: #fff;
    background: var(--dispensary);
    cursor: pointer;
}

.composer button:hover:not(:disabled) {
    background: var(--dispensary-deep);
}

.composer button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* The paperclip. ``.composer button`` above is the Send button -- a filled
   green block -- and this one must not be, or the composer reads as two
   submits. It keeps the same height so the two line up. */
.composer .attach-button {
    padding: 12px 15px;
    font-size: 18px;
    line-height: 1;
    color: var(--muted);
    background: transparent;
    border: 1px solid var(--rule);
}

.composer .attach-button:hover:not(:disabled) {
    background: var(--paper);
    border-color: var(--dispensary-soft);
    color: var(--dispensary-deep);
}

/* Every control on this page was keyboard-reachable but invisible to the
   keyboard: only the textarea had a focus style, so tabbing into "New chat"
   or Send left no trace of where you were. */
.chat-reset:focus-visible,
.jump-latest:focus-visible,
.composer button:focus-visible,
.attachment-remove:focus-visible {
    outline: 2px solid var(--dispensary);
    outline-offset: 2px;
}

/* On the dark header the ring has to be light, or it disappears into the
   green it is drawn against. */
.chat-reset:focus-visible {
    outline-color: #fff;
}

/* =========================================================
   ATTACHMENTS — prescriptions and documents
   ========================================================= */

/*
 * The pending file, above the composer. Outside the form, so it is a sibling
 * and not a control of it: it holds the file that has been uploaded and not yet
 * sent, and it collapses to nothing when there is none.
 */
.composer-attachment {
    flex: 0 0 auto;
    padding: 0 16px;
    background: var(--counter);
}

.composer-attachment:empty {
    display: none;
}

.attachment-note {
    margin: 0 0 10px;
    padding: 10px 12px;
    border-radius: 10px;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--ink);
    background: var(--clay-tint);
    border: 1px solid var(--clay-rule);
}

.attachment-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 8px;
}

/*
 * What the customer sees in place of the picture they sent.
 *
 * The bytes are readable only by staff, so this page never fetches them -- not
 * in the bubble just sent, and not after a reload. The chip says what was sent
 * and how big it is, which is what the customer needs to know; the file itself
 * is the pharmacist's to open.
 */
.attachment-chip {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    max-width: 260px;
    padding: 6px 10px 6px 7px;
    border-radius: 10px;
    background: var(--paper);
    border: 1px solid var(--rule);
    color: var(--ink);
}

/* Inside the customer's own bubble, which is filled and dark. Translucent so it
   reads as part of the bubble rather than as a light hole in it. */
.message.user .attachment-chip {
    background: rgba(255, 255, 255, 0.14);
    border-color: rgba(255, 255, 255, 0.3);
    color: inherit;
}

.attachment-glyph {
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
    border-radius: 8px;
    display: grid;
    place-items: center;
    font-size: 15px;
    background: var(--dispensary-tint);
}

.message.user .attachment-glyph {
    background: rgba(255, 255, 255, 0.16);
}

.attachment-meta {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.attachment-meta strong {
    font-size: 12.5px;
    font-weight: 600;
}

.attachment-meta small {
    font-size: 11px;
    opacity: 0.75;
    font-variant-numeric: tabular-nums;
}

.attachment-meta strong,
.attachment-meta small {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.attachment-remove {
    flex: 0 0 auto;
    border: none;
    background: transparent;
    color: inherit;
    font-size: 14px;
    line-height: 1;
    padding: 4px 3px;
    cursor: pointer;
    opacity: 0.7;
}

.attachment-remove:hover {
    opacity: 1;
}

/* A file input cannot be styled, so the real one is hidden and the paperclip
   presses it. `clip` rather than `display:none` keeps it in the accessibility
   tree. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* =========================================================
   STRUCTURED MESSAGE PARTS (emitted by formatBotMessage)
   ========================================================= */

/* The QR card and the pay button are the one place on this page where a
   surface is allowed to float: they are a thing the customer has to act on,
   not prose. They sit inside the bot bubble, so they take the paper's own
   ink rather than fighting the rule beside them. */
.payment-qr-card {
    display: inline-block;
    background: var(--counter);
    padding: 16px;
    border-radius: 13px;
    box-shadow: 0 3px 14px rgba(16, 27, 45, 0.1);
    border: 1px solid var(--rule);
    margin: 14px 0 10px 0;
    text-align: center;
    max-width: 100%;
    font-family: var(--ui);
}

.payment-qr-img {
    width: min(220px, 100%);
    height: auto;
    display: block;
    margin: 0 auto 10px auto;
    border-radius: 9px;
}

.qr-label {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--muted);
    margin-top: 4px;
}

.payment-action-btn {
    display: inline-block;
    background: var(--dispensary);
    color: #ffffff !important;
    text-decoration: none;
    font-family: var(--ui);
    font-size: 15px;
    font-weight: 650;
    padding: 13px 26px;
    border-radius: 12px;
    box-shadow: 0 3px 10px rgba(14, 92, 70, 0.22);
    margin: 8px 0;
    transition: background 0.15s ease;
}

.payment-action-btn:hover {
    background: var(--dispensary-deep);
}

.chat-divider {
    border: none;
    border-top: 1px solid var(--rule);
    margin: 14px 0;
}

/* =========================================================
   MOBILE
   ========================================================= */

@media (max-width: 560px) {
    .chat-header {
        padding: 13px 15px;
        padding-top: max(13px, env(safe-area-inset-top));
        gap: 11px;
    }

    .chat-avatar {
        width: 38px;
        height: 38px;
        flex-basis: 38px;
        font-size: 17px;
    }

    .chat-identity h1 {
        font-size: 18px;
    }

    .chat-stream {
        padding: 18px 15px 8px;
        gap: 14px;
    }

    /* A phone is not a wide column, so the measure cap stops biting and the
       bubbles take the room they can get. */
    .message.bot .bubble {
        max-width: 88%;
        font-size: 15.5px;
    }

    .message.user .bubble {
        max-width: 86%;
    }

    .composer {
        padding: 10px 12px;
        padding-bottom: max(10px, env(safe-area-inset-bottom));
        gap: 8px;
    }

    .composer button {
        padding: 12px 18px;
    }

    /* The composer gains a third control on a narrow screen, so the paperclip
       gives back the horizontal padding it can spare -- the button that is
       pressed every turn is the one that keeps its size. */
    .composer .attach-button {
        padding: 12px 13px;
    }

    .composer-attachment {
        padding: 0 12px;
    }

    .chat-reset {
        padding: 7px 11px;
        font-size: 12px;
    }

    /* No ``bottom`` override here any more: the pill is positioned against the
       composer's top edge now, so there is no offset to keep in step with it. */
}

/* =========================================================
   REDUCED MOTION

   The typing indicator is the only thing on this page that moves on its own,
   and for anyone who has asked their system to stop animations it is also the
   thing most likely to be unpleasant. It is replaced with a still label
   rather than merely slowed down -- a slow pulse is still a pulse. The dots
   become the sentence they were standing in for.
*/
@media (prefers-reduced-motion: reduce) {
    /* The dots are hidden rather than frozen. Three stationary dots read as a
       rendering fault -- it is not obvious whether the message is still
       coming or the page has broken. The sentence they were standing in for
       says it outright. `.typing` already carries an `aria-label` with the
       same words, so this adds nothing for a screen reader to read twice. */
    .typing i {
        display: none;
    }

    .typing::after {
        content: "Raunak is typing";
        font-size: 13px;
        font-style: italic;
        color: var(--muted);
    }

    .payment-action-btn {
        transition: none;
    }
}
