/*
 * Solipsism webclient customizations. Loaded by evennia's stock
 * webclient/base.html after webclient.css, so everything here is an
 * override or an addition; the stock client stays untouched.
 */

/*
 * Phone text size (decided 2026-07-21: 14px).
 *
 * Evennia's webclient.css sets `body { font-size: .5rem; line-height:
 * .7rem }` under 480px, which is 8px text. Nothing in this project
 * asked for that, and 8px proved unreadable on a real phone.
 * Both declarations have to be replaced: a .7rem line height is built
 * for 8px and crowds 14px badly.
 *
 * At the measured pane (357px inside a 375px viewport) this is 40
 * columns where 8px gave far more. (This comment read 42 until
 * 2026-07-22, when the pane was measured again through the plugin's own
 * `_measure`: 42 is what the pane holds with no padding at all, and
 * `.content` carries .5rem a side down here, which screenwidth.js
 * subtracts before it reports.) That only reads correctly because
 * web/static/webclient/js/plugins/screenwidth.js now reports the real
 * column count; before it, the server wrapped every line at 78 no
 * matter what the pane held, and raising the font without that would
 * have turned one orphaned word per line into most of a line.
 *
 * 16px is the threshold below which iOS zooms the page on input focus,
 * so the input field keeps its own larger size below.
 */
@media screen and (max-width: 480px) {
    body {
        font-size: 14px;
        line-height: 1.45;
    }

    /* iOS zooms the whole page when a focused input is under 16px, which
       throws the layout off and needs a manual zoom back out every time */
    #inputfield,
    .inputfield {
        font-size: 16px;
    }
}

/*
 * The map pane (2026-07-21). Evennia tags the area map as its own
 * message (type='xymap') and goldenlayout routes by that tag, so
 * web/static/webclient/js/plugins/layout.js gives it a pane and it
 * stops scrolling the log.
 *
 * The map is aligned ASCII, so the one thing it cannot survive is
 * being wrapped: a soft break puts a room on the wrong row and the
 * grid stops meaning anything. Scroll instead, in both directions.
 */
.content[types~="xymap"] {
    white-space: pre;
    overflow: auto;
    line-height: 1.15;
}

/*
 * The tap bar (mobile QOL, 2026-07-20): section tabs and one compact
 * strip of touch targets living inside the input pane, above the input
 * line. Buttons are real <button> elements with text labels,
 * so a screen reader hears them as what they are. 44px is the
 * common minimum comfortable touch-target height.
 */
/* The room the status strip and the command field keep for themselves,
   which the bar's cap and the wrapper's floor below both have to read
   as the same number: tapbar.js measures the bar *after* the cap has
   applied and writes the result to `--tapbar-height`, so a floor the
   cap did not also honour is subtracted twice and the field lands below
   the pane. Declared here rather than on either of them because they
   are siblings, which is how it was got wrong first and how
   tools/check_webclient.py caught it.

   One 18px line of strip plus the 14 the command field has always had,
   which is the 32 this was written as before the strip could wrap at
   all: every screen wide enough to hold the line on one line is
   untouched by that change.

   A phone reserves the second line. 520px because the widest line the
   strip can build is 73 characters (`Health 120/999 badly wounded` and
   a three-digit everything else), which at 6.5px a character needs a
   499px pane inside a 513px window. Measured rather than inherited:
   this file's other phone rules break at 480 and layout.js at 700, and
   neither is where this particular line stops fitting.

   For whoever sizes this pane to its content next: the second line is a
   reservation rather than slack. Anything that hands the input pane a
   height computed from the command field alone puts the strip back
   over the edge it was cut off at, and it will read as fixed on a
   desktop, where the line has never wrapped. */
#clientwrapper {
    --input-floor: 32px;
}

@media screen and (max-width: 520px) {
    #clientwrapper {
        --input-floor: 50px;
    }
}

#tapbar {
    box-sizing: border-box;
    width: 100%;
    max-height: calc(100% - var(--input-floor, 32px));
    padding: 2px 0;
    overflow-x: hidden;
    overflow-y: auto;
    scrollbar-width: thin;
}

/* An empty bar renders its padding as a phantom strip above the input
   row, five pixels the sizer's CSS variable says are not there, and
   the field's bottom leaves the pane by exactly that much. */
#tapbar:empty {
    display: none;
}

/* Golden layout gives its input wrapper 100 percent of the pane. The
   tap bar is its sibling, so the wrapper gives back the bar's measured
   height or the command field is pushed below the pane. */
#clientwrapper .inputwrap {
    display: flex;
    flex-direction: column;
    height: calc(100% - var(--tapbar-height, 0px));
    min-height: var(--input-floor, 32px);
}

/* The prompt keeps its own height and the field row takes the rest.
   Stock gives the wrapper height 100%, which ignores the prompt above
   it and pushes the field's bottom out of the pane by exactly the
   prompt's height. */
#clientwrapper .inputfieldwrapper {
    flex: 1 1 auto;
    height: auto;
    min-height: 0;
}

.tapbar-tabs {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    gap: 4px;
    padding: 2px 0 4px;
    background: #000;
}

.tapbar-tab {
    flex: 1 1 0;
    min-width: 0;
    min-height: 44px;
    padding: 4px 8px;
    color: #999;
    background: transparent;
    border: 0;
    border-bottom: 2px solid #333;
    font: inherit;
}

.tapbar-tab[aria-selected="true"] {
    color: #eee;
    border-bottom-color: #8d8060;
}

.tapbar-tab:focus-visible,
.tapbar-btn:focus-visible {
    outline: 2px solid #d0bd8e;
    outline-offset: 1px;
}

.tapbar-row {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    width: 100%;
}

.tapbar-btn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 6px 14px;
    font-size: 15px;
    font-family: inherit;
    color: #ddd;
    background: #222;
    border: 1px solid #555;
    border-radius: 6px;
    white-space: nowrap;
}

/*
 * The icons (item 1.3, A3, 2026-07-22), drawn in
 * web/static/webclient/js/plugins/icons.js.
 *
 * The glyph sits beside the label rather than replacing it, which is
 * ballot 16's condition, and the label keeps the row's full text size.
 *
 * `currentColor` is the entire palette this item spends. A glyph takes
 * the colour the control already uses for its text, so the picture side
 * of the client has added no value to the sixteen-colour game palette
 * and the greyscale chrome. That was the point of doing icons before a
 * tile pack is chosen: a palette locked ahead of the source packs'
 * value bands is the cheapest artefact in the plan to redo.
 *
 * The .78 is what keeps the picture under the word. Two things carry
 * the same meaning here, and the rule everywhere else in this project
 * is that the prose is the one that survives, so the glyph is drawn a
 * step quieter than the label it belongs to.
 *
 * An iconed button costs 24px of width (18 of glyph, 6 of gap) and the
 * padding gives 4 of that back. On the phone the exits row already
 * scrolls, so the cost lands as scrolling rather than as anything lost.
 */
.tapbar-btn .solipsism-icon {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    margin-right: 6px;
    opacity: 0.78;
}

.tapbar-iconed {
    padding-left: 12px;
    padding-right: 12px;
}

.tapbar-btn:active {
    background: #444;
}

/* the character's spells and earned moves, tinted apart from the
   common verbs so the strip scans in groups; meaning stays in the
   labels, the tint is redundant with position */
.tapbar-go {
    border-color: #556;
}

.tapbar-spells {
    border-color: #366;
}

.tapbar-moves {
    border-color: #663;
}

/* the target-picking row replaces the strip until a pick or cancel */
.tapbar-picking .tapbar-target {
    border-color: #833;
}

.tapbar-cancel {
    color: #999;
}

/* The status below is absolutely positioned, so the wrapper has to be
   its containing block. Evennia's stock webclient.css gives
   #clientwrapper a height and nothing else, which leaves the banner
   anchored to whatever happens to be positioned further up the tree. */
#clientwrapper {
    position: relative;
}

/*
 * Connection state stays visible until it is resolved instead of
 * disappearing into the scrolling game transcript.
 */
#connection-status {
    position: absolute;
    top: 30px;
    left: 50%;
    z-index: 20;
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 44px;
    padding: 8px 12px;
    color: #f0e5c8;
    background: #352c1d;
    border: 1px solid #8d7040;
    border-top: 0;
    border-radius: 0 0 6px 6px;
    transform: translateX(-50%);
    box-shadow: 0 4px 16px #000;
}

#connection-status[hidden] {
    display: none;
}

.connection-retry {
    min-height: 44px;
    padding: 4px 12px;
    color: #fff;
    background: #554224;
    border: 1px solid #a88a52;
    border-radius: 4px;
    font: inherit;
}

.connection-retry:focus-visible {
    outline: 2px solid #f0d696;
    outline-offset: 2px;
}

@media screen and (max-width: 480px) {
    #connection-status {
        top: 0;
        right: 0;
        left: 0;
        justify-content: space-between;
        border-right: 0;
        border-left: 0;
        border-radius: 0;
        transform: none;
    }
}

/*
 * ===================================================================
 * The frame (A1, 2026-07-22)
 * ===================================================================
 *
 * Item 1.4 of docs/graphical_client_action_plan.md, drawn at
 * tools/client_mockups/b_tracka.html: a title bar, pane chrome, a
 * ground, log spacing, and a measure the log can be read at.
 *
 * The status strip that mockup draws carries hp, sp, and level. Nothing
 * in this game has ever sent the web client a prompt (zero `prompt=`
 * sends anywhere under a grep of the tree), so the `.prompt` div golden
 * layout builds above the input line is permanently empty and that half
 * of the drawn frame is recorded as owed in docs/build_status.md rather
 * than given chrome with nothing behind it.
 *
 * Every rule below that names a golden layout class carries an id in
 * front of it. The goldenlayout plugin injects goldenlayout-base.css,
 * goldenlayout-dark-theme.css, and goldenlayout.css at runtime and they
 * land after this file in `document.styleSheets`, so a bare `.lm_header`
 * here loses to the dark theme's own. Read off the live client
 * 2026-07-22.
 */

/*
 * The ground. Stock is pure black everywhere, which leaves nothing to
 * tell a pane from the space around it. One step off black does that
 * with no borders spent on it.
 */
html,
body {
    background: #0a0a0a;
    font-size: 16px;
    line-height: 1.45;
}

/* stock `.out` is #aaa on its own black; #ccc is the colour the rest of
   the client already uses for body text, and the pane carries the
   ground so the run of text does not paint its own */
.out {
    color: #ccc;
    background-color: transparent;
}

/*
 * The title bar.
 *
 * `#clientwrapper`'s only in-flow child is `#main`: webclient.css fixes
 * `#toolbar` to the top right and `#input` to the bottom, both out of
 * flow. So a generated block before `#main` is the whole title bar and
 * the template needs no change. `#main` gives back the height it takes,
 * and because this file loads before the client's javascript, golden
 * layout reads the reduced container when it sizes `.lm_root` at init.
 *
 * The drawn bar carries the room name and the coordinates on its right.
 * Both need a server push that does not exist, so the bar carries the
 * game's name and the slot stays empty.
 */
#clientwrapper::before {
    content: "SOLIPSISM";
    display: block;
    box-sizing: border-box;
    height: 30px;
    padding: 0 14px;
    line-height: 30px;
    background: #141414;
    border-bottom: 1px solid #2a2a2a;
    color: #808080;
    font-size: 12px;
    letter-spacing: 2px;
}

/* `#clientwrapper #main.split` rather than `#main`, because
   goldenlayout.css carries `#clientwrapper #main { height: 100% }` and
   loads after this file, so it takes a third selector to outrank it.
   Measured: a bare `#main` here left the client 800px tall inside a
   770px hole and pushed the input line under the fold. */
#clientwrapper #main.split {
    height: calc(100% - 30px);
}

/* a phone has the least height to spend and already carries the game's
   name in the tab, the task switcher, and the installed icon, so the bar
   buys nothing there and costs two lines of log */
@media screen and (max-width: 480px) {
    html,
    body {
        font-size: 14px;
        line-height: 1.45;
    }

    #clientwrapper::before {
        display: none;
    }

    #clientwrapper #main.split {
        height: 100%;
    }
}

/*
 * Pane chrome. webclient.css puts `1px solid #C0C0C0` and a white inset
 * shadow around every `.content`, which is the loudest thing in the
 * stock client: a silver box on black.
 */
.content {
    border: 1px solid #262626;
    box-shadow: none;
    background-color: #000;
}

/* golden layout's dark theme fills the pane body #222, which shows as a
   band wherever `.content` does not reach */
#clientwrapper .lm_content {
    background: #000;
}

#clientwrapper .lm_goldenlayout {
    background: #0a0a0a;
}

/*
 * The pane heads. Golden layout's tab bar is what the mockup draws as a
 * panehead, so it is restyled rather than replaced and the tabs keep
 * their drag, split, and rename controls.
 */
#clientwrapper .lm_header {
    background: #141414;
    border-bottom: 1px solid #262626;
}

#clientwrapper .lm_header .lm_tab {
    background: transparent;
    box-shadow: none;
    color: #808080;
    font-size: 11px;
    letter-spacing: 2px;
    text-transform: uppercase;
}

#clientwrapper .lm_header .lm_tab.lm_active {
    background: transparent;
    box-shadow: none;
    color: #c0c0c0;
}

/* the drag handle between the panes, which the dark theme paints black
   against a black pane */
#clientwrapper .lm_splitter {
    background: #0a0a0a;
}

#clientwrapper .lm_splitter:hover,
#clientwrapper .lm_splitter.lm_dragging {
    background: #262626;
}

/*
 * ===================================================================
 * The touch floor on the pane chrome (2026-08-02)
 * ===================================================================
 *
 * Written 2026-08-02 against a report about a prospective player who
 * uses a phone and nothing else: "the panels are very awkward to click
 * on and move around."
 *
 * Measured on the live client that evening, at the connection screen:
 * the header 21px, each tab 17px, the splitter 5px, the control cluster
 * 18 by 18. Golden layout's own defaults are headerHeight 20 and
 * borderWidth 5, and neither Evennia nor this project had ever
 * overridden them, so those numbers reached a phone from the library
 * untouched. Every rule above this one styles that chrome for colour
 * alone. `handoffs/mobile-panes-brief.md` holds the full measurement,
 * and WCAG 2.5.8 asks 24 CSS px before this project's own 44 is
 * reached.
 *
 * 699 rather than the 480 the blocks above use, because
 * MIN_WIDTH_FOR_SIDE_PANE is 700 in
 * web/static/webclient/js/plugins/layout.js and that is the one answer
 * this client gives to "phone or not": `tileview.js` reads it to put
 * the picture behind the log as a tab, which is what makes the tab the
 * swap control at these widths in the first place. `max-width` is
 * inclusive, so the block stops one pixel short of the threshold and
 * tools/layout_harness.html pins both sides of it.
 *
 * The header height is set in two places on purpose. Golden layout lays
 * the column out from `config.dimensions.headerHeight` in javascript,
 * so a header grown here alone would leave the library reserving 20px
 * for a 44px band; `layout.js` publishes the matching dimension.
 */
@media screen and (max-width: 699px) {
    #clientwrapper .lm_header {
        height: 44px;
        min-height: 44px;
    }

    #clientwrapper .lm_header .lm_tabs {
        height: 44px;
    }

    /* the tab is the swap control between the log and the tile view at
       this width, so it takes the whole band rather than sitting in the
       top 17px of it. The paddings are golden layout's own, which put
       the label against the tab's top edge. */
    #clientwrapper .lm_header .lm_tab {
        display: flex;
        align-items: center;
        box-sizing: border-box;
        height: 44px;
        min-height: 44px;
        margin-top: 0;
        padding: 0 14px;
    }

    #clientwrapper .lm_header .lm_tab.lm_active {
        margin-top: 0;
        padding: 0 14px;
    }

    /* the gear, popout and close cluster, 18 square as it arrives */
    #clientwrapper .lm_controls {
        height: 44px;
    }

    #clientwrapper .lm_controls > li {
        width: 44px;
        height: 44px;
        background-position: center;
    }

    /*
     * The splitter keeps its 5px line and grows its hit area alone. A
     * 44px band of ink between the panes would cost about two lines of
     * prose on a 923px screen, and the phone's scarce dimension is the
     * reason the threshold above exists.
     *
     * The pseudo-element overflows a splitter whose height golden layout
     * sets inline, so it needs no positioning and cannot move the
     * splitter itself. Hit testing on a pseudo-element lands on the
     * element that owns it, which is what makes this a bigger grab
     * rather than a decoration: 19px above the line and 20 below it.
     *
     * `display: flex` on the splitter is what makes the negative margin
     * arrive. As a plain block child the pseudo-element's top margin
     * collapses through a parent that has no border and no padding, and
     * the band then hangs 44px below the line and nothing above it.
     * tools/pane_chrome_harness.html measured exactly that and is why
     * this reads as it does; a flex item's margins never collapse. The
     * splitter is an empty div, so it has nothing else to lay out.
     *
     * What it costs: a touch starting in that 19px band over the bottom
     * of the log begins a resize instead of a scroll. Starting a scroll
     * anywhere else in the log is unaffected, and a resize that cannot
     * be grabbed at all is the complaint being answered.
     */
    #clientwrapper .lm_splitter {
        display: flex;
        touch-action: none;
    }

    #clientwrapper .lm_splitter::before {
        content: "";
        flex: 0 0 auto;
        width: 100%;
        height: 44px;
        margin: -19px 0;
        background: transparent;
    }
}

/*
 * The measure, and the log's own spacing.
 *
 * Measured in the shipped default layout at 1280x800 on 2026-07-22: the
 * log pane holds 106 columns, screenwidth.js reports that number, and
 * `world/presentation.py`'s `wrap_for` passes `looker_width` straight to
 * `justify`, so a room description goes out wrapped at 106 characters.
 * `heading()` is the one thing that caps, at 78, so every ruled heading
 * today stops a little past halfway across the prose beneath it. Holding
 * the log to 78 columns settles both: the line comes back to a length
 * that can be read, and the rules reach the edge again.
 *
 * This has to sit on `#messagewindow` itself. screenwidth.js measures
 * that element's `clientWidth` minus its own horizontal padding, so a
 * cap set any further in would narrow what a player sees while the
 * server went on wrapping to the full pane, and every line would break
 * twice.
 *
 * `ch` is the advance width of "0", which for the client's monospace
 * stack is one column. The 34px is the two borders plus the 32 of
 * padding that border-box sizing folds into a max-width.
 *
 * The cap never binds on a phone: at 375px that pane holds 42 columns.
 *
 * The scrollbar has to be held out of that measurement for the same
 * reason. `overflow: auto` gives this pane its scrollbar only once the
 * log overflows, which takes 17px out of `clientWidth` partway through a
 * session, and screenwidth.js reports on `onLoggedIn` and on resize
 * alone, so nothing tells the server. The connection screen is 16
 * rendered lines and clears the pane height, so the width goes out while
 * no scrollbar exists yet. Measured on live 2026-07-22: 77 columns with
 * the log empty, 76 once it scrolls, and a 77 character line then
 * occupied two visual lines at 40.33px against a 76 character line's
 * 20.16px. That is the double break this comment already warns about,
 * arriving by a route the cap alone does not cover, and it predates the
 * cap: the client sent 106 into a 106 column pane the same way.
 *
 * `scrollbar-gutter: stable` reserves the space from the first paint, so
 * the pane holds one width at every scroll position and the number the
 * server is given stays true. It spends two columns of every desktop
 * player's prose permanently; decided 2026-07-22. `heading()` caps
 * at `min(looker_width, 78)`, so the rules follow the pane down on their
 * own and stay flush. A touch browser overlays its scrollbar at zero
 * width, where the property has nothing to reserve and the phone's 42
 * columns are untouched.
 */
#messagewindow {
    box-sizing: border-box;
    width: 100%;
    scrollbar-gutter: stable;
}

#messagewindow > * {
    max-width: 78ch;
    margin-left: auto;
    margin-right: auto;
}

/*
 * The log's own spacing, above phone width only.
 *
 * webclient.css drops `.content` to .5rem of padding under 480px, and
 * the column count rides on that number because screenwidth.js subtracts
 * padding from the width it reports. Measured at a 375px viewport on
 * 2026-07-22 through the plugin's own `_measure`: the stock 8px a side
 * reports 40 columns and 16px reports 38. Two columns is a real loss in
 * the dimension a phone has least of, so the phone keeps what it has and
 * this is a desktop change.
 */
@media screen and (min-width: 481px) {
    #messagewindow {
        padding: 12px 16px;
    }
}

/*
 * The status strip (2026-07-22), the half of the frame A1 drew and could
 * not build as a stylesheet.
 *
 * Fed by `world/prompt.py`, which sends the line only to a web client
 * session and never to one that has set `SCREENREADER`. That line was
 * drawn 2026-07-22; `docs/design_notes.md` carries why. Golden layout
 * builds the `.prompt` div inside the input pane above the input field,
 * which is where Evennia puts a prompt and where this sits.
 *
 * Two things about the stock renderer shape these rules. `onPrompt` in
 * `default_out.js` adds the `out` class, so the selector names both to
 * win the colour back. And it writes two inline heights every time a
 * line arrives, `1.5em` on the strip and `calc(100% - 1.5em)` on the
 * sibling that holds the input field, which between them fix the strip
 * at one line forever. An author `!important` outranks a non-important
 * inline declaration, which is how both are taken back below; the
 * comment here read "no stylesheet outranks" until 2026-07-27 and was
 * the reason the strip was left to overflow instead.
 *
 * Vertical padding is still unspent, because border-box sizing would
 * fold it into the line box and squeeze the text. Horizontal only.
 *
 * The line wraps rather than scrolling, changed 2026-07-27
 * ------------------------------------------------------
 * It ran off the right edge of a phone and cut "Level 1" to "Lev",
 * reported off a real phone on 2026-07-25
 * (docs/client_polish_2026_07_25.md) and measured at 414px of line in a
 * 376px box at a 390 viewport. The strip did scroll sideways, with its
 * scrollbar hidden, so the only thing telling a player there was more
 * was the half-drawn letter.
 *
 * Wrapping rather than shortening the labels or dropping a field,
 * because the line is not one length: at three-digit vitals and the
 * two-word `badly wounded` band it runs about 72 characters against the
 * 54 this screen fits. Abbreviating every label buys fourteen and still
 * clips, and a dropped field is a number the player came here to read.
 *
 * `world/prompt.py` puts a hard space inside each vital group so the
 * only breakable run in the line is the gap between groups, which is
 * what keeps a wrap off the middle of `100/100`. `pre-wrap` rather than
 * `normal` so those gaps survive at their full three spaces.
 *
 * `overflow-x: auto` stays as the floor under a case neither of us
 * predicted: one group wider than the box still scrolls rather than
 * spilling into the page.
 */
#clientwrapper .prompt.out {
    padding: 0 12px;
    background: #141414;
    /* The rule under the strip, painted rather than laid out. As a
       `border-bottom` it drew the same pixel but added it to the box,
       so the strip measured 19 and 37 instead of 18 and 36 and every
       floor downstream had to carry the odd pixel. An inset shadow
       costs the line box nothing, which leaves the reserved heights
       whole multiples of a line. */
    box-shadow: inset 0 -1px 0 #262626;
    color: #c0c0c0;
    font-size: 12px;
    /* The line box the inline 1.5em used to impose. Kept explicitly so
       one line stays the 18px it has always been and two lines are
       exactly one line more. */
    line-height: 1.5;
    height: auto !important;
    flex: 0 0 auto;
    white-space: pre-wrap;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
}

/*
 * The strip's other half. `.inputwrap` becomes the column the strip and
 * the input field share, so the field takes what the strip leaves
 * instead of the fixed `calc(100% - 1.5em)` its inline style asks for.
 *
 * `position: relative` is stock and stays: `.inputsend` is absolutely
 * positioned and would otherwise resolve against `#clientwrapper` and
 * land somewhere else entirely.
 */
#clientwrapper .inputwrap {
    display: flex;
    flex-direction: column;
}

#clientwrapper .inputwrap .inputfieldwrapper {
    height: auto !important;
    flex: 1 1 0;
    min-height: 0;
    /* The containing block for the stock `.inputsend`, which is
       `height: 100%` of whatever positions it. Left to `.inputwrap` it
       measures the strip's rows as well as the field's and hangs that
       far below the pane. */
    position: relative;
}

#clientwrapper .prompt.out::-webkit-scrollbar {
    height: 0;
}

/*
 * The input line. Stock is a black field with a nearly invisible border
 * sitting flush against the log.
 */
#clientwrapper .inputfield {
    box-sizing: border-box;
    background: #000;
    border: 1px solid #262626;
    color: #ddd;
}

#clientwrapper .inputfield:focus {
    border-color: #3a3a3a;
    outline: none;
}

#clientwrapper .inputsend {
    box-sizing: border-box;
    background: #141414;
    border: 1px solid #262626;
    color: #808080;
}

/*
 * The input row (decided 2026-07-27, docs/client_polish_2026_07_25.md).
 * Stock absolutely positions the send button over a full-width field.
 * The history buttons (input_history.js) joined the row, and as inline
 * blocks their line box shoved the field below the pane, so the
 * wrapper becomes a flex row and every control sits beside the field:
 * Previous, Next, the field, then Send. The send button's order rule
 * is what moves it from its markup position ahead of the field.
 */
#clientwrapper .inputfieldwrapper {
    display: flex;
    align-items: stretch;
}

/*
 * No min-height on any of these on purpose. The 44px touch floor is
 * delivered by the pane sizer in tapbar.js, which allocates the field
 * row before the pane exists; a height floor here instead would shove
 * the field out the bottom of a pane that is genuinely too small,
 * which is the degraded state the tapbar harness pins. The controls
 * stretch to the row the sizer provides.
 */
#clientwrapper .inputhistory-btn {
    box-sizing: border-box;
    flex: 0 0 auto;
    min-width: 44px;
    background: #141414;
    border: 1px solid #262626;
    color: #808080;
    font: inherit;
}

#clientwrapper .inputfieldwrapper .inputsend {
    position: static;
    flex: 0 0 auto;
    width: 44px;
    height: auto;
    order: 1;
}

#clientwrapper .inputfieldwrapper .inputfield {
    flex: 1 1 auto;
    width: auto;
}


/*
 * The tile view surface (web/static/webclient/js/plugins/tileview.js).
 *
 * The pane is a golden layout component, so its .lm_content is given an
 * explicit pixel width and height; these percentages resolve against
 * that. The plugin syncs the canvas backing store to the box on every
 * resize, which is what keeps the picture from stretching.
 *
 * `display: block` matters: an inline canvas sits on a text baseline and
 * leaves a few pixels of gap under it, which shows as a seam along the
 * bottom of the pane.
 */
.tilesurface {
    background: #0b0b0e;
    display: block;
    height: 100%;
    width: 100%;
}
