/* --- UI scale (accessibility size control) --------------------------------
   The header's "A A A" group sets --ui-scale on <html> (js/ui-scale.js, before
   first paint). `zoom` on the root scales the entire rendered page — text,
   images, borders, spacing — like browser zoom. This has to be `zoom` rather than
   a root font-size: the site is written in px/pt, so almost nothing would follow
   a font-size change.
   Note the media queries below still match against the *real* viewport width, not
   the scaled one, so the breakpoints do not shift as the reader scales up.
   Consequence: viewport units (vh/vw) are NOT scaled by zoom, so the handful of
   rules using them divide by --ui-scale to stay screen-sized (see #root,
   .toast-container, .modal). */
:root {
    --ui-scale: 1;
    zoom: var(--ui-scale);
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Noto Sans Lao Looped', sans-serif;
    display: flex;
    flex-direction: row;
    height: 100%;
    margin: 0;
}

#root {
    display: flex;
    flex-direction: column;
    width: 100%;
    /* Cap the overall content width and centre it so the site doesn't sprawl on
       wide monitors. Adjust this one value to make the whole site wider/narrower. */
    max-width: 1000px;
    margin: 0 auto;
    justify-content: space-between;
    position: relative;
    min-height: calc(100vh / var(--ui-scale));
}

/* The destaques column needs room *beside* the feed, so the cap above is lifted
   — but only on pages that actually have one. The aside is emitted only when
   something is marked as a destaque (see layout/base.html), so :has() is enough
   and the renderer needs no body class. The extra 186px is the column plus the
   row gap, which leaves the article cards exactly the width they have today.
   Browsers without :has() keep the 1000px cap: the feed narrows by the column's
   width, which is a tighter layout, not a broken one. */
#root:has(#destaques) {
    max-width: 1186px;
}

.blank-size-bar {
    width: 0px;
}

header {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    font-family: 'Oleo Script Swash Caps', sans-serif;
    margin-top: 5px;
}

#title {
    font-size: 40pt;
    /* Rendered as an <a> (links to the unfiltered home feed) but must look like
       plain header text, not a default blue underlined link. */
    color: inherit;
    text-decoration: none;
    cursor: pointer;
}

#header-top {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    width: 100%;
}

#social-media {
    display: flex;
    flex-direction: row;
    align-items: center;
    /* Gap between icons so each keeps ≥24px of clear space (WCAG 2.2 target-size). */
    gap: 4px;
    padding: 0px;
}

#social-media a {
    font-size: 16pt;
    /* ≥24×24 tap target (was 22×25). Centre the glyph in the padded box. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    min-width: 28px;
    min-height: 28px;
}

/* Size control: three "A"s of growing size, flat like the rest of the chrome
   (thin black rules, no filled "UI" buttons). The pressed one is outlined. */
#ui-scale {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 4px;
    padding: 0;
    /* #header-top is space-between, which would strand this middle child in the
       empty centre. Push it against the search box instead — the two are the
       reader's controls, the social icons on the left are links out. */
    margin-left: auto;
    margin-right: 10px;
}

.ui-scale-btn {
    /* ≥24×24 tap target (WCAG 2.2), same as the social icons. */
    min-width: 26px;
    min-height: 26px;
    padding: 0 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Not `inherit`: <header> is set in the Oleo Script display face, which would
       render these as decorative script "A"s instead of a legible size preview. */
    font-family: 'Noto Sans Lao Looped', sans-serif;
    line-height: 1;
    color: inherit;
    background: transparent;
    /* Transparent (not absent) so the box doesn't shift when it gains a border. */
    border: 1px solid transparent;
    border-radius: 3px;
    cursor: pointer;
    transition:
        border-color 150ms ease,
        background-color 150ms ease;
}

/* The glyph itself previews the size it selects — that is the whole affordance. */
.ui-scale-btn[data-scale='1'] {
    font-size: 11px;
}

.ui-scale-btn[data-scale='1.15'] {
    font-size: 14px;
}

.ui-scale-btn[data-scale='1.3'] {
    font-size: 17px;
}

.ui-scale-btn:hover,
.ui-scale-btn:focus-visible {
    border-color: black;
    outline: none;
}

.ui-scale-btn[aria-pressed='true'] {
    border-color: black;
    background-color: rgba(0, 0, 0, 0.06);
    font-weight: 600;
}

#search-box {
    height: 25px;
    /* Shrink-to-fit rather than a fixed 30%: its contents are fixed-width and
       right-aligned, so a wider box was just empty space on its left — which the
       size control (pushed right by its auto margin) would butt against instead
       of sitting next to the field. The mobile breakpoints size it by
       flex-basis, so they are unaffected. */
    width: auto;
    vertical-align: bottom;
    padding: 0px;
    display: flex;
    flex-direction: row;
    justify-content: end;
}

#search-box-input {
    height: 25px;
    width: 150px;
    font-size: 13px;
}

#search-box-btn {
    height: 25px;
    width: 25px;
    padding: 0px;
    margin: 0px;
    vertical-align: middle;
}

/* MAIN */

/* Header + nav + main wrapper. Grows to fill the viewport so a short listing
   (e.g. the last page with only a couple of cards) still pushes the footer —
   and the pagination bar (see #pagination) — to the bottom. */
.page-content {
    display: flex;
    flex-direction: column;
    flex: 1;
}

/* The body and the destaques column, side by side. The header and nav sit above
   it inside .page-content, so they keep the full width and are not narrowed by
   the column. */
.content-row {
    display: flex;
    flex-direction: row;
    /* Fill the leftover height inside .page-content, so main's own flex:1 still
       reaches the bottom and #pagination's auto top margin can sink to it. */
    flex: 1;
    gap: 16px;
    min-width: 0;
}

main {
    padding-left: 4px;
    padding-right: 4px;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* Fill the leftover height inside .content-row so #pagination's auto top
       margin can push it to the bottom on short pages. */
    flex: 1;
    /* A flex item's floor is its content, and the cards carry a fixed 200px
       thumbnail — without this the feed would refuse to shrink and push the
       destaques column off the row on narrow desktops. */
    min-width: 0;
}

/* DESTAQUES (the highlights column) */

#destaques {
    flex: none;
    /* Sets the tile size: the card is a full-width square inside this column, so
       this one value is what makes a destaque bigger or smaller. Keep it in step
       with the #root cap above. */
    width: 170px;
    /* Hug the content: the column must not stretch to the feed's height. */
    align-self: flex-start;
    margin-top: 4px;
    padding: 0 4px;
    /* Deliberately NOT sticky. Five square cards still run past a short
       viewport, and a sticky box taller than the viewport pins its top and
       hides its own bottom — worse than just scrolling with the page. */
}

#destaques h2 {
    /* The body face, not the brand script: the script is reserved for the site
       title, and a section marker reads better set as a label than as a
       signature competing with it. */
    font-family: 'Noto Sans Lao Looped', sans-serif;
    font-size: 0.85em;
    font-weight: 700;
    text-transform: uppercase;
    /* Tracking is what makes a short word read as a label, but it also has to
       fit the 170px column — hence looser type at a smaller size. */
    letter-spacing: 0.1em;
    margin: 3px 0 8px;
    padding-bottom: 6px;
    /* border-bottom: 1px solid #333; */
    text-align: center;
    /* TEST DESTAQUES BLACK */
    padding-top: 6px;
    color: white;
    background-color: #ff7e26;
}

#destaques ul {
    list-style: none;
    margin: 0;
    padding: 0;
}

#destaques li + li {
    margin-top: 8px;
}

/* A square tile: the cover fills the whole card and the title sits over its
   foot. Image and title only, deliberately — the column is too narrow for the
   excerpt a listing card carries, and the uniform square crop is what makes
   five of them read as one set rather than as a list of odds and ends. */
.destaque {
    position: relative;
    display: block;
    /* The card *is* the image, so a single ratio governs both. */
    aspect-ratio: 1 / 1;
    overflow: hidden;
    color: #fff;
    text-decoration: none;
    border: 1px solid black;
    box-shadow: 3px 3px 3px #ccc;
    /* Shows through only while the photo is still loading. */
    background: #333;
    transition: box-shadow 200ms ease-in-out;
}

.destaque:hover {
    box-shadow: 3px 3px 10px #999;
}

.destaque img,
.destaque-img-placeholder {
    display: block;
    /* Fill the card so the title can float over it. */
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
}

/* No cover image — only reachable for legacy or unpublished data, since
   publishing requires one. Dark, so the white title stays readable on it. */
.destaque-img-placeholder {
    background: #4a4a4a;
}

.destaque-text {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    flex-direction: column;
    padding: 18px 6px 6px;
    /* This gradient carries the contrast: the title is white over whatever
       photo the article happens to have, so legibility cannot depend on the
       image being dark. The tall transparent tail keeps the edge from reading
       as a band across the picture. */
    background: linear-gradient(to top, rgba(0, 0, 0, 0.88), rgba(0, 0, 0, 0.65) 55%, rgba(0, 0, 0, 0));
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}

.destaque-type {
    font-size: 0.6em;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #e0e0e0;
}

.destaque-title {
    /* Sized so three lines of a long title still fit inside a 160px tile. */
    font-size: 0.88em;
    line-height: 1.22em;
    font-weight: 600;
    margin-top: 2px;
    /* Clip at 3 lines so a long title can't climb over the whole photo. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Narrow (≤767px): the feed is already one card per row here, so there is no
   room for anything beside it — the column drops below the feed and spreads into
   a two-up grid so five destaques don't turn into five screens of scrolling. */
@media only screen and (max-width: 767px) {
    .content-row {
        flex-direction: column;
    }

    #destaques {
        width: 100%;
        align-self: stretch;
        margin-top: 20px;
    }

    #destaques ul {
        display: grid;
        /* auto-fill rather than a fixed column count: it keeps every tile close
           to the ~160px it has in the desktop column, letting the number of
           columns follow the width instead of stretching two tiles across a
           tablet. */
        grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
        gap: 8px;
    }

    #destaques li + li {
        margin-top: 0;
    }
}

/* ARTICLES */

#articles {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    margin-top: 4px;
    /* Guaranteed gap before the pagination bar. Its own margin-top is `auto`
       (to sink to the bottom on short pages), which collapses to 0 on full
       pages — so this keeps a consistent gap in both cases. */
    margin-bottom: 24px;
    gap: 8px;
    /* main centers its children (align-items: center), so this grid would
       otherwise shrink to its content width — collapsing the cards when they
       hold little text. Force it to span main's full width so the 50% cards
       keep their columns regardless of how short the text is. */
    width: 100%;
}

#articles article {
    position: relative;
    display: flex;
    flex-basis: -webkit-calc(50% - 4px);
    flex-basis: -moz-calc(50% - 4px);
    flex-basis: calc(50% - 4px);
    line-height: 1.2em;
    border: 1px solid black;
    margin-top: 4px;
    padding-left: 3px;
    padding-right: 3px;
    /* Fixed height (≈ the 200px image) so the preview clips instead of growing
       the card. */
    height: 210px;
    overflow: hidden;
    box-shadow: 3px 3px 3px #ccc;
    cursor: pointer;
    transition: box-shadow 200ms ease-in-out;
}

/* Whole listing card is a link. display:contents keeps the <article> as the
   flex item so the two-column grid is unaffected. */
.article-card {
    display: contents;
    color: inherit;
    text-decoration: none;
}

#articles .article-card:hover article {
    box-shadow: 3px 3px 10px #999;
}

.article-body {
    display: flex;
    flex-direction: row;
    /* Fill the card so the text column can stretch to the image's height. */
    flex: 1;
    min-width: 0;
    overflow: hidden;
}

#articles article img {
    width: 200px;
    height: 200px;
    object-fit: cover;
    /* padding: 4px; */
    margin-top: 4px;
    flex: none;
}

.article-text {
    margin: 0;
    margin-left: 4px;
    margin-bottom: 4px;
    /* Column layout so the preview paragraph can flex to fill remaining height. */
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
    overflow: hidden;
}

.article-text h2 {
    font-size: 1.1em;
    flex: none;
}

.article-date {
    display: block;
    flex: none;
    margin-top: 2px;
    font-size: 0.7em;
    /* AA-contrast gray on white (was #888, which fails WCAG AA). */
    color: #6b6b6b;
}

/* Keeps the card layout uniform when an article has no image (avoids an empty
   <img src>). */
.article-img-placeholder {
    width: 200px;
    height: 200px;
    margin-top: 4px;
    flex: none;
    background: #ececec;
}

.article-text p {
    margin-top: 6px;
    margin-bottom: 0;
    font-size: 0.85em;
    /* Airier line spacing so the preview is easier on the eye. */
    line-height: 1.5em;
    /* Grow the preview to the bottom of the card (dynamic, not a fixed line
       count) and clip the overflow. */
    flex: 1;
    min-height: 0;
    overflow: hidden;
    /* Fade only where long text actually reaches the bottom edge; short
       previews sit above the fade region and stay fully opaque. */
    -webkit-mask-image: linear-gradient(to bottom, #000 82%, transparent 100%);
    mask-image: linear-gradient(to bottom, #000 82%, transparent 100%);
}

/* The type bar. Its colours are a *section's* choice, set in the backoffice and
   emitted by the renderer as two custom properties in an inline style on this
   element (renderer.js -> typeBarStyle). The fallbacks below are the site
   default, so a section that has picked nothing carries no style attribute at
   all and looks exactly as it always did — and the responsive rule further down
   still only has to flip the geometry, never repeat a colour. */
.article-type {
    background-color: var(--type-bar-bg, black);
    color: var(--type-bar-fg, white);
    /* No vertical margin so the bar fills the full card height; keep the
       horizontal gap to the image. */
    margin: 0 4px;
    writing-mode: vertical-lr;
    transform: rotate(180deg);
    white-space: nowrap;
    display: flex;
    justify-content: center;
    align-items: center;
    min-width: 45px;
    /* Fill the card's height. align-self:stretch is unreliable here because the
       vertical (orthogonal) writing-mode makes browsers size the bar to its
       vertical text content instead of stretching, leaving white gaps top and
       bottom. An explicit height:100% resolves against the <article>'s definite
       210px height and forces a full-height bar regardless of label length. */
    align-self: stretch;
    height: 100%;
}

.article-type span {
    /* Inherit, never `white`: the label's colour is the bar's, and a literal
       here would beat the inline custom property set on the parent. */
    color: inherit;
    font-size: 1.3em;
    text-align: center;
    font-weight: 600;
}

/* RESPONSIVE LISTING CARDS
   Wide (default): two cards per row, horizontal — a lateral (vertical-text)
   type bar | photo | text.
   Narrow (≤767px): one vertical card per row. The type label moves to a
   horizontal bar on top (so its text is horizontal, not vertical), followed by
   title, date, photo, then the text preview. */
@media only screen and (max-width: 767px) {
    /* One card per line; column flow with no fixed height so the photo and text
       stack freely instead of being clipped to a horizontal card's height. */
    #articles article {
        flex-basis: 100%;
        flex-direction: column;
        height: auto;
    }

    .article-body {
        flex-direction: column;
    }

    /* Flatten .article-text so its title/date/text become flex siblings of the
       photo inside .article-body — letting `order` slot the photo between the
       date and the text preview. */
    .article-text {
        display: contents;
    }

    .article-text h2 {
        order: 1;
        margin-left: 4px;
    }

    .article-date {
        order: 2;
        margin-left: 4px;
    }

    /* Full-width cover, still forced square (crops the wider axis) like the
       desktop 200x200 thumbnail. The selector must include `#articles article`
       to beat the base `#articles article img { width: 200px }` rule (an ID
       selector outweighs a plain class, and media queries add no specificity). */
    #articles article img,
    .article-img-placeholder {
        order: 3;
        width: 100%;
        aspect-ratio: 1 / 1;
        height: auto;
        object-fit: cover;
        object-position: center;
        margin: 8px 0 0;
    }

    .article-text p {
        order: 4;
        /* Fixed clip height instead of flex-fill (there's no fixed card height
           to fill now). It has to be `height`, not `max-height`: an *empty*
           fade region under a short excerpt is what keeps short previews fully
           opaque — the desktop card gets that for free from `flex: 1` stretching
           the paragraph to the card's foot. 4.5 lines of 1.5em, so the cut
           always lands mid-line and the mask has half a line to dissolve. */
        flex: none;
        height: 6.75em;
        margin: 8px 4px 6px;
        /* The shared mask fades over a *percentage* of the box, and this box is
           barely half the desktop one — 18% of it is ~0.8 of a line, too little
           to read as a fade, so the preview looked hard-cut here. Fade over a
           fixed 1.9em (~1.25 lines) instead, matching the desktop feel. */
        -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 1.9em), transparent 100%);
        mask-image: linear-gradient(to bottom, #000 calc(100% - 1.9em), transparent 100%);
    }

    /* On top → horizontal bar with HORIZONTAL text: reset the wide layout's
       vertical writing-mode + rotate. (The vertical-text bar is only correct
       when it's lateral, i.e. the wide two-column layout above.) */
    .article-type {
        margin: 4px 0 0;
        min-width: 0;
        width: 100%;
        height: 30px;
        writing-mode: horizontal-tb;
        transform: none;
        justify-content: center;
    }

    .article-type span {
        font-size: 1em;
        width: 100%;
        text-align: center;
    }
}

.pagination {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    gap: 6px;
    /* Top margin `auto`: in main's flex column this sinks the bar to the bottom
       when the page is short (few cards), so it sits just above the footer
       instead of hugging the last row. On a full page the auto margin is 0 and
       #articles' margin-bottom supplies the gap. */
    margin: auto 0 8px;
}

.pagination.is-loading {
    opacity: 0.6;
    pointer-events: none;
}

/* The items are <a> links (a disabled control is a <span>), so the box has to be
   laid out explicitly — an inline anchor would ignore min-width and not centre
   its label the way a <button> did. */
.pagination .page-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    min-width: 40px;
    padding: 10px 12px;
    background-color: white;
    border: 1px solid #ddd;
    border-radius: 3px;
    cursor: pointer;
    transition: all 200ms ease-in-out;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.1rem;
    color: inherit;
    text-decoration: none;
}

.pagination .page-btn.page-control {
    letter-spacing: 0.05rem;
}

.pagination .page-btn:not(.is-disabled):hover {
    box-shadow: 0 1px 9px rgba(0, 0, 0, 0.2);
}

.pagination .page-btn.page-active {
    background-color: #1a1a1a;
    color: white;
    border-color: #1a1a1a;
    cursor: default;
}

.pagination .page-btn.is-disabled {
    background-color: #eaeaea;
    color: #999;
    cursor: default;
}

.pagination .page-ellipsis {
    padding: 0 4px;
    color: #999;
    user-select: none;
}

/* ARTICLE PAGE (full view) */

.article-page {
    display: flex;
    flex-direction: row;
    margin-top: 8px;
    /* border: 1px solid black; */
    /* box-shadow: 3px 3px 3px #ccc; */
    /* Warm off-white page for the article itself, so the reading surface reads
       as paper against the plain white chrome around it. */
    background-color: #fdf8f1;
    padding: 0 8px 8px;
    /* main is a centered flex column, so children shrink to content width by
       default; force full width so short articles aren't narrower. */
    width: 100%;
    box-sizing: border-box;
}

/* Let the type bar stretch to the full article height here (unlike the fixed
   200px preview card), but pin the label to the top so it stays visible on long
   articles instead of floating to the vertical centre. The bar uses
   writing-mode: vertical-lr + rotate(180deg), so flex-end (the pre-transform
   bottom) resolves to the visual top; the symmetric padding gives a small gap
   from the edge whichever way the rotation flips it. */
.article-page .article-type {
    height: auto;
    justify-content: flex-end;
    padding: 14px 0;
}

/* The text column. Indented from the black type bar so the title, date, lead
   and body all start on the same left edge, with the floated cover on the
   right. `overflow: hidden` is deliberately NOT used here — it would make this
   a block formatting context and stop the text wrapping beside the float; the
   ::after clearfix ends the float instead. */
.article-page-content {
    flex: 1;
    min-width: 0;
    margin-left: 8px;
    padding: 12px 8px 0 28px;
}

/* Static pages (e.g. sobre.html) reuse this shell with the title as a direct
   child — no .article-page-header wrapper to supply the top gap. */
.article-page-content > .article-page-title {
    margin-top: 8px;
}

.article-page-content::after {
    content: '';
    display: block;
    clear: both;
}

/* No extra top margin: the header starts at the same offset as the floated
   cover (both sit against .article-page-content's own padding-top), so the
   title's top edge lines up with the top of the image. */
.article-page-header {
    margin-top: 0;
}

.article-page-title {
    margin: 0;
    font-size: 1.9em;
    font-weight: 700;
    line-height: 1.15;
}

.article-page-date {
    display: block;
    margin-top: 8px;
    font-size: 0.8em;
    color: #6b6b6b;
}

/* Optional lead / teaser paragraph, shown under the date. Set off as a small
   "deck" with a black rule (echoing the type bar / share-bar language used
   elsewhere on the page) instead of italics — italic reads fine for a phrase
   but turns ropey and hard to scan once it's a full paragraph. */
.article-page-lead {
    margin: 18px 0 4px;
    padding-left: 16px;
    border-left: 3px solid black;
    font-size: 1.2em;
    font-weight: 400;
    line-height: 1.55em;
    color: #333;
    font-style: normal;
}

/* Cover photo: 45% wide, floated right so the whole text column runs beside it
   and reflows to full width underneath on longer articles. */
.article-page-hero {
    float: right;
    width: 45%;
    margin: 0 0 16px 28px;
}

.article-page-hero img {
    display: block;
    width: 100%;
    max-width: 100%;
    /* Force a square crop regardless of the source photo's own aspect ratio —
       object-fit centres it and trims the wider axis (almost always the
       laterals, since sleeve scans tend to run wider than tall). */
    aspect-ratio: 1 / 1;
    height: auto;
    object-fit: cover;
    object-position: center;
    border-radius: 4px;
}

.article-page-body {
    margin-top: 40px;
    line-height: 1.6em;
}

/* Narrow screens: stack the type bar, then the title, then a full-width cover
   (no float — there isn't room for a text column beside it), then the body. */
@media only screen and (max-width: 593px) {
    .article-page {
        flex-direction: column;
    }

    /* Source order puts the cover first (it has to, for the float); on the
       stacked layout the title should lead instead, so switch the column to
       flex and re-order the blocks. */
    .article-page-content {
        display: flex;
        flex-direction: column;
        margin-left: 0;
        padding: 0 4px;
    }

    .article-page-header {
        margin-top: 12px;
    }

    .article-page-hero {
        float: none;
        width: 100%;
        order: 2;
        margin: 12px 0 0;
    }

    .article-page-header {
        order: 1;
    }

    .article-page-body {
        order: 3;
    }

    .article-share {
        order: 4;
    }
}

/* Phones (≤480px): slightly larger body text for easier reading. */
@media only screen and (max-width: 480px) {
    .article-page-body {
        font-size: 1.08em;
    }
}

.article-page-body p {
    margin: 0.8em 0;
}

/* One deliberate empty line. draftToHtml emits one of these per *extra* blank
   paragraph in the editor (the first is the normal paragraph gap above), so
   pressing Enter three times between two paragraphs opens two real lines of
   air. It needs an explicit height because the <p> is empty and would otherwise
   collapse to nothing; margin: 0 keeps the arithmetic simple — the gap is
   exactly one line per press, on top of the paragraph margins that collapse
   around it. */
.article-page-body p.blank-line {
    height: 1.6em;
    margin: 0;
}

/* `clear: right` keeps body media out from under the floated cover: these are
   full-width blocks whose box ignores the float, so without it a body image or
   video near the top of a long article would be painted behind the photo. */
.article-image {
    clear: right;
    margin: 1em 0;
}

.article-image img {
    display: block;
    max-width: 100%;
    height: auto;
}

/* Per-image width and alignment, chosen in the backoffice editor and stored on
   the atomic block (see CLAUDE.md). The width arrives as an inline
   `style="width:N%"` on the figure — a free percentage can't be a class — so
   these rules only place the box; `is-resized` is what makes the img fill the
   narrowed figure, which it would not do on its own: a file narrower than the
   figure would keep rendering at its intrinsic size. An image with neither
   choice stored gets none of these classes and is untouched. */
.article-image.is-resized img {
    width: 100%;
}

.article-image.align-center {
    margin-left: auto;
    margin-right: auto;
}

.article-image.align-right {
    margin-left: auto;
    margin-right: 0;
}

.article-image.align-left {
    margin-left: 0;
    margin-right: auto;
}

/* The same placement on the img itself, for an image whose intrinsic width is
   narrower than the figure holding it (where the figure's own margins have
   nothing to push against). Left needs no rule — it is the default. */
.article-image.align-center img {
    margin-left: auto;
    margin-right: auto;
}

.article-image.align-right img {
    margin-left: auto;
    margin-right: 0;
}

/* Narrow screens: a body image drops whatever width it was given — a picture at
   30% of a 360px column is unreadable. `width` needs `!important` because the
   chosen width is an inline style on the figure and nothing else can beat it
   (the same mechanism, for the same reason, as --ui-scale being forced to 1 on
   this breakpoint); the img's own width does not, so it goes back to `auto` and
   is governed by max-width alone — otherwise a file narrower than the phone
   would be upscaled and blurred, which is not what it does today. This block
   must stay *after* the rules above: it shares their selectors, so source order
   is what decides. The margins are deliberately left alone — a full-width box
   has no free space for `auto` to distribute. */
@media only screen and (max-width: 593px) {
    .article-image {
        width: 100% !important;
    }

    .article-image.is-resized img {
        width: auto;
    }
}

/* Embedded YouTube video: responsive 16:9 wrapper around the iframe. */
.article-video {
    clear: right;
    margin: 1em 0;
}

.video-embed {
    position: relative;
    width: 100%;
    padding-top: 56.25%; /* 16:9 */
}

.video-embed iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

/* Share bar at the bottom of an article. Hrefs are wired up by js/share.js. */
/* `clear: both` keeps the bar below the floated cover, so it always spans the
   full text column instead of being squeezed beside the photo. */
.article-share {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 14px;
    clear: both;
    margin-top: 2em;
    padding-top: 0.5em;
}

.article-share-label {
    font-size: 0.9em;
    font-weight: 600;
}

/* Selectors are scoped to `.article-share` so they beat the generic
   `.fa { display: inline-block }` rule further down this file. */
.article-share .article-share-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border: 0;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.06);
    color: black;
    font-size: 15pt;
    line-height: 1;
    cursor: pointer;
    text-decoration: none;
    transition:
        background-color 0.15s ease,
        opacity 0.15s ease;
}

.article-share .article-share-btn::before {
    /* The glyph is a ::before pseudo-element; keep it from adding its own box
       metrics so flex centering lands it dead-centre. */
    line-height: 1;
}

.article-share .article-share-btn:hover {
    opacity: 1;
    background: rgba(0, 0, 0, 0.14);
}

/* "Leia também" / "Veja também": internal links to other articles, right
   after the share bar. `clear: both` for the same reason the share bar needs
   it — the cover floats, so this block must always start below it rather
   than being squeezed beside it. */
.article-related {
    clear: both;
    margin-top: 2em;
    padding-top: 1em;
    border-top: 1px solid rgba(0, 0, 0, 0.1);
}

.article-related h2 {
    /* Same label treatment as the Destaques heading, for the same reason: a
       section marker reads as a label, not a signature. */
    font-family: 'Noto Sans Lao Looped', sans-serif;
    font-size: 0.85em;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    margin: 0 0 12px;
}

.article-related ul {
    display: flex;
    flex-wrap: wrap;
    gap: 22px;
    list-style: none;
    margin: 0;
    padding: 0;
}

/* Same percentage-basis technique as .etiqueta-card and #articles article:
   (100% - 3 gaps) / 4 columns is what makes the column count exact rather
   than "however many happen to fit" with an auto-fill min-width. Still 4
   columns, just a wider gap (22px) eating into each column — smaller
   thumbnails with more air between them, nothing else changes. */
.article-related li {
    flex-basis: calc(25% - 16.5px);
}

.related-card {
    display: block;
    color: inherit;
    text-decoration: none;
}

.related-card:hover .related-title {
    text-decoration: underline;
}

.related-img,
.related-img-placeholder {
    display: block;
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    object-position: center;
    border: 1px solid black;
    /* Shows through only while the photo is still loading, or in the
       placeholder's case (a related article with no cover). */
    background: #333;
}

.related-text {
    display: block;
    margin-top: 6px;
}

.related-type {
    display: block;
    font-size: 0.7em;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #666;
}

.related-title {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    font-size: 0.9em;
    line-height: 1.25em;
    font-weight: 600;
}

/* Narrow (≤767px): same breakpoint the article card already uses for its own
   mobile-vs-desktop column count, with the same wider 22px gap eating into
   each of the two columns. */
@media only screen and (max-width: 767px) {
    .article-related li {
        flex-basis: calc(50% - 11px);
    }
}

.back-link {
    display: inline-block;
    margin: 12px 0;
    color: inherit;
    text-decoration: none;
    font-size: 0.9em;
}

.back-link:hover {
    text-decoration: underline;
}

/* SEARCH RESULTS PAGE (pesquisa.html) */

.search-results {
    width: 100%;
    box-sizing: border-box;
}

.search-results-head {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 16px;
    margin: 12px 0 4px;
}

.search-results-title {
    font-size: 1.4em;
    margin: 0;
}

.search-modes {
    display: inline-flex;
    border: 1px solid #999;
    border-radius: 4px;
    overflow: hidden;
}

.search-mode-btn {
    padding: 4px 12px;
    border: 0;
    background: #f2f2f2;
    color: #333;
    font-size: 0.85em;
    cursor: pointer;
}

.search-mode-btn + .search-mode-btn {
    border-left: 1px solid #999;
}

.search-mode-btn[aria-pressed='true'] {
    background: #333;
    color: #fff;
}

.search-status {
    width: 100%;
    margin: 4px 0 0;
    font-size: 0.9em;
    color: #6b6b6b;
}

/* FOOTER */

footer {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    width: 100%;
    min-height: 5em;
    margin-top: 30px;
    margin-bottom: 3px;
    background-color: black;
}

#copyright {
    display: flex;
    flex-direction: column;
}

#copyright span {
    font-size: 9pt;
    margin-left: 18px;
    line-height: 1.2;
    text-align: left;
    color: white;
    font-style: italic;
}

.footer-btn {
    font-size: 10pt;
    margin-top: 4px;
    color: white;
    transition: color 0.3s;
    flex-basis: 33%;
    text-align: center;
}

#footer {
    margin: 0;
    display: flex;
    flex-direction: row;
    color: white;
    align-items: center;
}

#footer-items {
    display: flex;
    flex-direction: row;
    line-height: 1.2;
}

.footer-btn:hover {
    color: #aeffec;
    cursor: pointer;
}

/* The e-mail field and the Subscrever button are one control pair: same height,
   centred on the same line, with a small gap. (They used to be nudged apart by
   ±3px margins, which left them visibly out of line.) */
#subscription-box {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 6px;
    /* Mirrors the 18px inset of the copyright block on the other side. */
    margin-right: 18px;
}

#subscription-box input,
#subscription-box button[type='submit'] {
    /* ≥24px tall so each is a valid tap target (WCAG 2.2 target-size). */
    height: 28px;
    margin: 0;
    padding: 2px 8px;
    border: 1px solid #ccc;
    font-size: 13px;
    line-height: 1;
}

#subscription-box input {
    width: 150px;
    text-align: center;
    color: black;
}

#subscription-box button[type='submit'] {
    width: 110px;
    background-color: #e5fbf6;
    display: flex;
    justify-content: center;
    align-items: center;
}

.subscribe button[type='submit'] {
    width: 80px;
    height: 20px;
}

/* The form is a transparent wrapper so #subscription-box lays out exactly as it
   did before the form was introduced (feedback is shown via toasts, not inline). */
#subscribe-form {
    display: contents;
}

/* --- Toasts (generic, reusable) ------------------------------------------ */

.toast-container {
    position: fixed;
    bottom: 20px;
    right: 20px;
    z-index: 1100;
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-width: min(360px, calc(100vw / var(--ui-scale) - 40px));
    pointer-events: none;
}

.toast {
    pointer-events: auto;
    padding: 12px 16px;
    border-radius: 6px;
    color: #1f2a28;
    font-size: 11pt;
    line-height: 1.4;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.15);
    cursor: pointer;
    opacity: 0;
    transform: translateY(12px);
    transition:
        opacity 0.25s ease,
        transform 0.25s ease;
    background: #f2f4f5;
    border: 1px solid rgba(0, 0, 0, 0.06);
    border-left: 4px solid #b9c2c0;
}

.toast.is-visible {
    opacity: 1;
    transform: translateY(0);
}

.toast.is-leaving {
    opacity: 0;
    transform: translateY(12px);
}

.toast-success {
    background: #e5fbf6;
    border-left-color: #3ec9a7;
    color: #0a5f4f;
}

.toast-error {
    background: #fdecec;
    border-left-color: #e88b8b;
    color: #a03434;
}

.toast-info {
    background: #eaf3fb;
    border-left-color: #86b8e0;
    color: #2c5578;
}

/* --- Footer modals ------------------------------------------------------- */

.modal-overlay {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    background: rgba(0, 0, 0, 0.6);
    animation: fadeInUp 0.25s ease-in-out;
}

.modal-overlay[hidden] {
    display: none;
}

body.modal-open {
    overflow: hidden;
}

.modal {
    position: relative;
    background: #ffffff;
    color: #1a1a1a;
    width: 100%;
    max-width: 560px;
    max-height: calc(85vh / var(--ui-scale));
    overflow-y: auto;
    padding: 28px 26px 24px;
    border-radius: 8px;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.35);
}

.modal-title {
    font-size: 18pt;
    margin-bottom: 14px;
    padding-right: 30px;
}

.modal-content p {
    margin-bottom: 12px;
    line-height: 1.55;
    font-size: 11pt;
}

.modal-content a {
    color: #0a7d68;
    text-decoration: none;
}

.modal-content a:hover {
    text-decoration: underline;
}

.modal-close {
    position: absolute;
    top: 10px;
    right: 12px;
    background: none;
    border: none;
    font-size: 26px;
    line-height: 1;
    color: #6b6b6b;
    cursor: pointer;
    padding: 4px 8px;
}

.modal-close:hover {
    color: #1a1a1a;
}

/* --- Legal modal (Termos e Políticas) ------------------------------------ */
/* Wider than the contacts modal: these are long texts, and the tab strip needs
   the room. `85vh` is divided by --ui-scale for the same reason .modal is —
   `zoom` does not scale viewport units. */

.modal-legal {
    max-width: 760px;
    /* Unlike the contacts modal, these texts are long enough to scroll — and a
       tab strip that scrolls out of view makes the other sections unreachable
       without scrolling back. So the dialog itself stops scrolling (the .modal
       rule above) and only .modal-content does, leaving the title, the close
       button and the tabs fixed in place. */
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.modal-legal .modal-content {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

.legal-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-bottom: 16px;
    border-bottom: 1px solid #dcdcdc;
    flex-shrink: 0;
}

.legal-tab {
    background: none;
    border: none;
    border-bottom: 3px solid transparent;
    margin-bottom: -1px;
    padding: 8px 12px;
    font-family: inherit;
    font-size: 10.5pt;
    color: #6b6b6b;
    cursor: pointer;
    transition:
        color 0.2s,
        border-color 0.2s;
}

.legal-tab:hover {
    color: #1a1a1a;
}

.legal-tab-active {
    color: #0a7d68;
    border-bottom-color: #0a7d68;
    font-weight: 600;
}

/* The panel takes focus when a tab is chosen, so it needs a visible ring for
   keyboard users — but not the default one around the whole text block. */
.legal-panel:focus-visible {
    outline: 2px solid #0a7d68;
    outline-offset: 4px;
}

.legal-heading {
    font-size: 12.5pt;
    margin: 18px 0 8px;
    color: #1a1a1a;
}

.legal-heading:first-child {
    margin-top: 0;
}

.legal-list {
    margin: 0 0 12px 18px;
    padding: 0;
}

.legal-list li {
    margin-bottom: 8px;
    line-height: 1.55;
    font-size: 11pt;
}

.contact-list {
    list-style: none;
    margin: 0 0 14px;
    padding: 0;
}

.contact-list li {
    margin-bottom: 8px;
    font-size: 11pt;
}

.contact-list .fa {
    width: 20px;
    color: #0a7d68;
}

.contact-social .fa {
    font-size: 15pt;
    margin-left: 6px;
    color: #0a7d68;
}

@keyframes fadeInUp {
    from {
        opacity: 0;
        transform: translateY(20px); /* Start 20px below the final position */
    }
    to {
        opacity: 1;
        transform: translateY(0); /* End at the final position */
    }
}

.fade-in-up {
    animation: fadeInUp 1s ease-in-out;
}

#popup-message {
    display: flex;
    align-items: center; /* Center vertically */
    justify-content: center; /* Center horizontally */
    text-align: center; /* Center text within the popup */
}

.gradient-background {
    position: fixed;
    width: 7%;
    height: 100%;
    background: linear-gradient(to right, #e5fbf6, #ffffff);
    z-index: -1;
}

/* Phones: no size control — the layout is always at normal size here. The top
   row has no room for three tap targets beside the search box, and phones have
   their own accessibility text-size settings.
   `--ui-scale` is reset rather than just hiding the buttons, so a size chosen on
   a wide window cannot leave a narrow one zoomed with no way to undo it. It must
   be `!important` because js/ui-scale.js sets the property inline on <html>, and
   it resets the variable rather than `zoom` so the rules dividing by it (see the
   top of this file) stay in step. The choice is kept, not erased — widen the
   window and it comes back. */
@media only screen and (max-width: 593px) {
    :root {
        --ui-scale: 1 !important;
    }

    #ui-scale {
        display: none;
    }
}

@media only screen and (max-width: 335px) {
    #title {
        font-size: 30pt;
    }

    header {
        margin-top: 0;
    }

    #social-media {
        margin-top: 5px;
        flex-basis: 25%;
        min-width: 80px;
    }

    #search-box {
        flex-basis: 85%;
        margin-top: 2px;
        padding-right: 2px;
    }

    #search-box-input {
        width: -webkit-calc(100% - 25px);
        width: -moz-calc(100% - 25px);
        width: calc(100% - 25px);
    }

    /* Card layout at this width is handled by the shared ≤480px vertical-card
       rules above; only the non-card header/footer tweaks live here. */

    #copyright {
        order: 2;
        display: flex;
        margin-top: 20px;
        margin-bottom: 30px;
        justify-content: center;
    }

    #copyright span {
        margin: 0;
    }

    footer {
        margin-bottom: 0;
        background-color: black;
        background: black;
        justify-content: center;
    }

    #footer {
        display: flex;
        flex-direction: column;
        margin-bottom: 30px;
    }

    #footer-items {
        flex-basis: 100%;
        justify-content: space-around;
        flex-direction: column;
        align-items: center;
        margin-bottom: 3px;
    }

    #subscription-box {
        position: absolute;
        width: 100%;
        flex-basis: 100%;
        bottom: 0;
        /* Full-bleed here, so drop the desktop right inset. */
        margin-right: 0;
        margin-bottom: 10px;
    }

    /* Button keeps its label width; the field takes whatever is left. */
    #subscription-box input {
        flex: 1;
        width: auto;
        min-width: 0;
    }

    #subscription-box button[type='submit'] {
        flex: none;
        width: auto;
    }

    .gradient-background {
        display: none;
    }
}

@media only screen and (min-width: 336px) and (max-width: 593px) {
    #title {
        font-size: 35pt;
    }

    header {
        flex-wrap: wrap;
        margin-top: 0;
    }

    #social-media {
        margin-top: 5px;
        flex-basis: 15%;
        min-width: 80px;
    }

    #search-box {
        flex-basis: 65%;
        margin-top: 3px;
        padding-right: 2px;
    }

    footer {
        margin-bottom: 0;
        background-color: black;
        background: black;
    }

    #footer {
        flex: auto;
        flex-wrap: wrap;
    }

    #footer-items {
        flex-basis: 100%;
        justify-content: space-around;
    }

    .footer-btn {
        margin-bottom: 3px;
        font-size: 10pt;
        margin-top: 2px;
        color: white;
        transition: color 0.3s;
        flex-basis: 33%;
    }

    #copyright {
        order: 2;
        display: flex;
        margin-top: 20px;
        margin-bottom: 10px;
        flex-basis: 55%;
    }

    #subscription-box {
        position: absolute;
        bottom: 10px;
        right: 0;
        width: 45%;
        /* Positioned against the right edge here; the inset comes from padding. */
        margin-right: 0;
        padding-right: 5px;
    }

    #subscription-box input {
        width: 100%;
    }

    #subscription-box button[type='submit'] {
        width: 100%;
    }

    .gradient-background {
        display: none;
    }
}

@media only screen and (min-width: 594px) and (max-width: 767px) {
    #copyright {
        display: flex;
    }

    .footer-btn {
        min-width: 100px;
    }
}

/* @media only screen and (min-width: 768px) and (max-width: 1023px) { */
@media only screen and (min-width: 768px) {
    .footer-btn {
        min-width: 150px;
    }
}

@media only screen and (min-width: 1024px) and (max-width: 1279px) {
}

@media only screen and (min-width: 1280px) {
    /* #root is capped and centred globally (see its max-width); on large
       screens just drop main's side padding so cards use the full width. */
    main {
        padding-left: 0;
        padding-right: 0;
    }
}

/* NAVIGATION BAR */
#top-nav {
    overflow: hidden;
    border-top: 1px solid black;
    border-bottom: 1px solid black;
    display: flex;
    flex-direction: row;
    justify-content: space-around;
    width: 100%;
    margin-bottom: 20px;
}

.nav-item {
    position: relative;
    height: 40px;
    float: left;
    display: block;
    color: black;
    text-align: center;
    padding: 7px 12px;
    text-decoration: none;
    font-size: 1.1em;
    transition: color 150ms ease;
}

/* Hover / keyboard-focus: no fill or glow, just a thin rule that grows in from
   the centre under the label — echoes the site's flat black borders (top-nav's
   own rule, .article-page's, the type bars) instead of a soft generic "UI"
   wash. Sits on ::after so it's independent of the label's text width. */
.nav-item::after {
    content: '';
    position: absolute;
    left: 12px;
    right: 12px;
    bottom: 4px;
    height: 2px;
    background-color: black;
    transform: scaleX(0);
    transition: transform 200ms ease;
}

.nav-item:hover,
.nav-item:focus-visible {
    font-weight: 500;
    outline: none;
}

.nav-item:hover::after,
.nav-item:focus-visible::after {
    transform: scaleX(1);
}

.nav-active {
    font-weight: 600;
    color: black;
}

#top-nav .icon {
    display: none;
}

#responsive-nav-title {
    display: none;
    width: 0px;
}

@media only screen and (max-width: 593px) {
    #top-nav {
        justify-content: space-between;
    }

    .nav-item {
        display: none;
    }

    #top-nav .icon {
        float: right;
        display: block;
        cursor: pointer;
    }

    .nav-active {
        display: block;
    }
}

@media only screen and (max-width: 593px) {
    #top-nav.responsive-nav-bar {
        position: relative;
        display: flex;
        flex-direction: column;
        border-top: none;
    }
    #top-nav.responsive-nav-bar .icon {
        position: absolute;
        right: 0;
        top: 0;
    }
    #top-nav.responsive-nav-bar .nav-item {
        border-top: 1px solid black;
        float: none;
        display: block;
        text-align: left;
    }
    #responsive-nav-title {
        display: block;
        width: 100%;
    }
}

/* SOCIAL MEDIA ICONS */
/* The icon glyphs, self-hosted. They used to come from a Font Awesome 4.7
   stylesheet on cdnjs, which cost the first paint a whole extra origin
   (DNS + TCP + TLS) *and* a render-blocking stylesheet before the font itself
   could even start — ~800 ms of the mobile critical path for eleven glyphs.
   This is the same font, subsetted to exactly those eleven (77 KB -> 2 KB),
   served from our own origin and preloaded next to the other two in base.html.
   Font: Font Awesome 4.7.0 by Dave Gandy, SIL OFL 1.1.

   Adding an icon means adding its codepoint here *and* re-subsetting the woff2
   with that codepoint (pyftsubset) — the glyph is not in the file otherwise. */
@font-face {
    font-family: 'FontAwesome';
    font-style: normal;
    font-weight: 400;
    /* block, not swap: there is no fallback glyph for a private-use codepoint,
       so a swap period would paint stray boxes. The file is 2 KB and same-origin
       — the invisible period is imperceptible. */
    font-display: block;
    src: url(./fonts/icons.woff2) format('woff2');
}

/* Style all font awesome icons */
.fa {
    display: inline-block;
    font: normal normal normal 14px/1 FontAwesome;
    font-size: 14pt;
    text-rendering: auto;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    text-decoration: none;
}

/* The eleven glyphs in use. Codepoints are Font Awesome 4.7's own, so the
   markup (class="fa fa-facebook") did not have to change anywhere. */
.fa-search::before {
    content: '\f002';
}

.fa-map-marker::before {
    content: '\f041';
}

.fa-phone::before {
    content: '\f095';
}

.fa-twitter::before {
    content: '\f099';
}

.fa-facebook::before {
    content: '\f09a';
}

.fa-link::before {
    content: '\f0c1';
}

.fa-bars::before {
    content: '\f0c9';
}

.fa-envelope::before {
    content: '\f0e0';
}

.fa-youtube::before {
    content: '\f167';
}

.fa-instagram::before {
    content: '\f16d';
}

.fa-whatsapp::before {
    content: '\f232';
}

/* Add a hover effect if you want */
.fa:hover {
    opacity: 0.7;
}

/* Set a specific color for each brand */
.fa-facebook {
    color: black;
}

.fa-instagram {
    color: black;
}

.fa-youtube {
    color: black;
}

/* FONTS */
@font-face {
    font-family: 'Noto Sans Lao Looped';
    font-style: normal;
    font-weight: 100 900;
    font-stretch: 100%;
    font-display: swap;
    src: url(./fonts/noto-sans-lao-looped.woff2) format('woff2');
    unicode-range: U+0E81-0EDF, U+200C-200D, U+25CC;
}
/* latin-ext */
@font-face {
    font-family: 'Noto Sans Lao Looped';
    font-style: normal;
    font-weight: 100 900;
    font-stretch: 100%;
    font-display: swap;
    src: url(./fonts/noto-sans-lao-looped.woff2) format('woff2');
    unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
    font-family: 'Noto Sans Lao Looped';
    font-style: normal;
    font-weight: 100 900;
    font-stretch: 100%;
    font-display: swap;
    src: url(./fonts/noto-sans-lao-looped.woff2) format('woff2');
    unicode-range:
        U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
        U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
    font-family: 'Oleo Script Swash Caps';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url(./fonts/oleo-script-swash-caps.woff2) format('woff2');
    unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
    font-family: 'Oleo Script Swash Caps';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url(./fonts/oleo-script-swash-caps.woff2) format('woff2');
    unicode-range:
        U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
        U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* PROJETO LOJAS (etiquetas)
   A record card/page rather than an article. Everything here is additive and
   scoped to .etiqueta-* so the article card and page are untouched.

   The card is a small square tile — image and title only, no type bar, no
   fields — deliberately smaller than an article card and no longer sharing its
   210px-tall two-column footprint: since etiqueta articles never join the
   mixed home feed (renderer.js -> homeFeedTypePages), nothing ties their shape
   to an article card's any more. Borrows the Destaques column's square-tile
   language for the title (over a dark gradient at the tile's foot, which is
   what keeps it legible over an arbitrary photo without depending on the photo
   being dark) but NOT its cropping: a destaque cover is cover-fit and can be
   cropped freely, but a store tag sticker is often wider than it is tall and
   never reliably square, so the photo here is contain-fit and centred instead
   — never cropped, letterboxed on the tile's cream background instead. */

/* Five per row on a normal desktop width, like the two-column article card's own
   `calc(50% - 4px)`: a percentage basis (not an auto-fill min-width) is what
   makes the count deterministic — (100% - 4 gaps) / 5 columns, gap matching
   #articles's own 8px. flex-grow stays at its default 0, same as the article
   card rule, so the five columns fill the row exactly with nothing left to grow
   into. Changing the column count means changing this basis AND the subtraction
   with it (n columns ⇒ 100%/n minus gap × (n-1)/n), or the last tile wraps. */
#articles .etiqueta-card article,
/* #lojas-list (the Projeto Lojas listing — js/lojas.js) reuses this same
   square-tile card and the same column count as every other etiqueta listing. */
#lojas-list .etiqueta-card article {
    position: relative;
    flex-basis: calc(20% - 6.4px);
    height: auto;
    aspect-ratio: 1 / 1;
    padding: 0;
    /* The neutral backdrop for the "empty" strip contain leaves top/bottom
       (or left/right) on a non-square photo. Unlike .etiqueta-page-hero (now
       transparent there, to keep that box's own size independent of any
       fill), this tile keeps a visible backdrop — the tile's outline would
       otherwise be invisible against a page of the same background. */
    background: #f4f1ea;
}

/* `#articles` + `.etiqueta-card` (id + class) is what beats the base
   `#articles article img` rule's fixed 200x200 (id + 2 elements: the class
   here outweighs its extra element, per CSS specificity comparison) — without
   it the image would render at a fixed 200px instead of filling the tile.
   #lojas-list has no such base rule to beat, but is included here so both
   places share one definition. */
#articles .etiqueta-card img,
#articles .etiqueta-card-img-placeholder,
#lojas-list .etiqueta-card img,
#lojas-list .etiqueta-card-img-placeholder {
    display: block;
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    /* `contain`, not `cover`: a store tag sticker is often wider than it is
       tall and never reliably square, so it must not be cropped. Centred
       inside the square tile, whatever empty strip is left top/bottom (or
       left/right) just shows the tile's own cream background rather than
       cutting into the photo. */
    object-fit: contain;
    object-position: center;
    margin: 0;
    flex: none;
}

.etiqueta-card-title {
    position: absolute;
    left: 0;
    right: 0;
    /* At the head, not the foot (unlike the Destaques tiles this otherwise
       mirrors): a label photo is usually widest across its middle and carries
       its own lettering low, so a caption at the bottom lands right on top of
       the shop's name. The gradient is flipped with it. */
    top: 0;
    /* The title comes *before* the image in the markup (so it is read first),
       and both are positioned — without this the image, being later in the DOM,
       would paint over it. */
    z-index: 1;
    padding: 6px 6px 16px;
    font-size: 0.9em;
    line-height: 1.25em;
    font-weight: 600;
    color: #fff;
    /* This gradient carries the contrast — see the Destaques tiles it mirrors. */
    background: linear-gradient(to bottom, rgba(0, 0, 0, 0.88), rgba(0, 0, 0, 0.65) 55%, rgba(0, 0, 0, 0));
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
    /* Clip at 3 lines so a long store name can't climb over the whole photo. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* --- Record page --------------------------------------------------------- */

/* The hero is the sticker itself: a fixed square box (same shape as the card
   tiles — see #articles .etiqueta-card article above) so a wide tag and a
   tall one both sit correctly without cropping, AND the box never changes
   size from one etiqueta to the next — with the old auto-height box, the
   float's own height tracked each photo's own aspect ratio, so the text
   column beside it (and everything below) reflowed differently on every
   record. object-fit: contain still does the letterboxing; the letterboxed
   strip is transparent (no backdrop fill) rather than the old cream, so it
   just shows whatever the page behind it is. */
.etiqueta-page-hero {
    aspect-ratio: 1 / 1;
    background: transparent;
    border: 1px solid #e6e1d6;
    padding: 12px;
    box-sizing: border-box;
}

.etiqueta-page-hero img {
    aspect-ratio: auto;
    width: 100%;
    height: 100%;
    object-fit: contain;
    border-radius: 0;
}

/* --- Record gallery ------------------------------------------------------ */

/* A store often had several labels over the years, so the hero holds every
   image of the record — the principal (the cover) first — and one is shown at a
   time. The class is only emitted when there really is more than one image
   (`hasGallery` in the renderer), so a single-image ficha keeps the plain hero
   above, arrows and all absent from the markup. */
.etiqueta-gallery {
    /* The anchor for the two arrows and the counter below. */
    position: relative;
}

/* Which slide is showing. Two classes, so this beats `.article-page-hero img`
   (one class + one element) and the hidden slides really do come out of the
   flow — otherwise they would stack down the page. Progressive enhancement:
   the renderer marks the first slide active, so the principal image is what a
   reader without JS sees. */
.etiqueta-gallery-slide:not(.is-active) {
    display: none;
}

/* The arrows sit in the letterboxed margin the contained image leaves, or over
   its edge when it fills the box. Deliberately drawn in CSS rather than with an
   icon glyph: the icon font is a subset of eleven codepoints and has no
   chevron, and adding one means re-subsetting the woff2 (see main.css's icon
   block) for two arrows that borders draw exactly as well. */
.etiqueta-gallery-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 34px;
    height: 34px;
    padding: 0;
    border: 1px solid rgba(0, 0, 0, 0.15);
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.88);
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
    cursor: pointer;
    /* Above the slide, which is a plain static image inside this box. */
    z-index: 1;
}

.etiqueta-gallery-nav:hover,
.etiqueta-gallery-nav:focus-visible {
    background: #fff;
    border-color: rgba(0, 0, 0, 0.35);
}

/* The chevron itself: half a square, rotated. */
.etiqueta-gallery-nav::before {
    content: '';
    display: block;
    width: 9px;
    height: 9px;
    margin: 0 auto;
    border-top: 2px solid #222;
    border-left: 2px solid #222;
}

.etiqueta-gallery-prev {
    left: 6px;
}

.etiqueta-gallery-prev::before {
    transform: translateX(2px) rotate(-45deg);
}

.etiqueta-gallery-next {
    right: 6px;
}

.etiqueta-gallery-next::before {
    transform: translateX(-2px) rotate(135deg);
}

/* "2 / 5" — which of the store's labels is on screen. Kept inside the framed
   box so it reads as part of the image, not as a caption of the ficha. */
.etiqueta-gallery-counter {
    position: absolute;
    right: 10px;
    bottom: 10px;
    z-index: 1;
    padding: 2px 8px;
    border-radius: 10px;
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    font-size: 0.75em;
    letter-spacing: 0.03em;
}

/* Field table: label above value, two columns on desktop, one on narrow. */
.etiqueta-fields {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px 24px;
    margin: 24px 0 0;
    max-width: 520px;
}

.etiqueta-field dt {
    font-size: 0.75em;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: #6b6b6b;
}

.etiqueta-field dd {
    margin: 2px 0 0;
    font-size: 1.05em;
    line-height: 1.4;
    /* Long addresses wrap instead of forcing the grid wider. */
    overflow-wrap: anywhere;
}

/* An unfilled field prints "Desconhecido" rather than vanishing (see
   metadataRows in renderer.js) — the catalog is built by contributions, and a
   visible gap invites one. Set apart from a real value so it never reads as a
   fact about the store. */
.etiqueta-field dd.is-unknown {
    color: #9a9a9a;
    font-style: italic;
}

/* Optional free-text notes below the fields. */
.etiqueta-notes {
    margin-top: 24px;
    padding-top: 16px;
    border-top: 1px solid rgba(0, 0, 0, 0.1);
}

/* "Observações" — metadata.observacoes, in its own block under the field grid
   rather than as a `<dl>` row (it is a paragraph, not a fact). Same separator as
   .etiqueta-notes above, which sits below it when a ficha has both. */
.etiqueta-observacoes {
    margin-top: 24px;
    padding-top: 16px;
    border-top: 1px solid rgba(0, 0, 0, 0.1);
}

.etiqueta-observacoes-title {
    margin: 0 0 8px;
    font-size: 0.75em;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    font-weight: 600;
    color: #6b6b6b;
}

.etiqueta-observacoes p {
    margin: 0;
    line-height: 1.55;
    /* The text is interpolated escaped, so its line breaks are real newlines in
       a single <p>; this is what renders them. */
    white-space: pre-line;
}

/* Both of these narrow the tile grid, and both match the unconditional rule's
   specificity (same selector), so source order alone decides — they must stay
   after it, and in this order.

   Tablet-ish: five columns of a ~740px main leave ~140px tiles, which is fine,
   but below that the fifth column starts clipping the title. Step down to
   four before the mobile break rather than jumping straight from five to two. */
@media only screen and (max-width: 991px) {
    #articles .etiqueta-card article,
    #lojas-list .etiqueta-card article {
        flex-basis: calc(25% - 6px);
    }
}

/* Narrow viewport: four columns of a ~155px main would squeeze each tile under
   40px, too small for a legible title. Drop to two — same breakpoint and the
   same `calc(50% - 4px)` the article card already uses for its own mobile-vs-
   desktop column count, just without the article card's row-to-column reflow
   (this tile has no separate body to reflow; aspect-ratio keeps it square at
   any width). */
@media only screen and (max-width: 767px) {
    #articles .etiqueta-card article,
    #lojas-list .etiqueta-card article {
        flex-basis: calc(50% - 4px);
    }
}

@media only screen and (max-width: 593px) {
    .etiqueta-fields {
        grid-template-columns: minmax(0, 1fr);
        gap: 12px;
    }
}

/* --- Projeto Lojas: card list + search (js/lojas.js) --------------------- */
/* Just a listing, same width and card language as every other one — the map
   used to sit permanently beside it; now it only exists inside the
   #modal-lojas-map modal below, opened from .lojas-map-btn.

   Two ways to find a store — the fields, then "ou", then the map — on a single
   row about one input tall. The fields used to be three bare boxes butted
   against the map button, which read as one undifferentiated toolbar; the
   fieldset border and the "ou" are what separate the two searches now, so the
   layout does not also have to spend stacked rows saying it. This block sits
   above the fold on every visit and the cards are what the reader came for, so
   every rule here is about height: the legend overlaps the border instead of
   adding a line, each label sits *beside* its input instead of above it, and
   both buttons share the field row rather than starting new ones.

   The labels stay real `<label for>`s rather than becoming placeholders. A
   placeholder would save the same space but disappears the moment the reader
   types — exactly when they need to know which box holds what — so the saving
   comes out of the layout instead. */

.lojas-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin-top: 4px;
}

.lojas-search {
    flex: 1 1 460px;
    min-width: 0;
    margin: 0;
}

.lojas-search-box {
    margin: 0;
    /* No top padding: the legend sits on the border line, so the row below it
       needs no clearance of its own. */
    padding: 0 10px 7px;
    border: 1px solid #ddd;
    border-radius: 4px;
    background-color: white;
}

.lojas-search-box legend {
    padding: 0 5px;
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #8a8a8a;
}

.lojas-search-fields {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 10px;
}

/* Label beside the input, not above it — the whole reason this row is one input
   tall. `min-width: 0` on the field (and on the input) is what lets it shrink
   below the input's intrinsic size instead of overflowing the fieldset. */
.lojas-search-field {
    display: flex;
    flex: 1 1 130px;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

.lojas-search-field label {
    flex: none;
    font-size: 0.72rem;
    letter-spacing: 0.03em;
    color: #8a8a8a;
    white-space: nowrap;
}

.lojas-search-field input {
    flex: 1 1 auto;
    min-width: 0;
    height: 28px;
    padding: 0 8px;
    font-family: inherit;
    font-size: 0.85rem;
    background-color: white;
    border: 1px solid #ddd;
    border-radius: 3px;
}

.lojas-search-field input:focus {
    outline: none;
    border-color: #0a7d68;
}

/* Secondary to the map button: outline rather than a filled block, so the two
   never compete for the same "this is the action" reading. */
.lojas-clear-btn {
    flex: none;
    height: 28px;
    padding: 0 11px;
    font-family: inherit;
    font-size: 0.8rem;
    color: #0a7d68;
    background-color: transparent;
    border: 1px solid #cfcfcf;
    border-radius: 3px;
    cursor: pointer;
}

.lojas-clear-btn:hover {
    border-color: #0a7d68;
}

.lojas-alt {
    display: flex;
    flex: none;
    align-items: center;
    gap: 10px;
}

.lojas-alt-sep {
    font-size: 0.8rem;
    font-style: italic;
    color: #8a8a8a;
}

.lojas-map-btn {
    flex: none;
    height: 30px;
    padding: 0 14px;
    font-family: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    color: #ffffff;
    background-color: #0a7d68;
    border: 1px solid #0a7d68;
    border-radius: 3px;
    cursor: pointer;
}

.lojas-map-btn:hover {
    background-color: #086354;
}

/* Narrow: the three fields can no longer share a row, so let them stack and let
   the map alternative centre itself under the box. */
@media only screen and (max-width: 593px) {
    .lojas-search-field {
        flex-basis: 100%;
    }

    .lojas-alt {
        justify-content: center;
        width: 100%;
    }
}

/* #lojas-list reuses the .etiqueta-card square tile and its shared 4-per-row
   (2 on mobile) layout — see the #articles .etiqueta-card rules above.
   js/pagination.js replaces its contents wholesale on every page change. */
#lojas-list {
    display: flex;
    flex-flow: row wrap;
    gap: 8px;
    margin-top: 12px;
}

#lojas-list .article-card:hover article {
    box-shadow: 3px 3px 10px #999;
}

.lojas-empty {
    color: #666;
    font-size: 0.9rem;
    padding: 8px 0;
}

/* --- Map modal (js/lojas.js) --------------------------------------------- */
/* Opened only from .lojas-map-btn; js/lojas.js creates the Leaflet map (and
   fetches its pins) the first time this opens rather than at page load, so a
   reader who never asks for the map never pays for either. Wide, like
   .modal-etiqueta, since a map needs real room to be useful. */
.modal-lojas-map {
    max-width: 900px;
    width: 100%;
}

#lojas-map {
    width: 100%;
    /* Explicit height, not a percentage: unlike the old side-by-side layout
       there is no stretched grid row to inherit from here. Divided by
       --ui-scale like .modal's own max-height, for the same reason (zoom
       does not scale viewport units). */
    height: calc(60vh / var(--ui-scale));
    min-height: 320px;
    border-radius: 4px;
    background: #f4f1ea;
}

/* Leaflet's own marker <img>, toggled on hover/focus of the matching card
   (js/lojas.js highlightPin) — a drop-shadow reads as "raised" without
   resizing the icon, which would shift its anchor point on the map. */
.leaflet-marker-icon.lojas-pin-active {
    filter: drop-shadow(0 0 6px rgba(10, 125, 104, 0.9));
}

/* --- Address lookup inside the map modal (js/lojas.js) ------------------- */
/* Geocodes what the reader types and moves the map there. The stores' pins are
   never hidden by it — the question being asked is which of them were near that
   address. */
.lojas-geo {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin: 0 0 10px;
}

.lojas-geo label {
    font-size: 0.72rem;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: #6b6b6b;
}

/* Positioned so the suggestion list can hang under the input rather than push
   the map down as results arrive. */
.lojas-geo-input {
    position: relative;
    flex: 1 1 240px;
    min-width: 0;
}

.lojas-geo-input input {
    width: 100%;
    height: 34px;
    padding: 0 10px;
    font-family: inherit;
    font-size: 0.85rem;
    background-color: white;
    border: 1px solid #ddd;
    border-radius: 3px;
}

.lojas-geo-input input:focus {
    outline: none;
    border-color: #0a7d68;
}

/* Above Leaflet's own panes, which sit at z-index 400-700. */
.lojas-geo-results {
    position: absolute;
    top: calc(100% + 2px);
    left: 0;
    right: 0;
    z-index: 800;
    margin: 0;
    padding: 0;
    list-style: none;
    max-height: 220px;
    overflow-y: auto;
    background: white;
    border: 1px solid #ddd;
    border-radius: 3px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.lojas-geo-results li {
    padding: 7px 10px;
    font-size: 0.82rem;
    line-height: 1.35;
    cursor: pointer;
}

.lojas-geo-results li:hover,
.lojas-geo-results li[aria-selected='true'] {
    background-color: #eef5f3;
}

.lojas-geo-clear {
    flex: none;
    height: 34px;
    padding: 0 12px;
    font-family: inherit;
    font-size: 0.8rem;
    color: #0a7d68;
    background-color: transparent;
    border: 1px solid #cfcfcf;
    border-radius: 3px;
    cursor: pointer;
}

.lojas-geo-clear:hover {
    border-color: #0a7d68;
}

/* Full-width so a message never squeezes the input on a narrow modal. */
.lojas-geo-status {
    flex: 1 0 100%;
    margin: 0;
    font-size: 0.78rem;
    color: #777;
}

/* The searched address itself. Drawn in CSS rather than with an icon glyph: the
   site's icon font is an eleven-glyph subset with nothing suitable in it (same
   reason the gallery arrows are CSS borders). Warm against Leaflet's blue store
   pins, so the two never read as the same kind of thing. */
.lojas-geo-pin {
    box-sizing: border-box;
    border: 3px solid #c0392b;
    border-radius: 50%;
    background-color: rgba(192, 57, 43, 0.25);
    box-shadow:
        0 0 0 2px white,
        0 1px 4px rgba(0, 0, 0, 0.4);
}

/* Wider than the plain contacts modal (see .modal-legal above) — a gallery and
   a field list need the room — and, like it, only .modal-content scrolls so
   the close button stays put. */
.modal-etiqueta {
    max-width: 640px;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.modal-etiqueta .modal-content {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

.etiqueta-modal-title {
    padding-right: 30px;
}

.lojas-modal-status {
    color: #666;
    padding: 24px 0;
    text-align: center;
}

.etiqueta-modal-link {
    display: inline-block;
    margin-top: 16px;
    color: #0a7d68;
    text-decoration: none;
    font-weight: 600;
}

.etiqueta-modal-link:hover {
    text-decoration: underline;
}
