/* /Components/BackToTop.razor.rz.scp.css */
.back-to-top[b-5dah9w7kof] {
    position: fixed;
    right: 1rem;
    bottom: 1rem;
    z-index: 90;
    width: 42px;
    height: 42px;
    border-radius: 50%;
    border: 1px solid rgba(192, 132, 252, 0.35);
    background: rgba(192, 132, 252, 0.12);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    color: var(--color-light-purple);
    cursor: pointer;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;

    /* Hidden by default. Kept in the DOM and faded rather than removed, so
       the transition has something to animate. visibility (not just opacity)
       is what stops it being clickable and reachable by keyboard while it is
       invisible - opacity: 0 alone would leave an invisible button that can
       still be tabbed to and pressed. */
    opacity: 0;
    visibility: hidden;
    transform: translateY(10px);
    transition: opacity 0.25s ease, transform 0.25s ease, visibility 0.25s,
                background 0.2s ease, border-color 0.2s ease;
}

.back-to-top.is-visible[b-5dah9w7kof] {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

.back-to-top:hover[b-5dah9w7kof] {
    background: rgba(192, 132, 252, 0.25);
    border-color: rgba(45, 212, 191, 0.5);
    color: var(--color-turquoise);
}

.back-to-top:focus-visible[b-5dah9w7kof] {
    outline: 2px solid var(--color-turquoise);
    outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
    .back-to-top[b-5dah9w7kof] {
        transition: opacity 0.2s ease, visibility 0.2s;
        transform: none;
    }

    .back-to-top.is-visible[b-5dah9w7kof] {
        transform: none;
    }
}
/* /Components/Carousel.razor.rz.scp.css */
.carousel[b-qo99x09uua] {
    position: relative;
    border-radius: 10px;
    overflow: hidden;
    border: 1px solid rgba(192, 132, 252, 0.2);
    background: rgba(7, 0, 31, 0.4);
}

.carousel:focus-visible[b-qo99x09uua] {
    outline: 2px solid var(--color-turquoise);
    outline-offset: 2px;
}

/* The viewport is the fixed-size window; anything outside it is clipped,
   which is what lets the long row of slides move behind it.
   --carousel-aspect is supplied per-instance by the AspectRatio parameter,
   with a fallback so the component still works if nobody sets it. */
.carousel-viewport[b-qo99x09uua] {
    overflow: hidden;
    aspect-ratio: var(--carousel-aspect, 16 / 10);
}

.carousel-track[b-qo99x09uua] {
    display: flex;
    height: 100%;
    transition: transform 0.35s ease;
}

/* flex: 0 0 100% means "don't grow, don't shrink, always exactly the full
   width of the viewport". Without the two zeros, flexbox would squeeze every
   slide in side by side to fit instead of keeping them one-per-screen. */
.carousel-slide[b-qo99x09uua] {
    flex: 0 0 100%;
    height: 100%;
    min-width: 0;
}

/* ---- Coverflow variant ---- */

/* perspective on the ancestor is what makes rotateY read as depth rather
   than a flat squash. All the cards share this one vanishing point, which
   is why they look wrapped around a single drum. */
.carousel-coverflow[b-qo99x09uua] {
    perspective: 1200px;
    border: none;
    background: none;
    overflow: visible;
}

.carousel-stage[b-qo99x09uua] {
    position: relative;
    height: var(--carousel-card-height, 340px);
    display: flex;
    align-items: center;
    justify-content: center;
    transform-style: preserve-3d;
}

.carousel-card[b-qo99x09uua] {
    position: absolute;
    width: var(--carousel-card-width, 380px);
    height: var(--carousel-card-height, 340px);
    border-radius: 12px;
    border: 1px solid rgba(192, 132, 252, 0.25);
    /* Angled cards get a near-solid fill instead of backdrop-filter: several
       browsers drop backdrop-filter entirely inside a preserve-3d context,
       and it is expensive to composite on rotated layers. */
    background: rgba(24, 10, 44, 0.94);
    overflow: hidden;
    transition: transform 0.45s ease, opacity 0.45s ease;
}

/* The dimming scrim. Sitting on top of the card's own content means text and
   background darken together, so the card reads as further away rather than
   as see-through. --card-dim is set per-card by the component. */
.carousel-card[b-qo99x09uua]::after {
    content: "";
    position: absolute;
    inset: 0;
    background: rgba(7, 0, 31, var(--card-dim, 0));
    pointer-events: none;
    transition: background 0.45s ease;
}

/* Only the front card - the one actually being read - pays for real glass.
   Opaque enough that body text stays legible over the particle background,
   which is the whole trade-off with glassmorphism on busy backdrops. */
.carousel-card-active[b-qo99x09uua] {
    background: rgba(28, 13, 52, 0.88);
    backdrop-filter: blur(16px) saturate(1.2);
    -webkit-backdrop-filter: blur(16px) saturate(1.2);
    border-color: rgba(192, 132, 252, 0.4);
    box-shadow: 0 12px 40px rgba(7, 0, 31, 0.5);
}

.carousel-arrow[b-qo99x09uua] {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: 1px solid rgba(192, 132, 252, 0.35);
    background: rgba(7, 0, 31, 0.55);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    color: var(--color-text);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    opacity: 0;
    transition: opacity 0.2s ease, background 0.2s ease;
}

/* Controls stay hidden until hover or keyboard focus lands inside, so the
   artwork is unobstructed at rest without the controls ever being
   unreachable for keyboard users. */
.carousel:hover .carousel-arrow[b-qo99x09uua],
.carousel:focus-within .carousel-arrow[b-qo99x09uua] {
    opacity: 1;
}

.carousel-arrow:hover:not(:disabled)[b-qo99x09uua] {
    background: rgba(124, 58, 237, 0.7);
}

.carousel-arrow:disabled[b-qo99x09uua] {
    cursor: default;
    opacity: 0.25;
}

.carousel-arrow:focus-visible[b-qo99x09uua] {
    opacity: 1;
    outline: 2px solid var(--color-turquoise);
    outline-offset: 2px;
}

.carousel-arrow-prev[b-qo99x09uua] {
    left: 0.6rem;
}

.carousel-arrow-next[b-qo99x09uua] {
    right: 0.6rem;
}

.carousel-dots[b-qo99x09uua] {
    position: absolute;
    bottom: 0.6rem;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 0.4rem;
    padding: 0.35rem 0.7rem;
    border-radius: 999px;
    background: rgba(7, 0, 31, 0.5);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

.carousel-dot[b-qo99x09uua] {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    border: none;
    padding: 0;
    cursor: pointer;
    background: rgba(229, 225, 240, 0.35);
    transition: background 0.2s ease, transform 0.2s ease;
}

.carousel-dot:hover[b-qo99x09uua] {
    background: rgba(229, 225, 240, 0.6);
}

.carousel-dot-active[b-qo99x09uua] {
    background: var(--color-turquoise);
    transform: scale(1.25);
}

.carousel-dot:focus-visible[b-qo99x09uua] {
    outline: 2px solid var(--color-turquoise);
    outline-offset: 2px;
}

/* Visually hidden but still announced. The aria-live region reads
   "Slide 2 of 3" as the user moves, so the carousel isn't silent for
   anyone who isn't looking at it. */
.carousel-status[b-qo99x09uua] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Honour an OS-level request for less motion: slides cut instantly instead
   of sliding. Identical functionality, no movement. */
@media (prefers-reduced-motion: reduce) {
    .carousel-track[b-qo99x09uua],
    .carousel-card[b-qo99x09uua],
    .carousel-dot[b-qo99x09uua],
    .carousel-arrow[b-qo99x09uua] {
        transition: none;
    }
}

/* Below the width where three cards can sit side by side, the coverflow
   collapses to a single upright card - the depth effect needs room either
   side to read as depth at all. */
@media (max-width: 720px) {
    .carousel-coverflow[b-qo99x09uua] {
        perspective: none;
    }

    .carousel-stage[b-qo99x09uua] {
        transform-style: flat;
    }

    .carousel-card[b-qo99x09uua] {
        width: min(var(--carousel-card-width, 380px), 100%);
    }
}

/* Touch devices have no hover state, so permanently-hidden arrows would be
   invisible and undiscoverable. */
@media (hover: none) {
    .carousel-arrow[b-qo99x09uua] {
        opacity: 1;
    }
}
/* /Components/ContactSection.razor.rz.scp.css */
.contact-section[b-ynis0vulhd] {
    max-width: 800px;
    margin: 0 auto;
    padding: 4rem 1.5rem 6rem;
    text-align: center;
}

/* auto-fit lets the row become a column on narrow screens without a media
   query: as many 220px-or-wider cards as fit, then it wraps. */
.contact-links[b-ynis0vulhd] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 1rem;
    text-align: left;
}

.contact-card[b-ynis0vulhd] {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    padding: 1rem 1.15rem;
    border-radius: 12px;
    border: 1px solid rgba(192, 132, 252, 0.25);
    background: rgba(229, 225, 240, 0.06);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    text-decoration: none;
    color: var(--color-text);
    transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.contact-card:hover[b-ynis0vulhd] {
    transform: translateY(-3px);
    border-color: rgba(45, 212, 191, 0.5);
    background: rgba(45, 212, 191, 0.08);
}

.contact-card:focus-visible[b-ynis0vulhd] {
    outline: 2px solid var(--color-turquoise);
    outline-offset: 3px;
}

.contact-icon[b-ynis0vulhd] {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    flex-shrink: 0;
    border-radius: 50%;
    background: rgba(192, 132, 252, 0.12);
    color: var(--color-light-purple);
    transition: color 0.2s ease;
}

.contact-icon svg[b-ynis0vulhd] {
    width: 19px;
    height: 19px;
}

.contact-card:hover .contact-icon[b-ynis0vulhd] {
    color: var(--color-turquoise);
}

.contact-text[b-ynis0vulhd] {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.contact-label[b-ynis0vulhd] {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: rgba(229, 225, 240, 0.55);
}

/* Long email addresses would otherwise push the card wider than its grid
   column; ellipsis keeps every card the same size. */
.contact-value[b-ynis0vulhd] {
    font-size: 0.9rem;
    color: var(--color-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.contact-location[b-ynis0vulhd] {
    margin: 2.5rem 0 0;
    font-size: 0.85rem;
    color: rgba(229, 225, 240, 0.5);
}

@media (prefers-reduced-motion: reduce) {
    .contact-card[b-ynis0vulhd] {
        transition: none;
    }

    .contact-card:hover[b-ynis0vulhd] {
        transform: none;
    }
}

@media (max-width: 600px) {
    .contact-section[b-ynis0vulhd] {
        padding: 3rem 1rem 4rem;
    }
}
/* /Components/Cv.razor.rz.scp.css */
.cv-section[b-p3f491ci68] {
    max-width: 1100px;
    margin: 0 auto;
    padding: 4rem 1.5rem;
}

.timeline[b-p3f491ci68] {
    display: grid;
    grid-template-columns: 220px 24px 1fr;
    row-gap: 2rem;
    position: relative;
}

    .timeline[b-p3f491ci68]::before {
        content: "";
        position: absolute;
        left: 232px;
        top: 0;
        bottom: 0;
        width: 2px;
        background: linear-gradient(to bottom, var(--color-turquoise), var(--color-purple));
    }

.timeline-brand[b-p3f491ci68] {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    align-self: start;
    padding-right: 1.5rem;
    gap: 0.75rem;
}

.stack-chips[b-p3f491ci68] {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.4rem;
}

.stack-chip[b-p3f491ci68] {
    background: rgba(192, 132, 252, 0.12);
    border: 1px solid rgba(192, 132, 252, 0.3);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
    color: var(--color-light-purple);
    font-size: 0.7rem;
    white-space: nowrap;
}
    .timeline-brand img[b-p3f491ci68] {
        width: 110px;
        height: 110px;
        object-fit: cover;
        border-radius: 50%;
    }

.timeline-dot[b-p3f491ci68] {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--color-turquoise), var(--color-purple));
    box-shadow: 0 0 0 4px rgba(192, 132, 252, 0.15);
    justify-self: center;
    align-self: center;
    /* The connecting line is drawn by .timeline::before, which is absolutely
       positioned and therefore paints above ordinary in-flow grid items - so
       the line was running straight over the dots. Giving the dots their own
       position and z-index lifts them into the same painting layer, above it. */
    position: relative;
    z-index: 1;
}

.timeline-card[b-p3f491ci68] {
    background: rgba(229, 225, 240, 0.06);
    border: 1px solid rgba(192, 132, 252, 0.25);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-radius: 12px;
    padding: 1.25rem 1.5rem;
    align-self:start;
}

.timeline-date[b-p3f491ci68] {
    display: inline-block;
    color: var(--color-turquoise);
    font-size: 0.8rem;
    font-weight: 600;
    margin-bottom: 0.4rem;
}

.timeline-role[b-p3f491ci68] {
    font-size: 1.1rem;
    margin: 0 0 0.15rem;
    color: var(--color-text);
}

.timeline-org[b-p3f491ci68] {
    color: var(--color-light-purple);
    font-size: 0.9rem;
    margin: 0 0 0.6rem;
}

.timeline-desc[b-p3f491ci68] {
    color: var(--color-text);
    opacity: 0.85;
    font-size: 0.9rem;
    line-height: 1.5;
    margin: 0;
}

.brand-badge[b-p3f491ci68] {
    background: rgba(45, 212, 191, 0.12);
    border: 1px solid rgba(45, 212, 191, 0.45);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    padding: 0.25rem 0.7rem;
    border-radius: 999px;
    color: var(--color-turquoise);
    font-size: 0.72rem;
    font-weight: 600;
    text-align: center;
}

.timeline-highlights[b-p3f491ci68] {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
}

    .timeline-highlights li[b-p3f491ci68] {
        position: relative;
        padding-left: 1.1rem;
        color: rgba(229, 225, 240, 0.85);
        font-size: 0.9rem;
        line-height: 1.55;
    }

        .timeline-highlights li[b-p3f491ci68]::before {
            content: "";
            position: absolute;
            left: 0;
            top: 0.55em;
            width: 6px;
            height: 6px;
            border-radius: 50%;
            background: var(--color-turquoise);
        }

    .timeline-highlights strong[b-p3f491ci68] {
        color: var(--color-light-purple);
        font-weight: 600;
    }

/* ---- Responsive ---- */

/* Tablet: keep the three-column timeline, just tighten it. The logo column
   and the line move together - the line must stay centred on the dot
   column, so its left value is (column width + half the dot column). */
@media (max-width: 900px) {
    .timeline[b-p3f491ci68] {
        grid-template-columns: 150px 20px 1fr;
    }

    .timeline[b-p3f491ci68]::before {
        left: 160px;
    }

    .timeline-brand[b-p3f491ci68] {
        padding-right: 1rem;
    }

    .timeline-brand img[b-p3f491ci68] {
        width: 84px;
        height: 84px;
    }

    .stack-chip[b-p3f491ci68] {
        font-size: 0.64rem;
        padding: 0.15rem 0.5rem;
    }
}

/* Phone: three columns cannot survive here. Rather than crushing the logo
   rail into an unreadable sliver, the timeline stops being a timeline and
   becomes a stacked list - each employer's logo and chips act as a header
   for the cards beneath it. Setting display:block also neutralises the
   inline "grid-row: span N" on the brand element, since grid-row has no
   meaning outside a grid - so the shared-logo trick unwinds by itself. */
@media (max-width: 640px) {
    .cv-section[b-p3f491ci68] {
        padding: 3rem 1rem;
    }

    .timeline[b-p3f491ci68] {
        display: block;
    }

    .timeline[b-p3f491ci68]::before,
    .timeline-dot[b-p3f491ci68] {
        display: none;
    }

    .timeline-brand[b-p3f491ci68] {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: center;
        justify-content: flex-start;
        padding-right: 0;
        gap: 0.5rem;
        margin: 2.5rem 0 1rem;
    }

    .timeline-brand:first-child[b-p3f491ci68] {
        margin-top: 0;
    }

    .timeline-brand img[b-p3f491ci68] {
        width: 56px;
        height: 56px;
    }

    /* flex-basis 100% forces the chips onto their own line beneath the logo
       and badge, instead of squeezing in beside a 56px circle. */
    .stack-chips[b-p3f491ci68] {
        flex: 1 1 100%;
        justify-content: flex-start;
    }

    .timeline-card[b-p3f491ci68] {
        padding: 1rem 1.1rem;
        margin-bottom: 1rem;
    }

    .timeline-role[b-p3f491ci68] {
        font-size: 1rem;
    }

    .timeline-highlights li[b-p3f491ci68],
    .timeline-desc[b-p3f491ci68] {
        font-size: 0.85rem;
    }
}
/* /Components/ImageCarousel.razor.rz.scp.css */
.image-slide[b-3nwd9doq47] {
    position: relative;
    margin: 0;
    width: 100%;
    height: 100%;
}

.image-slide img[b-3nwd9doq47] {
    width: 100%;
    height: 100%;
    /* contain, not cover. cover fills the frame by cropping from the centre,
       which on a tall screenshot throws away the top and bottom - including
       whatever you actually wanted people to read. contain fits the whole
       image inside the frame and letterboxes the difference, so nothing is
       lost and the image sits centred. */
    object-fit: contain;
    object-position: center;
    display: block;
    /* Fills the letterbox bars so they read as part of the card rather than
       as a gap. */
    background: rgba(7, 0, 31, 0.6);
}

.image-slide-caption[b-3nwd9doq47] {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: 2rem 1rem 0.75rem;
    font-size: 0.8rem;
    color: var(--color-text);
    /* Gradient rather than a solid bar, so the caption stays readable over
       a busy screenshot without hiding the image behind a hard edge. */
    background: linear-gradient(to top, rgba(7, 0, 31, 0.85), rgba(7, 0, 31, 0));
}
/* /Components/ProjectDialog.razor.rz.scp.css */
.project-dialog[b-lrf733h82v] {
    width: min(680px, calc(100vw - 2rem));
    max-height: min(88vh, 900px);
    padding: 0;
    border: 1px solid rgba(192, 132, 252, 0.35);
    border-radius: 16px;
    background: rgba(24, 10, 44, 0.96);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    color: var(--color-text);
    box-shadow: 0 24px 70px rgba(0, 0, 0, 0.6);
    overflow: hidden;
}

/* ::backdrop styles the layer the browser paints behind a modal dialog.
   It only exists for dialogs opened with showModal() - a dialog opened with
   show() has no backdrop at all. */
.project-dialog[b-lrf733h82v]::backdrop {
    background: rgba(7, 0, 31, 0.72);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}

.dialog-inner[b-lrf733h82v] {
    max-height: inherit;
    overflow-y: auto;
    /* Stops a scroll gesture inside the dialog from carrying on and scrolling
       the page behind it once the dialog hits its end - the "scroll chaining"
       effect that makes modals feel broken on touch devices. */
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
}

.dialog-close[b-lrf733h82v] {
    position: absolute;
    top: 0.75rem;
    right: 0.75rem;
    z-index: 2;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    border: 1px solid rgba(192, 132, 252, 0.35);
    background: rgba(7, 0, 31, 0.7);
    color: var(--color-text);
    font-size: 1.3rem;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    transition: background 0.2s ease;
}

.dialog-close:hover[b-lrf733h82v] {
    background: rgba(124, 58, 237, 0.7);
}

.dialog-close:focus-visible[b-lrf733h82v] {
    outline: 2px solid var(--color-turquoise);
    outline-offset: 2px;
}

.dialog-media[b-lrf733h82v] {
    height: 260px;
}

.dialog-body[b-lrf733h82v] {
    padding: 1.6rem 1.8rem 1.8rem;
}

.dialog-title[b-lrf733h82v] {
    font-size: 1.4rem;
    margin: 0 0 0.25rem;
    color: var(--color-text);
}

.dialog-subtitle[b-lrf733h82v] {
    color: var(--color-light-purple);
    font-size: 0.9rem;
    margin: 0 0 1.1rem;
}

/* No line clamp here - the whole point of the dialog is the full text. */
.dialog-desc[b-lrf733h82v] {
    color: rgba(229, 225, 240, 0.88);
    font-size: 0.95rem;
    line-height: 1.65;
    margin: 0 0 1.4rem;
}

.dialog-stack[b-lrf733h82v] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.stack-chip[b-lrf733h82v] {
    background: rgba(192, 132, 252, 0.12);
    border: 1px solid rgba(192, 132, 252, 0.3);
    padding: 0.2rem 0.65rem;
    border-radius: 999px;
    color: var(--color-light-purple);
    font-size: 0.72rem;
    white-space: nowrap;
}

.dialog-links[b-lrf733h82v] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    margin-top: 1.6rem;
}

.dialog-link[b-lrf733h82v] {
    padding: 0.5rem 1.2rem;
    border-radius: 999px;
    font-size: 0.85rem;
    font-weight: 600;
    text-decoration: none;
    border: 1px solid rgba(192, 132, 252, 0.35);
    color: var(--color-light-purple);
    background: rgba(192, 132, 252, 0.08);
    transition: background 0.2s ease;
}

.dialog-link:hover[b-lrf733h82v] {
    background: rgba(192, 132, 252, 0.2);
}

.dialog-link:focus-visible[b-lrf733h82v] {
    outline: 2px solid var(--color-turquoise);
    outline-offset: 2px;
}

.dialog-link-primary[b-lrf733h82v] {
    border-color: rgba(45, 212, 191, 0.5);
    color: var(--color-turquoise);
    background: rgba(45, 212, 191, 0.1);
}

.dialog-link-primary:hover[b-lrf733h82v] {
    background: rgba(45, 212, 191, 0.2);
}

@media (max-width: 600px) {
    /* Full-screen on phones. A centred floating panel wastes space at this
       size and leaves the close button easy to miss.
       100dvh rather than 100vh: dvh is the *dynamic* viewport height, which
       accounts for mobile browser chrome appearing and disappearing as you
       scroll. With 100vh the bottom of the dialog sits under the browser bar
       and cannot be reached. */
    .project-dialog[b-lrf733h82v] {
        width: 100%;
        max-width: 100%;
        height: 100dvh;
        max-height: 100dvh;
        margin: 0;
        border: none;
        border-radius: 0;
    }

    .dialog-inner[b-lrf733h82v] {
        height: 100%;
        max-height: 100%;
    }

    .dialog-media[b-lrf733h82v] {
        height: 190px;
    }

    .dialog-body[b-lrf733h82v] {
        padding: 1.25rem 1.25rem 2.5rem;
    }

    .dialog-title[b-lrf733h82v] {
        font-size: 1.2rem;
    }
}
/* /Components/ProjectsSection.razor.rz.scp.css */
/* Wider than the CV section on purpose: the coverflow only reads as depth
   while the cards either side stay visible, so the section has to be wide
   enough to hold the centre card plus both neighbours.
   The maths: a neighbour is offset by 62% of the card width, so with a 540px
   card its far edge sits 335 + 270 = 605px from centre. A 1280px section
   gives 640px of half-width, which clears it. Grow the cards much beyond
   this and the neighbours start getting clipped. */
.projects-section[b-rbjxgrh2g4] {
    max-width: 1280px;
    margin: 0 auto;
    padding: 4rem 1.5rem;
}

/* Controls sit centred beneath the stage. In a coverflow the eye settles on
   the middle card, so centred controls land where attention already is -
   and they stay clear of the rotated side cards, which arrows overlaid on
   the stage itself would collide with. */
.projects-nav[b-rbjxgrh2g4] {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    margin-top: 2rem;
}

/* Tabular figures stop the counter jittering as the digits change - without
   it, "01" and "04" can be different widths and the arrows shuffle sideways. */
.projects-counter[b-rbjxgrh2g4] {
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.08em;
    font-size: 0.85rem;
}

.projects-counter-current[b-rbjxgrh2g4] {
    color: var(--color-turquoise);
    font-weight: 600;
}

.projects-counter-total[b-rbjxgrh2g4] {
    color: rgba(229, 225, 240, 0.5);
}

.projects-arrow[b-rbjxgrh2g4] {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    border: 1px solid rgba(192, 132, 252, 0.35);
    background: rgba(192, 132, 252, 0.08);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    color: var(--color-text);
    font-size: 1.4rem;
    line-height: 1;
    padding: 0;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.2s ease, border-color 0.2s ease;
}

.projects-arrow:hover[b-rbjxgrh2g4] {
    background: rgba(192, 132, 252, 0.2);
    border-color: rgba(45, 212, 191, 0.5);
}

.projects-arrow:focus-visible[b-rbjxgrh2g4] {
    outline: 2px solid var(--color-turquoise);
    outline-offset: 2px;
}

/* The carousel card itself supplies the glass surface and the border, so the
   card content here is purely layout - no background of its own. */
.project-card[b-rbjxgrh2g4] {
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow: hidden;
}

.project-media[b-rbjxgrh2g4] {
    flex: 0 0 auto;
    height: 240px;
}

.project-content[b-rbjxgrh2g4] {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    flex: 1 1 auto;
    min-height: 0;
    padding: 1.25rem 1.35rem;
}

.project-title[b-rbjxgrh2g4] {
    font-size: 1.05rem;
    margin: 0 0 0.2rem;
    color: var(--color-text);
}

.project-subtitle[b-rbjxgrh2g4] {
    color: var(--color-light-purple);
    font-size: 0.8rem;
    margin: 0 0 0.7rem;
}

/* line-clamp truncates to a fixed number of lines with an ellipsis, which
   keeps every card the same height regardless of how long the description
   is. The full text still lives in the markup for screen readers. */
.project-desc[b-rbjxgrh2g4] {
    color: rgba(229, 225, 240, 0.88);
    font-size: 0.875rem;
    line-height: 1.55;
    margin: 0 0 1rem;
    display: -webkit-box;
    -webkit-line-clamp: 7;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.project-stack[b-rbjxgrh2g4] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}

.stack-chip[b-rbjxgrh2g4] {
    background: rgba(192, 132, 252, 0.12);
    border: 1px solid rgba(192, 132, 252, 0.3);
    padding: 0.15rem 0.55rem;
    border-radius: 999px;
    color: var(--color-light-purple);
    font-size: 0.68rem;
    white-space: nowrap;
}

.project-links[b-rbjxgrh2g4] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 1rem;
}

.project-link[b-rbjxgrh2g4] {
    padding: 0.4rem 1rem;
    border-radius: 999px;
    font-size: 0.8rem;
    font-weight: 600;
    text-decoration: none;
    border: 1px solid rgba(192, 132, 252, 0.35);
    color: var(--color-light-purple);
    background: rgba(192, 132, 252, 0.08);
    transition: background 0.2s ease;
}

.project-link:hover[b-rbjxgrh2g4] {
    background: rgba(192, 132, 252, 0.2);
}

/* A <button> next to two <a> elements, so it needs its font and cursor
   resetting - buttons do not inherit typography the way anchors do. */
.project-link-details[b-rbjxgrh2g4] {
    font-family: inherit;
    cursor: pointer;
}

.project-link:focus-visible[b-rbjxgrh2g4] {
    outline: 2px solid var(--color-turquoise);
    outline-offset: 2px;
}

.project-link-primary[b-rbjxgrh2g4] {
    border-color: rgba(45, 212, 191, 0.5);
    color: var(--color-turquoise);
    background: rgba(45, 212, 191, 0.1);
}

.project-link-primary:hover[b-rbjxgrh2g4] {
    background: rgba(45, 212, 191, 0.2);
}

@media (max-width: 600px) {
    .projects-section[b-rbjxgrh2g4] {
        padding: 3rem 1rem;
    }

    .projects-nav[b-rbjxgrh2g4] {
        margin-top: 1.5rem;
    }
}
/* /Components/SectionHeading.razor.rz.scp.css */
.section-heading[b-k835ohqj6t] {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.85rem;
    margin-bottom: 3rem;
    text-align: center;
}

/* The same glass pill used by the nav links and hero badge, so the section
   markers read as part of the same system rather than plain headings. */
.section-index[b-k835ohqj6t] {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    background: rgba(192, 132, 252, 0.12);
    border: 1px solid rgba(192, 132, 252, 0.3);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    padding: 0.3rem 0.95rem;
    border-radius: 999px;
    color: var(--color-light-purple);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    font-variant-numeric: tabular-nums;
}

/* Gradient text, matching the treatment on your name in the Hero - it ties
   the sections back to the top of the page. background-clip: text paints
   the gradient through the glyphs, and the transparent colour lets it
   show; without that the text would just be solid. */
.section-title[b-k835ohqj6t] {
    font-size: 2.2rem;
    font-weight: 800;
    margin: 0;
    line-height: 1.15;
    background: linear-gradient(90deg, var(--color-light-purple), var(--color-turquoise));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

/* A short accent rule under the title, echoing the timeline's gradient line. */
.section-title[b-k835ohqj6t]::after {
    content: "";
    display: block;
    width: 56px;
    height: 2px;
    margin: 0.9rem auto 0;
    border-radius: 2px;
    background: linear-gradient(90deg, var(--color-turquoise), var(--color-purple));
}

.section-subtitle[b-k835ohqj6t] {
    margin: 0;
    max-width: 34rem;
    color: rgba(229, 225, 240, 0.7);
    font-size: 0.95rem;
    line-height: 1.6;
}

@media (max-width: 600px) {
    .section-heading[b-k835ohqj6t] {
        margin-bottom: 2rem;
        gap: 0.7rem;
    }

    .section-title[b-k835ohqj6t] {
        font-size: 1.7rem;
    }

    .section-subtitle[b-k835ohqj6t] {
        font-size: 0.88rem;
    }
}
/* /Layout/MainLayout.razor.rz.scp.css */
/* This file used to contain leftover CSS from the default Blazor sidebar
   template (.sidebar, .top-row, and a ".page { flex-direction: row }"
   rule for min-width:641px screens) - that rule was silently forcing the
   nav and main content to sit side-by-side on any normal desktop width,
   which is why the layout looked broken. None of that applied to our
   markup anymore, so it's been removed entirely. */

.page[b-8qipnj36gr] {
    min-height: 100vh;
}
/* /Layout/NavMenu.razor.rz.scp.css */
.top-nav[b-net467zwfs] {
    position: fixed;
    top: 1.5rem;
    left: 1.5rem;
    z-index: 100;
    display: flex;
    align-items: center;
}

.nav-links[b-net467zwfs] {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

/* Each link is now its own individual glass chip, rather than all four
   sharing one pill-shaped background. */
/* Matches the .hero-badge "Welcome to my portfolio" pill exactly -
   same tinted purple background, border, and text color - so the nav
   chips read as the same visual language as that badge. */
/* ::deep tells Blazor's CSS isolation to reach INTO child components -
   necessary here because <NavLink> is a separate built-in component
   that renders its own <a> tag; without ::deep, this rule would be
   scoped to only elements written directly in NavMenu.razor itself,
   which the NavLink-rendered <a> doesn't count as. */
.top-nav[b-net467zwfs]  .nav-link {
    background: rgba(192, 132, 252, 0.12);
    border: 1px solid rgba(192, 132, 252, 0.3);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    padding: 0.4rem 1rem;
    border-radius: 999px;
    color: var(--color-light-purple);
    text-decoration: none;
    font-size: 0.9rem;
}

.top-nav[b-net467zwfs]  .nav-link.active {
    color: var(--color-turquoise);
    border-color: var(--color-turquoise);
    font-weight: 600;
}

.menu-toggle[b-net467zwfs] {
    display: none;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 5px;
    width: 44px;
    height: 44px;
    background: rgba(229, 225, 240, 0.08);
    border: 1px solid rgba(192, 132, 252, 0.25);
    backdrop-filter: blur(12px);
    border-radius: 50%;
    cursor: pointer;
}

.menu-toggle span[b-net467zwfs] {
    display: block;
    width: 20px;
    height: 2px;
    background-color: var(--color-text);
}

.menu-toggle:focus-visible[b-net467zwfs] {
    outline: 2px solid var(--color-turquoise);
    outline-offset: 2px;
}

@media (max-width: 768px) {
    .menu-toggle[b-net467zwfs] {
        display: flex;
    }

    .nav-links[b-net467zwfs] {
        display: none;
        position: absolute;
        top: calc(100% + 0.5rem);
        left: 0;
        flex-direction: column;
        align-items: flex-start;
        gap: 0.6rem;
    }

    .nav-links.nav-links-open[b-net467zwfs] {
        display: flex;
    }
}
