/* ==========================================================================
   Corner tokens
   ==========================================================================
   Every media surface (case-study screenshots, hero carousel, blog & legacy
   image containers, the lightbox) rounds from these two values, so corners
   stay in one system instead of drifting between 8/10/12px by hand.

   THE RULES - follow these for any new media element:

   1. ROUND ON ONE SURFACE ONLY. The container carries the radius & clips with
      overflow: hidden; the <img> inside stays at radius 0. Rounding BOTH
      stacks two curves on the same corner, and any sub-pixel disagreement
      (fractional layout heights, object-fit scaling, DPR) shows as a
      thickened or ragged edge. The hero carousel always looked right because
      its slides were already radius 0 - this is now the rule everywhere.
   2. CONCENTRIC RADII. If a container ever regains a border or padding, the
      inner radius must be outer minus the border/padding width, never equal -
      equal radii make the gap visibly pinch at the corners.
   3. SMOOTHING IS PROGRESSIVE. corner-shape: squircle (CSS Borders L4) is
      applied via @supports at the bottom of this file. Browsers without it
      fall back to a plain circular radius - same geometry, slightly less
      smooth, never broken. Don't hand-roll squircles with SVG masks.
   4. RADIUS SCALES WITH THE ELEMENT. A single fixed radius across wildly
      different sizes reads as "off" optically - the same 12px is chunky on a
      small thumbnail & mean on a full-bleed hero. Three tiers:
        --radius-media-sm  small / half-width media (craft grid, mobile posters,
                           hero supporting visuals)
        --radius-media     the default - standard project-block screenshots
        --radius-media-lg  heroes & full-width showcases, plus the lightbox
      Tiers are assigned by REDEFINING --radius-media on the element (see the
      "Corner scale" block further down) rather than by overriding
      border-radius, so every rule can just say var(--radius-media).
      The whole scale steps down on small viewports, where the same elements
      are physically much narrower.
   ========================================================================== */
:root {
    --radius-media-sm: 10px;
    --radius-media: 12px;
    --radius-media-lg: 20px;
}

@media (max-width: 768px) {
    :root {
        --radius-media-sm: 8px;
        --radius-media: 10px;
        --radius-media-lg: 12px;
    }
}

/* ==========================================================================
   Home entrance timeline — ONE source of truth
   ==========================================================================
   The reveal is a conversation, & a conversation is mostly the pauses. The
   greeting lands, then HOLDS long enough to be heard as its own sentence;
   the second line answers it; then another breath before the work cards
   arrive & push the hero up. Two beats do the work:

     hold 1  = --t-subtitle − (--t-greeting + --d-greeting)   ≈ 0.55s
     hold 2  = --t-cards    − (--t-subtitle + --d-subtitle)   ≈ 0.45s

   Both used to be ~0s: the subtitle started 0.12s after the greeting finished
   and the cards started the same instant the subtitle did, so the whole thing
   ran together as one gesture instead of three.

   These are absolute times from first paint, and they're read from BOTH
   places that need them — the animation delays further down this file, and
   the cascade controller in index.html, which parses these custom properties
   rather than repeating the numbers. Retiming the entrance is an edit here
   and nowhere else.

   Everything below --t-cards is the large-screen cascade only; sections
   under the fold are scroll-triggered and ignore it. */
:root {
    --t-greeting:  0.15s;   /* "Hi, this is Krishna," begins */
    --d-greeting:  0.55s;
    --t-subtitle:  1.25s;   /* "a digital product designer & builder." begins */
    --d-subtitle:  0.55s;
    --t-cards:     2.25s;   /* work cards in + hero moves up (the "push") */
    --t-projects:  3.6s;
    --t-thoughts:  5.5s;
    --t-footer:    7s;
}

/* ==========================================================================
   Reset & Base Styles
   ========================================================================== */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

html {
    font-size: 16px;

    /* The canvas colour, and it is load-bearing on mobile. `body`'s gradient is
       `background-attachment: fixed`, so it paints relative to the VIEWPORT —
       and when Chrome/Safari retract their toolbar mid-scroll the viewport
       grows by ~56px. The browser exposes that strip before the fixed
       background has repainted into it, so whatever the canvas is shows
       through for a frame or two. With no colour here that canvas is WHITE,
       which is the white square flashing along the bottom edge on a phone.
       Matching the gradient's darkest stop makes the same repaint gap
       invisible. Do not remove; `background-attachment: fixed` is what makes
       the gap possible, and the gradient needs it to stay put while scrolling. */
    background-color: #110C07;
}

body {
    font-family: 'komet', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    color: #F3E7D4;
    line-height: 1.6;
    min-height: 100vh;
    background: linear-gradient(180deg, #110C07 0%, rgba(26, 24, 22, 0.98) 100%);
    background-attachment: fixed;
    position: relative;
    overflow-x: hidden;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    
}

/* ==========================================================================
   Background Orbs
   ========================================================================== */
.background-orbs {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 0;
    pointer-events: none;
    opacity: 0.91;
}

.orb {
    position: absolute;
    border-radius: 50%;
    filter: blur(100px);
}

/* Updated orbs implementation */
.orbs-svg {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: -1;
}

/* Legacy orb styles commented out in HTML */
.orb-1, .orb-2, .orb-3 {
    display: none;
}

/* ==========================================================================
   Container
   ========================================================================== */
.container {
    max-width: 1100px;
    margin: 0 auto;
    padding: 40px 60px 30px;
    position: relative;
    z-index: 1;
    /* Proximity snap — gentle gravitational pull near section boundaries */
    scroll-snap-type: y proximity;
}

/* ==========================================================================
   Typography
   ========================================================================== */
h1 {
    font-size: 3.5rem;
    font-weight: 300;
    line-height: 1.3;
    margin-bottom: 0.5rem;
    letter-spacing: -0.02em;
    color: rgba(243, 231, 212, 0.95);
}

h2 {
    font-size: 2rem;
    font-weight: 400;
    margin-bottom: 2rem;
    color: rgba(243, 231, 212, 0.95);
}

h3 {
    font-size: 1.2rem;
    font-weight: 600;
    margin-bottom: 5px;
}

p {
    margin-bottom: 1rem;
    line-height: 1.6;
}

ul {
    margin-bottom: 1rem;
    padding-left: 1.5rem;
}

li {
    margin-bottom: 0.5rem;
    line-height: 1.6;
}

/* Links */
a {
    color: #4A9FF5;
    text-decoration: none;
}

a:hover {
    text-decoration: underline;
}

/* ==========================================================================
   Gradient Text
   ========================================================================== */
.gradient-text {
    background: linear-gradient(180deg, #E6864B 0%, #D7AF93 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    text-fill-color: transparent;
}

/* ==========================================================================
   Contact Navigation (Bottom)
   ========================================================================== */
.bottom-contact {
    text-align: center;
    margin-top: 3rem;
    margin-bottom: 0.75rem;
    position: relative;
    opacity: 0;
    transform: translateY(10px);
}

.bottom-contact.animate {
    animation: fadeInUp 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.contact-link {
    display: inline-block;
    background: rgba(74, 159, 245, 0.1);
    border: none;
    color: #4A9FF5;
    font-size: 0.9rem;
    font-family: inherit;
    cursor: pointer;
    transition: all 0.2s ease;
    padding: 0.65rem 1.5rem;
    text-decoration: none;
    border-radius: 24px;
}

.contact-link:hover {
    background: rgba(74, 159, 245, 0.15);
    transform: translateY(-1px);
    text-decoration: none;
}

.contact-dropdown {
    position: absolute;
    left: 50%;
    transform: translateX(-50%) translateY(10px);
    bottom: 100%;
    margin-bottom: 0.5rem;
    background: rgba(30, 30, 35, 0.95);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 12px;
    padding: 0;
    min-width: 150px;
    opacity: 0;
    visibility: hidden;
    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
    z-index: 10;
}

.contact-dropdown.show {
    opacity: 1;
    visibility: visible;
    transform: translateX(-50%) translateY(0);
}

.contact-dropdown a {
    display: block;
    color: #4A9FF5;
    text-decoration: none;
    padding: 0.5rem 1rem;
    border-radius: 0;
    transition: background 0.2s, color 0.2s;
    width: 100%;
    box-sizing: border-box;
    text-align: left;
    background: none;
}

.contact-dropdown a:hover,
.contact-dropdown a:focus {
    background: rgba(74, 159, 245, 0.08);
    color: #fff;
    outline: none;
}

/* ==========================================================================
   Hero Section
   ========================================================================== */
.hero {
    margin-top: 3rem;
    margin-bottom: 5rem;
    transition: transform 0.8s cubic-bezier(0.16, 1, 0.3, 1);
    will-change: transform;
}

.hero.centered {
    /* Transform will be set by JavaScript based on viewport */
}

.hero.moved-up {
    transform: translateY(0) !important;
    margin-bottom: 4.97rem;
}

.hero h1 {
    font-size: 3.5rem;
    font-weight: 300;
    line-height: 1.3;
    margin-bottom: 0.5rem;
    letter-spacing: -0.02em;
    opacity: 0;
    /* Delayed rather than firing at 0s: a beat of stillness before the first
       words reads as the page composing itself, not as lag. */
    animation: fadeInZoom var(--d-greeting) ease-out var(--t-greeting) forwards;
}

.highlight-name {
    font-weight: 400;
}

.hero-subtitle {
    font-size: 3.5rem;
    font-weight: 300;
    line-height: 1.3;
    color: rgba(243, 231, 212, 0.95);
    letter-spacing: -0.02em;
    opacity: 0;
    /* Opacity only, no rise — it should appear in place like a second chat
       message rather than travel in. The hold before it is what makes it read
       as a reply; see the timeline block at the top of this file. */
    animation: fadeIn var(--d-subtitle) ease-out var(--t-subtitle) forwards;
}

/* ==========================================================================
   Home card hover — one elevation for every clickable card
   ==========================================================================
   The home page has three families of clickable card: work cards, hobby
   project items & thought cards. Only the work cards carry a surface at rest;
   the other two are bare until you point at them. That difference is
   deliberate (see the note above .project-item) — but it only reads as
   deliberate if the HOVER is identical across all three, so all three pull
   their lift surface, shadow & timing from here rather than each inventing
   its own. Change the elevation once, not in three places.

   The bloom (::before) is part of the same system: always sized from the
   card's top-left corner, floods outward on hover over 0.65s. Work cards
   keep their bloom visible at rest because they have a surface to sit on;
   project items fade theirs in, since a floating corner glow with no card
   behind it reads as a smudge. */
:root {
    --card-hover-surface: rgba(255, 255, 255, 0.05);
    --card-hover-shadow: 0 20px 40px rgba(0, 0, 0, 0.25);
    /* Chrome (surface + shadow) settles in 0.35s; the bloom takes 0.65s so it
       is still spreading after the card has arrived. */
    --card-hover-chrome: background 0.35s ease, box-shadow 0.35s ease;
    --card-bloom-ease: cubic-bezier(0.16, 1, 0.3, 1);
}

/* ==========================================================================
   Work Cards Section
   ========================================================================== */
.work-cards {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 2rem;
    margin-bottom: 7rem;
    scroll-snap-align: start;
    scroll-snap-stop: normal;
}

.company-card {
    background: rgba(255, 255, 255, 0.03);
    /* No backdrop-filter here: the hovered card's box-shadow falls across its
       neighbours, and a filtered neighbour re-samples that changed backdrop —
       which produced an intermittent banding artifact on the preceding card.
       There's only a soft gradient behind these anyway, so the blur bought
       nothing visually. */
    border-radius: 16px;
    padding: 2rem;
    transition: var(--card-hover-chrome);
    display: flex;
    flex-direction: column;
    cursor: pointer;
    position: relative;
    overflow: hidden;
}

/* Resting fill drops as the bloom floods in, so the two don't stack. */
.company-card:hover {
    background: rgba(255, 255, 255, 0.01);
    box-shadow: var(--card-hover-shadow);
}

.company-card:hover .company-link {
    text-decoration: underline;
}

.company-logo {
    width: 30px;
    height: 30px;
    margin-bottom: 1.5rem;
    display: flex;
    align-items: center;
    justify-content: flex-start;
}

.company-logo img {
    max-width: 30px;
    max-height: 30px;
    /* object-fit: contain; */
}

.company-description {
    font-size: 0.95rem;
    font-weight: 100;
    line-height: 1.6;
    color: rgba(243, 231, 212, 0.9);
    margin-bottom: 1.5rem;
    flex-grow: 1;
}

.company-name {
    font-weight: 600;
}

.company-name.microsoft {
    color: rgb(var(--accent-microsoft));
}

.company-name.nutanix {
    color: rgb(var(--accent-nutanix));
}

.company-name.citrix {
    color: rgb(var(--accent-citrix));
}

.company-link {
    color: #4A9FF5;
    font-size: 0.9rem;
    text-decoration: none;
    transition: all 0.2s ease;
}

/* --- Metadata line (shared across all variants) --- */
.company-meta {
    display: block;
    font-size: 0.72rem;
    font-weight: 300;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: rgba(243, 231, 212, 0.35);
    margin-bottom: 1rem;
    margin-top: -0.75rem; /* tucks under logo */
}

/* ==========================================================================
   Work Card — Bloom Effect
   Always-on corner glow that floods the card on hover
   ========================================================================== */

.company-card {
    overflow: hidden;
}

.company-card > * {
    position: relative;
    z-index: 1;
}

/* Bloom layer — fills card, anchored top-left */
.company-card::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 0;
    transform: scale(0.5);
    transform-origin: top left;
    transition: transform 0.65s var(--card-bloom-ease),
                opacity 0.4s ease;
}

/* `ellipse farthest-side` (not `circle`) is deliberate: it sizes the falloff
   per-axis, so the tint is fully transparent before every edge of the bloom
   box no matter the card's aspect ratio. With `circle`, the falloff completed
   at 65% of the DIAGONAL, so on wide/short cards the bottom edge was still
   tinted when it hit the box boundary — a visible hard line on narrow screens. */
.company-card[data-company="microsoft"]::before {
    background: radial-gradient(ellipse farthest-side at 0% 0%, rgba(var(--accent-microsoft), 0.28) 0%, rgba(var(--accent-microsoft), 0.08) 40%, transparent 72%);
}
.company-card[data-company="nutanix"]::before {
    background: radial-gradient(ellipse farthest-side at 0% 0%, rgba(var(--accent-nutanix), 0.28) 0%, rgba(var(--accent-nutanix), 0.08) 40%, transparent 72%);
}
.company-card[data-company="citrix"]::before {
    background: radial-gradient(ellipse farthest-side at 0% 0%, rgba(var(--accent-citrix), 0.28) 0%, rgba(var(--accent-citrix), 0.08) 40%, transparent 72%);
}

.company-card:hover::before {
    transform: scale(2);
}

.company-card:hover {
    /* Lift above siblings so the hover shadow paints consistently over both
       neighbours — without this, DOM order meant a card's shadow was drawn
       under the card that follows it. */
    z-index: 2;
}


/* ==========================================================================
   Projects Section - Bifold Layout
   ========================================================================== */
.projects {
    /* One width for the artwork & the lede above it, so the subtitle's measure
       can't drift away from the illustration's edge. The tinkerer SVG's ink
       fills its viewBox exactly (0 inset on all four sides), so matching the
       box width IS the optical alignment — no edge correction needed.

       This can now fill the panel: with the clips playing inside the laptop
       screen there's no second footprint to match, so the old ceiling (keeping
       the artwork the same height as a 540x304 video) is gone. 518 is the
       panel's 540px width less 4% — edge-to-edge read a touch heavy, and the
       trim leaves a sliver of air without reopening real dead space.
       NOTE: .preview-screen's two perspective terms are computed against this
       value. A small change is harmless (see the note there); a large one
       means recomputing them. */
    --preview-art-width: 518px;
    margin-bottom: 7rem;
    padding: 0;
    scroll-snap-align: start;
    scroll-snap-stop: normal;
}

/* Bifold Container - with divider */
.projects-bifold {
    display: flex;
    gap: 0;
    align-items: stretch;
    /* Height is content-driven — the project list (360px for two items) is the
       tallest thing in the row & sets it. This used to be a flat 500px, which
       was ~140px more than anything in it needed: the media is ~305 tall, so
       it sat in a box 60% taller than itself & read as undersized no matter
       how wide the panel got. The floor below is just insurance so the media
       keeps ~20px of air if the list ever gets shorter than the artwork. */
    min-height: 350px;
}

/* Left Panel: the desk illustration & its live laptop screen.
   No radius or overflow here any more — the panel holds no media of its own,
   so there was nothing left for it to clip.

   THE PANEL CARRIES THE ARTWORK'S WIDTH, not a flex ratio. It used to be
   `flex: 7`, which made the panel 540-550 while the artwork inside was capped
   at 518 — so 22-32px of panel sat to the right of anything visible, and the
   divider centred on that invisible edge instead of the artwork's. The gutter
   came out 64px on one side & 32px on the other at 1440. Giving the panel the
   artwork's own width makes the two edges the same edge, so the gutter is
   symmetric by construction at every viewport. It can still shrink (the list's
   min-width wins at narrow widths); the artwork just follows it down. */
.projects-preview {
    flex: 0 1 var(--preview-art-width);
    min-width: 0;
    position: relative;
    display: flex;
    align-items: center;
    justify-content: flex-start;
}

/* --- Section heading (outside the panel — see the note in index.html) ---
   Matches .thoughts h2 exactly so the two sections read as siblings. */
.projects-title {
    font-size: 2.75rem;
    font-weight: 500;
    color: rgba(243, 231, 212, 0.95);
    margin-bottom: 0.5rem;
    letter-spacing: -0.02em;
    line-height: 1.2;
    opacity: 0;
    transform: translateY(20px);
}

.projects-title.animate {
    animation: fadeInUp 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.2s forwards;
}

/* Measure matches the illustration beneath it rather than running 110px past
   its right edge — worth an earlier line wrap to get the two edges to agree. */
.projects-subtitle {
    font-size: 0.95rem;
    font-weight: 200;
    color: rgba(243, 231, 212, 0.8);
    line-height: 1.5;
    margin-bottom: 2rem;
    opacity: 0;
    transform: translateY(20px);
    max-width: var(--preview-art-width);
}

.projects-subtitle.animate {
    animation: fadeInUp 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.2s forwards;
}

/* The stage: the illustration plus the screen overlay registered to it.
   `line-height: 0` stops the inline <img> from adding a descender gap, which
   would put the artwork & the overlay's percentages out of register. */
.preview-stage {
    position: relative;
    width: 100%;
    max-width: var(--preview-art-width);
    line-height: 0;
}

.preview-art {
    display: block;
    width: 100%;
    height: auto;
    opacity: 0;
    transform: translateY(20px);
}

.preview-art.animate {
    animation: fadeInUp 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.5s forwards;
}

/* --- The laptop screen ---
   An exact projective map onto the drawn screen. Two things this gets right
   that an affine shear could not:

   1. THE CORNERS ARE EDGE INTERSECTIONS, NOT PATH POINTS. The screen path has
      rounded corners, so every "corner" is a pair of fillet anchors sitting
      INSIDE the true corner. Taking those as corners shrank the quad by 1-3
      units on each side. These four come from intersecting the straight runs:
          TL(117.473, 16.241)  TR(211.126,  6.111)
          BR(227.353, 74.281)  BL(130.057, 83.935)
   2. IT'S A TRAPEZOID, NOT A PARALLELOGRAM. Bottom is 3.8% longer than top &
      right 1.8% longer than left. A shear can't produce that, so the error
      piled up toward the bottom-right — a few px of drift at render size,
      which reads as "not quite seated" without being obviously wrong.

   The fix is a homography (unit square -> quad) written as matrix3d: the m14
   & m24 slots carry the projective terms an affine matrix has no room for.
   Verified to land all four corners with 0px error.

   Position & size are the quad's BOUNDING BOX as percentages, so the overlay
   still tracks the artwork at any width. The two perspective terms are the one
   part that's tied to scale (they're per-pixel, computed at 518px of artwork);
   they encode a ~3% correction, so the ~4% of width variation across the
   desktop range moves the result by ~0.1% — far below a pixel. Recompute them
   only if --preview-art-width changes materially. */
.preview-screen {
    position: absolute;
    left: 33.468%;
    top: 2.115%;
    width: 31.305%;
    height: 26.929%;
    transform-origin: top left;
    transform: matrix3d(
        0.842092, -0.092191, 0, -0.000074,
        0.155967,  0.834394, 0, -0.000309,
        0,         0,        1,  0,
        0,        14.949687, 0,  1
    );
    overflow: hidden;
    border-radius: 2px;
    pointer-events: none;
}

/* At rest every clip is transparent, so the illustration's own cream screen
   shows through & the laptop reads as switched off. Hover lights it up. */
.preview-screen video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    /* The drawn screen is ~1.41 & the clips are ~1.78, so `cover` crops the
       sides rather than letterboxing — at this size there's nothing to read
       anyway, and a letterbox would show as bars inside the bezel. */
    object-fit: cover;
    border-radius: 0; /* corner rule #1 — .preview-screen does the clipping */
    opacity: 0;
    transition: opacity 0.35s ease;
}

.preview-screen video.is-live {
    opacity: 1;
}


/* Divider between panels */
/* `flex: 0 0 auto` so the gutter never shrinks — if it did, the panel and the
   divider would give up width together and the symmetric gap would drift. */
.projects-divider {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    padding: 0 2rem;
    opacity: 0.91;
}

/* 155 (was 130) renders ~337 tall. At 130 the plant was 283 in a 427 column —
   34% slack, and it read as floating again once the section grew back around
   it. This puts it at ~21%, roughly where it sat when the section was tightest. */
.divider-decoration {
    width: 155px;
    height: auto;
    max-height: 350px;
}

/* SVG Path Animations */
#stalk {
    fill: #D8D8D8;
    stroke: #D8D8D8;
    stroke-width: 1;
    clip-path: inset(0 0 100% 0);
}

.projects-divider.animate #stalk {
    animation: drawStalk 0.23s ease-out 0.2s forwards;
}

@keyframes drawStalk {
    from {
        clip-path: inset(0 0 100% 0);
    }
    to {
        clip-path: inset(0 0 0% 0);
    }
}

/* Leaf Animations - alternating right-left pattern */
#leaf1, #leaf2, #leaf3, #leaf4, #leaf5, #leaf6 {
    opacity: 0;
    transform: scale(0.3);
    transform-origin: center;
}

/* Order: 1 (right), 6 (left), 2 (right), 5 (left), 3 (right), 4 (left) */
.projects-divider.animate #leaf1 {
    animation: leafAppear 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) 0.5s forwards;
}

.projects-divider.animate #leaf6 {
    animation: leafAppear 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) 0.9s forwards;
}

.projects-divider.animate #leaf2 {
    animation: leafAppear 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) 1.2s forwards;
}

.projects-divider.animate #leaf5 {
    animation: leafAppear 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) 1.4s forwards;
}

.projects-divider.animate #leaf3 {
    animation: leafAppear 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) 1.6s forwards;
}

.projects-divider.animate #leaf4 {
    animation: leafAppear 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) 1.8s forwards;
}

@keyframes leafAppear {
    0% {
        opacity: 0;
        transform: scale(0.3);
    }
    100% {
        opacity: 1;
        transform: scale(1);
    }
}

/* Leaf Fill Animations */
#leaf1-fill, #leaf6-fill {
    transition: opacity 0.3s ease-in-out;
    pointer-events: none;
}

#leaf1-fill.show, #leaf6-fill.show {
    opacity: 1 !important;
}

/* Right Panel: Project List */
/* `flex: 1 1 0` — takes whatever the panel & gutter leave, rather than a share
   of the row. min-width is the floor that makes the panel shrink first at the
   narrow end of desktop, instead of squeezing these two cards to nothing. */
.projects-list {
    flex: 1 1 0;
    min-width: 220px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 1rem;
    padding-left: 0;
}

/* Project Items (No Card Backgrounds) */
/* Inline project thumbnail: hidden on desktop, where the large hover-driven
   preview panel does this job. Shown at <=1024px (see media query). */
.project-thumb {
    display: none;
}

/* No surface at rest — on purpose. These sit in the narrow half of the bifold
   opposite a large media panel; giving them work-card chrome would put three
   competing surfaces in one row & make the column read heavier than the
   preview it's meant to drive. The hover is what makes that legible as a
   choice, so it matches the work cards exactly (see --card-hover-* above).
   Explicit transition properties, not `all`: the entrance animation owns
   opacity & transform, and `all` had the transition fighting it. */
.project-item {
    padding: 1.5rem;
    border-radius: 12px;
    transition: var(--card-hover-chrome);
    cursor: pointer;
    background: transparent;
    outline: none;
    opacity: 0;
    transform: translateX(-30px);
    position: relative;
    overflow: hidden;
}

/* Animation states */
.project-item.animate {
    animation: slideInProject 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.project-item:nth-child(1).animate {
    animation-delay: 0.5s;
}

.project-item:nth-child(2).animate {
    animation-delay: 0.9s;
}

.project-item:nth-child(3).animate {
    animation-delay: 1.3s;
}

@keyframes slideInProject {
    0% {
        opacity: 0;
        transform: translateX(-30px);
    }
    100% {
        opacity: 1;
        transform: translateX(0);
    }
}

/* Bloom layer — mirrors work card pattern, anchored top-left */
.project-item > * {
    position: relative;
    z-index: 1;
}

.project-item::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 0;
    opacity: 0;
    transform: scale(0.5);
    transform-origin: top left;
    transition: transform 0.65s var(--card-bloom-ease),
                opacity 0.4s ease;
    /* Deliberately NOT `border-radius: inherit` — corner rule #1, round on one
       surface only. The item already clips this layer with overflow: hidden,
       so a radius here is a second curve on the same corner. Worse, the layer
       is scaled: at scale(2) an inherited 12px renders as a 24px arc tucked
       INSIDE the item's 12px clip, leaving an untinted wedge that read as a
       dark notch in the top-left corner. The work-card bloom never had one. */
    border-radius: 0;
}

/* Floods outward exactly like the work cards rather than cross-fading at full
   size — a fade-in at full coverage arrives everywhere at once & reads as an
   instant switch, which is what made this feel unrelated to the cards above.
   Unlike the work cards the bloom is invisible at rest: with no surface
   underneath, a permanent corner glow reads as a smudge, not a card edge. */
.project-item:hover::before,
.project-item:focus::before {
    opacity: 1;
    transform: scale(2);
}

/* Keyed off [data-project], mirroring the work cards' [data-company] — a hue
   is identity, so it must travel with the project, not with its position.
   (The .animate delays above stay nth-child: a stagger IS positional.) */

/* `ellipse farthest-side`, matching the work cards & for the same reason:
   these items are much wider than they are tall, so a `circle` falloff sized
   off the diagonal was still tinted when it reached the bottom edge. */

/* Crowd Lens — bright yellow-gold from icon */
.project-item[data-project="crowdlens"]::before {
    background: radial-gradient(ellipse farthest-side at 0% 0%, rgba(var(--accent-crowdlens), 0.32) 0%, rgba(var(--accent-crowdlens), 0.08) 40%, transparent 72%);
}

/* Skyflow — sky blue from icon */
.project-item[data-project="skyflow"]::before {
    background: radial-gradient(ellipse farthest-side at 0% 0%, rgba(var(--accent-skyflow), 0.28) 0%, rgba(var(--accent-skyflow), 0.08) 40%, transparent 72%);
}

/* Hover/Focus Background Fill Effect */
.project-item:hover,
.project-item:focus {
    background: var(--card-hover-surface);
    box-shadow: var(--card-hover-shadow);
    /* Same reason as .company-card:hover — the shadow must paint over the
       sibling below it, not under it. */
    z-index: 2;
}

.project-item:focus {
    outline: 2px solid rgba(74, 159, 245, 0.5);
    outline-offset: 2px;
}

.project-header {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0.5rem;
}

.project-icon {
    width: 30px;
    height: 30px;
    border-radius: 6px;
    flex-shrink: 0;
}

.project-name {
    font-size: 1.1rem;
    font-weight: 500;
    color: rgba(243, 231, 212, 0.95);
    margin: 0;
}

.project-description {
    font-size: 0.95rem;
    font-weight: 200;
    line-height: 1.6;
    color: rgba(243, 231, 212, 0.9);
    margin: 0 0 0.75rem 0;
}

.project-link {
    color: #4A9FF5;
    font-size: 0.9rem;
    text-decoration: none;
    transition: all 0.2s ease;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

.project-link:hover,
.project-link:focus {
    text-decoration: underline;
    opacity: 0.8;
}

.project-link svg {
    width: 14px;
    height: 14px;
    opacity: 0.7;
}

/* ==========================================================================
   Thoughts Section
   ========================================================================== */
.thoughts {
    margin-bottom: 5rem;
    scroll-snap-align: start;
    scroll-snap-stop: normal;
}

.thoughts-title {
    margin-bottom: 0;
}

.typing-text {
    display: inline;
}

.typing-cursor {
    display: inline-block;
    width: 2px;
    height: 0.85em;
    background: currentColor;
    margin-left: 2px;
    vertical-align: text-bottom;
    opacity: 1;
    animation: cursorBlink 0.9s ease-in-out infinite;
}

.typing-cursor.done {
    animation: cursorFadeOut 0.6s ease-out 0.8s forwards;
}

@keyframes cursorBlink {
    0%, 100% { opacity: 1; }
    50% { opacity: 0; }
}

@keyframes cursorFadeOut {
    to { opacity: 0; }
}

.thoughts h2 {
    font-size: 2.75rem;
    font-weight: 500;
    margin-bottom: 0;
    color: rgba(243, 231, 212, 0.95);
    letter-spacing: -0.02em;
    line-height: 1.2;
}

/* --- Thought Editorial Grid --- */
.thoughts-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 2.5rem 2rem;
    margin-top: 2rem;
}

.thought-card {
    display: flex;
    flex-direction: column;
    text-decoration: none;
    text-decoration-color: transparent;
    color: inherit;
    opacity: 0;
    transform: translateY(14px);
    cursor: pointer;
    border-radius: 16px;
    padding: 0.5rem 0.5rem 1.1rem;
    margin: -0.5rem -0.5rem -1.1rem;
    position: relative;
    transition: var(--card-hover-chrome);
}

.thought-card,
.thought-card:hover,
.thought-card:focus,
.thought-card:focus-visible,
.thought-card:active,
.thought-card:visited {
    text-decoration: none;
}

/* Same surface & shadow as the work and hobby cards. No bloom here: the cover
   image occupies the top two-thirds opaquely, so a corner glow would only be
   visible in the text strip below it & would read as a rendering fault. The
   cover's own brighten + scale is this family's equivalent gesture. */
.thought-card:hover,
.thought-card:focus-visible {
    background: var(--card-hover-surface);
    box-shadow: var(--card-hover-shadow);
    z-index: 2;
}

/* --- Unpublished tile ---
   A post that's announced but not written yet. Rendered as a <div>, not an
   <a>, so there is nothing to click or tab to; everything below just makes
   sure it doesn't PRETEND to be clickable. It keeps its cover & entrance
   animation so the grid rhythm is unbroken. */
.thought-card.is-upcoming {
    cursor: default;
}

.thought-card.is-upcoming:hover,
.thought-card.is-upcoming:focus-visible {
    background: transparent;
    box-shadow: none;
}

.thought-card.is-upcoming:hover .thought-card-cover::after,
.thought-card.is-upcoming:hover .thought-card-cover img {
    filter: none;
    transform: none;
}

/* Held back from the published tiles beside it — enough to read as "not yet",
   not so much that it looks broken or unreadable. Cover carries most of the
   dimming since it's the largest surface & has no text to protect; the copy
   steps down only slightly. */
.thought-card.is-upcoming .thought-card-cover {
    opacity: 0.5;
}

.thought-card.is-upcoming .thought-card-title,
.thought-card.is-upcoming:hover .thought-card-title {
    color: rgba(243, 231, 212, 0.62);
}

.thought-card.is-upcoming .thought-card-desc {
    color: rgba(243, 231, 212, 0.38);
}

/* Sits BELOW the description, not above the title: as an eyebrow it pushed the
   title down & broke the top alignment the tile shares with its neighbours.
   Reading last also matches what it is — a note about the post, not a label
   the post hangs under. Deliberately the one thing NOT dimmed, so the status
   still registers against the held-back copy above it. */
.thought-card-status {
    font-size: 0.68rem;
    font-weight: 400;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: rgba(243, 231, 212, 0.45);
    margin: 0.25rem 0 0;
}

/* Cover image — fully rounded, stays consistent pre and post hover */
.thought-card-cover {
    width: 100%;
    aspect-ratio: 4 / 3;
    /* Was a hardcoded 10px — same value, but via the token it now follows
       corner rule #4 & steps down to 8px on small viewports like every other
       small media surface. */
    border-radius: var(--radius-media-sm);
    position: relative;
    overflow: hidden;
}

/* Noise grain layer */
.thought-card-cover::before {
    content: '';
    position: absolute;
    inset: 0;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='225'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.72' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='300' height='225' filter='url(%23n)' opacity='0.14'/%3E%3C/svg%3E");
    background-size: cover;
    z-index: 1;
    border-radius: inherit;
    pointer-events: none;
}

/* Color tint base */
.thought-card-cover::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    border-radius: inherit;
    transition: filter 0.4s ease;
}

.thought-card[data-tint="green"] .thought-card-cover::after {
    background: radial-gradient(ellipse at 25% 35%, rgba(28, 80, 52, 0.9) 0%, rgba(8, 24, 16, 0.97) 100%);
}
.thought-card[data-tint="blue"] .thought-card-cover::after {
    background: radial-gradient(ellipse at 70% 25%, rgba(18, 50, 85, 0.9) 0%, rgba(6, 14, 30, 0.97) 100%);
}
.thought-card[data-tint="teal"] .thought-card-cover::after {
    background: radial-gradient(ellipse at 55% 30%, rgba(12, 62, 68, 0.9) 0%, rgba(4, 18, 24, 0.97) 100%);
}
.thought-card[data-tint="amber"] .thought-card-cover::after {
    background: radial-gradient(ellipse at 35% 55%, rgba(72, 44, 12, 0.9) 0%, rgba(20, 12, 4, 0.97) 100%);
}

.thought-card:hover .thought-card-cover::after,
.thought-card:focus-visible .thought-card-cover::after {
    filter: brightness(1.18);
}

/* Real image support — hides generated layers */
.thought-card-cover:has(img)::before,
.thought-card-cover:has(img)::after {
    display: none;
}

.thought-card-cover img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    border-radius: 0;
    transition: transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}

.thought-card:hover .thought-card-cover img,
.thought-card:focus-visible .thought-card-cover img {
    transform: scale(1.04);
}

/* Text flows directly below the image cover */
.thought-card-body {
    padding: 0.85rem 0.25rem 0;
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
}

.thought-card-title {
    font-size: 1.15rem;
    font-weight: 400;
    color: rgba(243, 231, 212, 0.92);
    line-height: 1.35;
    margin: 0;
    transition: color 0.2s ease;
}

.thought-card:hover .thought-card-title,
.thought-card:focus-visible .thought-card-title {
    color: rgba(243, 231, 212, 1);
}

.thought-card-desc {
    font-size: 0.83rem;
    font-weight: 200;
    color: rgba(243, 231, 212, 0.48);
    line-height: 1.6;
    margin: 0;
}

/* Entrance animations */
.thoughts.animate .thought-card {
    animation: fadeInUp 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.thoughts.animate .thought-card:nth-child(1) { animation-delay: 0.55s; }
.thoughts.animate .thought-card:nth-child(2) { animation-delay: 0.68s; }
.thoughts.animate .thought-card:nth-child(3) { animation-delay: 0.81s; }
.thoughts.animate .thought-card:nth-child(4) { animation-delay: 0.94s; }
.thoughts.animate .thought-card:nth-child(5) { animation-delay: 1.07s; }
.thoughts.animate .thought-card:nth-child(6) { animation-delay: 1.20s; }

/* ==========================================================================
   Footer
   ========================================================================== */
.footer {
    text-align: center;
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
    margin-top: 0;
    opacity: 0;
    transform: translateY(10px);
}

.footer.animate {
    animation: fadeInUp 0.6s ease-out 0.2s forwards;
}

.footer p {
    font-size: 0.85rem;
    font-weight: 400;
}

/* ==========================================================================
   Animations
   ========================================================================== */
/* Remove animation for now - focusing on static gradient border first */
@keyframes fadeInUp {
    from {
        opacity: 0;
        transform: translateY(20px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@keyframes fadeIn {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

@keyframes fadeInZoom {
    from {
        opacity: 0;
        transform: scale(0.97);
    }
    to {
        opacity: 1;
        transform: scale(1);
    }
}

/* Staggered animations for elegant entrance */

.work-cards .company-card {
    opacity: 0;
}

/* Delays below are relative to --t-cards — index.html adds .animate at that
   moment, the same instant the hero starts moving up, so the cards read as
   what displaced it. */
.work-cards .company-card.animate {
    animation: fadeInUp 0.7s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}

.work-cards .company-card:nth-child(1).animate {
    animation-delay: 0s;
}

.work-cards .company-card:nth-child(2).animate {
    animation-delay: 0.25s;
}

.work-cards .company-card:nth-child(3).animate {
    animation-delay: 0.5s;
}

/* Projects and Thoughts animations - triggered by JavaScript */

.thoughts {
    opacity: 0;
}

.thoughts.animate {
    animation: fadeInUp 0.8s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}

/* ==========================================================================
   Micro-interactions & Polish
   ========================================================================== */

/* Smooth focus states for accessibility */
*:focus {
    outline: 2px solid rgba(74, 159, 245, 0.5);
    outline-offset: 2px;
    border-radius: 4px;
}

/* Prevent text selection on interactive elements */
.company-card,
.project-card,
.contact-link {
    user-select: none;
}

/* ==========================================================================
   Project Pages
   ========================================================================== */
.project-page {
    background: linear-gradient(180deg, #110C07 0%, rgba(26, 24, 22, 0.98) 100%);
    background-attachment: fixed;
}

.project-page .container {
    max-width: 1100px;
}

.project-page .top-header {
    margin-bottom: 3rem;
}

.project-page .top-header a {
    color: #4A9FF5;
    font-size: 0.95rem;
    text-decoration: none;
    transition: opacity 0.2s ease;
}

.project-page .top-header a:hover {
    opacity: 0.8;
}

/* Hide the global header on home — home gets an inline bottom nav instead */
body[data-page="home"] .site-header {
    display: none;
}

/* Bottom nav (home only) — same pill content, but lives at the end of the
   page, not sticky, and its dropdowns open upward since it sits at the bottom. */
.site-bottom-nav {
    display: flex;
    align-items: center;
    justify-content: center;
    width: fit-content;
    max-width: calc(100vw - 2rem);
    margin: 4rem auto 2rem;
    padding: 0.4rem 0.65rem;
    gap: 0.25rem;
    background: rgba(20, 18, 15, 0.55);
    -webkit-backdrop-filter: blur(20px) saturate(180%);
    backdrop-filter: blur(20px) saturate(180%);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 100px;
    box-shadow:
        0 12px 40px rgba(0, 0, 0, 0.35),
        0 1px 0 rgba(255, 255, 255, 0.05) inset;
    position: relative;
    z-index: 50;
    opacity: 0;
    transform: translateY(10px);
}

.site-bottom-nav.animate {
    animation: fadeInUp 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.site-bottom-nav .logo-btn {
    padding: 0.35rem 0.85rem;
    font-size: 1rem;
}

/* Opens upward, so the slide starts below its resting place rather than above.
   Only the token flips — the generic open rule still lands it at 0. */
.site-bottom-nav .nav-dropdown {
    top: auto;
    bottom: calc(100% + var(--dd-gap));
    --dd-y: 6px;
}

/* Inner pages — sticky-to-dock handoff.
   The nav is the same component in two places: the sticky pill at the top &
   an inline copy at the end of the page. Reaching the end of the page adds
   `nav-docked` to <body>, which fades the sticky one out & plops this one
   into place. It reverses on scroll back up. Scoped to `.project-page`, which
   every page except home carries — home has its own always-inline bottom nav.

   The undocked state deliberately rests at `transform: none` — the plop is a
   keyframe animation, not a transition between two transforms. A persistent
   translate would shift the pill's box relative to the observer boundary the
   moment it docked & bounce the state back off. See initNavDock() in
   script.js, which watches a sentinel below the pill (never transformed)
   rather than the pill itself. */
.project-page .site-bottom-nav {
    margin: 2.5rem auto 0;
    opacity: 0;
    visibility: hidden;
    /* Overrides the base rule's translateY(10px) — that's the home page's
       pre-`.animate` offset. Here the resting transform must be none, or the
       pill snaps back down once navPlop (which has no fill mode) finishes. */
    transform: none;
    transition: opacity 0.3s ease, visibility 0s linear 0.3s;
}

/* The gap above the pill is set on the pill alone. The closing section hands
   its bottom margin over so the two don't collapse to the larger of the pair,
   which would sit the pill lower than the value above reads & delay the dock. */
.project-page .ms-closing,
.project-page .cs-closing {
    margin-bottom: 0;
}

.project-page.nav-docked .site-bottom-nav {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.28s ease-out, visibility 0s;
    animation: navPlop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.project-page.nav-docked .site-header {
    opacity: 0;
    visibility: hidden;
    transform: translateY(-10px) scale(0.97);
    pointer-events: none;
    transition: opacity 0.3s ease, transform 0.3s ease, visibility 0s linear 0.3s;
}

@keyframes navPlop {
    from { transform: translateY(-16px) scale(0.97); }
    to   { transform: none; }
}

/* The dock trigger. Its TOP edge is what the observer watches, and that edge
   sits flush against the bottom of the pill — the trailing space is carried
   as padding, which extends the box downward without pushing the trigger
   line down with it. (As margin it would do both, and every pixel of gap
   under the pill would become a pixel of scrolling before the nav appears.) */
.nav-dock-sentinel {
    height: 0;
    margin: 0;
    padding-bottom: 2.5rem;
}

@media (prefers-reduced-motion: reduce) {
    .project-page.nav-docked .site-bottom-nav {
        animation: none;
    }
    .project-page.nav-docked .site-header {
        transform: none;
    }
}

/* ==========================================================================
   Home entrance — reduced-motion fallback
   ==========================================================================
   Everything still arrives, in the same order, as a plain crossfade: no
   travel, no scale, no spring, and no holds. Two separate things get removed
   and they're easy to conflate:

   1. MOTION. Every entrance keyframe here is opacity + transform. Swapping in
      `fadeIn` kills the transform in the ANIMATION but not the one on the
      element's resting state — those `translateY(20px)` / `translateX(-30px)`
      / `scale(0.3)` values are declared on the base rule as the pre-animation
      offset. Leave them and the element fades in permanently displaced, which
      is worse than the animation was. **Every selector whose base rule sets a
      transform must also be reset to `none` here.** That's the long list
      below; it is not redundant with the `animation` overrides.
   2. WAITING. The timeline tokens collapse to a fast ripple. The full 2.25s
      hero entrance is built on two deliberate silences (see the timeline
      block at the top of this file) — but a hold only reads as intentional
      if something is moving through it. With the motion gone it's just a
      blank page, so the beats compress to ~0.1s apart: enough to keep the
      top-to-bottom order legible, not enough to be a wait.

   index.html reads these same tokens, so the JS-driven beats (work cards,
   projects, thoughts, footer) follow automatically. It separately skips
   centring the hero, since that transform is applied as an inline style. */
@media (prefers-reduced-motion: reduce) {
    :root {
        --t-greeting: 0s;
        --d-greeting: 0.3s;
        --t-subtitle: 0.1s;
        --d-subtitle: 0.3s;
        --t-cards:    0.2s;
        --t-projects: 0.35s;
        --t-thoughts: 0.5s;
        --t-footer:   0.65s;
    }

    /* The hero never travels — index.html skips centring it, this covers the
       transition & any inline transform already applied. */
    .hero {
        transition: none;
        transform: none !important;
    }

    /* Opacity-only entrances, everywhere the cascade touches. */
    .hero h1,
    .work-cards .company-card.animate,
    .projects-title.animate,
    .projects-subtitle.animate,
    .preview-art.animate,
    .project-item.animate,
    .thoughts.animate,
    .thoughts.animate .thought-card,
    .projects-divider.animate #leaf1,
    .projects-divider.animate #leaf2,
    .projects-divider.animate #leaf3,
    .projects-divider.animate #leaf4,
    .projects-divider.animate #leaf5,
    .projects-divider.animate #leaf6,
    .site-bottom-nav.animate,
    .footer.animate {
        animation-name: fadeIn;
        animation-duration: 0.3s;
        animation-timing-function: ease-out;
    }

    /* Resting offsets, cleared. See note 1 above — without these the fade
       lands the element 20px low / 30px left / at 30% scale, permanently. */
    .projects-title,
    .projects-subtitle,
    .preview-art,
    .project-item,
    .thought-card,
    .site-bottom-nav,
    .footer,
    #leaf1, #leaf2, #leaf3, #leaf4, #leaf5, #leaf6 {
        transform: none;
    }

    /* Within-section staggers collapse: the section arrives as one piece
       rather than as a queue the visitor has to wait out. */
    .projects-title.animate,
    .projects-subtitle.animate,
    .preview-art.animate,
    .footer.animate,
    .work-cards .company-card:nth-child(1).animate,
    .work-cards .company-card:nth-child(2).animate,
    .work-cards .company-card:nth-child(3).animate,
    .project-item:nth-child(1).animate,
    .project-item:nth-child(2).animate,
    .project-item:nth-child(3).animate,
    .projects-divider.animate #leaf1,
    .projects-divider.animate #leaf2,
    .projects-divider.animate #leaf3,
    .projects-divider.animate #leaf4,
    .projects-divider.animate #leaf5,
    .projects-divider.animate #leaf6,
    .thoughts.animate .thought-card:nth-child(1),
    .thoughts.animate .thought-card:nth-child(2),
    .thoughts.animate .thought-card:nth-child(3),
    .thoughts.animate .thought-card:nth-child(4),
    .thoughts.animate .thought-card:nth-child(5),
    .thoughts.animate .thought-card:nth-child(6) {
        animation-delay: 0s;
    }

    /* The stalk is a clip-path wipe, not a transform — it can't be faded, so
       it's simply already drawn. */
    #stalk {
        clip-path: inset(0 0 0% 0);
    }
    .projects-divider.animate #stalk {
        animation: none;
    }

    /* The "Thoughts" heading types itself out a character at a time — motion
       made of text. index.html prints it in one go instead; the cursor would
       be left blinking forever with nothing to type, so it's retired here. */
    .typing-cursor {
        display: none;
    }
}

/* Site Header — centered, sticky pill */
.site-header {
    display: flex;
    align-items: center;
    justify-content: center;
    width: fit-content;
    max-width: calc(100vw - 2rem);
    margin: 0 auto 2rem;
    padding: 0.4rem 0.65rem;
    gap: 0.25rem;
    background: rgba(20, 18, 15, 0.55);
    -webkit-backdrop-filter: blur(20px) saturate(180%);
    backdrop-filter: blur(20px) saturate(180%);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 100px;
    box-shadow:
        0 12px 40px rgba(0, 0, 0, 0.35),
        0 1px 0 rgba(255, 255, 255, 0.05) inset;
    position: sticky;
    top: 1rem;
    z-index: 100;
    transition: opacity 0.3s ease, transform 0.3s ease;
}

.site-header .logo-btn {
    padding: 0.35rem 0.85rem;
    font-size: 1rem;
}


/* Logo Nav Button */
.logo-btn {
    background: none;
    border: none;
    color: #F3E7D4;
    font-family: 'komet', sans-serif;
    font-size: 1.1rem;
    font-weight: 400;
    cursor: pointer;
    padding: 0.4rem 0;
    transition: color 0.2s ease;
}

.logo-at {
    color: rgba(243, 231, 212, 0.35);
    font-weight: 300;
    margin: 0 0.15rem;
}

.logo-context {
    font-weight: 300;
    position: relative;
    display: inline-block;
}

/* Brand accent tokens — one hue per entity, and the ONLY place each is
   defined. Every use derives from these:
     work cards on home  → .company-name.{co} (logo-matched name text)
                           .company-card[data-company]::before (bloom)
     hobby projects      → .project-item:nth-child(n)::before (bloom)
     case pages          → --squiggle-color (logo underline)
     nav dropdown        → a[data-accent] hover + current wash
   Bare RGB triples so they drop into rgba() at any alpha. */
:root {
    --accent-microsoft: 0, 164, 239;
    --accent-nutanix:   0, 212, 170;
    --accent-citrix:    255, 142, 83;
    --accent-crowdlens: 245, 200, 66;
    --accent-skyflow:   91, 184, 245;
}

/* Per-page squiggle color (item 2) */
:root {
    --squiggle-color: #F3E7D4;
}
body[data-page="microsoft"] { --squiggle-color: rgb(var(--accent-microsoft)); }
body[data-page="nutanix"]   { --squiggle-color: rgb(var(--accent-nutanix)); }
body[data-page="citrix"]    { --squiggle-color: rgb(var(--accent-citrix)); }
body[data-page="blog"]      { --squiggle-color: #F3E7D4; }
body[data-page="home"]      { --squiggle-color: #F3E7D4; }

/* Handwritten-style squiggly underline on hover */
.logo-btn .logo-context::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: -8px;
    height: 6px;
    background-color: var(--squiggle-color, #F3E7D4);
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 8' preserveAspectRatio='none'%3E%3Cpath d='M0,4Q5,0,10,4T20,4T30,4T40,4' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 8' preserveAspectRatio='none'%3E%3Cpath d='M0,4Q5,0,10,4T20,4T30,4T40,4' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
    -webkit-mask-repeat: repeat-x;
    mask-repeat: repeat-x;
    -webkit-mask-size: 36px 6px;
    mask-size: 36px 6px;
    -webkit-mask-position: left center;
    mask-position: left center;
    opacity: 1;
    transform: translateY(0);
    pointer-events: none;
}

/* The logo menu centres on the PILL, not on the logo button — and the pill is
   what's centred on the page, so this is what reads as centred.
   It used to be `left: 0` off the button, opening rightward. The button sits
   left-of-centre in the pill, so at phone widths the menu ran ~68px past the
   viewport and the document scrolled sideways *even with the menu closed*
   (`visibility: hidden` still counts toward scroll width).
   Centring on the pill needs the pill as the containing block, so the logo
   wrap gives up `position: relative`. The contact wrap keeps its own — that
   menu is right-anchored to the wave button and is working as-is. */
.logo-nav.nav-dropdown-wrap {
    position: static;
}

.logo-nav .nav-dropdown {
    left: 50%;
    right: auto;
    --dd-x: -50%;
    /* Anchored off the pill now rather than the button, so hand back the
       pill's own padding — the containing block is its padding box, so the
       border is already out. Lands the menu on the same line as the contact
       menu, which still measures from inside. */
    --dd-gap: calc(0.5rem - 0.4rem);
}

.logo-dropdown .current {
    color: rgba(243, 231, 212, 0.95);
    background: rgba(255, 255, 255, 0.04);
}

/* Colour hints in the dropdown — each work & project item carries the same
   hue its card blooms with on home, so hovering the menu echoes hovering the
   card, and the current page's row holds the colour its squiggle is drawing.
   Kept as a low-alpha wash rather than coloured text: at 0.85rem the hues go
   muddy against the cream, and a wash is the same gesture the cards make.
   `data-accent` comes from NAV_CONFIG via renderNavItem() in script.js. */
.logo-dropdown a[data-accent="microsoft"] { --nav-accent: var(--accent-microsoft); }
.logo-dropdown a[data-accent="nutanix"]   { --nav-accent: var(--accent-nutanix); }
.logo-dropdown a[data-accent="citrix"]    { --nav-accent: var(--accent-citrix); }
.logo-dropdown a[data-accent="crowdlens"] { --nav-accent: var(--accent-crowdlens); }
.logo-dropdown a[data-accent="skyflow"]   { --nav-accent: var(--accent-skyflow); }

.logo-dropdown a[data-accent]:hover,
.logo-dropdown a[data-accent]:focus-visible {
    background: rgba(var(--nav-accent), 0.16);
    color: rgba(243, 231, 212, 0.98);
}

/* Current page holds its hue as a wash only — no ring. An inset border reads
   as a second colour sitting next to the fill rather than the same one, which
   is too much for a menu this small. The wash sits above the hover alpha so
   "held on" still outranks "pointing at", & the brighter text from
   `.logo-dropdown .current` carries the rest of the signal. */
.logo-dropdown a[data-accent].current {
    background: rgba(var(--nav-accent), 0.18);
}

.logo-dropdown a[data-accent].current:hover {
    background: rgba(var(--nav-accent), 0.26);
}

/* Natural content width is ~334px, so the 350px floor — not the content — is
   what made this menu unable to fit a 375px phone. Below the breakpoint the
   floor is dropped and `max-content` governs. The clamp is the same
   `100vw - 2rem` the pill itself uses, so the menu can never be wider than the
   thing it hangs off; if content ever outgrows that, `.dropdown-section-items`
   already wraps. */
.logo-dropdown.nav-dropdown {
    min-width: 350px;
    width: max-content;
    max-width: calc(100vw - 2rem);
    padding: 0.75rem;
}

@media (max-width: 768px) {
    .logo-dropdown.nav-dropdown {
        min-width: 0;
    }
}


.dropdown-section {
    display: grid;
    grid-template-columns: 80px 1fr;
    gap: 0 30px;
    /* NOT `baseline`. The label is a flex row whose first item is an SVG, so
       the baseline it exports is the icon's bottom edge, not its text's — and
       the label is 0.75rem against the items' 0.85rem. Together those sat the
       label ~2.4px above the items in every row. Centring the two boxes is
       what actually reads as aligned here. */
    align-items: center;
    padding: 0.45rem 0;
}

.dropdown-section-label {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.75rem;
    font-weight: 400;
    color: rgba(243, 231, 212, 0.4);
    padding-left: 0.75rem;
    letter-spacing: 0.03em;
    white-space: nowrap;
}

.dropdown-section-label svg {
    opacity: 0.5;
    flex-shrink: 0;
}

.dropdown-section-items {
    display: flex;
    flex-wrap: wrap;
    gap: 0;
}

.logo-dropdown .dropdown-section-items a {
    display: inline-block;
    padding: 0.35rem 0.6rem;
    white-space: nowrap;
    flex: none;
}

/* Rows that leave the site (Crowd Lens & Skyflow, while they still point at
   their live sites) carry the arrow mark. Inline-flex only on those rows, so
   every other row keeps its inline-block box untouched. */
.logo-dropdown .dropdown-section-items a.has-ext {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

/* Held back from the label — it's a hint about where the row goes, not part of
   the name. Comes up to match on hover, alongside the accent wash. */
.logo-dropdown .nav-ext {
    flex-shrink: 0;
    opacity: 0.45;
    transition: opacity 0.2s ease;
}

.logo-dropdown a:hover .nav-ext,
.logo-dropdown a:focus-visible .nav-ext {
    opacity: 0.8;
}

.logo-btn {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.logo-text {
    font-weight: 400;
}

/* Site Nav */
.site-nav {
    display: flex;
    align-items: center;
    gap: 0.25rem;
}

.nav-btn {
    background: none;
    border: 1px solid transparent;
    color: rgba(243, 231, 212, 0.6);
    font-family: 'komet', sans-serif;
    font-size: 0.85rem;
    font-weight: 300;
    padding: 0.4rem 0.75rem;
    border-radius: 8px;
    cursor: pointer;
    transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}

.nav-btn:hover {
    color: rgba(243, 231, 212, 0.9);
    background: rgba(255, 255, 255, 0.04);
    border-color: rgba(255, 255, 255, 0.08);
}

.nav-contact {
    display: flex;
    align-items: center;
    text-decoration: none;
    margin-left: 0.25rem;
}

/* Nav Dropdowns */
.nav-dropdown-wrap {
    position: relative;
}

/* The transform carries two independent jobs, so they live in separate tokens
   and compose into one `translate()`. `--dd-y` is the open/close slide;
   `--dd-x` is horizontal anchoring (the logo menu centres itself with -50%).
   Written as one transform, whichever rule set it last would silently wipe the
   other — which is exactly how a centred menu loses its entrance.
   `--dd-gap` is the standoff from the pill; the logo menu trims it because it
   measures from a different box (see .logo-nav above). */
.nav-dropdown {
    --dd-x: 0px;
    --dd-y: -6px;
    --dd-gap: 0.5rem;
    position: absolute;
    top: calc(100% + var(--dd-gap));
    right: 0;
    min-width: 200px;
    background: #14120F;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 10px;
    padding: 0.5rem;
    opacity: 0;
    visibility: hidden;
    transform: translate(var(--dd-x), var(--dd-y));
    transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease;
    z-index: 100;
}

/* Opening the menu.

   `:hover`/`:focus-within` are gated on `body.using-touch` (set by JS from the
   pointerType actually in use). On a touch screen a tap leaves an EMULATED
   :hover on whatever you tapped that persists until you tap something else —
   so hover-opening made the menu re-open itself after every tap-navigation,
   and no amount of delaying the force-close fixed it, because the stale hover
   was still there whenever the force-close lifted. Touch opens via `.is-open`
   instead, toggled by a real click handler.

   Hybrid laptops are why this keys off `body.using-touch` rather than a
   `@media (hover: hover)` query: a touchscreen laptop with a mouse attached
   matches `(hover: hover)` and would keep the bug. The class tracks the input
   being used right now, not what the device could theoretically do. */
body:not(.using-touch) .nav-dropdown-wrap:hover .nav-dropdown,
body:not(.using-touch) .nav-dropdown-wrap:focus-within .nav-dropdown,
.nav-dropdown-wrap.is-open .nav-dropdown {
    opacity: 1;
    visibility: visible;
    --dd-y: 0px;
}

.nav-dropdown a {
    display: block;
    padding: 0.5rem 0.75rem;
    color: rgba(243, 231, 212, 0.7);
    text-decoration: none;
    font-size: 0.85rem;
    font-weight: 300;
    border-radius: 6px;
    transition: background 0.15s ease, color 0.15s ease;
}

.nav-dropdown a:hover {
    background: rgba(255, 255, 255, 0.06);
    color: rgba(243, 231, 212, 0.95);
}

/* Icon-only nav buttons */
.nav-icon-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0.45rem;
    width: 36px;
    height: 36px;
}

/* Wave hand emoji button */
.nav-wave .wave-emoji {
    font-size: 1.1rem;
    display: inline-block;
    transition: transform 0.3s ease;
}

.nav-wave:hover .wave-emoji {
    animation: waveHand 0.6s ease-in-out;
}

@keyframes waveHand {
    0% { transform: rotate(0deg); }
    15% { transform: rotate(14deg); }
    30% { transform: rotate(-8deg); }
    45% { transform: rotate(14deg); }
    60% { transform: rotate(-4deg); }
    75% { transform: rotate(10deg); }
    100% { transform: rotate(0deg); }
}

/* Video crossfade loop */
.hero-video-container {
    position: relative;
}

.hero-video {
    display: block;
    width: 100%;
    border-radius: 12px;
    position: absolute;
    top: 0;
    left: 0;
    opacity: 0;
    transition: opacity 1.2s ease-in-out;
}

.hero-video.active {
    opacity: 1;
    position: relative;
}

.project-page .main-content {
    background: none;
    padding: 0;
}

.project-page h1 {
    font-size: 2.8rem;
    font-weight: 300;
    margin-bottom: 0.5rem;
    color: rgba(243, 231, 212, 0.95);
}

.project-page .duration {
    color: rgba(243, 231, 212, 0.45);
    font-size: 0.95rem;
    margin-bottom: 2.5rem;
    font-weight: 300;
}

.project-page .project-description {
    margin-bottom: 3rem;
    line-height: 1.7;
    color: rgba(243, 231, 212, 0.8);
    max-width: 800px;
}

.project-page .project-description p {
    margin-bottom: 1.25rem;
    font-size: 1rem;
}

.project-page .project-description ul {
    margin: 1.25rem 0;
    padding-left: 1.5rem;
}

.project-page .project-description li {
    margin-bottom: 0.75rem;
    color: rgba(243, 231, 212, 0.8);
}

/* Image Grid Styles */
.project-page .image-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 1.5rem;
    margin-top: 3rem;
}

.project-page .image-container {
    overflow: hidden;
    border-radius: var(--radius-media);
    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    cursor: pointer;
    background: rgba(255, 255, 255, 0.02);
    border: 1px solid rgba(255, 255, 255, 0.05);
}

.project-page .image-container:hover {
    transform: translateY(-2px);
    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
    background: rgba(255, 255, 255, 0.04);
}

.project-page .full-width-image {
    grid-column: 1 / -1;
}

.project-page .image-container img {
    width: 100%;
    height: auto;
    display: block;
    border-radius: 0; /* container rounds & clips - corner rule 1 */
}

/* ==========================================================================
   Modal Styles
   ========================================================================== */
.modal {
    display: none;
    position: fixed;
    z-index: 1000;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.95);
    align-items: center;
    justify-content: center;
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
}

/* On the corner scale (was a one-off 8px). The large tier: a lightbox image
   fills most of the viewport, so it sits with the heroes, not the thumbnails. */
.modal-content {
    display: block;
    max-width: 90%;
    max-height: 90vh;
    object-fit: contain;
    border-radius: var(--radius-media-lg);
}

.close {
    position: absolute;
    top: 20px;
    right: 40px;
    color: rgba(243, 231, 212, 0.8);
    font-size: 35px;
    font-weight: 300;
    transition: opacity 0.3s;
    cursor: pointer;
}

.close:hover {
    opacity: 0.6;
}

.close:focus-visible {
    outline: 2px solid rgba(74, 159, 245, 0.9);
    outline-offset: 4px;
    border-radius: 4px;
}

/* ==========================================================================
   Blog Styles
   ========================================================================== */
.blog-content {
    max-width: 680px;
    margin: 0 auto;
}

.blog-content h1 {
    font-size: 2.8rem;
    font-weight: 300;
    margin-bottom: 0.5rem;
    color: rgba(243, 231, 212, 0.95);
    line-height: 1.3;
}

.blog-metadata {
    color: rgba(243, 231, 212, 0.45);
    font-size: 0.85rem;
    margin-bottom: 2.5rem;
    font-weight: 300;
}

.blog-body {
    font-size: 1rem;
    font-weight: 200;
    letter-spacing: .015em;
    line-height: 1.7;
    color: rgba(243, 231, 212, 0.85);
}

.blog-body p {
    margin-bottom: 1.5rem;
}

.blog-body h1 {
    display: none; /* Hide duplicate title in content */
}

.blog-body h2 {
    font-size: 1.5rem;
    font-weight: 400;
    margin: 2rem 0 1rem 0;
    color: rgba(243, 231, 212, 0.9);
}

.blog-body h3 {
    font-size: 1.2rem;
    font-weight: 500;
    margin: 1.5rem 0 0.75rem 0;
    color: rgba(243, 231, 212, 0.9);
}

.blog-body ul, .blog-body ol {
    margin: 1rem 0 1.5rem 1.5rem;
}

.blog-body li {
    margin-bottom: 0.5rem;
}

.blog-body blockquote {
    border-left: 3px solid #4A9FF5;
    padding-left: 1.5rem;
    margin: 1.5rem 0;
    font-style: italic;
    color: rgba(243, 231, 212, 0.7);
}

.blog-body a {
    color: #4A9FF5;
    text-decoration: none;
    border-bottom: 1px solid transparent;
    transition: border-color 0.2s ease;
}

.blog-body a:hover {
    border-bottom-color: #4A9FF5;
    text-decoration: none;
}

.blog-body .image-container {
    margin: 2rem 0;
    border-radius: var(--radius-media);
    overflow: hidden;
    background: rgba(255, 255, 255, 0.02);
    border: 1px solid rgba(255, 255, 255, 0.05);
    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.blog-body .image-container:hover {
    transform: translateY(-2px);
    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
    background: rgba(255, 255, 255, 0.04);
}

.blog-body .image-container img {
    width: 100%;
    height: auto;
    display: block;
    border-radius: 0; /* container rounds & clips - corner rule 1 */
}

/* ==========================================================================
   Microsoft Showcase Page - Reimagined Layout
   ========================================================================== */
.microsoft-showcase,
.case-showcase {
    max-width: 1000px;
    margin: 0 auto;
}

/* Header Section */
.ms-header {
    margin-bottom: 1rem;
}

.ms-header h1 {
    font-size: 2.8rem;
    font-weight: 300;
    margin-bottom: 0.5rem;
}

.ms-intro {
    font-size: 1.1rem;
    font-weight: 200;
    line-height: 1.7;
    color: rgba(243, 231, 212, 0.85);
    margin-top: 2rem;
    max-width: 700px;
}

/* Hero Project Section */
.ms-hero-project,
.cs-hero-project {
    margin-bottom: 6rem;
}

/* Tight variant — when the next section has no section-title, the full
   6rem gap reads as a hole; pull it in */
.cs-hero-project.tight {
    margin-bottom: 4rem;
}

/* Impact metrics — a compact, scannable stat row for hard outcome numbers.
   Placed at the END of a case (top of `.cs-closing`) as a "by the numbers"
   finale, centered to match the closing. */
.impact-metrics {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-start;
    align-items: flex-start;
    gap: 2.75rem;
    margin: 0;
}

/* Centered "narrative capper" variant — Citrix closing only. Everything else
   is left-aligned (the base); repeated rows read better left-aligned, and the
   closing re-centers for its single end-of-page moment. */
.cs-closing .impact-metrics {
    justify-content: center;
    margin-bottom: 2.75rem;
}
.cs-closing .impact-metric { text-align: center; }
.cs-closing .impact-label { margin: 0 auto; }
.cs-closing .impact-icons { justify-content: center; }

/* Inline under an intro paragraph — the hero caption (platform-level stats)
   or a project block's paragraph (Nutanix partner). Anchored to what they
   measure rather than floating as a divider. */
.cs-hero-project .impact-metrics,
.project-text .impact-metrics {
    margin-top: 1.75rem;
}

/* The narrower project-text column needs a tighter gap to keep 2 metrics on
   one row */
.project-text .impact-metrics {
    gap: 2rem;
}

.impact-metric {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    text-align: left;
}

.impact-num {
    font-size: 1.9rem;
    font-weight: 400;
    line-height: 1;
    letter-spacing: -0.01em;
    color: rgba(243, 231, 212, 0.95);
}

.impact-label {
    font-size: 0.8rem;
    font-weight: 300;
    line-height: 1.35;
    color: rgba(243, 231, 212, 0.5);
    max-width: 24ch;
    margin: 0;
}

/* Optional platform-icon row under a metric (e.g. Citrix "4 platforms") —
   the icons carry the range visually so the label can stay a single word */
.impact-icons {
    display: flex;
    justify-content: flex-start;
    align-items: center;
    gap: 0.55rem;
    margin-top: 0.5rem;
    color: rgba(243, 231, 212, 0.55);
}

.impact-icons svg {
    width: 16px;
    height: 16px;
    display: block;
}

@media (max-width: 768px) {
    .impact-metrics {
        gap: 1.75rem 2.25rem;
    }
    .impact-num {
        font-size: 1.55rem;
    }
}

.ms-hero-project h2,
.cs-hero-project h2 {
    font-size: 2rem;
    font-weight: 400;
    margin-top: 1.5rem;
    margin-bottom: 0.75rem;
    color: rgba(243, 231, 212, 0.95);
}

.project-caption {
    margin-top: 0.75rem;
    font-size: 0.95rem;
    font-weight: 200;
    line-height: 1.6;
    color: rgba(243, 231, 212, 0.75);
}

.project-caption + .project-caption {
    margin-top: 0.5rem;
}

.project-caption a {
    color: #4A9FF5;
    text-decoration: none;
    transition: all 0.2s ease;
}

.project-caption a:hover {
    text-decoration: underline;
}


.project-meta {
    font-size: 0.85rem;
    color: rgba(243, 231, 212, 0.5);
    font-weight: 300;
    margin-bottom: 1.5rem;
}

/* Journey step marker — numbered beats in a single narrative arc */
.step-marker {
    font-size: 0.75rem;
    font-weight: 400;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: rgba(74, 159, 245, 0.7);
    margin-bottom: 0.75rem;
}

/* Same treatment used as a subhead UNDER an h3 rather than above one (the
   Nutanix partner block names its product this way). Holds the meta line's
   old gap off the body copy, so only the type changes - not the rhythm. */
.project-text h3 + .step-marker {
    margin-bottom: 1.5rem;
}

.project-intro {
    font-size: 1rem;
    font-weight: 200;
    line-height: 1.7;
    color: rgba(243, 231, 212, 0.8);
    margin-bottom: 2.5rem;
}

/* Hero Visual Layout - Asymmetric grid */
.hero-visual-layout {
    display: grid;
    grid-template-columns: 1.5fr 1fr;
    gap: 1.5rem;
}

.hero-main-visual {
    grid-row: 1 / 3;
    min-height: 500px;
}

.hero-supporting-visuals {
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
}

.hero-supporting-visuals .placeholder-image {
    min-height: 240px;
}

/* Hero Video Container */
.hero-video-container {
    border-radius: var(--radius-media);
    overflow: hidden;
    border: 1px solid rgba(74, 159, 245, 0.15);
    background: rgba(255, 255, 255, 0.02);
}

.hero-video-container video {
    display: block;
    width: 100%;
    border-radius: 0; /* container rounds & clips - corner rule 1 */
}

/* Shipped Projects Section */
.ms-shipped-projects,
.cs-shipped-projects {
    margin-bottom: 6rem;
}

.section-title {
    font-size: 1.5rem;
    font-weight: 400;
    margin-bottom: 3rem;
    color: rgba(243, 231, 212, 0.9);
}

/* No divider between blocks — the 8rem of combined margin+padding separates
   them on its own, and once each block animates in as a unit (see
   `[data-reveal]`) a rule riding in with it just draws the eye to the seam. */
.project-block {
    display: grid;
    grid-template-columns: 1fr 1.2fr;
    gap: 3rem;
    align-items: start;
    margin-bottom: 4rem;
    padding-bottom: 4rem;
}

.project-block:last-child {
    padding-bottom: 0;
}

.project-block.reverse {
    grid-template-columns: 1.2fr 1fr;
}

.project-block.reverse .project-text {
    order: 2;
}

.project-block.reverse .project-visuals {
    order: 1;
}

.project-block h3 {
    font-size: 1.5rem;
    font-weight: 400;
    margin-bottom: 0.5rem;
    color: rgba(243, 231, 212, 0.95);
}

.project-block p {
    font-size: 0.95rem;
    font-weight: 200;
    line-height: 1.6;
    color: rgba(243, 231, 212, 0.75);
}

/* Gap between stacked body paragraphs (e.g. a two-era story split in two),
   without touching the meta/marker→body spacing above them */
.project-text p:not(.project-meta):not(.step-marker) + p:not(.project-meta):not(.step-marker) {
    margin-top: 1rem;
}

/* Named sub-parts of one story (e.g. the two halves of a platform), where a
   run-on sentence would bury the names. No leading marker: each line already
   opens with a bold name followed by a dash, so a bullet or rule in front of
   it reads as a second mark on the same line. The bold names do the
   delineating - which works because the list is short. */
.project-list {
    list-style: none;
    margin: 1rem 0 0;
    padding: 0;
}

.project-list li {
    font-size: 0.95rem;
    font-weight: 200;
    line-height: 1.6;
    color: rgba(243, 231, 212, 0.75);
}

.project-list li + li {
    margin-top: 0.6rem;
}

.project-list strong {
    font-weight: 400;
    color: rgba(243, 231, 212, 0.95);
}

/* Section build for inner pages (Aug 2026). Each `[data-reveal]` block rises &
   fades as ONE unit when it scrolls in, so a case page builds beat by beat the
   way home does — inner pages had no entrance motion at all before this.

   It started as a per-image stagger inside each screenshot group and was
   retargeted: at 3 images the offsets were too small & too quick to read as
   anything, and it competed with the images rather than presenting them.
   A stacked card-deck unfurl was considered and rejected — the container has to
   reserve its final height either way (animating height reflows), so a real
   stack leaves a gap under the pile, and on a case page the screenshots ARE the
   evidence.

   The resting state in CSS is fully visible. `.reveal-armed` is added by JS and
   ONLY by JS, so a script failure, a dead observer or no-JS all degrade to
   "the section is simply there" rather than "the section is invisible".
   Both classes are stripped once the run finishes, so nothing is left holding a
   transform on an element whose descendants have their own hover transforms. */
.reveal-armed {
    opacity: 0;
    transform: translateY(24px);
}

.reveal-run {
    opacity: 1;
    transform: none;
    transition: opacity 0.8s cubic-bezier(0.16, 1, 0.3, 1),
                transform 0.8s cubic-bezier(0.16, 1, 0.3, 1);
}

@media (prefers-reduced-motion: reduce) {
    /* belt & braces — initReveals() also refuses to arm under reduced motion */
    .reveal-armed {
        opacity: 1;
        transform: none;
    }
}

.project-visuals.two-up {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.5rem;
}

.project-visuals.two-up .placeholder-image {
    min-height: 280px;
}

/* Toolkits Section */
.ms-toolkits,
.cs-toolkits {
    margin-bottom: 5rem;
}

.toolkit-header {
    margin-bottom: 2.5rem;
}

.toolkit-header h2 {
    font-size: 1.5rem;
    font-weight: 400;
    margin-bottom: 0.75rem;
    color: rgba(243, 231, 212, 0.9);
}

.toolkit-header p {
    font-size: 0.95rem;
    font-weight: 200;
    color: rgba(243, 231, 212, 0.7);
}

.toolkit-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1.5rem;
    margin-bottom: 2rem;
}

/* No hover state - these cards are labels, not links. Hover treatment is
   reserved for elements that actually go somewhere. */
.toolkit-card {
    padding: 1.5rem;
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 12px;
}

.toolkit-card h4 {
    font-size: 1rem;
    font-weight: 500;
    margin-bottom: 0.5rem;
    color: rgba(243, 231, 212, 0.95);
}

.toolkit-card p {
    font-size: 0.85rem;
    font-weight: 200;
    line-height: 1.5;
    color: rgba(243, 231, 212, 0.6);
    margin: 0;
}

.toolkit-visual {
    min-height: 350px;
}

/* Two-up photo grid (side by side, e.g. research/workshop photos) */
.craft-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1.5rem;
}

.craft-grid .placeholder-image {
    min-height: 350px;
}

/* Closing Section */
.ms-closing,
.cs-closing {
    text-align: center;
    margin-bottom: 3rem;
}

.ms-closing p,
.cs-closing p {
    font-size: 1rem;
    font-weight: 300;
    color: rgba(243, 231, 212, 0.7);
}

/* The role/dates capper. Small-caps so it reads as a signature line rather than
   a second sentence under the reach-out line - at .cs-closing p's size & weight
   it was the same voice as the sentence above it. The type contrast is the
   whole fix; no rule above it (tried, and it separated what the type had
   already separated). Two classes (0,2,0) to beat `.cs-closing p` (0,1,1) on
   size & colour. Deliberately NOT a pill: the nav dock pill sits right below. */
.ms-closing .project-meta,
.cs-closing .project-meta {
    font-size: 0.75rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: rgba(243, 231, 212, 0.45);
}

/* Placeholder Images with Blobs */
.placeholder-image,
.placeholder-image-wide {
    position: relative;
    width: 100%;
    background: linear-gradient(135deg, rgba(74, 159, 245, 0.08) 0%, rgba(74, 159, 245, 0.03) 100%);
    border: 1px solid rgba(74, 159, 245, 0.15);
    border-radius: var(--radius-media);
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
                box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1),
                background 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.placeholder-image::before {
    content: attr(data-label);
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 0.9rem;
    font-weight: 300;
    color: rgba(74, 159, 245, 0.4);
    text-align: center;
    padding: 1rem;
    max-width: 80%;
}

/* Empty placeholders (no image yet) still get the original blue-gradient
   hover - it's a design-time slot. The lift for real screenshots is defined
   with the rest of the zoom affordance further down, scoped to img[onclick]. */
.placeholder-image:not(:has(img)):hover,
.placeholder-image-wide:not(:has(img)):hover {
    background: linear-gradient(135deg, rgba(74, 159, 245, 0.12) 0%, rgba(74, 159, 245, 0.05) 100%);
}

.placeholder-image-wide {
    min-height: 350px;
}

.placeholder-image-wide::before {
    content: attr(data-label);
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 0.9rem;
    font-weight: 300;
    color: rgba(74, 159, 245, 0.4);
    text-align: center;
    padding: 1rem;
}

/* When images are added, hide placeholder styling.
   Radius 0 on the image itself - the container rounds & clips (corner rule 1). */
.placeholder-image img,
.placeholder-image-wide img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 0;
}

.placeholder-image:has(img)::before,
.placeholder-image-wide:has(img)::before {
    display: none;
}

/* No border once a real image fills the slot. On .natural containers (which
   hug the image instead of cropping it) the 1px line traced the artwork edge
   exactly & read as a stray outline - most visible on the Microsoft job
   shadowing before/after & Nutanix step 02. The screenshots carry their own
   edges; the container just holds them. */
.placeholder-image:has(img),
.placeholder-image-wide:has(img) {
    background: rgba(255, 255, 255, 0.02);
    border: none;
}

/* ==========================================================================
   Corner scale (rule 4)
   ==========================================================================
   Each tier just redefines --radius-media for its subtree, so the shared
   `border-radius: var(--radius-media)` declarations pick it up automatically
   & nothing needs a second border-radius override. */

/* Large — heroes & full-width showcases. These are the biggest surfaces on a
   case page, where the default radius reads as too tight. */
.hero-carousel,
.hero-video-container,
.placeholder-image.hero-still,
.placeholder-image-wide {
    --radius-media: var(--radius-media-lg);
}

/* Small — media that renders at roughly half width or less. .craft-grid is
   2-up only above 1024px; below that it collapses to one column & goes back
   to the default tier, so the override is scoped to the 2-up range. */
.hero-supporting-visuals .placeholder-image {
    --radius-media: var(--radius-media-sm);
}

@media (min-width: 1025px) {
    .craft-grid .placeholder-image {
        --radius-media: var(--radius-media-sm);
    }
}

/* Hero still — top-anchored crop so a tall screenshot can open the page.
   16/10 matches the footprint of the ASN hero video (1440x900) on the
   Microsoft page, keeping case-study openers visually consistent. Aspect
   ratio (not fixed height) keeps the crop line at the same spot in the
   artwork at every viewport width — on n1.png the cut lands in the gap
   below the second card row, so it reads as a natural screen edge. */
.placeholder-image.hero-still {
    aspect-ratio: 16 / 10;
    min-height: 0;
}

.placeholder-image.hero-still img {
    object-position: center top;
}

/* Occupation tag at page bottom, below the closing line */
.cs-closing .project-meta,
.ms-closing .project-meta {
    margin-top: 1.75rem;
    margin-bottom: 0;
}

/* Natural-ratio variant — wide strips & diagrams that must not be cropped */
.placeholder-image.natural,
.project-visuals.two-up .placeholder-image.natural,
.hero-supporting-visuals .placeholder-image.natural {
    min-height: 0;
}

.placeholder-image.natural img {
    height: auto;
    object-fit: contain;
}

.placeholder-image img[onclick],
.placeholder-image-wide img[onclick] {
    cursor: zoom-in;
}

/* Every case-study screenshot opens the lightbox, so they all get the same
   hover: the original lift + shadow. No border change - the containers are
   borderless once filled & a line appearing on hover read as a stray edge. */
.placeholder-image:has(img[onclick]):hover,
.placeholder-image-wide:has(img[onclick]):hover {
    transform: translateY(-2px);
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.2);
}

/* A magnifier cue rides along with the lift, naming the interaction rather
   than leaving it to a vague gesture. Top-left stays free for .ph-badge. */
.placeholder-image:has(img[onclick])::after,
.placeholder-image-wide:has(img[onclick])::after {
    content: "";
    position: absolute;
    top: 0.75rem;
    right: 0.75rem;
    z-index: 2;
    width: 2rem;
    height: 2rem;
    border-radius: 999px;
    background-color: rgba(20, 20, 24, 0.72);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23F3E7D4' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cline x1='16.5' y1='16.5' x2='21' y2='21'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: center;
    background-size: 1rem 1rem;
    backdrop-filter: blur(6px);
    opacity: 0;
    transform: scale(0.9);
    transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1),
                transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
    pointer-events: none;
}

.placeholder-image:has(img[onclick]):hover::after,
.placeholder-image-wide:has(img[onclick]):hover::after {
    opacity: 1;
    transform: scale(1);
}

/* Keyboard parity: the image is focusable (see enhanceZoomableImages in
   script.js), so focus gets the same lift & cue as hover, plus a ring. The
   ring is an OUTLINE on the container - a border would reflow the image, and
   an outline on the img itself would be clipped by overflow: hidden. It only
   ever shows for keyboard focus, never on hover. */
.placeholder-image:has(img[onclick]:focus-visible),
.placeholder-image-wide:has(img[onclick]:focus-visible) {
    outline: 2px solid rgba(74, 159, 245, 0.9);
    outline-offset: 3px;
    transform: translateY(-2px);
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.2);
}

.placeholder-image:has(img[onclick]:focus-visible)::after,
.placeholder-image-wide:has(img[onclick]:focus-visible)::after {
    opacity: 1;
    transform: scale(1);
}

.placeholder-image img[onclick]:focus,
.placeholder-image-wide img[onclick]:focus {
    outline: none; /* replaced by the container ring above */
}

/* Touch devices have no hover to reveal the cue, so keep it always visible
   at a lower weight rather than hiding the affordance entirely. */
@media (hover: none) {
    .placeholder-image:has(img[onclick])::after,
    .placeholder-image-wide:has(img[onclick])::after {
        opacity: 0.75;
        transform: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .placeholder-image:has(img[onclick])::after,
    .placeholder-image-wide:has(img[onclick])::after {
        transform: none;
        transition: opacity 0.25s ease;
    }
}

/* Per-image crop anchor for cover-cropped artifacts */
.placeholder-image img.crop-left {
    object-position: left center;
}

/* Natural-ratio support for the wide showcase slot too */
.placeholder-image-wide.natural {
    min-height: 0;
}

.placeholder-image-wide.natural img {
    height: auto;
    object-fit: contain;
}

/* Before/After (or other) corner badge on a screenshot */
.placeholder-image .ph-badge {
    position: absolute;
    top: 0.75rem;
    left: 0.75rem;
    z-index: 2;
    padding: 0.3rem 0.7rem;
    border-radius: 999px;
    background: rgba(20, 20, 24, 0.72);
    backdrop-filter: blur(6px);
    color: rgba(243, 231, 212, 0.95);
    font-size: 0.72rem;
    font-weight: 400;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    pointer-events: none;
}

/* Hero image carousel (replaces the ASN walkthrough video) */
.hero-carousel {
    position: relative;
    aspect-ratio: 16 / 10;
    border-radius: var(--radius-media);
    overflow: hidden;
    border: 1px solid rgba(74, 159, 245, 0.15);
    background: rgba(255, 255, 255, 0.02);
}

.hero-slide {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center top;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.8s ease;
    cursor: zoom-in;
}

.hero-slide.active {
    opacity: 1;
    pointer-events: auto;
    z-index: 1;
}

.hero-carousel-controls {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.85rem;
    margin-top: 0.9rem;
}

/* Lightweight caret arrows flanking the dots */
.carousel-arrow {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border: none;
    background: none;
    color: rgba(243, 231, 212, 0.28);
    cursor: pointer;
    transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.carousel-arrow svg {
    width: 14px;
    height: 14px;
}

.carousel-arrow:hover,
.carousel-arrow:focus-visible {
    color: rgba(243, 231, 212, 0.85);
}

/* Nudge the caret matching the direction of travel on every transition —
   auto-advance, arrow clicks, and dot jumps alike.
   Note: color is only set at the midpoint so 0%/100% inherit the element's
   current color, letting the hover state survive a click-triggered nudge. */
.carousel-arrow.hint {
    animation: caretHint 0.7s cubic-bezier(0.4, 0, 0.2, 1);
}

.carousel-arrow[data-dir="-1"].hint {
    animation-name: caretHintBack;
}

@keyframes caretHint {
    0%   { transform: translateX(0); }
    35%  { transform: translateX(3px); color: rgba(243, 231, 212, 0.75); }
    100% { transform: translateX(0); }
}

@keyframes caretHintBack {
    0%   { transform: translateX(0); }
    35%  { transform: translateX(-3px); color: rgba(243, 231, 212, 0.75); }
    100% { transform: translateX(0); }
}

@media (prefers-reduced-motion: reduce) {
    .carousel-arrow.hint {
        animation: none;
    }
}

.hero-carousel-dots {
    display: flex;
    justify-content: center;
    gap: 0.5rem;
}

.hero-carousel-dots button {
    width: 8px;
    height: 8px;
    padding: 0;
    border: none;
    border-radius: 999px;
    background: rgba(243, 231, 212, 0.25);
    cursor: pointer;
    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.hero-carousel-dots button:hover {
    background: rgba(243, 231, 212, 0.5);
}

.hero-carousel-dots button.active {
    width: 22px;
    border-radius: 4px;
    background: rgba(243, 231, 212, 0.85);
}

/* ==========================================================================
   Responsive Design
   ========================================================================== */
@media (max-width: 1024px) {
    .work-cards {
        grid-template-columns: 1fr;
        gap: 1.5rem;
    }

    /* Bifold Layout - Tablet: the big hover preview panel can't work without a
       pointer, so it's dropped & each project carries its own inline thumbnail
       (see .project-thumb).

       NOTE: 1025px may be too aggressive a switch point — it drops the whole
       desktop treatment at widths that are still comfortably pointer-driven.
       Worth revisiting with a lower threshold for "tablet". */
    .projects-bifold {
        flex-direction: row;
        align-items: stretch;
        min-height: 0;
    }

    .projects-preview {
        display: none;
    }

    /* The plant goes with the panel rather than surviving as a slim rail. It
       used to shrink to a 62px sliver, which was too small to read as the
       drawn branch it is & left the section looking lopsided. Its whole point
       was the hover-driven leaf fill, and there's no hover here — so below the
       desktop layout it's decoration that isn't earning its space. */
    .projects-divider {
        display: none;
    }

    .projects-list {
        flex: 1 1 auto;
        min-width: 0;
    }

    /* Content aligns with the section heading; the hover surface bleeds past
       it — the same trick .thought-card uses. Horizontal padding is trimmed to
       0.75rem first so the bleed stays small enough never to reach the screen
       edge at the narrow breakpoints (gutter is 30px at <=768 and 20px at
       <=480). Vertical padding is untouched. */
    .project-item {
        padding: 1.25rem 0.75rem;
        margin-left: -0.75rem;
        margin-right: -0.75rem;
    }

    /* Inline preview — replaces the hover panel on touch screens. It's a
       <video> that plays on entering the viewport (see index.html); the still
       lives on its `poster`, so it only shows for the moment before the first
       frame decodes. object-fit guards against the poster and the clip
       disagreeing on aspect. */
    .project-thumb {
        display: block;
        width: 100%;
        height: auto;
        object-fit: cover;
        margin: 0.9rem 0 0.85rem;
        border-radius: var(--radius-media-sm);
        border: 1px solid rgba(255, 255, 255, 0.08);
        background: rgba(255, 255, 255, 0.02);
    }

    .project-page .image-grid {
        grid-template-columns: 1fr;
    }

    /* Microsoft Showcase - Tablet */
    .hero-visual-layout {
        grid-template-columns: 1fr;
    }

    .hero-main-visual {
        grid-row: auto;
        min-height: 350px;
    }

    .project-block,
    .project-block.reverse {
        grid-template-columns: 1fr;
        gap: 2rem;
    }

    .project-block.reverse .project-text,
    .project-block.reverse .project-visuals {
        order: initial;
    }

    .toolkit-grid {
        grid-template-columns: 1fr;
    }

    .craft-grid {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 768px) {
    .container {
        padding: 30px 30px 60px;
    }

    .hero h1,
    .hero-subtitle {
        font-size: 2.5rem;
    }

    .company-card,
    .project-card {
        padding: 1.5rem;
    }

    /* Thought grid: 2 columns on tablet */
    .thoughts-grid {
        grid-template-columns: repeat(2, 1fr);
        gap: 2rem 1.5rem;
    }

    .project-page h1 {
        font-size: 2.2rem;
    }

    .blog-content h1 {
        font-size: 2.2rem;
    }

    .project-page .container,
    .blog-content {
        padding: 30px;
    }

    /* Phone: reclaim the rail's width for content; thumbnails carry the
       section on their own here. */
    /* (.projects-divider is already hidden from 1024 down) */

    /* Microsoft Showcase - Mobile */
    .ms-header h1 {
        font-size: 2.2rem;
    }

    .ms-hero-project h2,
    .cs-hero-project h2 {
        font-size: 1.6rem;
    }


    .project-block h3 {
        font-size: 1.3rem;
    }
}

@media (max-width: 480px) {
    .container {
        padding: 20px 20px 40px;
    }

    /* No side padding on .projects — the container already sets the page
       gutter, and adding more here inset this section relative to the work
       cards above & the thoughts grid below. */

    .hero h1,
    .hero-subtitle {
        font-size: 2rem;
    }

    /* Thought grid: single column on small mobile */
    .thoughts-grid {
        grid-template-columns: 1fr;
        gap: 2rem 0;
    }

    .project-page h1 {
        font-size: 1.8rem;
    }

    .blog-content h1 {
        font-size: 1.8rem;
    }

    .project-page .container {
        padding: 20px;
    }

    /* Microsoft Showcase - Small Mobile */
    .ms-header h1 {
        font-size: 1.8rem;
    }

    .ms-hero-project h2,
    .cs-hero-project h2 {
        font-size: 1.4rem;
    }

    .hero-supporting-visuals .placeholder-image {
        min-height: 200px;
    }

    .project-visuals.two-up .placeholder-image {
        min-height: 220px;
    }

    .placeholder-image.natural,
    .project-visuals.two-up .placeholder-image.natural,
    .hero-supporting-visuals .placeholder-image.natural {
        min-height: 0;
    }
}

/* ===========================
   SPA-feel page transitions
   =========================== */
body {
    animation: pageFadeIn 0.4s ease-out both;
}

@keyframes pageFadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}

body.page-fade-out {
    animation: pageFadeOut 0.22s ease-in forwards;
}

@keyframes pageFadeOut {
    from { opacity: 1; }
    to   { opacity: 0; }
}

/* SPA navigation — paired fade-out + fade-in for the content swap.
   Old lifts up and fades; new rises up and fades in. Same easing as the
   site's other entrance animations so the motion language stays consistent. */
.main-content.is-leaving {
    animation: spaLeave 0.18s ease-in forwards;
}

@keyframes spaLeave {
    from { opacity: 1; transform: translateY(0); }
    to   { opacity: 0; transform: translateY(-4px); }
}

.main-content.is-arriving {
    animation: spaArrive 0.22s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes spaArrive {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* Force-close a dropdown after its link has been clicked to navigate.
   Without this, focus/:hover keep the menu open across the swap. */
.nav-dropdown-wrap.is-cooling .nav-dropdown {
    opacity: 0 !important;
    visibility: hidden !important;
    /* Must keep --dd-x in the transform, or a centred menu jumps half its own
       width sideways on the way out. The !important is on the token so it
       beats the :hover open rule wherever that lands in the cascade. */
    --dd-y: -6px !important;
    transform: translate(var(--dd-x), var(--dd-y)) !important;
}

@media (prefers-reduced-motion: reduce) {
    body,
    body.page-fade-out,
    .main-content.is-leaving,
    .main-content.is-arriving {
        animation: none;
    }
}

/* ===========================
   Clickable Home row in dropdown (item 5)
   =========================== */
/* Hugs its content rather than spanning the menu: a full-bleed hover band
   read as a heading strip rather than a link. `width: fit-content` shrinks
   the pill to the label, and 0.75rem of side padding lines its text up with
   the section labels below it (both land 1.5rem in from the menu edge).
   Smaller click target, but it now looks like the thing it is. */
.dropdown-section.dropdown-section-home {
    display: flex;
    width: fit-content;
    align-items: center;
    padding: 0.35rem 0.75rem;
    border-radius: 6px;
    text-decoration: none;
    transition: background 0.15s ease;
    margin-bottom: 0.1rem;
}

.dropdown-section-home .dropdown-section-label {
    padding-left: 0;
    color: rgba(243, 231, 212, 0.85);
    font-size: 0.85rem;
    font-weight: 400;
    transition: color 0.15s ease;
}

.dropdown-section-home .dropdown-section-label svg {
    opacity: 0.75;
    transition: opacity 0.15s ease;
}

.dropdown-section-home:hover {
    background: rgba(255, 255, 255, 0.06);
}

.dropdown-section-home:hover .dropdown-section-label {
    color: rgba(243, 231, 212, 1);
}

.dropdown-section-home:hover .dropdown-section-label svg {
    opacity: 1;
}


/* ===========================
   Corner smoothing (squircles)
   ===========================
   CSS Borders L4 `corner-shape` replaces the circular quarter-arc of a plain
   border-radius with a superellipse - the same "corner smoothing" Figma &
   Apple use. The curvature eases into the straight edge instead of meeting it
   at an abrupt tangent, which is what makes a rounded rectangle read as
   crisp rather than stamped-out.

   Progressive enhancement on purpose: browsers without corner-shape keep the
   plain radius from the rules above. Same geometry & same clipping - only the
   curve differs - so nothing breaks & there's no fallback to maintain.

   Applied to media surfaces only (the things that hold an image). Cards &
   pills keep their circular corners; mixing the two on the SAME element is
   what looks wrong, mixing across different component families is fine. */
@supports (corner-shape: squircle) {
    /* Inner pages */
    .placeholder-image,
    .placeholder-image-wide,
    .hero-carousel,
    .hero-video-container,
    .project-page .image-container,
    .blog-body .image-container,
    .modal-content,
    /* Home. Added July 2026 — the original pass only reached inner pages plus
       .project-thumb, which is display:none above 1024px, so desktop home had
       no smoothing at all while every case page did. These two are its media
       surfaces: the bifold's hover preview panel & the thought-card covers.
       The cards themselves (.company-card, .project-item, .thought-card) stay
       circular on purpose — cards & pills don't get the squircle. */
    .thought-card-cover,
    .project-thumb {
        corner-shape: squircle;
    }
}
