/*
 * Brandligo blog — the components the directory does not have.
 *
 * WHAT IS DELIBERATELY NOT IN THIS FILE: colours, spacing values, radii, shadows, type scale, and
 * every rule for the header, the footer, cards, buttons, the drawer and the palette. All of that
 * comes from /css/brandligo.css, which this blog links rather than copies — one source of truth, so
 * a token changed on the directory changes the blog on the next request instead of on the next time
 * somebody remembers.
 *
 * Everything below is either an article layout the directory has no equivalent of, or an ad wrapper.
 * Both are written against the same custom properties.
 */

/* ── The blog's own sections ─────────────────────────────────────────────────────────────────── */

/*
 * A second navigation row, under the shared header.
 *
 * These links exist only on this half of brandligo.com, which is why they get a row rather than a
 * place in the bar: the bar is the directory's header reproduced class for class, and it is a
 * single non-wrapping flex row at a fixed height. Six more items in it overlapped the search button
 * and the CTA — the row clips rather than wraps, and brandligo.css says so in its own comment.
 *
 * Opaque --surface rather than the header's translucent fill: this band is not sticky, so there is
 * nothing to show through it and no reason to pay for a backdrop filter.
 */
.bl-subnav {
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}

.bl-subnav__row {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    padding-block: var(--space-2);

    /*
     * THE STRIP SCROLLS; THE PAGE NEVER DOES. .bl-nav__link is white-space: nowrap, so a dozen
     * sections are wider than a phone — and a row that cannot scroll would either clip the last
     * items with no way to reach them or widen the document and let the whole page slide sideways.
     * overflow-x: auto keeps the overflow inside this element, where it belongs.
     */
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
}

/*
 * The scrollbar is hidden rather than styled. A horizontal bar under six links looks like a defect
 * on a desktop that never needs it, and the strip is reachable by touch drag, by trackpad, and by
 * keyboard through the links themselves — which is what actually matters. Nothing is hidden BEHIND
 * it: hiding a scrollbar on a container that is the only route to something would be.
 */
.bl-subnav__row::-webkit-scrollbar { display: none; }

/* Flex would otherwise shrink each link toward its content width and stack the labels. */
.bl-subnav__row .bl-nav__link { flex: 0 0 auto; }

/* ── The Browse menu's closed state ──────────────────────────────────────────────────────────── */

/*
 * Bootstrap already says this — `.dropdown-menu { display: none }` — and it is said again here
 * because on this side it is the ONLY thing keeping the menu shut.
 *
 * The directory loads Bootstrap's JavaScript as well as its CSS; this theme deliberately loads
 * neither the bundle nor a replacement for that one rule, so the whole closed state of the header's
 * only dropdown rested on a stylesheet fetched from a CDN. When that request fails — an extension, a
 * corporate proxy, an outage, or a country that blocks the host — the menu renders permanently open,
 * pushing the logo and the search button off the top of the page. Verified: with the CDN blocked the
 * header's own toggle sits at y = -165.
 *
 * Both halves are written, and the specificity of the pair is the load-bearing part. `.bl-nav__group
 * .dropdown-menu` is 0-2-0, the same as Bootstrap's own `.dropdown-menu.show`, and this file loads
 * last — so writing only the first half would win against Bootstrap's open state and the menu would
 * never open at all. The `.show` rule below is 0-3-0 and settles it in both directions.
 */
.bl-nav__group .dropdown-menu { display: none; }
.bl-nav__group .dropdown-menu.show { display: block; }

/* ── The article layout ──────────────────────────────────────────────────────────────────────── */

/*
 * Article and sidebar. A grid rather than a float or a flex row, so the two columns are declared
 * once and the single-column case is the absence of the second track rather than a second rule.
 *
 * `minmax(0, 1fr)` and not `1fr`: a grid track's default minimum is min-content, so one wide code
 * block or one unbreakable URL in the article pushes the whole grid wider than its container and
 * the page scrolls sideways. This is the single commonest way a two-column article template breaks,
 * and it only shows up on the one post that happens to contain the offending element.
 */
.bl-article-shell {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-8);
    align-items: start;
}

@media (min-width: 992px) {
    .bl-article-shell:has(.bl-article-aside) {
        grid-template-columns: minmax(0, 1fr) 20rem;
        gap: var(--space-10);
    }
}

/*
 * `:has()` above is the load-bearing part and is why sidebar.php returns early on an empty sidebar
 * rather than rendering an empty <aside>. Without it the second track exists whether or not there is
 * anything in it, and an article with no widgets and no ads is set to two-thirds width for nothing.
 *
 * Browsers without :has() get the single-column layout, which is the correct fallback: the aside
 * follows the article instead of sitting beside it, and nothing is lost or overlapped.
 */

.bl-article-aside {
    min-width: 0;
}

.bl-article-aside__sticky {
    position: sticky;
    /* Clear of the header, which is sticky itself. */
    top: calc(var(--space-6) + 4rem);
}

@media (max-width: 991.98px) {
    /* No column to stick to below the breakpoint, and a sticky unit in a single-column flow follows
       the reader down the page over the article they are trying to read. */
    .bl-article-aside__sticky { position: static; }
}

/*
 * Full-strength body text.
 *
 * .bl-longform sets paragraphs in --text-muted, which is right where it was written — the 800-word
 * block under a product listing is secondary to the listing above it. On an article the body IS the
 * page, and muted body text at that length is a readability regression rather than a hierarchy.
 */
.bl-article__body p,
.bl-article__body li {
    color: var(--text);
}

.bl-article__body > *:first-child { margin-top: 0; }

/* WordPress's own alignment and caption classes, which core emits and no design system covers. */
.bl-article__body .alignleft { float: left; margin: 0 var(--space-5) var(--space-4) 0; }
.bl-article__body .alignright { float: right; margin: 0 0 var(--space-4) var(--space-5); }
.bl-article__body .aligncenter { display: block; margin-inline: auto; }
.bl-article__body .alignwide,
.bl-article__body .alignfull { max-width: none; width: 100%; }

.bl-article__body img,
.bl-article__body iframe,
.bl-article__body video {
    max-width: 100%;
    height: auto;
    border-radius: var(--radius-md);
}

.bl-article__body figure { margin: var(--space-6) 0; }

.bl-article__body figcaption,
.bl-article__body .wp-caption-text {
    margin-top: var(--space-2);
    font-size: var(--text-sm);
    color: var(--text-muted);
}

/* A table in an article is the one element that cannot be made to fit, so it scrolls in its own box
   rather than widening the page. Same decision the guide tables on the directory make. */
.bl-article__body table {
    display: block;
    overflow-x: auto;
    width: 100%;
    border-collapse: collapse;
}

.bl-article__body th,
.bl-article__body td {
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--border);
    text-align: left;
}

/* ── Advertising ─────────────────────────────────────────────────────────────────────────────── */

/*
 * THE RESERVED BOX IS THE WHOLE POINT OF THIS BLOCK.
 *
 * An ad unit arrives after the page has painted. Without a reserved height the article below it
 * jumps down by however tall the creative turns out to be, which is a cumulative-layout-shift
 * penalty measured on every page view, on the one element that has nothing to do with the content.
 *
 * The height is a per-slot SETTING rather than a guess in this file, delivered as --bl-ad-min on the
 * element itself, because only the person who pasted the code knows what size unit it is. Desktop
 * and mobile are separate because a 90px leaderboard and the 100px unit that replaces it on a phone
 * are different boxes.
 */
.bl-ad {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: var(--bl-ad-min, 0px);
    margin-block: var(--space-6);
    /* A flex child's default minimum is its content, and a responsive ad unit reports a very wide
       one before it has resized. Without this the unit stretches its container. */
    min-width: 0;
    overflow: hidden;
}

.bl-ad > * { max-width: 100%; }

@media (max-width: 767.98px) {
    .bl-ad { min-height: var(--bl-ad-min-mobile, var(--bl-ad-min, 0px)); }
}

/*
 * The slot above the article and the one above the footer are full-bleed bands rather than blocks
 * inside the container, so they read as chrome rather than as part of the writing.
 */
.bl-ad--header,
.bl-ad--footer {
    margin-block: 0;
    padding-block: var(--space-5);
    background: var(--surface-sunken);
    border-bottom: 1px solid var(--border);
}

.bl-ad--footer {
    border-bottom: 0;
    border-top: 1px solid var(--border);
}

/* Inside the article the unit sits in the flow of the text and is labelled as not being it. */
.bl-ad--in_content,
.bl-ad--before_content,
.bl-ad--after_content {
    position: relative;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-4) 0;
    border-block: 1px solid var(--border);
}

/*
 * "Advertisement", set in the smallest muted type the system has.
 *
 * Required by AdSense policy for a unit that could be mistaken for the page's own content, which an
 * in-article unit between two paragraphs certainly could. Drawn from the wrapper's own aria-label so
 * the visible word and the accessible name cannot drift apart.
 */
.bl-ad--in_content::before,
.bl-ad--before_content::before,
.bl-ad--after_content::before,
.bl-ad--archive_in_feed::before {
    content: attr(aria-label);
    align-self: flex-start;
    font-size: var(--text-xs);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
}

/*
 * One grid cell among the cards. align-self: stretch so it is as tall as the row rather than as tall
 * as its reserved box, which would leave it floating against the top of a row of taller cards.
 */
.bl-ad--archive_in_feed {
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-4);
    border: 1px dashed var(--border);
    border-radius: var(--radius-lg);
    margin-block: 0;
    align-self: stretch;
    min-width: 0;
}

.bl-ad--sidebar_top,
.bl-ad--sidebar_sticky { margin-block: 0; }

/*
 * A lazy slot holds its markup in a <template>, which is inert and has no box — so before blog.js
 * fills it the wrapper is an empty flex container of exactly the reserved height. That is the
 * intended state, and it is why the reserved box has to be on the wrapper rather than on the unit.
 */

/* ── Small things WordPress emits that the design system has no rule for ─────────────────────── */

.bl-article__body .wp-block-quote,
.bl-article__body blockquote { max-width: 100%; }

.screen-reader-text {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ── The byline ──────────────────────────────────────────────────────────────────────────────── */

/*
 * Date · reading time · category, on one line. The separators are decorative bullets carried in
 * spans marked aria-hidden, so a screen reader gets "12 August 2026 4 min read Strategy" rather than
 * "middle dot" three times.
 */
.bl-post__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem;
    margin: 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.bl-post__meta a { color: inherit; text-decoration: none; }
.bl-post__meta a:hover { color: var(--text); text-decoration: underline; }

/*
 * The byline sits inside .bl-card, whose whole surface is a stretched link — so a category link in
 * a card is under that overlay and unclickable. It is raised above it here rather than removed from
 * the card, because on the article page the same markup is not inside a card and does work.
 */
.bl-card .bl-post__meta a { position: relative; z-index: 2; }

/* ── Widgets ─────────────────────────────────────────────────────────────────────────────────── */

/*
 * .bl-card carries no padding of its own — the directory always pads through .bl-card__body, which
 * a widget cannot emit because register_sidebar()'s before_widget is a single opening tag. So the
 * padding is stated here rather than by inventing a modifier the design system does not have.
 */
.bl-widget-card {
    padding: var(--space-5);
}

/*
 * BLOCK WIDGETS DO NOT USE before_title, AND THAT IS THE WHOLE REASON THIS BLOCK EXISTS.
 *
 * Since 5.8 the widget screen is the block editor, so "Recent Posts" emits its own
 * <h2 class="wp-block-heading"> inside the block rather than passing a title through the arguments
 * register_sidebar() was given. The theme's before_title is still used — a classic widget, or a
 * plugin registering one, goes through it — but the default widgets never touch it, so a theme that
 * styles only before_title renders the shipped sidebar at full h2 display size. Which it did:
 * "Recent Posts" set larger than the article's own headings.
 *
 * Both paths are covered, and they are covered to the same size on purpose.
 */
.bl-widget-card > h2,
.bl-widget-card .wp-block-heading {
    margin: 0 0 var(--space-3);
    font-size: var(--text-md);
    font-weight: var(--weight-semibold, 600);
    color: var(--text);
}

.bl-widget-card ul,
.bl-widget-card ol {
    margin: 0;
    padding: 0;
    list-style: none;
}

.bl-widget-card li {
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--border);
    font-size: var(--text-sm);
    line-height: 1.5;
}

.bl-widget-card li:last-child { border-bottom: 0; padding-bottom: 0; }
.bl-widget-card li:first-child { padding-top: 0; }

.bl-widget-card a { color: var(--text); text-decoration: none; }
.bl-widget-card a:hover { text-decoration: underline; }

/*
 * The core search block, which emits its own input and button and is themed by neither
 * brandligo.css (it has never seen this markup) nor bootstrap-bridge.css (these are not Bootstrap's
 * class names). Left alone it renders a browser-default input and a filled grey button in the middle
 * of a monochrome design system.
 */
.bl-widget-card .wp-block-search__label {
    display: block;
    margin-bottom: var(--space-2);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium, 500);
}

.bl-widget-card .wp-block-search__inside-wrapper {
    display: flex;
    gap: var(--space-2);
}

.bl-widget-card .wp-block-search__input {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.5rem var(--space-3);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-size: var(--text-sm);
}

.bl-widget-card .wp-block-search__input:focus-visible {
    outline: 2px solid var(--text);
    outline-offset: 1px;
}

.bl-widget-card .wp-block-search__button {
    flex: 0 0 auto;
    padding: 0.5rem var(--space-4);
    border: 1px solid var(--text);
    border-radius: var(--radius-md);
    background: var(--text);
    color: var(--surface);
    font: inherit;
    font-size: var(--text-sm);
    font-weight: var(--weight-medium, 500);
    cursor: pointer;
}

.bl-widget-card .wp-block-search__button:hover { opacity: 0.88; }

/* ── Comments ────────────────────────────────────────────────────────────────────────────────── */

/*
 * disable-comments is active on this install, so none of this renders today. It is written anyway
 * because the failure mode otherwise is delayed and confusing: somebody deactivates that plugin
 * months from now and the comment form appears as browser-default inputs in the middle of a
 * monochrome design system, on a page nobody was looking at when the change was made.
 *
 * comment_form() emits the author, email and url fields itself; only the textarea and the submit
 * button take classes from the arguments comments.php passes, which is why these are selected on
 * WordPress's own markup rather than on bl-* classes.
 */
.comment-form { display: flex; flex-direction: column; gap: var(--space-4); }

.comment-form p { margin: 0; }

.comment-form label {
    display: block;
    margin-bottom: var(--space-2);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium, 500);
}

.comment-form input[type="text"],
.comment-form input[type="email"],
.comment-form input[type="url"],
.comment-form textarea {
    width: 100%;
    padding: 0.5rem var(--space-3);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-size: var(--text-sm);
}

.comment-form input:focus-visible,
.comment-form textarea:focus-visible {
    outline: 2px solid var(--text);
    outline-offset: 1px;
}

.comment-form .comment-form-cookies-consent {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.comment-form .comment-form-cookies-consent label { margin: 0; font-weight: inherit; }
.comment-form .form-submit { margin: 0; }

/* The comment list: one card per comment, in the design system's own surface. */
.comment-body {
    padding: var(--space-4);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: var(--surface-raised);
}

.comment-body .comment-meta { font-size: var(--text-sm); color: var(--text-muted); }
.comment-body .avatar { border-radius: var(--radius-full); vertical-align: middle; }
.comment-list .children { list-style: none; margin: var(--space-4) 0 0; padding-left: var(--space-6); }
