/* ============================================================================
 * Everything that sat at the foot of app.css: animations, the reduced-motion
 * overrides, the three media queries — and after them a tail of publish-stage
 * component rules (.preflight, .pub-opt, .pub-sheets__none) which are NOT
 * responsive and by name belong in publish.css.
 *
 * Loaded LAST, and that is deliberate: a media query moved earlier can be beaten
 * by a later non-media rule of equal specificity, so the position of this file is
 * load-bearing.
 *
 * The publish tail was left here rather than moved into publish.css because
 * moving it changes the cascade, and this split was verified by comparing the
 * computed style of every element on every screen against the arrangement
 * exactly as it stands. Moving those rules is a follow-up needing that same
 * check re-run — not a tidy-up to do in passing.
 *
 * Split out of app.css on 19-08-2026 (was lines 1636-1773). Rules are the
 * originals, sliced not retyped.
 * ========================================================================== */

/* =========================================================== ANIMATIONS */
@keyframes modalSlideIn { from { opacity: 0; transform: scale(0.9) translateY(-20px); } to { opacity: 1; transform: scale(1) translateY(0); } }
@keyframes fadeIn { to { opacity: 1; } }
@keyframes spin { to { transform: rotate(360deg); } }

/* ====================================================== REDUCED MOTION
 *
 * prefers-reduced-motion is an OPERATING-SYSTEM setting (Windows: Accessibility
 * → Visual effects → Animation effects; macOS: Reduce Motion). People turn it on
 * for vestibular disorders, where on-screen movement causes real dizziness — not
 * for a mild preference. Honouring it is not optional politeness.
 *
 * What goes: the modal's scale-and-slide. A dialog lurching in from 90% and 20px
 * above is a large area moving, which is the trigger — and this app opens dialogs
 * constantly (confirmations, history, the price picker, change password). Under
 * this setting the dialog simply appears, which is the intended outcome: it is
 * already announced by the backdrop covering the page.
 *
 * The reset is BLANKET rather than aimed at the four animations that exist
 * today, so an animation added next year is covered without anyone remembering
 * this block. Durations go to near-zero rather than `none`, because
 * `animation: none` and `transition: none` suppress the animationend and
 * transitionend events — nothing depends on one today, but the next thing added
 * might, and it would fail only for the users who set this preference, which is
 * the hardest kind of bug to notice.
 *
 * The spinners are then EXEMPTED back. The guidance exempts motion that carries
 * meaning, and a loading indicator is the textbook case: a spinner frozen
 * mid-turn does not read as "reduced motion", it reads as a hung screen.
 * Rotation in place is also not what provokes vestibular symptoms — nothing
 * traverses the viewport and the moving area is a few pixels across.
 */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }

    /* The deliberate exceptions — see above. */
    .spinner,
    .upload-btn.is-busy .fa-spinner {
        animation-duration: 0.8s !important;
        animation-iteration-count: infinite !important;
    }
}

/* =========================================================== RESPONSIVE */
@media (max-width: 991px) {
    .sidebar { transform: translateX(-100%); }
    .sidebar.mobile-open { transform: translateX(0); }
    .main { margin-left: 0; }
    .layout--collapsed .main { margin-left: 0; }
}
@media (max-width: 767px) {
    .content { padding: 1rem; }
    .usermenu__button span { display: none; }
}

/* ------------------------------------------------------------ Publish stage */
/* The print target. Off-screen rather than display:none — a hidden iframe does
   not lay out, and an unlaid-out document prints blank. */
.print-frame {
    position: fixed; right: 0; bottom: 0;
    width: 0; height: 0; border: 0; visibility: hidden;
}

/* What goes into the quotation, and what travels alongside it.

   Tiles rather than a bare checkbox list: every choice carries a sentence of
   explanation, and stacking those in one column runs them together into a wall
   of prose where nothing is scannable. */
.pub-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
    gap: 0.5rem;
}

/*
 * The pre-flight line on the Print card: what is about to come out, stated
 * beside the button that does it.
 *
 * Same pale cyan as .note-line, but a left rule instead of a full border, so it
 * reads as a caption on the card rather than as another banner. The Print card
 * can already carry two real banners (no company details, publishing blocked)
 * and a third identical box would flatten the difference between "here is what
 * happens" and "something is wrong".
 */
.preflight {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.55rem 0.7rem;
    margin-bottom: 0.7rem;
    background: var(--pale-cyan, #EAF7FA);
    border-left: 3px solid var(--cyan-ink, #0A7A97);
    border-radius: 0 var(--radius-btn, 8px) var(--radius-btn, 8px) 0;
    font-size: 0.78rem;
    color: var(--text-dark, #0F1E45);
}

.preflight i { color: var(--cyan-ink, #0A7A97); }
.pub-opt {
    display: flex; align-items: flex-start; gap: 0.6rem;
    padding: 0.7rem 0.8rem;
    border: 1px solid var(--light-silver, #E3E4E9);
    border-radius: var(--radius-btn, 8px);
    cursor: pointer;
    transition: border-color 0.15s ease, background-color 0.15s ease;
}
.pub-opt:hover { border-color: var(--cyan-ink, #0A7A97); }
.pub-opt--on { background: var(--pale-cyan, #EAF7FA); border-color: var(--soft-cyan, #C3E7F0); }
.pub-opt--locked { cursor: default; }
.pub-opt--locked:hover { border-color: var(--light-silver, #E3E4E9); }
.pub-opt--on.pub-opt--locked:hover { border-color: var(--soft-cyan, #C3E7F0); }
/* The reset that matters. Without it the global `input { width: 100% }` above
   stretches the box across the tile, centring the tick and pushing the label
   out to the far edge. */
.pub-opt input { width: auto; flex: none; margin: 2px 0 0; }
.pub-opt__text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.pub-opt__head { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
.pub-opt__label { font-size: 0.8rem; font-weight: 700; color: var(--text-dark, #0F1E45); }
.pub-opt__help { font-size: 0.72rem; line-height: 1.5; color: var(--slate, #55648A); }

/* Every quoted product, listed — including the ones with nothing on file. A
   list of only the sheets that exist cannot answer "what is missing?", which is
   the question this table is here to settle. */
/* Capped rather than full-bleed: four short columns stretched across the whole
   card read as a skeleton with the data flung to the corners. */
.pub-sheets { margin-top: 0.9rem; max-width: 900px; }
.pub-sheets th:first-child { width: 100%; }
.pub-sheets__name { font-weight: 600; color: var(--text-dark, #0F1E45); }
.pub-sheets__fg { width: 9rem; font-family: 'Courier New', monospace; font-size: 0.78rem; color: var(--slate, #55648A); }
.pub-sheets__doc { width: 7rem; }
.pub-sheets__none { font-size: 0.72rem; color: var(--slate, #55648A); }
