/* I define reusable color values here as custom properties (CSS variables). Anywhere else in this file I can write var(--bg) instead of retyping #050506 — and if I ever want to retheme the whole site, I only change the value once, here, instead of hunting through every rule that uses it */
:root {

    --bg: #050506;
    --text: #EDEAE3;
    --dim: #6B7280;
    --accent: #214b53;
    --panel: #0c0d10;
    --border: rgba(255, 255, 255, 0.08);
    --accent-2: #214b53;

}

/* I apply box-sizing:border-box to every single element on the page (the * selector matches everything). This is the box-model behavior where width/height include padding and border instead of adding on top of them — without this, a lot of my clamp()-based spacing later on would make elements grow past the size I actually set */
* {

    box-sizing: border-box;

}

/* I turn on smooth scrolling site-wide — when a link jumps to an anchor on the same page, the browser animates the scroll instead of snapping instantly */
html {

    scroll-behavior: smooth;

}

/* I zero out the default margin browsers put on <html> and <body>, and make sure body can grow to fill the full page height */
html,
body {

    margin: 0;
    min-height: 100%;

}

/* my base styles for the whole page */
body {

    /* these pull straight from the variables above, instead of hardcoding the colors again */
    background: var(--bg);
    color: var(--text);

    /* my font stack: the browser tries IBM Plex Sans first (loaded from Google Fonts in my <head>); system-ui falls back to whatever clean default font the visitor's OS provides if that fails to load; sans-serif is the final generic fallback every browser understands */
    font-family: 'IBM Plex Sans', system-ui, sans-serif;

    /* a browser-specific hint (the -webkit- prefix means it's only recognized by Chrome/Safari/Edge) that smooths text edges a bit more, closer to how the design actually looks */
    -webkit-font-smoothing: antialiased;

    /* I reserve empty space at the very bottom of the page so my fixed tech-stack ticker (which floats on top of everything, position:fixed) never covers the real content underneath it */
    padding-bottom: 6.5rem;

}

/* browsers give <button> elements their own default font and color instead of inheriting from the page — this line tells buttons to just use whatever font/color their surrounding text has, so they blend in instead of looking like a native OS button */
button {

    font: inherit;
    color: inherit;

}

/* a utility class I'll reuse on things like project titles that need to be clickable but shouldn't look like a button or a link. "all: unset" strips away every single default style a browser would normally apply to whatever element this is on (color, font, borders, cursor, everything) — then I add cursor:pointer back on its own, since unset would have removed that too, and I still want it obvious this is clickable */
.link-reset {

    all: unset;
    cursor: pointer;

}

/* my fixed nav bar. It starts invisible (opacity:0) and slid up slightly (translateY(-10px)) — processing.js will add a "scrolled" class to <body> once the visitor scrolls past the hero, and the rule right after this one fades it in when that happens */
.site-nav {

    /* position:fixed takes it completely out of normal document flow and locks it to the browser window itself, not to wherever it sits in the HTML — so it stays put even while the rest of the page scrolls underneath it. Setting top, left, and right to 0 stretches it across the full width of the screen, pinned to the very top */
    position: fixed;
    top: 0;
    left: 0;
    right: 0;

    /* z-index controls stacking order when elements overlap — higher numbers sit on top of lower ones. 40 is high enough that this bar stays above the page content scrolling underneath it */
    z-index: 40;

    /* I lay the logo lockup and the nav links out in a row, vertically centered, with justify-content:space-between pushing them to opposite ends of the bar (logo far left, links far right) */
    display: flex;
    align-items: center;
    justify-content: space-between;

    /* my inner spacing: 0.9rem top/bottom, 1.5rem left/right */
    padding: 0.9rem 1.5rem;

    /* a mostly-opaque dark background, plus backdrop-filter which blurs whatever page content is scrolling underneath the bar — this is what gives it that frosted-glass look instead of a flat solid bar */
    background: rgba(5, 5, 6, 0.7);
    backdrop-filter: blur(14px);

    /* a barely-visible hairline along the bottom edge to separate it from the page below */
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);

    /* the bar starts invisible, nudged up 10px, and pointer-events:none means it can't be clicked or hovered while it's hidden — without this, an invisible bar could still block clicks meant for whatever's behind it */
    opacity: 0;
    transform: translateY(-10px);
    pointer-events: none;

    /* I animate opacity and transform smoothly over 0.3 seconds whenever either one changes, instead of snapping instantly */
    transition: opacity 0.3s ease, transform 0.3s ease;

}

/* this is a compound selector: it only matches .site-nav when it's nested inside a <body> that also has class="scrolled". processing.js will add "scrolled" to <body> once the visitor scrolls down — when that happens, these values override the hidden state above, and the transition I just set makes that change fade in smoothly instead of popping */
body.scrolled .site-nav {

    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;

}

/* .site-nav .lockup only matches an element with class="lockup" that's nested inside .site-nav — this lets me reuse simple class names like "lockup" elsewhere on the page without their styles leaking into each other */
.site-nav .lockup {

    display: flex;
    align-items: center;
    gap: 0.7rem;

}

/* my logo inside the nav bar, sized small and fixed at 32px tall; width:auto keeps its original proportions instead of stretching or squashing it */
.site-nav .mark {

    display: block;
    height: 32px;
    width: auto;

}

/* the name text sitting next to my logo — Fraunces gives it a slightly serif, editorial feel that plain sans-serif text wouldn't have */
.site-nav .name {

    font-family: 'Fraunces', serif;
    font-weight: 600;
    font-size: 0.92rem;
    color: var(--text);

}

/* my small tagline under the name in the nav bar — IBM Plex Mono at a tiny size keeps it reading as a label, not a second headline */
.site-nav .tag {

    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.56rem;
    letter-spacing: 0.1em;

    /* forces the text to render in all caps regardless of how it's typed in the HTML — I wrote "Full-stack developer" in the HTML, and this displays it as "FULL-STACK DEVELOPER" without me retyping it */
    text-transform: uppercase;
    color: var(--dim);
    margin-top: 0.1rem;

}

/* the row of nav links themselves — flex lays them out in a line with a fixed gap between each one */
.site-nav .navlinks {

    display: flex;
    gap: 1.5rem;
    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.72rem;
    letter-spacing: 0.04em;

}

/* text-decoration:none strips the underline browsers put on links by default */
.site-nav .navlinks a {

    color: #9BA3B0;
    text-decoration: none;
    transition: color 0.2s ease;

}

/* a comma here means "apply this to either selector" — so this rule covers both :hover (while the mouse is over a link) and .active (the link matching whichever screen is currently showing, which processing.js will keep updated) */
.site-nav .navlinks a:hover,
.site-nav .navlinks a.active {

    color: var(--accent);

}

/* a media query — a conditional block of CSS that only applies while the browser window matches the condition in parentheses. This one only takes effect when the viewport is 480px wide or narrower (roughly phone-sized), and only affects .site-nav rules, since that's all I'm nesting inside it */
@media (max-width: 480px) {

    /* at this narrow width the name+tagline text would wrap awkwardly and make the bar too tall, so I just hide that text block entirely and leave the logo mark and nav links */
    .site-nav .text {

        display: none;

    }

    /* I also shrink the link row's gap and font size a touch so it doesn't crowd the narrower bar */
    .site-nav .navlinks {

        gap: 1rem;
        font-size: 0.68rem;

    }

}

/* ---------- hero / Home section ---------- */

/* position:relative doesn't visually move this element at all, but it does one important thing: it makes .hero a "positioned ancestor" for anything inside it that uses position:absolute (like .scroll-hint further down) — an absolutely positioned element measures its top/left/bottom/right from the nearest ancestor that has any position value other than the default "static", so this line is what lets me anchor .scroll-hint precisely inside the hero instead of relative to the whole page */
.hero {

    position: relative;

    /* the hero fills the full height of the browser window on load */
    min-height: 100vh;

    /* I stack the logo, name, tagline, and scroll hint vertically, centered both horizontally (align-items) and vertically (justify-content) within the hero's height */
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;

    /* text-align:center handles the text itself; gap puts consistent breathing room between each stacked child without needing individual margins */
    text-align: center;
    gap: 1.6rem;
    padding: 2rem 1.5rem;

    /* background-image can take a comma-separated list of layers, stacked in order with the FIRST one on top — so this gradient sits visually above the photo, darkening it for text readability. The gradient goes from 50% dark at the top to 92% dark at the bottom, so the sky stays a little visible up top while the very bottom (behind the tech-stack ticker) goes nearly black */
    background-image:
        linear-gradient(180deg, rgba(5, 5, 6, 0.5) 0%, rgba(5, 5, 6, 0.72) 55%, rgba(5, 5, 6, 0.92) 100%),
        url(../artifacts/images/wallpaper.jpg);

    /* cover scales the image up or down so it completely fills the element's box with no gaps, cropping whatever overflows — this is different from "contain," which would shrink the image to fit fully inside the box and can leave empty space */
    background-size: cover;

    /* background-position takes a horizontal value then a vertical value. "center" keeps it horizontally centered; "35%" pulls the visible crop up so it shows more of the upper 35% of the photo (roughly where the eagle sits) rather than dead-center, which would crop closer to the mountains below it */
    background-position: center 35%;

    /* without this, background-size:cover would still tile/repeat the image if it needed to — no-repeat guarantees exactly one copy */
    background-repeat: no-repeat;

}

/* filter:drop-shadow works like box-shadow but follows the actual visible shape of the image/icon instead of its rectangular box — useful here since a PNG logo isn't a solid rectangle */
.hero .mark {

    display: block;
    height: 140px;
    width: auto;
    filter: drop-shadow(0 18px 40px rgba(240, 145, 63, 0.16));

}

/* clamp(min, preferred, max) again, same as the fundamentals primer covered — the name's font size scales with viewport width (5vw) but never drops below 2rem or grows past 3.1rem */
.hero .name {

    font-family: 'Fraunces', serif;
    font-weight: 600;
    font-size: clamp(2rem, 5vw, 3.1rem);
    color: var(--text);
    letter-spacing: -0.01em;

}

/* my tagline text in the hero itself — the same mono, uppercase treatment as the nav tagline above, just bigger since this is the main hero copy */
.hero .tag {

    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.85rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--dim);

}

/* .hero .tag b only targets the <b> tag (wrapping the middle-dot) inside .tag — I use this to recolor just that dot to the accent orange, without affecting the rest of the tagline text around it */
.hero .tag b {

    color: var(--accent);
    font-weight: 600;

}

/* position:absolute pulls this completely out of normal document flow — it won't take up space or push any sibling around. bottom/left/transform then place it 28px up from .hero's bottom edge (that's the "positioned ancestor" relationship from .hero's position:relative above), centered horizontally the same way the old .navigator rule did it: push its left edge to the exact horizontal center, then shift it back left by half its own width */
.scroll-hint {

    position: absolute;
    bottom: 28px;
    left: 50%;
    transform: translateX(-50%);

    /* I style the "scroll for more" hint text itself — small monospace type, muted gray, wide letter-spacing, forced uppercase, so it reads as a quiet UI cue rather than body copy */
    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.65rem;
    color: #5B6472;
    letter-spacing: 0.1em;
    text-transform: uppercase;

}

/* ---------- multi-screen navigation ---------- */

/* by default, EVERY element with class="screen" is hidden. This is what makes Home, About, Privacy, and Terms behave like separate pages instead of one long scrolling page */
.screen {

    display: none;

}

/* only the one screen that ALSO carries class="active" gets shown */
.screen.active {

    display: block;

}

/* shows the fixed nav immediately on any non-Home screen, instead of waiting for a scroll-position threshold that only makes sense on Home's tall hero */
body.on-subpage .site-nav {

    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;

}

/* cancels out body's global 6.5rem bottom padding (reserved for the floating ticker on long scrolling pages) specifically while the fixed-height, non-scrolling About screen is showing, since that padding would otherwise just be wasted empty space there */
#screen-about {

    margin-bottom: -6.5rem;

}

/* ---------- Projects: the pin-and-scrub magazine mechanic ---------- */

/* #projects .eyebrow is a descendant selector combining an id (#projects, the outer Projects section) with a class (.eyebrow) — it only matches an .eyebrow that's nested inside the element with id="projects", so it can never affect the About widgets' eyebrows, which live inside #about instead. This also sidesteps the "later rule wins" tie-breaker entirely: an id selector has higher specificity than a plain class selector, so #projects .eyebrow will always beat a bare .eyebrow rule no matter which one is written first or second in the file */
#projects .eyebrow {

    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.7rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--dim);
    text-align: center;

}

/* position:relative here for the same reason as .hero earlier — it makes .pin-stage a positioned ancestor for anything inside it that needs position:absolute */
.pin-stage {

    position: relative;

}

/* the inner content wrapper for the pinned Projects section — the same full-height, centered-column layout as .hero, just without the background image */
.pin-inner {

    min-height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 2rem 1.5rem;

    /* caps how wide the content can stretch on a big monitor, then centers that capped box horizontally with auto left/right margins */
    max-width: 1000px;
    margin: 0 auto;

}

/* a small centered caption-style note that sits under the Projects eyebrow, colored in my accent color so it doesn't compete with the heading */
.pinned-note {

    text-align: center;
    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.64rem;
    color: var(--accent);
    margin: 0.4rem 0 1.5rem;
    letter-spacing: 0.02em;

}

/* display:grid turns this into a CSS Grid container — a second layout system alongside flexbox, better suited here since I want two columns of very specific, unequal widths rather than flex's "share available space" behavior. grid-template-columns:1.3fr 1fr defines exactly two columns: the first takes up 1.3 "shares" of the available space, the second takes 1 share, so the featured project card ends up wider than the side list next to it */
.stage-grid {

    display: grid;
    grid-template-columns: 1.3fr 1fr;
    gap: 2rem;
    align-items: center;

}

/* on screens 760px wide or narrower, I switch to a single column (1fr means "one share, the only column") so the two pieces stack vertically instead of squeezing side by side on a narrow screen */
@media (max-width: 760px) {

    .stage-grid {

        grid-template-columns: 1fr;

    }
}

/* i put the background gradient on this wrap element instead of on the card stacked inside it, so the background never blurs/fades/moves — only the text card on top of it does, once processing.js starts animating between projects */
.feature-wrap {

    /* i give it a relative position so .feature-card can stack absolutely inside it, a fixed height, a dark panel background as a fallback, and a subtle border and rounded corners to match my other cards */
    position: relative;
    height: 340px;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 20px;

    /* i stack two gradient layers: the first (0deg, meaning bottom-to-top) darkens toward the bottom so text stays readable there; the second (135deg, a diagonal) tints the whole thing faintly with the accent color for some visual warmth */
    background-image: linear-gradient(0deg, rgba(5, 5, 6, 0.92), rgba(5, 5, 6, 0.25)), linear-gradient(135deg, rgba(240, 145, 63, 0.28), rgba(217, 102, 30, 0.06));

    /* i clip anything that would otherwise overflow this box's rounded corners — without it, a sharp-cornered child could poke out past the border-radius curve */
    overflow: hidden;

}

/* i use .feature-card for the actual project text, positioned on top of .feature-wrap's background. inset:0 is shorthand for setting top, right, bottom, AND left all to 0 in one line — combined with position:absolute, that stretches this element to exactly fill its positioned ancestor (.feature-wrap) completely */
.feature-card {

    /* i stretch it to fill .feature-wrap completely, with my own padding inside so the text doesn't touch the edges */
    position: absolute;
    inset: 0;
    padding: 2rem;

    /* justify-content:flex-end in a column flex container pushes all the content down to the bottom of the box instead of the top or center — so the title/text sit low, near the bottom edge, with empty space above them */
    display: flex;
    flex-direction: column;
    justify-content: flex-end;

}

/* i pin this small badge to the top right corner of the feature card, styled like a little pill-shaped tag */
.feature-card .pin-badge {

    position: absolute;
    top: 1rem;
    right: 1rem;
    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.6rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--accent);
    border: 1px solid rgba(39, 67, 101, 0.35);
    background: rgba(5, 5, 6, 0.5);
    padding: 0.25rem 0.55rem;
    border-radius: 999px;

}

/* i style the small eyebrow-style tag label that sits above the project title in the feature card */
.feature-card .tag {

    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.66rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--accent);

}

/* i style the project title itself, my serif display font at a large size */
.feature-card h2 {

    font-family: 'Fraunces', serif;
    font-weight: 700;
    font-size: 1.6rem;
    margin: 0.5rem 0 0.5rem;

}

/* i make the project title a <button> (so it's clickable to open the full project page) wrapped inside this h2 — buttons center their text by default, so this puts it back to the left like normal heading text */
.feature-card h2 button {

    text-align: left;

}

/* i tint the project title with my accent color on hover, a small cue that it's clickable */
.feature-card h2 button:hover {

    color: var(--accent);

}

/* ch is a unit meaning "the width of the character '0' in this element's current font" — I cap the paragraph at roughly 44ch wide per line, which keeps my body text at a comfortable, readable line length regardless of how wide the card actually is */
.feature-card p {

    font-size: 0.88rem;
    color: #C9CCD3;
    line-height: 1.55;
    margin: 0 0 0.25rem;
    max-width: 44ch;

}

/* i style the small date line under the project description */
.feature-card .date {

    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.66rem;
    color: #8a8f99;
    margin-top: 0.6rem;

}

/* i stack my side list of other projects in a column, next to the featured card */
.side-list {

    display: flex;
    flex-direction: column;
    gap: 0.4rem;

}

/* i lay out each row in my side list with the number and title side by side, some padding, and rounded corners */
.side-item {

    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.9rem 1rem;
    border-radius: 12px;

    /* i reserve a transparent 1px border the same size a visible border would take up, so when .active later gives it a real colored border, nothing shifts position by 1px — only the color appears */
    border: 1px solid transparent;
    color: #6B7280;
    transition: color 0.25s ease, border-color 0.25s ease, background 0.25s ease;

}

/* i style the number that sits in front of each side list title */
.side-item .n {

    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.72rem;
    opacity: 0.7;

}

/* i style the project title text in each side list row */
.side-item .t {

    font-family: 'Fraunces', serif;
    font-size: 0.98rem;
    font-weight: 600;

}

/* my processing.js will add "active" to whichever side item corresponds to the project currently shown in the featured card next to it */
.side-item.active {

    color: var(--text);
    border-color: rgba(68, 82, 136, 0.35);
    background: rgba(25, 44, 78, 0.06);

}

/* i brighten the number of whichever side item is active */
.side-item.active .n {

    color: var(--accent);
    opacity: 1;

}

/* i tint a side list title with my accent color on hover */
.side-item .t:hover {

    color: var(--accent);

}

/* will-change is a hint to the browser: "this property is about to be animated a lot, so prepare for it in advance" — it can make animations smoother by letting the browser optimize ahead of time, but I only use it on things that actually animate (via GSAP in processing.js later), since overusing it can waste memory */
.card-meta {

    will-change: opacity, filter, transform;

}

/* I will split the project title into one span per letter with processing.js, so each character can fade in/out on its own slightly staggered from its neighbors, rather than the whole word swapping at once */
.morph-title {

    display: inline-block;

}

/* I style each individual letter span that processing.js creates inside the title */
.morph-title .ch {

    display: inline-block;
    will-change: opacity, filter, transform;

}

/* I lay my little progress dots out in a centered row under the feature card */
.progress-dots {

    display: flex;
    justify-content: center;
    gap: 0.5rem;
    margin-top: 2rem;

}

/* I size each dot as a tiny circle, dim by default */
.progress-dots .dot {

    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgba(12, 189, 248, 0.15);
    transition: background 0.25s ease, transform 0.25s ease;

}

/* I brighten and enlarge whichever dot processing.js marks active, to show which project is showing */
.progress-dots .dot.active {

    background: var(--accent);
    transform: scale(1.4);

}

/* this is my second pin stage: full-bleed, with no framed card like the first one */
.pin-stage2 {

    position: relative;

}

/* I make this inner wrap fill the full viewport height and width, clipping anything that overflows it */
.pin-inner2 {

    position: relative;
    height: 100vh;
    width: 100%;
    overflow: hidden;

}

/* I stretch this full-bleed background panel to fill .pin-inner2 completely, with the same two-layer gradient tint I use on my other project cards */
.stage2-panel {

    position: absolute;
    inset: 0;
    background: var(--panel);
    background-image: linear-gradient(0deg, rgba(5, 5, 6, 0.92), rgba(5, 5, 6, 0.15)), linear-gradient(135deg, rgba(34, 53, 88, 0.22), rgba(24, 39, 83, 0.04));

}

/* I stretch the card's text content to fill the full-bleed panel too, pushed down to the bottom */
.stage2-card {

    position: absolute;
    inset: 0;

    /* padding with THREE values follows top | left-and-right | bottom — different from the usual two-value shorthand (vertical | horizontal). Here that's 3rem on top, 3rem on both sides, and 5rem on the bottom (extra room I reserve for the progress dots that overlay this card near the bottom edge) */
    padding: 3rem 3rem 5rem;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;

}

/* I style the eyebrow tag on my second stage card the same way as the first */
.stage2-card .tag {

    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--accent);

}

/* I size the title on the second stage card with clamp so it scales with the viewport instead of staying one fixed size */
.stage2-card h2 {

    font-family: 'Fraunces', serif;
    font-weight: 700;
    font-size: clamp(1.6rem, 3.6vw, 2.3rem);
    margin: 0.5rem 0 0.6rem;

}

/* I keep this title's button text left-aligned, same as my first stage card */
.stage2-card h2 button {

    text-align: left;

}

/* I tint this title with my accent color on hover too */
.stage2-card h2 button:hover {

    color: var(--accent);

}

/* I cap the description text on the second stage card to a comfortable reading width */
.stage2-card p {

    font-size: 0.9rem;
    color: #C9CCD3;
    line-height: 1.6;
    margin: 0;
    max-width: 52ch;

}

/* my second stage also needs its own overlay label, positioned on top of the full-bleed background rather than inside a framed card */
.stage-overlay-top {

    position: absolute;
    top: 2.5rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 5;
    text-align: center;

    /* this label sits directly over the scrollable card content — pointer-events:none lets clicks/scrolls "pass through" it to whatever's underneath it, instead of the label itself blocking interaction */
    pointer-events: none;

}

/* I give my second stage its own set of progress dots, floating over the full-bleed background near the bottom */
#progressDots2 {

    position: absolute;
    bottom: 2.5rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 5;
    margin-top: 0;

}

/* ---------- full-page project view (opened by clicking any project title) ---------- */

.view.fullpage {

    min-height: 100vh;

    /* I use 3-value padding again here: 5rem top, 1.5rem left-and-right, 5rem bottom */
    padding: 5rem 1.5rem 5rem;

}

/* I cap the project page's content to a readable width and center it on the page */
.project-page-inner {

    max-width: 760px;
    margin: 0 auto;

}

/* I style my "back to home" pill-shaped button that sits at the top of a full-page project view */
.back-btn {

    background: none;
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 0.5rem 1rem;
    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.7rem;
    color: var(--dim);
    cursor: pointer;
    margin-bottom: 2.5rem;
    transition: color 0.2s ease, border-color 0.2s ease;

    /* I reuse this same class on a real <a> link (the "Back to home" links on Privacy/Terms) as well as a <button> — display:inline-block and text-decoration:none make sure it looks identical either way, since a plain <a> would otherwise render as underlined inline text instead of matching the button's boxed look */
    display: inline-block;
    text-decoration: none;

}

/* I tint the back button with my accent color on hover, border included */
.back-btn:hover {

    color: var(--accent);
    border-color: rgba(26, 29, 84, 0.4);

}

/* I give the hero banner at the top of a project page rounded corners, my dark panel background, and the same two-layer gradient tint I use elsewhere on project cards */
.project-hero {

    border-radius: 22px;
    padding: 3rem 2rem;
    margin-bottom: 2rem;
    background: var(--panel);
    border: 1px solid var(--border);
    background-image: linear-gradient(0deg, rgba(5, 5, 6, 0.92), rgba(5, 5, 6, 0.15)), linear-gradient(135deg, rgba(6, 14, 242, 0.3), rgba(9, 5, 200, 0.05));

}

/* I style the small eyebrow tag above the project hero title */
.project-hero .project-hero-tag {

    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--accent);

}

/* I size the project hero's title with clamp so it scales with the viewport */
.project-hero h1 {

    font-family: 'Fraunces', serif;
    font-weight: 700;
    font-size: clamp(1.8rem, 4vw, 2.6rem);
    margin: 0.6rem 0 0.6rem;

}

/* I style the small date line under the project hero title */
.project-hero .project-hero-date {

    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.7rem;
    color: #8a8f99;

}

/* I cap the project body text to a comfortable reading width, same idea as my other body copy */
.project-body p {

    font-size: 0.95rem;
    color: #C9CCD3;
    line-height: 1.7;
    max-width: 60ch;

}

/* I lay out my three project meta stats (like role, stack, year) in an even 3 column grid */
.project-meta-row {

    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.75rem;
    margin-top: 2rem;

}

/* I give each meta stat its own bordered, rounded, centered box */
.project-meta-row .meta {

    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 0.8rem;
    text-align: center;

}

/* I style the small uppercase label above each meta stat's value */
.project-meta-row .k {

    display: block;
    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.6rem;
    text-transform: uppercase;
    color: var(--dim);
    letter-spacing: 0.06em;

}

/* I style the actual value text under each meta stat's label */
.project-meta-row .v {

    display: block;
    font-size: 0.85rem;
    font-weight: 600;
    margin-top: 0.25rem;

}

/* ---------- Privacy Policy / Terms & Conditions ---------- */
/* I reuse .view.fullpage, .project-page-inner, and .back-btn from directly above — same full-height padding, centered column, and back link, just with plainer text styling suited to legal copy instead of a project's big gradient hero */

.legal-page {

    max-width: 700px;
    margin: 0 auto;

}

/* I style the small eyebrow tag above a legal page's title */
.legal-page .legal-tag {

    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--accent);

}

/* I size a legal page's title with clamp so it scales with the viewport */
.legal-page h1 {

    font-family: 'Fraunces', serif;
    font-weight: 700;
    font-size: clamp(1.8rem, 4vw, 2.4rem);
    margin: 0.6rem 0 0.3rem;

}

/* I style the small "last updated" line under a legal page's title */
.legal-page .legal-updated {

    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.7rem;
    color: #8a8f99;
    margin-bottom: 2.5rem;

}

/* I style each section heading within the legal body copy */
.legal-body h2 {

    font-family: 'Fraunces', serif;
    font-weight: 700;
    font-size: 1.15rem;
    margin: 2.2rem 0 0.7rem;

}

/* I cap the legal body paragraphs to a comfortable reading width */
.legal-body p {

    font-size: 0.92rem;
    color: #C9CCD3;
    line-height: 1.7;
    max-width: 65ch;
    margin: 0 0 1rem;

}

/* I tint any link inside the legal body copy with my accent color */
.legal-body a {

    color: var(--accent);

}

/* I give bulleted lists in the legal body copy a little left padding for the bullets, and matching bottom margin */
.legal-body ul {

    padding-left: 1.1rem;
    margin: 0 0 1rem;

}

/* I style each individual bullet item the same way as my body paragraphs */
.legal-body li {

    font-size: 0.92rem;
    color: #C9CCD3;
    line-height: 1.7;
    max-width: 62ch;
    margin-bottom: 0.4rem;

}

/* ---------- shared footer ---------- */
/* I place this once, after every .screen, rather than duplicating it inside each one — since only one .screen is ever visible at a time, whichever footer follows just naturally renders under whatever screen is currently showing, no JS needed */

.site-footer {

    border-top: 1px solid var(--border);
    margin-top: 3rem;

}

/* I cap the footer's inner content to a readable width, centered, with some padding */
.footer-inner {

    max-width: 1000px;
    margin: 0 auto;
    padding: 1.6rem 1.5rem;

    /* I add flex-wrap:wrap so the copyright text and links can drop to a second line on a narrow screen instead of getting forced into one row and overflowing or squeezing painfully tight */
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.8rem;

}

/* I style the small copyright text in the footer */
.footer-copy {

    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.68rem;
    color: var(--dim);

}

/* I reuse the .navlinks class name for a second, deliberately quieter-looking nav in my footer — reusing the name means the shared JS click-handling (which looks for anything with a data-screen attribute) picks these up automatically too, without needing separate logic */
.site-footer .navlinks {

    display: flex;
    gap: 1.2rem;
    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.68rem;
    letter-spacing: 0.03em;

}

/* I style each footer nav link as plain dim text with no underline by default */
.site-footer .navlinks a {

    color: var(--dim);
    text-decoration: none;
    transition: color 0.2s ease;

}

/* I tint a footer nav link with my accent color on hover, or whenever it points at the screen currently showing */
.site-footer .navlinks a:hover,
.site-footer .navlinks a.active {

    color: var(--accent);

}

/* ---------- Posts: placeholder slot on Home ---------- */

.home-posts {

    max-width: 900px;
    margin: 0 auto;
    padding: 1rem 1.25rem 4rem;

}

/* I style this as a placeholder box until real posts/writing content exists here */
.posts-pending {

    /* "dashed" is a border-style value — like "solid" but drawn as a dashed line, a common visual convention for "this is a placeholder/incomplete area" */
    border: 1px dashed rgba(255, 255, 255, 0.14);
    border-radius: 16px;

    /* I center the placeholder text and keep it small and dim, so it reads as a quiet placeholder rather than a big statement */
    padding: 2.4rem 1.25rem;
    text-align: center;
    color: var(--dim);
    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.72rem;
    letter-spacing: 0.03em;

}

/* i am styling the about screen wrapper. i switched this from a normal block that could run taller than the viewport to one fixed viewport-height page. 100dvh tracks the real visible viewport on mobile, where 100vh can be taller than what is actually shown once the browser bar is accounted for. overflow hidden plus flex centering keeps the whole thing fitting in one screen instead of scrolling */
.about {

    height: 100dvh;
    max-height: 100dvh;
    overflow: hidden;
    box-sizing: border-box;

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

    /* i kept the bottom padding at the same minimum the floating ticker clearance uses everywhere else on my site, so my last contact row does not end up hidden behind it */
    padding: clamp(4rem, 11vh, 6rem) 1.25rem clamp(6.5rem, 12vh, 7.5rem);
    scroll-margin-top: 90px;

}

/* i am giving both my hero and my contact anchor the same scroll offset, so a jump to either one lands below my fixed nav bar instead of underneath it */
.hero,
#contact {

    scroll-margin-top: 90px;

}

/* i am laying out the about screen as a 2 column grid, my narrower left column (timezone plus hobbies panels) next to a wider right column i will use for the profile card */
.grid {

    width: 100%;
    max-width: 900px;
    margin: 0 auto;

    display: grid;
    grid-template-columns: minmax(240px, 300px) 1fr;
    gap: clamp(0.7rem, 2.2vh, 1.25rem);
    align-items: start;

}

/* below 720px i drop to a single column, and i move my profile card to the front so a visitor sees my photo and name first instead of the timezone widget */
@media (max-width: 720px) {

    .grid {

        grid-template-columns: 1fr;

    }

    .profile {

        order: -1;

    }

}

/* below 720px i also let the about screen scroll again instead of clamping to one fixed screen height, since 3 stacked panels can genuinely run taller than a short phone screen */
@media (max-width: 720px) {

    .about {

        height: auto;
        max-height: none;
        overflow: visible;
        display: block;
        padding-top: 6rem;
        padding-bottom: 8rem;

    }

}

/* my shared card look, reused by both left column panels and by the profile card on the right */
.panel {

    background: rgba(255, 255, 255, 0.02);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 20px;
    box-shadow: 0 16px 40px rgba(0, 0, 0, 0.6), inset 0 1px 1px rgba(255, 255, 255, 0.05);

    /* clamp instead of a fixed value so this shrinks on a shorter viewport rather than pushing the about screen taller than 100dvh */
    padding: clamp(0.65rem, 1.4vh, 1.5rem) clamp(1rem, 2.6vw, 1.4rem);

}

/* my left column, stacking the timezone panel above the hobbies panel */
.left {

    display: flex;
    flex-direction: column;
    gap: clamp(0.55rem, 1.8vh, 1.1rem);

}

/* my small uppercase label style, used above both the timezone widget and the hobbies list, and also above my contact rows in the profile card. i am leaving this unscoped on purpose since projects .eyebrow already wins inside the projects section no matter what order the rules sit in */
.eyebrow {

    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.68rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--dim);
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: clamp(0.25rem, 0.6vh, 0.5rem);

}

/* sizes the small icon that can sit inside one of my eyebrow labels */
.eyebrow svg {

    width: 13px;
    height: 13px;

}

/* my timezone widget, the big clock style number at the top of the left column */
.clock {

    font-family: 'IBM Plex Mono', monospace;
    font-weight: 600;
    font-size: clamp(1.5rem, 4.4vh, 2.3rem);
    letter-spacing: -0.01em;
    color: #fff;
    display: flex;
    align-items: baseline;
    gap: 0.15rem;

}

/* the small timezone abbreviation sitting next to the clock number */
.clock .zone {

    font-size: 0.85rem;
    color: var(--accent);
    margin-left: 0.3rem;
    font-weight: 500;

}

/* the date line under my clock */
.date {

    font-size: 0.82rem;
    color: var(--dim);
    margin-top: 0.5rem;

}

/* my city and country line under the date */
.place {

    font-size: 0.82rem;
    color: #9CA3AF;
    margin-top: 0.15rem;
    font-weight: 500;

}

/* the column that stacks my rows, used both for the hobbies list and for the contact rows in my profile card */
.rows {

    display: flex;
    flex-direction: column;
    gap: clamp(0.2rem, 0.4vh, 0.45rem);

}

/* my shared row style, one icon plus a label and a subline, reused for both a hobby row and a contact row */
.lrow {

    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: clamp(0.25rem, 0.5vh, 0.6rem) 0.85rem;
    border-radius: 13px;
    background: rgba(255, 255, 255, 0.025);
    border: 1px solid rgba(255, 255, 255, 0.06);

}

/* the small square icon tile on the left of each row */
.lrow .icon {

    flex: none;
    border-radius: 9px;
    width: clamp(22px, 3.6vh, 30px);
    height: clamp(22px, 3.6vh, 30px);
    background: #111214;
    border: 1px solid rgba(255, 255, 255, 0.05);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--accent);

}

/* wraps the label and subline text so they stack instead of sitting side by side */
.lrow .text {

    display: flex;
    flex-direction: column;
    min-width: 0;

}

/* my row's bold top line, for example a hobby name or a contact method name */
.lrow .label {

    font-size: 0.8rem;
    font-weight: 700;
    color: #EDEAE3;

}

/* my row's smaller second line underneath the label. text-overflow ellipsis plus white-space nowrap means a value too long for the row gets cut short with 3 dots instead of wrapping or overflowing */
.lrow .sub {

    font-size: 0.72rem;
    color: var(--dim);
    margin-top: 0.1rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;

}

/* my contact rows in the profile card are real clickable links, so this variant adds a hover state and a trailing arrow on top of the shared row look above */
.lrow.link {

    justify-content: space-between;
    text-decoration: none;
    transition: border-color 0.25s ease, background 0.25s ease;

}

/* i lighten the border and background of a contact row slightly on hover, as visual feedback that the whole row is clickable */
.lrow.link:hover {

    border-color: rgba(255, 255, 255, 0.16);
    background: rgba(255, 255, 255, 0.05);

}

/* i tint the icon with my accent color too, matching the hover state on its parent row */
.lrow.link:hover .icon {

    color: var(--accent);

}

/* i set flex-direction row explicitly here, because without it this collided with my unrelated top-level left class further up, which stacks flex-direction column, and that was stacking my icon above my label instead of beside it */
.lrow .left {

    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 0.85rem;
    min-width: 0;

}

/* the small trailing arrow on a contact row */
.lrow .arrow {

    flex: none;
    color: #4B5563;
    transition: transform 0.25s ease, color 0.25s ease;

}

/* on hover i brighten the arrow and nudge it up and to the right, a small directional cue that the row is clickable */
.lrow.link:hover .arrow {

    color: #9CA3AF;
    transform: translate(2px, -2px);

}

/* my profile card centers its children, so this row list needs its own explicit width, or it would shrink to hug its own text instead of spanning the card */
.profile .rows {

    width: 100%;
    max-width: 340px;
    text-align: left;

}

/* my profile card itself, the right column of the about grid */
.profile {

    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding-top: clamp(0.2rem, 0.6vh, 0.4rem);

}

/* wraps my avatar circle so i have a positioned box to place the glow behind it */
.avatar-wrap {

    position: relative;
    margin-bottom: clamp(0.25rem, 0.7vh, 0.8rem);
    width: clamp(52px, 7vh, 132px);
    height: clamp(52px, 7vh, 132px);

}

/* a soft blurred glow sitting behind my avatar circle */
.avatar-glow {

    position: absolute;
    inset: -10px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(10, 11, 101, 0.35), transparent 70%);
    filter: blur(6px);

}

/* my avatar circle. right now it just shows my initials as a placeholder until i swap in a real photo */
.avatar {

    position: relative;
    width: 100%;
    height: 100%;
    border-radius: 50%;
    background: #14151a;
    border: 1px solid rgba(255, 255, 255, 0.12);
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: 'Fraunces', serif;
    font-weight: 600;
    font-size: clamp(1rem, 3vh, 2.1rem);
    color: #8b90a0;

}

/* the small caption under my avatar */
.avatar-caption {

    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.62rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #4B5563;
    margin-bottom: clamp(0.3rem, 0.8vh, 0.7rem);

}

/* my name, shown big in the profile card */
.profile h1 {

    font-family: 'Fraunces', serif;
    font-weight: 600;
    font-size: clamp(1.35rem, 3.6vh, 2rem);
    margin: 0 0 0.2rem;

}

/* my role line under my name */
.role {

    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.85rem;
    color: var(--accent);
    letter-spacing: 0.02em;

}

/* my short tagline under my role. max-width is set in px instead of ch here, because i measured the actual rendered height at different widths against my real tagline text, and a ch based width still rendered 3 lines instead of the 2 i wanted */
.tagline {

    font-size: 0.88rem;
    color: #9CA3AF;
    line-height: 1.5;
    margin-top: clamp(0.25rem, 0.7vh, 0.6rem);
    margin-bottom: 0;
    max-width: 465px;

}

/* a thin divider line, used twice in my profile card, once before stats and once before contact */
.divider {

    height: 1px;
    background: rgba(255, 255, 255, 0.08);
    margin: clamp(0.3rem, 0.8vh, 0.7rem) 0;

}

/* my 3 column stat grid, sitting between the two dividers */
.stats {

    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.6rem;
    width: 100%;
    max-width: 340px;

}

/* one stat capsule */
.stat {

    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 11px;
    text-align: center;
    padding: clamp(0.25rem, 0.5vh, 0.4rem);

}

/* the small label on a stat, for example years or projects */
.stat .k {

    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.6rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--dim);

}

/* the actual number or value on a stat */
.stat .v {

    font-size: 0.82rem;
    font-weight: 700;
    margin-top: 0.2rem;

}

/* i am styling the floating tech-stack ticker that stays pinned near the bottom of the screen the whole time. left 50 percent plus translatex negative 50 percent centers it horizontally regardless of window width */
.ticker-wrap {

    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: 44px;
    width: min(320px, calc(100% - 40px));
    height: 52px;
    border-radius: 18px;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    backdrop-filter: blur(14px);
    box-shadow: 0 12px 34px rgba(0, 0, 0, 0.55);
    overflow: hidden;

    /* a mask fades the tiles to transparent right at each edge, so a tile scrolling in or out fades instead of getting a hard visual cut */
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 24px, #000 calc(100% - 24px), transparent 100%);
    mask-image: linear-gradient(90deg, transparent 0, #000 24px, #000 calc(100% - 24px), transparent 100%);

    /* i animate both bottom and width smoothly, since processing.js will later toggle this ticker between its normal position and a narrower docked position under my hero tagline */
    transition: bottom 0.5s cubic-bezier(0.4, 0, 0.2, 1), width 0.5s cubic-bezier(0.4, 0, 0.2, 1);

}

/* narrower variant while the ticker is docked under my hero tagline, showing fewer tiles at once. processing.js toggles this class on and off */
.ticker-wrap.docked-tag {

    width: min(276px, calc(100% - 40px));

}

/* the inner track that actually holds and scrolls the tech logo tiles. width max-content lets it grow as wide as all its tiles need, since processing.js scrolls it by nudging its position every frame instead of using a css keyframe animation */
.ticker-track {

    display: flex;
    align-items: center;
    gap: 1rem;
    height: 100%;
    width: max-content;
    padding: 0 1rem;
    will-change: transform;

}

/* if a visitor's system is set to reduce motion, i freeze the ticker in place instead of scrolling it */
@media (prefers-reduced-motion: reduce) {

    .ticker-track {

        transform: none !important;

    }

}

/* one square tile in the ticker, holding a single tech logo */
.tile {

    flex: none;
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: default;

}

/* sizes the actual logo svg inside a tile. i round the corners slightly so any logo with a square background of its own still matches the rest of the page */
.tile svg {

    width: 30px;
    height: 30px;
    border-radius: 6px;
    overflow: hidden;

}

/* i am styling the single shared tooltip that pops up over whichever ticker tile a visitor hovers, repositioned and refilled by processing.js instead of building one tooltip per tile */
#stack-tooltip {

    position: fixed;
    width: 230px;
    max-width: calc(100vw - 24px);
    background: #14161c;
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 26px;
    box-shadow: 0 16px 40px rgba(0, 0, 0, 0.55);
    padding: 0.9rem 1.1rem;
    opacity: 0;

    /* i anchor this by its bottom edge, so it always grows upward from the tile no matter how tall its content is or how close to the bottom of the screen the ticker sits */
    transform-origin: bottom center;
    transform: translate(-50%, -6px) scale(0.55);
    transition: opacity 0.2s ease, transform 0.38s cubic-bezier(0.34, 1.56, 0.64, 1);
    pointer-events: none;
    z-index: 50;

}

/* processing.js adds this class on hover. the overshoot in the transition curve above is what sells the popped out feel, and reversing this same transition on mouseleave is what makes it look like it sinks back in instead of just fading */
#stack-tooltip.visible {

    opacity: 1;
    transform: translate(-50%, 0) scale(1);

}

/* the tech name inside my tooltip */
#stack-tooltip .tt-name {

    font-family: 'Fraunces', serif;
    font-weight: 600;
    font-size: 0.95rem;
    color: #EDEAE3;

}

/* the short description line under the name */
#stack-tooltip .tt-desc {

    font-size: 0.76rem;
    color: #9CA3AF;
    line-height: 1.5;
    margin-top: 0.35rem;

}

/* the row holding my usage note and my usage percentage side by side */
#stack-tooltip .tt-row {

    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin-top: 0.6rem;
    padding-top: 0.6rem;
    border-top: 1px solid rgba(255, 255, 255, 0.08);

}

/* the short note on how i use this piece of tech */
#stack-tooltip .tt-use {

    font-size: 0.68rem;
    color: #7C8593;
    max-width: 130px;
    line-height: 1.4;

}

/* the big percentage number, for example how many of my projects use it */
#stack-tooltip .tt-pct {

    font-family: 'IBM Plex Mono', monospace;
    font-weight: 600;
    font-size: 0.95rem;
    color: #08E8DE;
    white-space: nowrap;

}

/* the small label under the percentage number */
#stack-tooltip .tt-pct-label {

    font-family: 'IBM Plex Mono', monospace;
    font-size: 0.58rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #5B6472;
    display: block;
    text-align: right;

}

/* the small triangle pointer under my tooltip, rotated 45 degrees so a plain square reads as a diamond tip pointing down at the tile */
#stack-tooltip .tt-arrow {

    position: absolute;
    bottom: -6px;
    left: 50%;
    transform: translateX(-50%) rotate(45deg);
    width: 11px;
    height: 11px;
    background: #14161c;
    border-right: 1px solid rgba(255, 255, 255, 0.1);
    border-bottom: 1px solid rgba(255, 255, 255, 0.1);

}