/* ============================================================
   InBetween — site stylesheet
   Brand carries forward from the original coming-soon index:
     - cream  #f5f1e8  (background)
     - ink    #1a1a1a  (text)
     - accent #8b1a2b  (burgundy / wine)
     - Montserrat (default body + display)
     - Caveat    (the cursive script accents) — self-hosted, see @font-face
   Layout patterns adapted from dianamoss.co.za (structure only):
     - asymmetric hero with offset right CTA
     - 3-col portfolio grid
     - portfolio detail = wide image stack + sticky right description
   ============================================================ */

/* @font-face declarations live in /static/css/fonts.css — that file is
   shared between the public site and the admin so the brand fonts are
   defined in exactly one place. */

:root {
    /* Palette */
    --cream: #f5f1e8;
    --cream-deep: #ece5d3;
    --ink: #1a1a1a;
    --ink-soft: #3a3a3a;
    --ink-mute: #6a6a6a;
    --accent: #691919;
    --accent-soft: #b34357;
    --line: rgba(26, 26, 26, 0.12);
    --shade: rgba(26, 26, 26, 0.04);

    /* Type */
    --font-sans: 'Montserrat', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --font-script: 'Caveat', cursive;

    /* Layout rhythm.
       Container max-width is intentionally smaller than the laptop-class
       breakpoint (~1280px) so the content panel never sits flush against the
       viewport edge; combined with the bumped gutter clamp this gives every
       page a comfortable side margin from ~1024px upward and a wider
       breathing area on the awkward 1200px-ish viewports specifically. */
    --container: 1140px;
    --container-narrow: 880px;
    /* Side gutter for every .container.
       Roughly one-third of the previous values now that the .section
       shorthand-padding bug is fixed and the gutter actually applies.
       Floor 14px → cap 16px keeps content close to the viewport edge on
       narrow phones without quite touching it; the 1.6vw slope is barely
       perceptible but lets larger screens take an extra hair of breathing
       room. Outer container margin still grows above 1140px viewports. */
    --gutter: clamp(0.875rem, 1.6vw, 1rem);
    --space-xs: 0.5rem;
    --space-sm: 1rem;
    --space-md: 1.75rem;
    --space-lg: 3rem;
    --space-xl: 5.5rem;
    --space-xxl: 8rem;

    /* Misc */
    --radius: 6px;
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 8px 30px rgba(0, 0, 0, 0.06);

    /* Motion design tokens.
       --ease-soft is the gentlest curve we use — quick start, long
       graceful decel — and is the right default for hovers, page
       fades, and image reveals. --ease-standard is Material's curve,
       reserved for purely-functional state transitions. Durations are
       deliberately on the slow side of comfortable so the site reads
       as composed rather than snappy. */
    --ease-soft: cubic-bezier(0.22, 1, 0.36, 1);
    --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
    --dur-fast: 180ms;
    --dur-base: 320ms;
    --dur-slow: 800ms;
}

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

html, body {
    margin: 0;
    padding: 0;
}

/* Smooth in-page anchor scrolling. Falls back gracefully on older
   browsers (no jump-cut, just no easing). */
html {
    scroll-behavior: smooth;
}

/* Page-load fade-in. Subtle (8px lift, 600ms) so it reads as polish,
   not as a transition the visitor has to wait through. The
   prefers-reduced-motion override at the bottom of this file
   short-circuits this for accessibility. */
body {
    opacity: 0;
    animation: page-in var(--dur-slow) var(--ease-soft) 60ms forwards;
}

@keyframes page-in {
    from {
        opacity: 0;
        transform: translateY(8px);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

/* Image reveal: opacity flips to 1 once <img> finishes loading
   (site.js sets data-loaded on each img after load/error). Without
   this, every page-load shows raw image-pop as the network resolves —
   with it, photos, logos, and PDFs slide in like the rest of the page. */
img {
    transition: opacity var(--dur-base) var(--ease-soft);
}

img:not([data-loaded]) {
    opacity: 0;
}

img[data-loaded] {
    opacity: 1;
}

html {
    scroll-behavior: smooth;
}

body {
    background: var(--cream);
    color: var(--ink);
    font-family: var(--font-sans);
    font-weight: 400;
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

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

a {
    color: var(--accent);
    text-decoration: none;
    transition: color 120ms ease, border-color 120ms ease;
}

a:hover, a:focus-visible {
    color: var(--ink);
}

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

/* ---------- Layout primitives ---------- */

.container {
    max-width: var(--container);
    margin: 0 auto;
    padding: 0 var(--gutter);
}

.container--narrow {
    max-width: var(--container-narrow);
    margin: 0 auto;
    padding: 0 var(--gutter);
}

.divider {
    border: none;
    border-top: 1.5px solid var(--accent);
    width: 100%;
    max-width: 600px;
    margin: var(--space-md) auto;
}

/* Section paddings use longhand top/bottom only.
   Using `padding: VAL 0` shorthand here (or `VAL 0 VAL`) zeroes out the
   left/right padding inherited from .container, leaving content flush
   against the viewport edge. Longhand keeps the .container gutter intact. */
.section {
    padding-top: var(--space-xl);
    padding-bottom: var(--space-xl);
}

.section--tight {
    padding-top: var(--space-lg);
    padding-bottom: var(--space-lg);
}

.section--alt {
    background: var(--cream-deep);
}

/* ---------- Header / nav ---------- */

.site-header {
    padding: var(--space-md) 0 var(--space-sm);
    position: relative;
    z-index: 5;
}

.site-header__inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
}

.site-header__brand a {
    display: inline-block;
    color: var(--ink);
}

.nav {
    display: flex;
    align-items: center;
    gap: var(--space-md);
}

.nav__list {
    display: flex;
    gap: var(--space-md);
    list-style: none;
    margin: 0;
    padding: 0;
}

.nav__list a {
    color: var(--ink);
    font-size: 0.95rem;
    font-weight: 500;
    letter-spacing: 0.04em;
    text-transform: lowercase;
    border-bottom: 1.5px solid transparent;
    padding: 0.25rem 0;
}

.nav__list a:hover, .nav__list a[aria-current="page"] {
    color: var(--accent);
    border-color: var(--accent);
}

/* CTA needs higher specificity than `.nav__list a` so the cream-on-ink
   color sticks. Without the `.nav__list a` qualifier the descendant rule
   above wins (it's specificity 0,1,1 vs the bare class's 0,1,0) and you
   get black text on a black background. */
.nav__list a.nav__cta {
    background: var(--ink);
    color: var(--cream);
    padding: 0.55rem 1.1rem;
    border-radius: var(--radius);
    font-size: 0.9rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    border-bottom: none;
    /* override the underline-on-hover treatment */
}

.nav__list a.nav__cta:hover,
.nav__list a.nav__cta:focus-visible,
.nav__list a.nav__cta[aria-current="page"] {
    background: var(--accent);
    color: var(--cream);
    border-bottom: none;
}

.nav__toggle {
    display: none;
    background: none;
    border: 1px solid var(--ink);
    color: var(--ink);
    font-family: inherit;
    font-size: 0.85rem;
    padding: 0.45rem 0.9rem;
    border-radius: var(--radius);
    cursor: pointer;
}

@media (max-width: 720px) {
    .nav__toggle {
        display: inline-flex;
    }

    .nav__list {
        display: none;
    }

    .nav.is-open .nav__list {
        display: flex;
        flex-direction: column;
        align-items: flex-end;
        gap: var(--space-sm);
        position: absolute;
        right: var(--gutter);
        top: 100%;
        background: var(--cream);
        padding: var(--space-sm) var(--space-md);
        border: 1px solid var(--line);
        border-radius: var(--radius);
        box-shadow: var(--shadow);
    }
}

/* ---------- Logo ----------
   The brand mark is now a flat SVG file under /static/img/. The hero version
   includes the wordmark + tagline strip; the small version is just the
   horizontal wordmark. We set max-width on the hero and a fixed height on
   the small variant so they sit right at every breakpoint without distorting. */

.logo {
    display: inline-block;
    line-height: 0;
}

.logo--hero {
    display: block;
    text-align: center;
    margin: 0 auto;
}

.logo--hero img {
    /* Hero mark: wide aspect (~1.4:1). Cap by width so it scales down
       gracefully on small screens. */
    width: 100%;
    max-width: 720px;
    height: auto;
    display: inline-block;
}

.logo--sm img {
    /* Header / footer / admin-sidebar wordmark — fixed height, natural
       aspect-driven width. Tweak the height to taste. */
    height: 2.6rem;
    width: auto;
    display: block;
}

@media (max-width: 540px) {
    .logo--hero img {
        max-width: 95%;
    }

    .logo--sm img {
        height: 2.1rem;
    }
}

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

.hero {
    /* Vertical padding only — horizontal is supplied by .container's gutter.
       Shorthand `padding: VAL 0 VAL` would zero the gutter (see comment on
       .section above). */
    padding-top: var(--space-xl);
    padding-bottom: var(--space-lg);
    position: relative;
}

.hero__inner {
    display: grid;
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
    gap: var(--space-lg);
    align-items: end;
}

.hero__lead {
    font-family: var(--font-sans);
    font-weight: 600;
    /* Lower clamp floor (was 2rem) so the heading shrinks far enough on
       narrow phones to fit "humming" / "in-between" on a single line.
       Slope steeper (5.5vw) so it grows back up on tablets+. Capped at
       2.5rem (40px) per design. */
    font-size: clamp(1.6rem, 5.5vw, 2.5rem);
    line-height: 1.12;
    letter-spacing: -0.005em;
    margin: 0 0 var(--space-sm);
    color: var(--ink);
    /* max-width is in ch (which scales with font-size). It stops wide
       lines on desktop without forcing a min-width on phones. The
       `min(...)` prevents 22ch from ever exceeding the column when the
       font-size is at its floor. */
    max-width: min(22ch, 100%);
    /* Last-line defense: if a single word is still too wide for the
       column, break it instead of overflowing the viewport. */
    overflow-wrap: anywhere;
    word-wrap: break-word;
    /* legacy alias; iOS Safari < 16 needs it */
    hyphens: auto;
}

.hero__lead em {
    font-family: var(--font-script);
    font-style: normal;
    color: var(--accent);
    font-weight: 700;
    font-size: larger;
}

.hero__sub {
    font-size: clamp(1rem, 1.6vw, 1.2rem);
    color: var(--ink-soft);
    line-height: 1.6;
    max-width: min(38ch, 100%);
    overflow-wrap: anywhere;
}

.hero__cta {
    text-align: right;
    align-self: end;
}

.hero__cta-text {
    font-size: clamp(1.1rem, 2.2vw, 1.6rem);
    color: var(--ink-soft);
    line-height: 1.4;
    margin-bottom: var(--space-sm);
    max-width: min(28ch, 100%);
    margin-left: auto;
    overflow-wrap: anywhere;
}

.hero__cta-link {
    display: inline-block;
    border-bottom: 2px solid var(--accent);
    color: var(--accent);
    font-weight: 600;
    padding-bottom: 2px;
}

.hero__cta-link:hover {
    color: var(--ink);
    border-color: var(--ink);
}

/* Stack the hero, portfolio-detail, and contact-grid columns earlier
   (was 820px). Below ~960px the asymmetric 1.4fr/1fr split leaves the
   right column too narrow for its CTA and inline arrow, and the 50/50
   contact split makes form labels run too tight. 960px keeps the
   2-column layout for desktop-ish viewports only. */
@media (max-width: 960px) {
    .hero__inner {
        grid-template-columns: 1fr;
        gap: var(--space-md);
    }

    .hero__cta {
        text-align: left;
    }

    .hero__cta-text {
        margin-left: 0;
    }
}

/* Hero specific: home page logo block */
.brand-block {
    text-align: center;
    margin-bottom: var(--space-md);
}

.brand-block .logo {
    display: inline-block;
}

.brand-block .tagline {
    font-weight: 500;
    font-size: clamp(1rem, 2vw, 1.3rem);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink);
    margin-top: var(--space-sm);
}

.brand-block .tagline .accent {
    color: var(--accent);
}

/* ---------- Section heads ---------- */

.section-head {
    margin-bottom: var(--space-lg);
}

.section-head__eyebrow {
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--accent);
    display: inline-block;
    margin-bottom: var(--space-xs);
}

.section-head__title {
    font-size: clamp(1.7rem, 3.5vw, 2.6rem);
    line-height: 1.15;
    font-weight: 600;
    margin: 0 0 var(--space-sm);
    max-width: 32ch;
}

.section-head__lede {
    font-size: 1.1rem;
    color: var(--ink-soft);
    max-width: 56ch;
    margin: 0;
}

/* ---------- Service cards ---------- */

.service-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: var(--space-md);
}

.service-card {
    background: var(--cream);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--space-md);
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    transition: transform var(--dur-base) var(--ease-soft), box-shadow var(--dur-base) var(--ease-soft), border-color var(--dur-fast) var(--ease-soft);
}

.service-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow);
    border-color: var(--accent);
}

.service-card__num {
    font-family: var(--font-script);
    font-size: 1.8rem;
    color: var(--accent);
    line-height: 1;
}

.service-card__name {
    font-size: 1.4rem;
    font-weight: 600;
    margin: 0;
    color: var(--ink);
}

.service-card__tag {
    font-size: 0.95rem;
    color: var(--ink-soft);
    margin: 0;
}

.service-card__more {
    margin-top: auto;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    font-weight: 600;
    color: var(--accent);
}

/* ---------- Portfolio grid (3-col) ---------- */

.portfolio-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-md);
}

@media (max-width: 900px) {
    .portfolio-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (max-width: 560px) {
    .portfolio-grid {
        grid-template-columns: 1fr;
    }
}

.portfolio-card {
    display: block;
    aspect-ratio: 1 / 1;
    background: var(--cream-deep);
    border-radius: var(--radius);
    overflow: hidden;
    position: relative;
    color: var(--ink);
}

.portfolio-card img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    /* show the whole image — no cropping */
    padding: 0.75rem;
    transition: transform var(--dur-slow) var(--ease-soft);
}

.portfolio-card:hover img {
    transform: scale(1.03);
}

.portfolio-card__placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    color: var(--ink-mute);
    font-style: italic;
    font-size: 0.95rem;
    background:
        repeating-linear-gradient(135deg,
            var(--cream-deep) 0 12px,
            var(--cream) 12px 24px);
}

.portfolio-card__caption {
    padding: 0.75rem 0.25rem 0;
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-sm);
}

.portfolio-card__title {
    font-weight: 600;
    margin: 0;
    font-size: 1rem;
}

.portfolio-card__svc {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--ink-mute);
}

/* ---------- Category grid (portfolio index) ----------
   3-column grid of category cards. Each card is a single anchor with a
   full-bleed cover and a name overlay that slides up on hover. On
   touch devices (no hover) the overlay is always shown so the user can
   read the label. */
.category-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-md);
}

@media (max-width: 900px) {
    .category-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (max-width: 560px) {
    .category-grid {
        grid-template-columns: 1fr;
    }
}

.category-card {
    position: relative;
    display: block;
    aspect-ratio: 1 / 1;
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--cream-deep);
    color: var(--ink);
    transition: transform var(--dur-base) var(--ease-soft), box-shadow var(--dur-base) var(--ease-soft);
}

.category-card:hover,
.category-card:focus-visible {
    transform: translateY(-2px);
    box-shadow: var(--shadow);
    color: var(--ink);
    outline: none;
}

.category-card__cover {
    width: 100%;
    height: 100%;
    object-fit: contain;
    /* show the whole cover — don't crop */
    display: block;
    padding: 0.75rem;
    background: var(--cream-deep);
    transition: transform var(--dur-slow) var(--ease-soft);
}

.category-card:hover .category-card__cover {
    transform: scale(1.04);
}

.category-card__placeholder {
    width: 100%;
    height: 100%;
    background:
        repeating-linear-gradient(135deg,
            var(--cream-deep) 0 12px,
            var(--cream) 12px 24px);
}

.category-card__overlay {
    position: absolute;
    inset: auto 0 0 0;
    padding: var(--space-md);
    background: linear-gradient(to top, rgba(26, 26, 26, 0.78) 0%, rgba(26, 26, 26, 0) 100%);
    color: var(--cream);
    transform: translateY(100%);
    transition: transform var(--dur-base) var(--ease-soft);
    display: flex;
    align-items: flex-end;
    min-height: 40%;
}

.category-card:hover .category-card__overlay,
.category-card:focus-visible .category-card__overlay {
    transform: translateY(0);
}

.category-card__name {
    font-family: var(--font-sans);
    font-weight: 600;
    font-size: clamp(1.1rem, 2vw, 1.5rem);
    letter-spacing: 0.02em;
    line-height: 1.2;
}

/* Touch / coarse-pointer devices have no hover state, so always show the
   overlay. We use (hover: none) which fires for true touchscreens and
   spares mouse users from the reveal we already do via :hover. */
@media (hover: none) {
    .category-card__overlay {
        transform: translateY(0);
    }
}

/* Asset grid — used inside a category page when the category has
   tagged assets directly attached. Each card is a link to the asset
   detail page; caption (if any) sits underneath. */
.asset-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-md);
}

@media (max-width: 900px) {
    .asset-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (max-width: 560px) {
    .asset-grid {
        grid-template-columns: 1fr;
    }
}

.asset-card {
    display: block;
    color: var(--ink);
    text-decoration: none;
}

.asset-card img {
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: contain;
    /* show the whole image — no cropping */
    border-radius: var(--radius);
    background: var(--cream-deep);
    padding: 0.5rem;
    /* a touch of breathing room around it */
    transition: transform var(--dur-base) var(--ease-soft), box-shadow var(--dur-base) var(--ease-soft);
}

.asset-card:hover img {
    transform: translateY(-2px);
    box-shadow: var(--shadow);
}

.asset-card__caption {
    display: block;
    margin-top: 0.5rem;
    font-size: 0.9rem;
    color: var(--ink-soft);
}

.asset-card__pdf {
    width: 100%;
    aspect-ratio: 1 / 1;
    border-radius: var(--radius);
    background: var(--cream-deep);
    color: var(--ink-mute);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    transition: transform var(--dur-base) var(--ease-soft), box-shadow var(--dur-base) var(--ease-soft);
}

.asset-card:hover .asset-card__pdf {
    transform: translateY(-2px);
    box-shadow: var(--shadow);
}

.asset-card__pdf svg {
    width: 2rem;
    height: 2rem;
}

/* Inline-PDF preview tile: same footprint as .asset-card img, but the
   <embed> renders the first page of the PDF. pointer-events: none on
   the embed so clicks bubble to the parent <a class="asset-card">.
   The embed is sized 24px wider than the wrapper and the wrapper is
   overflow:hidden — that pushes Chrome's PDF scrollbar off the right
   edge so it never appears in the card. */
.asset-card__pdf-embed {
    position: relative;
    width: 100%;
    aspect-ratio: 1 / 1;
    border-radius: var(--radius);
    background: var(--cream-deep);
    overflow: hidden;
    transition: transform var(--dur-base) var(--ease-soft), box-shadow var(--dur-base) var(--ease-soft);
}

.asset-card__pdf-embed embed {
    position: absolute;
    inset: 0;
    width: calc(100% + 24px);
    height: 100%;
    display: block;
    pointer-events: none;
    border: 0;
}

.asset-card:hover .asset-card__pdf-embed {
    transform: translateY(-2px);
    box-shadow: var(--shadow);
}

/* Home intro line — a plain editable sentence across the top of the page,
   above the carousel ribbon. */
.home-intro {
    padding-top: var(--space-xs);
    padding-bottom: 0;
}
.home-intro p {
    margin: 0;
    text-align: right;
}
/* Lead line — same size as the hero lead heading. */
.home-intro__lead {
    font-size: clamp(1.6rem, 5.5vw, 2.5rem);
    line-height: 1.3;
    color: var(--ink-soft);
}
/* Sub line — same size as the hero subtext, in the brand accent colour. */
.home-intro__sub {
    margin-top: var(--space-sm);
    font-size: clamp(1rem, 1.6vw, 1.2rem);
    line-height: 1.6;
    color: var(--accent);
}

/* ============================================================
   Home hero ribbon — a continuously scrolling image marquee that
   replaces the static logo when "Main Page Carousel" has images.
   The track holds two identical copies of the set; the animation
   translates it by -50% (exactly one copy) and loops. We space items
   with margin-right (not flex gap) so one copy's width — and therefore
   the -50% offset — is exact regardless of varying image widths,
   keeping the loop seamless.
   ============================================================ */
.hero-ribbon {
    /* Tight top gap so the ribbon sits just under the intro line; keep the
       larger gap below it before the next section. */
    margin: var(--space-sm) 0 var(--space-lg);
}

.ribbon {
    overflow: hidden;
    width: 100%;
    /* Fade the strip into the page at both edges. */
    -webkit-mask-image: linear-gradient(to right, transparent, #000 5%, #000 95%, transparent);
            mask-image: linear-gradient(to right, transparent, #000 5%, #000 95%, transparent);
}

.ribbon__track {
    display: flex;
    align-items: center;
    width: max-content;
    will-change: transform;
}

/* site.js measures one set's width, clones enough copies to overfill the
   viewport, sets --ribbon-shift / --ribbon-duration, then adds .is-ready.
   Gating the animation on that class means the loop only runs once the
   measurements exist, so it's seamless instead of jumping. */
.ribbon__track.is-ready {
    animation: ribbon-scroll var(--ribbon-duration, 40s) linear infinite;
}

/* Pause when the visitor hovers anywhere on the strip (or tabs into a
   link inside it) so they can aim for an image. */
.ribbon:hover .ribbon__track,
.ribbon__track:focus-within {
    animation-play-state: paused;
}

.ribbon__item {
    flex: 0 0 auto;
    margin-right: var(--space-md);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--cream-deep);
}

.ribbon__item img {
    display: block;
    height: clamp(140px, 22vh, 240px);
    width: auto;
    object-fit: contain;
    transition: transform var(--dur-base) var(--ease-soft);
}

.ribbon__item:hover img {
    transform: scale(1.03);
}

@keyframes ribbon-scroll {
    from { transform: translateX(0); }
    /* One set's width, measured by site.js; the cloned sets that follow
       keep the viewport filled so the reset back to 0 is invisible. */
    to   { transform: translateX(calc(-1 * var(--ribbon-shift, 50%))); }
}

/* Respect reduced-motion: stop the auto-scroll and let the user pan the
   strip by hand instead. */
@media (prefers-reduced-motion: reduce) {
    .ribbon { overflow-x: auto; }
    .ribbon__track.is-ready { animation: none; }
}

/* Asset detail — full-bleed image / PDF with caption + category chips. */
.asset-detail {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    gap: var(--space-lg);
    align-items: start;
}

@media (max-width: 820px) {
    .asset-detail {
        grid-template-columns: 1fr;
    }
}

/* PDF assets: the flipbook wants full width, so swap to a single column
   with meta stacked below. Otherwise the 2fr column crushes the spread
   and StPageFlip's absolute internals overflow into the section below. */
.asset-detail--pdf {
    grid-template-columns: 1fr;
}

.asset-detail img {
    width: 100%;
    height: auto;
    border-radius: var(--radius);
    background: var(--cream-deep);
}

.asset-detail__meta {
    position: sticky;
    top: var(--space-md);
}

/* In PDF mode the meta is below the book, not beside it — sticky makes
   no sense and would awkwardly trail the viewport. */
.asset-detail--pdf .asset-detail__meta {
    position: static;
    margin-top: var(--space-md);
    text-align: center;
}

.asset-detail__caption {
    font-size: 1.05rem;
    color: var(--ink-soft);
    line-height: 1.7;
    margin: 0 0 var(--space-md);
    white-space: pre-line;
}

.asset-detail__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    align-items: center;
    margin-top: var(--space-sm);
}

.asset-chip {
    display: inline-block;
    padding: 0.3rem 0.7rem;
    font-size: 0.8rem;
    border: 1px solid var(--line);
    border-radius: 999px;
    color: var(--ink);
    text-decoration: none;
    transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}

.asset-chip:hover {
    background: var(--ink);
    color: var(--cream);
    border-color: var(--ink);
}

/* PDF viewer with custom prev/next overlay.
   Arrows + indicator fade in on hover (or focus, for keyboard users).
   On touch devices :hover never matches, so we fall back to always-on.

   Multi-page: PDF.js paints canvases, StPageFlip wraps them into a 3D book.
   Single-page: <embed class="pdf-single"> uses the browser's native viewer. */
.pdf-viewer {
    position: relative;
    width: 100%;
    max-width: 1100px;
    margin: 0 auto;
    /* Two-page spread of portrait PDFs (8.5x11) → 17/11. Stays put for
       the lifetime of the page so StPageFlip's internal layout, which is
       absolutely positioned inside, never escapes its parent. JS overrides
       this inline once we know the actual PDF aspect. */
    aspect-ratio: 17 / 11;
    outline: none;
}

/* Narrow viewports: StPageFlip auto-switches to single-page portrait,
   so the container should be one page wide, not two. */
@media (max-width: 820px) {
    .pdf-viewer {
        aspect-ratio: 8.5 / 11;
        max-width: 600px;
    }
}

/* StPageFlip mount. The library injects its own .stf__parent / .stf__block
   children and sets dimensions; we just provide a positioned container. */
.pdf-viewer__book {
    width: 100%;
    height: 100%;
}

/* Wrapper around each rendered page. StPageFlip moves these into its
   internal structure; the canvas inside fills the available page area. */
.pdf-viewer__page {
    background: #fff;
    overflow: hidden;
}

.pdf-viewer__page canvas {
    display: block;
    width: 100%;
    height: 100%;
}

/* Loading state shown until the first three pages are rendered. */
.pdf-viewer__loading {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--ink-mute);
    font-size: 0.85rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    background: var(--cream-deep);
    border-radius: var(--radius);
    transition: opacity 200ms ease;
}

.pdf-viewer.is-ready .pdf-viewer__loading {
    opacity: 0;
    pointer-events: none;
}

/* Single-page PDFs keep the native viewer (no flip needed). Same
   scrollbar-clip trick as before: embed is 24px wider than its parent
   to push Chrome's PDF scrollbar past the visible edge. */
.pdf-single {
    display: block;
    width: calc(100% + 24px);
    aspect-ratio: 8.5 / 11;
    max-width: calc(900px + 24px);
    margin: 0 auto;
    border: 0;
    border-radius: var(--radius);
    background: var(--cream-deep);
}

.pdf-viewer__nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 999px;
    border: 0;
    background: rgba(26, 26, 26, 0.72);
    color: var(--cream);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    opacity: 0;
    transition: opacity 160ms ease, background 160ms ease, transform 160ms ease;
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
}

.pdf-viewer__nav:hover:not([disabled]) {
    background: var(--ink);
}

.pdf-viewer__nav:active:not([disabled]) {
    transform: translateY(-50%) scale(0.96);
}

.pdf-viewer__nav[disabled] {
    opacity: 0;
    /* fully hidden when at the boundary */
    cursor: default;
    pointer-events: none;
}

.pdf-viewer__nav--prev {
    left: 0.85rem;
}

.pdf-viewer__nav--next {
    right: 0.85rem;
}

.pdf-viewer__nav svg {
    width: 1.4rem;
    height: 1.4rem;
}

.pdf-viewer__indicator {
    position: absolute;
    bottom: 0.85rem;
    left: 50%;
    transform: translateX(-50%);
    padding: 0.35rem 0.85rem;
    border-radius: 999px;
    background: rgba(26, 26, 26, 0.72);
    color: var(--cream);
    font-size: 0.8rem;
    letter-spacing: 0.04em;
    opacity: 0;
    transition: opacity 160ms ease;
    pointer-events: none;
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
}

/* Reveal on hover or keyboard focus. */
.pdf-viewer:hover .pdf-viewer__nav:not([disabled]),
.pdf-viewer:focus-within .pdf-viewer__nav:not([disabled]),
.pdf-viewer:hover .pdf-viewer__indicator,
.pdf-viewer:focus-within .pdf-viewer__indicator {
    opacity: 1;
}

/* Touch / no-hover devices: keep controls visible since :hover never fires. */
@media (hover: none) {

    .pdf-viewer__nav:not([disabled]),
    .pdf-viewer__indicator {
        opacity: 1;
    }
}

/* Breadcrumb above category page heading. */
.breadcrumb {
    font-size: 0.85rem;
    color: var(--ink-mute);
    margin-bottom: var(--space-sm);
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    align-items: center;
}

.breadcrumb a {
    color: var(--ink-mute);
    text-decoration: none;
}

.breadcrumb a:hover {
    color: var(--accent);
}

.breadcrumb__current {
    color: var(--ink);
    font-weight: 500;
}

.portfolio-filters {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
    margin-bottom: var(--space-md);
}

.portfolio-filters a {
    border: 1px solid var(--line);
    color: var(--ink-soft);
    padding: 0.45rem 0.9rem;
    border-radius: 999px;
    font-size: 0.85rem;
    text-transform: lowercase;
    letter-spacing: 0.04em;
}

.portfolio-filters a:hover {
    border-color: var(--ink);
    color: var(--ink);
}

.portfolio-filters a.is-active {
    background: var(--ink);
    color: var(--cream);
    border-color: var(--ink);
}

/* ---------- Portfolio detail (asymmetric two-col) ---------- */

.portfolio-detail {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    gap: var(--space-lg);
    align-items: start;
    /* Vertical padding only — keep .container's horizontal gutter intact. */
    padding-top: var(--space-lg);
    padding-bottom: var(--space-xl);
}

.portfolio-detail__images {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
}

.portfolio-detail__images img,
.portfolio-detail__images .placeholder {
    width: 100%;
    border-radius: var(--radius);
    background: var(--cream-deep);
}

.portfolio-detail__images .placeholder {
    aspect-ratio: 16 / 10;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--ink-mute);
    font-style: italic;
}

/* PDF inline viewer — sits in the portfolio asset stack between images and
   reads as a tall content block. Aspect ratio defaults to US-letter portrait;
   for landscape decks override with `style="aspect-ratio: 11/8.5"` on the
   parent .pdf-embed. Multi-page PDFs are scrollable within the block via the
   browser's built-in PDF viewer. */
.pdf-embed {
    position: relative;
    width: 100%;
    aspect-ratio: 8.5 / 11;
    background: var(--cream-deep);
    border-radius: var(--radius);
    overflow: hidden;
    /* Subtle inner shadow keeps the block visually distinct from raw images
       — without it, white-page PDFs blend into the cream background. */
    box-shadow: inset 0 0 0 1px var(--line);
}

.pdf-embed embed,
.pdf-embed object,
.pdf-embed iframe {
    position: absolute;
    inset: 0;
    /* 24px wider than the wrapper pushes Chrome's PDF scrollbar past
       the clip edge so it never shows. The PDF content stays centered
       because PDF Open Parameters view=FitH fits to width. */
    width: calc(100% + 24px);
    height: 100%;
    display: block;
    border: 0;
    /* The PDF inside renders with its own white background; rounding the
       parent and clipping with overflow:hidden gives clean rounded corners. */
}

.portfolio-detail__panel {
    position: sticky;
    top: var(--space-md);
    align-self: start;
}

.portfolio-detail__title {
    font-size: clamp(1.6rem, 2.8vw, 2.2rem);
    font-weight: 600;
    line-height: 1.15;
    margin: 0 0 var(--space-sm);
}

.portfolio-detail__client {
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--accent);
    margin: 0 0 var(--space-md);
}

.portfolio-detail__body {
    color: var(--ink-soft);
    line-height: 1.7;
    white-space: pre-wrap;
}

.portfolio-detail__back {
    display: inline-block;
    margin-top: var(--space-md);
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--ink);
    border-bottom: 1.5px solid var(--accent);
}

@media (max-width: 960px) {
    .portfolio-detail {
        grid-template-columns: 1fr;
    }

    .portfolio-detail__panel {
        position: static;
    }
}

/* ---------- About strip ---------- */

.about-strip {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
    gap: var(--space-lg);
    align-items: start;
}

.about-strip__intro {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    align-items: start;
}

.about-strip__heading {
    font-size: clamp(1.8rem, 4vw, 2.8rem);
    font-weight: 600;
    color: var(--accent);
    line-height: 1.1;
    margin: 0;
}

/* Eyebrow above the coloured About block. */
.about-eyebrow {
    margin-top: var(--space-xl);
    margin-bottom: var(--space-sm);
}

/* Optional image in the About left column (where the why-card used to be). */
.about-image {
    margin-top: var(--space-md);
    border-radius: var(--radius);
    overflow: hidden;
}
.about-image img {
    display: block;
    width: 100%;
    height: auto;
}
.about-image__placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    aspect-ratio: 4 / 3;
    color: var(--ink-mute);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    border-radius: var(--radius);
    background:
        repeating-linear-gradient(135deg,
            var(--cream) 0 14px,
            var(--cream-deep) 14px 28px);
}

/* "Why work with me?" card — now in the right column, between the prose
   and the "More about" link. */
.why-card {
    margin: var(--space-md) 0;
    background: var(--cream);
    border: 1px solid rgba(26, 26, 26, 0.08);
    border-radius: var(--radius);
    padding: var(--space-md);
}
.why-card__title {
    margin: 0 0 var(--space-sm);
    font-size: 1.15rem;
    font-weight: 600;
    color: var(--ink);
}
.why-card__list {
    margin: 0;
    padding-left: 1.15rem;
    color: var(--ink-soft);
    font-size: 0.98rem;
    line-height: 1.65;
}
.why-card__list li {
    margin-bottom: 0.3rem;
}
.why-card__list li:last-child {
    margin-bottom: 0;
}

.about-strip__body {
    color: var(--ink-soft);
    font-size: 1.05rem;
    line-height: 1.75;
    max-width: 60ch;
}

.about-strip__body p+p {
    margin-top: var(--space-sm);
}

@media (max-width: 720px) {
    .about-strip {
        grid-template-columns: 1fr;
    }
}

/* ---------- CV button + PDF lightbox ---------- */
.cv-section {
    text-align: center;
}
/* CV button sitting just below the about text. */
.cv-line {
    margin: var(--space-md) 0 0;
}
.btn-cv {
    display: inline-block;
    background: var(--cream-deep);
    color: var(--ink);
    border: none;
    border-radius: var(--radius);
    padding: 0.85rem 1.7rem;
    font: inherit;
    font-weight: 600;
    letter-spacing: 0.01em;
    cursor: pointer;
    transition: background var(--dur-base) var(--ease-soft),
                color var(--dur-base) var(--ease-soft),
                transform var(--dur-base) var(--ease-soft);
}
.btn-cv:hover,
.btn-cv:focus-visible {
    background: var(--accent);
    color: var(--cream);
    transform: translateY(-1px);
    outline: none;
}

/* Lightbox — a native <dialog> opened with showModal() so it renders in the
   browser's top layer: always centred, full-screen backdrop, never clipped
   by ancestor transforms/overflow. Built on the fly by site.js. */
.lightbox-dialog {
    width: min(900px, 92vw);
    height: min(900px, 90vh);
    max-width: 92vw;
    max-height: 90vh;
    padding: 0;
    border: none;
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--cream);
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.3);
}
.lightbox-dialog::backdrop {
    background: rgba(26, 26, 26, 0.6);
    backdrop-filter: blur(2px);
}
.lightbox-dialog__inner {
    display: flex;
    flex-direction: column;
    height: 100%;
}
.lightbox__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    padding: 0.6rem 0.9rem;
    border-bottom: 1px solid var(--cream-deep);
}
.lightbox__title {
    font-weight: 600;
    color: var(--ink);
}
.lightbox__actions {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}
.lightbox__dl {
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--accent);
    text-decoration: underline;
}
.lightbox__close {
    background: none;
    border: none;
    font-size: 1.6rem;
    line-height: 1;
    padding: 0 0.2rem;
    cursor: pointer;
    color: var(--ink-mute);
}
.lightbox__close:hover {
    color: var(--ink);
}
.lightbox__frame {
    flex: 1;
    width: 100%;
    border: 0;
    background: #fff;
}

/* ---------- Contact form ---------- */

.contact-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-lg);
    align-items: start;
}

@media (max-width: 960px) {
    .contact-grid {
        grid-template-columns: 1fr;
    }
}

.form-field {
    margin-bottom: var(--space-sm);
}

.form-field label {
    display: block;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--ink-soft);
    margin-bottom: 0.35rem;
    font-weight: 600;
}

.form-field input,
.form-field select,
.form-field textarea {
    width: 100%;
    background: var(--cream);
    border: 1.5px solid var(--line);
    border-radius: var(--radius);
    padding: 0.75rem 0.9rem;
    font: inherit;
    color: var(--ink);
    transition: border-color 120ms ease, box-shadow 120ms ease;
}

.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(139, 26, 43, 0.12);
}

.form-field textarea {
    min-height: 9rem;
    resize: vertical;
}

.form-field--error input,
.form-field--error select,
.form-field--error textarea {
    border-color: var(--accent);
}

.form-field__error {
    color: var(--accent);
    font-size: 0.85rem;
    margin-top: 0.25rem;
}

/* hidden honeypot */
.form-field--honey {
    position: absolute;
    left: -9999px;
    height: 0;
    overflow: hidden;
}

.btn {
    display: inline-block;
    background: var(--ink);
    color: var(--cream);
    border: none;
    border-radius: var(--radius);
    padding: 0.85rem 1.6rem;
    font-family: inherit;
    font-size: 0.95rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease;
}

.btn:hover {
    background: var(--accent);
    color: var(--cream);
}

.btn--ghost {
    background: transparent;
    color: var(--ink);
    border: 1.5px solid var(--ink);
}

.btn--ghost:hover {
    background: var(--ink);
    color: var(--cream);
}

.btn--danger {
    background: var(--accent);
    color: var(--cream);
}

.btn--danger:hover {
    background: #6f1422;
}

.alert {
    border-left: 3px solid var(--accent);
    padding: var(--space-sm) var(--space-md);
    background: rgba(139, 26, 43, 0.06);
    color: var(--ink);
    border-radius: 0 var(--radius) var(--radius) 0;
    margin-bottom: var(--space-md);
}

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

.site-footer {
    border-top: 1px solid var(--line);
    padding: var(--space-lg) 0 var(--space-md);
    margin-top: var(--space-xxl);
    color: var(--ink-soft);
    font-size: 0.92rem;
}

.site-footer__inner {
    display: grid;
    grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-lg);
    margin-bottom: var(--space-md);
}

@media (max-width: 720px) {
    .site-footer__inner {
        grid-template-columns: 1fr;
        gap: var(--space-md);
    }
}

.site-footer h4 {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--ink-mute);
    margin: 0 0 var(--space-sm);
}

.site-footer ul {
    list-style: none;
    padding: 0;
    margin: 0;
}

.site-footer li {
    margin-bottom: 0.45rem;
}

.site-footer a {
    color: var(--ink);
}

.site-footer a:hover {
    color: var(--accent);
}

.site-footer__legal {
    border-top: 1px solid var(--line);
    padding-top: var(--space-sm);
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: var(--space-sm);
    font-size: 0.85rem;
    color: var(--ink-mute);
}

/* Footer social icon row */
.social-row {
    display: flex !important;
    flex-wrap: wrap;
    gap: 0.5rem;
    list-style: none;
    padding: 0;
    margin: var(--space-sm) 0 0 !important;
}

.social-row li {
    margin: 0 !important;
}

.social-row a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 50%;
    border: 1px solid var(--line);
    color: var(--ink) !important;
    transition: background 120ms ease, color 120ms ease, border-color 120ms ease, transform 120ms ease;
}

.social-row a:hover, .social-row a:focus-visible {
    background: var(--ink);
    color: var(--cream) !important;
    border-color: var(--ink);
    transform: translateY(-1px);
    outline: none;
}

.social-row svg {
    width: 1.05rem;
    height: 1.05rem;
    display: block;
}

/* ---------- Utility ---------- */

.text-accent {
    color: var(--accent);
}

.text-soft {
    color: var(--ink-soft);
}

.text-mute {
    color: var(--ink-mute);
}

.text-center {
    text-align: center;
}

.uppercase {
    text-transform: uppercase;
    letter-spacing: 0.12em;
}

.cta-line {
    text-align: center;
    font-size: clamp(1.3rem, 2.6vw, 1.9rem);
    font-weight: 500;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink);
}

.cta-line a {
    color: var(--accent);
    border-bottom: 2px solid var(--accent);
    padding-bottom: 2px;
}

.cta-line a:hover {
    color: var(--ink);
    border-color: var(--ink);
}

/* skip-to-content for keyboard / screen readers */
.skip-link {
    position: absolute;
    top: -40px;
    left: 8px;
    background: var(--ink);
    color: var(--cream);
    padding: 0.5rem 1rem;
    border-radius: var(--radius);
    z-index: 100;
}

.skip-link:focus {
    top: 8px;
}

/* ============================================================
   Reduced motion — respect the OS-level preference.
   Visitors who've turned on "reduce motion" (Windows Settings
   → Accessibility → Visual effects, macOS System Settings
   → Accessibility → Display) get an instant page render
   with no fades, slides, or smooth-scroll. We force
   transitions and animations to a 0.01ms tick rather than
   removing them outright so any JS that listens for
   transitionend still fires. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }

    html {
        scroll-behavior: auto;
    }

    body {
        opacity: 1;
        animation: none;
    }

    img {
        opacity: 1 !important;
    }
}