/*
 * Fabrik — the whole stylesheet.
 *
 * Hand-written CSS, served as-is. There is no preprocessor and no build step, so what you read
 * here is exactly what the browser receives (principle 5).
 *
 * Two rules shaped every decision below.
 *
 * 1. It has to work on a phone. That is a decision, not a nice-to-have. So: no fixed widths, no
 *    horizontal page scrolling ever, relative units throughout, and one column by default. Wider
 *    screens get more columns through a min-width query — the phone layout is the base case, not
 *    the fallback.
 * 2. The port is the identity, and it is never far from the name. It gets its own typographic
 *    treatment (monospace, boxed) precisely so it cannot be misread as part of the label.
 *
 * Colours live in custom properties and are declared twice: once for light, once inside a
 * `prefers-color-scheme: dark` block. Nothing below hard-codes a colour outside those two blocks.
 */

/* ---------------------------------------------------------------- design tokens */

:root {
    color-scheme: light dark;

    --bg: #f4f4f2;
    --surface: #ffffff;
    --surface-sunken: #ececea;
    --border: #d5d4cf;
    --border-strong: #b9b7b1;

    --text: #1b1a18;
    --text-muted: #66635d;

    --accent: #1d5e50;
    --accent-hover: #14453a;
    --accent-text: #ffffff;
    --accent-weak: #e2efeb;

    --danger: #96271f;
    --danger-weak: #fbe9e7;

    --notice: #7a5200;
    --notice-weak: #fdf1d8;

    --focus: #0b6bcb;

    --radius: 0.5rem;
    --radius-sm: 0.3rem;
    --gap: 1rem;
    --line: 1.55;

    --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-mono: ui-monospace, "SF Mono", "Cascadia Mono", "Segoe UI Mono", Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
    :root {
        --bg: #16181a;
        --surface: #1f2225;
        --surface-sunken: #191c1e;
        --border: #33383d;
        --border-strong: #4a5056;

        --text: #e7e5e1;
        --text-muted: #9ba1a6;

        --accent: #4fbfa2;
        --accent-hover: #74d3ba;
        --accent-text: #10201c;
        --accent-weak: #1c2f2a;

        --danger: #ef9b93;
        --danger-weak: #331d1b;

        --notice: #e5bd6b;
        --notice-weak: #302713;

        --focus: #6fb3ff;
    }
}

/* ---------------------------------------------------------------- base */

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    /* Percentage, not a pixel size: it scales with the reader's own font setting. */
    font-size: 100%;
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    /*
     * There is deliberately no `overflow-x: hidden` here.
     *
     * It looks like the rule that keeps the page from scrolling sideways on a phone, and it is not:
     * it keeps the SCROLLBAR from appearing while whatever overflowed still overflows, off screen,
     * unreachable and invisible. That is the symptom hidden and the fault kept. So nothing here
     * clips: every element that can hold something wide is made to wrap (`overflow-wrap: anywhere`
     * on names, addresses, paths and refusal texts), and the one genuinely wide thing — the stream —
     * scrolls inside its own box. If the page ever does scroll sideways, that is a bug worth seeing.
     */
    background: var(--bg);
    color: var(--text);
    font-family: var(--font);
    font-size: 1rem;
    line-height: var(--line);
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
}

h1,
h2,
h3 {
    margin: 0;
    line-height: 1.25;
    font-weight: 600;
}

h1 {
    font-size: 1.35rem;
}

h2 {
    font-size: 1.1rem;
}

p {
    margin: 0;
}

:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
}

[hidden] {
    display: none !important;
}

/* ---------------------------------------------------------------- layout */

.page {
    width: 100%;
    /* A reading measure, not a fixed width: below it, the page is fluid. */
    max-width: 60rem;
    margin: 0 auto;
    padding: 1rem;
    flex: 1 1 auto;
}

.page--narrow {
    max-width: 26rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.stack {
    display: flex;
    flex-direction: column;
    gap: var(--gap);
}

.stack--tight {
    gap: 0.5rem;
}

/* ---------------------------------------------------------------- top bar */

.topbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 0.75rem;
    padding: 0.6rem 1rem;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}

.topbar__brand {
    font-weight: 700;
    letter-spacing: 0.02em;
    /* Takes the leftover room so the buttons sit at the far end on wide screens, and wraps
       gracefully on a phone instead of squeezing the rest. */
    flex: 1 1 auto;
    min-width: 0;
}

.topbar__who {
    color: var(--text-muted);
    font-size: 0.9rem;
    min-width: 0;
    overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------- cards */

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

/* ---------------------------------------------------------------- forms */

.field {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}

.field__label {
    font-size: 0.9rem;
    font-weight: 600;
}

.field__hint {
    font-size: 0.85rem;
    color: var(--text-muted);
}

input[type="text"],
input[type="password"] {
    /* 100% of a flex column, never a fixed column count. */
    width: 100%;
    min-width: 0;
    padding: 0.6rem 0.7rem;
    font: inherit;
    /* 1rem exactly: iOS Safari zooms the page in on focus for anything smaller. */
    font-size: 1rem;
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
}

input::placeholder {
    color: var(--text-muted);
    opacity: 1;
}

input:disabled {
    background: var(--surface-sunken);
    color: var(--text-muted);
}

/* ---------------------------------------------------------------- buttons */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    /* Comfortably tappable with a thumb. */
    min-height: 2.75rem;
    padding: 0.55rem 0.95rem;
    font: inherit;
    font-weight: 600;
    line-height: 1.2;
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    cursor: pointer;
    text-decoration: none;
}

.btn:hover:not(:disabled) {
    border-color: var(--text-muted);
}

.btn:disabled {
    cursor: not-allowed;
    opacity: 0.55;
}

.btn--primary {
    color: var(--accent-text);
    background: var(--accent);
    border-color: var(--accent);
}

/*
 * Hover has to move in opposite directions in the two themes — darker on light, lighter on dark —
 * so it is a token rather than a filter. `brightness()` would lighten both, and on the dark theme
 * that reads as the button already being pressed.
 */
.btn--primary:hover:not(:disabled) {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
}

.btn--wide {
    width: 100%;
}

.btn--small {
    min-height: 2.25rem;
    padding: 0.35rem 0.7rem;
    font-size: 0.9rem;
}

.btn-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

/* ---------------------------------------------------------------- messages */

/*
 * Every failure lands in one of these. A message with no reason in it is a bug, not a style
 * choice: the server always says which condition fell, and the reason is what goes here.
 */
.message {
    padding: 0.7rem 0.85rem;
    border: 1px solid;
    border-radius: var(--radius-sm);
    font-size: 0.95rem;
    /* Server messages can carry long unbroken tokens (a path, a port list). They wrap; the page
       does not scroll sideways because of them. */
    overflow-wrap: anywhere;
}

.message--error {
    color: var(--danger);
    background: var(--danger-weak);
    border-color: var(--danger);
}

.message--notice {
    color: var(--notice);
    background: var(--notice-weak);
    border-color: var(--notice);
}

.message__title {
    display: block;
    font-weight: 700;
}

/* ---------------------------------------------------------------- the mandatory password change */

/*
 * The one screen the platform puts in front of somebody instead of the platform itself.
 *
 * It is a `.card` with a heavier border, and that is the whole visual difference: it must read as
 * something that has to be dealt with rather than something offered. What it must NOT be is a
 * modal — no overlay, no fixed positioning, no trapped scroll. This is a screen, hidden and shown
 * by the same `hidden` attribute as the other three (app.js `showScreen`), so on a small screen it
 * scrolls with the page like everything else and there is no layer to get stuck behind. A dialog
 * that cannot be closed is the shape people work around; a page with nothing else on it is not.
 *
 * No width of its own: it inherits `.page--narrow`, which is a reading measure in `rem` and fluid
 * below it.
 */
.gate {
    border-color: var(--border-strong);
    border-left: 3px solid var(--accent);
}

/*
 * Why this screen exists, and what the change is going to do. Full-width text at a smaller size,
 * wrapping — an account name is typed by a person and can be long, and nothing here may push the
 * page sideways.
 */
.gate__why {
    color: var(--text-muted);
    font-size: 0.9rem;
    overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------- application list */

.list-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
}

.count {
    color: var(--text-muted);
    font-size: 0.9rem;
}

.apps {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    /* One column on a phone. `auto-fill` with a minimum in rem adds columns only when they
       genuinely fit, so there is no breakpoint to maintain and never a squeezed card. */
    grid-template-columns: 1fr;
    gap: 0.75rem;
}

@media (min-width: 40rem) {
    .apps {
        grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
    }
}

.app {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.85rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.app__head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

/*
 * The port is the identity and is shown next to the name everywhere, always. Monospace and boxed
 * so it reads as a number the platform assigned, never as part of the label somebody typed.
 */
.port {
    flex: 0 0 auto;
    font-family: var(--font-mono);
    font-size: 0.95rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    padding: 0.15rem 0.45rem;
    color: var(--accent);
    background: var(--accent-weak);
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
}

.app__name {
    flex: 1 1 8rem;
    min-width: 0;
    font-size: 1.05rem;
    font-weight: 600;
    /* Names are free text and are not unique. A long one wraps rather than widening the card. */
    overflow-wrap: anywhere;
}

.app__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 0.75rem;
    font-size: 0.85rem;
    color: var(--text-muted);
}

.tag {
    display: inline-block;
    padding: 0.1rem 0.4rem;
    font-size: 0.8rem;
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    color: var(--text-muted);
}

/*
 * Names are labels, not keys: two applications may legitimately carry the same one. The list has
 * to say so out loud, otherwise the reader assumes the name identifies the application and picks
 * the wrong one.
 */
.tag--shared-name {
    color: var(--notice);
    background: var(--notice-weak);
    border-color: var(--notice);
}

.empty {
    padding: 1.25rem 1rem;
    text-align: center;
    color: var(--text-muted);
    background: var(--surface-sunken);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius);
}

/* ---------------------------------------------------------------- one application */

/*
 * The screen of one application, and every rule below it obeys the same two constraints as the rest
 * of this file — one column on a phone, nothing with a fixed width — plus a third that only applies
 * here: what is on this screen is the same for everybody watching, but HOW it is laid out is not.
 * "Vedem la fel" is the same content in the same place, not the same pixels (`10`), which is exactly
 * why the position that travels between these windows is a line or an item and never an offset.
 */

.app-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 0.75rem;
}

/*
 * Whether this window is receiving. It is a first-class thing on the screen rather than a detail,
 * because a stream that has quietly stopped looks exactly like an application where nothing is
 * happening — and those are opposite situations.
 */
.tag--link {
    font-family: var(--font-mono);
    font-size: 0.78rem;
}

.tag--link-live {
    color: var(--accent);
    background: var(--accent-weak);
    border-color: var(--accent);
}

.tag--link-opening,
.tag--link-waiting {
    color: var(--notice);
    background: var(--notice-weak);
    border-color: var(--notice);
}

.tag--link-closed {
    color: var(--danger);
    background: var(--danger-weak);
    border-color: var(--danger);
}

/* ------------------------------------------ the presence band */

.band,
.tv,
.flux {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    padding: 0.85rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.band__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.35rem 0.75rem;
}

.band__title {
    font-size: 0.95rem;
    text-transform: lowercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
}

.band__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    /* Chips wrap. On a phone that is a column of them; on a monitor, a row. Same band, two shapes. */
    flex-wrap: wrap;
    gap: 0.4rem;
}

.chip {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.25rem 0.5rem;
    /* Grows to the full width of a narrow screen instead of being squeezed into an unreadable box. */
    flex: 1 1 14rem;
    min-width: 0;
    padding: 0.35rem 0.6rem;
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    border-radius: 999px;
}

.chip__name {
    font-weight: 600;
    /* A name is free text typed by a person. It wraps; it does not widen the band. */
    overflow-wrap: anywhere;
}

.chip__meta {
    font-size: 0.78rem;
    color: var(--text-muted);
    /* An address can be a long IPv6 one. Same rule, same reason. */
    overflow-wrap: anywhere;
    min-width: 0;
}

.band__note {
    font-size: 0.8rem;
    color: var(--text-muted);
}

/* ------------------------------------------ the television */

.tv__tabs {
    display: flex;
    /* Wrapped rather than scrolled sideways: six short labels fit a phone in two rows, and a strip
       that scrolls horizontally hides the tabs that are off the end. */
    flex-wrap: wrap;
    gap: 0.4rem;
}

.tab {
    flex: 0 1 auto;
    min-height: 2.4rem;
    padding: 0.4rem 0.8rem;
    font: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--text);
    background: var(--surface-sunken);
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    cursor: pointer;
}

/*
 * The open tab is marked, and it is not marked as "the one you clicked": it is the screen everybody
 * watching this application is on. That is why it can change without this window touching anything.
 */
.tab--open {
    color: var(--accent-text);
    background: var(--accent);
    border-color: var(--accent);
}

.tv__moved {
    font-size: 0.9rem;
    overflow-wrap: anywhere;
}

/*
 * The transient line: who arrived, who left, who moved the screen. A change that is announced is
 * collaboration; a silent one is an interface losing its mind (`10`).
 */
.tv__announce {
    padding: 0.4rem 0.6rem;
    font-size: 0.9rem;
    color: var(--accent);
    background: var(--accent-weak);
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    overflow-wrap: anywhere;
}

/* ------------------------------------------ the stream */

.flux__note {
    font-size: 0.82rem;
    color: var(--text-muted);
}

/*
 * The scroll container, and the only thing on the page that scrolls inside itself.
 *
 * It has to be its own box for two reasons that both matter: a position is expressed against THIS
 * content, so the thing being scrolled must be the thing the anchors are in; and a phone must be
 * able to follow a running log without the whole document moving under the band and the tabs.
 *
 * `min(60vh, 30rem)` rather than a pixel height: it is a share of the screen on a phone and a
 * readable box on a monitor, and it shrinks with the viewport instead of pushing the page.
 */
.flux__list {
    list-style: none;
    margin: 0;
    padding: 0.25rem;
    max-height: min(60vh, 30rem);
    overflow-y: auto;
    /* The tail is what a live stream is usually read at, so growth pins to the bottom on its own. */
    overflow-anchor: auto;
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-size: 0.85rem;
}

/*
 * A stream with nothing in it yet is not drawn at all: an empty box with a border reads as a panel
 * that failed to load. The sentence underneath it says what the screen is waiting for, which is the
 * part worth showing.
 */
.flux__list:empty {
    display: none;
}

.row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.2rem 0.6rem;
    padding: 0.3rem 0.35rem;
    border-bottom: 1px solid var(--border);
}

.row:last-child {
    border-bottom: 0;
}

/*
 * The sequence number: monospace and boxed for the same reason the port is (`08` rule 4). It is the
 * platform's own key for this event — the cursor a reconnection resumes from, and the key a shared
 * position names — so it is the same number in everybody's window.
 */
.row__seq {
    flex: 0 0 auto;
    font-family: var(--font-mono);
    font-size: 0.75rem;
    color: var(--text-muted);
}

.row__when {
    flex: 0 0 auto;
    font-family: var(--font-mono);
    font-size: 0.75rem;
    color: var(--text-muted);
}

/*
 * Agent output arrives with its own line breaks and they are kept — but `anywhere` is set as well,
 * so a single 400-character token wraps instead of pushing this box sideways. `pre-wrap` alone is
 * how a log ends up with a horizontal scrollbar.
 */
.row__what {
    flex: 1 1 12rem;
    min-width: 0;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.row--agent-output .row__what {
    font-family: var(--font-mono);
}

/*
 * Where history is missing. Marked in the list itself, not only stated at the top: a list that
 * looked continuous across a gap would be the quiet failure the gap mechanism exists to prevent.
 */
.row--gap {
    padding: 0.45rem 0.35rem;
    color: var(--notice);
    background: var(--notice-weak);
    border-top: 1px dashed var(--notice);
    border-bottom: 1px dashed var(--notice);
    font-size: 0.8rem;
    overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------- misc */

.muted {
    color: var(--text-muted);
    font-size: 0.9rem;
}

.mono {
    font-family: var(--font-mono);
}

.busy {
    color: var(--text-muted);
    font-size: 0.9rem;
}

.footer {
    padding: 1rem;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.8rem;
}

/* Visible to a screen reader, out of the way of everyone else. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}
