/* ──────────────────────────────────────────────────────────────────────────
   photoblog.css
   ──────────────────────────────────────────────────────────────────────────
   The Mexican Pictures style. Image-dominant layout — chrome melts away,
   typography supplies the only ornament. Colors and a few spacing knobs
   are CSS custom properties at the :root level so per-blog tweaks are
   one-line edits, not template surgery.
   ────────────────────────────────────────────────────────────────────────── */

:root {
    --bg: #0a0a0a;
    --fg: #e8e4dc;
    --fg-dim: rgba(232, 228, 220, 0.55);
    --fg-faint: rgba(232, 228, 220, 0.25);
    --accent: #dfeeeb;
    --accent-hover: #de0100;          /* link mouseover */
    --accent-active: #c87a1c;         /* link pressed */
    --accent-current: #2c717f;        /* current project / "you are here" */
    --rule: rgba(232, 228, 220, 0.10);

    /* Layout knobs. */
    --max-archive-width: 1400px;
    --projects-sidebar-width: 280px;
    --thumb-min: 170px;
    --gutter-x: 8px;
    --gutter-y: 12px;                 /* +4px vertical breathing room over horizontal */

    /* Type scale. */
    --type-display-size: clamp(1.5rem, 2.4vw, 2.25rem);
    --type-body-size: 1rem;
    --type-caption-size: 0.875rem;
    --type-label-size: 0.75rem;

    /* Font families (overridable via Appearance settings) */
    --font-body: "EB Garamond", Georgia, serif;
    --font-display: "Barlow", system-ui, sans-serif;
}

/* @font-face — sourced from Google Fonts CDN for now. To self-host,
   download the woff2 files into ./fonts/ and swap the `url()` paths.
   Both Barlow and EB Garamond ship under the SIL Open Font License,
   so redistribution is fine when we get to that step. */
@import url("https://fonts.googleapis.com/css2?family=Barlow:wght@300;700&family=EB+Garamond:ital,wght@0,400;0,500;1,400&display=swap");

/* ── reset ─────────────────────────────────────────────────────────────── */

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

html, body { margin: 0; padding: 0; height: 100%; }

/* Tell the browser to use dark UI for native widgets (scrollbar
   thumbs, form controls, focus rings). Without this, macOS renders
   a translucent-white scrollbar thumb against the photoblog's
   black bg that reads as a bright white line. With it, the system
   draws a dark thumb that disappears against the chrome until the
   user starts scrolling. */
html { color-scheme: dark; }

/* Reserve a scrollbar gutter on every page so navigating between
   projects (or any pages where one fits-in-viewport and the next
   needs to scroll) doesn't reflow the layout horizontally. The
   gutter stays empty on short pages and gets used by the scrollbar
   on long ones — same column width either way, no bounce.
   Exception: the homepage locks itself to viewport height and
   never has a body scrollbar, so the reserved gutter just shows
   as an empty band to the right of the project list. The
   :has() override below switches gutter off there. */
html { scrollbar-gutter: stable; }
html:has(body.home-only) { scrollbar-gutter: auto; }

img, svg { display: block; max-width: 100%; }

a { color: var(--accent); text-decoration: none; transition: color .15s ease; }
a:hover { color: var(--accent-hover); }
a:active { color: var(--accent-active); }

button {
    font: inherit; color: inherit; background: none; border: 0;
    padding: 0; margin: 0; cursor: pointer;
}

/* ── chrome ────────────────────────────────────────────────────────────── */

body {
    background: var(--bg);
    color: var(--fg);
    font-family: var(--font-body);
    font-size: var(--type-body-size);
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* Display headings — Barlow Bold. */
h1, h2, h3, h4, .display, .site-title, .project-title, .archive-title {
    font-family: var(--font-display);
    font-weight: 700;
    letter-spacing: 0.01em;
    margin: 0;
}

/* Captions / labels / sidebar lists — Barlow Light. */
.caption, .label, .meta, .breadcrumb, .projects-sidebar, .info-pane {
    font-family: var(--font-display);
    font-weight: 300;
}

/* ──────────────────────────────────────────────────────────────────────────
   Top-left drawer toggle + closing affordance.
   The toggle is a 32×32 button that fades from 30% → 100% on hover. The
   drawer itself slides in from the left as an overlay (image stays
   behind, dimmed). Triggers: click toggle, press `[` or `e`, hover the
   left edge.
   ────────────────────────────────────────────────────────────────────── */

.drawer-toggle {
    position: fixed; top: 12px; left: 12px;
    z-index: 90;
    width: 32px; height: 32px;
    display: grid; place-items: center;
    color: var(--fg);
    opacity: 0.35;
    transition: opacity .2s ease;
}
.drawer-toggle:hover, .drawer-toggle:focus { opacity: 1; color: var(--accent-hover); outline: none; }
.drawer-toggle:active { color: var(--accent-active); }
/* Pages that already display the project list inline (home, project)
   don't need a chrome toggle for it. Hide the icon AND the edge trigger. */
body.no-drawer-toggle .drawer-toggle,
body.no-drawer-toggle .drawer-edge-trigger { display: none; }

/* The image hover-edge trigger sits invisibly at the left edge so a
   user can swipe their cursor to the edge to open the drawer. */
.drawer-edge-trigger {
    position: fixed; top: 0; left: 0;
    width: 18px; height: 100vh;
    z-index: 89;
}

.drawer-overlay {
    position: fixed; inset: 0;
    background: rgba(0, 0, 0, 0.55);
    z-index: 100;
    opacity: 0; pointer-events: none;
    transition: opacity .25s ease;
}
.drawer-overlay.is-open { opacity: 1; pointer-events: auto; }

.drawer-panel {
    position: fixed; top: 0; left: 0;
    height: 100vh; width: min(360px, 80vw);
    background: var(--bg);
    border-right: 1px solid var(--rule);
    transform: translateX(-100%);
    transition: transform .3s cubic-bezier(.2, .8, .2, 1);
    overflow-y: auto;
    padding: 64px 28px 28px;
    z-index: 101;
}
.drawer-overlay.is-open .drawer-panel { transform: translateX(0); }

.drawer-panel h2 {
    font-size: var(--type-label-size);
    color: var(--fg-dim);
    text-transform: uppercase;
    letter-spacing: 0.18em;
    margin-bottom: 16px;
}

.drawer-panel ul {
    list-style: none; margin: 0; padding: 0;
}

.drawer-panel li { margin-bottom: 8px; }

.drawer-panel a {
    display: flex; justify-content: space-between; align-items: baseline;
    color: var(--fg);
    padding: 4px 0;
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 1.05rem;
    border-bottom: 1px solid transparent;
    gap: 12px;
}
.drawer-panel a:hover { color: var(--accent-hover); border-bottom-color: var(--rule); }
.drawer-panel a:active { color: var(--accent-active); }
.drawer-panel a .count {
    font-weight: 300;
    color: var(--fg-dim);
    font-size: var(--type-caption-size);
}
.drawer-panel a.is-current { color: var(--accent-current); }

.drawer-close {
    position: absolute; top: 12px; right: 12px;
    width: 32px; height: 32px;
    display: grid; place-items: center;
    color: var(--fg-dim);
}
.drawer-close:hover { color: var(--fg); }

/* ──────────────────────────────────────────────────────────────────────────
   Post page — image takes the screen.
   ────────────────────────────────────────────────────────────────────── */

.post-stage {
    /* The image's "stage": fills viewport, contains the photo with
       letterboxing on whichever axis doesn't match aspect. The footer
       sits beneath in document flow; user scrolls down a touch to
       reveal it. */
    position: relative;
    height: 100vh;
    display: grid;
    grid-template-rows: auto 1fr auto;  /* title strip · image · breadcrumb */
    background: var(--bg);
}

/* Top strip: blog title, left-aligned, low-key. Sized to match the
   drawer-toggle (32px tall) so the brand line and the icon read as a
   single chrome row. */
.post-title-strip {
    padding: 8px 16px 0 56px;       /* leave room for the drawer toggle (top-left) */
    color: var(--fg);
    font-family: var(--font-display);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-size: 1.2rem;
    line-height: 32px;              /* match the 32×32 drawer toggle */
}
.post-title-strip a { color: var(--fg); }
.post-title-strip a:hover { color: var(--accent-hover); }

.post-image-wrap {
    position: relative;
    display: grid;
    place-items: center;
    overflow: hidden;
    min-height: 0;                   /* lets the 1fr row shrink-fit so img fills it */
}

/* Carousel-hero variant: the body's leading multi-image carousel
   takes the photo-content area instead of a single hero image. We
   keep the flex centering pattern that the single-image hero uses
   (the `.post-stage` middle row sizes itself to the viewport), and
   force the carousel + its track to fit the wrap so the row layout
   shrink-fits to the visible width instead of growing to natural
   image size. `min-width: 0` is the standard "flex doesn't override
   container width" escape hatch — without it, the carousel's flex
   row would push past the viewport and trigger horizontal scroll. */
.post-image-wrap--carousel-hero {
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    padding: 0 clamp(8px, 2vw, 24px);
    min-width: 0;
}
.post-image-wrap--carousel-hero .carousel {
    width: 100%;
    height: 100%;
    max-width: 100%;
    min-width: 0;
    margin: 0;
}
.post-image-wrap--carousel-hero .carousel-track {
    width: 100%;
    height: 100%;
    max-width: 100%;
    align-items: stretch;
}
/* Image rules: chained with `.carousel[data-layout="row"]` so this
   variant outranks the bottom-of-file row-layout rule (which forces
   `max-height: none` and would otherwise let tall portraits overflow
   the wrap and get cropped). The clamp on both axes is what makes
   the row letterbox properly: each image gets an equal share of
   width via `flex: 1 1 0`, fits inside that share + the wrap's
   height with `object-fit: contain`, and any unused space inside
   its slot fills with the wrap's background. Result: zero crops
   regardless of viewport aspect ratio (which was the
   olvia-aurora-in-paras bug — landscape windows clipped the tall
   portrait crops top/bottom). */
.post-image-wrap--carousel-hero .carousel-track img {
    flex: 1 1 0;
    width: 100%;
    height: 100%;
    min-width: 0;
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}
.post-image-wrap--carousel-hero .carousel[data-layout="row"] .carousel-track {
    align-items: center;
}
.post-image-wrap--carousel-hero .carousel[data-layout="row"] .carousel-track img {
    flex: 1 1 0;
    width: auto;
    height: auto;
    min-width: 0;
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

.post-image-wrap img {
    min-width: 0;
    min-height: 0;
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    opacity: 0;
    animation: fade-in .35s ease forwards;
}

@keyframes fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* Click-zones on the image. Left half goes back, right half goes
   forward in the current project. The actual prev/next URLs are
   resolved by JS from data-* attrs on the wrap. The cursor changes
   so the affordance is hinted without a permanent visual. */
.post-image-wrap[data-has-prev="1"] .post-clickzone-left  { cursor: w-resize; }
.post-image-wrap[data-has-next="1"] .post-clickzone-right { cursor: e-resize; }

.post-clickzone-left, .post-clickzone-right {
    position: absolute; top: 0; bottom: 0;
    width: 50%;
    background: transparent;
    z-index: 5;
}
.post-clickzone-left  { left: 0; }
.post-clickzone-right { right: 0; }

/* Subtle prev/next arrow hints overlaid on the image. They live above
   the click-zones so a hover on the arrow itself shows the cursor.
   They fade in with the click-zone hover. */
.post-arrow {
    position: absolute; top: 50%;
    transform: translateY(-50%);
    z-index: 6;
    opacity: 0;
    transition: opacity .2s ease;
    pointer-events: none;
    color: var(--fg);
    font-size: 2rem; line-height: 1;
    text-shadow: 0 1px 4px rgba(0,0,0,0.6);
    padding: 0 18px;
}
.post-arrow--prev { left: 0; }
.post-arrow--next { right: 0; }

.post-clickzone-left:hover  ~ .post-arrow--prev,
.post-clickzone-right:hover ~ .post-arrow--next {
    opacity: 0.85;
}

/* Breadcrumb + info trigger.
   Sits in the letterbox space below the image when there's room, off-
   screen-below otherwise (small phones). Fades after a few seconds
   like the captions, returns on mouse move. */
.post-breadcrumb-row {
    display: flex; justify-content: center; align-items: center; gap: 12px;
    padding: 12px 16px 18px;
    color: var(--fg-dim);
    font-size: var(--type-caption-size);
    font-weight: 300;
    transition: opacity .35s ease;
    flex-wrap: wrap;
}
.post-stage.is-idle .post-breadcrumb-row {
    opacity: 0;
}
.post-stage.is-blackbox .post-breadcrumb-row,
.post-stage.is-blackbox .drawer-toggle,
.post-stage.is-blackbox .drawer-edge-trigger,
.post-stage.is-blackbox .post-arrow {
    opacity: 0 !important;
    pointer-events: none !important;
}

.breadcrumb-sep {
    color: var(--fg-faint);
    margin: 0 4px;
}

.post-breadcrumb-row a {
    color: var(--fg);
}
.post-breadcrumb-row a:hover { color: var(--accent-hover); }
.post-breadcrumb-row a:active { color: var(--accent-active); }
.post-breadcrumb-row .current {
    color: var(--fg-dim);
}
.post-stage.is-blackbox .post-title-strip {
    opacity: 0 !important;
    pointer-events: none !important;
}

.post-info-trigger,
.post-comments-trigger {
    color: var(--fg-dim);
    width: 28px; height: 28px;
    display: inline-grid; place-items: center;
    border-radius: 50%;
    margin-left: 6px;
    transition: color .15s ease, background .15s ease;
}
.post-info-trigger:hover,
.post-comments-trigger:hover {
    color: var(--fg);
    background: rgba(232, 228, 220, 0.06);
}

/* Right-slide info pane.
   Description, location, raw alt — anything supplementary. Wider than
   a typical drawer so multi-paragraph notes don't read as a thin column. */
.info-pane {
    position: fixed; top: 0; right: 0;
    height: 100vh; width: min(560px, 92vw);
    background: var(--bg);
    border-left: 1px solid var(--rule);
    transform: translateX(100%);
    transition: transform .3s cubic-bezier(.2, .8, .2, 1);
    z-index: 110;
    padding: 64px 36px 36px;
    overflow-y: auto;
    color: var(--fg);
}
.info-pane.is-open { transform: translateX(0); }
.info-pane h2 {
    font-size: 1.25rem;
    color: var(--fg);
    text-transform: none;
    letter-spacing: 0.01em;
    margin-bottom: 20px;
    font-family: var(--font-display);
    font-weight: 700;
}
.info-pane dl { margin: 0; }
.info-pane dt {
    font-family: var(--font-display);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    font-size: var(--type-label-size);
    color: var(--fg-dim);
    margin-top: 22px;
}
.info-pane dd {
    margin: 6px 0 0;
    font-family: var(--font-body);
    font-size: 1.125rem;
    line-height: 1.65;
}
.info-pane a:hover  { color: var(--accent-hover); }
.info-pane a:active { color: var(--accent-active); }

.info-pane-close,
.comments-overlay-close {
    position: absolute; top: 12px; right: 12px;
    width: 32px; height: 32px;
    display: grid; place-items: center;
    color: var(--fg-dim);
}
.info-pane-close:hover,
.comments-overlay-close:hover { color: var(--fg); }

/* Comments overlay. Centered modal-ish panel; click backdrop to dismiss. */
.comments-overlay {
    position: fixed; inset: 0;
    background: rgba(0, 0, 0, 0.7);
    z-index: 120;
    opacity: 0; pointer-events: none;
    transition: opacity .25s ease;
    display: grid; place-items: center;
    padding: 24px;
}
.comments-overlay.is-open { opacity: 1; pointer-events: auto; }

.comments-overlay .comments-panel {
    background: var(--bg);
    border: 1px solid var(--rule);
    width: min(640px, 100%);
    max-height: 80vh;
    overflow-y: auto;
    padding: 32px;
    position: relative;
    color: var(--fg);
}
.comments-overlay h2 {
    font-size: var(--type-label-size);
    color: var(--fg-dim);
    text-transform: uppercase;
    letter-spacing: 0.18em;
    margin-bottom: 20px;
}
.comment {
    border-top: 1px solid var(--rule);
    padding: 16px 0;
    font-family: var(--font-body);
}
.comment:first-of-type { border-top: 0; }
.comment-meta {
    font-family: var(--font-display);
    font-weight: 300;
    font-size: var(--type-caption-size);
    color: var(--fg-dim);
    margin-bottom: 6px;
}
.comment-meta a { color: var(--fg); }
.comment-body p:first-child { margin-top: 0; }
.comment-body p:last-child  { margin-bottom: 0; }

/* Boundary overlay — appears when the user clicks past the first or
   last post in a project. */
.boundary-overlay {
    position: fixed; inset: 0;
    background: rgba(10, 10, 10, 0.86);
    z-index: 130;
    opacity: 0; pointer-events: none;
    transition: opacity .3s ease;
    display: grid; place-items: center;
}
.boundary-overlay.is-open { opacity: 1; pointer-events: auto; }
.boundary-overlay .boundary-message {
    text-align: center;
    color: var(--fg);
    max-width: 420px;
}
.boundary-overlay .boundary-message p {
    font-family: var(--font-body);
    font-size: 1.1rem;
    color: var(--fg-dim);
    margin: 0 0 20px;
}
.boundary-overlay .boundary-message a {
    font-family: var(--font-display);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--accent);
    border: 1px solid var(--rule);
    padding: 10px 18px;
    display: inline-block;
}
.boundary-overlay .boundary-message a + a { margin-left: 12px; }
.boundary-overlay .boundary-message .boundary-actions { margin-top: 4px; }
/* Direction-aware action visibility. JS adds `is-end` / `is-start`
   when showing the overlay; the matching action renders, the other
   stays hidden. Selectors include `.boundary-message a` to outrank
   the earlier `.boundary-message a { display: inline-block }` rule. */
.boundary-overlay .boundary-message a.boundary-action--end,
.boundary-overlay .boundary-message a.boundary-action--start { display: none; }
.boundary-overlay.is-end  .boundary-message a.boundary-action--end   { display: inline-block; }
.boundary-overlay.is-start .boundary-message a.boundary-action--start { display: inline-block; }
.boundary-overlay .boundary-message a:hover {
    color: var(--accent-hover);
    border-color: var(--fg-dim);
}
.boundary-overlay .boundary-message a:active {
    color: var(--accent-active);
}

/* ──────────────────────────────────────────────────────────────────────────
   Site footer — small, low-key, on every page. Hidden inside the
   post-stage view (the photo dominates there; footer would compete).
   Edit the markup in partials/footer.html.
   ────────────────────────────────────────────────────────────────────── */
.site-footer {
    border-top: 1px solid var(--rule);
    padding: 18px clamp(16px, 4vw, 48px);
    color: var(--fg-dim);
    font-family: var(--font-display);
    font-weight: 300;
    font-size: var(--type-caption-size);
}
.site-footer-inner {
    max-width: var(--max-archive-width);
    margin: 0 auto;
    display: flex; justify-content: space-between; align-items: baseline;
    gap: 16px;
    flex-wrap: wrap;
}
.site-footer a { color: var(--fg-dim); }
.site-footer a:hover  { color: var(--accent-hover); }
.site-footer a:active { color: var(--accent-active); }
.site-footer-nav a + a { margin-left: 16px; }

/* About-this-project pane — visual + slide animation match the post
   info-pane but lives on category pages. Shared rules above (.info-pane)
   handle width/transition; this just gives prose a sane reading width
   and styles for paragraphs and links inside the description. */
.about-pane .about-body {
    font-family: var(--font-body);
    font-size: 1.125rem;
    line-height: 1.65;
    color: var(--fg);
}
.about-pane .about-body p:first-child { margin-top: 0; }
.about-pane .about-body p:last-child  { margin-bottom: 0; }
.about-pane .about-body a {
    color: var(--accent);
    text-decoration: underline;
    text-decoration-color: var(--fg-faint);
}
.about-pane .about-body a:hover  { color: var(--accent-hover); }
.about-pane .about-body a:active { color: var(--accent-active); }

/* ──────────────────────────────────────────────────────────────────────────
   Text-only post — beautifully-typed page for posts without a hero
   image. Used for the occasional explanatory post.
   ────────────────────────────────────────────────────────────────────── */
/* Text-only post: rendered INSIDE the same .post-stage shell as image
   posts, so the chrome (drawer toggle, breadcrumb) sits in the same
   places. The text replaces the image in the middle row, scrollable
   if it overflows. */
.post-text-body {
    align-self: stretch;
    overflow-y: auto;
    padding: 24px clamp(16px, 6vw, 80px);
    display: grid;
    place-items: start center;
}
.post-text-body article {
    max-width: 680px;
    width: 100%;
}
.post-text-body header {
    margin-bottom: 28px;
}
.post-text-body h1 {
    font-size: clamp(1.6rem, 3vw, 2.4rem);
    line-height: 1.15;
    margin-bottom: 8px;
}
.post-text-body time {
    font-family: var(--font-display);
    font-weight: 300;
    color: var(--fg-dim);
    font-size: var(--type-caption-size);
}
.post-text-body .post-body {
    font-family: var(--font-body);
    font-size: 1.125rem;
    line-height: 1.65;
}
.post-text-body .post-body p { margin: 1em 0; }
.post-text-body .post-body img { margin: 1.5em auto; }
.post-text-body .post-body a { color: var(--accent); text-decoration: underline; text-decoration-color: var(--fg-faint); }
.post-text-body .post-body a:hover  { color: var(--accent-hover); text-decoration-color: var(--accent-hover); }
.post-text-body .post-body a:active { color: var(--accent-active); }
.post-text-body .post-body blockquote {
    border-left: 2px solid var(--rule);
    padding-left: 1em;
    color: var(--fg-dim);
    font-style: italic;
}

/* ──────────────────────────────────────────────────────────────────────────
   Project (category) page — title left, two-column body.
   Closely follows the original Mexican Pictures category-page layout.
   ────────────────────────────────────────────────────────────────────── */

.project-page {
    padding: 24px clamp(16px, 4vw, 48px);
}

.project-header {
    margin-bottom: 24px;
}
.project-header .home-link {
    display: inline-block;
    color: var(--fg-dim);
    font-family: var(--font-display);
    font-weight: 300;
    font-size: var(--type-caption-size);
    margin-bottom: 6px;
    letter-spacing: 0.05em;
}
.project-header .home-link:hover { color: var(--fg); }
.project-header h1 {
    font-size: clamp(1.6rem, 2.6vw, 2.4rem);
}

.project-layout {
    display: grid;
    grid-template-columns: 1fr var(--projects-sidebar-width);
    gap: clamp(24px, 4vw, 48px);
    align-items: start;
}

@media (max-width: 720px) {
    .project-layout { grid-template-columns: 1fr; }
    .projects-sidebar { order: 2; margin-top: 32px; }
}

.project-grid {
    display: grid;
    /* `--thumb-w` / `--thumb-h` / `--thumb-bg` / `--thumb-aspect` come
       from the per-blog thumbnail config (set by the generator as inline
       style on the .project-grid element). Falling back via `var(... ,
       <default>)` keeps any template that hasn't been regenerated yet
       rendering at the legacy defaults. */
    grid-template-columns: repeat(auto-fill, minmax(var(--thumb-min), 1fr));
    column-gap: var(--gutter-x);
    row-gap: var(--gutter-y);
}
.project-grid a {
    display: block;
    aspect-ratio: var(--thumb-aspect, 170 / 112);
    background: var(--thumb-bg, #111);
    overflow: hidden;
    transition: opacity .15s ease, outline-color .15s ease;
    outline: 2px solid transparent;
    outline-offset: 0;
}
.project-grid a:hover { opacity: 0.85; outline-color: var(--accent-hover); }
.project-grid a:active { outline-color: var(--accent-active); }
.project-grid img {
    width: 100%; height: 100%;
    object-fit: contain;
    background: var(--thumb-bg, #111);
    display: block;
}

/* Carousel-hero composite tile: every image in the post's leading
   carousel rendered inline, sharing the tile width via flex so the
   in-tile preview mirrors the post page's row layout. The link's
   `aspect-ratio` already matches the blog's standard thumb shape;
   the inner span fills it and lets the images letterbox themselves
   against the link's `--thumb-bg`. `min-width: 0` is the standard
   "flex doesn't override container width" escape hatch. */
.project-grid a > .carousel-thumb {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    justify-content: center;
    gap: 4px;
    width: 100%;
    height: 100%;
    min-width: 0;
    padding: 4px;
    box-sizing: border-box;
}
.project-grid a > .carousel-thumb img {
    flex: 1 1 0;
    width: auto;
    height: 100%;
    min-width: 0;
    max-height: 100%;
    object-fit: contain;       /* preserve aspect inside the share-of-row */
}

/* "About this project..." link sits top-right of the grid header row
   on project pages; only rendered when the category has description
   text. Overlay reuses the post-page info-pane visual. */
.project-grid-header {
    display: flex; justify-content: flex-end; align-items: baseline;
    margin-bottom: 12px;
    gap: 16px;
}
.project-grid-header .about-link {
    font-family: var(--font-display);
    font-weight: 300;
    font-size: var(--type-caption-size);
    color: var(--fg-dim);
    letter-spacing: 0.04em;
}
.project-grid-header .about-link:hover  { color: var(--accent-hover); }
.project-grid-header .about-link:active { color: var(--accent-active); }

.projects-sidebar h2 {
    font-size: var(--type-label-size);
    color: var(--fg-dim);
    text-transform: uppercase;
    letter-spacing: 0.18em;
    margin-bottom: 14px;
    font-family: var(--font-display);
    font-weight: 700;
    border-bottom: 1px dotted var(--rule);
    padding-bottom: 8px;
}
.projects-sidebar ul { list-style: none; margin: 0; padding: 0; }
.projects-sidebar li {
    display: flex; justify-content: space-between; align-items: baseline;
    padding: 6px 0;
    font-family: var(--font-display);
}
.projects-sidebar a {
    color: var(--fg);
    font-weight: 700;
    flex: 1;
}
.projects-sidebar a:hover  { color: var(--accent-hover); }
.projects-sidebar a:active { color: var(--accent-active); }
.projects-sidebar .count {
    font-weight: 300;
    color: var(--fg-dim);
    font-size: var(--type-caption-size);
}
.projects-sidebar a.is-current { color: var(--accent-current); }

/* ──────────────────────────────────────────────────────────────────────────
   Index / home page.
   "Mexican Pictures" + attribution top left, big rotating hero on the
   right (or below, on narrow viewports), project list below the hero.
   No date range.
   ────────────────────────────────────────────────────────────────────── */

/* The homepage is the only page that locks itself to viewport height
   so the blog title + hero stay in place while just the project list
   scrolls. Other pages use the document's natural scroll. The
   `100dvh` unit accounts for mobile address-bar collapse so the page
   doesn't shift around when the URL bar shows / hides. */
.home-page {
    height: 100dvh;
    display: grid;
    grid-template-rows: auto minmax(0, 1fr);
    padding: 32px clamp(16px, 4vw, 48px) 24px;
    box-sizing: border-box;
    overflow: hidden;                /* no page-level scroll */
}
/* Drop the site footer on the homepage so the page truly fits
   viewport height — without this the footer pushes total document
   height past 100dvh and re-introduces the page-level scroll the
   user asked us to remove. About + RSS still reachable from
   individual project pages, where the footer renders normally. */
body.home-only .site-footer { display: none; }
.home-header {
    display: flex; flex-direction: column;
    gap: 0;
    margin-bottom: 24px;
}
.home-header .site-title {
    font-size: clamp(1.8rem, 3vw, 2.6rem);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    line-height: 1.1;
}
.home-header .attribution {
    font-family: var(--font-display);
    font-weight: 300;
    color: var(--fg-dim);
    font-size: var(--type-caption-size);
    letter-spacing: 0.04em;
    margin-top: 2px;
}
.home-header .attribution a { color: var(--fg); }
.home-header .attribution a:hover  { color: var(--accent-hover); }
.home-header .attribution a:active { color: var(--accent-active); }

.home-body {
    display: grid;
    /* Hero on the LEFT (1fr column), project list on the RIGHT.
       Source order matches the visual order, so no per-child
       grid-column placement is needed. */
    grid-template-columns: 1fr var(--projects-sidebar-width);
    gap: clamp(24px, 4vw, 48px);
    align-items: stretch;
    min-height: 0;                   /* lets children's overflow:auto actually scroll */
}
@media (max-width: 720px) {
    /* Narrow viewports collapse to a single column. The page
       reverts to natural document scroll there because there's no
       useful split to lock — header + hero + projects all become
       one stack. */
    .home-page { height: auto; overflow: visible; }
    .home-body { grid-template-columns: 1fr; }
    .home-projects { margin-top: 32px; max-height: none; overflow: visible; }
}

/* Left column: hero + welcome text. Sized to the row height so the
   hero shrinks on shorter viewports instead of being clipped. */
.home-main {
    display: flex; flex-direction: column;
    gap: 12px;
    min-height: 0;
    align-self: stretch;
}

.home-hero {
    position: relative;
    background: #111;
    overflow: hidden;
    aspect-ratio: 850 / 562;
    display: grid;
    place-items: center;
    border: 1px solid var(--rule);
    /* Width-first sizing: take 100% of the column, but never push
       past the available height (1fr row minus the welcome text +
       its margin). `min-height: 0` lets it shrink below intrinsic;
       `min(100%, …)` clamps from both sides so a tall narrow viewport
       doesn't blow up the hero past the viewport's vertical room
       (the bug the user flagged: hero overflowed on smaller
       screens). object-fit: cover on the inner img keeps the photo
       filling the box at every breakpoint. */
    width: 100%;
    max-width: 100%;
    max-height: 100%;
    min-height: 0;
    margin: 0;
}
.home-hero a { display: block; width: 100%; height: 100%; }
.home-hero img {
    width: 100%; height: 100%;
    object-fit: cover;
    opacity: 0;
    animation: fade-in .5s ease forwards;
}

/* The home-hero-caption block was removed in the homepage cleanup
   (no project/title text under the image anymore). The CSS rules
   are also dropped — anything still tagged `.home-hero-caption` in
   downstream templates falls through to default browser styling. */

.home-welcome {
    font-family: var(--font-body);
    font-size: 1.05rem;
    line-height: 1.55;
    color: var(--fg);
    max-width: 640px;
    margin: 8px 0 0;
}

/* Right column scrolls; the rest of the page is fixed. Lets the
   browser-native dark scrollbar do its job (driven by the
   `color-scheme: dark` declaration on <html>) — the previous
   custom override was a light translucent-white thumb that read
   as bright on the photoblog's black chrome. Padding-right keeps
   the project count from kissing the scrollbar. */
.home-projects {
    align-self: stretch;
    max-height: 100%;
    overflow-y: auto;
    padding-right: 14px;
    scrollbar-width: thin;
}
.home-projects a:hover  { color: var(--accent-hover); }
.home-projects a:active { color: var(--accent-active); }

/* ──────────────────────────────────────────────────────────────────────────
   Archive / tag pages — minimal, padded, capped width. The user
   flagged date archive as a "later, with a scrubbable grid" — for now
   we render a clean dated list so the URL works and the schema is
   honored.
   ────────────────────────────────────────────────────────────────────── */

.archive-page {
    max-width: var(--max-archive-width);
    margin: 0 auto;
    padding: 48px clamp(16px, 4vw, 48px);
}
.archive-header { margin-bottom: 24px; }
.archive-header h1 { font-size: clamp(1.6rem, 2.6vw, 2.4rem); }
.archive-header .home-link {
    display: inline-block;
    color: var(--fg-dim);
    font-family: var(--font-display);
    font-weight: 300;
    font-size: var(--type-caption-size);
    margin-bottom: 6px;
    letter-spacing: 0.05em;
}
.archive-header .home-link:hover { color: var(--fg); }
.archive-list { list-style: none; padding: 0; margin: 0; }
.archive-list li {
    display: flex; justify-content: space-between; align-items: baseline;
    padding: 10px 0;
    border-bottom: 1px solid var(--rule);
    gap: 16px;
}
.archive-list a {
    color: var(--fg); font-weight: 700;
    font-family: var(--font-display);
    flex: 1;
}
.archive-list .archive-date {
    color: var(--fg-dim);
    font-family: var(--font-display);
    font-weight: 300;
    font-size: var(--type-caption-size);
    white-space: nowrap;
}

/* ──────────────────────────────────────────────────────────────────────────
   Static page (about, projects index, colophon, etc.). Same chrome as
   text-only post — beautifully-typed, capped width.
   ────────────────────────────────────────────────────────────────────── */
.static-page { /* alias of .about-page — same look-and-feel */ }

/* About page — single column, capped width. Hand-editable in
   templates/photoblog/about.html. */
.about-page {
    max-width: 720px;
    margin: 0 auto;
    padding: 48px clamp(16px, 4vw, 48px) 64px;
}
.about-header { margin-bottom: 32px; }
.about-header .home-link {
    display: inline-block;
    color: var(--fg-dim);
    font-family: var(--font-display);
    font-weight: 300;
    font-size: var(--type-caption-size);
    margin-bottom: 6px;
    letter-spacing: 0.05em;
}
.about-header .home-link:hover  { color: var(--accent-hover); }
.about-header .home-link:active { color: var(--accent-active); }
.about-header h1 {
    font-size: clamp(1.6rem, 2.6vw, 2.4rem);
}
.about-portrait {
    margin: 0 0 28px;
    background: #111;
    border: 1px solid var(--rule);
    aspect-ratio: 4 / 3;
    overflow: hidden;
    display: grid; place-items: center;
}
.about-portrait img {
    width: 100%; height: 100%;
    object-fit: cover;
}
.about-prose {
    font-family: var(--font-body);
    font-size: 1.125rem;
    line-height: 1.65;
}
.about-prose p { margin: 0 0 1em; }
.about-prose code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.9em;
    color: var(--fg-dim);
}
.about-social {
    list-style: none; margin: 28px 0 0; padding: 0;
    display: flex; flex-wrap: wrap; gap: 18px;
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--type-caption-size);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}
.about-social a { color: var(--fg); }
.about-social a:hover  { color: var(--accent-hover); }
.about-social a:active { color: var(--accent-active); }

/* 404 — same aesthetic as the boundary overlay's centered message. */
.not-found {
    min-height: calc(100vh - 80px);
    display: grid; place-items: center;
    padding: 24px;
}
.not-found .nf-inner {
    text-align: center;
    color: var(--fg);
    max-width: 480px;
}
.not-found h1 {
    font-size: clamp(2.4rem, 5vw, 4rem);
    margin-bottom: 8px;
}
.not-found p {
    font-family: var(--font-body);
    font-size: 1.1rem;
    color: var(--fg-dim);
    margin: 0 0 24px;
}
.not-found a {
    font-family: var(--font-display);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--accent);
    border: 1px solid var(--rule);
    padding: 10px 18px;
    display: inline-block;
}
.not-found a:hover  { color: var(--accent-hover); border-color: var(--fg-dim); }
.not-found a:active { color: var(--accent-active); }

/* ──────────────────────────────────────────────────────────────────────
   Carousels (mirrors the rules in shared/css/classic.css)

   Photo blogs default new image groups to `data-layout="row"` (the
   editor wires this) — every image side-by-side, fitted to the post
   body width, no JS chrome. The non-row form (single image at a
   time, prev/next arrows, dots) is supported too for completeness.
   ────────────────────────────────────────────────────────────────────── */
.carousel {
    position: relative;
    overflow: hidden;
    margin: 1.5em 0;
    border-radius: 4px;
}
.carousel-track {
    display: flex;
    align-items: center;
    transition: transform 0.3s ease;
}
.carousel-track img {
    flex: 0 0 100%;
    width: 100%;
    max-width: 100%;
    height: auto;
    max-height: 70vh;
    object-fit: contain;
    margin: 0;
    cursor: zoom-in;
    display: block;
}
.carousel-btn {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    background: rgba(0, 0, 0, 0.5);
    color: white;
    border: none;
    padding: 12px 16px;
    cursor: pointer;
    font-size: 1.2rem;
    border-radius: 2px;
    z-index: 1;
}
.carousel-btn:hover { background: rgba(0, 0, 0, 0.75); }
.carousel-btn--prev { left: 8px; }
.carousel-btn--next { right: 8px; }
.carousel-dots {
    text-align: center;
    padding: 8px 0;
}
.carousel-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--fg-dim);
    opacity: 0.5;
    margin: 0 3px;
    cursor: pointer;
}
.carousel-dot.active { opacity: 1; background: var(--fg); }

/* Row layout — every image side-by-side, fit-shrunk to share the
   post body width. CSS-only; carousel.js early-returns on row layout
   so its buttons / dots don't mount. */
.carousel[data-layout="row"] { overflow: visible; }
.carousel[data-layout="row"] .carousel-track {
    display: flex;
    flex-wrap: nowrap;
    gap: 8px;
    align-items: stretch;
    transition: none;
    transform: none !important;
}
.carousel[data-layout="row"] .carousel-track img {
    flex: 1 1 0;
    width: auto;
    min-width: 0;          /* let flex shrink images below natural width */
    max-width: none;
    max-height: none;
    height: auto;
    object-fit: contain;
    cursor: zoom-in;
}
.carousel[data-layout="row"] .carousel-btn,
.carousel[data-layout="row"] .carousel-dots { display: none; }
