/* ═══════════════════════════════════════════════════════════
   RAHUL MUKHIYA — PORTFOLIO
   Design tokens → Base → Layout → Components → Responsive
   ═══════════════════════════════════════════════════════════ */

/* ══════════ 1. DESIGN TOKENS ══════════ */
:root {
  /* surfaces */
  --bg: #08090a;
  --bg-alt: #0c0e10;
  --surface: #121417;
  --surface-hi: #171a1e;
  /* Were .07 / .13 — a contrast of 1.14 and 1.37 against the page, i.e. not
     actually visible. At 1px the antialiaser then dropped whatever did not land
     on a whole device pixel, which is why Edge showed broken threads and a
     higher-density phone screen did not. Width is only half the fix. */
  --border: rgba(255, 255, 255, .16);
  --border-hi: rgba(255, 255, 255, .26);
  --hair: 1.5px;          /* dividers and small chips */
  --stroke: 2px;          /* anything you click: buttons, cards, controls */

  /* A one-pixel inner highlight along the top edge. Cheap trick, big effect:
     it reads as a light source above the page, so panels look moulded rather
     than like flat rectangles with an outline drawn round them. */
  --sheen: inset 0 1px 0 rgba(255, 255, 255, .055);
  --shadow-soft: 0 10px 30px rgba(0, 0, 0, .35);
  --shadow-lift: 0 18px 48px rgba(0, 0, 0, .5);

  /* text — --fg-subtle was #6b7176, a 4.03 contrast, under the 4.5 WCAG asks
     for body text, and it carried the hero bio and every section subheading. */
  --fg: #f2f4f5;
  --fg-muted: #a8aeb3;
  --fg-subtle: #868c91;

  /* accent */
  --accent: #10b981;
  --accent-hi: #34d399;
  --accent-glow: rgba(16, 185, 129, .18);
  --accent-rgb: 16, 185, 129;
  --gradient: linear-gradient(135deg, #34d399 0%, #10b981 55%, #059f76 100%);

  /* type */
  --font-display: "Space Grotesk", system-ui, sans-serif;
  --font-body: "Inter", system-ui, -apple-system, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;

  /* layout */
  --container: 1140px;
  --radius: 18px;
  --radius-sm: 12px;

  /* Honeycomb: a flat --accent hex grid on the black page. Both tones paint the
     SAME shape — the cell borders. --honey-lit is that border everywhere;
     --honey-hot is the same border brightened where the cursor pool crosses it.
     The gaps between cells are never painted; they stay page-black, which is
     what keeps the cells reading as separate tiles. */
  --honey-lit: .09;      /* the hexagon border, at rest, everywhere */
  --honey-hot: .55;      /* the same border, directly under the cursor */

  /* motion */
  --ease: cubic-bezier(.22, 1, .36, 1);
  --dur: .55s;
}

/* ══════════ 2. BASE ══════════ */
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  scroll-behavior: smooth;
  scroll-padding-top: 90px;
}

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

body.is-locked {
  overflow: hidden;
}

img {
  max-width: 100%;
  display: block;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
}

svg {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
}

::selection {
  background: var(--accent);
  color: #04120d;
}

/* scrollbar */
::-webkit-scrollbar {
  width: 10px;
}

::-webkit-scrollbar-track {
  background: var(--bg);
}

::-webkit-scrollbar-thumb {
  background: #23272b;
  border-radius: 20px;
  border: 2px solid var(--bg);
}

::-webkit-scrollbar-thumb:hover {
  background: #2f3439;
}

/* ── Honeycomb ground ─────────────────────────────────────────────────────────
   Flat, and ONE masked layer — the whole effect comes from what lives inside it.

     .honey-lamp        the border grid -> hexagons shrunk to .906 of full size
                                           and stroked at 2.5, so neighbours do
                                           not share a wall and a black channel
                                           stays open between them. Painted at
                                           --honey-lit, everywhere, always.
     .honey-lamp__core  the light       -> a pool that follows the cursor,
                                           parked INSIDE .honey-lamp and so
                                           clipped to the very same border
                                           shape. It therefore brightens the
                                           borders it passes over instead of
                                           washing a disc across the page.

   Because both the resting colour and the light are the same shape, one layer
   does the lot: the background paints the dim border, the child brightens it.
   An earlier pass split these across two full-viewport layers to light the gap
   channels instead; lighting the borders needs only this one, at half the
   texture.

   The gaps themselves are never painted — they stay page-black, which is what
   keeps the cells reading as separate tiles rather than a shared-wall lattice.

   One solid stroke, no stacked semi-transparent widths: stacking was tried and
   reads as haze at this tile size.

   A mask rather than a baked-colour image, so both tones stay live --accent
   custom properties instead of freezing into a data URI. */

.honey-lamp {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  /* Fixed elements re-measure against the layout viewport, which changes
     height on mobile every time the URL bar collapses — that used to retile
     the mask mid-scroll. lvh is the largest viewport height and never moves;
     vh is the fallback for browsers without it. */
  height: 100vh;
  height: 100lvh;
  z-index: -1;
  pointer-events: none;
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat;
  -webkit-mask-size: 48.5px 84px;
  mask-size: 48.5px 84px;
}


/* The core is a child, not a mask offset. Moving a mask or a gradient centre
   repaints the whole layer; moving a child that owns its compositing layer does
   not. So this mask is static — rasterised once — and only the small core ever
   moves.

   .honey-lamp exactly fills the viewport now, so the cursor's viewport
   coordinates are already this element's local coordinates — main.js just
   parks the core there directly, no projection or scaling involved. */
.honey-lamp {
  background-color: rgba(var(--accent-rgb), var(--honey-lit));
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48.498' height='84' viewBox='0 0 48.498 84'%3E%3Cpath d='M0 -25.359L21.961 -12.679L21.961 12.679L0 25.359L-21.961 12.679L-21.961 -12.679ZM48.498 -25.359L70.459 -12.679L70.459 12.679L48.498 25.359L26.537 12.679L26.537 -12.679ZM0 58.641L21.961 71.321L21.961 96.679L0 109.359L-21.961 96.679L-21.961 71.321ZM48.498 58.641L70.459 71.321L70.459 96.679L48.498 109.359L26.537 96.679L26.537 71.321ZM24.249 16.641L46.21 29.321L46.21 54.679L24.249 67.359L2.288 54.679L2.288 29.321Z' fill='none' stroke='%23fff' stroke-width='2.5' opacity='1'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48.498' height='84' viewBox='0 0 48.498 84'%3E%3Cpath d='M0 -25.359L21.961 -12.679L21.961 12.679L0 25.359L-21.961 12.679L-21.961 -12.679ZM48.498 -25.359L70.459 -12.679L70.459 12.679L48.498 25.359L26.537 12.679L26.537 -12.679ZM0 58.641L21.961 71.321L21.961 96.679L0 109.359L-21.961 96.679L-21.961 71.321ZM48.498 58.641L70.459 71.321L70.459 96.679L48.498 109.359L26.537 96.679L26.537 71.321ZM24.249 16.641L46.21 29.321L46.21 54.679L24.249 67.359L2.288 54.679L2.288 29.321Z' fill='none' stroke='%23fff' stroke-width='2.5' opacity='1'/%3E%3C/svg%3E");
}

.honey-lamp__core {
  position: absolute;
  top: 0;
  left: 0;
  width: 250px;
  height: 250px;
  margin: -125px 0 0 -125px;      /* so the transform positions its centre */
  /* Tighter pool than before, so the lit patch stays a patch. Stops still fade
     gently to the edge rather than dying at 64% — a hard cutoff inside the
     circle wastes most of the radius and reads as a much smaller light. */
  background: radial-gradient(closest-side circle,
      rgba(var(--accent-rgb), var(--honey-hot)) 0%,
      rgba(var(--accent-rgb), calc(var(--honey-hot) * .5)) 40%,
      transparent 100%);
  will-change: transform;
  transform: translate3d(-9999px, -9999px, 0);   /* parked until the first move */
}

/* Nothing to follow without a cursor, and a light frozen at the last tap looks
   broken. The honeycomb itself stays — static, so it rasterises once. */
@media (hover: none) {

  .honey-lamp__core {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {

  .honey-lamp__core {
    display: none;
  }
}


/* ══════════ 3. UTILITIES ══════════ */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: 24px;
}

.mono {
  font-family: var(--font-mono);
  font-size: .75rem;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.gradient-text {
  background: var(--gradient);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.accent {
  color: var(--accent);
}

/* scroll reveal */
.reveal {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity .7s var(--ease), transform .7s var(--ease);
}

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

@media (prefers-reduced-motion: reduce) {

  html {
    scroll-behavior: auto;
  }

  .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }

  *,
  *::before,
  *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

/* ══════════ 4. BUTTONS ══════════ */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 13px 24px;
  border-radius: var(--radius-sm);
  font-weight: 600;
  font-size: .94rem;
  transition: transform .25s var(--ease), box-shadow .25s var(--ease), background .25s, border-color .25s;
}

.btn svg {
  width: 17px;
  height: 17px;
}

.btn--primary {
  background: var(--gradient);
  color: #04120d;
  box-shadow: 0 4px 20px var(--accent-glow);
}

.btn--primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 30px rgba(16, 185, 129, .32);
}

.btn--ghost {
  border: var(--stroke) solid var(--border-hi);
  color: var(--fg);
  background: rgba(255, 255, 255, .02);
}

.btn--ghost:hover {
  border-color: var(--accent);
  color: var(--accent-hi);
  transform: translateY(-2px);
}

/* ══════════ 5. NAV ══════════ */
.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  transition: background .35s, border-color .35s, backdrop-filter .35s;
  border-bottom: 1px solid transparent;
}

.nav.is-stuck {
  background: rgba(8, 9, 10, .78);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom-color: var(--border);
}

.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 74px;
}

.nav__logo {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.3rem;
  letter-spacing: -.02em;
}

.nav__links {
  display: flex;
  gap: 34px;
}

.nav__links a {
  position: relative;
  font-size: .92rem;
  color: var(--fg-muted);
  transition: color .25s;
}

.nav__links a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -6px;
  width: 0;
  height: 1.5px;
  background: var(--accent);
  transition: width .3s var(--ease);
}

.nav__links a:hover,
.nav__links a.is-active {
  color: var(--fg);
}

.nav__links a:hover::after,
.nav__links a.is-active::after {
  width: 100%;
}

.nav__toggle {
  display: none;
  flex-direction: column;
  gap: 5px;
  padding: 8px;
}

.nav__toggle span {
  width: 24px;
  height: 2px;
  background: var(--fg);
  border-radius: 2px;
  transition: transform .3s var(--ease), opacity .2s;
}

.nav__toggle.is-open span:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}

.nav__toggle.is-open span:nth-child(2) {
  opacity: 0;
}

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

/* ══════════ 6. HERO ══════════ */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: center;
  overflow: hidden;
  padding: 120px 0 90px;
}

/* Two columns so the screenshot can sit in the space beside the name. The
   screenshot column is capped, and the whole thing collapses to one column
   before the two could ever crowd each other. */
.hero__inner {
  position: relative;
  z-index: 1;
  display: grid;
  /* A share of the container, not of the viewport: .hero__inner IS .container,
     so it stops growing at 1140px and a vw-based column would keep eating the
     text column on wide screens for no gain. 48% is the most the screenshot can
     take: it leaves the text 512px, and the title needs 449 for "Mukhiya" to
     stay on its own second line. 50% is too far — the bio wraps to a fourth
     line there, which makes the hero taller and pushes every section down. */
  grid-template-columns: minmax(0, 1fr) minmax(320px, 48%);
  align-items: center;
  gap: 56px;
}

/* The screenshot showcase stands in the same box the sphere did — same
   column, same cap, same square — so replacing one with the other moves
   nothing else on the page. Each screenshot is fitted inside this box by
   main.js rather than stretched to it, so a portrait phone and a landscape
   desktop shot both get their whole selves at the largest size that fits. */
.hero__showcase {
  position: relative;
  justify-self: end;
  /* Fill the hero row rather than sitting centred in it: this puts the top of
     the screenshot level with the "Biratnagar, Nepal" line and its bottom level
     with the social icons, instead of leaving ~70px idle at each end. */
  align-self: stretch;
  width: 100%;
  max-width: 560px;
  /* the swipe window: a shot travels 62% of the box on its way out, which
     without this would run straight over the text column beside it */
  overflow: hidden;
  /* On a short screen the hero is taller than the viewport — its row alone is
     626px, plus 210px of padding — so a box that stretched the whole row put
     the bottom of the phone under the fold.

     The box edge is NOT the phone's edge: each framed PNG is 900x1620 with the
     device body only 1400 of that, so 6.8% of the box height is transparent
     padding and the phone already ends ~44px above the bottom of its own box.
     Capping against the box edge therefore shrank the device far more than it
     needed to. Dividing by .932 gives that padding back, so the constraint is
     on the visible device rather than the picture around it: 24px of real
     clearance under the phone.

     The constant is 95 + the clearance you want: the box starts at y=95 and the
     device body is 93.2% of the box, so device bottom = 95 + 0.932H. Solving
     for H gives (svh - 95 - clearance) / 0.932. It was 93 for a while, which is
     95 + -2 — the phone was guaranteed to hang 2px past the fold at every
     window height. 123 leaves 28px.

     Holding full height matters for more than composition: the density files
     are authored at exactly the size the shot is displayed at, so as long as
     the box is not capped the browser draws them 1:1. The moment the cap bites,
     the shot renders at some in-between size and every pixel is resampled — a
     scale of 1.045 blurs far worse than a clean 2:1 ever would.

     svh, not vh, so a mobile URL bar appearing cannot re-cut it. */
  max-height: calc((100svh - 123px) / 0.932);
  /* Then take the empty band above that too. The row starts at the hero's
     120px of top padding and the nav ends at 75, so -25px lifts the top to 95:
     20px clear of the nav, and above the eyebrow. A negative margin on a
     stretched grid item grows the item without growing the row, so the text
     column, the hero's height and every section below it stay exactly put. */
  margin-top: -25px;
  /* Pull the bottom up off the end of the text column. 90px would put it level
     with the View My Work / View Resume buttons, but that costs too much height;
     40px lands between the buttons and the social icons and keeps the device
     bigger. The phone still clears the fold by ~36px on a 700px-tall window,
     because the frame PNG carries ~7% transparent padding, so the device itself
     ends well above the box. */
  margin-bottom: 40px;
  /* a drag across a screenshot must not try to drag the image itself */
  -webkit-user-select: none;
  user-select: none;
  -webkit-user-drag: none;
  animation: rise .8s .55s var(--ease) both;
}

/* One screenshot's worth of pieces. JS sets the width/height to the fitted
   rect of whichever shot is showing, so this centres at whatever size that is. */
/* One screenshot, sized to its own fitted rect and centred. JS animates the
   transform, so the swipe is transform + opacity on a single already-rasterised
   layer and nothing else. */
.shot {
  position: absolute;
  /* Placed by JS at whole pixels rather than centred with left/top 50% and a
     -50% translate. That centring put the layer on a half pixel whenever the
     box and the picture had different parities, and the hero column resolves to
     a fractional x of its own — a layer drawn at a fractional device pixel gets
     resampled, which is what made the screenshots look soft. */
  will-change: transform, opacity;
}

/* One <img>, not a grid of pieces. The shatter that used to live here cut each
   shot into ~110 tiles, and tile edges land on fractional pixels — which drew
   faint seam lines across the picture however much the cells were overlapped.
   A single element has no seams to draw.

   No radius and no shadow here either: each screenshot arrives already sitting
   in its device as a transparent PNG, so the bezel, the rounded screen and the
   shadow are all in the pixels. */
.shot img {
  display: block;
  width: 100%;
  height: 100%;
}



.hero__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--accent-hi);
  margin-bottom: 22px;
  animation: rise .8s var(--ease) both;
}

.hero__eyebrow::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 0 var(--accent-glow);
  animation: pulse 2.4s infinite;
}

@keyframes pulse {
  0% {
    box-shadow: 0 0 0 0 rgba(16, 185, 129, .5);
  }

  70% {
    box-shadow: 0 0 0 11px rgba(16, 185, 129, 0);
  }

  100% {
    box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
  }
}

.hero__title {
  font-family: var(--font-display);
  font-size: clamp(3rem, 11vw, 7.2rem);
  font-weight: 700;
  line-height: .95;
  letter-spacing: -.035em;
  margin-bottom: 18px;
  animation: rise .8s .1s var(--ease) both;
}

.hero__role {
  font-size: clamp(1.1rem, 2.6vw, 1.6rem);
  color: var(--fg-muted);
  font-weight: 500;
  margin-bottom: 26px;
  animation: rise .8s .2s var(--ease) both;
}

.hero__bio {
  max-width: 620px;
  color: var(--fg-subtle);
  font-size: 1.03rem;
  line-height: 1.8;
  margin-bottom: 40px;
  animation: rise .8s .3s var(--ease) both;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-bottom: 46px;
  animation: rise .8s .4s var(--ease) both;
}

.hero__socials {
  display: flex;
  gap: 12px;
  animation: rise .8s .5s var(--ease) both;
}

.hero__socials a {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: var(--stroke) solid var(--border);
  border-radius: 12px;
  color: var(--fg-muted);
  background: rgba(255, 255, 255, .02);
  transition: all .28s var(--ease);
}

.hero__socials a:hover {
  color: var(--accent-hi);
  border-color: var(--accent);
  transform: translateY(-3px);
  background: var(--accent-glow);
}

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(26px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* ══════════ 7. SECTIONS ══════════ */
.section {
  --section-pad: 110px;
  padding: var(--section-pad) 0;
  position: relative;
  /* anchor jumps land on the heading, not on the padding above it */
  scroll-margin-top: calc(-1 * var(--section-pad));
}

.section__head {
  margin-bottom: 58px;
}

.section__num {
  display: block;
  color: var(--accent);
  margin-bottom: 12px;
}

.section__title {
  font-family: var(--font-display);
  font-size: clamp(2rem, 5vw, 3rem);
  font-weight: 700;
  letter-spacing: -.025em;
  line-height: 1.15;
}

.section__sub {
  color: var(--fg-subtle);
  margin-top: 14px;
  max-width: 540px;
}

/* ══════════ 8. TIMELINE ══════════ */
.timeline {
  position: relative;
  padding-left: 30px;
}

.timeline::before {
  content: "";
  position: absolute;
  left: 5px;
  top: 8px;
  bottom: 8px;
  width: 1px;
  background: linear-gradient(to bottom, var(--accent), var(--border) 55%, transparent);
}

.tl-item {
  position: relative;
  padding-bottom: 46px;
}

.tl-item:last-child {
  padding-bottom: 0;
}

.tl-item::before {
  content: "";
  position: absolute;
  left: -30px;
  top: 7px;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--bg-alt);
  border: 2px solid var(--accent);
}

.tl-item__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
}

.tl-item__role {
  font-family: var(--font-display);
  font-size: 1.22rem;
  font-weight: 600;
}

.tl-item__company {
  color: var(--accent-hi);
}

.tl-item__company--link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  transition: color .25s, border-color .25s;
  border-bottom: 1px solid transparent;
}

.tl-item__company--link svg {
  width: 13px;
  height: 13px;
  opacity: .6;
}

.tl-item__company--link:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

.tl-item__company--link:hover svg {
  opacity: 1;
}

.tl-item__period {
  color: var(--fg-subtle);
  font-family: var(--font-mono);
  font-size: .78rem;
  white-space: nowrap;
}

.tl-item__loc {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  color: var(--fg-subtle);
  font-size: .85rem;
  margin-bottom: 16px;
}

.tl-item__cert {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border: var(--stroke) solid var(--border);
  border-radius: 20px;
  background: rgba(255, 255, 255, .03);
  color: var(--fg-muted);
  font-size: .78rem;
  transition: all .25s var(--ease);
}

.tl-item__cert svg {
  width: 13px;
  height: 13px;
}

.tl-item__cert:hover {
  color: var(--accent-hi);
  border-color: var(--accent);
  background: var(--accent-glow);
}

.tl-item__bullets {
  list-style: none;
  display: grid;
  gap: 10px;
}

.tl-item__bullets li {
  position: relative;
  padding-left: 20px;
  color: var(--fg-muted);
  font-size: .94rem;
  line-height: 1.7;
}

.tl-item__bullets li::before {
  content: "▹";
  position: absolute;
  left: 0;
  color: var(--accent);
}

/* ══════════ 9. SKILLS ══════════ */
.skills {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 18px;
}

.skill-card {
  padding: 26px;
  background: var(--surface);
  border: var(--stroke) solid var(--border);
  border-radius: var(--radius);
  transition: border-color .3s, transform .3s var(--ease);
}

.skill-card:hover {
  border-color: var(--border-hi);
  transform: translateY(-4px);
  box-shadow: var(--sheen), var(--shadow-lift);
}

.skill-card__title {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--accent);
  margin-bottom: 18px;
}

.skill-card__title::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.skill-card__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.tag {
  padding: 6px 12px;
  font-size: .82rem;
  color: var(--fg-muted);
  background: rgba(255, 255, 255, .035);
  border: var(--hair) solid var(--border);
  border-radius: 7px;
  transition: all .22s;
}

.skill-card:hover .tag {
  border-color: var(--border-hi);
}

.tag:hover {
  color: var(--accent-hi);
  border-color: var(--accent);
  background: var(--accent-glow);
}

/* ══════════ 10. PROJECTS ══════════ */
.projects {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
  gap: 22px;
}

.project {
  position: relative;
  background: var(--surface);
  border: var(--stroke) solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
  text-align: left;
  width: 100%;
  transition: border-color .35s, transform .35s var(--ease), box-shadow .35s;
}

.project:hover {
  border-color: rgba(16, 185, 129, .4);
  transform: translateY(-6px);
  box-shadow: var(--sheen), var(--shadow-lift), 0 0 0 1px var(--accent-glow);
}

.project__media {
  position: relative;
  aspect-ratio: 16 / 10;
  background: #0e1013;
  overflow: hidden;
}

.project__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  transition: transform .6s var(--ease);
}

.project:hover .project__media img {
  transform: scale(1.06);
}

.project__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, var(--surface) 2%, transparent 55%);
}

.project__status {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 2;
  padding: 5px 11px;
  border-radius: 20px;
  background: rgba(8, 9, 10, .8);
  backdrop-filter: blur(8px);
  border: var(--stroke) solid var(--accent);
  color: var(--accent-hi);
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .05em;
  text-transform: uppercase;
}

.project__body {
  padding: 22px 24px 26px;
}

.project__title {
  font-family: var(--font-display);
  font-size: 1.28rem;
  font-weight: 600;
  margin-bottom: 4px;
  transition: color .25s;
}

.project:hover .project__title {
  color: var(--accent-hi);
}

.project__tagline {
  color: var(--fg-subtle);
  font-size: .82rem;
  margin-bottom: 14px;
}

.project__summary {
  color: var(--fg-muted);
  font-size: .92rem;
  line-height: 1.7;
  margin-bottom: 18px;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.project__tech {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-bottom: 18px;
}

.project__tech span {
  font-family: var(--font-mono);
  font-size: .7rem;
  color: var(--fg-subtle);
  padding: 4px 9px;
  border: var(--hair) solid var(--border);
  border-radius: 6px;
}

.project__cta {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--accent);
  font-size: .87rem;
  font-weight: 600;
}

.project__cta svg {
  width: 15px;
  height: 15px;
  transition: transform .3s var(--ease);
}

.project:hover .project__cta svg {
  transform: translateX(4px);
}

/* ══════════ 11. MODAL ══════════ */
.modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.modal.is-open {
  display: flex;
}

.modal__overlay {
  position: absolute;
  inset: 0;
  background: rgba(4, 5, 6, .82);
  backdrop-filter: blur(6px);
  animation: fade .3s;
}

.modal__panel {
  position: relative;
  width: min(880px, 100%);
  /* Right gutter clears space between the border and the scrollbar living in
     .modal__scroll below, so the thumb sits inside the frame instead of
     riding right on the border line. overflow:hidden clips that scrollbar's
     square ends to this element's own rounded corners. */
  padding-right: 6px;
  overflow: hidden;
  background: var(--surface);
  border: var(--stroke) solid var(--border-hi);
  border-radius: var(--radius);
  animation: modalIn .4s var(--ease);
}

.modal__scroll {
  max-height: 88vh;
  overflow-y: auto;
  overflow-x: hidden;
}

@keyframes fade {
  from {
    opacity: 0;
  }
}

@keyframes modalIn {
  from {
    opacity: 0;
    transform: translateY(24px) scale(.98);
  }
}

/* Floated so the body text starts beside it, sticky so it rides along as the
   panel scrolls. `top` matches the margin, so the cluster keeps the same 14px
   gap from the panel's top edge once it sticks instead of butting against it. */
/* Absolute, NOT `float: right` + `position: sticky`.

   A floated box narrows any sibling content that establishes its own formatting
   context — and .modal__gallery is `display: grid`, which does exactly that. So
   the gallery shrank to avoid this float and squeezed its four columns into the
   remaining width, leaving the float's area blank. Making it sticky made it
   worse: the float then tracked the scroll, so WHICH gallery got squeezed
   changed as you moved, which is why it looked random and cleared on reopen.

   .modal__panel is the containing block here and does not scroll (.modal__scroll
   does), so absolute pins these to the panel's top-right and content slides
   underneath — the same behaviour sticky was for, with nothing left in flow to
   push the grid around. `right` clears the panel's 6px gutter plus the
   scrollbar. */
.modal__controls {
  position: absolute;
  top: 14px;
  right: 26px;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 8px;
}

.modal__close {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 10px;
  background: rgba(8, 9, 10, .75);
  border: var(--stroke) solid var(--border);
  color: var(--fg-muted);
  transition: all .25s;
}

.modal__close:hover {
  color: var(--fg);
  border-color: var(--border-hi);
  background: var(--surface-hi);
}

/* Borrows the close button's chrome — same height, radius, backdrop and border —
   so the two read as one cluster. Only projects whose gallery has light/dark
   pairs get one (see Flirt Chat); main.js unhides it per project. */
.theme-switch {
  display: flex;
  align-items: center;
  gap: 3px;
  height: 38px;
  padding: 4px;
  border-radius: 10px;
  background: rgba(8, 9, 10, .75);
  border: var(--stroke) solid var(--border);
}

/* display:flex above would otherwise beat the hidden attribute */
.theme-switch[hidden] {
  display: none;
}

.theme-switch__btn {
  height: 100%;
  padding: 0 13px;
  border: 0;
  border-radius: 7px;
  background: none;
  color: var(--fg-subtle);
  font-size: .7rem;
  letter-spacing: .04em;
  cursor: pointer;
  transition: color .25s var(--ease), background .25s var(--ease);
}

.theme-switch__btn:hover {
  color: var(--fg);
}

.theme-switch__btn[aria-pressed="true"] {
  background: var(--accent);
  color: #04120d;
}

.modal__body {
  padding: 34px 38px 40px;
}

.modal__title {
  font-family: var(--font-display);
  font-size: 1.9rem;
  font-weight: 700;
  letter-spacing: -.02em;
  margin-bottom: 4px;
}

.modal__tagline {
  color: var(--accent-hi);
  font-size: .92rem;
  margin-bottom: 20px;
}

.modal__summary {
  color: var(--fg-muted);
  line-height: 1.8;
  margin-bottom: 28px;
}

.modal__links {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 14px;
  margin-bottom: 4px;
}

/* Official Google Play badge — kept unmodified per Google's brand guidelines.
   The asset ships with its own required clear space, so no extra padding. */
.play-badge {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.play-badge__note {
  color: var(--fg-subtle);
  padding-left: 12px;
}

.play-badge a {
  display: inline-block;
  transition: transform .25s var(--ease), filter .25s var(--ease);
}

.play-badge img {
  width: 186px;
  height: auto;
  display: block;
}

.play-badge a:hover {
  transform: translateY(-2px);
  filter: brightness(1.12);
}

.play-badge a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: 8px;
}

.modal__link {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 11px 18px;
  border-radius: var(--radius-sm);
  background: var(--gradient);
  color: #04120d;
  font-size: .88rem;
  font-weight: 600;
  box-shadow: 0 4px 18px var(--accent-glow);
  transition: transform .25s var(--ease), box-shadow .25s var(--ease);
}

.modal__link svg {
  width: 17px;
  height: 17px;
}

.modal__link:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 26px rgba(16, 185, 129, .32);
}

.modal__sub {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--accent);
  margin: 30px 0 16px;
}

.modal__sub::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.modal__list {
  list-style: none;
  display: grid;
  gap: 11px;
}

.modal__list li {
  position: relative;
  padding-left: 21px;
  color: var(--fg-muted);
  font-size: .94rem;
  line-height: 1.75;
}

.modal__list li::before {
  content: "▹";
  position: absolute;
  left: 0;
  color: var(--accent);
}

/* sub-heading when a gallery is split into groups (e.g. three separate apps) */
.gallery-group {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 20px 0 10px;
  color: var(--fg-muted);
  font-size: .85rem;
  font-weight: 600;
}

.gallery-group::before {
  content: "";
  width: 3px;
  height: 13px;
  border-radius: 2px;
  background: var(--accent);
}

.gallery-group::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.modal__gallery {
  display: grid;
  /* ~4 phone screenshots per row at full width, one per row on a phone. Six was
     tried and left each thumbnail ~126px, too small to tell the screens apart. */
  grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
  gap: 10px;
  align-items: start;
}

.modal__gallery button {
  /* default matches the phone screenshots (1080x2400); main.js overrides this
     per image with the file's true ratio once it loads, so nothing is cropped */
  aspect-ratio: 9 / 20;
  border-radius: var(--radius-sm);
  overflow: hidden;
  border: var(--stroke) solid var(--border);
  background: #0e1013;
  transition: border-color .25s, transform .25s var(--ease);
}

/* Desktop screenshots pack a whole UI into one frame, so at phone-thumbnail width
   they turn to mush. Span two columns (two per row) to roughly double their width. */
.modal__gallery button.is-landscape {
  grid-column: span 2;
  /* A rough landscape shape so the tile is close to its final height before the
     image arrives; main.js then sets the file's exact ratio. Without this the
     tile would start at the portrait 9/20 default and visibly snap. */
  aspect-ratio: 16 / 10;
}

.modal__gallery button:hover {
  border-color: var(--accent);
  transform: translateY(-3px);
}

.modal__gallery img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}

/* ══════════ 12. LIGHTBOX ══════════ */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(3, 4, 5, .95);
  padding: 60px 20px;
}

.lightbox.is-open {
  display: flex;
}

.lightbox__img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 8px;
  animation: fade .25s;
}

.lightbox__close,
.lightbox__nav {
  position: absolute;
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .07);
  color: #fff;
  transition: background .25s, transform .25s;
}

.lightbox__close:hover,
.lightbox__nav:hover {
  background: rgba(255, 255, 255, .16);
}

.lightbox__close {
  top: 22px;
  right: 22px;
}

.lightbox__nav--prev {
  left: 22px;
}

.lightbox__nav--next {
  right: 22px;
}

.lightbox__nav:hover {
  transform: scale(1.08);
}

.lightbox__counter {
  position: absolute;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  color: var(--fg-subtle);
}

/* ══════════ 13. CONTACT ══════════ */
.contact {
  text-align: center;
}

.contact__lead {
  max-width: 520px;
  margin: 0 auto 44px;
  color: var(--fg-muted);
  font-size: 1.05rem;
  line-height: 1.8;
}

.contact__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 16px;
  margin-bottom: 46px;
}

.contact__card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  padding: 30px 20px;
  background: var(--surface);
  border: var(--stroke) solid var(--border);
  border-radius: var(--radius);
  transition: all .3s var(--ease);
}

.contact__card svg {
  width: 24px;
  height: 24px;
  color: var(--accent);
  margin-bottom: 6px;
}

.contact__card:hover {
  border-color: var(--accent);
  transform: translateY(-5px);
  background: var(--surface-hi);
  box-shadow: var(--sheen), var(--shadow-lift);
}

.contact__label {
  color: var(--fg-subtle);
}

.contact__value {
  font-size: .93rem;
  word-break: break-word;
}

/* ══════════ 14. FOOTER ══════════ */
.footer {
  padding: 34px 0;
  border-top: var(--hair) solid var(--border);
  background: rgba(255, 255, 255, .022);
}

.footer__inner {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  align-items: center;
  justify-content: space-between;
  color: var(--fg-subtle);
  font-size: .87rem;
}

.footer__top {
  transition: color .25s;
}

.footer__top:hover {
  color: var(--accent);
}

/* Every panel on the page carries the same one-pixel top highlight, so they all
   read as lit from the same direction. Grouped rather than repeated per rule so
   the light source stays consistent if it is ever retuned. */
.skill-card,
.project,
.contact__card,
.tl-item__cert,
.modal__panel {
  box-shadow: var(--sheen), var(--shadow-soft);
}


/* ══════════ 15. RESPONSIVE ══════════ */

/* One column below this, with the screenshot centred under the text rather
   than hidden — the apps are the point of the page, so a phone is the last
   place to drop them. Stacking at 1080 rather than 1000: between those the two
   columns are both too narrow, the title wraps badly and the screenshot is
   squeezed into a corner. Sized off the viewport so a tablet gets a screenshot
   that fills the space it has, instead of the same small one a phone gets. */
@media (max-width: 1080px) {
  .hero__inner {
    grid-template-columns: 1fr;
    gap: 34px;
  }

  .hero__showcase {
    justify-self: center;
    /* One column here, so there is no row to stretch into and nothing above to
       reclaim — back to the square box, centred under the text. The viewport
       cap goes with it: this box is scrolled to, not competing with the fold. */
    align-self: center;
    margin-top: 0;
    margin-bottom: 0;
    max-height: none;
    /* Fill the text column instead of a fixed slice of the viewport: the phone
       then lines up with the copy above it and scales with the screen on its
       own, so a 320px phone gets a proportionally sized one rather than a fixed
       ratio that has to suit every device at once. 440 only caps it on tablets,
       where the column gets wide enough that the box would otherwise tower. */
    max-width: 440px;
    /* Portrait, matching the phone PNG's own proportions, instead of the square
       box this used to be. In a square box a portrait phone is fitted by HEIGHT,
       so it only ever filled about half the width and looked tiny — on a phone,
       of all places, showing a phone.

       Shaping the box to the phone means every shot is fitted by WIDTH instead,
       which is also what keeps the tablet and desktop shots from out-sizing it:
       all six span the same box width, so the landscape ones can never come out
       wider than the phone. They just sit centred with air above and below. */
    aspect-ratio: 1086 / 1953;
  }
}


@media (max-width: 760px) {
  .nav__toggle {
    display: flex;
  }

  .nav__links {
    position: fixed;
    inset: 74px 0 auto 0;
    flex-direction: column;
    gap: 0;
    background: rgba(8, 9, 10, .97);
    backdrop-filter: blur(16px);
    border-bottom: 1px solid var(--border);
    padding: 10px 24px 24px;
    transform: translateY(-130%);
    transition: transform .4s var(--ease);
  }

  .nav__links.is-open {
    transform: none;
  }

  .nav__links a {
    padding: 15px 0;
    border-bottom: 1px solid var(--border);
    font-size: 1rem;
  }

  .nav__links a::after {
    display: none;
  }

  .section {
    --section-pad: 78px;
  }

  .section__head {
    margin-bottom: 40px;
  }

  .modal__body {
    padding: 26px 22px 32px;
  }

  .modal__title {
    font-size: 1.5rem;
  }


  .lightbox__nav {
    width: 40px;
    height: 40px;
  }

  .lightbox__nav--prev {
    left: 10px;
  }

  .lightbox__nav--next {
    right: 10px;
  }
}

@media (max-width: 420px) {
  .hero__actions {
    flex-direction: column;
    align-items: stretch;
  }

  .btn {
    justify-content: center;
  }

}
