/* Module page layout, 2026-08-02.
   Loaded only on showfull pages, after style.css, so everything here can override the
   old single-column rules without touching them. Colours come from the existing
   :root / html.dark variables only, so dark mode keeps working with no extra rules. */

.pd {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 20px 40px;
}

/* The breadcrumb is rendered by main.tpl outside this layout and its inner block is
   absolutely positioned — it used to be laid over the old full-screen hero. With the
   compact head it lands on top of the title instead, so put it back in the flow.
   Scoped with :has() so blog posts, which share showfull.tpl's stylesheet but keep
   their own hero, are left alone. */
main:has(.pd) .speedbar { padding: 20px 0 0; }

main:has(.pd) .speedbar .over {
    position: static !important;
    font-size: 13px;
    color: var(--text-secondary);
}

main:has(.pd) .speedbar .over a { color: var(--text-secondary); }
main:has(.pd) .speedbar .over a:hover { color: var(--primary); }

/* The title spans both columns so that on a narrow screen the order can become
   title, then price card, then content — a price card above the module name reads
   as a shop widget that wandered onto the wrong page. */
.pd-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    grid-template-areas:
        "head head"
        "main side";
    column-gap: 40px;
    align-items: start;
}

.pd-head { grid-area: head; }
.pd-main { grid-area: main; min-width: 0; }
.pd-side { grid-area: side; }

/* ---------- head ---------- */

.pd-head {
    display: flex;
    gap: 20px;
    align-items: flex-start;
    padding: 16px 0 26px;
}

.pd-head-icon {
    flex: 0 0 auto;
    width: 64px;
    height: 64px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 16px;
    background: var(--primary);
}

/* The icons are inline SVGs stored in an extra field and most of them carry a hardcoded
   fill="#000000", which disappears into the coloured tile. Force them white in both
   themes, leaving fill="none" outlines alone so stroke-drawn icons still work. */
.pd-head-icon svg { width: 34px; height: 34px; color: #fff; }
.pd-head-icon svg [fill]:not([fill="none"]) { fill: #fff; }
.pd-head-icon svg [stroke]:not([stroke="none"]) { stroke: #fff; }

.pd-head h1 {
    margin: 0 0 10px;
    font-size: 30px;
    line-height: 1.2;
    color: var(--text);
}

/* Capped for readability: at full container width the summary ran the whole 1100px and
   read as a wall rather than a subtitle. */
.pd-lead {
    max-width: 720px;
    margin: 0;
    font-size: 16px;
    line-height: 1.65;
    color: var(--text-secondary);
}

.pd-lead p { margin: 0; }

/* ---------- section nav ---------- */

.pd-nav {
    position: sticky;
    top: 64px;
    z-index: 20;
    display: flex;
    gap: 4px;
    overflow-x: auto;
    scrollbar-width: none;
    margin-bottom: 8px;
    padding: 6px 0;
    background: var(--bg);
    border-bottom: 1px solid var(--border-color);
}

.pd-nav::-webkit-scrollbar { display: none; }

/* An underline rather than a filled pill: on the dark theme a 10%-opacity accent fill
   behind accent-coloured text turned into a muddy box with barely legible letters. */
.pd-nav a {
    flex: 0 0 auto;
    padding: 10px 14px;
    border-bottom: 2px solid transparent;
    font-size: 14px;
    color: var(--text-secondary);
    text-decoration: none;
    white-space: nowrap;
    transition: color .15s, border-color .15s;
}

.pd-nav a:hover { color: var(--text); }

.pd-nav a.is-active {
    color: var(--primary);
    border-bottom-color: var(--primary);
}

/* ---------- sections ---------- */

.pd-section { padding: 36px 0; border-bottom: 1px solid var(--border-color); }
.pd-section:last-child { border-bottom: 0; }

.pd-section > h2 {
    margin: 0 0 20px;
    font-size: 22px;
    color: var(--text);
}

.pd-section .static-text { max-width: 760px; }

/* ---------- sidebar ---------- */

.pd-side { position: sticky; top: 88px; }

.pd-card {
    background: var(--bg2);
    border: 1px solid var(--border-color);
    border-radius: 16px;
    padding: 22px;
    box-shadow: var(--card-shadow);
}

.pd-price-label {
    display: block;
    font-size: 13px;
    color: var(--text-secondary);
    margin-bottom: 2px;
}

.pd-price-value {
    display: block;
    font-size: 30px;
    font-weight: 600;
    color: var(--text);
    line-height: 1.1;
}

.pd-price-note {
    display: block;
    margin-top: 6px;
    font-size: 13px;
    color: var(--text-secondary);
}

.pd-buy {
    display: block;
    width: 100%;
    margin: 18px 0 6px;
    padding: 13px 20px;
    border-radius: 10px;
    background: var(--primary);
    color: #fff;
    font-size: 15px;
    text-align: center;
    text-decoration: none;
    border: 0;
    transition: background .15s;
}

/* style.css has `body a:hover { color: var(--secondary) }`, and the hover background here
   is var(--secondary) too — so on hover the label took exactly the colour of the fill it
   sits on and vanished. Two classes outrank `body a:hover`, so the label stays white. */
.pd-card .pd-buy:hover {
    background: var(--secondary);
    color: #fff;
}

.pd-card .pd-doc:hover {
    border-color: var(--primary);
    background: var(--btn-outline);
    color: var(--text);
}

.pd-specs {
    margin: 18px 0 0;
    padding: 18px 0 0;
    border-top: 1px solid var(--border-color);
    font-size: 14px;
}

.pd-spec {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    padding: 7px 0;
}

.pd-spec dt { color: var(--text-secondary); }
.pd-spec dd { margin: 0; color: var(--text); text-align: right; }

.pd-doc {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    margin-top: 18px;
    padding: 11px 16px;
    border: 1px solid var(--border-color);
    border-radius: 10px;
    font-size: 14px;
    color: var(--text);
    text-decoration: none;
    transition: border-color .15s, background .15s;
}

.pd-doc:hover { border-color: var(--primary); background: var(--btn-outline); }
.pd-doc svg { width: 16px; height: 16px; fill: currentColor; }

/* ---------- gallery ---------- */

.pd-gallery .xfieldimagegallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 14px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.pd-gallery .xfieldimagegallery li { margin: 0; }

.pd-gallery .xfieldimagegallery img {
    display: block;
    width: 100%;
    aspect-ratio: 4 / 3;
    /* The screenshots are tall admin captures. A centred crop shows a meaningless middle
       slice, so anchor to the top where the panel heading and toolbar are. */
    object-fit: cover;
    object-position: top center;
    background: var(--bg);
    border: 1px solid var(--border-color);
    border-radius: 10px;
    cursor: zoom-in;
    transition: border-color .15s, transform .15s;
}

.pd-gallery .xfieldimagegallery img:hover {
    border-color: var(--primary);
    transform: translateY(-2px);
}

.pd-hint {
    margin: 12px 0 0;
    font-size: 13px;
    color: var(--text-secondary);
}

/* ---------- lightbox ---------- */

/* The old rule capped the image at 648px, so a 1586x1929 screenshot opened at 32% and
   the interface text in it was unreadable. Let it use the whole viewport, and let
   product.js add a real zoom on top. */
.lightbox-content {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}

.lightbox-img {
    max-width: 94vw;
    max-height: 88vh;
    width: auto;
    height: auto;
    cursor: zoom-in;
    transform-origin: 0 0;
    transition: opacity .15s;
}

/* Only the cursor changes here. The magnification is done entirely by the transform in
   product.js, which is scaled against the fitted box — lifting max-width as well would
   grow the element to its natural size first and then scale that again, landing at
   roughly 300% instead of 100%. */
.lightbox-img.is-zoomed { cursor: grab; }

.lightbox-img.is-panning { cursor: grabbing; transition: none; }

.pd-zoom-hint {
    position: fixed;
    left: 50%;
    bottom: 26px;
    transform: translateX(-50%);
    padding: 7px 14px;
    border-radius: 20px;
    background: var(--lightbox2);
    color: var(--lightbox);
    font-size: 13px;
    pointer-events: none;
    z-index: 1002;
}

/* ---------- pricing ---------- */

.pd-plans-title { margin: 0 0 8px; font-size: 22px; color: var(--text); }

.pd-plans-sub {
    max-width: 640px;
    margin: 0 0 30px;
    font-size: 15px;
    line-height: 1.6;
    color: var(--text-secondary);
}

.pd-plans-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(215px, 1fr));
    gap: 16px;
    align-items: stretch;
}

/* Flex column so the call to action can be pushed to the bottom of every card with
   margin-top:auto — the three tiers list a different number of features and the buttons
   used to end up at three different heights. */
.pd-plan {
    position: relative;
    display: flex;
    flex-direction: column;
    padding: 26px 22px;
    background: var(--bg2);
    border: 1px solid var(--border-color);
    border-radius: 16px;
}

.pd-plan--best {
    border-color: var(--primary);
    box-shadow: 0 0 0 1px var(--primary);
}

.pd-plan-badge {
    position: absolute;
    top: -11px;
    left: 22px;
    padding: 4px 11px;
    border-radius: 20px;
    background: var(--primary);
    color: #fff;
    font-size: 11px;
    letter-spacing: .04em;
    text-transform: uppercase;
}

.pd-plan-name {
    margin-bottom: 14px;
    font-size: 13px;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

.pd-plan-price {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 7px;
}

.pd-plan-amount {
    font-size: 34px;
    font-weight: 600;
    line-height: 1;
    color: var(--text);
}

.pd-plan-cur { font-size: 19px; color: var(--text-secondary); }

.pd-plan-old {
    font-size: 14px;
    color: var(--text-secondary);
    text-decoration: line-through;
}

.pd-plan-off {
    padding: 3px 8px;
    border-radius: 20px;
    background: var(--btn-outline);
    color: var(--primary);
    font-size: 12px;
}

.pd-plan-save { margin-top: 7px; font-size: 13px; color: var(--success); }

.pd-plan-feats {
    margin: 24px 0 0;
    padding: 0;
    list-style: none;
}

.pd-plan-feats li {
    position: relative;
    padding: 9px 0 9px 26px;
    font-size: 14px;
    line-height: 1.5;
    color: var(--text);
    border-bottom: 1px solid var(--border-color);
}

.pd-plan-feats li:last-child { border-bottom: 0; }

/* Tick drawn from two borders instead of a glyph — no font dependency, no emoji
   fallback, and it takes the accent colour automatically. */
.pd-plan-feats li::before {
    content: "";
    position: absolute;
    left: 5px;
    top: 15px;
    width: 5px;
    height: 9px;
    border: solid var(--primary);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

/* A feature the tier does not include reads as a dash, not a tick. */
.pd-plan-feats li.is-off { color: var(--text-secondary); }

.pd-plan-feats li.is-off::before {
    top: 19px;
    left: 3px;
    width: 11px;
    height: 2px;
    border: 0;
    background: var(--text-secondary);
    transform: none;
}

.pd-plan-note {
    display: block;
    margin-top: 3px;
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--text-secondary);
}

.pd-plan-cta { margin-top: auto; padding-top: 22px; }

/* The buy control is rendered by the payments module and its markup differs between a
   guest and a signed-in customer, so style whatever it puts here. */
.pd-plans-grid .pd-plan-cta .btn,
.pd-plans-grid .pd-plan-cta > a,
.pd-plans-grid .pd-plan-cta button {
    display: block;
    width: 100%;
    padding: 12px 18px;
    border-radius: 10px;
    background: var(--btn-outline);
    border: 1px solid var(--primary);
    color: var(--primary);
    font-size: 15px;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    transition: background .15s, color .15s;
}

.pd-plans-grid .pd-plan-cta .btn:hover,
.pd-plans-grid .pd-plan-cta > a:hover,
.pd-plans-grid .pd-plan-cta button:hover {
    background: var(--primary);
    color: #fff;
}

.pd-plans-grid .pd-plan--best .pd-plan-cta .btn,
.pd-plans-grid .pd-plan--best .pd-plan-cta > a,
.pd-plans-grid .pd-plan--best .pd-plan-cta button {
    background: var(--primary);
    color: #fff;
}

.pd-plans-grid .pd-plan--best .pd-plan-cta .btn:hover,
.pd-plans-grid .pd-plan--best .pd-plan-cta > a:hover,
.pd-plans-grid .pd-plan--best .pd-plan-cta button:hover {
    background: var(--secondary);
    color: #fff;
}

/* Signed-in customers who already own a licence get a plain message instead. */
.pd-plan-cta > div {
    font-size: 14px;
    line-height: 1.5;
    text-align: center;
    color: var(--text-secondary);
}

/* ---------- changelog ---------- */

/* Updates Manager already trims its changelog to the two newest releases, but on a
   verbose module a single release runs 3300px — a third of the page on its own. Show
   the newest one only; the module's own "show all releases" button reveals the rest,
   and product.js drops this class when that button is pressed. */
.pd-main .changelog_section:not(.is-expanded) .um-release[data-index="1"] {
    display: none !important;
}

/* ---------- misc ---------- */

/* Admin-only shortcut. It used to render as a bare word under the sidebar card and
   looked like leftover debug text. */
.pd-edit {
    display: block;
    margin-top: 12px;
    text-align: center;
    font-size: 12px;
    color: var(--text-secondary);
    text-decoration: none;
    opacity: .6;
}

.pd-edit:hover { opacity: 1; color: var(--primary); }

/* ---------- description typography ---------- */

/* The article headings were 28.8px accent-coloured, which put a second giant green
   heading directly under the page title and read as two competing titles. Step them
   down so the hierarchy is: page title, then section, then article heading. */
.pd-main .static-text h2 {
    margin: 34px 0 12px;
    font-size: 22px;
    line-height: 1.3;
    color: var(--text);
}

.pd-main .static-text h3 {
    margin: 26px 0 10px;
    font-size: 17px;
    line-height: 1.35;
    color: var(--text);
}

.pd-main .static-text > *:first-child { margin-top: 0; }

/* ---------- table of contents ---------- */

.pd-main .pd-toc {
    margin: 0 0 28px;
    padding: 18px 20px;
    background: var(--bg2);
    border: 1px solid var(--border-color);
    border-radius: 12px;
}

.pd-main .pd-toc-title {
    margin-bottom: 10px;
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--text-secondary);
}

.pd-main .pd-toc ol {
    margin: 0;
    padding: 0;
    list-style: none;
    counter-reset: pd-toc;
}

.pd-main .pd-toc li { counter-increment: pd-toc; }

.pd-main .pd-toc a {
    display: block;
    padding: 7px 0;
    font-size: 15px;
    /* Inherited from the card rather than read from --text: these anchors are built in
       JS and sit in the article column, where the theme's generic link rules kept
       winning and left pale grey text on a white card. */
    color: inherit;
    text-decoration: none;
    border-bottom: 1px solid var(--border-color);
    transition: color .15s;
}

.pd-main .pd-toc li:last-child a { border-bottom: 0; }

.pd-main .pd-toc a::before {
    content: counter(pd-toc) ".";
    display: inline-block;
    min-width: 24px;
    color: var(--text-secondary);
}

.pd-main .pd-toc a:hover { color: var(--primary); }

/* ---------- code blocks ---------- */

/* Two systems were drawing the same block. prism.js injects its own stylesheet at
   runtime, so it lands after the theme's and its light block chrome overrode the dark
   one defined in style.css — the result read as two designs stacked on each other.
   Prism keeps doing what it is good at, the token colours; the block itself goes back
   to the site's own look. */
.static-text pre[class*="language-"] {
    margin: 20px 0;
    padding: 16px 20px;
    background: var(--code-bg);
    color: var(--code-text);
    border: 1px solid var(--border-color);
    border-radius: 10px;
    overflow-x: auto;
}

.static-text pre[class*="language-"] code {
    background: none;
    padding: 0;
    border-radius: 0;
    font-size: 13.5px;
    line-height: 1.6;
}

/* Every block had two copy controls: prism.js bundles the copy-to-clipboard plugin and
   js.js adds a .copy-code-btn of its own. Keep the site's one — it matches the rest of
   the design — and drop Prism's. */
.static-text .copy-to-clipboard-button,
.static-text div.code-toolbar > .toolbar { display: none !important; }

/* Prism's own palette was drawn for its light background, so on the site's dark block
   half the tokens sat at the wrong contrast and the highlighting looked broken. One
   coherent palette instead — literal values rather than theme variables, because
   --code-bg is dark in both light and dark mode, so the block never changes. */
.static-text pre[class*="language-"] .token.comment,
.static-text pre[class*="language-"] .token.prolog,
.static-text pre[class*="language-"] .token.doctype,
.static-text pre[class*="language-"] .token.cdata { color: #7f848e; font-style: italic; }

.static-text pre[class*="language-"] .token.punctuation { color: #9aa2b1; }

.static-text pre[class*="language-"] .token.tag,
.static-text pre[class*="language-"] .token.keyword,
.static-text pre[class*="language-"] .token.selector,
.static-text pre[class*="language-"] .token.important,
.static-text pre[class*="language-"] .token.deleted { color: #e06c75; }

.static-text pre[class*="language-"] .token.attr-name,
.static-text pre[class*="language-"] .token.property,
.static-text pre[class*="language-"] .token.number,
.static-text pre[class*="language-"] .token.boolean,
.static-text pre[class*="language-"] .token.constant,
.static-text pre[class*="language-"] .token.symbol { color: #d19a66; }

.static-text pre[class*="language-"] .token.attr-value,
.static-text pre[class*="language-"] .token.string,
.static-text pre[class*="language-"] .token.char,
.static-text pre[class*="language-"] .token.inserted,
.static-text pre[class*="language-"] .token.builtin { color: #98c379; }

.static-text pre[class*="language-"] .token.function,
.static-text pre[class*="language-"] .token.class-name,
.static-text pre[class*="language-"] .token.atrule { color: #61afef; }

.static-text pre[class*="language-"] .token.operator,
.static-text pre[class*="language-"] .token.entity,
.static-text pre[class*="language-"] .token.url,
.static-text pre[class*="language-"] .token.regex,
.static-text pre[class*="language-"] .token.variable { color: #56b6c2; background: none; }

/* The site's own copy button, positioned consistently on every block. */
.static-text .copy-code-btn {
    top: 10px;
    right: 10px;
    color: #9aa2b1;
    background: rgba(255, 255, 255, .06);
    border: 1px solid rgba(255, 255, 255, .12);
    border-radius: 8px;
}

.static-text .copy-code-btn:hover { color: #fff; background: rgba(255, 255, 255, .14); }

/* ---------- button contrast ---------- */

/* style.css paints button text with var(--white), but in the dark theme --white is
   #1e1f20 — a surface colour, not white — so a label on a filled button turned
   near-black and vanished. Only buttons that actually have a filled accent background
   are forced light here: an earlier blanket rule covering every .btn inside .pd also
   caught the outline buttons in the pricing cards and bleached their labels to white on
   a pale tint. The same bug affects filled buttons elsewhere on the site; fixing it
   globally means the same declaration in style.css. */
.pd .download-btn,
.pd .download-btn:hover { color: #fff; }

.pd-main .changelog_section,
.pd-main .screenshots,
.pd-main .static { padding: 0; background: none; border: 0; }

.pd-main .um-changelog { margin: 0; }

/* ---------- responsive ---------- */

@media (max-width: 1080px) {
    /* Price must not fall below the fold on a phone: the card sits directly under the
       title instead of at the end of the document. */
    .pd-grid {
        grid-template-columns: 1fr;
        grid-template-areas:
            "head"
            "side"
            "main";
    }

    .pd-side { position: static; margin-bottom: 20px; }
    .pd-head { padding-bottom: 18px; }
    .pd-nav { top: 56px; }
}

@media (max-width: 640px) {
    .pd { padding: 0 16px 32px; }
    .pd-head { gap: 14px; padding-top: 22px; }
    .pd-head-icon { width: 48px; height: 48px; border-radius: 12px; }
    .pd-head-icon svg { width: 26px; height: 26px; }
    .pd-head h1 { font-size: 23px; }
    .pd-section { padding: 26px 0; }
    .pd-gallery .xfieldimagegallery { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; }
    .lightbox-img { max-width: 100vw; max-height: 82vh; }
}
