@font-face {
    font-family: 'VG5000';
    src: url('fonts/VG5000-Regular_web.woff2') format('woff2'),
         url('fonts/VG5000-Regular_web.woff') format('woff'),
         url('fonts/VG5000-Regular_web.ttf') format('truetype'),
         url('fonts/VG5000-Regular_web.otf') format('opentype');
    font-weight: normal;
    font-style: normal;
    font-display: swap;
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

:root {
    /* Hero height, reused as the panel's offset so the two always match */
    --hero-height: 100vh;
    /* Width of the slide-in side menu */
    --sidebar-width: 320px;
    /* Fill colour of the pixel toggle */
    --pixel-accent: #ff0033;
    /* Toggle size and header inset — shared so the header line stays aligned */
    --toggle-size: 1.75rem;
    --header-top: 2.75rem;
    /* Shared left/right margin: the site title, the toggle and the titles laid
       over the artworks all read from it, so they stay on one vertical line */
    --edge-inset: 2rem;
    /* Full band height: the inset above and below the toggle, plus the toggle */
    --header-height: calc(var(--header-top) * 2 + var(--toggle-size));
}

@supports (height: 100svh) {
    :root {
        --hero-height: 100svh;
    }
}

/* On a phone, page scroll is frozen while the menu is open: the menu fills the
   screen, so scrolling would only move a page nobody can see. On desktop the
   menu is a drawer beside a page that stays readable, so it keeps scrolling.
   The class lands on both the root and the body because either can be the
   document's scroller. */
@media (max-width: 768px) {
    .menu-open {
        overflow: hidden;
    }
}

body {
    font-family: 'VG5000', 'Arial', sans-serif;
    color: #ffffff;
    background-color: #000000;
    /* No overflow-x:hidden here — it turns body into a scroll container and
       stops position:sticky from working. Full-bleed blocks use 100% instead
       of 100vw, so nothing overflows sideways in the first place. */
}

/* Menu toggle — an empty pixel that floods with colour on hover, and stays
   filled while the menu is open so the square doubles as the state. */
.menu-toggle {
    position: fixed;
    top: var(--header-top);
    right: var(--edge-inset);
    z-index: 60;
    width: var(--toggle-size);
    height: var(--toggle-size);
    padding: 0;
    background-color: transparent;
    border: 2px solid #ffffff;
    cursor: pointer;
    transition: background-color 0.25s ease;
}

/* Hover is declared only where a pointer can genuinely hover. On a touch
   screen the state sticks after a tap until something else is touched, which
   left the pixel filled long after the menu had been closed. */
@media (hover: hover) {
    .menu-toggle:hover {
        background-color: var(--pixel-accent);
    }
}

.menu-toggle:focus-visible,
.menu-open .menu-toggle {
    background-color: var(--pixel-accent);
}

.menu-toggle:focus-visible {
    outline: 2px solid #ffffff;
    outline-offset: 3px;
}

/* Side menu — nav at the top, identity and contact pinned to the bottom.
   Parked off-screen and slid in over the content on demand. */
.sidebar {
    /* Set here rather than inherited: on the light annexe pages the body text
       is black, which left the address and contact line black on black */
    color: #ffffff;
    position: fixed;
    top: 0;
    right: 0;
    width: var(--sidebar-width);
    max-width: 85vw;
    height: 100%;
    z-index: 50;
    display: flex;
    flex-direction: column;
    /* Top padding clears the toggle, which sits above the panel */
    padding: 6rem 1.5rem 2rem;
    background-color: #000000;
    transform: translateX(100%);
    /* visibility keeps the links out of the tab order while parked; it flips
       only once the slide has finished, hence the delayed second transition */
    visibility: hidden;
    transition: transform 0.4s ease, visibility 0s linear 0.4s;
}

.menu-open .sidebar {
    transform: translateX(0);
    visibility: visible;
    transition: transform 0.4s ease, visibility 0s;
}

/* The panel is a window onto the page, not an opaque slab: the canvas shows
   whatever sits behind it, in blocks. Black underneath stays as the fallback
   wherever no artwork is passing. */
.sidebar-pixels {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 0;
    display: block;
}

/* Scrim over the pixels, currently off so the raw effect can be judged. Raise
   the alpha back towards 0.55 if white type stops holding up over the artwork. */
.sidebar::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 1;
    background-color: rgba(0, 0, 0, 0);
    pointer-events: none;
}

.sidebar-nav,
.sidebar-info {
    position: relative;
    z-index: 2;
}

.sidebar-nav {
    display: flex;
    flex-direction: column;
}

/* Hard black shadow, no blur, fully opaque — the backdrop is unfiltered
   artwork, so the type needs an edge it keeps over any colour behind it. */
.sidebar-nav a,
.sidebar-info {
    text-shadow: 2px 2px 0 #000000;
}

.sidebar-social img {
    /* text-shadow does not reach an <img>; drop-shadow is its equivalent */
    filter: drop-shadow(2px 2px 0 #000000);
}

.sidebar-nav a {
    color: #ffffff;
    text-decoration: none;
    font-size: 1.5rem;
    line-height: 1.3;
    transition: opacity 0.3s ease;
}

.sidebar-info {
    /* Pushes the whole block to the bottom of the column */
    margin-top: auto;
    font-size: 0.8rem;
    line-height: 1.6;
}

.sidebar-info p:not(:last-child) {
    margin-bottom: 1.5rem;
}

.sidebar-info a {
    color: #ffffff;
    text-decoration: none;
    transition: opacity 0.3s ease;
}

.sidebar-nav a:hover,
.sidebar-info a:hover {
    opacity: 0.6;
}

/* A section not open yet: listed for what is coming, but inert. It has no
   href, so it is neither clickable nor reachable with the keyboard. */
.sidebar-nav a.is-disabled {
    opacity: 0.35;
    cursor: default;
}

.sidebar-nav a.is-disabled:hover {
    opacity: 0.35;
}

.sidebar-social {
    display: flex;
    gap: 1.1rem;
    margin-top: 1.5rem;
}

.sidebar-social img {
    width: 26px;
    height: 26px;
    display: block;
}

/* Hero Section — pinned behind the page, revealed again on scroll up */
.hero {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: var(--hero-height);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    z-index: 0;
    /* Opacity is driven by scroll position in script.js — no transition here,
       it has to track the scroll exactly rather than lag behind it. */
}

/* Panel stack — starts one screen down so the first panel rises over the hero.
   Each panel sticks at the top once reached, and the next one scrolls over it.
   Stacking order is hero (0) < bio (1) < projects (2). */
.panel-stack {
    margin-top: var(--hero-height);
}

.hero-background {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 0;
}

.hero-background-video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
    opacity: 1;
    display: block;
    pointer-events: none;
}

.hero-content {
    position: relative;
    z-index: 2;
    text-align: center;
}

/* Pixelated stand-ins, one per layer. Each sits above its own content and
   only appears once the block size is big enough to be worth the swap. */
.hero-canvas,
.bio-canvas {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 3;
    display: none;
    pointer-events: none;
}

.is-pixelated > .hero-canvas,
.is-pixelated > .bio-canvas {
    display: block;
}

/* visibility, not display: the canvas measures where the real text sits, so
   these must keep their place in the layout while hidden.

   The hero clips are deliberately absent from this list. Chrome stops
   decoding a video it considers invisible, and on a first visit the clip has
   nothing buffered yet when scrolling begins: hidden at once, it never
   produced a frame, and the canvas had nothing to sample until the viewer
   scrolled back up. Hiding it was never needed — the canvas is opaque and
   covers it completely. */
.hero.is-pixelated .hero-content,
.bio.is-pixelated .bio-content,
.project-hero.is-pixelated .project-hero-image,
.project-hero.is-pixelated .project-hero-title {
    visibility: hidden;
}

.hero-name {
    font-size: 4rem;
    font-weight: 300;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    margin-bottom: 1rem;
    color: #ffffff;
}

.hero-tagline {
    font-size: 1rem;
    letter-spacing: 0.2em;    
    text-transform: uppercase;
    color: #ffffff;
    font-weight: 300;
}

/* Bio Section */
.bio {
    position: sticky;
    top: 0;
    z-index: 1;
    /* A full screen tall, so the text is followed by a screenful of black */
    min-height: var(--hero-height);
    /* Gutter grows with the viewport so the text never touches the edge */
    padding: 6rem clamp(1.5rem, 7vw, 8rem);
    background-color: #000000;
}

.bio-content {
    max-width: 85ch;
}

.bio-text {
    font-size: clamp(1.35rem, 2.2vw, 2.1rem);
    line-height: 1.4;
    color: #ffffff;
}

/* Projects Section — the panel that slides up over the pinned bio */
.projects {
    position: relative;
    z-index: 2;
    width: 100%;
    background-color: #000000;
}

.project-category-title {
    width: 100%;
    padding: 3rem 2rem 2rem;
    font-size: 2.5rem;
    font-weight: 300;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: #ffffff;
    text-align: center;
    background-color: #000000;
}

/* Link out to the full project index */
.see-all-wrap {
    padding: 6rem 2rem;
    text-align: center;
    background-color: #000000;
}

.see-all {
    display: inline-block;
    color: #ffffff;
    text-decoration: none;
    font-size: 1.5rem;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    padding: 1rem 2.5rem;
    border: 1px solid #ffffff;
    transition: background-color 0.3s ease, color 0.3s ease;
}

.see-all:hover {
    background-color: #ffffff;
    color: #000000;
}


.project-item {
    position: relative;
    width: 100%;
    height: 70vh;
    overflow: hidden;
    display: block;
    text-decoration: none;
    cursor: pointer;
    opacity: 0.7;
    transition: opacity 0.3s ease;
}

.project-item:hover {
    opacity: 1;
}

/* Projects with no page yet aren't anchors — don't offer a clickable cursor */
div.project-item {
    cursor: default;
}

/* Three categories side by side, stacking to one column on phones */
.project-row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
}

.project-item-third {
    height: 75vh;
}

.project-image {
    width: 100%;
    height: 100%;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}

.project-image-video {
    width: 100%;
    height: 100%;
    position: relative;
    overflow: hidden;
}

.project-video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: top;
    display: block;
}

.project-title {
    position: absolute;
    bottom: 2rem;
    left: var(--edge-inset);
    font-size: 2.5rem;
    font-weight: 300;
    text-transform: uppercase;
    color: #ffffff;
    text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
    z-index: 1;
}

/* Header band: a pixelated window onto whatever scrolls beneath it. Opaque
   black where no thumbnail is crossing, so it always masks the real content. */
.header-pixels {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: var(--header-height);
    z-index: 40;
    pointer-events: none;
    display: block;
}

/* Carrying the attribute means the header waits for a panel to arrive before
   showing itself — the projects index has no attribute, so it shows at once. */
.header-pixels[data-reveal-after],
.site-title[data-reveal-after] {
    opacity: 0;
    transition: opacity 0.3s ease;
}

.site-title[data-reveal-after] {
    pointer-events: none;
}

.header-pixels.is-revealed,
.site-title.is-revealed {
    opacity: 1;
}

.site-title.is-revealed {
    pointer-events: auto;
}

/* Projects index page */
.works {
    /* Derived from the band's own height: a fixed value silently slid under
       the header the moment it was made taller. */
    padding: calc(var(--header-height) + 3rem) clamp(1.5rem, 5vw, 5rem) 8rem;
}

/* Header line: spans the full band and centres inside it, so the text stays
   on the band's axis whatever its font size — pegging it to the toggle's own
   height would push it off centre as soon as the type grew taller than 1.75rem */
.site-title {
    position: fixed;
    top: 0;
    /* Same inset as the toggle's right edge, and as the titles sitting on the
       artworks, so everything shares one vertical line down the page */
    left: var(--edge-inset);
    z-index: 60;
    display: flex;
    align-items: center;
    height: var(--header-height);
    color: #ffffff;
    text-decoration: none;
    font-size: 1.5rem;
    letter-spacing: 0.2em;
    /* Hard black shadow, no blur — same treatment as the menu type. The band
       behind it is pixelated artwork, so the letters need their own edge. */
    text-shadow: 2px 2px 0 #000000;
    /* Optical centring. Flex centres the line box, which reserves room for
       descenders this all-caps string never uses, so the letters land high.
       VG5000: cap height 700/1000, ascent 800, descent 200 — caps span
       0.2em-0.9em of a 1.2em line, centring at 0.55em against the box's
       0.6em. Nudging down by the 0.05em difference puts caps on the axis. */
    transform: translateY(0.05em);
    transition: opacity 0.3s ease;
}

/* The short form is a phone-only stand-in; the anchor carries an aria-label
   so the accessible name stays the full one either way. */
.site-title-short {
    display: none;
}

.site-title:hover {
    opacity: 0.6;
}

.works-title {
    font-size: 2.5rem;
    font-weight: 300;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    margin: 0 0 4rem;
}

.works-section {
    font-size: 2.5rem;
    font-weight: 300;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    margin: 6rem 0 4rem;
}

.works-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 3rem 2rem;
}

.work {
    display: block;
    text-decoration: none;
    color: #ffffff;
}

.work img {
    display: block;
    width: 100%;
    /* height:auto is what lets the ratio apply. The img tags carry width and
       height attributes, and a height attribute left unopposed is a fixed
       height: it overrides aspect-ratio and the thumbnail renders at the
       full pixel height of its file. */
    height: auto;
    /* A fixed ratio keeps the rows aligned whatever each source image measures */
    aspect-ratio: 4 / 3;
    object-fit: cover;
    opacity: 0.8;
    transition: opacity 0.3s ease;
}

a.work:hover img {
    opacity: 1;
}

.work-title {
    margin-top: 0.9rem;
    font-size: 1.1rem;
    font-weight: 300;
    letter-spacing: 0.12em;
    text-transform: uppercase;
}

/* Tablet: two columns */
@media (max-width: 1024px) {
    .works-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

/* About page ------------------------------------------------------------- */

/* Desktop: portrait on the left, text beside it. The grid starts at the very
   top of the page, under the fixed header, so the text column's own top edge
   sits at 0 on arrival — which is what reveals the header straight away. */
.about {
    /* The portrait is 2:3, so its column is derived from its height at that same
       ratio: the frame then matches the photograph and cover has nothing to
       crop. Height is capped so a large monitor stays sane. */
    --portrait-height: min(calc(var(--hero-height) - var(--header-height) - 4rem), 1000px);
    --about-gap: clamp(2rem, 5vw, 6rem);
    padding: 0 var(--edge-inset);
}

/* Portrait and biography side by side, centred as one block. They have a
   wrapper of their own because a sticky grid item stays pinned for the whole
   height of its grid: sharing one with Exhibitions, the portrait stayed stuck
   over the list as it scrolled past. Here it is released with the biography. */
.about-intro {
    display: grid;
    grid-template-columns: minmax(0, calc(var(--portrait-height) * 2 / 3)) minmax(0, 62ch);
    justify-content: center;
    column-gap: var(--about-gap);
    align-items: start;
}

/* Held in view while the text scrolls, so the left column never empties */
.about-portrait {
    position: sticky;
    top: calc(var(--header-height) + 2rem);
    height: var(--portrait-height);
    overflow: hidden;
}

.about-portrait-image {
    width: 100%;
    height: 100%;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}

/* Title laid on the foot of the portrait, phone only. On desktop the same
   word heads the text column instead, so this copy stays hidden there. */
.about-portrait-title {
    display: none;
    position: absolute;
    left: 1.5rem;
    bottom: 1.5rem;
    z-index: 1;
    font-size: clamp(1.5rem, 2.4vw, 2.4rem);
    font-weight: 300;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: #ffffff;
}

/* Section titles on About: Biography heading the text column on desktop, and
   Exhibitions below it on every screen */
.about-title,
.about-section-title {
    margin-bottom: 2rem;
    font-size: clamp(1.8rem, 2.6vw, 2.5rem);
    font-weight: 300;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: #ffffff;
}

.about-body {
    max-width: 62ch;
    padding: calc(var(--header-height) + 2rem) 0 6rem;
    background-color: #000000;
}

.about-body p {
    font-size: clamp(1.05rem, 1.3vw, 1.35rem);
    line-height: 1.6;
    color: #ffffff;
}

.about-body p + p {
    margin-top: 1.4em;
}

/* The last line is a sign-off, set apart from the argument before it */
.about-body .about-closing {
    margin-top: 2.4em;
    font-size: clamp(1.4rem, 2vw, 2rem);
    letter-spacing: 0.05em;
}

/* Exhibitions: a second page below the biography, spanning both columns on
   desktop. Year, venue and work on one line each, ruled like a catalogue. */
.about-exhibitions {
    /* As wide as the portrait and biography above, and centred with them */
    max-width: calc(var(--portrait-height) * 2 / 3 + var(--about-gap) + 62ch);
    margin: 0 auto;
    /* Generous top: a clear break between the biography and the list */
    padding: 8rem 0 8rem;
    background-color: #000000;
    /* White pixel dots in place of rules: square dots on a fixed pitch, from a
       hard-stopped gradient, since border-style dotted draws round dots.
       Every rule starts at the same left edge, so the dots line up in columns. */
    --dot: 3px;
    --pixel-rule: repeating-linear-gradient(to right,
        #ffffff 0 var(--dot),
        transparent var(--dot) calc(var(--dot) * 2));
}

.exhibition-list {
    list-style: none;
    padding-top: var(--dot);
}

.exhibition {
    display: grid;
    grid-template-columns: 6rem minmax(0, 1fr) minmax(0, 1fr);
    column-gap: 2rem;
    align-items: baseline;
    padding: 1.1rem 0;
    font-size: clamp(1rem, 1.3vw, 1.3rem);
    line-height: 1.4;
    color: #ffffff;
}

/* Partners, after Exhibitions on the About page: same column. Logos wrap
   onto as many centred rows as they need. Each logo carries --logo-scale in
   the page, worked out from its shape and how much ink it has, so a thin
   wide logo and a dense square one read at a similar size. */
.about-goldenpixel {
    max-width: calc(var(--portrait-height) * 2 / 3 + var(--about-gap) + 62ch);
    margin: 0 auto;
    padding: 2rem 0 8rem;
    background-color: #000000;
}

.goldenpixel-text {
    font-size: clamp(1rem, 1.3vw, 1.3rem);
    line-height: 1.6;
    color: #ffffff;
    max-width: 62ch;
}

.goldenpixel-contact {
    display: inline-block;
    margin-top: 1.5rem;
    padding: 0.6rem 1.6rem;
    border: 1px solid #ffffff;
    color: #ffffff;
    text-decoration: none;
    font-size: clamp(0.9rem, 1.1vw, 1.1rem);
    letter-spacing: 0.05em;
    transition: background-color 0.2s, color 0.2s;
}

.goldenpixel-contact:hover {
    background-color: #ffffff;
    color: #000000;
}

.about-partners {
    max-width: calc(var(--portrait-height) * 2 / 3 + var(--about-gap) + 62ch);
    margin: 0 auto;
    padding: 0 0 8rem;
    background-color: #000000;
}

.partners {
    --logo-base: 4.5rem;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    gap: 3rem 4rem;
    margin: 0;
    padding: 1rem 0 0;
    list-style: none;
}

.partner {
    display: flex;
    justify-content: center;
    align-items: center;
    max-width: 100%;
}

.partner-logo {
    display: block;
    width: auto;
    height: calc(var(--logo-base) * var(--logo-scale, 1));
    max-width: 100%;
}

.exhibition-year {
    opacity: 0.7;
}

/* Work titles in capitals, like the project names across the site */
.exhibition-work {
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

/* Phone: the portrait is pinned full screen and the text arrives as a panel
   sliding over it, which the scroll handler pixelates as it climbs. */
@media (max-width: 768px) {
    .about {
        display: block;
        padding: 0;
    }

    .about-intro {
        display: block;
    }

    .about-portrait {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        height: var(--hero-height);
        z-index: 0;
    }

    .about-body {
        position: relative;
        z-index: 1;
        max-width: none;
        min-height: var(--hero-height);
        /* Starts at 85% of the screen, so the top of the panel already shows on
           arrival and hints that the text is there. Keep in step with
           startAt: 0.15 on the portrait pixelator in script.js. */
        margin-top: calc(var(--hero-height) * 0.85);
        padding: 3rem var(--edge-inset) 5rem;
    }

    .about-body p {
        font-size: 1.05rem;
    }

    /* On a phone the title sits on the portrait instead of heading the text:
       bottom left, just above the text panel, whose top already covers the
       bottom 15% of the portrait on arrival. */
    .about-portrait-title {
        display: block;
        left: var(--edge-inset);
        bottom: calc(var(--hero-height) * 0.15 + 0.75rem);
        font-size: 2rem;
    }

    .about-title {
        display: none;
    }

    /* Follows the text panel over the pinned portrait, so it needs the same
       stacking and background to keep covering it */
    .about-exhibitions {
        position: relative;
        z-index: 1;
        padding: 4rem var(--edge-inset) 5rem;
    }

    /* Year in its own column, venue and work stacked beside it */
    .exhibition {
        grid-template-columns: 3.5rem minmax(0, 1fr);
        column-gap: 1rem;
        row-gap: 0.25rem;
        font-size: 1rem;
    }

    .exhibition-work {
        grid-column: 2;
    }

    .about-goldenpixel {
        position: relative;
        z-index: 1;
        padding: 3rem var(--edge-inset) 4rem;
    }

    .goldenpixel-text {
        font-size: 1.05rem;
    }

    /* Follows Exhibitions over the pinned portrait, with the same stacking */
    .about-partners {
        position: relative;
        z-index: 1;
        padding: 0 var(--edge-inset) 5rem;
    }

    .partners {
        --logo-base: 3.25rem;
        gap: 2.25rem 2.5rem;
    }
}

/* Responsive */
@media (max-width: 768px) {
    /* Side padding moves off the page and onto the headings, so the grid can
       run edge to edge instead of sitting inside a black frame. */
    .works {
        padding: calc(var(--header-height) + 1.5rem) 0 5rem;
    }

    .works-title,
    .works-section {
        font-size: 1.5rem;
        padding: 0 1.25rem;
    }

    .works-section {
        margin: 4rem 0 2.5rem;
    }

    /* One full-width thumbnail per row, reaching both edges. The row gap is
       the band the titles live in, not a margin around the images. */
    .works-grid {
        grid-template-columns: 1fr;
        gap: 1.75rem 0;
    }

    /* Inset to match the headings, so every line of text on the page starts
       on the same vertical now that the grid itself has no padding. */
    .work-title {
        margin-top: 0.7rem;
        padding: 0 1.25rem;
        font-size: 1rem;
    }

    /* Both header items read these, so they stay on the same lines */
    :root {
        --header-top: 1.25rem;
        --edge-inset: 1.5rem;
    }

    /* Two letters instead of twelve, so the type can be far larger without
       crowding the toggle on a phone-width header. */
    .site-title {
        font-size: 2.1rem;
        letter-spacing: 0.1em;
    }

    .site-title-full {
        display: none;
    }

    .site-title-short {
        display: inline;
    }

    /* Full screen on phones, and it drops in from above rather than sliding
       from the side: at this width a side drawer would leave a useless sliver
       of page behind it. The opacity runs first and the slide is delayed
       behind it, so it fades in and then settles down. */
    .sidebar {
        left: 0;
        right: 0;
        bottom: auto;
        width: auto;
        max-width: none;
        /* Dynamic viewport height: it tracks the browser bars as they show and
           hide, so the panel matches the visible area in both states. A fixed
           height leaves either a strip of page below it or its own footer
           stranded behind Chrome's bottom bar. Safe because page scrolling is
           locked while the menu is open, so this value cannot flicker. */
        height: 100vh;
        height: 100dvh;
        /* Long menus stay reachable — and this is what makes the touch guard
           below able to tell a real panel scroll from a page scroll. */
        overflow-y: auto;
        /* The bottom inset clears the iOS home indicator, which sits over the
           panel rather than shrinking the viewport it is measured against. */
        padding: calc(var(--header-height) + 1rem) 1.5rem
                 calc(2rem + env(safe-area-inset-bottom, 0px));
        transform: translateY(-8%);
        opacity: 0;
        transition:
            opacity 0.22s ease,
            transform 0.42s ease 0.1s,
            visibility 0s linear 0.52s;
    }

    .menu-open .sidebar {
        transform: translateY(0);
        opacity: 1;
        transition:
            opacity 0.22s ease,
            transform 0.42s ease 0.1s,
            visibility 0s;
    }

    .sidebar-nav a {
        font-size: 1.9rem;
    }

    .sidebar-info {
        font-size: 0.9rem;
    }

    /* Larger again on the full-screen menu, where they sit alone at the bottom
       of the panel and are the only touch targets in that block. */
    .sidebar-social {
        gap: 1.4rem;
        margin-top: 1.75rem;
    }

    .sidebar-social img {
        width: 34px;
        height: 34px;
    }


    .hero-name {
        font-size: 2rem;
    }
    
    .hero-tagline {
        font-size: 0.8rem;
    }
    
    .bio {
        padding-top: 3rem;
        padding-bottom: 3rem;
    }

    .project-category-title {
        font-size: 1.5rem;
        padding: 2rem 1rem 1rem;
    }
    
    .project-title {
        font-size: 1.8rem;
        bottom: 1.5rem;
    }

    .see-all-wrap {
        padding: 3.5rem 1rem;
    }

    .see-all {
        font-size: 1rem;
        padding: 0.8rem 1.5rem;
    }

    .project-row {
        grid-template-columns: 1fr;
    }

    .project-item-third {
        height: 55vh;
    }
}

