/* book-theme/shared.css
 *
 * Canonical CSS shared across Jupyter Book projects (thinkpy, thinkdsm, ...).
 * Each project keeps a real COPY at _static/shared.css (not a symlink --
 * that broke production once already, see ai_shared/memory/projects/
 * book-theme.md), synced by ai_shared/scripts/sync-book-theme.sh and
 * referenced in _config.yml's html_css_files BEFORE the project's own
 * custom.css so project-specific overrides can still win when a book
 * legitimately differs.
 *
 * Edit THIS file, then run sync-book-theme.sh to propagate it, then
 * commit/push each project. Do not fork a rule into a project's
 * custom.css unless that book is meant to look different on purpose --
 * if it should look the same, fix it here and re-sync.
 */

/* --- Primary sidebar TOC toggle triangle ---
 * Six coupled selectors control this one visual element: the expand/collapse
 * triangle must align on the same baseline as its menu-item text. Two
 * separate vertical offsets stack to produce the final position -- the
 * `summary` box's `top` AND the icon's `transform: translateY(...)` -- so
 * if the triangle looks off, check both, not just one.
 *
 * Regression history: on 2026-09-01 (thinkpy commit 0ed8f390, "Fix sidebar
 * arrow alignment") someone patched only the icon transform in one project
 * without the summary `top` offset, leaving the two projects at different
 * net offsets despite an identical intended rule ("align with menu text").
 * That's the reason this block now lives here instead of being duplicated
 * by hand in each project's custom.css.
 */

:root {
    --bd-primary-sidebar-width: 15.5rem;
    --bd-article-max-width: 68em;
    /* sphinx-book-theme 1.3.0's own default (17rem) reads noticeably wider
       than this project's proportions once the theme upgrade landed (see
       ai_shared/memory/projects/pst_sidebar_toggle_bug.md); narrow it to
       roughly match the primary sidebar. */
    --pst-sidebar-secondary: 14rem;
}

/* sphinx-book-theme 1.3.0 replaced the old checkbox+CSS off-canvas primary
 * sidebar with a JS class toggle: on screens >=992px, clicking the toggle
 * button adds/removes `.pst-sidebar-hidden` on `#pst-primary-sidebar`
 * (id="pst-primary-sidebar", class `.bd-sidebar-primary`).
 *
 * The theme's OWN default CSS for this state is `flex-basis:20%` (normal)
 * / `margin-left:-20%` (hidden) -- a percentage pair that cancels out
 * consistently, so hiding the sidebar doesn't disturb sibling layout at
 * all. This project overrides the normal (non-hidden) sidebar to a FIXED
 * `var(--bd-primary-sidebar-width)` (15.5rem) instead of the theme's 20%,
 * which breaks that percentage pairing: the theme's hidden-state margin is
 * still `-20%` (of the 1792px page-width container, i.e. ~358px), while
 * the reserved flex-basis it's supposed to cancel is our fixed 248px --
 * a ~110px mismatch. That mismatch corrupts the flex-grow calculation for
 * the sibling `.bd-main`, which balloons well past its own container's
 * width (confirmed live on thinkpy.org 2026-09-03: 2117px inside a 1792px
 * parent) and drags the toggle button off-screen to a negative
 * x-coordinate -- permanently unusable until the page is reloaded.
 *
 * (An even earlier version of this rule tried to fix a *different*,
 * smaller problem -- an empty gap left by the theme's hidden state not
 * touching width/flex-basis -- by forcing `width:0; flex:0 0 0`. That
 * made the same off-screen-button bug worse, not better; it was never
 * the width/flex-basis of the *hidden* state that mattered, it was the
 * mismatch against the *shown* state's fixed width.)
 *
 * Fix: give the hidden state the SAME fixed flex-basis as the shown
 * state (so nothing about the sidebar's reserved space changes at all,
 * matching the theme's own "hiding doesn't reflow siblings" intent) and
 * override the margin to the matching fixed negative value instead of
 * the theme's mismatched percentage. Verified live before/after: full
 * collapse -> reopen cycle keeps the toggle button on-screen and
 * clickable throughout (button x: 318 -> 70 -> 318).
 * See ai_shared/memory/projects/pst_sidebar_toggle_bug.md.
 */
@media (min-width: 900px) {
    .bd-sidebar-primary.pst-sidebar-hidden {
        width: var(--bd-primary-sidebar-width) !important;
        flex: 0 0 var(--bd-primary-sidebar-width) !important;
        margin-left: calc(-1 * var(--bd-primary-sidebar-width)) !important;
    }
}

/* sphinx-book-theme 1.3.0 also has its own internal breakpoint mismatch: the
 * capturing-phase listener above only intercepts clicks and uses the plain
 * class-toggle mechanism at >=992px (see its own matchMedia check); below
 * that, the click bubbles through to pydata-sphinx-theme.js's <dialog>-based
 * mechanism instead. But the theme's OWN CSS for styling that open dialog as
 * a proper fixed, full-height drawer only covers <=959.98px. Between 960px
 * and 991.98px the dialog opens (correctly, JS-wise) but gets none of that
 * positioning -- it falls back to the bare UA default <dialog> box (small,
 * bordered, centered-ish), which is what "toggles look different/broken in
 * a narrow window" turned out to be. This is an upstream breakpoint gap
 * (the JS and CSS thresholds don't line up), not something introduced here;
 * this project's own commitment to "half-screen desktop windows should
 * still work" (a few rules below) makes it visible. Give the dialog the
 * same fixed/full-height treatment the theme uses just below 960px so the
 * gap doesn't show. See ai_shared/memory/projects/pst_sidebar_toggle_bug.md.
 */
@media (min-width: 960px) and (max-width: 991.98px) {
    .bd-sidebar-primary[open] {
        position: fixed !important;
        top: 0 !important;
        left: 0 !important;
        height: 100vh !important;
        max-height: 100vh !important;
        margin: 0 !important;
        z-index: 1055;
    }
}

/* Both toggles fall back to a native <dialog>.showModal() below 992px (see
 * above), and a modal dialog ALWAYS paints a full-viewport ::backdrop
 * regardless of the dialog box's own size/position -- so even though the
 * primary/secondary drawers above are only ~250-350px wide and left/right
 * anchored, the rest of the page still gets dimmed and made inert behind
 * them. That directly contradicts this project's "half-screen desktop
 * windows should still show the primary book navigation" goal (a few rules
 * below): the reader can't see or use the article while the drawer is open.
 * Neutralize the backdrop's darkening in that same half-screen-desktop band
 * so opening either drawer reveals the page instead of covering it; true
 * mobile widths (<900px) keep the theme's normal dark, blocking backdrop,
 * since there's no room to show article + drawer together there anyway.
 * Verified live (both thinkpy.org and thinkdsm.org, real Chrome, 970px
 * viewport): background resolves to transparent, article stays fully
 * readable, and clicking outside the drawer still closes it as before.
 * See ai_shared/memory/projects/pst_sidebar_toggle_bug.md.
 */
@media (min-width: 900px) and (max-width: 991.98px) {
    #pst-primary-sidebar-modal::backdrop,
    #pst-secondary-sidebar-modal::backdrop {
        background: transparent !important;
    }
}

/* The transparent backdrop above stops the drawer from dimming the page,
 * but it doesn't fix the layout under it: `.showModal()` takes the dialog
 * out of normal flow (`position: fixed`), so it reserves no space and
 * `.bd-main` reflows to the FULL viewport width regardless -- the ~250px
 * (primary) / 350px (secondary) drawer then sits visually on top of the
 * article's first/last ~250-350px, hiding that text rather than revealing
 * the page next to it. Confirmed live (thinkpy.org, 900px, real Chrome):
 * article text visibly started mid-sentence, cut off behind the sidebar.
 * Push `.bd-main` over by the open drawer's width so content actually
 * docks beside it instead of underneath it -- this is what finally
 * delivers the "half-screen desktop windows show article + navigation
 * together" goal for real, rather than approximating it. `:has()` is used
 * to react to the dialog's `[open]` attribute without any JS; widely
 * supported in current Chrome/Firefox/Safari.
 * See ai_shared/memory/projects/pst_sidebar_toggle_bug.md.
 */
@media (min-width: 900px) and (max-width: 991.98px) {
    body:has(#pst-primary-sidebar-modal[open]) .bd-main {
        margin-left: var(--bd-primary-sidebar-width) !important;
    }

    body:has(#pst-secondary-sidebar-modal[open]) .bd-main {
        margin-right: 350px !important;
    }
}

/* --- Page shell and primary sidebar behavior ---
 * Keep the book usable in half-screen desktop windows. PyData/Sphinx Book
 * switches the primary sidebar to off-canvas below 960px. For our books,
 * half-screen desktop windows just below that breakpoint should still show
 * the primary book navigation by default, while smaller tablet/mobile widths
 * should keep the theme's normal off-canvas behavior.
 */

@media (min-width: 900px) {
    .bd-header.navbar {
        display: none !important;
        height: 0 !important;
        min-height: 0 !important;
        margin: 0 !important;
        padding: 0 !important;
        border: 0 !important;
    }
}

@media (min-width: 900px) {
    .bd-page-width {
        max-width: min(100%, 112rem) !important;
    }

    .bd-main .bd-content {
        justify-content: flex-start !important;
    }

    .bd-main .bd-content .bd-article-container {
        max-width: min(100%, var(--bd-article-max-width)) !important;
    }
}

@media (min-width: 900px) {
    .bd-sidebar-primary {
        flex: 0 0 var(--bd-primary-sidebar-width) !important;
        width: var(--bd-primary-sidebar-width) !important;
    }

    input#pst-primary-sidebar-checkbox:checked~.bd-container .bd-sidebar-primary {
        margin-left: calc(-1 * var(--bd-primary-sidebar-width)) !important;
        opacity: 0 !important;
        visibility: hidden !important;
    }

    body.bd-primary-sidebar-collapsed .bd-sidebar-primary {
        margin-left: calc(-1 * var(--bd-primary-sidebar-width)) !important;
        opacity: 0 !important;
        visibility: hidden !important;
    }

    .bd-header-article button.sidebar-toggle.primary-toggle {
        cursor: pointer !important;
        display: inline-flex !important;
        pointer-events: auto !important;
    }

    .bd-header-article {
        box-shadow: none !important;
    }
}

/* Below 960px the theme re-parents the sidebar's content from the plain
 * `#pst-primary-sidebar` div into `<dialog id="pst-primary-sidebar-modal">`
 * when the toggle opens it -- both elements carry the shared
 * `.bd-sidebar-primary` class. This rule targets the div's ID specifically
 * (not the shared class) so it only affects the resting/closed state and
 * never fights the dialog's own `position:fixed` full-height drawer styling.
 *
 * A class-scoped version of this rule shipped originally (pre-dialog,
 * checkbox-toggle era) and silently started clobbering the open dialog's
 * positioning once the theme upgrade introduced the dialog -- its
 * `position: sticky !important` beat the theme's `position: fixed` (same
 * specificity, but !important wins), leaving the open drawer as a
 * UA-default `position: absolute` box pinned to the right edge instead of
 * hidden or a proper left-anchored drawer. Confirmed via Chrome DevTools
 * Protocol (CSS.getMatchedStylesForNode) live on thinkpy.org at 937px.
 * The three sub-rules removed here (`input#pst-primary-sidebar-checkbox`,
 * `body.bd-primary-sidebar-collapsed`) referenced the checkbox mechanism
 * the theme upgrade already removed (see the fix-#1 note above) and were
 * pure dead weight even before this bug.
 *
 * `:not(.pst-sidebar-hidden)` was added after custom.js gained its own
 * hide toggle for this exact width band (see custom.js) -- without it,
 * this rule's `margin-left: 0 !important` (an ID selector) beat the
 * hide toggle's own `.bd-sidebar-primary.pst-sidebar-hidden` rule (an
 * ID always outranks a class combination), so "hiding" changed nothing
 * visually. Excluding the hidden state here instead of trying to out-
 * specificity it keeps this rule doing only what it was meant to do:
 * style the *resting* div.
 * See ai_shared/memory/projects/pst_sidebar_toggle_bug.md.
 */
@media (min-width: 900px) and (max-width: 959.98px) {
    #pst-primary-sidebar:not(.pst-sidebar-hidden) {
        flex-grow: 0 !important;
        height: auto !important;
        left: auto !important;
        margin-left: 0 !important;
        max-width: none !important;
        position: sticky !important;
        top: 0 !important;
        visibility: visible !important;
        z-index: auto !important;
    }
}

@media (max-width: 1199.98px) {
    body.bd-secondary-sidebar-open .bd-sidebar-secondary {
        margin-right: 0 !important;
        visibility: visible !important;
    }

    body.bd-secondary-sidebar-open label.overlay.overlay-secondary {
        height: 100vh !important;
        width: 100vw !important;
    }
}

@media (min-width: 1600px) {
    .bd-main .bd-content .bd-article-container {
        max-width: var(--bd-article-max-width) !important;
    }
}

.bd-sidebar-primary {
    line-height: 1.25 !important;
}

/* Keep sidebar drawer font sizes compact and consistent. */
div.bd-toc-item.navbar-nav,
div.bd-toc-item.navbar-nav a,
div.bd-toc-item.navbar-nav p.caption,
div.bd-toc-item.navbar-nav li {
    font-size: 0.85rem !important;
}

#pst-secondary-sidebar,
#pst-secondary-sidebar .sidebar-secondary-item,
#pst-secondary-sidebar .sidebar-secondary-item a,
#pst-secondary-sidebar .toc-entry a,
#pst-secondary-sidebar .nav-link {
    font-size: 0.85rem !important;
    line-height: 1.25 !important;
}

#pst-secondary-sidebar .sidebar-secondary-items,
#pst-secondary-sidebar .sidebar-secondary-items__start,
#pst-secondary-sidebar .sidebar-secondary-items__title,
#pst-secondary-sidebar nav.bd-toc-nav,
#pst-secondary-sidebar nav.bd-toc-nav ul,
#pst-secondary-sidebar nav.bd-toc-nav li,
#pst-secondary-sidebar nav.bd-toc-nav a,
.sidebar-secondary-item,
.sidebar-secondary-item a,
nav.bd-toc-nav,
nav.bd-toc-nav li,
nav.bd-toc-nav a {
    font-size: 0.85rem !important;
    line-height: 1.25 !important;
}

.bd-sidebar-primary nav.bd-links li,
.bd-sidebar-primary nav.bd-links li>a {
    line-height: 1.2 !important;
}

.bd-sidebar-primary nav.bd-links li.has-children {
    position: relative;
}

.bd-sidebar-primary nav.bd-links li.has-children > a {
    padding-right: 0.25rem !important;
    margin-right: 1.45rem;
    white-space: nowrap;
}

.bd-sidebar-primary nav.bd-links li.has-children > details {
    position: static !important;
}

.bd-sidebar-primary nav.bd-links li.has-children > details > summary {
    position: absolute;
    top: -0.18rem;
    right: 0.2rem;
    width: 1.2rem;
    height: calc(1.2em + 0.2rem);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transform: none;
    margin: 0;
    padding: 0;
    line-height: 1;
}

.bd-sidebar-primary nav.bd-links li.has-children > details > summary::-webkit-details-marker,
.bd-sidebar-primary nav.bd-links li.has-children > details > summary::marker {
    display: none;
    content: "";
}

.bd-sidebar-primary nav.bd-links .toctree-toggle,
.bd-sidebar-primary nav.bd-links .toctree-toggle i {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    vertical-align: middle;
    transform: translateY(-0.18rem);
}
