#viewer {

    display: flex;
    flex-direction: column;

    /* Horizontal is the CROSS axis here, so align-items is what centres the
     * pages.  Plain `center` silently loses content once a page is wider
     * than the viewer: centring pushes the overflow out both sides equally,
     * and the left half is then unreachable because scrollLeft cannot go
     * below 0 — at 225% zoom that hid ~120px of every page behind the
     * sidebar edge with no way to scroll to it.
     *
     * `safe center` keeps the centring while it fits and falls back to
     * start-alignment the moment it does not, so the whole page stays
     * reachable by scrolling.  The plain value is declared first as the
     * fallback for engines that do not know the `safe` keyword — they keep
     * today's behaviour rather than losing the centring altogether. */
    align-items: center;
    align-items: safe center;

    gap: 32px;

    /* The horizontal gutter lives HERE rather than on #viewerContainer,
     * because a scroll container's end-side padding is not part of its
     * scrollable overflow: with padding on the scroller, scrolling fully
     * right left the page flush against the edge and 32px of scroll range
     * missing.  Sizing the scrolled content to max-content makes it grow
     * around the widest page, so its own padding is real on both sides and
     * the gutter survives at either extreme.  min-width keeps it filling
     * the viewer while the pages still fit. */
    box-sizing: border-box;
    width: max-content;
    min-width: 100%;
    padding-inline: 32px;
}

#viewer.placeholder {

    min-height: 100%;

    justify-content: center;

    color: var(--text-dim);
}

.split-group-label {

    width: 100%;

    padding: 8px 12px;

    border-top:
        2px dashed var(--accent);

    color: var(--accent);
    font-size: 13px;
    font-weight: 600;

    text-align: center;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.split-group-label:first-child {

    border-top: none;
    padding-top: 0;
}

.split-preview-truncated {

    color: var(--text-dim);

    text-transform: none;
    letter-spacing: 0;
    font-weight: 400;
    font-style: italic;

    border-top-style: solid;
}

/* Sticky rail that pins the Split PDF / Download overlay button to the
 * middle of the visible #viewerContainer area while the user scrolls the
 * page previews underneath.  Mirrors the structure of .render-waiter-rail:
 * a zero-height sticky positioning anchor whose only child is the visible
 * pill, so the rail itself never displaces page content. */
.split-overlay-rail {

    position: sticky;

    top: 0;
    height: 0;

    display: flex;
    justify-content: center;
    align-items: flex-start;

    z-index: 10;
    pointer-events: none;
}

.split-overlay-rail[hidden] {

    display: none;
}

.split-overlay-btn {

    /* Vertical offset puts the button at roughly the centre of the
     * #viewerContainer's visible area (min-height: 70vh, so ~35vh = mid). */
    margin-top: 32vh;

    padding: 16px 32px;

    border: none;
    border-radius: 12px;

    background: var(--accent);
    color: var(--text);

    font-size: 16px;
    font-weight: 600;

    cursor: pointer;

    box-shadow: var(--shadow);

    transition: .15s;

    pointer-events: auto;
}

.split-overlay-btn:hover:not(:disabled) {

    transform: translateY(-1px);
}

.split-overlay-btn:disabled {

    opacity: .7;
    cursor: progress;
}

/* Transparent overlay shown in create.php when the generated PDF would
 * exceed the auto-render page cap.  The backdrop itself is transparent
 * and pointer-events: none so the user can still scroll the viewer
 * underneath; only the centred button captures clicks. */
.create-overlay {

    position: absolute;

    inset: 0;

    display: flex;

    align-items: center;
    justify-content: center;

    background: transparent;

    pointer-events: none;

    z-index: 4;
}

.create-overlay[hidden] {

    display: none;
}

.create-overlay-btn {

    pointer-events: auto;

    padding: 16px 32px;

    border: none;
    border-radius: 12px;

    background: var(--accent);
    color: var(--text);

    font-size: 16px;
    font-weight: 600;

    cursor: pointer;

    box-shadow: var(--shadow);

    transition: .15s;
}

.create-overlay-btn:hover {

    transform: translateY(-1px);
}

.page {

    position: relative;

    background: white;

    box-shadow: var(--shadow);
}

.pdf-layer {

    display: block;
}

/* ──────────────────────────────────────────────────────────────
 * Drag-to-pan affordance (ui/viewerPan.js).
 *
 * Offered only once there is a rendered page to pan — the placeholder
 * ("Open a PDF to start editing.") must not look draggable — and never
 * while one of edit.php's placement tools owns the drag, where the page
 * already shows a text/copy/crosshair cursor of its own.  Those tool
 * classes exist only on edit.php; the selector is simply inert elsewhere.
 * ──────────────────────────────────────────────────────────── */
#viewerContainer:has(.page):not(.tool-add-text):not(.tool-add-image):not(.tool-select) {

    cursor: grab;
}

#viewerContainer.viewer-panning {

    cursor: grabbing;

    /* a pan drag must not paint a text selection across the page */
    user-select: none;
}

/* Render-in-progress overlay.  Two-element structure:
 *  - .render-waiter-rail: the positioning frame, prepended to
 *    #viewerContainer.  As a flex parent it horizontally centres the pill.
 *  - .render-waiter: the visible pill, hidden by default.
 *
 * The rail is FIXED to the viewport, not sticky inside #viewerContainer.
 * Sticky was the original design and it silently failed: #viewerContainer
 * declares overflow:auto but never actually scrolls — #workspace is
 * content-height, so the container grows to fit every page (measured 3347px
 * of content in a 3347px box) and the *page* is what scrolls.  A sticky
 * child of a box that cannot scroll has nothing to stick to, so the pill
 * simply sat at the top of the viewer: on a 1440x900 desktop it was at
 * y=578 (visible) at the top of the document but y=-1922 (off-screen) once
 * you had scrolled to page 3, and on a 390x844 phone it was never on screen
 * at all.  That is the "sometimes does not appear".  Fixed positioning is
 * what "always visible while rendering" actually requires.
 *
 * Bottom-centred: the top of the viewport is taken by the 56px topbar plus,
 * on a phone, the sticky editor toolbar (~250px, see edit.css).  z-index sits
 * above the toolbar (20) and page content, below the topbar and modals (1000+). */
.render-waiter-rail {

    position: fixed;
    inset: auto 0 0 0;

    display: flex;
    justify-content: center;

    padding-bottom: 24px;

    z-index: 900;
    pointer-events: none;
}

/* Every colour goes through a token — the pill used to hard-code a near-black
 * fill (rgba(11,18,32,.88)) while taking its ink from --text, which is
 * near-black in the light skin: black-on-black, contrast ratio ~1.0, i.e.
 * invisible for every light-skin user.  --panel/--text move together, so the
 * pill now reads correctly in both skins.  The flat colour is declared first
 * as the fallback for engines without color-mix(). */
.render-waiter {

    display: none;
    align-items: center;
    gap: 10px;

    padding: 10px 14px;
    max-width: calc(100% - 24px);

    border-radius: 999px;

    background: var(--panel);
    background: color-mix(in srgb, var(--panel) 88%, transparent);
    color: var(--text);

    box-shadow: inset 0 0 0 1px var(--border), var(--shadow);
    backdrop-filter: blur(4px);

    pointer-events: none;
}

.render-waiter.is-visible {

    display: inline-flex;
}

.render-waiter-spinner {

    width: 16px;
    height: 16px;

    /* the unfilled part of the ring: was hard-coded white-on-dark, which
     * disappeared against the light-skin pill */
    border:
        2px solid var(--border);

    border-top-color: var(--accent);

    border-radius: 50%;

    animation:
        render-waiter-spin .8s linear infinite;
}

.render-waiter-label {

    font-size: 13px;
    line-height: 1;
}

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