/* =========================================================================
   www.vanille.de — three voices.

   Barlow reads, IBM Plex Sans Condensed announces, Vollkorn speaks: the
   navigation and every quotation stay with the serif, which is also the
   only face here with real small caps. Plex Condensed has none, so the
   remaining caps voices (hero, labels, table heads) use letterspaced
   uppercase instead.

   Both display faces are subset locally with every layout feature kept —
   see tools/build-fonts.py. Note the Plex sources come from IBM, not from
   Google Fonts, which still ships a v1.3 without oldstyle figures.
   ========================================================================= */

:root {
    color-scheme: light dark;

    /* -- Brand -- */
    --highlight-color: #5F65B2;
    --secondary-color: #8B362A;
    --lightgray-color: #AAAAAA;
    --midgray-color:   #888888;
    --darkgray-color:  #444444;

    /* -- Surfaces & ink -- */
    --paper:      #FDFCFA;
    --surface:    #F5F3EF;
    --ink:        #2B2A28;
    --ink-muted:  #6E6B66;
    --rule:       #E4E0D9;
    --amber:      #C0761C;
    --shot-frame: #021A40;
    --scribble-bg: #FBF7EF;
    --scribble-frame: #E3D6C2;

    /* -- Families -- */
    --font-text:    'Barlow', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --font-display: 'IBM Plex Sans Condensed', 'Helvetica Neue Condensed', sans-serif;
    --font-accent:  'Vollkorn', Georgia, 'Times New Roman', serif;
    --font-mono:    'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;

    /* -- Fluid modular scale, ratio ~1.25, across a 20rem → 64rem viewport -- */
    --step--2: clamp(0.72rem, 0.70rem + 0.10vw, 0.78rem);
    --step--1: clamp(0.86rem, 0.82rem + 0.18vw, 0.95rem);
    --step-0:  clamp(1.06rem, 1.00rem + 0.30vw, 1.20rem);
    --step-1:  clamp(1.24rem, 1.14rem + 0.50vw, 1.50rem);
    --step-2:  clamp(1.45rem, 1.28rem + 0.85vw, 1.88rem);
    --step-3:  clamp(1.70rem, 1.42rem + 1.40vw, 2.34rem);
    --step-4-sm: clamp(1.95rem, 1.60rem + 1.75vw, 2.75rem);
    --step-4:  clamp(2.60rem, 1.20rem + 7.00vw, 4.80rem);

    /* -- Reading measure: ~68 characters of Barlow -- */
    --measure: 40rem;

    /* -- Body leading, referenced by the float alignment below -- */
    --leading: 1.65;

    /* Prose uses justified text with automatic hyphenation. */
    --prose-align: justify;
}

@media (prefers-color-scheme: dark) {
    :root {
        --highlight-color: #A3A8E8;
        --secondary-color: #D98A78;
        --midgray-color:   #9A948C;
        --darkgray-color:  #C8C3BA;

        --paper:      #16151A;
        --surface:    #201F26;
        --ink:        #E4E0D8;
        --ink-muted:  #9A948C;
        --rule:       #35333C;
        --amber:      #E0A356;
        --shot-frame: #4A5878;
        --scribble-bg: #211E19;
        --scribble-frame: #3E362A;
    }
}

/* -- Base -------------------------------------------------------------- */

*, *::before, *::after {
    box-sizing: border-box;
}

html {
    -webkit-text-size-adjust: 100%;
}

body {
    font-family: var(--font-text);
    font-size: var(--step-0);
    font-weight: 400;
    line-height: var(--leading);
    margin: 3.125rem 1.5rem;
    color: var(--ink);
    background: var(--paper);

    /* Real 400/600 roman and italic ship for both families, so the browser
       never needs to fake a weight or a slant. */
    font-synthesis: none;
    font-variant-ligatures: common-ligatures contextual;
    font-kerning: normal;
    text-rendering: optimizeLegibility;
    hanging-punctuation: first allow-end last;
}

::selection {
    background: color-mix(in oklab, var(--highlight-color) 28%, var(--paper));
    color: var(--ink);
}

:focus-visible {
    outline: 2px solid var(--highlight-color);
    outline-offset: 3px;
    border-radius: 2px;
}

a {
    color: var(--secondary-color);
    text-decoration: none;
    transition: color 0.2s, text-decoration-color 0.2s;
}

a:hover {
    color: var(--highlight-color);
}

img {
    max-width: 100%;
    height: auto;
}

.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    padding: 0.5rem 1rem;
    background: var(--highlight-color);
    color: var(--paper);
    z-index: 100;
}

.skip-link:focus {
    left: 0;
}

/* -- Layout ------------------------------------------------------------
   The page is a grid with a named reading column. Prose sits in `text`;
   figures, code, tables and app entries break out into `wide`; the hero
   and the pagination span `full`.                                        */

header, footer {
    max-width: 64rem;
    margin: 0 auto;
    padding: 1.25rem 1.875rem;
}

/* The page is a plain block whose content box *is* the measure, not a grid:
   several pages float a .thumbnail and wrap text around it, and a grid item
   ignores `float` entirely. Wide elements escape with negative margins. */
div.page {
    --gutter: 1.875rem;
    max-width: calc(var(--measure) + 2 * var(--gutter));
    margin: 0 auto;
    padding: 1.25rem var(--gutter);
}

/* How far a wide element may spill past the measure, once the viewport can
   afford it either side. */
div.page {
    --bleed: 0rem;
}

@media (min-width: 56rem) {
    div.page { --bleed: 4rem; }
}

@media (min-width: 68rem) {
    div.page { --bleed: 6rem; }
}

:is(div.page, .blog-post) > :is(pre, table, hr,
                                .image-gallery, .image-group,
                                .app-entry,
                                p:has(> img:only-child):not(:has(.thumbnail, .app-icon)),
                                p:has(> a:only-child > img:only-child):not(:has(.thumbnail, .app-icon))) {
    width: calc(100% + 2 * var(--bleed));
    margin-inline: calc(-1 * var(--bleed));
}

/* Figures carry an intended width of their own, so they get centred rather
   than pinned to the left edge of the widened area. A percentage margin
   resolves against the container, so (100% - w)/2 centres the figure whether
   w is narrower than the measure or spills past it -- a flat negative margin
   would override the `auto` these used to centre with. */
:is(div.page, .blog-post) > figure {
    --fig-fit: min(var(--fig-w, calc(100% + 2 * var(--bleed))),
                   calc(100% + 2 * var(--bleed)));
    width: var(--fig-fit);
    margin-inline: calc((100% - var(--fig-fit)) / 2);
}

/* A tall float must not push into the next section's heading. h3 and below
   are excluded on purpose: entries like the book list on /words/ put a
   floated cover first and expect the entry's own heading beside it. */
div.page :is(h1, h2) {
    clear: both;
}

/* -- Prose -------------------------------------------------------------- */

div.page p {
    text-align: var(--prose-align);
    text-wrap: pretty;
    hyphens: auto;
    hyphenate-limit-chars: 7 3 3;
    margin: 0 0 1.15em;
}

div.page .blog-post p {
    /* WebKit's `pretty` avoids useful breaks in justified prose, opening
       up word spaces. Normal wrapping accepts more hyphenation and keeps
       both edges aligned. Captions and lists retain `pretty`. */
    text-wrap: wrap;
    hyphenate-limit-chars: 6 2 3;
    -webkit-hyphenate-limit-before: 2;
    -webkit-hyphenate-limit-after: 3;
}

div.page :is(ul, ol) {
    margin: 0 0 1.15em;
    padding-left: 1.4em;
}

div.page li {
    margin-bottom: 0.35em;
    text-wrap: pretty;
}

div.page strong {
    font-weight: 600;
}

div.page blockquote {
    margin: 0.6em 0 1.2em;
    padding-left: 1.1em;
    border-left: 2px solid var(--rule);
    font-family: var(--font-accent);
    font-style: italic;
    font-size: var(--step-1);
    line-height: 1.42;
    color: var(--darkgray-color);
    font-variant-numeric: oldstyle-nums proportional-nums;
}

div.page blockquote p {
    text-align: left;
}

/* -- Headings ----------------------------------------------------------
   A condensed face buys width: headings can run a step larger and still
   fit on one line, which is the whole point of the pairing. h1–h3 stay in
   case with slightly negative tracking; h4 drops to letterspaced uppercase
   so the smallest level reads as a label rather than a shrunken headline. */

div.page :is(h1, h2, h3, h4) {
    font-family: var(--font-display);
    font-weight: 600;
    line-height: 1.12;
    text-wrap: balance;
    hyphens: none;
    font-variant-numeric: oldstyle-nums proportional-nums;
}

div.page h1 {
    font-size: var(--step-4-sm);
    color: var(--highlight-color);
    letter-spacing: -0.012em;
    margin: 0 0 0.4em;
}

div.page h2 {
    font-size: var(--step-2);
    color: var(--secondary-color);
    letter-spacing: -0.008em;
    margin: 0.6em 0 0.3em;
}

div.page h3 {
    font-size: var(--step-1);
    color: var(--ink);
    letter-spacing: -0.003em;
    margin: 0.55em 0 0.25em;
}

div.page h4 {
    font-size: var(--step--1);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--ink-muted);
    margin: 0.7em 0 0.3em;
}

div.page :is(h1, h2, h3) > a {
    text-decoration: none;
}

div.page :is(h1, h2, h3) > a:hover {
    text-decoration: underline;
    text-decoration-thickness: 0.05em;
    text-underline-offset: 0.14em;
}

/* Body links carry a real underline; navigational chrome does not. */

div.page :is(p, li, blockquote, td) a {
    text-decoration: underline;
    text-decoration-thickness: 0.06em;
    text-underline-offset: 0.15em;
    text-decoration-color: color-mix(in oklab, currentColor 35%, transparent);
}

div.page :is(p, li, blockquote, td) a:hover {
    text-decoration-color: currentColor;
}

/* Bylines and pagination are chrome, not prose. */
div.page .meta a,
div.page .pagination a {
    text-decoration: none;
}

/* -- Header & Nav ------------------------------------------------------- */

/* The rule is structure, not emphasis: it matches the footer's hairline so
   the accent colour is reserved for the active item's underline. */
header {
    border-bottom: 1px solid var(--rule);
    font-family: var(--font-accent);
    font-size: var(--step-0);
    text-align: center;
    color: var(--darkgray-color);
    padding-bottom: 1rem;
    margin-bottom: 0.5rem;
}

header h1 {
    color: var(--secondary-color);
    margin: 0;
    font-family: var(--font-accent);
    font-size: var(--step-2);
    font-variant-caps: small-caps;
    letter-spacing: 0.03em;
}

header nav ul {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.25rem;
}

header nav ul li {
    margin: 0;
    padding: 0;
}

header nav ul li a {
    display: inline-block;
    padding: 0.2em 0.7em;
    color: var(--darkgray-color);
    font-variant-caps: small-caps;
    letter-spacing: 0.045em;
    border-bottom: 2px solid transparent;
    transition: color 0.2s, border-color 0.2s;
}

/* The house sits in the line box like a small-cap word, so its underline
   lands on the same baseline rule as its neighbours. */
header nav ul li a.nav-home {
    padding-inline: 0.6em;
}

header nav ul li a.nav-home svg {
    display: inline-block;
    width: 1.05em;
    height: 1.05em;
    vertical-align: -0.16em;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}

header nav ul li a:hover,
header nav ul li.active a {
    color: var(--highlight-color);
    border-bottom-color: var(--highlight-color);
}

/* -- Footer ------------------------------------------------------------- */

footer {
    border-top: 1px solid var(--rule);
    font-family: var(--font-text);
    font-size: var(--step--2);
    letter-spacing: 0.04em;
    text-align: center;
    color: var(--midgray-color);
    margin-top: 2.5rem;
    padding-top: 1.25rem;
}

footer a {
    color: var(--midgray-color);
}

footer a:hover {
    color: var(--highlight-color);
}

/* -- Blog --------------------------------------------------------------- */

.blog-post {
    margin-bottom: 2rem;
    padding-bottom: 2rem;
    border-bottom: 1px solid var(--rule);
}

.blog-post:last-child {
    border-bottom: none;
}

.blog-post .post-title {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: var(--step-4-sm);
    line-height: 1.12;
    color: var(--highlight-color);
    letter-spacing: -0.012em;
    text-wrap: balance;
    hyphens: none;
    margin: 0.2em 0 0.1em;
}

/* On the listing the entry title sits one level below the page's own
   heading, so it steps down a size; on the article itself it is the h1. */
.blog-post h2.post-title {
    font-size: var(--step-3);
}

.blog-post .post-title a {
    color: var(--highlight-color);
}

.blog-post .post-title a:hover {
    color: var(--secondary-color);
}

.blog-post .meta {
    font-family: var(--font-text);
    font-size: var(--step--1);
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.03em;
    color: var(--midgray-color);
    margin-bottom: 1.4em;
}

.blog-post .meta a {
    color: var(--midgray-color);
}

.blog-post .meta a:hover {
    color: var(--highlight-color);
}

/* -- Figures ------------------------------------------------------------ */

div.page figure {
    margin-block: 0.4em 1.6em;
}

figcaption,
.blog-post figure.scribble figcaption,
.blog-post figure.product-shot figcaption {
    margin-top: 0.5rem;
    font-family: var(--font-text);
    font-size: var(--step--1);
    font-style: italic;
    color: var(--midgray-color);
    text-wrap: pretty;
}

.blog-post figure.scribble {
    --fig-w: 920px;
    margin-block: 2rem;
    text-align: center;
}

.blog-post figure.scribble img {
    display: block;
    width: 100%;
    height: auto;
    border: 1px solid var(--scribble-frame);
    border-radius: 10px;
    background: var(--scribble-bg);
}

.blog-post figure.scribble img.image-thumbnail {
    max-width: none;
}

.blog-post figure.project-hero,
.blog-post figure.product-shot {
    margin-block: 2rem;
    text-align: center;
}

.blog-post figure.project-hero {
    --fig-w: 540px;
}

.blog-post figure.project-hero img,
.blog-post figure.product-shot img {
    display: block;
    width: 100%;
    height: auto;
    box-sizing: border-box;
}

.blog-post figure.project-hero img {
    border-radius: 18px;
}

.blog-post figure.product-shot {
    --fig-w: 760px;
}

.blog-post figure.product-shot-narrow {
    --fig-w: 350px;
}

.blog-post figure.product-shot-medium {
    --fig-w: 520px;
}

.blog-post figure.product-shot-panel-crop img {
    height: 285px;
    object-fit: cover;
    object-position: top;
}

.blog-post figure.product-shot img.image-thumbnail {
    max-width: none;
}

.blog-post h2.module-heading {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    margin-top: 2.2rem;
}

.blog-post h2.module-heading img {
    width: 64px;
    height: 64px;
    flex: 0 0 64px;
    object-fit: cover;
    border-radius: 14px;
    box-shadow: 0 2px 7px rgba(0, 0, 0, 0.18);
}

@media (max-width: 560px) {
    .blog-post h2.module-heading {
        gap: 0.65rem;
    }

    .blog-post h2.module-heading img {
        width: 52px;
        height: 52px;
        flex-basis: 52px;
        border-radius: 12px;
    }
}

/* -- Comments ----------------------------------------------------------- */

.comments {
    margin-top: 3rem;
}

.comments-toggle {
    font-family: var(--font-display);
    font-size: var(--step--1);
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--darkgray-color);
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 0.55em 1.2em;
    cursor: pointer;
    transition: background 0.2s, color 0.2s, border-color 0.2s;
}

.comments-toggle:hover {
    background: var(--highlight-color);
    border-color: var(--highlight-color);
    color: var(--paper);
}

/* -- Apps: legal footnote ----------------------------------------------- */

.legal-note {
    margin-top: 2rem;
    padding: 1rem 1.25rem;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: 6px;
    font-size: var(--step--1);
}

/* -- Pagination --------------------------------------------------------- */

.pagination {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 4px;
    margin: 2.5rem 0 0;
    font-family: var(--font-text);
    font-size: var(--step--1);
    font-variant-numeric: tabular-nums;
}

.pagination a,
.pagination .disabled,
.pagination .page-num,
.pagination .ellipsis {
    display: inline-block;
    padding: 0.3em 0.65em;
    border-radius: 4px;
    transition: background 0.2s, color 0.2s;
}

.pagination a {
    border: 1px solid var(--rule);
    color: var(--darkgray-color);
}

.pagination a:hover {
    background: var(--highlight-color);
    color: var(--paper);
    border-color: var(--highlight-color);
}

.pagination .page-num.current {
    background: var(--highlight-color);
    color: var(--paper);
    border: 1px solid var(--highlight-color);
}

.pagination .disabled {
    color: var(--rule);
    border: 1px solid var(--rule);
}

.pagination .ellipsis {
    color: var(--midgray-color);
    border: none;
    padding: 0.3em 0.25em;
}

/* -- Welcome / Hero ----------------------------------------------------- */

body.home div.page {
    max-width: 64rem;
    text-align: center;
}

.people {
    display: inline-block;
    text-align: center;
    margin: 1.25rem 0 0.5rem;
}

.col {
    display: inline-block;
}

.col.mid {
    vertical-align: middle;
}

.image-list {
    display: inline-block;
    vertical-align: middle;
}

.image-list ul {
    padding: 0 0 0 1.875rem;
    margin: 0;
    list-style: none;
}

.image-list li {
    line-height: 1.15;
    margin-bottom: 0.15em;
}

.avatar {
    width: 150px;
    border-radius: 50%;
    box-shadow: 0 0 0 3px var(--paper), 0 0 0 4px #999, 0 2px 5px 4px rgba(0, 0, 0, .2);
}

/* Condensed uppercase wants tracking pulled in, not pushed out: the
   counters are already narrow, so the letters sit close by design. */
/* Scoped past `div.page h1` (0,1,2), which would otherwise impose the
   ordinary page-title size on the masthead. */
div.page .big-title {
    display: inline-block;
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--step-4);
    font-weight: 600;
    text-transform: uppercase;
    line-height: 0.95;
    letter-spacing: -0.005em;
    color: var(--highlight-color);
}

/* A line of credentials sits under the name, so it stays clearly
   subordinate to it rather than competing at display size. */
.big-subtitle {
    display: inline-block;
    font-family: var(--font-display);
    font-size: var(--step-1);
    font-weight: 400;
    line-height: 1.3;
    letter-spacing: 0.01em;
    color: var(--secondary-color);
    text-wrap: balance;
    font-variant-numeric: oldstyle-nums proportional-nums;
}

/* Three lines of his own, not one borrowed from someone famous. Each is a
   separate thought, so they get their own line and room to breathe. */
.credo {
    max-width: 46rem;
    margin: 1.25rem auto 0;
}

/* Outruns `div.page p`, which is (0,1,2) and would otherwise impose the
   justified body setting on these. */
div.page .credo p {
    margin: 0 0 0.45em;
    font-family: var(--font-accent);
    font-style: italic;
    font-size: var(--step-0);
    line-height: 1.45;
    color: var(--darkgray-color);
    text-align: center;
    text-wrap: balance;
    font-variant-numeric: oldstyle-nums proportional-nums;
}

div.page .credo p:last-child {
    margin-bottom: 0;
}

/* -- Drop cap ----------------------------------------------------------- */

.intro {
    font-family: var(--font-display);
    font-weight: 600;
    color: var(--secondary-color);
    -webkit-initial-letter: 3 3;
    initial-letter: 3 3;
    margin-right: 0.08em;
}

@supports not ((initial-letter: 3 3) or (-webkit-initial-letter: 3 3)) {
    .intro {
        float: left;
        font-size: 3.4em;
        line-height: 0.82;
        padding-right: 0.07em;
    }
}

/* -- Code --------------------------------------------------------------- */

code {
    font-family: var(--font-mono);
    font-size: 0.82em;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: 4px;
    color: var(--darkgray-color);
    padding: 0.05em 0.3em;
    word-wrap: break-word;
    font-variant-ligatures: none;
}

.inline-code {
    font-family: var(--font-mono);
    font-size: 0.82em;
    background: var(--surface);
    border: 1px solid var(--rule);
    color: var(--darkgray-color);
}

pre {
    border: 1px solid var(--rule);
    border-left: 3px solid var(--highlight-color);
    page-break-inside: avoid;
    line-height: 1.55;
    margin: 0 0 1.6em;
    overflow: auto;
    padding: 1em 1.5em;
    border-radius: 0 4px 4px 0;
    font-family: var(--font-mono);
    font-size: 0.78em;
    font-variant-ligatures: none;
    tab-size: 4;
}

pre code {
    background: transparent;
    border: 0;
    border-radius: 0;
    display: block;
    padding: 0;
    font-size: inherit;
}

/* -- Tables ------------------------------------------------------------- */

div.page table {
    border-collapse: collapse;
    width: 100%;
    margin: 0 0 1.6em;
    font-size: var(--step--1);
    font-variant-numeric: tabular-nums lining-nums;
}

div.page th {
    font-family: var(--font-display);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    text-align: left;
    border-bottom: 2px solid var(--rule);
    padding: 0.45em 0.7em 0.45em 0;
}

div.page td {
    border-bottom: 1px solid var(--rule);
    padding: 0.45em 0.7em 0.45em 0;
    vertical-align: top;
}

/* -- Thumbnails --------------------------------------------------------- */

/* A float's top aligns with the top of the line box, which sits well above
   the cap height the eye reads as "the top of the text". Nudge it down by the
   half-leading plus Barlow's ascender-to-cap gap (content box 1.2em, cap at
   0.7em, ascender at 1.0em).

   The nudge is `top`, not `margin-top`, on purpose: a float reserves whole
   line boxes, so 10px of margin would round the reserved height up by a
   whole line and open a gap under the image. A relative offset moves the
   picture without touching the box it reserves. */
.thumbnail {
    --float-w: 257px; /* 250px + 2 * 2px padding + 2 * 1.5px border */
    float: left;
    position: relative;
    top: calc((var(--leading) * 1em - 1.2em) / 2 + 0.3em);
    margin: 0 15px 0.15em 0;
    width: 250px;
    height: auto;
    padding: 2px;
    border: 1.5px solid var(--shot-frame);
    background-color: var(--paper);
    border-radius: 4px;
    cursor: pointer;
    transition: transform 220ms cubic-bezier(0.22, 0.61, 0.36, 1), border-color 220ms cubic-bezier(0.22, 0.61, 0.36, 1), box-shadow 220ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.thumbnail:hover {
    border-color: var(--highlight-color);
    transform: scale(1.05);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}

/* A small app icon floated at the top of a post. Same float behaviour as a
   thumbnail, without the frame and the lightbox. */
.app-icon {
    --float-w: 128px;
    float: left;
    width: 128px;
    height: auto;
    margin: 0 1.2rem 0.8rem 0;
    border-radius: 24px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
}

/* -- Marginalia ---------------------------------------------------------
   Once the viewport can afford it, a floated image leaves the measure
   entirely and sits in the left margin, so the prose keeps its full width.
   Two states only, inside or outside: a float hanging half over the edge
   reads as a layout accident, which is why float paragraphs are excluded
   from the --bleed breakout above.

   Needed width: measure + 2 * gutter + 2 * (widest float + gap)
   = 40rem + 3.75rem + 2 * (257px + 1.25rem) ~ 78.5rem, rounded up.

   `clear: left` keeps consecutive marginal images from stacking beside each
   other into the measure when the previous one is taller than its text. */
div.page {
    --float-gap: 1.25rem;
}

@media (min-width: 80rem) {
    div.page :is(.thumbnail, .app-icon) {
        margin-left: calc(-1 * (var(--float-w) + var(--float-gap)));
        margin-right: 0;
        clear: left;
    }
}

/* -- Image gallery ------------------------------------------------------ */

.image-gallery {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin: 20px 0;
}

.image-thumbnail {
    cursor: pointer;
    max-width: 150px;
    height: auto;
    border: 2px solid var(--rule);
    border-radius: 4px;
    transition: transform 220ms cubic-bezier(0.22, 0.61, 0.36, 1), border-color 220ms cubic-bezier(0.22, 0.61, 0.36, 1), box-shadow 220ms cubic-bezier(0.22, 0.61, 0.36, 1);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.image-thumbnail:hover {
    border-color: var(--highlight-color);
    transform: scale(1.05);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}

.image-group {
    margin: 20px 0;
}

.image-group h4 {
    margin-bottom: 10px;
}

/* Scaling a 960px source down to 250px means the browser re-rasterises the
   bitmap on every frame of the hover, which is what makes the zoom stutter.
   Promoting to a compositor layer turns it into a pure GPU transform. Kept
   behind a hover query and bounded by how few of these a page carries
   (eight at most) -- `will-change` on everything would cost more than it saves. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
    .thumbnail,
    .image-thumbnail {
        will-change: transform;
    }
}

/* -- Modal overlay ------------------------------------------------------ */

.image-modal {
    display: none;
    position: fixed;
    z-index: 1000;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.8);
    cursor: pointer;
}

.image-modal img {
    display: block;
    margin: auto;
    max-width: 90%;
    max-height: 90%;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
    border-radius: 4px;
}

.image-modal .close {
    position: absolute;
    top: 20px;
    right: 35px;
    color: #fff;
    font-size: 40px;
    font-weight: bold;
    cursor: pointer;
}

.image-modal .close:hover {
    opacity: 0.7;
}

/* -- Tags & pills ------------------------------------------------------- */

.tag {
    display: inline-block;
    padding: 0.1em 0.6em;
    border-radius: 12px;
    font-size: 0.72em;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: #fff;
    background-color: #777;
    margin-left: 5px;
    vertical-align: middle;
}

.tag.available {
    background-color: #4CAF50;
}

.tag.oop {
    background-color: #f44336;
}

.unavailable {
    display: inline-block;
    padding: 0.05em 0.5em;
    border-radius: 10px;
    font-size: 0.7em;
    font-style: italic;
    color: #fff;
    background-color: #e8923a;
    vertical-align: middle;
    line-height: 1.6;
    cursor: help;
}

/* -- Language badges ---------------------------------------------------- */

/* Deliberately a different species from the inline code pill sitting next to
   it: rounded rather than rectangular, tinted rather than neutral grey. One
   marks code, the other marks a category. */
.lang {
    display: inline-block;
    font-family: var(--font-display);
    font-size: 0.7em;
    font-weight: 500;
    line-height: 1.6;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    white-space: nowrap;
    padding: 0 0.6em;
    margin-left: 0.3em;
    border: 1px solid color-mix(in oklab, var(--highlight-color) 30%, transparent);
    border-radius: 999px;
    background: color-mix(in oklab, var(--highlight-color) 9%, var(--paper));
    color: color-mix(in oklab, var(--highlight-color) 70%, var(--ink));
    vertical-align: 0.1em;
    font-variant-numeric: lining-nums;
}

.lang + .lang {
    margin-left: 0.25em;
}

/* -- "Coming soon" marker ------------------------------------------------
   Same badge family as .lang but amber, so availability and language read
   as different kinds of information at a glance. With data-hint it grows a
   tooltip on hover and on keyboard focus. */

.coming-soon {
    display: inline-block;
    font-family: var(--font-display);
    font-size: 0.7em;
    font-weight: 500;
    line-height: 1.6;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    white-space: nowrap;
    padding: 0 0.6em;
    border: 1px solid color-mix(in oklab, var(--amber) 45%, transparent);
    border-radius: 999px;
    background: color-mix(in oklab, var(--amber) 12%, var(--paper));
    color: color-mix(in oklab, var(--amber) 70%, var(--ink));
    vertical-align: 0.1em;
}

.coming-soon--block {
    font-size: 1.05rem;
    padding: 0.55em 1.5em;
    letter-spacing: 0.1em;
}

.coming-soon[data-hint] {
    position: relative;
    cursor: help;
}

.coming-soon[data-hint]::after {
    content: attr(data-hint);
    position: absolute;
    left: 50%;
    bottom: calc(100% + 0.6em);
    transform: translateX(-50%);
    z-index: 30;
    width: max-content;
    max-width: 17rem;
    padding: 0.5em 0.8em;
    border-radius: 6px;
    background: var(--ink);
    color: var(--paper);
    font-family: var(--font-text);
    font-size: 0.85rem;
    font-weight: 400;
    line-height: 1.4;
    letter-spacing: 0;
    text-transform: none;
    white-space: normal;
    text-align: center;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.15s ease, visibility 0.15s;
}

.coming-soon[data-hint]:hover::after,
.coming-soon[data-hint]:focus-visible::after {
    opacity: 1;
    visibility: visible;
}

/* -- Fancy HR ----------------------------------------------------------- */

hr.fancy-hr {
    border: 0;
    height: 1px;
    background-image: linear-gradient(to right, transparent, var(--rule), transparent);
    margin: 2.5rem 0;
}

/* -- App catalogue: consistent previews independent of copy length ------- */

.app-entry {
    display: flex;
    align-items: flex-start;
    gap: 1.75rem;
    margin: 2.25rem 0;
}

/* Reserve the same column for every device format; preserve the full image. */
.app-entry__shot {
    display: flex;
    justify-content: center;
    flex: 0 0 150px;
    height: 260px;
}

.app-entry__shot img {
    display: block;
    width: auto;
    height: 100%;
    max-width: 100%;
    object-fit: contain;
    padding: 2px;
    border: 1.5px solid var(--shot-frame);
    border-radius: 8px;
    box-sizing: border-box;
    background: var(--paper);
    cursor: pointer;
    transition: box-shadow 0.2s ease;
}

.app-entry__shot img:hover {
    transform: none;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.app-entry__body {
    flex: 1 1 auto;
    min-width: 0;
}

.app-entry__body > :first-child {
    margin-top: 0;
}

/* -- Typographic utilities ---------------------------------------------
   Opt-in classes for the parts of the Plex palette that are wrong as a
   default but right in the occasional display line.                      */

/* Set something in the display face on purpose. */
.display {
    font-family: var(--font-display);
}

/* Plex's single-storey a (ss01) and g (ss02) — its humanist alternates. */
.alt-forms {
    font-family: var(--font-display);
    font-feature-settings: "ss01" 1, "ss02" 1;
}

/* Slashed zero, for anything that could be mistaken for an O. */
.slashed-zero {
    font-family: var(--font-display);
    font-variant-numeric: slashed-zero;
}

/* Text figures. Only the display face has them; Barlow ships lining only. */
.oldstyle {
    font-family: var(--font-display);
    font-variant-numeric: oldstyle-nums proportional-nums;
}

/* Aligning figures, for anything that stacks: prices, versions, measurements. */
.lining {
    font-variant-numeric: lining-nums tabular-nums;
}

/* Real small caps — only the accent face has them. */
.smallcaps {
    font-family: var(--font-accent);
    font-variant-caps: small-caps;
    letter-spacing: 0.035em;
}

/* A caps voice in the condensed face, where small caps would be faked. */
.caps {
    font-family: var(--font-display);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.09em;
}

.fraction {
    font-variant-numeric: diagonal-fractions;
}

/* -- Responsive --------------------------------------------------------- */

@media (max-width: 768px) {
    body {
        margin: 1.25rem 0.5rem;
    }

    header, footer, div.page {
        padding: 1rem 0.75rem;
    }

    .people {
        display: block;
    }

    .col, .image-list {
        display: block;
    }

    .image-list ul {
        padding: 0;
    }

    .thumbnail {
        float: none;
        display: block;
        margin: 15px auto;
        max-width: 100%;
    }
}

@media (max-width: 640px) {
    .app-entry {
        flex-direction: column;
        gap: 1rem;
    }

    .app-entry__shot {
        flex: 0 0 auto;
        width: 150px;
        height: 220px;
        max-width: 100%;
        align-self: center;
    }
}

/* -- Motion ------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        scroll-behavior: auto !important;
    }
}

/* -- Print -------------------------------------------------------------- */

@media print {
    body {
        margin: 0;
        font-size: 11pt;
        background: #fff;
        color: #000;
    }

    header nav, footer, .corner-ribbon, .pagination, #disqus_thread {
        display: none;
    }

    div.page {
        max-width: none;
        padding: 0;
    }

    div.page :is(h1, h2, h3) {
        break-after: avoid;
    }

    div.page p {
        orphans: 3;
        widows: 3;
    }

    div.page a::after {
        content: " (" attr(href) ")";
        font-size: 0.8em;
        color: #555;
    }

    div.page a[href^="#"]::after,
    div.page a[href^="/"]::after {
        content: "";
    }
}
