div.b-widget {
    font-family : inherit;
}

div.pricing-offer__box-most-popular p {
    font-weight : 600;
}

.pricing-offer__box-s-attribute ul li:before {
    top : .5rem !important;
}

.wpcf7 form div.wpcf7-response-output {
    margin : 0
}

.oem-request .contact__socials-address,
.single-dev-request .contact__socials-address {
    display : none;
}

.page-template-checkout-page ul.bry-header__mmItems,
.page-template-checkout-page footer {
    display : none;
}

.main.checkout {
    padding-bottom : 10rem;
}

.payment-section h4,
.payment-options {
    display     : flex;
    align-items : center;
    overflow    : hidden;
}

.payment-section h4 {
    justify-content : space-between;
}

.payment-options {
    gap : 1rem;
}

.payment-options img {
    width : 4rem;
}

#card-element {
    padding : 2rem;
}

.form__label.card {
    padding : 0;
}

.form .wpcf7-not-valid-tip {
    line-height : 2rem;
    margin-top  : 0.5em;
}

.wpcf7 .form__checkbox .wpcf7-list-item-label:after {
    top : .5rem !important;
}

.singlePost table th {
    font-weight : 600;
}

section.singlePost__blog-footer {
    margin : 0 auto 4rem auto;
}

.changelog .badge {
    margin-inline-end : 0.5rem;
}

.checkout-renewal #coupon_validate_section {
    display : none;
}

.changelog .ul-bug-fixes .ticket-link {
    min-width: 2.6em !important;
    width: auto;
}

.author-bio-section,
.pricing-offer__box.lite {
    display:none !important;
}

/* Safari can render Prism punctuation as transparent on blog code samples.
   Keep this override out of product pages and interactive demos. */
.single-post .token.punctuation {
    opacity: 1 !important;
}


.introvideo .container {
    text-align : center;
}

.introvideo iframe {
    width : 80%;
    height : auto !important;
    aspect-ratio: 16 / 9;
}

iframe[src*="youtube"] {
    border-radius : 1.6rem;
}

@media (max-width: 1024px) {
    iframe[src*="youtube"] {
        flex: 0 0 100%;
        padding: 0;
        width: 100%;
    }
}

.wp-block-image.size-large {
    max-width: 100%;
}

.single-post figcaption {
    font-size : .8em;
    color:#777;
}
/* Avoid conflicts with WP */
.b-resource-info {
    line-height: 1.3;
}

.b-resource-avatar {
    max-width:unset;
}

body > .b-drag-proxy {
    font-size : 1.5rem;
}

/* Contact Form 7 >= 5.x always renders the response container (empty, aria-hidden)
   instead of hiding it, so the theme's styled green/checkmark box showed on page
   load. Hide it only while it is empty. CF7 6.x keeps aria-hidden="true" on the
   container even after a message is inserted (the screen-reader live region is a
   separate element), so we must NOT key off aria-hidden or the success/thank-you
   message stays hidden. */
.wpcf7-response-output:empty {
    display: none !important;
}

/* Show the reCAPTCHA v3 badge (overrides the theme's .grecaptcha-badge{visibility:hidden}).
   Google's terms require either showing the badge or the "protected by reCAPTCHA" notice. */
.grecaptcha-badge {
    visibility: visible !important;
}

.singlePost__cta {
    margin: 48px auto;
    max-width: 820px;
    padding: 32px;
    border: 1px solid #e2e4ec;
    border-radius: 12px;
    background: linear-gradient(135deg, #f7f8fc 0%, #eef1fb 100%);
    text-align: center;
}
.singlePost__cta-title {
    margin: 0 0 8px;
    font-size: 24px;
    line-height: 1.25;
    color: #1b1c2e;
}
.singlePost__cta-text {
    margin: 0 0 24px;
    font-size: 16px;
    color: #4a4c63;
}
.singlePost__cta-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    justify-content: center;
}
/* Selectors are prefixed with .singlePost__cta so they win over the theme's
   .wysywig a link color (#0076f8), which otherwise tints the button text. */
/* The theme forces link color with `color: var(--bryntum-primary) !important`,
   so the button text colors must use !important to win (otherwise the primary
   button gets blue-on-blue text). */
.singlePost__cta .singlePost__cta-btn {
    display: inline-block;
    padding: 12px 22px;
    border: 1px solid #c4c8dc;
    border-radius: 8px;
    background: #fff;
    color: #0076f8 !important;
    font-weight: 600;
    text-decoration: none;
    transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.singlePost__cta .singlePost__cta-btn:hover {
    border-color: #0076f8;
    color: #0061cf !important;
}
.singlePost__cta .singlePost__cta-btn--primary {
    background: #0076f8;
    border-color: #0076f8;
    color: #fff !important;
}
.singlePost__cta .singlePost__cta-btn--primary:hover {
    background: #0061cf;
    border-color: #0061cf;
    color: #fff !important;
}
@media (max-width: 600px) {
    .singlePost__cta { padding: 24px 18px; }
    .singlePost__cta-actions { flex-direction: column; }
    .singlePost__cta-btn { width: 100%; }
}

/* Related-posts module on single posts (audit F13 / ticket 49801007).
   The cards reuse the homepage post-list component (.post-list__wrapper +
   includes/Components/SinglePostItemVerticalSmall), so only the section
   wrapper and heading need styling here; the grid and card styles come from
   block-post-list/_style.scss and the component's own SCSS. */
.singlePost__related {
    margin: 0 auto 8rem auto;
}
.singlePost__related-title {
    margin: 0 0 2rem;
    font-size: 2.4rem;
}

/* "Code samples may be outdated" admonition, rendered before the post body by
   single-post.php. Ported from the inline <style> of the old WP block pattern
   and scoped to .singlePost__admonition so it never affects genuine post quotes.
   The theme's default blockquote styling adds decorative quote marks and a
   fixed/narrow box, which we override here. */
.singlePost__admonition {
    /* Match the 72rem centered column of .singlePost__section so the admonition
       lines up with the post body (it used to sit inside the content). */
    max-width: 72rem;
    margin: 0 auto;
}
.singlePost__admonition blockquote.wp-block-quote::before,
.singlePost__admonition blockquote.wp-block-quote::after {
    display: none !important;
}
.singlePost .singlePost__admonition blockquote.wp-block-quote {
    display: block !important;
    width: 100% !important;
    box-sizing: border-box !important;
    padding: 2rem 3.5rem !important;
    height: auto !important;
    min-height: auto !important;
    margin: 3rem auto !important;
}
.singlePost .singlePost__admonition blockquote.wp-block-quote > p {
    padding: 0 !important;
}

/* Footer rating badges (ticket 49328771), rendered by includes/layout/footer.php:
   Capterra, GetApp and Software Advice.

   The row sits INSIDE the footer's first .container, after .footer__main-section
   and before .footer__section. That position is load-bearing. The thin divider
   under the badges is .footer__section's own
   `border-top: .1rem solid var(--bryntum-secondary)`, and .footer__section is a
   full-width element while .container is not. Putting the badges above it keeps
   that divider full width and unchanged, where redrawing it under a badge row
   inside .container would have shortened it to the container width. Do not move
   the markup back inside .footer__section: the line would jump above the badges
   again.

   Hidden by default and revealed only once a badge <img> has a src, which
   Cookiebot sets when the visitor accepts marketing cookies (see footer.php).
   Without this the src-less <img>s render as broken-image glyphs plus their alt
   text, and each <a> around them would be an empty link. display:none takes the
   whole row out of the accessibility tree too, so pre-consent the footer is
   exactly what it is today.

   A browser too old for :has() simply never shows the row - the safe way to
   fail. Each <img> carries its own intrinsic width/height so the row does not
   shift as the badges arrive.

   The three badges have very different aspect ratios (Capterra 278x91, Software
   Advice 507x205, GetApp 1667x1125 - GetApp stacks logo, label and stars, so it
   is nearly square). They are sized by height rather than width, which is what
   makes the row read as a set. The target is a 64px frame, 20% below the 80px
   the home-page badge row renders these same three assets at (its
   `max-height: 8em` resolves to 80px). The smaller footer row is deliberate and
   was asked for; it is not meant to match the home page.

   Be aware of what the 20% costs. GetApp spends its height on three stacked
   bands where the other two spend it on one, so its rating is always the
   smallest of the three: about 8.6px at an 80px frame, about 7px at 64px. That
   is close to the size that made the original 56px row unusable. If the rating
   digits ever need to be legible again, raising this target is the lever.

   The heights are set PER BADGE, and that is deliberate - do not collapse them
   back to one rule. Giving all three the same box height leaves their visible
   frames unequal, because each vendor insets its frame differently inside its
   own artwork. Measured off the assets, per edge: GetApp's white border 1.4%,
   Software Advice's orange border 4.9%, Capterra's panel 0.77% (its SVG has no
   printed frame, so its frame is the keyline added below, which sits on the
   panel edge and adds 1.5px outside it rather than eating into the box). At a
   common 80px box that renders frames of Capterra 83px, GetApp 78px, Software
   Advice 72px - an 11px spread, and the thing the eye actually compares. The
   per-badge heights below cancel each vendor's inset so all three land on 80px.
   Every inset is symmetric top to bottom, so with align-items:center the frames
   align exactly.

   Note the vendor order: this block lists Capterra, Software Advice, GetApp
   (widths, aspect ratios) EXCEPT for the frame-height figures just above, which
   read Capterra, GetApp, Software Advice. Each of those is labelled for that
   reason. Check the label, not the position, before reusing a number.

   If a vendor reissues an asset, re-measure its inset rather than nudging these
   numbers by eye: frame height / box height, then height = 80px / that ratio.

   At these heights the rendered widths are 189.4 / 175.6 / 97.8px, so with the
   two 3.2rem gaps (unchanged by the resize, deliberately) the row needs 527px of
   content width. `.container` is border-box with 4rem of side padding above
   480px and 2rem at 480px and below (_dev/scss/layout/_grid.scss), so the three
   sit on one line from about a 607px viewport up and wrap below that. That 607
   is derived rather than measured - assume it is a pixel out either way, the way
   the earlier 705-vs-706 was.

   Below 480px they step down to a 56px frame on the same arithmetic. That step
   is now small (64px to 56px) because the desktop row shrank while the mobile
   sizes were left alone: shrinking those too would have put GetApp's rating
   near 5px, and mobile did not need it, since the narrower widths already wrap
   without a slab. The step happens at the same 480px
   breakpoint where .container drops to 2rem padding, so both changes to the
   available width land together. flex-wrap is what keeps either size from
   overflowing next to the copyright line. */
.footer__ratings {
    display: none;
}
.footer__ratings:has(img[src]) {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 1.6rem 3.2rem;
    margin: 1.6rem 0 3.2rem;
}
.footer__ratings img {
    width: auto;
}
/* 64px frame / vendor inset ratio. Capterra: (64 - 2x the 1.5px keyline)
   / 0.9846, its panel being inset 0.77% per edge inside the SVG box.
   Frames land at 64.05 / 64.13 / 64.07px. */
.footer__ratings-capterra {
    height: 6.2rem;
}
.footer__ratings-getapp {
    height: 6.6rem;
}
.footer__ratings-softwareadvice {
    height: 7.1rem;
}
@media (max-width: 480px) {
    /* same arithmetic against a 56px frame */
    .footer__ratings-capterra {
        height: 5.4rem;
    }
    .footer__ratings-getapp {
        height: 5.75rem;
    }
    .footer__ratings-softwareadvice {
        height: 6.2rem;
    }
}

/* The three vendors do not agree on framing: GetApp bakes a white border into
   its PNG and Software Advice an orange one, while Capterra's SVG carries no
   frame, so against the navy footer it was the one badge with no edge.

   This is a keyline built from drop-shadows rather than a `border` because
   Capterra's artwork does not fill its own box: the teal panel starts 7.4% in
   from the left and the paper-plane logo overhangs into that gap. A border
   would therefore float about 18px off the panel's left edge at this size,
   with the plane crossing it, and show the panel's own rounded corner nested
   inside a second one. drop-shadow follows the image's alpha, so the line hugs
   the panel and the plane the way the other two vendors' borders hug theirs.

   #68c5ed is Capterra's own accent, taken from the badge artwork - it is the
   colour of the "4.8" and the divider rule - so no colour is invented. 1.5px
   sits between the rendered thickness of the other two at 8rem (GetApp's white
   is about 2px, Software Advice's orange about 1.6px).

   Note this is a presentational keyline around the badge, not an edit to the
   vendor asset: the artwork itself is untouched and unscaled. */
.footer__ratings-capterra {
    filter:
        drop-shadow( 1.5px 0     0 #68c5ed)
        drop-shadow(-1.5px 0     0 #68c5ed)
        drop-shadow(0      1.5px 0 #68c5ed)
        drop-shadow(0     -1.5px 0 #68c5ed);
}

/* Hero animated words (ticket 49364112).

   The hero H1 used to hold all four animated phrases as text, so the heading
   text Google indexes read "World Class Web Components For SchedulingGantt
   ChartsCalendarsKanban Boards" - four category names run together.

   The fix is in the markup (scripts/homepage-hero-animated-words): the first
   phrase stays a text node, and the other three become empty spans carrying
   their phrase in a data-word attribute. The H1's text is therefore one clean
   phrase, while the page still shows four layers to animate.

   This is the only rule needed to paint them, because generated content is not
   part of the DOM's text - which is exactly why it is used here. Everything
   else is untouched: the layers keep their original .one/.two/.three/.four
   classes, so the theme bundle's existing `show-height` animation and its
   0/3/6/9s delays drive the crossfade unchanged, with no JavaScript involved
   and nothing for a slow load to make flicker. */
.animated-words span[data-word]:after {
    content: attr(data-word);
}

/* Heading hierarchy (ticket 49364129).

   Two of the home page's headings were styling choices rather than document
   structure - the framework labels under the integration logos and the name
   under the customer quote - and the migration in
   scripts/homepage-heading-hierarchy turns them into paragraphs carrying the
   theme's own .h5_style / .h6_style typography classes. Those classes restore
   the heading font, but two rules in the compiled bundle are keyed to the tag
   name and stop matching, so they are restated here for the paragraph.

   Both blocks appear on other pages too, where the headings have not been
   migrated; those keep matching the bundle's h5/h6 rules and are unaffected. */

/* .text-languages__box-text h5 in the bundle: 1.6rem, 2rem from large-phone up.
   Without this the label falls back to .h5_style's own 2rem/2.2rem and the logo
   row's labels come out a size too big. */
.text-languages__box-text p.h5_style {
    font-size: 1.6rem;
}
@media (min-width: 481px) {
    .text-languages__box-text p.h5_style {
        font-size: 2rem;
    }
}

/* .quote-image__text p in the bundle (0,1,1) outweighs .h6_style (0,1,0), so as
   a paragraph the attribution would take the 2.2rem/400/160% quote-body type.
   Restated at (0,2,1) - the h6 metrics, plus the h6's own top padding. */
.quote-image__text p.h6_style {
    font-size: 1.8rem;
    font-weight: 500;
    line-height: 120%;
    letter-spacing: .01em;
    padding: 1.6rem 0 0;
}
@media (min-width: 481px) {
    .quote-image__text p.h6_style {
        font-size: 2rem;
    }
}
@media (min-width: 1025px) {
    .quote-image__text p.h6_style {
        padding: 1.7rem 0 0;
    }
}
/* Visible breadcrumb trail (ticket 49801014), rendered by
   bryntum_the_breadcrumbs() in the theme's functions.php on single posts and
   blog category archives.

   This file rather than the theme SCSS: the compiled theme CSS bundle is a
   Nov 2022 build that is not rebuilt on deploy, so theme SCSS edits never
   reach the site. /css/custom.css is a plain deployed file, cache-busted by
   filemtime() in header.php - the same route the footer rating badge uses.

   Keep this as a quiet utility row above the centered blog hero. Constrain
   the repeated current title so the trail does not become a second heading. */
.breadcrumbs {
    margin    : 0 0 3.2rem;
    max-width : 100%;
}
.singlePost__header > .breadcrumbs {
    transform : translateY(-.8rem);
}
/* On the category archive the trail is the first thing inside `.main.posts`.
   The site header is fixed and `.main` starts at y=0 behind it, so the trail
   only clears the nav once its own padding exceeds (header height - .main
   padding-top). Both of those move with the viewport, measured on the sandbox:

     width      header   .main pad   minimum
     <=767px     88px      40px       48px
     768-1024    96px      60px       36px   <- tallest header, two-row nav
     >=1025px    88px      60px       28px

   2.4rem cleared none of them, so the trail sat behind the nav (4px at
   1440px, 12px at 768px). Each value below is its minimum plus roughly 3rem
   of air, which lands the trail about 40px under the nav on desktop and
   tapers on narrow screens where vertical space is worth more. Single posts
   need none: .singlePost__header already has its own. */
.posts .breadcrumbs {
    margin-bottom : 2.4rem;
    padding-top   : 7.2rem;
}
@media (min-width : 768px) {
    .posts .breadcrumbs {
        padding-top : 7.6rem;
    }
}
@media (min-width : 1025px) {
    .posts .breadcrumbs {
        padding-top : 7.2rem;
    }
}
.posts .breadcrumbs + .hero-text {
    padding-top : 3.2rem;
}
.breadcrumbs__list {
    display         : flex;
    flex-wrap       : nowrap;
    align-items     : center;
    justify-content : center;
    gap             : .8rem;
    max-width       : 100%;
    margin          : 0;
    padding         : 0;
    list-style      : none;
    overflow        : hidden;
}
.breadcrumbs .breadcrumbs__item {
    display         : flex;
    align-items     : center;
    flex-shrink     : 0;
    gap             : .4rem;
    margin          : 0;
    min-width       : 0;
    font-size       : 1.7rem;
    font-weight     : 600;
    line-height     : 1.5;
    letter-spacing  : .015em;
    color           : #747683;
}
.breadcrumbs__item:before {
    content : "\203A";
    color   : #747683;
    opacity : .55;
}
.breadcrumbs__item:first-child:before {
    content : none;
}
.breadcrumbs__item:last-child {
    flex-shrink : 1;
    max-width   : 40rem;
}
/* Two classes deep so both templates land on the same colours. Inside a post
   the theme's `.singlePost a:not(...)` rule would otherwise claim these links
   and the category archive's would not, leaving the same component blue on one
   page and grey on the other. This is the theme's own link treatment, stated
   explicitly; custom.css is loaded after the inline theme stylesheet, so the
   equal-specificity tie resolves here. */
.breadcrumbs .breadcrumbs__item a {
    color           : #747683;
    font-weight     : 600;
    text-decoration : none;
}
.breadcrumbs .breadcrumbs__item a:hover {
    color           : var(--bryntum-primary);
    text-decoration : underline;
}
.breadcrumbs .breadcrumbs__item a:focus-visible {
    background     : rgba(0, 118, 248, .12);
    border-radius  : .2rem;
    outline        : 2px solid var(--bryntum-primary);
    outline-offset : -2px;
}
.breadcrumbs__current {
    display       : block;
    min-width     : 0;
    overflow      : hidden;
    color         : #747683;
    text-overflow : ellipsis;
    white-space   : nowrap;
}
@media (max-width : 640px) {
    .breadcrumbs {
        margin-bottom : 2.4rem;
    }
    .breadcrumbs__item:last-child {
        max-width : 42vw;
    }
}

/* Visible focus indicator, sitewide.

   The compiled theme bundle strips the browser's default focus ring and never
   replaces it:
       :focus,a,a:focus,a:hover{outline:0;text-decoration:none}
       button,input,select,svg,textarea{...;outline:none;...}
   so tabbing through any page moves focus invisibly. A keyboard-only or
   low-vision visitor cannot tell what is focused or what Enter will activate,
   which fails WCAG 2.1 AA 2.4.7 (Focus Visible). Confirmed identical on prod
   and on the sandbox, so it is long-standing rather than a regression.

   :focus-visible, not :focus, so the ring appears for keyboard and
   assistive-tech users and never on a mouse click - which is what the bundle's
   blanket outline:0 was presumably trying to avoid in the first place.

   Specificity and order: each selector below is at least (0,1,1), tying with
   the bundle's own a:focus, and this file is loaded after the inline bundle,
   so the tie resolves here and no !important is needed. The form-control reset
   is (0,0,1) and is outranked outright.

   Colour is the solid brand blue rather than the 35%-alpha version used by the
   /company/ components: that alpha computes to about #a6cffd over white, which
   is roughly 1.7:1 against the page and would fail the very contrast
   requirement this rule exists to satisfy. Solid measures 4.25:1 on white and
   4.10:1 on the dark footer.

   outline rather than border or box-shadow, so nothing reflows: outlines are
   painted outside the box and take up no space, and they follow the element's
   own border-radius, so a pill gets a pill-shaped ring. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible {
    outline        : 3px solid var(--bryntum-primary, #0076f8);
    outline-offset : 2px;
}

/* Prose links get an underline rather than a box.

   A box around an inline link reads badly, and worse, a link that wraps across
   two lines is drawn as two separate boxes. A thick underline is unambiguous,
   follows the text wherever it wraps, and is distinguishable here because body
   links are not underlined at rest.

   Scoped to `.main`, which wraps page content sitewide and post content too
   (a post is <article class="main singlePost">), so a wrapped link reads the
   same on a product page as in a blog post. Deliberately limited to prose
   containers rather than every link under `.main`: list items outside post
   content are often cards, where an underline would read oddly and a ring is
   right. The :not() list mirrors the bundle's own prose rule, so anything the
   theme treats as a button or control keeps its ring. */
.main p a:not(.btn,.btn__link_arrow,.next,.prev,.label,.remove-style-link,.link-style-4):focus-visible,
.main td a:not(.btn,.btn__link_arrow,.next,.prev,.label,.remove-style-link,.link-style-4):focus-visible,
.main blockquote a:not(.btn,.btn__link_arrow,.next,.prev,.label,.remove-style-link,.link-style-4):focus-visible,
.main figcaption a:not(.btn,.btn__link_arrow,.next,.prev,.label,.remove-style-link,.link-style-4):focus-visible,
.singlePost__section li a:not(.btn,.btn__link_arrow,.next,.prev,.label,.remove-style-link,.link-style-4):focus-visible {
    outline                   : none;
    text-decoration           : underline;
    text-decoration-thickness : 3px;
    text-underline-offset     : 4px;
}

/* Rounded corners on the ring.

   An outline follows the element's own border-radius, so a square element gets
   a square ring. Setting a radius while focused shapes the ring and changes
   nothing else on these elements, because none of them paints a background or
   border of its own.

   Anything already round is excluded so it keeps its shape: .btn is a 60px
   radius, the post-card category pills and the blog nav pills are 34px, and
   the carousel controls (.carousel__navigation-button, .carousel__next,
   .carousel__prev) are circles at border-radius:50%. Without those exclusions
   a carousel dot visibly morphs from a circle into a rounded square while
   focused, and a nav pill squares off into an 8px rectangle. */
a:not(.btn):not([class*="__category"]):not([class*="carousel__"]):not([class*="blog-nav__"]):focus-visible,
button:not(.btn):not([class*="carousel__"]):focus-visible,
summary:focus-visible {
    border-radius : .8rem;
}

/* Top-level header links: draw the ring inside.

   .bry-header__mmItems is `overflow:hidden` and the links fill it exactly -
   measured clearance above and below is 0px - so an outward ring loses its top
   and bottom edges and only the sides survive. These links carry 14px/30px of
   padding, so a ring 3px inside the box still sits well clear of the text. */
.bry-header__mmLink:focus-visible {
    outline-offset : -3px;
}

/* The Free Trial button is a filled brand-blue pill, so a blue ring inside it
   would be invisible. White reads against the fill, and the pill's own 60px
   radius shapes the ring. */
.bry-header__mmLink.btn:focus-visible {
    outline        : 3px solid #fff;
    outline-offset : -6px;
}

/* Dropdown links have `padding: 5px 0` - no horizontal padding at all - so an
   inset ring would cut straight through the text. There is 100px of clearance
   to the panel edge, so the ring goes outward instead, far enough to leave a
   clear gap around the words. */
.bry-header__mmLinkChild:focus-visible {
    outline-offset : 4px;
}

/* Post-card category pills sit flush against their container: measured
   clearance is 0px left and below, so an outward ring is cut off. One pixel
   inside keeps the whole ring visible, and it follows the pill's own radius. */
.singlePostItemVerticalSmall__category:focus-visible,
.singlePostItemHorizontalBig__category:focus-visible {
    outline-offset : -1px;
}

/* The two card variants are filled differently, and only the small one has the
   pale #e9f1fe background that a blue ring shows up against. The big card's
   pill is filled with var(--bryntum-primary) itself, so a blue ring inside it
   is invisible - the same blue-on-blue trap as the Free Trial button, and on
   the exact control this change exists to make visible. White reads against
   the fill. */
.singlePostItemHorizontalBig__category:focus-visible {
    outline : 3px solid #fff;
}

/* Open the dropdown for keyboard users.

   The bundle reveals a submenu on .bry-header__mmItem:hover only, so tabbing
   into a child link moved focus into a panel that stays invisible - the link
   was focused and the ring was drawn, but inside something with opacity 0.
   Mirroring those two rules on :focus-within opens the panel when focus enters
   it and closes it again the moment focus leaves, with no JavaScript. Scoped to
   the desktop breakpoint, where the hover rules themselves live. */
@media (min-width : 1025px) {
    .bry-header__mmItem:focus-within .bry-header__mmChild,
    .bry-header__mmItem:focus-within .bry-header__mmChildOne {
        opacity          : 1;
        pointer-events   : auto;
        transition-delay : 0s;
    }
    .bry-header__mmItem:focus-within .bry-header__mmLink .bry-header__mmLinkLabel {
        transform : translateY(-1.2rem) rotate(180deg);
    }
}

/* WordPress image alignment classes in post content.

   The editor writes .alignleft / .alignright / .aligncenter onto an image when
   an author picks an alignment, and the theme styles none of them, so the
   choice is silently ignored: an image meant to have text wrapped around it
   renders as a plain inline image, and the paragraph's first line sits level
   with its bottom edge rather than beside it. 157 of 309 published posts carry
   at least one, 534 images in all.

   Scoped to .singlePost so nothing outside post content can move.

   Only the float cases are given a layout. .alignleft and .alignright are 15
   of those 534 and are the ones that actually read as broken, because wrapping
   is the whole point of them. .alignnone (337) means "no alignment" and needs
   nothing.

   .aligncenter (182) is deliberately left unstyled. Centring it safely means
   matching only images that are alone in their paragraph, and CSS cannot
   express that: :only-child counts element children and ignores text nodes, so
   `p:has(> img.aligncenter:only-child)` also matches <p>text <img> text</p>
   and would centre paragraphs of ordinary prose. Verified in a browser, not
   assumed. Making them display:block instead would reflow all 182, including
   the 176 wrapped in an <a> and the ones sitting in hand-built flex rows of
   logos. Not worth it to move an image from left to centre. */
@media (min-width : 768px) {
    .singlePost .alignleft {
        float  : left;
        margin : .5rem 2.4rem 1.6rem 0;
    }
    .singlePost .alignright {
        float  : right;
        margin : .5rem 0 1.6rem 2.4rem;
    }
    /* Keep a float from running past its section and pushing into the next
       heading. Inert until something actually floats, so this changes nothing
       on the posts that have no aligned image. */
    .singlePost h2,
    .singlePost h3 {
        clear : both;
    }
}

/* Author archive profile block (tickets 49343014 / 49801015).
   author.php used to render a generic "Blog" hero; it now renders the author's
   photo, name, role, bio, expertise and profile links above their posts. Every
   part is optional in the template, so these rules have to look right with any
   of them absent. Container widths and type come from the existing
   .container--small and .posts styles; this only lays out the new block. */
.authorProfile {
    padding : 4rem 0 1rem;
}

/* Photo against name plus role only. The link row is a sibling below, so it
   cannot drag the centring of that pair off the middle of the photo. */
.authorProfile__header {
    display     : flex;
    align-items : center;
    gap         : 3.2rem;
    flex-wrap   : wrap;
}

.authorProfile__photo img {
    width         : 12rem;
    height        : 12rem;
    border-radius : 50%;
    object-fit    : cover;
    display       : block;
}

.authorProfile__intro {
    flex : 1 1 24rem;
}

/* The theme's default h1 is sized for a hero and, next to a 120px photo,
   leaves the name and role too far apart to read as one unit. Sized down and
   tightened here so the pair sits centred against the photo. */
.authorProfile__intro h1 {
    margin      : 0;
    font-size   : 4.8rem;
    line-height : 1.15;
}

/* Selector deliberately over-qualified: a theme rule for paragraphs inside the
   content containers sets 3rem top and bottom margins, which pushes the role
   clear of the name and breaks the pair. */
.authorProfile .authorProfile__intro p.authorProfile__role {
    margin      : .8rem 0 0;
    font-size   : 1.8rem;
    line-height : 1.4;
    font-weight : 600;
    opacity     : .75;
}

/* Job titles are stored lowercase ("head of engineering at Bryntum"); capitalise
   for display here rather than with ucfirst(), which is byte-based and would
   mangle a multibyte first letter. */
.authorProfile .authorProfile__intro p.authorProfile__role::first-letter {
    text-transform : uppercase;
}

.authorProfile__links {
    display     : flex;
    align-items : center;
    flex-wrap   : wrap;
    gap         : 1.6rem;
    margin      : 2rem 0 0;
    padding     : 0;
    list-style  : none;
}

.authorProfile__links li:before {
    content : none;
}

.authorProfile__links a {
    text-decoration : underline;
}

/* Icon links carry the platform name in aria-label instead of visible text. */
.authorProfile__links a.authorProfile__link--icon {
    display         : flex;
    align-items     : center;
    justify-content : center;
    width           : 4rem;
    height          : 4rem;
    border-radius   : 50%;
    color           : #0076f8;
    background      : rgba(0, 118, 248, .08);
    text-decoration : none;
    transition      : color .15s ease, background-color .15s ease;
}

.authorProfile__links a.authorProfile__link--icon:hover,
.authorProfile__links a.authorProfile__link--icon:focus {
    color      : #fff;
    background : #0061cf;
}

.authorProfile__bio {
    margin : 2.4rem 0 0;
}

.authorProfile__bio p:last-child {
    margin-bottom : 0;
}

.authorProfile__expertise {
    margin : 2.4rem 0 0;
}

.authorProfile__expertise h2 {
    font-size      : 1.8rem;
    margin         : 0 0 1.2rem;
    text-transform : uppercase;
    letter-spacing : .05em;
    opacity        : .75;
}

.authorProfile__topics {
    display    : flex;
    flex-wrap  : wrap;
    gap        : .8rem;
    margin     : 0;
    padding    : 0;
    list-style : none;
}

/* Newsletter signup on the blog (ticket 49801025).
   The block was built to close a marketing page against the footer, so
   `.newsletter` carries a negative bottom margin (-2.4rem, -5rem from tablet
   up) to pull itself over the bottom padding of the wrapper it ends. That
   value does not fit a post. The post article is `class="main singlePost"`,
   and while `.main` sets `padding-bottom: 2.4rem/5rem`, `.singlePost` sets
   `padding-bottom: .8rem` and wins on source order (both are single-class,
   `.singlePost` comes later in the bundle). So 8px is the whole gap to close.

   Leaving the bundle's -5rem would drag the comments section up over the
   band; zeroing it leaves that 8px strip of page background showing as a pale
   line between the band and the site footer, which are the same colour. With
   -.8rem the band meets the footer exactly, and on a post that does have
   comments the 8px pull still leaves 42px of the `.comments` 5rem top margin.

   No media query needed, and none is wanted: `.singlePost`'s bottom padding
   is .8rem at every width (only its side padding changes at 768px), and
   custom.css loads after the bundle so this beats the tablet override.

   The blog index is left alone. There the bundle's -5rem outruns the 8px it
   has to cancel, but `.main` sets `overflow: hidden`, so the extra 42px is
   clipped rather than painted over the footer. Nothing shows, and the note
   still clears the cut. If you ever change `.posts` padding or that -5rem,
   check the note is still inside the clip. */
.newsletter--inline {
    margin-top    : 4rem;
    margin-bottom : -.8rem;
}

/* The band is a full-width colour block everywhere else it appears (marketing
   pages, and the blog index inside .posts, neither of which has horizontal
   padding). On a post it sits inside .singlePost, which pads 2rem, and 4rem
   from 768px up, so without this it would render with white gutters down both
   sides. The negative margins are exactly that padding, so the band reaches
   the viewport edges without overflowing it. */
.singlePost > .newsletter--inline {
    margin-left  : -2rem;
    margin-right : -2rem;
}

@media (min-width : 768px) {
    .singlePost > .newsletter--inline {
        margin-left  : -4rem;
        margin-right : -4rem;
    }
}

/* Reassurance line under the form. Marketing pages pass no note, so this is
   inert there.

   Scoped through `.newsletter` for specificity, not for reach: on the blog
   index the band sits inside `.posts`, whose `.posts p` rule (0,1,1) beats a
   bare `.newsletter__note` (0,1,0) on any source order and would give the note
   3rem block margins there while a post got 1.6rem/0. Two classes (0,2,0) win
   in both places, so the note spaces identically wherever it renders. */
.newsletter .newsletter__note {
    margin     : 1.6rem 0 0;
    text-align : center;
    font-size  : 1.4rem;
    opacity    : .75;
}

/* Copy button on blog code samples (ticket 49801026). js/custom.js wraps each
   post-body <pre> in .singlePost__codeBlock and puts the button in the wrapper.

   The button lives in the wrapper, not in the <pre>, because a <pre> here is
   overflow:auto: a child positioned against its right edge is positioned
   against the scrolled *content*, so on a wide sample it slides thousands of
   pixels out of view on the first sideways scroll. Measured in a browser at
   35,316px of drift, not assumed. `position: sticky` does not save it either,
   since a sticky box is still laid out in that same scrolled content. The
   wrapper does not scroll, so an absolute child of it stays where it is put.

   Wrapping costs the <pre> exactly one rule. The compiled theme bundle carries
   `.singlePost__section > pre { margin: 3rem auto; max-width: 72rem }`, a
   direct-child selector, and the wrapper is now that child instead. Without it
   the plain blocks lose their 3rem rhythm and move up by 50px, so it is
   reinstated below; the bundle is a 2022 build that is never rebuilt on
   deploy, so it is reinstated here rather than fixed at source.

   That reinstatement is scoped to the plain blocks on purpose. They were the
   only ones relying on the rule: a highlighted block is already claimed by a
   narrower one giving it a .75rem margin, and handing those 3rem instead
   pushed every sample on a tutorial down and the page out by 1,440px. Both
   numbers were measured in a browser, not inferred.

   The wrapper is otherwise a plain block, so every other block keeps the
   margins it already had and the page rhythm is unchanged. */
.singlePost__codeBlock {
    position : relative;
}

/* The cap goes on the wrapper, not the <pre>. The button is positioned against
   the wrapper, so capping only the <pre> left the button hanging off the right
   of the visible block - 136px adrift at 1440px, 376px at 1920px, growing with
   the viewport. Sizing the wrapper keeps the two in one box. */
.singlePost__section > .singlePost__codeBlock--plain {
    margin    : 3rem auto;
    max-width : 72rem;
}

/* Zero layout shift when the button appears. The theme reserves a 4rem top
   gutter on `pre[class*="language-"]` for the three macOS-style traffic-light
   dots it paints there, and the button sits in the free right-hand end of that
   same band. It is out of flow, so the block's height is unchanged. */
.singlePost__copyCode {
    background     : rgba(255, 255, 255, .1);
    border         : 1px solid rgba(255, 255, 255, .25);
    border-radius  : .8rem;
    color          : #fff;
    cursor         : pointer;
    font-family    : inherit;
    font-size      : 1.3rem;
    font-weight    : 500;
    line-height    : 1;
    padding        : .6rem 1rem;
    position       : absolute;
    right          : 1.5rem;
    top            : 1.2rem;
}

.singlePost__copyCode:hover {
    background : rgba(255, 255, 255, .2);
}

.singlePost__copyCode:focus-visible {
    outline        : 2px solid #5ea1fb;
    outline-offset : 2px;
}

/* The button paints at 57x27, under the 44px touch minimum, and it cannot just
   grow: it has to fit inside the 4rem gutter. Extend the hit area instead. The
   pseudo-element is never painted, so this costs no pixels and takes the
   target to 74x45. */
.singlePost__copyCode::before {
    content  : "";
    inset    : -1rem;
    position : absolute;
}

/* On a plain block the button sits in the 3rem gap above it, and the symmetric
   -1rem target would reach 4px into the paragraph above and 10px over the
   block's first line, stealing clicks from a link at the end of that
   paragraph. Trim the vertical reach; the row stays a 44px target because the
   horizontal padding is unchanged. */
.singlePost__codeBlock--plain .singlePost__copyCode::before {
    inset : -.2rem -1rem;
}

/* Blocks with no language- class (179 older posts, nearly all one-liners, all
   pre-2016 Siesta write-ups). The theme leaves those transparent with no
   padding, so there is no gutter to sit in and no dark ground to sit on: a
   button at the top right would cover the only line of code and be unreadable
   against the page. It goes in the 3rem gap the <pre>'s own top margin already
   leaves above the block, so this case costs no height either.

   `bottom: 100%` measures from the wrapper, whose top edge is the <pre>'s top
   edge because the margin collapses through it, so the button rises into that
   gap rather than into the paragraph above. */
.singlePost__codeBlock--plain .singlePost__copyCode {
    background    : rgba(0, 0, 0, .06);
    border-color  : rgba(0, 0, 0, .18);
    bottom        : 100%;
    color         : var(--bryntum-gray);
    font-size     : 1.2rem;
    margin-bottom : .2rem;
    padding       : .4rem .8rem;
    right         : 0;
    top           : auto;
}

.singlePost__codeBlock--plain .singlePost__copyCode:hover {
    background : rgba(0, 0, 0, .12);
}

/* Success and failure are announced through a role="status" span. Visually
   hidden: the button's own label already changes to "Copied", and printing the
   same news twice inside a code block is noise. */
.singlePost__copyCode-status {
    clip        : rect(0 0 0 0);
    clip-path   : inset(50%);
    height      : 1px;
    overflow    : hidden;
    position    : absolute;
    white-space : nowrap;
    width       : 1px;
}

/* Estimated reading time in the byline (ticket 49801026), rendered by
   bryntum_the_reading_time() in the theme's functions.php.

   Matches .singlePost__date's own values rather than the design system's meta
   role, for the same reason the "last updated" date does: they share a flex
   row, so matching the sibling beats matching the token. */
.singlePost__readingTime {
    color          : var(--bryntum-gray);
    font-size      : 1.6rem;
    font-weight    : 600;
    letter-spacing : .01em;
    line-height    : 152%;
    padding        : 0 .8rem;
    white-space    : nowrap;
}

/* Blog category navigation (ticket 49801010), rendered by
   bryntum_the_blog_nav() on the blog hub and the category archives.

   Reuses the pale-blue pill the post cards already use for their category
   tags (.singlePostItemVerticalSmall__category: #e9f1fe, 3.4rem radius,
   primary blue) so the row reads as the same kind of thing, one size up.
   Here rather than the theme SCSS for the usual reason: the compiled bundle
   is a Nov 2022 build and is not rebuilt on deploy. */
.blog-nav {
    margin : 0 auto 4rem;
}
.blog-nav__list {
    display         : flex;
    flex-wrap       : wrap;
    justify-content : center;
    gap             : .8rem;
    margin          : 0;
    padding         : 0;
    list-style      : none;
}
.blog-nav__item {
    margin : 0;
}
.blog-nav__item:before {
    display : none;
}
.blog-nav__link {
    display          : inline-block;
    padding          : .6rem 1.4rem;
    border-radius    : 3.4rem;
    background-color : #e9f1fe;
    color            : var(--bryntum-primary);
    font-size        : 1.4rem;
    font-weight      : 500;
    line-height      : 160%;
    text-decoration  : none;
    transition       : background-color .15s ease, color .15s ease;
}
.blog-nav__link:hover {
    background-color : var(--bryntum-primary);
    color            : #fff !important;
}
/* No focus rule of its own. The sitewide a:focus-visible ring is the solid
   brand blue; the 35%-alpha version fails the contrast requirement that rule
   exists to satisfy. The pill is excluded from the .8rem radius override
   above, so the ring follows its own 3.4rem shape. */
/* The page you are on renders as a <span>, not a link, so it only needs the
   solid fill. Same pattern as .breadcrumbs__current. */
.blog-nav__link--current {
    background-color : var(--bryntum-primary);
    color            : #fff;
    cursor           : default;
}
@media (max-width : 767px) {
    .blog-nav {
        margin-bottom : 3.2rem;
    }
    .blog-nav__link {
        font-size : 1.3rem;
        padding   : .5rem 1.2rem;
    }
}

/* Anchor links on post content headings (ticket 49801026).

   The site header is `position: fixed` with a min-height of 8.8rem, so a
   heading jumped to from an anchor would otherwise land underneath it.
   scroll-margin-top clears the header and leaves a little breathing room; the
   headings' own 2.4rem padding-top is inside the scroll box, so it does not
   count toward the gap. */
.singlePost__section :is(h2, h3, h4, h5, h6)[id] {
    position          : relative;
    scroll-margin-top : 11rem;
}


/* Hidden until the heading is hovered or the link itself is focused, which is
   the convention on the developer references this follows. Opacity rather than
   display:none so the link stays in the tab order and in the layout: revealing
   it must not reflow the heading. */
/* Scoped under .singlePost__section so colour and weight actually apply: the
   theme bundle's `.singlePost a:not(.btn,...)` is more specific than a lone
   class, so an unscoped rule here rendered the link blue and semibold at rest
   and near-black on hover, the reverse of what is wanted. */
.singlePost__section :is(h2, h3, h4, h5, h6) .singlePost__heading-anchor {
    display         : inline-block;
    opacity         : 0;
    /* Fixed, not inherited: a heading is up to 36px and the marker was
       rendering at the same size. Grey and lighter than the heading so it
       reads as a control beside the title rather than part of it. */
    color           : #a7a9b4;
    font-size       : 1.8rem;
    font-weight     : 400;
    line-height     : 1;
    text-decoration : none;
    /* Absolute, not inline: a heading that fills the column has no room left
       on its last line, so an inline marker wrapped onto a line of its own.
       Hanging it in the left margin keeps it beside the heading whatever the
       title length, and takes it out of the text flow so it can never reflow
       the heading either. */
    position        : absolute;
    left            : -2.4rem;
    top             : 0;
    white-space     : nowrap;
    transition      : opacity .15s ease, color .15s ease;
}

.singlePost__section :is(h2, h3, h4, h5, h6):hover .singlePost__heading-anchor,
.singlePost__section :is(h2, h3, h4, h5, h6) .singlePost__heading-anchor:hover,
.singlePost__section :is(h2, h3, h4, h5, h6) .singlePost__heading-anchor:focus-visible {
    opacity : 1;
}

/* Only the marker itself turns blue, and only when it is the thing being
   pointed at. Hovering the heading reveals it in grey. */
.singlePost__section :is(h2, h3, h4, h5, h6) .singlePost__heading-anchor:hover,
.singlePost__section :is(h2, h3, h4, h5, h6) .singlePost__heading-anchor:focus-visible {
    color : var(--bryntum-primary, #0076f8);
}
/* Below the point where the 72rem column has margin to hang the marker in,
   put it back in the flow after the text. */
@media (max-width : 1100px) {
    .singlePost__section :is(h2, h3, h4, h5, h6) .singlePost__heading-anchor {
        position    : static;
        left        : auto;
        top         : auto;
        margin-left : .8rem;
    }
}


/* Touch devices have no hover, so the link would never appear. Show it
   permanently there instead, muted so it does not compete with the heading.
   Scoped exactly like the rule that hides it: a media query adds no
   specificity, so a bare .singlePost__heading-anchor here loses to the (0,2,1)
   base rule and the marker stays invisible on every phone. */
@media (hover : none) {
    .singlePost__section :is(h2, h3, h4, h5, h6) .singlePost__heading-anchor {
        opacity : .5;
    }
}

/* Table of contents on long posts (ticket 49801026), emitted by
   bryntum_blog_toc().

   The markup reuses the Easy Table of Contents class names the theme bundle
   already styles, so type, list spacing and link colour all come from there.
   Only two things are missing: the container has no surface of its own, and
   its 6rem padding is sized for a full-width plugin block rather than an
   inline card in a 72rem column. */
.singlePost #ez-toc-container.singlePost__toc {
    max-width        : 72rem;
    padding          : 2.4rem 2.8rem;
    border           : 1px solid var(--bryntum-border, #e6e9f2);
    border-radius    : 1.4rem;
    background-color : #fbfbfe;
}

/* The plugin's own type is sized for a full-width block: a 2.4rem title over
   1.8rem entries with a 3.2rem gap made a six-item list 420px tall and read as
   body copy rather than a jump list. An eyebrow-scale label over 1.6rem links
   puts the emphasis on the entries, which are the part you use. */
.singlePost .singlePost__toc .ez-toc-title-container {
    margin-bottom : 1.6rem !important;
}

.singlePost .singlePost__toc .ez-toc-title {
    color          : var(--bryntum-body, #676978) !important;
    font-size      : 1.3rem !important;
    font-weight    : 700 !important;
    letter-spacing : .09em !important;
    text-transform : uppercase;
}

.singlePost .singlePost__toc .ez-toc-list {
    margin     : 0;
    padding    : 0;
    list-style : none;
}

/* #0061cf, the CTA hover blue already used in this file, not the #0076f8 brand
   blue: white on #0076f8 is 4.25:1, under the 4.5:1 WCAG AA floor at this size.
   #0061cf is 5.82:1 and reads as the same blue. */
.authorProfile__topics li {
    padding       : .6rem 1.6rem;
    background    : #0061cf;
    color         : #fff;
    border-radius : 2rem;
    font-size     : 1.4rem;
    font-weight   : 500;
}

.authorProfile__topics li:before {
    content : none;
}

.authorProfile__postsHeading {
    margin : 4.8rem auto 0;
}

@media (max-width : 767px) {
    .authorProfile {
        padding : 2.4rem 0 0;
    }

    .authorProfile__header {
        gap : 2rem;
    }

    .authorProfile__photo img {
        width  : 9rem;
        height : 9rem;
    }

    .authorProfile__intro h1 {
        font-size : 3.2rem;
    }

    .authorProfile .authorProfile__intro p.authorProfile__role {
        font-size : 1.6rem;
    }

    .authorProfile__links {
        gap : 1.2rem;
}
}

/* Real disc markers from the post-body list styling, not the :before bullets
   used elsewhere in the theme, so list-style is what removes them. */
.singlePost .singlePost__toc .ez-toc-list {
    row-gap : .8rem;
}

.singlePost .singlePost__toc .ez-toc-list > li {
    margin      : 0;
    padding     : 0;
    list-style  : none;
    display     : block;
    line-height : 1.5;
}

/* The theme's `.wysywig a` colours every link in the post body blue and wins
   on specificity over the bundle's own .ez-toc-link rule, so the entries came
   out as body links rather than the near-black the component intends. */
.singlePost .singlePost__toc .ez-toc-link {
    color     : var(--bryntum-primary, #0076f8);
    font-size : 1.6rem;
}

.singlePost .singlePost__toc .ez-toc-link:hover {
    text-decoration : underline;
}

.singlePost__toc .ez-toc-link:focus-visible {
    outline        : 3px solid rgba(0, 118, 248, .35);
    outline-offset : 3px;
}

@media (max-width : 767px) {
    .singlePost #ez-toc-container.singlePost__toc {
        padding : 2.4rem;
    }
}

/* Visible "last updated" byline (ticket 49801016), rendered by
   bryntum_the_updated_date() in the theme's functions.php beside the published
   date. Same file, same reason as the breadcrumb block above: theme SCSS edits
   never reach the site.

   Deliberately repeats .singlePost__date's own values rather than the design
   system's 1.4rem/400 meta role: the two sit in the same flex row, so matching
   the sibling matters more than matching the token. Color is the system's body
   gray either way (--bryntum-gray is #676978). No calendar icon - the word
   "Updated" already labels it, and a second identical icon reads as a repeat. */
.singlePost__updated {
    font-feature-settings : "liga" off;
    color                 : var(--bryntum-gray);
    font-size             : 1.6rem;
    font-weight           : 600;
    letter-spacing        : .01em;
    line-height           : 152%;
    padding               : 0 .8rem;
    white-space           : nowrap;
}
/* The meta row is a nowrap flex line sized for two items. A third one squeezes
   all three into narrow columns and breaks the dates mid-value ("6 / May /
   2026"), so below the theme's 768px step the row wraps and each item stays
   whole. The published date keeps its icon glued to it for the same reason. */
@media (max-width : 767px) {
    .singlePost__meta {
        flex-wrap : wrap;
        row-gap   : .8rem;
    }
    .singlePost__date {
        white-space : nowrap;
    }
}

/* Nothing to click on paper, and the chip would print over the sample. */
@media print {
    .singlePost__copyCode {
        display : none;
    }
}
