/* 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 {
    /* A series' bar and the frame around its table: one colour at two
       strengths, so they cannot drift apart. */
    --series-bar: rgba(9, 18, 39, 0.05);
    --series-edge: rgba(9, 18, 39, 0.14);
    --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;
    /* The rail is the viewport's height and never scrolls as a whole: the logo
       stays on top, Collapse at the bottom, and only the menu between them
       scrolls when its groups are taller than the screen. TASK-187. */
    height: 100vh;
    overflow: hidden;
}

.sidebar .brand {
    flex: 0 0 auto;
    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;
    /* The one part of the rail that scrolls. `min-height: 0` lets it shrink
       below its content in the column; without it the flex item keeps its full
       height and pushes Collapse off the screen. */
    flex: 1 1 auto;
    min-height: 0;
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
    /* Thin and quiet on the dark rail. The standard pair is what Chrome and
       Firefox use; Safari reads the -webkit- rules below. */
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, 0.22) transparent;
}

.sidebar nav::-webkit-scrollbar {
    width: 6px;
}

.sidebar nav::-webkit-scrollbar-track {
    background: transparent;
}

.sidebar nav::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.22);
    border-radius: 3px;
}

.sidebar nav::-webkit-scrollbar-thumb:hover {
    background: rgba(255, 255, 255, 0.36);
}

/* ---------- 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. */
/* Collapse and the version under it: the rail's footer, outside the scrolling
   menu, so both stay on screen however long the menu grows. TASK-187/188. */
.sidebar-footer {
    flex: 0 0 auto;
    margin-top: auto;
    display: flex;
    flex-direction: column;
    border-top: 1px solid rgba(135, 183, 255, 0.16);
}

.sidebar-toggle {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 24px;
    background: none;
    border: 0;
    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;
}

/* Small and quiet: something to read when asked, not to notice every day.
   Lined up with the Collapse icon above it, which leaves the staging line
   ("Version 0.2.001 · staging a1b2c3d") room to fit. */
.sidebar-version {
    padding: 0 12px 0 24px;
    color: rgba(255, 255, 255, 0.4);
    font-size: 0.7rem;
    line-height: 1.4;
    letter-spacing: 0.01em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-variant-numeric: tabular-nums;
}

.sidebar-version__short {
    display: none;
}

/* Collapsed to icons: the number alone, centred; the full line is its
   tooltip. */
.app.sidebar-collapsed .sidebar-version {
    padding: 0 2px;
    text-align: center;
    text-overflow: clip;
}

.app.sidebar-collapsed .sidebar-version__full {
    display: none;
}

.app.sidebar-collapsed .sidebar-version__short {
    display: inline;
}

.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.

   The brand pink since TASK-185, so it is noticed, and 1.5 times as wide as it
   was: 92 px with 14 px sides, measured on "STAGING", so 138 px with 37 px
   sides. The text is white, the owner's choice (2026-09-18), although white on
   #E53272 is 4.17:1, a little under the 4.5:1 twelve-pixel text asks for. */
.env-badge {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    padding: 4px 37px;
    border-radius: 4px;
    background: var(--accent);
    color: var(--white);
    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;
    }

    .sidebar-version {
        padding: 0 2px;
        text-align: center;
        text-overflow: clip;
    }

    .sidebar-version__full {
        display: none;
    }

    .sidebar-version__short {
        display: inline;
    }

    .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;
}

/* The same rule, counted for a table whose sized columns take 674px of it: below
   this, Document, Counterparty and Template are narrower than their own headers
   and the three of them run together. Scrolling is the answer here as it is at
   960 — the alternative is the mess this table was (TASK-131). */
#documentsTable {
    min-width: 1120px;
}

/* And for Contracts, which took a sized column on (TASK-137): Updated's width
   came out of the four columns that share what is left, so the floor goes up by
   the same amount and those four are no narrower at it than they were at 960. */
#contractsTable {
    min-width: 1160px;
}

.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;
    /* A warning you click for the details (TASK-184). */
    cursor: pointer;
}

.form-view .audio-note__text { color: var(--blue-mid); }
.col-w-date { width: 132px; }
/* **A column is at least as wide as its own name.** A header carries the label,
   a sort arrow and a filter icon, so a two-word date column needs more than the
   132px a date does — Documents' **Effective Date**, which spilled its header
   over Expires at every window width. */
.col-w-date2 { width: 172px; }
.col-w-status { width: 128px; }
/* And at least as wide as the longest word it holds. A document's statuses are
   sentences — *Awaiting the Licensor*, *Filled in, not signed* — and a badge is
   one box: it cannot ellipsise, so a status column of the usual width is a
   coloured shape with the answer cut off it. */
.col-w-status2 { width: 196px; }
/* **When, then who** — `11 Sep 2026 10:14 · admin` (TASK-137). Wide enough for
   the time in the longer of the spellings a browser gives it, `Sep 11, 2026,
   10:14 AM`, with a login beside it. It is a `col-text` cell, so a longer name
   clips like any other value; the time never does, because it comes first. */
.col-w-stamp { width: 200px; }
.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, the
   form's rocket (back in TASK-180 revision 2). */
.col-w-actions { width: 152px; }
.col-w-actions--5 { width: 176px; }

/* **A value longer than its column has to be a clipped cell, or it draws over
   the cell beside it.** The layout hands every column a width and `nowrap` stops
   anything wrapping, so a `td` that is not one of these keeps painting to the
   right until it runs out of text — the Documents table's Template was drawn
   straight across Version and Status, which is what made the rows read as one
   mess (TASK-131). A column whose content has no known length takes `col-text`
   and clips; every other column is sized for what it holds. */
.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;
}

/* A contract's status in a table cell is cut inside its pill rather than spilt
   over the next column (TASK-164): *Contract signed by author* is wider than
   Status. inline-block, because an ellipsis does not reach text inside a flex
   box. The badge's hint starts with the whole word. The card's badge is not in a
   cell and keeps its full width. */
td > .badge[class*="badge--contract-"] {
    display: inline-block;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    vertical-align: middle;
}

.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. Every label on one line
   (TASK-136): a button whose words wrap is two buttons' height in a row of
   one-line ones, and reads as two acts. Scoped to the footer, so it outweighs
   `.btn` without touching a button anywhere else. */
.modal__foot .btn {
    padding: 9px 20px;
    font-size: 0.9rem;
    white-space: nowrap;
}

/* The contract card's principal act stands on its own at the left, the saves
   together at the right (owner, 2026-09-11). `margin-right: auto` on the first
   child is what splits a `flex-end` row in two. */
.modal__foot .btn-send {
    margin-right: auto;
}

/* Save changes: saves and keeps the card open. At rest it is Cancel's ghost
   (owner, 2026-09-11 — nothing in a resting footer should shout but the send);
   its hover is its own, accent rather than the ghost's navy, so the pointer can
   tell the two ghosts apart. */
.modal__foot .btn-save-stay:hover:not(:disabled) {
    color: var(--accent);
    border-color: var(--accent);
}

.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 ---------- */

/* Wide enough for the word over the number as well as the number: `VERSION`
   with a sort arrow and a filter icon beside it needs more room than `v1.2`
   does, and at 112px it drew the last letters of its own header over the column
   to its right — on Templates as well as on Documents. */
.col-w-count { width: 128px; }

/* 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, when it ends, and —
   for a draft — the way to remove it. Six columns: 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; the end date
   came down to the book, because a licence may end earlier for one of them. */
.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. */
    /* The end date is a link now, not a field, so it needs the width of a date
       and no more — 144px was a box's width and it was taking it from the
       title, which is why an `uploaded` chip fell to a second line. */
    grid-template-columns: 24px minmax(0, 1fr) 100px 94px 104px 68px;
    align-items: center;
    gap: 10px;
    padding: 7px 12px;
    border-bottom: 1px solid var(--border-gray);
}

/* No pencil and no bin anywhere in this series, so no column for them and the
   table takes the width back. Set on the block rather than the row: inside one
   table the columns still have to line up. */
.contract-series--noactions .contract-book {
    grid-template-columns: 24px minmax(0, 1fr) 100px 94px 104px;
}

.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;
}

/* The end date of one book: a link, not a field. It reads the date the book ends
   on — its own, or the contract's — and clicking it asks whether to change it.
   An open date box on every row of a twelve-book contract is twelve invitations
   to change something nobody asked to change. Styled as text rather than as a
   button so the row still reads as a row. */
.contract-book__ends {
    /* By the text, not by the cell: a dashed rule running the width of the
       column read as a field somebody had emptied. */
    justify-self: start;
    max-width: 100%;
    background: none;
    border: 0;
    padding: 6px 0;
    font: inherit;
    font-size: 0.85rem;
    color: var(--primary);
    text-align: left;
    cursor: pointer;
    border-bottom: 1px dashed transparent;
}

.contract-book__ends:hover {
    border-bottom-color: var(--primary);
}

/* No date anywhere — neither the book's nor the contract's. Says so in words
   rather than showing an empty cell nobody can tell from a missing control. */
.contract-book__ends--none {
    color: var(--text-gray);
    font-style: italic;
}

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

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

/* **One frame around the whole thing.** Borders on each row left the bar
   looking like a lid resting on a separate table; the block itself is what
   carries the outline, in the bar's own tone, and the rows carry nothing. */
.contract-series {
    border: 1px solid var(--series-edge);
    border-radius: 8px;
    overflow: hidden;
}

.contract-series .contract-book:last-child {
    border-bottom: 0;
}

.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;
}


/* ---------- 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);
}

/* An option that cannot be chosen now (TASK-154 revision 2): its text grey, as a
   disabled button's, and no hover. Nothing else marks it. */
.ac-pop__item--off,
.ac-pop__item--off:hover {
    color: #9CA3AF;
    background: none;
    cursor: not-allowed;
}

/* Several answers from one list: the same rows as the single dropdown, with
   room for a tick in front. The tick keeps its space when it is not shown, so
   the labels stay in one column and nothing shifts as entries are picked. */
.ac-pop__item--multi {
    justify-content: flex-start;
}

.ac-pop__item--multi .ac-pop__text {
    flex: 1 1 auto;
}

.ac-pop__tick {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    color: var(--accent);
    visibility: hidden;
}

.ac-pop__item--on .ac-pop__tick {
    visibility: visible;
}

.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;
}

/* ---------- files chosen for a letter ---------- */

/* Our own button over the browser's picker, and what was chosen underneath it.
   The chips wrap: three attachments should push the Send button down, not off
   the side of the panel. */
.ac-files {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
    min-width: 0;
}

.ac-files__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    max-width: 100%;
}

/* One file: what it is called, how big it is, and — where it can be had — a way
   to get it. The name is the only part allowed to shrink, so the size never
   wraps away from the name it belongs to. */
.ac-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    max-width: 100%;
    padding: 4px 8px;
    border: 1px solid var(--border-gray);
    border-radius: 8px;
    background: var(--light-gray);
    font-size: 0.8rem;
    color: var(--primary);
    text-decoration: none;
}

.ac-chip__name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ac-chip__size {
    flex: 0 0 auto;
    color: var(--text-gray);
    font-variant-numeric: tabular-nums;
}

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

/* A chip that leads somewhere says so under the cursor. */
.ac-chip--link:hover {
    border-color: var(--accent);
    color: var(--accent);
}

.ac-chip--link:hover .icon {
    color: var(--accent);
}

/* A chip that leads nowhere — the file is in Gmail and we hold its name. */
.ac-chip--flat {
    background: transparent;
    border-style: dashed;
    color: var(--text-gray);
}

.ac-chip__drop {
    flex: 0 0 auto;
    border: 0;
    padding: 0 2px;
    background: none;
    color: var(--text-gray);
    font-size: 0.95rem;
    line-height: 1;
    cursor: pointer;
}

.ac-chip__drop:hover {
    color: var(--accent);
}

/* ---------- 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;
}

/* **A bar over the block, not a line beside it.** The series' name and its own
   buttons sit on one strip that spans the table it heads, so `add book` plainly
   belongs to this series rather than floating in the corner of the form. The
   title is small caps with a count rather than a larger font: a bigger word
   would have been louder without being clearer. */
.contract-series__head {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 0;
    padding: 8px 12px;
    background: var(--series-bar);
    border-bottom: 1px solid var(--series-edge);
}

.contract-series__head .contract-series__title {
    margin: 0;
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-dark);
}

/* How many books are under the bar, answered here so nobody counts rows. */
.contract-series__count {
    font-size: 0.78rem;
    color: var(--text-gray);
}

/* Standalone is a category, not a series, and a bar identical to a series' own
   would read as one more series. Paler, and the difference is deliberate. */
.contract-series__head--standalone {
    background: rgba(9, 18, 39, 0.025);
}



/* 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 form's version of that row carries five facts about the whole agreement,
   not two: the territory, the language and the term length govern the licence
   over every book the way the currency governs every price. The territory and
   the language share what is left — both are lists of names, and neither is
   worth more room than the other; the term length is one value out of four and
   needs no more room than the currency. */
#modalBody .contract-money-row--terms {
    grid-template-columns: 105px 150px minmax(0, 1fr) minmax(0, 1fr) 130px;
    align-items: start;
}

/* The territory and the language are dropdowns that take several answers, so
   each reads back as a list of names in a box the width of its column. It
   ellipsises rather than wrapping — the full answer is one click away in the
   list, where the chosen entries sit at the top. */
.contract-terms-pick {
    min-width: 0;
}

/* ---------- 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;
}

/* Wide is for a dialog holding a table rather than a question. **Written after
   `.ac-dialog`, and that is the whole of it:** the two selectors weigh the same,
   so the later one wins. Put above, this rule did nothing twice — the reference
   lists went on being squeezed into 440px, and I moved it and left it above
   again. */
.ac-dialog--wide { width: min(880px, 100%); }

.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 two rungs between Invoice Paid and Active (TASK-180 revision 1), in the
   colours of the book status they wait for: Books Uploaded is every book at
   least Uploaded (blue), Books Deployed every book at least Deployed (indigo).
   Active, every book Published, is the green below. */
.badge--contract-books-uploaded { background: rgba(135, 183, 255, 0.22); color: #2B5FA8; }
.badge--contract-books-deployed { background: rgba(99, 102, 241, 0.16); color: #4338CA; }

/* 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;
}

/* ---------- Templates, and the constructor (TASK-112) ---------- */

/* Three states and not four: a draft nothing points at, the one in use, and the
   past. Archived is the quiet one — it is not a warning, it is a document that
   has stopped being the current one. */
.badge--template-draft    { background: rgba(135, 183, 255, 0.22); color: #335495; }
.badge--template-active   { background: rgba(95, 212, 155, 0.22); color: #10704A; }
.badge--template-archived { background: #F1F3F5; color: #6B7280; }

/* Three icons where a template can carry three: edit, duplicate, archive. */
.col-w-action3 { width: 118px; }
.col-w-action4 { width: 154px; }

/* A document type's whole name. "Audio Book Distribution Agreement" clipped to
   "Audio Bo…" says nothing, and 230px clipped it.

   The name column is sized too, and that is the point: with every column given
   a width the table spreads what is left over across all of them in proportion,
   instead of pouring it all into the one column nobody had sized — which is
   what put a hand's width of blank between a template's name and its type. */
.col-w-doctype { width: 280px; }
.col-w-tplname { width: 300px; }

/* How many versions there have been, beside the one being looked at. Spaced
   from the number: `v4` and `4 versions` running together read as `v44`. */
.tpl-versions {
    margin-left: 8px;
    color: var(--text-gray);
    font-size: 0.76rem;
}

/* The name is a link to the document, so it reads as one and still clips with
   the rest of its column. */
.cell-link {
    color: var(--primary);
    text-decoration: none;
    border-bottom: 1px solid rgba(9, 18, 39, 0.25);
}

.cell-link:hover {
    color: var(--accent);
    border-bottom-color: var(--accent);
}

/* The blocks on the left, the block being edited in the middle. The left column
   is fixed: a list of eighteen labels does not want to grow with the window,
   and the editor does. */
.tpl-grid {
    display: grid;
    grid-template-columns: 320px minmax(0, 1fr);
    gap: 16px;
    align-items: start;
}

.tpl-blocks { padding: 12px; }

.tpl-editor {
    padding: 18px 20px;
    min-height: 420px;
}

/* One block in the list: what it is called, what kind it is, and the one thing
   that may be done to it — a bin, or a lock with the reason on it. */
.tpl-block {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: 1;
    min-width: 0;
}

.tpl-block--on {
    background: var(--series-bar);
    border-radius: 8px;
}

.tpl-block__open {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1px;
    padding: 6px 8px;
    background: none;
    border: 0;
    border-radius: 8px;
    font: inherit;
    text-align: left;
    color: var(--primary);
    cursor: pointer;
}

.tpl-block__open:hover { background: rgba(9, 18, 39, 0.04); }

.tpl-block__label {
    font-size: 0.86rem;
    font-weight: 600;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tpl-block__kind {
    font-size: 0.74rem;
    color: var(--text-gray);
}

/* A lock where a bin would be. The colour separates the two strengths: the
   machinery cannot be written at all, the lawyers' wording can be, carefully. */
.tpl-block__lock {
    display: inline-flex;
    padding: 4px;
    color: #9A6200;
}

.tpl-block__lock--machinery { color: var(--blue-mid); }

.tpl-add { margin-top: 8px; }

/* The editor and the variable panel side by side. The panel is narrow and
   scrolls: it is a list to reach into, not a thing to read. */
.tpl-text {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 250px;
    gap: 16px;
    align-items: start;
}

.tpl-text__main { min-width: 0; }

.ac-input--area {
    height: auto;
    min-height: 120px;
    padding: 10px 12px;
    line-height: 1.5;
    resize: vertical;
    /* **It wraps, like any editor.** It used to hold the agreement's own line
       breaks and scroll sideways, which meant reading a paragraph by dragging.
       One paragraph is one line and a line break starts a new paragraph; where
       the line is too long for the box it is folded for the eye and nothing is
       put into the text. The seeded blocks were re-flowed to match (027). */
    font-size: 0.88rem;
    white-space: pre-wrap;
    overflow-wrap: break-word;
    overflow-x: hidden;
}

.tpl-vars {
    border: 1px solid var(--border-gray);
    border-radius: 10px;
    padding: 10px;
    max-height: 520px;
    overflow-y: auto;
}

.tpl-vars__title {
    font-size: 0.86rem;
    font-weight: 700;
}

.tpl-vars__note {
    margin: 4px 0 10px;
    font-size: 0.74rem;
    color: var(--text-gray);
}

.tpl-vars__group {
    margin: 10px 0 4px;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-gray);
}

.tpl-var {
    display: block;
    width: 100%;
    padding: 5px 8px;
    margin-bottom: 3px;
    background: var(--light-gray);
    border: 1px solid transparent;
    border-radius: 7px;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.tpl-var:hover { border-color: var(--accent); }

.tpl-var__label {
    display: block;
    font-size: 0.8rem;
}

.tpl-var__key {
    display: block;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.7rem;
    color: var(--text-gray);
}

/* The one variable the model does not hold yet, marked rather than hidden: it
   is in the agreement, and pretending otherwise would lose it. */
.tpl-var--pending { opacity: 0.7; }
.tpl-var--pending .tpl-var__key::after { content: ' · not held yet'; }

/* A row of the deal terms: its label, the words around its variables, and the
   one act — taking it off the document. The words are shown and not editable,
   which is the whole difference between assembled and written. */
.tpl-term {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex: 1;
    min-width: 0;
    padding: 5px 4px;
}

.tpl-term__label {
    width: 150px;
    flex: none;
    font-size: 0.84rem;
    font-weight: 600;
}

.tpl-term__text {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.74rem;
    color: var(--text-gray);
}

/* 360px, and it keeps it: a flex item shrinks by default, and at 280 the buttons
   opposite were taking it down to 236. The bar does not wrap, so pinning the
   field cannot push it onto a second row — which is what happened the last time
   this was pinned. */
.tpl-bar { flex-wrap: nowrap; }

/* The bar's own field is named, because two names sit on this screen: the
   template's, here, and the document's below. `flex: none` so a long template
   name cannot squeeze the label onto two lines. */
.tpl-bar__label {
    flex: none;
    font-size: 13px;
    font-weight: 500;
    color: #6B7280;
}

/* `input.` carries the weight of `input[type="text"]`, which sets `width: 100%`
   on line 148 and beats a bare class — the same trap `.ac-search input` and
   `.modal-form input.ac-input` are both written to get out of. Three attempts
   at this field changed a rule that never applied: the field was sitting at the
   width its container gave it, and every number written here was ignored. */
.tpl-bar input.tpl-name-field {
    width: 400px;
    flex: none;
}

/* The line under the constructor's bar carries one sentence about versions and
   nothing else, so it is that tall. `table-note` is sized for a row count and a
   deploy pill and stays as it is everywhere else. */
/* The two facts about the paper itself, above the blocks: which mailbox its
   letters go from, and how long a document drawn from it is good for. Side by
   side, because they are one thought — how this template reaches somebody. */
.tpl-settings {
    display: grid;
    grid-template-columns: minmax(0, 300px) minmax(0, 300px) 220px;
    gap: 18px;
    margin-bottom: 18px;
}

/* The payment methods an invoice offers, read-only in the constructor (TASK-150). */
.tpl-methods {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 14px;
    margin-top: 10px;
}
.tpl-method {
    border: 1px solid var(--border-color, #e2e2e2);
    border-radius: 8px;
    padding: 10px 12px;
}
.tpl-method__label { font-weight: 600; margin-bottom: 6px; }
.tpl-method__fields { margin: 0; padding-left: 18px; font-size: 0.85rem; color: var(--text-gray); }

/* An invoice template has two more: the addresses the filled invoice goes to, and
   its Payment due days (TASK-150 revision 1). */
.tpl-settings--invoice {
    grid-template-columns: minmax(0, 220px) minmax(0, 220px) 160px minmax(0, 300px) 140px 170px;
}

/* A tax form template has one more, on the same row as the other three: the
   addresses the completed form goes to (TASK-154 revision 1). */
.tpl-settings--tax {
    grid-template-columns: minmax(0, 260px) minmax(0, 260px) 180px minmax(0, 360px);
}

/* The contract card's invoice row: the template, then its two terms. */
#modalBody .contract-invoice-row {
    display: grid;
    /* Invoice template | Payment due | Tax | Tax Form (TASK-154) */
    grid-template-columns: minmax(0, 1fr) 130px 110px 140px;
    gap: 14px;
    align-items: start;
}
#modalBody .contract-invoice-row .form-group { margin-bottom: 14px; }

@media (max-width: 900px) {
    .tpl-settings,
    .tpl-settings--invoice,
    .tpl-settings--tax,
    #modalBody .contract-invoice-row { grid-template-columns: minmax(0, 1fr); }
}

.tpl-note {
    margin-bottom: 0;
    min-height: 14px;
}
.tpl-label-field { max-width: 380px; }

/* ---------- a list whose order is the answer ---------- */

.ac-reorder__row {
    display: flex;
    align-items: center;
    gap: 4px;
    border-radius: 8px;
}

.ac-reorder__row--moving { opacity: 0.45; }

.ac-reorder__grip {
    display: inline-flex;
    padding: 4px 2px;
    background: none;
    border: 0;
    color: #A3ABB8;
    cursor: grab;
}

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

.ac-reorder__grip:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
    border-radius: 4px;
}

/* ---------- the reference lists ---------- */

.ref-list__title {
    margin: 14px 0 4px;
    font-size: 0.86rem;
}

.ref-list {
    border: 1px solid var(--border-gray);
    border-radius: 10px;
    padding: 6px 8px;
}

/* **One grid for the head, the rows and the line that adds one**, so the three
   columns line up down the list. Three flex rows with their own rules did not,
   and a field appearing under nothing looked like a field from nowhere.

   The code is as wide as its own placeholder and no wider; the two names take
   what is left, and the longer share goes to the one that has to hold
   `United States of America`. */
.ref-grid {
    display: grid;
    /* Two columns and the bin. The first is a code or a short name — `US`,
       `1 year` — and the second has to hold `United States of America`. */
    grid-template-columns: minmax(0, 0.6fr) minmax(0, 1fr) 34px;
    align-items: center;
    gap: 10px;
}

.ref-head {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-gray);
    padding-bottom: 4px;
}

.ref-value {
    padding: 3px 0;
}

/* A row switched off: still listed, plainly not on the paper. */
.tpl-term--off { opacity: 0.5; }
.tpl-term--off .tpl-term__text { text-decoration: line-through; }

/* The row's own text, with the variable panel beside it. Shorter than a
   section's, because a deal term is a line or two. */
.tpl-term__body { width: 100%; min-height: 80px; }

/* The row being written, picked out of the list the same way a block is. */
.tpl-term--on { background: rgba(9, 18, 39, 0.05); }

/* **Who fills this row in.** Read off the variables in it: a row naming
   `licensor.*` is answered by the other side when they open the document, one
   naming anything else comes off the deal, and one naming nothing is fixed
   prose. Worth saying on the row, because "assembled from the deal" is not true
   of all of them and the difference decides who has to be chased. */
.tpl-term__who {
    flex: none;
    font-size: 0.7rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 2px 7px;
    border-radius: 999px;
    background: rgba(9, 18, 39, 0.07);
    color: var(--text-gray);
}

.tpl-term__who--licensor {
    background: rgba(233, 168, 61, 0.22);
    color: #8A5A00;
}

.tpl-term__who--deal {
    background: rgba(95, 212, 155, 0.22);
    color: #10704A;
}

.tpl-term__open {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: baseline;
    gap: 10px;
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

/* The template stands between the terms and the books, and touched the table
   under it. Written against `.contract-form .form-group`, which zeroes every
   group's bottom margin inside this form — a bare class loses to it, which is
   why the gap did not appear the first time this was written. */
.contract-form .form-group.contract-template { margin-bottom: 20px; }

/* **The two names read the same way**: a word saying which name it is, the name
   itself in bold, and — for the one that can be changed — the way to change it
   at the right-hand end. Two boxes standing open mid-form asked a question that
   had already been answered. */
.contract-party__line {
    display: flex;
    align-items: baseline;
    gap: 8px;
    font-size: 0.9rem;
    padding: 2px 0;
}

.contract-party__what { color: var(--text-gray); }
.contract-party__who { font-weight: 700; }
.contract-party__edit { margin-left: auto; }

.ref-value__code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.78rem;
}


.ref-add {
    margin-top: 6px;
    padding-top: 8px;
    border-top: 1px solid var(--border-gray);
}

/* `input[type="text"]` sets `width: 100%` and outweighs a bare class, so these
   carry `input.` — the trap `.ac-search` and `.modal-form` are both written to
   get out of, and the one that cost four goes at the template name field. In a
   grid the column decides the width anyway; this is only here to stop the base
   rule fighting it. */
.ref-add input.ac-input { width: 100%; min-width: 0; }

/* What the document prints, beside what the form shows. A dash where the two
   are the same, which is every country and every currency today. */
.ref-value__doc { font-size: 0.84rem; color: var(--text-gray); }
.ref-value__label { font-size: 0.84rem; }

/* The constructor's fields are outside the modal, so the label and the note the
   modal form gives them are not there: a label on its own line, the control
   under it, the note under that. Without this the three ran together on one
   line and the sentence explaining a field read as part of the field. */
.tpl-editor .form-group {
    margin-bottom: 16px;
}

.tpl-editor .form-group > label {
    display: block;
    font-weight: 700;
    font-size: 0.86rem;
    margin-bottom: 4px;
}

.tpl-editor .form-group > .field-note,
.tpl-vars__note,
.tpl-editor > .field-note {
    display: block;
    font-size: 0.78rem;
    line-height: 1.5;
    color: var(--text-gray);
    margin-top: 4px;
}

/* ---------- The correspondence tab (TASK-120) ---------- */

/* Who the letters are with on the left, what can be done about it on the right.
   It wraps rather than clips: an address is long and the three buttons are not
   negotiable. */
.mail-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 14px;
    margin-bottom: 14px;
}

.mail-bar__who {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 8px;
    min-width: 0;
    font-size: 0.88rem;
}

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

.mail-bar__address {
    font-weight: 600;
    overflow-wrap: anywhere;
}

.mail-bar__acts {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* Where a refusal from Gmail lands. Above the two columns, because it is about
   the tab and not about one conversation. */
.mail-problem {
    margin-bottom: 12px;
    padding: 9px 12px;
    border-radius: 8px;
    background: rgba(220, 38, 38, 0.08);
    color: #B91C1C;
    font-size: 0.85rem;
}

/* **The threads on the left, the chosen one as a chat on the right.** The list
   is a fixed column: a column of subjects does not want to grow with the
   window, and a conversation does. */
.mail-grid {
    display: grid;
    grid-template-columns: 300px minmax(0, 1fr);
    gap: 16px;
    align-items: start;
}

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

.mail-threads__head {
    padding: 10px 14px;
    background: var(--series-bar);
    border-bottom: 1px solid var(--border-gray);
    font-size: 0.76rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-gray);
}

.mail-threads__none {
    padding: 14px;
    font-size: 0.85rem;
    line-height: 1.5;
    color: var(--text-gray);
}

/* One conversation in the list: its subject, and under it the date of the last
   letter and how many there are. */
.mail-thread {
    display: flex;
    flex-direction: column;
    gap: 4px;
    width: 100%;
    padding: 10px 14px;
    border: 0;
    border-bottom: 1px solid var(--border-gray);
    background: none;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.mail-thread:last-child {
    border-bottom: 0;
}

.mail-thread:hover {
    background: var(--light-gray);
}

/* The one being read. The bar down the edge survives the hover colour, which a
   background alone would not. */
.mail-thread--on {
    background: rgba(229, 50, 114, 0.06);
    box-shadow: inset 3px 0 0 var(--accent);
}

.mail-thread__top {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

/* One line, clipped. A subject that wraps to four lines turns the list into a
   page of paragraphs. */
.mail-thread__subject {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--primary);
}

/* Ours: a letter has arrived in this thread since somebody here last looked
   (`unread`, TASK-138/139). Gmail's own unread is untouched and unreadable — the
   service never asked for permission to alter a mailbox. */
.mail-thread__dot {
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--accent);
}

.mail-thread__meta {
    font-size: 0.78rem;
    color: var(--text-gray);
}

.mail-chat {
    display: flex;
    flex-direction: column;
    min-width: 0;
    border: 1px solid var(--border-gray);
    border-radius: var(--radius);
    overflow: hidden;
}

.mail-chat__empty {
    padding: 28px;
    font-size: 0.9rem;
    color: var(--text-gray);
}

.mail-chat__head {
    padding: 12px 16px;
    background: var(--series-bar);
    border-bottom: 1px solid var(--border-gray);
}

.mail-chat__subject {
    margin: 0;
    font-size: 1rem;
    overflow-wrap: anywhere;
}

.mail-chat__who {
    margin-top: 4px;
    font-size: 0.8rem;
    color: var(--text-gray);
    overflow-wrap: anywhere;
}

/* The letters scroll inside the chat rather than lengthening the page: the
   composer stays where the writer left it. */
.mail-chat__letters {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 16px;
    max-height: 520px;
    overflow-y: auto;
}

/* Ours on one side and theirs on the other, which is the whole reason this is a
   chat and not a table: who spoke is read from where the letter sits before a
   word of it is read. */
.mail-letter {
    display: flex;
    flex-direction: column;
    gap: 4px;
    max-width: 78%;
}

.mail-letter--in {
    align-self: flex-start;
    align-items: flex-start;
}

.mail-letter--out {
    align-self: flex-end;
    align-items: flex-end;
}

.mail-letter__bubble {
    min-width: 0;
    max-width: 100%;
    padding: 10px 14px;
    border: 1px solid var(--border-gray);
    border-radius: 14px;
    background: var(--white);
}

.mail-letter--out .mail-letter__bubble {
    background: rgba(135, 183, 255, 0.14);
    border-color: rgba(51, 84, 149, 0.22);
}

/* A letter keeps its own line breaks, and a pasted link breaks anywhere rather
   than scrolling the page sideways. */
.mail-letter__body {
    font-size: 0.88rem;
    line-height: 1.55;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.mail-letter__files {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 8px;
}

.mail-letter__meta {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 8px;
    font-size: 0.76rem;
    color: var(--text-gray);
}

.mail-letter__who {
    font-weight: 600;
    color: var(--primary);
}

/* **`gmail direct`.** Not a name, so it does not read as one: a letter sent
   from Gmail rather than from here cannot say which of us wrote it, and the
   mark is how somebody learns to answer here instead. */
.mail-letter__who--direct {
    padding: 1px 6px;
    border: 1px dashed var(--border-gray);
    border-radius: 6px;
    color: var(--text-gray);
    font-size: 0.72rem;
    letter-spacing: 0.02em;
}

/* The reply is written at the foot of the thread it belongs to. */
.mail-composer {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px 16px;
    border-top: 1px solid var(--border-gray);
    background: var(--light-gray);
}

.mail-composer__error {
    padding: 8px 12px;
    border-radius: 8px;
    background: rgba(220, 38, 38, 0.08);
    color: #B91C1C;
    font-size: 0.82rem;
}

.mail-composer__text {
    min-height: 88px;
}

.mail-composer__row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px;
}

.mail-composer .field-note {
    margin: 0;
    font-size: 0.78rem;
    color: var(--text-gray);
}

/* What Gmail has and we do not, with ticks. One thread a line, and the subject
   is allowed to wrap: this is a list somebody is choosing from, not a column. */
.mail-offer {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 12px;
    max-height: 340px;
    overflow-y: auto;
}

.mail-offer .ac-check {
    display: flex;
    align-items: flex-start;
    gap: 9px;
}

.mail-offer .ac-check__box {
    margin-top: 2px;
}

.mail-offer .ac-check__label {
    min-width: 0;
    overflow-wrap: anywhere;
}

/* Below a laptop the two columns would each be too narrow to read a subject in,
   so they stack: the list first, the conversation under it. The list gets a
   scroll of its own so twenty subjects cannot push the conversation off the
   bottom of the screen. */
@media (max-width: 1100px) {
    .mail-grid {
        grid-template-columns: minmax(0, 1fr);
    }

    .mail-threads {
        max-height: 260px;
        overflow-y: auto;
    }

    .mail-letter {
        max-width: 92%;
    }
}

/* ===========================================================================
   TASK-129 and TASK-130 — the mail settings page and the letters on a card.
   Appended as one block: another agent is editing this file, and a block at
   the end touches nothing above it.
   =========================================================================== */

/* A block that folds. Built for Google API Settings, whose four rows are about
   credentials: keys are entered once and read never, so the panel that holds
   them should not sit open on a screen for the rest of the day. */
.fold {
    border: 1px solid var(--border-gray);
    border-radius: var(--radius);
    background: var(--white);
    overflow: hidden;
}

/* An image in the signature editor never runs past the editor. The image's own
   `max-width` — the one the constructor asks for — is inline and wins over this;
   an image given none is only kept inside the box here, not in the letter. */
.sig-editor img {
    max-width: 100%;
}

.fold__head {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    padding: 12px 16px;
    border: 0;
    background: transparent;
    font: inherit;
    font-weight: 600;
    color: var(--primary);
    text-align: left;
    cursor: pointer;
}

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

/* The triangle turns as it opens, so the state is read from the shape rather
   than remembered. */
/* **Drawn, not typed** (owner, 2026-09-11). It was the glyph `▸`, and a glyph's
   ink does not sit in the middle of its line: small, nobody noticed; at twice
   the size it rode off the title's centre and moved again as it turned. A
   triangle made of borders is its own box — flex centres it exactly, and it
   turns about its own middle. 10px tall — 14px was a size and a half too big
   (owner, 2026-09-11). */
.fold__caret {
    flex: none;
    width: 0;
    height: 0;
    border-left: 7px solid var(--text-gray);
    border-top: 5px solid transparent;
    border-bottom: 5px solid transparent;
    transform: rotate(90deg);
    transition: transform 0.18s ease;
}

.fold--shut .fold__caret {
    transform: rotate(0deg);
}

.fold__body {
    padding: 0 16px 16px;
}

/* The two blocks on the Mail page were flush, so the setup panel and the table
   read as one thing with a line through it. */
#mailSetup {
    margin-bottom: 20px;
}

/* ---------- the signature constructor ---------- */

.sig-tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin: 12px 0 0;
    padding: 8px;
    border: 1px solid var(--border-gray);
    border-bottom: 0;
    border-radius: var(--radius) var(--radius) 0 0;
    background: var(--light-gray);
}

.sig-tool {
    min-width: 34px;
}

.sig-tools__size {
    width: auto;
    height: 32px;
    padding: 0 8px;
    font-size: 0.82rem;
}

.sig-tools__colour {
    width: 34px;
    height: 32px;
    padding: 2px;
    border: 1px solid var(--border-gray);
    border-radius: 8px;
    background: var(--white);
    cursor: pointer;
}

/* A signature is short. The box is tall enough for the block the mailbox needs
   today and scrolls rather than growing, so the buttons under it stay put. */
.sig-editor {
    min-height: 160px;
    max-height: 320px;
    overflow-y: auto;
    padding: 12px 14px;
    border: 1px solid var(--border-gray);
    border-radius: 0 0 var(--radius) var(--radius);
    background: var(--white);
    font-size: 0.9rem;
    line-height: 1.55;
    overflow-wrap: anywhere;
}

.sig-editor:focus {
    outline: 2px solid var(--blue-light);
    outline-offset: -2px;
}

.sig-editor img {
    max-width: 100%;
    height: auto;
}

/* ---------- a letter that reads like a message ---------- */

/* The formatted half of a letter. `pre-wrap` is right for words typed into a
   box and wrong for markup, whose paragraphs carry their own spacing — so the
   rich body sets it back and gives the block elements the margins the letter
   assumed they had. */
.mail-letter__body--rich {
    white-space: normal;
}

.mail-letter__body--rich p,
.mail-letter__body--rich ul,
.mail-letter__body--rich ol,
.mail-letter__body--rich blockquote,
.mail-letter__body--rich table {
    margin: 0 0 8px;
}

.mail-letter__body--rich > :last-child {
    margin-bottom: 0;
}

.mail-letter__body--rich ul,
.mail-letter__body--rich ol {
    padding-left: 20px;
}

.mail-letter__body--rich blockquote {
    padding-left: 10px;
    border-left: 2px solid var(--border-gray);
    color: var(--text-gray);
}

.mail-letter__body--rich a {
    color: var(--blue-mid);
}

/* A letter cannot decide how wide the conversation is. Its own table is allowed
   to scroll inside the bubble rather than to push the page sideways. */
.mail-letter__body--rich table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
    border-collapse: collapse;
}

.mail-letter__body--rich td,
.mail-letter__body--rich th {
    padding: 3px 7px;
}

.mail-letter__image {
    max-width: 100%;
    height: auto;
    border-radius: 8px;
}

/* Where a picture would be. It says what it is and, in its title, where it
   would have come from — so the decision to fetch it is made knowing that. */
.mail-img {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 1px 7px;
    border: 1px dashed var(--border-gray);
    border-radius: 6px;
    color: var(--text-gray);
    font-size: 0.74rem;
    vertical-align: middle;
}

.mail-img__name {
    max-width: 220px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The one control that fetches from a stranger's server, and it is pressed per
   letter. Quiet on purpose: it is an offer, not an instruction. */
.mail-images {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-top: 8px;
    padding: 4px 10px;
    border: 1px solid var(--border-gray);
    border-radius: 8px;
    background: var(--light-gray);
    color: var(--text-gray);
    font: inherit;
    font-size: 0.76rem;
    cursor: pointer;
}

.mail-images:hover {
    border-color: var(--blue-mid);
    color: var(--primary);
}

/* The setup panel keeps its own spacing inside the fold and loses its frame:
   two boxes drawn one inside the other read as a mistake rather than as a
   section. */
.fold__body > .panel {
    padding: 0;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    background: transparent;
}

/* ---------- TASK-126: the one button on a contract ---------- */

/* **The left menu's colour**, because this is the card's principal action and
   the rail is where this admin puts the thing it is. Not `btn-primary`: the pink
   is Save and Create, and an agreement leaving the building is not a save. */
.btn-send {
    background-color: var(--primary);
    color: var(--white);
    border-color: var(--primary);
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

.btn-send:hover:not(.btn-send--off) {
    background-color: var(--navy-deep);
    border-color: var(--navy-deep);
    transform: translateY(-2px);
    box-shadow: 0 8px 24px rgba(9, 18, 39, 0.32);
}

/* Not `disabled`, and it must not look clickable-but-broken either: it is a
   button that still answers, and what it answers is what is missing. Faded to
   say "not yet", and it keeps the pointer because pressing it is the way to ask.
   `.modal__foot .btn-primary` lifts on hover; this one deliberately does not. */
.btn-send--off {
    opacity: 0.45;
    box-shadow: none;
    transform: none;
}

.modal__foot .btn-send:hover:not(.btn-send--off) {
    transform: none;
    box-shadow: 0 4px 14px rgba(9, 18, 39, 0.3);
}

/* Inside the button, not beside it: the wait belongs to the act, and a spinner
   somewhere else on the card leaves somebody wondering what is spinning. */
.btn-send__spin {
    display: inline-block;
    width: 14px;
    height: 14px;
    border: 2px solid rgba(255, 255, 255, 0.35);
    border-top-color: var(--white);
    border-radius: 50%;
    animation: admin-spin 0.7s linear infinite;
}

/* The same trap as the note above: a `display` of our own outlives [hidden]. */
.btn-send__spin[hidden] {
    display: none;
}

@keyframes admin-spin {
    to { transform: rotate(360deg); }
}

/* ---------- TASK-125: the ten readings of a document ---------- */

/* Read as three groups rather than ten colours. Grey is a document nobody has
   answered yet or one that is over; blue is somebody working on it; green is
   signed, which is the only one that means the deal is closed; and the two
   endings somebody chose — declined and revoked — carry the warning colours,
   because a paper that was refused is a fact somebody has to act on. */
/* The five rungs the Licensor climbs (TASK-136) are also the contract's own
   statuses while its agreement is out, so each colour is written once for both
   families: the same moment cannot be blue on the Documents table and grey on
   the contract. `signed by author` takes the amber `awaiting us` wore — it is
   the same moment, the next move ours — and `awaiting licensor` keeps it for
   the documents sent under the old two-party shape. */
.badge--document-draft             { background: #F1F3F5; color: #4B5563; }
.badge--document-sent,
.badge--contract-sent              { background: rgba(135, 183, 255, 0.22); color: #2B5FA8; }
.badge--document-viewed,
.badge--contract-viewed            { background: rgba(135, 183, 255, 0.28); color: #24518F; }
.badge--document-filled,
.badge--contract-filled            { background: rgba(86, 200, 232, 0.20); color: #0F6C88; }
.badge--document-awaiting-licensor,
.badge--contract-awaiting-licensor { background: rgba(255, 176, 32, 0.18); color: #9A6200; }
.badge--document-signed-by-author,
.badge--contract-signed-by-author  { background: rgba(255, 176, 32, 0.18); color: #9A6200; }
.badge--document-signed,
.badge--contract-signed            { background: rgba(95, 212, 155, 0.22); color: #10704A; }
/* The invoice's rungs on the contract (TASK-151 revision 2), in the colours
   of the paper's own: sent and viewed blue, filled teal. A lapsed or revoked
   latest invoice takes the amber of a move that is ours to make — another
   invoice has to be sent. */
.badge--contract-invoice-sent      { background: rgba(135, 183, 255, 0.22); color: #2B5FA8; }
.badge--contract-invoice-viewed    { background: rgba(135, 183, 255, 0.28); color: #24518F; }
.badge--contract-invoice-filled    { background: rgba(86, 200, 232, 0.20); color: #0F6C88; }
.badge--contract-invoice-lapsed,
.badge--contract-invoice-revoked   { background: rgba(255, 176, 32, 0.18); color: #9A6200; }
/* The tax form's rungs (TASK-154), the invoice's colours one rung up. Completed
   keeps the `tax form done` class a hand tick always wore. */
.badge--document-tax-form-sent,
.badge--contract-tax-form-sent     { background: rgba(135, 183, 255, 0.22); color: #2B5FA8; }
.badge--document-tax-form-viewed,
.badge--contract-tax-form-viewed   { background: rgba(135, 183, 255, 0.28); color: #24518F; }
.badge--contract-tax-form-lapsed,
.badge--contract-tax-form-revoked  { background: rgba(255, 176, 32, 0.18); color: #9A6200; }
/* Awaiting Payment (TASK-157 revisions 1 and 2), between Tax form signed (teal)
   and Invoice Paid (blue): light lilac, which no other status wears (owner,
   2026-09-16: not green). The same on the contract, the card's invoice line, the
   Documents table and the invoice's page. #5B3FA8 on the tint reads at about 7:1.
   An invoice's Invoice Paid wears the contract's Invoice Paid. */
.badge--document-awaiting-payment,
.badge--contract-awaiting-payment  { background: rgba(167, 139, 250, 0.22); color: #5B3FA8; }
.badge--document-paid              { background: rgba(135, 183, 255, 0.22); color: #2B5FA8; }
/* Lapsed, and it read Expired until TASK-134: the word now means the day the
   agreement ends, and a badge cannot go on saying the signing link ran out with
   it. The status key was renamed with the word, so the class follows. */
.badge--document-lapsed            { background: #F1F3F5; color: #6B7280; }
.badge--document-declined          { background: rgba(255, 122, 89, 0.20); color: #9A3412; }
.badge--document-revoked         { background: #F1F3F5; color: #6B7280; }
/* Superseded (TASK-170): a signed tax form an updated one took the place of.
   Over, and not a failure: the grey of the other papers that are done with. */
.badge--document-superseded        { background: #F1F3F5; color: #4B5563; }

/* The dialog of *Request an updated form* (TASK-170): the author, the address,
   the kind. */
.doc-ask__row { margin-top: 14px; }
.doc-ask__row .ac-input { width: 100%; }
.doc-ask__kind { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin-top: 6px; }
.doc-ask__kind label { display: inline; margin: 0; text-transform: none; letter-spacing: 0; font-size: 0.95rem;
    font-weight: 500; color: inherit; }
.doc-ask__kind .ac-dialog__note { flex-basis: 100%; margin: 0 0 0 22px; }

/* ---------- TASK-136: the contract card has three states ---------- */

/* **A fifth wider than every other modal, for the contract alone.** Five terms
   in one row and a books table under them were the cramped ones; a form view or
   a confirm is no better for the width. Keyed to what the body holds rather
   than to a class the contract adds, because every other modal clears only
   `modal--narrow` when it opens — a class of ours would stay on and widen the
   next one. `:has` costs nothing to undo: the width leaves with the form. */
.modal:has(.contract-form),
.modal:has(.contract-status) {
    max-width: 864px;
}

/* **The status section under the title bar**: what the agreement is, where it
   has got to, the two ways to open it, and one line of note. A band across the
   top of the body, pulled out to its edges, so it reads as part of the frame
   rather than as the first field of the form. */
.contract-status {
    margin: -24px -24px 20px;
    padding: 12px 24px;
    background: var(--light-gray);
    border-bottom: 1px solid var(--border-gray);
}

.contract-status__line {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
}

.contract-status__label {
    font-weight: 700;
    color: var(--primary);
}

/* Quiet links, not buttons: the principal act is Send in the footer, and these
   two are ways of looking. The word beside each icon because a bare arrow does
   not say which paper it gives. */
.contract-status__paper {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 8px;
    border-radius: 6px;
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--text-gray);
    text-decoration: none;
    transition: var(--transition);
}

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

.contract-status__note {
    margin-top: 4px;
    font-size: 0.82rem;
    color: var(--text-gray);
}

/* **A term read, not chosen**: the answer where the dropdown was, the height of
   the dropdown so the row keeps its line, several answers on one line clipped
   the way a table cell clips several books. Block and not flex, because an
   ellipsis only happens in a block. */
.contract-value {
    height: var(--ac-control-h);
    line-height: var(--ac-control-h);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--primary);
}

/* Set only where there are several answers, and those are the ones that clip. */
.contract-value[title] {
    cursor: help;
}

/* Cut, it looks the way a cut cell of any table looks: a dashed underline that
   turns accent on hover, and a click opens the whole list (owner, 2026-09-11 —
   Book Forms' Books column is the model). */
.contract-value.clipped .cell-text {
    cursor: pointer;
    border-bottom: 1px dashed var(--border-gray);
}

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

/* The card's own list hint sits above the modal: the backdrop is at 100, the
   tables' hints at 60, and a list opened from inside the card has to be read. */
.hint-pop--modal {
    z-index: 110;
}

.contract-value--blank {
    font-weight: 400;
    font-style: italic;
    color: var(--text-gray);
}

/* **The books an agreement names**, in the shelf's table without the two
   columns only choosing needs — the tick and the last price. Written against
   `.contract-books--agreed` so they outweigh the shelf's own grid, and below it
   in the file so the variants resolve by order: with the Published column
   after without, and a series with no row icons after one with. */
.contract-books--agreed .contract-book {
    grid-template-columns: minmax(0, 1fr) 120px 104px 68px;
}

.contract-books--agreed .contract-series--noactions .contract-book {
    grid-template-columns: minmax(0, 1fr) 120px 104px;
}

.contract-books--published .contract-book {
    grid-template-columns: minmax(0, 1fr) 120px 104px 104px 68px;
}

.contract-books--published .contract-series--noactions .contract-book {
    grid-template-columns: minmax(0, 1fr) 120px 104px 104px;
}

.contract-book__figure,
.contract-book__day {
    font-size: 0.85rem;
    color: var(--primary);
}

.contract-book__day--none {
    color: var(--text-gray);
    font-style: italic;
}

/* A tick that can no longer be changed still says whether it is ticked. The
   disabled rule above paints the box pale grey, which left the white tick
   invisible on a checked one — a paid invoice read as unpaid. Muted, not pink:
   the accent is for what can still be acted on. */
.ac-check input:checked:disabled + .ac-check__box {
    background: var(--text-gray);
    border-color: var(--text-gray);
}

/* ---------- the authors list says what is new (TASK-138, TASK-140) ---------- */

/* **Messages** is eight letters, a sort arrow and a filter icon over a `5 | 43`.
   The count pair's 104px holds the numbers and not the name, and a header
   spilling over Status is the mistake `col-w-date2` was made to end. */
.col-w-messages { width: 132px; }

/* The new half of `new | total`. Grey at nought, so the red is only ever on a
   row somebody should open; bold like the live half of a count pair, which it
   sits in the shape of. */
.msg-pair__new {
    font-weight: 700;
    color: var(--text-gray);
}

.msg-pair__new--some {
    color: #DC2626;
}

/* The four words of the ladder that had no badge. Expires soon and expired take
   the Contracts table's colours for the same words, so an author and their
   contract read alike; new is the quiet grey of a contract just created.
   Negotiating, deal won, deal lost and active keep the badges they had. */
.badge--new          { background: #F1F3F5; color: #4B5563; }
.badge--outreached   { background: rgba(135, 183, 255, 0.22); color: #2B5FA8; }
.badge--expires_soon { background: rgba(255, 176, 32, 0.18); color: #9A6200; }
.badge--expired      { background: #F1F3F5; color: #6B7280; }

/* Deal lost under the name: why, who, when, and the one act beside it. Standing
   it carries the badge's colour; overtaken by a later reply or signature it is
   history, and reads as quietly as the rest of the header's notes. */
.author-lost {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px 12px;
    margin-top: 8px;
    font-size: 0.85rem;
    color: var(--accent-dark);
}

.author-lost--past {
    color: var(--text-gray);
}

.author-lost__text {
    min-width: 0;
    overflow-wrap: anywhere;
}

/* ---------- the authors board (TASK-141) ---------- */

/* A column per status, side by side, filling what the table would. The board
   scrolls sideways inside itself when eight columns do not fit — the page never
   does — and each column scrolls down inside itself, so a long Expired cannot
   push the other columns' cards off the screen. */
.author-board {
    flex: 1;
    min-height: 0;
    display: flex;
    gap: 12px;
    overflow-x: auto;
    overflow-y: hidden;
    padding-bottom: 4px;
}

/* Wide enough for the longest badge beside its count and a pair per line, so
   all eight fit side by side on a 1920 screen; wider when fewer columns are on,
   and not so wide that two columns become two tables. */
.board-col {
    flex: 1 0 184px;
    max-width: 360px;
    min-height: 0;
    display: flex;
    flex-direction: column;
    background: #F1F3F5;
    border: 1px solid var(--border-gray);
    border-radius: var(--radius);
    transition: opacity 0.15s ease, background-color 0.15s ease;
}

/* The head on white, because the badges are drawn for white: New and Expired
   are the column's own grey and vanished into it. On white each reads exactly
   as it does in the table's Status cell. */
.board-col__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 10px 12px;
    margin-bottom: 8px;
    background: var(--white);
    border-bottom: 1px solid var(--border-gray);
    border-radius: var(--radius) var(--radius) 0 0;
}

.board-col__count {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-gray);
    font-variant-numeric: tabular-nums;
}

.board-col__body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 0 8px 8px;
}

.board-col__empty {
    padding: 6px 4px;
    font-size: 0.8rem;
    color: var(--text-gray);
}

/* A card is a row of the table: white on the column's grey, the ghost button's
   border, and the ghost button's darker edge under the cursor. */
.board-card {
    flex: none;
    background: var(--white);
    border: 1px solid var(--border-gray);
    border-radius: 8px;
    padding: 10px 12px;
    box-shadow: 0 1px 2px rgba(9, 18, 39, 0.05);
    cursor: pointer;
    transition: border-color 0.15s ease;
}

.board-card:hover {
    border-color: var(--text-gray);
}

.board-card:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.board-card__name {
    font-size: 0.9rem;
    font-weight: 600;
    line-height: 1.35;
    overflow-wrap: anywhere;
}

/* The two pairs at the size the table's cells are, labelled because a card has
   no column header to say which is which. */
.board-card__figures {
    display: flex;
    flex-wrap: wrap;
    gap: 2px 14px;
    margin-top: 6px;
    font-size: 0.85rem;
}

.board-card__figure {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
}

.board-card__label {
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-gray);
}

/* The card left behind while its picture is carried. */
.board-card--dragging {
    opacity: 0.4;
}

/* While a card is carried: Deal lost outlined in its badge's colour as the one
   place it can go, and every other column faded as a place it cannot. Said
   before the drop rather than after it. */
.board-col--refuses {
    opacity: 0.5;
}

.board-col--target {
    outline: 2px dashed var(--accent);
    outline-offset: -2px;
}

.board-col--over {
    background: rgba(229, 50, 114, 0.1);
}

.board-hint {
    color: var(--accent-dark);
}

/* The unread count on the Messages heading (owner, 2026-09-11): a red disc with
   the number in white, raised to the word's top right like a notification mark.
   The cells' red, so the disc and the counts under it read as one thing. It
   stretches into a pill past 9 and stops at 99+. */
.th-inner .th-count {
    display: inline-block;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    margin-left: 2px;
    border-radius: 999px;
    background: #DC2626;
    color: #FFFFFF;
    font-size: 10px;
    font-weight: 700;
    line-height: 16px;
    text-align: center;
    box-sizing: border-box;
    position: relative;
    top: -7px;
}

/* ===========================================================================
   TASK-149, revision 1 — a template's letters: a second block list under the
   agreement's, behind a rule, and a letter block open in the middle panel.
   =========================================================================== */

.tpl-letters-rule {
    margin: 18px 0 12px;
    border: 0;
    border-top: 1px solid var(--border-gray);
}

.tpl-letters-head {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin: 0 4px 8px;
}

.tpl-letters-head__title {
    font-size: 0.9rem;
    font-weight: 700;
}

.tpl-letters-head__note {
    font-size: 0.74rem;
    color: var(--text-gray);
}

.tpl-letters-signature { margin-bottom: 4px; }

.tpl-add-letter { margin-top: 8px; }

.tpl-letter-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 12px;
    margin: 12px 0;
}

.tpl-letter-options {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 18px;
    margin: 12px 0 10px;
}

.tpl-letter__preview { margin-top: 12px; }

.tpl-letter__subject {
    margin: 6px 0 8px;
    font-size: 0.9rem;
    overflow-wrap: anywhere;
}

.tpl-letter__frame {
    display: block;
    width: 100%;
    height: 560px;
    border: 1px solid var(--border-gray);
    border-radius: 10px;
    background: #fff;
}

/* The one-line notes beside the ticks read as notes, not as body text: outside a
   form group `.field-note` has no size of its own. */
.tpl-letter-options .field-note {
    font-size: 0.78rem;
    color: var(--text-gray);
}

/* TASK-152 — an archived contract: filed away, and plainly not a stage of the
   ladder. The same quiet grey as Expired, with a dashed edge so the two cannot
   be read for one another. */
.badge--contract-archived {
    background: #F7F7F8;
    color: #6B7280;
    box-shadow: inset 0 0 0 1px #D1D5DB;
}

/* Our signatory's signature on the template's Signatures block (TASK-153). */
.tpl-our-signature__held { margin: 4px 0 8px; max-width: 420px; }
.tpl-our-signature__held .sigpad__typed { font-size: 34px; min-height: 64px; }
.tpl-our-signature__actions { display: flex; gap: 8px; flex-wrap: wrap; margin: 6px 0 8px; }
.tpl-our-signature__editor { max-width: 560px; margin: 6px 0 8px; }
.tpl-our-signature__problem { color: #b42318; font-size: 13px; margin: 4px 0 0; }

/* ---------- Book by book (TASK-180) ---------- */

/* The form's progress, under its status in the Book Forms list: "2 of 6
   uploaded", then "1 deployed". Two short lines rather than one long one — the
   Status column is 128px. */
.book-progress {
    display: flex;
    flex-direction: column;
    margin-top: 4px;
    font-size: 0.72rem;
    line-height: 1.3;
    color: var(--text-gray);
    white-space: nowrap;
}

.book-progress__line--deployed { color: #4338CA; }
.book-progress__line--published { color: #10704A; }

/* The same sentence at the top of the form view, with the form's rocket at
   the end of the line (revision 2) and its reason under it when blocked. */
.book-progress-summary {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin: 0 0 12px;
    font-size: 0.92rem;
    font-weight: 600;
}

.book-progress-summary__deploy { margin-left: auto; }
.book-progress-summary__said { margin: -4px 0 12px; }

/* **A book's one status** (TASK-180 revision 1): four values, one badge each,
   on every screen that shows a book — Book Forms, the contract card and table,
   an author's books, the shelf — and the same four colours on the form pages
   (styles.css). Drawn by `AdminControls.bookStatus`.
     Pending    grey    #F1F3F5 on #6B7280
     Uploaded   blue    rgba(135, 183, 255, 0.22) on #2B5FA8
     Deployed   indigo  rgba(99, 102, 241, 0.16) on #4338CA
     Published  green   rgba(95, 212, 155, 0.22) on #10704A
   The contract's rungs read in the same colours: Books Uploaded blue, Books
   Deployed indigo, Active green. */
.badge--book-pending   { background: #F1F3F5; color: #6B7280; }
.badge--book-uploaded  { background: rgba(135, 183, 255, 0.22); color: #2B5FA8; }
.badge--book-deployed  { background: rgba(99, 102, 241, 0.16); color: #4338CA; }
.badge--book-published { background: rgba(95, 212, 155, 0.22); color: #10704A; }

/* Beside a title in a line of text (the contract card, the shelf, an author's
   books, the Contracts table's Books cell): smaller than a status column's. */
.badge--book {
    min-width: 0;
    margin-left: 8px;
    padding: 1px 8px;
    font-size: 0.68rem;
    vertical-align: middle;
}

.book-state .badge--book,
.book-card__head .badge--book { margin-left: 0; font-size: 0.75rem; padding: 3px 10px; }

.contract-bookline { white-space: nowrap; }
.contract-bookline + .contract-bookline::before { content: ', '; }
.contract-bookline .badge--book { margin-left: 4px; }

.ac-dialog__books { list-style: none; padding: 0; margin: 4px 0 0; }
.ac-dialog__books li { padding: 3px 0; }

/* The head of a book's card: title, its pills, and its rocket at the far end. */
.book-card__head {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 12px;
}

.form-view .book-card .book-card__head h4,
.form-edit .book-card .book-card__head h4 {
    margin: 0;
}

.book-state {
    display: inline-flex;
    gap: 6px;
    flex-wrap: wrap;
}

.book-card__deploy {
    margin-left: auto;
}

/* What the rocket or a failed deploy says when clicked, under the head. */
.book-card__said {
    margin: -4px 0 12px;
}

.book-card__said .note {
    margin-top: 0;
}

.book-card__said .error-text {
    margin-top: 6px;
}

.book-card__when {
    margin: -6px 0 10px;
    font-size: 0.8rem;
    color: var(--text-gray);
}

.book-card__needed {
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px dashed var(--border-gray);
}

.book-card__needed h5 {
    font-size: 0.85rem;
    margin: 0 0 4px;
}

.book-card__needed .muted {
    font-size: 0.82rem;
    margin: 0;
}

.form-edit .book-card > .field-note {
    display: block;
    margin: -6px 0 12px;
}

/* The Contracts table's form icon, with how many of the form's books are in:
   a count tucked on the icon's corner, so the icon column keeps its width. */
.icon-btn--counted {
    position: relative;
}

.icon-btn__count {
    position: absolute;
    right: -6px;
    bottom: -4px;
    padding: 0 3px;
    border-radius: 6px;
    background: var(--white, #fff);
    border: 1px solid var(--border-gray);
    font-size: 0.6rem;
    font-weight: 600;
    line-height: 1.3;
    color: var(--text-gray);
    pointer-events: none;
}
