/* =============================================
   Page templates — the public pages outside the archive SPA: wallpaper
   detail, home, the category / series / artist hubs, blog, about, donate,
   takedown, app, 404.

   Why this is not merged into style.css: the two hardly overlap (one shared
   selector at the time of the rename), and style.css is also loaded by every
   admin page and all the generators, which do not load this file. Merging
   would push this whole file into admin, including the bare h2/h3/h4 rule
   below.

   This file was css/seo.css and these classes were .seo-*, from when the
   site was split into an SPA and a set of crawlable "SEO pages". The site is
   no longer split, so the prefix is now wn-. It stays a prefix because 21 of
   the bare names (btn, tab, pagination, wallpaper-grid, resolution-select…)
   are already in use by style.css and admin.css.
   ============================================= */

h2,h3,h4 { font-weight: 400; }  

/* Layout */
.wn-page { padding: 40px 0; min-height: calc(100vh - 170px); }
.wn-container { margin: 0 auto; padding: 0 40px; }

/* Breadcrumb */
.wn-breadcrumb { font-size: 11px; color: var(--text-secondary); letter-spacing: 1px; margin-bottom: 20px; text-transform: uppercase; }
.wn-breadcrumb a { color: var(--text-secondary); text-decoration: none; }
.wn-breadcrumb a:hover { color: var(--text-primary); }
.wn-breadcrumb span { margin: 0 8px; opacity: 0.4; }

/* Page header */
.wn-page-header { margin-bottom: 30px; }
.wn-page-title { font-size: 58px; line-height:56px; font-weight: 400; letter-spacing: -0.6px; margin: 0 0 8px 0; text-transform: uppercase;  }
.wn-grid-intro-icon { width: 0.95em; height: 0.95em; vertical-align: -0.15em; margin-right: 0.4em; flex-shrink: 0; stroke-width: 1.8; }
.wn-page-subtitle { font-size: 12px; color: var(--text-secondary); letter-spacing: 0.5px; text-transform:uppercase; }
.wn-page-header-row { display: flex; justify-content: space-between; align-items: flex-end; gap: 24px; }

/* Split header layout (breadcrumb + title left, subtitle + dropdown right) */
.wn-page-header-split {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    gap: 40px;
    margin-bottom: 40px;
}
.wn-page-header-left {
    flex: 1 1 auto;
    min-width: 0;
}
.wn-page-header-left .wn-breadcrumb {
    margin-bottom: 10px;
}
.wn-page-header-left .wn-page-title {
    margin: 0;
}
.wn-page-header-right {
    flex: 0 0 auto;
    /* Enough room to comfortably fit Search + Select + Sort on one row at
       desktop widths. Was 420px before the search was added — with three
       controls the old ceiling forced the last dropdown onto a new row. */
    max-width: 720px;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 12px;
    text-align: right;
}
.wn-page-header-right .wn-page-subtitle {
    margin: 0;
}
.wn-series-byline a {
    color: inherit;
    text-decoration: none;
    border-bottom: 1px solid transparent;
    transition: border-color 0.2s ease, color 0.2s ease;
}
.wn-series-byline a:hover {
    color: var(--text-primary);
    border-bottom-color: currentColor;
}
.wn-page-header-right .wn-category-dropdown-wrap {
    width: 100%;
    justify-content: flex-end;
}

/* Section title */
.wn-section-title { font-size: 28px;  line-height: 1;  letter-spacing: 1.5px; margin-bottom: 30px; color: var(--text-primary); text-transform: uppercase; }

/* Tags */
.wn-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 32px; }
.series-page .wn-tags,
.artist-page .wn-tags  { display:none; } /* disable for now */
.wn-tag { font-size: 10px; letter-spacing: 1px; padding: 6px 12px; border: 1px solid var(--border); color: var(--text-secondary); text-decoration: none; transition: all 0.2s; text-transform: uppercase; border-radius: var(--radius-sm); }
.wn-tag:hover { border-color: var(--text-primary); color: var(--text-primary); }

/* Buttons */
.wn-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: auto; align-items: stretch; }
.wn-btn { padding: 14px 22px; font-family: inherit; font-size: 11px; letter-spacing: 1.5px; cursor: pointer; border: 1px solid var(--border); background: transparent; color: var(--text-primary); text-decoration: none; text-align: center; transition: all 0.2s; white-space: nowrap; display: inline-flex; align-items: center; justify-content: center; text-transform: uppercase; border-radius: var(--radius-md); }
.wn-btn:hover { border-color: var(--text-primary); background: var(--bg-secondary); }
.wn-btn-primary { background: var(--text-primary); color: var(--bg-primary); border-color: var(--text-primary); }
.wn-btn-primary:hover { background: #ccc; }

/* Resolution selector */
.wn-resolution-select {
    padding: 14px 12px;
    font-family: inherit;
    font-size: 11px;
    letter-spacing: 1px;
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text-primary);
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%23999' stroke-width='1.2'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 10px center;
    padding-right: 28px;
    min-width: 120px;
    align-self: flex-end;
    text-transform: uppercase;
    border-radius: var(--radius-md);
}
.wn-resolution-select:focus { outline: none; border-color: var(--text-primary); }
.wn-resolution-select option { background: var(--bg-primary); color: var(--text-primary); }

/* Pagination — spread layout */
.wn-pagination { display: flex; align-items: center; margin-top: 40px; gap: 8px; }
.wn-pagination-prev { flex: 0 0 auto; }
.wn-pagination-pages { flex: 1; display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
.wn-pagination-next { flex: 0 0 auto; }
.wn-pagination a,
.wn-pagination span { padding: 8px 14px; font-size: 11px; letter-spacing: 1px; border: 1px solid var(--border); color: var(--text-secondary); text-decoration: none; font-family: inherit; transition: all 0.2s; text-transform: uppercase; border-radius: var(--radius-md); }
.wn-pagination a:hover { border-color: var(--text-primary); color: var(--text-primary); }
.wn-pagination .active { border-color: var(--text-primary); color: var(--text-primary); }
.wn-pagination .wn-pagination-ellipsis { padding: 8px 4px; border: none; color: var(--text-secondary); -webkit-user-select: none; user-select: none; }

/* =============================================
   Main-style wallpaper grid (shared)
   Matches the masonry-like grid from archive
   ============================================= */

.wn-wallpaper-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    grid-auto-rows: 200px;
    grid-auto-flow: dense;
    gap: 20px;
    margin-bottom: 80px;
}
.wn-grid-item {
    display: flex;
    flex-direction: column;
    gap: 8px;
    text-decoration: none;
    color: inherit;
    cursor: pointer;
}

/* Row spanning pattern to create visual variety */
.wn-grid-item:nth-child(3n+1) { grid-row: span 2; }
.wn-grid-item:nth-child(5n+2) { grid-row: span 3; }
.wn-grid-item:nth-child(7n+3) { grid-row: span 2; }
.wn-grid-item:nth-child(1) { grid-row: span 2 !important; }
.wn-grid-item:nth-child(2) { grid-column: span 2; grid-row: span 2 !important; }
.wn-grid-item:nth-child(3) { grid-row: span 3 !important; }

/* Additional 2-column spans throughout the grid */
.wn-grid-item:nth-child(8) { grid-column: span 2; }
.wn-grid-item:nth-child(15) { grid-column: span 2; }
.wn-grid-item:nth-child(23) { grid-column: span 2; }
.wn-grid-item:nth-child(32) { grid-column: span 2; }
.wn-grid-item:nth-child(11n+9) { grid-column: span 2; }

/* Prevent tall items on the last row for a clean bottom edge.
   The :not(.wn-grid-intro) guard keeps this rule from collapsing the intro
   card on sparse series (≤5 wallpapers) where the intro would otherwise
   fall inside the last-5-children selector. */
.wn-grid-item:not(.wn-grid-intro):nth-last-child(-n+5) { grid-row: span 1 !important; grid-column: span 1 !important; }

/* =============================================
   Series intro card — featured grid item
   Sits as nth-child(1) inside .wn-wallpaper-grid--with-intro.
   We override the original featured-slot rules (originally nth-child 1, 2, 3)
   so the layout doesn't end up with two giant blocks stacked together.
   ============================================= */
.wn-grid-intro {
    grid-column: span 2;
    grid-row: span 2;
    position: relative;
    overflow: hidden;
    border: 1px solid var(--border);
    background-color: var(--bg-secondary, #161616);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    color: var(--text-primary, #fff);
    text-decoration: none;
    cursor: default;
    border-radius: var(--radius-media);
}

/* Dark overlay layered on top of the background image */
.wn-grid-intro-overlay {
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(0, 0, 0, 0.55) 0%, rgba(0, 0, 0, 0.85) 60%, rgba(0, 0, 0, 0.92) 100%);
    pointer-events: none;
}

.wn-grid-intro-content {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: 20px;
    padding: 28px;
    height: 100%;
    box-sizing: border-box;
    overflow: hidden;
}

.wn-grid-intro-body {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.wn-grid-intro-name {
    margin: 0;
    font-size: 22px;
    font-weight: 500;
    line-height: 1.1;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--text-primary, #fff);
}

.wn-intro-lead {
    margin: 0;
    font-size: 13px;
    line-height: 1.6;
    color: var(--text-primary, #e8e8e8);
}

.wn-intro-lead a {
    color: inherit;
    text-decoration: none;
    border-bottom: 1px solid rgba(255, 255, 255, 0.25);
    transition: border-color 0.2s ease, color 0.2s ease;
}

.wn-intro-lead a:hover {
    border-bottom-color: var(--accent, #fff);
    color: var(--accent, #fff);
}

/* When the intro card is present, neutralize the dramatic featured slots
   (originally nth-child 1, 2, 3) so we don't stack giant blocks next to
   the intro. The intro itself already provides the visual anchor. */
.wn-wallpaper-grid--with-intro .wn-grid-item:nth-child(2),
.wn-wallpaper-grid--with-intro .wn-grid-item:nth-child(3) {
    grid-column: span 1 !important;
    grid-row: span 1 !important;
}
/* Restore some variety to the next few items without going as big as the originals */
.wn-wallpaper-grid--with-intro .wn-grid-item:nth-child(4) { grid-row: span 2; }
.wn-wallpaper-grid--with-intro .wn-grid-item:nth-child(6) { grid-row: span 2; }

/* Sparse mode: when there are very few wallpapers on the page, the 2×2 intro
   leaves awkward empty cells. Make the intro a full-width banner row instead
   so the wallpapers sit cleanly below it. Also short-circuit the variety
   spans so we don't end up with weird gaps. */
.wn-wallpaper-grid--sparse .wn-grid-intro {
    grid-column: 1 / -1;
    grid-row: span 2;
}
.wn-wallpaper-grid--sparse .wn-grid-item:not(.wn-grid-intro) {
    grid-row: span 1 !important;
    grid-column: span 1 !important;
}

/* Mobile: intro collapses to a single column, auto height */
@media (max-width: 768px) {
    .wn-grid-intro {
        grid-column: span 1;
        grid-row: auto;
        min-height: 320px;
    }
    .wn-grid-intro-content {
        padding: 20px;
    }
}

/* Large viewports: intro grows wider and taller for more visual weight.
   At ~1400px+ the grid is typically showing 5–6 columns, so a 2-col intro
   starts looking lost. Bump to 3 cols × 3 rows. */
@media (min-width: 1400px) {
    .wn-grid-intro {
        grid-column: span 3;
        grid-row: span 3;
    }
    .wn-grid-intro-content {
        padding: 36px;
    }
    .wn-grid-intro-name {
        font-size: 24px;
    }
    .wn-intro-lead {
        font-size: 13px;
    }
}



.wn-grid-image-wrapper {
    width: 100%;
    flex: 1;
    min-height: 0;
    overflow: hidden;
    border: 1px solid var(--border);
    position: relative; /* anchor for .like-btn overlay */
    /* Placeholder shimmer + fade-in (.is-lazy-*) is shared with the archive
       grid — see style.css and WU.createLazyImageLoader in user.js. */
    border-radius: var(--radius-media);
    isolation: isolate;
}

.wn-grid-image-wrapper img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: opacity 0.6s ease, transform 0.6s ease;
}

.wn-grid-item:hover .wn-grid-image-wrapper img {
    opacity: 0.85;
    transform: scale(1.1);
}

.wn-grid-overlay {
    overflow: hidden;
    min-height: 14px;
    line-height: 1.1;
    color: var(--text-secondary);
}

.wn-grid-title {
    font-size: 12px;
    font-weight: 400;
    letter-spacing: 0.5px;
    line-height: 1.1;
    display: inline;
    text-transform: uppercase;
    color: var(--text-primary);
}

.wn-grid-meta {
    font-size: 12px;
    color: var(--text-secondary);
    letter-spacing: 0.5px;
    line-height: 1.1;
    display: inline;
    text-transform: uppercase;
}

.wn-grid-meta::before {
    content: '/';
    color: var(--text-secondary);
    margin: 0 6px;
}

/* =============================================
   Related Categories block (series detail page)
   Uniform grid — sits below the wallpaper grid and
   surfaces internal links to /c/[tag] pages so series
   traffic feeds back into categories.

   Mirrors the main .wn-wallpaper-grid column sizing
   so the related row visually aligns underneath it,
   but disables the masonry nth-child row/col spans
   from .wn-grid-item — we want a clean uniform row,
   not a chaotic featured layout.
   ============================================= */

.wn-related-categories {
    margin-top: 20px;
    margin-bottom: 60px;
}

.wn-related-categories .wn-section-title {
    margin-bottom: 20px;
}

.wn-related-grid {
    display: grid;
    /* Same minmax as .wn-wallpaper-grid so columns line up under the main grid. */
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    grid-auto-rows: 200px;
    gap: 20px;
}

/* Neutralize every span rule that .wn-grid-item layers on via nth-child.
   Without this, related cards inherit "row span 2" / "col span 2" from the
   featured-tile pattern and leave masonry gaps in this uniform row. */
.wn-related-grid .wn-grid-item,
.wn-related-grid .wn-grid-item:nth-child(1),
.wn-related-grid .wn-grid-item:nth-child(2),
.wn-related-grid .wn-grid-item:nth-child(3),
.wn-related-grid .wn-grid-item:nth-child(3n+1),
.wn-related-grid .wn-grid-item:nth-child(5n+2),
.wn-related-grid .wn-grid-item:nth-child(7n+3),
.wn-related-grid .wn-grid-item:nth-child(8),
.wn-related-grid .wn-grid-item:nth-child(15),
.wn-related-grid .wn-grid-item:nth-child(23),
.wn-related-grid .wn-grid-item:nth-child(32),
.wn-related-grid .wn-grid-item:nth-child(11n+9) {
    grid-row: span 1 !important;
    grid-column: span 1 !important;
}

/* Type icon (folder for categories, layers for series) prefixed to grid card
   titles. Inherits currentColor so it picks up the overlay text color and any
   hover state automatically. Kept inline (not inline-flex) so the title
   baseline still aligns with the meta text that follows it on the same line. */
.wn-grid-icon {
    display: inline-block;
    width: 12px;
    height: 12px;
    vertical-align: -2px;
    margin-right: 6px;
    flex-shrink: 0;
    stroke-width: 1.5;
    opacity: 0.85;
}

@media (max-width: 600px) {
    .wn-related-grid {
        grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
        grid-auto-rows: 160px;
        gap: 14px;
    }
}

/* =============================================
   Wallpaper detail page
   ============================================= */

.wn-wallpaper { display: grid; grid-template-columns: 1fr 390px; gap: 30px; margin-bottom: 60px; }
.wn-wallpaper-image { position: relative; border: 1px solid var(--border); overflow: hidden; border-radius: var(--radius-media); }
.wn-wallpaper-image img { width: 100%; height: 100%; object-fit: cover; display: block; }
.wn-wallpaper-image-portrait { max-height: 80vh; display: flex; align-items: center; justify-content: center; background: var(--bg-secondary); }
.wn-wallpaper-image-portrait img { width: auto; max-width: 100%; height: 100%; max-height: 80vh; object-fit: contain; }

.wn-wallpaper-info { display: flex; flex-direction: column; gap: 24px; }
.wn-wallpaper-title { font-size: 34px; line-height:36px; font-weight: 400; letter-spacing: 1px; margin: 0; margin-bottom:5px; text-transform:uppercase}
.wn-wallpaper-artist { font-size: 12px; color: var(--text-secondary); letter-spacing: 1px; text-transform: uppercase; }
.wn-wallpaper-artist-link { color: var(--text-secondary); text-decoration: none; border-bottom: 1px solid transparent; transition: color 0.2s, border-color 0.2s; }
.wn-wallpaper-artist-link:hover { color: var(--text-primary); border-bottom-color: var(--text-primary); }
.artist-website-icon { color: var(--text-secondary); text-decoration: none; font-size: 11px; margin-left: 4px; transition: color 0.2s; }
.artist-website-icon:hover { color: var(--primary); }
.external-link-icon { width: 1.3em; height: 1.3em; vertical-align: -0.3em; }
.artist-website-link { color: var(--text-secondary); text-decoration: none; border-bottom: 1px solid transparent; transition: color 0.2s, border-color 0.2s; }
.artist-website-link:hover { color: var(--text-primary); border-bottom-color: var(--text-primary); }
.wn-wallpaper-credits { margin-top: 4px; }
.wn-wallpaper-credit { font-size: 11px; color: var(--text-secondary); letter-spacing: 1px; text-transform: uppercase; margin: 2px 0; }
.wn-wallpaper-description { font-size: 12px; color: var(--text-secondary); line-height: 1.8; }
.wn-wallpaper-description .wn-desc-link { color: var(--text); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2px; transition: color 0.15s ease; }
.wn-wallpaper-description .wn-desc-link:hover { color: var(--accent, #fff); text-decoration-thickness: 2px; }
.wn-wallpaper-description .wn-desc-link--external { word-break: break-word; }

.wn-detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.wn-detail { border: 1px solid var(--border); padding: 14px; display: flex; align-items: center; gap: 14px; border-radius: var(--radius-lg); }
.wn-detail-icon { width: 28px; height: 28px; flex-shrink: 0; stroke-width: 1.6; color: var(--text-secondary); }
.wn-detail-text { display: flex; flex-direction: column; min-width: 0; }
.wn-detail-label { font-size: 9px; color: var(--text-secondary); letter-spacing: 1.5px; margin-bottom: 3px; text-transform: uppercase; }
.wn-detail-value { font-size: 14px; letter-spacing: 0.5px; }

/* Row wrapping the series pill (left) and the prev/next arrow group (right).
   The pill keeps its own background and stays compact; the arrows are
   detached, sitting borderless on the opposite edge of the sidebar. */
.wn-series-line {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}

.wn-series-badge {
    display: inline-flex;
    align-items: center;
    padding: 4px 10px;
    font-size: 9px;
    letter-spacing: 1px;
    background-color: var(--border);
    color: var(--text-secondary);
    text-transform: uppercase;
    text-decoration: none;
    border-radius: var(--radius-sm);
}
.wn-series-badge:hover { color: var(--text-primary); }

/* Series prev/next arrows — matched to the .wn-series-badge pill: grey
   background at rest, text lights up on hover. Slight background shift on
   hover mirrors the badge's own hover treatment. */
.wn-series-nav-group {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.wn-series-nav {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 4px 8px;
    background-color: var(--border);
    color: var(--text-secondary);
    text-decoration: none;
    transition: color 0.15s ease, background-color 0.15s ease;
    line-height: 0;
    border-radius: var(--radius-sm);
}
.wn-series-nav:hover {
    color: var(--text-primary);
    background-color: rgba(255, 255, 255, 0.14);
}
.wn-series-nav.is-disabled {
    opacity: 0.35;
    pointer-events: none;
}

/* Wallpaper info tabs (Details + FAQ) */
.wn-wallpaper-tabs { margin-top: 40px; margin-bottom: 80px; max-width: 100%; }
.wn-tabs-strip {
    display: flex;
    gap: 32px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 24px;
}
.wn-tab {
    font: inherit;
    background: transparent;
    color: var(--text-secondary);
    border: 0;
    border-bottom: 1px solid transparent;
    padding: 14px 0;
    margin-bottom: -1px;
    font-size: 14px;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    text-align: left;
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s;
}
.wn-tab:hover { color: var(--text-primary); }
.wn-tab:focus-visible { outline: none; color: var(--text-primary); border-bottom-color: var(--text-secondary); }
.wn-tab.is-active {
    color: var(--text-primary);
    border-bottom-color: var(--text-primary);
}
.wn-tab-panel { display: none; }
.wn-tab-panel.is-active { display: block; }
/* Fallback: when JS is disabled the [hidden] attribute still hides panel 2,
   but the content remains in the DOM so search engines index everything. */

/* Details panel content (spans full tab width) */
.wn-about-text { font-size: 13px; line-height: 1.9; color: var(--text-secondary); margin: 0 0 18px 0; }
.wn-about-text a { color: var(--text-primary); text-decoration: none; border-bottom: 1px solid var(--border); transition: border-color 0.15s; }
.wn-about-text a:hover { border-bottom-color: var(--text-primary); }
.wn-about-perfect-for { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; font-size: 12px; color: var(--text-secondary); letter-spacing: 0.5px; margin: 16px 0 0 0; }
.wn-about-perfect-for strong { color: var(--text-primary); font-weight: 500; text-transform: uppercase; font-size: 10px; letter-spacing: 1.5px; }
.wn-device-chips { display: flex; flex-wrap: wrap; gap: 8px; list-style: none; padding: 0; margin: 0; }
.wn-device-chip { font-size: 10px; letter-spacing: 1px; padding: 6px 12px; border: 1px solid var(--border); color: var(--text-secondary); text-transform: uppercase; border-radius: var(--radius-sm); }

/* FAQ panel content — 2-column grid on desktop, 1 column on mobile */
.wn-faq-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin: 0; padding: 0; }
.wn-faq-item { border: 1px solid var(--border); padding: 18px 20px; border-radius: var(--radius-lg); }
.wn-faq-q { font-size: 13px; letter-spacing: 0.5px; margin: 0 0 8px 0; color: var(--text-primary); }
.wn-faq-a { font-size: 13px; line-height: 1.9; color: var(--text-secondary); margin: 0; }
@media (max-width: 768px) {
    .wn-faq-list { grid-template-columns: 1fr; }
}

/* Comments panel */
.wn-comment-form-wrap { margin-bottom: 32px; }
.wn-comment-form, .wn-comment-reply-form { display: flex; flex-direction: column; gap: 12px; }
.wn-comment-reply-form { margin: 14px 0 0 24px; }
/* Higher specificity than the class rule above so the `hidden` attribute
   actually hides the form — otherwise `display: flex` on the class wins
   the cascade over the UA stylesheet's `[hidden] { display: none }` and
   the form stays visible regardless of the hidden attribute. */
.wn-comment-form[hidden], .wn-comment-reply-form[hidden] { display: none; }

.wn-comment-login-cta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    padding: 18px 20px;
    border: 1px solid var(--border);
    background: var(--bg-secondary);
    border-radius: var(--radius-lg);
}
.wn-comment-login-cta[hidden] { display: none; }
.wn-comment-login-cta-text { font-size: 13px; color: var(--text-secondary); margin: 0; }
.wn-comment-textarea {
    width: 100%;
    min-height: 90px;
    resize: vertical;
    padding: 14px 16px;
    font: inherit;
    font-size: 13px;
    line-height: 1.7;
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-primary);
    border-radius: var(--radius-md);
}
.wn-comment-textarea:focus { outline: none; border-color: var(--text-primary); }
.wn-comment-form-actions { display: flex; align-items: center; justify-content: flex-end; gap: 10px; flex-wrap: wrap; }
.wn-comment-honeypot { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; }

/* Pixel emoticons — rendered inline in comment bodies (see commentBodyHtml
   in includes/comments-helpers.php / js/comments.js) and as picker buttons
   injected into the comment forms. currentColor keeps them matched to the
   surrounding text. */
/* 16x16 grid at 16px = 1px per cell. NO crispEdges here: at 1px cells it
   snaps each square's edges to device pixels independently, so on any
   fractional scale (Windows 125% scaling, browser zoom) the two eyes come
   out different sizes and the ring wobbles. Default antialiasing keeps the
   faces symmetric at every zoom, at the cost of a slightly soft edge. */
.px-emoji {
    width: 16px;
    height: 16px;
    display: inline-block;
    vertical-align: -3px;
    margin: 0 1px;
}
.wn-emoji-row { display: flex; gap: 2px; margin-right: auto; flex-wrap: wrap; }
.wn-emoji-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    padding: 0;
    background: none;
    border: none;
    cursor: pointer;
    color: var(--text-secondary);
    transition: color 0.15s;
}
.wn-emoji-btn:hover { color: var(--text-primary); }
.wn-emoji-btn .px-emoji { width: 16px; height: 16px; vertical-align: 0; }

.wn-comments-empty { font-size: 13px; color: var(--text-secondary); }

.wn-comment { padding: 20px 0; border-top: 1px solid var(--border); }
.wn-comments-list .wn-comment:first-child { border-top: none; }
.wn-comment-header { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; flex-wrap: wrap; }
.wn-comment-avatar {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    border-radius: 50%;
    border: 1px solid var(--border);
    color: var(--text-secondary);
}
.wn-comment-avatar svg { width: 15px; height: 15px; }
.wn-comment-reply .wn-comment-avatar { width: 22px; height: 22px; }
.wn-comment-reply .wn-comment-avatar svg { width: 13px; height: 13px; }
.wn-comment-author { font-size: 12px; letter-spacing: 0.5px; color: var(--text-primary); text-transform: uppercase; }
.wn-comment-date { font-size: 11px; color: var(--text-secondary); letter-spacing: 0.5px; }
.wn-comment-pending-badge {
    font-size: 10px;
    letter-spacing: 1px;
    text-transform: uppercase;
    padding: 3px 8px;
    border: 1px solid var(--border);
    color: var(--text-secondary);
    border-radius: var(--radius-sm);
}
.wn-comment-pending { opacity: 0.7; }
.wn-comment-body { font-size: 13px; line-height: 1.8; color: var(--text-secondary); margin: 0 0 10px 0; white-space: pre-wrap; word-break: break-word; }
.wn-comment-actions { display: flex; gap: 16px; }
.wn-comment-reply-btn, .wn-comment-delete-btn {
    font: inherit;
    font-size: 10px;
    letter-spacing: 1px;
    text-transform: uppercase;
    background: none;
    border: none;
    padding: 0;
    color: var(--text-secondary);
    cursor: pointer;
    transition: color 0.15s;
    display: inline-flex;
    align-items: center;
    gap: 5px;
}
.wn-comment-reply-btn:hover, .wn-comment-delete-btn:hover { color: var(--text-primary); }
.wn-comment-reply-btn svg { width: 12px; height: 12px; flex-shrink: 0; }
.wn-comment-delete-btn svg { width: 13px; height: 13px; flex-shrink: 0; }

.wn-comment-replies { margin-left: 24px; }
.wn-comment-replies:not(:empty) { margin-top: 14px; }

/* Thread connector: an "L" elbow curves from the trunk line into each
   reply's avatar, and a straight segment continues down to the next
   reply's elbow (omitted after the last reply, where the line just ends). */
.wn-comment-replies .wn-comment-reply {
    position: relative;
    padding: 14px 0 0 24px;
    border-top: none;
    border-left: none;
}
/* Trunk: one continuous vertical bar per reply, full height so it meets the
   next reply's elbow with no seam — except the last reply, which stops
   right at its own elbow since there's nothing below it to connect to. */
.wn-comment-reply::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 1px;
    background: var(--border);
}
.wn-comment-reply:last-child::before {
    bottom: auto;
    height: 25px;
}
/* Elbow foot: a separate horizontal bar (not a border corner) so it never
   double-paints the same pixel as the trunk above — that double coverage
   was what made the corners look brighter than the rest of the line. */
.wn-comment-reply::after {
    content: '';
    position: absolute;
    left: 0;
    top: 25px;
    width: 16px;
    height: 1px;
    background: var(--border);
}

.wn-comments-load-more { margin-top: 20px; width: 100%; }

@media (max-width: 640px) {
    .wn-comment-replies { margin-left: 12px; }
    .wn-comment-reply-form { margin-left: 12px; }
}

/* =============================================
   Category pages
   ============================================= */

/* Category dropdown (replaces sidebar) */
.wn-category-dropdown-wrap {
    display: flex;
    align-items: flex-end;
    gap: 8px;
    /* Allow controls to wrap onto new rows at intermediate viewport widths
       (tablets, ~800-1200px) so the Select + Sort + Search trio doesn't get
       jammed into one row and overflow. Each child sets its own flex-basis. */
    flex-wrap: wrap;
}
.wn-category-dropdown-label {
    font-size: 10px;
    letter-spacing: 1.5px;
    color: var(--text-secondary);
    white-space: nowrap;
}
.wn-category-dropdown {
    padding: 10px 36px 10px 14px;
    font-family: inherit;
    font-size: 11px;
    letter-spacing: 0.5px;
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text-primary);
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%23999' stroke-width='1.2'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    /* Shrink-friendly: distribute available row width evenly across the three
       header controls (search + select + sort) with a small min-width so they
       compress before wrapping. */
    flex: 1 1 0;
    min-width: 140px;
    max-width: 320px;
    text-transform: uppercase;
    border-radius: var(--radius-md);
}
.wn-category-dropdown:focus { outline: none; border-color: var(--text-primary); }
.wn-category-dropdown option { background: var(--bg-primary); color: var(--text-primary); }

/* Sort dropdown - matching variant */
.wn-sort-dropdown {
    flex: 1 1 0;
    min-width: 130px;
    max-width: 220px;
}

/* Category index grid */
.wn-categories-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; }
.wn-category-card { border: 1px solid var(--border); overflow: hidden; text-decoration: none; color: var(--text-primary); transition: border-color 0.2s; border-radius: var(--radius-media); }
.wn-category-card:hover { border-color: var(--text-primary); }
.wn-category-card img { width: 100%; aspect-ratio: 16/10; object-fit: cover; display: block; }
.wn-category-card-info { padding: 14px 16px; }
.wn-category-card-name { font-size: 13px; letter-spacing: 1px; margin-bottom: 4px; }
.wn-category-card-count { font-size: 10px; color: var(--text-secondary); }

/* =============================================
   Series page
   ============================================= */

.series-header { margin-bottom: 40px; }
.series-title { font-size: 22px; font-weight: 400; letter-spacing: 1.5px; margin: 0 0 8px 0; }
.series-description { font-size: 12px; color: var(--text-secondary); line-height: 1.7; max-width: 700px; margin-bottom: 16px; }

.series-meta { display: flex; gap: 24px; font-size: 11px; color: var(--text-secondary); letter-spacing: 0.5px; }
.series-meta-item { display: flex; align-items: center; gap: 6px; }

.series-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 16px; }
.series-tag { font-size: 10px; letter-spacing: 0.5px; padding: 4px 10px; border: 1px solid var(--border); color: var(--text-secondary); text-decoration: none; transition: all 0.2s; border-radius: var(--radius-sm); }
.series-tag:hover { border-color: var(--text-primary); color: var(--text-primary); }

.more-series { border-top: 1px solid var(--border); padding-top: 40px; }
.more-series-title { font-size: 11px; letter-spacing: 1.5px; color: var(--text-secondary); margin-bottom: 20px; }
.more-series-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; }
.more-series-card { padding: 20px; border: 1px solid var(--border); text-decoration: none; color: var(--text-primary); transition: border-color 0.2s; border-radius: var(--radius-lg); }
.more-series-card:hover { border-color: var(--text-primary); }
.more-series-card-name { font-size: 12px; letter-spacing: 0.5px; margin-bottom: 4px; }
.more-series-card-count { font-size: 10px; color: var(--text-secondary); }


/* 404 page */
.not-found-page { display: flex; align-items: center; justify-content: center; }
.not-found-page .container { margin: 0 auto; }
.not-found-inner { text-align: left; width: 100%; }
.not-found-logo { width: 60px; height: 60px; opacity: 0.2; margin: 0 auto 30px; }
.not-found-code { font-size: 72px; line-height:70px; font-weight: 400; letter-spacing: 6px; margin: 0 0 15px; }
.not-found-links { display: flex; gap: 15px; justify-content: left; flex-wrap: wrap; margin-top: 40px; }
.not-found-links .nav-link { border: 1px solid var(--border); padding: 12px 24px; border-radius: var(--radius-md); }
.not-found-steps { margin: 30px 0; padding: 24px 24px 24px 0; border: 1px solid var(--border); border-radius: var(--radius-lg); }
.not-found-steps-title { font-weight: 500; margin: 0 0 12px; font-size: 13px; text-transform: uppercase; letter-spacing: 2px; padding-left: 24px; }
.not-found-steps ol { margin: 0; padding-left: 44px; }
.not-found-steps li { color: var(--text-secondary); margin-bottom: 8px; font-size: 13px; line-height: 1.6; }
.not-found-steps li:last-child { margin-bottom: 0; }
.not-found-contact { color: var(--text-secondary); font-size: 12px; margin-top: 20px; }
.not-found-contact a { color: var(--accent); }

/* site map for users page */
.sm-section { margin-bottom: 56px; }

.sm-section-label {
    font-size: 22px;
    line-height: 1;
    letter-spacing: 1px;
    font-weight: 400;
    color: var(--text-primary);
    text-transform: uppercase;
    margin-bottom: 24px;
     padding-bottom: 14px;
    border-bottom: 1px solid var(--border);
}
/* site map Main pages — same grid as categories/series */
.sm-pages {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    column-gap: 48px;
            row-gap: 0;
}
.sm-page-link {
    font-size: 12px;
    letter-spacing: 0.5px;
    color: var(--text-secondary);
    text-decoration: none;
    padding: 10px 0;
    border-bottom: 1px solid var(--border);
    display: block;
    transition: color 0.15s;
    text-transform: uppercase;
}
 .sm-page-link:hover { color: var(--text-primary); }

.page-home .main { padding-top: 0; }


/* =============================================
   Hero Search Section
   ============================================= */

.hero-section {
    position: relative;
    width: 100%;
    /* Capped against the viewport as well as in pixels — a flat 720px fills
       a 13" laptop edge to edge and leaves nothing below the fold to
       suggest the page continues. */
    height: min(720px, 82vh);
    overflow: visible;
    margin-bottom: 72px;
    z-index: 30;
    isolation: isolate;
}

.hero-visual {
    position: absolute;
    inset: 0;
    overflow: hidden;
    z-index: 1;
    /* The lens. It lives here rather than inside .hero-collage's transform so
       that ONE vanishing point, at the centre of the hero, governs both the
       collage's lean and every strip's depth -- which is what makes the strips
       read as a curved surface instead of six independently skewed columns. */
    perspective: 1400px;
    perspective-origin: 50% 50%;
}

/* The collage is five independent film strips running in parallel, not a
   grid. A grid forces every cell onto shared row lines, so whole rows of
   frames start and end together; two earlier versions of this (an 8x8 and
   an 8x12) both read as a rank of aligned blocks no matter how the spans
   were varied. Strips remove the shared lines entirely: each column owns
   its own sequence of frame heights, so an edge in one column falls
   wherever it falls in the next.

   Each strip scrolls vertically on its own loop, alternating direction and
   at a different duration, which is also what keeps the frame edges from
   settling into alignment — the columns drift against each other forever
   because no two periods divide evenly.

   THE ONE CONSTRAINT, if you change frame counts or heights: the strip
   markup repeats its frames TWICE and animates 0 -> -50%, which lands copy
   two exactly where copy one was. That is seamless only while a single
   period (the sum of one copy's frame heights) is at least as tall as the
   collage — otherwise a gap opens and slides through the hero. It is also
   what stops the same wallpaper appearing twice in one column at once.
   Periods are 4.67-5.0x the column width against a worst case of 4.30 at
   390px, the tightest common phone. Re-check the arithmetic if you touch
   the hero height, the insets, or the column count. */
.hero-collage {
    position: absolute;
    inset: -14% -6%;
    display: flex;
    align-items: flex-start;
    will-change: transform, translate;

    /* The lean. The bulge is on the strips below; this is only the tip back.

       THE ORIGIN IS THE TRICK, and it is not a style choice. Rotated about the
       centre the top edge recedes, perspective shrinks it, and a triangular
       gap of page background opens in both top corners -- the margins here are
       ~15px and the shrink is more than that. Compensating with a scale costs
       26% magnification on every thumbnail once the pointer swing is in.
       Rotated about the TOP EDGE nothing shrinks: that edge is fixed, all of it
       is nearer the viewer and therefore magnified, and it still reads as a
       lean because the bottom is wider than the top.

       What it costs: rotateY tips that pinned edge, dropping one end by about
       (width/2)*sin(swingY)*sin(tilt), which grows with the viewport -- 2560px
       is the binding case, not 1280. That is paid for by the parallax cap in
       setupHeroParallax(), now 0.50 of the overhang rather than 0.85.

       Verified as a union over all six strips, every pointer position, 320px
       to 2560px: 10-30px of headroom. Re-run it if you change the tilt, the
       swing, the perspective, the hero height, the insets, the strip count or
       that cap. The pointer writes --hero-mx / --hero-my, both -1..1, and
       never `transform`. */
    --hero-tilt: 12deg;
    --hero-swing-x: 4deg;
    --hero-swing-y: 3deg;
    --hero-mx: 0;
    --hero-my: 0;
    transform-origin: 50% 0%;
    transform:
        rotateX(calc(var(--hero-tilt) + var(--hero-my) * var(--hero-swing-x)))
        rotateY(calc(var(--hero-mx) * var(--hero-swing-y)));
    /* preserve-3d, so the strips' translateZ survives into this space instead
       of being flattened into the collage's plane. It is what the whole bulge
       depends on. */
    transform-style: preserve-3d;
}

/* ── The bulge ────────────────────────────────────────────────────────────
   Each column is pushed toward the viewer by an amount that falls off from the
   centre, so the six of them describe a shallow dome under one shared lens.
   Nearer columns project larger; the outer ones stay where a flat collage put
   them. That is a real fisheye rather than a drawn one, and the browser sorts
   the columns by depth for free.

   Two properties of this particular curve are what make it safe, and both
   depend on THE OUTERMOST COLUMNS SITTING AT ZERO:

   1. It cannot open a seam. Between neighbours the gap works out to
      (x + w) * (f(i+1) - f(i)), and on both sides of centre those two factors
      have opposite signs -- so every boundary overlaps, never parts. The
      overlap is widest at the outside (~70px of a 325px column) and zero in
      the middle, which is exactly the shape you want.
   2. It costs no coverage at all. The envelope of the collage is set by the
      outer edges of the first and last columns, which are at z = 0 and so
      project exactly where they always did. The depth can be raised or dropped
      without re-running the coverage sum -- it was measured at z-max 0 through
      260 and the headroom does not move.

   A wrapper rather than the strip itself because .hero-strip's scroll loop
   already owns `transform`, and one element cannot hold both. */
.hero-strip-wrap {
    /* The flex item now, in .hero-collage's row -- this is what `flex: 1 1 0`
       used to do for .hero-strip, and it has to move with the role. A block,
       not a flex container: a column flex parent would make the strip's own
       `flex` basis mean height instead of width. */
    flex: 1 1 0;
    min-width: 0;
    /* Depth, then the defocus that belongs with it: the columns that sit
       furthest back are the ones out of focus. Blurring the element that
       scrolls (rather than this wrapper) keeps the blur in the rasterised
       layer, so the animation still moves a finished bitmap instead of
       re-blurring the subtree every frame.

       The faded edge a blur leaves is why the collage got wider (-6% from -4%):
       inside the collage those edges are hidden under the neighbouring column's
       overlap, but the outer two have nothing over them, and the extra width
       puts that fade off screen. Widening costs nothing -- it makes the strip
       period longer against an unchanged collage height, so the seamless-loop
       ratio improved from 1.08 to 1.35. */
    --hero-z: 0px;
    --hero-blur: 0px;
    transform: translateZ(var(--hero-z));
}

/* Parabolic, centre nearest, ends at zero. Written out per column rather than
   computed because there is no CSS that can count siblings into a curve --
   and restated in each breakpoint below, because the curve has to re-centre
   on however many columns are actually showing.

   The depth is also what sets how much bigger the middle of the hero reads.
   z-max 150 against a 1400px perspective magnifies the centre column by 12%,
   which is what the seventh column pays for: seven narrower columns magnified
   12% land the middle frames within half a percent of the size they were
   before any of this, and the outer ones about a tenth smaller. A stronger
   bulge is free as far as coverage goes (see above) but not as far as the
   composition goes -- past about 200px the hero starts to feel zoomed in. */
.hero-strip-wrap-1, .hero-strip-wrap-7 { --hero-z:   0px; --hero-blur: 5px; }
.hero-strip-wrap-2, .hero-strip-wrap-6 { --hero-z:  83px; --hero-blur: 3px; }
.hero-strip-wrap-3, .hero-strip-wrap-5 { --hero-z: 133px; --hero-blur: 1px; }
.hero-strip-wrap-4                     { --hero-z: 150px; --hero-blur:   0px; }

.hero-strip {
    width: 100%;
    display: flex;
    flex-direction: column;
    will-change: transform;
    filter: blur(var(--hero-blur));
}

/* Loop distance is 50% because the frames are rendered twice. */
@keyframes heroStripUp {
    from { transform: translateY(0);     }
    to   { transform: translateY(-50%);  }
}
@keyframes heroStripDown {
    from { transform: translateY(-50%);  }
    to   { transform: translateY(0);     }
}

/* Durations are deliberately coprime-ish and none is a multiple of another,
   so the columns never return to the same relative offset. */
.hero-strip-1 { animation: heroStripUp   127s linear infinite;                        }
.hero-strip-2 { animation: heroStripDown 149s linear infinite; animation-delay: -37s; }
.hero-strip-3 { animation: heroStripUp   173s linear infinite; animation-delay: -91s; }
.hero-strip-4 { animation: heroStripDown 113s linear infinite; animation-delay: -19s; }
.hero-strip-5 { animation: heroStripUp   191s linear infinite; animation-delay: -64s; }
.hero-strip-6 { animation: heroStripDown 167s linear infinite; animation-delay: -83s; }
.hero-strip-7 { animation: heroStripUp   139s linear infinite; animation-delay: -53s; }

/* A column has to stay wide enough for a frame to read, so columns are dropped
   rather than squeezed: seven above 1400px, six to 1200px, five to 769px,
   three below. The thresholds moved up by one column when the bulge arrived --
   see the depth curve above for why. */
/* Six columns, and the dome re-centres between the third and fourth. Without
   re-stating it the curve keeps its seven-column shape and the row reads
   lopsided -- one edge far back, the other mid-depth. */
@media (max-width: 1399px) {
    .hero-strip-wrap-7 { display: none; }
    .hero-strip-wrap-1, .hero-strip-wrap-6 { --hero-z:   0px; --hero-blur: 5px; }
    .hero-strip-wrap-2, .hero-strip-wrap-5 { --hero-z: 100px; --hero-blur: 3px; }
    .hero-strip-wrap-3, .hero-strip-wrap-4 { --hero-z: 150px; --hero-blur: 1px; }
}

/* Five columns. The bulge eases off with the column count: fewer columns are
   already wider, so the same magnification here would put the middle frames
   back where this change was meant to bring them down from. */
@media (max-width: 1199px) {
    .hero-strip-wrap-6 { display: none; }
    .hero-strip-wrap-1, .hero-strip-wrap-5 { --hero-z:   0px; --hero-blur: 4px; }
    .hero-strip-wrap-2, .hero-strip-wrap-4 { --hero-z:  75px; --hero-blur: 2px; }
    .hero-strip-wrap-3                     { --hero-z: 100px; --hero-blur:   0px; }
}

.hero-cell {
    flex: 0 0 auto;   /* never let a frame compress — height is the aspect */
    width: 100%;
    overflow: hidden;
    position: relative;
}

/* The frame vocabulary. Height comes from the column width via the ratio,
   so everything stays proportional at any viewport with no media queries. */
.hero-frame-xwide  { aspect-ratio: 12 / 5; }
.hero-frame-wide   { aspect-ratio: 16 / 9; }
.hero-frame-square { aspect-ratio: 1 / 1;  }
.hero-frame-tall   { aspect-ratio: 3 / 4;  }
.hero-frame-xtall  { aspect-ratio: 3 / 5;  }

.hero-cell img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* Ken Burns, on a minority of frames only.
   Every frame animating at once reads as the whole hero swimming, and it
   costs a compositor layer each — the strips already carry the motion, so
   this is seasoning. index.php marks roughly a third of the frames and
   assigns one of the three variants below.

   Both copies of a frame are always marked the same way and given the same
   delay. They must be: the strip loop swaps copy two into copy one's place,
   and if their motion differed the wrap would be visible. Sharing the wall
   clock means the two are always at the same phase, so it is not.

   Drift comes from transform-origin, not from a translate. Scaling about an
   origin inside the box is the one form of this that cannot uncover a frame
   edge at any scale >= 1, so there is no pan-vs-overhang budget to keep in
   sync — an off-centre origin pushes the image across the frame on its own.
   An earlier version paired scale with an explicit translate and needed the
   two capped against each other; this needs nothing. The 1.02 floor rather
   than a flat 1.0 is only to keep sub-pixel rounding off the edges.

   Two independent knobs, worth keeping straight. The scale FLOOR sets the
   safety margin — how far the image overhangs at its smallest, measured at
   about 1px in the tightest frame. The CEILING sets how much travel you
   actually see. Wanting more motion means raising the ceiling; lowering the
   floor buys nothing but risk. */
@keyframes heroKenA {
    from { transform: scale(1.02); }
    to   { transform: scale(1.16); }
}
@keyframes heroKenB {
    from { transform: scale(1.16); }
    to   { transform: scale(1.02); }
}
@keyframes heroKenC {
    from { transform: scale(1.03); }
    to   { transform: scale(1.15); }
}

.hero-ken-a img { animation: heroKenA 19s ease-in-out infinite alternate; transform-origin: 30% 35%; }
.hero-ken-b img { animation: heroKenB 23s ease-in-out infinite alternate; transform-origin: 72% 62%; }
.hero-ken-c img { animation: heroKenC 15s ease-in-out infinite alternate; transform-origin: 45% 22%; }

/* Radial defocus, over the collage and under the darkening.
 *
 * The per-column blur above is depth of field between columns; this is the
 * lens falling off toward its own edges, which is the part that reads as a
 * fisheye rather than as a row of blurred strips. A backdrop-filter rather
 * than a filter on the collage: it blurs what is already painted, so no edge
 * of anything fades and there is nothing to keep in sync with the coverage
 * arithmetic.
 *
 * Masked to the outside, so the centre -- where the search box sits -- is
 * untouched. The mask is the whole reason this is affordable: the blurred
 * region is a ring, not the full hero.
 */
.hero-focus {
    position: absolute;
    inset: 0;
    pointer-events: none;
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    -webkit-mask-image: radial-gradient(ellipse 62% 78% at 50% 44%,
        rgba(0,0,0,0) 0%, rgba(0,0,0,0) 52%, #000 100%);
    mask-image: radial-gradient(ellipse 62% 78% at 50% 44%,
        rgba(0,0,0,0) 0%, rgba(0,0,0,0) 52%, #000 100%);
}

/* Dual-layer overlay: uniform darkening + hard fade-to-page at bottom */
.hero-overlay {
    position: absolute;
    inset: 0;
    background:
        linear-gradient(
            to bottom,
            rgba(10,10,10,0.08)  0%,
            rgba(10,10,10,0.10) 30%,
            rgba(10,10,10,0.85) 72%,
            #0a0a0a            100%
        ),
        rgba(0,0,0,0.65);
}

/* Centered content */
.hero-content {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 20px;
    padding: 0 20px;
    z-index: 2;
}

.hero-tagline {
    font-size: 14px;
    letter-spacing: 3px;
    color: rgba(255,255,255,0.5);
    text-transform: uppercase;
    margin: 0;
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.hero-search-wrap {
    position: relative;
    width: calc(100% - 80px);
    max-width: 1000px;
    z-index: 40;
}

/* The what's-new phrases in the tagline's rotation are real links once the
   scramble reveal finishes. (They used to be a second line under the search,
   .hero-ticker; see index.php.) */
.hero-tagline a {
    color: inherit;
    text-decoration: none;
    border-bottom: 1px solid transparent;
    transition: color 0.15s, border-color 0.15s;
}
.hero-tagline a:hover {
    color: rgba(255, 255, 255, 0.85);
    border-bottom-color: rgba(255, 255, 255, 0.4);
}

/* Match archive search bar — form wraps icon + input, button sits flush right */
.hero-search-form {
    display: flex;
    align-items: center;
    width: 100%;
    border: 1px solid #4f4f4f;
    padding: 4px 6px 4px 16px;
    background: rgba(20, 20, 20, 0.8);
    transition: border-color 0.2s;
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
    border-radius: var(--radius-md);
}

.hero-search-form:focus-within {
    border-color: var(--text-secondary);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
}

.hero-search-icon {
    color: var(--text-secondary);
    flex-shrink: 0;
    opacity: 0.6;
    display: flex;
    align-items: center;
    padding-right: 12px;
    transition: opacity 0.2s;
}

.hero-search-form:focus-within .hero-search-icon {
    opacity: 1;
}

.hero-search-input {
    flex: 1;
    background: transparent;
    border: none;
    outline: none;
    color: var(--text-primary);
    font-family: inherit;
    font-size: 13px;
    font-weight: 300;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    min-width: 0;
    padding: 16px 0;
}

.hero-search-input::placeholder {
    color: var(--text-secondary);
    text-transform: uppercase;
}

.hero-search-btn {
    background: var(--text-primary);
    border: none;
    color: var(--bg-primary);
    cursor: pointer;
    padding: 14px 22px;
    display: flex;
    align-items: center;
    flex-shrink: 0;
    font-family: inherit;
    font-size: 11px;
    font-weight: 400;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    transition: background 0.2s;
    border-radius: var(--radius-sm);
}

.hero-search-btn:hover {
    background: #d0d0d0;
}

.hero-search-feedback {
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    right: 0;
    border: 1px solid var(--border);
    background: rgba(10, 10, 10, 0.703);
    display: none;
    z-index: 80;
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.hero-search-feedback.active {
    display: block;
}

.hero-search-status {
    font-size: 11px;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--text-secondary);
    padding: 11px 14px;
    border-bottom: 1px solid var(--border);
    transition: background 0.2s, color 0.2s;
    display: flex;
    align-items: center;
}

.hero-search-status:hover {
    background: rgba(255, 255, 255, 0.03);
    color: var(--text-primary);
}

.hero-search-status-icon {
    flex-shrink: 0;
    margin-right: 8px;
    opacity: 0.5;
}

.hero-search-status-text {
    flex: 1;
}

.hero-search-suggestions {
    display: flex;
    flex-direction: column;
}

.hero-search-suggestion {
    display: flex;
    align-items: baseline;
    opacity: 0;
    transform: translateY(-8px);
    animation: heroSuggestionSlideIn 0.28s ease-out forwards;
    justify-content: space-between;
    gap: 14px;
    padding: 11px 14px;
    color: var(--text-primary);
    text-decoration: none;
    border-bottom: 1px solid var(--border);
    transition: background 0.2s;
}

.hero-search-suggestion:last-child {
    border-bottom: none;
}

.hero-search-suggestion:hover,
.hero-search-suggestion.is-active {
    background: var(--bg-secondary);
}

.hero-search-suggestion-title {
    font-size: 12px;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.hero-search-suggestion-meta {
    font-size: 10px;
    letter-spacing: 1px;
    color: var(--text-secondary);
    text-transform: uppercase;
    white-space: nowrap;
}

.hero-search-empty {
    font-size: 10px;
    letter-spacing: 0.8px;
    color: var(--text-secondary);
    text-transform: uppercase;
    padding: 12px 14px;
    opacity: 0;
    transform: translateY(-8px);
    animation: heroSuggestionSlideIn 0.28s ease-out forwards;
}

@keyframes heroSuggestionSlideIn {
    from {
        opacity: 0;
        transform: translateY(-8px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* =============================================
   Home Page Sections
   ============================================= */

.home-section {
    margin-bottom: 60px;
}
.home-section:last-child {
    margin-bottom: 0;
}
.home-section-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 24px;
    padding-bottom: 14px;
}
.home-section-title {
    font-size: 36px;
    line-height: 1;
    letter-spacing: 1px;
    font-weight: 400;
    color: var(--text-primary);
    text-transform: uppercase;
    margin: 0;
}
.home-section-link {
    font-size: 11px;
    letter-spacing: 1.5px;
    cursor: pointer;
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text-primary);
    text-decoration: none;
    text-align: center;
    transition: all 0.2s;
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    text-transform: uppercase;
    padding: 10px 18px;
    font-family: inherit;
    border-radius: var(--radius-md);
}
.home-section-link:hover {
    border-color: var(--text-primary);
    background: var(--bg-secondary);
}

/* Home grid — same patterns as related wallpapers grid */
.home-grid {
    margin-bottom: 0;
}

/* Categories — same grid as series */
.sm-categories {
   display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    column-gap: 48px;
    row-gap: 0;
}
.sm-category-link {
    font-size: 12px;
    letter-spacing: 0.5px;
    color: var(--text-secondary);
    text-decoration: none;
    padding: 10px 0;
    border-bottom: 1px solid var(--border);
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    transition: color 0.15s;
    gap: 12px;
    text-transform: uppercase;
}
.sm-category-link:hover { color: var(--text-primary); }

/* Series — multi-column list */
 .sm-series {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    column-gap: 48px;
    row-gap: 0;
}
.sm-series-link {
    font-size: 12px;     
    letter-spacing: 0.5px;
    color: var(--text-secondary);
    text-decoration: none;
    padding: 10px 0;
    border-bottom: 1px solid var(--border);
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    transition: color 0.15s;
    gap: 12px;
    text-transform: uppercase;
}
.sm-series-link:hover { color: var(--text-primary); }
.sm-item-count {
    opacity: 0.35;
    font-size: 10px;
    white-space: nowrap;
    flex-shrink: 0;
}


/* =============================================
   Responsive
   ============================================= */

@media (max-width: 1440px) {
    .wn-container { padding: 0 20px; }
    .hero-section {  height: 720px; }  
}

@media (max-width: 1024px) {  
    .hero-section {  height: min(620px, 78vh); }

    /* Stack split header earlier — title + dropdown crowd each other below this */
    .wn-page-header-split {
        flex-direction: column;
        align-items: flex-start;
        gap: 20px;
    }
    .wn-page-header-right {
        width: 100%;
        max-width: none;
        align-items: flex-start;
        text-align: left;
    }
    .wn-page-header-right .wn-category-dropdown-wrap {
        justify-content: flex-start;
    }
}

@media (max-width: 768px) {
    .wn-page-title { font-size: 46px; line-height:44px; font-weight: 400; letter-spacing: -0.4px; margin: 0 0 8px 0; text-transform: uppercase; }

    /* Hero — three strips instead of five, so a column stays wide enough
       for a frame to read. The hidden strips' images are still fetched —
       see the note in index.php for why deferring them is not safe here.
       Vertical inset is tighter than desktop because the period-vs-height
       constraint above is at its worst on a narrow phone. */
    .hero-section { margin-bottom: 48px; height: min(520px, 62vh); }
    /* Same angle against a 520px hero reads steeper than against 720px, and
       there are three strips rather than six to carry it. No swing values to
       set: the pointer handler binds only on (hover: hover) and (pointer:
       fine), so --hero-mx/--hero-my stay 0 here and the angle is all there
       is. The coverage sum above covers this breakpoint at 9deg. */
    /* Smaller swing as well as a smaller angle: rotateY tips the collage's
       pinned top edge, and at 320px the headroom runs out at the full swing.
       Verified at 3deg/2deg down to 320px with a mouse attached. */
    .hero-collage {
        inset: -8% -6%;
        --hero-tilt: 9deg;
        --hero-swing-x: 3deg;
        --hero-swing-y: 2deg;
    }
    .hero-strip-wrap-4,
    .hero-strip-wrap-5,
    .hero-strip-wrap-6,
    .hero-strip-wrap-7 { display: none; }
    /* Three columns: one in front, two at the back. A blur this heavy over a
       third of a narrow hero is too much, so it comes down with the count. */
    .hero-strip-wrap-1, .hero-strip-wrap-3 { --hero-z:   0px; --hero-blur: 3px; }
    .hero-strip-wrap-2                     { --hero-z: 100px; --hero-blur:   0px; }
    .hero-tagline { font-size: 13px; letter-spacing: 2px; }
    .hero-content { align-items: stretch !important; padding: 0 20px; }
    .hero-search-wrap { width: 100% !important; max-width: none !important; }
    .hero-search-btn { padding: 14px 16px; letter-spacing: 1px; }

    /* Wallpaper detail */
    .wn-wallpaper { grid-template-columns: 1fr; gap: 24px; }
    .wn-wallpaper-image-portrait { max-height: 60vh; }
    .wn-wallpaper-image-portrait img { max-height: 60vh; }
    .wn-resolution-select { min-width: 0; }

    /* Grids */
    .wn-wallpaper-grid {
        grid-template-columns: repeat(2, 1fr);
        grid-auto-rows: 150px;
    }
    .wn-grid-item:nth-child(2),
    .wn-grid-item:nth-child(8),
    .wn-grid-item:nth-child(15),
    .wn-grid-item:nth-child(23),
    .wn-grid-item:nth-child(32),
    .wn-grid-item:nth-child(11n+9) { grid-column: span 1; }

    /* Category */
    .wn-page-header-row { flex-direction: column; align-items: flex-start; }
    .wn-page-header-split { 
        flex-direction: column; 
        align-items: flex-start; 
        gap: 20px; 
    }
    .wn-page-header-right { 
        width: 100%; 
        align-items: flex-start; 
        text-align: left; 
    }
    .wn-page-header-right .wn-category-dropdown-wrap { 
        justify-content: flex-start; 
        flex-wrap: wrap;
    }
    .wn-category-dropdown { min-width: 0; flex: 1; }
    .wn-categories-grid { grid-template-columns: repeat(2, 1fr); }

    /* Series */
    .series-meta { flex-wrap: wrap; gap: 12px; }
}

@media (max-width: 480px) {
    /* Hero — the strips already reflow; only the type scale changes here. */
    .hero-section { margin-bottom: 36px; }
    .home-section-title { font-size: 24px; }

    .wn-wallpaper-grid {
        grid-template-columns: 1fr;
        grid-auto-rows: 180px;
    }
    .wn-grid-item:nth-child(2),
    .wn-grid-item:nth-child(8),
    .wn-grid-item:nth-child(15),
    .wn-grid-item:nth-child(23),
    .wn-grid-item:nth-child(32),
    .wn-grid-item:nth-child(11n+9) { grid-column: span 1; }
}


/* =============================================
   Like button styles moved to css/style.css so the archive page (which uses
   .wallpaper-item cards rendered by main.js and does NOT load pages.css) gets
   the same treatment. See "Like button" block in style.css.
   ============================================= */


/* User profile modal — Likes tab */
.user-profile-likes-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 16px;
    gap: 12px;
}

/* Grid/List view toggle in the Likes tab header. Two square icon buttons
   glued together with a shared border, mirroring similar segmented controls
   elsewhere on the site (sort dropdown / category chips). */
.user-likes-view-toggle {
    display: inline-flex;
    align-items: stretch;
    border: 1px solid var(--border, #222);
    border-radius: var(--radius-md);
    overflow: hidden;
}
.user-likes-view-btn {
    width: 32px;
    height: 32px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    color: var(--text-secondary, #888);
    border: 0;
    cursor: pointer;
    transition: color 0.15s ease, background-color 0.15s ease;
}
.user-likes-view-btn + .user-likes-view-btn {
    border-left: 1px solid var(--border, #222);
}
.user-likes-view-btn svg {
    width: 16px;
    height: 16px;
    display: block;
}
.user-likes-view-btn:hover {
    color: var(--text-primary, #fff);
}
.user-likes-view-btn.is-active {
    background: var(--bg-secondary, #141414);
    color: var(--text-primary, #fff);
}
.user-likes-summary {
    font-size: 11px;
    color: var(--text-secondary);
    letter-spacing: 0.5px;
    text-transform: uppercase;
    margin-bottom: 12px;
}
.user-likes-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 16px;
}
.user-likes-item {
    position: relative; /* anchor for .user-likes-remove in grid mode */
    text-decoration: none;
    color: inherit;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.user-likes-thumb {
    position: relative;
    width: 100%;
    aspect-ratio: 3 / 2;
    background-color: var(--bg-secondary, #161616);
    background-size: cover;
    background-position: center;
    border: 1px solid var(--border, #333);
    overflow: hidden;
    border-radius: var(--radius-media);
}
.user-likes-remove {
    position: absolute;
    top: 6px;
    right: 6px;
    width: 24px;
    height: 24px;
    padding: 0;
    line-height: 1;
    font-size: 18px;
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    border: 0;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.15s ease, background-color 0.15s ease;
    border-radius: var(--radius-sm);
}
.user-likes-item:hover .user-likes-remove,
.user-likes-remove:focus-visible {
    opacity: 1;
}
.user-likes-remove:hover {
    background: rgba(0, 0, 0, 0.85);
}
.user-likes-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0; /* let title/meta ellipsis work inside flex/grid children */
}
.user-likes-title {
    font-size: 9px;
    letter-spacing: 0.4px;
    text-transform: uppercase;
    color: var(--text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Meta line (artist · resolution · time) — hidden in grid, shown in list. */
.user-likes-meta {
    display: none;
    font-size: 10px;
    color: var(--text-secondary);
    letter-spacing: 0.4px;
    text-transform: uppercase;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.user-likes-meta-sep {
    opacity: 0.5;
    margin: 0 2px;
}

/* ---- List view -----------------------------------------------------------
   Same DOM, different layout: one item per row, small thumb on the left,
   title stretching in the middle, meta pinned to the right. Kept tight
   vertically so a whole page of 24 items fits without much scrolling. */
.user-likes-grid.is-list {
    display: flex;
    flex-direction: column;
    gap: 0;
    width: 100%;
}
.user-likes-grid.is-list .user-likes-item {
    flex-direction: row;
    align-items: center;
    gap: 12px;
    padding: 8px 4px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
    transition: background-color 0.15s ease;
}
.user-likes-grid.is-list .user-likes-item:last-child {
    border-bottom: none;
}
.user-likes-grid.is-list .user-likes-item:hover {
    background: var(--bg-secondary, #141414);
}
.user-likes-grid.is-list .user-likes-thumb {
    flex: 0 0 auto;
    width: 72px;
    aspect-ratio: 3 / 2;
}
.user-likes-grid.is-list .user-likes-remove {
    /* Row action at the end of the row instead of overlaying the thumb.
       Stays faintly visible (rather than the grid mode's opacity:0) so the
       row doesn't look like it has an empty gap at the end — brightens on
       hover instead of appearing from nothing. */
    position: static;
    flex: 0 0 auto;
    width: 24px;
    height: 24px;
    font-size: 16px;
    background: transparent;
    color: var(--text-secondary);
    border-radius: var(--radius-sm);
    opacity: 0.5;
}
.user-likes-grid.is-list .user-likes-remove:hover {
    background: rgba(220, 60, 60, 0.15);
    color: #ff5c5c;
    opacity: 1;
}
.user-likes-grid.is-list .user-likes-body {
    flex: 1 1 auto;
    flex-direction: row;
    align-items: baseline;
    gap: 16px;
    min-width: 0;
    /* Fill all remaining horizontal room so meta can sit hard-right. */
}
.user-likes-grid.is-list .user-likes-title {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 12px;
    letter-spacing: 0.5px;
    text-transform: none;
    color: var(--text-primary, #fff);
    /* Keep the ellipsis behaviour inherited from the base rule. */
}
.user-likes-grid.is-list .user-likes-meta {
    display: block;
    flex: 0 0 auto;
    text-align: right;
    max-width: 55%;
}

/* Narrow modal widths — fall back to the wrapped two-row layout so the
   meta line doesn't get crushed against the title. */
@media (max-width: 640px) {
    .user-likes-grid.is-list .user-likes-body {
        flex-direction: column;
        gap: 2px;
        align-items: stretch;
    }
    .user-likes-grid.is-list .user-likes-meta {
        text-align: left;
        max-width: none;
    }
}

/* Infinite-scroll sentinel — needs a small non-zero height so
   IntersectionObserver reliably fires as the user scrolls the profile
   content area to the bottom. Kept invisible; the pre-fetch happens
   via `rootMargin` so the next page loads before it's actually seen. */
.user-likes-sentinel {
    height: 20px;
    width: 100%;
    margin-top: 8px;
    pointer-events: none;
}


/* =============================================
   Compact header search — sits in .wn-category-dropdown-wrap on
   category/series/artist pages, next to the Select + Sort dropdowns.
   Submits to /archive?search=... (site-wide search target).
   ============================================= */
.wn-header-search {
    position: relative;
    display: flex;
    align-items: center;
    /* Appears FIRST in the header row via `order`, then the dropdowns. */
    order: -1;
    flex: 1 1 0;
    min-width: 140px;
    max-width: 320px;
}
.wn-header-search-icon {
    position: absolute;
    left: 10px;
    top: 50%;
    transform: translateY(-50%);
    display: inline-flex;
    color: var(--text-secondary);
    pointer-events: none;
}
.wn-header-search-input {
    width: 100%;
    padding: 10px 12px 10px 32px;
    font-family: inherit;
    font-size: 11px;
    letter-spacing: 0.5px;
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text-primary);
    text-transform: uppercase;
    -webkit-appearance: none;
    appearance: none;
    border-radius: var(--radius-md);
}
.wn-header-search-input::placeholder {
    color: var(--text-secondary);
    letter-spacing: 0.5px;
    opacity: 0.7;
}
.wn-header-search-input:focus {
    outline: none;
    border-color: var(--text-primary);
}
.wn-header-search-input:focus + .wn-header-search-icon,
.wn-header-search:focus-within .wn-header-search-icon {
    color: var(--text-primary);
}

/* Kill the browser-native "X" clear button on <input type="search">.
   Chrome/Safari render a small button that inherits the accent color
   (blue by default); Firefox uses its own reset. Hide them all so the
   input stays visually consistent with the rest of the header controls. */
.wn-header-search-input::-webkit-search-cancel-button,
.wn-header-search-input::-webkit-search-decoration {
    -webkit-appearance: none;
    appearance: none;
    display: none;
}
.wn-header-search-input::-ms-clear {
    display: none;
    width: 0;
    height: 0;
}

/* Intermediate widths (tablets, small laptops): full-width search on its own
   row so it stays comfortable to tap; dropdowns share the row below. */
@media (max-width: 900px) {
    .wn-header-search {
        flex: 1 1 100%;
        min-width: 0;
        max-width: none;
    }
    .wn-category-dropdown {
        flex: 1 1 45%;
        min-width: 0;
    }
    .wn-sort-dropdown {
        flex: 1 1 45%;
        min-width: 0;
    }
}

@media (max-width: 480px) {
    .wn-category-dropdown,
    .wn-sort-dropdown {
        flex: 1 1 100%;
        max-width: none;
    }
}


/* =============================================
   Donate page (/donate + /donate-complete + /donate-cancel)
   ============================================= */
.donate-page,
.static-page,
.takedown-page {
    /* Local stacking context so the background layers stay behind
       page content but in front of body. */
    position: relative;
    isolation: isolate;
    /* Clips the slow-scaling ::before background pan so it never bleeds
       past the fade or introduces a horizontal scrollbar. */
    overflow: hidden;
    padding-top:100px;
}

/* Ambient background — same "infinite wallnode" image the admin login uses,
   pinned to the top of the page. Split across two pseudo-elements so the
   image can slowly drift while the gradient overlay stays perfectly aligned
   with the fold. */
.donate-page::before,
.donate-page::after,
.static-page::before,
.static-page::after,
.takedown-page::before,
.takedown-page::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: -1;
    pointer-events: none;
}

/* Layer 1 — the image itself, slowly Ken Burns'ing between scale(1) and a
   1.12 zoom with a small offset. Cycle is 60s each direction (2 minutes
   round trip) — still slow but visible enough that you can see it drift
   while reading. */
.donate-page::before,
.static-page::before,
.takedown-page::before {
    height: 1000px;
    background: url('../images/infinite-wallnode2.jpg') center / cover no-repeat;
    transform-origin: 50% 40%;
    will-change: transform;
    animation: donatePan 40s ease-in-out infinite alternate;
}

/* Layer 2 — static dark gradient overlay. Kept dark enough that the image
   is a subtle atmospheric texture, not a competing focal element.
   Overlay is TALLER than the image (900px vs 720px) and reaches solid
   bg-primary well before its own bottom, so when the image scales up to
   ~806px it stays fully covered underneath — no exposed band at the seam. */
.donate-page::after,
.static-page::after,
.takedown-page::after {
    height: 1200px;
    background: linear-gradient(
        180deg,
        rgba(10, 10, 10, 0.72) 0%,
        rgba(10, 10, 10, 0.88) 30%,
        rgba(10, 10, 10, 0.93) 55%,
        var(--bg-primary, #0a0a0a) 88%,
        var(--bg-primary, #0a0a0a) 100%
    );
}

@keyframes donatePan {
    from { transform: scale(1)    translate(0,     0);     }
    to   { transform: scale(1.12) translate(-2.5%, -1.5%); }
}

/* Reduced motion: freeze the pan for users who've asked to avoid animation. */
@media (prefers-reduced-motion: reduce) {
    .donate-page::before,
    .static-page::before,
    .takedown-page::before { animation: none; }

    /* The home hero was the loudest thing on the site for this preference:
       a 45s collage pan plus sixteen independent Ken Burns loops, all
       running forever. Killing the animation drops the collage to its
       untransformed position, which the inset overshoot already covers on
       every breakpoint, so no edge comes into view. Declared here rather
       than beside the hero rules because the breakpoint overrides further
       up the file re-assign animation-name and would otherwise win. */
    .hero-strip,
    .hero-ken-a img,
    .hero-ken-b img,
    .hero-ken-c img { animation: none; }
}

.donate-page .wn-container,
.static-page .wn-container,
.takedown-page .wn-container {
    max-width: 1200px;
    position: relative; /* content stays above the pseudo layers */
}

/* Inline prose links inside content pages (donate / takedown / any future
   static page). Without this the UA default sends visited links purple,
   which is jarring against the site's monochrome palette. Scoped to these
   page wrappers so we don't touch archive/category/wallpaper cards where
   links intentionally look different. */
.donate-page .static-lede a,
.donate-page .static-flash-body a,
.donate-page .donate-thanks a,
.static-page .static-lede a,
.static-page .static-flash-body a,
.takedown-page .static-lede a,
.takedown-page .static-flash-body a,
.takedown-page .donate-thanks a {
    color: var(--text-primary, #fff);
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-color: rgba(255, 255, 255, 0.45);
    transition: text-decoration-color 0.15s ease, color 0.15s ease;
}
.donate-page .static-lede a:hover,
.donate-page .static-flash-body a:hover,
.donate-page .donate-thanks a:hover,
.static-page .static-lede a:hover,
.static-page .static-flash-body a:hover,
.takedown-page .static-lede a:hover,
.takedown-page .static-flash-body a:hover,
.takedown-page .donate-thanks a:hover {
    text-decoration-color: var(--text-primary, #fff);
}
.donate-page .static-lede a:visited,
.donate-page .static-flash-body a:visited,
.donate-page .donate-thanks a:visited,
.static-page .static-lede a:visited,
.static-page .static-flash-body a:visited,
.takedown-page .static-lede a:visited,
.takedown-page .static-flash-body a:visited,
.takedown-page .donate-thanks a:visited {
    color: var(--text-primary, #fff);
}

/* Flash banners — shown on complete / cancel returns, form errors, etc. */
.static-flash {
    margin: 0 0 32px;
    padding: 18px 22px;
    border: 1px solid var(--border);
    background: var(--bg-secondary, #161616);
    border-radius: var(--radius-lg);
}
.static-flash-success { border-color: rgba(120, 220, 140, 0.5); }
.static-flash-neutral { border-color: var(--border); }
.static-flash-title {
    font-size: 13px;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--text-primary);
    margin-bottom: 6px;
    font-weight: 500;
}
.static-flash-body {
    font-size: 13px;
    line-height: 1.55;
    color: var(--text-secondary);
}

.static-hero {
    margin: 60px 0 88px;
}
.static-lede {
    max-width: 760px;
    margin: 18px 0 0;
    font-size: 15px;
    line-height: 1.7;
    color: var(--text-secondary);
}

/* Donation methods — array-driven grid, PayPal + QR side-by-side on desktop,
   stacked on mobile. Add more methods to $donateMethods; layout adapts. */
.donate-methods { margin-bottom: 56px; }
.donate-methods-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 20px;
}
.donate-method {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 14px;
    padding: 24px;
    border: 1px solid var(--border);
    background: var(--bg-secondary, #161616);
    border-radius: var(--radius-lg);
}
.donate-method-head {
    display: flex;
    align-items: center;
    gap: 10px;
}
.donate-method-icon {
    display: inline-flex;
    width: 22px;
    height: 22px;
    color: var(--text-secondary);
}
.donate-method-icon svg {
    width: 100%;
    height: 100%;
}
.donate-method-label {
    font-size: 13px;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: var(--text-primary);
    font-weight: 500;
}
.donate-method-note {
    margin: 0;
    font-size: 12px;
    line-height: 1.55;
    color: var(--text-secondary);
}

/* Primary CTA button used across content pages (donate methods, takedown
   submit, any future call-to-action). White fill, dark text, inverts on
   hover — matches the site's overall CTA vocabulary. */
.static-cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 12px 20px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    text-decoration: none;
    background: var(--text-primary);
    color: var(--bg-primary);
    border: 1px solid var(--text-primary);
    transition: background-color 0.2s ease, color 0.2s ease;
    border-radius: var(--radius-md);
}
.static-cta:hover {
    background: transparent;
    color: var(--text-primary);
    font-weight: 600;
}

/* Small print under a .static-cta. Reassures users of security / destination.
   The lock glyph is a Unicode padlock; no image dep. */
.static-cta-hint {
    margin: 2px 0 34px;
    font-size: 10.5px;
    line-height: 1.5;
    letter-spacing: 0.3px;
    color: var(--text-secondary);
    opacity: 0.85;
}
.static-cta-hint::before {
    content: '\1F512'; /* 🔒 */
    margin-right: 6px;
    opacity: 0.75;
    font-size: 10px;
    vertical-align: baseline;
}

/* Embedded payment widgets (e.g. Stripe Buy Button). The Stripe web
   component renders its own styled button; we just give it a consistent
   container. */
.donate-method-embed { width: 100%; }
.donate-method-embed-stripe {
    display: flex;
    justify-content: flex-start;
}

/* Optional QR paired with a CTA. The whole block is one full-width white
   plate at the bottom of the card, holding the "or scan to donate" label +
   the QR image centered inside. Width fixed to card, height auto so non-
   square QR sources (e.g. Stripe's 880×1055 tile) don't get squished. */
.donate-method-qr-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    margin-top: auto;
    padding: 18px 16px;
    background: #fff;
    width: 100%;
    box-sizing: border-box;
}
.donate-method-qr-label {
    font-size: 10px;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    /* Dark text now that the container background is white. */
    color: #555;
}
.donate-method-qr {
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 0;
    max-width: 220px;
    width: 100%;
}
.donate-method-qr img {
    display: block;
    width: 100%;
    height: auto;
    max-width: 200px;
    image-rendering: pixelated;
    -ms-interpolation-mode: nearest-neighbor;
}

/* Cost transparency + benefits sections. */
.donate-transparency,
.donate-benefits { margin-bottom: 48px; }

.donate-transparency-lede {
    margin: 0 0 16px;
    font-size: 13px;
    color: var(--text-secondary);
    line-height: 1.55;
}
.donate-costs {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    /* Sits over the dark background — subtle border wrapper defines the table
       edge without needing a full container. */
    border: 1px solid var(--border);
    background: rgba(10, 10, 10, 0.4);
}
.donate-costs thead th {
    text-align: left;
    padding: 12px 16px;
    font-size: 10px;
    font-weight: 400;
    letter-spacing: 1.5px;
    color: var(--text-secondary);
    background: rgba(255, 255, 255, 0.03);
    border-bottom: 1px solid var(--border);
}
.donate-costs .donate-cost-col-value { text-align: right; }
.donate-costs tbody tr {
    border-bottom: 1px solid var(--border);
}
.donate-costs tbody tr:last-child { border-bottom: none; }
.donate-costs tbody tr:hover { background: rgba(255, 255, 255, 0.02); }
.donate-costs td {
    padding: 14px 16px;
    vertical-align: middle;
}
.donate-cost-label { color: var(--text-secondary); }
.donate-cost-value {
    color: var(--text-primary);
    font-variant-numeric: tabular-nums;
    text-align: right;
    white-space: nowrap;
}
.donate-cost-period {
    color: var(--text-secondary);
    letter-spacing: 0.3px;
    margin-left: 2px;
    font-size: 11px;
}

.donate-benefits-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 16px 28px;
}
.donate-benefits-list li {
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    gap: 14px;
    padding: 14px 0;
    border-top: 1px solid var(--border);
}
.donate-benefit-icon {
    display: inline-flex;
    flex-shrink: 0;
    width: 22px;
    height: 22px;
    margin-top: 1px; /* optical alignment with the title cap-height */
    color: var(--text-secondary);
}
.donate-benefit-icon svg {
    width: 100%;
    height: 100%;
}
.donate-benefit-text {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
}
.donate-benefit-title {
    font-size: 13px;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--text-primary);
}
.donate-benefit-body {
    font-size: 13px;
    line-height: 1.55;
    color: var(--text-secondary);
}

.donate-thanks {
    margin: 40px 0 20px;
    font-size: 13px;
    color: var(--text-secondary);
    line-height: 1.55;
}

/* ============================================
   /takedown — DMCA / copyright takedown form
   Shares the ambient background + hero styling
   with .donate-page (see selectors above); only
   the form + info-sidebar are scoped here.
   ============================================ */

/* Two-column layout: form on the left, guidance cards on the right.
   Collapses to a single stacked column under 900px so long labels don't
   truncate awkwardly. */
.takedown-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: 40px;
    align-items: start;
    margin-bottom: 60px;
}
@media (max-width: 900px) {
    .takedown-layout {
        grid-template-columns: 1fr;
        gap: 28px;
    }
}

.takedown-form-section {
    /* Same card treatment as .donate-method so the form visually reads as a
       single "container" against the dark ambient bg. */
    padding: 28px 28px 20px;
    border: 1px solid var(--border);
    background: var(--bg-secondary, #161616);
    border-radius: var(--radius-lg);
}
@media (max-width: 640px) {
    .takedown-form-section { padding: 20px 18px 14px; }
}

.takedown-form-section .wn-section-title {
    margin-top: 0;
}

/* Honeypot — hide it from users completely without triggering "hidden input
   won't be submitted" logic. Positioned off-screen; tab-index and aria hide
   it from keyboard + screen-reader users too. */
.takedown-honeypot {
    position: absolute;
    left: -9999px;
    top: auto;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* Fieldset groups — separated by hairline dividers to show the three
   phases of the form (You / The material / Sworn statements). */
.takedown-fieldset {
    margin-bottom: 28px;
    padding-bottom: 24px;
    border-bottom: 1px solid var(--border);
}
.takedown-fieldset:last-of-type {
    border-bottom: none;
    padding-bottom: 8px;
}
.takedown-fieldset-title {
    font-size: 11px;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    color: var(--text-secondary);
    margin-bottom: 18px;
}

/* Row of two side-by-side fields. Wraps to a single column on narrow
   screens so labels + inputs don't get squished. */
.takedown-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    margin-bottom: 16px;
}
@media (max-width: 560px) {
    .takedown-row { grid-template-columns: 1fr; }
}

.takedown-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-bottom: 16px;
}
.takedown-field:last-child { margin-bottom: 0; }

.takedown-field label {
    font-size: 11px;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--text-secondary);
}
.takedown-field input[type="text"],
.takedown-field input[type="email"],
.takedown-field textarea {
    width: 100%;
    box-sizing: border-box;
    padding: 10px 12px;
    background: rgba(10, 10, 10, 0.6);
    border: 1px solid var(--border);
    color: var(--text-primary);
    font: inherit;
    font-size: 13px;
    line-height: 1.55;
    transition: border-color 0.15s ease, background-color 0.15s ease;
    /* Undo browser autofill styling so it doesn't turn our fields yellow. */
    -webkit-text-fill-color: var(--text-primary);
    border-radius: var(--radius-md);
}
.takedown-field textarea {
    resize: vertical;
    min-height: 72px;
    font-family: inherit;
}
.takedown-field input:focus,
.takedown-field textarea:focus {
    outline: none;
    border-color: var(--text-primary);
    background: rgba(10, 10, 10, 0.85);
}
.takedown-field input[aria-invalid="true"],
.takedown-field textarea[aria-invalid="true"] {
    border-color: #d24d4d;
}
.takedown-field-error {
    font-size: 11px;
    color: #d97a7a;
    letter-spacing: 0.3px;
    margin-top: 2px;
}

/* Checkbox rows for the sworn statements. Give the label a bit of breathing
   room so the legal copy is readable, and align the box to the top of the
   first line rather than centre. */
.takedown-check {
    display: grid;
    grid-template-columns: 20px 1fr;
    align-items: start;
    gap: 12px;
    margin-bottom: 16px;
}
.takedown-check:last-child { margin-bottom: 0; }
.takedown-check input[type="checkbox"] {
    width: 16px;
    height: 16px;
    margin-top: 2px;
    accent-color: var(--text-primary, #fff);
    cursor: pointer;
}
.takedown-check label {
    font-size: 12.5px;
    line-height: 1.55;
    color: var(--text-secondary);
    cursor: pointer;
    letter-spacing: 0.2px;
    /* No transform — legal statements should read as normal prose. */
    text-transform: none;
}
.takedown-check label a {
    color: var(--text-primary);
    text-decoration: underline;
}
.takedown-check.has-error label { color: #d97a7a; }
.takedown-check .takedown-field-error {
    grid-column: 2;
    margin-top: 4px;
}

.takedown-actions {
    margin-top: 24px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
}
.takedown-submit {
    /* Inherit .static-cta button styling; just remove its narrow
       "text-only" horizontal padding so the label reads clearly. */
    padding: 14px 28px;
    cursor: pointer;
    font-family: inherit;
}
.takedown-submit:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

/* ============================================
   Side panel / side cards
   Generic "right column" container used by the
   takedown form (What to include / What happens
   next / Prefer email), the about page (Contact
   / Legal / More), and any future static page
   that wants a quiet aside next to its main copy.
   ============================================ */

/* Outer wrapper — flex column of cards with vertical rhythm. */
.side-panel {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* Card container. Quiet in tone (semi-transparent bg over the ambient page
   background) so it reads as "guidance" rather than a primary content block. */
.side-card {
    padding: 20px;
    border: 1px solid var(--border);
    background: rgba(10, 10, 10, 0.4);
    border-radius: var(--radius-lg);
}
.side-card-title {
    font-size: 11px;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    color: var(--text-primary);
    margin-bottom: 12px;
}

/* Paragraph body inside a side card. */
.side-card-body {
    margin: 0 0 10px 0;
    font-size: 12.5px;
    line-height: 1.6;
    color: var(--text-secondary);
}
.side-card-body:last-child { margin-bottom: 0; }
.side-card-body a {
    color: var(--text-primary);
    text-decoration: underline;
}

/* Standalone link (e.g. contact email) inside a side card. */
.side-card-link {
    display: inline-block;
    font-size: 12px;
    letter-spacing: 0.3px;
    color: var(--text-primary);
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-color: rgba(255, 255, 255, 0.45);
    word-break: break-all;
}
.side-card-link:hover { text-decoration-color: var(--text-primary); }

/* Bulleted list — short horizontal dash before each item aligned with the
   first line of copy. Used for the guidance bullets on /takedown. */
.side-card-list {
    list-style: none;
    padding: 0;
    margin: 0;
}
.side-card-list li {
    font-size: 12.5px;
    line-height: 1.55;
    color: var(--text-secondary);
    padding: 6px 0 6px 14px;
    position: relative;
}
.side-card-list li::before {
    content: '';
    position: absolute;
    left: 0;
    top: 12px;
    width: 6px;
    height: 1px;
    background: var(--text-secondary);
    opacity: 0.7;
}

/* Flat vertical link stack — no bullets, subdued colour, hovers up to
   text-primary. Used for the Legal / More menus on /about. */
.side-card-linklist {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.side-card-linklist li a {
    font-size: 12.5px;
    color: var(--text-secondary);
    text-decoration: none;
    letter-spacing: 0.3px;
    transition: color 0.15s ease;
}
.side-card-linklist li a:hover { color: var(--text-primary); }

/* ============================================
   /about — About Wallnode page
   Uses the shared .static-page frame; adds a
   two-column body + side layout for the copy.
   ============================================ */

.about-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 40px;
    align-items: start;
    margin-bottom: 80px;
}
@media (max-width: 900px) {
    .about-layout {
        grid-template-columns: 1fr;
        gap: 32px;
    }
}

.about-body {
    display: flex;
    flex-direction: column;
    gap: 36px;
}
/* Each .about-section is a self-contained block (heading + paragraphs +
   optional list). No container styling needed \u2014 the flex gap on .about-body
   handles the vertical rhythm. */
.about-section .wn-section-title {
    margin-bottom: 14px;
}
.about-p {
    margin: 0 0 14px 0;
    font-size: 13px;
    line-height: 1.7;
    color: var(--text-secondary);
    max-width: 760px;
}
.about-p:last-child { margin-bottom: 0; }
.about-p strong {
    color: var(--text-primary);
    font-weight: 500;
}
.about-p em {
    font-style: normal;
    color: var(--text-primary);
    text-transform: uppercase;
    letter-spacing: 1px;
    font-size: 14px;
}
.about-p a {
    color: var(--text-primary);
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-color: rgba(255, 255, 255, 0.45);
}
.about-p a:visited { color: var(--text-primary); }
.about-p a:hover { text-decoration-color: var(--text-primary); }

.about-list {
    list-style: none;
    padding: 0;
    margin: 8px 0 0 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-width: 660px;
}
.about-list li {
    font-size: 13px;
    line-height: 1.65;
    color: var(--text-secondary);
    padding-left: 28px;
    position: relative;
}
/* Subtle bullet \u2014 short horizontal dash aligned with the first line of copy. */
.about-list li::before {
    content: '';
    position: absolute;
    left: 0;
    top: 12px;
    width: 10px;
    height: 1px;
    background: var(--text-secondary);
    opacity: 0.7;
}
.about-list li strong {
    color: var(--text-primary);
    font-weight: 400;
    text-transform: uppercase;
}
.about-list li a {
    color: var(--text-primary);
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-color: rgba(255, 255, 255, 0.45);
}
.about-list li a:visited { color: var(--text-primary); }
.about-list li a:hover { text-decoration-color: var(--text-primary); }


.home-section-title, .wn-page-title, .wn-section-title, .wn-wallpaper-title { font-family:'Space Grotesk', sans-serif; font-weight: 500; text-transform: uppercase; }

/* =============================================
   Blog — post meta + block rendering
   ============================================= */
/* Author / date as pills — same treatment as .wn-tag so the post header
   matches the tag and device chips used elsewhere. */
.blog-post-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 16px;
}
.blog-post-meta-pill {
    font-size: 10px;
    letter-spacing: 1px;
    padding: 6px 12px;
    border: 1px solid var(--border);
    color: var(--text-secondary);
    text-transform: uppercase;
    white-space: nowrap;
    border-radius: var(--radius-sm);
}
.blog-post-draft-badge {
    color: #e0a030;
    border-color: rgba(224, 160, 48, 0.5);
}
.blog-post-body { max-width: 1200px; margin: 40px auto 60px; }

/* =============================================
   Blog listing — uniform cards
   /blog reuses the archive's masonry-like grid classes, but that grid's
   fixed 200px row tracks + flex:1 image wrapper make a long post title eat
   into its own cover image (equal cells, unequal thumbs). Blog exception:
   neutralize every span rule, let rows size to content, and pin all covers
   to one fixed aspect so every thumb is the same width and height.
   ============================================= */
.blog-page .wn-wallpaper-grid { grid-auto-rows: auto; }
.blog-page .wn-grid-item {
    grid-row: auto !important;
    grid-column: auto !important;
}
.blog-page .wn-grid-image-wrapper {
    flex: none;
    aspect-ratio: 16/9;
}

/* Comments under a post — reuses the wallpaper page's .wn-comment-* system;
   only the section shell and its heading are blog-specific. The heading's
   "Comments (N)" text is live — js/comments.js bumps the count in place. */
.blog-post-comments { max-width: 1200px; margin: 0 auto 60px; padding-top: 32px; border-top: 1px solid var(--border); }
.wn-comments-title {
    font-family: 'Space Grotesk', sans-serif;
    font-weight: 500;
    font-size: 18px;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--text-primary);
    margin: 0 0 24px 0;
}

.blog-post-back { max-width: 1200px; margin: 0 auto 60px; }
.blog-post-back a { color: var(--text-secondary); text-decoration: none; font-size: 13px; display: inline-flex; align-items: center; gap: 6px; }
.blog-post-back a:hover { color: var(--text-primary); }
.blog-post-back a svg { width: 14px; height: 14px; flex-shrink: 0; }

.blog-block { margin-bottom: 28px; overflow: hidden; }
.blog-block--divider { border-top: 1px solid var(--border); padding-top: 28px; }

.blog-block-text p { font-size: 15px; line-height: 1.8; color: var(--text-secondary); margin: 0 0 16px 0; }
.blog-block-text h2 { font-size: 26px; color: var(--text-primary); margin: 32px 0 16px; font-weight: 400; text-transform: uppercase; letter-spacing: 0.5px; }
.blog-block-text h3 { font-size: 20px; color: var(--text-primary); margin: 24px 0 12px; font-weight: 400; }
/* Custom-drawn markers (not the browser's default ::marker) — avoids any
   browser/font-specific rendering quirk with generated counter glyphs and
   gives full control over spacing instead of the cramped default indent. */
.blog-block-text ul, .blog-block-text ol {
    font-size: 15px;
    line-height: 1.8;
    color: var(--text-secondary);
    margin: 0 0 16px 0;
    padding-left: 0;
    list-style: none;
}
.blog-block-text li {
    position: relative;
    padding-left: 28px;
    margin-bottom: 8px;
}
.blog-block-text li:last-child { margin-bottom: 0; }
.blog-block-text ul li::before {
    content: '';
    position: absolute;
    left: 6px;
    top: 0.7em;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--text-secondary);
}
.blog-block-text ol { counter-reset: blog-ol; }
.blog-block-text ol li { counter-increment: blog-ol; }
.blog-block-text ol li::before {
    content: counter(blog-ol) '.';
    position: absolute;
    left: 0;
    top: 0;
    min-width: 22px;
    color: var(--text-primary);
    font-weight: 500;
}
.blog-block-text blockquote { margin: 0 0 16px 0; padding: 4px 0 4px 18px; border-left: 3px solid var(--border); color: var(--text-secondary); font-style: italic; }
.blog-block-text a { color: var(--text-primary); text-decoration: underline; text-decoration-color: var(--border); }
.blog-block-text a:hover { text-decoration-color: var(--text-primary); }

.blog-block-image img { width: 100%; height: auto; display: block; border-radius: var(--radius-media); }
.blog-block-image--left { max-width: 50%; float: left; margin: 0 24px 16px 0; }
.blog-block-image--right { max-width: 50%; float: right; margin: 0 0 16px 24px; }
.blog-block-image--center { max-width: 100%; }
.blog-block-image-caption { font-size: 12px; color: var(--text-secondary); margin-top: 8px; text-align: left; }

.blog-block-feature-title { display: block; font-size: 18px; color: var(--text-primary); text-decoration: none; margin-bottom: 12px; }
.blog-block-feature-title:hover { text-decoration: underline; }
.blog-block-feature-image-link img { width: 100%; display: block; margin-bottom: 12px; border-radius: var(--radius-media); }
.blog-block-feature-caption { font-size: 14px; color: var(--text-secondary); margin-bottom: 12px; }
.blog-block-feature-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-bottom: 12px; }
.blog-block-feature-grid-item img { width: 100%; aspect-ratio: 16/9; object-fit: cover; display: block; border-radius: var(--radius-media); }
/* Odd leftover (3, 5, 7 items): span the full row as a panoramic strip
   instead of leaving an empty cell. 32/9 at full width is the same height
   as the 16/9 half-width cells above it, so rows stay uniform. A lone
   single item gets the full-width 16/9 hero treatment instead. */
.blog-block-feature-grid-item:nth-child(odd):last-child { grid-column: 1 / -1; }
.blog-block-feature-grid-item:nth-child(odd):last-child img { aspect-ratio: 32/9; }
.blog-block-feature-grid-item:only-child img { aspect-ratio: 16/9; }
.blog-block-feature-cta { display: inline-block; padding: 10px 20px; border: 1px solid var(--border); color: var(--text-primary); text-decoration: none; font-size: 11px; letter-spacing: 1.5px; text-transform: uppercase; border-radius: var(--radius-md); }
.blog-block-feature-cta:hover { border-color: var(--text-primary); background: var(--bg-secondary); }

.blog-block-divider { border: none; border-top: 1px solid var(--border); margin: 0; }
.blog-block-divider--small { margin: 16px 0; }
.blog-block-divider--medium { margin: 28px 0; }
.blog-block-divider--large { margin: 48px 0; }
.blog-block-spacer--small { height: 16px; }
.blog-block-spacer--medium { height: 28px; }
.blog-block-spacer--large { height: 48px; }

@media (max-width: 640px) {
    .blog-block-image--left, .blog-block-image--right { max-width: 100%; float: none; margin: 0 0 16px 0; }
    .blog-block-feature-grid { grid-template-columns: 1fr; }
    /* Single-column: every item is full width already — no panoramic crop. */
    .blog-block-feature-grid-item:nth-child(odd):last-child img { aspect-ratio: 16/9; }
}
/* ============================================
   /app - WALLNODE for Windows
   ============================================ */

/* The hero subtitle sits between a title with 8px under it and a lede with 18px
   over it, so a paragraph's own margins would double the gap at both ends. */
.static-hero .wn-page-subtitle { margin: 0; }

/* The download button. Its own block rather than a line of prose: .about-p a
   colours prose links at a higher specificity than .wn-btn-primary, which put
   white text on a white button. */
.app-download { margin: 0 0 16px; }
.app-download .wn-btn { gap: 10px; }
.app-download-mark { width: 13px; height: 13px; flex: 0 0 auto; }

/* The two lines under the button: version, size, requirements, then the hash.
   Same size as each other deliberately - the hash is a fact about the download,
   not a footnote, and setting it smaller made it read as one. */
.app-meta {
    margin: 0 0 6px 0;
    font-size: 13px;
    line-height: 1.7;
    color: var(--text-secondary);
}
.app-meta:last-child { margin-bottom: 0; }

/* 64 characters, which has to wrap on a phone rather than push the page
   sideways. */
.app-hash { word-break: break-all; }

/* The download sits inside .static-hero, under a lede that carries 18px of its
   own top margin — so the gap here is the one between prose and a call to
   action, not the hero's internal rhythm. */
.static-hero .app-download { margin-top: 34px; }
.static-hero .app-download-none { margin-top: 34px; }

/* ── The screenshot tour ──────────────────────────────────────────────────
   Four screens, each captured in both app themes, sharing wallpaper.php's tab
   component. The captures are windows with their own drop shadow on a
   transparent ground, so there is deliberately no frame, border or background
   here: a second edge around a window that already has one reads as a mistake,
   and on #0a0a0a the shadow simply disappears. */
.app-shots {
    margin: 0 0 80px;
    max-width: 100%;
}

/* The tabs and the theme switch share one rule, which is why the underline
   belongs to the bar rather than to .wn-tabs-strip: a border on the strip
   alone would stop halfway across the page. The active tab's -1px bottom
   margin still lands on it. */
.app-shots-bar {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 24px;
    flex-wrap: wrap;
    border-bottom: 1px solid var(--border);
    margin: 0 0 28px;
}
.app-shots .wn-tabs-strip {
    border-bottom: 0;
    margin-bottom: 0;
}

.app-shots-theme {
    display: inline-flex;
    border: 1px solid var(--border);
    margin-bottom: 12px;
    border-radius: var(--radius-md);
    overflow: hidden;
}
.app-shots-theme-btn {
    font: inherit;
    font-size: 10px;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    background: transparent;
    border: 0;
    color: var(--text-secondary);
    padding: 7px 14px;
    cursor: pointer;
    transition: color 0.15s, background 0.15s;
}
.app-shots-theme-btn + .app-shots-theme-btn { border-left: 1px solid var(--border); }
.app-shots-theme-btn:hover { color: var(--text-primary); }
.app-shots-theme-btn:focus-visible { outline: none; color: var(--text-primary); }
.app-shots-theme-btn.is-active {
    background: var(--text-primary);
    color: var(--bg-primary);
}

/* A terminal command with a COPY button: the winget install line. The button
   is the DARK / LIGHT toggle's, so the page has one small-button style. The
   command wraps rather than scrolls on a phone - a hidden half of a command is
   worse than a broken line, and COPY takes it whole either way. */
.app-cmd {
    display: flex;
    align-items: center;
    gap: 16px;
    width: fit-content;
    max-width: 100%;
    margin: 0 0 18px;
    padding: 8px 8px 8px 16px;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}
.app-cmd code {
    color: var(--text-primary);
    font-size: 14px;
    overflow-wrap: anywhere;
}
.app-cmd-copy {
    flex: 0 0 auto;
    min-width: 72px;
    font: inherit;
    font-size: 10px;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-secondary);
    padding: 7px 14px;
    cursor: pointer;
    transition: color 0.15s, background 0.15s, border-color 0.15s;
    border-radius: var(--radius-sm);
}
.app-cmd-copy:hover,
.app-cmd-copy:focus-visible { outline: none; color: var(--text-primary); border-color: var(--text-primary); }
.app-cmd-copy.is-copied {
    background: var(--text-primary);
    border-color: var(--text-primary);
    color: var(--bg-primary);
}

.app-shot { margin: 0; }
/* Both themes are in the panel; one is shown. The width/height attributes on
   each <img> reserve its box, so the light shot arriving late after a theme
   switch does not shift the caption. */
.app-shot picture { display: none; }
.app-shot .app-shot-dark { display: block; }
.app-shots.is-light .app-shot .app-shot-dark { display: none; }
.app-shots.is-light .app-shot .app-shot-light { display: block; }
.app-shot img {
    display: block;
    width: 100%;
    height: auto;
}
/* ── Settings modals on a stage ───────────────────────────────────────────
   The two settings captures are a modal over the app window, 1327 of its 1930
   pixels across. Stretched to the width of a whole window they read as a
   different and much larger program, so they are drawn at that real fraction
   (as an inline width, from the capture's own dimensions) over the home screen
   blurred back — which is what the modal actually opens on top of.

   The stage keeps the app window's aspect ratio, so all four panels are the
   same height and switching tabs moves nothing below them. */
.app-shot-stage {
    position: relative;
    aspect-ratio: 1930 / 1240;
    overflow: hidden;
}
.app-shot-backdrop { position: absolute; inset: 0; }
/* The <picture> has to be stretched too, not just the image inside it: a
   percentage height on an img resolves against its parent, and an auto-height
   <picture> gives it nothing to resolve against. */
.app-shot-backdrop picture { position: absolute; inset: 0; }
/* contain, and no transform: the backdrop is the same capture at the same size
   the Library tab shows it, which is the whole point of it — the window the
   modal opens on top of, not a crop of one. The blur only has to say "behind" —
   the brightness is what pushes it back, and past about 8px the window stops
   being recognisable as the one on the Library tab. Soft edges where the blur
   samples the capture's own transparent margin are the window's shape, not an
   artefact. */
.app-shot-backdrop img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    filter: blur(4px) brightness(0.45) saturate(0.85);
}
/* The light capture is a white UI; the same brightness that pushes the dark one
   back would leave this one glaring through the modal sitting on it. */
.app-shots.is-light .app-shot-backdrop img {
    filter: blur(4px) brightness(0.55) saturate(0.85);
}
.app-shot-modal {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: var(--shot-scale, 100%);
}
/* The captures carry their own drop shadow, but it was drawn against a desktop,
   not against sixteen pixels of blur. This is what separates the two planes. */
.app-shot-modal picture { filter: drop-shadow(0 24px 48px rgba(0, 0, 0, 0.55)); }

.app-shot-caption {
    margin: 20px 0 0;
    font-size: 13px;
    line-height: 1.7;
    color: var(--text-secondary);
    max-width: 760px;
}

/* Below this the stage stops helping: 69% of a phone is a 240px-wide settings
   modal, unreadable, and the argument for the stage — that a modal blown up to
   window size reads as a bigger program — does not apply at a width where
   nothing looks big. The modal takes the full column and the backdrop goes. */
@media (max-width: 760px) {
    .app-shot-stage {
        aspect-ratio: auto;
        overflow: visible;
    }
    .app-shot-backdrop { display: none; }
    .app-shot-modal {
        position: static;
        transform: none;
        width: 100%;
    }
    .app-shot-modal picture { filter: none; }
}

/* Four uppercase labels at 14px do not fit a phone. They scroll sideways
   instead of wrapping, because a wrapped tab strip puts the underline in two
   places at once. */
@media (max-width: 640px) {
    /* column-reverse, not column: the bar's border-bottom is the tab strip's
       underline, and the active tab's -1px bottom margin only lands on it if
       the strip is the last thing in the box. */
    .app-shots-bar {
        align-items: stretch;
        flex-direction: column-reverse;
        gap: 0;
    }
    .app-shots .wn-tabs-strip {
        gap: 20px;
        overflow-x: auto;
        scrollbar-width: none;
    }
    .app-shots .wn-tabs-strip::-webkit-scrollbar { display: none; }
    .app-shots .wn-tab {
        font-size: 12px;
        white-space: nowrap;
    }
    .app-shots-theme {
        align-self: flex-start;
        margin: 0 0 16px;
    }
}

/* One version in the changelog. .about-p has no top margin and .about-list no
   bottom one, so consecutive versions collided: the heading sat flush against
   the last bullet of the version above it. */
.app-release + .app-release { margin-top: 28px; }
.app-release-title {
    margin: 0;
    font-size: 13px;
    color: var(--text-primary);
}
.app-release-date {
    margin-left: 10px;
    font-size: 12px;
    color: var(--text-secondary);
}

/* The release index in the sidebar, newest first. */
.app-releases {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
}
/* Two lines per release: the version and date, then its download. One line
   held all three and a 300px column could not: version, date, a current badge
   and a size ran into each other at 12.5px. */
.app-releases li {
    display: flex;
    flex-direction: column;
    gap: 3px;
}
.app-releases-head {
    display: flex;
    align-items: baseline;
    gap: 8px;
}
.app-releases-head a {
    display: flex;
    align-items: baseline;
    gap: 8px;
    text-decoration: none;
    transition: color 0.15s ease;
}
.app-releases-v {
    font-size: 12.5px;
    color: var(--text-secondary);
    letter-spacing: 0.3px;
}
.app-releases-head a:hover .app-releases-v { color: var(--text-primary); }
.app-releases-d {
    font-size: 11px;
    color: var(--text-secondary);
    opacity: 0.7;
}
.app-releases-now {
    font-size: 10px;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--text-primary);
    border: 1px solid var(--border);
    padding: 1px 6px;
    border-radius: var(--radius-sm);
}

/* The per-version download, in both places it appears. No global anchor reset
   exists on this site, so an unstyled link here would come out browser blue and
   underlined. */
.app-releases-dl {
    font-size: 11px;
    letter-spacing: 0.3px;
    color: var(--text-secondary);
    text-decoration: none;
    border-bottom: 1px solid var(--border);
    align-self: flex-start;
    transition: color 0.15s ease, border-color 0.15s ease;
}
.app-releases-dl:hover {
    color: var(--text-primary);
    border-bottom-color: var(--text-primary);
}

/* The same link under a What's new entry, where there is room for the version
   number in the label and the size beside it. */
.app-release-get {
    margin: 6px 0 0 0;
    font-size: 12px;
}
.app-release-get a {
    color: var(--text-secondary);
    text-decoration: none;
    border-bottom: 1px solid var(--border);
    transition: color 0.15s ease, border-color 0.15s ease;
}
.app-release-get a:hover {
    color: var(--text-primary);
    border-bottom-color: var(--text-primary);
}
.app-release-size {
    margin-left: 10px;
    color: var(--text-secondary);
    opacity: 0.7;
}

/* ============================================
   /terms, /privacy, /license — legal documents
   Same copy as the footer modals (includes/legal/*-body.php),
   so the modal typography is mirrored here and a document
   reads identically whether it is opened as a page or a modal.
   ============================================ */

.legal-body .info-modal-paragraph {
    margin: 0 0 18px 0;
    font-size: 13px;
    line-height: 1.7;
    color: var(--text-secondary);
    max-width: 760px;
    text-align: left;
}
.legal-body .info-modal-paragraph:last-child { margin-bottom: 0; }

/* Section labels ("1. Acceptance", "What you can do:") sit on their own line,
   matching the modal treatment. */
.legal-body .info-modal-paragraph strong {
    display: block;
    font-size: 13px;
    font-weight: 500;
    letter-spacing: 1.5px;
    color: var(--text-primary);
    margin-top: 28px;
    margin-bottom: 8px;
}
.legal-body .info-modal-paragraph:first-of-type strong { margin-top: 0; }

.legal-body .info-modal-paragraph a {
    color: var(--text-primary);
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-color: rgba(255, 255, 255, 0.45);
}
.legal-body .info-modal-paragraph a:visited { color: var(--text-primary); }
.legal-body .info-modal-paragraph a:hover { text-decoration-color: var(--text-primary); }

/* The document you are currently reading, in the side panel list. */
.side-card-linklist li a[aria-current="page"] {
    color: var(--text-primary);
}
