/* hajoepitok.hu — public stylesheet. Hand-written, no build step, no npm.
 *
 * THE IDEA
 * These people build boats out of plywood and epoxy in their living rooms, and
 * the graphic language of that world is the boat plan: hairlines, a table of
 * offsets, station numbers, varnished wood on lake water. So the page is drawn
 * rather than decorated — cool drafting paper instead of warm cream, ink
 * instead of black, one amber that is the colour of epoxy on pine, and one
 * loud moment (the community's three numbers, set like a table of offsets)
 * with everything around it quiet.
 *
 * WHAT WAS KEPT FROM THE OLD SITE (client request, 2026-09-10)
 * The old theme had a handful of nautical graphics the members know the site
 * by, and they come along as material, not as a template: a length of rope
 * (assets/img/rope.png) hangs under the header, edges the figures band and
 * tops the footer; the team photos sit in the old knotted-rope frame
 * (ramka.webp) with a white mount; the engraved lifebuoy drawing sits in the
 * margins of the first band, the underwater photograph under the figures.
 * The homepage opens with the old full-screen slider — four photographs and
 * the words the old site put on them. The ship's wheel is the logo.
 * tools/derive_theme_images.php is how every file in assets/img/ was made.
 *
 * TYPE
 * One family, Archivo, self-hosted as a variable font. The WIDTH axis carries
 * the hierarchy: wide for display (it reads like a name on a transom), normal
 * for text. Two families would have cost another 220 KB on a site whose
 * longest page is a 51 KB handbook.
 *
 * RULES OF THE HOUSE
 * - anything given height/width also declares display
 * - `.x[hidden] { display: none }` wherever .x has its own display rule
 * - screenshots at 390/768/1366/1920 before calling a layout change done
 *   (`php tools/shots.php` — it renders in an iframe for a reason, see the file)
 */

/* ---------------------------------------------------------------- font */
/* Variable font: one file per subset covers 400–700 and the whole width axis.
   latin-ext is not optional here — Hungarian ő and ű live in it. */
@font-face {
    font-family: 'Archivo';
    src: url('/assets/fonts/archivo-latin.woff2') format('woff2');
    font-weight: 400 700;
    font-stretch: 62% 125%;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
        U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
        U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Archivo';
    src: url('/assets/fonts/archivo-latin-ext.woff2') format('woff2');
    font-weight: 400 700;
    font-stretch: 62% 125%;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
        U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
        U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ---------------------------------------------------------------- tokens */
:root {
    --paper:    #f1f3f0;   /* drafting paper: cool, faintly green */
    --paper-2:  #e4e9e5;   /* the quiet band */
    --ink:      #12232a;   /* drawing ink, not black */
    --hull:     #17323b;   /* the mass: header, footer, the offsets band */
    --lake:     #245e56;   /* Hungarian lake water is green. Links live here. */
    --lake-lit: #7fbfae;   /* the same green, for use ON ink */
    --epoxy:    #8f5a12;   /* varnish on pine. Exactly one thing at a time. */
    --rope:     #d9c49e;   /* the rope's own highlight; text ON photographs */
    --line:     #c3cfcb;   /* hairline */
    --line-ink: #2d4a54;   /* hairline on the ink ground */
    --muted:    #55666c;

    --max:      1200px;
    --aside:    300px;     /* the sidebar column on article/page/guide */
    --gutter:   clamp(20px, 4vw, 40px);
    --rhythm:   clamp(48px, 8vw, 104px);   /* space between sections */
    --header-h: 132px;     /* the slider's height is the viewport minus this;
                              measured: logo row + two wrapped menu rows */

    --font: 'Archivo', 'Segoe UI', system-ui, -apple-system, Arial, sans-serif;
}
@media (min-width: 768px)  { :root { --header-h: 122px; } }   /* logo row + one menu row */
@media (min-width: 1100px) { :root { --header-h: 80px; } }    /* one row */

/* ---------------------------------------------------------------- reset */
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    background: var(--paper);
    color: var(--ink);
    font-family: var(--font);
    font-size: 1.0625rem;
    font-stretch: 100%;
    line-height: 1.6;
    text-rendering: optimizeLegibility;
}
@media (min-width: 768px) { body { font-size: 1.125rem; } }

img { max-width: 100%; height: auto; }
a { color: var(--lake); text-underline-offset: 0.18em; }
a:hover { color: var(--ink); }

:focus-visible {
    outline: 3px solid var(--epoxy);
    outline-offset: 2px;
}

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

.wrap {
    max-width: var(--max);
    margin-inline: auto;
    padding-inline: var(--gutter);
}

.skip-link { position: absolute; left: -9999px; top: 0; background: var(--ink); color: var(--paper); padding: 10px 14px; }
.skip-link:focus { left: 8px; top: 8px; z-index: 100; }

/* ---------------------------------------------------------------- type scale */
h1, h2, h3, h4 {
    font-weight: 600;
    line-height: 1.1;
    letter-spacing: -0.015em;
    margin: 0;
    text-wrap: balance;
}
h1 { font-size: clamp(1.9rem, 4vw, 3rem); font-stretch: 112%; }
h2 { font-size: clamp(1.45rem, 2.6vw, 2rem); font-stretch: 108%; }
h3 { font-size: 1.2rem; font-stretch: 104%; }

/* ---------------------------------------------------------------- rope
   One 147×18 tile repeated. Every use is decorative (aria-hidden in the
   markup) and sits ON the boundary between two grounds — half over each —
   which is what makes it read as a rope lying across the page rather than a
   stripe painted on one section. Hosts must be `position: relative`. */
.rope {
    display: block;
    height: 18px;
    background: url('/assets/img/rope.png') repeat-x center / auto 18px;
    pointer-events: none;
}
.rope--header, .rope--footer, .offsets .rope { position: absolute; left: 0; right: 0; }
.rope--header { bottom: -9px; }
.rope--footer { top: -9px; }
.offsets .rope { top: -9px; }
.offsets .rope--bottom { top: auto; bottom: -9px; }

/* ---------------------------------------------------------------- header
   The logo is the old site's wheel, and it is the whole brand: no wordmark
   next to it (the name is drawn into the image). */
.site-header {
    position: relative;
    z-index: 5;
    background: var(--hull);
    color: var(--paper);
}
.site-header__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px var(--gutter);
    min-height: 64px;   /* --header-h is the slider's estimate of this box, not a rule for it */
    padding-block: 8px;
}
.brand { display: block; flex: 0 0 auto; line-height: 0; }
.brand__logo {
    display: block;
    width: 48px;
    height: 48px;
    border-radius: 50%;
    background: #fff;
}
@media (min-width: 768px) { .brand__logo { width: 62px; height: 62px; } }

/* Under 1100px the menu does not fit beside the logo, and a menu that
   wraps unevenly looks broken; so it takes a full row of its own, with the
   English link staying up on the logo's row where it is found first. */
.site-nav { display: flex; flex-wrap: wrap; gap: 4px 22px; order: 3; flex-basis: 100%; }
.lang-en { order: 2; margin-left: auto; }
@media (min-width: 1100px) {
    .site-nav { order: 2; flex-basis: auto; margin-left: auto; }
    .lang-en { order: 3; margin-left: 0; }
}
.site-nav a {
    color: var(--paper);
    text-decoration: none;
    font-size: 0.95rem;
    font-weight: 500;
    padding-block: 2px;
    border-bottom: 2px solid transparent;
}
.site-nav a:hover { color: var(--paper); border-bottom-color: var(--rope); }

/* Dropdowns (the old menu had two). Below 1100px the header menu is already
   its own full row, so a group simply lists its children inline, indented by
   nothing more than a middle dot — a hover menu on a touch screen is a menu
   nobody can open. From 1100px the children hang under the parent on hover
   or on keyboard focus anywhere inside the group (:focus-within), which is
   what makes them reachable by Tab without any script. */
.site-nav__group { display: contents; }
.site-nav__menu { display: contents; }
.site-nav__menu a { font-weight: 400; opacity: 0.85; }
.site-nav__menu a::before { content: "· "; opacity: 0.6; }
@media (min-width: 1100px) {
    .site-nav__group { display: block; position: relative; }
    .site-nav__menu {
        display: none;
        position: absolute;
        top: 100%;
        left: -14px;
        z-index: 30;
        min-width: 260px;
        padding: 10px 0;
        background: var(--hull);
        box-shadow: 0 10px 24px rgb(0 0 0 / 0.28);
        border-top: 2px solid var(--rope);
    }
    .site-nav__group:hover .site-nav__menu,
    .site-nav__group:focus-within .site-nav__menu { display: grid; }
    .site-nav__menu a { display: block; padding: 8px 14px; border-bottom: 0; opacity: 1; white-space: nowrap; }
    .site-nav__menu a::before { content: none; }
    .site-nav__menu a:hover, .site-nav__menu a:focus { background: rgb(255 255 255 / 0.08); }
    /* Keep the parent's underline while its menu is open. */
    .site-nav__group:hover .site-nav__parent,
    .site-nav__group:focus-within .site-nav__parent { border-bottom-color: var(--rope); }
}

/* The English link. A flag on a dark ground needs an edge of its own or its
   blue field dissolves into the header, hence the hairline ring. */
.lang-en {                                            /* order/margin: see .site-nav */
    display: inline-flex;
    align-items: center;
    gap: 7px;
    color: var(--lake-lit);
    text-decoration: none;
    font-size: 0.85rem;
    font-weight: 500;
    white-space: nowrap;
}
.lang-en img { display: block; border-radius: 2px; box-shadow: 0 0 0 1px rgb(241 243 240 / 0.4); }
.lang-en:hover { color: var(--paper); }

/* ---------------------------------------------------------------- slider
   Full viewport under the header, capped so a 4K screen does not get a
   900px-tall wall of sky. All slides occupy the same grid cell: the track's
   height is the TALLEST slide's words, so nothing is ever clipped, and the
   photographs are absolutely positioned behind. Crossfade is opacity plus a
   delayed visibility flip, so the outgoing slide stays clickable for exactly
   as long as it is visible and not a moment more.
   Without JavaScript only the first slide exists visually (and the controls
   do not), which is also what a search engine's first paint is. */
.slider {
    position: relative;
    isolation: isolate;
    background: var(--hull);
    color: #fff;
}
.slider__track {
    display: grid;
    min-height: 560px;
    min-height: clamp(560px, calc(100svh - var(--header-h)), 900px);
}
.slider__slide {
    grid-area: 1 / 1;
    position: relative;
    display: grid;
    align-items: end;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.9s ease, visibility 0s linear 0.9s;
}
.slider__slide.is-active { opacity: 1; visibility: visible; transition-delay: 0s; }
html:not(.js) .slider__slide:not(:first-child) { display: none; }
html:not(.js) .slider__controls { display: none; }

.slider__image {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
/* The scrim is the price of words on a photograph; it is heaviest where the
   words are and gone by the middle, so the top half of each picture is shown
   as it was taken. */
.slider__slide::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to top, rgba(18, 35, 42, 0.88) 0%, rgba(18, 35, 42, 0.55) 38%, rgba(18, 35, 42, 0.08) 72%, rgba(18, 35, 42, 0) 100%);
}
.slider__words {
    position: relative;
    z-index: 1;
    width: 100%;
    padding-block: clamp(40px, 7vw, 96px) clamp(96px, 13vw, 128px);
}
.slider__kicker {
    margin: 0 0 0.7rem;
    color: var(--rope);
    font-weight: 600;
    font-stretch: 104%;
    font-size: clamp(1rem, 1.5vw, 1.2rem);
}
.slider__title {
    font-size: clamp(2rem, 5.4vw, 4rem);
    font-stretch: 118%;
    line-height: 0.98;
    letter-spacing: -0.025em;
    color: #fff;
    max-width: 18ch;
}
.slider__lead {
    max-width: 56ch;
    margin: 1.1rem 0 1.7rem;
    font-size: clamp(1.02rem, 1.5vw, 1.25rem);
    color: #e3ebe9;
}

.slider__controls {
    position: absolute;
    left: 0;
    right: 0;
    bottom: clamp(22px, 4vw, 40px);
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: clamp(12px, 3vw, 28px);
    pointer-events: none;      /* the bar spans the width; only its buttons are targets */
}
.slider__controls > * { pointer-events: auto; }
.slider__arrow {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 1.5px solid rgba(255, 255, 255, 0.6);
    border-radius: 50%;
    background: rgba(18, 35, 42, 0.35);
    color: #fff;
    cursor: pointer;
}
.slider__arrow:hover { background: var(--epoxy); border-color: var(--epoxy); }
.slider__dots { display: flex; gap: 10px; }
.slider__dot {
    display: block;
    width: 12px;
    height: 12px;
    padding: 0;
    border: 1.5px solid rgba(255, 255, 255, 0.8);
    border-radius: 50%;
    background: transparent;
    cursor: pointer;
}
.slider__dot.is-active { background: var(--rope); border-color: var(--rope); }

/* ---------------------------------------------------------------- buttons */
.button {
    display: inline-block;
    background: var(--epoxy);
    color: #fff;
    text-decoration: none;
    font-weight: 600;
    font-stretch: 104%;
    font-size: 1rem;
    padding: 13px 26px;
    border: 2px solid var(--epoxy);
}
.button:hover { background: var(--ink); border-color: var(--ink); color: #fff; }
.button--quiet { background: transparent; color: var(--ink); border-color: var(--ink); }
.button--quiet:hover { background: var(--ink); color: var(--paper); }
/* On a photograph the ink hover would vanish into the scrim: go light instead. */
.button--light:hover { background: #fff; border-color: #fff; color: var(--ink); }

/* ---------------------------------------------------------------- sections */
.band { padding-block: var(--rhythm); }
.band__title { margin-bottom: 0.6em; }
.band__lead { color: var(--muted); font-size: 1.05rem; margin: 0 0 2.2rem; }
.band__more { margin: 2rem 0 0; font-weight: 600; }

/* The old theme's engraved lifebuoy and knotted post, drawn in the margins.
   The drawing is transparent, so it takes the paper's colour, and it is
   pinned to the section's top-left/bottom-right like the old site had it. */
.band--lifebuoy {
    background: var(--paper) url('/assets/img/bg-lifebuoy.webp') center top / cover no-repeat;
}
/* Clouds, the old "count-up" ground: nearly white, a lift before the footer. */
.band--clouds {
    background: #fbfcfb url('/assets/img/bg-clouds.webp') center bottom / cover no-repeat;
}

/* Three teasers, side by side on desktop, no card chrome at all: an image, a
   line of context, a heading. Borders and shadows here would be decoration. */
.cards { display: grid; gap: clamp(28px, 4vw, 44px); grid-template-columns: 1fr; }
@media (min-width: 640px) { .cards { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .cards { grid-template-columns: repeat(3, 1fr); } }

/* ---------------------------------------------------------------- the offsets
   The one loud moment. A boat plan's table of offsets: the figure large and
   wide with tabular digits, the label underneath, on the old site's underwater
   photograph with a rope along each edge.
   Two of the three numbers are counted from the database (Home\Figures).

   THREE BOXES, not three stacked rows (client, 2026-09-10): the old theme's
   Bootstrap `col-md-4 col-xs-12`, which here is one column below 768px and
   three equal ones above. It is a media query rather than `auto-fit`
   because a figure of 0 is skipped in the template — with auto-fit, two
   surviving boxes would silently stretch to half the band each, and the
   client asked for thirds. `align-items: stretch` (the grid default) is what
   keeps the three boxes the same height when one label wraps to two lines. */
.offsets {
    position: relative;
    z-index: 1;
    background: var(--hull) url('/assets/img/bg-water.webp') center / cover no-repeat;
    color: var(--paper);
}
.offsets__inner { padding-block: var(--rhythm); }
.offsets__title {
    font-size: clamp(1.2rem, 2vw, 1.55rem);
    font-stretch: 104%;
    font-weight: 500;
    color: var(--rope);
    margin-bottom: 1.4em;
}
.offsets__table {
    margin: 0;
    display: grid;
    grid-template-columns: 1fr;
    gap: clamp(16px, 2.4vw, 28px);
}
@media (min-width: 768px) {
    .offsets__table { grid-template-columns: repeat(3, 1fr); }
}
.offsets__row {
    display: flex;
    flex-direction: column;
    gap: 0.5em;
    padding: clamp(22px, 3vw, 36px) clamp(20px, 2.4vw, 30px);
    border: 1px solid rgba(255, 255, 255, 0.28);
    background: rgba(18, 35, 42, 0.42);
}
.offsets__n {
    display: block;
    font-size: clamp(3rem, 7vw, 5rem);
    font-weight: 700;
    font-stretch: 125%;
    line-height: 0.85;
    letter-spacing: -0.03em;
    font-variant-numeric: tabular-nums;
    color: #fff;
}
.offsets__label {
    margin: 0;
    font-size: clamp(0.95rem, 1.3vw, 1.15rem);
    color: #d5dfe6;
}

/* ---------------------------------------------------------------- split rows */
.split { padding-block: var(--rhythm); }
.split--quiet { background: var(--paper-2); }
.split__inner { display: grid; gap: clamp(24px, 4vw, 56px); align-items: center; }
.split__media img { display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.split__words > * + * { margin-top: 1.1rem; }
.split__words p { color: var(--muted); }
.split__words .button { margin-top: 0.8rem; }

/* Prose fields (home.*.body/lead) are now editor-formatted HTML: one or more
   <p> (occasionally <ul>), not a single bare string. These three blocks used
   to hold exactly one margin-0 <p>; keep that same flush look for a single
   paragraph while giving a second paragraph the space it needs. */
.band__lead p, .slider__lead p, .split__text p { margin: 0 0 0.8em; }
.band__lead > :last-child, .slider__lead > :last-child, .split__text > :last-child { margin-bottom: 0; }
.split__text { color: var(--muted); }
.split__text ul, .band__lead ul { padding-left: 1.2em; }

@media (min-width: 860px) {
    .split__inner { grid-template-columns: 1fr 1fr; }
    .split--reverse .split__media { order: 2; }
}

/* ---------------------------------------------------------------- faces
   The old team card: a square photo with a white mount inside the knotted
   rope frame. The frame is a background stretched to the square (it IS
   square, so nothing distorts); the padding keeps the photo off the rope. */
.faces {
    list-style: none;
    margin: 0 0 1rem;
    padding: 0;
    display: grid;
    gap: clamp(20px, 3vw, 32px) clamp(16px, 2.5vw, 28px);
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
}
.faces__item { display: flex; flex-direction: column; gap: 2px; text-align: center; }
.faces__frame {
    display: block;
    aspect-ratio: 1;
    padding: 12.5%;
    background: url('/assets/img/ramka.webp') center / 100% 100% no-repeat;
}
.faces__photo {
    display: block;
    width: 100%;
    height: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border: 5px solid #fff;
    box-shadow: 0 1px 4px rgba(18, 35, 42, 0.18);
}
.faces__name { margin-top: 10px; font-weight: 600; font-size: 0.95rem; }
.faces__role { color: var(--muted); font-size: 0.85rem; }

/* A face with a bio is a link to its <dialog> (acceptance 8.8). The hover/
   focus affordance moves the frame, not the whole card, so the layout never
   shifts. */
.faces__link { display: flex; flex-direction: column; gap: 2px; color: inherit; text-decoration: none; cursor: pointer; }
.faces__link .faces__frame { transition: transform 0.15s ease; }
.faces__link:hover .faces__frame,
.faces__link:focus-visible .faces__frame { transform: translateY(-3px); }
.faces__link:hover .faces__name,
.faces__link:focus-visible .faces__name { text-decoration: underline; }

/* The bio itself: a native <dialog>, opened with showModal() by app.js.
   Without JS the fragment link (#bio-N) still finds it — :target below shows
   the SAME element as a fixed overlay, so a reader with JS off gets the same
   content, just not the same focus-trapping. */
.bio { width: min(640px, calc(100% - 32px)); max-height: calc(100% - 32px); padding: 0; border: 0; border-radius: 6px; color: var(--ink); background: #fff; }
.bio::backdrop { background: rgba(9, 18, 22, 0.72); }
.bio__inner { position: relative; padding: clamp(20px, 4vw, 36px); text-align: left; }
.bio__photo { float: left; width: 110px; height: 110px; margin: 0 18px 10px 0; object-fit: cover; border: 5px solid #fff; box-shadow: 0 1px 4px rgba(18, 35, 42, 0.18); }
.bio__name { margin: 0 2.5rem 0.2em 0; }
.bio__role { margin: 0 0 0.8em; color: var(--muted); }
.bio__text { clear: none; }
.bio__close { position: absolute; top: 10px; right: 12px; font-size: 1.8rem; line-height: 1; color: var(--ink); text-decoration: none; }
/* No-JS: the fragment link targets the closed <dialog>. */
.bio:target { display: block; position: fixed; inset: 16px; margin: auto; z-index: 60; overflow: auto; box-shadow: 0 0 0 100vmax rgba(9, 18, 22, 0.72); }
@media (max-width: 480px) { .bio__photo { float: none; display: block; margin: 0 0 12px; } }

/* ---------------------------------------------------------------- voices */
.voices { padding-block: var(--rhythm); border-top: 1px solid var(--line); }
.voices__title { margin-bottom: 1.4em; }
.voices__list { display: grid; gap: clamp(28px, 4vw, 48px); }
@media (min-width: 900px) { .voices__list { grid-template-columns: repeat(3, 1fr); } }
.voice { margin: 0; }
.voice__quote {
    margin: 0;
    padding-left: 20px;
    border-left: 2px solid var(--epoxy);
    font-size: 1.05rem;
}
.voice__quote p { margin: 0 0 0.7em; }
.voice__quote p:last-child { margin-bottom: 0; }
.voice__by { margin-top: 1rem; padding-left: 22px; font-weight: 600; font-size: 0.95rem; }
.voice__source { display: block; font-weight: 400; color: var(--muted); }

/* ---------------------------------------------------------------- breadcrumb */
.breadcrumb { padding-top: 28px; font-size: 0.875rem; color: var(--muted); }
.breadcrumb__list { list-style: none; display: flex; flex-wrap: wrap; gap: 2px 8px; margin: 0; padding: 0; }
.breadcrumb__item + .breadcrumb__item::before { content: "/"; margin-right: 8px; color: var(--line); }
.breadcrumb__item [aria-current="page"] { color: var(--muted); }

/* ---------------------------------------------------------------- article / page */
.article__header, .page__header, .archive__header { padding-top: clamp(20px, 3vw, 36px); }
.article__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 20px;
    margin: 0.9rem 0 2rem;
    color: var(--muted);
    font-size: 0.92rem;
}
.article__cover, .page__cover { margin: 0 0 clamp(28px, 4vw, 48px); }
.article__cover img, .page__cover img { display: block; width: 100%; max-height: 560px; object-fit: cover; }
.article__body, .page__body { padding-bottom: var(--rhythm); }

/* Long-form. The ONLY place the sanitiser's tag set is styled, so an article,
   a page and (from M5) a guide cannot drift apart.

   NO `max-width` here, deliberately (client, 2026-09-10): a text column
   narrower than the page around it read to the client as text cut off, and
   they asked for the full container everywhere. What keeps an article's lines
   from running the whole 1200px is the SIDEBAR — `.with-aside` below gives
   the body a ~780px column and puts the WordPress-style widget list beside
   it. On a template with no sidebar the prose really is full width, and that
   is the intended look.
   The two rules below must stay at equal specificity: `.prose p { margin: 0 }`
   (0-1-1) beat `.prose > * + *` (0-1-0) and silently removed the space between
   every paragraph — visible only on screen. */
.prose > * { margin-block: 0; }
.prose > * + * { margin-top: 1.15em; }
.prose h2 { margin-top: 1.9em; font-size: 1.5rem; }
.prose h3 { margin-top: 1.7em; font-size: 1.2rem; }
.prose img { display: block; margin-block: 1.6em; }
.prose figure { margin: 1.6em 0; }
.prose figcaption { margin-top: 0.6em; font-size: 0.9rem; color: var(--muted); }
.prose blockquote { margin: 1.6em 0; padding-left: 22px; border-left: 2px solid var(--epoxy); color: var(--muted); }
.prose ul, .prose ol { padding-left: 1.3em; }
.prose li + li { margin-top: 0.45em; }
.prose table { width: 100%; border-collapse: collapse; display: block; overflow-x: auto; font-size: 0.95rem; }
.prose th, .prose td { border: 1px solid var(--line); padding: 9px 12px; text-align: left; }
.prose hr { border: 0; border-top: 1px solid var(--line); margin-block: 2.2em; }
.prose pre { overflow-x: auto; background: var(--paper-2); padding: 14px; }
.prose a { font-weight: 500; }

/* ---------------------------------------------------------------- the aside
   The WordPress-shaped sidebar the client asked for (2026-09-10) — categories,
   the latest articles, one card for the registry. It is the reason `.prose`
   needs no `max-width`: at 1200px the body column lands near 780px, which is a
   readable line, and the page still fills its container edge to edge.

   Below 1000px the aside falls UNDER the article rather than beside it, and it
   is last in the source order, so that is also the reading order: an article
   never starts with a list of links to somewhere else.

   `minmax(0, 1fr)` and not `1fr`: a grid track's default `min-width: auto` is
   its content's intrinsic width, and one long unbroken URL in an article body
   would push the aside off the page instead of scrolling inside its own
   `.prose table`/`pre`. */
.with-aside { display: grid; gap: clamp(32px, 5vw, 56px); align-items: start; }
@media (min-width: 1000px) {
    .with-aside { grid-template-columns: minmax(0, 1fr) var(--aside); }
}

.sidebar { min-width: 0; display: grid; gap: clamp(28px, 3vw, 38px); align-content: start; }
.sidebar__widget { min-width: 0; }
.sidebar__title {
    margin: 0 0 0.9em;
    padding-bottom: 0.5em;
    border-bottom: 1px solid var(--line);
    font-size: 1rem;
    font-weight: 600;
    font-stretch: 108%;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}
.sidebar__list { list-style: none; margin: 0; padding: 0; }
.sidebar__list li + li { margin-top: 0.7em; }
.sidebar__list a { text-decoration: none; }
.sidebar__list a:hover { text-decoration: underline; }
/* The subcategories of a section, indented under it the way the old blog's
   category widget nested them. */
.sidebar__sub { list-style: none; margin: 0.6em 0 0; padding-left: 14px; border-left: 1px solid var(--line); }
.sidebar__sub li + li { margin-top: 0.55em; }
.sidebar__sub a { font-size: 0.94rem; color: var(--muted); }

.sidebar__posts { list-style: none; margin: 0; padding: 0; display: grid; gap: 1.1rem; }
.sidebar__post { display: grid; grid-template-columns: 74px minmax(0, 1fr); gap: 12px; align-items: start; }
.sidebar__post--noimage { grid-template-columns: minmax(0, 1fr); }
.sidebar__thumb { display: block; width: 74px; height: 56px; object-fit: cover; }
.sidebar__post-title { display: block; font-size: 0.95rem; line-height: 1.35; font-weight: 500; text-decoration: none; }
.sidebar__post-title:hover { text-decoration: underline; }
.sidebar__date { display: block; margin-top: 4px; font-size: 0.82rem; color: var(--muted); font-variant-numeric: tabular-nums; }

.sidebar__card { padding: 20px; background: var(--paper-2); border: 1px solid var(--line); }
.sidebar__card p { margin: 0 0 1rem; font-size: 0.95rem; color: var(--muted); }
.sidebar__card .button { font-size: 0.9rem; padding: 9px 18px; }

/* ---------------------------------------------------------------- archive */
.archive__intro { color: var(--muted); }
.archive__children-list { list-style: none; margin: 1.4rem 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px 10px; }
.archive__children-list a {
    display: inline-block;
    padding: 6px 14px;
    background: var(--paper-2);
    border: 1px solid var(--line);
    text-decoration: none;
    font-size: 0.92rem;
    font-weight: 500;
}
.archive__children-list a:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.archive__grid, .related__grid {
    display: grid;
    gap: clamp(28px, 4vw, 44px);
    grid-template-columns: 1fr;
    margin-block: clamp(32px, 4vw, 48px);
}
@media (min-width: 640px) { .archive__grid, .related__grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .archive__grid, .related__grid { grid-template-columns: repeat(3, 1fr); } }
.archive__empty { color: var(--muted); padding-block: 40px; }

/* ---------------------------------------------------------------- teaser */
.teaser { display: flex; flex-direction: column; }
.teaser__media { display: block; }
.teaser__media img { display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.teaser__body { padding-top: 14px; }
.teaser__meta { margin: 0 0 6px; font-size: 0.85rem; font-weight: 500; }
.teaser__meta a { text-decoration: none; }
.teaser__meta a:hover { text-decoration: underline; }
.teaser__title { margin: 0 0 6px; font-size: 1.18rem; line-height: 1.22; font-stretch: 104%; }
.teaser__title a { color: var(--ink); text-decoration: none; }
.teaser__title a:hover { color: var(--lake); }
.teaser__date { margin: 0 0 8px; color: var(--muted); font-size: 0.85rem; font-variant-numeric: tabular-nums; }
.teaser__excerpt { margin: 0; color: var(--muted); font-size: 0.97rem; }

/* ---------------------------------------------------------------- pagination */
.pagination { display: flex; align-items: center; gap: 22px; padding-bottom: var(--rhythm); font-weight: 500; }
.pagination__status { color: var(--muted); font-size: 0.9rem; font-variant-numeric: tabular-nums; }

.related { margin-top: var(--rhythm); padding-top: clamp(24px, 3vw, 36px); border-top: 1px solid var(--line); }
.related__title { font-size: 1.3rem; }

/* ---------------------------------------------------------------- contact */
/* The form takes the width it needs to be comfortable; the club's address is
   a narrow column beside it on desktop and simply follows it on a phone.
   `align-items: start` keeps the address at the top of its column instead of
   stretching it down the height of an eight-row form. */
.contact { padding-bottom: var(--rhythm); }
.contact__grid { display: grid; gap: clamp(28px, 4vw, 56px); align-items: start; }
@media (min-width: 900px) { .contact__grid { grid-template-columns: minmax(0, 3fr) minmax(220px, 1fr); } }
.contact__title { font-size: clamp(1.3rem, 2.2vw, 1.7rem); }
.contact__main .form { margin-top: clamp(14px, 2vw, 22px); }

.contact-details { padding-bottom: var(--rhythm); }
.contact-details__title { font-size: 1.15rem; margin-bottom: 0.6em; }
.contact-details__address { font-style: normal; line-height: 1.9; color: var(--muted); }
/* Inside the two-column layout the address is a sidebar, not a band: the
   section below it already pays for the space. */
.contact__grid .contact-details { padding-bottom: 0; }

/* ---------------------------------------------------------------- catalogue
   A guide is a boat plan with two prices — printed and PDF — and the two
   numbers are the whole decision. So they are set as a small table of their
   own wherever they appear: label left, figure right, tabular digits, a
   hairline between rows. It is the offsets band's treatment at reading size,
   which is what keeps a shop page looking like this site and not like a shop.
   No card chrome, same as `.cards`: the photograph is the card. */
.prices { margin: 12px 0 0; display: grid; gap: 0; }
.prices__row {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: baseline;
    gap: 0 16px;
    padding-block: 7px;
    border-top: 1px solid var(--line);
}
.prices__kind { font-size: 0.9rem; color: var(--muted); }
.prices__amount {
    margin: 0;
    font-weight: 600;
    font-size: 1.02rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.guide-card { display: flex; flex-direction: column; }
.guide-card__media { display: block; }
.guide-card__media img { display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.guide-card__body { padding-top: 14px; }
.guide-card__title { margin: 0; font-size: 1.18rem; line-height: 1.22; font-stretch: 104%; }
.guide-card__title a { color: var(--ink); text-decoration: none; }
.guide-card__title a:hover { color: var(--lake); }

/* The guide page: the boat on the left, the decision on the right. Someone who
   arrived from a search result sees the hull and the price without scrolling;
   the long description is for after they are interested. */
.guide__top {
    display: grid;
    gap: clamp(24px, 4vw, 48px);
    align-items: start;
    padding-top: clamp(16px, 2.5vw, 28px);
}
@media (min-width: 900px) { .guide__top { grid-template-columns: minmax(0, 3fr) minmax(280px, 2fr); } }
.guide__cover { margin: 0; }
.guide__cover img { display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.guide__title { font-size: clamp(1.7rem, 3.4vw, 2.5rem); }
.guide__summary { color: var(--muted); margin: 0.9rem 0 0; }
.guide__meta { margin: 1.4rem 0 0; font-size: 0.92rem; color: var(--muted); }
.guide__body { padding-block: clamp(28px, 4vw, 48px) var(--rhythm); }

/* One row per variant: what it is, what it costs, and the way to order it. */
.buy { list-style: none; margin: 1.6rem 0 0; padding: 0; }
.buy__row {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 8px 16px;
    padding-block: 14px;
    border-top: 1px solid var(--line);
}
.buy__row:last-child { border-bottom: 1px solid var(--line); }
.buy__kind { font-weight: 600; }
.buy__amount {
    font-size: 1.3rem;
    font-weight: 700;
    font-stretch: 108%;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    text-align: right;
}
.buy__button { grid-column: 1 / -1; justify-self: start; padding: 10px 22px; font-size: 0.95rem; }

/* ---------------------------------------------------------------- ordering */
.order { padding-block: clamp(20px, 3vw, 36px) var(--rhythm); }
.order__subtitle { margin-top: 2rem; font-size: 1.2rem; }
.order__ref strong, .order__bank strong { font-variant-numeric: tabular-nums; }
.order__payment { color: var(--muted); }

/* What is being ordered, restated from the database above the form. */
.order__item {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 16px clamp(16px, 3vw, 28px);
    margin: clamp(16px, 2.5vw, 24px) 0 0;
    padding: 16px;
    background: var(--paper-2);
    border-left: 3px solid var(--epoxy);
}
.order__thumb { display: block; width: 120px; height: 80px; object-fit: cover; }
.order__what { min-width: 0; }
.order__label { margin: 0 0 2px; font-size: 0.85rem; color: var(--muted); }
.order__name { margin: 0; font-weight: 600; font-size: 1.05rem; }
.order__name a { color: var(--ink); }
.order__kind { display: block; font-weight: 400; font-size: 0.9rem; color: var(--muted); }
.order__price { margin: 6px 0 0; font-size: 1.15rem; font-weight: 700; font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- error pages
   The number is set like a station number on a boat plan — large, wide, in
   the hairline colour — so it reads as a label on the page rather than as an
   alarm. The links below it are the point of the page: someone who got here
   from a ten-year-old forum post needs a way on, not an apology. */
.errorpage { padding-block: clamp(40px, 7vw, 96px); }
.errorpage__code {
    margin: 0 0 0.1em;
    font-size: clamp(4rem, 14vw, 9rem);
    font-weight: 700;
    font-stretch: 125%;
    line-height: 0.8;
    letter-spacing: -0.04em;
    color: var(--line);
    font-variant-numeric: tabular-nums;
}
.errorpage__title { font-size: clamp(1.6rem, 3.4vw, 2.4rem); }
.errorpage__lead { color: var(--muted); margin: 1rem 0 2rem; }
.errorpage__links { display: flex; flex-wrap: wrap; gap: 10px 12px; }
.errorpage__links a {
    display: inline-block;
    padding: 8px 16px;
    background: var(--paper-2);
    border: 1px solid var(--line);
    text-decoration: none;
    font-weight: 500;
    font-size: 0.95rem;
}
.errorpage__links a:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }

/* ---------------------------------------------------------------- footer
   Rope along the top edge (the old theme did the same), BOTH marks bottom-left
   beside the name (client, 2026-09-10): the site's ship's wheel, which is what
   the old footer carried, and the association's anchor badge, which is the
   legal entity signing the page. They are two different things and the client
   wants both, so they sit side by side in `.site-footer__marks`, which is the
   first grid column.

   Both sources are dark artwork meant for a white page — the wheel is brown
   line work, the badge a JPEG with a white ground — so each gets a white
   mount. Without it the wheel all but vanishes into the hull colour and the
   badge shows as a bare white rectangle. The extra 9px of top padding is the
   half of the rope that lies on the footer. */
.site-footer {
    position: relative;
    background: var(--ink);
    color: #b9c8c9;
    padding-block: calc(clamp(36px, 5vw, 60px) + 9px) clamp(36px, 5vw, 60px);
    margin-top: auto;
    font-size: 0.95rem;
}
.site-footer a { color: var(--paper); }
.site-footer__inner { display: grid; gap: 24px clamp(24px, 4vw, 48px); align-items: start; }
.site-footer__marks {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 14px;
    grid-row: 1;
}
.site-footer__mark {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 84px;
    height: 84px;
    padding: 7px;
    background: #fff;
    border-radius: 6px;
}
.site-footer__mark img { display: block; width: 100%; height: 100%; object-fit: contain; }
@media (min-width: 720px) {
    .site-footer__inner { grid-template-columns: auto minmax(0, 2fr) minmax(0, 1fr); }
    .site-footer__social { grid-column: 3; }
    .site-footer__payment { grid-column: 3; }
    .site-footer__consent { grid-column: 3; }
}
.site-footer__org { margin: 0; line-height: 1.9; font-style: normal; }
.site-footer__org strong { display: block; color: var(--paper); font-weight: 600; font-stretch: 108%; }
.site-footer__legal { display: grid; gap: 8px; align-content: start; }
.site-footer__social { display: grid; gap: 8px; align-content: start; }
.site-footer__payment { margin: 0; }
.site-footer__barion { display: block; width: min(100%, 260px); height: auto; margin-top: 6px; background: #fff; padding: 4px 6px; border-radius: 4px; }
.site-footer__copy { grid-column: 1 / -1; margin: 0; padding-top: 20px; border-top: 1px solid var(--line-ink); color: #8ba0a2; font-size: 0.875rem; }

body { display: flex; flex-direction: column; min-height: 100vh; }
main { flex: 1 0 auto; }

/* ---------------------------------------------------------------- registry
   The boat registry. A card per boat in the old page's order — builder,
   city | type | designer, the boat's name and year, the words, the link —
   and the photo in the same knotted-rope frame the old page's cards had
   (`team-item`, the team's own class, reused there too). Square crop: the
   frame is square and the full photo is one click away. */
.registry__header { padding-top: clamp(20px, 3vw, 36px); }
.registry__count { margin: 0.8rem 0 1.4rem; color: var(--muted); }
.registry__count strong { color: var(--ink); font-size: 1.15em; font-variant-numeric: tabular-nums; }
.registry__filter {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 14px;
    margin-top: clamp(20px, 3vw, 32px);
    padding-block: 16px;
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    font-size: 0.95rem;
    font-weight: 500;
}
.registry__filter .button { padding: 8px 18px; font-size: 0.92rem; }

.boats {
    list-style: none;
    margin: 0;
    padding: clamp(28px, 4vw, 48px) 0;
    display: grid;
    gap: clamp(32px, 4vw, 52px) clamp(20px, 3vw, 36px);
    grid-template-columns: 1fr;
}
@media (min-width: 640px)  { .boats { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .boats { grid-template-columns: repeat(3, 1fr); } }
.boat { display: flex; flex-direction: column; min-width: 0; }
.boat__frame {
    display: block;
    aspect-ratio: 1;
    padding: 11%;
    margin-bottom: 14px;
    background: url('/assets/img/ramka.webp') center / 100% 100% no-repeat;
}
.boat__photo {
    display: block;
    width: 100%;
    height: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border: 5px solid #fff;
    box-shadow: 0 1px 4px rgba(18, 35, 42, 0.18);
}
.boat__builder { font-size: 1.2rem; font-stretch: 106%; margin-bottom: 4px; }
.boat__builder a { text-decoration: none; color: var(--ink); }
.boat__builder a:hover { text-decoration: underline; color: var(--lake); }
.boat__meta { margin: 0 0 6px; font-size: 0.9rem; color: var(--muted); display: flex; flex-wrap: wrap; gap: 2px 0; }
.boat__meta span + span::before { content: "|"; margin-inline: 8px; color: var(--line); }
/* `margin-top: auto` on a flex column pins the button to the bottom, so the
   three cards in a row line their buttons up even when one city wraps. */
.boat__more { margin: 14px 0 0; padding-top: 4px; margin-top: auto; }
.boat__more .button { padding: 8px 18px; font-size: 0.9rem; }

/* ------------------------------------------------- one boat's page
   The gallery on the left, the record on the right, from 900px. Everything
   here is escaped plain text: `boat_registry.description_hu` stores no markup
   at all, so `pre-line` is what carries the builder's paragraphs. */
.boatpage { padding-block: clamp(20px, 3vw, 36px) var(--rhythm); }
.boatpage__header { margin-bottom: clamp(20px, 3vw, 32px); }
.boatpage__meta { margin: 0.7rem 0 0; color: var(--muted); display: flex; flex-wrap: wrap; gap: 2px 0; }
.boatpage__meta span + span::before { content: "|"; margin-inline: 8px; color: var(--line); }

.boatpage__grid { display: grid; gap: clamp(28px, 4vw, 48px); align-items: start; }
@media (min-width: 900px) {
    .boatpage__grid { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); }
}
.boatpage__photo { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; background: var(--paper-2); }
.boatpage__gallery-title {
    margin: 1.6rem 0 0.8rem;
    font-size: 1rem;
    font-weight: 600;
    font-stretch: 108%;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}
.boatpage__thumbs {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    gap: 10px;
}
.boatpage__thumbs img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; background: var(--paper-2); }

/* The record. A definition list drawn as the offsets band is at reading size:
   hairline between rows, the term quiet and the value in ink. */
.facts { margin: 0; }
.facts__row {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 2px 20px;
    padding-block: 11px;
    border-top: 1px solid var(--line);
}
.facts__row:last-child { border-bottom: 1px solid var(--line); }
@media (min-width: 420px) {
    .facts__row { grid-template-columns: minmax(110px, 30%) minmax(0, 1fr); }
}
.facts dt { color: var(--muted); font-size: 0.9rem; }
.facts dd { margin: 0; overflow-wrap: anywhere; }
.boatpage__text { margin: clamp(20px, 3vw, 28px) 0 0; white-space: pre-line; overflow-wrap: anywhere; }
.boatpage__back { margin: clamp(20px, 3vw, 28px) 0 0; font-weight: 500; }

/* ---------------------------------------------------------------- forms
   One form family for the public site (the registry today, contact next).
   Label above the control, a two-column grid from 640px, the error state a
   rule of epoxy under the control. The honeypot is off-canvas, not hidden:
   `display: none` is the one thing every bot checks. */
.registry__form { padding-top: clamp(20px, 3vw, 36px); padding-bottom: var(--rhythm); }
.registry__intro { color: var(--muted); }
.form { max-width: 820px; margin-top: clamp(20px, 3vw, 32px); }
.form__grid { display: grid; gap: 18px 24px; grid-template-columns: 1fr; }
@media (min-width: 640px) {
    .form__grid { grid-template-columns: 1fr 1fr; }
    .field--wide { grid-column: 1 / -1; }
}
.field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.field label { font-weight: 600; font-size: 0.95rem; }
.field input, .field select, .field textarea {
    font: inherit;
    font-size: 1rem;
    color: var(--ink);
    background: #fff;
    border: 1px solid #a9b7b3;
    border-radius: 0;
    padding: 10px 12px;
    width: 100%;
    max-width: 100%;
}
.field textarea { resize: vertical; min-height: 140px; }
/* The list's type filter shares the controls' look but not their width. */
.registry__filter select { font: inherit; font-size: 1rem; color: var(--ink); background: #fff; border: 1px solid #a9b7b3; border-radius: 0; padding: 8px 12px; width: auto; min-width: 220px; max-width: 100%; }
.field input:focus, .field select:focus, .field textarea:focus { outline: 3px solid var(--epoxy); outline-offset: 1px; border-color: var(--epoxy); }
.field input[type="file"] { padding: 8px; background: var(--paper-2); }
.field__hint { color: var(--muted); font-size: 0.85rem; }
.field__error { color: #8a2f1c; font-size: 0.88rem; font-weight: 500; }
.field--error input, .field--error select, .field--error textarea { border-color: #8a2f1c; border-bottom-width: 3px; }
.field--check { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; align-items: start; }
.field--check input { width: 20px; height: 20px; margin-top: 3px; padding: 0; accent-color: var(--epoxy); }
.field--check label { font-weight: 400; font-size: 0.95rem; }
.field--check .field__error { grid-column: 2; }
/* Radio groups on the order form: one line per method, the price on the
   right. The fieldset is a .field so the error styling applies unchanged. */
.field--radios { border: 0; padding: 0; margin: 0; }
.field--radios legend { font-weight: 600; font-size: 0.95rem; padding: 0; margin-bottom: 6px; }
.radio { display: grid; grid-template-columns: auto 1fr auto; gap: 2px 10px; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--line); }
.radio input { width: 20px; height: 20px; margin: 0; accent-color: var(--epoxy); }
.radio__price { font-variant-numeric: tabular-nums; font-weight: 600; }
.radio__hint { grid-column: 2 / -1; color: var(--muted); font-size: 0.85rem; }
.radio[hidden] { display: none; }
.order__total { display: grid; gap: 4px; padding: 12px 16px; background: var(--paper-2); font-variant-numeric: tabular-nums; }
.order__total strong { font-size: 1.15rem; }
.form__trap { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; }
.form__note { color: var(--muted); font-size: 0.9rem; margin: 1.4rem 0 1rem; }

.notice { padding: 14px 18px; border-left: 4px solid var(--lake); background: #fff; margin: 1rem 0 1.4rem; }
.notice--ok { border-left-color: var(--lake); }
.notice--error { border-left-color: #8a2f1c; }

/* ---------------------------------------------------------------- lightbox
   The image viewer (client, 2026-09-10). Markup is server-rendered in
   layout.twig; assets/js/app.js fills it and calls showModal().

   `.is-zoomable` is added by SCRIPT, never by a template: without JavaScript
   an article image is not a control and must not advertise itself as one.

   The <dialog> keeps the browser's own top layer, focus trap, Esc and inert
   background — all of which a hand-rolled overlay gets wrong. Only two things
   have to be undone: its default centred box (we want the whole viewport, so
   a click beside the picture can close it) and its `::backdrop`, which is
   nearly transparent by default. */
.is-zoomable { cursor: zoom-in; }

.lightbox {
    position: fixed;
    inset: 0;
    display: none;              /* a closed <dialog> — see the [open] rule */
    width: 100%;
    max-width: 100%;
    height: 100%;
    max-height: 100%;
    margin: 0;
    padding: clamp(44px, 6vw, 72px) clamp(16px, 5vw, 88px);
    border: 0;
    background: transparent;
    color: #fff;
    overflow: hidden;
}
.lightbox[open] { display: grid; grid-template-rows: minmax(0, 1fr) auto auto; justify-items: center; align-content: center; gap: 14px; }
.lightbox::backdrop { background: rgba(9, 18, 22, 0.92); }

.lightbox__image {
    display: block;
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    object-fit: contain;
    cursor: zoom-out;
    background: rgba(255, 255, 255, 0.04);
}
.lightbox__caption { margin: 0; max-width: 70ch; text-align: center; font-size: 0.95rem; color: #d5dfe6; }
.lightbox__status { margin: 0; font-size: 0.85rem; color: #8ba0a2; font-variant-numeric: tabular-nums; }

.lightbox__close, .lightbox__nav {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    padding: 0;
    border: 1px solid rgba(255, 255, 255, 0.32);
    border-radius: 50%;
    background: rgba(9, 18, 22, 0.6);
    color: #fff;
    cursor: pointer;
}
.lightbox__close:hover, .lightbox__nav:hover { background: #fff; color: var(--ink); border-color: #fff; }
.lightbox__close[hidden], .lightbox__nav[hidden] { display: none; }
.lightbox__close { top: clamp(10px, 2vw, 20px); right: clamp(10px, 2vw, 20px); }
.lightbox__nav { top: 50%; transform: translateY(-50%); }
.lightbox__nav--prev { left: clamp(6px, 1.6vw, 22px); }
.lightbox__nav--next { right: clamp(6px, 1.6vw, 22px); }

/* Under 560px the round buttons would sit on top of the picture. Drop the two
   arrows to the bottom edge, where a thumb reaches them. */
@media (max-width: 560px) {
    .lightbox { padding-inline: 10px; }
    .lightbox__nav { top: auto; bottom: 14px; transform: none; }
}

/* ---------------------------------------------------------------- go to top
   Client, 2026-09-10: some pages are very long. The old theme had the same
   control in the same corner (a little anchor drawing and the words this uses).

   It is a plain anchor to `#top`, which every browser resolves to the top of
   the document, so it works with no JavaScript at all. That is also why the
   hidden state is scoped to `.js` — the class layout.twig's inline script puts
   on <html>: without JS the control is simply always there, rather than
   waiting for a scroll handler that will never run.

   `visibility` and not `display`, so the fade has something to animate and the
   control keeps its place in the layout; `[hidden]` is honoured on top of that
   because .go-top declares its own display. */
.go-top {
    position: fixed;
    right: clamp(12px, 2vw, 24px);
    bottom: clamp(12px, 2vw, 24px);
    z-index: 40;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 46px;
    height: 46px;
    border: 1px solid var(--line-ink);
    border-radius: 50%;
    background: var(--hull);
    color: var(--paper);
    text-decoration: none;
    box-shadow: 0 2px 10px rgba(18, 35, 42, 0.28);
}
.go-top[hidden] { display: none !important; }
.go-top:hover { background: var(--epoxy); border-color: var(--epoxy); color: #fff; }

.js .go-top {
    opacity: 0;
    visibility: hidden;
    transform: translateY(8px);
    transition: opacity 0.18s ease, visibility 0.18s ease, transform 0.18s ease;
}
.js .go-top.is-visible { opacity: 1; visibility: visible; transform: none; }

/* On a narrow screen it would sit on top of the last line of the footer, so it
   shrinks and tucks closer to the corner. */
@media (max-width: 480px) {
    .go-top { width: 40px; height: 40px; }
}

/* ---- Article gallery (partials/gallery.twig) ----------------------------
   The block sits inside `.prose` (see post.twig/page.twig/guide.twig), so
   `.prose h2`, `.prose ul`/`.prose ol` and `.prose li + li` reach it too — all
   three are a DESCENDANT selector with a class+type, specificity (0,1,1),
   which beats a single class like `.gallery__title` or `.gallery__grid`
   (0,1,0) regardless of source order. `.gallery__title`'s heading size/margin
   and `.gallery__grid`'s flush padding are re-stated under `.prose …` to win
   that fight; `.gallery__grid img` doesn't need it because `.prose img` is
   the same (0,1,1) as `.gallery__grid img` and source order (this file, last)
   already decides it. */
.gallery { margin-top: 2.4rem; }
.prose .gallery__title { margin: 0 0 0.8rem; font-size: 1rem; font-weight: 600; font-stretch: 108%; letter-spacing: 0.02em; text-transform: uppercase; }
.gallery__grid { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.6rem; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
.prose .gallery__grid { padding: 0; }
.prose .gallery__grid li { margin: 0; }
.gallery__grid a { display: block; }
.gallery__grid img { display: block; width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; margin: 0; }

/* ---- Video facade → player (acceptance 3.3) ------------------------------ */
.prose figure[data-youtube] { position: relative; aspect-ratio: 16 / 9; margin-inline: 0; background: #000; }
.prose figure[data-youtube] > a,
.prose figure[data-youtube] img,
.prose figure[data-youtube] iframe { display: block; width: 100%; height: 100%; border: 0; }
.prose figure[data-youtube] img { object-fit: cover; }
.prose figure[data-youtube] > a::after {
    content: '';
    position: absolute;
    inset: 50% auto auto 50%;
    width: 68px;
    height: 48px;
    transform: translate(-50%, -50%);
    border-radius: 12px;
    background: rgba(9, 18, 22, 0.78) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 7l8 5-8 5z' fill='%23fff'/%3E%3C/svg%3E") center / 30px no-repeat;
    transition: background-color 0.15s ease;
}
.prose figure[data-youtube] > a:hover::after,
.prose figure[data-youtube] > a:focus-visible::after { background-color: #c4302b; }

/* ---------------------------------------------------------------- consent
   GA4 loads only after "accept" (user ruling 2026-09-15). The banner is a bar
   fixed to the bottom, full width, overlaying the page rather than pushing it
   (no body padding), so it sits above everything until the visitor chooses —
   including `.go-top` (z-index: 40), hence z-index: 50 here. `[hidden]` is
   given its own rule because a component this specific can otherwise win over
   the browser's own `display: none` for the attribute. */
.consent {
    position: fixed;
    inset: auto 0 0 0;
    z-index: 50;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px 24px;
    padding: 18px clamp(16px, 4vw, 32px);
    background: var(--ink);
    color: #fff;
    box-shadow: 0 -2px 16px rgba(9, 18, 22, 0.35);
}
.consent[hidden] { display: none !important; }
.consent__text { margin: 0; flex: 1 1 320px; font-size: 0.95rem; line-height: 1.6; }
.consent__text a { color: #fff; text-decoration: underline; }
.consent__actions { display: flex; flex-wrap: wrap; gap: 10px; }
.consent__actions .button { padding: 10px 20px; font-size: 0.92rem; }
@media (max-width: 480px) {
    .consent { flex-direction: column; align-items: stretch; }
    .consent__actions { justify-content: flex-start; }
}

/* A button styled as a footer link — the "cookie settings" control that
   reopens the consent banner. It is a <button>, not an <a>, because it has no
   URL of its own; it stays hidden until app.js reveals it (a no-JS reader can
   never do anything with it, since the banner it reopens never disappears
   without JS to begin with). */
.link-button {
    display: inline;
    padding: 0;
    border: 0;
    background: none;
    color: var(--paper);
    text-decoration: underline;
    font: inherit;
    cursor: pointer;
}
.link-button:hover { color: #fff; }
.link-button[hidden] { display: none; }
