/* Admin UI
 *
 * Palette taken from the wromance.com site (seo.wromance.com): navy #091227 as
 * the dark surface, #E53272 as the accent, #87B7FF for quiet secondary accents.
 * Layout tokens (radius, shadow, control sizing) follow the author form so the
 * two read as one product.
 */

/* Manrope, the site's typeface. Self-hosted rather than pulled from Google so
   the CSP can keep font-src at 'self'. One variable file per subset covers the
   whole 200–800 range. */
@font-face {
    font-family: 'Manrope';
    font-style: normal;
    font-weight: 200 800;
    font-display: swap;
    src: url('/admin/assets/fonts/manrope-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
        U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
        U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'Manrope';
    font-style: normal;
    font-weight: 200 800;
    font-display: swap;
    src: url('/admin/assets/fonts/manrope-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
        U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
        U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Author names and book titles can arrive in Cyrillic. */
@font-face {
    font-family: 'Manrope';
    font-style: normal;
    font-weight: 200 800;
    font-display: swap;
    src: url('/admin/assets/fonts/manrope-cyrillic.woff2') format('woff2');
    unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

:root {
    --white: #FFFFFF;
    --primary: #091227;
    --navy-deep: #07101F;
    --navy-soft: #1C3055;
    --accent: #E53272;
    --accent-dark: #C42760;
    --blue-light: #87B7FF;
    --blue-mid: #335495;
    --light-gray: #F8F9FA;
    --border-gray: #E5E7EB;
    --text-gray: #4B5563;
    --shadow: 0 4px 12px rgba(9, 18, 39, 0.08);
    --radius: 12px;
    --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);

    --sidebar-width: 240px;
    --header-height: 72px;
    /* One height for every control in a form — text, dropdown, date, money,
       and the ghost button beside them. */
    --ac-control-h: 44px;
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

/* The UA rule for [hidden] is display:none at the lowest specificity, so any
   class setting display — .btn, .modal-backdrop — silently overrides it. */
[hidden] {
    display: none !important;
}

body {
    font-family: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
        'Helvetica Neue', sans-serif;
    background-color: var(--light-gray);
    color: var(--primary);
    line-height: 1.7;
    font-size: 16px;
    min-height: 100vh;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* ---------- Login ---------- */

.login-page {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    padding: 24px;
    background-color: var(--white);
}

.login-card {
    width: 100%;
    max-width: 420px;
    background: var(--white);
    border: 1px solid var(--border-gray);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 48px 40px;
}

.login-logo {
    display: block;
    height: 64px;
    width: auto;
    margin: 0 auto 28px;
}

.login-card h1 {
    font-size: 1.35rem;
    letter-spacing: -0.02em;
    margin-bottom: 6px;
    text-align: center;
}

.login-card .subtitle {
    color: var(--text-gray);
    font-size: 0.95rem;
    margin-bottom: 32px;
    text-align: center;
}

.field {
    margin-bottom: 20px;
}

.field label {
    display: block;
    font-weight: 600;
    font-size: 0.95rem;
    margin-bottom: 8px;
}

input[type="text"],
input[type="password"] {
    width: 100%;
    padding: 14px 18px;
    border: 2px solid var(--border-gray);
    border-radius: var(--radius);
    font-size: 1rem;
    font-family: inherit;
    color: var(--primary);
    background: var(--white);
    transition: var(--transition);
}

input[type="text"]:focus,
input[type="password"]:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 4px rgba(229, 50, 114, 0.12);
}

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 14px 28px;
    border: 2px solid transparent;
    border-radius: var(--radius);
    font-size: 1rem;
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
    transition: var(--transition);
}

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

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

.btn-primary:hover:not(:disabled) {
    background-color: var(--accent-dark);
    border-color: var(--accent-dark);
    transform: translateY(-2px);
    box-shadow: 0 8px 24px rgba(229, 50, 114, 0.4);
}

.btn-ghost {
    background: transparent;
    color: var(--text-gray);
    border-color: var(--border-gray);
    padding: 10px 18px;
    font-size: 0.9rem;
}

.btn-ghost:hover {
    color: var(--primary);
    border-color: var(--primary);
}

.btn-block {
    width: 100%;
}

.message {
    display: none;
    padding: 12px 16px;
    border-radius: var(--radius);
    font-size: 0.9rem;
    margin-bottom: 20px;
}

/* A message that is not a failure — an acknowledgement to give, say — carries
   its own note inside and needs the box shown without the error's colours. */
.message--note {
    display: block;
    padding: 0;
    background: none;
    border: 0;
}

.message.error {
    display: block;
    background: rgba(229, 50, 114, 0.1);
    color: var(--accent-dark);
    border: 1px solid rgba(229, 50, 114, 0.3);
}

/* ---------- App shell ---------- */

/* The shell owns the viewport height and hands it down to the table, so the
   table body is what scrolls and the header can stay put above it. */
.app {
    display: none;
    height: 100vh;
}

.app.ready {
    display: flex;
}

.sidebar {
    width: var(--sidebar-width);
    flex: 0 0 var(--sidebar-width);
    background: var(--primary);
    color: var(--white);
    display: flex;
    flex-direction: column;
    padding: 28px 0;
}

.sidebar .brand {
    padding: 0 24px 24px;
    border-bottom: 1px solid rgba(135, 183, 255, 0.16);
}

/* The site ships a white version of the mark, which is what the dark rail
   needs — the dark .png used on the author form would disappear here. */
.sidebar .brand img {
    height: 44px;
    width: auto;
    display: block;
}

.sidebar nav {
    padding-top: 20px;
    display: flex;
    flex-direction: column;
}

/* ---------- groups in the rail (TASK-072) ----------
 *
 * The header is a button that folds its group and never navigates. It carries
 * the same 3px transparent left border as an item so its title lines up with
 * the icons below it. */
.nav-group__header {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 16px 24px 6px;
    border: 0;
    border-left: 3px solid transparent;
    background: none;
    color: rgba(255, 255, 255, 0.4);
    font-family: inherit;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    text-align: left;
    white-space: nowrap;
    overflow: hidden;
    cursor: pointer;
    transition: var(--transition);
}

.nav-group__header:hover {
    color: rgba(255, 255, 255, 0.72);
}

.nav-group__title {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.nav-group__chevron {
    width: 14px;
    height: 14px;
    transition: var(--transition);
}

/* Rotating the chevron rather than swapping the symbol: it is the same arrow
   pointing somewhere else, and the turn shows which way the group is going. */
.nav-group.folded .nav-group__chevron {
    transform: rotate(-90deg);
}

/* A column of its own, like the nav around it: a button sizes to its content,
   so without the stretch "Storage" would wear a hover strip narrower than the
   rail. */
.nav-group__items {
    display: flex;
    flex-direction: column;
}

.nav-group.folded .nav-group__items {
    display: none;
}

/* The section you are in must never vanish. Fold its group by hand and the
   header keeps a dot where the children were. */
.nav-group__dot {
    display: none;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent);
}

.nav-group.folded.has-active .nav-group__dot {
    display: block;
}

/* The children are indented, the marker is not: it stays at the rail's edge so
   the eye finds the active row in the same place in every group. */
.nav-group__items .nav-item {
    padding-left: 40px;
}

.nav-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 24px;
    color: rgba(255, 255, 255, 0.72);
    text-decoration: none;
    font-weight: 500;
    border-left: 3px solid transparent;
    transition: var(--transition);
    cursor: pointer;
    background: none;
    border-top: 0;
    border-right: 0;
    border-bottom: 0;
    font-family: inherit;
    font-size: 1rem;
    text-align: left;
    white-space: nowrap;
    overflow: hidden;
}

.nav-item .icon {
    flex: 0 0 auto;
}

.nav-item:hover {
    color: var(--white);
    background: rgba(135, 183, 255, 0.08);
}

.nav-item.active {
    color: var(--white);
    border-left-color: var(--accent);
    background: rgba(229, 50, 114, 0.16);
}

/* Collapsed to a rail of icons, because the table wants the room and the menu
   is three items anyone has already learned. The labels become tooltips; the
   choice is remembered, since whoever wants the width wants it every day. */
.sidebar-toggle {
    margin-top: auto;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 24px;
    background: none;
    border: 0;
    border-top: 1px solid rgba(135, 183, 255, 0.16);
    color: rgba(255, 255, 255, 0.55);
    font-family: inherit;
    font-size: 0.9rem;
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
    transition: var(--transition);
}

.sidebar-toggle:hover {
    color: var(--white);
    background: rgba(135, 183, 255, 0.08);
}

.sidebar-toggle .icon {
    flex: 0 0 auto;
}

.app.sidebar-collapsed .sidebar {
    width: 64px;
    flex-basis: 64px;
}

.app.sidebar-collapsed .nav-item__label {
    display: none;
}

.app.sidebar-collapsed .brand {
    padding: 0 0 24px;
}

.app.sidebar-collapsed .brand img {
    height: 28px;
    margin: 0 auto;
}

.app.sidebar-collapsed .nav-item,
.app.sidebar-collapsed .sidebar-toggle {
    padding-left: 0;
    padding-right: 0;
    justify-content: center;
    gap: 0;
}

.app.sidebar-collapsed .nav-item {
    border-left-width: 3px;
    padding-left: 0;
}

/* Collapsed, a group header is a word with nowhere to go, and a group folded
   behind a header nobody can read is not a state worth reaching. The header
   leaves entirely — no reserved space — and the group becomes a divider
   between two runs of icons. Seven items do not need flyout submenus. */
.app.sidebar-collapsed .nav-group__header {
    display: none;
}

.app.sidebar-collapsed .nav-group.folded .nav-group__items {
    display: flex;
}

/* `~` and not `+`: a group hidden by role can sit between two visible ones,
   and a rule that only looked at the previous sibling would either miss the
   divider or draw one above the first run. */
.app.sidebar-collapsed .nav-group:not([hidden]) ~ .nav-group:not([hidden]) {
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid rgba(135, 183, 255, 0.16);
}

.main {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.topbar {
    position: relative;
    height: var(--header-height);
    flex: 0 0 var(--header-height);
    background: var(--white);
    border-bottom: 1px solid var(--border-gray);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 32px;
}

/* Which installation this is. Centred by position rather than by flex order:
   the section title on the left and the user block on the right are never the
   same width, so a third flex child would sit wherever they left room. TASK-107. */
.env-badge {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    padding: 4px 14px;
    border-radius: 4px;
    background: #b45309;
    color: #fff;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    white-space: nowrap;
    pointer-events: none;
}

/* The login page has no header to sit in. */
.env-badge--fixed {
    position: fixed;
    top: 16px;
    z-index: 10;
}

.topbar h1 {
    font-size: 1.25rem;
    font-weight: 600;
    letter-spacing: -0.02em;
}

.topbar .user {
    display: flex;
    align-items: center;
    gap: 16px;
    color: var(--text-gray);
    font-size: 0.95rem;
}

.content {
    flex: 1;
    min-height: 0;
    padding: 32px;
    overflow: auto;
    display: flex;
    flex-direction: column;
}

.content > section {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.panel {
    background: var(--white);
    border: 1px solid var(--border-gray);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 40px;
}

.placeholder {
    color: var(--text-gray);
}

.placeholder h2 {
    font-size: 1.1rem;
    color: var(--primary);
    margin-bottom: 8px;
}

/* On a 1280- or 1440-wide laptop the page padding is what pushes the forms
   table into a horizontal scroll. Give some of it back. */
@media (max-width: 1440px) {
    .content {
        padding: 24px;
    }
}

@media (max-width: 768px) {
    :root {
        --sidebar-width: 64px;
    }

    /* The rail is already this narrow here, so it wears the collapsed face
       whatever the toggle says — a label has nowhere to go in 64px. */
    .nav-item__label {
        display: none;
    }

    .sidebar .brand,
    .nav-item,
    .nav-group__items .nav-item,
    .sidebar-toggle {
        padding-left: 0;
        padding-right: 0;
        justify-content: center;
        gap: 0;
    }

    /* Same reason as the collapsed rail: no room for a header, so the groups
       are dividers here too, and nothing stays folded behind a title that is
       not on screen. */
    .nav-group__header {
        display: none;
    }

    .nav-group.folded .nav-group__items {
        display: flex;
    }

    .nav-group:not([hidden]) ~ .nav-group:not([hidden]) {
        margin-top: 10px;
        padding-top: 10px;
        border-top: 1px solid rgba(135, 183, 255, 0.16);
    }

    .sidebar .brand img {
        height: 28px;
        margin: 0 auto;
    }

    .content {
        padding: 16px;
    }

    .panel {
        padding: 24px;
    }
}

/* ---------- Toolbar ---------- */

/* Buttons on the left, the search box on the right, and the two never stack:
   the box has to be findable in the same place whatever is on the left, and a
   box that drops under the buttons mid-word is the jump this layout exists to
   stop. Contracts is the widest of the four and runs out of room first — when
   it does, the buttons wrap among themselves and the box stays put. */
.toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 20px;
    flex-wrap: nowrap;
}

.toolbar-left,
.toolbar-right {
    display: flex;
    align-items: center;
    gap: 12px;
}

.toolbar-left {
    flex-wrap: wrap;
    min-width: 0;
}

/* Never squeezed to make room for the left-hand side: it is a field somebody is
   typing into, and a field that narrows as a button appears has moved. */
.toolbar-right {
    flex: none;
}

/* Toolbar buttons share one size regardless of variant, so Create New Form and
   Show Archived line up. */
.toolbar .btn {
    padding: 10px 18px;
    font-size: 0.9rem;
}

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

/* The line under the toolbar: the row count, and anything else that reports
   rather than acts. Out of the toolbar because a count is a footnote to the
   table — it took header width for a number nobody presses, and it was one more
   thing the search box had to fit beside.

   `min-height` is the tallest thing the line can hold — the deploy pill on Book
   Forms — so the table below does not move when the count arrives or the pill
   comes and goes. */
.table-note {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 10px;
    min-height: 30px;
}

.table-note .row-count {
    font-size: 0.85rem;
}

/* The toolbar's own 20px would leave the note stranded halfway between the
   buttons and the table it belongs to. */
.toolbar:has(+ .table-note) {
    margin-bottom: 10px;
}

/* ---------- Table ---------- */

.table-panel {
    padding: 0;
    overflow: hidden;
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* This is the scrolling element, not the page — that is what makes the sticky
   header stick, and it keeps a column's filter button reachable however far
   down the list you are. */
.table-scroll {
    flex: 1;
    min-height: 0;
    overflow: auto;
}

.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9rem;
}

/* Nine columns have to share the width left over after the sidebar. The
   horizontal padding is the cheapest thing to spend less of: 4px off each side
   is 72px across the row, more than any single column would give up. */
.data-table th,
.data-table td {
    padding: 13px 12px;
    text-align: left;
    border-bottom: 1px solid var(--border-gray);
    white-space: nowrap;
}

.data-table th {
    background: var(--light-gray);
    font-weight: 600;
    font-size: 0.76rem;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    color: var(--text-gray);
    position: sticky;
    top: 0;
    z-index: 1;
    /* border-collapse leaves the header's own border behind when it sticks, so
       the line under it is drawn inside the cell instead. */
    border-bottom: 0;
    box-shadow: inset 0 -1px 0 var(--border-gray);
}

.data-table tbody tr {
    cursor: pointer;
    transition: background 0.15s ease;
}

.data-table tbody tr:hover {
    background: rgba(135, 183, 255, 0.08);
}

.data-table tbody tr:last-child td {
    border-bottom: 0;
}

.th-inner {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.th-label {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
    cursor: default;
}

th[data-sort] .th-label {
    cursor: pointer;
}

th[data-sort] .th-label:hover {
    color: var(--primary);
}

.sort-arrow {
    font-size: 0.7rem;
    color: var(--accent);
}

/* Tabler icons, pulled from one sprite. currentColor lets each context set the
   colour; size is set per use site. */
.icon {
    width: 18px;
    height: 18px;
    flex: 0 0 auto;
    stroke-width: 2;
}

.icon--sm { width: 14px; height: 14px; }
.icon--lg { width: 20px; height: 20px; }

.filter-btn {
    background: none;
    border: 0;
    padding: 2px;
    cursor: pointer;
    color: #9CA3AF;
    line-height: 0;
    border-radius: 4px;
    display: inline-flex;
}

.filter-btn:hover {
    color: var(--primary);
}

.filter-btn.on {
    color: var(--accent);
}

.cell-id {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.82rem;
    color: var(--text-gray);
}

/* Author, series and books take whatever width is left and clip there. The
   dashed underline — and the click that opens the full value — appear only when
   a cell is really cut off, which depends on the window, so book-forms.js
   measures it rather than guessing from the character count. */
/* Below this the headers themselves stop fitting, so let it scroll rather than
   overlap. Measured from the widest header plus the widest badge row. */
.data-table--fixed {
    table-layout: fixed;
    min-width: 960px;
}

.col-w-audio { width: 84px; }

/* The sample turning out to be the same audio as a chapter is not a fault — an
   author may well offer chapter one as the sample. Marked so nobody wonders why
   the folder holds it twice, in the quiet blue rather than the accent. */
.audio-note { color: var(--blue-mid); }

.audio-note__mark {
    display: inline-block;
    margin-left: 4px;
    font-weight: 700;
}

.form-view .audio-note__text { color: var(--blue-mid); }
.col-w-date { width: 132px; }
.col-w-status { width: 128px; }
.col-w-user { width: 98px; }
.col-w-type { width: 90px; }
/* Four icon buttons: copy link, tropes, deploy, archive. It was 116 while there
   were three, and the sixteen pixels that did not fit were the whole reason the
   table scrolled sideways on any window. */
/* Four icons plus the cell's own padding. The books table carries a fifth. */
.col-w-actions { width: 152px; }
.col-w-actions--5 { width: 176px; }

.data-table--fixed td.col-text,
.data-table--fixed td.col-user {
    overflow: hidden;
    text-overflow: ellipsis;
}

td.col-text.clipped .cell-text {
    cursor: pointer;
    border-bottom: 1px dashed var(--border-gray);
}

td.col-text.clipped .cell-text:hover {
    border-bottom-color: var(--accent);
}

.muted {
    color: #9CA3AF;
}

.col-actions {
    text-align: right;
}

td.col-actions {
    text-align: right;
    white-space: nowrap;
}

.icon-btn {
    background: none;
    border: 0;
    cursor: pointer;
    padding: 6px;
    border-radius: 6px;
    line-height: 0;
    color: var(--text-gray);
    display: inline-flex;
    transition: var(--transition);
}

.icon-btn:hover:not(:disabled) {
    color: var(--primary);
    background: rgba(9, 18, 39, 0.06);
}

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

/* A button that will not do its job but still answers when asked why. It looks
   spent, like a disabled one, and stays clickable — a disabled button fires no
   click event, and the reason is shown on click rather than on hover. */
.icon-btn.is-blocked {
    opacity: 0.3;
}

.icon-btn.is-blocked:hover {
    opacity: 0.55;
    color: var(--text-gray);
    background: none;
}

.icon-btn--danger:hover:not(:disabled) {
    color: var(--accent);
    background: rgba(229, 50, 114, 0.08);
}

.icon-btn--ok {
    color: #10704A;
}

/* ---------- Filter popup search ---------- */

.filter-pop__search {
    margin-bottom: 8px;
}

.filter-pop__search input {
    width: 100%;
    padding: 8px 12px;
    font-size: 0.88rem;
    border: 2px solid var(--border-gray);
    border-radius: 8px;
    font-family: inherit;
}

.filter-pop__search input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(229, 50, 114, 0.1);
}

.filter-pop__empty {
    padding: 12px 4px;
    color: var(--text-gray);
    font-size: 0.85rem;
}

/* ---------- Status badges ---------- */

/* A floor of six characters, so "off", "user" and "public" are the same pill and
   a column of them reads as a column. Anything longer sets its own width.
   inline-flex rather than inline-block: the text has to sit in the middle of the
   pill, not at the start of it. */
.badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Six characters of text. The padding is added on because box-sizing is
       border-box everywhere here, and without it the floor would be six
       characters minus the padding. */
    min-width: calc(6ch + 20px);
    padding: 4px 10px;
    border-radius: 20px;
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    white-space: nowrap;
    text-align: center;
}

.badge--pending      { background: #F1F3F5; color: #6B7280; }
.badge--submitted    { background: rgba(135, 183, 255, 0.22); color: #2B5FA8; }
.badge--submit_fail  { background: rgba(229, 50, 114, 0.14); color: var(--accent-dark); }
.badge--running      { background: rgba(255, 176, 32, 0.18); color: #9A6200; }
.badge--deployed     { background: rgba(95, 212, 155, 0.22); color: #10704A; }
.badge--deploy_fail  { background: rgba(229, 50, 114, 0.14); color: var(--accent-dark); }

.badge--private { background: #F1F3F5; color: #6B7280; }
.badge--public  { background: rgba(135, 183, 255, 0.22); color: #2B5FA8; }

.table-empty {
    padding: 48px;
    text-align: center;
    color: var(--text-gray);
}

/* ---------- Popups ---------- */

/* Absolute, and closed on any scroll — a popup that follows the viewport while
   the rows move underneath it points at nothing. The sticky header means the
   filter button is still where you left it. */
.filter-pop,
.hint-pop {
    position: absolute;
    z-index: 60;
    background: var(--white);
    border: 1px solid var(--border-gray);
    border-radius: var(--radius);
    box-shadow: 0 12px 32px rgba(9, 18, 39, 0.18);
}

.filter-pop {
    width: 280px;
    padding: 12px;
}

.filter-pop__head {
    display: flex;
    gap: 16px;
    padding-bottom: 10px;
    margin-bottom: 8px;
    border-bottom: 1px solid var(--border-gray);
}

.link-btn {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    font-size: 0.85rem;
    color: var(--accent);
    cursor: pointer;
}

.link-btn:hover {
    text-decoration: underline;
}

/* Folder name, then how much of it is left, then the link — one row each. The
   name is the only part allowed to wrap, so it can never run under the link. */
.storage-list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.storage-list li {
    display: flex;
    align-items: baseline;
    gap: 12px;
}

.storage-list__name {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 0.92rem;
    overflow-wrap: anywhere;
}

.storage-list__count,
.storage-list__gone {
    flex: 0 0 auto;
    font-size: 0.85rem;
    color: var(--text-gray);
}

.storage-list__gone {
    color: var(--accent);
}

.storage-list .link-btn {
    flex: 0 0 auto;
}

.note {
    font-size: 0.88rem;
    color: var(--text-gray);
    margin-top: 12px;
}

.note--warn {
    color: var(--accent);
}

/* The retry link, in the failure modal. */
.retry-block {
    margin: 12px 0 20px;
}

.retry-link {
    display: flex;
    gap: 8px;
    align-items: center;
    margin-bottom: 8px;
}

.retry-link input {
    flex: 1 1 auto;
    min-width: 0;
    padding: 10px 12px;
    border: 2px solid var(--border-gray);
    border-radius: 10px;
    font-size: 0.88rem;
    font-family: inherit;
    color: var(--primary);
    background: var(--white);
}

.retry-link .btn {
    flex: 0 0 auto;
}

/* A name n8n sent that pairs with no user here. */
.user-unmatched {
    color: var(--accent);
    font-weight: 700;
    cursor: help;
}

/* What a form is short of before it can be deployed — named one per line, in
   the form view and in the deploy refusal. */
.missing-list {
    margin: 8px 0 0;
    padding-left: 18px;
    font-size: 0.9rem;
    color: var(--accent);
}

.missing-list li {
    margin-bottom: 4px;
}

/* Sits under a field in the edit modal to say why it is there. */
.form-edit .field-note {
    display: block;
    margin-top: 6px;
    font-size: 0.82rem;
    font-weight: 400;
    color: var(--text-gray);
}

.filter-pop__list {
    max-height: 300px;
    overflow-y: auto;
}

.filter-pop__list label {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 4px;
    font-size: 0.88rem;
    cursor: pointer;
    border-radius: 6px;
}

.filter-pop__list label:hover {
    background: var(--light-gray);
}

.filter-pop__list input {
    accent-color: var(--accent);
}

/* ---------- a period, in place of a column's tick list ----------

   A date column asks for a period rather than for a tick per distinct day, so
   its filter popup holds the panel the reports' period picker already is — the
   presets as a list, the two dates under them. The classes are that picker's on
   purpose: one period control, learnt once.

   The panel replaces the list rather than sitting inside it. The list scrolls
   at 300px, and an Apply button that scrolls out of reach is not a button. */
.period-now {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--primary);
    padding: 0 10px 8px;
}

/* `.pick-list` caps itself at 320px, which is eight presets and a little over
   — so Expires, the only column with eight, shows all of them without a scroll
   bar that hides the last one. */
.period-panel .period-custom {
    margin-top: 0;
}

.period-panel .period-row__fields {
    flex: 1;
}

/* Shorter than a form's fields: two of these and a list of presets have to fit
   in a popup that hangs off a table header. */
.period-panel .period-row__fields .ac-input {
    height: 32px;
    min-height: 32px;
    padding: 5px 8px;
    font-size: 0.85rem;
}

/* The refusal gets its own line above the two buttons. The popup is 280px and
   the reports' foot puts the message beside the button, which here squeezed
   "The start is after the end" into three words a line. */
.period-panel .period-foot {
    flex-wrap: wrap;
}

.period-panel .period-foot .period-error {
    order: -1;
    flex: 1 0 100%;
    padding-bottom: 6px;
}

/* Only drawn when some row really has nothing in the column. */
.period-empty {
    padding: 8px 6px 0;
    margin-top: 8px;
    border-top: 1px solid var(--border-gray);
}

.hint-pop {
    max-width: 340px;
    padding: 12px 16px;
    font-size: 0.88rem;
    line-height: 1.6;
}

.hint-pop ul {
    margin: 0;
    padding-left: 18px;
}

/* ---------- Modal ---------- */

.modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(9, 18, 39, 0.55);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    z-index: 100;
}

.modal {
    background: var(--white);
    border-radius: var(--radius);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3);
    width: 100%;
    max-width: 720px;
    max-height: 88vh;
    display: flex;
    flex-direction: column;
}

.modal--narrow {
    max-width: 460px;
}

.modal__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 20px 24px;
    border-bottom: 1px solid var(--border-gray);
}

.modal__head h2 {
    font-size: 1.15rem;
    font-weight: 600;
}

.modal__close {
    background: none;
    border: 0;
    font-size: 1.6rem;
    line-height: 1;
    cursor: pointer;
    color: var(--text-gray);
    padding: 0 4px;
}

.modal__close:hover {
    color: var(--primary);
}

.modal__body {
    padding: 24px;
    overflow-y: auto;
}

.modal__foot {
    padding: 16px 24px;
    border-top: 1px solid var(--border-gray);
    display: flex;
    justify-content: flex-end;
    gap: 12px;
}

.modal__foot:empty {
    display: none;
}

/* Read-only rendering of a form inside the modal */
.form-view h3 {
    font-size: 0.95rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-gray);
    margin: 24px 0 12px;
}

.form-view h3:first-child {
    margin-top: 0;
}

.form-view dl {
    display: grid;
    grid-template-columns: 190px 1fr;
    gap: 8px 16px;
}

.form-view dt {
    color: var(--text-gray);
    font-size: 0.88rem;
}

.form-view dd {
    margin: 0;
    font-size: 0.92rem;
    word-break: break-word;
    /* A blurb is written in paragraphs and was arriving as one block of text. */
    white-space: pre-wrap;
}

/* ---------- links ---------- */

.form-view .ext-link {
    color: var(--accent);
    text-decoration: none;
    border-bottom: 1px solid rgba(229, 50, 114, 0.35);
}

.form-view .ext-link:hover {
    border-bottom-color: var(--accent);
}

/* Six socials as six rows of full URLs is a wall of text. One row of named
   chips says the same and can be read at a glance; the address itself is in the
   href and the tooltip, where it is of use to a machine rather than an eye. */
.form-view .link-row {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    white-space: normal;
}

.form-view .link-chip {
    display: inline-block;
    padding: 2px 10px;
    border: 1px solid var(--border-gray);
    border-radius: 999px;
    font-size: 0.84rem;
    color: var(--text-gray);
    text-decoration: none;
    transition: var(--transition);
}

.form-view .link-chip:hover {
    color: var(--accent);
    border-color: var(--accent);
}

/* ---------- narrators ---------- */

.form-view .narrator-details {
    margin-top: 14px;
    padding-top: 12px;
    border-top: 1px solid var(--border-gray);
}

.form-view .narrator-details h5 {
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-gray);
    margin-bottom: 10px;
}

.form-view .narrator-detail {
    padding: 10px 0 10px 14px;
    border-left: 3px solid var(--border-gray);
    margin-bottom: 8px;
}

.form-view .narrator-detail:last-child {
    margin-bottom: 0;
}

.form-view .narrator-detail__name {
    display: block;
    font-weight: 600;
    font-size: 0.94rem;
    margin-bottom: 6px;
}

/* Narrator fields sit one level in, so their labels get a narrower column than
   the book's — otherwise the value column all but disappears. */
.form-view .narrator-detail dl {
    grid-template-columns: 96px 1fr;
    gap: 6px 14px;
}

.form-view .book-card {
    border: 1px solid var(--border-gray);
    border-radius: var(--radius);
    padding: 16px 20px;
    margin-bottom: 12px;
}

.form-view .book-card h4 {
    font-size: 1rem;
    margin-bottom: 12px;
}

.radio-row {
    display: flex;
    gap: 12px;
    margin: 16px 0 4px;
}

.radio-card {
    flex: 1;
    border: 2px solid var(--border-gray);
    border-radius: var(--radius);
    padding: 16px;
    cursor: pointer;
    transition: var(--transition);
}

.radio-card:hover {
    border-color: var(--blue-mid);
}

.radio-card input {
    accent-color: var(--accent);
    margin-right: 8px;
}

.radio-card.selected {
    border-color: var(--accent);
    background: rgba(229, 50, 114, 0.05);
}

.radio-card small {
    display: block;
    color: var(--text-gray);
    margin-top: 6px;
    font-size: 0.82rem;
}

.copy-link-row {
    display: flex;
    gap: 8px;
    margin-top: 16px;
}

.copy-link-row input {
    font-size: 0.85rem;
}

/* ---------- Storage ---------- */

.storage-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 20px;
    flex-wrap: wrap;
}

.storage-health {
    display: flex;
    align-items: center;
    gap: 14px;
    font-size: 0.9rem;
    color: var(--text-gray);
}

.health-badge {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 6px 14px;
    border-radius: 20px;
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.health-badge::before {
    content: '';
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: currentColor;
}

.health-badge--healthy { background: rgba(95, 212, 155, 0.22); color: #10704A; }
.health-badge--warning { background: rgba(255, 176, 32, 0.22); color: #9A6200; }
.health-badge--alert   { background: rgba(229, 50, 114, 0.16); color: var(--accent-dark); }

.storage-meter {
    width: 160px;
    height: 6px;
    border-radius: 3px;
    background: var(--border-gray);
    overflow: hidden;
}

.storage-meter span {
    display: block;
    height: 100%;
    background: var(--blue-mid);
}

.storage-meter--warning span { background: #D99400; }
.storage-meter--alert span   { background: var(--accent); }

.storage-tabs {
    display: flex;
    gap: 4px;
    background: var(--border-gray);
    padding: 4px;
    border-radius: var(--radius);
}

.tab {
    border: 0;
    background: transparent;
    padding: 8px 20px;
    border-radius: 8px;
    font: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--text-gray);
    cursor: pointer;
    transition: var(--transition);
}

.tab:hover { color: var(--primary); }

.tab.active {
    background: var(--white);
    color: var(--primary);
    box-shadow: 0 1px 3px rgba(9, 18, 39, 0.12);
}

.crumbs {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    padding: 14px 20px;
    border-bottom: 1px solid var(--border-gray);
    font-size: 0.88rem;
}

.crumbs button {
    background: none;
    border: 0;
    padding: 2px 4px;
    font: inherit;
    color: var(--accent);
    cursor: pointer;
    border-radius: 4px;
}

.crumbs button:hover { text-decoration: underline; }

.crumbs .sep { color: #9CA3AF; }
.crumbs .current { color: var(--text-gray); padding: 2px 4px; }

.name-cell {
    display: inline-flex;
    align-items: center;
    gap: 10px;
}

.name-cell .icon { color: #9CA3AF; }
.row-folder .name-cell .icon { color: var(--blue-mid); }
.row-folder .name-cell { font-weight: 600; }

.storage-actions-head {
    display: flex;
    gap: 8px;
    align-items: center;
}

/* ---------- Edit mode: the author form's own components ----------
 *
 * Copied from frontend/css/styles.css rather than reinvented, so the modal
 * looks and behaves like the form the author filled in — same peppers, same
 * trope search, same genre checkboxes. Scoped under .form-edit so none of it
 * leaks into the tables.
 */

.form-edit .form-group {
    margin-bottom: 20px;
}

.form-edit .form-group label {
    display: block;
    font-weight: 600;
    color: var(--primary);
    font-size: 0.92rem;
    letter-spacing: -0.01em;
    margin-bottom: 8px;
}

.form-edit .form-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 20px;
}

.form-edit input[type="text"],
.form-edit input[type="url"],
.form-edit input[type="number"],
.form-edit textarea,
.form-edit select {
    width: 100%;
    padding: 11px 14px;
    border: 2px solid var(--border-gray);
    border-radius: 10px;
    font-size: 0.95rem;
    font-family: inherit;
    color: var(--primary);
    background: var(--white);
    transition: var(--transition);
}

.form-edit textarea {
    resize: vertical;
    line-height: 1.6;
}

.form-edit input:focus,
.form-edit textarea:focus,
.form-edit select:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 4px rgba(229, 50, 114, 0.12);
}

/* Spicy rating — the peppers from the form */
.form-edit .spicy-rating {
    display: flex;
    gap: 8px;
    align-items: center;
    padding: 6px 0;
}

.form-edit .spicy-pepper {
    font-size: 1.5rem;
    cursor: pointer;
    transition: var(--transition);
    opacity: 0.3;
    user-select: none;
}

.form-edit .spicy-pepper.active {
    opacity: 1;
}

.form-edit .spicy-pepper:hover {
    transform: scale(1.1);
}

/* Genres — checkbox row */
.form-edit .genres-checkboxes {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.form-edit .genres-option {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    border: 2px solid var(--border-gray);
    border-radius: 10px;
    cursor: pointer;
    font-size: 0.92rem;
    font-weight: 500;
    transition: var(--transition);
    margin-bottom: 0;
}

.form-edit .genres-option:hover {
    border-color: var(--blue-mid);
}

.form-edit .genres-option:has(input:checked) {
    border-color: var(--accent);
    background: rgba(229, 50, 114, 0.06);
}

.form-edit .genres-option input {
    accent-color: var(--accent);
}

/* Tropes — search with dropdown and chips, as on the form */
.form-edit .tropes-search-wrapper {
    position: relative;
}

.form-edit .tropes-dropdown {
    display: none;
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    max-height: 220px;
    overflow-y: auto;
    background: var(--white);
    border: 2px solid var(--border-gray);
    border-radius: 10px;
    box-shadow: 0 12px 32px rgba(9, 18, 39, 0.16);
    z-index: 20;
}

.form-edit .tropes-dropdown.open {
    display: block;
}

.form-edit .tropes-option {
    padding: 9px 14px;
    cursor: pointer;
    font-size: 0.92rem;
}

.form-edit .tropes-option:hover {
    background: rgba(135, 183, 255, 0.16);
}

.form-edit .tropes-option.chosen {
    color: #9CA3AF;
    cursor: default;
}

.form-edit .selected-tropes {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 10px;
}

.form-edit .selected-trope {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 5px 12px;
    background: rgba(229, 50, 114, 0.1);
    color: var(--accent-dark);
    border-radius: 20px;
    font-size: 0.85rem;
    font-weight: 600;
}

.form-edit .trope-remove {
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    color: inherit;
    line-height: 0;
    display: inline-flex;
    opacity: 0.65;
}

.form-edit .trope-remove:hover {
    opacity: 1;
}

/* Section headings and book cards inside the editor */
.form-edit .section-title {
    font-size: 0.82rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-gray);
    margin: 28px 0 14px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--border-gray);
}

.form-edit .section-title:first-child {
    margin-top: 0;
}

.form-edit .book-card {
    border: 1px solid var(--border-gray);
    border-radius: var(--radius);
    padding: 20px;
    margin-bottom: 16px;
    background: var(--light-gray);
}

.form-edit .book-card h4 {
    font-size: 1rem;
    margin-bottom: 16px;
}

/* Footer buttons: the modal is not a landing page */
.modal__foot .btn {
    padding: 9px 20px;
    font-size: 0.9rem;
}

.modal__foot .btn-primary:hover:not(:disabled) {
    transform: none;
    box-shadow: 0 4px 14px rgba(229, 50, 114, 0.3);
}

.auto-note {
    font-size: 0.82rem;
    color: #9A6200;
    background: rgba(255, 176, 32, 0.16);
    padding: 4px 12px;
    border-radius: 20px;
    white-space: nowrap;
}

/* ---------- Failure detail + file preview ---------- */

.badge--clickable {
    cursor: pointer;
    text-decoration: underline dotted;
    text-underline-offset: 3px;
}

.badge--clickable:hover {
    filter: brightness(0.94);
}

.error-text {
    white-space: pre-wrap;
    word-break: break-word;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.82rem;
    background: var(--light-gray);
    border: 1px solid var(--border-gray);
    border-radius: 8px;
    padding: 12px 14px;
    line-height: 1.55;
}

.preview {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--light-gray);
    border: 1px solid var(--border-gray);
    border-radius: var(--radius);
    padding: 16px;
    min-height: 140px;
}

.preview img {
    max-width: 100%;
    max-height: 60vh;
    border-radius: 8px;
    display: block;
}

.preview audio {
    width: 100%;
}

.preview-meta {
    margin-top: 12px;
    font-size: 0.88rem;
}

.preview-path {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.78rem;
    margin-top: 4px;
}

/* Storage rows: folders and previewable files respond to a click, everything
   else does not pretend to. Needs to out-specify `.data-table tbody tr`, which
   sets pointer for the forms table. */
.data-table tbody tr.row-plain {
    cursor: default;
}

.data-table tbody tr.row-plain:hover {
    background: transparent;
}

/* ---------- Podcast section ---------- */

.col-w-count { width: 112px; }

/* The episode count reads as a number until you learn it opens the titles, so
   it gets the same dashed cue the clipped cells use. */
td.col-count.has-hint .cell-text {
    cursor: pointer;
    border-bottom: 1px dashed var(--border-gray);
}

td.col-count.has-hint .cell-text:hover {
    border-bottom-color: var(--accent);
}

.form-edit .person-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: 10px;
}

.form-edit .person-row {
    display: grid;
    grid-template-columns: 1fr 1fr auto;
    gap: 8px;
}

.form-edit .person-remove {
    border: 1px solid var(--border-gray);
    background: var(--white);
    border-radius: 8px;
    width: 40px;
    cursor: pointer;
    color: var(--text-gray);
    font-size: 1.1rem;
    line-height: 1;
}

.form-edit .person-remove:hover {
    color: var(--accent);
    border-color: var(--accent);
}

.link-btn--danger {
    color: var(--accent);
}

.btn-small {
    padding: 8px 14px;
    font-size: 0.85rem;
}

.link-field {
    width: 100%;
    padding: 10px 12px;
    border: 2px solid var(--border-gray);
    border-radius: 8px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.85rem;
    margin: 8px 0;
}

/* Two-step remove inside the episode editor. A strip of its own under the
   header: the warning is a full sentence, and in the header row it squeezed the
   episode title until it broke mid-word. */
.confirm-strip {
    background: rgba(229, 50, 114, 0.08);
    border-left: 3px solid var(--accent);
    border-radius: 8px;
    padding: 14px 16px;
    margin-bottom: 16px;
}

/* Plain text, not accent: the tint already says this is a warning, and accent
   on accent is harder to read than it looks. */
.confirm-strip__text {
    font-size: 0.9rem;
    color: var(--primary);
    margin-bottom: 12px;
}

.confirm-strip__actions {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
}

.episode-head-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: nowrap;
    margin-bottom: 12px;
}

/* The title keeps the room; a long one clips rather than wrapping into a
   narrow column. */
.episode-title {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.episode-head-row .link-btn {
    flex: 0 0 auto;
}

/* ---------- Unattributed n8n errors ---------- */

/* In the topbar rather than in a section: an error from the cleanup workflow
   belongs to neither product. */
.orphan-pill {
    background: rgba(229, 50, 114, 0.1);
    color: var(--accent-dark);
    border: 1px solid rgba(229, 50, 114, 0.3);
    border-radius: 999px;
    padding: 6px 14px;
    font-family: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    transition: var(--transition);
}

.orphan-pill:hover {
    background: rgba(229, 50, 114, 0.18);
}

.orphan-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-top: 16px;
}

.orphan-row {
    border: 1px solid var(--border-gray);
    border-left: 3px solid var(--accent);
    border-radius: 8px;
    padding: 12px 14px;
}

.orphan-row__message {
    font-size: 0.92rem;
    word-break: break-word;
}

.orphan-row__meta {
    font-size: 0.8rem;
    color: var(--text-gray);
    margin-top: 4px;
}

/* Genres and tropes, corrected from the row. */
.trope-head {
    margin-bottom: 16px;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--border-gray);
}

.trope-head__name {
    font-weight: 700;
    font-size: 1.05rem;
    color: var(--primary);
}

.trope-book {
    padding: 16px 0;
    border-bottom: 1px solid var(--border-gray);
}

.trope-book:last-of-type {
    border-bottom: none;
}

.trope-book h4 {
    margin: 0 0 12px;
    font-size: 1rem;
    color: var(--primary);
}

.trope-book .form-group {
    margin-bottom: 16px;
}

.trope-book .form-group > label {
    display: block;
    font-weight: 600;
    color: var(--primary);
    font-size: 0.92rem;
    margin-bottom: 8px;
}

.trope-book .genres-checkboxes {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 20px;
}

.trope-book .genres-option {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.9rem;
    cursor: pointer;
}

/* The list as it stands, numbered — the order is the thing being edited. */
.trope-current {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-bottom: 8px;
    min-height: 26px;
}

.trope-chip {
    padding: 3px 10px;
    border-radius: 999px;
    background: var(--light-gray);
    font-size: 0.82rem;
    color: var(--primary);
    white-space: nowrap;
}

.trope-chip--unknown {
    background: rgba(229, 50, 114, 0.12);
    color: var(--accent-dark);
    font-weight: 600;
    cursor: help;
}

.trope-input {
    width: 100%;
    padding: 10px 12px;
    border: 2px solid var(--border-gray);
    border-radius: 10px;
    font-size: 0.9rem;
    font-family: inherit;
    color: var(--primary);
    resize: vertical;
}

.trope-book .field-note {
    display: block;
    margin-top: 6px;
    font-size: 0.82rem;
    color: var(--text-gray);
}

/* Users. */
.col-w-role { width: 110px; }

.badge--admin {
    background: rgba(229, 50, 114, 0.12);
    color: var(--accent-dark);
}

/* The site's quiet blue, the same tone the submitted badge wears. Grey read as
   "nothing here", and a user is not nothing. */
.badge--user {
    background: rgba(135, 183, 255, 0.22);
    color: #2B5FA8;
}

/* The numbers, in the navy the report's own group headers wear. */
.badge--analytic {
    background: rgba(28, 48, 85, 0.12);
    color: var(--navy-soft);
}

.role-cell {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}

/* Roles add up, so they are ticked rather than picked. */
.role-picker {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.role-picker__item {
    display: flex;
    align-items: baseline;
    gap: 8px;
    cursor: pointer;
}

.role-picker__hint {
    font-size: 0.8rem;
    color: var(--text-gray);
}


/* ---------- Fields in a modal ----------
 *
 * The book editor's own styles are scoped to .form-edit, so anything else built
 * in a modal fell back to the browser's defaults — which is why the role list
 * looked like a system control in Safari and the hint under a label read as
 * another label.
 *
 * Order in each group is label, then the hint, then the field: the hint belongs
 * to what comes after it, and the spacing has to say so. */

.modal-form .form-group {
    margin-bottom: 24px;
}

.modal-form .form-group:last-child {
    margin-bottom: 0;
}

.modal-form label {
    display: block;
    font-weight: 700;
    color: var(--primary);
    font-size: 0.95rem;
    letter-spacing: -0.01em;
    margin-bottom: 4px;
}

/* Quieter than the label in every way that reads at a glance: lighter, smaller,
   grey. It was the same weight and colour, so it looked like a second heading. */
.modal-form .field-note {
    display: block;
    font-weight: 400;
    font-size: 0.82rem;
    line-height: 1.5;
    color: var(--text-gray);
    margin-bottom: 10px;
}

.modal-form input[type="text"],
.modal-form input[type="password"],
.modal-form input[type="email"],
.modal-form input[type="url"],
.modal-form select,
.option-field__row select,
.option-row__input,
.option-row__count {
    width: 100%;
    padding: 12px 14px;
    border: 2px solid var(--border-gray);
    border-radius: 10px;
    font-size: 0.95rem;
    font-family: inherit;
    color: var(--primary);
    background-color: var(--white);
    transition: var(--transition);
}

.modal-form input:focus,
.modal-form select:focus,
.option-field__row select:focus,
.option-row__input:focus,
.option-row__count:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 4px rgba(229, 50, 114, 0.12);
}

/* A login cannot change, so the box says so rather than only refusing. */
.modal-form input:disabled {
    background-color: var(--light-gray);
    color: var(--text-gray);
    cursor: not-allowed;
}

/* Safari draws its own control unless the appearance is dropped, and then it
   draws no arrow at all — so the arrow comes from here. */
.modal-form select,
.form-edit select,
.option-field__row select {
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%234B5563' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6l6 -6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 14px center;
    padding-right: 42px;
    cursor: pointer;
}

.badge--active {
    background: rgba(95, 212, 155, 0.22);
    color: #10704A;
}

.badge--inactive {
    background: #F1F3F5;
    color: #6B7280;
}

/* Which format to download, asked at the icon rather than in a modal — it is one
   question with two answers. */
.format-pop {
    position: absolute;
    z-index: 900;
    background: var(--white);
    border: 1px solid var(--border-gray);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 6px;
    min-width: 220px;
}

.format-pop__item {
    display: block;
    width: 100%;
    padding: 10px 12px;
    border: 0;
    border-radius: 8px;
    background: none;
    font-family: inherit;
    font-size: 0.9rem;
    color: var(--primary);
    text-align: left;
    cursor: pointer;
    transition: var(--transition);
}

.format-pop__item:hover {
    background: var(--light-gray);
}

/* ---------- Form Settings ---------- */

/* Three fields in a row, as the owner drew it. They fold into a column when
   the window cannot hold them side by side: three selects need about 900px,
   and the admin is opened on laptops. */
.option-fields {
    display: grid;
    grid-template-columns: repeat(3, minmax(240px, 1fr));
    gap: 24px;
    padding: 24px;
}

@media (max-width: 1000px) {
    .option-fields { grid-template-columns: 1fr; }
}

.option-field label {
    display: block;
    margin-bottom: 8px;
    font-weight: 600;
    font-size: 0.9rem;
    color: var(--primary);
}

.option-field__row {
    display: flex;
    align-items: center;
    gap: 8px;
}

.option-field__row select { flex: 1; min-width: 0; }

/* ---------- the editor ---------- */

.option-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-bottom: 16px;
}

.option-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 8px;
    border-radius: 8px;
    border: 1px solid transparent;
}

.option-row:hover { background: var(--light-gray); }

.option-row__grip {
    width: 18px;
    color: var(--text-gray);
    cursor: grab;
    line-height: 0;
    flex: 0 0 auto;
}

.option-row__name { flex: 1; min-width: 0; }

.option-row__rule,
.option-row__tag {
    font-size: 0.8rem;
    color: var(--text-gray);
    white-space: nowrap;
}

.option-row__input { flex: 1; min-width: 0; }

.option-row__rules {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
}

.option-row__count { width: 64px; }

.option-row__more {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 0.8rem;
    color: var(--text-gray);
    white-space: nowrap;
}

/* Archived rows stay legible — someone is reading them to decide what to bring
   back — but clearly not part of the form any more. */
.option-row--archived .option-row__name { color: var(--text-gray); }

.option-row.is-dragging { opacity: 0.4; }

/* The line the row would land on. */
.option-row.is-over { border-top-color: var(--accent); }

.option-list__actions {
    display: flex;
    gap: 8px;
    justify-content: space-between;
}

/* ==========================================================================
   Reports (TASK-062)

   A constructor at the top and a matrix underneath. The matrix is wide by
   nature — nine metrics over twelve months is a hundred and eight number
   columns — so the meta columns freeze to the left and the numbers scroll
   under them.
   ========================================================================== */

.report-head {
    padding: 12px 16px;
    margin-bottom: 12px;
}

/* One row, and it stays one row. Fixed tracks rather than wrapping flex items:
   a value that grows a character should not move everything after it onto the
   next line. */
.report-grid {
    display: grid;
    /* The period is the one field whose value is a sentence — "Mar 2026 — Jul
       2026" — so it gets a track of its own, half again as wide as the four
       that hold a single word. */
    grid-template-columns: repeat(4, minmax(88px, 124px)) minmax(180px, 210px) auto;
    align-items: end;
    gap: 12px;
}

/* Books Report carries one narrow field more than the Users Report — Columns
   sits between View and Metrics — and the period still wants its wide track. */
.report-grid--five {
    grid-template-columns: repeat(5, minmax(88px, 124px)) minmax(180px, 210px) auto;
}

.report-field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.label-info {
    border: 0;
    background: none;
    padding: 0 0 0 3px;
    line-height: 0;
    color: #C4CAD4;
    cursor: pointer;
    vertical-align: -2px;
}

.label-info:hover {
    color: var(--accent);
}

/* Plain text, not a list — so it sets its own width and reads as a sentence. */
.info-pop {
    width: 240px;
    padding: 10px 12px;
    font-size: 0.8rem;
    line-height: 1.4;
    color: var(--primary);
    text-transform: none;
    letter-spacing: normal;
    font-weight: 400;
}

.report-field__label {
    font-size: 0.66rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-gray);
}

/* Every control in the header, and the little ones inside the period panel.
   A button rather than a select: a select's drop-down cannot be made to match
   the panels beside it.
   
   Not `.field` — that name was taken by the modal form styles, and inheriting
   their twenty pixels of bottom margin pushed the buttons off the line. */
.picker {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    width: 100%;
    min-width: 0;
    font: inherit;
    font-size: 0.82rem;
    padding: 6px 8px;
    border: 1px solid var(--border-gray);
    border-radius: 9px;
    background: var(--white);
    color: var(--primary);
    cursor: pointer;
    text-align: left;
}

.picker:hover {
    border-color: #C9D2E0;
}

.picker__value {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.picker .icon {
    color: #9CA3AF;
}

.picker.is-marked {
    border-color: var(--accent);
    color: var(--accent);
}

.picker--mini {
    width: auto;
    min-width: 74px;
}

.picker--num {
    width: 62px;
    text-align: center;
    cursor: text;
}

/* On the line of the fields, not floating in the middle of the panel.
   `max-content` because this column must never be squeezed: the fields can give
   up a few pixels each, a clipped button cannot. */
.report-actions-field {
    min-width: max-content;
}

.report-actions {
    display: flex;
    align-items: center;
    gap: 8px;
}

.report-actions .btn {
    white-space: nowrap;
    /* The same height as a picker, so the buttons and the fields share a line
       exactly rather than nearly. */
    height: 36px;
}

/* How many rows, and how old the numbers are — inside the panel, because a
   strip of its own between here and the table is all gap. */
.report-note {
    display: flex;
    gap: 0;
    margin-top: 8px;
    font-size: 0.76rem;
    color: var(--text-gray);
}

.report-note > span:not(:empty) ~ span:not(:empty)::before {
    content: ' · ';
    white-space: pre;
}

.report-note .is-warning {
    color: var(--accent);
}

.report-chosen {
    color: var(--accent);
    font-weight: 600;
}

/* ---------- the panels the fields open ---------- */

.pick-pop {
    position: absolute;
    z-index: 60;
    min-width: 168px;
    padding: 6px;
    background: var(--white);
    border: 1px solid var(--border-gray);
    border-radius: var(--radius);
    box-shadow: 0 12px 32px rgba(9, 18, 39, 0.18);
}

/* The month and year lists open from inside the period panel, so they have to
   sit above it. Equal z-index would leave them behind — the panel comes later
   in the markup and wins the tie. Declared after `.pick-pop` for the same
   reason. */
.pick-pop--nested {
    z-index: 70;
}

.pick-list {
    display: flex;
    flex-direction: column;
    max-height: 320px;
    overflow-y: auto;
}

.pick-item {
    border: 0;
    background: none;
    font: inherit;
    font-size: 0.85rem;
    text-align: left;
    padding: 7px 10px;
    border-radius: 8px;
    cursor: pointer;
    color: var(--primary);
    white-space: nowrap;
}

.pick-item:hover {
    background: var(--light-gray);
}

.pick-item.is-on {
    color: var(--accent);
    font-weight: 600;
}

/* Shown, not hidden: a month missing without explanation reads as a bug. */
.pick-item.is-off {
    color: #C4CAD4;
    cursor: not-allowed;
}

.pick-item.is-off:hover {
    background: none;
}

.period-pop {
    width: 264px;
}

.period-custom {
    margin-top: 6px;
    padding-top: 10px;
    border-top: 1px solid var(--border-gray);
}

.period-custom__title {
    font-size: 0.66rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-gray);
    padding: 0 10px 8px;
}

.period-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 0 10px 8px;
}

.period-row__fields .picker,
.period-row__fields input.picker {
    height: 30px;
}

.period-row__label {
    width: 34px;
    font-size: 0.8rem;
    color: var(--text-gray);
}

.period-row__fields {
    display: flex;
    gap: 6px;
}

.period-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 8px 10px 2px;
    border-top: 1px solid var(--border-gray);
}

.period-error {
    font-size: 0.74rem;
    color: var(--accent);
    line-height: 1.2;
}

/* ---------- the matrix ---------- */

.table-scroll--matrix {
    overflow: auto;
}

/* Fixed, over a `colgroup` script fills in. The body is virtualised, so an
   auto-laid-out column follows whichever rows happen to be on screen: a long
   value scrolled in and the column grew, while the frozen offsets — worked out
   when it was measured — stayed where they were, and the frozen block slid
   against itself as you scrolled (TASK-079). Fixed makes the measured width the
   width, so an over-long value clips instead of pushing. The forms table has
   been laid out this way all along; this is the same arrangement.

   A fixed table is only fixed once it knows how wide it is, so script sets that
   too, from the widths it just pinned. `max-content` is what it falls back to
   for the one moment the table is measured — the layout switched back to
   automatic, every column free to ask for what its contents need. */
.report-table {
    table-layout: fixed;
    width: max-content;
    min-width: 100%;
}

.report-table th,
.report-table td {
    padding: 7px 10px;
    font-size: 0.85rem;
}

/* Fixed, and matching ROW_H in reports.js: only the rows in view are in the
   document, and the spacers above and below them are sized in whole rows. */
.report-table tbody tr {
    height: 34px;
    cursor: default;
}

.report-table tbody tr.spacer,
.report-table tbody tr.spacer:hover {
    background: none;
    height: auto;
}

.report-table tbody tr.is-picked {
    background: rgba(229, 50, 114, 0.06);
}

/* The two header rows both stick, one under the other. */
.report-table thead tr:first-child th {
    top: 0;
    z-index: 3;
}

.report-table thead tr:nth-child(2) th {
    top: 34px;
    z-index: 3;
}

.report-table th.col-group {
    text-align: center;
    overflow: visible;
    background: var(--primary);
    color: var(--white);
    border-left: 2px solid var(--white);
    box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.2);
}

/* Sticky sideways, so a metric's name is on screen for as long as any of its
   columns are. `left` is set from script, where the width of the frozen block
   is known. */
.col-group__label {
    position: sticky;
    display: inline-block;
    white-space: nowrap;
}

.report-table th.col-period,
.report-table th.col-meta {
    cursor: pointer;
    user-select: none;
}

.report-table th.col-period:hover,
.report-table th.col-meta:hover {
    color: var(--primary);
}

/* Frozen to the left. The offsets are set from script, because how far across
   a column sits depends on which subject is on screen. */
.report-table .col-check,
.report-table .col-meta {
    position: sticky;
    background: var(--white);
    z-index: 2;
}

/* Frozen *and* stuck to the top, so this corner outranks both the period row
   sliding under it and the group bar above it. Written with `tr th` to beat
   `thead tr:first-child th`, which is more specific than it looks. */
.report-table thead tr th.col-check,
.report-table thead tr th.col-meta {
    background: var(--light-gray);
    top: 0;
    z-index: 6;
}

.report-table tbody tr:hover .col-meta,
.report-table tbody tr:hover .col-check {
    background: #F2F6FF;
}

.report-table tbody tr.is-picked .col-meta,
.report-table tbody tr.is-picked .col-check {
    background: #FDEFF4;
}

/* The last frozen column carries the seam, so the numbers slide under a line
   rather than under nothing. */
.report-table .col-meta.is-last-frozen {
    box-shadow: inset -1px 0 0 var(--border-gray), 2px 0 6px rgba(9, 18, 39, 0.06);
}

/* The ceiling on a meta column, and what happens past it. A column narrower
   than this still sizes to its content — the cap clips, it does not pad — and
   the value that does not fit is cut with an ellipsis rather than allowed to
   push the figures off the screen (TASK-078).

   `max-width` binds while the widths are being measured, which is where the
   ceiling is applied. Afterwards the column is as wide as its `col` says and
   `overflow` is what does the cutting — for the rows measured and the rows that
   scroll in later alike (TASK-079). */
.report-table td.col-meta {
    max-width: 220px;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Only the cells actually cut say so, and say it the way the forms table does:
   a dashed underline, and the whole value on click. `has-note` is the Users
   Report's placement column, which has something to show whatever its width —
   the other weeks were never in the cell to be cut off. */
.report-table td.col-meta.clipped .cell-text,
.report-table td.col-meta.has-note .cell-text {
    cursor: pointer;
    border-bottom: 1px dashed var(--border-gray);
}

.report-table td.col-meta.clipped .cell-text:hover,
.report-table td.col-meta.has-note .cell-text:hover {
    border-bottom-color: var(--accent);
}

/* A folded column keeps its place in the row — the frozen offsets are counted
   in whole columns — but gives up everything except the chevron that unfolds
   it again. */
.report-table .col-meta.is-folded {
    max-width: 26px;
    width: 26px;
    padding-left: 2px;
    padding-right: 2px;
    overflow: hidden;
}

.col-fold {
    background: none;
    border: 0;
    padding: 2px 0;
    margin-left: 2px;
    line-height: 0;
    color: #C4CAD4;
    cursor: pointer;
    display: inline-flex;
}

.col-fold:hover {
    color: var(--primary);
}

.report-table .col-meta.is-folded .th-inner {
    gap: 0;
}

.report-table td.col-flag {
    max-width: none;
    text-align: center;
    color: var(--text-gray);
}

.report-table .col-check {
    width: 36px;
    text-align: center;
    padding-left: 12px;
    padding-right: 4px;
}

/* Hiding a meta column, or folding one, is done to give the room to the figures.
   `min-width: 100%` stretches the table whenever it is narrower than the panel,
   and the browser hands that surplus out in proportion to every column that is
   still `auto` — so the columns meant to shrink came back fatter and the
   numbers gained a fraction of what was freed (TASK-077).

   The empty column at the right edge is the answer: script gives every other
   `col` a width of its own, which leaves this one the only `auto` column in
   the table and therefore the only one the surplus can reach. It takes exactly
   what is left over and exactly nothing when the table is already too wide. */
.report-table .col-slack {
    padding: 0;
}

.report-table td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* How wide a number column is. Script works it out from the room the frozen
   block leaves — that is what hiding a meta column is for — and stops at a
   ceiling: one week and two metrics would otherwise pull a three-digit column
   across three hundred pixels, which is worse than the bug. The width itself
   goes on the column's `col`, because that is the only place a fixed table
   reads one from; `min-width` is the floor a five-figure count needs, and it
   applies while the widths are being measured, which is the one moment this
   table is laid out automatically. The header label can still push a column
   past both — script carries that measurement over. */
.report-table th.col-period,
.report-table td.num {
    min-width: 76px;
}

/* Against the period before it. Backgrounds rather than text colour: a column
   of numbers is read as a shape first, and a tint carries further than a hue. */
.report-table td.num.is-up {
    background: rgba(22, 163, 74, 0.12);
    color: #14532D;
}

.report-table td.num.is-down {
    background: rgba(220, 38, 38, 0.10);
    color: #7F1D1D;
}

.sort-mark {
    display: inline-block;
    width: 10px;
    margin-left: 4px;
    font-size: 0.6rem;
    color: var(--accent);
}

.th-filter {
    border: 0;
    background: none;
    font: inherit;
    font-size: 0.7rem;
    line-height: 1;
    padding: 2px 3px;
    margin-left: 2px;
    color: var(--border-gray);
    cursor: pointer;
    border-radius: 4px;
}

.th-filter:hover {
    color: var(--primary);
    background: rgba(9, 18, 39, 0.06);
}

.th-filter.is-on {
    color: var(--accent);
}

.metric-unit {
    margin-left: auto;
    font-size: 0.7rem;
    color: var(--text-gray);
    text-transform: uppercase;
}

/* ---------- the numeric filter ---------- */

.num-pop {
    position: absolute;
    z-index: 60;
    width: 232px;
    padding: 12px;
    background: var(--white);
    border: 1px solid var(--border-gray);
    border-radius: var(--radius);
    box-shadow: 0 12px 32px rgba(9, 18, 39, 0.18);
}

.num-pop__row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 10px;
}

.num-pop select,
.num-pop input {
    font: inherit;
    font-size: 0.85rem;
    padding: 7px 9px;
    border: 1px solid var(--border-gray);
    border-radius: 8px;
    width: 100%;
    min-width: 0;
}

.num-pop__and {
    font-size: 0.8rem;
    color: var(--text-gray);
}

.num-pop__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-top: 10px;
    border-top: 1px solid var(--border-gray);
}

.btn-sm {
    padding: 6px 14px;
    font-size: 0.82rem;
}


/* ==========================================================================
   Tropes in Form Settings (TASK-068)

   Two tables side by side: what the form offers, and what the platform holds.
   The right one is read-only — the platform is never written to from here.
   ========================================================================== */

.trope-panel {
    margin-top: 16px;
    padding: 16px 18px;
}

/* A panel that holds two titled columns needs to say what the pair of them is
   for, or the page reads as two unrelated tables that happen to be adjacent. */
.panel__title {
    font-size: 1rem;
    font-weight: 700;
    margin-bottom: 14px;
}

.trope-columns {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 20px;
    align-items: start;
}

/* Below a laptop the two would be too narrow to read a title in. */
@media (max-width: 1100px) {
    .trope-columns {
        grid-template-columns: 1fr;
    }
}

.trope-side {
    min-width: 0;
    border: 1px solid var(--border-gray);
    border-radius: var(--radius);
    overflow: hidden;
}

.trope-side__head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    background: var(--light-gray);
    border-bottom: 1px solid var(--border-gray);
}

.trope-side__title {
    flex: 1;
    font-size: 0.86rem;
    font-weight: 700;
    white-space: nowrap;
}

/* Tall enough to work in, short enough that the page does not become a
   scrollbar with two tables in it. */
.trope-scroll {
    max-height: 420px;
    overflow: auto;
}

.trope-table th,
.trope-table td {
    padding: 7px 10px;
    font-size: 0.85rem;
}

.trope-table th[data-sort] {
    cursor: pointer;
    user-select: none;
}

.trope-table .col-w-id {
    width: 72px;
}

.trope-table .col-w-flag {
    width: 92px;
}

.trope-table td.col-flag {
    text-align: center;
    color: var(--accent);
}

.trope-table .col-check {
    width: 34px;
    text-align: center;
    padding-left: 10px;
    padding-right: 2px;
}

.trope-table tbody tr {
    cursor: default;
}

.trope-table tbody tr.is-picked {
    background: rgba(229, 50, 114, 0.06);
}

/* Already on one of the two lists, so there is nothing to add. */
.trope-table tbody tr.is-taken {
    color: #9CA3AF;
}

.trope-table tbody tr.is-taken .icon {
    color: #B6BDC9;
}

.trope-table tbody tr.trope-group td {
    background: var(--light-gray);
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-gray);
    position: sticky;
    top: 0;
    z-index: 1;
}

.trope-note {
    padding: 8px 12px;
    font-size: 0.78rem;
    color: var(--text-gray);
    border-top: 1px solid var(--border-gray);
}

.trope-note.is-warning {
    color: var(--accent);
}

.trope-confirm {
    margin: 10px 0 0;
    padding-left: 18px;
    max-height: 200px;
    overflow: auto;
    font-size: 0.88rem;
}

.modal-input {
    width: 100%;
    font: inherit;
    padding: 9px 11px;
    margin-top: 10px;
    border: 1px solid var(--border-gray);
    border-radius: 10px;
}

/* ---------- the user report ---------- */
/* Everything the table needs it already has from `.report-table`; these are the
   two things that page has and Reports does not. Scoped to `.user-table` so no
   rule here can reach the Reports matrix. */

/* The week list inside the week panel, lined up with the rows above it. */
.week-pick {
    padding: 0 10px 8px;
}

/* A ratio above one hundred per cent. It happens — a book can be started
   without being tapped that week, through autoplay or "continue listening" —
   so the cell is marked rather than clamped or flagged as an error.

   Deliberately not a colour: a green tint here would read as "excellent
   conversion" for a number that only means the book is reached another way.
   A grey rule down the left edge asks the reader to look, and says nothing
   about whether looking is good news. */
.user-table td.num.is-over {
    box-shadow: inset 2px 0 0 var(--text-gray);
}

/* Seven columns to a week makes a group wide enough that its centre can sit off
   screen while the group itself is in view — and a centred label is then
   invisible exactly when it is most wanted. Left-aligned it starts at the
   group's own edge, and the sticky offset set from script keeps it clear of the
   frozen block as the week scrolls past. Reports keeps its centred label: its
   groups are narrower, and this is not a rule it can reach. */
.user-table th.col-group {
    text-align: left;
}

/* The refusal line under the metric list, in the same red the period panel uses
   for the same job. Only shown while the message is up, so the popup does not
   carry an empty gap the rest of the time. */
.filter-pop__note {
    padding-top: 10px;
    margin-top: 8px;
    border-top: 1px solid var(--border-gray);
}

/* ---------- CRM: authors ---------- */

/* The section holds two views — the list and one author — and only one of them
   is on screen. Each has to be the column that fills the section, or the table
   panel inside it has nothing to stretch against. */
.crm-view {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* The pipeline stages. Two more statuses reach this table without being stored
   anywhere — active and inactive already have their badges above, next to the
   accounts that share the words. */
.badge--lead        { background: #F1F3F5; color: #6B7280; }
.badge--contacted   { background: rgba(135, 183, 255, 0.22); color: #2B5FA8; }
.badge--negotiating { background: rgba(255, 176, 32, 0.18); color: #9A6200; }
.badge--deal_won    { background: rgba(95, 212, 155, 0.22); color: #10704A; }
.badge--deal_lost   { background: rgba(229, 50, 114, 0.14); color: var(--accent-dark); }

.author-head {
    padding: 24px 28px;
    margin-bottom: 20px;
}

.author-head__line {
    display: flex;
    align-items: center;
    gap: 12px;
}

.author-head__name {
    font-size: 1.25rem;
    font-weight: 700;
}

/* The legal name is the one thing on this page the catalogue cannot answer, so
   it sits in the header rather than a tab down. */
.author-head__legal {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin-top: 10px;
    font-size: 0.95rem;
}

.author-head__label {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-gray);
}

.author-head__meta {
    margin-top: 6px;
    font-size: 0.85rem;
    color: var(--text-gray);
}

.author-tab {
    flex: 1;
    min-height: 0;
    overflow: auto;
    padding: 28px;
}

/* The way back and the four tabs on one line. The tabs keep their own pill
   background, so they need no rule beyond sitting next to the button. */
.author-bar .toolbar-left {
    gap: 18px;
}

/* ID is four digits at most, and the two count pairs are `5 | 1`. Left to the
   shared columns they would each take a sixth of the table. */
.col-w-id { width: 78px; }
.col-w-count-pair { width: 104px; }

/* Live and expired in one cell. The live half is the one being asked about, so
   it carries the colour; the lapsed half is present but quiet. */
.count-pair {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    font-variant-numeric: tabular-nums;
}

.count-pair__live {
    font-weight: 700;
    color: #10704A;
}

.count-pair__sep {
    color: #C7CBD1;
}

.count-pair__expired {
    color: var(--text-gray);
}

/* Nothing to say when both halves are nothing. */
.count-pair__live:first-child {
    min-width: 1ch;
}

/* Editing a field is a pencil and a modal, so the page stays a page of values
   rather than a form nobody submitted.

   The pencil is always there. A control that appears only under the cursor
   cannot be found by anyone who does not already know it exists, and it makes
   the page look different in a screenshot than it does in use. Resting state is
   quiet grey; hover says it is clickable. A field with no pencil is a field that
   is not yours to change, and that absence is the signal. */
.author-facts dd {
    display: flex;
    align-items: baseline;
    gap: 8px;
}

.field-edit {
    color: #9CA3AF;
}

.field-edit:hover {
    color: var(--primary);
}

.icon-btn--sm {
    width: 26px;
    height: 26px;
    border-radius: 6px;
}

/* Ours, and the platform has not been told. It stays until something carries
   the value over — there is nothing that does yet. */
.field-mark {
    padding: 2px 8px;
    border-radius: 20px;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    background: rgba(255, 176, 32, 0.18);
    color: #9A6200;
    white-space: nowrap;
}

/* What we already hold that looks like the name being typed. */
.match-row {
    border: 1px solid var(--border-gray);
    border-radius: 10px;
    padding: 12px 14px;
    margin-bottom: 10px;
    background: var(--light-gray);
}

.match-row__name {
    display: flex;
    align-items: baseline;
    gap: 10px;
    font-weight: 700;
    font-size: 0.95rem;
}

.match-row__id {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--text-gray);
}

.match-row__meta {
    margin: 4px 0 6px;
    font-size: 0.82rem;
    color: var(--text-gray);
}

/* The last thing before the author exists: the name, and the id we would ask
   the platform for. */
.add-confirm {
    margin-top: 18px;
    padding-top: 16px;
    border-top: 1px solid var(--border-gray);
}

.add-confirm__name {
    font-size: 1.05rem;
    font-weight: 700;
}

.add-confirm__id {
    margin-top: 4px;
    font-size: 0.9rem;
    color: var(--text-gray);
}

.modal-form textarea {
    width: 100%;
    min-height: 120px;
    padding: 10px 14px;
    border: 1px solid var(--border-gray);
    border-radius: 8px;
    font: inherit;
    font-size: 0.92rem;
    resize: vertical;
}

.modal-form textarea:focus {
    outline: none;
    border-color: var(--accent);
}

/* Why a field is empty, where the dash alone would not say. */
.fact-note {
    margin-left: 10px;
    font-size: 0.8rem;
    color: #9CA3AF;
}

/* A biography is written in paragraphs and was arriving as one block. */
.author-facts__bio {
    white-space: pre-wrap;
}

/* The contracts table clips its multi-value cells through the same
   `.data-table--fixed td.col-text` rule the forms table uses; it needs nothing
   of its own beyond the fixed layout it already carries. */

.author-facts {
    display: grid;
    grid-template-columns: 190px 1fr;
    gap: 8px 16px;
}

.author-facts dt {
    color: var(--text-gray);
    font-size: 0.88rem;
}

.author-facts dd {
    margin: 0;
    font-size: 0.92rem;
    word-break: break-word;
}

/* Kept visibly apart from the internal contour above it: what the app publishes
   is read here and changed elsewhere. */
.author-published {
    margin-top: 28px;
    padding-top: 22px;
    border-top: 1px solid var(--border-gray);
}

.author-published__title {
    font-size: 0.95rem;
    font-weight: 700;
    margin-bottom: 12px;
}

.crm-series + .crm-series {
    margin-top: 30px;
}

.crm-series__title {
    display: flex;
    align-items: baseline;
    gap: 10px;
    font-size: 1rem;
    font-weight: 700;
    margin-bottom: 10px;
}

.crm-series__count {
    font-size: 0.82rem;
    font-weight: 500;
    color: var(--text-gray);
}

/* Inside a tab the tables are read, not clicked into. */
.author-tab .data-table th {
    position: static;
}

/* ---------- CRM: contracts ---------- */

/* Four statuses, and they are not the six a form goes through — a contract is
   active, ending soon, over, or not yet signed. Ending soon is a warning
   colour: it is the one that wants doing something about. */
.badge--contract-active        { background: rgba(95, 212, 155, 0.22); color: #10704A; }
.badge--contract-expires-soon  { background: rgba(255, 176, 32, 0.18); color: #9A6200; }
.badge--contract-expired       { background: #F1F3F5; color: #6B7280; }

/* The contracts table clips its multi-value cells through the same
   `.data-table--fixed td.col-text` rule the forms table uses, and its date and
   status columns through the widths that were already there. Inside a profile
   tab the head does not stick: the tab scrolls, not the table. */
.author-tab .contract-table th {
    position: static;
}

/* ---------- Contracts made here (TASK-084) ---------- */

/* Two more readings of the same derived status. Pending is quiet — a signed
   contract waiting for its upload is the ordinary early life of one. Warning is
   not: the books are live in the app and the paperwork is not done. */
.badge--contract-warning       { background: rgba(255, 122, 89, 0.20); color: #9A3412; }

/* The ladder: created → paid → tax form done → active. Progress, not alarm, so
   it climbs from grey towards the green `active` wears rather than borrowing a
   colour that means something else. */
.badge--contract-created       { background: #F1F3F5; color: #4B5563; }
.badge--contract-paid          { background: rgba(135, 183, 255, 0.22); color: #2B5FA8; }
.badge--contract-tax-form-done { background: rgba(86, 200, 232, 0.20); color: #0F6C88; }

/* A badge that answers when you click it. The cursor is the whole affordance —
   a second control beside the badge would say the same thing twice. */
.badge--asks {
    cursor: pointer;
    text-decoration: underline dotted;
    text-underline-offset: 2px;
}

/* Draft: the record exists here and not in the app. Deliberately not a badge —
   it is not a status of the thing, it is a third axis, and giving it the shape
   of a status is how the two get confused. */
.draft-chip {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 7px;
    border-radius: 999px;
    border: 1px dashed var(--border-gray);
    background: transparent;
    color: var(--text-gray);
    font-size: 0.72rem;
    letter-spacing: 0.02em;
    vertical-align: middle;
}

.tab-bar {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-bottom: 16px;
}

.cell-actions {
    text-align: right;
    white-space: nowrap;
}

.contract-section {
    margin: 22px 0 10px;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-gray);
}

.contract-section:first-child {
    margin-top: 0;
}

.contract-head {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 4px;
}

.contract-head__dates,
.contract-head__source {
    font-size: 0.85rem;
    color: var(--text-gray);
}

.contract-party {
    border: 1px solid var(--border-gray);
    border-radius: var(--radius);
    padding: 12px 14px;
    margin-bottom: 10px;
}

.contract-party--read {
    border-style: dashed;
}

.contract-party__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
}

.contract-party__label {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-gray);
}

.contract-party__name {
    font-weight: 600;
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
}

.contract-party__pair {
    display: flex;
    gap: 8px;
}

.contract-party__pair input {
    flex: 1;
}

.contract-dates {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 14px;
}

/* Tick, title, what it cost last time, what it costs now, and — for a draft —
   the way to remove it. Five columns rather than seven: the currency and the
   payment type moved up to the contract, which is where one agreement's terms
   belong, and took twenty-four dropdowns out of a twelve-book contract. */
.contract-book {
    display: grid;
    /* The last column holds two icons now — edit and delete — so it is wide
       enough for both. At 34px they sat over the price: a number somebody is
       typing and the controls that act on the row are two different things and
       should not touch. */
    grid-template-columns: 24px minmax(0, 1fr) 118px 104px 68px;
    align-items: center;
    gap: 10px;
    padding: 7px 4px;
    border-bottom: 1px solid var(--border-gray);
}

.contract-book--head {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-gray);
    border-bottom-width: 2px;
}

.contract-book--read {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
}

.contract-book--on {
    background: rgba(95, 212, 155, 0.08);
}

.contract-book__title {
    min-width: 0;
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
}

.contract-book__price {
    width: 100%;
}

/* "Not recorded" is a finding, not a blank: the 623 recovered contracts carry
   no prices because there were none to recover. It must never read as a zero,
   which is why it is a word and not a dash. */
.price-unknown {
    font-style: italic;
    color: var(--text-gray);
    font-size: 0.82rem;
    border-bottom: 1px dotted var(--border-gray);
    cursor: help;
}

.price-was {
    color: var(--text-gray);
    font-size: 0.85rem;
}

.price-free {
    color: #10704A;
    font-weight: 600;
}

.contract-series + .contract-series {
    margin-top: 18px;
}

.contract-series__title {
    font-size: 0.9rem;
    font-weight: 600;
    margin-bottom: 6px;
}


.contract-ticks {
    display: flex;
    gap: 20px;
    flex-wrap: wrap;
}

.contract-tick {
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: 0.9rem;
}

.contract-tick input {
    margin: 0;
}

.contract-form .form-group {
    margin-bottom: 0;
}


.warning-list {
    margin: 10px 0 14px 20px;
    list-style: disc;
}

.warning-list li {
    margin: 4px 0;
    font-weight: 600;
}

/* ---------- Our own form controls (TASK-084) ---------- */
/*
   Every dropdown, date, number and checkbox in a form was the browser's own
   until now, so a page built from our panels ended in a row of widgets belonging
   to somebody else's operating system — and ones whose size and behaviour differ
   per platform. `controls.js` draws them; this styles them.

   The dropdown deliberately looks like the filter popups: same anchored popup,
   same search box, same rows. A second idiom for choosing from a list would mean
   two things to learn.
*/

.ac-select {
    display: inline-flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    min-height: var(--ac-control-h);
    padding: 9px 12px;
    background: var(--white);
    border: 1px solid var(--border-gray);
    border-radius: 8px;
    font: inherit;
    font-size: 0.9rem;
    color: var(--primary);
    cursor: pointer;
    text-align: left;
}

.ac-select:hover {
    border-color: #C9CFD8;
}

.ac-select--open,
.ac-select:focus-visible {
    border-color: var(--accent);
    outline: none;
    box-shadow: 0 0 0 3px rgba(229, 50, 114, 0.12);
}

.ac-select__label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The placeholder is a prompt, not an answer, and must not read as one. */
.ac-select--empty .ac-select__label {
    color: var(--text-gray);
}

.ac-select__caret {
    flex: 0 0 auto;
    color: var(--text-gray);
}

/* Above the dialog layer: a dropdown inside a dialog has to draw over it. */
.ac-pop {
    position: absolute;
    z-index: 500;
    background: var(--white);
    border: 1px solid var(--border-gray);
    border-radius: 10px;
    box-shadow: 0 12px 32px rgba(9, 18, 39, 0.16);
    padding: 6px;
    max-width: 420px;
}

.ac-pop__search {
    width: 100%;
    padding: 7px 9px;
    margin-bottom: 6px;
    border: 1px solid var(--border-gray);
    border-radius: 7px;
    font: inherit;
    font-size: 0.88rem;
}

.ac-pop__search:focus {
    outline: none;
    border-color: var(--accent);
}

.ac-pop__list {
    max-height: 260px;
    overflow-y: auto;
}

.ac-pop__item {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    width: 100%;
    padding: 7px 9px;
    background: none;
    border: 0;
    border-radius: 7px;
    font: inherit;
    font-size: 0.88rem;
    color: var(--primary);
    cursor: pointer;
    text-align: left;
}

.ac-pop__item:hover {
    background: var(--light-gray);
}

.ac-pop__item--on {
    font-weight: 600;
    color: var(--accent);
}

.ac-pop__note,
.ac-pop__empty {
    color: var(--text-gray);
    font-size: 0.8rem;
}

.ac-pop__empty {
    padding: 10px 9px;
}

/* **One height for every control.** A text field, a dropdown, a date and a
   button stand side by side in these forms, and until now each was a different
   size — the text inputs picked up the modal's own wider rule and the rest kept
   theirs. The number is `--ac-control-h`, and the ghost button matches it.

   `.modal-form input.ac-input` is here to out-specify `.modal-form
   input[type="text"]`, which is where the difference came from. */
.ac-input,
.modal-form input.ac-input {
    width: 100%;
    /* Height as well as min-height: a date field's own content is taller than
       the others', so a floor alone left it two pixels out of line. */
    height: var(--ac-control-h);
    min-height: var(--ac-control-h);
    padding: 9px 12px;
    background: var(--white);
    border: 1px solid var(--border-gray);
    border-radius: 8px;
    font: inherit;
    font-size: 0.9rem;
    line-height: 1.4;
    color: var(--primary);
    transition: none;
}

.ac-input:hover {
    border-color: #C9CFD8;
}

.ac-input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(229, 50, 114, 0.12);
}

/* A placeholder is a prompt, not an answer. `--text-gray` is all but the text
   colour, which is how `$ 0,000.00` came to read as a figure somebody had
   typed. */
.ac-input::placeholder,
.modal-form input.ac-input::placeholder {
    color: #A3ABB8;
}

/* ---------- money ----------

   The currency symbol and the shape of the number are the placeholder, so the
   box says what goes in it without putting anything in it. The separators are
   written as the number is typed; the decimal separator is a full stop and a
   comma is never accepted, because the commas are ours. Zero reads `$ 0.00` —
   a decision about the book, and not an empty box. */
.ac-input--money {
    font-variant-numeric: tabular-nums;
}

/* The spinner is a pair of two-pixel arrows nobody has ever hit on purpose, and
   it is drawn differently by every browser. The keyboard and the field still
   work; only the arrows go. */
.ac-input--number {
    -moz-appearance: textfield;
    appearance: textfield;
}

.ac-input--number::-webkit-outer-spin-button,
.ac-input--number::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* The calendar button is the one part of a date field we cannot draw ourselves
   without losing the picker, so it is recoloured and given room rather than
   replaced. */
/* The calendar button is the one part of a date field we cannot draw ourselves
   without losing the picker, so it is recoloured and given room rather than
   replaced.

   **Hovering makes it clearer.** It used to fade instead: the browser puts its
   own hover on this button, and a rule that only raised the opacity left that
   showing through. The state is now written out in full — opacity, colour and
   a background that reads against the field. */
.ac-input--date::-webkit-calendar-picker-indicator {
    opacity: 0.55;
    cursor: pointer;
    padding: 3px;
    margin-left: 4px;
    border-radius: 5px;
    filter: none;
    background-color: transparent;
    transition: opacity 0.12s, background-color 0.12s;
}

.ac-input--date:hover::-webkit-calendar-picker-indicator,
.ac-input--date::-webkit-calendar-picker-indicator:hover,
.ac-input--date::-webkit-calendar-picker-indicator:focus {
    opacity: 1;
    background-color: #E9ECF1;
}

/* ---------- the search box ----------

   Four tables carry one of these and it sits in the same place on each, so it
   is described here beside the other controls rather than per section. It
   stands in a row of ghost buttons, which is why it keeps `--ac-control-h` —
   the one height every control in this service shares. */
.ac-search {
    position: relative;
    display: inline-flex;
    align-items: center;
}

/* `input.` carries the weight of `input[type="text"]`, which is where the
   admin's own wider padding comes from — the same trap `.modal-form
   input.ac-input` above is written to get out of. A bare class loses to it and
   the icon lands on top of the placeholder. */
.ac-search input.ac-search__input {
    /* Wide enough for the longest of the four placeholders to read whole —
       a prompt that says what the box searches is no use cut off mid-word —
       and for a name with a word after it once something is typed. */
    width: 300px;
    /* Room at both ends for the two things drawn over the field. */
    padding: 9px 32px 9px 36px;
}

/* Over the field rather than beside it: a magnifier in a box of its own would
   be one more control in a row that is already a row of them. */
.ac-search__icon {
    position: absolute;
    left: 12px;
    color: #9CA3AF;
    pointer-events: none;
}

/* Only there when there is something to clear, so an empty box is a box. */
.ac-search__clear {
    position: absolute;
    right: 8px;
    display: inline-flex;
    padding: 2px;
    background: none;
    border: 0;
    border-radius: 4px;
    line-height: 0;
    color: #9CA3AF;
    cursor: pointer;
}

.ac-search__clear:hover {
    color: var(--primary);
}

/* The slot each toolbar keeps for the box, alone on the right-hand side. Empty
   until its section's script fills it, and gone while it is, so a script that
   never ran leaves no empty column of nothing. */
.toolbar-search:empty {
    display: none;
}

/* A real checkbox underneath — focusable, keyboard-operable, readable by
   anything that reads a form — with the box drawn from its own state. */
.ac-check {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    cursor: pointer;
    font-size: 0.9rem;
    user-select: none;
}

.ac-check input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.ac-check__box {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    border: 1px solid #C4CAD3;
    border-radius: 5px;
    background: var(--white);
    position: relative;
    transition: background 0.12s, border-color 0.12s;
}

.ac-check__box::after {
    content: "";
    position: absolute;
    left: 5px;
    top: 1px;
    width: 5px;
    height: 10px;
    border: solid var(--white);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg) scale(0.6);
    opacity: 0;
    transition: opacity 0.12s, transform 0.12s;
}

.ac-check input:checked + .ac-check__box {
    background: var(--accent);
    border-color: var(--accent);
}

.ac-check input:checked + .ac-check__box::after {
    opacity: 1;
    transform: rotate(45deg) scale(1);
}

.ac-check input:focus-visible + .ac-check__box {
    box-shadow: 0 0 0 3px rgba(229, 50, 114, 0.16);
}

.ac-check input:disabled + .ac-check__box {
    background: #EEF0F3;
    border-color: var(--border-gray);
    cursor: not-allowed;
}

.ac-check__note {
    color: var(--text-gray);
    font-size: 0.82rem;
}

/* ---------- The contract form, after the owner's review ---------- */

/* Renewal is one click from any row. A fixed narrow column outside the column
   chooser: it is a way to act on the row, not a fact somebody might hide. */
.col-w-action { width: 46px; }

.th-action { padding: 0; }

.contract-book__actions {
    display: flex;
    justify-content: flex-end;
    gap: 2px;
    padding-left: 6px;
}

.contract-series__head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 6px;
}

.contract-series__head .contract-series__title {
    margin: 0;
}

/* One currency and one payment type, above the books, because they govern every
   price under them. */
.contract-money-row {
    display: grid;
    grid-template-columns: 130px 190px;
    gap: 14px;
    margin-bottom: 14px;
}

.contract-dates {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 220px));
    gap: 14px;
}


.contract-ticks {
    display: flex;
    gap: 26px;
    flex-wrap: wrap;
    margin-top: 20px;
}

/* Books uploaded is shown and never set: it is the app answering through n8n,
   and a control would let somebody assert that books are live when they are
   not. A line of state, styled as a statement rather than as a field. */
.contract-uploadstate {
    margin-top: 12px;
    padding: 9px 12px;
    background: var(--light-gray);
    border-radius: 8px;
    color: var(--text-gray);
    font-size: 0.85rem;
}

/* `.ac-check` is a <label>, and inside a modal form `.modal-form label` makes
   every label a block in bold — which turned the drawn box into an inline span
   with no width, so the checkbox vanished. The control keeps its own shape
   wherever it is used. */
.modal-form .ac-check,
.contract-book .ac-check {
    display: inline-flex;
    align-items: center;
    font-weight: 400;
    font-size: 0.9rem;
    letter-spacing: normal;
    margin-bottom: 0;
}

.modal-form .ac-check__box,
.contract-book .ac-check__box {
    display: block;
}

.modal-form .ac-check__label {
    font-weight: 500;
}

/* A row's tick has no words beside it — the title is the label — so the gap
   would otherwise sit there as an empty column. */
.contract-book .ac-check {
    gap: 0;
}

/* A contract made here shows its prices as fields: the figure is often agreed
   after the paper, and a missing one holds the contract at Warning, so there has
   to be somewhere to put it. Everything else about the terms stays fixed. */
.contract-book--edit {
    grid-template-columns: minmax(0, 1fr) 132px;
    align-items: center;
}

.contract-saveprices {
    display: flex;
    justify-content: flex-end;
    margin-top: 10px;
}

/* The currency row inside the contract card is two short fields, not the
   form's wider pair. */
#modalBody .contract-money-row {
    grid-template-columns: 120px 180px;
    margin-top: 4px;
}

/* ---------- The second review: series, books, money, marks ---------- */

/* A dialog over whatever is already open. The admin has one modal element, so a
   form that has to ask something — a title, a number in the series, "are you
   sure" — had nowhere to ask it without throwing the form away. This is its own
   layer, above the modal and below the dropdown popups. */
.ac-dialog__back {
    position: fixed;
    inset: 0;
    z-index: 400;
    background: rgba(9, 18, 39, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}

.ac-dialog {
    width: min(440px, 100%);
    max-height: 90vh;
    overflow-y: auto;
    background: var(--white);
    border-radius: 14px;
    box-shadow: 0 24px 64px rgba(9, 18, 39, 0.28);
    padding: 22px 24px 18px;
}

.ac-dialog__title {
    font-size: 1.05rem;
    font-weight: 700;
    margin-bottom: 14px;
}

.ac-dialog__body .form-group + .form-group {
    margin-top: 14px;
}

.ac-dialog__body label {
    display: block;
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-gray);
    margin-bottom: 6px;
}

.ac-dialog__text {
    font-size: 0.95rem;
    line-height: 1.6;
}

.ac-dialog__note {
    margin-top: 8px;
    font-size: 0.85rem;
    color: var(--text-gray);
}

.ac-dialog__error {
    margin-top: 12px;
    padding: 9px 12px;
    border-radius: 8px;
    background: rgba(220, 38, 38, 0.08);
    color: #B91C1C;
    font-size: 0.85rem;
}

.ac-dialog__foot {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    margin-top: 20px;
}

.ac-dialog__foot .btn {
    padding: 10px 18px;
    font-size: 0.9rem;
}

/* Deleting is the only destructive act in these forms, so the button that does
   it does not look like the one beside it that adds. */
.btn-danger {
    background-color: #DC2626;
    color: var(--white);
    border-color: #DC2626;
}

.btn-danger:hover:not(:disabled) {
    background-color: #B91C1C;
    border-color: #B91C1C;
}

/* The mark on outstanding paperwork: Paid, the tax form, and books that are not
   uploaded. One idea, so one icon — and it goes when the thing is done. */
.warn-icon {
    flex: 0 0 auto;
    color: #C2410C;
}

/* Every series row carries its own way to add a book, because a book belongs to
   a series and that is where the question starts. The bin sits beside it and
   always asks first. */
.contract-series__tools {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-left: auto;
}

/* `add book` sits on a series head, among titles and chips, and as a ghost it
   read as another label. It takes the sidebar's own colour: the darkest thing on
   the page is unmistakably a control, and it is not the accent, which belongs to
   the one primary action of a screen. */
.btn-tiny {
    gap: 5px;
    padding: 5px 10px;
    font-size: 0.78rem;
    font-weight: 600;
    border-width: 1px;
    border-radius: 8px;
    background: var(--primary);
    border-color: var(--primary);
    color: var(--white);
}

.btn-tiny:hover:not(:disabled) {
    background: var(--navy-soft);
    border-color: var(--navy-soft);
    color: var(--white);
}

/* Adding a series has its own block: a dropdown offering New Series and
   Standalone, and one button to confirm. Standalone leaves the list once the
   author has one — it is a category, not a series, and there is only ever one
   of it. */
.contract-addseries {
    margin-top: 18px;
    padding-top: 14px;
    border-top: 1px dashed var(--border-gray);
}

.contract-addseries__row {
    display: flex;
    gap: 8px;
    align-items: center;
}

.contract-addseries__row .ac-select {
    flex: 0 1 260px;
}

.contract-addseries__row .btn {
    flex: 0 0 auto;
    min-height: var(--ac-control-h);
    white-space: nowrap;
}

/* The two ticks, each with its mark beside it. */
.contract-ticks .contract-tick {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* The state line carries the same mark as the ticks. */
.contract-uploadstate {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* ---------- The third review ---------- */

/* A legal name is entered as two fields and read as one. The pair is the same
   shape the new-author names use, because it is the same question asked twice. */
.field-pair {
    display: flex;
    gap: 8px;
}

.field-pair input {
    flex: 1;
    min-width: 0;
}

/* The switch and the remove on a party block sit together, away from the label. */
.contract-party__controls {
    display: flex;
    align-items: center;
    gap: 14px;
}

/* Create is what starts the existence check, and what it found is said beside
   it rather than in a banner somewhere else on the form. */
.contract-party__make {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 10px;
    flex-wrap: wrap;
}

.contract-party__settled {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.84rem;
    color: #10704A;
}

.contract-party__settled:empty {
    display: none;
}

/* The size of a button inside a modal foot — the weight the act actually has. */
.btn-compact {
    padding: 9px 20px;
    font-size: 0.9rem;
}

/* Deleting a contract names every row that goes with it. */
.ac-dialog__list {
    margin: 8px 0 0 20px;
    list-style: disc;
    font-size: 0.88rem;
    color: var(--text-gray);
}

.ac-dialog__list li {
    margin: 3px 0;
}

/* ---------- TASK-086: from a contract to the app ---------- */

/* The step between "made here" and "active": the app has the books and has not
   published them all. Blue, like `paid` — it is progress, not an alarm. */
.badge--contract-uploaded      { background: rgba(135, 183, 255, 0.28); color: #24518F; }

/* Draft, uploaded, published — one chip with three readings.
   Draft keeps the dashed outline it has always had: it is the only one of the
   three that exists nowhere but here. Uploaded is solid and quiet, published
   quieter still, because 2000 books carry it and a loud chip on every row is a
   chip nobody reads. */
.draft-chip--uploaded {
    border-style: solid;
    border-color: rgba(135, 183, 255, 0.5);
    background: rgba(135, 183, 255, 0.16);
    color: #24518F;
}

.draft-chip--published {
    border-style: solid;
    border-color: var(--border-gray);
    background: transparent;
    color: var(--text-gray);
    opacity: 0.7;
}

/* A name the app sent back that we could not place. It sits beside the status
   badge and says how many, because the row is where somebody notices. */
.draft-chip--asks {
    border-style: solid;
    border-color: rgba(255, 122, 89, 0.5);
    background: rgba(255, 122, 89, 0.14);
    color: #9A3412;
}

/* One form, one line: what it is, and the link. */
.contract-formline {
    padding: 10px 0;
    border-bottom: 1px solid var(--border-gray);
}

.contract-formline:last-child { border-bottom: none; }

.contract-formline__id {
    font-size: 0.78rem;
    color: var(--text-gray);
    margin-bottom: 6px;
}

.link-box {
    width: 100%;
    height: var(--ac-control-h, 44px);
    padding: 0 12px;
    border: 1px solid var(--border-gray);
    border-radius: 8px;
    background: #F8F9FA;
    font-size: 0.82rem;
}

/* What the app said back, one line each. A placed name is a statement; an
   unplaced one carries the chooser that answers it. */
.contract-answer {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    padding: 6px 0;
    font-size: 0.85rem;
}

.contract-answer__pick {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    margin-top: 4px;
}

/* The chooser and its button share a line, and the button never wraps: "It is
   this one" over two lines reads as two buttons. */
.contract-answer__pick > :first-child { flex: 1; min-width: 0; }
.contract-answer__pick .btn { white-space: nowrap; }

/* Two icons in the slot at the end of a row: the form a contract went out on,
   and the one act left on the contract itself. */
.col-w-action2 { width: 82px; }

/* An icon whose work is done but whose link still leads somewhere — the form
   after it has been sent. Present, quiet, still clickable. */
.icon-btn--quiet { opacity: 0.45; }
.icon-btn--quiet:hover { opacity: 0.8; }

/* What a form made from a contract will carry, above the private/public
   question rather than instead of it. */
.form-carry {
    margin-bottom: 14px;
    font-size: 0.85rem;
}

.form-carry__line {
    display: flex;
    gap: 10px;
    align-items: baseline;
    padding: 3px 0;
}

.form-carry__id {
    color: var(--text-gray);
    white-space: nowrap;
}

/* "(empty)" in a filter list: a value like any other, said quietly. */
.ac-check--muted .ac-check__label { color: var(--text-gray); }

/* A contract sent in waves can have a form still open. Said where the next one
   is being made, not discovered afterwards. */
.form-carry__note {
    margin: 2px 0 6px;
    padding-left: 2px;
    color: #9A6200;
    font-size: 0.8rem;
}
