@import url('https://fonts.googleapis.com/css2?family=Hi+Melody&family=Instrument+Serif:ital@0;1&family=Space+Grotesk:wght@300..700&display=swap');

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

html {
    scroll-behavior: smooth;
}

html, body {
    margin: 0;
}

body {
    cursor: url("src/small_cursor.png"), auto;
}

.u-homepage {
    width: 100vw;
    height: 100vh;
    height: 100dvh; /* accounts for mobile browser chrome (address bar) */

    display: flex;
    justify-content: center;
    align-items: center;
}

.IntroCard {
    width: 75vw;
    height: 85vh;
    height: 85dvh;

    position: relative;

    /* One knob for the border thickness — equal on all four sides.
       vw-scaled (relative to viewport, same basis as the card's own 75vw)
       so it shrinks proportionally on mobile instead of looking oversized
       on a narrow card; clamped so it never over/undershoots. */
    --border-gap: clamp(10px, 3.5vw, 24px);
}

.ic-ImageBorder {
    position: absolute;
    inset: 0;

    /* Same vw-scaling approach as --border-gap: keeps the rounding in
       proportion to the card at any viewport width. */
    border-radius: clamp(20px, 15vw, 104px);

    background: #888;
    /* Larger blur = softer feather that fades off at the edge. */
    filter: blur(10px);

    opacity: 0.5;

    z-index: 10;
}

.ic-ImageContainer {
    position: absolute;
    /* Equal pixel gap on every side, so the glow border reads the same all around. */
    inset: var(--border-gap);

    border-radius: clamp(16px, 11.5vw, 80px);

    overflow: hidden;

    z-index: 20;
}

.ic-ImageContainer img {
    position: absolute;
    inset: 0;

    /* Vertical focus point on the source image (0% = top, 100% = bottom)
       and extra zoom beyond the cover-fit crop. Both are %-based, so the
       framing stays correct at any container size — no JS needed. */
    --img-focus-y: 90%;
    --img-zoom: 1.25;
    /* Set by index.js on scroll — px, bounded by the zoom's own overscan
       margin so shifting right can never reveal an edge. */
    --img-shift-x: 0px;

    /* Always fully covers the container at any aspect ratio — no gaps,
       no manual width/position tuning that only worked for one shape. */
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center var(--img-focus-y);

    /* Zoom anchored at the same focus point, so it magnifies that area
       instead of re-centering the crop. translateX rides on top for the
       scroll-driven shift. */
    transform: scale(var(--img-zoom)) translateX(var(--img-shift-x));
    transform-origin: center var(--img-focus-y);
    will-change: transform;

    filter: grayscale(100%);
}

/* Film-grain noise for the whole card (image + glow border).
   z-index 40 sits above the title (30), so the grain covers the title too.
   To keep the title clean, change this to 25 (above the image, below the title). */
.IntroCard::after {
    content: "";
    position: absolute;
    inset: 0px;

    pointer-events: none;
    z-index: 40;

    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='600'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.7' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
    background-repeat: repeat;

    /* Feather the grain's own edges so it fades out with the border instead of
       ending on a hard line. One knob, even on all four sides. */
    --grain-feather: clamp(16px, 6vw, 40px);
    -webkit-mask-image:
        linear-gradient(to right, transparent, #000 var(--grain-feather), #000 calc(100% - var(--grain-feather)), transparent),
        linear-gradient(to bottom, transparent, #000 var(--grain-feather), #000 calc(100% - var(--grain-feather)), transparent);
    -webkit-mask-composite: source-in;
    mask-image:
        linear-gradient(to right, transparent, #000 var(--grain-feather), #000 calc(100% - var(--grain-feather)), transparent),
        linear-gradient(to bottom, transparent, #000 var(--grain-feather), #000 calc(100% - var(--grain-feather)), transparent);
    mask-composite: intersect;

    opacity: 1;
    mix-blend-mode: overlay;

    /* Jitter the texture, not the element, so the layer stays pinned to the card. */
    animation: filmGrain 1s steps(2) infinite;
}

@keyframes filmGrain {
    0% {
        background-position: 0 0;
    }

    25% {
        background-position: -12px 8px;
    }

    50% {
        background-position: 8px -10px;
    }

    75% {
        background-position: 10px 12px;
    }

    100% {
        background-position: -8px -6px;
    }
}

.ic-Title {
    position: absolute;
    z-index: 30;

    /* Cover the image box exactly, so "inside the image" is literally this box. */
    inset: var(--border-gap);

    margin: 0;
    /* Keeps text clear of a phone notch / home-indicator on the full-bleed
       mobile view. Resolves to 0 on desktop and non-notched devices, so
       this is harmless there. Needs viewport-fit=cover in the HTML meta
       tag to report real values on notched devices. */
    padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px)
        env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);

    display: flex;
    align-items: center;
    justify-content: center;

    /* Never let the title spill outside the image. */
    overflow: hidden;

    text-align: center;
    color: white;

    font-family: "Instrument Serif", serif;
    font-weight: 400;
    font-style: italic;
    line-height: 0.95;

    text-shadow: 6px 9px 1px rgba(0,0,0,0.5)
}

/* The measured/scaled element. font-size is set by index.js to fill the box.
   Row layout ("Hey!" + "I'm Lucas" side by side) keeps it on one line on
   wide screens. index.js measures this element's own bounding box, and a
   flex container's box is correct in either direction — row or column —
   so no JS changes are needed when the layout below switches. */
.tt-Fit {
    display: flex;
    flex-direction: row;
    align-items: baseline;
    justify-content: center;
    gap: 0.3em;
    max-width: 100%;
}

/* Mobile breakpoint. Two changes below this width:
   1. Title stacks to two lines (see .tt-Fit comment above it).
   2. The card goes full-bleed — no border/glow/rounding — so it's just the
      image and text filling the screen, for maximum readability. */
@media (max-width: 600px) {
    .tt-Fit {
        flex-direction: column;
        align-items: center;
        gap: 0;
    }

    .IntroCard {
        width: 100vw;
        height: 100vh;
        height: 100dvh;

        /* Image and title both key off this — dropping it to 0 makes them
           span edge-to-edge with no extra CSS needed. */
        --border-gap: 0px;
    }

    .ic-ImageBorder {
        display: none;
    }

    .ic-ImageContainer {
        border-radius: 0;
    }

    .IntroCard::after {
        /* The feather exists to fade the grain into the border's blur.
           There's no border here, so a feather just reads as an unexplained
           vignette at the screen edge — run the grain flush edge-to-edge instead. */
        --grain-feather: 0px;
    }
}

.tt-Line {
    white-space: nowrap;
}

.ht-Style {
    font-family: "Hi Melody", sans-serif;
    font-weight: 400;
    font-style: normal;
    color: white;
    font-size: 1em; /* scale together with the rest of the title */
}

.u-details {
    width: 80%;
    max-width: 1100px;
    margin: auto;

    padding-block: clamp(48px, 10vh, 120px);
}

.u-whoami {
    width: 100%;

    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: clamp(32px, 6vw, 80px);
}

.wai-blurb {
    width: 52%;
}

.wai-title {
    font-family: "Hi Melody", sans-serif;
    font-weight: 400;
    font-style: normal;
    line-height: 0.85;
    /* Scales with the viewport instead of sitting fixed at 150px. */
    font-size: clamp(48px, 9vw, 130px);
    color: #14142b;

    margin: 0;
}

.wai-text {
    font-family: "Space Grotesk", sans-serif;
    font-weight: 400;
    font-style: normal;
    line-height: 1.5;
    font-size: clamp(16px, 1.4vw, 20px);
    color: #4a4a5a;

    max-width: 60ch;
    margin: 0 0 20px;
}

/* The three short accent ticks + the long baseline. Even gap instead of a
   one-off margin-right hack, and each hr scales in from the left on
   scroll (see .js-reveal / index.js) instead of just appearing. */
.wai-lines {
    display: flex;
    align-items: center;
    gap: 10px;

    margin: 18px 0 24px;
}

.wai-leftline,
.wai-midline,
.wai-rightline,
.wai-finalline {
    margin: 0;
    height: 5px;
    border: 0;
    border-radius: 3px;

    transform: scaleX(0);
    transform-origin: left center;
    transition: transform 0.6s ease;
}

.wai-leftline {
    width: 32px;
    background-color: #000986;
}

.wai-midline {
    width: 32px;
    background-color: #ff8a54;
}

.wai-rightline {
    width: 32px;
    background-color: #ffc1fb;
    margin-right: 12px;
}

.wai-finalline {
    flex: 1;
    background-color: #14142b;
}

.wai-lines.is-visible .wai-leftline,
.wai-lines.is-visible .wai-midline,
.wai-lines.is-visible .wai-rightline,
.wai-lines.is-visible .wai-finalline {
    transform: scaleX(1);
}

/* Stagger the draw-in left to right for a small cascading feel. */
.wai-lines.is-visible .wai-midline { transition-delay: 0.08s; }
.wai-lines.is-visible .wai-rightline { transition-delay: 0.16s; }
.wai-lines.is-visible .wai-finalline { transition-delay: 0.24s; }

/* Bulleted list — custom dots cycling through the accent colors above,
   tying the two together instead of using the default browser bullet. */
.wai-list {
    list-style: none;
    margin: 0;
    padding: 0;

    display: flex;
    flex-direction: column;
    gap: 10px;
}

.wai-list li {
    position: relative;
    padding-left: 26px;

    font-family: "Space Grotesk", sans-serif;
    font-size: clamp(14px, 1.2vw, 17px);
    color: #4a4a5a;

    transition: color 0.2s ease, transform 0.2s ease;
}

.wai-list li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.5em;

    width: 10px;
    height: 10px;
    border-radius: 50%;
    background-color: #ff8a54;
}

.wai-list li:nth-child(3n+1)::before { background-color: #000986; }
.wai-list li:nth-child(3n+2)::before { background-color: #ff8a54; }
.wai-list li:nth-child(3n)::before { background-color: #ffc1fb; }

.wai-list li:hover {
    color: #14142b;
    transform: translateX(4px);
}

.wai-imageContainer {
    width: 44%;
    max-width: 420px;
    flex-shrink: 0;
}

.wai-image {
    display: block;
    width: 100%;
    aspect-ratio: 1 / 1;

    /* object-fit: cover works at any source-image aspect ratio, same
       approach used for the hero banner. */
    object-fit: cover;
    object-position: center 25%;

    border-radius: 32px;
    box-shadow: 0 20px 40px -12px rgba(20, 20, 43, 0.35);

    filter: grayscale(25%);
    transition: transform 0.35s ease, box-shadow 0.35s ease, filter 0.35s ease;
}

.wai-image:hover {
    transform: translateY(-6px) scale(1.02);
    box-shadow: 0 28px 48px -12px rgba(20, 20, 43, 0.45);
    filter: grayscale(0%);
}

/* Scroll-reveal: elements fade + rise into place the first time they enter
   the viewport (see the IntersectionObserver in index.js). */
.js-reveal {
    opacity: 0;
    transform: translateY(28px);
    transition: opacity 0.7s ease, transform 0.7s ease;
}

.js-reveal.is-visible {
    opacity: 1;
    transform: none;
}

@media (max-width: 600px) {
    /* Match the 90% width every other section uses on mobile — this was
       stuck at the desktop 80%, creating a visible margin jump between
       "Who am I" and the section right after it. */
    .u-details {
        width: 90%;
    }

    .u-whoami {
        flex-direction: column;
        align-items: center;
        text-align: center;
    }

    /* Photo first on mobile — a bio reads better with the face up top. */
    .wai-imageContainer {
        order: -1;
        width: min(70vw, 320px);
    }

    .wai-blurb {
        width: 100%;
    }

    .wai-text {
        max-width: none;
    }

    .wai-lines {
        justify-content: center;
    }

    .wai-list {
        align-items: center;
    }

    .wai-list li {
        padding-left: 0;
        padding-top: 18px;
    }

    .wai-list li::before {
        left: 50%;
        top: 0;
        transform: translateX(-50%);
    }

    .wai-list li:hover {
        transform: none;
    }
}

/* ==========================================================================
   Nav bar — fixed, transparent over the hero, gains a background once you
   scroll past it (see .is-scrolled, toggled in index.js).
   ========================================================================== */

:root {
    --nav-height: 72px;
}

/* Anchor targets land below the fixed nav instead of tucked under it. */
#whoami,
#experience,
#projects,
#gallery,
#contact,
#site-footer {
    scroll-margin-top: var(--nav-height);
}

.u-nav {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 100;

    height: var(--nav-height);
    padding-inline: clamp(20px, 5vw, 64px);

    display: flex;
    align-items: center;
    justify-content: space-between;

    background: transparent;
    transition: background-color 0.3s ease, box-shadow 0.3s ease;
}

.u-nav.is-scrolled {
    background: rgba(255, 255, 255, 0.85);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    box-shadow: 0 2px 20px rgba(20, 20, 43, 0.08);
}

.nav-brand {
    font-family: "Hi Melody", sans-serif;
    font-size: 24px;
    color: white;
    text-decoration: none;
    transition: color 0.3s ease;
}

.u-nav.is-scrolled .nav-brand {
    color: #14142b;
}

.nav-links {
    list-style: none;
    display: flex;
    gap: clamp(16px, 3vw, 36px);
    margin: 0;
    padding: 0;
}

.nav-links a {
    font-family: "Space Grotesk", sans-serif;
    font-size: 15px;
    color: white;
    text-decoration: none;
    cursor: url("src/small_cursor2.png"), pointer;

    position: relative;
    transition: color 0.3s ease;
}

.u-nav.is-scrolled .nav-links a {
    color: #14142b;
}

/* Small underline-on-hover, drawn with the same accent orange used elsewhere. */
.nav-links a::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: -4px;
    width: 100%;
    height: 2px;
    background: #ff8a54;

    transform: scaleX(0);
    transform-origin: left center;
    transition: transform 0.25s ease;
}

.nav-links a:hover::after {
    transform: scaleX(1);
}

.nav-toggle {
    display: none;

    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 5px;

    width: 40px;
    height: 40px;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: url("src/small_cursor2.png"), pointer;
}

.nav-toggle span {
    width: 22px;
    height: 2px;
    background: white;
    transition: background-color 0.3s ease, transform 0.3s ease, opacity 0.3s ease;
}

.u-nav.is-scrolled .nav-toggle span {
    background: #14142b;
}

/* Morph the three bars into an X while the menu is open. */
.u-nav.nav-open .nav-toggle span:nth-child(1) {
    transform: translateY(7px) rotate(45deg);
}

.u-nav.nav-open .nav-toggle span:nth-child(2) {
    opacity: 0;
}

.u-nav.nav-open .nav-toggle span:nth-child(3) {
    transform: translateY(-7px) rotate(-45deg);
}

@media (max-width: 700px) {
    .nav-toggle {
        display: flex;
    }

    /* Collapsed by default; becomes a full-width dropdown panel under the
       nav bar once .nav-open is toggled (see index.js). */
    .nav-links {
        position: fixed;
        top: var(--nav-height);
        left: 0;
        right: 0;

        flex-direction: column;
        align-items: center;
        gap: 0;

        background: rgba(255, 255, 255, 0.97);
        backdrop-filter: blur(12px);
        -webkit-backdrop-filter: blur(12px);
        box-shadow: 0 12px 24px rgba(20, 20, 43, 0.1);

        max-height: 0;
        overflow: hidden;
        transition: max-height 0.3s ease;
    }

    .u-nav.nav-open .nav-links {
        max-height: 320px;
    }

    .nav-links a {
        display: block;
        width: 100%;
        padding: 18px 0;
        text-align: center;
        color: #14142b;
    }

    .nav-links a::after {
        display: none;
    }

    .nav-links li {
        width: 100%;
        border-top: 1px solid rgba(20, 20, 43, 0.08);
    }
}

/* ==========================================================================
   Skills strip
   ========================================================================== */

.u-skills {
    width: 80%;
    max-width: 1100px;
    margin: 0 auto;
    padding-block: clamp(24px, 5vh, 56px);
}

.skills-list {
    list-style: none;
    margin: 0;
    padding: 0;

    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 12px;
}

.skill-pill {
    font-family: "Space Grotesk", sans-serif;
    font-size: 15px;
    color: #14142b;

    padding: 8px 20px;
    border: 1.5px solid #14142b;
    border-radius: 999px;

    /* Hover-only transition — deliberately has no transition-delay of its
       own, ever. The entrance stagger below uses `animation-delay`
       instead of `transition-delay` specifically so it can never leak
       into this. (transition-delay applies to ANY change of a property,
       not just the one it was intended for — an earlier fix only zeroed
       it back out on :hover, which fixed hovering IN but not hovering
       OUT: the moment the cursor leaves, :hover stops matching and the
       stagger delay comes back for the reverse transition, which is
       exactly the "fast on the left, slow on the right" you're still
       seeing. Using a separate `animation` for the one-time entrance
       removes the shared property entirely, so there's nothing left to
       leak either direction.) */
    transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease;
}

.skill-pill:hover {
    background-color: #14142b;
    color: white;
    transform: translateY(-3px);
}

/* One-time entrance: fades/rises in when .is-visible is added, via
   `animation` rather than `transition` (see the comment above). */
.skill-pill.is-visible {
    animation: skillPillIn 0.5s ease both;
}

@keyframes skillPillIn {
    from {
        opacity: 0;
        transform: translateY(28px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* Stagger the entrance left to right. */
.skill-pill:nth-child(1) { animation-delay: 0s; }
.skill-pill:nth-child(2) { animation-delay: 0.05s; }
.skill-pill:nth-child(3) { animation-delay: 0.1s; }
.skill-pill:nth-child(4) { animation-delay: 0.15s; }
.skill-pill:nth-child(5) { animation-delay: 0.2s; }
.skill-pill:nth-child(6) { animation-delay: 0.25s; }
.skill-pill:nth-child(7) { animation-delay: 0.3s; }
.skill-pill:nth-child(8) { animation-delay: 0.35s; }

/* ==========================================================================
   Experience — stacked cards
   ========================================================================== */

.u-experience {
    width: 80%;
    max-width: 1100px;
    margin: 0 auto;
    padding-block: clamp(48px, 10vh, 100px);
}

.exp-list {
    display: flex;
    flex-direction: column;
    gap: 20px;
    margin-top: 24px;
}

.exp-card {
    background: #fafaf9;
    border-radius: 20px;
    padding: 28px 32px;

    /* Cycles through the three accent colors used throughout the site. */
    border-left: 5px solid #000986;
    box-shadow: 0 12px 28px -16px rgba(20, 20, 43, 0.25);

    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.exp-card:nth-child(3n+2) { border-left-color: #ff8a54; }
.exp-card:nth-child(3n) { border-left-color: #ffc1fb; }

.exp-card:hover {
    transform: translateY(-4px);
    box-shadow: 0 20px 36px -16px rgba(20, 20, 43, 0.32);
}

.exp-card-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
}

.exp-role {
    font-family: "Space Grotesk", sans-serif;
    font-weight: 600;
    font-size: clamp(18px, 2vw, 22px);
    color: #14142b;
    margin: 0;
}

.exp-dates {
    font-family: "Space Grotesk", sans-serif;
    font-size: 14px;
    color: #8a8a99;
}

.exp-company {
    font-family: "Space Grotesk", sans-serif;
    font-size: 15px;
    font-weight: 600;
    color: #ff8a54;
    margin: 4px 0 12px;
}

.exp-desc {
    font-family: "Space Grotesk", sans-serif;
    font-size: 15px;
    line-height: 1.5;
    color: #4a4a5a;
    margin: 0;
}

/* Same colored-dot marker style as .wai-list, but each card's dots match
   that card's own left-border accent color (see .exp-card:nth-child). */
.exp-bullets {
    list-style: none;
    margin: 12px 0 0;
    padding: 0;

    display: flex;
    flex-direction: column;
    gap: 8px;
}

.exp-bullets li {
    position: relative;
    padding-left: 20px;

    font-family: "Space Grotesk", sans-serif;
    font-size: 14px;
    line-height: 1.5;
    color: #4a4a5a;
}

.exp-bullets li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.55em;

    width: 8px;
    height: 8px;
    border-radius: 50%;
    background-color: #000986;
}

.exp-card:nth-child(3n+2) .exp-bullets li::before { background-color: #ff8a54; }
.exp-card:nth-child(3n) .exp-bullets li::before { background-color: #ffc1fb; }

/* ==========================================================================
   Projects — grid of cards
   ========================================================================== */

.u-projects {
    width: 80%;
    max-width: 1100px;
    margin: 0 auto;
    padding-block: clamp(48px, 10vh, 100px);
}

.proj-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 28px;
    margin-top: 24px;
}

.proj-card {
    display: flex;
    flex-direction: column;

    border-radius: 20px;
    overflow: hidden;
    background: #fafaf9;
    box-shadow: 0 12px 28px -16px rgba(20, 20, 43, 0.25);

    /* Cards are real interactive controls (role="button", tabindex="0" in
       the HTML) — they open the project modal on click or Enter/Space. */
    cursor: url("src/small_cursor2.png"), pointer;

    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.proj-card:hover,
.proj-card:focus-visible {
    transform: translateY(-6px);
    box-shadow: 0 22px 40px -16px rgba(20, 20, 43, 0.32);
}

.proj-card:focus-visible {
    outline: 2px solid #ff8a54;
    outline-offset: 3px;
}

.proj-more {
    font-family: "Space Grotesk", sans-serif;
    font-size: 14px;
    font-weight: 600;
    color: #ff8a54;

    margin: 0 24px 20px;
}

/* No real project screenshots yet — a colored gradient stands in, and
   desaturates/brightens on hover the same way the profile photo does. */
.proj-thumb {
    display: block;
    width: 100%;
    height: 160px;

    /* Each card sets its own --proj-a/--proj-b inline, so every project
       gets a distinct two-color gradient instead of one shared look. */
    background-image: linear-gradient(135deg, var(--proj-a, #000986), var(--proj-b, #ff8a54));

    filter: grayscale(70%);
    transition: filter 0.35s ease;
}

.proj-card:hover .proj-thumb {
    filter: grayscale(0%);
}

.proj-title {
    font-family: "Space Grotesk", sans-serif;
    font-weight: 600;
    font-size: 20px;
    color: #14142b;
    margin: 20px 24px 0;
}

.proj-desc {
    font-family: "Space Grotesk", sans-serif;
    font-size: 15px;
    line-height: 1.5;
    color: #4a4a5a;
    margin: 8px 24px 0;
    flex: 1;
}

.proj-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 16px 24px 12px;
}

.proj-tag {
    font-family: "Space Grotesk", sans-serif;
    font-size: 13px;
    color: #14142b;
    background: rgba(20, 20, 43, 0.06);
    padding: 4px 12px;
    border-radius: 999px;
}

/* ==========================================================================
   Footer
   ========================================================================== */

.u-footer {
    width: 80%;
    max-width: 1100px;
    margin: 0 auto;
    padding-block: clamp(32px, 6vh, 56px);
}

.footer-line {
    margin-bottom: 24px;
}

.footer-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
}

.footer-copy {
    font-family: "Space Grotesk", sans-serif;
    font-size: 14px;
    color: #8a8a99;
    margin: 0;
}

.footer-top {
    font-family: "Space Grotesk", sans-serif;
    font-size: 14px;
    color: #14142b;
    text-decoration: none;
    cursor: url("src/small_cursor2.png"), pointer;

    transition: color 0.2s ease;
}

.footer-top:hover {
    color: #ff8a54;
}

/* ==========================================================================
   Project detail modal — opened from a .proj-card, populated in index.js
   from that card's data-* attributes.
   ========================================================================== */

.modal-overlay {
    position: fixed;
    inset: 0;
    z-index: 200;

    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;

    background: rgba(20, 20, 43, 0.55);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);

    opacity: 0;
    transition: opacity 0.25s ease;
}

/* Author `display: flex` above always beats the browser's built-in
   `[hidden] { display: none }` rule (author styles win regardless of
   specificity), so without this override the modal would stay laid out
   full-viewport at all times — invisible (opacity: 0) but still
   intercepting every click on the page. This is the fix. */
.modal-overlay[hidden] {
    display: none;
}

.modal-overlay:not([hidden]) {
    opacity: 1;
}

.modal-box {
    position: relative;
    width: min(560px, 100%);
    max-height: 85vh;
    overflow-y: auto;

    background: white;
    border-radius: 24px;
    padding: 32px;
    box-shadow: 0 32px 64px -16px rgba(0, 0, 0, 0.4);

    transform: translateY(12px) scale(0.98);
    transition: transform 0.25s ease;
}

.modal-overlay:not([hidden]) .modal-box {
    transform: none;
}

.modal-close {
    position: absolute;
    top: 16px;
    right: 16px;

    width: 36px;
    height: 36px;
    border: 0;
    border-radius: 50%;
    background: rgba(20, 20, 43, 0.06);
    color: #14142b;
    font-size: 22px;
    line-height: 1;

    cursor: url("src/small_cursor2.png"), pointer;
    transition: background-color 0.2s ease;
}

.modal-close:hover {
    background: rgba(20, 20, 43, 0.12);
}

.modal-thumb {
    height: 180px;
    border-radius: 16px;
    margin-bottom: 20px;
    background-size: cover;
    background-position: center;
    /* Filled in from the clicked card's thumbnail in index.js, so it
       always matches — a gradient placeholder or a real screenshot. */
    background-image: linear-gradient(135deg, #000986, #ff8a54, #ffc1fb);
}

.modal-title {
    font-family: "Space Grotesk", sans-serif;
    font-weight: 600;
    font-size: 24px;
    color: #14142b;
    margin: 0 0 12px;
}

.modal-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 16px;
}

.modal-tags .proj-tag {
    background: rgba(20, 20, 43, 0.06);
}

.modal-desc {
    font-family: "Space Grotesk", sans-serif;
    font-size: 15px;
    line-height: 1.6;
    color: #4a4a5a;
    margin: 0;
}

/* Optional demo/GitHub buttons — each is shown/hidden individually in
   index.js depending on whether that project has a data-demo-url /
   data-github-url. Hidden entirely (not just empty) when a project has
   neither, so no empty gap is left at the bottom of the modal. */
.modal-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 24px;
}

/* index.js hides this whole row when neither link applies. Same fix as
   .modal-overlay/.lightbox-overlay earlier: author `display: flex` above
   always beats the browser's own `[hidden] { display: none }`, so this
   override is required or the row would stay laid out (as an empty gap)
   even when hidden. */
.modal-actions[hidden] {
    display: none;
}

.modal-link[hidden] {
    display: none;
}

.modal-link {
    font-family: "Space Grotesk", sans-serif;
    font-weight: 600;
    font-size: 14px;
    text-decoration: none;

    padding: 10px 22px;
    border-radius: 999px;

    cursor: url("src/small_cursor2.png"), pointer;
    transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease;
}

.modal-link:hover {
    transform: translateY(-2px);
}

.modal-link-demo {
    background: #14142b;
    color: white;
}

.modal-link-demo:hover {
    background: #ff8a54;
}

.modal-link-github {
    background: transparent;
    color: #14142b;
    border: 1.5px solid #14142b;
}

.modal-link-github:hover {
    background: #14142b;
    color: white;
}

/* ==========================================================================
   Photo gallery — deliberately small and quiet (smaller title, muted note,
   horizontal scroll strip instead of a big grid) so it reads as a fun
   extra rather than competing with the professional sections.
   ========================================================================== */

.u-gallery {
    width: 80%;
    max-width: 1100px;
    margin: 0 auto;
    padding-block: clamp(32px, 6vh, 64px);
}

/* A quiet, distinct panel (same card background used for exp/proj cards)
   so the gallery reads as an intentional, embedded interlude rather than
   a loose row of tiles floating on the page background. */
.gallery-panel {
    background: #fafaf9;
    border-radius: 28px;
    padding: clamp(24px, 4vw, 40px);
}

.gallery-title {
    font-family: "Hi Melody", sans-serif;
    font-weight: 400;
    /* Noticeably smaller than the main section titles (.wai-title) on
       purpose — this section is a bonus, not a headline. */
    font-size: clamp(28px, 4vw, 44px);
    color: #14142b;
    margin: 0;
}

.gallery-note {
    font-family: "Space Grotesk", sans-serif;
    font-size: 14px;
    color: #8a8a99;
    margin: 6px 0 24px;
}

.gallery-strip {
    display: flex;
    gap: 16px;
    overflow-x: auto;
    padding: 4px 4px 12px;

    scroll-snap-type: x proximity;

    /* Fade the strip's own edges — same feather technique used on the hero
       grain — so partially-cut-off tiles read as "scroll for more"
       instead of looking abruptly clipped. */
    -webkit-mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent);
    mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent);
}

.gallery-item {
    flex: 0 0 auto;
    width: 240px;
    /* 3:2 — the standard landscape photo ratio, instead of the odd
       portrait-ish box this used to be. */
    aspect-ratio: 3 / 2;
    border-radius: 14px;
    scroll-snap-align: start;
    display: block;

    /* Crops to fill the 3:2 box instead of stretching to match it. */
    object-fit: cover;
    object-position: center;

    /* No desaturation here on purpose — these are real photos and should
       stay fully saturated, unlike the grayscale motif used elsewhere. */
    box-shadow: 0 10px 24px -12px rgba(20, 20, 43, 0.3);

    cursor: url("src/small_cursor2.png"), pointer;
    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.gallery-item:hover,
.gallery-item:focus-visible {
    transform: translateY(-4px) scale(1.03);
    box-shadow: 0 16px 32px -12px rgba(20, 20, 43, 0.38);
}

.gallery-item:focus-visible {
    outline: 2px solid #ff8a54;
    outline-offset: 3px;
}

/* Prev/next buttons that page the strip — a click-based alternative to
   dragging/scrolling. Hidden on touch-sized screens, where swipe-scrolling
   the strip directly is already the natural interaction. */
.gallery-strip-wrap {
    position: relative;
}

.gallery-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 5;

    width: 40px;
    height: 40px;
    border: 0;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.9);
    box-shadow: 0 4px 12px rgba(20, 20, 43, 0.25);
    color: #14142b;
    font-size: 16px;
    line-height: 1;

    cursor: url("src/small_cursor2.png"), pointer;
    transition: background-color 0.2s ease, transform 0.2s ease;
}

.gallery-nav:hover {
    background: white;
    transform: translateY(-50%) scale(1.08);
}

.gallery-nav-prev {
    left: -6px;
}

.gallery-nav-next {
    right: -6px;
}

@media (max-width: 600px) {
    .gallery-item {
        width: 200px;
    }

    .gallery-nav {
        display: none;
    }
}

/* ==========================================================================
   Photo lightbox — opened from a .gallery-item, shows the full photo
   (object-fit: contain, not cover — the point here is to see the whole
   image, unlike the cropped thumbnails) with prev/next navigation.
   ========================================================================== */

.lightbox-overlay {
    position: fixed;
    inset: 0;
    z-index: 210;

    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;

    background: rgba(10, 10, 20, 0.85);

    opacity: 0;
    transition: opacity 0.25s ease;
}

/* Same fix as .modal-overlay: author `display: flex` above always beats
   the browser's built-in `[hidden] { display: none }`, so this override
   is required or the lightbox would sit invisible-but-clickable over the
   whole page at all times. */
.lightbox-overlay[hidden] {
    display: none;
}

.lightbox-overlay:not([hidden]) {
    opacity: 1;
}

.lightbox-image {
    max-width: min(900px, 86vw);
    max-height: 80vh;
    width: auto;
    height: auto;

    object-fit: contain;
    border-radius: 12px;
    box-shadow: 0 32px 64px -16px rgba(0, 0, 0, 0.5);
}

.lightbox-close,
.lightbox-arrow {
    position: absolute;
    border: 0;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.15);
    color: white;
    line-height: 1;

    cursor: url("src/small_cursor2.png"), pointer;
    transition: background-color 0.2s ease, transform 0.2s ease;
}

.lightbox-close:hover,
.lightbox-arrow:hover {
    background: rgba(255, 255, 255, 0.3);
}

.lightbox-close {
    top: 20px;
    right: 20px;
    width: 40px;
    height: 40px;
    font-size: 22px;
}

.lightbox-arrow {
    top: 50%;
    transform: translateY(-50%);
    width: 48px;
    height: 48px;
    font-size: 20px;
}

.lightbox-arrow:hover {
    transform: translateY(-50%) scale(1.08);
}

.lightbox-prev {
    left: 16px;
}

.lightbox-next {
    right: 16px;
}

@media (max-width: 600px) {
    .lightbox-arrow {
        width: 38px;
        height: 38px;
    }

    .lightbox-prev {
        left: 6px;
    }

    .lightbox-next {
        right: 6px;
    }
}

/* ==========================================================================
   Contact
   ========================================================================== */

.u-contact {
    width: 80%;
    max-width: 1100px;
    margin: 0 auto;
    padding-block: clamp(48px, 10vh, 100px);
    text-align: center;
}

.u-contact .wai-lines {
    justify-content: center;
}

.contact-text {
    font-family: "Space Grotesk", sans-serif;
    font-size: clamp(16px, 1.6vw, 20px);
    color: #4a4a5a;
    max-width: 50ch;
    margin: 0 auto 32px;
}

.contact-actions {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 20px;
}

.contact-cta {
    font-family: "Space Grotesk", sans-serif;
    font-weight: 600;
    font-size: 17px;
    color: white;
    text-decoration: none;

    background: #14142b;
    padding: 16px 36px;
    border-radius: 999px;

    cursor: url("src/small_cursor2.png"), pointer;
    transition: background-color 0.25s ease, transform 0.25s ease;
}

.contact-cta:hover {
    background: #ff8a54;
    transform: translateY(-3px);
}

.contact-socials {
    list-style: none;
    display: flex;
    gap: 24px;
    margin: 0;
    padding: 0;
}

.contact-socials a {
    font-family: "Space Grotesk", sans-serif;
    font-size: 15px;
    color: #14142b;
    text-decoration: none;
    cursor: url("src/small_cursor2.png"), pointer;

    transition: color 0.2s ease;
}

.contact-socials a:hover {
    color: #ff8a54;
}

@media (max-width: 600px) {
    .u-skills,
    .u-experience,
    .u-projects,
    .u-gallery,
    .u-contact,
    .u-footer {
        width: 90%;
    }

    .exp-card {
        padding: 22px 20px;
    }

    .footer-row {
        flex-direction: column;
        align-items: flex-start;
    }

    .modal-box {
        padding: 24px;
    }
}