/* ==========================================================================
   levels-blog — styles for the lvl/* content blocks
   --------------------------------------------------------------------------
   Loaded on is_singular('post') only. Oxygen owns the shell (header, footer,
   single template, 1000px inner content) and the typography — the blocks
   inherit both. Nothing here sets a font-family or a font size in px.

   Colour comes exclusively from the site's own custom properties, each with
   the literal from docs/evidence/universal-css-vars.txt as a fallback:

     --text-color --bg-color --inverse-color      flip with .dark-mode
     --yellow --orange --pink --violet --teal --green (+ *-rgb)  flip too
     --brand-*                                     fixed, used as fallbacks
     --h2-base --base-lg --base-xs --base-20/40    clamp scales

   Dark mode is the site default (body.dark-mode); light mode is the toggle.
   Only the tinted surfaces need to be restated per mode — every colour token
   above already flips on its own.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Local token layer
   -------------------------------------------------------------------------- */

/*
 * ⚠ The site's --text-color / --bg-color do NOT flip with dark mode: Oxygen
 * switches the theme with a `body.dark-mode { color: … }` rule, while the
 * custom properties keep their light values. A block that sets
 * `color: var(--text-color)` therefore paints dark ink on a dark ground.
 *
 * Rule for this file: blocks NEVER set a text colour from --text-color. Text
 * is `inherit` / currentColor, so the body's own colour wins in either mode.
 * Everything that genuinely needs a mode-specific value — tinted surfaces,
 * borders, accents, and text sitting on a fixed-colour surface — is declared
 * twice below, once on :root (light) and once on body.dark-mode.
 */

:root {
    /* Ink and paper as literals — used only where a fixed surface demands an
       explicit colour, never as the default text colour. */
    --lvl-blog-ink: var(--text-black-brand, #090920);
    --lvl-blog-paper: var(--text-shade-white, #f3f5fc);

    --lvl-blog-accent: var(--brand-rose, #ce7aa3);
    --lvl-blog-accent-rgb: 206, 122, 163;
    /* The brand rose is a surface colour: as text on a near-white ground it
       only reaches 2.6:1. Accent TEXT therefore uses a darkened rose — 7.3:1
       on the card tint, WCAG AAA. The surface colour above is unchanged. */
    --lvl-blog-accent-text: #7d3556;

    /*
     * Two roles per hue, deliberately split (J3):
     *   --lvl-blog-<hue>      the SOLID colour — icon glyph, rule, border. On a
     *                         near-white ground the brand hue only reaches
     *                         1.35–2.05:1, which fails SC 1.4.11, so light mode
     *                         uses a darkened variant of the SAME hue.
     *   --lvl-blog-<hue>-rgb  the SURFACE tint (10 % wash). Unchanged brand hue,
     *                         so the boxes keep their colour.
     * Measured with the WCAG formula against each box's own tint over #f3f5fc:
     *   info #0f5f63 on #e6eef4 = 6.31   tip #3f6b22 on #e9eeed = 5.37
     *   warn #6f5606 on #f2f1ec = 6.17
     */
    --lvl-blog-info: #0f5f63;
    --lvl-blog-info-rgb: 108, 180, 177;
    --lvl-blog-tip: #3f6b22;
    --lvl-blog-tip-rgb: 140, 179, 105;
    --lvl-blog-warn: #6f5606;
    --lvl-blog-warn-rgb: 232, 209, 94;

    /* Tinted surfaces — light mode: ink on paper. Two steps, not four (J9):
       --lvl-blog-surface for every reading box, --lvl-blog-surface-2 for code
       and video. --lvl-blog-surface-strong is kept as the old name so
       single.css, which is outside this work package, keeps resolving. */
    --lvl-blog-surface: rgba(9, 9, 32, 0.045);
    --lvl-blog-surface-2: rgba(9, 9, 32, 0.075);
    --lvl-blog-surface-strong: var(--lvl-blog-surface-2);
    --lvl-blog-border: rgba(9, 9, 32, 0.14);
    --lvl-blog-border-soft: rgba(9, 9, 32, 0.08);
    --lvl-blog-shadow: 0 0.125rem 1.25rem rgba(9, 9, 32, 0.08);

    /* J9: ONE radius. --lvl-blog-radius-sm stays as a name because single.css
       (outside this package) resolves it, but it is no longer a second value. */
    --lvl-blog-radius: 0.75rem;
    --lvl-blog-radius-sm: var(--lvl-blog-radius);
    /* Paper as bare channels, for the code-overflow fade. Flips with the mode. */
    --lvl-blog-fade-rgb: 243, 245, 252;
    --lvl-blog-gap: 1.25rem;
    /* One padding for every box, one rhythm between blocks (J9). */
    --lvl-blog-pad: 1.75rem;
    --lvl-blog-rhythm: clamp(1.25rem, 3vw, 2rem);
    --lvl-blog-ease: cubic-bezier(0.84, 0.05, 0.31, 0.93);

    /*
     * Local type scale (J1).
     * --------------------------------------------------------------------
     * The site's --base-20 / --base-xs / --base-lg / --base-40 clamps bottom
     * out at 12.8px on a 390px viewport while an Oxygen paragraph stays at
     * 20px — every lvl/* block rendered a third smaller than the prose around
     * it. These four tokens are relative to the inherited body size instead,
     * so a block can never undercut the paragraph it sits between:
     *
     *   --lvl-blog-text     1em    body copy inside a block = the paragraph
     *   --lvl-blog-small    >= 15px at every width, secondary copy only
     *   --lvl-blog-heading-sm / --lvl-blog-heading   always > the paragraph
     *   --lvl-blog-figure   key-figure values, >= 1.75rem on a phone
     */
    --lvl-blog-text: 1em;
    --lvl-blog-small: clamp(0.9375rem, 0.85rem + 0.4vw, 1.0625rem);
    --lvl-blog-heading-sm: 1.25em;
    --lvl-blog-heading: 1.5em;
    --lvl-blog-figure: clamp(1.75rem, 1.2rem + 2.5vw, 2.75rem);
}

body.dark-mode {
    --lvl-blog-ink: var(--text-shade-white, #f3f5fc);
    --lvl-blog-paper: var(--text-black-brand, #090920);

    /*
     * Dark mode accents (J3). The four neon literals that used to sit here
     * (#ff80bf #8aff80 #80ffea #ffff80) are not brand colours — they are the
     * corner of the sRGB gamut the hue happens to point at. These are the real
     * brand hues lifted 15 % toward white, which is enough to carry on the
     * near-black ground without leaving the palette. Contrast on #090920,
     * WCAG formula:
     *   accent #d58eb1  7.78    info #82bfbd  9.46
     *   tip    #9dbe80  9.44    warn #ebd876 13.64
     */
    --lvl-blog-accent: #d58eb1;
    --lvl-blog-accent-rgb: 213, 142, 177;
    --lvl-blog-accent-text: #d58eb1;
    --lvl-blog-info: #82bfbd;
    --lvl-blog-info-rgb: 130, 191, 189;
    --lvl-blog-tip: #9dbe80;
    --lvl-blog-tip-rgb: 157, 190, 128;
    --lvl-blog-warn: #ebd876;
    --lvl-blog-warn-rgb: 235, 216, 118;

    --lvl-blog-surface: rgba(243, 245, 252, 0.05);
    --lvl-blog-surface-2: rgba(243, 245, 252, 0.09);
    --lvl-blog-surface-strong: var(--lvl-blog-surface-2);
    --lvl-blog-fade-rgb: 9, 9, 32;
    --lvl-blog-border: rgba(243, 245, 252, 0.16);
    --lvl-blog-border-soft: rgba(243, 245, 252, 0.09);
    --lvl-blog-shadow: 0 0.125rem 1.5rem rgba(0, 0, 0, 0.35);
}

/* --------------------------------------------------------------------------
   Shared: rhythm, alignment, editor placeholders
   -------------------------------------------------------------------------- */

.lvl-tldr,
.lvl-toc,
.lvl-info-box,
.lvl-cta-box,
.lvl-image-text,
.lvl-faq,
.lvl-author-box,
.lvl-related-links,
.lvl-video {
    margin-block: var(--lvl-blog-rhythm);
    /* ⚠ never a variable here — see the note at the top of this file. */
    color: inherit;
    box-sizing: border-box;
    max-width: 100%;
}

.lvl-tldr *,
.lvl-toc *,
.lvl-info-box *,
.lvl-cta-box *,
.lvl-image-text *,
.lvl-faq *,
.lvl-author-box *,
.lvl-related-links *,
.lvl-video * {
    box-sizing: border-box;
}

/* The Oxygen inner content is 1000px. "wide" reaches a little past it without
   ever touching the viewport edge. */
.lvl-tldr.alignwide,
.lvl-cta-box.alignwide,
.lvl-image-text.alignwide,
.lvl-faq.alignwide,
.lvl-related-links.alignwide,
.lvl-video.alignwide {
    width: auto;
    margin-inline: calc(-1 * clamp(0rem, 4vw, 5rem));
}

.lvl-image-text.alignfull,
.lvl-video.alignfull {
    width: auto;
    margin-inline: calc(-1 * clamp(0rem, 6vw, 7.5rem));
}

/*
 * Visually hidden, still announced. The Oxygen shell ships no such utility and
 * this repo delivers no theme, so the blocks bring their own. Used for the
 * info-box variant word and the checklist item state (SC 1.4.1 — neither may
 * depend on colour alone).
 */
.lvl-tldr .screen-reader-text,
.lvl-toc .screen-reader-text,
.lvl-info-box .screen-reader-text,
.lvl-cta-box .screen-reader-text,
.lvl-checklist .screen-reader-text,
.lvl-key-figures .screen-reader-text,
.lvl-faq .screen-reader-text,
.lvl-related-links .screen-reader-text,
.lvl-author-box .screen-reader-text,
.lvl-case-card .screen-reader-text,
.lvl-code-snippet .screen-reader-text,
.lvl-video .screen-reader-text {
    position: absolute !important;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

.lvl-block-placeholder {
    padding: var(--lvl-blog-pad);
    border: 1px dashed var(--lvl-blog-border);
    border-radius: var(--lvl-blog-radius);
    background: var(--lvl-blog-surface);
    opacity: 0.75;
}

/* --------------------------------------------------------------------------
   lvl/tldr
   -------------------------------------------------------------------------- */

.lvl-tldr {
    padding: var(--lvl-blog-pad);
    border-radius: var(--lvl-blog-radius);
    background: var(--lvl-blog-surface);
    border-inline-start: 0.25rem solid var(--lvl-blog-accent);
}

.lvl-tldr__heading {
    margin: 0 0 var(--lvl-blog-gap);
    font-size: var(--lvl-blog-heading-sm);
    line-height: 1.3;
}

.lvl-tldr__list {
    margin: 0;
    padding: 0;
    list-style: none;
}

/*
 * J8: no hairline between items and no check glyph. A TL;DR item is a claim,
 * not a completed task — the check mark promised an affordance the item does
 * not have, and the rules made a five-line summary look like a table. A short
 * rule in the accent marks the item instead; the checklist keeps the tick.
 */
.lvl-tldr__item {
    position: relative;
    margin: 0;
    padding: 0.45rem 0 0.45rem 1.75rem;
    font-size: var(--lvl-blog-text);
    line-height: 1.55;
}

.lvl-tldr__item::before {
    content: "";
    position: absolute;
    inset-inline-start: 0;
    inset-block-start: 0.78em;
    width: 0.75rem;
    height: 0.125rem;
    border-radius: 0.125rem;
    background-color: var(--lvl-blog-accent);
}

.lvl-tldr__label {
    color: inherit;
    font-weight: 600;
}

/* --------------------------------------------------------------------------
   lvl/toc  (markup is emitted server-side by the the_content filter)
   -------------------------------------------------------------------------- */

.lvl-toc {
    padding: var(--lvl-blog-pad);
    border: 1px solid var(--lvl-blog-border);
    border-radius: var(--lvl-blog-radius);
    background: var(--lvl-blog-surface);
}

.lvl-toc__title {
    margin: 0 0 0.75rem;
    font-size: var(--lvl-blog-heading-sm);
    line-height: 1.3;
}

.lvl-toc__details > summary.lvl-toc__title {
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
}

.lvl-toc__details > summary::-webkit-details-marker {
    display: none;
}

.lvl-toc__details > summary::after {
    content: "";
    width: 0.6rem;
    height: 0.6rem;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg);
    transition: transform 0.25s var(--lvl-blog-ease);
}

.lvl-toc__details:not([open]) > summary::after {
    transform: rotate(-45deg);
}

.lvl-toc__list {
    margin: 0;
    padding: 0;
    list-style: none;
    counter-reset: lvl-toc;
}

.lvl-toc__item {
    margin: 0;
    padding: 0;
    font-size: var(--lvl-blog-text);
    line-height: 1.45;
}

.lvl-toc__item--h3 {
    padding-inline-start: 1.25rem;
}

.lvl-toc__item--h4 {
    padding-inline-start: 2.5rem;
    font-size: var(--lvl-blog-small);
}

/* Same Oxygen !important underline — the TOC is navigation, not prose.
   J5: the link, not the li, carries the box, so the whole 44px row is the
   target (it measured 15px before). */
.lvl-toc__item a {
    display: flex;
    align-items: center;
    min-height: 2.75rem;
    padding-block: 0.35rem;
    color: inherit;
    text-decoration: none !important;
    border-bottom: 1px solid transparent;
    transition: border-color 0.2s var(--lvl-blog-ease), color 0.2s var(--lvl-blog-ease);
}

.lvl-toc__item a:hover,
.lvl-toc__item a:focus-visible {
    color: var(--lvl-blog-accent-text);
    border-bottom-color: currentColor;
}

/*
 * ⚠ !important, and it has to be. Oxygen prints a per-mode link colour from an
 * inline <style> in the BODY, i.e. after this file in source order, so the
 * scroll-spy state loses the tie on every page without it (see CLAUDE.md,
 * "Oxygen-CSS-Kaskade").
 */
.lvl-toc__item a.is-current {
    color: var(--lvl-blog-accent-text) !important;
    border-bottom-color: currentColor;
}

.lvl-toc__placeholder {
    margin: 0;
    opacity: 0.7;
}

/* --------------------------------------------------------------------------
   lvl/info-box
   -------------------------------------------------------------------------- */

.lvl-info-box {
    --lvl-info-color: var(--lvl-blog-info);
    --lvl-info-rgb: var(--lvl-blog-info-rgb);
    display: flex;
    gap: 1rem;
    padding: var(--lvl-blog-pad);
    border-radius: var(--lvl-blog-radius);
    border-inline-start: 0.25rem solid var(--lvl-info-color);
    background: rgba(var(--lvl-info-rgb), 0.1);
}

.lvl-info-box--tip {
    --lvl-info-color: var(--lvl-blog-tip);
    --lvl-info-rgb: var(--lvl-blog-tip-rgb);
}

.lvl-info-box--warning {
    --lvl-info-color: var(--lvl-blog-warn);
    --lvl-info-rgb: var(--lvl-blog-warn-rgb);
}

.lvl-info-box__icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    color: var(--lvl-info-color);
}

.lvl-info-box__icon svg {
    width: 1.25rem;
    height: 1.25rem;
}

.lvl-info-box__body {
    min-width: 0;
}

/* Callout fatigue: two boxes in a row read as one group, not two shouts. */
.lvl-info-box + .lvl-info-box {
    margin-block-start: calc(var(--lvl-blog-rhythm) * 0.5);
}

.lvl-info-box__heading {
    margin: 0 0 0.4rem;
    font-weight: 600;
    font-size: var(--lvl-blog-heading-sm);
    line-height: 1.3;
}

.lvl-info-box__text {
    font-size: var(--lvl-blog-text);
    line-height: 1.6;
}

.lvl-info-box__text > :first-child {
    margin-top: 0;
}

.lvl-info-box__text > :last-child {
    margin-bottom: 0;
}

/* --------------------------------------------------------------------------
   lvl/cta-box
   -------------------------------------------------------------------------- */

.lvl-cta-box {
    padding: var(--lvl-blog-pad);
    border-radius: var(--lvl-blog-radius);
    text-align: start;
}

/*
 * J2 — flat, not a gradient. The old 0.85 -> 0.35 wash put the forced dark ink
 * on rgb(95,54,80) at the bottom edge: 1.96:1, a straight SC 1.4.3 failure.
 * One flat rose, one fixed ink, both modes. WCAG formula:
 *   light  #090920 on #ce7aa3 = 6.47
 *   dark   #090920 on #d58eb1 = 7.78
 * The ink is deliberately NOT mode-dependent — the fill is a fixed brand
 * surface in both modes, so the text on it has to be fixed too.
 */
.lvl-cta-box--primary {
    background: var(--lvl-blog-accent);
    color: var(--text-black-brand, #090920);
}

.lvl-cta-box--secondary {
    border: 1px solid var(--lvl-blog-border);
    background: transparent;
}

.lvl-cta-box--subtle {
    background: var(--lvl-blog-surface-2);
}

.lvl-cta-box__heading {
    margin: 0 0 0.6rem;
    font-size: var(--lvl-blog-heading);
    line-height: 1.2;
    color: inherit;
}

.lvl-cta-box__text {
    margin: 0 0 1.25rem;
    font-size: var(--lvl-blog-text);
    line-height: 1.6;
    color: inherit;
}

/*
 * ⚠ Oxygen forces a link colour per mode from an inline <style> in the body,
 * which sits after this file in the cascade and cannot be beaten by
 * specificity. So the button never sets its own label colour: the label is
 * whatever the mode forces (dark ink in light mode, light ink in dark mode)
 * and the FILL is what adapts — --lvl-blog-paper is the page ground for the
 * current mode, so label and fill can never collide.
 */
.lvl-cta-box__button {
    /* J5: 2.75rem = 44px, WCAG 2.2 SC 2.5.8 at AAA. J9: the block radius, not
       a third pill value. */
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
    padding: 0.7rem 1.5rem;
    border-radius: var(--lvl-blog-radius);
    border: 1px solid currentColor;
    background: transparent;
    color: inherit;
    font-size: var(--lvl-blog-text);
    font-weight: 400;
    line-height: 1.3;
    text-decoration: none !important;
    transition: box-shadow 0.25s var(--lvl-blog-ease), border-color 0.25s var(--lvl-blog-ease);
}

.lvl-cta-box--primary .lvl-cta-box__button {
    background: var(--lvl-blog-paper);
    border-color: var(--lvl-blog-paper);
}

/* Hover never swaps fill and label — that is what breaks under the forced
   link colour. It widens the ring instead. */
.lvl-cta-box__button:hover,
.lvl-cta-box__button:focus-visible {
    border-color: var(--lvl-blog-accent);
    box-shadow: 0 0 0 0.1875rem rgba(var(--lvl-blog-accent-rgb), 0.45);
}

.lvl-cta-box--secondary .lvl-cta-box__button,
.lvl-cta-box--subtle .lvl-cta-box__button {
    background: var(--lvl-blog-surface-2);
    border-color: var(--lvl-blog-border);
}

.lvl-cta-box--secondary .lvl-cta-box__button:hover,
.lvl-cta-box--secondary .lvl-cta-box__button:focus-visible,
.lvl-cta-box--subtle .lvl-cta-box__button:hover,
.lvl-cta-box--subtle .lvl-cta-box__button:focus-visible {
    border-color: var(--lvl-blog-accent);
}

/* With image */
.lvl-cta-box--has-image {
    display: grid;
    gap: clamp(1.25rem, 3vw, 2rem);
    align-items: center;
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.8fr);
}

.lvl-cta-box--img-left .lvl-cta-box__media {
    order: -1;
}

.lvl-cta-box--img-right .lvl-cta-box__media {
    order: 1;
}

.lvl-cta-box__media {
    margin: 0;
    min-width: 0;
}

.lvl-cta-box__image {
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--lvl-blog-radius-sm);
}

.lvl-cta-box__body {
    min-width: 0;
}

@media (max-width: 768px) {
    .lvl-cta-box--has-image {
        grid-template-columns: minmax(0, 1fr);
    }

    .lvl-cta-box--img-left .lvl-cta-box__media,
    .lvl-cta-box--img-right .lvl-cta-box__media {
        order: -1;
    }
}

/* --------------------------------------------------------------------------
   lvl/image-text
   -------------------------------------------------------------------------- */

.lvl-image-text {
    display: grid;
    gap: clamp(1.25rem, 3vw, 2.5rem);
    align-items: start;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

.lvl-image-text--right .lvl-image-text__media {
    order: 1;
}

.lvl-image-text__media {
    margin: 0;
    min-width: 0;
}

.lvl-image-text__image {
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--lvl-blog-radius-sm);
}

.lvl-image-text__caption {
    margin-top: 0.5rem;
    font-size: var(--lvl-blog-small);
    line-height: 1.45;
    opacity: 0.72;
}

.lvl-image-text__body {
    min-width: 0;
    font-size: var(--lvl-blog-text);
    line-height: 1.6;
}

.lvl-image-text__body > :first-child {
    margin-top: 0;
}

.lvl-image-text__body > :last-child {
    margin-bottom: 0;
}

.lvl-image-text__placeholder {
    display: grid;
    place-items: center;
    aspect-ratio: 4 / 3;
    border: 1px dashed var(--lvl-blog-border);
    border-radius: var(--lvl-blog-radius-sm);
    opacity: 0.7;
}

@media (max-width: 768px) {
    .lvl-image-text {
        grid-template-columns: minmax(0, 1fr);
    }

    .lvl-image-text--right .lvl-image-text__media {
        order: -1;
    }
}

/* --------------------------------------------------------------------------
   lvl/faq
   -------------------------------------------------------------------------- */

.lvl-faq__heading {
    margin: 0 0 var(--lvl-blog-gap);
    font-size: var(--lvl-blog-heading);
    line-height: 1.2;
}

.lvl-faq__list {
    border-top: 1px solid var(--lvl-blog-border);
}

.lvl-faq__item {
    border-bottom: 1px solid var(--lvl-blog-border);
}

.lvl-faq__question {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 1rem 0;
    cursor: pointer;
    list-style: none;
    font-size: var(--lvl-blog-heading-sm);
    line-height: 1.4;
    transition: color 0.2s var(--lvl-blog-ease);
}

.lvl-faq__question::-webkit-details-marker {
    display: none;
}

.lvl-faq__question::marker {
    content: "";
}

.lvl-faq__question:hover,
.lvl-faq__question:focus-visible {
    color: var(--lvl-blog-accent-text);
}

.lvl-faq__question-text {
    min-width: 0;
}

.lvl-faq__icon {
    position: relative;
    flex: 0 0 auto;
    width: 1rem;
    height: 1rem;
}

.lvl-faq__icon::before,
.lvl-faq__icon::after {
    content: "";
    position: absolute;
    inset-block-start: 50%;
    inset-inline-start: 0;
    width: 100%;
    height: 2px;
    background: currentColor;
    transform: translateY(-50%);
    transition: transform 0.25s var(--lvl-blog-ease), opacity 0.25s var(--lvl-blog-ease);
}

.lvl-faq__icon::after {
    transform: translateY(-50%) rotate(90deg);
}

.lvl-faq__item[open] .lvl-faq__icon::after {
    transform: translateY(-50%) rotate(0deg);
    opacity: 0;
}

.lvl-faq__answer {
    padding: 0 0 1.25rem;
    font-size: var(--lvl-blog-text);
    line-height: 1.6;
}

.lvl-faq__answer > :first-child {
    margin-top: 0;
}

.lvl-faq__answer > :last-child {
    margin-bottom: 0;
}

/* --------------------------------------------------------------------------
   lvl/author-box
   -------------------------------------------------------------------------- */

/* J9: the same box every other block has — border on all four sides and the
   shared radius, instead of two loose rules. */
.lvl-author-box {
    display: flex;
    gap: clamp(1rem, 2.5vw, 1.75rem);
    align-items: flex-start;
    padding: var(--lvl-blog-pad);
    border: 1px solid var(--lvl-blog-border);
    border-radius: var(--lvl-blog-radius);
}

/*
 * J8 — nothing but a name and an avatar. A framed box around two words reads
 * as an empty component; the byline just sits on the page instead.
 */
.lvl-author-box--bare {
    align-items: center;
    gap: 0.75rem;
    padding: 0;
    border: 0;
    border-radius: 0;
}

.lvl-author-box--bare .lvl-author-box__avatar {
    width: 2.5rem;
    height: 2.5rem;
}

.lvl-author-box__avatar-wrap {
    flex: 0 0 auto;
}

.lvl-author-box__avatar {
    display: block;
    width: 4.5rem;
    height: 4.5rem;
    border-radius: 50%;
    object-fit: cover;
}

.lvl-author-box__content {
    min-width: 0;
}

.lvl-author-box__name {
    margin: 0;
    font-size: var(--lvl-blog-heading-sm);
    line-height: 1.3;
}

.lvl-author-box__role {
    margin: 0.15rem 0 0;
    font-size: var(--lvl-blog-small);
    line-height: 1.4;
    opacity: 0.72;
}

.lvl-author-box__bio {
    margin: 0.75rem 0 0;
    font-size: var(--lvl-blog-text);
    line-height: 1.6;
}

.lvl-author-box__links {
    display: flex;
    flex-wrap: wrap;
    gap: 0 1.25rem;
    margin: 0.35rem 0 0;
    padding: 0;
    list-style: none;
}

.lvl-author-box__links li {
    margin: 0;
}

.lvl-author-box__links a {
    /* J5: the click area was the text line, ~15px. */
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
    color: inherit;
    font-size: var(--lvl-blog-small);
    text-decoration: none !important;
    border-bottom: 1px solid var(--lvl-blog-border);
    transition: color 0.2s var(--lvl-blog-ease), border-color 0.2s var(--lvl-blog-ease);
}

.lvl-author-box__links a:hover,
.lvl-author-box__links a:focus-visible {
    color: var(--lvl-blog-accent-text);
    border-bottom-color: currentColor;
}

@media (max-width: 480px) {
    .lvl-author-box {
        flex-direction: column;
    }

    .lvl-author-box--bare {
        flex-direction: row;
    }
}

/* --------------------------------------------------------------------------
   lvl/related-links
   -------------------------------------------------------------------------- */

.lvl-related-links__heading {
    margin: 0 0 var(--lvl-blog-gap);
    font-size: var(--lvl-blog-heading);
    line-height: 1.2;
}

.lvl-related-links__grid {
    display: grid;
    gap: var(--lvl-blog-gap);
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    align-items: stretch;
    margin: 0;
    padding: 0;
    list-style: none;
}

.lvl-related-links__item {
    margin: 0;
    min-width: 0;
}

/*
 * Oxygen underlines every link in the content area with an !important rule
 * (universal.css, cross-origin so it cannot be inspected from here). A card is
 * one big link around an image, a title and an excerpt — an underline through
 * all of it reads as body copy, not as a card. This is the one place that has
 * to answer !important with !important.
 */
.lvl-related-links__card,
.lvl-related-links__card:hover,
.lvl-related-links__card:focus,
.lvl-related-links__card:focus-visible,
.lvl-related-links__card span {
    text-decoration: none !important;
}

.lvl-related-links__card {
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow: hidden;
    border: 1px solid var(--lvl-blog-border);
    border-radius: var(--lvl-blog-radius);
    background: var(--lvl-blog-surface);
    color: inherit;
    text-decoration: none;
    transition: border-color 0.25s var(--lvl-blog-ease), box-shadow 0.25s var(--lvl-blog-ease),
        background-color 0.25s var(--lvl-blog-ease);
}

.lvl-related-links__card:hover,
.lvl-related-links__card:focus-visible {
    border-color: var(--lvl-blog-accent);
    background: var(--lvl-blog-surface-2);
    box-shadow: var(--lvl-blog-shadow);
}

/*
 * J8 — when NOT ONE card has an image the grid drops to a compact list: three
 * empty 16:9 tints in a row read as a broken layout, not as a design. A mixed
 * set keeps the tinted placeholder, because there the tile is what holds the
 * row's text on one line.
 */
.lvl-related-links--list .lvl-related-links__grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
}

.lvl-related-links--list .lvl-related-links__card {
    height: auto;
    border-inline: 0;
    border-block-start: 0;
    border-radius: 0;
    background: transparent;
}

.lvl-related-links--list .lvl-related-links__item:first-child .lvl-related-links__card {
    border-block-start: 1px solid var(--lvl-blog-border);
}

.lvl-related-links--list .lvl-related-links__card:hover,
.lvl-related-links--list .lvl-related-links__card:focus-visible {
    background: var(--lvl-blog-surface);
    box-shadow: none;
}

.lvl-related-links--list .lvl-related-links__media {
    display: none;
}

.lvl-related-links--list .lvl-related-links__body {
    padding: 0.85rem 0.5rem;
}

.lvl-related-links--list .lvl-related-links__cta {
    margin-top: 0.25rem;
    padding-top: 0;
}

/* Always present, even without an image, so every card in a row starts its
   text at the same height. */
.lvl-related-links__media {
    display: block;
    flex: 0 0 auto;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    background: var(--lvl-blog-surface-2);
}

.lvl-related-links__media--empty {
    background: linear-gradient(
        135deg,
        rgba(var(--lvl-blog-accent-rgb), 0.18) 0%,
        rgba(var(--lvl-blog-accent-rgb), 0.04) 100%
    );
}

.lvl-related-links__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.lvl-related-links__body {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    flex: 1 1 auto;
    padding: 1rem 1.125rem 1.125rem;
}

.lvl-related-links__type {
    font-size: var(--lvl-blog-small);
    line-height: 1.2;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    opacity: 0.66;
}

.lvl-related-links__title {
    font-size: var(--lvl-blog-heading-sm);
    line-height: 1.32;
}

.lvl-related-links__desc {
    font-size: var(--lvl-blog-small);
    line-height: 1.5;
    opacity: 0.78;
}

.lvl-related-links__cta {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    margin-top: auto;
    padding-top: 0.5rem;
    font-size: var(--lvl-blog-small);
    color: var(--lvl-blog-accent-text);
}

.lvl-related-links__cta svg {
    transition: transform 0.25s var(--lvl-blog-ease);
}

.lvl-related-links__card:hover .lvl-related-links__cta svg,
.lvl-related-links__card:focus-visible .lvl-related-links__cta svg {
    transform: translateX(0.2rem);
}

/* --------------------------------------------------------------------------
   lvl/video-embed
   -------------------------------------------------------------------------- */

.lvl-video__player {
    position: relative;
    display: block;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    border-radius: var(--lvl-blog-radius);
    background: var(--lvl-blog-surface-2);
}

.lvl-video__poster {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 0.4s var(--lvl-blog-ease), filter 0.4s var(--lvl-blog-ease);
}

.lvl-video__player:hover .lvl-video__poster {
    transform: scale(1.02);
    filter: brightness(0.85);
}

.lvl-video__playbtn {
    position: absolute;
    inset-block-start: 50%;
    inset-inline-start: 50%;
    transform: translate(-50%, -50%);
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    /* One line, always: the provider name made the label wrap to three lines
       on a 390px poster and run into the consent bar underneath it. */
    padding: 0.7rem 1.4rem;
    min-height: 2.75rem;
    max-width: calc(100% - 2rem);
    white-space: nowrap;
    border: 1px solid var(--text-shade-white, #f3f5fc);
    border-radius: var(--lvl-blog-radius);
    background: rgba(9, 9, 32, 0.72);
    color: var(--text-shade-white, #f3f5fc);
    font: inherit;
    font-size: var(--lvl-blog-text);
    line-height: 1.2;
    cursor: pointer;
    transition: background-color 0.25s var(--lvl-blog-ease), color 0.25s var(--lvl-blog-ease);
}

.lvl-video__playbtn:hover,
.lvl-video__playbtn:focus-visible {
    background: var(--text-shade-white, #f3f5fc);
    color: var(--text-black-brand, #090920);
}

.lvl-video__icon {
    display: inline-flex;
    align-items: center;
}

.lvl-video__icon svg {
    width: 1.1rem;
    height: 1.1rem;
}

.lvl-video__consent {
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    margin: 0;
    padding: 0.5rem 1rem;
    background: rgba(9, 9, 32, 0.72);
    color: var(--text-shade-white, #f3f5fc);
    font-size: var(--lvl-blog-small);
    line-height: 1.35;
    text-align: center;
}

@media (max-width: 480px) {
    .lvl-video__playbtn {
        padding: 0.6rem 0.9rem;
        font-size: var(--lvl-blog-small);
    }
}

.lvl-video__player iframe {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
}

/* Only ever rendered inside <noscript>, for crawlers and JS-off visitors. */
.lvl-video__fallback {
    position: absolute;
    inset-block-end: 0.6rem;
    inset-inline-start: 50%;
    transform: translateX(-50%);
    color: var(--text-shade-white, #f3f5fc);
    font-size: var(--lvl-blog-small);
}

.lvl-video__caption {
    margin-top: 0.6rem;
    font-size: var(--lvl-blog-small);
    line-height: 1.45;
    opacity: 0.72;
}

/* --------------------------------------------------------------------------
   Focus visibility (WCAG) — never removed, always on the site's own colours
   -------------------------------------------------------------------------- */

.lvl-tldr a:focus-visible,
.lvl-toc a:focus-visible,
.lvl-info-box a:focus-visible,
.lvl-cta-box a:focus-visible,
.lvl-image-text a:focus-visible,
.lvl-faq summary:focus-visible,
.lvl-author-box a:focus-visible,
.lvl-related-links a:focus-visible,
.lvl-video button:focus-visible {
    /* J3: --lvl-blog-accent is a SURFACE colour and only reaches 2.6:1 as a
       ring on the light ground. The accent TEXT token is 6.53:1. */
    outline: 2px solid var(--lvl-blog-accent-text);
    outline-offset: 3px;
    border-radius: var(--lvl-blog-radius-sm);
}

/* --------------------------------------------------------------------------
   Reduced motion
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    .lvl-tldr *,
    .lvl-toc *,
    .lvl-info-box *,
    .lvl-cta-box *,
    .lvl-image-text *,
    .lvl-faq *,
    .lvl-author-box *,
    .lvl-related-links *,
    .lvl-video * {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
    }

    .lvl-video__player:hover .lvl-video__poster,
    .lvl-related-links__card:hover .lvl-related-links__cta svg {
        transform: none;
    }
}

/* ==========================================================================
   Phase 2 — lvl/case-card, lvl/key-figures, lvl/code-snippet, lvl/checklist
   --------------------------------------------------------------------------
   Same rules as above: no font-family, no px, no hard-coded colour except as
   a var() fallback, and text colour is always `inherit` — Oxygen flips the
   theme with a `body.dark-mode { color: … }` rule while --text-color keeps its
   light value, so a block that reads --text-color paints dark ink on a dark
   ground. Surfaces, borders and accents come from the --lvl-blog-* layer at
   the top of this file, which is declared once per mode.
   ========================================================================== */

/*
 * One more token, same construction as --lvl-blog-accent-text above: the brand
 * green is a surface colour and only reaches ~2.4:1 as text on the near-white
 * ground, so the checklist's "done" cues use a darkened green in light mode.
 * In dark mode the bright green already carries and is used unchanged.
 */
:root {
    --lvl-blog-tip-text: #4a6b2f;
}

body.dark-mode {
    --lvl-blog-tip-text: var(--lvl-blog-tip);
}

.lvl-case-card,
.lvl-key-figures,
.lvl-code-snippet,
.lvl-checklist {
    margin-block: var(--lvl-blog-rhythm);
    /* ⚠ never a variable here — see the note at the top of this file. */
    color: inherit;
    box-sizing: border-box;
    max-width: 100%;
}

.lvl-case-card *,
.lvl-key-figures *,
.lvl-code-snippet *,
.lvl-checklist * {
    box-sizing: border-box;
}

.lvl-case-card.alignwide,
.lvl-key-figures.alignwide,
.lvl-code-snippet.alignwide,
.lvl-checklist.alignwide {
    width: auto;
    margin-inline: calc(-1 * clamp(0rem, 4vw, 5rem));
}

/* --------------------------------------------------------------------------
   lvl/case-card
   -------------------------------------------------------------------------- */

/*
 * Whole-card link, same reasoning as related-links: Oxygen underlines every
 * link in the content area with an !important rule, and an underline running
 * through image, title and excerpt reads as body copy instead of a card. This
 * is the second place that has to answer !important with !important.
 */
.lvl-case-card__link,
.lvl-case-card__link:hover,
.lvl-case-card__link:focus,
.lvl-case-card__link:focus-visible,
.lvl-case-card__link span {
    text-decoration: none !important;
}

.lvl-case-card__link {
    display: grid;
    grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
    align-items: stretch;
    overflow: hidden;
    border: 1px solid var(--lvl-blog-border);
    border-radius: var(--lvl-blog-radius);
    background: var(--lvl-blog-surface);
    color: inherit;
    transition: border-color 0.25s var(--lvl-blog-ease), box-shadow 0.25s var(--lvl-blog-ease),
        background-color 0.25s var(--lvl-blog-ease);
}

.lvl-case-card__link--no-media {
    grid-template-columns: minmax(0, 1fr);
}

.lvl-case-card__link:hover,
.lvl-case-card__link:focus-visible {
    border-color: var(--lvl-blog-accent);
    background: var(--lvl-blog-surface-2);
    box-shadow: var(--lvl-blog-shadow);
}

.lvl-case-card__media {
    display: block;
    overflow: hidden;
    min-height: 100%;
    aspect-ratio: 4 / 3;
    background: var(--lvl-blog-surface-2);
}

.lvl-case-card__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.lvl-case-card__body {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    padding: var(--lvl-blog-pad);
}

.lvl-case-card__kicker {
    font-size: var(--lvl-blog-small);
    line-height: 1.2;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    opacity: 0.66;
}

.lvl-case-card__title {
    font-size: var(--lvl-blog-heading-sm);
    line-height: 1.3;
    font-weight: 600;
}

.lvl-case-card__excerpt {
    font-size: var(--lvl-blog-small);
    line-height: 1.55;
    opacity: 0.78;
}

.lvl-case-card__cta {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    margin-top: auto;
    padding-top: 0.75rem;
    font-size: var(--lvl-blog-small);
    color: var(--lvl-blog-accent-text);
}

.lvl-case-card__cta svg {
    transition: transform 0.25s var(--lvl-blog-ease);
}

.lvl-case-card__link:hover .lvl-case-card__cta svg,
.lvl-case-card__link:focus-visible .lvl-case-card__cta svg {
    transform: translateX(0.2rem);
}

@media (max-width: 768px) {
    .lvl-case-card__link {
        grid-template-columns: minmax(0, 1fr);
    }

    .lvl-case-card__media {
        aspect-ratio: 16 / 9;
    }
}

/* --------------------------------------------------------------------------
   lvl/key-figures
   -------------------------------------------------------------------------- */

.lvl-key-figures__grid {
    display: grid;
    /* One column per figure (2–4); render.php sets the count inline because
       "as many columns as items" has no static CSS equivalent. */
    grid-template-columns: repeat(var(--lvl-key-figures-columns, 2), minmax(0, 1fr));
    /* J8: one row height for the whole grid. Without it a figure with a note
       stretches its row and the values stop sharing a baseline. */
    grid-auto-rows: 1fr;
    gap: var(--lvl-blog-gap);
    margin: 0;
    padding: 0;
    list-style: none;
}

/* At 1024px four figures already shrink to ~220px each; two columns from
   there down, one below 480px. */
@media (max-width: 1024px) {
    .lvl-key-figures__grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 480px) {
    .lvl-key-figures__grid {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* The theme styles content list items as `display: list-item` with a two-class
   selector, which outranks a single class. Repeating the block class is enough
   to win — no !important needed. */
.lvl-key-figures .lvl-key-figures__item {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    margin: 0;
    min-width: 0;
    padding: var(--lvl-blog-pad);
    border: 1px solid var(--lvl-blog-border-soft);
    border-radius: var(--lvl-blog-radius);
    background: var(--lvl-blog-surface);
}

.lvl-key-figures__value {
    font-size: var(--lvl-blog-figure);
    line-height: 1.05;
    font-weight: 300;
    color: var(--lvl-blog-accent-text);
    overflow-wrap: anywhere;
}

.lvl-key-figures__label {
    font-size: var(--lvl-blog-text);
    line-height: 1.35;
}

.lvl-key-figures__note {
    font-size: var(--lvl-blog-small);
    line-height: 1.45;
    opacity: 0.7;
}

.lvl-key-figures__source {
    margin: 0.75rem 0 0;
    font-size: var(--lvl-blog-small);
    line-height: 1.45;
    opacity: 0.66;
}

.lvl-key-figures__heading {
    margin: 0 0 var(--lvl-blog-gap);
    font-size: var(--lvl-blog-heading-sm);
    line-height: 1.3;
}

/* --------------------------------------------------------------------------
   lvl/code-snippet
   -------------------------------------------------------------------------- */

.lvl-code-snippet {
    margin-inline: 0;
}

.lvl-code-snippet__frame {
    position: relative;
    border: 1px solid var(--lvl-blog-border);
    border-radius: var(--lvl-blog-radius);
    background: var(--lvl-blog-surface-2);
    overflow: hidden;
}

.lvl-code-snippet__lang {
    position: absolute;
    inset-block-start: 0.75rem;
    inset-inline-start: var(--lvl-blog-pad);
    font-size: var(--lvl-blog-small);
    line-height: 1;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    opacity: 0.55;
}

/*
 * Injected by blocks.js — absent without JS, and the snippet is complete then.
 * The same button is nailed onto core/code (see "Code highlighting" below), so
 * the rules are written against the class, never against a parent block.
 *
 * Quiet by default: it fades in on pointer hover and on keyboard focus. On a
 * touch device there is no hover, so it is simply always visible.
 */
.lvl-code-snippet__copy {
    position: absolute;
    inset-block-start: 0.5rem;
    inset-inline-end: 0.5rem;
    z-index: 2;
    padding: 0.35rem 0.9rem;
    border: 1px solid var(--lvl-blog-border);
    border-radius: var(--lvl-blog-radius-sm);
    background: var(--lvl-blog-surface-2);
    color: inherit;
    font: inherit;
    font-size: var(--lvl-blog-small);
    line-height: 1.2;
    cursor: pointer;
    /* J5: 44px. Never fully invisible any more — a keyboard user could not
       know there was anything to tab to. */
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
    opacity: 0.45;
    transition: opacity 0.2s var(--lvl-blog-ease),
        border-color 0.2s var(--lvl-blog-ease),
        background-color 0.2s var(--lvl-blog-ease);
}

.lvl-code-snippet__frame:hover .lvl-code-snippet__copy,
.lvl-code-snippet__frame:focus-within .lvl-code-snippet__copy,
.lvl-code-frame:hover .lvl-code-snippet__copy,
.lvl-code-frame:focus-within .lvl-code-snippet__copy,
.lvl-code-snippet__copy:hover,
.lvl-code-snippet__copy:focus,
.lvl-code-snippet__copy:focus-visible {
    opacity: 1;
}

.lvl-code-snippet__copy:hover,
.lvl-code-snippet__copy:focus-visible {
    border-color: var(--lvl-blog-accent-text);
    background: var(--lvl-blog-surface-2);
}

/* Copy failed: say so on the button itself, not only in the live region. */
.lvl-code-snippet__copy--failed {
    opacity: 1;
    border-color: var(--lvl-blog-warn);
}

/* No hover to reveal it with — keep it on screen. */
@media (hover: none) {
    .lvl-code-snippet__copy {
        opacity: 1;
    }
}

.lvl-code-snippet__status {
    position: absolute;
    inset-block-start: 0.5rem;
    /* Left of the button; the button is ~6rem wide including its padding. */
    inset-inline-end: 6.5rem;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
    padding: 0.35rem 0;
    font-size: var(--lvl-blog-small);
    line-height: 1.2;
    color: var(--lvl-blog-accent-text);
    pointer-events: none;
}

.lvl-code-snippet__pre {
    margin: 0;
    /* Top padding clears the language label AND the 44px copy button. */
    padding: 3.5rem var(--base-20, 1.125rem) var(--base-20, 1.125rem);
    overflow-x: auto;
    background: transparent;
    color: inherit;
}

.lvl-code-snippet__code {
    display: block;
    /* em, not a --base-* clamp: code should track the surrounding copy. */
    font-size: 0.85em;
    font-family: var(--lvl-blog-mono);
    line-height: 1.6;
    /*
     * J8 — no wrapping, a visible scroll edge instead. `pre-wrap` under 768px
     * was the alternative and was rejected: code is the one content type where
     * the line break carries meaning, and soft-wrapping a shell pipeline or an
     * indented PHP block at an arbitrary column makes it unreadable and
     * uncopyable. blocks.js marks a <pre> that actually overflows; the fade
     * below shows there is more, and the tabindex it adds makes the box
     * keyboard-scrollable (SC 2.1.1).
     */
    white-space: pre;
    background: transparent;
    color: inherit;
    tab-size: 4;
    -moz-tab-size: 4;
}

/*
 * The fade sits on the frame, not on the <pre>, so it does not scroll away.
 * Only present once JS has confirmed the code really is wider than the box.
 */
.lvl-code-snippet__frame--scrolls::after,
.lvl-code-frame--scrolls::after {
    content: "";
    position: absolute;
    inset-block: 0;
    inset-inline-end: 0;
    width: 2.5rem;
    pointer-events: none;
    background: linear-gradient(
        to right,
        rgba(var(--lvl-blog-fade-rgb), 0),
        rgba(var(--lvl-blog-fade-rgb), 0.92)
    );
}

.lvl-code-snippet__pre:focus-visible,
.lvl-code-frame pre.wp-block-code:focus-visible {
    outline: 2px solid var(--lvl-blog-accent-text);
    outline-offset: -2px;
}

.lvl-code-snippet__caption {
    margin: 0.6rem 0 0;
    font-size: var(--lvl-blog-small);
    line-height: 1.45;
    opacity: 0.7;
}

/* --------------------------------------------------------------------------
   lvl/checklist
   -------------------------------------------------------------------------- */

.lvl-checklist {
    padding: var(--lvl-blog-pad);
    border: 1px solid var(--lvl-blog-border);
    border-radius: var(--lvl-blog-radius);
    background: var(--lvl-blog-surface);
}

.lvl-checklist__title {
    margin: 0 0 var(--lvl-blog-gap);
    font-size: var(--lvl-blog-heading-sm);
    line-height: 1.3;
}

.lvl-checklist__list {
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Same specificity bump as key-figures — the theme's list-item rule outranks a
   single class. */
.lvl-checklist .lvl-checklist__item {
    position: relative;
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    margin: 0;
    padding: 0.55rem 0 0.55rem 1.75rem;
    border-bottom: 1px solid var(--lvl-blog-border-soft);
    font-size: var(--lvl-blog-text);
    line-height: 1.55;
}

.lvl-checklist .lvl-checklist__item:last-child {
    border-bottom: 0;
    padding-bottom: 0;
}

/* Open item: an empty box. Done item: the site's own check glyph. */
.lvl-checklist .lvl-checklist__item::before {
    content: "";
    position: absolute;
    inset-inline-start: 0;
    inset-block-start: 1em;
    width: 0.8rem;
    height: 0.8rem;
    transform: translateY(-50%);
    border: 1px solid currentColor;
    border-radius: var(--lvl-blog-radius-sm);
    opacity: 0.45;
}

.lvl-checklist .lvl-checklist__item--done::before {
    border: 0;
    border-radius: 0;
    opacity: 1;
    background-color: var(--lvl-blog-tip-text);
    -webkit-mask: var(--check-icon) center / contain no-repeat;
    mask: var(--check-icon) center / contain no-repeat;
}

/* No --check-icon on the page (editor iframe, for instance): show a dot. */
@supports not (mask: url("")) {
    .lvl-checklist .lvl-checklist__item--done::before {
        border-radius: 50%;
        width: 0.5rem;
        height: 0.5rem;
    }
}

.lvl-checklist .lvl-checklist__item--done .lvl-checklist__text {
    opacity: 0.62;
}

/*
 * J8: the "ERLEDIGT" badge is gone. It collided with the text on a phone and
 * said a third time what the tick and the dimmed text already say. The state
 * now reaches assistive technology through the visually hidden label the
 * renderer puts in front of the item text.
 */

/* --------------------------------------------------------------------------
   Phase 2 — focus and reduced motion (mirrors the blocks above)
   -------------------------------------------------------------------------- */

.lvl-case-card a:focus-visible,
.lvl-code-snippet__copy:focus-visible {
    outline: 2px solid var(--lvl-blog-accent-text);
    outline-offset: 3px;
    border-radius: var(--lvl-blog-radius-sm);
}

@media (prefers-reduced-motion: reduce) {
    .lvl-case-card *,
    .lvl-key-figures *,
    .lvl-code-snippet *,
    .lvl-code-frame *,
    .lvl-checklist * {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
    }

    .lvl-case-card__link:hover .lvl-case-card__cta svg {
        transform: none;
    }
}

/* ==========================================================================
   Code — core/code box + Prism theme
   --------------------------------------------------------------------------
   Two blocks show code: core/code (what the six Gutenberg posts actually use)
   and lvl/code-snippet. Until now only the latter had a box; core/code fell
   through to Oxygen, which styles nothing, so it rendered as bare monospace
   text glued to the paragraph above it (see levels.dev/acf-wysiwyg-block-save-fix).
   This section gives core/code the same frame and gives BOTH the same
   highlighting.

   Highlighting is Prism 1.29.0, vendored under assets/vendor/prism/ — no CDN
   call, nothing leaves the visitor's browser. It is loaded only on single
   posts that really contain a code block (see lvl_blog_enqueue_block_script)
   and runs in manual mode: blocks.js highlights exactly the two selectors
   below, never anything else Oxygen may have put on the page.

   Without JS both blocks stay complete: box, padding, scroll and monospace
   come from CSS; only colour and the copy button are the enhancement.
   ========================================================================== */

:root {
    /*
     * The only font-family in this file, and it is a stack of system faces:
     * Oxygen sets no monospace face, and the site's own display faces are not
     * fixed-pitch. `ui-monospace` picks up whatever the OS considers the
     * coding face, so the code column stays aligned everywhere.
     */
    --lvl-blog-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;

    /*
     * Prism token colours. Same construction as --lvl-blog-accent-text: the
     * brand hues are surface colours and fail as text on the near-white code
     * tint, so light mode uses darkened variants of the same hue. Measured
     * against the actual code surface — rgba(9,9,32,0.075) over #f3f5fc, i.e.
     * rgb(226,227,236) — with the WCAG formula, all ≥ 4.5:1 (AA):
     *
     *   keyword  #4b3aa8  6.65   (brand violet, darkened)
     *   string   #2f6030  5.79   (brand green, darkened)
     *   number   #8a4212  5.73   (brand orange, darkened)
     *   comment  #565669  5.61   (neutral, italic)
     *   function #0f5f63  5.79   (brand teal, darkened)
     *   tag      #7d3556  6.53   (brand rose, = --lvl-blog-accent-text)
     *   operator #5a3a6e  7.25
     *   property #3a4a7a  6.73
     *   punctuation inherits the body ink at 0.75 alpha — 7.98:1
     */
    --lvl-code-keyword: #4b3aa8;
    --lvl-code-string: #2f6030;
    --lvl-code-number: #8a4212;
    --lvl-code-comment: #565669;
    --lvl-code-function: #0f5f63;
    --lvl-code-tag: #7d3556;
    --lvl-code-operator: #5a3a6e;
    --lvl-code-property: #3a4a7a;
}

body.dark-mode {
    /*
     * Dark mode: the bright site accents already carry. Measured against
     * rgba(243,245,252,0.09) over #090920, i.e. rgb(30,30,52):
     *
     *   keyword  #b8b5ff   8.59
     *   string   #8aff80  12.94  (= --lvl-blog-tip)
     *   number   #ffb37c   9.29
     *   comment  #a0a0bd   6.40
     *   function #80ffea  13.49  (= --lvl-blog-info)
     *   tag      #ff80bf   7.05  (= --lvl-blog-accent)
     *   operator #ffff80  15.40  (= --lvl-blog-warn)
     *   property #c9cbff  10.44
     *   punctuation inherits the body ink at 0.75 alpha — 8.92:1
     */
    --lvl-code-keyword: #b8b5ff;
    --lvl-code-string: var(--lvl-blog-tip, #8aff80);
    --lvl-code-number: #ffb37c;
    --lvl-code-comment: #a0a0bd;
    --lvl-code-function: var(--lvl-blog-info, #80ffea);
    --lvl-code-tag: var(--lvl-blog-accent, #ff80bf);
    --lvl-code-operator: var(--lvl-blog-warn, #ffff80);
    --lvl-code-property: #c9cbff;
}

/* --------------------------------------------------------------------------
   core/code — the same frame lvl/code-snippet has
   --------------------------------------------------------------------------
   `pre.wp-block-code` rather than `.wp-block-code`: core prints its block
   styles inline in the <head>, so a single class would lose the tie on source
   order. The element+class selector wins it outright.
   -------------------------------------------------------------------------- */

pre.wp-block-code {
    margin-block: var(--lvl-blog-rhythm);
    padding: var(--base-20, 1.125rem);
    border: 1px solid var(--lvl-blog-border);
    border-radius: var(--lvl-blog-radius);
    background: var(--lvl-blog-surface-2);
    /* ⚠ never a variable here — see the note at the top of this file. */
    color: inherit;
    overflow-x: auto;
    box-sizing: border-box;
    max-width: 100%;
}

pre.wp-block-code code {
    display: block;
    font-family: var(--lvl-blog-mono);
    font-size: 0.85em;
    line-height: 1.6;
    white-space: pre;
    background: transparent;
    color: inherit;
    tab-size: 4;
    -moz-tab-size: 4;
}

/*
 * blocks.js wraps each core/code <pre> in this bare positioning context and
 * hangs the copy button off it. The wrapper carries no box of its own — the
 * <pre> keeps it — it only stops the absolutely positioned button from
 * scrolling away with the code, which is what would happen inside the
 * overflowing <pre>. It inherits the rhythm and hands it back to the <pre>.
 */
.lvl-code-frame {
    position: relative;
    margin-block: var(--lvl-blog-rhythm);
    max-width: 100%;
}

/*
 * The extra head room only exists once the button does — without JS there is
 * no wrapper, no button, and no empty strip at the top of the block. Same
 * 2.5rem the snippet block reserves for its language label.
 */
.lvl-code-frame > pre.wp-block-code {
    margin-block: 0;
    padding-block-start: 3.5rem;
}

/* --------------------------------------------------------------------------
   Prism token theme
   --------------------------------------------------------------------------
   Scoped to our two code blocks so nothing else on an Oxygen page can pick it
   up. Colour only: no font-size, no font-weight, no padding, no display — a
   highlighted block has to occupy exactly the same box as the unhighlighted
   one, or the page would shift when Prism runs.
   -------------------------------------------------------------------------- */

.lvl-code-snippet__code .token,
pre.wp-block-code code .token {
    background: none;
    font: inherit;
}

.lvl-code-snippet__code .token.comment,
.lvl-code-snippet__code .token.prolog,
.lvl-code-snippet__code .token.cdata,
.lvl-code-snippet__code .token.doctype,
pre.wp-block-code code .token.comment,
pre.wp-block-code code .token.prolog,
pre.wp-block-code code .token.cdata,
pre.wp-block-code code .token.doctype {
    color: var(--lvl-code-comment);
    font-style: italic;
}

.lvl-code-snippet__code .token.punctuation,
.lvl-code-snippet__code .token.attr-equals,
pre.wp-block-code code .token.punctuation,
pre.wp-block-code code .token.attr-equals {
    color: inherit;
    opacity: 0.75;
}

.lvl-code-snippet__code .token.keyword,
.lvl-code-snippet__code .token.atrule,
.lvl-code-snippet__code .token.rule,
.lvl-code-snippet__code .token.important,
.lvl-code-snippet__code .token.boolean,
.lvl-code-snippet__code .token.constant,
.lvl-code-snippet__code .token.symbol,
pre.wp-block-code code .token.keyword,
pre.wp-block-code code .token.atrule,
pre.wp-block-code code .token.rule,
pre.wp-block-code code .token.important,
pre.wp-block-code code .token.boolean,
pre.wp-block-code code .token.constant,
pre.wp-block-code code .token.symbol {
    color: var(--lvl-code-keyword);
}

.lvl-code-snippet__code .token.string,
.lvl-code-snippet__code .token.char,
.lvl-code-snippet__code .token.attr-value,
.lvl-code-snippet__code .token.regex,
.lvl-code-snippet__code .token.url,
.lvl-code-snippet__code .token.inserted,
pre.wp-block-code code .token.string,
pre.wp-block-code code .token.char,
pre.wp-block-code code .token.attr-value,
pre.wp-block-code code .token.regex,
pre.wp-block-code code .token.url,
pre.wp-block-code code .token.inserted {
    color: var(--lvl-code-string);
}

.lvl-code-snippet__code .token.number,
.lvl-code-snippet__code .token.unit,
.lvl-code-snippet__code .token.hexcode,
.lvl-code-snippet__code .token.entity,
pre.wp-block-code code .token.number,
pre.wp-block-code code .token.unit,
pre.wp-block-code code .token.hexcode,
pre.wp-block-code code .token.entity {
    color: var(--lvl-code-number);
}

.lvl-code-snippet__code .token.function,
.lvl-code-snippet__code .token.function-variable,
.lvl-code-snippet__code .token.class-name,
.lvl-code-snippet__code .token.builtin,
.lvl-code-snippet__code .token.selector,
pre.wp-block-code code .token.function,
pre.wp-block-code code .token.function-variable,
pre.wp-block-code code .token.class-name,
pre.wp-block-code code .token.builtin,
pre.wp-block-code code .token.selector {
    color: var(--lvl-code-function);
}

.lvl-code-snippet__code .token.tag,
.lvl-code-snippet__code .token.namespace,
.lvl-code-snippet__code .token.deleted,
pre.wp-block-code code .token.tag,
pre.wp-block-code code .token.namespace,
pre.wp-block-code code .token.deleted {
    color: var(--lvl-code-tag);
}

/* A tag's own punctuation sits inside .token.tag — keep the brackets quiet. */
.lvl-code-snippet__code .token.tag .token.punctuation,
pre.wp-block-code code .token.tag .token.punctuation {
    color: inherit;
}

.lvl-code-snippet__code .token.operator,
.lvl-code-snippet__code .token.variable,
pre.wp-block-code code .token.operator,
pre.wp-block-code code .token.variable {
    color: var(--lvl-code-operator);
}

.lvl-code-snippet__code .token.property,
.lvl-code-snippet__code .token.attr-name,
pre.wp-block-code code .token.property,
pre.wp-block-code code .token.attr-name {
    color: var(--lvl-code-property);
}

/*
 * PHP inside markup: Prism wraps the whole `<?php … ?>` run in .token.php and
 * would otherwise inherit the tag colour from the surrounding markup.
 */
.lvl-code-snippet__code .token.php,
pre.wp-block-code code .token.php {
    color: inherit;
}

@media (prefers-reduced-motion: reduce) {
    .lvl-code-snippet__copy {
        transition-duration: 0.01ms !important;
    }
}

/* CTA box — optional second button (shared with landing.css so a two-button
   CTA renders correctly on blog posts, where landing.css is not loaded). */
.lvl-cta-box__actions { display: flex; flex-wrap: wrap; gap: 0.75rem 1rem; align-items: center; }
.lvl-cta-box__actions .lvl-cta-box__button { margin: 0; }
.lvl-cta-box__button--secondary { background: transparent; border: 2px solid currentColor; }
