/*
 * Sermoncast admin — one hand-written stylesheet.
 *
 * No framework and no build step. This is a single-operator tool with six screens; a CSS
 * framework would be more bytes than the whole application's templates and would have to be
 * kept current for the rest of the project's life. Everything here is plain CSS with custom
 * properties, and it follows the browser's light/dark preference rather than shipping a theme
 * switch nobody asked for.
 */

:root {
    color-scheme: light dark;

    --ink: #1b1b1f;
    --ink-soft: #55555f;
    --paper: #fbfbfd;
    --card: #ffffff;
    --line: #d9d9e0;
    --accent: #2f5d8a;
    --accent-ink: #ffffff;
    --ok: #1f6b3a;
    --ok-bg: #e6f4ea;
    --warn: #8a4b00;
    --warn-bg: #fdf1e0;
    --bad: #9b1c1c;
    --bad-bg: #fdeaea;
    --radius: 0.4rem;
}

@media (prefers-color-scheme: dark) {
    :root {
        --ink: #e8e8ee;
        --ink-soft: #a4a4b0;
        --paper: #16161a;
        --card: #1e1e24;
        --line: #35353f;
        --accent: #7aa9d6;
        --accent-ink: #10161d;
        --ok: #7ede9f;
        --ok-bg: #14301f;
        --warn: #f0b46a;
        --warn-bg: #33240f;
        --bad: #f39494;
        --bad-bg: #331616;
    }
}

* { box-sizing: border-box; }

body {
    margin: 0;
    background: var(--paper);
    color: var(--ink);
    font: 16px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/*
 * 80rem is 1280px at this root size — a 1K screen's width, which is what these screens are
 * actually used on. It was 62rem (992px), chosen as a comfortable measure for reading prose, and
 * the admin UI is not prose: it is tables with six columns, a waveform, and forms whose fields sit
 * in rows of three. On a 1280px screen that left a quarter of the window empty on each side while
 * the episode list wrapped.
 *
 * The public pages keep their narrow measure. congregation.html, privacy.html and home.html ARE
 * prose, read by listeners on phones, and a 1280px line of text is unreadable.
 */
main {
    max-width: 80rem;
    margin: 0 auto;
    padding: 1.5rem 1.25rem 4rem;
}

h1 { font-size: 1.5rem; margin: 0; }
h2 { font-size: 1.15rem; margin: 2rem 0 0.75rem; }
code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.85em; }

a { color: var(--accent); }

/* ---------------------------------------------------------------- the bar at the top */

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

.topbar .brand { font-weight: 700; text-decoration: none; color: var(--ink); }
.topbar nav { display: flex; gap: 1rem; }
.topbar nav a { text-decoration: none; }
.topbar nav a:hover { text-decoration: underline; }
.topbar .who {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.topbar .who form.inline { margin: 0; }

/*
 * THE ACCOUNT CORNER.
 *
 * It had been three unrelated shapes in a row — a bare text link, a pill-shaped badge and a
 * rounded-rectangle button — at three sizes, and normalising the type size alone did not fix it,
 * because a pill beside a rectangle still reads as two different kinds of thing.
 *
 * So there are TWO objects here now, not three: who you are, and one control. The name and the
 * role are one chip, because they answer one question, and the divider inside it is what tells
 * them apart — not a second border. The chip and the button then share a height, a radius, a
 * border and a type size, so the only thing distinguishing them is the one that should: the chip
 * is quiet, the control is not.
 *
 * The height is fixed rather than left to padding because a chip holding two spans and a button
 * holding one word compute different line boxes, and a two-pixel difference in a row of two
 * items is exactly the kind of thing that reads as "unfinished" without being nameable.
 */
.topbar .who .account,
.topbar .who button {
    height: 2rem;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0 0.65rem;
    font-size: 0.85rem;
    line-height: 1;
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

.topbar .who .account {
    background: var(--paper);
    color: var(--ink);
    text-decoration: none;
}

/* D14 put the password screen behind the account's own name, so the chip is a link — and a link
   that gives no sign of being one is a link nobody presses. */
.topbar .who .account:hover,
.topbar .who .account:focus-visible { border-color: var(--accent); color: var(--accent); }

.topbar .who .who-name { font-weight: 600; }

/* The role is subordinate to the name, so it is separated by a rule rather than boxed. */
.topbar .who .who-role {
    padding-left: 0.5rem;
    border-left: 1px solid var(--line);
    color: var(--ink-soft);
}

/* button.small lived here, away from the other modifiers, and was the one of them written for
   only half its selector. It is now beside its siblings in the button section below. */

/* ---------------------------------------------------------------- the sign-in page */

body.centred { display: grid; place-items: center; min-height: 100vh; }

.signin {
    width: min(24rem, calc(100vw - 2rem));
    padding: 2rem 1.5rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--card);
}

.signin h1 { font-size: 1.3rem; margin: 0 0 0.35rem; }
.signin .actions { margin-top: 0.5rem; }
.signin .actions button { width: 100%; justify-content: center; text-align: center; }

/* Matched to main above, or the footer's rule would sit short of the content it closes. */
.foot {
    max-width: 80rem;
    margin: 0 auto;
    padding: 0 1.25rem 2rem;
    color: var(--ink-soft);
    font-size: 0.85rem;
}

.page-head {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}

/*
 * The actions sit at the far end of the heading's row. `margin-left: auto` used to be on
 * `.page-head .button`, which matched the buttons INSIDE .actions rather than the group: the
 * first of them was pushed away from the second, so the pair arrived split across the row with a
 * gap between them instead of together on the right. Pushing the group is what was meant.
 */
.page-head .actions { margin-left: auto; }
.lead { color: var(--ink-soft); margin-top: 0; }
.hint { color: var(--ink-soft); font-size: 0.85rem; margin: 0.35rem 0 0; }
.empty { color: var(--ink-soft); font-style: italic; }

/* ---------------------------------------------------------------- flashes and badges */

.flash {
    padding: 0.7rem 0.9rem;
    border-radius: var(--radius);
    border: 1px solid var(--line);
    margin-bottom: 1rem;
}

.flash.ok { background: var(--ok-bg); border-color: var(--ok); color: var(--ok); }
.flash.error { background: var(--bad-bg); border-color: var(--bad); color: var(--bad); }

.badge {
    display: inline-block;
    padding: 0.1rem 0.45rem;
    border: 1px solid var(--line);
    border-radius: 1rem;
    font-size: 0.78rem;
    white-space: nowrap;
    color: var(--ink-soft);
}

.badge.ok { border-color: var(--ok); color: var(--ok); background: var(--ok-bg); }
.badge.warn { border-color: var(--warn); color: var(--warn); background: var(--warn-bg); }
.badge.busy { border-color: var(--accent); color: var(--accent); }

/* ---------------------------------------------------------------- tables */

table { width: 100%; border-collapse: collapse; margin-top: 0.5rem; }
th, td { text-align: left; padding: 0.55rem 0.6rem; border-bottom: 1px solid var(--line); vertical-align: top; }
th { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ink-soft); }
td.numeric { text-align: right; font-variant-numeric: tabular-nums; }
.path { margin-top: 0.25rem; word-break: break-all; }

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

.stack { display: flex; flex-direction: column; gap: 1rem; margin-top: 1rem; }
.row { display: flex; gap: 1rem; flex-wrap: wrap; }
.row > .field { flex: 1 1 14rem; }

.field label { display: block; font-weight: 600; font-size: 0.9rem; margin-bottom: 0.25rem; }
.field.checkbox { display: flex; align-items: center; gap: 0.5rem; }
.field.checkbox label { margin: 0; font-weight: 400; }

/*
 * Every input EXCEPT the handful that are not text boxes.
 *
 * Written as an exclusion rather than as a list of types, because the list was one: it named
 * text, url, email, number and date, and the login form's password field — added later — fell
 * straight through it to the browser's own styling, which on the sign-in page meant one full
 * width field above one narrow native one. The set of inputs that must NOT look like a text box
 * is small and finished; the set that must is open, and every future addition to it would have
 * arrived the same way.
 */
input:not([type="checkbox"], [type="radio"], [type="file"], [type="submit"], [type="button"],
          [type="reset"], [type="image"], [type="range"], [type="color"], [type="hidden"]),
select,
textarea {
    width: 100%;
    padding: 0.5rem 0.6rem;
    font: inherit;
    color: var(--ink);
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

textarea { resize: vertical; }

/*
 * One focus ring for everything, so that the field being typed into is obvious without each
 * control keeping the platform's own. :focus-visible rather than :focus, so a field clicked with
 * a mouse does not light up the way one reached by keyboard does.
 */
input:focus-visible, select:focus-visible, textarea:focus-visible, button:focus-visible,
.button:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
    border-color: var(--accent);
}

.field-errors {
    list-style: none;
    margin: 0.35rem 0 0;
    padding: 0;
    color: var(--bad);
    font-size: 0.85rem;
}

.actions { display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; }

button, .button {
    display: inline-block;
    padding: 0.5rem 0.9rem;
    font: inherit;
    border: 1px solid var(--accent);
    border-radius: var(--radius);
    background: var(--accent);
    color: var(--accent-ink);
    text-decoration: none;
    cursor: pointer;
}

/*
 * Every modifier below re-states BOTH background and colour, and that is the rule rather than
 * repetition. The base above sets a filled accent background with white text; a modifier that
 * changes only the colour keeps that background, so .danger — which used to set nothing but
 * border-color and color — rendered dark red text on dark blue and was unreadable. It looked
 * right only where it happened to be written as class="secondary danger", because .secondary
 * supplied the missing background. StylesheetSpec now refuses a modifier that sets one without
 * the other.
 */
button.secondary, .button.secondary {
    background: transparent;
    color: var(--accent);
}

button.danger, .button.danger {
    background: transparent;
    border-color: var(--bad);
    color: var(--bad);
}

/*
 * Both halves of the selector, like every modifier above, and that is the whole fix here. It was
 * written as `button.small` alone, which does not match <a class="button small"> — so in the
 * actions column of a table the edit LINK kept the full-size padding while the remove BUTTON
 * beside it shrank, and the pair looked like two different kinds of control. Anything that styles
 * a button has to name .button too, because a link and a button are used interchangeably for
 * these: whichever one submits a form is a <button>, and the rest are <a>.
 */
button.small, .button.small {
    padding: 0.2rem 0.55rem;
    font-size: 0.8rem;
}

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

/*
 * brightness() on a transparent background changes almost nothing, so the two outline variants
 * get a hover of their own. --bad-bg is the tint already paired with --bad in both themes, which
 * is what keeps the text readable on it without a second colour being chosen here.
 */
button.secondary:hover, .button.secondary:hover { background: var(--card); }
button.danger:hover, .button.danger:hover { background: var(--bad-bg); }

/* ------------------------------------------------------- pager, disclosure */

.pager { display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap; margin-top: 0.75rem; }
.pager .hint { margin: 0; }

/*
 * A <summary> styled as a button. list-style:none plus the ::-webkit-details-marker rule removes
 * the disclosure triangle, which would otherwise sit inside the button next to the label; Safari
 * needs the vendor pseudo-element even though every other engine honours list-style. display is
 * set explicitly because a bare <summary> is display:list-item, and inline-block is what makes it
 * size to its text the way the real buttons beside it do.
 */
.disclosure { margin-top: 1.5rem; }
.disclosure > summary {
    display: inline-block;
    list-style: none;
    user-select: none;
}
.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure[open] > summary { background: transparent; color: var(--accent); }

.filter { display: flex; gap: 0.75rem; align-items: end; flex-wrap: wrap; margin-bottom: 0.5rem; }
.filter label { font-size: 0.9rem; font-weight: 600; display: block; }
.filter select, .filter input { width: auto; }
.filter .queue { color: var(--ink-soft); font-size: 0.9rem; }

/* One control and its label as a unit, so a wrapped filter row breaks between fields rather than
   between a label and the box it names. */
.filter-field { display: flex; flex-direction: column; gap: 0.2rem; }

/* ------------------------------------------------- sortable columns, and the status column */

/*
 * A heading that is a link, drawn as a heading. The whole cell is the target — a two-word column
 * name is a small thing to hit, and the padding around it is dead space that looks clickable.
 */
table.sortable th { padding: 0; }

table.sortable th a {
    display: block;
    padding: 0.55rem 0.6rem;
    color: inherit;
    text-decoration: none;
    white-space: nowrap;
}

table.sortable th a:hover { color: var(--accent); }

/*
 * The arrow is content here rather than a character in the template, which keeps every visible
 * glyph out of the HTML (N4) and means the indicator cannot be left behind when a column changes.
 */
table.sortable th.sorted a { color: var(--accent); font-weight: 700; }
table.sortable th.sorted.asc a::after { content: " \2191"; }
table.sortable th.sorted.desc a::after { content: " \2193"; }

/*
 * The status as WORDS. It was a pill, and a column of pills is a column of things each asking to
 * be looked at first — which is the opposite of what a column is for. Plain text lines up, so the
 * eye can run down it; colour carries the two states that are not ordinary, and nothing else is
 * decorated at all.
 */
.status { font-size: 0.9rem; }
.status-PUBLISHED { color: var(--ok); font-weight: 600; }
.status-FAILED { color: var(--bad); font-weight: 600; }

.facts { display: grid; grid-template-columns: max-content 1fr; gap: 0.35rem 1rem; }
.facts dt { color: var(--ink-soft); font-size: 0.9rem; }
.facts dd { margin: 0; word-break: break-all; }

.cover-preview {
    display: block;
    width: 10rem;
    height: 10rem;
    object-fit: cover;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    margin-bottom: 0.5rem;
    background: var(--card);
}

/* ---------------------------------------------------------------- permission (§2.7) */

.permission {
    padding: 0.7rem 0.9rem;
    border: 1px solid var(--line);
    border-left-width: 4px;
    border-radius: var(--radius);
    margin: 1rem 0;
    background: var(--card);
}

.permission.ok { border-left-color: var(--ok); }
.permission.warn { border-left-color: var(--warn); background: var(--warn-bg); color: var(--warn); }
.permission code { display: block; margin-top: 0.25rem; word-break: break-all; }

/* ---------------------------------------------------------------- the job panel */

.job {
    padding: 0.9rem 1rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--card);
    margin: 1rem 0;
}

.job.placeholder { border: none; background: none; padding: 0; margin: 0; }
.job h2 { margin-top: 0; }
.job-state { margin: 0 0 0.6rem; color: var(--ink-soft); font-size: 0.9rem; }
.job .failed { color: var(--bad); }
.job .failed code { display: block; margin: 0.35rem 0; word-break: break-word; }
.job .detail { color: var(--ink-soft); font-size: 0.85rem; margin: 0.5rem 0 0; }

.steps {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    list-style: none;
    margin: 0 0 0.75rem;
    padding: 0;
    font-size: 0.8rem;
}

.steps li {
    padding: 0.15rem 0.5rem;
    border: 1px solid var(--line);
    border-radius: 1rem;
    color: var(--ink-soft);
}

.steps li.done { border-color: var(--ok); color: var(--ok); }
.steps li.current { border-color: var(--accent); color: var(--accent); font-weight: 700; }

.bar {
    position: relative;
    height: 1.35rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--paper);
}

.bar-fill { height: 100%; background: var(--accent); transition: width 0.4s linear; }

.bar-label {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
    mix-blend-mode: difference;
    color: #ffffff;
}

/* An indeterminate bar, for RESOLVE and DOWNLOAD, which report no fraction at all. */
.bar.indeterminate .bar-fill {
    width: 35%;
    animation: sweep 1.6s ease-in-out infinite;
}

@keyframes sweep {
    0% { margin-left: -35%; }
    100% { margin-left: 100%; }
}

@media (prefers-reduced-motion: reduce) {
    .bar.indeterminate .bar-fill { animation: none; width: 100%; opacity: 0.35; }
    .bar-fill { transition: none; }
}

/* ---------------------------------------------------------------- the waveform (§6.3) */

.waveform-section { margin: 1.5rem 0; }

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

.waveform-canvas {
    display: block;
    width: 100%;
    height: 180px;
    cursor: crosshair;
    touch-action: none;
}

/*
 * Read-only while a job runs. The <fieldset disabled> over the form below cannot reach this — a
 * canvas is not a form control — and the picture is worth keeping: it shows the selection that is
 * at this moment being cut. What it must not do is accept a new one, because the job already has
 * its numbers and the screen would quietly stop describing what is happening.
 *
 * pointer-events is the visible half; waveform.js checks the same state before touching a field,
 * so a stylesheet that fails to load cannot hand the gestures back.
 */
.waveform.inert .waveform-canvas {
    pointer-events: none;
    cursor: default;
    opacity: 0.6;
}

.waveform-status { margin: 0.4rem 0 0; color: var(--ink-soft); font-size: 0.85rem; min-height: 1.2em; }
.waveform-audio { display: block; width: 100%; margin-top: 0.6rem; }

/* ---------------------------------------------------------------- the seam previews (§8.3.3) */

/*
 * Empty until a preview has been asked for, and it must not reserve space while it is: an empty
 * gap under every timestamp field would read as something having failed to load.
 */
.seam:empty { display: none; }
.seam { margin-top: 0.5rem; }
.seam-audio { display: block; width: 100%; }

/*
 * The strip under a seam player saying which half of the clip survives.
 *
 * Both halves of the clip are audible on purpose — that is what the preview is for — which is
 * exactly why hearing alone cannot say which is which: a quiet passage might be the discarded
 * run-up or it might be the preacher pausing. Red is thrown away, accent is published.
 *
 * seam.js sets the two widths and the direction from the clamped lengths, so a cut two seconds
 * into the source shows the seam two seconds along and not a third of the way.
 */
.seam-bar {
    position: relative;
    display: flex;
    height: 0.7rem;
    margin-top: 0.3rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
}

.seam-bar .seam-cut { background: var(--bad); opacity: 0.55; }
.seam-bar .seam-kept { background: var(--accent); }

/*
 * WHICH WAY ROUND, and where the seam line goes — both here, and that is the fix rather than a
 * tidy-up. A head clip is discarded-then-kept and a tail clip is kept-then-discarded, so the two
 * are mirror images; the direction used to be set by seam.js and the line by this file, and they
 * disagreed. `border-inline-start` is the LEFT edge in a left-to-right document whatever the flex
 * direction is, so on a tail clip — where the kept span is laid out first, on the left — the line
 * drew at the outer edge of the bar and marked t=0 as the cut, while the real seam two thirds
 * along had nothing on it. The colours were right, so it misled rather than lied.
 *
 * The markup order is always [cut, kept]; row-reverse is what puts the kept part first for a tail.
 */
.seam-bar[data-seam-boundary="head"] { flex-direction: row; }
.seam-bar[data-seam-boundary="tail"] { flex-direction: row-reverse; }

.seam-bar[data-seam-boundary="head"] .seam-kept { border-inline-start: 2px solid var(--ink); }
.seam-bar[data-seam-boundary="tail"] .seam-kept { border-inline-end: 2px solid var(--ink); }

.seam-bar .seam-playhead {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 2px;
    margin-left: -1px;
    background: var(--ink);
    mix-blend-mode: difference;
}

.seam-legend {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    margin: 0.3rem 0 0;
    color: var(--ink-soft);
    font-size: 0.8rem;
}

.seam-legend .seam-key {
    display: inline-block;
    width: 0.7rem;
    height: 0.7rem;
    border-radius: 0.15rem;
}

.seam-legend .seam-key.cut { background: var(--bad); opacity: 0.55; }
.seam-legend .seam-key.kept { background: var(--accent); margin-left: 0.6rem; }

/* ---------------------------------------------------------------- the produced / published view */

.result { margin-top: 1.5rem; }
.result-audio { display: block; width: 100%; margin: 0.6rem 0 1rem; }
.result .facts { margin-top: 0.5rem; }

/*
 * A fieldset used only to carry `disabled` over a whole form at once. It has no border, no
 * padding and no legend: the grouping is behavioural, not visual, and anything else would draw a
 * box round the editor that means nothing to the operator.
 */
fieldset.bare {
    border: 0;
    padding: 0;
    margin: 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

/* Everything inside a disabled fieldset, including the htmx buttons, which fire no request. */
fieldset.bare:disabled { opacity: 0.6; }
button:disabled, .button:disabled { cursor: not-allowed; opacity: 0.65; filter: none; }

/*
 * The snap and preview buttons sit under their field rather than beside it. Beside would be
 * right on a wide screen and wrong on a narrow one, where the button and a 100%-wide input
 * cannot share a line; .actions already wraps, and starting wrapped is one less layout to
 * think about.
 */
.field .actions { margin-top: 0.4rem; }

.publish { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--line); }
.publish form { margin-top: 0.5rem; }
