* {
    box-sizing: border-box;
}

html {
    height: 100%;
}

body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 0;
    height: 100%;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* Buttons
   One shared system: the bare <button> element is the neutral/secondary
   style, with `primary` (filled accent), `ghost` (quiet) and `danger`
   (destructive outline) variants. Colors adapt to dark mode via
   light-dark(), which requires color-scheme on :root. */
:root {
    color-scheme: light dark;
    --btn-accent: light-dark(#0f766e, #2dd4bf);
    --btn-accent-hover: light-dark(#115e59, #5eead4);
    --btn-accent-text: light-dark(#ffffff, #03332d);
    --btn-danger: light-dark(#b42318, #f97066);
    --btn-surface: light-dark(#ffffff, #25292e);
    --btn-surface-hover: light-dark(#f1f4f6, #2f353c);
    --btn-border: light-dark(#c4ccd4, #454d56);
    --btn-text: light-dark(#1f2933, #e4e6e8);
    /* Site header fill: the brand teal in light mode (matches --btn-accent and the page's
       <meta name="theme-color">), collapsing to the page background in dark mode. */
    --header-bg: light-dark(teal, #1a1a1a);
}

button {
    appearance: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 9px 16px;
    border: 1px solid var(--btn-border);
    border-radius: 8px;
    background: var(--btn-surface);
    color: var(--btn-text);
    font: inherit;
    font-weight: 600;
    line-height: 1.2;
    cursor: pointer;
    user-select: none;
    touch-action: manipulation;
    transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}

button:hover {
    background: var(--btn-surface-hover);
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
    transform: translateY(-2px);
}

button:active {
    transform: translateY(1px);
    box-shadow: none;
}

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

button:disabled {
    opacity: 0.55;
    cursor: not-allowed;
    transform: none;
}

button.primary {
    background: var(--btn-accent);
    border-color: transparent;
    color: var(--btn-accent-text);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
}

button.primary:hover {
    background: var(--btn-accent-hover);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.22);
}

button.ghost {
    background: transparent;
    border-color: light-dark(#bbb, #555);
    color: inherit;
}

button.ghost:hover {
    background: var(--btn-surface-hover);
}

button.danger {
    background: transparent;
    border-color: currentColor;
    color: var(--btn-danger);
}

button.danger:hover {
    background: light-dark(rgba(180, 35, 24, 0.08), rgba(249, 112, 102, 0.14));
}

@media (prefers-reduced-motion: reduce) {
    button {
        transition: none;
    }

    button:active {
        transform: none;
    }
}

.site-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    background-color: var(--header-bg);
    padding: 20px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    flex-shrink: 0;
}

.banner-title {
    display: block;
    font-size: 2.5rem;
    font-weight: 800;
    color: #fff;
    text-decoration: none;
    font-family: 'Trebuchet MS', sans-serif;
    letter-spacing: 2px;
    text-transform: uppercase;
}

.banner-title:hover {
    color: #bdc3c7;
    transition: color 0.3s ease;
}

/* Icon-only variant of the logo (template.js), hidden until the ≤480px swap below — `fill:
   currentColor` picks up .banner-title's own color, so :hover recolours it along with the text
   for free. */
.banner-title-mark {
    display: none;
    width: 22px;
    height: auto;
    fill: currentColor;
    vertical-align: middle;
}

.search-wrapper {
    position: relative;
    flex: 1;
    max-width: 360px;
    margin: 0 20px;
}

.site-search {
    width: 100%;
    padding: 7px 14px;
    border: none;
    border-radius: 20px;
    background: rgba(255, 255, 255, 0.25);
    color: #fff;
}

.site-search::placeholder {
    color: rgba(255, 255, 255, 0.7);
}

.site-search:focus {
    outline: none;
    background: rgba(255, 255, 255, 0.92);
    color: #222;
}

.site-search:focus::placeholder {
    color: rgba(0, 0, 0, 0.35);
}

@media (prefers-color-scheme: dark) {
    .site-search:focus {
        background: rgba(30, 30, 30, 0.92);
        color: #eee;
    }

    .site-search:focus::placeholder {
        color: rgba(255, 255, 255, 0.35);
    }
}

/* Two independently-scrolling columns (search-col-main, search-col-recent) so "Recently viewed"
   has its own always-visible pane instead of being pushed below a long menu. Side by side once
   there's width to spare (700px+); stacked back into one column on narrow viewports. */
.search-results {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    background: light-dark(#fff, #25292e);
    border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
    z-index: 100;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    overflow: hidden;
}

/* The explicit `display: flex` above is an author rule, so it now beats the UA stylesheet's
   `[hidden] { display: none }` at equal specificity — redeclare it here so hiding still works.
   Without this the closed box collapses to its empty columns' padding instead of disappearing:
   a thin white bar under the input. */
.search-results[hidden] {
    display: none;
}

.search-col {
    list-style: none;
    margin: 0;
    padding: 4px 0;
    max-height: 300px;
    overflow-y: auto;
    min-width: 0;
}

.search-col-recent {
    border-top: 1px solid light-dark(#eee, #3a3f45);
}

@media (min-width: 700px) {
    .search-results {
        flex-direction: row;
        right: auto;
        width: 460px;
        max-width: calc(100vw - 24px);
    }

    .search-col-main {
        flex: 1 1 auto;
    }

    .search-col-recent {
        flex: 0 0 42%;
        border-top: none;
        border-left: 1px solid light-dark(#eee, #3a3f45);
    }
}

.search-results li a {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 8px 14px;
    text-decoration: none;
    color: light-dark(#222, #e0e0e0);
}

/* The recent-viewed column runs narrower than the main one — truncate rather than wrap. */
.search-result-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 1 1 auto;
    min-width: 0;
}

.search-results li a:hover {
    background: light-dark(#e6edf6, #333a42);
}

.search-results li.active a {
    background: light-dark(#c2d6f0, #3a4a5c);
}

.search-result-type {
    font-size: 0.7rem;
    color: #888;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-left: 8px;
    flex-shrink: 0;
}

.search-results .menu-header {
    padding: 6px 14px 2px;
    font-size: 0.7rem;
    font-weight: 600;
    color: #888;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    cursor: default;
}

.search-results li.menu-child a {
    padding-left: 28px;
}

.search-results li.menu-search {
    border-top: 1px solid #eee;
}

.search-results li.menu-search a {
    color: light-dark(#1f4f8f, #6aadff);
}

.notif-wrapper {
    position: relative;
    display: flex;
    align-items: center;
    margin-right: 12px;
}

.dev-user-switcher {
    margin-right: 12px;
    padding: 4px 8px;
    border: 1px dashed #f90;
    border-radius: 6px;
    background: rgba(0, 0, 0, 0.25);
    color: #fff;
    font-size: 0.75rem;
    max-width: 160px;
}

.notif-bell {
    padding: 6px;
    line-height: 0;
    border: none;
    background: transparent;
    color: #fff;
    cursor: pointer;
}

.notif-bell svg {
    width: 22px;
    height: 22px;
    display: block;
}

.notif-bell:hover {
    background: rgba(255, 255, 255, 0.15);
    border-radius: 50%;
}

.notif-badge {
    position: absolute;
    top: 2px;
    right: 2px;
    min-width: 16px;
    height: 16px;
    padding: 0 3px;
    border-radius: 8px;
    background: #d9534f;
    color: #fff;
    font-size: 0.65rem;
    font-weight: 700;
    line-height: 16px;
    text-align: center;
    pointer-events: none;
}

.notif-panel {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 100;
    width: 22rem;
    max-width: 90vw;
    max-height: 24rem;
    overflow-y: auto;
    border-radius: 10px;
    background: light-dark(#fff, #25292e);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
}

.notif-panel[hidden] {
    display: none;
}

.notif-empty {
    margin: 0;
    padding: 20px 16px;
    color: #888;
    text-align: center;
}

.notif-list {
    list-style: none;
    margin: 0;
    padding: 4px 0;
}

.notif-item {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 10px 14px;
    cursor: pointer;
    border-bottom: 1px solid light-dark(#eee, #3a4048);
}

.notif-item:last-child {
    border-bottom: none;
}

.notif-item:hover {
    background: light-dark(#e6edf6, #313842);
}

.notif-item.unread {
    background: light-dark(#f0f6ff, #1f2a38);
}

.notif-item-title {
    font-weight: 600;
    color: light-dark(#222, #e0e0e0);
}

.notif-item-body {
    color: light-dark(#555, #aaa);
    white-space: pre-wrap;
    overflow-wrap: break-word;
}

/* Contact widget — bottom-right floating button + expandable conversation panel
   (public/contact.js), and its admin-panel counterpart on /admin. */
.contact-widget {
    position: fixed;
    right: 20px;
    bottom: 20px;
    z-index: 200;
}

.contact-widget-toggle {
    position: relative;
    width: 52px;
    height: 52px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: var(--btn-accent);
    color: var(--btn-accent-text);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
}

.contact-widget-toggle:hover {
    background: var(--btn-accent-hover);
    transform: translateY(-2px);
}

.contact-widget-toggle svg {
    width: 24px;
    height: 24px;
    fill: currentColor;
    stroke: none;
}

.contact-widget-dot {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: #d9534f;
    border: 2px solid light-dark(#fff, #25292e);
}

.contact-widget-panel {
    position: absolute;
    right: 0;
    bottom: calc(100% + 12px);
    width: 20rem;
    max-width: calc(100vw - 40px);
    display: flex;
    flex-direction: column;
    border-radius: 12px;
    background: light-dark(#fff, #25292e);
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.3);
    overflow: hidden;
}

.contact-widget-panel[hidden] {
    display: none;
}

.contact-widget-header {
    padding: 12px 16px;
    background: var(--btn-accent);
    color: var(--btn-accent-text);
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

/* Bell toggle for "notify me when we reply" — sits on the accent-colored header bar, so it
   overrides .icon-btn's default bordered-surface chrome rather than inheriting it. */
.contact-widget-notify {
    padding: 4px;
    border: none;
    background: transparent;
    color: var(--btn-accent-text);
    opacity: 0.75;
}

.contact-widget-notify svg {
    width: 18px;
    height: 18px;
    display: block;
}

.contact-widget-notify:hover {
    opacity: 1;
}

.contact-widget-notify[aria-pressed="true"] {
    opacity: 1;
    background: rgba(255, 255, 255, 0.2);
    border-radius: 6px;
}

.contact-widget-messages {
    max-height: 16rem;
    overflow-y: auto;
    padding: 12px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.contact-widget-empty {
    margin: 0;
    color: light-dark(#777, #999);
}

.contact-widget-msg {
    padding: 8px 12px;
    border-radius: 10px;
    white-space: pre-wrap;
    overflow-wrap: break-word;
    max-width: 85%;
}

.contact-widget-msg.visitor {
    align-self: flex-end;
    background: var(--btn-accent);
    color: var(--btn-accent-text);
}

.contact-widget-msg.admin {
    align-self: flex-start;
    background: light-dark(#eef1f4, #2f353c);
    color: var(--btn-text);
}

.contact-widget-form {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 12px;
    border-top: 1px solid light-dark(#e5e5e5, #3a4048);
}

.contact-widget-form textarea {
    resize: vertical;
    font: inherit;
    padding: 8px;
    border: 1px solid var(--btn-border);
    border-radius: 6px;
    background: var(--btn-surface);
    color: var(--btn-text);
}

.contact-widget-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
}

.contact-widget-status {
    font-size: 0.8rem;
    color: light-dark(#777, #999);
}

/* Admin variant of the Contact widget: a thread list that drills into a per-visitor detail
   view, in place of the ordinary widget's single cached conversation. */
.contact-widget-body {
    max-height: 20rem;
    overflow-y: auto;
    padding: 12px;
    display: flex;
    flex-direction: column;
}

.contact-thread-list {
    list-style: none;
    margin: 0;
    padding: 0;
    border: 1px solid light-dark(#e5e5e5, #3a4048);
    border-radius: 8px;
    overflow: hidden;
}

.contact-thread-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    padding: 10px 12px;
    border: none;
    border-bottom: 1px solid light-dark(#eee, #3a4048);
    border-radius: 0;
    background: var(--btn-surface);
    text-align: left;
    font-weight: 400;
}

.contact-thread-row:last-child {
    border-bottom: none;
}

.contact-thread-row.unread {
    background: light-dark(#f0f6ff, #1f2a38);
    font-weight: 600;
}

.contact-thread-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    overflow: hidden;
}

.contact-thread-name {
    font-size: 0.8rem;
    font-weight: 600;
    color: light-dark(#555, #aaa);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.contact-thread-row.unread .contact-thread-name {
    color: inherit;
}

.contact-thread-preview {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.contact-thread-detail-header {
    display: flex;
    align-items: center;
    gap: 8px;
    font-weight: 600;
    margin-bottom: 4px;
}

.contact-thread-back {
    flex-shrink: 0;
    width: 24px;
    height: 24px;
    padding: 0;
    border: none;
    border-radius: 6px;
    background: transparent;
    font-size: 1rem;
    line-height: 1;
}

.contact-thread-back:hover {
    background: var(--btn-surface);
}

.contact-thread-badge {
    flex-shrink: 0;
    min-width: 18px;
    height: 18px;
    padding: 0 4px;
    border-radius: 9px;
    background: #d9534f;
    color: #fff;
    font-size: 0.7rem;
    font-weight: 700;
    line-height: 18px;
    text-align: center;
}

/* Concept create-or-pick typeahead (source concept selector + concept-DAG edge picker) */
.concept-typeahead {
    position: relative;
    display: flex;
    flex: 1;
    min-width: 8rem;
}

.concept-typeahead > input {
    flex: 1;
    min-width: 0;
    padding: 4px 8px;
    font: inherit;
}

/* position/left/top/width are set inline by concepttypeahead.js's _positionMenu — the menu is
   portalled to <body> as a fixed-positioned element so it can float above `overflow: hidden`
   ancestors (e.g. the source panel's accordion rows) instead of being clipped by them. */
.concept-typeahead-menu {
    background: light-dark(#fff, #25292e);
    border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
    list-style: none;
    margin: 0;
    padding: 4px 0;
    z-index: 100;
    max-height: 260px;
    overflow-y: auto;
}

.concept-typeahead-menu[hidden] {
    display: none;
}

.concept-typeahead-option {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
    padding: 6px 12px;
    cursor: pointer;
    color: light-dark(#222, #e0e0e0);
}

.concept-typeahead-option:hover,
.concept-typeahead-option.active {
    background: light-dark(#e6edf6, #313842);
}

.concept-typeahead-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* A personal Code's token is only unique per-owner, so two rows can show similar-looking name
   text — this excerpt (from Specification) is what actually tells them apart. */
.concept-typeahead-snippet {
    font-size: 0.76rem;
    color: light-dark(#777, #999);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.concept-typeahead-badge {
    font-size: 0.7rem;
    color: #888;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    flex-shrink: 0;
}

.concept-typeahead-create {
    border-top: 1px solid light-dark(#eee, #3a4048);
    color: light-dark(#1f4f8f, #6aadff);
}

.concept-typeahead-create .concept-typeahead-badge {
    color: inherit;
    opacity: 0.7;
}

.concept-typeahead-link {
    border-top: 1px solid light-dark(#eee, #3a4048);
    color: var(--btn-accent);
}

.concept-typeahead-link .concept-typeahead-badge {
    color: inherit;
    opacity: 0.7;
}

/* The "not found here" hint (concept creation isn't offered inline) — muted and italic so it
   reads as a note, not a pickable row, even though it's keyboard/click-selectable like one. */
.concept-typeahead-hint {
    border-top: 1px solid light-dark(#eee, #3a4048);
    color: light-dark(#777, #999);
    font-style: italic;
}

/* Full Search results page */
.search-page-form {
    display: flex;
    gap: 8px;
    margin: 0 0 20px;
}

.search-page-form input[type="search"] {
    flex: 1;
    padding: 8px 12px;
    border: 1px solid #ccc;
    border-radius: 6px;
}

.author-new-form {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 20px;
}

.author-new-form input[type="text"] {
    flex: 1;
    max-width: 20rem;
    padding: 8px 12px;
    border: 1px solid #ccc;
    border-radius: 6px;
}

footer {
    flex-shrink: 0;
    max-width: 800px;
    margin: 0 auto;
    padding: 0 20px;
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    align-items: center;
    gap: 8px;
}

/* The `button` rule below sets display:inline-flex, which (as author CSS) beats
   the browser's built-in [hidden]{display:none}. Restore it so the hidden
   attribute actually hides the footer action buttons. */
button[hidden] {
    display: none;
}

/* Groups the footer's destructive actions so they float left, isolated from Cancel/Primary by a
   spacer, per the site's button-ordering convention. On the source page this sits empty — Delete
   moved into the "More actions" overflow menu (2026-07-22) alongside Revert — but stays for other
   pages' (e.g. concept) direct reuse of #deleteButton. The auto margin lives on the group rather
   than on whichever button happens to be last, since it can be hidden independently. */
.footer-danger-group {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-right: auto;
}

/* "More actions" overflow (2026-07-22): Delete/Revert/Replace/Compare/Edit-video-URL are all
   infrequent, one-off actions, so they collapse behind a single ⋯ toggle instead of permanently
   occupying the bar — Open/Play video and Done stay put since those get used every visit.
   Opens upward (`bottom: 100%`) since the footer sits flush with the viewport's bottom edge. */
.footer-overflow {
    position: relative;
}

.footer-overflow-menu {
    position: absolute;
    bottom: calc(100% + 8px);
    right: 0;
    z-index: 20;
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 12rem;
    padding: 6px;
    border: 1px solid var(--btn-border);
    border-radius: 10px;
    background: var(--btn-surface);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
}

.footer-overflow-menu[hidden] {
    display: none;
}

.footer-overflow-menu button {
    justify-content: flex-start;
    gap: 8px;
    width: 100%;
    padding: 8px 10px;
    line-height: 1.2;
    white-space: nowrap;
}

/* The visible label reads straight from the button's own tooltip text — the two-step "Sure?"
   confirm buttons (deleteButton/revertBtn) clear `title` for exactly this reason, so the label
   disappears the moment the button's real "Sure?" text takes over instead of doubling up. */
.footer-overflow-menu button::after {
    content: attr(title);
    font-weight: 600;
}

/* Isolates the destructive pair (Revert, Delete) from the neutral actions above them, per the
   site's button-ordering convention, now that they all share one dropdown instead of separate
   footer clusters. A dedicated element rather than an adjacent-sibling selector on .danger — a
   hidden (but still-in-DOM) Revert would otherwise leave the selector's "previous sibling" check
   matching the wrong element and the separator silently missing. */
.footer-overflow-divider {
    border-top: 1px solid var(--btn-border);
    margin: 2px 4px;
}

/* Every footer/toolbar action is icon-only (button text reads as a tooltip instead) so the
   bar stays compact and language-independent. Square target sized to match the old labeled
   buttons' full height (9px padding + a 22px icon ≈ the same footprint as 9px16px + text),
   not just the glyph — clearly bigger than the old bare .source-delete-icon trash (16px+6px). */
.icon-btn,
a.icon-btn {
    padding: 9px;
    line-height: 0;
}

.icon-btn svg,
a.icon-btn svg {
    width: 22px;
    height: 22px;
    display: block;
}

/* A two-step destructive confirm ("Sure?") needs to read as text, not an ambiguous icon —
   while armed it reverts to a normal padded, bordered text button. */
.icon-btn[data-armed] {
    padding: 9px 16px;
    line-height: normal;
}

/* Toggle state for icon buttons that show/hide something (Compare original). */
.icon-btn[aria-pressed="true"] {
    background: var(--btn-surface-hover);
    border-color: var(--btn-accent);
    color: var(--btn-accent);
}

/* Publish button when Consider The Evidence already holds the current content
   (docs/design/publishModel.md, Option A): present but receded — a status, not a call to
   action. Still clickable (a forced re-publish is a harmless no-op), just not inviting one. */
.icon-btn.is-published {
    opacity: 0.45;
    cursor: default;
}

.icon-btn.is-published:hover {
    background: var(--btn-surface);
    box-shadow: none;
    transform: none;
}

/* `sourceUrlLink` is a real <a> (needs `.href` for open-in-new-tab, set from JS), but it sits
   in the footer beside real <button> icons and has to match their chrome exactly — the base
   `button{}` rules are element-scoped and don't reach an anchor. */
a.icon-btn {
    appearance: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--btn-border);
    border-radius: 8px;
    background: var(--btn-surface);
    color: var(--btn-text);
    text-decoration: none;
    cursor: pointer;
    transition: background-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}

a.icon-btn:hover {
    background: var(--btn-surface-hover);
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
    transform: translateY(-2px);
}

/* Same [hidden]-vs-author-display(flex) override as `button[hidden]` above. */
a.icon-btn[hidden] {
    display: none;
}

/* Spin animation for the "Saving…" status icon. */
.icon-spin {
    animation: icon-spin 0.9s linear infinite;
    transform-origin: center;
}

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

/* Separator above the action buttons — only when they're actually shown. */
footer:has(button:not([hidden])) {
    border-top: 1px solid #ddd;
    padding-top: 16px;
    margin-top: 12px;
    margin-bottom: 12px;
}

/* Saving/Saved render as an icon (spinner / check); an error still renders as readable text
   (CLAUDE.md: errors must stay inline and visible, not hidden behind a hover tooltip), so
   this stays flex to hold either shape. */
.draft-status {
    display: inline-flex;
    align-items: center;
    font-size: 0.85rem;
    color: #666;
}

.draft-status svg {
    width: 20px;
    height: 20px;
    display: block;
}

/* Footer link to /publications, shown next to Publish on a publishable editor. Plain text
   link by default; turns amber and gains a marker when the essay has drifted citations
   (docs/design/publishModel.md). */
.footer-manage-link {
    font-size: 0.85rem;
    color: light-dark(#5c6670, #9aa0a6);
    text-decoration: none;
    align-self: center;
}

.footer-manage-link:hover {
    text-decoration: underline;
}

.footer-manage-link.has-drift {
    color: light-dark(#8a5a00, #f0c26b);
    font-weight: 600;
}

@media (prefers-color-scheme: dark) {
    .draft-status {
        color: #aaa;
    }

    footer:has(button:not([hidden])) {
        border-top-color: #444;
    }
}

.data-table {
    border-collapse: collapse;
    width: 100%;
    margin: 8px 0 12px;
}

.data-table th,
.data-table td {
    padding: 6px 10px;
    text-align: left;
    vertical-align: top;
}

/* No gridlines, just a single rule under the header
   (dark in light mode, light in dark mode). */
.data-table th {
    border-bottom: 2px solid light-dark(#333, #ccc);
    font-weight: 600;
    font-size: 0.85rem;
}

.data-table th a.sort-header {
    color: inherit;
    text-decoration: none;
    white-space: nowrap;
}

.data-table th a.sort-header:hover {
    text-decoration: underline;
}

/* Quote authoring (owner-only): a Kind-aware card per quote. */
.quote-card {
    border: 1px solid light-dark(#ddd, #444);
    border-radius: 6px;
    padding: 12px;
    margin: 0 0 12px;
}

.quote-card-head {
    margin-bottom: 8px;
}

.quote-field {
    display: block;
    margin: 0 0 8px;
}

.quote-field-name {
    display: block;
    font-size: 0.8rem;
    font-weight: 600;
    color: light-dark(#555, #bbb);
    margin-bottom: 2px;
}

.quote-field input,
.quote-internal-text {
    width: 100%;
    box-sizing: border-box;
}

.quote-internal-text {
    font-family: monospace;
    white-space: pre;
}

.quote-copyright {
    display: block;
    margin: 0 0 8px;
}

/* Standalone New Source form (/sources/new). */
.source-new-form {
    max-width: 32rem;
}

.source-new-form .field-label {
    display: block;
    font-size: 0.8rem;
    font-weight: 600;
    color: light-dark(#555, #bbb);
    margin: 0 0 10px;
}

.source-new-form .field-label select,
.source-new-form .field-label input,
.source-new-form .field-label textarea {
    display: block;
    width: 100%;
    box-sizing: border-box;
    margin-top: 2px;
    font-weight: 400;
    font-family: inherit;
}

/* ---- Import dock (public/importdock.js, mounted by SourceEditor) ---- */
.import-dock-zone {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 28px 16px;
    border: 2px dashed light-dark(#c9c9c9, #555);
    border-radius: 8px;
    background: light-dark(#fafafa, #25292e);
    text-align: center;
    cursor: copy;
    /* Instructions, not content: unselectable so they can't be dragged onto their own drop target. */
    user-select: none;
}

.import-dock-zone.import-dock-over {
    border-color: var(--btn-accent);
    background: light-dark(#f0faf8, #1f3a35);
}

.import-dock-hint {
    margin: 0;
    font-weight: 600;
}

.import-dock-sub {
    margin: 0;
    color: light-dark(#666, #aaa);
}

.import-dock-browse {
    color: var(--btn-accent);
    cursor: pointer;
    text-decoration: underline;
}

.import-dock-status.error {
    color: light-dark(#b42318, #f97066);
}

.import-dock-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
}

/* Import preview + collapsed alternatives (public/importdock.js). */
.import-dock-preview {
    max-height: 320px;
    overflow-y: auto;
    margin: 4px 0 8px;
    padding: 8px 12px;
    border: 1px solid light-dark(#ddd, #444);
    border-radius: 6px;
    background: light-dark(#fff, #1c1f23);
}

.import-preview-block { margin: 0 0 6px; line-height: 1.4; }
.import-preview-heading { font-weight: 700; }
.import-preview-meta { color: light-dark(#666, #aaa); font-size: 0.85rem; font-weight: 600; }
.import-preview-more { color: light-dark(#666, #aaa); font-style: italic; }
.import-preview-pagebreak { color: light-dark(#666, #aaa); font-size: 0.85rem; text-align: center; }

/* "Ask your own AI" panel (idle) and the review stage: editable SignalCodes text beside a live
   preview, with errors and hints (public/importdock.js, docs/design/importFormat.md). */
.import-dock-ai {
    margin: 10px 0 0;
    padding: 10px 12px;
    border: 1px solid light-dark(#ddd, #444);
    border-radius: 6px;
}

.import-dock-ai-row {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 6px;
}

.import-dock-ai-instruction {
    flex: 1 1 24ch;
    min-width: 0;
    padding: 4px 8px;
    border-radius: 4px;
    background: light-dark(#f3f3f3, #2a2e33);
    font-family: ui-monospace, Consolas, monospace;
    font-size: 0.85rem;
    overflow-wrap: anywhere;
    user-select: all;
}

.import-dock-original {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin: 0 0 8px;
    padding: 8px 12px;
    border-left: 3px solid light-dark(#d97706, #fbbf24);
    background: light-dark(#fffbeb, #2b2410);
    border-radius: 4px;
}

.import-dock-original span { flex: 1 1 30ch; }

.import-review-head {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin: 0 0 8px;
}

.import-review {
    display: grid;
    grid-template-columns: 1fr;
    gap: 12px;
    margin: 0 0 8px;
}

@media (min-width: 900px) {
    .import-review { grid-template-columns: 1fr 1fr; }
}

.import-review-text {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
}

.import-review-textarea {
    box-sizing: border-box;
    width: 100%;
    min-height: 26em;
    padding: 10px 12px;
    border: 1px solid light-dark(#ccc, #555);
    border-radius: 6px;
    background: light-dark(#fff, #1c1f23);
    color: inherit;
    font-family: ui-monospace, Consolas, monospace;
    font-size: 0.85rem;
    line-height: 1.45;
    tab-size: 4;
    resize: vertical;
}

.import-review-preview .import-dock-preview {
    max-height: 26em;
    margin: 0;
}

.import-review-diagnostics {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: 12em;
    overflow-y: auto;
    font-size: 0.9rem;
}

.import-review-diagnostics li { margin: 0 0 4px; }
.import-review-error { color: light-dark(#b42318, #f97066); }
.import-review-hint { color: light-dark(#8a5a00, #fbbf24); }

button.import-review-line {
    padding: 0 6px;
    border: none;
    background: none;
    color: inherit;
    font: inherit;
    font-weight: 600;
    text-decoration: underline;
    cursor: pointer;
}

/* The fair-use quoted slice shown to non-owners under a quote. */
.quote-text {
    margin: 4px 0 0;
    padding: 4px 0 4px 10px;
    border-left: 3px solid light-dark(#ddd, #444);
    color: light-dark(#444, #ccc);
    white-space: pre-wrap;
}

/* Per-speaker-turn attribution under a .quote-text block (renderInterpretationItem) — mirrors
   .source-quote-cite's muted, right-aligned styling in the source panel. */
.quote-cite {
    display: block;
    margin: 2px 0 0;
    padding-left: 10px;
    text-align: right;
    font-size: 0.78rem;
    font-weight: 600;
    font-style: normal;
    color: light-dark(#777, #999);
}

/* Source attribution ("— Author in Title") closing every quote card (renderInterpretationItem) —
   had no rule at all, so it rendered as plain paragraph text; matches the .quote-cite/
   .source-other-byline byline treatment (weight over size for contrast) for consistency. */
.quote-attribution {
    margin: 4px 0 0;
    font-weight: 600;
    font-size: 0.85rem;
    color: light-dark(#666, #999);
}

/* Examples authoring editor (owner-only). */
.example-card {
    border: 1px solid light-dark(#ddd, #444);
    border-radius: 6px;
    padding: 12px;
    margin: 0 0 12px;
}

.example-card-head {
    display: flex;
    gap: 8px;
    align-items: center;
    margin-bottom: 8px;
}

.example-desc-input {
    width: 100%;
    box-sizing: border-box;
    margin-bottom: 8px;
}

.example-citation-rows {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.example-citation-row {
    display: flex;
    gap: 6px;
    align-items: center;
}

.example-section-input {
    flex: 1;
}

.example-add-citation {
    margin-top: 6px;
}

.example-card-actions {
    display: flex;
    gap: 8px;
    align-items: center;
    margin-top: 10px;
}

.example-card-status {
    display: inline-flex;
    align-items: center;
    font-size: 0.85rem;
    color: light-dark(#666, #aaa);
}

.example-card-status svg {
    width: 20px;
    height: 20px;
    display: block;
}

.example-card-status.saved {
    color: light-dark(#333, #ccc);
}

.example-card-status.error {
    color: light-dark(#c62828, #ef9a9a);
    animation: status-shake 0.4s ease;
}

@keyframes status-shake {
    10%, 90% { transform: translateX(-1px); }
    20%, 80% { transform: translateX(2px); }
    30%, 50%, 70% { transform: translateX(-4px); }
    40%, 60% { transform: translateX(4px); }
}

@media (prefers-reduced-motion: reduce) {
    .example-card-status.error {
        animation: none;
    }
}

/* Long description (Specification/Explanation on the concept page): clamp to a few lines with
   a More/Less toggle (concepthelp.js) so a verbose one doesn't dominate the page. */
.concept-desc.clamped {
    min-height: 0;
    max-height: 5.4em;
    overflow: hidden;
}

button.concept-more {
    margin-top: 6px;
    padding: 3px 12px;
    font-size: 0.8rem;
}

.example-links {
    list-style: none;
    margin: 0.25rem 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.example-links li {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.example-links li.muted {
    color: light-dark(#999, #777);
}

.example-add {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.5rem;
}

/* The reader's interpretation, shown as the lead above the quoted evidence. */
.quote-interpretation {
    font-weight: 600;
    margin: 0 0 0.25rem;
}

.source-interp-text-row {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    margin-top: 0.35rem;
}

/* Base shape only. Borders/focus treatment come from `.interp-node .source-interp-text` in the
   containment rules below, which reveal an edge on hover/focus so an interpretation reads as
   prose at rest rather than as a form. */
.source-interp-text {
    flex: 1;
    resize: none;
    overflow: hidden;
    font: inherit;
    line-height: 1.4;
    white-space: pre-wrap;
    word-break: break-word;
    border: none;
    background: none;
    color: inherit;
}

.source-interp-text:focus {
    outline: none;
}

/* DAG-linker overlay (overlaylinker.js) — a dismissible, non-blocking panel summoned to
   file/link concepts and climb the concept graph without leaving the page. */
.overlay-scrim {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 6vh 1rem 1rem;
    background: rgba(0, 0, 0, 0.45);
    overflow-y: auto;
}

.overlay-scrim[hidden] {
    display: none;
}

.overlay-panel {
    width: min(560px, 100%);
    background: var(--btn-surface);
    color: var(--btn-text);
    border: 1px solid var(--btn-border);
    border-radius: 12px;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
    padding: 1.1rem 1.25rem 1.25rem;
}

/* Bottom-sheet variant of the same overlay-scrim/-panel pair (same scrim-click/Esc/✕ dismiss
   wiring, projectcontrols.js) — for content summoned from a footer button, so it visibly slides
   up from where it was opened rather than fading in centred like the DAG-linker/quote popovers.
   Uses opacity/visibility instead of [hidden] so the transform can transition; .overlay-scrim's
   own `display: flex` positioning still applies underneath. `bottom` stops the scrim short of
   the footer (via --footer-h, kept in sync by site.js) so the sheet's flex-end anchor — and thus
   its slide-up — starts from the button bar's own top edge, not the viewport's, and the button
   bar itself stays visible (unscrimmed) underneath. */
.sheet-scrim {
    bottom: var(--footer-h, 0px);
    align-items: flex-end;
    justify-content: center;
    padding: 0;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.2s ease, visibility 0s linear 0.2s;
}

.sheet-scrim.open {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.2s ease;
}

.sheet-panel {
    position: relative;
    width: 100%;
    max-width: 560px;
    max-height: 70vh;
    overflow-y: auto;
    border-radius: 12px 12px 0 0;
    transform: translateY(100%);
    transition: transform 0.2s ease;
}

.sheet-scrim.open .sheet-panel {
    transform: translateY(0);
}

/* Quote-in-context: fading sentence context each side of a quoted quote (docs/design/
   quoteInContext.md), shown identically to every viewer regardless of copy ownership — the
   source-page popover (.quote-snip-panel, built on .overlay-scrim/.overlay-panel). */
.quote-snip-panel {
    position: relative;
    width: min(480px, 100%);
}

/* The panel's round ✕ close button, cascaded top-right (quotesnippet.js). */
.concept-pop-close {
    position: absolute;
    top: 6px;
    right: 6px;
    width: 30px;
    height: 30px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: light-dark(#5b6773, #9aa4ad);
    font-size: 1.2rem;
    line-height: 1;
}

.concept-pop-close:hover {
    background: var(--btn-surface-hover);
    color: var(--btn-text);
    box-shadow: none;
    transform: none;
}

/* One .quote-snip-body per quote (docs/design/quoteInContext.md). A quote is always exactly one
   range now (2026-08-10), so this is a one-item list, but the list wrapper stays since the
   surrounding gap/stacking rules are shared with other list-shaped call sites. */
.quote-snip-list {
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
}

.quote-snip-body {
    display: block;
    max-width: 56ch;
    line-height: 1.5;
}

.quote-snip-before,
.quote-snip-after {
    color: light-dark(#5b6773, #9aa4ad);
}

.quote-snip-quote {
    background: light-dark(rgba(15, 118, 110, 0.14), rgba(45, 212, 191, 0.18));
    border-radius: 2px;
    padding: 0.02rem 0.15rem;
}

/* The trailing sentence is hard-capped to ~1 line, then opacity-masked to a fade — a mask (not a
   background gradient) so it looks right against either popover's own surface colour. */
.quote-snip-after-wrap {
    display: inline-block;
    max-width: 100%;
    max-height: 1.4em;
    overflow: hidden;
    vertical-align: bottom;
    -webkit-mask-image: linear-gradient(to right, black 55%, transparent 92%);
    mask-image: linear-gradient(to right, black 55%, transparent 92%);
}

a.quote-snip-clickable {
    display: block;
    color: inherit;
    text-decoration: none;
    cursor: pointer;
    border-radius: 6px;
    margin: -4px;
    padding: 4px;
}

a.quote-snip-clickable:hover {
    background: var(--btn-surface-hover);
}

.quote-snip-actions {
    display: flex;
    justify-content: flex-end;
    margin-top: 0.75rem;
}

/* Source panel redesign: user dropdown + single current-quote editor. */
.source-user-row {
    margin-bottom: 0.6rem;
}

.source-user-select {
    width: 100%;
    padding: 6px 10px;
    font-size: 1rem;
    font-weight: 700;
    border: 1px solid var(--btn-border);
    border-radius: 8px;
    background: var(--btn-surface);
    color: var(--btn-text);
}

.source-current-quote {
    padding: 0.6rem 0.75rem;
}

.source-concept-select {
    flex: 1;
    min-width: 8rem;
    padding: 4px 8px;
    font: inherit;
}

.source-section-label {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: light-dark(#888, #999);
    margin: 0.85rem 0 0.4rem;
}

/* Natural containment (2026-08-24, docs/design/universalTagging.md "Panel layout"). Every level
   of the panel is the same shape — the thing, then the concepts it is an example of directly
   beneath it — so `.concept-row` below is one rule serving the Source, a Quote and every
   Interpretation alike. That replaced the tab strip, which put EVERY interpretation's pills in
   one row above a box showing only ONE interpretation's text; past a single interpretation the
   pills and the prose disagreed. */

/* THE repeated primitive (buildConceptRow). Wraps rather than scrolls — every filed concept
   stays visible, which is the property the tab strip was protecting and the one thing worth
   carrying over from it. */
.concept-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.32rem;
    margin-top: 0.35rem;
}

.concept-row-label {
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: light-dark(#7b858f, #79828c);
    white-space: nowrap;
}

.concept-row-empty {
    font-size: 0.74rem;
    font-style: italic;
    color: light-dark(#7b858f, #79828c);
}

/* The label the whole stack now carries once, in place of the per-item "INTERPRETATION" gutter
   word (2026-08-24 follow-up) — a short rule, the word, then a rule filling the rest, echoing a
   plain-text section break ("----- INTERPRETATIONS ----------------"). `::before`/`::after`
   become flex items either side of the text node when the container itself is a flex row. */
.interp-section-divider {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0.7rem 0 0 0.1rem;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: light-dark(#9aa3ad, #6d757e);
}

.interp-section-divider::before {
    content: '';
    width: 0.7rem;
    height: 1px;
    background: var(--btn-border);
}

.interp-section-divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--btn-border);
}

/* The interpretation stack, indented just a touch under its divider — not the 0.6rem it used to
   carry (2026-08-24 follow-up: cut down further once the indent stopped having to visually stand
   in for the removed rule line). */
.interp-kids {
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    margin: 0.4rem 0 0 0.1rem;
    padding-left: 0.15rem;
}

/* One interpretation — the viewer's own or anyone else's, same shape at the same level, which is
   the whole point of the redesign: a rival's reading is a peer, not an appendix. */
/* Gutter now carries only a byline (someone else's name) or "New" — the "INTERPRETATION" word
   itself moved up to `.interp-section-divider` (2026-08-24 follow-up), so this no longer needs to
   fit that word and shrank from 6.5rem accordingly. Blank for the viewer's own: the divider above
   already said what the stack is. */
.interp-node {
    display: grid;
    grid-template-columns: 4rem minmax(0, 1fr);
    column-gap: 0.5rem;
}

/* The gutter names whose reading this is (blank for the viewer's own — see above). On someone
   else's it carries their name; on a draft, "New". Left-aligned, no extra padding: the column's
   own left edge already lands within a couple px of the quote's own text above (both nest under
   the same 0.6rem left padding, `.source-interp-quote` and `.source-current-quote` alike) — that's
   the thing this lines up with (2026-08-24 follow-up), not `.interp-section-divider`'s
   "Interpretations", which sits further right past its own leading rule. */
.interp-node > .interp-kind {
    font-size: 0.58rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--btn-accent);
    text-align: left;
    padding-top: 0.3rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* `padding-top: 0` (not the base rule's 0.3rem) — re-measured against a live render together
   with `.source-interp-readonly`'s own `margin-top: 0` fix above: at this larger font-size, zero
   is what lines this name up with the top of the reading (and its first concept pill) beside it,
   both of which start higher, at the flex row's own top edge, than the 0.58rem base case does. */
.interp-node.theirs > .interp-kind {
    color: light-dark(#7b858f, #868f99);
    text-transform: none;
    font-size: 0.68rem;
    letter-spacing: 0;
    padding-top: 0;
}

.interp-node.draft > .interp-kind {
    color: light-dark(#9aa3ad, #6d757e);
}

/* The reading itself plus the concepts it is an example of, side by side — mirrors
   `.source-quote-top` (2026-08-24 follow-up: pills moved from a row below the text to a rail
   beside it, same as a quote's own bare tags). */
.interp-node > .interp-body {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    min-width: 0;
}

.interp-node > .interp-body > .source-interp-text,
.interp-node > .interp-body > .source-interp-readonly {
    flex: 1;
    min-width: 0;
}

/* The text field only shows its edges on hover/focus — at rest an interpretation should read as
   prose, not as a form. */
.interp-node .source-interp-text {
    width: 100%;
    border: 1px solid transparent;
    border-radius: 6px;
    padding: 0.15rem 0.3rem;
    margin-left: -0.3rem;
    background: none;
}

.interp-node .source-interp-text:hover {
    border-color: light-dark(#e3e8ea, #343a41);
}

.interp-node .source-interp-text:focus {
    outline: none;
    border-color: var(--btn-accent);
    background: light-dark(#f9f9f9, #2a2a2a);
}

.interp-node.draft .source-interp-text {
    border-style: dashed;
    border-color: light-dark(#c4ccd4, #454d56);
}

.reading-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
}

/* A chip is removed by dragging it out of its concept row (buildTagChip) — while the drag is in
   flight the original fades so the release point reads as the decision. */
.reading-chip.dragging {
    opacity: 0.35;
}

/* Someone else's tag: visible, never actionable — no drag handle, no remove. */
.reading-chip.theirs {
    cursor: default;
}

/* The (?) repurposed for a tag's Why (buildTagChip's `opts.info`, buildConceptPill) — coloured
   once a Why is set, plain (same as the default "what does this mean" (?) elsewhere) when not.
   No separate control: the pill's main text already covers the spec popover in this context
   (`mainOpensInfo`), so the (?) was otherwise redundant and free to repurpose. */
.concept-pill-info.active {
    border-color: var(--btn-accent);
    color: var(--btn-accent);
    opacity: 1;
}

/* Layout for the viewer's own Why popover content (`_buildWhyPopoverContent`) — the popover
   itself is a plain .concept-pill-popover (shared with the spec popover's own chrome); someone
   else's read-only content is just text and needs none of this. */
.reading-chip-why-editor {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.reading-chip-why-input {
    width: 14rem;
    max-width: calc(100vw - 3rem);
    box-sizing: border-box;
    padding: 5px 7px;
    font: inherit;
    font-size: 0.74rem;
    border: 1px solid var(--btn-border);
    border-radius: 6px;
    background: var(--btn-surface);
    color: var(--btn-text);
}

.reading-chip-why-actions {
    display: flex;
    justify-content: flex-end;
    gap: 6px;
}

.interp-add-chip-wrap {
    position: relative;
    display: inline-flex;
}

/* The one way to file a concept: a bare "+" glyph trailing the tab's chip row (alone on an empty
   tab). Deliberately no pill/circle dress-up — it isn't a concept yet, so it doesn't wear the
   concept costume; the pill outline appears only once something is actually filed. */
.interp-add-chip-btn {
    all: unset;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    padding: 0.1rem;
    color: light-dark(#666, #999);
    cursor: pointer;
    font-size: 0.95rem;
    font-weight: 700;
    line-height: 1;
}

.interp-add-chip-btn:hover {
    color: var(--btn-accent);
}

/* An interpretation whose text is under 2 non-whitespace characters (`hasEnoughText`) — dimmed
   rather than hidden, so the "+" doesn't jump into existence mid-typing (sourcecontrols.js's
   `_setAddChipEnabled`, kept live by the textarea's own `input` handler). */
.interp-add-chip-btn:disabled {
    color: light-dark(#c4ccd4, #454d56);
    cursor: default;
}

.interp-add-chip-btn:disabled:hover {
    color: light-dark(#c4ccd4, #454d56);
}

/* visibility (not display: none) so an inactive tab still reserves the "+"'s width — a tab
   shouldn't visibly resize the moment it's activated or left. */
.interp-add-chip-btn.add-hidden {
    visibility: hidden;
}

/* Takes carry the tint the detail panel used to have, now that the panel itself is plain — the
   contrast between card and ground is preserved, just inverted. */
.source-take-card {
    border: 1px solid var(--btn-border);
    border-radius: 8px;
    padding: 0.5rem 0.6rem;
    background: light-dark(#f9f9f9, #2a2a2a);
}

.interp-top-row.source-interp-text-row {
    margin-top: 0;
}

.source-interp-text-row > .source-interp-readonly {
    flex: 1;
    margin-bottom: 0;
}

.source-other-byline {
    font-weight: 600;
    font-size: 0.85rem;
    margin: 0.4rem 0 0.15rem;
    opacity: 0.8;
}

/* margin-top: 0 overrides the <p> default — without it, a rival's reading (the only live caller)
   sits visibly lower than the tag rail beside it, which shares the same flex row's top edge but
   carries no such built-in margin (2026-08-24 follow-up). */
.source-interp-readonly {
    margin-top: 0;
    margin-bottom: 0.2rem;
}

.source-interp-readonly.muted {
    opacity: 0.6;
    font-style: italic;
}

/* Legacy: another user's take as a separate dashed card. Superseded 2026-08-24 — a rival's
   interpretation is now a peer in the same stack (`.interp-node.theirs`), not a card of its own.
   Kept only because `.source-take-card` is still used elsewhere in the panel. */
.source-take-card.rival {
    background: none;
    border-style: dashed;
}

/* Panel header extra: the note explaining what a no-copy visitor can and can't do here (the
   link to the original used to live here too — it's now beside the title, see .source-title-link). */
.source-panel-nocopy-note {
    opacity: 0.75;
    margin: 0 0 1rem;
}

/* The permanent points-of-interest list (docs/design/sourcePanelReorg.md follow-up 3): every
   quote on the source, collapsed to a compact row, one expanding in place to show its takes. */
.source-quote-item {
    margin-bottom: 0.8rem;
    padding: .5rem;
    border: 2px solid var(--btn-border);
    border-radius: 6px;
    background: var(--btn-surface);
    overflow: hidden;
    cursor: pointer;
}

/* No tag and no filed interpretation yet — still just the bare passage it was carved as. Dashed
   rather than a colour/opacity change so it stays legible beside .expanded's accent border-color
   (both can apply to the same row: an untouched quote you've just opened to start tagging) and
   doesn't read as an error or a muted/disabled state. */
.source-quote-item.untouched {
    border-style: dashed;
}

/* Runs in lockstep with .source-quote-body's own max-height transition (same duration, no delay)
   so the border tint and the grow read as one motion rather than "grow, then a separate phase
   where just the border catches up" — a delayed version of this (confirm-after-grow) was tried
   and read as sequential rather than simultaneous once the two could be told apart. @starting-style
   is needed here too: the row is a fresh DOM insertion each time (full re-render on every state
   change), so there is no persistent element to interpolate a plain :hover-style transition from. */
/* Colour only — border-width never changes (always the base rule's 1px). The previous version
   paired this with an inset 1px box-shadow riding right against the real border, which reads as
   the border itself thickening to 2px even though box-shadow never touches layout. */
.source-quote-item.expanded {
    border-color: var(--btn-accent);
    transition: border-color 0.3s ease;
}

@starting-style {
    .source-quote-item.expanded {
        border-color: var(--btn-border);
    }
}

/* Mirror of the above for the row a click just switched AWAY from: it keeps rendering its
   expanded content (see buildQuoteRow's `closing`) for one more render pass so there's something
   to visibly shrink, instead of the old row's detail vanishing the instant the new one opens and
   yanking everything below it up underneath the row you just clicked. Same duration as .expanded's
   border transition and as .source-quote-body's max-height, so open and close move as one
   synchronized motion in both directions. */
.source-quote-item.closing {
    transition: border-color 0.3s ease;
}

@starting-style {
    .source-quote-item.closing {
        border-color: var(--btn-accent);
    }
}

.source-quote-text {
    min-width: 0;
}

/* ---- concept pills ----
   Coded ("shared", owned by admin) = compact/monospace, same for every viewer. Uncoded
   ("personal", any other owner) = dashed border, muted colour. Every pill also carries its own (?) opening a
   Specification popover, deliberately separate from whatever the pill's main text opens — see
   buildConceptPill in sourcecontrols.js. */
.concept-row, .source-take-concept-cell {
    --pill-personal-border: light-dark(#8992a0, #7d858d);
}

.concept-pill-unit {
    display: inline-flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.2rem;
    vertical-align: top;
}

.concept-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    font-size: 0.72rem;
    font-weight: 600;
    padding: 0.24rem 0.4rem 0.24rem 0.6rem;
    border-radius: 99px;
    border: 1px solid var(--btn-border);
    background: var(--btn-surface);
}

.concept-pill-main {
    min-width: 0;
    max-width: 8rem;
    color: inherit;
    text-decoration: none;
}

a.concept-pill-main:hover {
    text-decoration: underline;
}

/* -webkit-box + line-clamp (not inline-flex) so a hyphenated personal code can wrap onto a second
   line instead of just truncating; hyphens are a natural break point under the default
   line-breaking rules, so "loss-aversion" wraps as "loss-" / "aversion" for free. Applied to the
   inner <span> rather than `.concept-pill-main` itself: Chromium corrupts glyph rendering when
   this combination of properties lands directly on a <button> (the clickable take-pill variant),
   turning e.g. "polif" into something reading like "polit" — the text node's own span sidesteps
   it regardless of what `.concept-pill-main` renders as. */
.concept-pill-main .code-text,
.concept-pill-main .personal-code-text {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    line-clamp: 2;
    -webkit-line-clamp: 2;
    overflow: hidden;
    overflow-wrap: anywhere;
    text-overflow: ellipsis;
}

.concept-pill .code-text {
    font-family: ui-monospace, Consolas, monospace;
    font-weight: 700;
    letter-spacing: 0.02em;
}

.concept-pill.uncoded {
    border-style: dashed;
    border-color: var(--pill-personal-border);
    color: light-dark(#555, #aaa);
}

.concept-pill-info {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.15rem;
    height: 1.15rem;
    flex: 0 0 auto;
    padding: 0;
    border: 1px solid currentColor;
    border-radius: 50%;
    background: transparent;
    color: inherit;
    font-size: 0.62rem;
    font-weight: 700;
    line-height: 1;
    opacity: 0.6;
}

.concept-pill-info:hover,
.concept-pill-info:focus-visible {
    opacity: 1;
}

/* Floats via the same portal-to-<body> mechanism as .concept-picker (_openConceptPicker in
   sourcecontrols.js), so it needs the same z-index/shadow to sit in that same floating layer.
   Pale yellow reuses the site-wide help colour (.concept-help) rather than the neutral
   --btn-surface it used to sit in, marking it as read-only reference content the way the concept
   Help panel does. */
.concept-pill-popover {
    max-width: 14rem;
    padding: 0.4rem 0.55rem;
    border-radius: 6px;
    border: 1px solid light-dark(#e6cf80, #6a5520);
    background: light-dark(#fbf2d3, #3a3318);
    color: light-dark(#3a3a3a, #ddd);
    font-size: 0.74rem;
    font-style: normal;
    font-weight: 400;
    line-height: 1.4;
    z-index: 40;
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.25);
}

.concept-pill-popover[hidden] {
    display: none;
}

/* "More" link inside the (?) popover (buildConceptPill's `visitHref` opt) — the popover already
   answers "what does this mean," so "go look at its own page" lives there too instead of a
   separate always-visible control. */
.concept-pill-visit {
    display: inline-block;
    margin-top: 0.35rem;
    padding-top: 0.35rem;
    border-top: 1px solid light-dark(#e6cf80, #6a5520);
    color: var(--btn-accent);
    font-weight: 700;
    text-decoration: none;
}

.concept-pill-visit:hover {
    text-decoration: underline;
}

/* A take's own editable concept pill (buildConceptSelector): main text is a real <button> that
   opens the picker panel below it, so it needs its own reset — the plain `button {}` rule's
   bordered/padded chrome would otherwise fight `.concept-pill-main`'s existing link-shaped
   layout. */
button.concept-pill-main {
    appearance: none;
    border: none;
    background: none;
    padding: 0;
    margin: 0;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

button.concept-pill-main:hover {
    box-shadow: none;
    transform: none;
}

.concept-pill.concept-pill-clickable {
    cursor: pointer;
    transition: background-color 0.12s ease, border-color 0.12s ease;
}

.concept-pill.concept-pill-clickable:hover,
.concept-pill.concept-pill-clickable:focus-within {
    background: var(--btn-surface-hover);
}

/* Who applied a chip in a .concept-row (buildConceptPill's `opts.mine`) — the viewer's own read
   accent teal, another user's read medium grey. That distinction carries more weight now that
   own and rival tags sit side by side in the same row at every level, rather than being
   segregated into separate cards. Solid border-style is forced below (higher specificity than
   `.uncoded`'s dashed) so a personal (non-shared) concept chip never reads as a sibling of a
   dashed "nothing chosen" placeholder. */
.concept-pill.mine {
    --pill-whose: var(--btn-accent);
}

.concept-pill.theirs {
    --pill-whose: light-dark(#6b7280, #9aa0a6);
}

.concept-row .concept-pill.mine,
.concept-row .concept-pill.theirs {
    border-style: solid;
    border-color: var(--pill-whose);
}

/* The picker opened by clicking an already-active pill (buildConceptPickerPanel) — floated above
   the take editor below it (sourcecontrols.js's _openConceptPicker portals it to <body> as
   `position: fixed`, set inline), with a shadow deep enough to read as its own raised layer: the
   one visual cue that clicking the pill again segues into concept creation/selection, no label
   text needed. */
.concept-picker {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    width: 18rem;
    max-width: calc(100vw - 2rem);
    padding: 0.6rem 0.7rem;
    border-radius: 8px;
    border: 1px solid var(--btn-border);
    background: var(--btn-surface);
    z-index: 40;
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.25);
}

.concept-picker[hidden] {
    display: none;
}

/* The search field's dropdown renders in-flow here (ConceptTypeahead's `portalMenu: false`)
   rather than floating separately, so typing and picking both happen on the one raised surface
   above instead of stacking a second elevated popup on top of the first. */
.concept-picker .concept-typeahead {
    /* The base `.concept-typeahead` rule assumes a flex-row parent (`flex:1; min-width:8rem`) —
       this panel is a column, so input and dropdown need to stack instead of sitting side by side. */
    width: 100%;
    flex-direction: column;
}

/* Borderless and background-less so it reads as the platform's own surface, not a text box
   nested inside the picker's box — only a hairline (brightening to the accent colour on focus)
   marks it as the editable part of that surface. */
.concept-picker .concept-typeahead > input {
    /* The base rule's `flex: 1` sizes along whatever the parent's main axis is — harmless in the
       usual row layout, but this parent is a column (above), where it would instead mean
       "grow to fill leftover vertical space" if the picker were ever height-constrained. */
    flex: 0 0 auto;
    border: none;
    border-bottom: 1px solid var(--btn-border);
    background: transparent;
    color: var(--btn-text);
    padding: 4px 2px 8px;
}

.concept-picker .concept-typeahead > input:focus-visible {
    outline: none;
    border-bottom-color: var(--btn-accent);
}

/* Normally a floating popup in its own right (see the portal note in concepttypeahead.js) — here
   it's `portalMenu: false`, so it's just the results half of the one raised card above: no
   independent background/shadow/radius of its own. */
.concept-picker .concept-typeahead-menu {
    position: static;
    width: 100%;
    margin-top: 0.3rem;
    padding: 0;
    background: transparent;
    border: none;
    border-radius: 0;
    box-shadow: none;
}

.concept-picker-code-input {
    width: 100%;
    box-sizing: border-box;
    padding: 6px 8px;
    font: inherit;
    border: 1px solid var(--btn-border);
    border-radius: 6px;
    background: var(--btn-surface);
    color: var(--btn-text);
}

.concept-picker-spec-input {
    width: 100%;
    box-sizing: border-box;
    min-height: 3.6rem;
    padding: 6px 8px;
    font: inherit;
    resize: vertical;
    border: 1px solid var(--btn-border);
    border-radius: 6px;
    background: var(--btn-surface);
    color: var(--btn-text);
}

.concept-picker-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}

/* The row's whole body — quote text plus, when open, its takes/rival-readings detail — grows and
   shrinks as ONE clipped unit via max-height, rather than the quote text sitting at a fixed full
   height with only the detail beneath it separately animating (two independently-timed height
   changes stacked on each other read as uneven, two-stage motion once the quote text itself
   needed to be part of what collapses). Collapsed, max-height clips it down to a single line —
   a peek of the quote's start, not the whole thing — and open, it grows to `max-content`, the
   row's own real content height, not an arbitrary fixed cap. An earlier version animated to a
   generous fixed px cap instead; since real content is usually far shorter than that cap, the
   opening row visually finished almost immediately and then sat there while the number kept
   climbing pointlessly for the rest of the duration, while the closing row's max-height had to
   fall all the way past its own content height before any visible shrinking began — so it stayed
   looking fully open for most of the duration and only visibly collapsed right at the end. Two
   rows animating on the same clock but with all their VISIBLE motion bunched at opposite ends of
   it read as sequential (open, pause, then close) rather than simultaneous, even though both were
   animating the whole time. `interpolate-size: allow-keywords` is what makes `max-content`
   transitionable at all (by default only two lengths can interpolate, not a length and a
   keyword) — recent enough that unsupported browsers just snap instead of transitioning, no worse
   than before. @starting-style, not a JS-driven rAF class toggle, animates the very first style
   change on this fresh DOM insertion (the panel fully re-renders on every state change). */
.source-quote-body {
    interpolate-size: allow-keywords;
    max-height: calc(1lh - 3px);
    overflow: hidden;
    transition: max-height 0.3s ease;
}

.source-quote-item.expanded .source-quote-body {
    max-height: max-content;
}

@starting-style {
    .source-quote-item.expanded .source-quote-body {
        max-height: calc(1lh - 3px);
    }
}

/* A row patched in place while already open (SourcePanel._refreshQuoteRow, e.g. switching which
   take's pill is active) still gets a fresh `.source-quote-body` node — without this, the
   `@starting-style` rule above can't tell that apart from an actual expand, and replays the
   collapsed-to-open grow on a row that never closed. */
.source-quote-item.no-open-anim .source-quote-body {
    transition: none;
}

/* The row a click just switched away from: still rendering full content (buildQuoteRow's
   `closing`), so its actual/final size already falls out of the base rule above (one line, no
   .expanded) — only the starting point needs stating, to interpolate the shrink instead of
   snapping shut the instant the newly-clicked row starts to grow. */
@starting-style {
    .source-quote-item.closing .source-quote-body {
        max-height: max-content;
    }
}

/* The detail sits directly under the quote (always rendered, see buildQuoteRow), so no gap or
   background of its own is needed — the panel's own grey background is dropped here for the same
   reason a rule dividing it from the quote above would be: the row's accent border/ring already
   marks it as one open unit. */
.source-quote-item .source-quote-body .source-current-quote {
    margin-top: 0;
    padding: 0.5rem 0.6rem 0.6rem;
    background: none;
    border-radius: 0;
}

/* The quote text plus its own bare-tag rail, side by side (2026-08-24 follow-up: the rail
   replaces the "Tagged" row that used to sit beneath the quote, putting the tags right next to
   the claim they're attached to). Only built expanded/closing (buildQuoteRow) — collapsed, it's
   just .source-interp-quote's clipped one-line peek, no rail alongside it. No border of its own
   any more: `.interp-section-divider` is what now separates the quotation+rail from the
   interpretations below, only appearing when there's a stack to separate from. */
.source-quote-top {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
}

.source-quote-top .source-interp-quote {
    flex: 1;
    min-width: 0;
}

/* Vertically stacked, right-aligned against the panel's edge rather than wrapped in a row — the
   "+" that files a new tag lives in the same stack, last. */
.quote-tag-rail {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.3rem;
    flex-shrink: 0;
    padding-top: 0.1rem;
}

@media (prefers-reduced-motion: reduce) {
    .source-quote-body {
        transition: none;
    }
}

.when-line {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.when-label {
    font-weight: 600;
}

.when-control {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

.when-control .when-type {
    padding: 2px 6px;
}

.when-control .when-periods {
    padding: 2px 6px;
    min-width: 16ch;
}

.search-section {
    margin: 18px 0 6px;
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #888;
}

.search-page-results {
    list-style: none;
    margin: 0;
    padding: 0;
}

.search-page-results li {
    padding: 8px 0;
    border-bottom: 1px solid #f0f0f0;
}

.result-snippet {
    color: #666;
    margin-top: 2px;
}

.result-snippet mark {
    background: #fff3b0;
    color: inherit;
    padding: 0 1px;
}

.search-empty {
    color: #888;
}



.new-concept-visibility {
    font-size: 0.85rem;
    display: flex;
    align-items: center;
    gap: 6px;
    cursor: pointer;
}


.site-container {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    margin: 0 auto;
    padding: 20px 20px 0;
    max-width: 800px;
}

/* Tool-like pages (e.g. the transcript editor) opt out of the narrow reading column.
   The base rule's `margin: 0 auto` must be cleared: as a flex item in the column-flex
   body, auto cross-axis margins suppress the default stretch and instead centre the
   container on its content width — which collapses to a narrow wedge whenever the content
   is narrow (e.g. the line-edit block), leaving big empty gutters either side. */
.site-container.wide {
    max-width: none;
    margin: 0;
    overflow: hidden;
    padding: 20px 20px 0;
    display: flex;
    flex-direction: column;
}

/* Source/Comments tab bar (YouTube, signed-in only — see index.js showCommentsTab). Sticky within
   .source-main's own scroll so it stays reachable no matter how far a long comment thread scrolls.
   Follows the site's unstyled/ghost button conventions rather than introducing a new button
   variant: an inactive tab reads as a plain label, the active one gets an accent underline. */
.source-tabs {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    gap: 4px;
    background: light-dark(#fff, #1a1a1a);
    border-bottom: 1px solid light-dark(#e0e0e0, #444);
    margin-bottom: 12px;
}

button.source-tab {
    border: none;
    border-radius: 0;
    background: none;
    box-shadow: none;
    padding: 8px 4px;
    margin-right: 16px;
    font-weight: 400;
    color: light-dark(#666, #999);
    cursor: pointer;
    border-bottom: 2px solid transparent;
    transform: none;
}

button.source-tab:hover,
button.source-tab:active {
    background: none;
    box-shadow: none;
    transform: none;
    color: var(--btn-text);
}

button.source-tab.active {
    color: var(--btn-text);
    border-bottom-color: var(--btn-accent);
    font-weight: 600;
}

.source-tab-panel[hidden] {
    display: none;
}

/* Empty-state hint shown in the Comments tab before any fetch has happened. */
.source-comments-empty {
    color: light-dark(#666, #999);
}

/* "Replace comments" peer action above a Statement's pasted CommentsView (public/sourceeditor.js). */
.source-comments-actions {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 8px;
}

.source-header {
    margin-bottom: 12px;
}

.source-header h2 {
    margin: 0 0 2px;
}

/* Title + source link share a row so the title no longer claims the full page width (the
   fixed .source-header comment that used to justify the footer placement for the link doesn't
   apply here — see index.js). flex-basis caps the title at a readable width and lets it shrink
   on narrow screens; the link wraps to its own line below if there's no room beside it. */
.source-title-row {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 4px 12px;
}

/* The title reads as the page heading but is an editable field, so the source's
   title can be renamed from its editor page. Borderless until focused. */
.source-title-input {
    width: auto;
    flex: 0 1 560px;
    min-width: 0;
    margin: 0 0 2px;
    padding: 2px 4px;
    border: 1px solid transparent;
    border-radius: 4px;
    background: transparent;
    font: inherit;
    font-size: 1.5rem;
    font-weight: 600;
    line-height: 1.2;
    color: inherit;
}

.source-title-input:hover {
    border-color: light-dark(#ddd, #444);
}

.source-title-input:focus {
    outline: none;
    border-color: var(--btn-accent);
    background: light-dark(#fff, #1a1a1a);
}

.source-editor-actions {
    margin-top: 12px;
    display: flex;
    align-items: center;
    gap: 10px;
}

/* Plaintext-only contenteditable, borderless until focus like the title field. */
.source-meta-field {
    margin-top: 2px;
    padding: 2px 4px;
    min-height: 1.2em;
    border: 1px solid transparent;
    border-radius: 4px;
    background: transparent;
    font: inherit;
    font-weight: 400;
    line-height: 1.3;
    color: inherit;
    white-space: pre-wrap;
    word-break: break-word;
}

.source-meta-field:hover {
    border-color: light-dark(#ddd, #444);
}

.source-meta-field:focus {
    outline: none;
    border-color: var(--btn-accent);
    background: light-dark(#fff, #1a1a1a);
}

/* No native placeholder on contenteditable — surface data-placeholder when empty. */
.source-meta-field:empty::before {
    content: attr(data-placeholder);
    color: light-dark(#999, #666);
    font-weight: 400;
}

/* Dual-purpose URL row, sitting beside the title (index.js's urlFieldMarkup): a compact
   open-in-new-tab link, the editable field it toggles with, and the pencil that toggles them.
   Only one of link/field shows at a time. A read-only viewer (no canEditMeta) gets just the
   bare <a> below, no wrapper. */
.source-url-field {
    margin-top: 0;
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}

.source-url-field .source-meta-field {
    margin-top: 0;
    flex: 1 1 220px;
}

/* The source's own date, sitting beside the title/URL — narrower than the URL field since it's
   a native date picker, not a link to fit. */
.source-date-field {
    margin-top: 0;
    flex: 0 1 160px;
}

/* The link itself — small and secondary next to the title's own weight, for both the
   canEditMeta pair above and the plain read-only link everyone else gets. */
.source-title-link {
    font-size: 0.85rem;
    color: var(--btn-accent);
    text-decoration: none;
    white-space: nowrap;
}

.source-title-link:hover {
    text-decoration: underline;
}

/* The edit pencil next to the source link: still a real <button> for a click target and
   keyboard focus, but deliberately not styled as one — no border, no fill, no hover
   lift/shadow, just a small muted glyph that darkens on hover. Mirrors .tag-remove-icon's
   border-none override, taken further since this sits beside a bare link, not a bordered row. */
.source-url-edit-btn {
    padding: 2px;
    border: none;
    border-radius: 0;
    background: none;
    color: light-dark(#999, #777);
    line-height: 0;
}

.source-url-edit-btn svg {
    width: 15px;
    height: 15px;
}

.source-url-edit-btn:hover {
    background: none;
    box-shadow: none;
    transform: none;
    color: var(--btn-accent);
}

.source-url-edit-btn:active {
    transform: none;
}

/* Compact borderless icon button — the ✕ (projectcontrols.js) that REMOVES a source from a
   project, an unlink rather than a deletion, hence its own small glyph rather than the
   full-size .icon-btn treatment the footer trash button now uses. */
.tag-remove-icon {
    border: none;
    padding: 6px;
    line-height: 0;
}

.tag-remove-icon svg {
    display: block;
}

/* Prose editor (Statement sources): a read/edit toggle above the prose body. */
.prose-toolbar {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 10px;
}

.prose-toolbar-hint {
    color: light-dark(#888, #888);
}

.prose-doc {
    line-height: 1.6;
}

.prose-doc p,
.prose-doc li {
    margin: 0 0 0.8em;
}

/* Read mode: dragging a span pulls a quote, so the text reads as selectable prose. */
.prose-doc:not(.prose-doc-editing) {
    cursor: text;
    user-select: text;
}

/* Edit mode: a plain contenteditable for pasting/fixing the published text. */
.prose-doc-editing {
    outline: none;
    border: 1px solid light-dark(#c9c9c9, #555);
    border-radius: 6px;
    padding: 10px 12px;
    min-height: 6em;
}

.prose-doc-empty {
    color: light-dark(#888, #888);
    font-style: italic;
}

/* Curated passages in a Statement — char-precise marks (the transcript gives no resting
   highlight, since a whole timed line is coarser than the captured text). */
/* Dim: every quote in the viewed user's set. Bold: the current quote. */
.prose-quote {
    background: light-dark(#fbf2d3, #3a3318);
    color: inherit;
    cursor: pointer;
    border-radius: 3px;
}

.prose-quote-bold {
    background: light-dark(#ffe38a, #5c4a12);
}

.prose-quote:hover {
    background: light-dark(#ffeaa0, #5a4d20);
}

/* Where several quotes overlap, a cycle cursor signals "click to step through them". */
.prose-quote-multi {
    cursor: url('/cycle-cursor.svg') 14 14, pointer;
}

/* Semantic blocks. A heading groups what follows; a blockquote is someone other than the
   source author speaking; a paragraph number is the citation handle (painted, not text). */
.prose-doc .prose-heading {
    font-size: 1.05rem;
    font-weight: 600;
    margin: 1.2em 0 0.5em;
    line-height: 1.3;
}

.prose-doc .prose-quote {
    margin: 0 0 0.8em;
    padding: 0.1em 0 0.1em 1em;
    border-left: 3px solid light-dark(#c9c9c9, #555);
    color: light-dark(#444, #c4c4c4);
    font-style: normal;
}

.prose-doc .prose-cite {
    display: block;
    margin-top: 0.3em;
    font-style: normal;
    font-size: 0.85rem;
    font-weight: 600;
    color: light-dark(#888, #999);
}

/* Paragraph number: an out-of-flow hanging marker so it never enters the text model. */
.prose-doc [data-num] {
    position: relative;
}

.prose-doc [data-num]::before {
    content: "[" attr(data-num) "]";
    position: absolute;
    left: -3.2em;
    width: 2.8em;
    text-align: right;
    color: light-dark(#999, #888);
    font-size: 0.85rem;
    user-select: none;
}

/* Per-block tagging: a left gutter handle + (when open) inline controls. */
.prose-row {
    display: flex;
    align-items: stretch;
    gap: 8px;
}

.prose-rowcol {
    flex: 1;
    min-width: 0;
}

.prose-row > :not(.prose-gutter) {
    flex: 1;
    min-width: 0;
}

.prose-gutter {
    flex: 0 0 auto;
    width: 14px;
    padding: 0;
    position: relative;
    border: 1px solid transparent;
    border-radius: 4px;
    background: transparent;
    cursor: pointer;
    user-select: none;
}

.prose-gutter::before {
    content: "";
    position: absolute;
    top: 4px;
    bottom: 4px;
    left: 50%;
    width: 2px;
    transform: translateX(-1px);
    border-radius: 1px;
    background: light-dark(#d4d4d4, #4d4d4d);
}

.prose-gutter:hover {
    background: light-dark(#eef4fb, #243240);
    border-color: light-dark(#cfe0f0, #2f4a60);
}

.prose-gutter:hover::before {
    background: light-dark(#0a66c2, #6db3f2);
}

.prose-gutter-active {
    background: light-dark(#fff3cd, #4a3f1a);
    border-color: light-dark(#e0b400, #8a6d00);
}

.prose-gutter-active::before {
    background: light-dark(#8a6d00, #e0b400);
}

.prose-blockedit {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 12px;
    margin-bottom: 8px;
    padding: 10px 12px;
    border: 1px solid light-dark(#e0b400, #8a6d00);
    border-radius: 6px;
    background: light-dark(#fffbe9, #2c2716);
}

.prose-blockedit-types {
    display: flex;
    gap: 6px;
}

.prose-blockedit-types button {
    padding: 3px 10px;
    font-size: 0.85rem;
}

.prose-blockedit-field {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.85rem;
    color: light-dark(#555, #bbb);
}

.prose-blockedit-field input {
    padding: 3px 6px;
}

.prose-blockedit-text {
    flex-basis: 100%;
    width: 100%;
    padding: 6px 8px;
    font: inherit;
    resize: vertical;
    border: 1px solid light-dark(#ccc, #555);
    border-radius: 4px;
    background: light-dark(#fff, #1e1e1e);
    color: inherit;
    box-sizing: border-box;
}

.prose-blockedit-note {
    flex-basis: 100%;
    margin: 0;
    color: light-dark(#777, #999);
}

.prose-blockedit-actions {
    flex-basis: 100%;
    display: flex;
    align-items: center;
    gap: 8px;
}

/* The quote panel scrolls within whatever height remains below the video. */
.source-panel {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding-bottom: 16px;
}

.source-panel-title {
    margin: 0 0 4px;
}

/* "Projects" heading + its discrete "+" add toggle on one row. */
.source-projects-head {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

/* The add form starts hidden; .example-add's display:flex would otherwise beat the UA
   [hidden] rule and keep it visible, so re-assert none here. */
.source-projects .example-add[hidden] {
    display: none;
}

/* Compact circular "+" that summons the project add form (hidden until clicked). */
.project-add-toggle {
    padding: 0;
    width: 22px;
    height: 22px;
    line-height: 1;
    font-size: 1rem;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* Fixed block above the scrolling quote panel — doesn't grow/shrink with it. */
.source-projects {
    flex: 0 0 auto;
    padding-bottom: 12px;
    margin-bottom: 12px;
    border-bottom: 1px solid light-dark(#e0e0e0, #444);
}

.source-panel-hint {
    color: light-dark(#888, #888);
    margin: 0;
}

.source-panel-locked-notice {
    margin: 0;
    padding: 8px 10px;
    color: light-dark(#6b5800, #d9c879);
    background: light-dark(#fffbe9, #2c2716);
    border: 1px solid light-dark(#e0b400, #8a6d00);
    border-radius: 6px;
}

/* Locked panel: the current quote is inert until the document text is saved. */
.source-panel-locked .source-interp-own {
    opacity: 0.55;
    pointer-events: none;
}

/* The current quote card. */
.source-interp-own {
    margin-top: 8px;
    padding: 8px 10px;
    background: light-dark(#f9f9f9, #2a2a2a);
    border-radius: 6px;
}

/* This IS the row: rendered identically whether the row is collapsed, expanded or mid-close (see
   buildQuoteRow), and it's the row's own click/keyboard open-close toggle (role="button", see
   quoteEl in buildDisplayQuote) — clicking or Enter/Space-ing the words toggles it, in either
   direction. .source-quote-play sub-elements and the quote actions opt back out of that so
   seeking and editing/deleting the quote aren't swallowed by it. */
.source-interp-quote {
    padding: 0 0.6rem;
    white-space: pre-wrap;
    overflow-wrap: break-word;
    color: light-dark(#333, #ddd);
    cursor: pointer;
}

.source-interp-quote:hover {
    background: var(--btn-surface-hover);
}

/* Remove-this-quote control follows the quote (below, trailing) rather than sitting beside it
   in a fixed top corner disconnected from what it acts on. */
.source-quote-range {
    display: block;
}

.source-quote-range-body {
    min-width: 0;
}

.source-quote-range-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 4px;
    margin-top: 2px;
}

/* A PDF quote's automatically-decided page number (schema.sql's Quotes.Page) — plain read-only
   text, pinned to the left of the row, distinct from the icon actions (edit/revert/remove) it
   sits beside rather than one more of them. Never editable: see sourcecontrols.js's
   _buildPageIndicator. */
.source-quote-page {
    margin-right: auto;
    font-size: 0.78rem;
    color: light-dark(#777, #999);
}

.source-quote-remove {
    border: none;
    padding: 0 4px;
    line-height: 1.2;
    font-size: 0.95rem;
    background: none;
}

.source-quote-remove svg {
    display: block;
    width: 14px;
    height: 14px;
}

/* The inline Cancel that appears next to an armed "Sure?" remove button, and the "Done" that
   closes the DisplayText editor — both small, low-emphasis actions in this compact row. */
.source-quote-range-actions button.ghost {
    padding: 0 6px;
    line-height: 1.2;
    font-size: 0.78rem;
}

.source-quote-edit, .source-quote-revert {
    border: none;
    padding: 0 4px;
    line-height: 1.2;
    font-size: 0.78rem;
    background: none;
}

.source-quote-edit svg, .source-quote-revert svg {
    display: block;
    width: 14px;
    height: 14px;
}

/* The reworded-quote flag: a quiet reminder this isn't verbatim, not an alarm. */
.source-quote-edited-flag {
    margin-top: 2px;
    font-size: 0.75rem;
    font-style: italic;
    color: light-dark(#777, #999);
}

.source-quote-edit-field {
    width: 100%;
    box-sizing: border-box;
}

/* One speaker turn within a quote: the words, then a single muted attribution. */
.source-quote-group + .source-quote-group {
    margin-top: 8px;
}


.source-quote-cite {
    margin-top: 2px;
    text-align: right;
    font-size: 0.78rem;
    font-weight: 600;
    font-style: normal;
    color: light-dark(#777, #999);
}

/* Timed-transcript quote: clicking plays the video from this passage's start. */
.source-quote-play {
    cursor: pointer;
    border-radius: 4px;
    transition: background 0.15s;
}

.source-quote-play:hover {
    background: light-dark(#f1f1f1, #333);
}

@media (max-width: 800px) {
    body {
        overflow-y: auto;
    }

    /* On desktop the header is a non-scrolling flex child of an overflow:hidden body; here the
       body scrolls, so `sticky` reproduces the same "S / Go to / bell bar stays put" behaviour.
       Its own opaque background (--header-bg) covers the content scrolling under it. */
    .site-header {
        position: sticky;
        top: 0;
        z-index: 30;
    }

    /* Same for the footer action bar, so Done / Publish / Copy Essay stay reachable while
       editing instead of sitting past the bottom of a long page — matching desktop, where the
       footer never scrolls. Only when it actually holds buttons; an empty footer shouldn't
       reserve a strip. It needs its own opaque background here since content now scrolls
       beneath it, and the resting margin below it has to go or it floats above the edge. */
    footer:has(button:not([hidden])) {
        position: sticky;
        bottom: 0;
        z-index: 30;
        margin-bottom: 0;
        background: light-dark(#fff, #1a1a1a);
    }

    .site-container, .site-container.wide {
        flex: none;
        overflow-y: visible;
        padding-top: 20px;
    }
}

/* Mobile-first: full width on small screens */
@media (max-width: 768px) {
    .site-container {
        max-width: 100%;
        padding-left: 15px;
        padding-right: 15px;
    }

    footer {
        max-width: 100%;
        padding: 0 15px;
    }

    .banner-title {
        font-size: 2rem;
        letter-spacing: 1px;
    }
}

/* Below 768px's shrunk wordmark still crowds the search box/notif bell/avatar off a phone-width
   header (verified via scripts/screenshot.mjs's iPhone 15 profile) — swap to the icon-only mark. */
@media (max-width: 480px) {
    .banner-title-text {
        display: none;
    }

    .banner-title-mark {
        display: inline-block;
    }
}

.summary {
    font-weight: bold;
}

.field-label {
    display: block;
    font-size: 0.8rem;
    color: var(--cmap-muted, #666);
    margin: 0.8rem 0 0.2rem;
}

/* Row holding the concept name/heading and its Help toggle (conceptHelpToggle in src/index.js),
   on /concepts/new and the concept page. */
.concept-editor-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
}

.concept-editor-head h2 {
    margin: 0;
}

/* On the concept page .concept-editor-head now holds only the Help toggle (Code moved into
   .concept-top-row below) — margin-left:auto keeps it pinned right whether it's alone (concept
   page) or paired with an h2 via justify-content:space-between (/concepts/new); auto margins
   claim the free space first, so it wins over space-between either way. */
.concept-editor-head .concept-help-toggle {
    margin-left: auto;
}

/* Code + Specification share the reading column's width: Code keeps its normal shrink-to-fit
   size (align-items:flex-start stops it being stretched to Specification's height), Specification
   takes the rest of the row and grows downward as its content grows. Explanation stays full-width
   below, unaffected. */
.concept-top-row {
    display: flex;
    align-items: flex-start;
    gap: 16px;
}

.concept-top-row > :first-child {
    flex: 0 0 auto;
}

.concept-top-row > :last-child {
    flex: 1 1 0;
    min-width: 0;
}

.concept-top-row > .concept-desc.single-line {
    margin-top: 0;
}

.concept-top-row .concept-desc.single-line {
    min-height: 5.5rem;
}

/* Phones: stack Code above Specification in portrait (not enough width for both side by side),
   keep them side by side in landscape. Tablets/desktops always keep the row regardless of
   orientation — only the narrow breakpoint is tight enough to need stacking. */
@media (max-width: 768px) and (orientation: portrait) {
    .concept-top-row {
        flex-direction: column;
    }

    .concept-top-row > :last-child {
        width: 100%;
    }
}

.concept-divider {
    margin: 1.25rem 0 0;
    border: none;
    border-top: 1px solid light-dark(#ddd, #444);
}

/* "?" Help toggle: a compact circle rather than a labelled button, since it sits right next to
   the thing it explains (the heading, or the map's own header) and needs no further caption. */
.concept-help-toggle {
    width: 32px;
    height: 32px;
    padding: 0;
    flex-shrink: 0;
    border-radius: 50%;
    font-size: 1.15rem;
    font-weight: 700;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* Credited concepts (docs/design/creditedConcepts.md): the "Credited to: ..." byline, its
   optional og:image thumbnail (fills the space Explanation would otherwise occupy, since a
   credited concept never shows one), and the "who thought of this?" picker's fields/hint. */
.concept-credit { margin: 0.25rem 0; }
.concept-credit-image { margin: 0.5rem 0; }
.concept-credit-image img { max-width: 100%; max-height: 12rem; border-radius: 6px; display: block; }
.concept-credit-picker { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1rem; margin: 0.5rem 0; }
.concept-credited-fields { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem 1rem; flex-basis: 100%; }
/* display:flex above beats the UA [hidden]{display:none} — restore it so "Me" actually hides Who/Link. */
.concept-credited-fields[hidden] { display: none; }
.concept-credit-hint { flex-basis: 100%; font-size: 0.85em; }
.concept-steward-control { display: flex; align-items: center; gap: 0.5rem; margin: 0.5rem 0; }

/* Wraps the page's own content (.concept-editor-main) and the Help panel (conceptHelpPanel in
   src/index.js) as siblings. Below the wide breakpoint this is just a plain block — the panel is
   position:fixed regardless of where it sits in the DOM, so nesting doesn't matter there. At the
   wide breakpoint it becomes a real flex row, so the browser centers .concept-editor-main in
   whatever width is actually left over once the panel takes its share — the layout math a
   fixed-position rail plus a margin can't do on its own (margin:auto only splits leftover space
   evenly when *both* margins are auto; pin one to a fixed px value and the other absorbs 100% of
   the slack, which is what produced the lopsided "empty gap, column, panel" layout this replaced). */
.concept-editor-main {
    min-width: 0;
}

/* Help panel: a centred, backdropped overlay on narrow screens (a "popup"); a plain boxed card
   sitting beside the content on wide ones (a "split view") — see the min-width block below. Only
   the open/close wiring is JS (concepthelp.js InitConceptHelp, which toggles the .open class);
   which presentation applies, and both open/close animations, are pure CSS. Modelled on
   the .sheet-scrim/.sheet-panel pair (opacity+visibility on the scrim, a transform on the panel,
   driven by a class rather than [hidden] so the transition actually has something to animate).

   Timing is deliberately asymmetric, and every open/close pair below follows the same rule: the
   entrance is slower and decelerating (--help-ease-out settles the panel into place), the exit is
   quicker and accelerating (--help-ease-in gets it out of the way). Opening is a reveal the eye
   needs to follow; closing is a decision the user already made, so making the exit as long as the
   entrance just reads as lag. */
.concept-help-overlay {
    --help-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
    --help-ease-in: cubic-bezier(0.4, 0, 1, 1);
    --help-in: 0.24s;
    --help-out: 0.14s;
    /* The wide-screen reveal moves the reading column as well as the panel, so it gets a little
       more time than the popup's in-place fade — a layout shift that snaps is harder to follow
       than a card that snaps. Same in/out asymmetry. */
    --help-in-wide: 0.28s;
    --help-out-wide: 0.18s;
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    background: rgba(0, 0, 0, 0.45);
    opacity: 0;
    visibility: hidden;
    /* visibility is delayed by exactly the fade duration so it flips only once the scrim has
       finished fading — set it any earlier and the fade runs on an already-invisible element. */
    transition: opacity var(--help-out) var(--help-ease-in), visibility 0s linear var(--help-out);
}

.concept-help-overlay.open {
    opacity: 1;
    visibility: visible;
    transition: opacity var(--help-in) var(--help-ease-out), visibility 0s linear 0s;
}

.concept-help {
    position: relative;
    /* Fixed width rather than shrink-to-fit: .concept-help-overlay centers this box on both axes
       (align-items/justify-content: center), so without an explicit width it hugs whatever's the
       widest line of the *currently visible* content — collapsed <details> summaries are short,
       so the box opens narrow, then snaps wider the instant a section expands. */
    width: 100%;
    max-width: 480px;
    max-height: 80vh;
    overflow-y: auto;
    padding: 20px 24px;
    border-radius: 12px;
    /* Pale yellow marks this as read-only reference material, distinct from the editable page
       around it — reuses the site's existing "highlight" yellow (.prose-quote/.src-quote). */
    background: light-dark(#fbf2d3, #3a3318);
    border: 1px solid light-dark(#e6cf80, #6a5520);
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.3);
    color: light-dark(#3a3a3a, #ddd);
    transform: scale(0.96) translateY(8px);
    transition: transform var(--help-out) var(--help-ease-in);
}

/* Panels built from <details> sections resize instantly (no native transition on the browser's
   own open/close), so a box sized to its collapsed content jumps hard the moment a section opens.
   Pre-allocating partway toward the fully-expanded height (~440px) keeps any single toggle from
   reading as a big lurch, without leaving the collapsed state mostly empty. */
.concept-help:has(details) {
    min-height: 330px;
}

.concept-help-overlay.open .concept-help {
    transform: none;
    transition: transform var(--help-in) var(--help-ease-out);
}

.concept-help h3 {
    margin: 0.9rem 0 0.3rem;
    font-size: 1rem;
}

.concept-help h3:first-child {
    margin-top: 0;
}

.concept-help h5 {
    margin: 0.9rem 0 0.2rem;
    font-size: 0.88rem;
}

.concept-help p {
    margin: 0 1.0rem 0.6rem;
    line-height: 1.5;
}

.concept-help details > summary { 
    margin: 1.0rem 0;
    cursor: pointer;
}

.concept-help-close {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 30px;
    height: 30px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: var(--cmap-muted, #666);
    font-size: 1.2rem;
    line-height: 1;
}

.concept-help-close:hover {
    background: var(--cmap-hl, rgba(0, 0, 0, 0.06));
}

/* Wide screens: the panel becomes a real flex item beside the content instead of a fixed,
   backdropped overlay. .site-container's own max-width has to lift here (only for pages with
   this layout — :has() scopes it, other pages keep their normal 800px reading column) so the row
   has the full viewport width to divide between the two; .concept-editor-main puts its own
   800px cap back on so the reading column still looks the same width as it always has; the row's
   justify-content:center then centres the (main + panel) group as a unit in whatever's left of
   the viewport — when the panel is closed (width/margin 0, not just invisible) that's just main
   on its own, i.e. unchanged from the closed-panel look.
   The open/close animation here is a width+margin reveal rather than the mobile popup's
   opacity+transform: it's a flex item now, so growing it (rather than fading it in place) is what
   makes the reading column visibly slide over to make room. .concept-help itself keeps a fixed
   340px width throughout (only the outer, overflow:hidden .concept-help-overlay's width animates)
   so its text never re-wraps mid-transition — it's revealed/clipped by the outer box instead. */
@media (min-width: 1100px) {
    .site-container:has(.concept-editor-layout) {
        max-width: none;
        /* margin:0 auto (base rule) disables flex stretch for this item's cross-axis size, so
           without an explicit width it shrinks to fit its content instead of filling the row —
           invisible on most pages (long prose already wraps wide enough to mask it) but glaring
           on a concept with short field values. */
        width: 100%;
    }

    .concept-editor-layout {
        display: flex;
        justify-content: center;
        align-items: flex-start;
    }

    .concept-editor-main {
        flex: 0 1 800px;
    }

    .concept-help-overlay {
        /* visibility/opacity's closed values still come from the unscoped base rule above (and
           .open's visibility:visible still applies here too) — only the reveal mechanics change. */
        position: static;
        display: block;
        padding: 0;
        background: none;
        flex: 0 0 0;
        width: 0;
        margin-left: 0;
        overflow: hidden;
        /* Without this, the box's height stays auto = the full height of .concept-help's help
           text at all times, even while closed — width:0 + overflow:hidden only clips it
           horizontally, so the closed panel still silently occupied its whole natural content
           height and could push .concept-editor-layout past the viewport, toggling
           .site-container's scrollbar depending on window height alone. */
        max-height: 0;
        /* The `visibility 0s linear var(--help-out)` delay is load-bearing, and its absence was a
           real bug: the base rule sets visibility:hidden for the closed state, and an unlisted
           property applies on frame 0. Without the delay the panel went invisible instantly while
           the width/opacity transitions kept running on nothing — so the close read as "content
           pops out of existence, then an empty gap collapses" instead of one movement. The delay
           must match the longest exit duration below, not --help-out. */
        transition: flex-basis var(--help-out-wide) var(--help-ease-in),
                    width var(--help-out-wide) var(--help-ease-in),
                    max-height var(--help-out-wide) var(--help-ease-in),
                    margin-left var(--help-out-wide) var(--help-ease-in),
                    opacity var(--help-out-wide) var(--help-ease-in),
                    visibility 0s linear var(--help-out-wide);
    }

    .concept-help-overlay.open {
        flex: 0 0 340px;
        width: 340px;
        margin-left: 24px;
        /* Comfortably above any realistic help-text height; only needs to clear the tallest
           content so it never clips, not to match it exactly. */
        max-height: 2000px;
        /* Redeclared here (not just on the non-.open rule above): the unscoped
           .concept-help-overlay.open rule (mobile popup's opacity transition) matches this same
           two-class specificity, and without this override it wins for the `transition` property
           at this breakpoint too — silently dropping the width/margin/flex-basis animation. */
        transition: flex-basis var(--help-in-wide) var(--help-ease-out),
                    width var(--help-in-wide) var(--help-ease-out),
                    max-height var(--help-in-wide) var(--help-ease-out),
                    margin-left var(--help-in-wide) var(--help-ease-out),
                    opacity var(--help-in) var(--help-ease-out),
                    visibility 0s linear 0s;
    }

    .concept-help {
        width: 340px;
        max-width: 340px;
        /* Base rule caps this at 80vh with its own scrollbar for the mobile modal case; here the
           panel is a normal-flow flex item, so it should grow with its content and scroll as part
           of the page (.site-container's own overflow-y) — not carry a second, independent one. */
        max-height: none;
        overflow-y: visible;
        /* Cancel the mobile popup's scale reveal, but keep a small horizontal slide. The outer
           overlay is overflow:hidden and this card is a fixed 340px, so widening the overlay alone
           reveals the card by un-clipping it left-to-right — which exposes half-words at the clip
           edge for the whole animation. Sliding the card in behind that same clip means the text
           arrives as a block instead of being wiped into view a few characters at a time. */
        transform: translateX(16px);
        transition: transform var(--help-out-wide) var(--help-ease-in);
    }

    .concept-help-overlay.open .concept-help {
        transform: none;
        transition: transform var(--help-in-wide) var(--help-ease-out);
    }
}

/* Must stay after the min-width block: media queries add no specificity, so these same-specificity
   selectors only win the `transition` property by source order. Zeroing the durations (rather than
   `transition: none`) keeps the visibility delays working off the same variables — with `none`,
   visibility would flip on frame 0 and the panel would vanish before the class change settled. */
@media (prefers-reduced-motion: reduce) {
    .concept-help-overlay {
        --help-in: 0s;
        --help-out: 0s;
        --help-in-wide: 0s;
        --help-out-wide: 0s;
    }

    .concept-help,
    .concept-help-overlay.open .concept-help {
        transform: none;
    }
}

.concept-code {
    display: inline-block;
    min-width: 4ch;
    font-weight: 900;
    font-size: 1.15em;
    text-transform: uppercase;
    margin-right: 0.4em;
}

.author {
    font-style: italic;
    color: #555;
    text-align: right;
}

.single-line {
    padding: 5px;
    min-width: 10rem;
}

/* Code (the only .single-line field with a real length cap — CONCEPT_CODE_MAX_LEN /
   _MAX_LEN_PERSONAL in src/index.js) gets a fixed width instead of .single-line's shrink-to-fit
   sizing, so a longer personal code wraps onto a second line inside the box rather than growing
   it wider. Hyphens are a natural break point under default line-breaking (see
   .concept-pill-main), so a two-word code breaks as "loss-" / "aversion"; overflow-wrap:anywhere
   is just the fallback for an unbroken run (e.g. mid-edit, before the length/charset is enforced
   on save). */
.summary.single-line {
    width: 10rem;
    overflow-wrap: anywhere;
}

.multi-line {
    padding: 10px;
    min-height: 100px;
    margin-top: 10px;
}

/* Contenteditable affordance (only rendered where editcontrols.js has actually made the field
   editable — [contenteditable] — so a viewer without edit rights never sees a false hover cue).
   position: relative + inline-block give an empty field its own box to anchor the placeholder
   and, for .single-line, a line box to blink the caret in — a plain empty inline <span> can end
   up with no line box at all in some browsers, which reads as "no caret". */
.single-line[contenteditable],
.multi-line[contenteditable] {
    position: relative;
    border: 1px solid transparent;
    border-radius: 4px;
}

.single-line[contenteditable] {
    display: inline-block;
}

/* The rule above (as author CSS) beats the browser's built-in [hidden]{display:none} — same
   override as button[hidden] elsewhere in this file. Essay's Title shadow field
   (src/lib/essays.js) is a `.single-line[data-field-name]` kept `hidden` on purpose (the visible
   editing surface is `.essay-document`; the shadow field only exists so EntityEditor's generic
   readModel/saveDraft scan has something to read) but still gets swept up by
   initFieldControls()'s generic `contenteditable="plaintext-only"` wiring — without this, it
   silently reappears as a second live-updating title box. */
.single-line[contenteditable][hidden] {
    display: none;
}

/* Specification uses the single-line editing model (plain text, Enter blurs instead of
   breaking a paragraph) but reads as prose, not a short inline token like Code — give it
   .multi-line's spacing and keep it block-level so it takes the full paragraph width and
   wraps instead of shrinking to fit its content. */
.concept-desc.single-line {
    padding: 10px;
    margin-top: 10px;
}

.concept-desc.single-line[contenteditable] {
    display: block;
}

.single-line[contenteditable]:hover,
.multi-line[contenteditable]:hover {
    border-color: light-dark(#ddd, #444);
}

.single-line[contenteditable]:focus,
.multi-line[contenteditable]:focus {
    outline: none;
    border-color: var(--btn-accent);
    background: light-dark(#fff, #1a1a1a);
}

/* No native placeholder on contenteditable — surface data-placeholder when empty. Absolutely
   positioned so the placeholder never becomes part of the real line box — letting it sit in
   normal flow (e.g. via float) can visually cover the actual (real, if empty) caret position.
   Rich-text .multi-line fields always keep a trailing <p><br></p> (see normalizeContent in
   editcontrols.js), so they're never truly :empty — editcontrols.js toggles .rt-empty instead. */
.single-line[contenteditable]:empty::before,
.multi-line[contenteditable].rt-empty::before {
    content: attr(data-placeholder);
    position: absolute;
    color: light-dark(#999, #666);
    font-weight: 400;
    pointer-events: none;
}

.single-line[contenteditable]:empty::before {
    left: 6px;
    top: 6px;
    white-space: nowrap;
}

.multi-line[contenteditable].rt-empty::before {
    left: 11px;
    right: 11px;
    top: 11px;
}

/* Owner-editable project description: stays a click target when empty, showing its
   placeholder, so the owner can start typing a description that doesn't exist yet. */
.project-description {
    padding: 5px;
    min-height: 1.5rem;
}

.project-description:empty::before {
    content: attr(data-placeholder);
    color: light-dark(#555, #bbb);
}

/* An in-body sub-heading reads smaller than the page/section headings. */
.multi-line h3 {
    font-size: 1.05rem;
    font-weight: 700;
    margin: 0.6em 0 0.2em;
}

/* Author page: Bio is plain text (not the rich .multi-line editor's HTML), so newlines are
   kept as-is and shown via pre-wrap rather than markup. Editable Bio reuses the .multi-line
   box (padding/min-height) plus a plaintext-only placeholder like .project-description. */
.author-bio {
    white-space: pre-wrap;
    margin: 8px 0;
}

.author-bio-field {
    white-space: pre-wrap;
    word-break: break-word;
}

.author-bio-field:empty::before {
    content: attr(data-placeholder);
    color: light-dark(#555, #bbb);
}

/* Author page: ShortName is shown after this Author's first mention within one document render
   (schema.sql) — a single line, styled like .author-bio but without the pre-wrap a multi-line
   field needs. */
.author-shortname {
    color: light-dark(#555, #b7b7b7);
    font-size: 0.9rem;
    margin: 4px 0 8px;
}

.author-shortname-field {
    color: light-dark(#555, #b7b7b7);
    font-size: 0.9rem;
    margin: 4px 0 8px;
}

.author-shortname-field:empty::before {
    content: attr(data-placeholder);
    color: light-dark(#777, #999);
}

/* Author page: the public contact routes (docs/design/rightOfReply.md, "Amendment 2026-10-02"):
   one row per live route; the admin gets edit/retire per row and an add form. */
.author-route-list {
    list-style: none;
    padding: 0;
    margin: 8px 0;
}

.author-route {
    font-size: 0.9rem;
    margin: 6px 0;
    word-break: break-word;
}

.author-route-flag {
    color: light-dark(#8a5a00, #e0b050);
}

.author-route-edit-form,
.author-route-add {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin: 6px 0;
}

.author-route-edit-form input,
.author-route-add input {
    flex: 1 1 220px;
}

.author-claim-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 12px 0;
}

/* Rich-text formatting toolbar shown while a multi-line field is focused. */
.rt-toolbar {
    display: flex;
    gap: 4px;
    margin-top: 10px;
    margin-bottom: -4px;
}

.rt-toolbar[hidden] {
    display: none;
}

.rt-tool {
    padding: 4px 10px;
    font-size: 0.9rem;
    border-radius: 6px;
    min-width: 2rem;
}

.auth-card {
    max-width: 480px;
    margin: 16px auto;
    padding: 20px 24px;
    border-radius: 12px;
    background: #ffffff;
    box-shadow: 0 10px 24px rgba(44, 62, 80, 0.08);
}

.auth-card:first-of-type {
    margin-top: 40px;
}

.auth-card h2 {
    margin-top: 0;
}

.auth-card label {
    display: block;
    margin: 12px 0 6px;
    font-weight: 600;
}

.auth-card input {
    width: 100%;
    padding: 10px 12px;
    border-radius: 8px;
    border: 1px solid #ccd2db;
    font-size: 1rem;
}

.auth-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 16px;
}

.auth-status {
    margin-top: 16px;
    min-height: 20px;
    font-weight: 600;
}

/* Turnstile bot gate on /createaccount. The widget renders itself in an iframe we don't
   control, so this only reserves space — min-height holds the layout steady while the script
   loads, so the Create Account button doesn't jump down under the cursor mid-click. */
.turnstile-widget {
    margin-top: 16px;
    min-height: 65px;
}

.auth-card .data-mode-toggle {
    display: inline-flex;
    gap: 2px;
    margin: 12px 0;
    border: 1px solid #ccd2db;
    border-radius: 8px;
    padding: 2px;
}

.auth-card .data-mode-btn {
    border: none;
    background: none;
    padding: 6px 16px;
    border-radius: 6px;
    font: inherit;
    font-weight: 600;
    cursor: pointer;
    color: inherit;
}

.auth-card .data-mode-btn.active {
    background: var(--btn-accent);
    color: #fff;
}

.auth-card #export-checklist fieldset,
.auth-card #import-checklist fieldset {
    border: 1px solid #e0e0e0;
    border-radius: 8px;
    margin: 10px 0;
    padding: 10px 14px;
}

.auth-card #export-checklist legend,
.auth-card #import-checklist legend {
    font-weight: 600;
    padding: 0 6px;
}

.auth-card .data-checklist {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: 220px;
    overflow-y: auto;
}

.auth-card .data-checklist li {
    margin: 4px 0;
}

.auth-card .data-checklist label {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    font-weight: 400;
}

.auth-card .data-checklist input[type="checkbox"] {
    width: auto;
    flex: none;
}

.auth-card .data-checklist .import-item-reason {
    display: block;
    margin: 2px 0 0 26px;
    color: #b00020;
    font-weight: 400;
}

#userIcon {
    fill: black;
}

/* Signed-in state: an initials avatar circle rather than the person silhouette. */
#userIcon.signed-in {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--btn-accent);
    color: var(--btn-accent-text);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1;
    user-select: none;
}

#userIcon:hover {
    filter: brightness(1.1);
    cursor: pointer;
}

/* Help button: a plain neutral "?" circle, same treatment as .concept-help-toggle elsewhere —
   the accent teal is reserved for primary actions (CLAUDE.md Button Variants), not this. */
.help-button {
    width: 30px;
    height: 30px;
    padding: 0;
    border-radius: 50%;
    font-size: 18px;
    font-weight: bold;
    vertical-align: middle;
    margin-left: 8px;
}

/* Pale yellow reuses the site-wide help colour (.concept-help, .concept-pill-popover) rather than
   a plain white card, marking this as the same read-only reference content. */
[popover] {
    width: 300px;
    max-width: 90vw;
    padding: 16px;
    border: 1px solid light-dark(#e6cf80, #6a5520);
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
    background: light-dark(#fbf2d3, #3a3318);
    color: light-dark(#3a3a3a, #ddd);
    line-height: 1.5;
    margin: 0;
    top: anchor(bottom);
    left: anchor(left);
    margin-top: 8px;
}

[popover] p {
    margin: 8px 0;
}

[popover] p:first-child {
    margin-top: 0;
}

[popover] p:last-child {
    margin-bottom: 0;
}

/* Admin panel */
.admin-panel {
    max-width: 1200px;
    margin: 20px auto;
}

.admin-section {
    margin: 30px 0;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
}

.admin-section h3 {
    margin-top: 0;
}

#regoLinkForm {
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-width: 500px;
}

#regoLinkForm label {
    display: block;
    margin: 12px 0 6px;
    font-weight: 600;
}

#regoLinkForm input {
    padding: 8px;
    border: 1px solid #ccc;
    border-radius: 4px;
}

#regoLinksTable {
    width: 100%;
    border-collapse: collapse;
}

#regoLinksTable tr {
    border-bottom: 1px solid #ddd;
}

#regoLinksTable td,
#regoLinksTable th {
    text-align: left;
    padding: 8px;
}

#regoLinksTable thead tr {
    border-bottom: 2px solid #ccc;
}

.status-pending {
    color: #0066cc;
    font-weight: bold;
}

.status-expired {
    color: #a40000;
}

.status-ok {
    color: var(--btn-accent);
    font-weight: bold;
}

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

.status-fail {
    color: var(--btn-danger);
    font-weight: bold;
}

#dbHealthTable {
    width: 100%;
    border-collapse: collapse;
    margin-top: 16px;
}

#dbHealthTable tr {
    border-bottom: 1px solid #ddd;
}

#dbHealthTable td,
#dbHealthTable th {
    text-align: left;
    padding: 8px;
}

/* Generic muted secondary text, reused wherever a bare "nothing here yet" message needs to read
   as secondary rather than as content. */
.muted {
    color: light-dark(#666, #999);
}

/* Essay editor (docs/design/essay.md) */

.essay-editor {
    max-width: 44rem;
    margin: 0 auto;
    /* Anchors the floating .rt-toolbar (below) — it's inserted as .essay-document's sibling. */
    position: relative;
}

/* The ONE surface the author types into: Title and Body share it because they are the essay
   (docs/design/essay.md). Finding things to insert is a Ctrl+I menu that floats at the caret
   (.essay-insert-menu/.essay-insert-picker below), not a separate panel outside the box; Copy
   Essay lives in the site-wide footer bar instead, alongside Done. The first top-level child
   is always the title line, styled
   structurally rather than via a JS-assigned class since its tag can end up being whatever a
   <p> normalizes to. */
.essay-document {
    position: relative;
    padding: 2.75rem 1.5rem 1.25rem;
    border: 1px solid var(--btn-border);
    border-radius: 10px;
    background: light-dark(#fff, #22262b);
    outline: none;
}

.essay-document > :first-child {
    font-size: 1.6rem;
    font-weight: bold;
    margin-bottom: 0.5rem;
}

/* No native placeholder on contenteditable. essayeditor.js toggles title-empty/body-empty on
   sync() (distinct because Title and Body are two different completeness states of the one
   host); both are :empty-proof (a blank line is still `<p><br></p>`, never truly :empty). */
.essay-document.title-empty > :first-child::before {
    content: attr(data-title-placeholder);
    color: light-dark(#999, #666);
    font-weight: 400;
    pointer-events: none;
}

.essay-document.body-empty > :first-child + *::before {
    content: attr(data-body-placeholder);
    color: light-dark(#999, #666);
    font-weight: 400;
    pointer-events: none;
}

/* Pin the toolbar to the bottom edge of the site header — which never scrolls (on desktop the
   body doesn't scroll at all; on mobile .site-header is sticky) — so the formatting controls
   stay put no matter how far a long essay is scrolled. `position: fixed` rather than absolutely
   anchored to .essay-editor, which used to carry the toolbar off-screen with the content. It
   also can't collapse out of flow on blur: `.rt-toolbar[hidden]` sets `display: none`, and blur
   fires on MOUSEDOWN, so an in-flow toolbar shortened the page by its own height between
   mousedown and mouseup and lost the click; taken out of flow, showing/hiding it moves nothing.
   --header-h is kept current by site.js; the buttons are inset to line up with the centred
   essay column (clamped so they don't leave the viewport on a narrow screen). */
.essay-editor > .rt-toolbar {
    position: fixed;
    top: var(--header-h, 0px);
    left: 0;
    right: 0;
    margin: 0;
    z-index: 15;
    padding: 8px max(1.5rem, calc((100% - 44rem) / 2));
    background: light-dark(#f1f4f6, #2a2f36);
    border-bottom: 1px solid var(--btn-border);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
}

/* Ctrl+I "Insert" menu (essayeditor.js's openInsertMenu) — a vertical list of up to four
   mnemonic-lettered items, floating on `.concept-mention-picker`'s chrome. */
.essay-insert-menu {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* Overrides the generic `button` rule's centered/padded look for a flush-left menu-row shape,
   same override `.footer-overflow-menu button` uses for its own dropdown list. */
.essay-insert-menu-item {
    justify-content: flex-start;
    gap: 8px;
    width: 100%;
    padding: 8px 10px;
}

.essay-insert-menu-key {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.4rem;
    height: 1.4rem;
    padding: 0 4px;
    border: 1px solid var(--btn-border);
    border-radius: 4px;
    font-size: 0.75rem;
    font-weight: 700;
}

/* Quote/Reading picker (openReferencePicker) — same floating chrome as the Insert menu above it,
   plus a filter input (essay-insert-picker-input styling reused from concept-mention-input) pinned
   above the scrollable row list, since a source's quote list has no natural cap. */
.essay-insert-picker {
    display: flex;
    flex-direction: column;
    max-height: 60vh;
}

.essay-insert-picker > .concept-mention-input {
    flex: none;
    margin-bottom: 6px;
}

.essay-picker-rows {
    overflow-y: auto;
}

.essay-picker-quote {
    padding: 8px 6px;
    border-bottom: 1px solid light-dark(#e5e5e5, #333);
    border-radius: 6px;
    cursor: pointer;
}

.essay-picker-quote:hover {
    background: light-dark(#e6edf6, #313842);
}

.essay-picker-quote:last-child {
    border-bottom: none;
}

.essay-picker-quote-text {
    font-style: italic;
    margin: 0 0 6px;
}

/* One row per interpretation (not per concept-tag): its argument, then every concept it licenses
   as its own pill beneath. The flat one-row-per-tag listing this replaces repeated identical
   argument text once per concept, and showed a bare code with a dangling em-dash when the
   argument was empty. */
.essay-picker-interpretation {
    padding: 5px 6px;
    border-radius: 6px;
    cursor: pointer;
}

.essay-picker-interpretation:hover {
    background: light-dark(#e6edf6, #313842);
}

.essay-picker-interpretation:not(:last-child) {
    border-bottom: 1px solid light-dark(#e5e5e5, #333);
}

.essay-picker-quote.active,
.essay-picker-interpretation.active {
    background: light-dark(#e6edf6, #313842);
}

.essay-picker-interpretation-text {
    margin: 0;
}

.essay-picker-interpretation-text.muted {
    font-style: italic;
    color: light-dark(#7b858f, #868f99);
}

.essay-picker-interpretation-concepts,
.essay-interpretation-concepts {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    margin: 0.3rem 0 0;
}

/* A concept pill inside an essay — the embed and its picker share one look, so what you choose
   in the picker is what lands in the document. */
.essay-interpretation-code {
    display: inline-flex;
    align-items: center;
    padding: 0.1rem 0.45rem;
    border: 1px solid var(--btn-accent);
    border-radius: 99px;
    font-family: ui-monospace, Consolas, monospace;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.02em;
}

/* The Examples row inside a Quote/Interpretation card — click target for `openExamplePicker`
   (docs/design/essayExamplesAndBadge.md). Only rendered at all when the target has at least one
   Example, so a dashed underline here always means "click to choose which show." */
.essay-embed-examples {
    cursor: pointer;
    border-bottom: 1px dashed transparent;
    width: fit-content;
}

.essay-embed-examples:hover {
    border-bottom-color: var(--btn-accent);
}

.essay-embed-examples-more,
.essay-embed-examples-empty {
    font-size: 0.72rem;
    font-style: italic;
    color: light-dark(#7b858f, #868f99);
}

/* Atomic quote/interpretation embed — a block-level sibling of the document's <p>/<ul>/<h3> children,
   contenteditable="false" (set by JS). No reorder/remove controls: moved via cut/paste, removed
   via a whole-chip Backspace/Delete, same as an inline mention chip one level up. */
.essay-embed {
    margin: 10px 0;
    padding: 10px;
    border: 1px solid var(--btn-border);
    border-radius: 8px;
    cursor: default;
}

/* Clicking a chip selects it as a real Range (see .essay-chip-selected below) so
   Backspace/Delete/Cut work, but the browser's native text-highlight over its contents reads as
   "this text is selected/editable" — it isn't. `user-select: none` would suppress that paint too,
   but it also makes execCommand('cut') copy nothing, breaking cut/paste relocation — so instead
   this just makes the ::selection paint transparent, leaving the underlying Range (and its
   clipboard content) untouched. */
.essay-embed::selection,
.essay-embed *::selection {
    background: transparent;
}

.essay-quote-text {
    margin: 0 0 6px;
    padding-left: 12px;
    border-left: 3px solid var(--btn-accent);
    font-style: italic;
}

.essay-quote-cite {
    margin: 0;
    text-align: right;
    color: light-dark(#666, #999);
    font-size: 0.9rem;
}

/* An Interpretation gets the same oval/rounded box border as a Quote embed (both share
   `.essay-embed`'s base box) — what tells the two apart at a glance is no longer the shape but
   the "INTERPRETATION" label set into the top border (below), fieldset-legend style. Indented
   1.5rem so it visually reads as belonging to the Quote embed above it, not a second, independent
   citation. */
.essay-embed-interpretation {
    position: relative;
    margin-left: 1.5rem;
}

/* The label sits centered on the top border line, background-matched to `.essay-document`'s own
   background (light-dark(#fff, #22262b)) so it masks the border beneath it, the same way a native
   `<fieldset>`/`<legend>` pair breaks its border around the legend text. */
.essay-embed-interpretation::before {
    content: "INTERPRETATION";
    position: absolute;
    top: -0.62em;
    left: 14px;
    padding: 0 6px;
    background: light-dark(#fff, #22262b);
    font-family: ui-monospace, Consolas, monospace;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    color: light-dark(#7b858f, #868f99);
}

.essay-interpretation-text {
    margin: 0;
}

.essay-interpretation-cite {
    margin: 6px 0 0;
    text-align: right;
    color: light-dark(#666, #999);
    font-size: 0.9rem;
}

.essay-embed-missing {
    color: var(--btn-danger);
    font-style: italic;
    margin: 0;
}

.essay-paste-status {
    display: block;
    margin-top: 0.75rem;
    color: light-dark(#666, #999);
    font-size: 0.9rem;
}

.essay-list-page {
    max-width: 44rem;
    margin: 0 auto;
}

.essay-list-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.essay-list {
    list-style: none;
    padding: 0;
    margin-top: 1rem;
}

.essay-list li {
    padding: 10px 0;
    border-bottom: 1px solid light-dark(#e5e5e5, #333);
}

/* /publications — the published-artifacts overview + citation-drift review
   (docs/design/publishModel.md, publish model v2). */
.publications-page {
    max-width: 44rem;
    margin: 0 auto;
}

.publications-intro {
    color: light-dark(#5c6670, #9aa0a6);
    margin: 0.25rem 0 1rem;
}

.publications-list {
    list-style: none;
    padding: 0;
    margin: 0;
}

.pub-row {
    padding: 12px 0;
    border-bottom: 1px solid light-dark(#e5e5e5, #333);
}

.pub-row-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

.pub-kind {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: light-dark(#5c6670, #9aa0a6);
    border: 1px solid var(--btn-border);
    border-radius: 4px;
    padding: 1px 6px;
}

.pub-name {
    font-weight: 600;
    text-decoration: none;
    color: var(--btn-accent);
}

.pub-cte-link {
    margin-left: auto;
    font-size: 0.85rem;
    white-space: nowrap;
}

.pub-review {
    margin: 8px 0 2px;
    font-size: 0.9rem;
}

/* The panel summary is now the only "something's pending here" cue (no amber pill above it),
   so it carries full-strength body text + a little weight rather than muted fine print. */
.pub-review > summary {
    cursor: pointer;
    font-weight: 500;
}

/* An essay review panel that also carries "a concept you cite changed" — amber summary. */
.pub-essay-review.has-updates > summary {
    color: light-dark(#8a5a00, #f0c26b);
}

/* One reviewable change inside a panel: the "Your text edits" diff, or one changed concept. */
.pub-rev-section {
    margin: 12px 0;
    padding-top: 12px;
    border-top: 1px solid light-dark(#ececec, #2c2c2c);
}

.pub-rev-section:first-child {
    margin-top: 4px;
    padding-top: 0;
    border-top: 0;
}

.pub-rev-h {
    margin: 0 0 6px;
    font-size: 0.9rem;
    font-weight: 600;
}

.pub-drift-code {
    font-weight: 600;
}

.pub-review-hint {
    font-size: 0.8rem;
    color: light-dark(#5c6670, #9aa0a6);
}

/* The staged Keep / Use choice under each column of a concept's side-by-side. */
.pub-decide {
    font-size: 0.8rem;
    padding: 2px 10px;
    margin-top: auto;   /* sink to the column's bottom so the two choices line up */
    align-self: flex-start;
}

.pub-decide.is-chosen {
    border-color: var(--btn-accent);
    background: var(--btn-accent);
    color: var(--btn-accent-text);
}

/* In-situ before/after for one updated cited concept (citationCompareHtml). */
.pub-sxs {
    display: flex;
    gap: 12px;
}

.pub-sxs-col {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.pub-sxs-h {
    margin: 0 0 4px;
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: light-dark(#5c6670, #9aa0a6);
}

.pub-sxs-body {
    border: 1px solid light-dark(#e5e5e5, #333);
    border-radius: 6px;
    overflow-x: auto;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.82rem;
    line-height: 1.5;
    margin-bottom: 8px;
}

.pub-sxs-line {
    padding: 1px 10px;
    white-space: pre-wrap;
    word-break: break-word;
}

@media (max-width: 640px) {
    .pub-sxs {
        flex-direction: column;
    }

    /* stacked columns: keep each choice just below its own block, not pushed down the page */
    .pub-decide {
        margin-top: 0;
    }
}

.pub-review-foot {
    margin-top: 8px;
    display: flex;
    align-items: center;
    gap: 8px;
}

.pub-review-body {
    margin: 8px 0 4px;
}

.pub-diff-note {
    margin: 4px 0;
    color: light-dark(#5c6670, #9aa0a6);
    font-size: 0.85rem;
}

.pub-diff-note.error {
    color: var(--btn-danger);
}

/* Right of reply panel on /publications (public/replypanel.js, docs/design/rightOfReply.md):
   one section per Mentioned person, with the send/record steps as nested disclosures. */
.ror-body {
    margin: 8px 0 4px;
}

.ror-entry {
    border-top: 1px solid light-dark(#e5e5e5, #333);
    padding: 8px 0;
}

.ror-name {
    margin: 0 0 4px;
    font-size: 0.95rem;
}

.ror-route,
.ror-state {
    margin: 4px 0;
    word-break: break-word;
}

.ror-nominate {
    display: inline-flex;
    gap: 6px;
    align-items: center;
}

.ror-route-form,
.ror-sent-form {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 10px;
    align-items: center;
    margin: 6px 0;
}

.ror-sent-form label {
    display: inline-flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.ror-route-form input[type="text"],
.ror-sent-form input[type="text"] {
    min-width: 0;
    width: min(320px, 100%);
}

.ror-step {
    margin: 6px 0;
}

.ror-step > summary {
    cursor: pointer;
}

.ror-message {
    display: block;
    width: 100%;
    box-sizing: border-box;
    margin: 6px 0;
    font: inherit;
}

.ror-responses {
    margin: 4px 0;
    padding-left: 18px;
}

.ror-response-body {
    margin: 4px 0;
    white-space: pre-wrap;
}

.ror-quote {
    margin: 4px 0;
    padding-left: 8px;
    border-left: 3px solid var(--border, #ccc);
    font-style: italic;
}

.ror-admin-row {
    display: flex;
    gap: 8px;
    align-items: center;
    margin: 6px 0;
}

.pub-diff-body {
    border: 1px solid light-dark(#e5e5e5, #333);
    border-radius: 6px;
    overflow-x: auto;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.82rem;
    line-height: 1.5;
}

.pub-diff-line {
    display: flex;
    gap: 8px;
    padding: 1px 10px;
    white-space: pre-wrap;
    word-break: break-word;
}

.pub-diff-sign {
    flex: none;
    width: 1ch;
    color: light-dark(#8a8f96, #7a8087);
    user-select: none;
}

.pub-diff-ins {
    background: light-dark(#e7f6ec, #16301f);
    color: light-dark(#1f6f3f, #7ee2a4);
}

.pub-diff-del {
    background: light-dark(#fdecec, #34191a);
    color: light-dark(#b23b3b, #f0a3a3);
}

.pub-diff-gap {
    padding: 2px 10px;
    color: light-dark(#8a8f96, #7a8087);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.78rem;
    background: light-dark(#fafafa, #202020);
}

.pub-act-status {
    font-size: 0.8rem;
    color: light-dark(#5c6670, #9aa0a6);
}

.pub-act-status.error {
    color: var(--btn-danger);
}

.pub-archive,
.pub-publish,
.pub-essay-publish {
    font-size: 0.8rem;
    padding: 2px 10px;
}

.publications-archived {
    margin-top: 1.5rem;
}

.publications-archived > summary {
    cursor: pointer;
    color: light-dark(#5c6670, #9aa0a6);
    font-size: 0.9rem;
}

.pub-row-archived .pub-name {
    color: light-dark(#5c6670, #9aa0a6);
}

.pub-row-archived {
    opacity: 0.75;
}

/* Inline reference chips inside Essay prose (docs/design/essay.md). contenteditable="false"
   is set by JS (essayeditor.js), not here — this is purely the visual affordance that it's a
   reference, not ordinary text. */
.concept-mention,
.source-mention,
.example-mention {
    display: inline-block;
    padding: 0 6px;
    border-radius: 4px;
    background: light-dark(#e0f2f0, #123331);
    color: var(--btn-accent);
    font-weight: 600;
    text-decoration: none;
    cursor: default;
}

/* Same reasoning as .essay-embed's ::selection rule above: click-select is real (for
   Backspace/Delete/Cut) but the native highlight over the label reads as editable text. */
.concept-mention::selection,
.source-mention::selection,
.example-mention::selection {
    background: transparent;
}

/* A source mention names a work rather than a coinage, so it reads as a cited title (quieter,
   italic) instead of borrowing the accent colour a Code earns. */
.source-mention {
    background: light-dark(#eceef1, #2a2f36);
    color: var(--btn-text);
    font-weight: 500;
    font-style: italic;
}

/* An Example mention asserts a different relationship than a plain Concept mention ("this is an
   example of X" vs. just naming X), so it earns its own colour rather than sharing
   .concept-mention's teal — a violet tint, distinct from both the concept accent and the neutral
   source citation (docs/design/essayExamplesAndBadge.md, point 4). */
.example-mention {
    background: light-dark(#f1e6fb, #2f2440);
    color: light-dark(#7c4dbd, #c9a3f7);
}

/* Click (or Shift+Arrow across) a chip selects it as one atomic unit for Backspace/Delete/Cut —
   the browser's own text-selection highlight renders as disconnected per-line bars over a
   multi-line block embed, which doesn't read as "this whole card is selected", so this gives an
   unambiguous outline on top of it. Toggled by EssayDocument's selectionchange listener. */
.essay-chip-selected {
    outline: 2px solid var(--btn-accent);
    outline-offset: 2px;
    /* A slight fill (same grey as .btn-surface-hover elsewhere) reinforces that this is a
       selected read-only unit, not text sitting in an edit caret. */
    background: var(--btn-surface-hover);
}

.mention-broken {
    background: light-dark(#fbe4e1, #3a1f1c);
    color: var(--btn-danger);
    font-style: italic;
    font-weight: 400;
}

/* Empty placeholder standing in for the not-yet-chosen chip while the picker is open — a hair of
   width so it stays visible/positionable, no visible content of its own. */
.concept-mention-marker {
    display: inline-block;
    width: 1px;
}

/* Floating host for the caret-anchored ConceptTypeahead (position/left/top set inline by
   essayeditor.js). .concept-typeahead's own base rule assumes a flex-row parent (flex:1); this
   gives it a fixed width instead, same override .concept-picker uses elsewhere. */
.concept-mention-picker {
    z-index: 60;
    width: 18rem;
    padding: 4px;
    border-radius: 8px;
    background: light-dark(#fff, #25292e);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
}

.concept-mention-picker .concept-typeahead {
    display: block;
    width: 100%;
}

/* Example picker (openExamplePicker, essayeditor.js) — floats on the same chrome as the Ctrl+I
   pickers above, but is a plain live checklist: no search input, no insert action, stays open
   across multiple toggles until Escape or a click outside. */
.essay-example-picker {
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 60vh;
    overflow-y: auto;
}

.essay-example-picker-heading {
    margin: 2px 6px 4px;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: light-dark(#7b858f, #868f99);
}

.essay-example-picker-row {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 6px;
    border-radius: 6px;
    cursor: pointer;
}

.essay-example-picker-row:hover {
    background: light-dark(#e6edf6, #313842);
}

.essay-example-picker-row input[type="checkbox"] {
    align-self: center;
    flex-shrink: 0;
}

.essay-example-picker-why {
    font-size: 0.78rem;
    font-style: italic;
    color: light-dark(#7b858f, #868f99);
}

/* portalMenu: false (essayeditor.js) — the menu is an in-flow child of .concept-mention-picker,
   which is already the one floating, shadowed surface, so it gets no independent background/
   shadow/radius of its own (same reasoning as .concept-picker .concept-typeahead-menu above). */
.concept-mention-picker .concept-typeahead-menu {
    position: static;
    width: 100%;
    margin-top: 0.3rem;
    padding: 0;
    box-shadow: none;
    max-height: 200px;
}

.concept-mention-input {
    width: 100%;
    padding: 6px 8px;
    border: 1px solid var(--btn-border);
    border-radius: 6px;
    background: var(--btn-surface);
    color: var(--btn-text);
    font: inherit;
}

#dbHealthTable thead tr {
    border-bottom: 2px solid #ccc;
}

.copy-button {
    padding: 4px 12px;
    font-size: 0.85rem;
}

#closeReviewButton {
    margin: 10px;
}

/* Review modal */
#reviewModal {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0,0,0,0.5);
    z-index: 1000;
}

#reviewModal > div {
    background: var(--btn-surface);
    margin: 50px auto;
    padding: 20px;
    max-width: 800px;
    max-height: 80vh;
    overflow: auto;
    border-radius: 8px;
}

/* Dark mode styles */
@media (prefers-color-scheme: dark) {
    body {
        background-color: #1a1a1a;
        color: #e0e0e0;
    }
    
    .site-header {
        /* --header-bg already flips to the dark fill; only the divider is dark-mode-only. */
        border-bottom: 2px solid #444;
    }

    .summary {
        color: #e0e0e0;
    }

    .author {
        color: #b0b0b0;
    }

    .multi-line {
        background-color: #2a2a2a;
        color: #e0e0e0;
        border: 1px solid #444;
    }

    .auth-card {
        background: #1f1f1f;
        border-color: #3a3a3a;
        box-shadow: none;
    }

    .auth-card input {
        background: #2a2a2a;
        color: #e0e0e0;
        border-color: #3a3a3a;
    }

    /* Admin panel dark mode */
    .admin-section {
        background-color: #2a2a2a;
        border-color: #444;
    }

    #regoLinkForm input {
        background: #1f1f1f;
        color: #e0e0e0;
        border-color: #444;
    }

    #regoLinksTable {
        color: #e0e0e0;
    }

    #regoLinksTable thead {
        background-color: #1f1f1f;
    }

    #regoLinksTable tr {
        border-bottom-color: #444;
    }

}
/* ============================================================================
   Unified source editor (public/sourceeditor.js). One surface over the Doc
   model, replacing the prose and transcript editors; these mirror the old
   prose and tx rules so the source page looks unchanged. A timed (YouTube) doc
   shows time leads and a player; a Statement shows neither.
   ============================================================================ */

/* Undo/Replace text/Compare original — a horizontal group living in the sitewide footer now
   (2026-07-21 follow-up; Revert to original moves separately, into .footer-danger-group beside
   Delete, since it's destructive). */
.src-toolbar {
    display: flex;
    align-items: center;
    gap: 8px;
}

.src-toolbar button {
    white-space: nowrap;
}

/* display:flex above would defeat the hidden attribute (author rule beats UA [hidden]);
   restore it here — matches .rt-toolbar[hidden] below. */
.src-toolbar[hidden] {
    display: none;
}

/* Dirty-state warning only ("you have unsaved changes — don't draw quotes yet"): carries real
   state, unlike the clean-state usage tip that used to share this line (dropped 2026-07-21 as
   redundant with the panel's own "How this works" and Quotes hint). Sits above the transcript,
   in-flow, now that the toolbar buttons and video have moved off .source-main entirely. */
.src-toolbar-hint {
    margin: 0 0 10px;
    color: light-dark(#888, #888);
}

.src-toolbar-hint[hidden] {
    display: none;
}

.src-doc {
    line-height: 1.6;
    cursor: text;
    user-select: text;
}

.src-doc p,
.src-doc li {
    margin: 0 0 0.8em;
}

/* Comment text carries real author-typed line breaks straight through from the YouTube API,
   unpacked verbatim into a block's own text (commentThreadsToBlocks, src/index.js) — unlike
   prose/transcript blocks, whose ingestion normalizes internal whitespace, so this never applies
   there. Scoped to the comments view only, so a stray blank line in a comment renders as one,
   matching the "points of interest" quote panel (.source-interp-quote, below), which already
   shows the same text with its break intact. */
.src-comments-view {
    white-space: pre-wrap;
}

/* A paragraph immediately followed by another one (no .src-speaker label between them, so it's the
   same speaker continuing) sits closer to it than the gap before a new turn — a multi-paragraph
   turn should read as one voice, not a series of separate statements. Timed docs wrap each block in
   .src-blockrow (for the time column), so the same rule targets the wrapped <p>/<li> via the row's
   own next-sibling instead of the paragraph's. */
.src-doc p:has(+ p),
.src-doc li:has(+ li),
.src-doc .src-blockrow:has(+ .src-blockrow) p,
.src-doc .src-blockrow:has(+ .src-blockrow) li {
    margin-bottom: 0.3em;
}

/* A comment's own paragraph sits close to its reactions note (below) rather than the standard
   inter-paragraph gap, so the note visibly belongs to the comment above it, not the one after. */
.src-doc p:has(+ .src-reaction),
.src-doc li:has(+ .src-reaction) {
    margin-bottom: 0.1em;
}

.src-doc-empty {
    color: light-dark(#888, #888);
    font-style: italic;
}

/* Char-precise quote highlights: dim = every quote, bold = the current one. */
.src-quote {
    background: light-dark(#ffe0b0, #3a3318);
    color: inherit;
    cursor: pointer;
    border-radius: 3px;
}

.src-quote-bold {
    background: light-dark(#d0e080, #5c4a12);
}

/* pb decided hover unnecessary
.src-quote:hover {
    background: light-dark(#ffeaa0, #5a4d20);
}*/

.src-quote-multi {
    cursor: url('/cycle-cursor.svg') 14 14, pointer;
}

/* Semantic blocks: heading groups what follows; a quote is a non-source voice. */
.src-doc .src-heading {
    font-size: 1.05rem;
    font-weight: 600;
    margin: 1.2em 0 0.5em;
    line-height: 1.3;
}

.src-doc .src-blockquote {
    margin: 0 0 0.8em;
    padding: 0.1em 0 0.1em 1em;
    border-left: 3px solid light-dark(#c9c9c9, #555);
    color: light-dark(#444, #c4c4c4);
    font-style: normal;
}

.src-doc .src-cite {
    display: block;
    margin-top: 0.3em;
    font-style: normal;
    font-size: 0.85rem;
    font-weight: 600;
    color: light-dark(#888, #999);
}

/* Speaker turn label: the name heading a run of one speaker (the first is the document's author).
   Never itself editable — naming or renaming the speaker happens in the floating toolbar's field
   instead (.src-bt-speaker below). Plain text (a <div>) for a block-local, uncatalogued voice; a real
   <a> to the author page for one matched to the shared Authors catalog (a.src-speaker below) — the
   link styling is the only signal the match happened. */
.src-speaker {
    display: block;
    margin: 0.9em 0 0.2em;
    font-size: 0.82rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: light-dark(#555, #b7b7b7);
    max-width: min(20rem, 50%);
}

a.src-speaker {
    color: var(--btn-accent);
    text-decoration: underline;
    cursor: pointer;
}

a.src-speaker:hover {
    color: var(--btn-accent-hover);
}

.src-doc > .src-speaker:first-child,
.src-doc-live > .src-speaker:first-child {
    margin-top: 0;
}

/* The comments panel's own column is already much narrower than the transcript body .src-speaker
   normally sizes against, so the same 50% cap wraps a commenter's name away from its -> addressee
   (.src-reply-to above) far more eagerly than intended; give it the column's full width instead. */
.src-comments-view .src-speaker {
    max-width: 100%;
}

.src-list {
    margin: 0 0 0.8em;
}

/* A comments thread (CommentsView) nests by indentation, capped at 3 visible levels
   (public/sourceimport.js capCommentTreeDepth) — no bullet glyph, unlike an ordinary bulleted
   list in the main document body (bare .src-list, untouched above). */
.src-comments-view .src-list {
    list-style: none;
}

/* A comment/reply's addressee (CommentsView, public/sourceimport.js labelCommentReplies): a
   muted suffix on the speaker line ("Author -> Parent", the arrow standing in for "replying to"
   so the pair stays as short as possible and, as far as line-wrapping allows, sits on one line
   with the speaker name), not part of the name itself. */
.src-reply-to {
    display: inline-flex;
    align-items: center;
    margin-left: 0.35em;
    font-weight: 400;
    color: light-dark(#777, #999);
}

.src-reply-to-icon {
    display: inline-flex;
    margin-right: 0.15em;
}

.src-reply-to-icon svg {
    width: 0.8em;
    height: 0.8em;
    stroke-width: 2.5;
}

/* When a comment was posted (sourcedoc.js Block.posted): muted, beside the speaker name. */
.src-posted {
    margin-left: 0.5em;
    font-size: 0.78rem;
    font-weight: 400;
    color: light-dark(#777, #999);
}

/* A comment's `reactions` note (sourcedoc.js Block.reactions, docs/design/statementComments.md):
   free text, e.g. "15 likes" (YouTube, auto-filled) or a viewer's own hand-typed aside (Statement).
   Small and muted, under the comment text rather than beside the speaker line — it describes the
   comment's reception, not who said it. */
.src-reaction {
    display: block;
    margin: 0 0 0.6em;
    font-size: 0.78rem;
    color: light-dark(#777, #999);
}

.src-reaction-input {
    width: 100%;
    max-width: 22rem;
    box-sizing: border-box;
    font: inherit;
    font-size: 0.78rem;
    color: inherit;
    background: transparent;
    border: 0;
    padding: 0.15em 0.4em;
    margin-left: 0.7rem;
}

.src-reaction-input:focus {
    outline: none;
    border-color: var(--btn-accent);
}

/* Citation number: an out-of-flow hanging marker, out of the text model. */
.src-doc [data-num] {
    position: relative;
}

.src-doc [data-num]::before {
    content: "[" attr(data-num) "]";
    position: absolute;
    left: -3.2em;
    width: 2.8em;
    text-align: right;
    color: light-dark(#999, #888);
    font-size: 0.85rem;
    user-select: none;
}

/* PDF page marker (sourcedoc.js's `Page` block kind): a plain grey divider the owner drops in
   between pasted pages (source editor's per-block toolbar "+ Page" button) — never part of the
   addressable text a quote anchors against, just a visual + data boundary a new quote's Page
   number defaults from. */
.src-page-marker {
    display: flex;
    align-items: center;
    gap: 0.6em;
    margin: 1em 0;
    color: light-dark(#999, #888);
    font-size: 0.8rem;
    user-select: none;
}

.src-page-marker::before,
.src-page-marker::after {
    content: '';
    flex: 1 1 auto;
    height: 1px;
    background: light-dark(#ddd, #444);
}

.src-page-marker-label {
    white-space: nowrap;
}

.src-page-marker-num {
    width: 3em;
    text-align: center;
    font: inherit;
    color: inherit;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 3px;
    padding: 0.1em 0.2em;
}

input.src-page-marker-num:hover,
input.src-page-marker-num:focus {
    border-color: light-dark(#ccc, #555);
    outline: none;
}

.src-page-marker-del {
    border: none;
    padding: 0 0.3em;
    font-size: 1rem;
    line-height: 1;
}

/* Edit-on-demand (source mode): a block is inert selectable text until clicked; only then does its
   whole content element become the sole contenteditable region (block-level, not per-sentence — a
   click anywhere in it lets you type/select across sentence boundaries like normal prose). Idle
   blocks show a pointer + hover tint to invite the click. */
.src-block-idle {
    cursor: pointer;
}

.src-block-idle:hover {
    background: light-dark(#f0f4f8, #23303c);
}

[data-block][contenteditable] {
    outline: none;
    cursor: text;
}

[data-block][contenteditable]:focus {
    background: light-dark(#eef5ff, #1b2836);
    box-shadow: 0 0 0 2px light-dark(#cfe3ff, #274056);
}

.src-seek {
    cursor: pointer;
}

.src-seek:hover {
    background: light-dark(#f1f1f1, #333);
}

/* Floating block toolbar (source edit mode): block-level actions for the caret's block. */
.source-editor {
    position: relative;
}

.src-blocktoolbar {
    position: absolute;
    z-index: 5;
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 3px 4px;
    border: 1px solid light-dark(#d0d0d0, #4a4a4a);
    border-radius: 6px;
    background: light-dark(#fff, #262626);
    box-shadow: 0 2px 8px light-dark(rgba(0, 0, 0, 0.14), rgba(0, 0, 0, 0.5));
    font-size: 0.8rem;
    transform: translate(2rem, -0.5rem);
}

/* display:flex above beats the UA [hidden]{display:none} (author rule wins) — without this the
   toolbar's `hidden = true` was cosmetic and it stayed on screen after the caret left. */
.src-blocktoolbar[hidden] {
    display: none;
}

/* Rests at 60% transparent; firms up when the pointer is near (.src-bt-near, set from
   sourceeditor.js) or a field inside it has focus (speaker typeahead, page number). */
.src-blocktoolbar {
    opacity: .5;
    transition: opacity 0.15s;
    background-color: light-dark(gainsboro,darkgrey);
}

.src-blocktoolbar.src-bt-near,
.src-blocktoolbar:hover,
.src-blocktoolbar:focus-within {
    opacity: 1;
}

.src-blocktoolbar button {
    padding: 2px 7px;
    font: inherit;
    font-size: 0.8rem;
    line-height: 1.4;
    /* Transparent like the delete button: the toolbar's own surface shows through. */
    background: transparent;
    border-color: transparent;
    box-shadow: none;
    transform: none;
}

.src-blocktoolbar button:hover {
    background: light-dark(rgba(0, 0, 0, 0.08), rgba(255, 255, 255, 0.14));
    box-shadow: none;
    transform: none;
}

.src-blocktoolbar button.danger:hover {
    background: light-dark(rgba(180, 35, 24, 0.12), rgba(249, 112, 102, 0.18));
}

.src-blocktoolbar .src-bt-seg {
    display: inline-flex;
    gap: 2px;
}

/* The selected kind (¶ / H / •) is the only one that keeps a fill and border. */
.src-blocktoolbar .src-bt-seg button.active {
    background: light-dark(#e6f0fb, #2b3f55);
    border-color: light-dark(#9bbce0, #3d6a97);
}

.src-blocktoolbar .src-bt-num {
    width: 3em;
    padding: 2px 4px;
    font: inherit;
    font-size: 0.8rem;
    text-align: center;
    border: 1px solid light-dark(#ccc, #555);
    border-radius: 4px;
    background: light-dark(#fff, #1e1e1e);
    color: inherit;
}

/* The toolbar's speaker field — the sole place any speaker gets named or renamed (§ turn heads and
   continuation blocks alike). An unnamed continuation block stays narrow until focused, so it doesn't
   crowd the toolbar; an already-named turn head starts at a comfortable reading width
   (`.src-bt-speaker-named`) instead, since there's a name to actually read. Either widens further on
   focus, pushing the delete button right rather than overlaying a dropdown elsewhere. Width is a
   fixed multi-state swap, not autosize-to-content, so it never needs the toolbar's own clamped
   positioning recomputed. Overrides the base .concept-typeahead's `flex: 1; min-width: 8rem` (sized
   for its popover/panel use), which would otherwise fight both the toolbar's own flex layout and
   these fixed widths. */
.src-blocktoolbar .concept-typeahead {
    flex: 0 0 auto;
    width: 5.5em;
    min-width: 0;
    transition: width 0.15s ease;
}

.src-blocktoolbar .concept-typeahead.src-bt-speaker-named {
    width: 9em;
}

.src-blocktoolbar .concept-typeahead:focus-within {
    width: 11em;
}

/* The field itself (ConceptTypeahead's `portalMenu: false`) stays an in-flow child rather than
   floating to <body> — the toolbar it lives in is already its own elevated surface (position:
   absolute, own shadow), so a second floating layer would be redundant and would orphan the
   dropdown from `editor.toolbar`'s own subtree. Positioned below the field rather than the
   .concept-picker pattern of sharing one card, since the narrow speaker field is far too tight to
   also host the dropdown at its own width — this keeps its own background/shadow/radius (the base
   rule) and just widens past the field to stay readable. */
.src-blocktoolbar .concept-typeahead-menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    width: max-content;
    min-width: 100%;
    max-width: 16rem;
}

.src-blocktoolbar .src-bt-speaker {
    width: 100%;
    padding: 2px 6px;
    font: inherit;
    font-size: 0.8rem;
    border: 1px solid light-dark(#ccc, #555);
    border-radius: 4px;
    background: light-dark(#fff, #1e1e1e);
    color: inherit;
}

/* Left-margin time column (read + edit): a persistent grid column beside each timed block's
   content, replacing the old floating/hover chip entirely — always visible, never hover/caret-gated. */
.src-blockrow {
    display: grid;
    grid-template-columns: max-content 1fr;
    column-gap: 5.6px;
    align-items: start;
}

.src-timecol {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding-top: 0.15em;
    font-variant-numeric: tabular-nums;
}

.src-tc-time {
    padding: 0;
    background: none;
    border: none;
    font: inherit;
    font-size: 0.72rem;
    color: light-dark(#888, #999);
    width: auto;
    /* Room for "mm:ss½" so a half-second time never widens the column and shifts the spoken text. */
    min-width: 6ch;
    text-align: center;
    cursor: pointer;
}

.src-tc-time:hover:not(:disabled) {
    color: light-dark(#111, #eee);
}

.src-tc-time:disabled {
    cursor: default;
}

.src-tc-controls {
    display: flex;
    gap: 2px;
}

/* touch-action: none on the nudge buttons so a press-and-hold repeat doesn't scroll the page on touch. */
.src-tc-nudge {
    padding: 1px 3px;
    background: none;
    border: none;
    cursor: pointer;
    font-size: 0.68rem;
    line-height: 1;
    color: light-dark(#aaa, #888);
    touch-action: none;
    user-select: none;
}

.src-tc-nudge:hover:not(:disabled) {
    color: light-dark(#111, #eee);
}

.src-tc-nudge:disabled {
    opacity: 0.4;
    cursor: default;
}

/* Comparing: original panel and the live doc sit as two flex columns instead of stacked. Wraps to a
   single column below ~640px so neither one is squeezed unreadably narrow. */
.src-doc-split {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 20px;
}

.src-doc-split > .src-compare,
.src-doc-split > .src-doc-live {
    flex: 1 1 320px;
    min-width: 0;
}

/* Original-vs-doc compare panel (read-only). */
.src-compare {
    margin: 0 0 14px;
    padding: 10px 12px;
    border: 1px solid light-dark(#c9c9c9, #555);
    border-radius: 6px;
    background: light-dark(#f6f6f6, #242424);
}

.src-compare-head {
    margin-bottom: 6px;
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: light-dark(#777, #999);
}

.src-compare-body {
    line-height: 1.5;
    color: light-dark(#555, #bbb);
}

/* Timed original: one imported line per row, its as-imported timestamp alongside the text. */
.src-compare-speaker {
    margin: 10px 0 2px;
    font-weight: 600;
    color: light-dark(#444, #ccc);
}

.src-compare-speaker:first-child {
    margin-top: 0;
}

.src-compare-line {
    display: flex;
    gap: 8px;
    margin: 0 0 4px;
}

.src-compare-time {
    flex: 0 0 auto;
    font-variant-numeric: tabular-nums;
    color: light-dark(#888, #888);
}

.src-compare-text {
    flex: 1 1 auto;
}

/* Video player (timed sources only). */
.src-player {
    margin-bottom: 6px;
}

.src-player-note {
    color: light-dark(#666, #aaa);
}

/* Phase 5 concept version-awareness chrome (docs/design/conceptsSingleHome.md):
   the ?v= "a newer version exists" affordance and the frozen-snapshot notice, rendered locally
   by src/lib/versionNotice.js. Class names match Consider The Evidence's. (2026-09-16: the
   ⇄ diff-icon was replaced with a coloured "Update available" banner — readers are pointed at
   the current text, not asked to read a line-diff.) */
.update-banner {
    margin: 0 0 16px;
}

.update-banner a {
    display: inline-block;
    padding: 6px 14px;
    border-radius: 5px;
    background: light-dark(#0b5a52, #134e4a);
    color: light-dark(#fff, #5eead4);
    font-size: 0.85rem;
    font-weight: 600;
    text-decoration: none;
}

.update-banner a:hover {
    text-decoration: underline;
}

.frozen-notice {
    margin: 0 0 16px;
    padding: 8px 12px;
    border-left: 3px solid light-dark(#d9b310, #7a6a1f);
    background: light-dark(#fbf6e3, #2a2510);
    color: light-dark(#6b5a1e, #cdbd7a);
    font-size: 0.85rem;
}

.frozen-notice a {
    color: inherit;
}

.concept-published .concept-frozen h1 {
    margin-top: 0;
}

.concept-diff-page .pub-diff-body {
    margin-top: 8px;
}

/* ---- Tutorial walk (docs/design/tutorial.md) ----------------------------------
   A stepped reader view: one explanation beat plus its COMPLETE on-screen element
   set. State is the whole emphasis axis — `focused` steps forward, `muted` stays
   fully readable (~0.82 opacity, no desaturation); absence from a step means the
   card is simply not here. No spatial stage in this build. */
.tutorial-walk {
    max-width: 44rem;
    margin: 0 auto;
    padding-bottom: 5rem;
}

.tutorial-title {
    margin: 0 0 1.25rem;
}

/* Three-panel walk (docs/design/tutorial.md, prototype's Gettysburg columns):
   teacher note | element stage | reader margin. The stage is the widest lane.
   This is what the published CTE artifact also renders (tutorialWalkAssets.js);
   the live owner ?preview uses the same grid. */
.tutorial-walk-3col {
    max-width: 60rem;
}

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

.tutorial-col {
    min-width: 0;
}

.tutorial-col-teacher .tutorial-explanation {
    margin-bottom: 0;
}

@media (max-width: 860px) {
    .tutorial-columns {
        grid-template-columns: 1fr;
        gap: 20px;
    }
}

/* Wide layout only: the teacher's line is a running commentary, so pin it while the
   stage scrolls and its cards swap underneath (docs/design/tutorial.md). */
@media (min-width: 861px) {
    .tutorial-col-teacher {
        position: sticky;
        top: 5rem;
    }
}

.tutorial-sceneline { display: flex; align-items: baseline; gap: 0.5rem; margin: 0 0 0.75rem; }
.tutorial-sceneline .tutorial-scene { margin: 0; }
.tutorial-scene {
    margin: 0 0 0.75rem;
    font-size: 0.72rem;
    font-weight: 650;
    color: light-dark(#7b858f, #868f99);
}

.tutorial-scene b {
    color: light-dark(#1a1a1a, #e6e6e6);
    font-weight: 650;
}

/* The teacher's voice, set apart from the sans card stage: a serif register.
   Deliberately NOT a bordered card — it reads as prose spoken over the stage, not another
   element on it. */
.tutorial-explanation {
    font-family: Georgia, 'Iowan Old Style', 'Times New Roman', serif;
    font-size: 1.3rem;
    font-weight: 600;
    line-height: 1.6;
    margin-bottom: 1.25rem;
    color: light-dark(#2b2b2b, #dcdcdc);
}

.tutorial-explanation p:first-child { margin-top: 0; }
.tutorial-explanation p:last-child { margin-bottom: 0; }
.tutorial-explanation ul,
.tutorial-explanation ol { margin: 0.6em 0; padding-left: 1.5em; }
.tutorial-explanation li { margin: 0.2em 0; }

/* A wrapping row, not a column: a focused card (and the never-collapsing quote/source) takes a
   full-width line of its own, while the muted siblings that collapsed to chips sit together on
   the shared rows between them, in element order (docs/design/tutorial.md, "Siblings of the
   focus collapse"). */
.tutorial-elements {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 10px 8px;
    min-height: 4rem;
}

/* Anything still in full form breaks the line. */
.tutorial-element:not(.tutorial-element-collapsed) {
    flex: 0 0 100%;
}

/* A focused stand-alone concept floats over the stage instead of stacking beneath the other
   cards the way examples do; the stage keeps a minimum height so it has room when alone. */
.tutorial-elements.tutorial-has-overlay {
    position: relative;
    min-height: 9rem;
}

/* A stand-alone concept is a diversion, not a build on the quote, so the stage beneath it is
   shrouded — a grey translucent sheet over the whole pane — rather than just dimmed. Only the
   concept card rides above it (z-index 2). A focused interpretation or example gets the same sheet
   but stays in its place in the flow (the focus sheet, below). The sheet never takes clicks, so a
   collapsed chip under it still opens its popover. */
.tutorial-elements.tutorial-has-overlay::before,
.tutorial-elements.tutorial-focus-sheet::before {
    content: "";
    position: absolute;
    inset: -8px;
    z-index: 1;
    border-radius: 8px;
    background: light-dark(rgba(150, 156, 164, 0.35), rgba(0, 0, 0, 0.6));
    pointer-events: none;
    animation: tut-shroud-in 0.25s ease;
}

@keyframes tut-shroud-in {
    from { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .tutorial-elements.tutorial-has-overlay::before,
    .tutorial-elements.tutorial-focus-sheet::before { animation: none; }
}

/* Centred vertically in the pane: pinned top and bottom with auto block margins and a fit-content
   height, rather than a translate, so the forward fade-in's transform still works. */
.tutorial-element-overlay:not(.tutorial-element-collapsed) {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 6%;
    right: 6%;
    height: fit-content;
    margin-block: auto;
    flex: none;
    z-index: 2;
    box-shadow: 0 6px 24px light-dark(rgba(0, 0, 0, 0.22), rgba(0, 0, 0, 0.6));
}

/* Reuses `.essay-embed`'s box, on an explicit card surface so the top-border legend below masks
   the rule cleanly. `data-state` carries the emphasis. Persisting cards only ever transition
   their dim; the `tut-entering` fade-in is played on new cards when moving forward. */
.tutorial-element {
    position: relative;
    margin: 0;
    padding-top: 15px;
    background: var(--btn-surface);
    transition: opacity 0.32s ease, transform 0.32s ease, border-color 0.32s ease;
}

/* muted = de-emphasised but still legible (docs/design/tutorial.md, "What each part carries").
   A reading that collapsed to a chip is already de-emphasised by losing its body, so it is NOT
   also dimmed — its identity stays fully legible (override below). */
.tutorial-element[data-state="muted"] {
    opacity: 0.4;
}

.tutorial-element-collapsed[data-state="muted"] {
    opacity: 1;
}

.tutorial-element[data-state="focused"] {
    opacity: 1;
    border-color: var(--btn-accent);
    box-shadow: 0 1px 10px light-dark(rgba(15, 109, 102, 0.14), rgba(64, 190, 180, 0.14));
}

/* The quote stands on its own — no card box, just its text and left rule. The concept pills
   applied to it ride their own `example` element cards (docs/design/tutorial.md, "Composite
   cards"). It still dims like any other muted element via the `.tutorial-element[data-state="muted"]`
   rule above. */
.tutorial-element-quote {
    padding: 0;
    border: 0;
    background: transparent;
}

.tutorial-element-source {
    padding: 0;
    border: 0;
    background: transparent;
}

/* The derived "Sources" section (docs/design/sourcesSection.md): every card is a source, shown as
   a vertical list: the image, with its title underneath, one source per row. */
.tutorial-elements.tutorial-sources-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 20px;
    align-items: start;
}
.tutorial-elements.tutorial-sources-grid .tutorial-element {
    flex: none;
}

.tutorial-element-source[data-state="focused"] {
    border-color: transparent;
    box-shadow: none;
}

.tutorial-element-quote[data-state="focused"] {
    border-color: transparent;
    box-shadow: none;
}

/* A collapsed sibling reading: no card box, just the identity chip below. Never focused (focus
   is what un-collapses it), so it needs no focused variant. */
.tutorial-element-collapsed {
    padding: 0;
    border: 0;
    background: transparent;
}

/* A focused interpretation is the same box as a concept/example card — identity on the top border,
   fieldset-legend style ("Interpretation by <author>") — but VIOLET-bordered, not teal, so a
   reading and an applied concept read apart at a glance (docs/design/tutorial.md, "behavioural
   sibling"). It only ever renders focused; a muted interpretation has collapsed to a chip. */
.tutorial-element-interpretation {
    --tut-interp-accent: light-dark(#7c3aed, #a78bfa);
    border-color: var(--tut-interp-accent);
}

.tutorial-element-interpretation[data-state="focused"] {
    border-color: var(--tut-interp-accent);
    box-shadow: 0 1px 10px light-dark(rgba(124, 58, 237, 0.16), rgba(167, 139, 250, 0.16));
}

.tutorial-element-interpretation[data-state="focused"] .tutorial-el-legend {
    color: var(--tut-interp-accent);
}

/* A focused interpretation is set in a larger, heavier face (docs/design/tutorial.md). */
.tutorial-element-interpretation .essay-interpretation-text {
    font-size: 1.15rem;
    font-weight: 600;
    line-height: 1.4;
}

/* In the walkthrough editor the viewer's own interpretation is written and edited in place: the
   finished text itself, made plaintext-editable, so the writer types into what readers will see.
   A dashed outline marks it as editable on hover and while editing, as the reworded quote does. */
.tutorial-element-interpretation .ted-interp-edit {
    cursor: text;
    min-height: 1.4em;
    outline: 1px dashed transparent;
    outline-offset: 3px;
}

.tutorial-element-interpretation .ted-interp-edit:hover,
.tutorial-element-interpretation .ted-interp-edit:focus {
    outline-color: currentColor;
}

/* The stage card is an .essay-embed, whose selection paint is made transparent (its chips are
   selected invisibly); this is real text, so a double-click or shift-arrow must show its highlight. */
.tutorial-element-interpretation .ted-interp-edit::selection,
.tutorial-element-quote .ted-quote-edit::selection {
    background: Highlight;
    color: HighlightText;
}

.tutorial-element-interpretation .ted-interp-edit:empty::before {
    content: attr(data-placeholder);
    color: var(--muted, #777);
    font-weight: 400;
}

/* The lit quote is reworded in place: the quote text itself, plaintext-editable, with the same
   dashed hint on hover and while editing as the interpretation box. A faint dash stays once it has
   been reworded, so the change is visible. */
.tutorial-element-quote .ted-quote-edit {
    cursor: text;
    outline: 1px dashed transparent;
    outline-offset: 3px;
}

.tutorial-element-quote.ted-reworded .ted-quote-edit {
    outline-color: color-mix(in srgb, currentColor 35%, transparent);
}

.tutorial-element-quote .ted-quote-edit:hover,
.tutorial-element-quote .ted-quote-edit:focus {
    outline-color: currentColor;
}

/* Remove quote, under the lit quote: only while it is being edited, or once Delete has armed it. */
.tutorial-element-quote .ted-quote-actions {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.5rem;
}

/* Play window: where a reader's player starts and stops for this video quote. The times and
   arrows reuse the source copy's time column styles (.src-tc-time / .src-tc-nudge); only the row
   that holds them is new. */
.tutorial-element-quote .ted-timing {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    margin-top: 0.5rem;
    font-variant-numeric: tabular-nums;
}
/* The arrows hug their time: the source copy's 6ch time width (room for a ½) and arrow padding go,
   since nothing here shifts under the times. */
.tutorial-element-quote .ted-timing .src-tc-time { min-width: 0; padding: 0 2.75px 0 1.5px; }
/* (Uneven on purpose: the digits carry more space on their left than their right, and ▶ sits a hair
   tighter than ◀, so this evens the gap either side of a whole-second time, 3px each; a ½ ends a pixel tighter.) */
.tutorial-element-quote .ted-timing .src-tc-nudge { padding: 1px 0; }
/* The arrows hug their time; the "to" between the two is what holds them apart. */
.tutorial-element-quote .ted-timing-label {
    font-size: 0.72rem;
    color: light-dark(#888, #999);
    margin: 0 0.75rem;
}

/* "Also at": other renditions of this quote the writer cited (docs/design/quoteAlternatives.md). */
.tutorial-element-quote .ted-alts {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.4rem 0.5rem;
    margin-top: 0.6rem;
    font-size: 0.85rem;
}
.tutorial-element-quote .ted-alts-label {
    font-size: 0.72rem;
    color: light-dark(#888, #999);
}
.tutorial-element-quote .ted-alt {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0 0.2rem 0 0.6em;
    border: 1px solid var(--btn-border, currentColor);
    border-radius: 999px;
}
.tutorial-element-quote .ted-alt-x {
    border: none;
    padding: 0 0.35rem;
    font-size: 0.85rem;
    line-height: 1.4;
}
.tutorial-element-quote .ted-alt-form {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    width: 100%;
}
.tutorial-element-quote .ted-alt-form select,
.tutorial-element-quote .ted-alt-form input { max-width: 100%; }

/* "modified": this quote's wording differs from the transcript. Becomes a button when the
   difference is spelling fixes the transcript can take (docs/design/quoteCorrection.md). */
.tutorial-element-quote .ted-pill {
    display: inline-block;
    margin-top: 0.4rem;
    padding: 0 0.6em;
    border: 1px solid var(--btn-border, currentColor);
    border-radius: 999px;
    font-size: 0.8em;
    color: var(--muted, #777);
}

.tutorial-element-quote .ted-pill-fix {
    cursor: pointer;
    color: inherit;
    border-color: currentColor;
}

.tutorial-element-quote .ted-pill-fix:hover,
.tutorial-element-quote .ted-pill-fix:focus-visible {
    background: color-mix(in srgb, currentColor 12%, transparent);
}

.tutorial-element-quote .ted-pill.error {
    color: var(--danger, #b3261e);
    border-color: var(--danger, #b3261e);
}

/* ...and the quote above it stays in place under the same sheet a stand-alone concept uses,
   with the interpretation riding over it. A focused example gets the same treatment. The sheet
   does the dimming, so the quote is not also faded to the usual muted 0.4. */
.tutorial-elements.tutorial-focus-sheet {
    position: relative;
}

.tutorial-elements.tutorial-focus-sheet .tutorial-element[data-state="focused"] {
    z-index: 2;
}

.tutorial-elements.tutorial-focus-sheet .tutorial-element-quote[data-state="muted"] {
    opacity: 1;
}

/* Forward-only reveal: a card new to this step starts here and transitions in. Going back never
   adds this class, so a card reappearing on a Back press just snaps into place. */
.tutorial-element.tut-entering {
    opacity: 0;
    transform: translateY(6px);
}

/* The card's identity — a Concept's Code, or the word "Interpretation" — set into the top border
   fieldset/legend style: pulled up onto the border line over a surface-matched background that
   masks the rule beneath it (docs/design/tutorial.md, "behavioural sibling"). Replaces the old
   uppercase label line and the Code pill that read as a clickable control. */
.tutorial-el-legend {
    position: absolute;
    top: 0;
    left: 12px;
    transform: translateY(-50%);
    margin: 0;
    padding: 0 6px;
    background: var(--btn-surface);
    font-family: ui-monospace, Consolas, monospace;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: light-dark(#7b858f, #868f99);
}

/* Lit card: its legend takes the accent too, so "what is this" and "is it the point right now"
   read off one element. */
.tutorial-element[data-state="focused"] .tutorial-el-legend {
    color: var(--btn-accent);
}

.tutorial-source-title {
    margin: 0;
    font-weight: 600;
}

/* A source card's picture: the page's og:image, or a bracketed kind badge when it has none —
   always the same 1200:630 frame so the two read as one shape. */
.tutorial-source-media {
    aspect-ratio: 1200 / 630;
    margin: 0 0 8px;
    border-radius: 4px;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--btn-surface, transparent);
}

.tutorial-source-link {
    display: flex;
    width: 100%;
    height: 100%;
    align-items: center;
    justify-content: center;
}

.tutorial-source-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.tutorial-source-badge {
    font-family: ui-monospace, 'IBM Plex Mono', monospace;
    font-size: 1.4rem;
    letter-spacing: 0.04em;
    color: light-dark(#7b858f, #868f99);
}

/* Provenance chip on a quote card: which source the quote is cut from. Not a placement — it
   rides the quote card and inherits its dim (docs/design/tutorial.md, "Source citation chip"). */
.tutorial-source-chip {
    margin: 2px 0 0;
    text-align: right;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: light-dark(#7b858f, #868f99);
}

.tut-flat .tutorial-source-chip {
    font-size: 0.78rem;
}

.tutorial-pill-row {
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

/* The chip look is shared: a concept pill applied to a quote, and a whole reading collapsed to
   its identity because a sibling has the focus, read as the same kind of token. */
.tutorial-pill,
.tutorial-collapsed-label {
    display: inline-block;
    font-size: 0.8rem;
    font-weight: 550;
    padding: 3px 10px;
    border-radius: 999px;
    background: light-dark(#e4f0ef, #1e2b2a);
    color: var(--btn-accent);
    border: 1px solid light-dark(#bfe0dc, #2f4a47);
}

.tutorial-pill-spec {
    margin: 6px 0 0;
    font-size: 0.9rem;
    color: light-dark(#555, #b0b0b0);
}

/* A concept's Code — card legend, collapsed chip or pill — is a button opening its popover
   (public/tutorialConcept.js; docs/design/tutorial.md, "Clicking a concept"). Undo the site-wide
   button chrome so each keeps the look of what it replaced; the legend keeps its border-riding
   transform through hover/active. */
button.tutorial-concept-trigger {
    display: inline-block;
    gap: 0;
    font-weight: inherit;
    line-height: inherit;
    cursor: pointer;
    user-select: text;
}

button.tutorial-concept-trigger:hover,
button.tutorial-concept-trigger:active {
    box-shadow: none;
    transform: none;
    text-decoration: underline dotted;
    text-underline-offset: 3px;
}

/* A concept mentioned in narration (docs/design/conceptsInNarration.md): an inline chip like an
   essay's concept mention, but a button that opens the concept popover. */
button.tutorial-concept-mention {
    display: inline;
    padding: 0 6px;
    border: 0;
    border-radius: 4px;
    background: light-dark(#e0f2f0, #123331);
    color: var(--btn-accent);
    font: inherit;
    font-weight: 600;
    line-height: inherit;
    text-decoration: none;
}

button.tutorial-el-legend.tutorial-concept-trigger {
    border: 0;
    border-radius: 0;
    background: var(--btn-surface);
    font-family: ui-monospace, Consolas, monospace;
    font-size: 0.68rem;
    font-weight: 700;
    color: light-dark(#7b858f, #868f99);
}

.tutorial-element[data-state="focused"] button.tutorial-el-legend.tutorial-concept-trigger {
    color: var(--btn-accent);
}

button.tutorial-el-legend.tutorial-concept-trigger:hover,
button.tutorial-el-legend.tutorial-concept-trigger:active {
    background: var(--btn-surface);
    transform: translateY(-50%);
}

button.tutorial-pill.tutorial-concept-trigger,
button.tutorial-collapsed-label.tutorial-concept-trigger {
    padding: 3px 10px;
    border-radius: 999px;
    border: 1px solid light-dark(#bfe0dc, #2f4a47);
    background: light-dark(#e4f0ef, #1e2b2a);
    font-size: 0.8rem;
    font-weight: 550;
    color: var(--btn-accent);
}

/* The panel itself rides the site-wide [popover] look (anchored under its trigger, pale help
   yellow); these just lay out its parts, which are spans so the panel can sit inside a <p>. */
.tutorial-concept-popover .tcp-spec,
.tutorial-concept-popover .tcp-credit,
.tutorial-concept-popover .tcp-why,
.tutorial-concept-popover .tcp-links,
.tutorial-concept-popover .tcp-img {
    display: block;
    margin-top: 8px;
}

.tutorial-concept-popover .tcp-credit {
    font-size: 0.85rem;
    font-style: italic;
}

.tutorial-concept-popover .tcp-img {
    width: 100%;
    aspect-ratio: 1200 / 630;
    object-fit: cover;
    border-radius: 4px;
}

.tutorial-concept-popover .tcp-links {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.tutorial-concept-popover .tcp-secondary {
    font-size: 0.85rem;
    opacity: 0.8;
}

.tutorial-controls {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: clamp(16px, 5vh, 44px);
    width: 100%;
    max-width: 44rem;
    padding: 0 1rem;
    display: flex;
    align-items: center;
    gap: 10px;
}

.tutorial-spacer { flex: 1; }

.tutorial-counter {
    font-size: 0.8rem;
    color: light-dark(#7b858f, #868f99);
    font-variant-numeric: tabular-nums;
}

.tutorial-empty {
    color: light-dark(#666, #999);
    font-style: italic;
}

/* Only a scene change animates — it is a hard cut to unrelated material. Step-to-step within
   a scene changes text and cards in place, with no wash over what stayed put. */
.tut-cut {
    animation: tutCut 0.4s ease both;
}

@keyframes tutCut {
    from { opacity: 0.3; }
    to   { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .tutorial-element,
    .tut-cut { transition: none; animation: none; }
}

.tutorial-flat-link {
    font-size: 0.85rem;
    color: light-dark(#666, #999);
}

/* ---- Flattened projection (?flat) — the citable/indexable form, and what gets
   published externally: a frozen artifact cannot be an interactive stepper. */
.tut-flat {
    max-width: 44rem;
    margin: 0 auto;
}

.tut-flat-scene {
    margin: 2rem 0 0.5rem;
    padding-top: 1rem;
    border-top: 1px solid var(--btn-border);
}

.tut-flat-step {
    margin-bottom: 1.25rem;
}

.tut-flat-source {
    margin: 0 0 0.5rem;
}

.tut-flat-back {
    margin-top: 2rem;
}

/* ---- Tutorial editor (docs/design/tutorial.md, open question 6) --------------
   A step/timeline editor: scenes hold ordered steps; a step holds the teacher's
   beat plus the complete on-screen element set, each focused or muted. */
.tutorial-editor {
    max-width: 48rem;
    margin: 0 auto;
    padding-bottom: 5rem;
}

/* ---- Split editor (docs/design/splitEditor.md) -------------------------------
   Walkthrough pane (left) | source pane (right, once a source is placed). Each pane scrolls on
   its own inside .site-container.wide. Below 900px one pane shows at a time, picked by the
   Source / Walkthrough toggle (data-pane on .split-editor). */
/* Source details page (/sources/:id, docs/design/splitEditor.md phase 6): the header, then the
   way into a walkthrough. */
.source-details-work {
    margin-top: 1.5rem;
}

/* The viewer's own copy of the text, read-only, scrolling inside its own box. */
.source-copy-text {
    max-height: 24rem;
    overflow-y: auto;
    margin: 0.75rem 0 1rem;
    padding: 0.5rem 1rem;
    border: 1px solid var(--btn-border);
    border-radius: 6px;
}
.source-copy-speaker { font-weight: 600; }
.source-copy-page { opacity: 0.6; font-size: 0.85em; text-align: center; }

.split-editor {
    display: flex;
    flex: 1;
    min-height: 0;
}

.split-toggle { display: none; }

.split-pane {
    flex: 1 1 0;
    min-width: 0;
    overflow-y: auto;
}

.split-source-pane {
    padding: 0 0 16px 20px;
}

/* Until the walkthrough places a source there is nothing to show: the walkthrough takes the whole
   width (the source pane opens, and stays open, on the first source — splitsourcepane.js show()). */
.split-editor[data-source="none"] .split-source-pane,
.split-editor[data-source="none"] .split-splitter,
.split-editor[data-source="none"] .split-toggle { display: none; }

/* The draggable bar between the panes; its centre line is the divider. */
.split-splitter {
    flex: 0 0 9px;
    cursor: col-resize;
    touch-action: none;
    background: linear-gradient(var(--btn-border), var(--btn-border)) center / 1px 100% no-repeat;
}
.split-splitter:hover,
.split-splitter:focus-visible,
.split-splitter.dragging {
    background: linear-gradient(var(--btn-accent), var(--btn-accent)) center / 3px 100% no-repeat;
    outline: none;
}

/* The walkthrough pane is the container the walk's layout keys off, not the viewport. */
.split-walk-pane {
    padding: 0 20px 0 0;
    container: walkpane / inline-size;
}

/* Alone (no source pane yet) it needs no gutter for a splitter that isn't there. */
.split-editor[data-source="none"] .split-walk-pane { padding-right: 0; }

.split-source-head {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 12px;
    padding-bottom: 8px;
    margin-bottom: 8px;
    background: light-dark(#fff, #1a1a1a);
    border-bottom: 1px solid light-dark(#e0e0e0, #444);
}

/* Same look as the source panel's own dropdown (.source-user-select). */
.split-source-picker {
    flex: 1 1 auto;
    min-width: 0;
    max-width: 100%;
    padding: 6px 10px;
    font-size: 1rem;
    font-weight: 700;
    border: 1px solid var(--btn-border);
    border-radius: 8px;
    background: var(--btn-surface);
    color: var(--btn-text);
}

.split-source-open {
    flex: none;
    font-size: 0.85rem;
    white-space: nowrap;
}

/* The quote card (stepquotecard.js): under the stage when the step lights a quote, the source
   page's current-quote view — concepts on the quote, then every interpretation. The panel's own
   .interp-* / .reading-chip rules style its insides unchanged. */
.ted-quotecard {
    margin-top: 12px;
    padding: 10px 12px;
    border: 1px solid var(--btn-border);
    border-radius: 10px;
    background: var(--btn-surface);
}

.ted-quotecard[hidden] { display: none; }

.ted-quotecard-concepts {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

.ted-quotecard-label {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: light-dark(#888, #999);
}

/* The source page pins this rail down the panel's right edge as a column; here it runs inline. */
.ted-quotecard .ted-quotecard-concepts .quote-tag-rail {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    padding-top: 0;
}

/* The viewer's own quote: [Remove quote] · spacer · [Revert wording] [Reword]. */
.ted-quotecard-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
}

.ted-quotecard-spacer { flex: 1; }

.ted-quotecard-foot {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 8px;
}

.ted-quotecard-foot:empty { display: none; }

.ted-quotecard-offer {
    margin-right: auto;
    color: light-dark(#555, #bbb);
}

.split-source-pane { position: relative; }

.split-source-note {
    color: light-dark(#666, #aaa);
}

/* Undo / Replace text / Compare / Revert (/ Play) above the text; each hides itself when it
   doesn't apply (SourceEditor._syncToolbar). */
.split-source-tools {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 6px;
}

.split-source-tools .src-toolbar { display: flex; gap: 6px; }
.split-source-tools [hidden] { display: none; }

/* The source page pins its tab bar in its own scroll column; in the pane the picker head is
   what sticks, so the tabs just flow. */
.split-source-tabs { position: static; }

/* The video rides the top of the pane, under the picker, rather than the source page's fixed
   dock — it is a reference beside the text being read. */
.split-source-player {
    position: sticky;
    top: 44px;
    z-index: 1;
    background: light-dark(#fff, #1a1a1a);
    padding-bottom: 6px;
}

.split-source-player .src-player iframe {
    display: block;
    width: 100%;
    max-width: 360px;
    height: auto;
    aspect-ratio: 16 / 9;
    border: 0;
    border-radius: 8px;
}

/* Inside the pane the walk is always the reader's narrow layout: narration stacked over the
   stage (the walk's own below-860px form), and the teacher column no longer pinned. */
@container walkpane (max-width: 860px) {
    .tutorial-columns {
        grid-template-columns: 1fr;
        gap: 20px;
    }
    .tutorial-col-teacher {
        position: static;
    }
}

@media (max-width: 900px) {
    .split-editor {
        flex-direction: column;
    }
    .split-toggle {
        display: flex;
        gap: 4px;
        margin-bottom: 12px;
        border-bottom: 1px solid light-dark(#e0e0e0, #444);
    }
    button.split-toggle-btn {
        border: none;
        border-radius: 0;
        background: none;
        box-shadow: none;
        padding: 8px 4px;
        margin-right: 16px;
        font-weight: 400;
        color: light-dark(#666, #999);
        border-bottom: 2px solid transparent;
        transform: none;
    }
    button.split-toggle-btn[aria-selected="true"] {
        color: var(--btn-text);
        font-weight: 600;
        border-bottom-color: var(--btn-accent);
    }
    .split-source-pane,
    .split-walk-pane {
        padding: 0;
        border: none;
    }
    .split-splitter { display: none; }
    .split-source-pane { flex: 1 1 0 !important; }
    .split-editor[data-pane="walk"] .split-source-pane,
    .split-editor[data-pane="source"] .split-walk-pane {
        display: none;
    }
}

/* The site's own narrow mode scrolls the body, not the container, so the panes stop scrolling
   on their own and just flow — and the picker/video stop sticking: their nearest scroll box is now
   .site-container (overflow-x: hidden makes it one), which never scrolls, so a sticky offset only
   pushes them down over the text. */
@media (max-width: 800px) {
    .split-editor { flex: none; }
    .split-pane { overflow-y: visible; }
    .split-source-head,
    .split-source-player { position: static; }
}

.tutorial-title-field {
    font-size: 1.6rem;
    font-weight: 700;
    padding: 4px 0;
    margin-bottom: 1rem;
    border-bottom: 1px solid var(--btn-border);
    outline: none;
}

.tutorial-title-field:empty::before {
    content: attr(data-placeholder);
    color: light-dark(#999, #777);
    font-weight: 400;
}

.tutorial-editor-foot {
    display: flex;
    align-items: center;
    gap: 12px;
}

.ted-scene {
    border: 1px solid var(--btn-border);
    border-radius: 10px;
    padding: 12px;
    margin-bottom: 16px;
}

.ted-scene-head,
.ted-step-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 10px;
}

.ted-spacer { flex: 1; }

.ted-badge {
    font-family: ui-monospace, Consolas, monospace;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: light-dark(#7b858f, #868f99);
    white-space: nowrap;
}


.ted-icon {
    padding: 4px 10px;
    font-size: 0.82rem;
    line-height: 1.2;
}

.ted-step {
    border-left: 3px solid var(--btn-border);
    padding: 8px 0 8px 12px;
    margin-bottom: 12px;
}



.ted-elements {
    margin: 10px 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.ted-narration {
    margin: 0;
    font-size: 0.88rem;
    font-style: italic;
    color: light-dark(#7b858f, #868f99);
}

.ted-element {
    border: 1px solid var(--btn-border);
    border-radius: 8px;
    padding: 8px 10px;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
}

/* Emphasis is derived, not set (docs/design/tutorial.md, "State is derived"); the row still
   carries `data-state` so the teacher sees which elements the walk will light and which it dims. */
.ted-element[data-state="focused"] { border-color: var(--btn-accent); }
.ted-element[data-state="muted"] { opacity: 0.62; }

.ted-el-main {
    flex: 1;
    min-width: 12rem;
    display: flex;
    align-items: baseline;
    gap: 8px;
}

.ted-el-kind {
    font-family: ui-monospace, Consolas, monospace;
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: light-dark(#7b858f, #868f99);
    white-space: nowrap;
}

.ted-el-text {
    font-size: 0.9rem;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

.ted-el-controls {
    display: flex;
    align-items: center;
    gap: 6px;
}

.ted-picker {
    border: 1px solid var(--btn-accent);
    border-radius: 8px;
    padding: 10px;
    margin-bottom: 8px;
}

.ted-picker-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin-bottom: 8px;
    font-size: 0.85rem;
}

.ted-picker-head select,
.ted-concept-filter {
    font: inherit;
    font-size: 0.85rem;
    padding: 5px 8px;
    border: 1px solid var(--btn-border);
    border-radius: 6px;
    background: light-dark(#fff, #1c1f23);
    color: inherit;
}

.ted-picker-list {
    max-height: 16rem;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.ted-picker-item {
    display: flex;
    align-items: baseline;
    gap: 8px;
    width: 100%;
    text-align: left;
    font: inherit;
    padding: 6px 8px;
    border: 1px solid transparent;
    border-radius: 6px;
    background: none;
    color: inherit;
    cursor: pointer;
}

.ted-picker-item:hover { border-color: var(--btn-accent); }

.ted-picker-loading,
.ted-picker-empty {
    margin: 0;
    font-size: 0.85rem;
    color: light-dark(#7b858f, #868f99);
}

.ted-el-warn { display: block; margin-top: 2px; font-size: 0.85em; color: var(--danger, #b3261e); }
.ted-stage { margin: 6px 0 4px; font-size: 0.85em; color: var(--text-muted, #666); }
.ted-stage-item[data-state="focused"] { font-weight: 600; color: var(--btn-accent); }
.ted-stage-note { color: var(--danger, #b3261e); }

.tutorial-outline { display: flex; flex-direction: column; gap: 4px; margin: 0 0 0.75rem; max-width: 34rem; }
.tutorial-outline[hidden] { display: none; }
.tutorial-outline-item {
    display: flex; align-items: baseline; gap: 0.6rem; text-align: left;
    padding: 6px 10px; min-height: 36px;
}
.tutorial-outline-item[aria-current="step"] { border-color: var(--btn-accent); font-weight: 600; }
.tutorial-outline-num { color: var(--text-muted, #666); min-width: 1.2em; }
.tutorial-outline-name { flex: 1; }
.tutorial-outline-count { color: var(--text-muted, #666); font-size: 0.85em; }

/* In-place tutorial editor: step chips, under the scene name row, above the real walk stage. */
.ted-outline { display: flex; flex-direction: column; gap: 6px; margin: 0.4rem 0 1rem; }
.ted-oc { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.ted-sceneswitch { position: relative; }
/* The section heading is itself the jump button (editor and walk): the name in the scene line's
   own type, no button frame, a small caret saying it opens. */
button.ted-scenetitle {
    display: inline-flex; align-items: baseline; gap: 0.3em; padding: 2px 6px; margin-left: -6px;
    border-color: transparent; background: transparent; font: inherit; color: inherit; cursor: pointer;
}
button.ted-scenetitle:hover:not(:disabled) { border-color: transparent; background: light-dark(rgba(0,0,0,0.04), rgba(255,255,255,0.06)); }
button.ted-scenetitle[hidden] { display: none; }
button.ted-scenetitle:disabled { cursor: default; opacity: 1; }
button.ted-scenetitle:disabled .ted-scenecaret { display: none; }
.ted-scenetitle b { color: light-dark(#1a1a1a, #e6e6e6); font-weight: 650; }
.ted-scenetitle b.is-unnamed { color: inherit; font-weight: 400; font-style: italic; }
.ted-scenecaret { font-size: 0.85em; opacity: 0.6; }
button.ted-scenepencil { border-color: transparent; background: transparent; padding: 2px 4px; opacity: 0.6; }
button.ted-scenepencil:hover { opacity: 1; }
.ted-scenemenu {
    position: absolute; top: 100%; left: 0; z-index: 20; min-width: 14rem; max-width: 22rem;
    display: flex; flex-direction: column; gap: 2px; padding: 4px; margin-top: 4px;
    background: var(--surface, Canvas); border: 1px solid var(--btn-border); border-radius: 6px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.18);
}
.ted-scenemenu[hidden] { display: none; }
/* The step's Move menu sits in the controls under the stage, so it opens upward. */
.ted-scenemenu-up { top: auto; bottom: 100%; margin-top: 0; margin-bottom: 4px; }
.ted-scenemenu-up .ted-scenemenu-item { font-size: 0.85rem; font-weight: 400; }
.ted-scenemenu-item { text-align: left; border-color: transparent; }
.ted-scenemenu-item[aria-current="true"] { border-color: var(--btn-accent); font-weight: 600; }
/* "+ Add section" closes the menu and is an action, not a destination: set apart by a rule above it,
   dashed accent border and accent text. */
.ted-scenemenu-add {
    margin-top: 6px;
    border: 1px dashed var(--btn-accent);
    color: var(--btn-accent);
    font-weight: 600;
    text-align: center;
}
/* Reordering sections by dragging their entries: a bar marks where the dragged one will land. */
.ted-scenemenu-sec.is-dragging { opacity: 0.4; }
.ted-scenemenu-sec.is-dropat { box-shadow: 0 -3px 0 var(--btn-accent); }
.ted-scenemenu-sec.is-dropat.is-dropafter { box-shadow: 0 3px 0 var(--btn-accent); }
/* The heading's menu while a step chip is dragged onto it: items are drop targets, not buttons. */
.ted-dropmenu { margin-top: 0; }
.ted-dropitem { display: block; padding: 4px 10px; white-space: nowrap; }
.ted-dropitem.is-dropat { border-color: var(--btn-accent); background: var(--btn-accent); color: #fff; }
.ted-dropitem.is-disabled { opacity: 0.45; }
.ted-dropsub { display: flex; flex-direction: column; gap: 2px; margin-left: 12px; padding-left: 6px; border-left: 2px solid var(--btn-border); }
.ted-dropsub[hidden] { display: none; }
.ted-oc-steps { display: flex; flex-wrap: wrap; gap: 4px; flex: 1; }
.ted-chip {
    min-width: 34px; min-height: 30px; padding: 2px 8px; display: inline-flex; align-items: baseline; gap: 3px;
}
.ted-chip[aria-current="step"] { border-color: var(--btn-accent); background: var(--btn-accent); color: #fff; }
.ted-chip { cursor: grab; }
.ted-delconfirm { display: flex; align-items: center; gap: 8px; margin: -0.5rem 0 1rem; font-size: 0.9em; }
.ted-delconfirm[hidden] { display: none; }
.ted-chip.is-dragging { opacity: 0.4; }
/* The trash: only present while a step chip is being dragged. It sits in the section line, right
   after Merge next, close to the chips being dragged; compact so its appearing doesn't push the
   chips down under the cursor. */
.ted-trash {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 2px 10px;
    border: 2px dashed var(--danger, #b3261e);
    border-radius: 8px;
    background: var(--surface, #fff);
    color: var(--danger, #b3261e);
    font-size: 0.9rem;
    white-space: nowrap;
}

.ted-trash[hidden] {
    display: none;
}

.ted-trash.is-over {
    background: var(--danger, #b3261e);
    color: #fff;
}

.ted-trash-icon {
    font-size: 1.1rem;
}

.ted-chip.is-deleting { opacity: 0.6; border: 1px dashed var(--danger, #b3261e); color: var(--danger, #b3261e); text-decoration: line-through; }
.ted-chip { position: relative; }
/* Insertion caret: a vertical bar with a downward arrowhead, in the gap on the side where the step will land. */
.ted-chip.is-dropat::before {
    content: ''; position: absolute; top: -4px; bottom: -4px; left: -5px; width: 3px;
    background: var(--btn-accent); border-radius: 2px; pointer-events: none;
}
.ted-chip.is-dropat::after {
    content: ''; position: absolute; top: -9px; left: -8px; pointer-events: none;
    border: 4.5px solid transparent; border-top: 6px solid var(--btn-accent); border-bottom: 0;
}
.ted-chip.is-dropat.is-dropafter::before { left: auto; right: -5px; }
.ted-chip.is-dropat.is-dropafter::after { left: auto; right: -8px; }
.ted-chip.is-issue { border-color: var(--danger, #b3261e); }
/* A card dragged off the stage: the empty steps it may move to are outlined, the one under it fills. */
.ted-chip.is-movetarget { outline: 2px dashed var(--btn-accent); outline-offset: 1px; }
.ted-chip.is-moveover { background: var(--btn-accent); color: #fff; }
/* A quote dragged out of the source pane: its drop targets (step content, chips, "+"). */
.is-quotetarget { outline: 2px dashed var(--btn-accent); outline-offset: 2px; }
.is-quotetarget.is-quoteover { background: color-mix(in srgb, var(--btn-accent) 18%, transparent); }
.ted-chip.is-quotetarget.is-quoteover { background: var(--btn-accent); color: #fff; }
.ted-walk .tutorial-element[draggable="true"], .ted-walk .tutorial-element[draggable="true"] .tutorial-el-legend { cursor: grab; }
.ted-chip-kind { font-size: 0.7em; opacity: 0.75; }
.ted-oc-ctl { display: inline-flex; gap: 4px; }
.ted-walk .ted-sceneline { display: flex; align-items: baseline; gap: 0.5rem; }
/* Live fields: the walk's own typography, editable in place. A hover/focus wash says "editable"
   without boxing it; the placeholder is drawn over an empty field. */
.ted-live { position: relative; outline: none; border-radius: 4px; cursor: text; }
.ted-live:hover { background: light-dark(rgba(0,0,0,0.03), rgba(255,255,255,0.05)); }
.ted-live:focus { background: light-dark(rgba(0,0,0,0.04), rgba(255,255,255,0.06)); box-shadow: 0 0 0 2px var(--btn-accent); }
.ted-live[data-empty]::before {
    content: attr(data-placeholder); position: absolute; left: 0; top: 0;
    pointer-events: none; opacity: 0.5; font-weight: 400; text-transform: none; letter-spacing: 0;
}
.ted-expl-live { min-height: 8rem; padding: 2px 6px; margin-left: -6px; }
.ted-expl-live[data-empty]::before { left: 6px; top: 2px; }
.ted-sceneedit { flex: 0 1 auto; max-width: 26rem; min-width: 8rem; min-height: 1.2em; }
/* The "+" after the step chips: a bare glyph, not a button — it adds rather than selects. */
.ted-addstep, .ted-addstep:hover, .ted-addstep:focus-visible {
    min-width: 34px; min-height: 30px; padding: 2px 8px; border: none; background: none; box-shadow: none;
    font-size: 1.25em; line-height: 1; color: inherit; opacity: 0.55; cursor: pointer;
}
.ted-addstep:hover, .ted-addstep:focus-visible { opacity: 1; color: var(--btn-accent); }

/* Saved-importer actions in the review header (importdock.js). */
.import-dock-row-actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}


/* /admin/replies: every right-of-reply response, newest first. */
.ror-admin-list {
    list-style: none;
    padding: 0;
}

.ror-admin-item {
    border-top: 1px solid light-dark(#e5e5e5, #333);
    padding: 8px 0;
}

.ror-acks {
    margin: 4px 0;
    padding-left: 18px;
    font-size: 0.85rem;
}

/* "Used in …" under the source header (docs/design/splitEditor.md, phase 1): the kind/author
   suffix recedes so the titles carry the line. */
.source-used-in-kind {
    color: light-dark(#7b858f, #868f99);
}

/* The writer's right-of-reply page (rightOfReply.md, "Writer's setup"): a mode of the
   walkthrough editor, and the panel on /publications. */
.ted-reply-panel {
  max-width: 46rem;
  margin: 1rem auto;
}
.ted-replybtn {
  margin-left: auto;
}
.ror-suggest-list {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
}
.ror-suggest-list li {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.35rem 0;
}
.ror-badge {
  display: inline-block;
  margin-left: 0.5rem;
  padding: 0.05rem 0.5rem;
  border: 1px solid var(--border, currentColor);
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 400;
  vertical-align: middle;
}
.ror-badge-draft { opacity: 0.7; }
.ror-badge-sent { border-color: var(--accent, currentColor); }
.ror-badge-responded { border-color: var(--accent, currentColor); background: var(--accent-soft, transparent); }
.ror-badge-revoked { border-color: var(--danger, currentColor); color: var(--danger, inherit); }
.ted-walk .tutorial-columns[hidden] {
  display: none;
}
