/* ==========================================================================
   style.css
   1. Tokens   2. Base   3. Header   4. Hero   5. Sections
   6. Rows (definition lists)   7. Showcase (skills strip + project cards)
   8. Contact   9. Footer   10. Reveal on load   11. Small screens
   12. Reduced motion
   ========================================================================== */


/* 1. Tokens ----------------------------------------------------------------
   Change colours, fonts and spacing here first. Everything below reads
   from these variables. */

:root {
    --paper:    #f1eee6;   /* page background */
    --ink:      #1c1b18;   /* main text */
    --ink-soft: #5f5d55;   /* secondary text, labels */
    --rule:     #cbc6b8;   /* thin lines */
    --accent:   #b83a18;   /* hover, focus */

    --stage:     #f26a1b;  /* the orange project band (same in light and dark) */
    --stage-ink: #1c1b18;  /* text on the orange band */
    --strip:     #ffffff;  /* the white skills strip */
    --strip-ink: #1c1b18;

    --font-sans: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
    --font-display: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
    --font-serif:   "Newsreader", Georgia, "Times New Roman", serif;
    --font-mono: "IBM Plex Mono", ui-monospace, "Cascadia Code", Consolas, monospace;

    --max-width: 62rem;
    --gutter:    clamp(1.25rem, 4vw, 2.5rem);
    --label-col: 11rem;    /* width of the left label column on desktop */
}

@media (prefers-color-scheme: dark) {
    :root {
        --paper:    #151513;
        --ink:      #e9e6dc;
        --ink-soft: #9b988c;
        --rule:     #34332e;
        --accent:   #ff7a4d;
    }
}


/* 2. Base ---------------------------------------------------------------- */

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

html {
    color-scheme: light dark;
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
}

body {
    margin: 0;
    overflow-x: clip;   /* never scroll sideways on phones */
    background: var(--paper);
    color: var(--ink);
    font-family: var(--font-sans);
    font-size: 1.0625rem;
    line-height: 1.6;
}

h1, h2, h3, p, dl, dd, ol, ul {
    margin: 0;
}

a {
    color: inherit;
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-decoration-color: var(--ink-soft);
    text-underline-offset: 0.2em;
}

a:hover {
    color: var(--accent);
    text-decoration-color: var(--accent);
}

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

::selection {
    background: var(--accent);
    color: var(--paper);
}

.wrap {
    max-width: var(--max-width);
    margin-inline: auto;
    padding-inline: var(--gutter);
}

/* same width as the orange project band */
.wrap--wide {
    max-width: 90rem;
}

.skip {
    position: absolute;
    left: var(--gutter);
    top: -4rem;
    padding: 0.5rem 0.75rem;
    background: var(--ink);
    color: var(--paper);
    font-family: var(--font-mono);
    font-size: 0.875rem;
}

.skip:focus {
    top: 0.75rem;
}


/* 3. Header -------------------------------------------------------------- */

.site-header {
    border-bottom: 1px solid var(--rule);
}

.site-header .wrap {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.5rem 2rem;
    padding-block: 1rem;
    font-family: var(--font-mono);
    font-size: 0.875rem;
}

.brand {
    font-weight: 500;
    text-decoration: none;
}

.site-header ul {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1.5rem;
    list-style: none;
    padding: 0;
}

.site-header ul a {
    text-decoration: none;
}

.site-header ul a:hover {
    text-decoration: underline;
}


/* 4. Hero ---------------------------------------------------------------- */

/* The hero fills the first screen: header on top, white skills strip right at
   the bottom edge. --chrome-h (header + strip height) is measured in script.js. */
.hero {
    display: flex;
    flex-direction: column;
    min-height: calc(100vh - var(--chrome-h, 9rem));
    min-height: calc(100svh - var(--chrome-h, 9rem));
    padding-block: clamp(1rem, 3svh, 3rem);
}

/* The statement takes all free space and sits in the middle of the screen;
   photo, name and the facts table stay at the bottom of the hero. */
.statement {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    width: 100%;
    max-width: 62rem;
    margin-inline: auto;
    text-align: center;
    font-family: var(--font-display);
    font-size: clamp(1.3rem, min(4.2vw, 4.3svh), 3.25rem);
    font-weight: 800;
    line-height: 1.38;
    letter-spacing: -0.02em;
    text-wrap: balance;
}

.statement p + p {
    margin-top: 0.8em;
}

/* Highlighted keywords: a marker colour swipes in as the typing reaches them. */
.kw {
    --kw: #f26a1b;
    color: #1c1b18;
    padding: 0.02em 0.2em;
    white-space: nowrap;   /* a highlighted phrase and its emoji never split across lines */
    background-image: linear-gradient(var(--kw), var(--kw));
    background-repeat: no-repeat;
    background-size: 100% 100%;
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
    transition: background-size 0.3s ease-out;
}

.kw-tech  { --kw: #f26a1b; }   /* orange  - software, CS */
.kw-skill { --kw: #f5c518; }   /* yellow  - master any skill */
.kw-ai    { --kw: #34d1c4; }   /* teal    - AI */
.kw-movie { --kw: #ff6b9a; }   /* pink    - movie lover */
.kw-game  { --kw: #a58bff; }   /* violet  - gamer */
.kw-gym   { --kw: #b6e33c; }   /* lime    - gym rat */

/* Typing effect (script.js adds .typing, then .on to each letter / keyword). */
.statement.typing .ch {
    opacity: 0;
}

.statement.typing .ch.on {
    opacity: 1;
}

.statement.typing .kw {
    background-size: 0% 100%;
}

.statement.typing .kw.on {
    background-size: 100% 100%;
}

.caret {
    display: inline-block;
    width: 0.07em;
    height: 0.95em;
    margin-inline: 0.03em;
    vertical-align: -0.1em;
    background: var(--accent);
    animation: blink 0.8s steps(1) infinite;
}

@keyframes blink {
    50% { opacity: 0; }
}

/* Everything in the hero scales with the screen HEIGHT (svh), so the whole
   first screen fits and the strip is never pushed below the fold. */
.hero .rows > div {
    padding-block: clamp(0.4rem, 1.2svh, 0.75rem);
}

.eyebrow {
    margin-top: 0.75rem;
    font-family: var(--font-mono);
    font-size: 0.875rem;
    color: var(--ink-soft);
}

/* Photo + name + title: left-aligned, right above the facts table, at about
   half the size they had when they were the headline. */
.hero-head {
    display: flex;
    align-items: center;
    gap: clamp(0.75rem, 1.5vw, 1rem);
    margin-block: clamp(1rem, 3svh, 2rem) clamp(0.75rem, 2svh, 1.25rem);
}

.pfp {
    flex: none;
    width: clamp(3rem, 16vw, 9rem);
    aspect-ratio: 1;
    object-fit: cover;
    border: 1px solid var(--rule);
}

/* The photo is a button: click it to open the full-screen viewer below. */
.pfp-button {
    flex: none;
    display: block;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: none;
    line-height: 0;
    cursor: zoom-in;
}

.pfp-button:hover .pfp {
    border-color: var(--ink-soft);
}

/* Full-screen photo viewer: blurred page behind, X in the top-right corner. */
.lightbox {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: clamp(1rem, 4vw, 3rem);
    background: rgba(10, 10, 9, 0.55);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    opacity: 0;
    transition: opacity 0.25s ease;
    cursor: zoom-out;
}

.lightbox[hidden] {
    display: none;
}

.lightbox.is-open {
    opacity: 1;
}

.lightbox img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    transform: scale(0.92);
    transition: transform 0.3s cubic-bezier(0.2, 0.7, 0.2, 1);
}

.lightbox.is-open img {
    transform: none;
}

.lightbox-close {
    position: absolute;
    top: clamp(0.75rem, 2vw, 1.5rem);
    right: clamp(0.75rem, 2vw, 1.5rem);
    display: grid;
    place-items: center;
    width: 2.75rem;
    height: 2.75rem;
    padding: 0;
    border: 1px solid rgba(255, 255, 255, 0.5);
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    cursor: pointer;
}

.lightbox-close:hover {
    background: #fff;
    color: #1c1b18;
}

.lightbox-close:focus-visible {
    outline-color: #fff;
}

/* page does not scroll behind the viewer */
html.lightbox-open {
    overflow: hidden;
}

.hero h1 {
    font-size: clamp(1.25rem, 3.5vw, 2.375rem);
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.03em;
}

.hero .eyebrow {
    margin-top: 0.4rem;
    font-size: 0.8125rem;
}


/* 5. Sections -------------------------------------------------------------
   Two columns: sticky label on the left, content on the right. */

.section {
    display: grid;
    grid-template-columns: var(--label-col) minmax(0, 1fr);
    gap: 1rem 2rem;
    padding-block: 2.5rem 3.5rem;
    border-top: 1px solid var(--rule);
}

.section-label {
    position: sticky;
    top: 1.5rem;
    align-self: start;
    font-family: var(--font-mono);
    font-size: 0.875rem;
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

/* label above the content instead of beside it */
.section--stack {
    grid-template-columns: minmax(0, 1fr);
}

.section--stack .section-label {
    position: static;
}

.prose p + p {
    margin-top: 1rem;
}

.prose {
    max-width: 38rem;
}


/* About text ---------------------------------------------------------------
   One large opening line whose words rise out of a mask, a plain body, and
   a quieter closing paragraph with a single underlined phrase. One colour
   only (the site accent). */

.prose .about-lead {
    margin-top: clamp(2.5rem, 5vw, 3.5rem);
    font-family: var(--font-display);
    font-size: clamp(1.625rem, 3.2vw, 2.375rem);
    font-weight: 700;
    line-height: 1.18;
    letter-spacing: -0.025em;
    text-wrap: balance;
}

.prose .about-body {
    margin-top: 1.25rem;
    max-width: 34rem;
}

.prose .about-body--quiet {
    color: var(--ink-soft);
}

.about-mark {
    color: var(--ink);
    padding-bottom: 0.1em;
    background: linear-gradient(var(--accent), var(--accent)) no-repeat 0 100% / 100% 2px;
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
    transition: background-size 1s cubic-bezier(0.2, 0.7, 0.2, 1) 0.9s;
}

/* script.js wraps every word of [data-words] in .w > span */
.w {
    display: inline-block;
    overflow: hidden;
    vertical-align: top;
    padding: 0 0.04em 0.14em;
    margin: 0 -0.04em -0.14em;
}

.w > span {
    display: inline-block;
}

.js .w > span {
    transform: translateY(115%);
    transition: transform 0.8s cubic-bezier(0.2, 0.7, 0.2, 1);
    transition-delay: calc(var(--i, 0) * 55ms + 200ms);
}

.js #about.is-in .w > span {
    transform: none;
}

.js .about-mark {
    background-size: 0% 2px;
}

.js #about.is-in .about-mark {
    background-size: 100% 2px;
}


/* Quote -------------------------------------------------------------------
   Plain on purpose: serif, no colour, no highlights. */

.quote {
    margin: 2.5rem 0 0;
    padding-left: clamp(1rem, 2.5vw, 1.5rem);
    border-left: 2px solid var(--ink);
}

.quote blockquote {
    margin: 0;
}

.quote blockquote p {
    font-family: var(--font-serif);
    font-size: clamp(1.25rem, 2.2vw, 1.625rem);
    font-style: italic;
    font-weight: 400;
    line-height: 1.45;
    text-wrap: pretty;
}

.quote figcaption {
    margin-top: 1rem;
    font-family: var(--font-mono);
    font-size: 0.8125rem;
    letter-spacing: 0.04em;
    color: var(--ink-soft);
}

.quote figcaption::before {
    content: "\2014\00a0";
}

.quote cite {
    font-style: normal;
}

/* 6. Rows -----------------------------------------------------------------
   Label / value pairs. Used for the hero facts. */

.rows > div {
    display: grid;
    grid-template-columns: 9.5rem minmax(0, 1fr);
    gap: 0 1rem;
    padding-block: 0.75rem;
    border-bottom: 1px solid var(--rule);
}

.rows > div:first-child {
    border-top: 1px solid var(--rule);
}

/* The next section's rule (or the footer's) closes the list. */
.rows > div:last-child {
    border-bottom: 0;
}

.rows dt {
    font-family: var(--font-mono);
    font-size: 0.875rem;
    color: var(--ink-soft);
    padding-top: 0.15rem;
}


/* 7. Showcase --------------------------------------------------------------
   Full-width block: white skills strip (scrolling) above a big orange
   stage with the project cards. */

.band-label {
    margin: 0;
    font-family: var(--font-sans);
    font-size: clamp(1.125rem, 2.4vw, 1.625rem);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    line-height: 1;
}

/* Skills strip */

.ticker {
    display: flex;
    align-items: stretch;
    background: var(--strip);
    color: var(--strip-ink);
}

.ticker .band-label {
    flex: none;
    display: flex;
    align-items: center;
    padding-inline: var(--gutter) clamp(1rem, 2.5vw, 2rem);
    background: var(--strip-ink);
    color: var(--strip);
}

.marquee {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    /* skills fade in at one edge and out at the other */
    -webkit-mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
    mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
}

.marquee-track {
    display: flex;
    width: max-content;
    margin: 0;
    padding: 0;
    list-style: none;
}

.marquee.is-running .marquee-track {
    animation: scroll 55s linear infinite;
}

.marquee:hover .marquee-track {
    animation-play-state: paused;
}

.skill {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: clamp(1rem, 2vw, 1.4rem) clamp(1.25rem, 2.5vw, 2rem);
    white-space: nowrap;
    font-family: var(--font-mono);
    font-size: clamp(0.9375rem, 1.6vw, 1.1875rem);
    font-weight: 500;
}

.skill img,
.skill svg {
    flex: none;
    width: clamp(1.75rem, 3vw, 2.375rem);
    height: clamp(1.75rem, 3vw, 2.375rem);
}

/* Skills pop in one after another once the strip is revealed. */
.ticker.is-in .skill {
    animation: skill-in 0.6s cubic-bezier(0.2, 0.7, 0.2, 1) both;
    animation-delay: calc(var(--d, 0ms) + 500ms + var(--i, 0) * 70ms);
}

@keyframes skill-in {
    from { opacity: 0; transform: translateY(0.6rem) scale(0.9); }
    to   { opacity: 1; transform: none; }
}

/* The track holds two identical copies, so -50% is exactly one loop. */
@keyframes scroll {
    to { transform: translateX(-50%); }
}

/* Orange stage with project cards */

.stage {
    padding-block: clamp(1.5rem, 3.5vw, 3rem);
    background: var(--stage);
    color: var(--stage-ink);
}

.stage .wrap {
    max-width: 90rem;
}

.cards {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: clamp(1rem, 2vw, 1.75rem);
    margin: clamp(1rem, 2vw, 1.75rem) 0 0;
    padding: 0;
    list-style: none;
    counter-reset: card;
}

.card {
    position: relative;   /* the title link stretches over the whole card */
    display: flex;
    flex-direction: column;
    min-height: clamp(20rem, 32vw, 29rem);
    padding: clamp(1.5rem, 2.5vw, 2.25rem);
    background: var(--paper);
    color: var(--ink);
    border: 1px solid var(--stage-ink);
    counter-increment: card;
    transition: background-color 0.2s, color 0.2s;
}

.card::before {
    content: counter(card, decimal-leading-zero);
    margin-bottom: 1.75rem;
    font-family: var(--font-mono);
    font-size: 0.875rem;
    color: var(--ink-soft);
}

.card h3 {
    margin-bottom: 0.75rem;
    font-size: clamp(1.5rem, 2.4vw, 2rem);
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: -0.02em;
}

.card p {
    font-size: clamp(1rem, 1.3vw, 1.125rem);
}

.card a,
.card a:hover {
    color: inherit;
    text-decoration: none;
}

.card h3 a::after {
    content: "";
    position: absolute;
    inset: 0;
}

.card .meta {
    margin-top: auto;
    padding-top: 1.5rem;
    font-family: var(--font-mono);
    font-size: 0.8125rem;
    color: var(--ink-soft);
}

/* Hover only on devices that really hover, so a tap on a phone does not
   leave the card stuck inverted. */
@media (hover: hover) {
    .card:hover {
        background: var(--ink);
        color: var(--paper);
    }

    .card:hover::before,
    .card:hover .meta {
        color: inherit;
        opacity: 0.7;
    }
}

.card:has(a:focus-visible) {
    outline: 3px solid var(--stage-ink);
    outline-offset: 3px;
}

.card a:focus-visible {
    outline: 0;
}


/* 8. Contact --------------------------------------------------------------
   One box split into three cells. Icons are black on light, white on dark. */

.contact-box {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    border: 1px solid var(--ink);
}

.contact-cell {
    padding: clamp(1.25rem, 2vw, 1.5rem);
}

.contact-cell + .contact-cell {
    border-left: 1px solid var(--ink);
}

.cell-title {
    margin-bottom: 1.25rem;
    font-family: var(--font-mono);
    font-size: 0.8125rem;
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

.contact-cell ul {
    display: grid;
    gap: 1rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.contact-cell a {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    font-size: 1rem;
    text-decoration: none;
    overflow-wrap: anywhere;
}

.contact-cell a:hover span {
    color: var(--accent);
    text-decoration: underline;
}

.contact-cell img,
.contact-cell svg {
    flex: none;
    width: 1.75rem;
    height: 1.75rem;
}

@media (prefers-color-scheme: dark) {
    .icon-mono {
        filter: invert(1);
    }
}


/* 9. Footer --------------------------------------------------------------- */

.site-footer {
    border-top: 1px solid var(--rule);
    font-family: var(--font-mono);
    font-size: 0.8125rem;
    color: var(--ink-soft);
}

.site-footer .wrap {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 0.25rem 2rem;
    padding-block: 1.5rem;
}


/* 10. Reveal on load ------------------------------------------------------
   Anything with data-reveal starts hidden (only when JS runs) and gets
   .is-in from script.js. The photo goes first; everything else waits for it.
   --d is the delay, set by script.js. */

.js [data-reveal] {
    opacity: 0;
    transform: translateY(1.5rem);
}

.js [data-reveal].is-in {
    animation: rise 0.8s cubic-bezier(0.2, 0.7, 0.2, 1) both;
    animation-delay: var(--d, 0ms);
}

.js [data-reveal="photo"] {
    transform: scale(0.9);
}

.js [data-reveal="photo"].is-in {
    animation-name: pop;
    animation-duration: 1s;
}

/* fade only, no movement (used where the content animates itself) */
.js [data-reveal="fade"] {
    transform: none;
}

.js [data-reveal="fade"].is-in {
    animation-name: fade-in;
}

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

/* The strip is not hidden itself (an element clipped to nothing never counts
   as visible). A page-coloured cover sits on it and wipes away left to right. */
.js [data-reveal="strip"] {
    position: relative;
    overflow: hidden;
    opacity: 1;
    transform: none;
}

.js [data-reveal="strip"]::after {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--paper);
    z-index: 1;
}

.js [data-reveal="strip"].is-in {
    animation: none;
}

.js [data-reveal="strip"].is-in::after {
    animation: strip-wipe 1s cubic-bezier(0.6, 0, 0.2, 1) both;
    animation-delay: var(--d, 0ms);
}

@keyframes rise {
    from { opacity: 0; transform: translateY(1.5rem); }
    to   { opacity: 1; transform: none; }
}

@keyframes pop {
    from { opacity: 0; transform: scale(0.9); }
    to   { opacity: 1; transform: none; }
}

@keyframes strip-wipe {
    from { transform: translateX(0); }
    to   { transform: translateX(100%); }
}


/* 11. Small screens --------------------------------------------------------- */

@media (max-width: 52rem) {
    .cards {
        grid-template-columns: minmax(0, 1fr);
    }

    .card {
        min-height: 0;
    }

    .contact-box {
        grid-template-columns: minmax(0, 1fr);
    }

    .contact-cell + .contact-cell {
        border-left: 0;
        border-top: 1px solid var(--ink);
    }
}

@media (max-width: 40rem) {
    /* bigger tap targets in the nav and contact links */
    .site-header ul {
        gap: 0 1.25rem;
    }

    .site-header ul a {
        display: inline-block;
        padding-block: 0.4rem;
    }

    .contact-cell a {
        padding-block: 0.25rem;
    }

    .section {
        grid-template-columns: minmax(0, 1fr);
        padding-block: 2rem 2.5rem;
    }

    .section-label {
        position: static;
    }

    .rows > div {
        grid-template-columns: minmax(0, 1fr);
        gap: 0.1rem;
    }

}


/* 12. Reduced motion ------------------------------------------------------
   No scrolling, fades or reveals: everything is simply there, and the
   skills wrap into a static list. */

@media (prefers-reduced-motion: reduce) {
    .js [data-reveal],
    .js [data-reveal="photo"],
    .js [data-reveal="strip"],
    .js [data-reveal].is-in {
        opacity: 1;
        transform: none;
        animation: none;
    }

    .js [data-reveal="strip"]::after {
        display: none;
    }

    .ticker.is-in .skill,
    .marquee.is-running .marquee-track {
        animation: none;
    }

    .marquee {
        -webkit-mask-image: none;
        mask-image: none;
    }

    .marquee-track {
        flex-wrap: wrap;
        width: auto;
    }

    .skill[aria-hidden="true"] {
        display: none;
    }

    .lightbox,
    .lightbox img {
        transition: none;
    }

    .js .w > span,
    .js #about .w > span {
        transform: none;
        transition: none;
    }

    .js .about-mark,
    .js #about .about-mark {
        background-size: 100% 2px;
        transition: none;
    }
}


/* Round photo: up to 300px on wide screens, shrinks on phones so the name fits. */
.pfp {
    width: clamp(3rem, min(15vw, 13svh), 150px);
    height: auto;
    border-radius: 50%;
}
