/* Builder — app styles.
 *
 * Deliberately small. This covers the auth pages only; the real chassis arrives with the /build
 * app shell (Phase 1, item 2), copied from the AI Chat mobile layout. Nothing here should grow
 * into a design system in the meantime.
 */

:root {
    --bg:        #0e1013;
    --surface:   #171a1f;
    --border:    #262b33;
    --text:      #e7e9ec;
    --muted:     #9aa3af;
    --accent:    #4f8ef7;
    --accent-fg: #ffffff;
    --danger:    #f2555a;
}

* { box-sizing: border-box; }

/* `hidden` has to beat any display a class also sets. The attribute's rule lives in the browser's
   own stylesheet, so ANY author rule outranks it: `.plan-actions { display: flex }` alone leaves
   `el.hidden = true` doing nothing at all, on screen, with no error anywhere. */
[hidden] { display: none !important; }

html, body {
    margin: 0;
    padding: 0;
    background: var(--bg);
    color: var(--text);
    font: 16px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    /* Avoids the iOS zoom-on-focus jump without disabling pinch zoom. */
    -webkit-text-size-adjust: 100%;
}

.shell {
    max-width: 27rem;
    /* Respects the notch and the home indicator; the /build shell will need more of this. */
    margin: max(2rem, env(safe-area-inset-top)) auto 2rem;
    padding: 0 1.25rem calc(1.25rem + env(safe-area-inset-bottom));
}

.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 1.5rem;
}

h1 {
    margin: 0 0 .35rem;
    font-size: 1.35rem;
    font-weight: 600;
}

.hint, .muted {
    margin: 0 0 1.25rem;
    color: var(--muted);
    font-size: .925rem;
}

.message { margin: 0 0 1rem; }

label {
    display: block;
    margin-bottom: .4rem;
    font-size: .875rem;
    color: var(--muted);
}

input[type="email"] {
    width: 100%;
    padding: .8rem .9rem;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 10px;
    color: var(--text);
    font-size: 1rem;
}

input[type="email"]:focus {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
    border-color: var(--accent);
}

button, .button {
    display: inline-block;
    margin-top: 1rem;
    padding: .8rem 1.1rem;
    background: var(--accent);
    color: var(--accent-fg);
    border: 0;
    border-radius: 10px;
    font-size: 1rem;
    font-weight: 500;
    text-decoration: none;
    cursor: pointer;
}

button:hover, .button:hover { filter: brightness(1.08); }

.actions { margin: 1rem 0 0; }

.error {
    margin: 0 0 1rem;
    padding: .75rem .9rem;
    background: rgba(242, 85, 90, .12);
    border: 1px solid rgba(242, 85, 90, .35);
    border-radius: 10px;
    color: var(--text);
    font-size: .925rem;
}

.debug-link {
    margin-top: 1.25rem;
    padding: .75rem .9rem;
    background: var(--bg);
    border: 1px dashed var(--border);
    border-radius: 10px;
    font-size: .8rem;
    word-break: break-all;
}

.debug-link a { color: var(--accent); }

/* ── Platform admin (admin-models.php, admin-smoke.php) ──────────────────────
   Operator UI, not customer UI. Dense on purpose: the point of the page is to
   compare a dozen rows of prices at a glance. */

.shell:has(table) { max-width: 74rem; }

table {
    width: 100%;
    margin: .75rem 0 1.5rem;
    border-collapse: collapse;
    font-size: .85rem;
}

th, td {
    padding: .5rem .6rem;
    border-bottom: 1px solid var(--border);
    text-align: left;
    vertical-align: top;
}

th { font-weight: 600; color: var(--muted, inherit); }

.inline { display: flex; gap: .4rem; flex-wrap: wrap; align-items: center; }
.inline select, .inline input { font-size: .85rem; padding: .3rem .4rem; }
.inline button { padding: .35rem .7rem; font-size: .8rem; }

.tag {
    display: inline-block;
    padding: .1rem .45rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    font-size: .72rem;
}

.tag.warn {
    background: rgba(242, 85, 90, .12);
    border-color: rgba(242, 85, 90, .35);
}

/* The smoke test reads an encoded request back before it is paid for. Monospaced
   because it is JSON being checked key by key, not prose. */

.smoke-call { display: grid; gap: .35rem; }
.smoke-call select { width: 100%; font-size: .8rem; padding: .3rem .4rem; }
.smoke-call button { justify-self: start; margin: .15rem 0 0; padding: .35rem .7rem; font-size: .8rem; }

.wire {
    margin: .35rem 0 0;
    padding: .5rem .6rem;
    background: var(--bg);
    border: 1px dashed var(--border);
    border-radius: 8px;
    font: .75rem/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    white-space: pre-wrap;
    word-break: break-word;
}

.wire-more { margin-top: .35rem; font-size: .78rem; color: var(--muted); }
.wire-more summary { cursor: pointer; }

/* ── Brief-engine test surface (brief-test.php) ──────────────────────────────
   Operator UI again. The real conversation chassis arrives with the /build
   shell; none of this is it. */

.shell:has(.chat-log) { max-width: 46rem; }

.convo-list {
    margin: 0 0 .5rem;
    padding-left: 1.1rem;
    font-size: .9rem;
}

.convo-list a { color: var(--accent); }
.convo-list .current a { font-weight: 600; }

.chat-log { margin-bottom: 1rem; }

.chat-msg {
    margin: 0 0 .9rem;
    padding: .6rem .8rem;
    border-radius: 10px;
    border: 1px solid var(--border);
}

.chat-msg p { margin: 0; white-space: pre-wrap; }
.chat-msg .hint { display: block; margin: .35rem 0 0; font-size: .75rem; }

.chat-user { background: var(--bg); margin-left: 2rem; }
.chat-assistant { background: var(--surface); margin-right: 2rem; border-color: var(--accent); }

textarea {
    width: 100%;
    padding: .8rem .9rem;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 10px;
    color: var(--text);
    font: inherit;
    resize: vertical;
}

textarea:focus {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
    border-color: var(--accent);
}

.diag {
    margin: 0 0 1.25rem;
    padding: .75rem .9rem;
    background: var(--bg);
    border: 1px dashed var(--border);
    border-radius: 10px;
    font-size: .78rem;
    overflow-x: auto;
    white-space: pre-wrap;
    word-break: break-word;
}

.send-row { display: flex; gap: .5rem; align-items: center; }

#mic-button { background: var(--surface); border: 1px solid var(--border); color: var(--text); }

#mic-button.listening {
    border-color: var(--danger);
    color: var(--danger);
    animation: mic-pulse 1.2s ease-in-out infinite;
}

@keyframes mic-pulse {
    50% { box-shadow: 0 0 0 5px rgba(242, 85, 90, .18); }
}

/* The three dots that say a reply is on its way. */
.chat-typing { display: flex; gap: .3rem; padding: .85rem .8rem; }

.chat-typing span {
    width: .45rem;
    height: .45rem;
    border-radius: 50%;
    background: var(--muted);
    animation: typing-bounce 1.2s infinite;
}

.chat-typing span:nth-child(2) { animation-delay: .2s; }
.chat-typing span:nth-child(3) { animation-delay: .4s; }

@keyframes typing-bounce {
    0%, 60%, 100% { opacity: .35; transform: translateY(0); }
    30%           { opacity: 1;   transform: translateY(-3px); }
}

/* ── The /build conversation shell ─────────────────────────────────────
   The customer chassis. Mobile-first: the app fills the visual viewport (js/build.js pins it
   under the on-screen keyboard); on desktop it becomes a two-pane layout with a persistent
   drawer. Same palette as everything else — the shell should feel like the product, not like a
   different app bolted on. */

/* The page wrapper stops being a centred column when it is carrying the app. */
.shell:has(.app) {
    max-width: none;
    margin: 0;
    padding: 0;
}

body:has(.app) { overflow: hidden; overscroll-behavior: none; }

.app {
    display: flex;
    height: 100vh;   /* Fallback — the JS pins the height on mobile anyway. */
    height: 100dvh;
    background: var(--bg);
}

/* ── Drawer ────────────────────────────────────────────────────────── */

.drawer {
    display: flex;
    flex-direction: column;
    width: 280px;
    flex-shrink: 0;
    background: var(--surface);
    border-right: 1px solid var(--border);
}

.drawer-head { padding: .9rem .9rem .5rem; }

.drawer-new {
    width: 100%;
    margin: 0;
    padding: .65rem .9rem;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 12px;
    color: var(--text);
    font-size: .95rem;
    text-align: left;
    transition: border-color .15s, background .15s;
}

.drawer-new:hover { border-color: var(--accent); background: rgba(79, 142, 247, .08); filter: none; }

.drawer .convo-list {
    flex: 1;
    overflow-y: auto;
    margin: 0;
    padding: .4rem .55rem calc(.75rem + env(safe-area-inset-bottom));
    list-style: none;
    font-size: inherit;
}

.convo {
    display: flex;
    align-items: center;
    gap: .1rem;
    margin: 0 0 .15rem;
    border-radius: 10px;
}

.convo:hover { background: rgba(255, 255, 255, .045); }
.convo.active { background: rgba(79, 142, 247, .14); }

.convo-title {
    flex: 1;
    min-width: 0;
    margin: 0;
    padding: .55rem .65rem;
    background: none;
    border: 0;
    border-radius: 10px;
    color: var(--text);
    font-size: .9rem;
    text-align: left;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: pointer;
}

.convo-title:hover { filter: none; }

.convo-act {
    margin: 0;
    padding: .3rem .4rem;
    background: none;
    border: 0;
    border-radius: 8px;
    color: var(--muted);
    font-size: .8rem;
    opacity: 0;
    cursor: pointer;
    transition: opacity .12s;
}

.convo:hover .convo-act, .convo-act:focus-visible { opacity: 1; }
.convo-act:hover { color: var(--text); filter: none; }
.convo-act.danger:hover { color: var(--danger); }

/* Touch screens have no hover — the actions must simply be there. */
@media (hover: none) {
    .convo-act { opacity: .6; }
}

.convo-empty { padding: .6rem .65rem; color: var(--muted); font-size: .875rem; }

.drawer-foot {
    padding: .7rem .9rem calc(.7rem + env(safe-area-inset-bottom));
    border-top: 1px solid var(--border);
    font-size: .8rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.drawer-foot a { color: var(--muted); text-decoration: none; }
.drawer-foot a:hover { color: var(--text); }

.drawer-backdrop { display: none; }

/* ── Chat pane ────────────────────────────────────────────────────── */

.chat {
    position: relative; /* The containing block for the out-of-flow composer. */
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
}

.chat-top {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: calc(.6rem + env(safe-area-inset-top)) .75rem .6rem;
    border-bottom: 1px solid var(--border);
    background: var(--surface); /* Fallback where color-mix is unsupported. */
    background: color-mix(in srgb, var(--surface) 82%, transparent);
    backdrop-filter: blur(10px);
}

.chat-title {
    flex: 1;
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    text-align: center;
}

.icon-btn {
    display: grid;
    place-items: center;
    margin: 0;
    padding: .45rem .6rem;
    background: none;
    border: 0;
    border-radius: 10px;
    color: var(--muted);
    font-size: 1.05rem;
    line-height: 1;
    cursor: pointer;
}

.icon-btn:hover { color: var(--text); background: rgba(255, 255, 255, .06); filter: none; }
.icon-btn.on { color: var(--accent); }

/* Icons inherit currentColor, so `.on` above is what makes a toggle readable. Only one glyph of
   a pair is ever shown; the other stays in the DOM so no state has to rebuild markup. */
.ico { display: block; }
.icon-btn .ico-on { display: none; }
.icon-btn.on .ico-off { display: none; }
.icon-btn.on .ico-on { display: block; }

#menu-btn { display: none; }

/* ── Thread ────────────────────────────────────────────────────────── */

.thread {
    flex: 1;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    /* The composer is out of flow (see below) so the thread runs the full height and slides
       underneath it. This padding is what keeps the last message clear of it; js/build.js keeps
       --composer-h in step with the growing textarea. */
    padding: 1.1rem .9rem calc(var(--composer-h, 4rem) + 1.25rem);
    scrollbar-width: thin;
}

.thread > * { max-width: 44rem; margin-left: auto; margin-right: auto; }

.msg {
    position: relative;
    display: flex;
    margin-bottom: .9rem;
    animation: msg-in .18s ease-out;
}

@keyframes msg-in {
    from { opacity: 0; transform: translateY(5px); }
    to   { opacity: 1; transform: translateY(0); }
}

.msg.user      { justify-content: flex-end; }
.msg.assistant { justify-content: flex-start; }

.bubble {
    max-width: 85%;
    padding: .7rem .95rem;
    border-radius: 18px;
    font-size: .975rem;
    line-height: 1.5;
    overflow-wrap: break-word;
}

.msg.user .bubble {
    background: linear-gradient(135deg, var(--accent), #3d6fd6);
    color: var(--accent-fg);
    border-bottom-right-radius: 6px;
    white-space: pre-wrap;
}

.msg.assistant .bubble {
    background: var(--surface);
    border: 1px solid var(--border);
    border-bottom-left-radius: 6px;
}

.bubble p { margin: 0 0 .55rem; }
.bubble p:last-child, .bubble ul:last-child, .bubble ol:last-child { margin-bottom: 0; }
.bubble ul, .bubble ol { margin: 0 0 .55rem; padding-left: 1.25rem; }
.bubble li { margin-bottom: .2rem; }
.bubble code {
    padding: .1em .35em;
    background: rgba(255, 255, 255, .07);
    border-radius: 5px;
    font-size: .9em;
}

/* The pulsing edge that says the reply is still arriving. */
.msg.streaming .bubble { animation: streaming-pulse 1.6s ease-in-out infinite; }

@keyframes streaming-pulse {
    50% { border-color: var(--accent); }
}

.status-hint { color: var(--muted); font-size: .9rem; }

/* Read-this-aloud, tucked under an assistant bubble. */
.msg-speak {
    position: absolute;
    bottom: -1.25rem;
    left: .4rem;
    margin: 0;
    padding: .1rem .3rem;
    background: none;
    border: 0;
    border-radius: 6px;
    color: var(--muted);
    opacity: 0;
    cursor: pointer;
    transition: opacity .12s, color .12s;
}

.msg-speak .ico { width: 16px; height: 16px; }
.msg-speak .ico-stop { display: none; }
.msg-speak.playing .ico-play { display: none; }
.msg-speak.playing .ico-stop { display: block; }

/* Waiting on the spoken audio. Only the paid voice can be slow — the browser's own starts at once
   — so this pulse is also the only visible difference between the two. */
.msg-speak.loading {
    opacity: 1;
    color: var(--accent);
    animation: speak-wait 1.1s ease-in-out infinite;
}

.msg-speak.loading .ico-stop { display: none; }
.msg-speak.loading .ico-play { display: block; }

@keyframes speak-wait {
    50% { opacity: .35; }
}

.msg:hover .msg-speak, .msg-speak:focus-visible { opacity: 1; }
.msg-speak:hover { color: var(--text); filter: none; }

/* Playing is state, not decoration — it stays visible with no pointer to reveal it. */
.msg-speak.playing { opacity: 1; color: var(--accent); }

@media (hover: none) {
    .msg-speak { opacity: .7; }
    .msg:has(.msg-speak) { margin-bottom: 1.8rem; }
}

.msg-error {
    margin: 0 auto .9rem;
    padding: .6rem .9rem;
    background: rgba(242, 85, 90, .12);
    border: 1px solid rgba(242, 85, 90, .35);
    border-radius: 12px;
    font-size: .9rem;
}

.welcome {
    margin-top: 18vh;
    text-align: center;
    padding: 0 1.5rem;
}

.welcome h2 { margin: 0 0 .5rem; font-size: 1.45rem; }
.welcome p  { margin: 0 auto; max-width: 30rem; color: var(--muted); }

/* ── Composer ───────────────────────────────────────────── */

/* OUT OF FLOW ON PURPOSE. Translucency only reads as translucency when something moves behind
   it; while the composer sat in the column flow there was nothing under it but background, and
   the blur was invisible work. Absolute + the thread's bottom padding is AI Chat's proven
   arrangement (aicms/index-ai-chat.php, mobile). */
.composer {
    position: absolute;
    z-index: 20;
    left: 0;
    right: 0;
    bottom: 0;
    padding: .6rem .75rem calc(.75rem + env(safe-area-inset-bottom));
    border-top: 1px solid var(--border);
    background: var(--surface); /* Fallback where color-mix is unsupported. */
    background: color-mix(in srgb, var(--surface) 72%, transparent);
    -webkit-backdrop-filter: blur(14px); /* iOS Safari still needs the prefix. */
    backdrop-filter: blur(14px);
}

.composer-row {
    display: flex;
    align-items: flex-end;
    gap: .5rem;
    max-width: 44rem;
    margin: 0 auto;
}

.composer textarea {
    flex: 1;
    min-height: 2.75rem;
    /* Six lines here, three on a phone (see the mobile block). CSS owns the cap so that
       js/build.js can set the grown height without a second copy of the number. */
    line-height: 1.45;
    max-height: calc(6 * 1.45em + 1.4rem + 2px);
    padding: .7rem 1rem;
    border-radius: 22px;
    resize: none;
    /* The platform scrollbar inside a 22px-radius pill looks like a mistake. A thin translucent
       thumb on no track at all is what belongs there. */
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, .22) transparent;
}

.composer textarea::-webkit-scrollbar { width: 6px; }
.composer textarea::-webkit-scrollbar-track { background: transparent; }

.composer textarea::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, .22);
    border-radius: 3px;
}

.composer textarea::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, .34); }

.composer-error {
    max-width: 44rem;
    margin: 0 auto .5rem;
    padding: .5rem .8rem;
    background: rgba(242, 85, 90, .12);
    border: 1px solid rgba(242, 85, 90, .35);
    border-radius: 10px;
    font-size: .85rem;
}

.round-btn {
    flex-shrink: 0;
    width: 2.75rem;
    height: 2.75rem;
    margin: 0;
    padding: 0;
    border-radius: 50%;
    font-size: 1.05rem;
    line-height: 1;
    display: grid;
    place-items: center;
}

.round-btn.mic {
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--text);
    font-size: .95rem;
}

.round-btn.mic.listening {
    border-color: var(--danger);
    color: var(--danger);
    animation: mic-pulse 1.2s ease-in-out infinite;
}

.round-btn.mic.busy { opacity: .6; }

/* Three faces, one button: the mic, the retry arrow, and a short text label for the recording
   timer and the transcribing ellipsis. */
.round-btn.mic .ico-retry,
.round-btn.mic .mic-label { display: none; }

.round-btn.mic.retry .ico-mic { display: none; }
.round-btn.mic.retry .ico-retry { display: block; }

.round-btn.mic.labelled .ico-mic,
.round-btn.mic.labelled .ico-retry { display: none; }

.round-btn.mic.labelled .mic-label {
    display: block;
    font-size: .78rem;
    font-variant-numeric: tabular-nums;
}

.round-btn.attach {
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--text);
}

/* Open is state, not decoration — the button has to look pressed while its sheet is showing. */
.round-btn.attach[aria-expanded="true"] {
    border-color: var(--accent);
    color: var(--accent);
}

.round-btn.attach .ico { transition: transform .18s ease; }
.round-btn.attach[aria-expanded="true"] .ico { transform: rotate(45deg); }

.round-btn.send:disabled { opacity: .45; cursor: default; }

/* ── "Show us what you have" ─────────────────────── */

/* Inside the composer, so the thread's bottom padding already accounts for it. Capped and
   scrollable because on a short phone in landscape the sheet is otherwise taller than the page. */
.intake {
    max-width: 44rem;
    max-height: min(60vh, 26rem);
    margin: 0 auto .6rem;
    padding: .9rem 1rem;
    overflow-y: auto;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 16px;
    scrollbar-width: thin;
}

.intake-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
}

.intake-head h2 { margin: 0; font-size: 1rem; }

.intake-lead {
    margin: .35rem 0 .9rem;
    color: var(--muted);
    font-size: .85rem;
    line-height: 1.45;
}

.intake-error {
    margin: 0 0 .8rem;
    padding: .5rem .8rem;
    background: rgba(242, 85, 90, .12);
    border: 1px solid rgba(242, 85, 90, .35);
    border-radius: 10px;
    font-size: .85rem;
}

.intake-block + .intake-block {
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border);
}

.intake-block h3 { margin: 0 0 .2rem; font-size: .9rem; }

.intake-block > p {
    margin: 0 0 .55rem;
    color: var(--muted);
    font-size: .82rem;
    line-height: 1.45;
}

.intake-row { display: flex; gap: .5rem; align-items: stretch; }

/* The base sheet styles only input[type="email"], and this one cannot be type="url" (the browser
   would reject "example.com"). Without this it renders as a white box on a dark page. */
.intake-row input {
    flex: 1;
    min-width: 0;
    margin: 0;
    padding: .55rem .8rem;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 10px;
    color: var(--text);
    font: inherit;
    font-size: .9rem;
}

.intake-row input:focus {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
    border-color: var(--accent);
}

.intake-block textarea {
    width: 100%;
    margin: 0 0 .5rem;
    resize: vertical;
    font-size: .9rem;
}

.intake-go {
    flex-shrink: 0;
    margin: 0;
    padding: .55rem 1rem;
    font-size: .85rem;
    white-space: nowrap;
}

.intake-go:disabled { opacity: .5; cursor: default; }

/* Sits under the picker button, so it needs the margin on the other side from a hint. Two classes
   deep to outrank `.intake-block > p`. */
.intake-block .intake-note {
    margin: .5rem 0 0;
    font-size: .75rem;
    opacity: .75;
}

/* ── Material in the transcript ────────────────────── */

/* Handed-over material is not something the customer typed, so it must not look like it. A folded
   card naming the source keeps a 6,000-character website out of the transcript while leaving it
   one tap away — they are entitled to see exactly what we read about them. */
.msg.user .bubble.material { white-space: normal; }

.material-name {
    display: block;
    font-size: .9rem;
    font-weight: 600;
}

.material-toggle {
    margin: .3rem 0 0;
    padding: 0;
    background: none;
    border: 0;
    color: inherit;
    opacity: .8;
    font-size: .8rem;
    text-decoration: underline;
}

.material-toggle:hover { opacity: 1; background: none; filter: none; }

.material-body {
    margin: .6rem 0 0;
    padding-top: .6rem;
    border-top: 1px solid rgba(255, 255, 255, .25);
    max-height: 18rem;
    overflow-y: auto;
    font-size: .85rem;
    line-height: 1.5;
    white-space: pre-wrap;
    scrollbar-width: thin;
}

/* ── The plan ───────────────────────────── */

/* Above the box, inside the composer, so the thread's bottom padding already accounts for it —
   the same arrangement as the intake sheet, for the same reason. */
.plan-bar {
    display: flex;
    gap: .5rem;
    max-width: 44rem;
    margin: 0 auto .6rem;
}

.plan-go,
.plan-see {
    margin: 0;
    padding: .55rem 1rem;
    font-size: .85rem;
    white-space: nowrap;
}

.plan-go { flex: 1; }
.plan-go:disabled { opacity: .5; cursor: default; }

/* Secondary: looking at the plan again must not compete with making one. */
.plan-see {
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--text);
}

.plan-see:hover { border-color: var(--accent); background: rgba(79, 142, 247, .08); filter: none; }

/* OVER the conversation, not in it. The plan is the one thing on this page the customer is asked
   to decide, and a decision that scrolls away with the chat is a decision half of them never make.
   Absolute rather than fixed: .chat is the positioned ancestor, so this covers the conversation
   and leaves the drawer reachable. */
.plan-sheet {
    position: absolute;
    inset: 0;
    z-index: 40;
    display: grid;
    place-items: center;
    padding: 1rem;
    background: rgba(0, 0, 0, .55);
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
}

.plan-card {
    display: flex;
    flex-direction: column;
    width: min(44rem, 100%);
    max-height: 100%;
    padding: 1rem 1.1rem 1.1rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 16px;
    box-shadow: 0 18px 48px rgba(0, 0, 0, .5);
}

.plan-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
}

.plan-head h2 { margin: 0; font-size: 1.05rem; }

/* ABOVE the plan, never a footnote under it. "Some of this is not possible" read after the pages
   have been described is a correction; read first, it is simply what the plan is. */
.plan-scope {
    flex-shrink: 0;
    margin: .85rem 0 0;
    padding: .6rem .8rem;
    background: rgba(79, 142, 247, .12);
    border: 1px solid rgba(79, 142, 247, .35);
    border-radius: 10px;
    font-size: .875rem;
    line-height: 1.5;
}

/* The only part that scrolls, so the heading stays put and the buttons never leave the screen. */
.plan-body {
    margin: .85rem 0 0;
    overflow-y: auto;
    line-height: 1.55;
    scrollbar-width: thin;
}

.plan-body h3 { margin: 1.1rem 0 .4rem; font-size: .95rem; }
.plan-body h3:first-child { margin-top: 0; }
.plan-body p { margin: 0 0 .6rem; }
.plan-body ul, .plan-body ol { margin: 0 0 .6rem; padding-left: 1.25rem; }
.plan-body li { margin-bottom: .25rem; }
.plan-body > *:last-child { margin-bottom: 0; }

.plan-done {
    flex-shrink: 0;
    margin: .9rem 0 0;
    padding: .7rem .85rem;
    background: rgba(79, 142, 247, .12);
    border: 1px solid rgba(79, 142, 247, .35);
    border-radius: 10px;
    font-size: .9rem;
    line-height: 1.5;
}

.plan-actions {
    display: flex;
    flex-shrink: 0;
    gap: .5rem;
    margin: 1rem 0 0;
}

.plan-approve { flex: 1; margin: 0; }
.plan-approve:disabled { opacity: .5; cursor: default; }

.plan-change {
    margin: 0;
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--text);
    white-space: nowrap;
}

.plan-change:hover { border-color: var(--accent); background: rgba(79, 142, 247, .08); filter: none; }

/* ── Mobile ──────────────────────────────────────────────── */

@media (max-width: 720px) {
    /* js/build.js pins .app to the visual viewport (height/width/translate). Fixed positioning
       is what lets it ride above the layout viewport the keyboard pans. */
    .app {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100vh;   /* Fallback; fitApp() takes over immediately. */
        height: 100dvh;
    }

    #menu-btn { display: grid; }

    .drawer {
        position: absolute;
        z-index: 30;
        top: 0;
        bottom: 0;
        left: 0;
        width: min(82vw, 320px);
        transform: translateX(-102%);
        transition: transform .22s ease-out;
        box-shadow: 8px 0 32px rgba(0, 0, 0, .45);
    }

    .drawer.open { transform: translateX(0); }

    .drawer-backdrop {
        display: block;
        position: absolute;
        inset: 0;
        z-index: 20;
        background: rgba(0, 0, 0, .5);
        opacity: 0;
        pointer-events: none;
        transition: opacity .22s;
    }

    .drawer-backdrop.show { opacity: 1; pointer-events: auto; }

    .bubble { max-width: 88%; }

    /* A floating pill with the thread visible behind it, same as AI Chat. Ported rather than
       reinvented: the inset, the radius and the shadow are what make a translucent bar read as
       floating instead of as a bar that failed to paint. */
    .composer {
        left: .5rem;
        right: .5rem;
        bottom: calc(.5rem + env(safe-area-inset-bottom));
        padding: .5rem .55rem;
        border: 1px solid rgba(255, 255, 255, .12);
        border-radius: 28px;
        box-shadow: 0 8px 30px rgba(0, 0, 0, .5);
        /* Translucency and blur come from the base rule — not restated, so there is one place to
           change how see-through this is. */
    }

    .thread { padding-bottom: calc(var(--composer-h, 4rem) + 1.5rem + env(safe-area-inset-bottom)); }

    /* Never below 16px on iOS — anything smaller zooms the page on focus. Three lines, not six:
       vertical space is the scarcest thing on a phone, and a rambling message is dictated far
       more often than typed. */
    .composer textarea {
        font-size: 16px;
        max-height: calc(3 * 1.45em + 1.4rem + 2px);
    }

    /* The composer is already a floating card here, so the sheet's own border and fill would draw
       a second edge just inside the first. It keeps only the rule that divides it from the row. */
    .intake {
        max-height: min(50vh, 20rem);
        margin: 0 0 .5rem;
        padding: .3rem .4rem .7rem;
        background: none;
        border: 0;
        border-bottom: 1px solid var(--border);
        border-radius: 0;
    }

    /* Side by side there is no room for both a web address and a button that says what it does. */
    .intake-row { flex-direction: column; }

    .intake-row input { font-size: 16px; } /* Below 16px iOS zooms the page on focus. */
    .intake-block textarea { font-size: 16px; }
    .intake-go { width: 100%; }

    /* Same reasoning as the intake sheet: inside a card that is already floating, a second border
       just inside the first reads as a mistake. */
    .plan-bar {
        margin: 0 0 .5rem;
        padding: 0 .1rem;
    }

    /* Edge to edge. A plan is several hundred words the customer has to read before they can
       decide anything, and a phone has no space to spend on a margin around it. */
    .plan-sheet { padding: .5rem; }

    .plan-card {
        max-height: 100%;
        padding: .8rem .85rem .9rem;
    }

    /* Stacked, and the approval on top: the thumb lands on "carry on", which is what most people
       are here to press. Side by side, "something needs changing" wraps to two lines and the two
       start to look like the same button. */
    .plan-actions { flex-direction: column; }
    .plan-actions button { width: 100%; }
}
